894 lines
26 KiB
CoffeeScript
894 lines
26 KiB
CoffeeScript
#todo: remove close()?, make hiddenReplies/hiddenThreads local, comments, gc
|
|
#todo: remove stupid 'obj', arr el, make hidden an object, smarter xhr, text(), @this, images, clear hidden
|
|
#todo: watch - add board in updateWatcher?, redundant move divs?, redo css / hiding, manual clear
|
|
#todo: hotkeys? navlink at top?
|
|
#thread watching doesn't work in opera?
|
|
|
|
config =
|
|
'Thread Hiding': true
|
|
'Reply Hiding': true
|
|
'Show Stubs': true
|
|
'Thread Navigation': true
|
|
'Reply Navigation': true
|
|
'Thread Watcher': true
|
|
'Thread Expansion': true
|
|
'Comment Expansion': true
|
|
'Quick Reply': true
|
|
'Persistent QR': false
|
|
'Quick Report': true
|
|
'Auto Watch': true
|
|
'Anonymize': false
|
|
getConfig = (name) ->
|
|
GM_getValue(name, config[name])
|
|
x = (path, root) ->
|
|
root or= document.body
|
|
document.
|
|
evaluate(path, root, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).
|
|
singleNodeValue
|
|
$ = (selector, root) ->
|
|
root or= document.body
|
|
root.querySelector(selector)
|
|
$$ = (selector, root) ->
|
|
root or= document.body
|
|
result = root.querySelectorAll(selector)
|
|
#magic that turns the results object into an array:
|
|
node for node in result
|
|
inBefore = (root, el) ->
|
|
root.parentNode.insertBefore(el, root)
|
|
inAfter = (root, el) ->
|
|
root.parentNode.insertBefore(el, root.nextSibling)
|
|
tag = (el) ->
|
|
document.createElement(el)
|
|
hide = (el) ->
|
|
el.style.display = 'none'
|
|
show = (el) ->
|
|
el.style.display = ''
|
|
remove = (el) ->
|
|
el.parentNode.removeChild(el)
|
|
replace = (root, el) ->
|
|
root.parentNode.replaceChild(el, root)
|
|
getTime = ->
|
|
Math.floor(new Date().getTime() / 1000)
|
|
n = (tag, props) -> #new
|
|
el = document.createElement tag
|
|
if props then (el[key] = val) for key, val of props
|
|
el
|
|
slice = (arr, id) ->
|
|
# the while loop is the only low-level loop left in coffeescript.
|
|
# we need to use it to see the index.
|
|
# would it be better to just use objects and the `delete` keyword?
|
|
i = 0
|
|
l = arr.length
|
|
while (i < l)
|
|
if id == arr[i].id
|
|
arr.splice(i, 1)
|
|
return arr
|
|
i++
|
|
position = (el) ->
|
|
id = el.id
|
|
if left = GM_getValue("#{id}Left", '0px')
|
|
el.style.left = left
|
|
else
|
|
el.style.right = '0px'
|
|
if top = GM_getValue("#{id}Top", '0px')
|
|
el.style.top = top
|
|
else
|
|
el.style.bottom = '0px'
|
|
|
|
|
|
# x-browser
|
|
if typeof GM_deleteValue == 'undefined'
|
|
this.GM_setValue = (name, value) ->
|
|
value = (typeof value)[0] + value
|
|
localStorage.setItem(name, value)
|
|
|
|
this.GM_getValue = (name, defaultValue) ->
|
|
if not value = localStorage.getItem(name)
|
|
return defaultValue
|
|
type = value[0]
|
|
value = value.substring(1)
|
|
switch type
|
|
when 'b'
|
|
return value == 'true'
|
|
when 'n'
|
|
return Number(value)
|
|
else
|
|
return value
|
|
|
|
this.GM_addStyle = (css) ->
|
|
style = tag('style')
|
|
style.type = 'text/css'
|
|
style.textContent = css
|
|
$('head', document).appendChild(style)
|
|
|
|
watched = JSON.parse(GM_getValue('watched', '{}'))
|
|
if location.hostname.split('.')[0] is 'sys'
|
|
if b = $('table font b')
|
|
GM_setValue('error', b.firstChild.textContent)
|
|
else
|
|
GM_setValue('error', '')
|
|
if GM_getValue('Auto Watch')
|
|
html = $('b').innerHTML
|
|
[nop, thread, id] = html.match(/<!-- thread:(\d+),no:(\d+) -->/)
|
|
if thread is '0'
|
|
board = $('meta', document).content.match(/4chan.org\/(\w+)\//)[1]
|
|
watched[board] or= []
|
|
watched[board].push({
|
|
id: id,
|
|
text: GM_getValue('autoText')
|
|
})
|
|
GM_setValue('watched', JSON.stringify(watched))
|
|
return
|
|
|
|
[nop, BOARD, magic] = location.pathname.split('/')
|
|
if magic is 'res'
|
|
REPLY = magic
|
|
else
|
|
PAGENUM = parseInt(magic) || 0
|
|
xhrs = []
|
|
r = null
|
|
iframeLoop = false
|
|
move = { }
|
|
callbacks = []
|
|
#godammit moot
|
|
head = $('head', document)
|
|
if not favicon = $('link[rel="shortcut icon"]', head)#/f/
|
|
favicon = tag('link')
|
|
favicon.rel = 'shortcut icon'
|
|
favicon.href = 'http://static.4chan.org/image/favicon.ico'
|
|
head.appendChild(favicon)
|
|
favNormal = favicon.href
|
|
favEmpty = 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw=='
|
|
|
|
hiddenThreads = JSON.parse(GM_getValue("hiddenThreads/#{BOARD}/", '[]'))
|
|
hiddenReplies = JSON.parse(GM_getValue("hiddenReplies/#{BOARD}/", '[]'))
|
|
|
|
lastChecked = GM_getValue('lastChecked', 0)
|
|
now = getTime()
|
|
DAY = 24 * 60 * 60
|
|
if lastChecked < now - 1*DAY
|
|
cutoff = now - 7*DAY
|
|
while hiddenThreads.length
|
|
if hiddenThreads[0].timestamp > cutoff
|
|
break
|
|
hiddenThreads.shift()
|
|
|
|
while hiddenReplies.length
|
|
if hiddenReplies[0].timestamp > cutoff
|
|
break
|
|
hiddenReplies.shift()
|
|
|
|
GM_setValue("hiddenThreads/#{BOARD}/", JSON.stringify(hiddenThreads))
|
|
GM_setValue("hiddenReplies/#{BOARD}/", JSON.stringify(hiddenReplies))
|
|
GM_setValue('lastChecked', now)
|
|
|
|
GM_addStyle('
|
|
#watcher {
|
|
position: absolute;
|
|
border: 1px solid;
|
|
}
|
|
#watcher div.move {
|
|
text-decoration: underline;
|
|
padding: 5px 5px 0 5px;
|
|
}
|
|
#watcher div:last-child {
|
|
padding: 0 5px 5px 5px;
|
|
}
|
|
span.error {
|
|
color: red;
|
|
}
|
|
#qr.auto:not(:hover) form {
|
|
visibility: collapse;
|
|
}
|
|
#qr span.error {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
}
|
|
#qr {
|
|
position: fixed;
|
|
border: 1px solid;
|
|
}
|
|
#qr > div {
|
|
text-align: right;
|
|
}
|
|
#qr > form > div {/* ad */
|
|
display: none;
|
|
}
|
|
#qr td.rules {
|
|
display: none;
|
|
}
|
|
#options {
|
|
position: fixed;
|
|
border: 1px solid;
|
|
padding: 5px;
|
|
text-align: right;
|
|
}
|
|
span.navlinks {
|
|
position: absolute;
|
|
right: 5px;
|
|
}
|
|
span.navlinks > a {
|
|
font-size: 16px;
|
|
text-decoration: none;
|
|
}
|
|
.move {
|
|
cursor: move;
|
|
}
|
|
.pointer, #options label, #options a {
|
|
cursor: pointer;
|
|
}
|
|
')
|
|
|
|
|
|
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 = []
|
|
|
|
|
|
options = ->
|
|
#redo this
|
|
if div = $('#options')
|
|
remove(div)
|
|
else
|
|
hiddenNum = hiddenReplies.length + hiddenThreads.length
|
|
div = tag('div')
|
|
div.id = 'options'
|
|
div.className = 'reply'
|
|
position(div)
|
|
html = '<div class="move">4chan X</div><div>'
|
|
for option of config
|
|
checked = if getConfig(option) then "checked" else ""
|
|
html += "<label>#{option}<input #{checked} name=\"#{option}\" type=\"checkbox\"></label><br>"
|
|
html += "<input type=\"button\" value=\"hidden: #{hiddenNum}\"><br>"
|
|
html += '<a name="save">save</a> <a name="cancel">cancel</a></div>'
|
|
div.innerHTML = html
|
|
$('div', div).addEventListener('mousedown', mousedown, true)
|
|
$('input[type="button"]', div).addEventListener('click', clearHidden, true)
|
|
$('a[name="save"]', div).addEventListener('click', optionsSave, true)
|
|
$('a[name="cancel"]', div).addEventListener('click', close, true)
|
|
document.body.appendChild(div)
|
|
|
|
|
|
mousedown = (e) ->
|
|
div = this.parentNode
|
|
move.div = div
|
|
move.clientX = e.clientX
|
|
move.clientY = e.clientY
|
|
move.bodyX = document.body.clientWidth
|
|
move.bodyY = document.body.clientHeight
|
|
|
|
# check if the string exists. parseInt('0px') is falsey.
|
|
l = div.style.left
|
|
move.divX = if l then parseInt(l) else move.bodyX - div.offsetWidth
|
|
t = div.style.top
|
|
move.divY = if t then parseInt(t) else move.bodyY - div.offsetHeight
|
|
window.addEventListener('mousemove', mousemove, true)
|
|
window.addEventListener('mouseup', mouseup, true)
|
|
|
|
|
|
mousemove = (e) ->
|
|
div = move.div
|
|
realX = move.divX + (e.clientX - move.clientX)# x + dx
|
|
left = if realX < 20 then 0 else realX
|
|
|
|
if move.bodyX - div.offsetWidth - realX < 20
|
|
div.style.left = ''
|
|
div.style.right = '0px'
|
|
else
|
|
div.style.left = left + 'px'
|
|
div.style.right = ''
|
|
|
|
realY = move.divY + (e.clientY - move.clientY)# y + dy
|
|
top = if realY < 20 then 0 else realY
|
|
|
|
if move.bodyY - div.offsetHeight - realY < 20
|
|
div.style.top = ''
|
|
div.style.bottom = '0px'
|
|
else
|
|
div.style.top = top + 'px'
|
|
div.style.bottom = ''
|
|
|
|
|
|
mouseup = ->
|
|
id = move.div.id
|
|
GM_setValue("#{id}Left", move.div.style.left)
|
|
GM_setValue("#{id}Top", move.div.style.top)
|
|
window.removeEventListener('mousemove', mousemove, true)
|
|
window.removeEventListener('mouseup', mouseup, true)
|
|
|
|
|
|
showThread = ->
|
|
div = this.nextSibling
|
|
show(div)
|
|
hide(this)
|
|
id = div.id
|
|
slice(hiddenThreads, id)
|
|
GM_setValue("hiddenThreads/#{BOARD}/", JSON.stringify(hiddenThreads))
|
|
|
|
|
|
hideThread = (div) ->
|
|
if p = this.parentNode
|
|
div = p
|
|
hiddenThreads.push({
|
|
id: div.id
|
|
timestamp: getTime()
|
|
})
|
|
GM_setValue("hiddenThreads/#{BOARD}/", JSON.stringify(hiddenThreads))
|
|
hide(div)
|
|
if getConfig('Show Stubs')
|
|
a = tag('a')
|
|
if span = $('.omittedposts', div)
|
|
n = Number(span.textContent.match(/\d+/)[0])
|
|
else
|
|
n = 0
|
|
n += $$('table', div).length
|
|
text = if n is 1 then "1 reply" else "#{n} replies"
|
|
name = $('span.postername', div).textContent
|
|
trip = $('span.postername + span.postertrip', div)?.textContent || ''
|
|
a.textContent = "[ + ] #{name}#{trip} (#{text})"
|
|
a.className = 'pointer'
|
|
a.addEventListener('click', showThread, true)
|
|
inBefore(div, a)
|
|
|
|
|
|
threadF = (current) ->
|
|
div = tag('div')
|
|
div.className = 'thread'
|
|
a = tag('a')
|
|
a.textContent = '[ - ]'
|
|
a.className = 'pointer'
|
|
a.addEventListener('click', hideThread, true)
|
|
div.appendChild(a)
|
|
|
|
inBefore(current, div)
|
|
while (!current.clear)#<br clear>
|
|
div.appendChild(current)
|
|
current = div.nextSibling
|
|
div.appendChild(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 = tag('a')
|
|
a.textContent = "[ + ] #{name} #{trip}"
|
|
a.className = 'pointer'
|
|
a.addEventListener('click', showReply, true)
|
|
div = tag('div')
|
|
div.appendChild(a)
|
|
inBefore(table, div)
|
|
|
|
|
|
optionsSave = ->
|
|
div = this.parentNode.parentNode
|
|
inputs = $$('input', div)
|
|
for input in inputs
|
|
GM_setValue(input.name, input.checked)
|
|
remove(div)
|
|
|
|
|
|
close = ->
|
|
div = this.parentNode.parentNode
|
|
remove(div)
|
|
|
|
|
|
iframeLoad = ->
|
|
if iframeLoop = !iframeLoop
|
|
return
|
|
$('iframe').src = 'about:blank'
|
|
|
|
qr = $('#qr')
|
|
if error = GM_getValue('error')
|
|
$('form', qr).style.visibility = ''
|
|
span = n 'span', {
|
|
textContent: error
|
|
className: 'error'
|
|
}
|
|
qr.appendChild(span)
|
|
else if REPLY and getConfig('Persistent QR')
|
|
$('textarea', qr).value = ''
|
|
else
|
|
remove qr
|
|
|
|
window.location = 'javascript:Recaptcha.reload()'
|
|
|
|
|
|
submit = (e) ->
|
|
if span = @nextSibling
|
|
remove(span)
|
|
recaptcha = $('#recaptcha_response_field', this)
|
|
if not recaptcha.value
|
|
e.preventDefault()
|
|
span = n 'span', {
|
|
className: 'error'
|
|
textContent: 'You forgot to type in the verification.'
|
|
}
|
|
@parentNode.appendChild span
|
|
alert 'You forgot to type in the verification.'
|
|
recaptcha.focus()
|
|
else
|
|
$('#qr input[title=autohide]:not(:checked)')?.click()
|
|
|
|
|
|
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 = tag('div')
|
|
qr.id = 'qr'
|
|
qr.className = 'reply'
|
|
position(qr)
|
|
|
|
div = tag('div')
|
|
div.innerHTML = 'Quick Reply '
|
|
div.className = 'move'
|
|
div.addEventListener('mousedown', mousedown, true)
|
|
qr.appendChild(div)
|
|
|
|
autohideB = n 'input', {
|
|
type: 'checkbox'
|
|
className: 'pointer'
|
|
title: 'autohide'
|
|
}
|
|
autohideB.addEventListener('click', autohide, true)
|
|
div.appendChild(autohideB)
|
|
div.appendChild(document.createTextNode(' '))
|
|
closeB = n 'a', {
|
|
textContent: 'X'
|
|
className: 'pointer'
|
|
title: 'close'
|
|
}
|
|
closeB.addEventListener('click', close, true)
|
|
div.appendChild(closeB)
|
|
|
|
form = $ 'form[name=post]'
|
|
clone = form.cloneNode(true)
|
|
#remove recaptcha scripts
|
|
for script in $$ 'script', clone
|
|
remove script
|
|
clone.addEventListener('submit', submit, true)
|
|
clone.target = 'iframe'
|
|
if not REPLY
|
|
xpath = 'preceding::span[@class="postername"][1]/preceding::input[1]'
|
|
input = n 'input', {
|
|
value: x(xpath, this).name
|
|
type: 'hidden'
|
|
name: 'resto'
|
|
}
|
|
clone.appendChild(input)
|
|
qr.appendChild(clone)
|
|
inBefore(document.body.firstChild, qr)
|
|
#XXX - put qr first in body, so `Recaptcha.reload()`,
|
|
# when using document.getElementById, sees and focuses this
|
|
# instead of the original.
|
|
|
|
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, nop, id] =
|
|
this.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 = tag('div')
|
|
for board of watched
|
|
for thread in watched[board]
|
|
a = tag('a')
|
|
a.textContent = 'X'
|
|
a.className = 'pointer'
|
|
a.addEventListener('click', watchX, true)
|
|
div.appendChild(a)
|
|
div.appendChild(document.createTextNode(' '))
|
|
link = tag('a')
|
|
link.textContent = thread.text
|
|
link.href = "/#{board}/res/#{thread.id}"
|
|
div.appendChild(link)
|
|
div.appendChild(tag('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
|
|
div.appendChild(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) ->
|
|
[nop, 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 = ->
|
|
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}"
|
|
|
|
|
|
#graceful exit
|
|
unless navtopr = $ '#navtopr a'
|
|
return
|
|
text = navtopr.nextSibling
|
|
a = tag('a')
|
|
a.textContent = 'X'
|
|
a.className = 'pointer'
|
|
a.addEventListener('click', options, true)
|
|
inBefore(text, document.createTextNode(' / '))
|
|
inBefore(text, a)
|
|
|
|
#hack to tab from comment straight to recaptcha
|
|
for el in $$ '#recaptcha_table a'
|
|
el.tabIndex = 1
|
|
|
|
if getConfig('Reply Hiding')
|
|
callbacks.push((root) ->
|
|
tds = $$('td.doubledash', root)
|
|
for td in tds
|
|
a = tag('a')
|
|
a.textContent = '[ - ]'
|
|
a.className = 'pointer'
|
|
a.addEventListener('click', hideReply, true)
|
|
replace(td.firstChild, a)
|
|
|
|
next = td.nextSibling
|
|
id = next.id
|
|
for obj in hiddenReplies
|
|
if obj.id is id
|
|
hideReply(next)
|
|
)
|
|
|
|
if getConfig('Quick Reply')
|
|
iframe = n 'iframe', {
|
|
name: 'iframe'
|
|
}
|
|
hide(iframe)
|
|
iframe.addEventListener('load', iframeLoad, true)
|
|
document.body.appendChild(iframe)
|
|
|
|
callbacks.push((root) ->
|
|
quotes = $$('a.quotejs:not(:first-child)', root)
|
|
for quote in quotes
|
|
quote.addEventListener('click', quickReply, true)
|
|
)
|
|
|
|
|
|
if getConfig('Quick Report')
|
|
callbacks.push((root) ->
|
|
arr = $$('span[id^=no]', root)
|
|
for el in arr
|
|
a = n 'a', {
|
|
textContent: '[ ! ]'
|
|
className: 'pointer'
|
|
}
|
|
a.addEventListener('click', report, true)
|
|
inAfter(el, a)
|
|
inAfter(el, document.createTextNode(' '))
|
|
)
|
|
|
|
if getConfig('Thread Watcher')
|
|
#create watcher
|
|
watcher = tag('div')
|
|
watcher.innerHTML = '<div class="move">Thread Watcher</div><div></div>'
|
|
watcher.className = 'reply'
|
|
watcher.id = 'watcher'
|
|
position(watcher)
|
|
$('div', watcher).addEventListener('mousedown', mousedown, true)
|
|
document.body.appendChild(watcher)
|
|
watcherUpdate()
|
|
|
|
#add buttons
|
|
threads = watched[BOARD] || []
|
|
#normal, threading
|
|
inputs = $$('form > input[value="delete"], div > input[value="delete"]')
|
|
for input in inputs
|
|
img = tag('img')
|
|
id = input.name
|
|
for thread in threads
|
|
if id == thread.id
|
|
img.src = favNormal
|
|
break
|
|
img.src or= favEmpty
|
|
img.className = 'pointer'
|
|
img.addEventListener('click', watch, true)
|
|
inBefore(input, img)
|
|
|
|
if getConfig('Anonymize')
|
|
callbacks.push((root) ->
|
|
names = $$('span.postername, span.commentpostername', root)
|
|
for name in names
|
|
name.innerHTML = 'Anonymous'
|
|
trips = $$('span.postertrip', root)
|
|
for trip in trips
|
|
if trip.parentNode.nodeName is 'A'
|
|
remove(trip.parentNode)
|
|
else
|
|
remove(trip)
|
|
)
|
|
|
|
if getConfig('Reply Navigation')
|
|
callbacks.push((root) ->
|
|
arr = $$('span[id^=norep]', root)
|
|
for el in arr
|
|
span = tag('span')
|
|
up = tag('a')
|
|
up.textContent = '▲'
|
|
up.className = 'pointer'
|
|
up.addEventListener('click', replyNav, true)
|
|
down = tag('a')
|
|
down.textContent = '▼'
|
|
down.className = 'pointer'
|
|
down.addEventListener('click', replyNav, true)
|
|
span.appendChild(document.createTextNode(' '))
|
|
span.appendChild(up)
|
|
span.appendChild(document.createTextNode(' '))
|
|
span.appendChild(down)
|
|
inAfter(el, span)
|
|
)
|
|
|
|
if REPLY
|
|
if getConfig('Quick Reply') and getConfig('Persistent QR')
|
|
quickReply()
|
|
$('#qr input[title=autohide]').click()
|
|
|
|
else # not reply
|
|
if getConfig('Thread Hiding')
|
|
delform = $('form[name=delform]')
|
|
#don't confuse other scripts
|
|
document.addEventListener('DOMNodeInserted', stopPropagation, true)
|
|
threadF(delform.firstChild)
|
|
document.removeEventListener('DOMNodeInserted', stopPropagation, true)
|
|
|
|
if getConfig('Auto Watch')
|
|
$('form[name="post"]').addEventListener('submit', autoWatch, true)
|
|
|
|
if getConfig('Thread Navigation')
|
|
arr = $$('div > span.filesize, form > span.filesize')
|
|
i = 0
|
|
l = arr.length
|
|
l1 = l + 1
|
|
#should this be a while loop?
|
|
for el in arr
|
|
up = tag('a')
|
|
up.className = 'pointer'
|
|
if i isnt 0
|
|
up.textContent = '▲'
|
|
up.href = "##{i}"
|
|
else if PAGENUM isnt 0
|
|
up.textContent = '◀'
|
|
up.href = "#{PAGENUM - 1}"
|
|
else
|
|
up.textContent = '▲'
|
|
up.href = "#navtop"
|
|
|
|
span = tag('span')
|
|
span.className = 'navlinks'
|
|
span.id = ++i
|
|
i1 = i + 1
|
|
down = tag('a')
|
|
down.className = 'pointer'
|
|
span.appendChild(up)
|
|
span.appendChild(document.createTextNode(' '))
|
|
span.appendChild(down)
|
|
if i1 == l1
|
|
down.textContent = '▶'
|
|
down.href = "#{PAGENUM + 1}#1"
|
|
else
|
|
down.textContent = '▼'
|
|
down.href = "##{i1}"
|
|
inBefore(el, span)
|
|
if location.hash is '#1'
|
|
window.location = window.location
|
|
|
|
if getConfig('Thread Expansion')
|
|
omitted = $$('span.omittedposts')
|
|
for span in omitted
|
|
a = tag('a')
|
|
a.className = 'pointer omittedposts'
|
|
a.textContent = "+ #{span.textContent}"
|
|
a.addEventListener('click', expandThread, true)
|
|
replace(span, a)
|
|
|
|
if getConfig('Comment Expansion')
|
|
as = $$('span.abbr a')
|
|
for a in as
|
|
a.addEventListener('click', expandComment, true)
|
|
|
|
for callback in callbacks
|
|
callback()
|
|
document.body.addEventListener('DOMNodeInserted', nodeInserted, true)
|