formatting
alphabetize funks, put main in one place
This commit is contained in:
parent
975fb0ebff
commit
52b6d7982f
915
4chan_x.coffee
915
4chan_x.coffee
@ -4,6 +4,7 @@
|
||||
#
|
||||
#TODO - 4chan time
|
||||
#addClass, removeClass; remove hide / show; makeDialog el, 'center'
|
||||
#TODO - expose 'hidden' configs
|
||||
|
||||
config =
|
||||
'Thread Hiding': [true, 'Hide entire threads']
|
||||
@ -23,8 +24,7 @@ config =
|
||||
'Post in Title': [true, 'Show the op\'s post in the tab title']
|
||||
'Sauce': [true, 'Add sauce to images']
|
||||
|
||||
#TODO - expose 'hidden' configs
|
||||
|
||||
#utility
|
||||
AEOS =
|
||||
init: ->
|
||||
#x-browser
|
||||
@ -49,7 +49,6 @@ AEOS =
|
||||
style.type = 'text/css'
|
||||
style.textContent = css
|
||||
document.getElementsByTagName('head')[0].appendChild style
|
||||
|
||||
#dialog styling
|
||||
GM_addStyle '
|
||||
div.dialog {
|
||||
@ -63,13 +62,11 @@ AEOS =
|
||||
cursor: pointer;
|
||||
}
|
||||
'
|
||||
|
||||
#dialog creation
|
||||
makeDialog: (id, position) ->
|
||||
dialog = document.createElement 'div'
|
||||
dialog.className = 'reply dialog'
|
||||
dialog.id = id
|
||||
|
||||
switch position
|
||||
when 'topleft'
|
||||
left = '0px'
|
||||
@ -83,14 +80,11 @@ AEOS =
|
||||
when 'bottomright'
|
||||
left = null
|
||||
top = null
|
||||
|
||||
left = GM_getValue "#{id}Left", left
|
||||
top = GM_getValue "#{id}Top", top
|
||||
if left then dialog.style.left = left else dialog.style.right = '0px'
|
||||
if top then dialog.style.top = top else dialog.style.bottom = '0px'
|
||||
|
||||
dialog
|
||||
|
||||
#movement
|
||||
move: (e) ->
|
||||
div = @parentNode
|
||||
@ -101,39 +95,31 @@ AEOS =
|
||||
#factor out div from document dimensions
|
||||
AEOS.width = document.body.clientWidth - div.offsetWidth
|
||||
AEOS.height = document.body.clientHeight - div.offsetHeight
|
||||
|
||||
document.addEventListener 'mousemove', AEOS.moveMove, true
|
||||
document.addEventListener 'mouseup', AEOS.moveEnd, true
|
||||
|
||||
moveMove: (e) ->
|
||||
div = AEOS.div
|
||||
|
||||
left = e.clientX - AEOS.dx
|
||||
if left < 20 then left = '0px'
|
||||
else if AEOS.width - left < 20 then left = ''
|
||||
right = if left then '' else '0px'
|
||||
div.style.left = left
|
||||
div.style.right = right
|
||||
|
||||
top = e.clientY - AEOS.dy
|
||||
if top < 20 then top = '0px'
|
||||
else if AEOS.height - top < 20 then top = ''
|
||||
bottom = if top then '' else '0px'
|
||||
div.style.top = top
|
||||
div.style.bottom = bottom
|
||||
|
||||
moveEnd: ->
|
||||
document.removeEventListener 'mousemove', AEOS.moveMove, true
|
||||
document.removeEventListener 'mouseup', AEOS.moveEnd, true
|
||||
|
||||
div = AEOS.div
|
||||
id = div.id
|
||||
GM_setValue "#{id}Left", div.style.left
|
||||
GM_setValue "#{id}Top", div.style.top
|
||||
|
||||
AEOS.init()
|
||||
d = document
|
||||
#utility funks
|
||||
$ = (selector, root) ->
|
||||
root or= d.body
|
||||
root.querySelector(selector)
|
||||
@ -190,6 +176,434 @@ x = (path, root) ->
|
||||
d.evaluate(path, root, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).
|
||||
singleNodeValue
|
||||
|
||||
#funks
|
||||
autohide = ->
|
||||
qr = $ '#qr'
|
||||
klass = qr.className
|
||||
if klass.indexOf('auto') is -1
|
||||
klass += ' auto'
|
||||
else
|
||||
klass = klass.replace(' auto', '')
|
||||
qr.className = klass
|
||||
|
||||
autoWatch = ->
|
||||
#TODO look for subject
|
||||
autoText = $('textarea', this).value.slice(0, 25)
|
||||
GM_setValue('autoText', "/#{BOARD}/ - #{autoText}")
|
||||
|
||||
close = ->
|
||||
div = this.parentNode.parentNode
|
||||
remove div
|
||||
|
||||
clearHidden = ->
|
||||
#'hidden' might be misleading; it's the number of IDs we're *looking* for,
|
||||
# not the number of posts actually hidden on the page.
|
||||
GM_deleteValue("hiddenReplies/#{BOARD}/")
|
||||
GM_deleteValue("hiddenThreads/#{BOARD}/")
|
||||
@value = "hidden: 0"
|
||||
hiddenReplies = []
|
||||
hiddenThreads = []
|
||||
|
||||
cooldown = ->
|
||||
submit = $ '#qr input[type=submit]'
|
||||
seconds = parseInt submit.value
|
||||
if seconds == 0
|
||||
submit.disabled = false
|
||||
submit.value = 'Submit'
|
||||
auto = submit.previousSibling.lastChild
|
||||
if auto.checked
|
||||
$('#qr form').submit()
|
||||
#submit.click() doesn't work
|
||||
else
|
||||
submit.value = seconds - 1
|
||||
window.setTimeout cooldown, 1000
|
||||
|
||||
editSauce = ->
|
||||
ta = $ '#options textarea'
|
||||
if ta.style.display then show ta else hide ta
|
||||
|
||||
expandComment = (e) ->
|
||||
e.preventDefault()
|
||||
a = this
|
||||
href = a.getAttribute('href')
|
||||
r = new XMLHttpRequest()
|
||||
r.onload = ->
|
||||
onloadComment(this.responseText, a, href)
|
||||
r.open('GET', href, true)
|
||||
r.send()
|
||||
xhrs.push({
|
||||
r: r,
|
||||
id: href.match(/\d+/)[0]
|
||||
})
|
||||
|
||||
expandThread = ->
|
||||
id = x('preceding-sibling::input[1]', this).name
|
||||
span = this
|
||||
#close expanded thread
|
||||
if span.textContent[0] is '-'
|
||||
#goddamit moot
|
||||
num = if board is 'b' then 3 else 5
|
||||
table = x("following::br[@clear][1]/preceding::table[#{num}]", span)
|
||||
while (prev = table.previousSibling) and (prev.nodeName is 'TABLE')
|
||||
remove(prev)
|
||||
span.textContent = span.textContent.replace('-', '+')
|
||||
return
|
||||
span.textContent = span.textContent.replace('+', 'X Loading...')
|
||||
#load cache
|
||||
for xhr in xhrs
|
||||
if xhr.id == id
|
||||
#why can't we just xhr.r.onload()?
|
||||
onloadThread(xhr.r.responseText, span)
|
||||
return
|
||||
#create new request
|
||||
r = new XMLHttpRequest()
|
||||
r.onload = ->
|
||||
onloadThread(this.responseText, span)
|
||||
r.open('GET', "res/#{id}", true)
|
||||
r.send()
|
||||
xhrs.push({
|
||||
r: r,
|
||||
id: id
|
||||
})
|
||||
|
||||
hideReply = (reply) ->
|
||||
if p = this.parentNode
|
||||
reply = p.nextSibling
|
||||
hiddenReplies.push({
|
||||
id: reply.id
|
||||
timestamp: getTime()
|
||||
})
|
||||
GM_setValue("hiddenReplies/#{BOARD}/", JSON.stringify(hiddenReplies))
|
||||
name = $('span.commentpostername', reply).textContent
|
||||
trip = $('span.postertrip', reply)?.textContent || ''
|
||||
table = x('ancestor::table', reply)
|
||||
hide(table)
|
||||
if getConfig 'Show Stubs'
|
||||
a = n 'a',
|
||||
textContent: "[ + ] #{name} #{trip}"
|
||||
className: 'pointer'
|
||||
listener: ['click', showReply]
|
||||
div = n 'div'
|
||||
addTo div, a
|
||||
inBefore table, div
|
||||
|
||||
hideThread = (div) ->
|
||||
if p = @parentNode
|
||||
div = p
|
||||
hiddenThreads.push {
|
||||
id: div.id
|
||||
timestamp: getTime()
|
||||
}
|
||||
GM_setValue("hiddenThreads/#{BOARD}/", JSON.stringify(hiddenThreads))
|
||||
hide div
|
||||
if getConfig 'Show Stubs'
|
||||
if span = $ '.omittedposts', div
|
||||
num = Number(span.textContent.match(/\d+/)[0])
|
||||
else
|
||||
num = 0
|
||||
num += $$('table', div).length
|
||||
text = if num is 1 then "1 reply" else "#{num} replies"
|
||||
name = $('span.postername', div).textContent
|
||||
trip = $('span.postername + span.postertrip', div)?.textContent || ''
|
||||
a = n 'a',
|
||||
textContent: "[ + ] #{name}#{trip} (#{text})"
|
||||
className: 'pointer'
|
||||
listener: ['click', showThread]
|
||||
inBefore div, a
|
||||
|
||||
iframeLoad = ->
|
||||
if iframeLoop = !iframeLoop
|
||||
return
|
||||
$('iframe').src = 'about:blank'
|
||||
qr = $('#qr')
|
||||
if error = GM_getValue('error')
|
||||
span = n 'span',
|
||||
textContent: error
|
||||
className: 'error'
|
||||
addTo qr, span
|
||||
$('input[title=autohide]:checked', qr)?.click()
|
||||
else if REPLY and getConfig 'Persistent QR'
|
||||
$('textarea', qr).value = ''
|
||||
$('input[name=recaptcha_response_field]', qr).value = ''
|
||||
submit = $('input[type=submit]', qr)
|
||||
submit.value = 30
|
||||
submit.disabled = true
|
||||
window.setTimeout cooldown, 1000
|
||||
auto = submit.previousSibling.lastChild
|
||||
if auto.checked
|
||||
#unhide the qr so you know it's ready for the next item
|
||||
$('input[title=autohide]:checked', qr)?.click()
|
||||
else
|
||||
remove qr
|
||||
recaptchaReload()
|
||||
|
||||
nodeInserted = (e) ->
|
||||
target = e.target
|
||||
if target.nodeName is 'TABLE'
|
||||
for callback in callbacks
|
||||
callback(target)
|
||||
else if target.id is 'recaptcha_challenge_field' and qr = $ '#qr'
|
||||
$('#recaptcha_image img', qr).src = "http://www.google.com/recaptcha/api/image?c=" + target.value
|
||||
$('#recaptcha_challenge_field', qr).value = target.value
|
||||
|
||||
onloadComment = (responseText, a, href) ->
|
||||
[_, op, id] = href.match(/(\d+)#(\d+)/)
|
||||
[replies, opbq] = parseResponse(responseText)
|
||||
if id is op
|
||||
html = opbq.innerHTML
|
||||
else
|
||||
#css selectors don't like ids starting with numbers,
|
||||
# getElementById only works for root document.
|
||||
for reply in replies
|
||||
if reply.id == id
|
||||
html = $('blockquote', reply).innerHTML
|
||||
bq = x('ancestor::blockquote', a)
|
||||
bq.innerHTML = html
|
||||
|
||||
onloadThread = (responseText, span) ->
|
||||
[replies, opbq] = parseResponse(responseText)
|
||||
span.textContent = span.textContent.replace('X Loading...', '- ')
|
||||
#make sure all comments are fully expanded
|
||||
span.previousSibling.innerHTML = opbq.innerHTML
|
||||
while (next = span.nextSibling) and not next.clear#<br clear>
|
||||
remove(next)
|
||||
if next
|
||||
for reply in replies
|
||||
inBefore next, x('ancestor::table', reply)
|
||||
else#threading
|
||||
div = span.parentNode
|
||||
for reply in replies
|
||||
addTo div, x('ancestor::table', reply)
|
||||
|
||||
options = ->
|
||||
if div = $ '#options'
|
||||
remove div
|
||||
else
|
||||
div = AEOS.makeDialog 'options', 'center'
|
||||
hiddenNum = hiddenReplies.length + hiddenThreads.length
|
||||
html = '<div class="move">Options <a class=pointer>X</a></div><div>'
|
||||
for option, value of config
|
||||
description = value[1]
|
||||
checked = if getConfig option then "checked" else ""
|
||||
html += "<label title=\"#{description}\">#{option}<input #{checked} name=\"#{option}\" type=\"checkbox\"></label><br>"
|
||||
html += "<div><a class=sauce>Edit Sauce</a></div>"
|
||||
html += "<div><textarea cols=50 rows=4 style=\"display: none;\"></textarea></div>"
|
||||
html += "<input type=\"button\" value=\"hidden: #{hiddenNum}\"><br>"
|
||||
div.innerHTML = html
|
||||
$('div.move', div).addEventListener 'mousedown', AEOS.move, true
|
||||
$('a.pointer', div).addEventListener 'click', optionsClose, true
|
||||
$('a.sauce', div).addEventListener 'click', editSauce, true
|
||||
$('textarea', div).value = GM_getValue 'saucePrefix', defaultSaucePrefix
|
||||
$('input[type="button"]', div).addEventListener 'click', clearHidden, true
|
||||
addTo d.body, div
|
||||
|
||||
optionsClose = ->
|
||||
div = this.parentNode.parentNode
|
||||
inputs = $$('input', div)
|
||||
for input in inputs
|
||||
GM_setValue(input.name, input.checked)
|
||||
GM_setValue 'saucePrefix', $('textarea', div).value
|
||||
remove div
|
||||
|
||||
parseResponse = (responseText) ->
|
||||
body = n 'body',
|
||||
innerHTML: responseText
|
||||
replies = $$('td.reply', body)
|
||||
opbq = $('blockquote', body)
|
||||
return [replies, opbq]
|
||||
|
||||
quickReply = (e) ->
|
||||
unless qr = $('#qr')
|
||||
#make quick reply dialog
|
||||
qr = AEOS.makeDialog 'qr', 'topleft'
|
||||
titlebar = n 'div',
|
||||
innerHTML: 'Quick Reply '
|
||||
className: 'move'
|
||||
listener: ['mousedown', AEOS.move]
|
||||
addTo qr, titlebar
|
||||
autohideB = n 'input',
|
||||
type: 'checkbox'
|
||||
className: 'pointer'
|
||||
title: 'autohide'
|
||||
listener: ['click', autohide]
|
||||
closeB = n 'a',
|
||||
textContent: 'X'
|
||||
className: 'pointer'
|
||||
title: 'close'
|
||||
listener: ['click', close]
|
||||
addTo titlebar, autohideB, tn(' '), closeB
|
||||
form = $ 'form[name=post]'
|
||||
clone = form.cloneNode(true)
|
||||
#remove recaptcha scripts
|
||||
for script in $$ 'script', clone
|
||||
remove script
|
||||
$('input[name=recaptcha_response_field]', clone).
|
||||
addEventListener('keydown', recaptchaListener, true)
|
||||
clone.addEventListener('submit', submit, true)
|
||||
clone.target = 'iframe'
|
||||
if not REPLY
|
||||
#figure out which thread we're replying to
|
||||
xpath = 'preceding::span[@class="postername"][1]/preceding::input[1]'
|
||||
input = n 'input',
|
||||
type: 'hidden'
|
||||
name: 'resto'
|
||||
value: x(xpath, this).name
|
||||
addTo clone, input
|
||||
else if getConfig 'Persistent QR'
|
||||
submit = $ 'input[type=submit]', clone
|
||||
auto = n 'label',
|
||||
textContent: 'Auto'
|
||||
autoBox = n 'input',
|
||||
type: 'checkbox'
|
||||
addTo auto, autoBox
|
||||
inBefore submit, auto
|
||||
addTo qr, clone
|
||||
addTo d.body, qr
|
||||
if e
|
||||
e.preventDefault()
|
||||
$('input[title=autohide]:checked', qr)?.click()
|
||||
selection = window.getSelection()
|
||||
id = x('preceding::span[@id][1]', selection.anchorNode)?.id
|
||||
text = selection.toString()
|
||||
textarea = $('textarea', qr)
|
||||
textarea.focus()
|
||||
#we can't just use @textContent b/c of the xxxs. goddamit moot.
|
||||
textarea.value += '>>' + @parentNode.id.match(/\d+$/)[0] + '\n'
|
||||
if text and id is this.parentNode.id
|
||||
textarea.value += ">#{text}\n"
|
||||
|
||||
recaptchaListener = (e) ->
|
||||
if e.keyCode is 8 and this.value is ''
|
||||
recaptchaReload()
|
||||
|
||||
recaptchaReload = ->
|
||||
window.location = 'javascript:Recaptcha.reload()'
|
||||
|
||||
redirect = ->
|
||||
switch BOARD
|
||||
when 'a', 'g', 'lit', 'sci', 'tv'
|
||||
url = "http://green-oval.net/cgi-board.pl/#{BOARD}/thread/#{THREAD_ID}#p"
|
||||
when 'cgl', 'jp', 'm', 'tg'
|
||||
url = "http://archive.easymodo.net/cgi-board.pl/#{BOARD}/thread/#{THREAD_ID}#p"
|
||||
else
|
||||
url = "http://boards.4chan.org/#{BOARD}"
|
||||
location.href = url
|
||||
|
||||
replyNav = ->
|
||||
if REPLY
|
||||
window.location = if @textContent is '▲' then '#navtop' else '#navbot'
|
||||
else
|
||||
direction = if @textContent is '▲' then 'preceding' else 'following'
|
||||
op = x("#{direction}::span[starts-with(@id, 'nothread')][1]", this).id
|
||||
window.location = "##{op}"
|
||||
|
||||
report = ->
|
||||
input = x('preceding-sibling::input[1]', this)
|
||||
input.click()
|
||||
$('input[value="Report"]').click()
|
||||
input.click()
|
||||
|
||||
showReply = ->
|
||||
div = this.parentNode
|
||||
table = div.nextSibling
|
||||
show(table)
|
||||
remove(div)
|
||||
id = $('td.reply, td.replyhl', table).id
|
||||
slice(hiddenReplies, id)
|
||||
GM_setValue("hiddenReplies/#{BOARD}/", JSON.stringify(hiddenReplies))
|
||||
|
||||
showThread = ->
|
||||
div = @nextSibling
|
||||
show div
|
||||
hide this
|
||||
id = div.id
|
||||
slice hiddenThreads, id
|
||||
GM_setValue("hiddenThreads/#{BOARD}/", JSON.stringify(hiddenThreads))
|
||||
|
||||
submit = (e) ->
|
||||
if span = @nextSibling
|
||||
remove(span)
|
||||
recaptcha = $('input[name=recaptcha_response_field]', this)
|
||||
if recaptcha.value
|
||||
$('#qr input[title=autohide]:not(:checked)')?.click()
|
||||
else
|
||||
e.preventDefault()
|
||||
span = n 'span',
|
||||
className: 'error'
|
||||
textContent: 'You forgot to type in the verification.'
|
||||
addTo @parentNode, span
|
||||
alert 'You forgot to type in the verification.'
|
||||
recaptcha.focus()
|
||||
|
||||
stopPropagation = (e) ->
|
||||
e.stopPropagation()
|
||||
|
||||
threadF = (current) ->
|
||||
div = n 'div',
|
||||
className: 'thread'
|
||||
a = n 'a',
|
||||
textContent: '[ - ]'
|
||||
className: 'pointer'
|
||||
listener: ['click', hideThread]
|
||||
addTo div, a
|
||||
inBefore current, div
|
||||
while (!current.clear)#<br clear>
|
||||
addTo div, current
|
||||
current = div.nextSibling
|
||||
addTo div, current
|
||||
current = div.nextSibling
|
||||
id = $('input[value="delete"]', div).name
|
||||
div.id = id
|
||||
#check if we should hide the thread
|
||||
for hidden in hiddenThreads
|
||||
if id == hidden.id
|
||||
hideThread(div)
|
||||
current = current.nextSibling.nextSibling
|
||||
if current.nodeName isnt 'CENTER'
|
||||
threadF(current)
|
||||
|
||||
watch = ->
|
||||
id = this.nextSibling.name
|
||||
if this.src[0] is 'd'#data:png
|
||||
this.src = favNormal
|
||||
text = "/#{BOARD}/ - " +
|
||||
x('following-sibling::blockquote', this).textContent.slice(0,25)
|
||||
watched[BOARD] or= []
|
||||
watched[BOARD].push({
|
||||
id: id,
|
||||
text: text
|
||||
})
|
||||
else
|
||||
this.src = favEmpty
|
||||
watched[BOARD] = slice(watched[BOARD], id)
|
||||
GM_setValue('watched', JSON.stringify(watched))
|
||||
watcherUpdate()
|
||||
|
||||
watcherUpdate = ->
|
||||
div = n 'div'
|
||||
for board of watched
|
||||
for thread in watched[board]
|
||||
a = n 'a',
|
||||
textContent: 'X'
|
||||
className: 'pointer'
|
||||
listener: ['click', watchX]
|
||||
link = n 'a',
|
||||
textContent: thread.text
|
||||
href: "/#{board}/res/#{thread.id}"
|
||||
addTo div, a, tn(' '), link, n('br')
|
||||
old = $('#watcher div:last-child')
|
||||
replace(old, div)
|
||||
|
||||
watchX = ->
|
||||
[board, _, id] = @nextElementSibling.
|
||||
getAttribute('href').substring(1).split('/')
|
||||
watched[board] = slice(watched[board], id)
|
||||
GM_setValue('watched', JSON.stringify(watched))
|
||||
watcherUpdate()
|
||||
if input = $("input[name=\"#{id}\"]")
|
||||
favicon = input.previousSibling
|
||||
favicon.src = favEmpty
|
||||
|
||||
#let's get this party started.
|
||||
watched = JSON.parse(GM_getValue('watched', '{}'))
|
||||
if location.hostname.split('.')[0] is 'sys'
|
||||
@ -311,475 +725,8 @@ GM_addStyle('
|
||||
')
|
||||
|
||||
|
||||
clearHidden = ->
|
||||
#'hidden' might be misleading; it's the number of IDs we're *looking* for,
|
||||
# not the number of posts actually hidden on the page.
|
||||
GM_deleteValue("hiddenReplies/#{BOARD}/")
|
||||
GM_deleteValue("hiddenThreads/#{BOARD}/")
|
||||
@value = "hidden: 0"
|
||||
hiddenReplies = []
|
||||
hiddenThreads = []
|
||||
|
||||
|
||||
editSauce = ->
|
||||
ta = $ '#options textarea'
|
||||
if ta.style.display then show ta else hide ta
|
||||
|
||||
options = ->
|
||||
if div = $ '#options'
|
||||
remove div
|
||||
else
|
||||
div = AEOS.makeDialog 'options', 'center'
|
||||
hiddenNum = hiddenReplies.length + hiddenThreads.length
|
||||
html = '<div class="move">Options <a class=pointer>X</a></div><div>'
|
||||
for option, value of config
|
||||
description = value[1]
|
||||
checked = if getConfig option then "checked" else ""
|
||||
html += "<label title=\"#{description}\">#{option}<input #{checked} name=\"#{option}\" type=\"checkbox\"></label><br>"
|
||||
html += "<div><a class=sauce>Edit Sauce</a></div>"
|
||||
html += "<div><textarea cols=50 rows=4 style=\"display: none;\"></textarea></div>"
|
||||
html += "<input type=\"button\" value=\"hidden: #{hiddenNum}\"><br>"
|
||||
div.innerHTML = html
|
||||
$('div.move', div).addEventListener 'mousedown', AEOS.move, true
|
||||
$('a.pointer', div).addEventListener 'click', optionsClose, true
|
||||
$('a.sauce', div).addEventListener 'click', editSauce, true
|
||||
$('textarea', div).value = GM_getValue 'saucePrefix', defaultSaucePrefix
|
||||
$('input[type="button"]', div).addEventListener 'click', clearHidden, true
|
||||
addTo d.body, div
|
||||
|
||||
|
||||
showThread = ->
|
||||
div = @nextSibling
|
||||
show div
|
||||
hide this
|
||||
id = div.id
|
||||
slice hiddenThreads, id
|
||||
GM_setValue("hiddenThreads/#{BOARD}/", JSON.stringify(hiddenThreads))
|
||||
|
||||
|
||||
hideThread = (div) ->
|
||||
if p = @parentNode
|
||||
div = p
|
||||
hiddenThreads.push {
|
||||
id: div.id
|
||||
timestamp: getTime()
|
||||
}
|
||||
GM_setValue("hiddenThreads/#{BOARD}/", JSON.stringify(hiddenThreads))
|
||||
hide div
|
||||
if getConfig 'Show Stubs'
|
||||
if span = $ '.omittedposts', div
|
||||
num = Number(span.textContent.match(/\d+/)[0])
|
||||
else
|
||||
num = 0
|
||||
num += $$('table', div).length
|
||||
text = if num is 1 then "1 reply" else "#{num} replies"
|
||||
name = $('span.postername', div).textContent
|
||||
trip = $('span.postername + span.postertrip', div)?.textContent || ''
|
||||
a = n 'a',
|
||||
textContent: "[ + ] #{name}#{trip} (#{text})"
|
||||
className: 'pointer'
|
||||
listener: ['click', showThread]
|
||||
inBefore div, a
|
||||
|
||||
|
||||
threadF = (current) ->
|
||||
div = n 'div',
|
||||
className: 'thread'
|
||||
a = n 'a',
|
||||
textContent: '[ - ]'
|
||||
className: 'pointer'
|
||||
listener: ['click', hideThread]
|
||||
addTo div, a
|
||||
|
||||
inBefore current, div
|
||||
while (!current.clear)#<br clear>
|
||||
addTo div, current
|
||||
current = div.nextSibling
|
||||
addTo div, current
|
||||
current = div.nextSibling
|
||||
|
||||
id = $('input[value="delete"]', div).name
|
||||
div.id = id
|
||||
#check if we should hide the thread
|
||||
for hidden in hiddenThreads
|
||||
if id == hidden.id
|
||||
hideThread(div)
|
||||
|
||||
current = current.nextSibling.nextSibling
|
||||
if current.nodeName isnt 'CENTER'
|
||||
threadF(current)
|
||||
|
||||
|
||||
showReply = ->
|
||||
div = this.parentNode
|
||||
table = div.nextSibling
|
||||
show(table)
|
||||
remove(div)
|
||||
id = $('td.reply, td.replyhl', table).id
|
||||
slice(hiddenReplies, id)
|
||||
GM_setValue("hiddenReplies/#{BOARD}/", JSON.stringify(hiddenReplies))
|
||||
|
||||
|
||||
hideReply = (reply) ->
|
||||
if p = this.parentNode
|
||||
reply = p.nextSibling
|
||||
hiddenReplies.push({
|
||||
id: reply.id
|
||||
timestamp: getTime()
|
||||
})
|
||||
GM_setValue("hiddenReplies/#{BOARD}/", JSON.stringify(hiddenReplies))
|
||||
|
||||
name = $('span.commentpostername', reply).textContent
|
||||
trip = $('span.postertrip', reply)?.textContent || ''
|
||||
table = x('ancestor::table', reply)
|
||||
hide(table)
|
||||
if getConfig 'Show Stubs'
|
||||
a = n 'a',
|
||||
textContent: "[ + ] #{name} #{trip}"
|
||||
className: 'pointer'
|
||||
listener: ['click', showReply]
|
||||
div = n 'div'
|
||||
addTo div, a
|
||||
inBefore table, div
|
||||
|
||||
|
||||
optionsClose = ->
|
||||
div = this.parentNode.parentNode
|
||||
inputs = $$('input', div)
|
||||
for input in inputs
|
||||
GM_setValue(input.name, input.checked)
|
||||
GM_setValue 'saucePrefix', $('textarea', div).value
|
||||
remove div
|
||||
|
||||
|
||||
close = ->
|
||||
div = this.parentNode.parentNode
|
||||
remove div
|
||||
|
||||
cooldown = ->
|
||||
submit = $ '#qr input[type=submit]'
|
||||
seconds = parseInt submit.value
|
||||
if seconds == 0
|
||||
submit.disabled = false
|
||||
submit.value = 'Submit'
|
||||
auto = submit.previousSibling.lastChild
|
||||
if auto.checked
|
||||
$('#qr form').submit()
|
||||
#submit.click() doesn't work
|
||||
else
|
||||
submit.value = seconds - 1
|
||||
window.setTimeout cooldown, 1000
|
||||
|
||||
iframeLoad = ->
|
||||
if iframeLoop = !iframeLoop
|
||||
return
|
||||
$('iframe').src = 'about:blank'
|
||||
|
||||
qr = $('#qr')
|
||||
if error = GM_getValue('error')
|
||||
span = n 'span',
|
||||
textContent: error
|
||||
className: 'error'
|
||||
addTo qr, span
|
||||
$('input[title=autohide]:checked', qr)?.click()
|
||||
else if REPLY and getConfig 'Persistent QR'
|
||||
$('textarea', qr).value = ''
|
||||
$('input[name=recaptcha_response_field]', qr).value = ''
|
||||
submit = $('input[type=submit]', qr)
|
||||
submit.value = 30
|
||||
submit.disabled = true
|
||||
window.setTimeout cooldown, 1000
|
||||
auto = submit.previousSibling.lastChild
|
||||
if auto.checked
|
||||
#unhide the qr so you know it's ready for the next item
|
||||
$('input[title=autohide]:checked', qr)?.click()
|
||||
else
|
||||
remove qr
|
||||
|
||||
recaptchaReload()
|
||||
|
||||
|
||||
submit = (e) ->
|
||||
if span = @nextSibling
|
||||
remove(span)
|
||||
recaptcha = $('input[name=recaptcha_response_field]', this)
|
||||
if recaptcha.value
|
||||
$('#qr input[title=autohide]:not(:checked)')?.click()
|
||||
else
|
||||
e.preventDefault()
|
||||
span = n 'span',
|
||||
className: 'error'
|
||||
textContent: 'You forgot to type in the verification.'
|
||||
addTo @parentNode, span
|
||||
alert 'You forgot to type in the verification.'
|
||||
recaptcha.focus()
|
||||
|
||||
|
||||
autohide = ->
|
||||
qr = $ '#qr'
|
||||
klass = qr.className
|
||||
if klass.indexOf('auto') is -1
|
||||
klass += ' auto'
|
||||
else
|
||||
klass = klass.replace(' auto', '')
|
||||
qr.className = klass
|
||||
|
||||
|
||||
quickReply = (e) ->
|
||||
unless qr = $('#qr')
|
||||
#make quick reply dialog
|
||||
qr = AEOS.makeDialog 'qr', 'topleft'
|
||||
|
||||
titlebar = n 'div',
|
||||
innerHTML: 'Quick Reply '
|
||||
className: 'move'
|
||||
listener: ['mousedown', AEOS.move]
|
||||
addTo qr, titlebar
|
||||
|
||||
autohideB = n 'input',
|
||||
type: 'checkbox'
|
||||
className: 'pointer'
|
||||
title: 'autohide'
|
||||
listener: ['click', autohide]
|
||||
closeB = n 'a',
|
||||
textContent: 'X'
|
||||
className: 'pointer'
|
||||
title: 'close'
|
||||
listener: ['click', close]
|
||||
addTo titlebar, autohideB, tn(' '), closeB
|
||||
|
||||
form = $ 'form[name=post]'
|
||||
clone = form.cloneNode(true)
|
||||
#remove recaptcha scripts
|
||||
for script in $$ 'script', clone
|
||||
remove script
|
||||
$('input[name=recaptcha_response_field]', clone).
|
||||
addEventListener('keydown', recaptchaListener, true)
|
||||
clone.addEventListener('submit', submit, true)
|
||||
clone.target = 'iframe'
|
||||
if not REPLY
|
||||
#figure out which thread we're replying to
|
||||
xpath = 'preceding::span[@class="postername"][1]/preceding::input[1]'
|
||||
input = n 'input',
|
||||
type: 'hidden'
|
||||
name: 'resto'
|
||||
value: x(xpath, this).name
|
||||
addTo clone, input
|
||||
else if getConfig 'Persistent QR'
|
||||
submit = $ 'input[type=submit]', clone
|
||||
auto = n 'label',
|
||||
textContent: 'Auto'
|
||||
autoBox = n 'input',
|
||||
type: 'checkbox'
|
||||
addTo auto, autoBox
|
||||
inBefore submit, auto
|
||||
addTo qr, clone
|
||||
addTo d.body, qr
|
||||
|
||||
if e
|
||||
e.preventDefault()
|
||||
|
||||
$('input[title=autohide]:checked', qr)?.click()
|
||||
|
||||
selection = window.getSelection()
|
||||
id = x('preceding::span[@id][1]', selection.anchorNode)?.id
|
||||
text = selection.toString()
|
||||
|
||||
textarea = $('textarea', qr)
|
||||
textarea.focus()
|
||||
#we can't just use @textContent b/c of the xxxs. goddamit moot.
|
||||
textarea.value += '>>' + @parentNode.id.match(/\d+$/)[0] + '\n'
|
||||
if text and id is this.parentNode.id
|
||||
textarea.value += ">#{text}\n"
|
||||
|
||||
watch = ->
|
||||
id = this.nextSibling.name
|
||||
if this.src[0] is 'd'#data:png
|
||||
this.src = favNormal
|
||||
text = "/#{BOARD}/ - " +
|
||||
x('following-sibling::blockquote', this).textContent.slice(0,25)
|
||||
watched[BOARD] or= []
|
||||
watched[BOARD].push({
|
||||
id: id,
|
||||
text: text
|
||||
})
|
||||
else
|
||||
this.src = favEmpty
|
||||
watched[BOARD] = slice(watched[BOARD], id)
|
||||
|
||||
GM_setValue('watched', JSON.stringify(watched))
|
||||
watcherUpdate()
|
||||
|
||||
|
||||
watchX = ->
|
||||
[board, _, id] = @nextElementSibling.
|
||||
getAttribute('href').substring(1).split('/')
|
||||
watched[board] = slice(watched[board], id)
|
||||
GM_setValue('watched', JSON.stringify(watched))
|
||||
watcherUpdate()
|
||||
if input = $("input[name=\"#{id}\"]")
|
||||
favicon = input.previousSibling
|
||||
favicon.src = favEmpty
|
||||
|
||||
|
||||
watcherUpdate = ->
|
||||
div = n 'div'
|
||||
for board of watched
|
||||
for thread in watched[board]
|
||||
a = n 'a',
|
||||
textContent: 'X'
|
||||
className: 'pointer'
|
||||
listener: ['click', watchX]
|
||||
link = n 'a',
|
||||
textContent: thread.text
|
||||
href: "/#{board}/res/#{thread.id}"
|
||||
addTo div, a, tn(' '), link, n('br')
|
||||
old = $('#watcher div:last-child')
|
||||
replace(old, div)
|
||||
|
||||
|
||||
parseResponse = (responseText) ->
|
||||
body = n 'body',
|
||||
innerHTML: responseText
|
||||
replies = $$('td.reply', body)
|
||||
opbq = $('blockquote', body)
|
||||
return [replies, opbq]
|
||||
|
||||
|
||||
onloadThread = (responseText, span) ->
|
||||
[replies, opbq] = parseResponse(responseText)
|
||||
span.textContent = span.textContent.replace('X Loading...', '- ')
|
||||
|
||||
#make sure all comments are fully expanded
|
||||
span.previousSibling.innerHTML = opbq.innerHTML
|
||||
while (next = span.nextSibling) and not next.clear#<br clear>
|
||||
remove(next)
|
||||
if next
|
||||
for reply in replies
|
||||
inBefore next, x('ancestor::table', reply)
|
||||
else#threading
|
||||
div = span.parentNode
|
||||
for reply in replies
|
||||
addTo div, x('ancestor::table', reply)
|
||||
|
||||
|
||||
expandThread = ->
|
||||
id = x('preceding-sibling::input[1]', this).name
|
||||
span = this
|
||||
|
||||
#close expanded thread
|
||||
if span.textContent[0] is '-'
|
||||
#goddamit moot
|
||||
num = if board is 'b' then 3 else 5
|
||||
table = x("following::br[@clear][1]/preceding::table[#{num}]", span)
|
||||
while (prev = table.previousSibling) and (prev.nodeName is 'TABLE')
|
||||
remove(prev)
|
||||
span.textContent = span.textContent.replace('-', '+')
|
||||
return
|
||||
|
||||
span.textContent = span.textContent.replace('+', 'X Loading...')
|
||||
#load cache
|
||||
for xhr in xhrs
|
||||
if xhr.id == id
|
||||
#why can't we just xhr.r.onload()?
|
||||
onloadThread(xhr.r.responseText, span)
|
||||
return
|
||||
|
||||
#create new request
|
||||
r = new XMLHttpRequest()
|
||||
r.onload = ->
|
||||
onloadThread(this.responseText, span)
|
||||
r.open('GET', "res/#{id}", true)
|
||||
r.send()
|
||||
xhrs.push({
|
||||
r: r,
|
||||
id: id
|
||||
})
|
||||
|
||||
|
||||
onloadComment = (responseText, a, href) ->
|
||||
[_, op, id] = href.match(/(\d+)#(\d+)/)
|
||||
[replies, opbq] = parseResponse(responseText)
|
||||
if id is op
|
||||
html = opbq.innerHTML
|
||||
else
|
||||
#css selectors don't like ids starting with numbers,
|
||||
# getElementById only works for root document.
|
||||
for reply in replies
|
||||
if reply.id == id
|
||||
html = $('blockquote', reply).innerHTML
|
||||
bq = x('ancestor::blockquote', a)
|
||||
bq.innerHTML = html
|
||||
|
||||
|
||||
expandComment = (e) ->
|
||||
e.preventDefault()
|
||||
a = this
|
||||
href = a.getAttribute('href')
|
||||
r = new XMLHttpRequest()
|
||||
r.onload = ->
|
||||
onloadComment(this.responseText, a, href)
|
||||
r.open('GET', href, true)
|
||||
r.send()
|
||||
xhrs.push({
|
||||
r: r,
|
||||
id: href.match(/\d+/)[0]
|
||||
})
|
||||
|
||||
|
||||
report = ->
|
||||
input = x('preceding-sibling::input[1]', this)
|
||||
input.click()
|
||||
$('input[value="Report"]').click()
|
||||
input.click()
|
||||
|
||||
|
||||
nodeInserted = (e) ->
|
||||
target = e.target
|
||||
if target.nodeName is 'TABLE'
|
||||
for callback in callbacks
|
||||
callback(target)
|
||||
else if target.id is 'recaptcha_challenge_field' and qr = $ '#qr'
|
||||
$('#recaptcha_image img', qr).src = "http://www.google.com/recaptcha/api/image?c=" + target.value
|
||||
$('#recaptcha_challenge_field', qr).value = target.value
|
||||
|
||||
|
||||
autoWatch = ->
|
||||
#TODO look for subject
|
||||
autoText = $('textarea', this).value.slice(0, 25)
|
||||
GM_setValue('autoText', "/#{BOARD}/ - #{autoText}")
|
||||
|
||||
|
||||
stopPropagation = (e) ->
|
||||
e.stopPropagation()
|
||||
|
||||
|
||||
replyNav = ->
|
||||
if REPLY
|
||||
window.location = if @textContent is '▲' then '#navtop' else '#navbot'
|
||||
else
|
||||
direction = if @textContent is '▲' then 'preceding' else 'following'
|
||||
op = x("#{direction}::span[starts-with(@id, 'nothread')][1]", this).id
|
||||
window.location = "##{op}"
|
||||
|
||||
recaptchaReload = ->
|
||||
window.location = 'javascript:Recaptcha.reload()'
|
||||
|
||||
recaptchaListener = (e) ->
|
||||
if e.keyCode is 8 and this.value is ''
|
||||
recaptchaReload()
|
||||
|
||||
redirect = ->
|
||||
switch BOARD
|
||||
when 'a', 'g', 'lit', 'sci', 'tv'
|
||||
url = "http://green-oval.net/cgi-board.pl/#{BOARD}/thread/#{THREAD_ID}#p"
|
||||
when 'cgl', 'jp', 'm', 'tg'
|
||||
url = "http://archive.easymodo.net/cgi-board.pl/#{BOARD}/thread/#{THREAD_ID}#p"
|
||||
else
|
||||
url = "http://boards.4chan.org/#{BOARD}"
|
||||
location.href = url
|
||||
|
||||
#main part 2...
|
||||
AEOS.init()
|
||||
if navtopr = $ '#navtopr a'
|
||||
text = navtopr.nextSibling #css doesn't see text nodes
|
||||
a = n 'a',
|
||||
|
||||
1069
4chan_x.js
1069
4chan_x.js
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user