Fix rare race conditions with thread/reply hiding, unread, your posts. #968
Added DataBoards for simplifications and to reduce asyncings. Made it possible to export/import those. Made it possible for (You) to work with cross-board quotelinks. Replaced Misc.clearThreads. This is more v3-like.
This commit is contained in:
parent
435c411a36
commit
203bbf091d
@ -17,6 +17,7 @@ module.exports = function(grunt) {
|
|||||||
'src/features.coffee',
|
'src/features.coffee',
|
||||||
'src/qr.coffee',
|
'src/qr.coffee',
|
||||||
'src/report.coffee',
|
'src/report.coffee',
|
||||||
|
'src/databoard.coffee',
|
||||||
'src/main.coffee'
|
'src/main.coffee'
|
||||||
],
|
],
|
||||||
dest: 'tmp/script.coffee'
|
dest: 'tmp/script.coffee'
|
||||||
|
|||||||
80
src/databoard.coffee
Normal file
80
src/databoard.coffee
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
DataBoards = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts']
|
||||||
|
|
||||||
|
class DataBoard
|
||||||
|
constructor: (@key, sync) ->
|
||||||
|
@data = Conf[key]
|
||||||
|
$.sync key, @onSync.bind @
|
||||||
|
@clean()
|
||||||
|
return unless sync
|
||||||
|
# Chrome also fires the onChanged callback on the current tab,
|
||||||
|
# so we only start syncing when we're ready.
|
||||||
|
$.on d, '4chanXInitFinished', => @sync = sync
|
||||||
|
|
||||||
|
delete: ({boardID, threadID, postID}) ->
|
||||||
|
if postID
|
||||||
|
delete @data.boards[boardID][threadID][postID]
|
||||||
|
@deleteIfEmpty {boardID, threadID}
|
||||||
|
else if threadID
|
||||||
|
delete @data.boards[boardID][threadID]
|
||||||
|
@deleteIfEmpty {boardID}
|
||||||
|
else
|
||||||
|
delete @data.boards[boardID]
|
||||||
|
$.set @key, @data
|
||||||
|
deleteIfEmpty: ({boardID, threadID}) ->
|
||||||
|
if threadID
|
||||||
|
unless Object.keys(@data.boards[boardID][threadID]).length
|
||||||
|
delete @data.boards[boardID][threadID]
|
||||||
|
@deleteIfEmpty {boardID}
|
||||||
|
else unless Object.keys(@data.boards[boardID]).length
|
||||||
|
delete @data.boards[boardID]
|
||||||
|
set: ({boardID, threadID, postID, val}) ->
|
||||||
|
if postID
|
||||||
|
((@data.boards[boardID] or= {})[threadID] or= {})[postID] = val
|
||||||
|
else if threadID
|
||||||
|
(@data.boards[boardID] or= {})[threadID] = val
|
||||||
|
else
|
||||||
|
@data.boards[boardID] = val
|
||||||
|
$.set @key, @data
|
||||||
|
get: ({boardID, threadID, postID, defaultValue}) ->
|
||||||
|
if board = @data.boards[boardID]
|
||||||
|
unless threadID
|
||||||
|
if postID
|
||||||
|
for ID, thread in board
|
||||||
|
if postID of thread
|
||||||
|
val = thread[postID]
|
||||||
|
break
|
||||||
|
else
|
||||||
|
val = board
|
||||||
|
else if thread = board[threadID]
|
||||||
|
val = if postID
|
||||||
|
thread[postID]
|
||||||
|
else
|
||||||
|
thread
|
||||||
|
val or defaultValue
|
||||||
|
|
||||||
|
clean: ->
|
||||||
|
for boardID of @data.boards
|
||||||
|
@deleteIfEmpty {boardID}
|
||||||
|
|
||||||
|
now = Date.now()
|
||||||
|
if @data.lastChecked < now - 12 * $.HOUR
|
||||||
|
@data.lastChecked = now
|
||||||
|
for boardID of @data.boards
|
||||||
|
@ajaxClean boardID
|
||||||
|
|
||||||
|
$.set @key, @data
|
||||||
|
ajaxClean: (boardID) ->
|
||||||
|
$.ajax "//api.4chan.org/#{boardID}/threads.json", onload: (e) =>
|
||||||
|
board = @data.boards[boardID]
|
||||||
|
threads = {}
|
||||||
|
for page in JSON.parse e.target.response
|
||||||
|
for thread in page.threads
|
||||||
|
if thread.no of board
|
||||||
|
threads[thread.no] = board[thread.no]
|
||||||
|
@data.boards[boardID] = threads
|
||||||
|
@deleteIfEmpty {boardID}
|
||||||
|
$.set @key, @data
|
||||||
|
|
||||||
|
onSync: (data) ->
|
||||||
|
@data = data
|
||||||
|
@sync?()
|
||||||
@ -347,18 +347,20 @@ Settings =
|
|||||||
innerHTML: "<button></button><span class=description>: Clear manually hidden threads and posts on /#{g.BOARD}/."
|
innerHTML: "<button></button><span class=description>: Clear manually hidden threads and posts on /#{g.BOARD}/."
|
||||||
button = $ 'button', div
|
button = $ 'button', div
|
||||||
hiddenNum = 0
|
hiddenNum = 0
|
||||||
ThreadHiding.getHiddenThreads (hiddenThreads) ->
|
$.get 'hiddenThreads', boards: {}, (item) ->
|
||||||
for ID, thread of hiddenThreads.threads
|
for ID, board of item.hiddenThreads.boards
|
||||||
hiddenNum++
|
for ID, thread of board
|
||||||
button.textContent = "Hidden: #{hiddenNum}"
|
|
||||||
ReplyHiding.getHiddenPosts (hiddenPosts) ->
|
|
||||||
for ID, thread of hiddenPosts.threads
|
|
||||||
for ID, post of thread
|
|
||||||
hiddenNum++
|
hiddenNum++
|
||||||
button.textContent = "Hidden: #{hiddenNum}"
|
button.textContent = "Hidden: #{hiddenNum}"
|
||||||
|
$.get 'hiddenPosts', boards: {}, (item) ->
|
||||||
|
for ID, board of item.hiddenPosts.boards
|
||||||
|
for ID, thread of board
|
||||||
|
for ID, post of thread
|
||||||
|
hiddenNum++
|
||||||
|
button.textContent = "Hidden: #{hiddenNum}"
|
||||||
$.on button, 'click', ->
|
$.on button, 'click', ->
|
||||||
@textContent = 'Hidden: 0'
|
@textContent = 'Hidden: 0'
|
||||||
$.delete ["hiddenThreads.#{g.BOARD}", "hiddenPosts.#{g.BOARD}"]
|
$.delete ['hiddenThreads', 'hiddenPosts']
|
||||||
$.after $('input[name="Stubs"]', section).parentNode.parentNode, div
|
$.after $('input[name="Stubs"]', section).parentNode.parentNode, div
|
||||||
export: (now, data) ->
|
export: (now, data) ->
|
||||||
unless typeof now is 'number'
|
unless typeof now is 'number'
|
||||||
@ -367,9 +369,14 @@ Settings =
|
|||||||
version: g.VERSION
|
version: g.VERSION
|
||||||
date: now
|
date: now
|
||||||
Conf: Conf
|
Conf: Conf
|
||||||
$.get 'WatchedThreads', {}, (item) ->
|
items = WatchedThreads: {}
|
||||||
data.WatchedThreads = item.WatchedThreads
|
for db in DataBoards
|
||||||
|
items[db] = boards: {}
|
||||||
|
$.get items (items) ->
|
||||||
|
for key, val in items
|
||||||
|
data[key] = val
|
||||||
Settings.export now, data
|
Settings.export now, data
|
||||||
|
return
|
||||||
a = $.el 'a',
|
a = $.el 'a',
|
||||||
className: 'warning'
|
className: 'warning'
|
||||||
textContent: 'Save me!'
|
textContent: 'Save me!'
|
||||||
@ -472,6 +479,8 @@ Settings =
|
|||||||
"Shift+#{s[0...-1]}#{s[-1..].toLowerCase()}"
|
"Shift+#{s[0...-1]}#{s[-1..].toLowerCase()}"
|
||||||
for key, val of data.Conf
|
for key, val of data.Conf
|
||||||
$.set key, val
|
$.set key, val
|
||||||
|
for db in DataBoards
|
||||||
|
$.set db, data[db]
|
||||||
$.set 'WatchedThreads', data.WatchedThreads
|
$.set 'WatchedThreads', data.WatchedThreads
|
||||||
convertSettings: (data, map) ->
|
convertSettings: (data, map) ->
|
||||||
for prevKey, newKey of map
|
for prevKey, newKey of map
|
||||||
@ -1060,43 +1069,38 @@ ThreadHiding =
|
|||||||
init: ->
|
init: ->
|
||||||
return if g.VIEW isnt 'index' or !Conf['Thread Hiding'] and !Conf['Thread Hiding Link']
|
return if g.VIEW isnt 'index' or !Conf['Thread Hiding'] and !Conf['Thread Hiding Link']
|
||||||
|
|
||||||
Misc.clearThreads "hiddenThreads.#{g.BOARD}"
|
@db = new DataBoard 'hiddenThreads'
|
||||||
@syncFromCatalog()
|
@syncFromCatalog()
|
||||||
Thread::callbacks.push
|
Thread::callbacks.push
|
||||||
name: 'Thread Hiding'
|
name: 'Thread Hiding'
|
||||||
cb: @node
|
cb: @node
|
||||||
|
|
||||||
node: ->
|
node: ->
|
||||||
if data = ThreadHiding.hiddenThreads.threads[@]
|
if data = ThreadHiding.db.get {boardID: @board.ID, threadID: @ID}
|
||||||
ThreadHiding.hide @, data.makeStub
|
ThreadHiding.hide @, data.makeStub
|
||||||
return unless Conf['Thread Hiding']
|
return unless Conf['Thread Hiding']
|
||||||
$.prepend @OP.nodes.root, ThreadHiding.makeButton @, 'hide'
|
$.prepend @OP.nodes.root, ThreadHiding.makeButton @, 'hide'
|
||||||
|
|
||||||
getHiddenThreads: (cb) ->
|
|
||||||
$.get "hiddenThreads.#{g.BOARD}", threads: {}, (item) ->
|
|
||||||
ThreadHiding.hiddenThreads = item["hiddenThreads.#{g.BOARD}"]
|
|
||||||
cb ThreadHiding.hiddenThreads if cb
|
|
||||||
|
|
||||||
syncFromCatalog: ->
|
syncFromCatalog: ->
|
||||||
# Sync hidden threads from the catalog into the index.
|
# Sync hidden threads from the catalog into the index.
|
||||||
ThreadHiding.getHiddenThreads (hiddenThreads) ->
|
hiddenThreads = ThreadHiding.db.get
|
||||||
{threads} = hiddenThreads
|
boardID: g.BOARD.ID
|
||||||
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {}
|
defaultValue: {}
|
||||||
|
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {}
|
||||||
|
|
||||||
# Add threads that were hidden in the catalog.
|
# Add threads that were hidden in the catalog.
|
||||||
for threadID of hiddenThreadsOnCatalog
|
for threadID of hiddenThreadsOnCatalog
|
||||||
continue if threadID of threads
|
unless threadID of hiddenThreads
|
||||||
threads[threadID] = {}
|
hiddenThreads[threadID] = {}
|
||||||
|
|
||||||
# Remove threads that were un-hidden in the catalog.
|
# Remove threads that were un-hidden in the catalog.
|
||||||
for threadID of threads
|
for threadID of hiddenThreads
|
||||||
continue if threadID of threads
|
unless threadID of hiddenThreadsOnCatalog
|
||||||
delete threads[threadID]
|
delete hiddenThreads[threadID]
|
||||||
|
|
||||||
if Object.keys(threads).length
|
ThreadHiding.db.set
|
||||||
$.set "hiddenThreads.#{g.BOARD}", ThreadHiding.hiddenThreads
|
boardID: g.BOARD.ID
|
||||||
else
|
val: hiddenThreads
|
||||||
$.delete "hiddenThreads.#{g.BOARD}"
|
|
||||||
|
|
||||||
menu:
|
menu:
|
||||||
init: ->
|
init: ->
|
||||||
@ -1141,17 +1145,19 @@ ThreadHiding =
|
|||||||
a
|
a
|
||||||
|
|
||||||
saveHiddenState: (thread, makeStub) ->
|
saveHiddenState: (thread, makeStub) ->
|
||||||
# Get fresh hidden threads.
|
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {}
|
||||||
ThreadHiding.getHiddenThreads (hiddenThreads) ->
|
if thread.isHidden
|
||||||
hiddenThreadsCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {}
|
ThreadHiding.db.set
|
||||||
if thread.isHidden
|
boardID: thread.board.ID
|
||||||
hiddenThreads.threads[thread] = {makeStub}
|
threadID: thread.ID
|
||||||
hiddenThreadsCatalog[thread] = true
|
val: {makeStub}
|
||||||
else
|
hiddenThreadsOnCatalog[thread] = true
|
||||||
delete hiddenThreads.threads[thread]
|
else
|
||||||
delete hiddenThreadsCatalog[thread]
|
ThreadHiding.db.delete
|
||||||
$.set "hiddenThreads.#{g.BOARD}", hiddenThreads
|
boardID: thread.board.ID
|
||||||
localStorage.setItem "4chan-hide-t-#{g.BOARD}", JSON.stringify hiddenThreadsCatalog
|
threadID: thread.ID
|
||||||
|
delete hiddenThreadsOnCatalog[thread]
|
||||||
|
localStorage.setItem "4chan-hide-t-#{g.BOARD}", JSON.stringify hiddenThreadsOnCatalog
|
||||||
|
|
||||||
toggle: (thread) ->
|
toggle: (thread) ->
|
||||||
unless thread instanceof Thread
|
unless thread instanceof Thread
|
||||||
@ -1204,29 +1210,22 @@ ReplyHiding =
|
|||||||
init: ->
|
init: ->
|
||||||
return if g.VIEW is 'catalog' or !Conf['Reply Hiding'] and !Conf['Reply Hiding Link']
|
return if g.VIEW is 'catalog' or !Conf['Reply Hiding'] and !Conf['Reply Hiding Link']
|
||||||
|
|
||||||
Misc.clearThreads "hiddenPosts.#{g.BOARD}"
|
@db = new DataBoard 'hiddenPosts'
|
||||||
@getHiddenPosts()
|
|
||||||
Post::callbacks.push
|
Post::callbacks.push
|
||||||
name: 'Reply Hiding'
|
name: 'Reply Hiding'
|
||||||
cb: @node
|
cb: @node
|
||||||
|
|
||||||
node: ->
|
node: ->
|
||||||
return if !@isReply or @isClone
|
return if !@isReply or @isClone
|
||||||
if thread = ReplyHiding.hiddenPosts.threads[@thread]
|
if data = ReplyHiding.db.get {boardID: @board.ID, threadID: @thread.ID, postID: @ID}
|
||||||
if data = thread[@]
|
if data.thisPost
|
||||||
if data.thisPost
|
ReplyHiding.hide @, data.makeStub, data.hideRecursively
|
||||||
ReplyHiding.hide @, data.makeStub, data.hideRecursively
|
else
|
||||||
else
|
Recursive.apply ReplyHiding.hide, @, data.makeStub, true
|
||||||
Recursive.apply ReplyHiding.hide, @, data.makeStub, true
|
Recursive.add ReplyHiding.hide, @, data.makeStub, true
|
||||||
Recursive.add ReplyHiding.hide, @, data.makeStub, true
|
|
||||||
return unless Conf['Reply Hiding']
|
return unless Conf['Reply Hiding']
|
||||||
$.replace $('.sideArrows', @nodes.root), ReplyHiding.makeButton @, 'hide'
|
$.replace $('.sideArrows', @nodes.root), ReplyHiding.makeButton @, 'hide'
|
||||||
|
|
||||||
getHiddenPosts: (cb) ->
|
|
||||||
$.get "hiddenPosts.#{g.BOARD}", threads: {}, (item) ->
|
|
||||||
ReplyHiding.hiddenPosts = item["hiddenPosts.#{g.BOARD}"]
|
|
||||||
cb ReplyHiding.hiddenPosts if cb
|
|
||||||
|
|
||||||
menu:
|
menu:
|
||||||
init: ->
|
init: ->
|
||||||
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Reply Hiding Link']
|
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Reply Hiding Link']
|
||||||
@ -1279,10 +1278,9 @@ ReplyHiding =
|
|||||||
el: div
|
el: div
|
||||||
order: 20
|
order: 20
|
||||||
open: (post) ->
|
open: (post) ->
|
||||||
if !post.isReply or post.isClone
|
if !post.isReply or post.isClone or !post.isHidden
|
||||||
return false
|
return false
|
||||||
thread = ReplyHiding.hiddenPosts.threads[post.thread]
|
unless data = ReplyHiding.db.get {boardID: post.board.ID, threadID: post.thread.ID, postID: post.ID}
|
||||||
unless post.isHidden or data = thread?[post]
|
|
||||||
return false
|
return false
|
||||||
ReplyHiding.menu.post = post
|
ReplyHiding.menu.post = post
|
||||||
thisPost.firstChild.checked = post.isHidden
|
thisPost.firstChild.checked = post.isHidden
|
||||||
@ -1309,8 +1307,6 @@ ReplyHiding =
|
|||||||
thisPost = $('input[name=thisPost]', parent).checked
|
thisPost = $('input[name=thisPost]', parent).checked
|
||||||
replies = $('input[name=replies]', parent).checked
|
replies = $('input[name=replies]', parent).checked
|
||||||
{post} = ReplyHiding.menu
|
{post} = ReplyHiding.menu
|
||||||
thread = ReplyHiding.hiddenPosts.threads[post.thread]
|
|
||||||
data = thread?[post]
|
|
||||||
if thisPost
|
if thisPost
|
||||||
ReplyHiding.show post, replies
|
ReplyHiding.show post, replies
|
||||||
else if replies
|
else if replies
|
||||||
@ -1318,7 +1314,7 @@ ReplyHiding =
|
|||||||
Recursive.rm ReplyHiding.hide, post, true
|
Recursive.rm ReplyHiding.hide, post, true
|
||||||
else
|
else
|
||||||
return
|
return
|
||||||
if data
|
if data = ReplyHiding.db.get {boardID: post.board.ID, threadID: post.thread.ID, postID: post.ID}
|
||||||
ReplyHiding.saveHiddenState post, !(thisPost and replies), !thisPost, data.makeStub, !replies
|
ReplyHiding.saveHiddenState post, !(thisPost and replies), !thisPost, data.makeStub, !replies
|
||||||
$.event 'CloseMenu'
|
$.event 'CloseMenu'
|
||||||
|
|
||||||
@ -1331,21 +1327,18 @@ ReplyHiding =
|
|||||||
a
|
a
|
||||||
|
|
||||||
saveHiddenState: (post, isHiding, thisPost, makeStub, hideRecursively) ->
|
saveHiddenState: (post, isHiding, thisPost, makeStub, hideRecursively) ->
|
||||||
# Get fresh hidden posts.
|
data =
|
||||||
ReplyHiding.getHiddenPosts (hiddenPosts) ->
|
boardID: post.board.ID
|
||||||
if isHiding
|
threadID: post.thread.ID
|
||||||
unless thread = hiddenPosts.threads[post.thread]
|
postID: post.ID
|
||||||
thread = hiddenPosts.threads[post.thread] = {}
|
if isHiding
|
||||||
thread[post] =
|
data.val =
|
||||||
thisPost: thisPost isnt false # undefined -> true
|
thisPost: thisPost isnt false # undefined -> true
|
||||||
makeStub: makeStub
|
makeStub: makeStub
|
||||||
hideRecursively: hideRecursively
|
hideRecursively: hideRecursively
|
||||||
else
|
ReplyHiding.db.set data
|
||||||
thread = hiddenPosts.threads[post.thread]
|
else
|
||||||
delete thread[post]
|
ReplyHiding.db.delete data
|
||||||
unless Object.keys(thread).length
|
|
||||||
delete hiddenPosts.threads[post.thread]
|
|
||||||
$.set "hiddenPosts.#{g.BOARD}", hiddenPosts
|
|
||||||
|
|
||||||
toggle: ->
|
toggle: ->
|
||||||
post = Get.postFromNode @
|
post = Get.postFromNode @
|
||||||
@ -1448,8 +1441,8 @@ QuoteStrikeThrough =
|
|||||||
node: ->
|
node: ->
|
||||||
return if @isClone
|
return if @isClone
|
||||||
for quotelink in @nodes.quotelinks
|
for quotelink in @nodes.quotelinks
|
||||||
{board, postID} = Get.postDataFromLink quotelink
|
{boardID, postID} = Get.postDataFromLink quotelink
|
||||||
if g.posts["#{board}.#{postID}"]?.isHidden
|
if g.posts["#{boardID}.#{postID}"]?.isHidden
|
||||||
$.addClass quotelink, 'filtered'
|
$.addClass quotelink, 'filtered'
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -1597,7 +1590,7 @@ DeleteLink =
|
|||||||
|
|
||||||
cooldown:
|
cooldown:
|
||||||
start: (post, node) ->
|
start: (post, node) ->
|
||||||
unless (thread = QR.yourPosts?.threads?[post.thread]) and post.ID in thread
|
unless QR.db?.get {boardID: post.board.ID, threadID: post.thread.ID, postID: post.ID}
|
||||||
# Only start counting on our posts.
|
# Only start counting on our posts.
|
||||||
delete DeleteLink.cooldown.counting
|
delete DeleteLink.cooldown.counting
|
||||||
return
|
return
|
||||||
@ -2304,15 +2297,15 @@ Get =
|
|||||||
postDataFromLink: (link) ->
|
postDataFromLink: (link) ->
|
||||||
if link.hostname is 'boards.4chan.org'
|
if link.hostname is 'boards.4chan.org'
|
||||||
path = link.pathname.split '/'
|
path = link.pathname.split '/'
|
||||||
board = path[1]
|
boardID = path[1]
|
||||||
threadID = path[3]
|
threadID = path[3]
|
||||||
postID = link.hash[2..]
|
postID = link.hash[2..]
|
||||||
else # resurrected quote
|
else # resurrected quote
|
||||||
board = link.dataset.board
|
boardID = link.dataset.board
|
||||||
threadID = link.dataset.threadid or 0
|
threadID = link.dataset.threadid or 0
|
||||||
postID = link.dataset.postid
|
postID = link.dataset.postid
|
||||||
return {
|
return {
|
||||||
board: board
|
boardID: boardID
|
||||||
threadID: +threadID
|
threadID: +threadID
|
||||||
postID: +postID
|
postID: +postID
|
||||||
}
|
}
|
||||||
@ -2340,8 +2333,8 @@ Get =
|
|||||||
# Third:
|
# Third:
|
||||||
# Filter out irrelevant quotelinks.
|
# Filter out irrelevant quotelinks.
|
||||||
quotelinks.filter (quotelink) ->
|
quotelinks.filter (quotelink) ->
|
||||||
{board, postID} = Get.postDataFromLink quotelink
|
{boardID, postID} = Get.postDataFromLink quotelink
|
||||||
board is post.board.ID and postID is post.ID
|
boardID is post.board.ID and postID is post.ID
|
||||||
postClone: (board, threadID, postID, root, context) ->
|
postClone: (board, threadID, postID, root, context) ->
|
||||||
if post = g.posts["#{board}.#{postID}"]
|
if post = g.posts["#{board}.#{postID}"]
|
||||||
Get.insert post, root, context
|
Get.insert post, root, context
|
||||||
@ -2510,34 +2503,6 @@ Get =
|
|||||||
Main.callbackNodes Post, [post]
|
Main.callbackNodes Post, [post]
|
||||||
Get.insert post, root, context
|
Get.insert post, root, context
|
||||||
|
|
||||||
Misc = # super semantic
|
|
||||||
clearThreads: (key, data) ->
|
|
||||||
unless data
|
|
||||||
$.get key, null, (item) ->
|
|
||||||
data = item[key]
|
|
||||||
return unless data
|
|
||||||
Misc.clearThreads key, data
|
|
||||||
return
|
|
||||||
|
|
||||||
unless Object.keys(data.threads).length
|
|
||||||
$.delete key
|
|
||||||
return
|
|
||||||
|
|
||||||
return if data.lastChecked > Date.now() - 12 * $.HOUR
|
|
||||||
|
|
||||||
$.ajax "//api.4chan.org/#{g.BOARD}/threads.json", onload: ->
|
|
||||||
threads = {}
|
|
||||||
for page in JSON.parse @response
|
|
||||||
for thread in page.threads
|
|
||||||
if thread.no of data.threads
|
|
||||||
threads[thread.no] = data.threads[thread.no]
|
|
||||||
unless Object.keys(threads).length
|
|
||||||
$.delete key
|
|
||||||
return
|
|
||||||
data.threads = threads
|
|
||||||
data.lastChecked = Date.now()
|
|
||||||
$.set key, data
|
|
||||||
|
|
||||||
Quotify =
|
Quotify =
|
||||||
init: ->
|
init: ->
|
||||||
return if g.VIEW is 'catalog' or !Conf['Resurrect Quotes']
|
return if g.VIEW is 'catalog' or !Conf['Resurrect Quotes']
|
||||||
@ -2623,13 +2588,13 @@ QuoteInline =
|
|||||||
toggle: (e) ->
|
toggle: (e) ->
|
||||||
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
|
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
{board, threadID, postID} = Get.postDataFromLink @
|
{boardID, threadID, postID} = Get.postDataFromLink @
|
||||||
context = Get.contextFromLink @
|
context = Get.contextFromLink @
|
||||||
if $.hasClass @, 'inlined'
|
if $.hasClass @, 'inlined'
|
||||||
QuoteInline.rm @, board, threadID, postID, context
|
QuoteInline.rm @, boardID, threadID, postID, context
|
||||||
else
|
else
|
||||||
return if $.x "ancestor::div[@id='p#{postID}']", @
|
return if $.x "ancestor::div[@id='p#{postID}']", @
|
||||||
QuoteInline.add @, board, threadID, postID, context
|
QuoteInline.add @, boardID, threadID, postID, context
|
||||||
@classList.toggle 'inlined'
|
@classList.toggle 'inlined'
|
||||||
|
|
||||||
findRoot: (quotelink, isBacklink) ->
|
findRoot: (quotelink, isBacklink) ->
|
||||||
@ -2637,15 +2602,15 @@ QuoteInline =
|
|||||||
quotelink.parentNode.parentNode
|
quotelink.parentNode.parentNode
|
||||||
else
|
else
|
||||||
$.x 'ancestor-or-self::*[parent::blockquote][1]', quotelink
|
$.x 'ancestor-or-self::*[parent::blockquote][1]', quotelink
|
||||||
add: (quotelink, board, threadID, postID, context) ->
|
add: (quotelink, boardID, threadID, postID, context) ->
|
||||||
isBacklink = $.hasClass quotelink, 'backlink'
|
isBacklink = $.hasClass quotelink, 'backlink'
|
||||||
inline = $.el 'div',
|
inline = $.el 'div',
|
||||||
id: "i#{postID}"
|
id: "i#{postID}"
|
||||||
className: 'inline'
|
className: 'inline'
|
||||||
$.after QuoteInline.findRoot(quotelink, isBacklink), inline
|
$.after QuoteInline.findRoot(quotelink, isBacklink), inline
|
||||||
Get.postClone board, threadID, postID, inline, context
|
Get.postClone boardID, threadID, postID, inline, context
|
||||||
|
|
||||||
return unless (post = g.posts["#{board}.#{postID}"]) and
|
return unless (post = g.posts["#{boardID}.#{postID}"]) and
|
||||||
context.thread is post.thread
|
context.thread is post.thread
|
||||||
|
|
||||||
# Hide forward post if it's a backlink of a post in this thread.
|
# Hide forward post if it's a backlink of a post in this thread.
|
||||||
@ -2659,7 +2624,7 @@ QuoteInline =
|
|||||||
Unread.posts.splice i, 1
|
Unread.posts.splice i, 1
|
||||||
Unread.update()
|
Unread.update()
|
||||||
|
|
||||||
rm: (quotelink, board, threadID, postID, context) ->
|
rm: (quotelink, boardID, threadID, postID, context) ->
|
||||||
isBacklink = $.hasClass quotelink, 'backlink'
|
isBacklink = $.hasClass quotelink, 'backlink'
|
||||||
# Select the corresponding inlined quote, and remove it.
|
# Select the corresponding inlined quote, and remove it.
|
||||||
root = QuoteInline.findRoot quotelink, isBacklink
|
root = QuoteInline.findRoot quotelink, isBacklink
|
||||||
@ -2670,21 +2635,21 @@ QuoteInline =
|
|||||||
return unless el = root.firstElementChild
|
return unless el = root.firstElementChild
|
||||||
|
|
||||||
# Dereference clone.
|
# Dereference clone.
|
||||||
post = g.posts["#{board}.#{postID}"]
|
post = g.posts["#{boardID}.#{postID}"]
|
||||||
post.rmClone el.dataset.clone
|
post.rmClone el.dataset.clone
|
||||||
|
|
||||||
# Decrease forward count and unhide.
|
# Decrease forward count and unhide.
|
||||||
if Conf['Forward Hiding'] and
|
if Conf['Forward Hiding'] and
|
||||||
isBacklink and
|
isBacklink and
|
||||||
context.thread is g.threads["#{board}.#{threadID}"] and
|
context.thread is g.threads["#{boardID}.#{threadID}"] and
|
||||||
not --post.forwarded
|
not --post.forwarded
|
||||||
delete post.forwarded
|
delete post.forwarded
|
||||||
$.rmClass post.nodes.root, 'forwarded'
|
$.rmClass post.nodes.root, 'forwarded'
|
||||||
|
|
||||||
# Repeat.
|
# Repeat.
|
||||||
while inlined = $ '.inlined', el
|
while inlined = $ '.inlined', el
|
||||||
{board, threadID, postID} = Get.postDataFromLink inlined
|
{boardID, threadID, postID} = Get.postDataFromLink inlined
|
||||||
QuoteInline.rm inlined, board, threadID, postID, context
|
QuoteInline.rm inlined, boardID, threadID, postID, context
|
||||||
$.rmClass inlined, 'inlined'
|
$.rmClass inlined, 'inlined'
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -2704,13 +2669,13 @@ QuotePreview =
|
|||||||
mouseover: (e) ->
|
mouseover: (e) ->
|
||||||
return if $.hasClass @, 'inlined'
|
return if $.hasClass @, 'inlined'
|
||||||
|
|
||||||
{board, threadID, postID} = Get.postDataFromLink @
|
{boardID, threadID, postID} = Get.postDataFromLink @
|
||||||
|
|
||||||
qp = $.el 'div',
|
qp = $.el 'div',
|
||||||
id: 'qp'
|
id: 'qp'
|
||||||
className: 'dialog'
|
className: 'dialog'
|
||||||
$.add d.body, qp
|
$.add d.body, qp
|
||||||
Get.postClone board, threadID, postID, qp, Get.contextFromLink @
|
Get.postClone boardID, threadID, postID, qp, Get.contextFromLink @
|
||||||
|
|
||||||
UI.hover
|
UI.hover
|
||||||
root: @
|
root: @
|
||||||
@ -2720,7 +2685,7 @@ QuotePreview =
|
|||||||
cb: QuotePreview.mouseout
|
cb: QuotePreview.mouseout
|
||||||
asapTest: -> qp.firstElementChild
|
asapTest: -> qp.firstElementChild
|
||||||
|
|
||||||
return unless origin = g.posts["#{board}.#{postID}"]
|
return unless origin = g.posts["#{boardID}.#{postID}"]
|
||||||
|
|
||||||
if Conf['Quote Highlighting']
|
if Conf['Quote Highlighting']
|
||||||
posts = [origin].concat origin.clones
|
posts = [origin].concat origin.clones
|
||||||
@ -2823,11 +2788,12 @@ QuoteYou =
|
|||||||
return if @isClone
|
return if @isClone
|
||||||
# Stop there if there's no quotes in that post.
|
# Stop there if there's no quotes in that post.
|
||||||
return unless (quotes = @quotes).length
|
return unless (quotes = @quotes).length
|
||||||
|
{db} = QR
|
||||||
|
return unless db
|
||||||
{quotelinks} = @nodes
|
{quotelinks} = @nodes
|
||||||
|
|
||||||
for quotelink in quotelinks
|
for quotelink in quotelinks
|
||||||
{threadID, postID} = Get.postDataFromLink quotelink
|
if db.get Get.postDataFromLink quotelink
|
||||||
if (thread = QR.yourPosts.threads[threadID]) and postID in thread
|
|
||||||
$.add quotelink, $.tn QuoteYou.text
|
$.add quotelink, $.tn QuoteYou.text
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -2856,8 +2822,8 @@ QuoteOP =
|
|||||||
# add (OP) to quotes quoting this context's OP.
|
# add (OP) to quotes quoting this context's OP.
|
||||||
return unless op in quotes
|
return unless op in quotes
|
||||||
for quotelink in quotelinks
|
for quotelink in quotelinks
|
||||||
{board, postID} = Get.postDataFromLink quotelink
|
{boardID, postID} = Get.postDataFromLink quotelink
|
||||||
if "#{board}.#{postID}" is op
|
if "#{boardID}.#{postID}" is op
|
||||||
$.add quotelink, $.tn QuoteOP.text
|
$.add quotelink, $.tn QuoteOP.text
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -2879,11 +2845,11 @@ QuoteCT =
|
|||||||
|
|
||||||
{board, thread} = if @isClone then @context else @
|
{board, thread} = if @isClone then @context else @
|
||||||
for quotelink in quotelinks
|
for quotelink in quotelinks
|
||||||
data = Get.postDataFromLink quotelink
|
{boardID, threadID} = Get.postDataFromLink quotelink
|
||||||
continue unless data.threadID # deadlink
|
continue unless threadID # deadlink
|
||||||
if @isClone
|
if @isClone
|
||||||
quotelink.textContent = quotelink.textContent.replace QuoteCT.text, ''
|
quotelink.textContent = quotelink.textContent.replace QuoteCT.text, ''
|
||||||
if data.board is @board.ID and data.threadID isnt thread.ID
|
if boardID is @board.ID and threadID isnt thread.ID
|
||||||
$.add quotelink, $.tn QuoteCT.text
|
$.add quotelink, $.tn QuoteCT.text
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -3612,13 +3578,11 @@ Unread =
|
|||||||
init: ->
|
init: ->
|
||||||
return if g.VIEW isnt 'thread' or !Conf['Unread Count'] and !Conf['Unread Tab Icon']
|
return if g.VIEW isnt 'thread' or !Conf['Unread Count'] and !Conf['Unread Tab Icon']
|
||||||
|
|
||||||
Unread.hr = $.el 'hr',
|
@db = new DataBoard 'lastReadPosts', @sync
|
||||||
|
@hr = $.el 'hr',
|
||||||
id: 'unread-line'
|
id: 'unread-line'
|
||||||
Misc.clearThreads "lastReadPosts.#{g.BOARD}"
|
@posts = []
|
||||||
$.sync "lastReadPosts.#{g.BOARD}", @sync
|
@postsQuotingYou = []
|
||||||
|
|
||||||
Unread.posts = []
|
|
||||||
Unread.postsQuotingYou = []
|
|
||||||
|
|
||||||
Thread::callbacks.push
|
Thread::callbacks.push
|
||||||
name: 'Unread'
|
name: 'Unread'
|
||||||
@ -3630,23 +3594,29 @@ Unread =
|
|||||||
posts = []
|
posts = []
|
||||||
for ID, post of @posts
|
for ID, post of @posts
|
||||||
posts.push post if post.isReply
|
posts.push post if post.isReply
|
||||||
$.get "lastReadPosts.#{@board}", threads: {}, (item) =>
|
Unread.lastReadPost = Unread.db.get
|
||||||
Unread.lastReadPost = item["lastReadPosts.#{@board}"].threads[@] or 0
|
boardID: @board.ID
|
||||||
Unread.addPosts posts
|
threadID: @ID
|
||||||
if (hash = location.hash.match /\d+/) and post = @posts[hash[0]]
|
defaultValue: 0
|
||||||
post.nodes.root.scrollIntoView()
|
Unread.addPosts posts
|
||||||
else if Unread.posts.length
|
if (hash = location.hash.match /\d+/) and post = @posts[hash[0]]
|
||||||
# Scroll to before the first unread post.
|
post.nodes.root.scrollIntoView()
|
||||||
$.x('preceding-sibling::div[contains(@class,"postContainer")][1]', Unread.posts[0].nodes.root).scrollIntoView false
|
else if Unread.posts.length
|
||||||
else if posts.length
|
# Scroll to before the first unread post.
|
||||||
# Scroll to the last read post.
|
$.x('preceding-sibling::div[contains(@class,"postContainer")][1]', Unread.posts[0].nodes.root).scrollIntoView false
|
||||||
posts[posts.length - 1].nodes.root.scrollIntoView()
|
else if posts.length
|
||||||
|
# Scroll to the last read post.
|
||||||
|
posts[posts.length - 1].nodes.root.scrollIntoView()
|
||||||
$.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']
|
$.on d, 'visibilitychange', Unread.setLine if Conf['Unread Line']
|
||||||
|
|
||||||
sync: (lastReadPosts) ->
|
sync: ->
|
||||||
return unless (lastReadPost = lastReadPosts?.threads?[Unread.thread]) and Unread.lastReadPost < lastReadPost
|
lastReadPost = Unread.db.get
|
||||||
|
boardID: Unread.thread.board.ID
|
||||||
|
threadID: Unread.thread.ID
|
||||||
|
defaultValue: 0
|
||||||
|
return unless Unread.lastReadPost < lastReadPost
|
||||||
Unread.lastReadPost = lastReadPost
|
Unread.lastReadPost = lastReadPost
|
||||||
Unread.readArray Unread.posts
|
Unread.readArray Unread.posts
|
||||||
Unread.readArray Unread.postsQuotingYou
|
Unread.readArray Unread.postsQuotingYou
|
||||||
@ -3654,29 +3624,30 @@ Unread =
|
|||||||
Unread.update()
|
Unread.update()
|
||||||
|
|
||||||
addPosts: (newPosts) ->
|
addPosts: (newPosts) ->
|
||||||
if Conf['Quick Reply']
|
|
||||||
{yourPosts} = QR
|
|
||||||
youInThisThread = yourPosts.threads[Unread.thread]
|
|
||||||
for post in newPosts
|
for post in newPosts
|
||||||
{ID} = post
|
{ID} = post
|
||||||
if ID <= Unread.lastReadPost or post.isHidden or youInThisThread and ID in youInThisThread
|
if ID <= Unread.lastReadPost or post.isHidden
|
||||||
continue
|
continue
|
||||||
|
if QR.db
|
||||||
|
data =
|
||||||
|
boardID: post.board.ID
|
||||||
|
threadID: post.thread.ID
|
||||||
|
postID: post.ID
|
||||||
|
continue if QR.db.get data
|
||||||
Unread.posts.push post
|
Unread.posts.push post
|
||||||
Unread.addPostQuotingYou post, yourPosts if yourPosts
|
Unread.addPostQuotingYou post
|
||||||
if Conf['Unread Line']
|
if Conf['Unread Line']
|
||||||
# Force line on visible threads if there were no unread posts previously.
|
# Force line on visible threads if there were no unread posts previously.
|
||||||
Unread.setLine Unread.posts[0] in newPosts
|
Unread.setLine Unread.posts[0] in newPosts
|
||||||
Unread.read()
|
Unread.read()
|
||||||
Unread.update()
|
Unread.update()
|
||||||
|
|
||||||
addPostQuotingYou: (post, yourPosts) ->
|
addPostQuotingYou: (post) ->
|
||||||
for quote in post.quotes
|
return unless QR.db
|
||||||
[board, quoteID] = quote.split '.'
|
for quotelink in post.nodes.quotelinks
|
||||||
continue unless board is Unread.thread.board.ID
|
if QR.db.get Get.postDataFromLink quotelink
|
||||||
for thread, postIDs of yourPosts.threads
|
Unread.postsQuotingYou.push post
|
||||||
if +quoteID in postIDs
|
return
|
||||||
Unread.postsQuotingYou.push post
|
|
||||||
return
|
|
||||||
|
|
||||||
onUpdate: (e) ->
|
onUpdate: (e) ->
|
||||||
if e.detail[404]
|
if e.detail[404]
|
||||||
@ -3704,10 +3675,10 @@ Unread =
|
|||||||
Unread.update() if e
|
Unread.update() if e
|
||||||
|
|
||||||
saveLastReadPost: $.debounce 2 * $.SECOND, ->
|
saveLastReadPost: $.debounce 2 * $.SECOND, ->
|
||||||
$.get "lastReadPosts.#{Unread.thread.board}", threads: {}, (item) ->
|
Unread.db.set
|
||||||
lastReadPosts = item["lastReadPosts.#{Unread.thread.board}"]
|
boardID: Unread.thread.board.ID
|
||||||
lastReadPosts.threads[Unread.thread] = Unread.lastReadPost
|
threadID: Unread.thread.ID
|
||||||
$.set "lastReadPosts.#{Unread.thread.board}", lastReadPosts
|
val: Unread.lastReadPost
|
||||||
|
|
||||||
setLine: (force) ->
|
setLine: (force) ->
|
||||||
return unless d.hidden or force is true
|
return unless d.hidden or force is true
|
||||||
|
|||||||
@ -282,22 +282,25 @@ class Clone extends Post
|
|||||||
|
|
||||||
Main =
|
Main =
|
||||||
init: (items) ->
|
init: (items) ->
|
||||||
unless items
|
# flatten Config into Conf
|
||||||
# flatten Config into Conf
|
# and get saved or default values
|
||||||
# and get saved or default values
|
flatten = (parent, obj) ->
|
||||||
flatten = (parent, obj) ->
|
if obj instanceof Array
|
||||||
if obj instanceof Array
|
Conf[parent] = obj[0]
|
||||||
Conf[parent] = obj[0]
|
else if typeof obj is 'object'
|
||||||
else if typeof obj is 'object'
|
for key, val of obj
|
||||||
for key, val of obj
|
flatten key, val
|
||||||
flatten key, val
|
else # string or number
|
||||||
else # string or number
|
Conf[parent] = obj
|
||||||
Conf[parent] = obj
|
|
||||||
return
|
|
||||||
flatten null, Config
|
|
||||||
$.get Conf, Main.init
|
|
||||||
return
|
return
|
||||||
|
flatten null, Config
|
||||||
|
for db in DataBoards
|
||||||
|
Conf[db] = boards: {}
|
||||||
|
$.get Conf, Main.initFeatures
|
||||||
|
|
||||||
|
$.on d, '4chanMainInit', Main.initStyle
|
||||||
|
|
||||||
|
initFeatures: (items) ->
|
||||||
Conf = items
|
Conf = items
|
||||||
|
|
||||||
pathname = location.pathname.split '/'
|
pathname = location.pathname.split '/'
|
||||||
@ -385,10 +388,10 @@ Main =
|
|||||||
# c.timeEnd 'All initializations'
|
# c.timeEnd 'All initializations'
|
||||||
|
|
||||||
$.on d, 'AddCallback', Main.addCallback
|
$.on d, 'AddCallback', Main.addCallback
|
||||||
$.on d, '4chanMainInit', Main.initStyle
|
|
||||||
$.ready Main.initReady
|
$.ready Main.initReady
|
||||||
|
|
||||||
initStyle: ->
|
initStyle: ->
|
||||||
|
$.off d, '4chanMainInit', Main.initStyle
|
||||||
return unless Main.isThisPageLegit()
|
return unless Main.isThisPageLegit()
|
||||||
# disable the mobile layout
|
# disable the mobile layout
|
||||||
$('link[href*=mobile]', d.head)?.disabled = true
|
$('link[href*=mobile]', d.head)?.disabled = true
|
||||||
|
|||||||
@ -2,8 +2,7 @@ QR =
|
|||||||
init: ->
|
init: ->
|
||||||
return if g.VIEW is 'catalog' or !Conf['Quick Reply']
|
return if g.VIEW is 'catalog' or !Conf['Quick Reply']
|
||||||
|
|
||||||
Misc.clearThreads "yourPosts.#{g.BOARD}"
|
@db = new DataBoard 'yourPosts'
|
||||||
@syncYourPosts()
|
|
||||||
|
|
||||||
if Conf['Hide Original Post Form']
|
if Conf['Hide Original Post Form']
|
||||||
$.addClass doc, 'hide-original-post-form'
|
$.addClass doc, 'hide-original-post-form'
|
||||||
@ -88,14 +87,6 @@ QR =
|
|||||||
else
|
else
|
||||||
QR.unhide()
|
QR.unhide()
|
||||||
|
|
||||||
syncYourPosts: (yourPosts) ->
|
|
||||||
if yourPosts
|
|
||||||
QR.yourPosts = yourPosts
|
|
||||||
return
|
|
||||||
$.get "yourPosts.#{g.BOARD}", threads: {}, (item) ->
|
|
||||||
QR.syncYourPosts item["yourPosts.#{g.BOARD}"]
|
|
||||||
$.sync "yourPosts.#{g.BOARD}", QR.syncYourPosts
|
|
||||||
|
|
||||||
error: (err) ->
|
error: (err) ->
|
||||||
QR.open()
|
QR.open()
|
||||||
if typeof err is 'string'
|
if typeof err is 'string'
|
||||||
@ -984,8 +975,11 @@ QR =
|
|||||||
threadID = +threadID or postID
|
threadID = +threadID or postID
|
||||||
isReply = threadID isnt postID
|
isReply = threadID isnt postID
|
||||||
|
|
||||||
(QR.yourPosts.threads[threadID] or= []).push postID
|
QR.db.set
|
||||||
$.set "yourPosts.#{g.BOARD}", QR.yourPosts
|
boardID: g.BOARD.ID
|
||||||
|
threadID: threadID
|
||||||
|
postID: postID
|
||||||
|
val: true
|
||||||
|
|
||||||
# Post/upload confirmed as successful.
|
# Post/upload confirmed as successful.
|
||||||
$.event 'QRPostSuccessful', {
|
$.event 'QRPostSuccessful', {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user