Finish Thread Watcher rewrite. #99

This commit is contained in:
Mayhem 2013-06-11 14:21:01 +02:00
parent 940202150a
commit a1d238b233
4 changed files with 169 additions and 116 deletions

View File

@ -1,10 +1,11 @@
- **Thread Watcher** rewrite: - **Thread Watcher** improvements:
- It is now possible to open all watched threads via the `Open all threads` button in the Thread Watcher's menu. - It is now possible to open all watched threads via the `Open all threads` button in the Thread Watcher's menu.
- Added the `Current Board` setting to switch between showing watched threads from the current board or all boards, disabled by default. - Added the `Current Board` setting to switch between showing watched threads from the current board or all boards, disabled by default.
- About dead (404'd) threads: - About dead (404'd) threads:
- Dead threads will be typographically indicated with a strikethrough. - Dead threads will be typographically indicated with a strikethrough.
- Dead threads will directly link to the corresponding archive when possible. - Dead threads will directly link to the corresponding archive when available.
- A button to prune all 404'd threads from the list is now available. - A button to prune all 404'd threads from the list is now available.
- Added the `Auto Prune` setting to automatically prune 404'd threads, disabled by default.
- The current thread is now highlighted in the list of watched threads. - The current thread is now highlighted in the list of watched threads.
- Watching the current thread can be done in the Header's menu too. - Watching the current thread can be done in the Header's menu too.
- Removed the `Check for Updates` setting: - Removed the `Check for Updates` setting:

View File

@ -80,6 +80,7 @@ Config =
'Current Board': [false, 'Only show watched threads from the current board.'] 'Current Board': [false, 'Only show watched threads from the current board.']
'Auto Watch': [true, 'Automatically watch threads you start.'] 'Auto Watch': [true, 'Automatically watch threads you start.']
'Auto Watch Reply': [false, 'Automatically watch threads you reply to.'] 'Auto Watch Reply': [false, 'Automatically watch threads you reply to.']
'Auto Prune': [false, 'Automatically prune 404\'d threads.']
filter: filter:
name: """ name: """
# Filter any namefags: # Filter any namefags:

View File

@ -112,6 +112,7 @@ Main =
initFeature 'Thread Stats', ThreadStats initFeature 'Thread Stats', ThreadStats
initFeature 'Thread Updater', ThreadUpdater initFeature 'Thread Updater', ThreadUpdater
initFeature 'Thread Watcher', ThreadWatcher initFeature 'Thread Watcher', ThreadWatcher
initFeature 'Thread Watcher (Menu)', ThreadWatcher.menu
initFeature 'Index Navigation', Nav initFeature 'Index Navigation', Nav
initFeature 'Keybinds', Keybinds initFeature 'Keybinds', Keybinds
initFeature 'Show Dice Roll', Dice initFeature 'Show Dice Roll', Dice

View File

@ -8,14 +8,16 @@ ThreadWatcher =
""" """
@list = @dialog.lastElementChild @list = @dialog.lastElementChild
@menuInit()
@addHeaderMenuEntry()
$.on $('.menu-button', @dialog), 'click', @cb.menuToggle
$.on d, 'QRPostSuccessful', @cb.post $.on d, 'QRPostSuccessful', @cb.post
$.on d, 'ThreadUpdate', @cb.threadUpdate if g.VIEW is 'thread' $.on d, 'ThreadUpdate', @cb.threadUpdate if g.VIEW is 'thread'
$.on d, '4chanXInitFinished', @ready $.on d, '4chanXInitFinished', @ready
now = Date.now()
if (@db.data.lastChecked or 0) < now - 2 * $.HOUR
@db.data.lastChecked = now
ThreadWatcher.fetchAllStatus()
@db.save()
# XXX tmp conversion from old to new format # XXX tmp conversion from old to new format
$.get 'WatchedThreads', null, ({WatchedThreads}) -> $.get 'WatchedThreads', null, ({WatchedThreads}) ->
return unless WatchedThreads return unless WatchedThreads
@ -27,97 +29,36 @@ ThreadWatcher =
Thread::callbacks.push Thread::callbacks.push
name: 'Thread Watcher' name: 'Thread Watcher'
cb: @node cb: @node
node: -> node: ->
toggler = $.el 'img', toggler = $.el 'img',
className: 'watcher-toggler' className: 'watcher-toggler'
$.on toggler, 'click', ThreadWatcher.cb.toggle $.on toggler, 'click', ThreadWatcher.cb.toggle
$.before $('input', @OP.nodes.post), toggler $.before $('input', @OP.nodes.post), toggler
return if g.VIEW isnt 'thread' or !Conf['Auto Watch']
$.get 'AutoWatch', 0, ({AutoWatch}) =>
return if AutoWatch isnt @ID
ThreadWatcher.add @
$.delete 'AutoWatch'
menuInit: ->
ThreadWatcher.menu = new UI.Menu 'thread watcher'
# `Open all` entry
entry =
type: 'thread watcher'
el: $.el 'a',
textContent: 'Open all threads'
href: 'javascript:;'
open: ->
(if ThreadWatcher.list.firstElementChild then $.rmClass else $.addClass) @el, 'disabled'
true
$.event 'AddMenuEntry', entry
$.on entry.el, 'click', ThreadWatcher.cb.openAll
# `Prune 404'd threads` entry
entry =
type: 'thread watcher'
el: $.el 'a',
textContent: 'Prune 404\'d threads'
href: 'javascript:;'
open: ->
(if $('.dead-thread', ThreadWatcher.list) then $.rmClass else $.addClass) @el, 'disabled'
true
$.event 'AddMenuEntry', entry
$.on entry.el, 'click', ThreadWatcher.cb.pruneDeads
# `Settings` entries:
subEntries = []
for name, conf of Config.threadWatcher
subEntries.push ThreadWatcher.createSubEntry name, conf[1]
$.event 'AddMenuEntry',
type: 'thread watcher'
el: $.el 'span', textContent: 'Settings'
subEntries: subEntries
createSubEntry: (name, desc) ->
entry =
type: 'thread watcher'
el: $.el 'label',
innerHTML: "<input type=checkbox name='#{name}'> #{name}"
title: desc
input = entry.el.firstElementChild
input.checked = Conf[name]
$.on input, 'change', $.cb.checked
$.on input, 'change', ThreadWatcher.refresh if name is 'Current Board'
entry
addHeaderMenuEntry: ->
return if g.VIEW isnt 'thread'
ThreadWatcher.entryEl = $.el 'a', href: 'javascript:;'
entry =
type: 'header'
el: ThreadWatcher.entryEl
order: 60
$.event 'AddMenuEntry', entry
$.on entry.el, 'click', -> ThreadWatcher.toggle g.threads["#{g.BOARD}.#{g.THREADID}"]
ready: -> ready: ->
$.off d, '4chanXInitFinished', ThreadWatcher.ready $.off d, '4chanXInitFinished', ThreadWatcher.ready
return unless Main.isThisPageLegit() return unless Main.isThisPageLegit()
ThreadWatcher.refresh() ThreadWatcher.refresh()
$.add d.body, ThreadWatcher.dialog $.add d.body, ThreadWatcher.dialog
return unless Conf['Auto Watch']
$.get 'AutoWatch', 0, ({AutoWatch}) ->
return unless thread = g.BOARD.threads[AutoWatch]
ThreadWatcher.add thread
$.delete 'AutoWatch'
cb: cb:
menuToggle: (e) ->
ThreadWatcher.menu.toggle e, @, ThreadWatcher
openAll: -> openAll: ->
return if $.hasClass @, 'disabled' return if $.hasClass @, 'disabled'
for a in $$ 'a[title]', ThreadWatcher.list for a in $$ 'a[title]', ThreadWatcher.list
$.open a.href $.open a.href
$.event 'CloseMenu' $.event 'CloseMenu'
checkThreads: ->
return if $.hasClass @, 'disabled'
ThreadWatcher.fetchAllStatus()
pruneDeads: -> pruneDeads: ->
return if $.hasClass @, 'disabled' return if $.hasClass @, 'disabled'
for boardID, threads of ThreadWatcher.db.data.boards for {boardID, threadID, data} in ThreadWatcher.getAll() when data.isDead
if Conf['Current Board'] and boardID isnt g.BOARD.ID delete ThreadWatcher.db.data.boards[boardID][threadID]
continue
for threadID, data of threads
continue unless data.isDead
delete threads[threadID]
ThreadWatcher.db.deleteIfEmpty {boardID} ThreadWatcher.db.deleteIfEmpty {boardID}
ThreadWatcher.db.save() ThreadWatcher.db.save()
ThreadWatcher.refresh() ThreadWatcher.refresh()
@ -125,7 +66,7 @@ ThreadWatcher =
toggle: -> toggle: ->
ThreadWatcher.toggle Get.postFromNode(@).thread ThreadWatcher.toggle Get.postFromNode(@).thread
rm: -> rm: ->
[boardID, threadID] = @parentNode.dataset.fullid.split '.' [boardID, threadID] = @parentNode.dataset.fullID.split '.'
ThreadWatcher.rm boardID, +threadID ThreadWatcher.rm boardID, +threadID
post: (e) -> post: (e) ->
{board, postID, threadID} = e.detail {board, postID, threadID} = e.detail
@ -135,57 +76,77 @@ ThreadWatcher =
else if Conf['Auto Watch Reply'] else if Conf['Auto Watch Reply']
ThreadWatcher.add board.threads[threadID] ThreadWatcher.add board.threads[threadID]
threadUpdate: (e) -> threadUpdate: (e) ->
# Update 404 status.
return unless e.detail[404]
{thread} = e.detail {thread} = e.detail
return unless ThreadWatcher.db.get {boardID: thread.board.ID, threadID: thread.ID} return unless e.detail[404] and ThreadWatcher.db.get {boardID: thread.board.ID, threadID: thread.ID}
# Update 404 status.
ThreadWatcher.add thread ThreadWatcher.add thread
refresh: -> fetchAllStatus: ->
nodes = [] # XXX need visual feedback
for thread in ThreadWatcher.getAll()
ThreadWatcher.fetchStatus thread
return
fetchStatus: ({boardID, threadID, data}) ->
return if data.isDead
$.ajax "//api.4chan.org/#{boardID}/res/#{threadID}.json",
onload: ->
return if @status isnt 404
if Conf['Auto Prune']
ThreadWatcher.rm boardID, threadID
else
data.isDead = true
ThreadWatcher.db.set {boardID, threadID, val: data}
ThreadWatcher.refresh()
,
type: 'head'
getAll: ->
all = []
for boardID, threads of ThreadWatcher.db.data.boards for boardID, threads of ThreadWatcher.db.data.boards
if Conf['Current Board'] and boardID isnt g.BOARD.ID if Conf['Current Board'] and boardID isnt g.BOARD.ID
continue continue
for threadID, data of threads for threadID, data of threads
x = $.el 'a', all.push {boardID, threadID, data}
textContent: '×' all
href: 'javascript:;'
$.on x, 'click', ThreadWatcher.cb.rm
if data.isDead makeLine: (boardID, threadID, data) ->
href = Redirect.to 'thread', {boardID, threadID} x = $.el 'a',
link = $.el 'a', textContent: '×'
href: href or "/#{boardID}/res/#{threadID}" href: 'javascript:;'
textContent: data.excerpt $.on x, 'click', ThreadWatcher.cb.rm
title: data.excerpt
nodes.push div = $.el 'div' if data.isDead
div.setAttribute 'data-fullid', "#{boardID}.#{threadID}" href = Redirect.to 'thread', {boardID, threadID}
$.addClass div, 'dead-thread' if data.isDead link = $.el 'a',
$.add div, [x, $.tn(' '), link] href: href or "/#{boardID}/res/#{threadID}"
textContent: data.excerpt
title: data.excerpt
list = ThreadWatcher.dialog.lastElementChild div = $.el 'div'
fullID = "#{boardID}.#{threadID}"
div.dataset.fullID = fullID
$.addClass div, 'current' if g.VIEW is 'thread' and fullID is "#{g.BOARD}.#{g.THREADID}"
$.addClass div, 'dead-thread' if data.isDead
$.add div, [x, $.tn(' '), link]
div
refresh: ->
nodes = []
for {boardID, threadID, data} in ThreadWatcher.getAll()
nodes.push ThreadWatcher.makeLine boardID, threadID, data
{list} = ThreadWatcher
$.rmAll list $.rmAll list
$.add list, nodes $.add list, nodes
if g.VIEW is 'thread'
{entryEl} = ThreadWatcher
if div = $ "div[data-fullid='#{g.BOARD}.#{g.THREADID}']", list
$.addClass div, 'current'
$.addClass entryEl, 'unwatch-thread'
$.rmClass entryEl, 'watch-thread'
entryEl.textContent = 'Unwatch thread'
else
$.addClass entryEl, 'watch-thread'
$.rmClass entryEl, 'unwatch-thread'
entryEl.textContent = 'Watch thread'
for threadID, thread of g.BOARD.threads for threadID, thread of g.BOARD.threads
toggler = $ '.watcher-toggler', thread.OP.nodes.post toggler = $ '.watcher-toggler', thread.OP.nodes.post
toggler.src = if ThreadWatcher.db.get {boardID: thread.board.ID, threadID} toggler.src = if ThreadWatcher.db.get {boardID: thread.board.ID, threadID}
Favicon.default Favicon.default
else else
Favicon.empty Favicon.empty
for refresher in ThreadWatcher.menu.refreshers
refresher()
return return
toggle: (thread) -> toggle: (thread) ->
@ -196,13 +157,16 @@ ThreadWatcher =
else else
ThreadWatcher.add thread ThreadWatcher.add thread
add: (thread) -> add: (thread) ->
data = excerpt: Get.threadExcerpt thread data = {}
boardID = thread.board.ID
threadID = thread.ID
if thread.isDead if thread.isDead
if Conf['Auto Prune'] and ThreadWatcher.db.get {boardID, threadID}
ThreadWatcher.rm boardID, threadID
return
data.isDead = true data.isDead = true
ThreadWatcher.db.set data.excerpt = Get.threadExcerpt thread
boardID: thread.board.ID ThreadWatcher.db.set {boardID, threadID, val: data}
threadID: thread.ID
val: data
ThreadWatcher.refresh() ThreadWatcher.refresh()
rm: (boardID, threadID) -> rm: (boardID, threadID) ->
ThreadWatcher.db.delete {boardID, threadID} ThreadWatcher.db.delete {boardID, threadID}
@ -214,3 +178,89 @@ ThreadWatcher =
for threadID, data of threads for threadID, data of threads
(newFormat[boardID] or= {})[threadID] = excerpt: data.textContent (newFormat[boardID] or= {})[threadID] = excerpt: data.textContent
newFormat newFormat
menu:
refreshers: []
init: ->
return if !Conf['Thread Watcher']
menu = new UI.Menu 'thread watcher'
$.on $('.menu-button', ThreadWatcher.dialog), 'click', (e) ->
menu.toggle e, @, ThreadWatcher
@addHeaderMenuEntry()
@addMenuEntries()
addHeaderMenuEntry: ->
return if g.VIEW isnt 'thread'
entryEl = $.el 'a',
href: 'javascript:;'
$.event 'AddMenuEntry',
type: 'header'
el: entryEl
order: 60
$.on entryEl, 'click', -> ThreadWatcher.toggle g.threads["#{g.BOARD}.#{g.THREADID}"]
@refreshers.push ->
[addClass, rmClass, text] = if $ '.current', ThreadWatcher.list
['unwatch-thread', 'watch-thread', 'Unwatch thread']
else
['watch-thread', 'unwatch-thread', 'Watch thread']
$.addClass entryEl, addClass
$.rmClass entryEl, rmClass
entryEl.textContent = text
addMenuEntries: ->
entries = []
# `Open all` entry
entries.push
cb: ThreadWatcher.cb.openAll
entry:
type: 'thread watcher'
el: $.el 'a',
textContent: 'Open all threads'
refresh: -> (if ThreadWatcher.list.firstElementChild then $.rmClass else $.addClass) @el, 'disabled'
# `Check 404'd threads` entry
entries.push
cb: ThreadWatcher.cb.checkThreads
entry:
type: 'thread watcher'
el: $.el 'a',
textContent: 'Check 404\'d threads'
refresh: -> (if $('div:not(.dead-thread)', ThreadWatcher.list) then $.rmClass else $.addClass) @el, 'disabled'
# `Prune 404'd threads` entry
entries.push
cb: ThreadWatcher.cb.pruneDeads
entry:
type: 'thread watcher'
el: $.el 'a',
textContent: 'Prune 404\'d threads'
refresh: -> (if $('.dead-thread', ThreadWatcher.list) then $.rmClass else $.addClass) @el, 'disabled'
# `Settings` entries:
subEntries = []
for name, conf of Config.threadWatcher
subEntries.push @createSubEntry name, conf[1]
entries.push
entry:
type: 'thread watcher'
el: $.el 'span',
textContent: 'Settings'
subEntries: subEntries
for {entry, cb, refresh} in entries
entry.el.href = 'javascript:;' if entry.el.nodeName is 'A'
$.on entry.el, 'click', cb if cb
@refreshers.push refresh.bind entry if refresh
$.event 'AddMenuEntry', entry
createSubEntry: (name, desc) ->
entry =
type: 'thread watcher'
el: $.el 'label',
innerHTML: "<input type=checkbox name='#{name}'> #{name}"
title: desc
input = entry.el.firstElementChild
input.checked = Conf[name]
$.on input, 'change', $.cb.checked
$.on input, 'change', ThreadWatcher.refresh if name is 'Current Board'
entry