-
Notifications
You must be signed in to change notification settings - Fork 137
/
Copy pathUnread.coffee
252 lines (221 loc) · 7.76 KB
/
Unread.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
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'
@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
<% if (readJSON('/.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: ->
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, 'ThreadUpdate', Unread.onUpdate
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
{root} = position.data.nodes
if !root.getBoundingClientRect().height
# Don't try to scroll to posts with display: none
position = position.prev
else
Header.scrollToIfNeeded root, true
break
return
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.db?.get {
boardID: @board.ID
threadID: @thread.ID
postID: @ID
}
Unread.posts.add @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 post.ID
Unread.openNotification post
return
openNotification: (post) ->
return unless Header.areNotificationsEnabled
notif = new Notification "#{post.info.nameBlock} replied to you",
body: post.commentDisplay()
icon: Favicon.logo
notif.onclick = ->
Header.scrollToIfNeeded post.nodes.root, true
window.focus()
notif.onshow = ->
setTimeout ->
notif.close()
, 7 * $.SECOND
onUpdate: (e) ->
if !e.detail[404]
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
{root} = data.nodes
break unless !root.getBoundingClientRect().height or # post has been hidden
Header.getBottomOf(root) > -1 # post is completely read
count++
Unread.posts.delete ID
Unread.postsQuotingYou.delete ID
if QuoteYou.db?.get {
boardID: data.board.ID
threadID: data.thread.ID
postID: ID
}
QuoteYou.lastRead = root
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.forceSync()
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)
if (Unread.linePosition = Unread.positionPrev())
$.after Unread.linePosition.data.nodes.root, 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']
{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)
ThreadWatcher.update Unread.thread.board.ID, Unread.thread.ID,
isDead: Unread.thread.isDead
unread: Unread.posts.size
quotingYou: Unread.postsQuotingYou.size