281 lines
8.9 KiB
CoffeeScript
281 lines
8.9 KiB
CoffeeScript
ThreadWatcher =
|
||
init: ->
|
||
return if !Conf['Thread Watcher']
|
||
|
||
@db = new DataBoard 'watchedThreads', @refresh, true
|
||
@dialog = UI.dialog 'thread-watcher', 'top: 50px; left: 0px;', """
|
||
<%= grunt.file.read('html/Monitoring/ThreadWatcher.html').replace(/>\s+</g, '><').trim() %>
|
||
"""
|
||
@status = $ '#watcher-status', @dialog
|
||
@list = @dialog.lastElementChild
|
||
|
||
$.on d, 'QRPostSuccessful', @cb.post
|
||
$.on d, 'ThreadUpdate', @cb.threadUpdate if g.VIEW is 'thread'
|
||
$.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
|
||
$.get 'WatchedThreads', null, ({WatchedThreads}) ->
|
||
return unless WatchedThreads
|
||
for boardID, threads of ThreadWatcher.convert WatchedThreads
|
||
for threadID, data of threads
|
||
ThreadWatcher.db.set {boardID, threadID, val: data}
|
||
$.delete 'WatchedThreads'
|
||
|
||
Thread::callbacks.push
|
||
name: 'Thread Watcher'
|
||
cb: @node
|
||
node: ->
|
||
toggler = $.el 'img',
|
||
className: 'watcher-toggler'
|
||
$.on toggler, 'click', ThreadWatcher.cb.toggle
|
||
$.before $('input', @OP.nodes.post), toggler
|
||
ready: ->
|
||
$.off d, '4chanXInitFinished', ThreadWatcher.ready
|
||
return unless Main.isThisPageLegit()
|
||
ThreadWatcher.refresh()
|
||
$.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:
|
||
openAll: ->
|
||
return if $.hasClass @, 'disabled'
|
||
for a in $$ 'a[title]', ThreadWatcher.list
|
||
$.open a.href
|
||
$.event 'CloseMenu'
|
||
checkThreads: ->
|
||
return if $.hasClass @, 'disabled'
|
||
ThreadWatcher.fetchAllStatus()
|
||
pruneDeads: ->
|
||
return if $.hasClass @, 'disabled'
|
||
for {boardID, threadID, data} in ThreadWatcher.getAll() when data.isDead
|
||
delete ThreadWatcher.db.data.boards[boardID][threadID]
|
||
ThreadWatcher.db.deleteIfEmpty {boardID}
|
||
ThreadWatcher.db.save()
|
||
ThreadWatcher.refresh()
|
||
$.event 'CloseMenu'
|
||
toggle: ->
|
||
ThreadWatcher.toggle Get.postFromNode(@).thread
|
||
rm: ->
|
||
[boardID, threadID] = @parentNode.dataset.fullID.split '.'
|
||
ThreadWatcher.rm boardID, +threadID
|
||
post: (e) ->
|
||
{board, postID, threadID} = e.detail
|
||
if postID is threadID
|
||
if Conf['Auto Watch']
|
||
$.set 'AutoWatch', threadID
|
||
else if Conf['Auto Watch Reply']
|
||
ThreadWatcher.add board.threads[threadID]
|
||
threadUpdate: (e) ->
|
||
{thread} = e.detail
|
||
return unless e.detail[404] and ThreadWatcher.db.get {boardID: thread.board.ID, threadID: thread.ID}
|
||
# Update 404 status.
|
||
ThreadWatcher.add thread
|
||
|
||
fetchCount:
|
||
fetched: 0
|
||
fetching: 0
|
||
fetchAllStatus: ->
|
||
ThreadWatcher.status.textContent = '...'
|
||
for thread in ThreadWatcher.getAll()
|
||
ThreadWatcher.fetchStatus thread
|
||
return
|
||
fetchStatus: ({boardID, threadID, data}) ->
|
||
return if data.isDead
|
||
{fetchCount} = ThreadWatcher
|
||
fetchCount.fetching++
|
||
$.ajax "//api.4chan.org/#{boardID}/res/#{threadID}.json",
|
||
onloadend: ->
|
||
fetchCount.fetched++
|
||
if fetchCount.fetched is fetchCount.fetching
|
||
fetchCount.fetched = 0
|
||
fetchCount.fetching = 0
|
||
status = ''
|
||
else
|
||
status = "#{Math.round fetchCount.fetched / fetchCount.fetching * 100}%"
|
||
ThreadWatcher.status.textContent = status
|
||
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
|
||
if Conf['Current Board'] and boardID isnt g.BOARD.ID
|
||
continue
|
||
for threadID, data of threads
|
||
all.push {boardID, threadID, data}
|
||
all
|
||
|
||
makeLine: (boardID, threadID, data) ->
|
||
x = $.el 'a',
|
||
textContent: '×'
|
||
href: 'javascript:;'
|
||
$.on x, 'click', ThreadWatcher.cb.rm
|
||
|
||
if data.isDead
|
||
href = Redirect.to 'thread', {boardID, threadID}
|
||
link = $.el 'a',
|
||
href: href or "/#{boardID}/res/#{threadID}"
|
||
textContent: data.excerpt
|
||
title: data.excerpt
|
||
|
||
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
|
||
$.add list, nodes
|
||
|
||
for threadID, thread of g.BOARD.threads
|
||
toggler = $ '.watcher-toggler', thread.OP.nodes.post
|
||
toggler.src = if ThreadWatcher.db.get {boardID: thread.board.ID, threadID}
|
||
Favicon.default
|
||
else
|
||
Favicon.empty
|
||
|
||
for refresher in ThreadWatcher.menu.refreshers
|
||
refresher()
|
||
return
|
||
|
||
toggle: (thread) ->
|
||
boardID = thread.board.ID
|
||
threadID = thread.ID
|
||
if ThreadWatcher.db.get {boardID, threadID}
|
||
ThreadWatcher.rm boardID, threadID
|
||
else
|
||
ThreadWatcher.add thread
|
||
add: (thread) ->
|
||
data = {}
|
||
boardID = thread.board.ID
|
||
threadID = thread.ID
|
||
if thread.isDead
|
||
if Conf['Auto Prune'] and ThreadWatcher.db.get {boardID, threadID}
|
||
ThreadWatcher.rm boardID, threadID
|
||
return
|
||
data.isDead = true
|
||
data.excerpt = Get.threadExcerpt thread
|
||
ThreadWatcher.db.set {boardID, threadID, val: data}
|
||
ThreadWatcher.refresh()
|
||
rm: (boardID, threadID) ->
|
||
ThreadWatcher.db.delete {boardID, threadID}
|
||
ThreadWatcher.refresh()
|
||
|
||
convert: (oldFormat) ->
|
||
newFormat = {}
|
||
for boardID, threads of oldFormat
|
||
for threadID, data of threads
|
||
(newFormat[boardID] or= {})[threadID] = excerpt: data.textContent
|
||
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
|