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:
Nicolas Stepien 2013-04-01 01:42:57 +02:00
parent 435c411a36
commit 203bbf091d
5 changed files with 247 additions and 198 deletions

View File

@ -17,6 +17,7 @@ module.exports = function(grunt) {
'src/features.coffee',
'src/qr.coffee',
'src/report.coffee',
'src/databoard.coffee',
'src/main.coffee'
],
dest: 'tmp/script.coffee'

80
src/databoard.coffee Normal file
View 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?()

View File

@ -347,18 +347,20 @@ Settings =
innerHTML: "<button></button><span class=description>: Clear manually hidden threads and posts on /#{g.BOARD}/."
button = $ 'button', div
hiddenNum = 0
ThreadHiding.getHiddenThreads (hiddenThreads) ->
for ID, thread of hiddenThreads.threads
hiddenNum++
button.textContent = "Hidden: #{hiddenNum}"
ReplyHiding.getHiddenPosts (hiddenPosts) ->
for ID, thread of hiddenPosts.threads
for ID, post of thread
$.get 'hiddenThreads', boards: {}, (item) ->
for ID, board of item.hiddenThreads.boards
for ID, thread of board
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', ->
@textContent = 'Hidden: 0'
$.delete ["hiddenThreads.#{g.BOARD}", "hiddenPosts.#{g.BOARD}"]
$.delete ['hiddenThreads', 'hiddenPosts']
$.after $('input[name="Stubs"]', section).parentNode.parentNode, div
export: (now, data) ->
unless typeof now is 'number'
@ -367,9 +369,14 @@ Settings =
version: g.VERSION
date: now
Conf: Conf
$.get 'WatchedThreads', {}, (item) ->
data.WatchedThreads = item.WatchedThreads
items = WatchedThreads: {}
for db in DataBoards
items[db] = boards: {}
$.get items (items) ->
for key, val in items
data[key] = val
Settings.export now, data
return
a = $.el 'a',
className: 'warning'
textContent: 'Save me!'
@ -472,6 +479,8 @@ Settings =
"Shift+#{s[0...-1]}#{s[-1..].toLowerCase()}"
for key, val of data.Conf
$.set key, val
for db in DataBoards
$.set db, data[db]
$.set 'WatchedThreads', data.WatchedThreads
convertSettings: (data, map) ->
for prevKey, newKey of map
@ -1060,43 +1069,38 @@ ThreadHiding =
init: ->
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()
Thread::callbacks.push
name: 'Thread Hiding'
cb: @node
node: ->
if data = ThreadHiding.hiddenThreads.threads[@]
if data = ThreadHiding.db.get {boardID: @board.ID, threadID: @ID}
ThreadHiding.hide @, data.makeStub
return unless Conf['Thread Hiding']
$.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: ->
# Sync hidden threads from the catalog into the index.
ThreadHiding.getHiddenThreads (hiddenThreads) ->
{threads} = hiddenThreads
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {}
hiddenThreads = ThreadHiding.db.get
boardID: g.BOARD.ID
defaultValue: {}
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {}
# Add threads that were hidden in the catalog.
for threadID of hiddenThreadsOnCatalog
continue if threadID of threads
threads[threadID] = {}
# Add threads that were hidden in the catalog.
for threadID of hiddenThreadsOnCatalog
unless threadID of hiddenThreads
hiddenThreads[threadID] = {}
# Remove threads that were un-hidden in the catalog.
for threadID of threads
continue if threadID of threads
delete threads[threadID]
# Remove threads that were un-hidden in the catalog.
for threadID of hiddenThreads
unless threadID of hiddenThreadsOnCatalog
delete hiddenThreads[threadID]
if Object.keys(threads).length
$.set "hiddenThreads.#{g.BOARD}", ThreadHiding.hiddenThreads
else
$.delete "hiddenThreads.#{g.BOARD}"
ThreadHiding.db.set
boardID: g.BOARD.ID
val: hiddenThreads
menu:
init: ->
@ -1141,17 +1145,19 @@ ThreadHiding =
a
saveHiddenState: (thread, makeStub) ->
# Get fresh hidden threads.
ThreadHiding.getHiddenThreads (hiddenThreads) ->
hiddenThreadsCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {}
if thread.isHidden
hiddenThreads.threads[thread] = {makeStub}
hiddenThreadsCatalog[thread] = true
else
delete hiddenThreads.threads[thread]
delete hiddenThreadsCatalog[thread]
$.set "hiddenThreads.#{g.BOARD}", hiddenThreads
localStorage.setItem "4chan-hide-t-#{g.BOARD}", JSON.stringify hiddenThreadsCatalog
hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {}
if thread.isHidden
ThreadHiding.db.set
boardID: thread.board.ID
threadID: thread.ID
val: {makeStub}
hiddenThreadsOnCatalog[thread] = true
else
ThreadHiding.db.delete
boardID: thread.board.ID
threadID: thread.ID
delete hiddenThreadsOnCatalog[thread]
localStorage.setItem "4chan-hide-t-#{g.BOARD}", JSON.stringify hiddenThreadsOnCatalog
toggle: (thread) ->
unless thread instanceof Thread
@ -1204,29 +1210,22 @@ ReplyHiding =
init: ->
return if g.VIEW is 'catalog' or !Conf['Reply Hiding'] and !Conf['Reply Hiding Link']
Misc.clearThreads "hiddenPosts.#{g.BOARD}"
@getHiddenPosts()
@db = new DataBoard 'hiddenPosts'
Post::callbacks.push
name: 'Reply Hiding'
cb: @node
node: ->
return if !@isReply or @isClone
if thread = ReplyHiding.hiddenPosts.threads[@thread]
if data = thread[@]
if data.thisPost
ReplyHiding.hide @, data.makeStub, data.hideRecursively
else
Recursive.apply ReplyHiding.hide, @, data.makeStub, true
Recursive.add ReplyHiding.hide, @, data.makeStub, true
if data = ReplyHiding.db.get {boardID: @board.ID, threadID: @thread.ID, postID: @ID}
if data.thisPost
ReplyHiding.hide @, data.makeStub, data.hideRecursively
else
Recursive.apply ReplyHiding.hide, @, data.makeStub, true
Recursive.add ReplyHiding.hide, @, data.makeStub, true
return unless Conf['Reply Hiding']
$.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:
init: ->
return if g.VIEW is 'catalog' or !Conf['Menu'] or !Conf['Reply Hiding Link']
@ -1279,10 +1278,9 @@ ReplyHiding =
el: div
order: 20
open: (post) ->
if !post.isReply or post.isClone
if !post.isReply or post.isClone or !post.isHidden
return false
thread = ReplyHiding.hiddenPosts.threads[post.thread]
unless post.isHidden or data = thread?[post]
unless data = ReplyHiding.db.get {boardID: post.board.ID, threadID: post.thread.ID, postID: post.ID}
return false
ReplyHiding.menu.post = post
thisPost.firstChild.checked = post.isHidden
@ -1309,8 +1307,6 @@ ReplyHiding =
thisPost = $('input[name=thisPost]', parent).checked
replies = $('input[name=replies]', parent).checked
{post} = ReplyHiding.menu
thread = ReplyHiding.hiddenPosts.threads[post.thread]
data = thread?[post]
if thisPost
ReplyHiding.show post, replies
else if replies
@ -1318,7 +1314,7 @@ ReplyHiding =
Recursive.rm ReplyHiding.hide, post, true
else
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
$.event 'CloseMenu'
@ -1331,21 +1327,18 @@ ReplyHiding =
a
saveHiddenState: (post, isHiding, thisPost, makeStub, hideRecursively) ->
# Get fresh hidden posts.
ReplyHiding.getHiddenPosts (hiddenPosts) ->
if isHiding
unless thread = hiddenPosts.threads[post.thread]
thread = hiddenPosts.threads[post.thread] = {}
thread[post] =
thisPost: thisPost isnt false # undefined -> true
makeStub: makeStub
hideRecursively: hideRecursively
else
thread = hiddenPosts.threads[post.thread]
delete thread[post]
unless Object.keys(thread).length
delete hiddenPosts.threads[post.thread]
$.set "hiddenPosts.#{g.BOARD}", hiddenPosts
data =
boardID: post.board.ID
threadID: post.thread.ID
postID: post.ID
if isHiding
data.val =
thisPost: thisPost isnt false # undefined -> true
makeStub: makeStub
hideRecursively: hideRecursively
ReplyHiding.db.set data
else
ReplyHiding.db.delete data
toggle: ->
post = Get.postFromNode @
@ -1448,8 +1441,8 @@ QuoteStrikeThrough =
node: ->
return if @isClone
for quotelink in @nodes.quotelinks
{board, postID} = Get.postDataFromLink quotelink
if g.posts["#{board}.#{postID}"]?.isHidden
{boardID, postID} = Get.postDataFromLink quotelink
if g.posts["#{boardID}.#{postID}"]?.isHidden
$.addClass quotelink, 'filtered'
return
@ -1597,7 +1590,7 @@ DeleteLink =
cooldown:
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.
delete DeleteLink.cooldown.counting
return
@ -2304,15 +2297,15 @@ Get =
postDataFromLink: (link) ->
if link.hostname is 'boards.4chan.org'
path = link.pathname.split '/'
board = path[1]
boardID = path[1]
threadID = path[3]
postID = link.hash[2..]
else # resurrected quote
board = link.dataset.board
boardID = link.dataset.board
threadID = link.dataset.threadid or 0
postID = link.dataset.postid
return {
board: board
boardID: boardID
threadID: +threadID
postID: +postID
}
@ -2340,8 +2333,8 @@ Get =
# Third:
# Filter out irrelevant quotelinks.
quotelinks.filter (quotelink) ->
{board, postID} = Get.postDataFromLink quotelink
board is post.board.ID and postID is post.ID
{boardID, postID} = Get.postDataFromLink quotelink
boardID is post.board.ID and postID is post.ID
postClone: (board, threadID, postID, root, context) ->
if post = g.posts["#{board}.#{postID}"]
Get.insert post, root, context
@ -2510,34 +2503,6 @@ Get =
Main.callbackNodes Post, [post]
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 =
init: ->
return if g.VIEW is 'catalog' or !Conf['Resurrect Quotes']
@ -2623,13 +2588,13 @@ QuoteInline =
toggle: (e) ->
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
e.preventDefault()
{board, threadID, postID} = Get.postDataFromLink @
{boardID, threadID, postID} = Get.postDataFromLink @
context = Get.contextFromLink @
if $.hasClass @, 'inlined'
QuoteInline.rm @, board, threadID, postID, context
QuoteInline.rm @, boardID, threadID, postID, context
else
return if $.x "ancestor::div[@id='p#{postID}']", @
QuoteInline.add @, board, threadID, postID, context
QuoteInline.add @, boardID, threadID, postID, context
@classList.toggle 'inlined'
findRoot: (quotelink, isBacklink) ->
@ -2637,15 +2602,15 @@ QuoteInline =
quotelink.parentNode.parentNode
else
$.x 'ancestor-or-self::*[parent::blockquote][1]', quotelink
add: (quotelink, board, threadID, postID, context) ->
add: (quotelink, boardID, threadID, postID, context) ->
isBacklink = $.hasClass quotelink, 'backlink'
inline = $.el 'div',
id: "i#{postID}"
className: '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
# 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.update()
rm: (quotelink, board, threadID, postID, context) ->
rm: (quotelink, boardID, threadID, postID, context) ->
isBacklink = $.hasClass quotelink, 'backlink'
# Select the corresponding inlined quote, and remove it.
root = QuoteInline.findRoot quotelink, isBacklink
@ -2670,21 +2635,21 @@ QuoteInline =
return unless el = root.firstElementChild
# Dereference clone.
post = g.posts["#{board}.#{postID}"]
post = g.posts["#{boardID}.#{postID}"]
post.rmClone el.dataset.clone
# Decrease forward count and unhide.
if Conf['Forward Hiding'] and
isBacklink and
context.thread is g.threads["#{board}.#{threadID}"] and
context.thread is g.threads["#{boardID}.#{threadID}"] and
not --post.forwarded
delete post.forwarded
$.rmClass post.nodes.root, 'forwarded'
# Repeat.
while inlined = $ '.inlined', el
{board, threadID, postID} = Get.postDataFromLink inlined
QuoteInline.rm inlined, board, threadID, postID, context
{boardID, threadID, postID} = Get.postDataFromLink inlined
QuoteInline.rm inlined, boardID, threadID, postID, context
$.rmClass inlined, 'inlined'
return
@ -2704,13 +2669,13 @@ QuotePreview =
mouseover: (e) ->
return if $.hasClass @, 'inlined'
{board, threadID, postID} = Get.postDataFromLink @
{boardID, threadID, postID} = Get.postDataFromLink @
qp = $.el 'div',
id: 'qp'
className: 'dialog'
$.add d.body, qp
Get.postClone board, threadID, postID, qp, Get.contextFromLink @
Get.postClone boardID, threadID, postID, qp, Get.contextFromLink @
UI.hover
root: @
@ -2720,7 +2685,7 @@ QuotePreview =
cb: QuotePreview.mouseout
asapTest: -> qp.firstElementChild
return unless origin = g.posts["#{board}.#{postID}"]
return unless origin = g.posts["#{boardID}.#{postID}"]
if Conf['Quote Highlighting']
posts = [origin].concat origin.clones
@ -2823,11 +2788,12 @@ QuoteYou =
return if @isClone
# Stop there if there's no quotes in that post.
return unless (quotes = @quotes).length
{db} = QR
return unless db
{quotelinks} = @nodes
for quotelink in quotelinks
{threadID, postID} = Get.postDataFromLink quotelink
if (thread = QR.yourPosts.threads[threadID]) and postID in thread
if db.get Get.postDataFromLink quotelink
$.add quotelink, $.tn QuoteYou.text
return
@ -2856,8 +2822,8 @@ QuoteOP =
# add (OP) to quotes quoting this context's OP.
return unless op in quotes
for quotelink in quotelinks
{board, postID} = Get.postDataFromLink quotelink
if "#{board}.#{postID}" is op
{boardID, postID} = Get.postDataFromLink quotelink
if "#{boardID}.#{postID}" is op
$.add quotelink, $.tn QuoteOP.text
return
@ -2879,11 +2845,11 @@ QuoteCT =
{board, thread} = if @isClone then @context else @
for quotelink in quotelinks
data = Get.postDataFromLink quotelink
continue unless data.threadID # deadlink
{boardID, threadID} = Get.postDataFromLink quotelink
continue unless threadID # deadlink
if @isClone
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
return
@ -3612,13 +3578,11 @@ Unread =
init: ->
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'
Misc.clearThreads "lastReadPosts.#{g.BOARD}"
$.sync "lastReadPosts.#{g.BOARD}", @sync
Unread.posts = []
Unread.postsQuotingYou = []
@posts = []
@postsQuotingYou = []
Thread::callbacks.push
name: 'Unread'
@ -3630,23 +3594,29 @@ Unread =
posts = []
for ID, post of @posts
posts.push post if post.isReply
$.get "lastReadPosts.#{@board}", threads: {}, (item) =>
Unread.lastReadPost = item["lastReadPosts.#{@board}"].threads[@] or 0
Unread.addPosts posts
if (hash = location.hash.match /\d+/) and post = @posts[hash[0]]
post.nodes.root.scrollIntoView()
else if Unread.posts.length
# Scroll to before the first unread post.
$.x('preceding-sibling::div[contains(@class,"postContainer")][1]', Unread.posts[0].nodes.root).scrollIntoView false
else if posts.length
# Scroll to the last read post.
posts[posts.length - 1].nodes.root.scrollIntoView()
Unread.lastReadPost = Unread.db.get
boardID: @board.ID
threadID: @ID
defaultValue: 0
Unread.addPosts posts
if (hash = location.hash.match /\d+/) and post = @posts[hash[0]]
post.nodes.root.scrollIntoView()
else if Unread.posts.length
# Scroll to before the first unread post.
$.x('preceding-sibling::div[contains(@class,"postContainer")][1]', Unread.posts[0].nodes.root).scrollIntoView false
else if posts.length
# Scroll to the last read post.
posts[posts.length - 1].nodes.root.scrollIntoView()
$.on d, 'ThreadUpdate', Unread.onUpdate
$.on d, 'scroll visibilitychange', Unread.read
$.on d, 'visibilitychange', Unread.setLine if Conf['Unread Line']
sync: (lastReadPosts) ->
return unless (lastReadPost = lastReadPosts?.threads?[Unread.thread]) and Unread.lastReadPost < lastReadPost
sync: ->
lastReadPost = Unread.db.get
boardID: Unread.thread.board.ID
threadID: Unread.thread.ID
defaultValue: 0
return unless Unread.lastReadPost < lastReadPost
Unread.lastReadPost = lastReadPost
Unread.readArray Unread.posts
Unread.readArray Unread.postsQuotingYou
@ -3654,29 +3624,30 @@ Unread =
Unread.update()
addPosts: (newPosts) ->
if Conf['Quick Reply']
{yourPosts} = QR
youInThisThread = yourPosts.threads[Unread.thread]
for post in newPosts
{ID} = post
if ID <= Unread.lastReadPost or post.isHidden or youInThisThread and ID in youInThisThread
if ID <= Unread.lastReadPost or post.isHidden
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.addPostQuotingYou post, yourPosts if yourPosts
Unread.addPostQuotingYou post
if Conf['Unread Line']
# Force line on visible threads if there were no unread posts previously.
Unread.setLine Unread.posts[0] in newPosts
Unread.read()
Unread.update()
addPostQuotingYou: (post, yourPosts) ->
for quote in post.quotes
[board, quoteID] = quote.split '.'
continue unless board is Unread.thread.board.ID
for thread, postIDs of yourPosts.threads
if +quoteID in postIDs
Unread.postsQuotingYou.push post
return
addPostQuotingYou: (post) ->
return unless QR.db
for quotelink in post.nodes.quotelinks
if QR.db.get Get.postDataFromLink quotelink
Unread.postsQuotingYou.push post
return
onUpdate: (e) ->
if e.detail[404]
@ -3704,10 +3675,10 @@ Unread =
Unread.update() if e
saveLastReadPost: $.debounce 2 * $.SECOND, ->
$.get "lastReadPosts.#{Unread.thread.board}", threads: {}, (item) ->
lastReadPosts = item["lastReadPosts.#{Unread.thread.board}"]
lastReadPosts.threads[Unread.thread] = Unread.lastReadPost
$.set "lastReadPosts.#{Unread.thread.board}", lastReadPosts
Unread.db.set
boardID: Unread.thread.board.ID
threadID: Unread.thread.ID
val: Unread.lastReadPost
setLine: (force) ->
return unless d.hidden or force is true

View File

@ -282,22 +282,25 @@ class Clone extends Post
Main =
init: (items) ->
unless items
# flatten Config into Conf
# and get saved or default values
flatten = (parent, obj) ->
if obj instanceof Array
Conf[parent] = obj[0]
else if typeof obj is 'object'
for key, val of obj
flatten key, val
else # string or number
Conf[parent] = obj
return
flatten null, Config
$.get Conf, Main.init
# flatten Config into Conf
# and get saved or default values
flatten = (parent, obj) ->
if obj instanceof Array
Conf[parent] = obj[0]
else if typeof obj is 'object'
for key, val of obj
flatten key, val
else # string or number
Conf[parent] = obj
return
flatten null, Config
for db in DataBoards
Conf[db] = boards: {}
$.get Conf, Main.initFeatures
$.on d, '4chanMainInit', Main.initStyle
initFeatures: (items) ->
Conf = items
pathname = location.pathname.split '/'
@ -385,10 +388,10 @@ Main =
# c.timeEnd 'All initializations'
$.on d, 'AddCallback', Main.addCallback
$.on d, '4chanMainInit', Main.initStyle
$.ready Main.initReady
initStyle: ->
$.off d, '4chanMainInit', Main.initStyle
return unless Main.isThisPageLegit()
# disable the mobile layout
$('link[href*=mobile]', d.head)?.disabled = true

View File

@ -2,8 +2,7 @@ QR =
init: ->
return if g.VIEW is 'catalog' or !Conf['Quick Reply']
Misc.clearThreads "yourPosts.#{g.BOARD}"
@syncYourPosts()
@db = new DataBoard 'yourPosts'
if Conf['Hide Original Post Form']
$.addClass doc, 'hide-original-post-form'
@ -88,14 +87,6 @@ QR =
else
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) ->
QR.open()
if typeof err is 'string'
@ -984,8 +975,11 @@ QR =
threadID = +threadID or postID
isReply = threadID isnt postID
(QR.yourPosts.threads[threadID] or= []).push postID
$.set "yourPosts.#{g.BOARD}", QR.yourPosts
QR.db.set
boardID: g.BOARD.ID
threadID: threadID
postID: postID
val: true
# Post/upload confirmed as successful.
$.event 'QRPostSuccessful', {