There are 39 previous versions of this script.
Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)
// ==UserScript==
// @name 4chan X
// @namespace aeosynth
// @include http://cgi.4chan.org/*
// @include http://img.4chan.org/*
// @include http://orz.4chan.org/*
// @include http://zip.4chan.org/*
// @description Lightweight, featureful alternative to the 4chan extension / fychan
// @version 0.20.1
// @copyright 2009, James Campos
// @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
// ==/UserScript==
//TODO
// tw - bold when threads watched
// tw - manual label
// tw - show thread died
// tw - respawn/refresh
// try objects for movement, watched threads
// 'restart updating' button
// trim ()
// quick quote - highlighted text appended to quotes
// qr restore
// following a quote breaks watcher?
// update notification don't reset if page is too small to scroll
// only show 'new posts' if filter didn't hide them
// quick spoiler
// issues when below /b/ackwash
(function () {// <-- Opera wrapper
function inBefore (root, el) {
root.parentNode.insertBefore(el, root)
}
function inAfter (root, el) {
root.parentNode.insertBefore(el, root.nextSibling)
}
function remove (el) {
el.parentNode.removeChild (el)
}
function tag (el) {
return document.createElement (el)
}
function text (el) {
return document.createTextNode (el)
}
function $ (selector, root) {
if (!root) root = document.body
return root.querySelector(selector)
}
function $$ (selector, root) {
if (!root) root = document.body
var result = root.querySelectorAll(selector)
var a = []
for (var i = 0, l = result.length; i < l; i++)
a.push(result[i])
return a
}
function x (xpath, root) {
if (!root) root = document.body
return document.evaluate(xpath, root, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue
}
function X (xpath, root) {
if (!root) root = document.body
var result = document.evaluate(xpath, root, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null)
var a = [], item
while (item = result.iterateNext())
a.push (item)
return a
}
if (/tmp|bin|dat|nov/.test(window.location.hostname)) {//quick reply error checking
var body = document.body
var error = $('table b')
if (error)
GM_setValue('error', error.firstChild.textContent)
else if (!/Updating page/.test(body.textContent))//nginx
GM_setValue('error', body.textContent)
else
GM_setValue('error', '')
}
const form = x('./form')
if (!form)
return
const pathname = window.location.pathname
const board = pathname.match(/\/\w+\//)[0]
const reply = /res/.test(pathname)
var threadHidden = GM_getValue(board + 'thread', '')
var watchedBoards = GM_getValue('watchedBoards', '')
var threadSpans = $$('span[id^=nothread]', form)
if (GM_getValue('Forced Anon')) {
anonymize (form)
form.addEventListener('DOMNodeInserted', function(e){ if (e.target.nodeName == 'TABLE') anonymize(e.target) }, true)
}
function anonymize (root) {
var names = $$('.commentpostername, .postername', root)
for (var i = 0, l = names.length; i < l; i++)
names[i].innerHTML = 'Anonymous'
var trips = $$('.postertrip', root)
for (var i = 0, l = trips.length; i < l; i++)
remove (trips[i])
}
var filesize = $$('form > span.filesize')
if (!reply && GM_getValue('Thread Navigating', true))
for (var i = 0, l = filesize.length; i < l; i++) {
var nav = tag ('span')
nav.className = 'navlinks'
var top = tag ('a')
top.name = i
top.href = '#' + (i ? i - 1 : 'navtop')
top.textContent = '▲'
var bot = tag ('a')
if (i == l - 1) {
bot.href = 'http://' + window.location.hostname + board + (pathname.match(/\d+$/) + 1) + '.html'
bot.textContent = '▶'
} else {
bot.href = '#' + (i + 1)
bot.textContent = '▼'
}
nav.appendChild(top)
nav.appendChild(text(' '))
nav.appendChild(bot)
inBefore(filesize[i], nav)
}
if (!reply && GM_getValue('Thread Hiding', true))
for (var i = 0, item; item = filesize[i]; i++) {
var div = tag ('div')
div.id = x('./span[starts-with(@id, "nothread")]', form).id
div.innerHTML = '<a style = "cursor: pointer" title = "Hide Thread">[ - ]</a> '
div.firstChild.addEventListener('click', hideThread, true)
inBefore (item, div)
while (!item.clear) {//<br clear="left"/>
div.appendChild(item)
item = div.nextSibling
}
if (threadHidden.indexOf(div.id) != -1)
hideThread (div)
}
function hideThread (div) {
if (this.nodeName) {
div = this.parentNode
threadHidden += div.id + '\n'
GM_setValue(board + 'thread', threadHidden)
}
div.style.display = 'none'
var a = tag ('a')
a.style.cursor = 'pointer'
a.textContent = '[ + ]'
a.title = 'Show Thread'
a.addEventListener('click', showThread, true)
var span = tag ('span')
span.appendChild(a)
span.appendChild(text (' '))
span.appendChild($('.filetitle', div).cloneNode(true))
span.appendChild(text (' '))
span.appendChild(text ($('blockquote', div).textContent.slice(0, 25)))
if (!GM_getValue ('Show Stubs', true)) {
div.nextSibling.style.display = 'none'
div.nextSibling.nextSibling.style.display = 'none'
var prev = div.previousSibling
if (prev.nodeName == 'SPAN')//Thread nav
prev.style.display = 'none'
span.style.display = 'none'
}
inBefore (div, span)
}
function showThread () {
var div = this.parentNode.nextSibling
div.style.display = ''
threadHidden = threadHidden.replace(div.id + '\n', '')
GM_setValue(board + 'thread', threadHidden)
remove (this.parentNode)
}
if (GM_getValue('Thread Watcher', true)) {
var watcher = tag ('div')
watcher.innerHTML = '\
<div>\
Watched Threads\
</div>\
<div>\
</div>'
watcher.id = 'tw'
watcher.className = 'reply'
watcher.style.display = GM_getValue('watcherDisplay', '')
var top = GM_getValue('tw_top', '0px')
if (top)
watcher.style.top = top
else
watcher.style.bottom = '0px'
var left = GM_getValue('tw_left', '0px')
if (left)
watcher.style.left = left
else
watcher.style.right = '0px'
watcher.firstChild.addEventListener('mousedown', startMove, true)
var tw = tag ('a')
tw.textContent = 'TW'
tw.className = 'pointer'
tw.addEventListener('click', twF, true)
var navtop = $('#navtop')
inBefore(navtop.lastChild, text(' / '))
inBefore(navtop.lastChild, tw)
var re = /(.+)\n(.+)/g
var tempArray
var matches = watchedBoards.match(/\/\w+\//g)
for (i in matches) {
var tempWatched = GM_getValue(matches[i] + 'watched', '')
while (tempArray = re.exec(tempWatched))
addToWatcher (tempArray[1], tempArray[2])
}
document.body.appendChild(watcher)
var watched = GM_getValue (board + 'watched', '')
var threadSpans = $$('span[id^=nothread]', form)
for (var i = 0, el; el = threadSpans[i]; i++) {
var watch = tag ('a')
watch.textContent = watched.indexOf(el.id.match(/\d+/)[0]) > -1 ? 'Unwatch' : 'Watch'
watch.addEventListener('click', watchThread, true)
watch.className = 'pointer'
inAfter(el, watch)
inAfter(el, text(' '))
}
}
function twF () {
var watcherDisplay = watcher.style.display ? '' : 'none'
GM_setValue ('watcherDisplay', watcherDisplay)
watcher.style.display = watcherDisplay
}
function addToWatcher (url, txt) {
var x = tag ('a')
x.textContent = 'X'
x.addEventListener('click', watchThread, true)
var a = tag ('a')
a.href = url
a.textContent = txt
var span = tag ('span')
span.appendChild(x)
span.appendChild(text(' '))
span.appendChild(a)
span.appendChild(tag ('br'))
watcher.lastChild.appendChild(span)
}
function watchThread() {
if (this.textContent == 'X')
var url = this.nextSibling.nextSibling.href
else {
if (reply)
url = window.location.href.match(/[^#]+/)[0]
else
url = x("preceding-sibling::span[1]/a[3]", this).href
}
if (this.textContent == 'Watch') {
this.textContent = 'Unwatch'
var txt = x ('preceding-sibling::span[@class="filetitle"]', this).textContent//First look for subject
if (!txt) {//then OP's text
txt = x ('following-sibling::blockquote', this).textContent
if (txt)
txt = txt.slice(0,25)
else {// finally OP's name/trip
txt = x ('preceding-sibling::span[@class="postername"]', this).textContent
var temp = x ('preceding-sibling::span[@class="postertrip"]', this)
if (temp)
txt += temp.textContent
}
}
addToWatcher (url, txt)
var watched = GM_getValue (board + 'watched', '')
watched += url + '\n' + txt + '\n\n'
GM_setValue(board + 'watched', watched)
if (watchedBoards.indexOf(board) == -1)
GM_setValue('watchedBoards', watchedBoards + board)
} else {
var tempBoard = url.match(/\/\w+\//)[0]
if (this.textContent == 'Unwatch')
this.textContent = 'Watch'
else if (tempBoard == board) {// X button pressed, Unwatch button may or may not be present
var watch = $('span#nothread' + url.match(/\/(\d+)/)[1] + ' + a', form)
if (watch)
watch.textContent = 'Watch'
}
var tempWatched = GM_getValue(tempBoard + 'watched', '')
var re = new RegExp(url + '\n.+\n\n')
tempWatched = tempWatched.replace(re, '')
if (tempWatched)
GM_setValue(tempBoard + 'watched', tempWatched)
else {
GM_deleteValue (tempBoard + 'watched')
watchedBoards = watchedBoards.replace (tempBoard, '')
GM_setValue ('watchedBoards', watchedBoards)
}
Array.forEach($$('a', watcher), function(el){ if(el.href == url) remove(el.parentNode) })
}
}
if (GM_getValue('Thread Expansion', true)) {
var omitted = $$('.omittedposts', form)
for(var i = 0, omit; omit = omitted[i]; i++){
var plus = tag ('a')
plus.title = 'Load omitted posts'
plus.textContent = '+'
plus.className = 'load'
plus.addEventListener('click', expandThread, true)
inBefore(omit, plus)
inBefore(omit, text(' '))
}
}
function expandThread () {
var text = this.nextSibling
if (this.textContent == 'x') {
text.textContent = ' '
this.textContent = '+'
return this.title = 'Expand Thread'
}
var omitted = text.nextSibling
var l = parseInt(omitted.textContent)//XXX what about deleted posts? YOU SHUT YOUR WHORE MOUTH
if (this.textContent == '-') {
for (var i = 0; i < l; i++)
remove (omitted.nextElementSibling)
this.textContent = '+'
return this.title = 'Expand Thread'
}
text.textContent = ' Loading... '
this.textContent = 'x'
this.title = 'Cancel Expansion'
var url = x("preceding-sibling::span[starts-with(@id, 'nothread')]/a[contains(text(), 'Reply')]", this).href
xThread(text, url)
}
function xThread (text, url) {
var r = new XMLHttpRequest ()
r.onreadystatechange = function() {
if (this.readyState == 4 && text.textContent == ' Loading... ') {
if (this.status == 200) {
text.textContent = ' '
text.previousSibling.textContent = '-'
text.previousSibling.title = 'Contract Thread'
const newPosts = this.responseText.match(/<a name=.*?>\n<table>[^]*?<\/table>/g)
const first = x("following-sibling::a", text)
var l = parseInt(text.nextSibling.textContent)
for (var i = 0; i < l; i++) {
var table = tag ('table')
table.innerHTML = newPosts[i].match(/<table>([^]*?)<\/table>/)[1]
inBefore(first, table)
}
} else {
text.textContent = ' ' + this.status + ' ' + this.statusText + ' '
text.previousSibling.textContent = '+'
text.previousSibling.title = 'Expand Thread'
}
}
}
r.open('GET', url, true)
r.send(null)
}
if (GM_getValue('Post Navigating', true)) {
addThreadStartButton (form)
form.addEventListener('DOMNodeInserted', function(e){ if (e.target.nodeName == 'TABLE') addThreadStartButton(e.target) }, true)
}
function addThreadStartButton (root) {
var spans = $$('[id^=norep]', root)
for (var i = 0, el; el = spans[i]; i++) {
var bottom = tag ('a')
bottom.textContent = '▼'
bottom.style.cursor = 'pointer'
bottom.addEventListener('click',
function () {
var temp = x("following::span[starts-with(@id, 'nothread')][1]", this)
window.location = '#' + (temp ? temp.id : 'navbot')
},
true)
inAfter(el, bottom)
inAfter(el, text(' '))
var top = tag ('a')
top.textContent = '▲'
top.style.cursor = 'pointer'
top.addEventListener('click',
function () { window.location = '#' + x("preceding::span[starts-with(@id, 'nothread')][1]", this).id },
true)
inAfter(el, top)
inAfter(el, text(' '))
}
}
if (GM_getValue('Report Button')) {
addReportButton (form)
form.addEventListener('DOMNodeInserted', function(e){ if (e.target.nodeName == 'TABLE') addReportButton(e.target) }, true)
}
function addReportButton (root) {
var no = $$('span[id^=no]', root)
for (var i = 0, l = no.length; i < l; i++) {
var report = tag ('a')
report.className = 'pointer'
report.textContent = '[ ! ]'
report.addEventListener('click', reportF, true)
inAfter (no[i], report)
inAfter (no[i], text (' '))
}
}
function reportF () {
x('preceding-sibling::input', this).click ()
$('.deletebuttons input[type=button]').click ()
}
if (GM_getValue('Post Expansion', true)) {
var abbr = $$('.abbr a')
for (var i = 0, el; el = abbr[i]; i++)
el.addEventListener('click', xPost, true)
}
function xPost (e) {
e.preventDefault()
var el = this.parentNode
el.textContent = 'Loading...'
var url = this.href
var r = new XMLHttpRequest ()
r.onreadystatechange = function() {
if (this.readyState == 4){
if(this.status == 200) {
var id = url.match(/\d+$/)[0]
var re = new RegExp('<a.*?name="' + id + '[^]*?<blockquote>([^]*?)<\/blockquote>', '')
el.parentNode.innerHTML = this.responseText.match(re)[1]
} else
el.textContent = this.status + ' ' + this.statusText
}
}
r.open('GET', url, true)
r.send(null)
}
if (GM_getValue('Quick Reply', true)) {
var iframe = tag ('iframe')
iframe.name = 'iframe'
var iframeReturn = false
iframe.style.display = 'none'
document.body.appendChild(iframe)
iframe.addEventListener('load', doIframe, true)
addQR(document.body)
form.addEventListener('DOMNodeInserted', function(e){ if (e.target.nodeName == 'TABLE') addQR(e.target) }, true)
if (reply && GM_getValue('Quick Post'))
$('.postarea > form').target = 'iframe'
}
function doIframe () {
if (iframeReturn = !iframeReturn)
return//stop infinite loop when loading about:blank
var qr = $('#qr')
if (qr) {
var error = GM_getValue('error', '')
if (error) {
qr.lastChild.style.visibility = ''
var span = tag('span')
span.textContent = error
span.className = 'error'
qr.appendChild(span)
} else
remove (qr)
}
iframe.src = 'about:blank'
}
function addQR (el) {
var quotelinks = $$('span[id^=no] > a:nth-child(2)', el)
for (var i = 0, l = quotelinks.length; i < l; i++)
quotelinks[i].addEventListener('click', qr, true)
}
function qr (e) {//this could also be wrapped up in an object
e.preventDefault()
var qr = $('#qr')
if (!qr) {
qr = tag ('div')
qr.id = 'qr'
qr.className = 'reply'
var top = GM_getValue('qr_top', '0px')
if (top)
qr.style.top = top
else
qr.style.bottom = '0px'
var left = GM_getValue('qr_left', '0px')
if (left)
qr.style.left = left
else
qr.style.right = '0px'
qr.innerHTML = '\
<div style="cursor: move">\
Quick Reply \
</div>'
qr.firstChild.addEventListener('mousedown', startMove, true)
var qr_shade = tag ('a')
qr_shade.textContent = '_'
qr_shade.addEventListener('click', function () {
var qr_form = this.parentNode.nextSibling
qr_form.style.visibility = qr_form.style.visibility ? '' : 'collapse'
},
true)
var qr_close = tag ('a')
qr_close.textContent = 'X'
qr_close.addEventListener('click', function () {remove(qr)}, true)
qr.firstChild.appendChild(qr_shade)
qr.firstChild.appendChild(text(' '))
qr.firstChild.appendChild(qr_close)
var qr_form = $('.postarea > form').cloneNode(true)
remove ($('tr:last-child', qr_form))
qr_form.target = 'iframe'
if (!reply) {
var input = tag ('input')
input.name = 'resto'
input.type = 'hidden'
var id = this.parentNode.id
if (!/thread/.test(id))
id = x('preceding::span[starts-with(@id, "nothread")][1]', this).id
input.value = id.match(/\d+$/)[0]
qr_form.appendChild(input)
}
qr_form.addEventListener('submit',
function () {
this.style.visibility = 'collapse'
var span = this.nextSibling
if (span)
remove(span)
}, true)
qr.appendChild (qr_form)
document.body.appendChild(qr)
}
var textArea = $('textArea', qr)
var selection = window.getSelection()
var selAnchor = selection.anchorNode
if (selAnchor && selAnchor.parentNode.parentNode == this.parentNode.parentNode)
var selText = selection.toString()
textArea.value += '>>' + this.textContent.match(/\d+/) + '\n' + (selText ? '>' + selText + '\n' : '')
textArea.scrollTop = textArea.scrollHeight
textArea.focus()
}
const update_interval = GM_getValue('Interval', 30) * 1000
var last_modified = new Date(document.lastModified).toUTCString()
var pendingRequest = false
if (reply) {
var replies = $$('td.reply')
var fav = tag('link')
fav.type = 'image/x-icon'
fav.rel = 'shortuct icon'
fav.href = '/favicon.ico'
$('head', document).appendChild(fav)
var threadDied = false
window.addEventListener('scroll',
function() {
if (window.scrollY > window.scrollMaxY - 100)
favicon()
},
true)
if (GM_getValue('Show Updater', true) || GM_getValue('Auto-start Updater')) {
document.title = '(0) ' + document.title
scroll()
window.addEventListener('scroll', scroll, true)
}
if(GM_getValue('Show Updater', true)) {
var updater = tag('div')
updater.id = 'updater'
var left = GM_getValue('updater_left')
if (left)
updater.style.left = left
else
updater.style.right = '0px'
var top = GM_getValue('updater_top')
if (top)
updater.style.top = top
else
updater.style.bottom = '0px'
updater.className = 'reply'
var updaterC = tag('div')//updaterChild. my move function requires it.
updater.appendChild(updaterC)
if (GM_getValue('Auto-start Updater')) {
updaterC.textContent = 'On'
var int = setTimeout(xUpdate, update_interval)
} else
updaterC.textContent = 'Off'
updaterC.addEventListener('click', toggleUpdating, true)
updaterC.addEventListener('mousedown', startMove, true)
document.body.appendChild(updater)
} else if (GM_getValue('Auto-start Updater'))
int = setTimeout(xUpdate, update_interval)
}
function favicon (state) {
if (!GM_getValue('Update Favicon', true))
return
var newFav = fav.cloneNode(true)
switch (state) {
case 'new'://halo
newFav.href = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEUAAABmzDP///8AAABet0i+AAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII='
break
case 'dead'://new posts ? red halo : red
newFav.href = replies.length ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAWUlEQVR4XrWSAQoAIAgD/f+njSApsTqjGoTQ5oGWPJMOOs60CzsWwIwz1I4PUIYh+WYEMGQ6I/txw91kP4oA9BdwhKp1My4xQq6e8Q9ANgDJjOErewFiNesV2uGSfGv1/HYAAAAASUVORK5CYII='
: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAACVBMVEUAAAAAAAD/AAA9+90tAAAAAXRSTlMAQObYZgAAADtJREFUCB0FwUERxEAIALDszMG730PNSkBEBSECoU0AEPe0mly5NWprRUcDQAdn68qtkVsj3/84z++CD5u7CsnoBJoaAAAAAElFTkSuQmCC'
threadDied = true
break
default://threadDied ? red : default
newFav.href = threadDied ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAACVBMVEUAAAAAAAD/AAA9+90tAAAAAXRSTlMAQObYZgAAADtJREFUCB0FwUERxEAIALDszMG730PNSkBEBSECoU0AEPe0mly5NWprRUcDQAdn68qtkVsj3/84z++CD5u7CsnoBJoaAAAAAElFTkSuQmCC'
: '/favicon.ico'
}
fav.parentNode.replaceChild(newFav, fav)
fav = newFav
}
function toggleUpdating() {
if (this.textContent != 'Off') {
this.textContent = 'Off'
clearInterval(int)
} else {
this.textContent = 'On'
if (!(pendingRequest))
int = setTimeout(xUpdate, update_interval)
}
}
function xUpdate () {
pendingRequest = true
if (updaterC)
updaterC.textContent = 'Updating...'
var r = new XMLHttpRequest ()
r.open ('GET', pathname)
r.onreadystatechange = function () {
if (this.readyState == 4) {
switch (this.status) {// 200 = ok, 304 = not modified, other = ;_;
case 200:
last_modified = this.getResponseHeader('Last-Modified')
const newPosts = this.responseText.match(/<table>[^]*?<\/table>/g)
var lastOld = x('.//br[@clear]/preceding-sibling::*[1]')
var oldId = lastOld.nodeName == 'TABLE' ? $('.reply, .replyhl', lastOld).id : 0
var i = newPosts.length - 1
var newPost = newPosts[i]
if (oldId < newPost.match(/\d+/)[0]) {
favicon('new')
do {
var table = tag ('table')
table.innerHTML = newPost.match(/<table>([^]*?)<\/table>/)[1]
inAfter (lastOld, table)
replies.push(table)
i--
} while ((newPost = newPosts[i]) && (oldId < newPost.match(/\d+/)[0]))
}
document.title = document.title.replace(/\d+/, replies.length)
case 304:
if (!updaterC || updaterC.textContent != 'Off')
int = setTimeout(xUpdate, update_interval)
break
default:
try {// thread died
document.title = '(' + replies.length + ') ' + board + ' - ' + this.status + ' ' + this.statusText
//document.title = (/^\* /.test(document.title) ? '* ':'') + board + ' - ' + this.status + ' ' + this.statusText
var inputs = $$('.postarea > form input[type=submit]')
for (var i = 0, l = inputs.length; i < l; i++)
inputs[i].disabled = 'disabled'
favicon('dead')
} catch (e) {// connection error
if (!updaterC || updaterC.textContent != 'Off')
int = setTimeout(xUpdate, update_interval)
}
}
pendingRequest = false
if (updaterC)
updaterC.textContent = this.status + ' ' + this.statusText
}
}
r.setRequestHeader ('If-Modified-Since', last_modified)
r.send ()
}
function scroll () {
var height = document.body.clientHeight
var unread = false
for (var i in replies)
if (replies[i].getBoundingClientRect().top > height - 50) {
unread = true
break
}
if (unread)
replies = replies.splice(i)
else
replies = []
document.title = document.title.replace(/\d+/, replies.length)
}
GM_registerMenuCommand('4chan X Options', options)
function options () {
var div = tag ('div')
div.id = 'options'
div.className = 'reply'
var top = GM_getValue('options_top', document.body.clientHeight / 2)
if (top)
div.style.top = top
else
div.style.bottom = '0px'
var left = GM_getValue('options_left', document.body.clientWidth / 2)
if (left)
div.style.left = left
else
div.style.right = '0px'
div.innerHTML = '\
<div>4chan X</div>\
<div>\
<label>Forced Anon<input type = "checkbox"></label><br>\
<label>Report Button<input type = "checkbox"></label><br>\
<label>Quick Post<input type = "checkbox"></label><br>\
<label>Quick Reply<input type = "checkbox" checked="true"></label><br>\
<label>Show Stubs<input type = "checkbox" checked="true"></label><br>\
<label>Post Expansion<input type = "checkbox" checked="true"></label><br>\
<label>Post Navigating<input type = "checkbox" checked="true"></label><br>\
<label>Thread Watcher<input type = "checkbox" checked="true"></label><br>\
<label>Thread Hiding<input type="checkbox" checked="true"></label><br>\
<label>Thread Expansion<input type = "checkbox" checked="true"></label><br>\
<label>Thread Navigating<input type = "checkbox" checked="true"></label><br>\
<label>Show Updater<input type = "checkbox" checked="true"></label><br>\
<label>Auto-start Updater<input type = "checkbox"></label><br>\
<label>Update Favicon<input type = "checkbox" checked="true"></label><br>\
Interval (s)<input size = 3 maxlength = 5><br>\
<input type = "button" value = "Clear Hidden"><br>\
<a>save</a> <a>cancel</a>\
</div>'
div.firstChild.addEventListener('mousedown', startMove, true)
var temp = $$('input[type=checkbox]', div)
for (var i = 0, l = temp.length; i < l; i++)
temp[i].checked = GM_getValue(temp[i].parentNode.textContent, temp[i].checked)
temp = $('input[size]', div)
temp.value = GM_getValue('Interval', 30)
temp = $('input[type=button]', div)
temp.addEventListener('click',
function () {
GM_deleteValue (board + 'thread')
GM_deleteValue (board + 'reply')
},
true)
temp = $$('a', div)
temp[0].addEventListener('click', function () {//save
var div = this.parentNode.parentNode
var temp = $$('input[type=checkbox]', div)
for (var i = 0, l = temp.length; i < l; i++)
GM_setValue(temp[i].parentNode.textContent, temp[i].checked)
temp = $('input[size]', div)
if (!temp.value.length || isNaN(temp.value) || temp.value < 0)
temp.value = 20
GM_setValue('Interval', temp.value)
remove (div)
},
true)
temp[1].addEventListener('click', function () {//cancel
var div = this.parentNode.parentNode
remove (div)
},
true)
document.body.appendChild(div)
}
var initial_mouseX
var initial_mouseY
var initial_boxX
var initial_boxY
var el
function startMove (event) {//TODO wrap this up in an object
el = this.parentNode
initial_mouseX = event.clientX
initial_mouseY = event.clientY
if (el.style.right)
initial_boxX = 0
else
initial_boxX = document.body.clientWidth - el.offsetWidth - parseInt(el.style.left)
if (el.style.bottom)
initial_boxY = document.body.clientHeight - el.offsetHeight
else
initial_boxY = parseInt(el.style.top)
document.addEventListener('mousemove', move, true)
document.addEventListener('mouseup', endMove, true)
}
function move (event) {
var right = initial_boxX + initial_mouseX - event.clientX
var left = document.body.clientWidth - el.offsetWidth - right
el.style.right = ''
if (right < 15) {
el.style.right = 0
el.style.left = ''
} else if (left < 15)
el.style.left = 0
else
el.style.left = left + 'px'
var top = initial_boxY - initial_mouseY + event.clientY
var bottom = document.body.clientHeight - el.offsetHeight - top
el.style.bottom = ''
if (bottom < 15) {
el.style.bottom = 0
el.style.top = ''
} else if (top < 15)
el.style.top = 0
else
el.style.top = top + 'px'
}
function endMove () {
document.removeEventListener('mousemove', move, true)
document.removeEventListener('mouseup', endMove, true)
GM_setValue(el.id + '_left', el.style.left)
GM_setValue(el.id + '_top', el.style.top)
}
GM_addStyle ("\
.error { color: red; }\
.navlinks { position: absolute; right: 5px }\
.navlinks > a { text-decoration: none; font-size: 16px;}\
.load { font-size: 16px; cursor: pointer; font-weight: bold }\
.pointer { cursor: pointer; }\
#qr { position: fixed; border: 1px ridge; color: inherit; text-align: right; }\
#qr a { cursor: pointer; }\
#qr > span { position: absolute; bottom: 0; left: 0; }\
#options { position: fixed; border: 1px ridge; color: inherit; text-align: right; }\
#options a,\
#options label { cursor: pointer; }\
#options div:first-child { cursor: move; padding: 5px 0 0 0; text-align: center; text-decoration: underline; }\
#tw { position: absolute; border: 1px ridge; color: inherit; }\
#tw a { cursor: pointer; }\
#tw div:first-child { text-decoration: underline; }\
#tw div:first-child { cursor: move; padding: 5px 5px 0 5px; }\
#tw div:last-child,\
#options div:last-child { padding: 0 5px 5px 5px; }\
#updater { position:fixed; border: 1px solid; cursor:pointer; padding:10px; color:inherit; }\
#updater:active { cursor:move; }\
")
}) ()
