diff --git a/src/Miscellaneous/PostJumper.coffee b/src/Miscellaneous/PostJumper.coffee
new file mode 100644
index 000000000..d24b7d575
--- /dev/null
+++ b/src/Miscellaneous/PostJumper.coffee
@@ -0,0 +1,64 @@
+PostJumper =
+ init: ->
+ @capcode = new Map()
+ @uniqueID = new Map()
+ return unless g.VIEW in ['index', 'thread']
+
+ Callbacks.Post.push
+ name: 'Post Jumper'
+ cb: @node
+
+ node: ->
+ return unless Conf['Unique ID and capcode Navigation']
+
+ if @nodes.uniqueIDRoot and not @isClone
+ PostJumper.addButtons @,'uniqueID'
+
+ if @nodes.capcode and not @isClone
+ PostJumper.addButtons @,'capcode'
+
+ addButtons: (post,type) ->
+ value = post.nodes[type].innerText
+ buttons = PostJumper.makeButtons type+'Jumper'
+ $.after post.nodes[type+(if type is 'capcode' then '' else 'Root')], buttons
+ $.on buttons.firstChild, 'click', PostJumper.buttonClick post,type,-1
+ $.on buttons.lastChild, 'click', PostJumper.buttonClick post,type,1
+ if not PostJumper[type].has value
+ PostJumper[type].set value, []
+ PostJumper[type].get(value).push {key: post.ID, val: post.fullID}
+ PostJumper[type].get(value).sort (first,second) -> first.key-second.key
+
+ buttonClick: (post,type,dir) -> ->
+ return if PostJumper[type].size is 0
+ value = (if type is 'capcode' then '## ' else '') + post.info[type]
+ fromID = post.ID
+ idx = PostJumper.indexOfPair PostJumper[type].get(value),fromID
+ fromID = PostJumper[type].get(value)[idx].val
+ return if idx is -1
+ idx = (idx + dir) %% PostJumper[type].get(value).length
+ toID= PostJumper[type].get(value)[idx].val
+ PostJumper.scroll fromID,toID
+
+ makeButtons: ->
+ charPrev = '\u{23EB}'
+ charNext = '\u{23EC}'
+ classPrev = 'prev'
+ classNext = 'next'
+ span = $.el 'span',
+ className: 'postJumper'
+ $.extend span, <%= html('${charPrev}${charNext}') %>
+ span
+
+ scroll: (fromID,toID) ->
+ prevPos = g.posts[fromID].nodes.nameBlock.getBoundingClientRect().top
+ destPos = g.posts[toID].nodes.nameBlock.getBoundingClientRect().top
+ window.scrollBy 0, destPos-prevPos
+
+ indexOfPair: (array,key) ->
+ result = -1;
+ for i in [0..array.length-1] by 1
+ if array[i].key is key
+ result = i
+ break
+ result
+
\ No newline at end of file
diff --git a/src/config/Config.coffee b/src/config/Config.coffee
index 175025dd0..5e0e48fee 100644
--- a/src/config/Config.coffee
+++ b/src/config/Config.coffee
@@ -92,6 +92,10 @@ Config =
false
'Add buttons to navigate to top / bottom of thread.'
]
+ 'Unique ID and capcode Navigation': [
+ false
+ 'Add buttons to navigate to posts having the same unique ID or capcode.'
+ ]
'Custom Board Titles': [
true
'Allow editing of the board title and subtitle by ctrl/\u2318+clicking them.'
diff --git a/src/css/style.css b/src/css/style.css
index 64ff45256..f487e8764 100644
--- a/src/css/style.css
+++ b/src/css/style.css
@@ -920,6 +920,8 @@ div[data-checked="false"] > .suboption-list {
.catalog-post > .postInfo > :not(.subject):not(.nameBlock):not(.dateTime),
.catalog-post > .postInfo > .nameBlock > .contact-links,
.catalog-post > * > * > .posteruid,
+.catalog-post > * > * > .uniqueIDJumper,
+.catalog-post > * > * > .capcodeJumper,
:root.bottom-backlinks .catalog-post > .container,
.post:not(.catalog-post) > .catalog-link,
.post:not(.catalog-post) > .catalog-stats,
@@ -2417,3 +2419,9 @@ a:only-of-type > .remove {
.invisible {
font-size: 0;
}
+
+/* PostJumper */
+.postJumper > .prev,
+.postJumper > .next {
+ font-size: 120%;
+}
diff --git a/src/main/Main.coffee b/src/main/Main.coffee
index 9d0ed1c54..ca428d504 100644
--- a/src/main/Main.coffee
+++ b/src/main/Main.coffee
@@ -555,6 +555,7 @@ Main =
['Quick Reply Personas', QR.persona]
['Quick Reply', QR]
['Cooldown', QR.cooldown]
+ ['Post Jumper', PostJumper]
['Pass Link', PassLink]
['Menu', Menu]
['Index Generator (Menu)', Index.menu]