Overhaul quote threading.
- Separate post order data from read/unread status. - Make compatible with unread line and scroll to last read post. - Implement [Thread New Posts] link.
This commit is contained in:
parent
748258f59b
commit
c38c37c720
@ -197,6 +197,7 @@ Main =
|
|||||||
|
|
||||||
Main.callbackNodes Thread, threads
|
Main.callbackNodes Thread, threads
|
||||||
Main.callbackNodesDB Post, posts, ->
|
Main.callbackNodesDB Post, posts, ->
|
||||||
|
QuoteThreading.insert post for post in posts
|
||||||
$.event '4chanXInitFinished'
|
$.event '4chanXInitFinished'
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|||||||
@ -320,12 +320,8 @@ ThreadUpdater =
|
|||||||
ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25
|
ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25
|
||||||
|
|
||||||
for post in posts
|
for post in posts
|
||||||
root = post.nodes.root
|
unless QuoteThreading.insert post
|
||||||
if post.cb
|
$.add ThreadUpdater.root, post.nodes.root
|
||||||
unless post.cb()
|
|
||||||
$.add ThreadUpdater.root, root
|
|
||||||
else
|
|
||||||
$.add ThreadUpdater.root, root
|
|
||||||
$.event 'PostsInserted'
|
$.event 'PostsInserted'
|
||||||
|
|
||||||
if scroll
|
if scroll
|
||||||
|
|||||||
@ -154,7 +154,7 @@ ThreadWatcher =
|
|||||||
|
|
||||||
unread = quotingYou = 0
|
unread = quotingYou = 0
|
||||||
|
|
||||||
for postObj in @response.posts[1..]
|
for postObj in @response.posts
|
||||||
continue unless postObj.no > lastReadPost
|
continue unless postObj.no > lastReadPost
|
||||||
continue if QR.db?.get {boardID, threadID, postID: postObj.no}
|
continue if QR.db?.get {boardID, threadID, postID: postObj.no}
|
||||||
unread++
|
unread++
|
||||||
|
|||||||
@ -12,8 +12,10 @@ Unread =
|
|||||||
@db = new DataBoard 'lastReadPosts', @sync
|
@db = new DataBoard 'lastReadPosts', @sync
|
||||||
@hr = $.el 'hr',
|
@hr = $.el 'hr',
|
||||||
id: 'unread-line'
|
id: 'unread-line'
|
||||||
@posts = new RandomAccessList
|
@posts = new Set
|
||||||
@postsQuotingYou = new Set
|
@postsQuotingYou = new Set
|
||||||
|
@order = new RandomAccessList
|
||||||
|
@position = null
|
||||||
|
|
||||||
Thread.callbacks.push
|
Thread.callbacks.push
|
||||||
name: 'Unread'
|
name: 'Unread'
|
||||||
@ -22,7 +24,26 @@ Unread =
|
|||||||
name: 'Unread'
|
name: 'Unread'
|
||||||
cb: @addPost
|
cb: @addPost
|
||||||
|
|
||||||
readCount: 0
|
<% if (tests_enabled) { %>
|
||||||
|
testLink = $.el 'a',
|
||||||
|
textContent: 'Test Post Order'
|
||||||
|
$.on testLink, 'click', ->
|
||||||
|
list1 = (x.ID for x in Unread.order.order())
|
||||||
|
list2 = (+x.id[2..] for x in $$ '.postContainer')
|
||||||
|
pass = do ->
|
||||||
|
return false unless list1.length is list2.length
|
||||||
|
for i in [0...list1.length] by 1
|
||||||
|
return false if list1[i] isnt list2[i]
|
||||||
|
true
|
||||||
|
if pass
|
||||||
|
new Notice 'success', "Orders same (#{list1.length} posts)", 5
|
||||||
|
else
|
||||||
|
new Notice 'warning', 'Orders differ.', 30
|
||||||
|
c.log list1
|
||||||
|
c.log list2
|
||||||
|
Header.menu.addEntry
|
||||||
|
el: testLink
|
||||||
|
<% } %>
|
||||||
|
|
||||||
node: ->
|
node: ->
|
||||||
Unread.thread = @
|
Unread.thread = @
|
||||||
@ -31,31 +52,33 @@ Unread =
|
|||||||
boardID: @board.ID
|
boardID: @board.ID
|
||||||
threadID: @ID
|
threadID: @ID
|
||||||
defaultValue: 0
|
defaultValue: 0
|
||||||
for ID in @posts.keys when +ID <= Unread.lastReadPost
|
Unread.readCount = 0
|
||||||
Unread.readCount++
|
Unread.readCount++ for ID in @posts.keys when +ID <= Unread.lastReadPost
|
||||||
$.one d, '4chanXInitFinished', Unread.ready
|
$.one d, '4chanXInitFinished', Unread.ready
|
||||||
$.on d, 'ThreadUpdate', Unread.onUpdate
|
$.on d, 'ThreadUpdate', Unread.onUpdate
|
||||||
$.on d, 'scroll visibilitychange', Unread.read
|
$.on d, 'scroll visibilitychange', Unread.read
|
||||||
$.on d, 'visibilitychange', Unread.setLine if Conf['Unread Line'] and not Conf['Quote Threading']
|
$.on d, 'visibilitychange', Unread.setLine if Conf['Unread Line']
|
||||||
|
|
||||||
ready: ->
|
ready: ->
|
||||||
if Conf['Quote Threading']
|
Unread.setLine true
|
||||||
QuoteThreading.force()
|
Unread.read()
|
||||||
else
|
Unread.update()
|
||||||
Unread.setLine true if Conf['Unread Line']
|
Unread.scroll() if Conf['Scroll to Last Read Post']
|
||||||
Unread.read()
|
|
||||||
Unread.update()
|
positionPrev: ->
|
||||||
Unread.scroll() if Conf['Scroll to Last Read Post'] and not Conf['Quote Threading']
|
if Unread.position then Unread.position.prev else Unread.order.last
|
||||||
|
|
||||||
scroll: ->
|
scroll: ->
|
||||||
# Let the header's onload callback handle it.
|
# Let the header's onload callback handle it.
|
||||||
return if (hash = location.hash.match /\d+/) and hash[0] of Unread.thread.posts
|
return if (hash = location.hash.match /\d+/) and hash[0] of Unread.thread.posts
|
||||||
|
|
||||||
# Scroll to the last displayed non-deleted read post.
|
position = Unread.positionPrev()
|
||||||
{posts} = Unread.thread
|
while position
|
||||||
for i in [Unread.readCount-1..0] by -1
|
{root} = position.data.nodes
|
||||||
{root} = posts[posts.keys[i]].nodes
|
if !root.getBoundingClientRect().height
|
||||||
if root.getBoundingClientRect().height
|
# Don't try to scroll to posts with display: none
|
||||||
|
position = position.prev
|
||||||
|
else
|
||||||
Header.scrollToIfNeeded root, true
|
Header.scrollToIfNeeded root, true
|
||||||
break
|
break
|
||||||
return
|
return
|
||||||
@ -71,23 +94,27 @@ Unread =
|
|||||||
|
|
||||||
postIDs = Unread.thread.posts.keys
|
postIDs = Unread.thread.posts.keys
|
||||||
for i in [Unread.readCount...postIDs.length] by 1
|
for i in [Unread.readCount...postIDs.length] by 1
|
||||||
ID = postIDs[i]
|
ID = +postIDs[i]
|
||||||
break if +ID > Unread.lastReadPost
|
break if ID > Unread.lastReadPost
|
||||||
Unread.posts.rm ID
|
Unread.posts.delete ID
|
||||||
Unread.postsQuotingYou.delete ID
|
Unread.postsQuotingYou.delete ID
|
||||||
Unread.readCount++
|
Unread.readCount++
|
||||||
|
|
||||||
Unread.setLine() if Conf['Unread Line'] and not Conf['Quote Threading']
|
Unread.updatePosition()
|
||||||
|
Unread.setLine()
|
||||||
Unread.update()
|
Unread.update()
|
||||||
|
|
||||||
addPost: ->
|
addPost: ->
|
||||||
return if @isClone or @ID <= Unread.lastReadPost or !@isReply or @isHidden or QR.db?.get {
|
return if @isFetchedQuote or @isClone
|
||||||
|
Unread.order.push @
|
||||||
|
return if @ID <= Unread.lastReadPost or @isHidden or QR.db?.get {
|
||||||
boardID: @board.ID
|
boardID: @board.ID
|
||||||
threadID: @thread.ID
|
threadID: @thread.ID
|
||||||
postID: @ID
|
postID: @ID
|
||||||
}
|
}
|
||||||
Unread.posts.push @
|
Unread.posts.add @ID
|
||||||
Unread.addPostQuotingYou @
|
Unread.addPostQuotingYou @
|
||||||
|
Unread.position ?= Unread.order[@ID]
|
||||||
|
|
||||||
addPostQuotingYou: (post) ->
|
addPostQuotingYou: (post) ->
|
||||||
for quotelink in post.nodes.quotelinks when QR.db?.get Get.postDataFromLink quotelink
|
for quotelink in post.nodes.quotelinks when QR.db?.get Get.postDataFromLink quotelink
|
||||||
@ -110,31 +137,31 @@ Unread =
|
|||||||
|
|
||||||
onUpdate: (e) ->
|
onUpdate: (e) ->
|
||||||
if !e.detail[404]
|
if !e.detail[404]
|
||||||
# Force line on visible threads if there were no unread posts previously.
|
Unread.setLine()
|
||||||
Unread.setLine(!Unread.hr.parentNode) if Conf['Unread Line'] and not Conf['Quote Threading']
|
|
||||||
Unread.read()
|
Unread.read()
|
||||||
Unread.update()
|
Unread.update()
|
||||||
|
|
||||||
readSinglePost: (post) ->
|
readSinglePost: (post) ->
|
||||||
{ID} = post
|
{ID} = post
|
||||||
{posts} = Unread
|
return unless Unread.posts.has ID
|
||||||
return unless posts[ID]
|
Unread.posts.delete ID
|
||||||
posts.rm ID
|
|
||||||
Unread.postsQuotingYou.delete ID
|
Unread.postsQuotingYou.delete ID
|
||||||
|
Unread.updatePosition()
|
||||||
Unread.saveLastReadPost()
|
Unread.saveLastReadPost()
|
||||||
Unread.update()
|
Unread.update()
|
||||||
|
|
||||||
read: $.debounce 100, (e) ->
|
read: $.debounce 100, (e) ->
|
||||||
return if d.hidden or !Unread.posts.length
|
return if d.hidden or !Unread.posts.size
|
||||||
height = doc.clientHeight
|
height = doc.clientHeight
|
||||||
|
|
||||||
{posts} = Unread
|
|
||||||
count = 0
|
count = 0
|
||||||
while post = posts.first
|
while Unread.position
|
||||||
break unless Header.getBottomOf(post.data.nodes.root) > -1 # post is not completely read
|
{ID, data} = Unread.position
|
||||||
{ID, data} = post
|
{root} = data.nodes
|
||||||
|
break unless !root.getBoundingClientRect().height or # post has been hidden
|
||||||
|
Header.getBottomOf(root) > -1 # post is completely read
|
||||||
count++
|
count++
|
||||||
posts.rm ID
|
Unread.posts.delete ID
|
||||||
Unread.postsQuotingYou.delete ID
|
Unread.postsQuotingYou.delete ID
|
||||||
|
|
||||||
if Conf['Mark Quotes of You'] and QR.db?.get {
|
if Conf['Mark Quotes of You'] and QR.db?.get {
|
||||||
@ -142,18 +169,24 @@ Unread =
|
|||||||
threadID: data.thread.ID
|
threadID: data.thread.ID
|
||||||
postID: ID
|
postID: ID
|
||||||
}
|
}
|
||||||
QuoteYou.lastRead = data.nodes.root
|
QuoteYou.lastRead = root
|
||||||
|
Unread.position = Unread.position.next
|
||||||
|
|
||||||
return unless count
|
return unless count
|
||||||
|
Unread.updatePosition()
|
||||||
Unread.saveLastReadPost()
|
Unread.saveLastReadPost()
|
||||||
Unread.update() if e
|
Unread.update() if e
|
||||||
|
|
||||||
|
updatePosition: ->
|
||||||
|
while Unread.position and !Unread.posts.has Unread.position.ID
|
||||||
|
Unread.position = Unread.position.next
|
||||||
|
|
||||||
saveLastReadPost: $.debounce 2 * $.SECOND, ->
|
saveLastReadPost: $.debounce 2 * $.SECOND, ->
|
||||||
postIDs = Unread.thread.posts.keys
|
postIDs = Unread.thread.posts.keys
|
||||||
for i in [Unread.readCount...postIDs.length] by 1
|
for i in [Unread.readCount...postIDs.length] by 1
|
||||||
ID = postIDs[i]
|
ID = +postIDs[i]
|
||||||
break if Unread.posts[ID]
|
break if Unread.posts.has ID
|
||||||
Unread.lastReadPost = +ID
|
Unread.lastReadPost = ID
|
||||||
Unread.readCount++
|
Unread.readCount++
|
||||||
return if Unread.thread.isDead and !Unread.thread.isArchived
|
return if Unread.thread.isDead and !Unread.thread.isArchived
|
||||||
Unread.db.forceSync()
|
Unread.db.forceSync()
|
||||||
@ -163,15 +196,16 @@ Unread =
|
|||||||
val: Unread.lastReadPost
|
val: Unread.lastReadPost
|
||||||
|
|
||||||
setLine: (force) ->
|
setLine: (force) ->
|
||||||
return unless d.hidden or force is true
|
return unless Conf['Unread Line']
|
||||||
return $.rm Unread.hr unless Unread.posts.length
|
if d.hidden or (force is true)
|
||||||
{posts} = Unread.thread
|
if Unread.linePosition = Unread.positionPrev()
|
||||||
for i in [Unread.readCount-1..0] by -1
|
$.after Unread.linePosition.data.nodes.root, Unread.hr
|
||||||
return $.after posts[posts.keys[i]].nodes.root, Unread.hr
|
else
|
||||||
return
|
$.rm Unread.hr
|
||||||
|
Unread.hr.hidden = Unread.linePosition is Unread.order.last
|
||||||
|
|
||||||
update: ->
|
update: ->
|
||||||
count = Unread.posts.length
|
count = Unread.posts.size
|
||||||
countQuotingYou = Unread.postsQuotingYou.size
|
countQuotingYou = Unread.postsQuotingYou.size
|
||||||
|
|
||||||
if Conf['Unread Count']
|
if Conf['Unread Count']
|
||||||
|
|||||||
@ -9,96 +9,116 @@ QuoteThreading =
|
|||||||
@enabled = true
|
@enabled = true
|
||||||
@controls = $.el 'span',
|
@controls = $.el 'span',
|
||||||
<%= html('<label><input id="threadingControl" type="checkbox" checked> Threading</label>') %>
|
<%= html('<label><input id="threadingControl" type="checkbox" checked> Threading</label>') %>
|
||||||
|
@threadNewLink = $.el 'span',
|
||||||
|
className: 'brackets-wrap threadnewlink'
|
||||||
|
hidden: true
|
||||||
|
$.extend @threadNewLink, <%= html('<a href="javascript:;">Thread New Posts</a>') %>
|
||||||
|
|
||||||
input = $ 'input', @controls
|
$.on $('input', @controls), 'change', ->
|
||||||
$.on input, 'change', @toggle
|
QuoteThreading.rethread @checked
|
||||||
|
$.on @threadNewLink.firstElementChild, 'click', ->
|
||||||
|
QuoteThreading.threadNewLink.hidden = true
|
||||||
|
QuoteThreading.rethread true
|
||||||
|
|
||||||
Header.menu.addEntry @entry =
|
Header.menu.addEntry @entry =
|
||||||
el: @controls
|
el: @controls
|
||||||
order: 98
|
order: 98
|
||||||
|
|
||||||
|
Thread.callbacks.push
|
||||||
|
name: 'Quote Threading'
|
||||||
|
cb: @setThread
|
||||||
Post.callbacks.push
|
Post.callbacks.push
|
||||||
name: 'Quote Threading'
|
name: 'Quote Threading'
|
||||||
cb: @node
|
cb: @node
|
||||||
|
|
||||||
force: ->
|
parent: {}
|
||||||
g.posts.forEach (post) ->
|
children: {}
|
||||||
post.cb true if post.cb
|
inserted: {}
|
||||||
Unread.read()
|
|
||||||
Unread.update()
|
setThread: ->
|
||||||
|
QuoteThreading.thread = @
|
||||||
|
$.asap (-> !Conf['Thread Updater'] or $ '.navLinksBot > .updatelink'), ->
|
||||||
|
$.add $('.navLinksBot'), [$.tn(' '), QuoteThreading.threadNewLink]
|
||||||
|
|
||||||
node: ->
|
node: ->
|
||||||
{posts} = g
|
return if @isFetchedQuote or @isClone or !@isReply
|
||||||
return if @isClone or not QuoteThreading.enabled or !@isReply or @isHidden
|
{thread} = QuoteThreading
|
||||||
|
parents = for quote in @quotes
|
||||||
|
parent = g.posts[quote]
|
||||||
|
continue if !parent or parent.isFetchedQuote or !parent.isReply or parent.ID >= @ID
|
||||||
|
parent
|
||||||
|
if parents.length is 1
|
||||||
|
QuoteThreading.parent[@fullID] = parents[0]
|
||||||
|
|
||||||
keys = []
|
descendants: (post) ->
|
||||||
len = g.BOARD.ID.length + 1
|
posts = [post]
|
||||||
keys.push quote for quote in @quotes when (quote[len..] < @ID) and quote of posts
|
if children = QuoteThreading.children[post.fullID]
|
||||||
|
for child in children
|
||||||
|
posts = posts.concat QuoteThreading.descendants child
|
||||||
|
posts
|
||||||
|
|
||||||
return unless keys.length is 1
|
insert: (post) ->
|
||||||
|
return false unless QuoteThreading.enabled and
|
||||||
|
(parent = QuoteThreading.parent[post.fullID]) and
|
||||||
|
!QuoteThreading.inserted[post.fullID]
|
||||||
|
|
||||||
@threaded = keys[0]
|
descendants = QuoteThreading.descendants post
|
||||||
@cb = QuoteThreading.nodeinsert
|
if !Unread.posts.has(parent.ID) and descendants.some((x) -> Unread.posts.has(x.ID))
|
||||||
|
QuoteThreading.threadNewLink.hidden = false
|
||||||
|
return false
|
||||||
|
|
||||||
nodeinsert: (force) ->
|
{order} = Unread
|
||||||
post = g.posts[@threaded]
|
children = (QuoteThreading.children[parent.fullID] or= [])
|
||||||
|
threadContainer = parent.nodes.threadContainer or $.el 'div', className: 'threadContainer'
|
||||||
return false if @thread.OP is post
|
nodes = [post.nodes.root]
|
||||||
|
nodes.push post.nodes.threadContainer if post.nodes.threadContainer
|
||||||
{posts} = Unread
|
|
||||||
{root} = post.nodes
|
|
||||||
|
|
||||||
unless force
|
|
||||||
height = doc.clientHeight
|
|
||||||
{bottom, top} = root.getBoundingClientRect()
|
|
||||||
|
|
||||||
# Post is unread or is fully visible.
|
|
||||||
return false unless posts[post.ID] or ((bottom < height) and (top > 0))
|
|
||||||
|
|
||||||
if $.hasClass root, 'threadOP'
|
|
||||||
threadContainer = root.nextElementSibling
|
|
||||||
post = Get.postFromRoot $.x 'descendant::div[contains(@class,"postContainer")][last()]', threadContainer
|
|
||||||
$.add threadContainer, @nodes.root
|
|
||||||
|
|
||||||
|
i = children.length
|
||||||
|
i-- for child in children by -1 when child.ID >= post.ID
|
||||||
|
if i isnt children.length
|
||||||
|
next = children[i]
|
||||||
|
order.before order[next.ID], order[x.ID] for x in descendants
|
||||||
|
children.splice i, 0, post
|
||||||
|
$.before next.nodes.root, nodes
|
||||||
else
|
else
|
||||||
threadContainer = $.el 'div',
|
prev = parent
|
||||||
className: 'threadContainer'
|
while (prev2 = QuoteThreading.children[prev.fullID]) and prev2.length
|
||||||
$.add threadContainer, @nodes.root
|
prev = prev2[prev2.length-1]
|
||||||
$.after root, threadContainer
|
order.after order[prev.ID], order[x.ID] for x in descendants by -1
|
||||||
$.addClass root, 'threadOP'
|
children.push post
|
||||||
|
$.add threadContainer, nodes
|
||||||
|
|
||||||
if post = posts[post.ID]
|
QuoteThreading.inserted[post.fullID] = true
|
||||||
posts.after post, posts[@ID]
|
|
||||||
|
|
||||||
else if posts[@ID]
|
unless parent.nodes.threadContainer
|
||||||
posts.prepend posts[@ID]
|
parent.nodes.threadContainer = threadContainer
|
||||||
|
$.addClass parent.nodes.root, 'threadOP'
|
||||||
|
$.after parent.nodes.root, threadContainer
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
toggle: ->
|
rethread: (enabled) ->
|
||||||
if QuoteThreading.enabled = @checked
|
{thread} = QuoteThreading
|
||||||
QuoteThreading.force()
|
{posts} = thread
|
||||||
|
|
||||||
|
if QuoteThreading.enabled = enabled
|
||||||
|
posts.forEach QuoteThreading.insert
|
||||||
else
|
else
|
||||||
thread = $('.thread')
|
|
||||||
posts = []
|
|
||||||
nodes = []
|
nodes = []
|
||||||
|
Unread.order = new RandomAccessList
|
||||||
|
QuoteThreading.inserted = {}
|
||||||
|
posts.forEach (post) ->
|
||||||
|
Unread.order.push post
|
||||||
|
nodes.push post.nodes.root if post.isReply
|
||||||
|
if QuoteThreading.children[post.fullID]
|
||||||
|
delete QuoteThreading.children[post.fullID]
|
||||||
|
$.rmClass post.nodes.root, 'threadOP'
|
||||||
|
$.rm post.nodes.threadContainer
|
||||||
|
delete post.nodes.threadContainer
|
||||||
|
$.add thread.OP.nodes.root.parentNode, nodes
|
||||||
|
|
||||||
g.posts.forEach (post) ->
|
Unread.position = Unread.order.first
|
||||||
posts.push post unless post is post.thread.OP or post.isClone
|
Unread.updatePosition()
|
||||||
|
Unread.setLine true
|
||||||
posts.sort (a, b) -> a.ID - b.ID
|
Unread.read()
|
||||||
|
Unread.update()
|
||||||
nodes.push post.nodes.root for post in posts
|
|
||||||
$.add thread, nodes
|
|
||||||
|
|
||||||
containers = $$ '.threadContainer', thread
|
|
||||||
$.rm container for container in containers
|
|
||||||
$.rmClass post, 'threadOP' for post in $$ '.threadOP'
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
kb: ->
|
|
||||||
control = $.id 'threadingControl'
|
|
||||||
control.checked = not control.checked
|
|
||||||
QuoteThreading.toggle.call control
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user