// ==UserScript== // @name 4chan x // @namespace aeosynth // @description Adds various features. // @version 1.27.8.2 // @copyright 2009-2011 James Campos // @license MIT; http://en.wikipedia.org/wiki/Mit_license // @include http://boards.4chan.org/* // @include http://sys.4chan.org/* // @include file://* // @updateURL http://userscripts.org/scripts/source/51412.meta.js // ==/UserScript== /* LICENSE * * Copyright (c) 2009-2011 James Campos * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ /* HACKING * * 4chan x is written in CoffeeScript[1], and developed on github[2]. * * [1]: http://jashkenas.github.com/coffee-script/ * [2]: http://github.com/aeosynth/4chan-x */ /* CONTRIBUTORS * * Mayhem - fix updater default options * Ongpot - sfw favicon * thisisanon - nsfw + 404 favicons * Anonymous - empty favicon * Seiba - chrome quick reply focusing * herpaderpderp - recaptcha fixes * wakimoko - recaptcha tab order http://userscripts.org/scripts/show/82657 * All the people who've taken the time to write bug reports. * * Thank you. */ (function() { var $, $$, Favicon, NAMESPACE, Recaptcha, anonymize, config, d, expandComment, expandThread, g, imageHover, imgExpand, imgGif, imgPreloading, keybinds, localize, log, main, nav, nodeInserted, options, qr, quickReport, redirect, replyHiding, sauce, threadHiding, titlePost, ui, unread, updater, watcher, _config, _ref; var __slice = Array.prototype.slice; if (typeof console !== "undefined" && console !== null) { log = function(arg) { return console.log(arg); }; } config = { main: { checkbox: { '404 Redirect': [true, 'Redirect dead threads'], 'Anonymize': [false, 'Make everybody anonymous'], 'Auto Watch': [true, 'Automatically watch threads that you start'], 'Comment Expansion': [true, 'Expand too long comments'], 'Image Auto-Gif': [false, 'Animate gif thumbnails'], 'Image Expansion': [true, 'Expand images'], 'Image Hover': [false, 'Show full image on mouseover'], 'Image Preloading': [false, 'Preload Images'], 'Keybinds': [false, 'Binds actions to keys'], 'Localize Time': [true, 'Show times based on your timezone'], 'Persistent QR': [false, 'Quick reply won\'t disappear after posting. Only in replies.'], 'Post in Title': [true, 'Show the op\'s post in the tab title'], 'Quick Reply': [true, 'Reply without leaving the page'], 'Quick Report': [true, 'Add quick report buttons'], 'Reply Hiding': [true, 'Hide single replies'], 'Sauce': [true, 'Add sauce to images'], 'Show Stubs': [true, 'Of hidden threads / replies'], 'Thread Expansion': [true, 'View all replies'], 'Thread Hiding': [true, 'Hide entire threads'], 'Thread Navigation': [true, 'Navigate to previous / next thread'], 'Thread Updater': [true, 'Update threads'], 'Thread Watcher': [true, 'Bookmark threads'], 'Unread Count': [true, 'Show unread post count in tab title'] } }, updater: { checkbox: { 'Verbose': [true, 'Show countdown timer, new post count'], 'Auto Update': [false, 'Automatically fetch new posts'] }, 'Interval': 30 } }; _config = {}; (function(parent, obj) { var key, val, _results; if (obj.length) { return _config[parent] = obj[0]; } else if (typeof obj === 'object') { _results = []; for (key in obj) { val = obj[key]; _results.push(arguments.callee(key, val)); } return _results; } else { return _config[parent] = obj; } })(null, config); ui = { dialog: function(id, position, html) { var el, left, top, _ref, _ref2, _ref3; el = document.createElement('div'); el.className = 'reply dialog'; el.innerHTML = html; el.id = id; if (typeof position === 'object') { left = position.left, top = position.top; } else { switch (position) { case 'topleft': left = '0px'; top = '0px'; break; case 'topright': left = null; top = '0px'; break; case 'bottomleft': left = '0px'; top = null; break; case 'bottomright': left = null; top = null; break; case 'center': left = '50%'; top = '25%'; } } left = (_ref = localStorage["" + id + "Left"]) != null ? _ref : left; top = (_ref2 = localStorage["" + id + "Top"]) != null ? _ref2 : top; if (left) { el.style.left = left; } else { el.style.right = '0px'; } if (top) { el.style.top = top; } else { el.style.bottom = '0px'; } el.querySelector('div.move').addEventListener('mousedown', ui.move, true); if ((_ref3 = el.querySelector('div.move a[name=close]')) != null) { _ref3.addEventListener('click', (function() { return el.parentNode.removeChild(el); }), true); } return el; }, move: function(e) { var el, rect; ui.el = el = e.target.parentNode; rect = el.getBoundingClientRect(); ui.dx = e.clientX - rect.left; ui.dy = e.clientY - rect.top; ui.width = document.body.clientWidth - el.offsetWidth; ui.height = document.body.clientHeight - el.offsetHeight; document.addEventListener('mousemove', ui.moveMove, true); return document.addEventListener('mouseup', ui.moveEnd, true); }, moveMove: function(e) { var bottom, el, left, right, top; el = ui.el; left = e.clientX - ui.dx; if (left < 20) { left = '0px'; } else if (ui.width - left < 20) { left = ''; } right = left ? '' : '0px'; el.style.left = left; el.style.right = right; top = e.clientY - ui.dy; if (top < 20) { top = '0px'; } else if (ui.height - top < 20) { top = ''; } bottom = top ? '' : '0px'; el.style.top = top; return el.style.bottom = bottom; }, moveEnd: function() { var el, id; document.removeEventListener('mousemove', ui.moveMove, true); document.removeEventListener('mouseup', ui.moveEnd, true); el = ui.el; id = el.id; localStorage["" + id + "Left"] = el.style.left; return localStorage["" + id + "Top"] = el.style.top; } }; d = document; g = null; $ = function(selector, root) { if (root == null) { root = d.body; } return root.querySelector(selector); }; $.extend = function(object, properties) { var key, val; for (key in properties) { val = properties[key]; object[key] = val; } return object; }; $.extend($, { globalEval: function(code) { var script; script = $.el('script', { textContent: "(" + code + ")()" }); $.append(d.head, script); return $.remove(script); }, get: function(url, cb) { var r; r = new XMLHttpRequest(); r.onload = cb; r.open('get', url, true); r.send(); return r; }, cb: { checked: function() { return $.setValue(this.name, this.checked); }, value: function() { return $.setValue(this.name, this.value); } }, deleteValue: function(name) { name = NAMESPACE + name; return delete localStorage[name]; }, getValue: function(name, defaultValue) { var value; name = NAMESPACE + name; if (value = localStorage[name]) { return JSON.parse(value); } else { return defaultValue; } }, setValue: function(name, value) { name = NAMESPACE + name; return localStorage[name] = JSON.stringify(value); }, addStyle: function(css) { var style; style = document.createElement('style'); style.type = 'text/css'; style.textContent = css; return $.append(d.head, style); }, config: function(name) { return $.getValue(name, _config[name]); }, zeroPad: function(n) { if (n < 10) { return '0' + n; } else { return n; } }, x: function(path, root) { if (root == null) { root = d.body; } return d.evaluate(path, root, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue; }, tn: function(s) { return d.createTextNode(s); }, replace: function(root, el) { return root.parentNode.replaceChild(el, root); }, hide: function(el) { return el.style.display = 'none'; }, show: function(el) { return el.style.display = ''; }, addClass: function(el, className) { return el.className += ' ' + className; }, removeClass: function(el, className) { return el.className = el.className.replace(' ' + className, ''); }, remove: function(el) { return el.parentNode.removeChild(el); }, append: function() { var child, children, parent, _i, _len, _results; parent = arguments[0], children = 2 <= arguments.length ? __slice.call(arguments, 1) : []; _results = []; for (_i = 0, _len = children.length; _i < _len; _i++) { child = children[_i]; _results.push(parent.appendChild(child)); } return _results; }, prepend: function(parent, child) { return parent.insertBefore(child, parent.firstChild); }, after: function(root, el) { return root.parentNode.insertBefore(el, root.nextSibling); }, before: function(root, el) { return root.parentNode.insertBefore(el, root); }, el: function(tag, properties) { var el; el = d.createElement(tag); if (properties) { $.extend(el, properties); } return el; }, bind: function(el, eventType, handler) { return el.addEventListener(eventType, handler, true); }, unbind: function(el, eventType, handler) { return el.removeEventListener(eventType, handler, true); }, isDST: function() { /* http://en.wikipedia.org/wiki/Daylight_saving_time_in_the_United_States Since 2007, daylight saving time starts on the second Sunday of March and ends on the first Sunday of November, with all time changes taking place at 2:00 AM (0200) local time. */ var date, month, sunday; date = new Date(); month = date.getMonth(); if (month < 2 || 10 < month) { return false; } if ((2 < month && month < 10)) { return true; } sunday = date.getDate() - date.getDay(); if (month === 2) { if (sunday < 8) { return false; } if (sunday < 15 && date.getDay() === 0) { if (date.getHour() < 1) { return false; } return true; } return true; } if (month === 10) { if (sunday < 1) { return true; } if (sunday < 8 && date.getDay() === 0) { if (date.getHour() < 1) { return true; } return false; } return false; } } }); $$ = function(selector, root) { var node, result, _i, _len, _results; if (root == null) { root = d.body; } result = root.querySelectorAll(selector); _results = []; for (_i = 0, _len = result.length; _i < _len; _i++) { node = result[_i]; _results.push(node); } return _results; }; expandComment = { init: function() { var a, _i, _len, _ref, _results; _ref = $$('span.abbr a'); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { a = _ref[_i]; _results.push($.bind(a, 'click', expandComment.cb.expand)); } return _results; }, cb: { expand: function(e) { var a, href, replyID, threadID, _, _ref; e.preventDefault(); a = e.target; a.textContent = 'Loading...'; href = a.getAttribute('href'); _ref = href.match(/(\d+)#(\d+)/), _ = _ref[0], threadID = _ref[1], replyID = _ref[2]; return g.cache[threadID] = $.get(href, (function() { return expandComment.load(this, a, threadID, replyID); })); } }, load: function(xhr, a, threadID, replyID) { var body, bq, reply, _i, _len, _ref; body = $.el('body', { innerHTML: xhr.responseText }); if (threadID === replyID) { bq = $('blockqoute', body); } else { _ref = $$('td[id]', body); for (_i = 0, _len = _ref.length; _i < _len; _i++) { reply = _ref[_i]; if (reply.id === replyID) { bq = $('blockquote', reply); break; } } } return $.replace(a.parentNode.parentNode, bq); } }; expandThread = { init: function() { var a, span, _i, _len, _ref, _results; _ref = $$('span.omittedposts'); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { span = _ref[_i]; a = $.el('a', { textContent: "+ " + span.textContent, className: 'omittedposts' }); $.bind(a, 'click', expandThread.cb.toggle); _results.push($.replace(span, a)); } return _results; }, cb: { toggle: function(e) { var thread; thread = e.target.parentNode; return expandThread.toggle(thread); }, load: function(xhr, thread, a) { var html, id; if (xhr.status === 404) { a.textContent.replace('X Loading...', '404'); return $.unbind(a, 'click', expandThread.cb.toggle); } else { html = xhr.responseText; id = thread.firstChild.id; g.cache[id] = html; return expandThread.expand(html, thread, a); } } }, toggle: function(thread) { var a, html, id, num, prev, table, _results; id = thread.firstChild.id; a = $('a.omittedposts', thread); switch (a.textContent[0]) { case '+': a.textContent = a.textContent.replace('+', 'X Loading...'); if (html = g.cache[id]) { return expandThread.expand(html, thread, a); } else { return g.requests[id] = $.get("res/" + id, (function() { return expandThread.cb.load(this, thread, a); })); } break; case 'X': a.textContent = a.textContent.replace('X Loading...', '+'); return g.requests[id].abort(); case '-': a.textContent = a.textContent.replace('-', '+'); num = g.BOARD === 'b' ? 3 : 5; table = $.x("following::br[@clear][1]/preceding::table[" + num + "]", a); _results = []; while ((prev = table.previousSibling) && (prev.nodeName === 'TABLE')) { _results.push($.remove(prev)); } return _results; } }, expand: function(html, thread, a) { var body, br, next, table, tables, _i, _len, _results; a.textContent = a.textContent.replace('X Loading...', '-'); while ((next = a.nextSibling) && !next.clear) { $.remove(next); } br = next; body = $.el('body', { innerHTML: html }); tables = $$('form[name=delform] table', body); tables.pop(); _results = []; for (_i = 0, _len = tables.length; _i < _len; _i++) { table = tables[_i]; _results.push($.before(br, table)); } return _results; } }; replyHiding = { init: function() { return g.callbacks.push(replyHiding.cb.node); }, cb: { hide: function(e) { var reply; reply = e.target.parentNode.nextSibling; return replyHiding.hide(reply); }, node: function(root) { var a, id, reply, td, tds, _i, _len, _results; tds = $$('td.doubledash', root); _results = []; for (_i = 0, _len = tds.length; _i < _len; _i++) { td = tds[_i]; a = $.el('a', { textContent: '[ - ]' }); $.bind(a, 'click', replyHiding.cb.hide); $.replace(td.firstChild, a); reply = td.nextSibling; id = reply.id; _results.push(id in g.hiddenReply ? replyHiding.hide(reply) : void 0); } return _results; }, show: function(e) { var div, table; div = e.target.parentNode; table = div.nextSibling; replyHiding.show(table); return $.remove(div); } }, hide: function(reply) { var a, div, id, name, table, trip, _ref; table = reply.parentNode.parentNode.parentNode; $.hide(table); if ($.config('Show Stubs')) { name = $('span.commentpostername', reply).textContent; trip = ((_ref = $('span.postertrip', reply)) != null ? _ref.textContent : void 0) || ''; a = $.el('a', { textContent: "[ + ] " + name + " " + trip }); $.bind(a, 'click', replyHiding.cb.show); div = $.el('div'); $.append(div, a); $.before(table, div); } id = reply.id; g.hiddenReply[id] = Date.now(); return $.setValue("hiddenReply/" + g.BOARD + "/", g.hiddenReply); }, show: function(table) { var id; $.show(table); id = $('td[id]', table).id; delete g.hiddenReply[id]; return $.setValue("hiddenReply/" + g.BOARD + "/", g.hiddenReply); } }; keybinds = { init: function() { $.bind(d, 'keydown', keybinds.cb.keydown); return $.bind(d, 'keypress', keybinds.cb.keypress); }, cb: { keydown: function(e) { var kc, key, _ref; if ((_ref = d.activeElement.nodeName) === 'TEXTAREA' || _ref === 'INPUT') { keybinds.mode = keybinds.insert; } else { keybinds.mode = keybinds.normal; } kc = e.keyCode; if ((65 <= kc && kc <= 90)) { key = String.fromCharCode(kc); if (!e.shiftKey) { key = key.toLowerCase(); } if (e.ctrlKey) { key = '^' + key; } } else { if (kc === 27) { key = ''; } } return keybinds.key = key; }, keypress: function(e) { return keybinds.mode(e); } }, insert: function(e) { var range, selEnd, selStart, ta, valEnd, valMid, valStart, value; switch (keybinds.key) { case '': e.preventDefault(); return $.remove($('#qr')); case '^s': ta = d.activeElement; if (ta.nodeName !== 'TEXTAREA') { return; } e.preventDefault(); value = ta.value; selStart = ta.selectionStart; selEnd = ta.selectionEnd; valStart = value.slice(0, selStart) + '[spoiler]'; valMid = value.slice(selStart, selEnd); valEnd = '[/spoiler]' + value.slice(selEnd); ta.value = valStart + valMid + valEnd; range = valStart.length + valMid.length; return ta.setSelectionRange(range, range); } }, normal: function(e) { var thread; thread = nav.getThread(); switch (keybinds.key) { case 'I': return keybinds.qr(thread); case 'J': return keybinds.hl.next(thread); case 'K': return keybinds.hl.prev(thread); case 'M': return keybinds.img(thread, true); case 'O': return keybinds.open(thread); case 'i': return keybinds.qr(thread, true); case 'm': return keybinds.img(thread); case 'n': return nav.next(); case 'o': return keybinds.open(thread, true); case 'p': return nav.prev(); case 'u': return updater.update(); case 'w': return watcher.toggle(thread); case 'x': return threadHiding.toggle(thread); } }, img: function(thread, all) { var root, thumb; if (all) { return $("#imageExpand").click(); } else { root = $('td.replyhl', thread) || thread; thumb = $('img[md5]', root); return imgExpand.toggle(thumb); } }, qr: function(thread, quote) { var qrLink; if (!(qrLink = $('td.replyhl span[id] a:not(:first-child)', thread))) { qrLink = $("span[id^=nothread] a:not(:first-child)", thread); } if (quote) { return qr.quote(qrLink); } else { if (!$('#qr')) { qr.dialog(qrLink); } return $('#qr textarea').focus(); } }, open: function(thread, tab) { var id, url; id = thread.firstChild.id; url = "http://boards.4chan.org/" + g.BOARD + "/res/" + id; if (tab) { return window.open(url, "_blank"); } else { return location.href = url; } }, hl: { next: function(thread) { var next, rect, replies, reply, td, top, _i, _len; if (td = $('td.replyhl', thread)) { td.className = 'reply'; rect = td.getBoundingClientRect(); if (rect.top > 0 && rect.bottom < d.body.clientHeight) { next = $.x('following::td[@class="reply"]', td); rect = next.getBoundingClientRect(); if (rect.top > 0 && rect.bottom < d.body.clientHeight) { next.className = 'replyhl'; } return; } } replies = $$('td.reply', thread); for (_i = 0, _len = replies.length; _i < _len; _i++) { reply = replies[_i]; top = reply.getBoundingClientRect().top; if (top > 0) { reply.className = 'replyhl'; return; } } }, prev: function(thread) { var bot, height, prev, rect, replies, reply, td, _i, _len; if (td = $('td.replyhl', thread)) { td.className = 'reply'; rect = td.getBoundingClientRect(); if (rect.top > 0 && rect.bottom < d.body.clientHeight) { prev = $.x('preceding::td[@class="reply"][1]', td); rect = prev.getBoundingClientRect(); if (rect.top > 0 && rect.bottom < d.body.clientHeight) { prev.className = 'replyhl'; } return; } } replies = $$('td.reply', thread); replies.reverse(); height = d.body.clientHeight; for (_i = 0, _len = replies.length; _i < _len; _i++) { reply = replies[_i]; bot = reply.getBoundingClientRect().bottom; if (bot < height) { reply.className = 'replyhl'; return; } } } } }; nav = { init: function() { var next, prev, span; span = $.el('span', { id: 'navlinks' }); prev = $.el('a', { textContent: '▲' }); next = $.el('a', { textContent: '▼' }); $.bind(prev, 'click', nav.prev); $.bind(next, 'click', nav.next); $.append(span, prev, $.tn(' '), next); $.append(d.body, span); return nav.threads = $$('div.thread'); }, prev: function() { return nav.scroll(-1); }, next: function() { return nav.scroll(+1); }, threads: [], getThread: function(full) { var bottom, i, rect, thread, _len, _ref; _ref = nav.threads; for (i = 0, _len = _ref.length; i < _len; i++) { thread = _ref[i]; rect = thread.getBoundingClientRect(); bottom = rect.bottom; if (bottom > 0) { if (full) { return [thread, i, rect]; } return thread; } } return null; }, scroll: function(delta) { var i, rect, thread, top, _ref; _ref = nav.getThread(true), thread = _ref[0], i = _ref[1], rect = _ref[2]; top = rect.top; if (!((delta === -1 && Math.ceil(top) < 0) || (delta === +1 && top > 1))) { i += delta; } if (i === -1) { window.scrollTo(0, 0); return; } if (i === 10) { window.location = "" + (g.PAGENUM + 1) + "#p0"; return; } top = nav.threads[i].getBoundingClientRect().top; return window.scrollBy(0, top); } }; options = { init: function() { var a, home; home = $('#navtopr a'); a = $.el('a', { textContent: '4chan X' }); $.bind(a, 'click', options.toggle); $.replace(home, a); home = $('#navbotr a'); a = $.el('a', { textContent: '4chan X' }); $.bind(a, 'click', options.toggle); return $.replace(home, a); }, toggle: function() { var dialog; if (dialog = $('#options')) { return $.remove(dialog); } else { return options.dialog(); } }, dialog: function() { var checked, conf, dialog, hiddenNum, hiddenThread, html, input, name, title, _i, _len, _ref; html = "
Options X
"; conf = config.main.checkbox; for (name in conf) { title = conf[name][1]; checked = $.config(name) ? "checked" : ""; html += "
"; } html += "
Flavors
"; html += "
"; hiddenThread = $.getValue("hiddenThread/" + g.BOARD + "/", {}); hiddenNum = Object.keys(g.hiddenReply).length + Object.keys(hiddenThread).length; html += "
"; html += "
"; html += "
support throd
"; html += '
Donate
'; dialog = ui.dialog('options', { top: '25%', left: '50%' }, html); _ref = $$('input[type=checkbox]', dialog); for (_i = 0, _len = _ref.length; _i < _len; _i++) { input = _ref[_i]; $.bind(input, 'click', $.cb.checked); } $.bind($('input[type=button]', dialog), 'click', options.cb.clearHidden); $.bind($('a[name=flavors]', dialog), 'click', options.flavors); $.bind($('textarea', dialog), 'change', $.cb.value); return $.append(d.body, dialog); }, flavors: function() { var ta; ta = $('#options textarea'); if (ta.style.display) { return $.show(ta); } else { return $.hide(ta); } }, cb: { clearHidden: function(e) { $.deleteValue("hiddenReply/" + g.BOARD + "/"); $.deleteValue("hiddenThread/" + g.BOARD + "/"); this.value = "hidden: 0"; return g.hiddenReplies = {}; } } }; qr = { /* In order to get qr error notifications in chrome, there are two issues we have to work around: http://code.google.com/p/chromium/issues/detail?id=20773 Let content scripts see other frames (instead of them being undefined) We can't directly pass messages between the top window and the iframe, so we have to break out of the sandbox and evaulate code in the global context. http://code.google.com/p/chromium/issues/detail?id=61856 Support @run-at for user scripts http://www.chromium.org/developers/design-documents/user-scripts In Chromium/Google Chrome, Greasemonkey scripts are injected by default at a new point called "document-idle". This is different than Greasemonkey, which always injects at document-end. ... However, if the page loads quickly, scripts may not be run until after window.onload has occurred -- much later than with Greasemonkey. We can't force the script to run at document-end, so we can't rely on the load event to tell us when the hidden iframe is ready to receive our ping; instead, we have to emit our own event. */ init: function() { var iframe; g.callbacks.push(qr.cb.node); iframe = $.el('iframe', { name: 'iframe' }); $.append(d.body, iframe); $.globalEval(qr.eval.load); $.globalEval(qr.eval.messageTop); $.bind(window, 'message2', qr.cb.messageTop); return $('#recaptcha_response_field').id = ''; }, autohide: { set: function() { var _ref; return (_ref = $('#qr input[title=autohide]:not(:checked)')) != null ? _ref.click() : void 0; }, unset: function() { var _ref; return (_ref = $('#qr input[title=autohide]:checked')) != null ? _ref.click() : void 0; } }, eval: { load: function() { var load; load = function(e) { return e.target.contentWindow.postMessage('', '*'); }; return document.querySelector('iframe[name=iframe]').addEventListener('load', load, true); }, messageTop: function() { var message; message = function(e) { var e2; e2 = document.createEvent('MessageEvent'); e2.initMessageEvent('message2', true, true, e.data, e.origin, '', null, null); return window.dispatchEvent(e2); }; return window.addEventListener('message', message, true); }, messageIframe: function() { var messageIframe; messageIframe = function(e) { var message, _ref; message = ((_ref = document.querySelector('table font b')) != null ? _ref.firstChild.textContent : void 0) || ''; e.source.postMessage(message, '*'); return window.location = 'about:blank'; }; return window.addEventListener('message', messageIframe, true); } }, cb: { autohide: function(e) { var dialog; dialog = $('#qr'); if (this.checked) { return $.addClass(dialog, 'auto'); } else { return $.removeClass(dialog, 'auto'); } }, load: function(e) { return e.target.contentWindow.postMessage('', '*'); }, messageTop: function(e) { var data, dialog, error; data = e.data; dialog = $('#qr'); if (data) { error = $.el('span', { className: 'error', textContent: data }); $.append(dialog, error); qr.autohide.unset(); } else { if (dialog) { if ($.config('Persistent QR')) { qr.refresh(dialog); } else { $.remove(dialog); } } } return Recaptcha.reload(); }, node: function(root) { var quote, quotes, _i, _len, _results; quotes = $$('a.quotejs:not(:first-child)', root); _results = []; for (_i = 0, _len = quotes.length; _i < _len; _i++) { quote = quotes[_i]; _results.push($.bind(quote, 'click', qr.cb.quote)); } return _results; }, submit: function(e) { var form, isQR, recaptcha, span; form = e.target; isQR = form.parentNode.id === 'qr'; if (isQR) { if (span = this.nextSibling) { $.remove(span); } } if (g.seconds = $.getValue('seconds')) { e.preventDefault(); qr.cooldownStart(); alert('Stop posting so often!'); if (isQR) { span = $.el('span', { className: 'error', textContent: 'Stop posting so often!' }); $.append(this.parentNode, span); } return; } recaptcha = $('input[name=recaptcha_response_field]', this); if (recaptcha.value) { g.sage = $('input[name=email]', form).value === 'sage'; if (isQR) { return qr.autohide.set(); } } else { e.preventDefault(); alert('You forgot to type in the verification.'); recaptcha.focus(); if (isQR) { span = $.el('span', { className: 'error', textContent: 'You forgot to type in the verification.' }); return $.append(this.parentNode, span); } } }, quote: function(e) { e.preventDefault(); return qr.quote(e.target); } }, quote: function(link) { var dialog, id, s, selection, selectionID, ta, text, _ref; if (dialog = $('#qr')) { qr.autohide.unset(); } else { dialog = qr.dialog(link); } id = link.textContent; text = ">>" + id + "\n"; selection = window.getSelection(); if (s = selection.toString()) { selectionID = (_ref = $.x('preceding::input[@type="checkbox"][1]', selection.anchorNode)) != null ? _ref.name : void 0; if (selectionID === id) { text += ">" + s + "\n"; } } ta = $('textarea', dialog); ta.focus(); return ta.value += text; }, refresh: function(dialog) { var f; $('textarea', dialog).value = ''; $('input[name=recaptcha_response_field]', dialog).value = ''; f = $('input[type=file]', dialog).parentNode; return f.innerHTML = f.innerHTML; }, cooldown: function() { var submit, submits, _i, _len; submits = $$('#qr input[type=submit], form[name=post] input[type=submit]'); for (_i = 0, _len = submits.length; _i < _len; _i++) { submit = submits[_i]; if (g.seconds === 0) { submit.disabled = false; submit.value = 'Submit'; } else { submit.value = g.seconds = g.seconds - 1; $.setValue('seconds', g.seconds); } } if (g.seconds !== 0) { return window.setTimeout(qr.cooldown, 1000); } }, cooldownStart: function() { var submit, submits, _i, _len; $.setValue('seconds', g.seconds); submits = $$('#qr input[type=submit], form[name=post] input[type=submit]'); for (_i = 0, _len = submits.length; _i < _len; _i++) { submit = submits[_i]; submit.value = g.seconds; submit.disabled = true; } return window.setTimeout(qr.cooldown, 1000); }, dialog: function(link) { var clone, dialog, el, html, resto, script, xpath, _i, _len, _ref; html = "
Quick Reply X
"; dialog = ui.dialog('qr', { top: '0px', left: '0px' }, html); el = $('input[title=autohide]', dialog); $.bind(el, 'click', qr.cb.autohide); clone = $('form[name=post]').cloneNode(true); _ref = $$('script', clone); for (_i = 0, _len = _ref.length; _i < _len; _i++) { script = _ref[_i]; $.remove(script); } clone.target = 'iframe'; $.bind(clone, 'submit', qr.cb.submit); $.bind($('input[name=recaptcha_response_field]', clone), 'keydown', Recaptcha.listener); if (!g.REPLY) { xpath = 'preceding::span[@class="postername"][1]/preceding::input[1]'; resto = $.el('input', { type: 'hidden', name: 'resto', value: $.x(xpath, link).name }); $.before(clone.lastChild, resto); } $.append(dialog, clone); $.append(d.body, dialog); dialog.style.width = dialog.offsetWidth; return dialog; }, persist: function() { $.append(d.body, qr.dialog()); return qr.autohide.set(); }, sys: function() { var board, c, id, recaptcha, thread, _, _ref, _ref2; if (recaptcha = $('#recaptcha_response_field')) { $.bind(recaptcha, 'keydown', Recaptcha.listener); return; } c = $('b').lastChild; if (c.nodeType === 8) { _ref = c.textContent.match(/thread:(\d+),no:(\d+)/), _ = _ref[0], thread = _ref[1], id = _ref[2]; if (thread === '0') { _ref2 = $('meta', d).content.match(/4chan.org\/(\w+)\//), _ = _ref2[0], board = _ref2[1]; window.location = "http://boards.4chan.org/" + board + "/res/" + id + "#watch"; return; } } return $.globalEval(qr.eval.messageIframe); } }; threadHiding = { init: function() { var a, hiddenThreads, node, op, thread, _i, _len, _ref, _results; node = $('form[name=delform] > *'); threadHiding.thread(node); hiddenThreads = $.getValue("hiddenThread/" + g.BOARD + "/", {}); _ref = $$('div.thread'); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { thread = _ref[_i]; op = thread.firstChild; a = $.el('a', { textContent: '[ - ]' }); $.bind(a, 'click', threadHiding.cb.hide); $.prepend(op, a); _results.push(op.id in hiddenThreads ? threadHiding.hideHide(thread) : void 0); } return _results; }, cb: { hide: function(e) { var thread; thread = e.target.parentNode.parentNode; return threadHiding.hide(thread); }, show: function(e) { var thread; thread = e.target.parentNode.parentNode; return threadHiding.show(thread); } }, toggle: function(thread) { if (thread.className.indexOf('stub') !== -1 || thread.style.display === 'none') { return threadHiding.show(thread); } else { return threadHiding.hide(thread); } }, hide: function(thread) { var hiddenThreads, id; threadHiding.hideHide(thread); id = thread.firstChild.id; hiddenThreads = $.getValue("hiddenThread/" + g.BOARD + "/", {}); hiddenThreads[id] = Date.now(); return $.setValue("hiddenThread/" + g.BOARD + "/", hiddenThreads); }, hideHide: function(thread) { var a, div, name, num, span, text, trip, _ref; if ($.config('Show Stubs')) { if (span = $('.omittedposts', thread)) { num = Number(span.textContent.match(/\d+/)[0]); } else { num = 0; } num += $$('table', thread).length; text = num === 1 ? "1 reply" : "" + num + " replies"; name = $('span.postername', thread).textContent; trip = ((_ref = $('span.postername + span.postertrip', thread)) != null ? _ref.textContent : void 0) || ''; a = $.el('a', { textContent: "[ + ] " + name + trip + " (" + text + ")" }); $.bind(a, 'click', threadHiding.cb.show); div = $.el('div', { className: 'block' }); $.append(div, a); $.append(thread, div); return $.addClass(thread, 'stub'); } else { $.hide(thread); return $.hide(thread.nextSibling); } }, show: function(thread) { var hiddenThreads, id; $.remove($('div.block', thread)); $.removeClass(thread, 'stub'); $.show(thread); $.show(thread.nextSibling); id = thread.firstChild.id; hiddenThreads = $.getValue("hiddenThread/" + g.BOARD + "/", {}); delete hiddenThreads[id]; return $.setValue("hiddenThread/" + g.BOARD + "/", hiddenThreads); }, thread: function(node) { var div, op; op = $.el('div', { className: 'op' }); $.before(node, op); while (node.nodeName !== 'BLOCKQUOTE') { $.append(op, node); node = op.nextSibling; } $.append(op, node); op.id = $('input[name]', op).name; node = op; div = $.el('div', { className: 'thread' }); $.before(node, div); while (node.nodeName !== 'HR') { $.append(div, node); node = div.nextSibling; } node = node.nextElementSibling; if (node.nodeName !== 'CENTER') { return threadHiding.thread(node); } } }; updater = { init: function() { var autoUpT, checked, conf, dialog, html, input, interva, name, title, updNow, verbose, _i, _len, _ref; html = "
"; conf = config.updater.checkbox; for (name in conf) { title = conf[name][1]; checked = $.config(name) ? "checked" : ""; html += "
"; } name = 'Auto Update This'; title = 'Controls whether *this* thread auotmatically updates or not'; checked = $.config('Auto Update') ? 'checked' : ''; html += "
"; html += "
"; html += "
"; dialog = ui.dialog('updater', { bottom: '0px', right: '0px' }, html); _ref = $$('input[type=checkbox]', dialog); for (_i = 0, _len = _ref.length; _i < _len; _i++) { input = _ref[_i]; $.bind(input, 'click', $.cb.checked); } $.bind($('input[type=text]', dialog), 'change', $.cb.value); verbose = $('input[name=\"Verbose\"]', dialog); autoUpT = $('input[name=\"Auto Update This\"]', dialog); interva = $('input[name=\"Interval\"]', dialog); updNow = $('input[type=button]', dialog); $.bind(verbose, 'click', updater.cb.verbose); $.bind(autoUpT, 'click', updater.cb.autoUpdate); $.bind(updNow, 'click', updater.update); $.append(d.body, dialog); updater.cb.verbose.call(verbose); return updater.cb.autoUpdate.call(autoUpT); }, cb: { verbose: function(e) { var t; if (this.checked) { $.show($('#count')); return $('#timer').textContent = (t = updater.timer) ? t : 'Thread Updater'; } else { $.hide($('#count')); return $('#timer').textContent = 'Thread Updater'; } }, autoUpdate: function(e) { var timer; timer = $('#timer'); if (this.checked) { timer.textContent = '-' + $.config('Interval'); return updater.intervalID = window.setInterval(updater.timeout, 1000); } else { timer.textContent = 'Thread Updater'; return window.clearInterval(updater.intervalID); } }, update: function(e) { var arr, body, br, count, id, input, replies, reply, s, timer, _i, _len, _ref, _ref2, _results; count = $('#count'); timer = $('#timer'); if (this.status === 404) { count.textContent = 404; count.className = 'error'; timer.textContent = ''; clearInterval(updater.intervalID); _ref = $$('input[type=submit]'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { input = _ref[_i]; input.disabled = true; input.value = 404; } s = d.title.match(/.+- /)[0]; s += '404'; return; } br = $('br[clear]'); id = Number(((_ref2 = $('td[id]', br.previousElementSibling)) != null ? _ref2.id : void 0) || 0); arr = []; body = $.el('body', { innerHTML: this.responseText }); replies = $$('td[id]', body); while ((reply = replies.pop()) && (reply.id > id)) { arr.push(reply.parentNode.parentNode.parentNode); } if ($.config('Verbose')) { timer.textContent = '-' + $.config('Interval'); count.textContent = '+' + arr.length; if (arr.length === 0) { count.className = ''; } else { count.className = 'new'; } } _results = []; while (reply = arr.pop()) { _results.push($.before(br, reply)); } return _results; } }, timeout: function() { var count, n, timer; timer = $('#timer'); n = Number(timer.textContent); n += 1; timer.textContent = n; if (n === 10) { updater.r.abort(); count = $('#count'); counte.textContent = 'retry'; count.className = ''; n = 0; } if (n === 0) { return updater.update(); } }, update: function() { var cb, url, _ref; if ((_ref = updater.request) != null) { _ref.abort(); } url = location.href; cb = updater.cb.update; return updater.request = $.get(url, cb); } }; watcher = { init: function() { var board, dialog, favicon, html, id, input, inputs, props, src, watched, watchedBoard, _i, _len, _ref, _results; html = '
Thread Watcher
'; dialog = ui.dialog('watcher', { top: '50px', left: '0px' }, html); $.append(d.body, dialog); watched = $.getValue('watched', {}); for (board in watched) { _ref = watched[board]; for (id in _ref) { props = _ref[id]; watcher.addLink(props, dialog); } } watchedBoard = watched[g.BOARD] || {}; inputs = $$('form > input[value=delete], div.thread > input[value=delete]'); _results = []; for (_i = 0, _len = inputs.length; _i < _len; _i++) { input = inputs[_i]; id = input.name; if (id in watchedBoard) { src = Favicon["default"]; } else { src = Favicon.empty; } favicon = $.el('img', { src: src, className: 'favicon' }); $.bind(favicon, 'click', watcher.cb.toggle); _results.push($.before(input, favicon)); } return _results; }, addLink: function(props, dialog) { var div, link, x; dialog || (dialog = $('#watcher')); div = $.el('div'); x = $.el('a', { textContent: 'X' }); $.bind(x, 'click', watcher.cb.x); link = $.el('a', props); $.append(div, x, $.tn(' '), link); return $.append(dialog, div); }, cb: { toggle: function(e) { return watcher.toggle(e.target.parentNode); }, x: function(e) { var board, id, _, _ref; _ref = e.target.nextElementSibling.getAttribute('href').substring(1).split('/'), board = _ref[0], _ = _ref[1], id = _ref[2]; return watcher.unwatch(board, id); } }, toggle: function(thread) { var favicon, id; favicon = $('img.favicon', thread); id = favicon.nextSibling.name; if (favicon.src === Favicon.empty) { return watcher.watch(thread); } else { return watcher.unwatch(g.BOARD, id); } }, unwatch: function(board, id) { var div, favicon, href, input, watched; href = "/" + board + "/res/" + id; div = $("#watcher a[href=\"" + href + "\"]").parentNode; $.remove(div); if (input = $("input[name=\"" + id + "\"]")) { favicon = input.previousSibling; favicon.src = Favicon.empty; } watched = $.getValue('watched', {}); delete watched[board][id]; return $.setValue('watched', watched); }, watch: function(thread) { var favicon, id, props, tc, watched, _name; favicon = $('img.favicon', thread); if (favicon.src === Favicon["default"]) { return; } favicon.src = Favicon["default"]; id = favicon.nextSibling.name; tc = $('span.filetitle', thread).textContent || $('blockquote', thread).textContent; props = { textContent: "/" + g.BOARD + "/ - " + tc.slice(0, 25), href: "/" + g.BOARD + "/res/" + id }; watched = $.getValue('watched', {}); watched[_name = g.BOARD] || (watched[_name] = {}); watched[g.BOARD][id] = props; $.setValue('watched', watched); return watcher.addLink(props); } }; anonymize = { init: function() { return g.callbacks.push(anonymize.cb.node); }, cb: { node: function(root) { var name, names, trip, _i, _j, _len, _len2, _ref, _results; names = $$('span.postername, span.commentpostername', root); for (_i = 0, _len = names.length; _i < _len; _i++) { name = names[_i]; name.innerHTML = 'Anonymous'; } _ref = $$('span.postertrip', root); _results = []; for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) { trip = _ref[_j]; _results.push(trip.parentNode.nodeName === 'A' ? $.remove(trip.parentNode) : $.remove(trip)); } return _results; } } }; sauce = { init: function() { return g.callbacks.push(sauce.cb.node); }, cb: { node: function(root) { var i, link, names, prefix, prefixes, span, suffix, _i, _len, _ref, _results; prefixes = $.getValue('flavors', g.flavors).split('\n'); names = (function() { var _i, _len, _results; _results = []; for (_i = 0, _len = prefixes.length; _i < _len; _i++) { prefix = prefixes[_i]; _results.push(prefix.match(/(\w+)\./)[1]); } return _results; })(); _ref = $$('span.filesize', root); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { span = _ref[_i]; suffix = $('a', span).href; _results.push((function() { var _len2, _results2; _results2 = []; for (i = 0, _len2 = prefixes.length; i < _len2; i++) { prefix = prefixes[i]; link = $.el('a', { textContent: names[i], href: prefix + suffix }); _results2.push($.append(span, $.tn(' '), link)); } return _results2; })()); } return _results; } } }; localize = { init: function() { return g.callbacks.push(function(root) { var date, day, dotw, hour, min_sec, month, s, span, year, _, _i, _len, _ref, _ref2, _results; _ref = $$('span[id^=no]', root); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { span = _ref[_i]; s = span.previousSibling; _ref2 = s.textContent.match(/(\d+)\/(\d+)\/(\d+)\(\w+\)(\d+):(\S+)/), _ = _ref2[0], month = _ref2[1], day = _ref2[2], year = _ref2[3], hour = _ref2[4], min_sec = _ref2[5]; year = "20" + year; month -= 1; hour = g.chanOffset + Number(hour); date = new Date(year, month, day, hour); year = date.getFullYear() - 2000; month = $.zeroPad(date.getMonth() + 1); day = $.zeroPad(date.getDate()); hour = $.zeroPad(date.getHours()); dotw = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][date.getDay()]; _results.push(s.textContent = " " + month + "/" + day + "/" + year + "(" + dotw + ")" + hour + ":" + min_sec + " "); } return _results; }); } }; titlePost = { init: function() { var tc; tc = $('span.filetitle').textContent || $('blockquote').textContent; return d.title = "/" + g.BOARD + "/ - " + tc; } }; quickReport = { init: function() { return g.callbacks.push(quickReport.cb.node); }, cb: { node: function(root) { var a, arr, el, _i, _len, _results; arr = $$('span[id^=no]', root); _results = []; for (_i = 0, _len = arr.length; _i < _len; _i++) { el = arr[_i]; a = $.el('a', { textContent: '[ ! ]' }); $.bind(a, 'click', quickReport.cb.report); $.after(el, a); _results.push($.after(el, $.tn(' '))); } return _results; }, report: function(e) { var target; target = e.target; return quickReport.report(target); } }, report: function(target) { var input; input = $.x('preceding-sibling::input[1]', target); input.click(); $('input[value="Report"]').click(); return input.click(); } }; unread = { init: function() { unread.replies = []; d.title = '(0) ' + d.title; $.bind(window, 'scroll', unread.cb.scroll); return g.callbacks.push(unread.cb.node); }, cb: { node: function(root) { unread.replies = unread.replies.concat($$('td[id]', root)); unread.updateTitle(); return Favicon.update(); }, scroll: function(e) { var bottom, height, i, reply, _len, _ref; height = d.body.clientHeight; _ref = unread.replies; for (i = 0, _len = _ref.length; i < _len; i++) { reply = _ref[i]; bottom = reply.getBoundingClientRect().bottom; if (bottom > height) { break; } } if (i === 0) { return; } unread.replies = unread.replies.slice(i); unread.updateTitle(); if (unread.replies.length === 0) { return Favicon.update(); } } }, updateTitle: function() { return d.title = d.title.replace(/\d+/, unread.replies.length); } }; Favicon = { dead: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAACVBMVEUAAAAAAAD/AAA9+90tAAAAAXRSTlMAQObYZgAAADtJREFUCB0FwUERxEAIALDszMG730PNSkBEBSECoU0AEPe0mly5NWprRUcDQAdn68qtkVsj3/84z++CD5u7CsnoBJoaAAAAAElFTkSuQmCC', deadHalo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAWUlEQVR4XrWSAQoAIAgD/f+njSApsTqjGoTQ5oGWPJMOOs60CzsWwIwz1I4PUIYh+WYEMGQ6I/txw91kP4oA9BdwhKp1My4xQq6e8Q9ANgDJjOErewFiNesV2uGSfGv1/HYAAAAASUVORK5CYII=', "default": ((_ref = $('link[rel="shortcut icon"]', d.head)) != null ? _ref.href : void 0) || '', empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==', haloSFW: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAZklEQVR4XrWRQQoAIQwD+6L97j7Ih9WTQQxhDqJQCk4Mranuvqod6LgwawSqSuUmWSPw/UNlJlnDAmA2ARjABLYj8ZyCzJHHqOg+GdAKZmKPIQUzuYrxicHqEgHzP9g7M0+hj45sAnRWxtPj3zSPAAAAAElFTkSuQmCC', haloNSFW: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEUAAABmzDP///8AAABet0i+AAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=', update: function() { var clone, favicon, href, l; l = unread.replies.length; if (g.dead) { if (l > 0) { href = Favicon.headHalo; } else { href = Favicon.dead; } } else { if (l > 0) { href = Favicon.halo; } else { href = Favicon["default"]; } } favicon = $('link[rel="shortcut icon"]', d.head); clone = favicon.cloneNode(true); clone.href = href; return $.replace(favicon, clone); } }; redirect = function() { var url; switch (g.BOARD) { case 'a': case 'g': case 'lit': case 'sci': case 'tv': url = "http://green-oval.net/cgi-board.pl/" + g.BOARD + "/thread/" + g.THREAD_ID; break; case 'jp': case 'm': case 'tg': url = "http://archive.easymodo.net/cgi-board.pl/" + g.BOARD + "/thread/" + g.THREAD_ID; break; case '3': case 'adv': case 'an': case 'c': case 'ck': case 'co': case 'fa': case 'fit': case 'int': case 'k': case 'mu': case 'n': case 'o': case 'p': case 'po': case 'soc': case 'sp': case 'toy': case 'trv': case 'v': case 'vp': case 'x': url = "http://archive.no-ip.org/" + g.BOARD + "/thread/" + g.THREAD_ID; break; default: url = "http://boards.4chan.org/" + g.BOARD; } return location.href = url; }; Recaptcha = { init: function() { var el, recaptcha, _i, _len, _ref2; _ref2 = $$('#recaptcha_table a'); for (_i = 0, _len = _ref2.length; _i < _len; _i++) { el = _ref2[_i]; el.tabIndex = 1; } recaptcha = $('#recaptcha_response_field'); return $.bind(recaptcha, 'keydown', Recaptcha.listener); }, listener: function(e) { if (e.keyCode === 8 && this.value === '') { return Recaptcha.reload(); } }, reload: function() { return window.location = 'javascript:Recaptcha.reload()'; } }; nodeInserted = function(e) { var callback, dialog, target, _i, _len, _ref2, _results; target = e.target; if (target.nodeName === 'TABLE') { _ref2 = g.callbacks; _results = []; for (_i = 0, _len = _ref2.length; _i < _len; _i++) { callback = _ref2[_i]; _results.push(callback(target)); } return _results; } else if (target.id === 'recaptcha_challenge_field' && (dialog = $('#qr'))) { $('#recaptcha_image img', dialog).src = "http://www.google.com/recaptcha/api/image?c=" + target.value; return $('#recaptcha_challenge_field', dialog).value = target.value; } }; imageHover = { init: function() { var img; img = $.el('img', { id: 'iHover' }); $.hide(img); d.body.appendChild(img); return g.callbacks.push(imageHover.cb.node); }, offset: { x: 45, y: -120 }, cb: { node: function(root) { var thumb, thumbs, _i, _len, _results; thumbs = $$('img[md5]', root); _results = []; for (_i = 0, _len = thumbs.length; _i < _len; _i++) { thumb = thumbs[_i]; _results.push($.bind(thumb, 'mouseover', imageHover.cb.mouseover)); } return _results; }, mouseover: function(e) { var clientX, clientY, img, target; target = e.target, clientX = e.clientX, clientY = e.clientY; img = $('#iHover'); img.src = target.parentNode.href; $.show(img); imageHover.winHeight = d.body.clientHeight; imageHover.winWidth = d.body.clientWidth; $.bind(target, 'mousemove', imageHover.cb.mousemove); return $.bind(target, 'mouseout', imageHover.cb.mouseout); }, mousemove: function(e) { var bot, clientX, clientY, img, imgHeight, top; clientX = e.clientX, clientY = e.clientY; img = $('#iHover'); imgHeight = img.offsetHeight; top = clientY + imageHover.offset.y; bot = top + imgHeight; img.style.top = imageHover.winHeight < imgHeight || top < 0 ? '0px' : bot > imageHover.winHeight ? imageHover.winHeight - imgHeight + 'px' : top + 'px'; return img.style.left = clientX + imageHover.offset.x; }, mouseout: function(e) { var img, target; target = e.target; img = $('#iHover'); $.hide(img); img.src = null; $.unbind(target, 'mousemove', imageHover.cb.mousemove); return $.unbind(target, 'mouseout', imageHover.cb.mouseout); } } }; imgPreloading = { init: function() { return g.callbacks.push(function(root) { var el, parent, thumb, thumbs, _i, _len, _results; thumbs = $$('img[md5]', root); _results = []; for (_i = 0, _len = thumbs.length; _i < _len; _i++) { thumb = thumbs[_i]; parent = thumb.parentNode; _results.push(el = $.el('img', { src: parent.href })); } return _results; }); } }; imgGif = { init: function() { return g.callbacks.push(function(root) { var src, thumb, thumbs, _i, _len, _results; thumbs = $$('img[md5]', root); _results = []; for (_i = 0, _len = thumbs.length; _i < _len; _i++) { thumb = thumbs[_i]; src = thumb.parentNode.href; _results.push(/gif$/.test(src) ? thumb.src = src : void 0); } return _results; }); } }; imgExpand = { init: function() { g.callbacks.push(imgExpand.cb.node); return imgExpand.dialog(); }, cb: { node: function(root) { var thumb, _i, _len, _ref2, _results; _ref2 = $$('img[md5]', root); _results = []; for (_i = 0, _len = _ref2.length; _i < _len; _i++) { thumb = _ref2[_i]; $.bind(thumb.parentNode, 'click', imgExpand.cb.toggle); _results.push(imgExpand.on ? imgExpand.expand(thumb.parentNode) : void 0); } return _results; }, toggle: function(e) { if (e.shiftKey || e.altKey || e.ctrlKey) { return; } e.preventDefault(); return imgExpand.toggle(e.target); }, all: function(e) { var ch, cw, imageType, thumb, thumbs, _i, _j, _len, _len2, _results, _results2; thumbs = $$('img[md5]'); imgExpand.on = e.target.checked; cw = d.body.clientWidth; ch = d.body.clientHeight; imageType = $("#imageType").value; if (imgExpand.on) { _results = []; for (_i = 0, _len = thumbs.length; _i < _len; _i++) { thumb = thumbs[_i]; _results.push(!thumb.style.display ? imgExpand.expand(thumb, cw, ch, imageType) : void 0); } return _results; } else { _results2 = []; for (_j = 0, _len2 = thumbs.length; _j < _len2; _j++) { thumb = thumbs[_j]; _results2.push(thumb.style.display ? imgExpand.contract(thumb) : void 0); } return _results2; } }, typeChange: function(e) { var ch, cw, imageType, img, _i, _len, _ref2, _results; cw = d.body.clientWidth; ch = d.body.clientHeight; imageType = e.target.value; _ref2 = $$('img[md5] + img'); _results = []; for (_i = 0, _len = _ref2.length; _i < _len; _i++) { img = _ref2[_i]; _results.push(imgExpand.resize(cw, ch, imageType, img)); } return _results; } }, toggle: function(img) { var ch, cw, imageType, thumb; thumb = img.parentNode.firstChild; cw = d.body.clientWidth; ch = d.body.clientHeight; imageType = $("#imageType").value; if (thumb.style.display) { return imgExpand.contract(thumb); } else { return imgExpand.expand(thumb, cw, ch, imageType); } }, contract: function(thumb) { $.show(thumb); return $.remove(thumb.nextSibling); }, expand: function(thumb, cw, ch, imageType) { var a, img; $.hide(thumb); a = thumb.parentNode; img = $.el('img', { src: a.href }); a.appendChild(img); return imgExpand.resize(cw, ch, imageType, img); }, resize: function(cw, ch, imageType, img) { var ih, iw, ratio, _, _ref2; _ref2 = $.x("preceding::span[@class][1]/text()[2]", img).textContent.match(/(\d+)x(\d+)/), _ = _ref2[0], iw = _ref2[1], ih = _ref2[2]; iw = Number(iw); ih = Number(ih); switch (imageType) { case 'full': return img.removeAttribute('style'); case 'fit width': if (iw > cw) { img.style.width = '100%'; return img.style.margin = '0px'; } break; case 'fit screen': ratio = Math.min(cw / iw, ch / ih); if (ratio < 1) { img.style.width = Math.floor(ratio * iw); return img.style.margin = '0px'; } } }, dialog: function() { var controls, delform, imageType, option, _i, _len, _ref2; controls = $.el('div', { id: 'imgControls', innerHTML: " " }); imageType = $.getValue('imageType', 'full'); _ref2 = $$('option', controls); for (_i = 0, _len = _ref2.length; _i < _len; _i++) { option = _ref2[_i]; if (option.textContent === imageType) { option.selected = true; break; } } $.bind($('select', controls), 'change', $.cb.value); $.bind($('select', controls), 'change', imgExpand.cb.typeChange); $.bind($('input', controls), 'click', imgExpand.cb.all); delform = $('form[name=delform]'); return $.prepend(delform, controls); } }; NAMESPACE = 'AEOS.4chan_x.'; g = { cache: {}, requests: {}, callbacks: [], flavors: ['http://regex.info/exif.cgi?url=', 'http://iqdb.org/?url=', 'http://tineye.com/search?url='].join('\n') }; main = { init: function() { var callback, navtopr, pathname, temp, tzOffset, _i, _len, _ref2; Favicon.halo = /ws/.test(Favicon["default"]) ? Favicon.haloSFW : Favicon.haloNSFW; pathname = location.pathname.substring(1).split('/'); g.BOARD = pathname[0], temp = pathname[1]; if (temp === 'res') { g.REPLY = temp; g.THREAD_ID = pathname[2]; } else { g.PAGENUM = parseInt(temp) || 0; } g.hiddenReply = $.getValue("hiddenReply/" + g.BOARD + "/", {}); tzOffset = (new Date()).getTimezoneOffset() / 60; g.chanOffset = 5 - tzOffset; if ($.isDST()) { g.chanOffset -= 1; } /* lastChecked = Number GM_getValue('lastChecked', '0') now = Date.now() DAY = 1000 * 60 * 60 * 24 if lastChecked < now - 1*DAY cutoff = now - 7*DAY while g.hiddenThreads.length if g.hiddenThreads[0].timestamp > cutoff break g.hiddenThreads.shift() while g.hiddenReplies.length if g.hiddenReplies[0].timestamp > cutoff break g.hiddenReplies.shift() GM_setValue("hiddenThreads/#{g.BOARD}/", JSON.stringify(g.hiddenThreads)) GM_setValue("hiddenReplies/#{g.BOARD}/", JSON.stringify(g.hiddenReplies)) GM_setValue('lastChecked', now.toString()) */ $.addStyle(main.css); if (location.hostname === 'sys.4chan.org') { qr.sys(); return; } if (navtopr = $('#navtopr')) { options.init(); } else if ($.config('404 Redirect') && d.title === '4chan - 404') { redirect(); } else { return; } Recaptcha.init(); $.bind($('form[name=post]'), 'submit', qr.cb.submit); if ($.config('Image Expansion')) { imgExpand.init(); } if ($.config('Image Auto-Gif')) { imgGif.init(); } if ($.config('Localize Time')) { localize.init(); } if ($.config('Sauce')) { sauce.init(); } if ($.config('Anonymize')) { anonymize.init(); } if ($.config('Image Hover')) { imageHover.init(); } if ($.config('Reply Hiding')) { replyHiding.init(); } if ($.config('Quick Reply')) { qr.init(); } if ($.config('Quick Report')) { quickReport.init(); } if ($.config('Thread Watcher')) { watcher.init(); } if ($.config('Keybinds')) { keybinds.init(); } if (g.REPLY) { if ($.config('Thread Updater')) { updater.init(); } if ($.config('Image Preloading')) { imgPreloading.init(); } if ($.config('Quick Reply') && $.config('Persistent QR')) { qr.persist(); } if ($.config('Post in Title')) { titlePost.init(); } if ($.config('Unread Count')) { unread.init(); } if ($.config('Auto Watch') && location.hash === '#watch') { watcher.watch(); } } else { if ($.config('Thread Hiding')) { threadHiding.init(); } if ($.config('Thread Navigation')) { nav.init(); } if ($.config('Thread Expansion')) { expandThread.init(); } if ($.config('Comment Expansion')) { expandComment.init(); } } _ref2 = g.callbacks; for (_i = 0, _len = _ref2.length; _i < _len; _i++) { callback = _ref2[_i]; callback(); } return $.bind(d.body, 'DOMNodeInserted', nodeInserted); }, css: '\ /* dialog styling */\ div.dialog {\ border: 1px solid;\ }\ div.dialog > div.move {\ cursor: move;\ }\ label, a {\ cursor: pointer;\ }\ \ .new {\ background: lime;\ }\ .favicon {\ cursor: pointer;\ }\ .error {\ color: red;\ }\ \ div.thread.stub > *:not(.block) {\ display: none;\ }\ \ form[name=delform] a img {\ border: 0px;\ float: left;\ margin: 0px 20px;\ }\ iframe {\ display: none;\ }\ \ #iHover {\ position: fixed;\ }\ \ #navlinks {\ position: fixed;\ top: 25px;\ right: 5px;\ }\ #navlinks > a {\ font-size: 16px;\ }\ \ #options {\ position: fixed;\ padding: 5px;\ text-align: right;\ }\ #options textarea {\ height: 100px;\ width: 500px;\ }\ \ #qr {\ position: fixed;\ }\ #qr > div.move {\ text-align: right;\ }\ #qr > form > div, /* ad */\ #qr td.rules {\ display: none;\ }\ #qr.auto:not(:hover) form {\ display: none;\ }\ #qr span.error {\ position: absolute;\ bottom: 0;\ left: 0;\ }\ \ #updater {\ position: fixed;\ text-align: right;\ }\ #updater input[type=text] {\ width: 50px;\ }\ #updater:not(:hover) {\ border: none;\ background: transparent;\ }\ #updater:not(:hover) > div:not(.move) {\ display: none;\ }\ \ #watcher {\ position: absolute;\ }\ #watcher > div.move {\ text-decoration: underline;\ padding: 5px 5px 0 5px;\ }\ #watcher > div:last-child {\ padding: 0 5px 5px 5px;\ }\ ' }; main.init(); }).call(this);