unoop the thread updater.
This commit is contained in:
parent
bfffdbee91
commit
b90931b80d
512
4chan_x.user.js
512
4chan_x.user.js
File diff suppressed because one or more lines are too long
@ -178,12 +178,16 @@ a[href="javascript:;"] {
|
||||
}
|
||||
|
||||
/* Thread Updater */
|
||||
#updater {
|
||||
text-align: right;
|
||||
}
|
||||
#updater:not(:hover) {
|
||||
background: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
#updater > .move {
|
||||
padding: 0 3px;
|
||||
}
|
||||
#updater > div:last-child {
|
||||
text-align: center;
|
||||
}
|
||||
#updater input[type=number] {
|
||||
width: 4em;
|
||||
|
||||
@ -2313,234 +2313,221 @@ ThreadUpdater =
|
||||
init: ->
|
||||
return if g.VIEW isnt 'thread' or !Conf['Thread Updater']
|
||||
|
||||
html = ''
|
||||
for name, conf of Config.updater.checkbox
|
||||
checked = if Conf[name] then 'checked' else ''
|
||||
html += "<div><label title='#{conf[1]}'><input name='#{name}' type=checkbox #{checked}> #{name}</label></div>"
|
||||
|
||||
checked = if Conf['Auto Update'] then 'checked' else ''
|
||||
html = """
|
||||
<div class=move><span id=update-status></span> <span id=update-timer></span></div>
|
||||
#{html}
|
||||
<div><label title='Controls whether *this* thread automatically updates or not'><input type=checkbox name='Auto Update This' #{checked}> Auto Update This</label></div>
|
||||
<div><label><input type=number name=Interval class=field min=5 value=#{Conf['Interval']}> Refresh rate (s)</label></div>
|
||||
<div><input value='Update Now' type=button name='Update Now'></div>
|
||||
"""
|
||||
|
||||
@dialog = UI.dialog 'updater', 'bottom: 0; right: 0;', html
|
||||
@timer = $ '#update-timer', @dialog
|
||||
@status = $ '#update-status', @dialog
|
||||
|
||||
Thread::callbacks.push
|
||||
name: 'Thread Updater'
|
||||
cb: @node
|
||||
|
||||
node: ->
|
||||
new ThreadUpdater.Updater @
|
||||
ThreadUpdater.thread = @
|
||||
ThreadUpdater.root = @posts[@].nodes.root.parentNode
|
||||
ThreadUpdater.lastPost = +ThreadUpdater.root.lastElementChild.id.match(/\d+/)[0]
|
||||
ThreadUpdater.outdateCount = 0
|
||||
ThreadUpdater.lastModified = '0'
|
||||
|
||||
for input in $$ 'input', ThreadUpdater.dialog
|
||||
if input.type is 'checkbox'
|
||||
$.on input, 'change', $.cb.checked
|
||||
switch input.name
|
||||
when 'Scroll BG'
|
||||
$.on input, 'change', ThreadUpdater.cb.scrollBG
|
||||
ThreadUpdater.cb.scrollBG()
|
||||
when 'Auto Update This'
|
||||
$.on input, 'change', ThreadUpdater.cb.autoUpdate
|
||||
$.event 'change', null, input
|
||||
when 'Interval'
|
||||
$.on input, 'change', ThreadUpdater.cb.interval
|
||||
ThreadUpdater.cb.interval.call input
|
||||
when 'Update Now'
|
||||
$.on input, 'click', ThreadUpdater.update
|
||||
|
||||
$.on window, 'online offline', ThreadUpdater.cb.online
|
||||
$.on d, 'QRPostSuccessful', ThreadUpdater.cb.post
|
||||
$.on d, 'visibilitychange ovisibilitychange mozvisibilitychange webkitvisibilitychange', ThreadUpdater.cb.visibility
|
||||
|
||||
ThreadUpdater.cb.online()
|
||||
$.add d.body, ThreadUpdater.dialog
|
||||
|
||||
###
|
||||
http://freesound.org/people/pierrecartoons1979/sounds/90112/
|
||||
cc-by-nc-3.0
|
||||
###
|
||||
beep: 'data:audio/wav;base64,UklGRjQDAABXQVZFZm10IBAAAAABAAEAgD4AAIA+AAABAAgAc21wbDwAAABBAAADAAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkYXRhzAIAAGMms8em0tleMV4zIpLVo8nhfSlcPR102Ki+5JspVEkdVtKzs+K1NEhUIT7DwKrcy0g6WygsrM2k1NpiLl0zIY/WpMrjgCdbPhxw2Kq+5Z4qUkkdU9K1s+K5NkVTITzBwqnczko3WikrqM+l1NxlLF0zIIvXpsnjgydZPhxs2ay95aIrUEkdUdC3suK8N0NUIjq+xKrcz002WioppdGm091pK1w0IIjYp8jkhydXPxxq2K295aUrTkoeTs65suK+OUFUIzi7xqrb0VA0WSoootKm0t5tKlo1H4TYqMfkiydWQBxm16+85actTEseS8y7seHAPD9TIza5yKra01QyWSson9On0d5wKVk2H4DYqcfkjidUQB1j1rG75KsvSkseScu8seDCPz1TJDW2yara1FYxWSwnm9Sn0N9zKVg2H33ZqsXkkihSQR1g1bK65K0wSEsfR8i+seDEQTxUJTOzy6rY1VowWC0mmNWoz993KVc3H3rYq8TklSlRQh1d1LS647AyR0wgRMbAsN/GRDpTJTKwzKrX1l4vVy4lldWpzt97KVY4IXbUr8LZljVPRCxhw7W3z6ZISkw1VK+4sMWvXEhSPk6buay9sm5JVkZNiLWqtrJ+TldNTnquqbCwilZXU1BwpKirrpNgWFhTaZmnpquZbFlbVmWOpaOonHZcXlljhaGhpZ1+YWBdYn2cn6GdhmdhYGN3lp2enIttY2Jjco+bnJuOdGZlZXCImJqakHpoZ2Zug5WYmZJ/bGlobX6RlpeSg3BqaW16jZSVkoZ0bGtteImSk5KIeG5tbnaFkJKRinxxbm91gY2QkIt/c3BwdH6Kj4+LgnZxcXR8iI2OjIR5c3J0e4WLjYuFe3VzdHmCioyLhn52dHR5gIiKioeAeHV1eH+GiYqHgXp2dnh9hIiJh4J8eHd4fIKHiIeDfXl4eHyBhoeHhH96eHmA'
|
||||
|
||||
|
||||
Updater: class
|
||||
constructor: (@thread) ->
|
||||
html = '<div class=move><span id=status></span> <span id=timer></span></div>'
|
||||
for name, val of Config.updater.checkbox
|
||||
title = val[1]
|
||||
checked = if Conf[name] then 'checked' else ''
|
||||
html += "<div><label title='#{title}'>#{name}<input name='#{name}' type=checkbox #{checked}></label></div>"
|
||||
|
||||
checked = if Conf['Auto Update'] then 'checked' else ''
|
||||
html += """
|
||||
<div><label title='Controls whether *this* thread automatically updates or not'>Auto Update This<input name='Auto Update This' type=checkbox #{checked}></label></div>
|
||||
<div><label>Interval (s)<input type=number name=Interval class=field min=5 value=#{Conf['Interval']}></label></div>
|
||||
<div><input value='Update Now' type=button name='Update Now'></div>
|
||||
"""
|
||||
|
||||
dialog = UI.dialog 'updater', 'bottom: 0; right: 0;', html
|
||||
|
||||
@timer = $ '#timer', dialog
|
||||
@status = $ '#status', dialog
|
||||
|
||||
@unsuccessfulFetchCount = 0
|
||||
@lastModified = '0'
|
||||
@threadRoot = thread.posts[thread].nodes.root.parentNode
|
||||
@lastPost = +@threadRoot.lastElementChild.id[2..]
|
||||
|
||||
for input in $$ 'input', dialog
|
||||
if input.type is 'checkbox'
|
||||
$.on input, 'click', @cb.checkbox.bind @
|
||||
$.event 'click', null, input
|
||||
switch input.name
|
||||
when 'Scroll BG'
|
||||
$.on input, 'click', @cb.scrollBG.bind @
|
||||
@cb.scrollBG.call @
|
||||
when 'Auto Update This'
|
||||
$.on input, 'click', @cb.autoUpdate.bind @
|
||||
when 'Interval'
|
||||
$.on input, 'change', @cb.interval.bind @
|
||||
$.event 'change', null, input
|
||||
when 'Update Now'
|
||||
$.on input, 'click', @update.bind @
|
||||
|
||||
$.on window, 'online offline', @cb.online.bind @
|
||||
$.on d, 'QRPostSuccessful', @cb.post.bind @
|
||||
$.on d, 'visibilitychange ovisibilitychange mozvisibilitychange webkitvisibilitychange', @cb.visibility.bind @
|
||||
|
||||
@cb.online.call @
|
||||
$.add d.body, dialog
|
||||
|
||||
cb:
|
||||
online: ->
|
||||
if @online = navigator.onLine
|
||||
@unsuccessfulFetchCount = 0
|
||||
@set 'timer', @getInterval()
|
||||
@update() if Conf['Auto Update This']
|
||||
@set 'status', null
|
||||
@status.className = null
|
||||
cb:
|
||||
online: ->
|
||||
if ThreadUpdater.online = navigator.onLine
|
||||
ThreadUpdater.outdateCount = 0
|
||||
ThreadUpdater.set 'timer', ThreadUpdater.getInterval()
|
||||
ThreadUpdater.update() if Conf['Auto Update This']
|
||||
ThreadUpdater.set 'status', null, null
|
||||
else
|
||||
ThreadUpdater.set 'timer', null
|
||||
ThreadUpdater.set 'status', 'Offline', 'warning'
|
||||
ThreadUpdater.cb.autoUpdate()
|
||||
post: (e) ->
|
||||
return unless Conf['Auto Update This'] and +e.detail.threadID is @thread.ID
|
||||
@outdateCount = 0
|
||||
setTimeout @update.bind(@), 1000 if @seconds > 2
|
||||
visibility: ->
|
||||
return if $.hidden()
|
||||
# Reset the counter when we focus this tab.
|
||||
ThreadUpdater.outdateCount = 0
|
||||
if ThreadUpdater.seconds > ThreadUpdater.interval
|
||||
ThreadUpdater.set 'timer', ThreadUpdater.getInterval()
|
||||
scrollBG: ->
|
||||
ThreadUpdater.scrollBG = if Conf['Scroll BG']
|
||||
-> true
|
||||
else
|
||||
-> not $.hidden()
|
||||
autoUpdate: ->
|
||||
if Conf['Auto Update This'] and ThreadUpdater.online
|
||||
ThreadUpdater.timeoutID = setTimeout ThreadUpdater.timeout, 1000
|
||||
else
|
||||
clearTimeout ThreadUpdater.timeoutID
|
||||
interval: ->
|
||||
val = Math.max 5, parseInt @value, 10
|
||||
ThreadUpdater.interval = @value = val
|
||||
$.cb.value.call @
|
||||
load: ->
|
||||
{req} = ThreadUpdater
|
||||
switch req.status
|
||||
when 200
|
||||
ThreadUpdater.parse JSON.parse(req.response).posts
|
||||
ThreadUpdater.lastModified = req.getResponseHeader 'Last-Modified'
|
||||
ThreadUpdater.set 'timer', ThreadUpdater.getInterval()
|
||||
when 404
|
||||
ThreadUpdater.set 'timer', null
|
||||
ThreadUpdater.set 'status', '404', 'warning'
|
||||
clearTimeout ThreadUpdater.timeoutID
|
||||
ThreadUpdater.thread.kill()
|
||||
# if Conf['Unread Count']
|
||||
# Unread.title = Unread.title.match(/^.+-/)[0] + ' 404'
|
||||
# else
|
||||
# d.title = d.title.match(/^.+-/)[0] + ' 404'
|
||||
# Unread.update true
|
||||
# QR.abort()
|
||||
ThreadUpdater.outdateCount++
|
||||
ThreadUpdater.set 'timer', ThreadUpdater.getInterval()
|
||||
else
|
||||
@status.className = 'warning'
|
||||
@set 'status', 'Offline'
|
||||
@set 'timer', null
|
||||
@cb.autoUpdate.call @
|
||||
post: (e) ->
|
||||
return unless @['Auto Update This'] and +e.detail.threadID is @thread.ID
|
||||
@unsuccessfulFetchCount = 0
|
||||
setTimeout @update.bind(@), 1000 if @seconds > 2
|
||||
visibility: ->
|
||||
return if $.hidden()
|
||||
# Reset the counter when we focus this tab.
|
||||
@unsuccessfulFetchCount = 0
|
||||
if @seconds > @interval
|
||||
@set 'timer', @getInterval()
|
||||
checkbox: (e) ->
|
||||
input = e.target
|
||||
{checked, name} = input
|
||||
@[name] = checked
|
||||
$.cb.checked.call input
|
||||
scrollBG: ->
|
||||
@scrollBG =
|
||||
if @['Scroll BG']
|
||||
-> true
|
||||
else
|
||||
-> not $.hidden()
|
||||
autoUpdate: ->
|
||||
if @['Auto Update This'] and @online
|
||||
@timeoutID = setTimeout @timeout.bind(@), 1000
|
||||
else
|
||||
clearTimeout @timeoutID
|
||||
interval: (e) ->
|
||||
input = e.target
|
||||
val = Math.max 5, parseInt input.value, 10
|
||||
@interval = input.value = val
|
||||
$.cb.value.call input
|
||||
load: ->
|
||||
switch @req.status
|
||||
when 404
|
||||
@set 'timer', null
|
||||
@set 'status', '404'
|
||||
@status.className = 'warning'
|
||||
clearTimeout @timeoutID
|
||||
@thread.isDead = true
|
||||
# if Conf['Unread Count']
|
||||
# Unread.title = Unread.title.match(/^.+-/)[0] + ' 404'
|
||||
# else
|
||||
# d.title = d.title.match(/^.+-/)[0] + ' 404'
|
||||
# Unread.update true
|
||||
# QR.abort()
|
||||
ThreadUpdater.outdateCount++
|
||||
ThreadUpdater.set 'timer', ThreadUpdater.getInterval()
|
||||
###
|
||||
Status Code 304: Not modified
|
||||
By sending the `If-Modified-Since` header we get a proper status code, and no response.
|
||||
This saves bandwidth for both the user and the servers and avoid unnecessary computation.
|
||||
###
|
||||
# XXX 304 -> 0 in Opera
|
||||
when 0, 304
|
||||
###
|
||||
Status Code 304: Not modified
|
||||
By sending the `If-Modified-Since` header we get a proper status code, and no response.
|
||||
This saves bandwidth for both the user and the servers and avoid unnecessary computation.
|
||||
###
|
||||
@unsuccessfulFetchCount++
|
||||
@set 'timer', @getInterval()
|
||||
@set 'status', null
|
||||
@status.className = null
|
||||
when 200
|
||||
@parse JSON.parse(@req.response).posts
|
||||
@lastModified = @req.getResponseHeader 'Last-Modified'
|
||||
@set 'timer', @getInterval()
|
||||
[text, klass] = if req.status in [0, 304]
|
||||
[null, null]
|
||||
else
|
||||
@unsuccessfulFetchCount++
|
||||
@set 'timer', @getInterval()
|
||||
@set 'status', "#{@req.statusText} (#{@req.status})"
|
||||
@status.className = 'warning'
|
||||
delete @req
|
||||
["#{req.statusText} (#{req.status})", 'warning']
|
||||
ThreadUpdater.set 'status', text, klass
|
||||
delete ThreadUpdater.req
|
||||
|
||||
getInterval: ->
|
||||
i = @interval
|
||||
j = Math.min @unsuccessfulFetchCount, 10
|
||||
unless $.hidden()
|
||||
# Lower the max refresh rate limit on visible tabs.
|
||||
j = Math.min j, 7
|
||||
@seconds = Math.max i, [0, 5, 10, 15, 20, 30, 60, 90, 120, 240, 300][j]
|
||||
getInterval: ->
|
||||
i = ThreadUpdater.interval
|
||||
j = Math.min ThreadUpdater.outdateCount, 10
|
||||
unless $.hidden()
|
||||
# Lower the max refresh rate limit on visible tabs.
|
||||
j = Math.min j, 7
|
||||
ThreadUpdater.seconds = Math.max i, [0, 5, 10, 15, 20, 30, 60, 90, 120, 240, 300][j]
|
||||
|
||||
set: (name, text) ->
|
||||
el = @[name]
|
||||
if node = el.firstChild
|
||||
# Prevent the creation of a new DOM Node
|
||||
# by setting the text node's data.
|
||||
node.data = text
|
||||
else
|
||||
el.textContent = text
|
||||
set: (name, text, klass) ->
|
||||
el = ThreadUpdater[name]
|
||||
if node = el.firstChild
|
||||
# Prevent the creation of a new DOM Node
|
||||
# by setting the text node's data.
|
||||
node.data = text
|
||||
else
|
||||
el.textContent = text
|
||||
el.className = klass if klass isnt undefined
|
||||
|
||||
timeout: ->
|
||||
@timeoutID = setTimeout @timeout.bind(@), 1000
|
||||
unless n = --@seconds
|
||||
@update()
|
||||
else if n <= -60
|
||||
@set 'status', 'Retrying'
|
||||
@status.className = null
|
||||
@update()
|
||||
else if n > 0
|
||||
@set 'timer', n
|
||||
timeout: ->
|
||||
ThreadUpdater.timeoutID = setTimeout ThreadUpdater.timeout, 1000
|
||||
unless n = --ThreadUpdater.seconds
|
||||
ThreadUpdater.update()
|
||||
else if n <= -60
|
||||
ThreadUpdater.set 'status', 'Retrying', null
|
||||
ThreadUpdater.update()
|
||||
else if n > 0
|
||||
ThreadUpdater.set 'timer', n
|
||||
|
||||
update: ->
|
||||
return unless @online
|
||||
@seconds = 0
|
||||
@set 'timer', '...'
|
||||
if @req
|
||||
# abort() triggers onloadend, we don't want that.
|
||||
@req.onloadend = null
|
||||
@req.abort()
|
||||
url = "//api.4chan.org/#{@thread.board}/res/#{@thread}.json"
|
||||
@req = $.ajax url, onloadend: @cb.load.bind @,
|
||||
headers: 'If-Modified-Since': @lastModified
|
||||
update: ->
|
||||
return unless ThreadUpdater.online
|
||||
ThreadUpdater.seconds = 0
|
||||
ThreadUpdater.set 'timer', '...'
|
||||
if ThreadUpdater.req
|
||||
# abort() triggers onloadend, we don't want that.
|
||||
ThreadUpdater.req.onloadend = null
|
||||
ThreadUpdater.req.abort()
|
||||
url = "//api.4chan.org/#{ThreadUpdater.thread.board}/res/#{ThreadUpdater.thread}.json"
|
||||
ThreadUpdater.req = $.ajax url, onloadend: ThreadUpdater.cb.load,
|
||||
headers: 'If-Modified-Since': ThreadUpdater.lastModified
|
||||
|
||||
parse: (postObjects) ->
|
||||
Build.spoilerRange[@thread.board] = postObjects[0].custom_spoiler
|
||||
parse: (postObjects) ->
|
||||
Build.spoilerRange[ThreadUpdater.thread.board] = postObjects[0].custom_spoiler
|
||||
|
||||
nodes = [] # post container elements
|
||||
posts = [] # post objects
|
||||
index = [] # existing posts
|
||||
image = [] # existing images
|
||||
count = 0 # new posts count
|
||||
# Build the index, create posts.
|
||||
for postObject in postObjects
|
||||
num = postObject.no
|
||||
index.push num
|
||||
image.push num if postObject.ext
|
||||
continue if num <= @lastPost
|
||||
# Insert new posts, not older ones.
|
||||
count++
|
||||
node = Build.postFromObject postObject, @thread.board.ID
|
||||
nodes.push node
|
||||
posts.push new Post node, @thread, @thread.board
|
||||
nodes = [] # post container elements
|
||||
posts = [] # post objects
|
||||
index = [] # existing posts
|
||||
files = [] # existing files
|
||||
count = 0 # new posts count
|
||||
# Build the index, create posts.
|
||||
for postObject in postObjects
|
||||
num = postObject.no
|
||||
index.push num
|
||||
files.push num if postObject.fsize
|
||||
continue if num <= ThreadUpdater.lastPost
|
||||
# Insert new posts, not older ones.
|
||||
count++
|
||||
node = Build.postFromObject postObject, ThreadUpdater.thread.board.ID
|
||||
nodes.push node
|
||||
posts.push new Post node, ThreadUpdater.thread, ThreadUpdater.thread.board
|
||||
|
||||
# Check for deleted posts and deleted images.
|
||||
for i, post of @thread.posts
|
||||
continue if post.isDead
|
||||
{ID} = post
|
||||
if -1 is index.indexOf ID
|
||||
post.kill()
|
||||
else if post.file and !post.file.isDead and -1 is image.indexOf ID
|
||||
post.kill true
|
||||
# Check for deleted posts/files.
|
||||
for ID, post of ThreadUpdater.thread.posts
|
||||
continue if post.isDead
|
||||
ID = +ID
|
||||
if -1 is index.indexOf ID
|
||||
post.kill()
|
||||
else if post.file and !post.file.isDead and -1 is files.indexOf ID
|
||||
post.kill true
|
||||
|
||||
if count
|
||||
if Conf['Beep'] and $.hidden() and (Unread.replies.length is 0)
|
||||
unless @audio
|
||||
@audio = $.el 'audio', src: ThreadUpdater.beep
|
||||
audio.play()
|
||||
@set 'status', "+#{count}"
|
||||
@status.className = 'new'
|
||||
@unsuccessfulFetchCount = 0
|
||||
else
|
||||
@set 'status', null
|
||||
@status.className = null
|
||||
@unsuccessfulFetchCount++
|
||||
return
|
||||
if count
|
||||
if Conf['Beep'] and $.hidden() #and !Unread.replies.length
|
||||
unless ThreadUpdater.audio
|
||||
ThreadUpdater.audio = $.el 'audio', src: ThreadUpdater.beep
|
||||
ThreadUpdater.audio.play()
|
||||
ThreadUpdater.set 'status', "+#{count}", 'new'
|
||||
ThreadUpdater.outdateCount = 0
|
||||
else
|
||||
ThreadUpdater.set 'status', null, null
|
||||
ThreadUpdater.outdateCount++
|
||||
return
|
||||
|
||||
@lastPost = posts[count - 1].ID
|
||||
Main.callbackNodes Post, posts
|
||||
ThreadUpdater.lastPost = posts[count - 1].ID
|
||||
Main.callbackNodes Post, posts
|
||||
|
||||
scroll = @['Auto Scroll'] and @scrollBG() and
|
||||
@threadRoot.getBoundingClientRect().bottom - doc.clientHeight < 25
|
||||
$.add @threadRoot, nodes
|
||||
if scroll
|
||||
nodes[0].scrollIntoView()
|
||||
scroll = Conf['Auto Scroll'] and ThreadUpdater.scrollBG() and
|
||||
ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25
|
||||
$.add ThreadUpdater.root, nodes
|
||||
if scroll
|
||||
nodes[0].scrollIntoView()
|
||||
|
||||
@ -24,6 +24,10 @@ class Thread
|
||||
|
||||
g.threads["#{board}.#{@}"] = board.threads[@] = @
|
||||
|
||||
kill: ->
|
||||
@isDead = true
|
||||
@timeOfDeath = Date.now()
|
||||
|
||||
class Post
|
||||
callbacks: []
|
||||
toString: -> @ID
|
||||
|
||||
@ -611,7 +611,7 @@ QR =
|
||||
|
||||
# Create a custom event when the QR dialog is first initialized.
|
||||
# Use it to extend the QR's functionalities, or for XTRM RICE.
|
||||
$.event new CustomEvent 'QRDialogCreation', null, QR.el
|
||||
$.event 'QRDialogCreation', null, QR.el
|
||||
|
||||
submit: (e) ->
|
||||
e?.preventDefault()
|
||||
@ -777,7 +777,7 @@ QR =
|
||||
[_, threadID, postID] = h1.nextSibling.textContent.match /thread:(\d+),no:(\d+)/
|
||||
|
||||
# Post/upload confirmed as successful.
|
||||
$.event new CustomEvent 'QRPostSuccessful', {
|
||||
$.event 'QRPostSuccessful', {
|
||||
threadID
|
||||
postID
|
||||
}, QR.el
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user