267 lines
8.6 KiB
CoffeeScript
267 lines
8.6 KiB
CoffeeScript
Unread =
|
|
init: ->
|
|
return unless g.VIEW is 'thread' and (
|
|
Conf['Unread Count'] or
|
|
Conf['Unread Favicon'] or
|
|
Conf['Unread Line'] or
|
|
Conf['Remember Last Read Post'] or
|
|
Conf['Desktop Notifications'] or
|
|
Conf['Quote Threading']
|
|
)
|
|
|
|
if Conf['Remember Last Read Post']
|
|
$.sync 'Remember Last Read Post', (enabled) -> Conf['Remember Last Read Post'] = enabled
|
|
@db = new DataBoard 'lastReadPosts', @sync
|
|
|
|
@hr = $.el 'hr',
|
|
id: 'unread-line'
|
|
className: 'unread-line'
|
|
@posts = new Set()
|
|
@postsQuotingYou = new Set()
|
|
@order = new RandomAccessList()
|
|
@position = null
|
|
|
|
Callbacks.Thread.push
|
|
name: 'Unread'
|
|
cb: @node
|
|
|
|
Callbacks.Post.push
|
|
name: 'Unread'
|
|
cb: @addPost
|
|
|
|
node: ->
|
|
Unread.thread = @
|
|
Unread.title = d.title
|
|
Unread.lastReadPost = Unread.db?.get(
|
|
boardID: @board.ID
|
|
threadID: @ID
|
|
) or 0
|
|
Unread.readCount = 0
|
|
Unread.readCount++ for ID in @posts.keys when +ID <= Unread.lastReadPost
|
|
$.one d, '4chanXInitFinished', Unread.ready
|
|
$.on d, 'PostsInserted', Unread.onUpdate
|
|
$.on d, 'ThreadUpdate', (e) -> Unread.update() if e.detail[404]
|
|
resetLink = $.el 'a',
|
|
href: 'javascript:;'
|
|
className: 'unread-reset'
|
|
textContent: 'Mark all unread'
|
|
$.on resetLink, 'click', Unread.reset
|
|
Header.menu.addEntry
|
|
el: resetLink
|
|
order: 70
|
|
|
|
ready: ->
|
|
Unread.scroll() if Conf['Remember Last Read Post'] and Conf['Scroll to Last Read Post']
|
|
Unread.setLine true
|
|
Unread.read()
|
|
Unread.update()
|
|
$.on d, 'scroll visibilitychange', Unread.read
|
|
$.on d, 'visibilitychange', Unread.setLine if Conf['Unread Line']
|
|
|
|
positionPrev: ->
|
|
if Unread.position then Unread.position.prev else Unread.order.last
|
|
|
|
scroll: ->
|
|
# Let the header's onload callback handle it.
|
|
return if (hash = location.hash.match /\d+/) and hash[0] of Unread.thread.posts
|
|
|
|
position = Unread.positionPrev()
|
|
while position
|
|
{bottom} = position.data.nodes
|
|
if !bottom.getBoundingClientRect().height
|
|
# Don't try to scroll to posts with display: none
|
|
position = position.prev
|
|
else
|
|
Header.scrollToIfNeeded bottom, true
|
|
break
|
|
return
|
|
|
|
reset: ->
|
|
return unless Unread.lastReadPost?
|
|
|
|
Unread.posts = new Set()
|
|
Unread.postsQuotingYou = new Set()
|
|
Unread.order = new RandomAccessList()
|
|
Unread.position = null
|
|
Unread.lastReadPost = 0
|
|
Unread.readCount = 0
|
|
Unread.thread.posts.forEach (post) -> Unread.addPost.call post
|
|
|
|
$.forceSync 'Remember Last Read Post'
|
|
if Conf['Remember Last Read Post'] and (!Unread.thread.isDead or Unread.thread.isArchived)
|
|
Unread.db.set
|
|
boardID: Unread.thread.board.ID
|
|
threadID: Unread.thread.ID
|
|
val: 0
|
|
|
|
Unread.updatePosition()
|
|
Unread.setLine()
|
|
Unread.update()
|
|
|
|
sync: ->
|
|
return unless Unread.lastReadPost?
|
|
lastReadPost = Unread.db.get
|
|
boardID: Unread.thread.board.ID
|
|
threadID: Unread.thread.ID
|
|
defaultValue: 0
|
|
return unless Unread.lastReadPost < lastReadPost
|
|
Unread.lastReadPost = lastReadPost
|
|
|
|
postIDs = Unread.thread.posts.keys
|
|
for i in [Unread.readCount...postIDs.length] by 1
|
|
ID = +postIDs[i]
|
|
unless Unread.thread.posts[ID].isFetchedQuote
|
|
break if ID > Unread.lastReadPost
|
|
Unread.posts.delete ID
|
|
Unread.postsQuotingYou.delete ID
|
|
Unread.readCount++
|
|
|
|
Unread.updatePosition()
|
|
Unread.setLine()
|
|
Unread.update()
|
|
|
|
addPost: ->
|
|
return if @isFetchedQuote or @isClone
|
|
Unread.order.push @
|
|
return if @ID <= Unread.lastReadPost or @isHidden or QuoteYou.isYou(@)
|
|
Unread.posts.add (Unread.posts.last = @ID)
|
|
Unread.addPostQuotingYou @
|
|
Unread.position ?= Unread.order[@ID]
|
|
|
|
addPostQuotingYou: (post) ->
|
|
for quotelink in post.nodes.quotelinks when QuoteYou.db?.get Get.postDataFromLink quotelink
|
|
Unread.postsQuotingYou.add (Unread.postsQuotingYou.last = post.ID)
|
|
Unread.openNotification post
|
|
return
|
|
|
|
openNotification: (post, predicate=' replied to you') ->
|
|
return unless Header.areNotificationsEnabled
|
|
notif = new Notification "#{post.info.nameBlock}#{predicate}",
|
|
body: post.commentDisplay()
|
|
icon: Favicon.logo
|
|
notif.onclick = ->
|
|
Header.scrollToIfNeeded post.nodes.bottom, true
|
|
window.focus()
|
|
notif.onshow = ->
|
|
setTimeout ->
|
|
notif.close()
|
|
, 7 * $.SECOND
|
|
|
|
onUpdate: ->
|
|
$.queueTask -> # ThreadUpdater may scroll immediately after inserting posts
|
|
Unread.setLine()
|
|
Unread.read()
|
|
Unread.update()
|
|
|
|
readSinglePost: (post) ->
|
|
{ID} = post
|
|
return unless Unread.posts.has ID
|
|
Unread.posts.delete ID
|
|
Unread.postsQuotingYou.delete ID
|
|
Unread.updatePosition()
|
|
Unread.saveLastReadPost()
|
|
Unread.update()
|
|
|
|
read: $.debounce 100, (e) ->
|
|
# Update the lastReadPost when hidden posts are added to the thread.
|
|
if !Unread.posts.size and Unread.readCount isnt Unread.thread.posts.keys.length
|
|
Unread.saveLastReadPost()
|
|
|
|
return if d.hidden or !Unread.posts.size
|
|
|
|
count = 0
|
|
while Unread.position
|
|
{ID, data} = Unread.position
|
|
{bottom} = data.nodes
|
|
break unless !bottom.getBoundingClientRect().height or # post has been hidden
|
|
Header.getBottomOf(bottom) > -1 # post is completely read
|
|
count++
|
|
Unread.posts.delete ID
|
|
Unread.postsQuotingYou.delete ID
|
|
Unread.position = Unread.position.next
|
|
|
|
return unless count
|
|
Unread.updatePosition()
|
|
Unread.saveLastReadPost()
|
|
(Unread.update() if e)
|
|
|
|
updatePosition: ->
|
|
while Unread.position and !Unread.posts.has Unread.position.ID
|
|
Unread.position = Unread.position.next
|
|
return
|
|
|
|
saveLastReadPost: $.debounce 2 * $.SECOND, ->
|
|
$.forceSync 'Remember Last Read Post'
|
|
return unless Conf['Remember Last Read Post'] and Unread.db
|
|
postIDs = Unread.thread.posts.keys
|
|
for i in [Unread.readCount...postIDs.length] by 1
|
|
ID = +postIDs[i]
|
|
unless Unread.thread.posts[ID].isFetchedQuote
|
|
break if Unread.posts.has ID
|
|
Unread.lastReadPost = ID
|
|
Unread.readCount++
|
|
return if Unread.thread.isDead and !Unread.thread.isArchived
|
|
Unread.db.set
|
|
boardID: Unread.thread.board.ID
|
|
threadID: Unread.thread.ID
|
|
val: Unread.lastReadPost
|
|
|
|
setLine: (force) ->
|
|
return unless Conf['Unread Line']
|
|
if Unread.hr.hidden or d.hidden or (force is true)
|
|
oldPosition = Unread.linePosition
|
|
if (Unread.linePosition = Unread.positionPrev())
|
|
if Unread.linePosition isnt oldPosition
|
|
node = Unread.linePosition.data.nodes.bottom
|
|
node = node.nextSibling if node.nextSibling?.tagName is 'BR'
|
|
$.after node, Unread.hr
|
|
else
|
|
$.rm Unread.hr
|
|
Unread.hr.hidden = Unread.linePosition is Unread.order.last
|
|
|
|
update: ->
|
|
count = Unread.posts.size
|
|
countQuotingYou = Unread.postsQuotingYou.size
|
|
|
|
if Conf['Unread Count']
|
|
titleQuotingYou = if Conf['Quoted Title'] and countQuotingYou then '(!) ' else ''
|
|
titleCount = if count or !Conf['Hide Unread Count at (0)'] then "(#{count}) " else ''
|
|
titleDead = if Unread.thread.isDead
|
|
Unread.title.replace '-', (if Unread.thread.isArchived then '- Archived -' else '- 404 -')
|
|
else
|
|
Unread.title
|
|
d.title = "#{titleQuotingYou}#{titleCount}#{titleDead}"
|
|
|
|
Unread.saveThreadWatcherCount()
|
|
|
|
if Conf['Unread Favicon'] and g.SITE.software is 'yotsuba'
|
|
{isDead} = Unread.thread
|
|
Favicon.el.href =
|
|
if countQuotingYou
|
|
Favicon[if isDead then 'unreadDeadY' else 'unreadY']
|
|
else if count
|
|
Favicon[if isDead then 'unreadDead' else 'unread']
|
|
else
|
|
Favicon[if isDead then 'dead' else 'default']
|
|
# `favicon.href = href` doesn't work on Firefox.
|
|
$.add d.head, Favicon.el
|
|
|
|
saveThreadWatcherCount: $.debounce 2 * $.SECOND, ->
|
|
$.forceSync 'Remember Last Read Post'
|
|
if Conf['Remember Last Read Post'] and (!Unread.thread.isDead or Unread.thread.isArchived)
|
|
quotingYou = if !Conf['Require OP Quote Link'] and QuoteYou.isYou(Unread.thread.OP) then Unread.posts else Unread.postsQuotingYou
|
|
if !quotingYou.size
|
|
quotingYou.last = 0
|
|
else if !quotingYou.has(quotingYou.last)
|
|
quotingYou.last = 0
|
|
posts = Unread.thread.posts.keys
|
|
for i in [posts.length - 1 .. 0] by -1
|
|
if quotingYou.has(+posts[i])
|
|
quotingYou.last = posts[i]
|
|
break
|
|
ThreadWatcher.update g.SITE.ID, Unread.thread.board.ID, Unread.thread.ID,
|
|
last: Unread.thread.lastPost
|
|
isDead: Unread.thread.isDead
|
|
unread: Unread.posts.size
|
|
quotingYou: (quotingYou.last or 0)
|