unoop the thread updater.

This commit is contained in:
Nicolas Stepien 2013-02-14 21:31:31 +01:00
parent bfffdbee91
commit b90931b80d
5 changed files with 452 additions and 481 deletions

File diff suppressed because one or more lines are too long

View File

@ -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;

View File

@ -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()

View File

@ -24,6 +24,10 @@ class Thread
g.threads["#{board}.#{@}"] = board.threads[@] = @
kill: ->
@isDead = true
@timeOfDeath = Date.now()
class Post
callbacks: []
toString: -> @ID

View File

@ -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