rendered paste body// ==UserScript==// @name 4chan x// @version 2.24.2// @namespace aeosynth// @description Adds various features.// @copyright 2009-2011 James Campos <james.r.campos@gmail.com>// @license MIT; http://en.wikipedia.org/wiki/Mit_license// @include http://boards.4chan.org/*// @include http://images.4chan.org/*// @include http://sys.4chan.org/*// @run-at document-start// @updateURL https://raw.github.com/MayhemYDG/4chan-x/stable/4chan_x.user.js// @icon http://mayhemydg.github.com/4chan-x/favicon.gif// ==/UserScript==/* LICENSE * * Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com> * Copyright (c) 2012 Nicolas Stepien <stepien.nicolas@gmail.com> * http://mayhemydg.github.com/4chan-x/ * 4chan X 2.24.2 * * 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://coffeescript.org/ * [2]: https://github.com/MayhemYDG/4chan-x * * CONTRIBUTORS * * Shou- - pentadactyl fixes * ferongr - new favicons * xat- - new favicons * Zixaphir - fix qr textarea - captcha-image gap * Ongpot - sfw favicon * thisisanon - nsfw + 404 favicons * Anonymous - empty favicon * Seiba - chrome quick reply focusing * herpaderpderp - recaptcha fixes * WakiMiko - 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 $, $$, DAY, Favicon, HOUR, MINUTE, Main, NAMESPACE, Recaptcha, SECOND, Time, VERSION, anonymize, conf, config, cooldown, d, engine, expandComment, expandThread, filter, flatten, g, getTitle, imgExpand, imgGif, imgHover, imgPreloading, key, keybinds, log, nav, options, qr, quoteBacklink, quoteDR, quoteInline, quoteOP, quotePreview, redirect, replyHiding, reportButton, revealSpoilers, sauce, strikethroughQuotes, threadHiding, threadStats, threading, titlePost, ui, unread, updater, val, watcher, _base, __slice = Array.prototype.slice; config = { main: { Enhancing: { '404 Redirect': [true, 'Redirect dead threads and images'], 'Keybinds': [true, 'Binds actions to keys'], 'Time Formatting': [true, 'Arbitrarily formatted timestamps, using your local time'], 'Report Button': [true, 'Add report buttons'], 'Comment Expansion': [true, 'Expand too long comments'], 'Thread Expansion': [true, 'View all replies'], 'Index Navigation': [true, 'Navigate to previous / next thread'], 'Reply Navigation': [false, 'Navigate to top / bottom of thread'], 'Check for Updates': [true, 'Check for updated versions of 4chan X'] }, Filtering: { 'Anonymize': [false, 'Make everybody anonymous'], 'Filter': [false, 'Self-moderation placebo'], 'Filter OPs': [false, 'Filter OPs along with their threads'], 'Reply Hiding': [true, 'Hide single replies'], 'Thread Hiding': [true, 'Hide entire threads'], 'Show Stubs': [true, 'Of hidden threads / replies'] }, Imaging: { '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'], 'Sauce': [true, 'Add sauce to images'], 'Reveal Spoilers': [false, 'Replace spoiler thumbnails by the original thumbnail'] }, Monitoring: { 'Thread Updater': [true, 'Update threads. Has more options in its own dialog.'], 'Unread Count': [true, 'Show unread post count in tab title'], 'Post in Title': [true, 'Show the op\'s post in the tab title'], 'Thread Stats': [true, 'Display reply and image count'], 'Thread Watcher': [true, 'Bookmark threads'], 'Auto Watch': [true, 'Automatically watch threads that you start'], 'Auto Watch Reply': [false, 'Automatically watch threads that you reply to'] }, Posting: { 'Auto Noko': [true, 'Always redirect to your post'], 'Cooldown': [true, 'Prevent `flood detected` errors'], 'Quick Reply': [true, 'Reply without leaving the page'], 'Persistent QR': [false, 'Quick reply won\'t disappear after posting. Only in replies.'], 'Auto Hide QR': [true, 'Automatically auto-hide the quick reply when posting'], 'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting'] }, Quoting: { 'Quote Backlinks': [true, 'Add quote backlinks'], 'OP Backlinks': [false, 'Add backlinks to the OP'], 'Quote Highlighting': [true, 'Highlight the previewed post'], 'Quote Inline': [true, 'Show quoted post inline on quote click'], 'Quote Preview': [true, 'Show quote content on hover'], 'Indicate OP quote': [true, 'Add \'(OP)\' to OP quotes'], 'Indicate Cross-thread Quotes': [true, 'Add \'(Cross-thread)\' to cross-threads quotes'], 'Forward Hiding': [true, 'Hide original posts of inlined backlinks'] } }, filter: { name: '', tripcode: '', email: '', subject: '', comment: '', filename: '', filesize: '', md5: '' }, flavors: ['http://iqdb.org/?url=', 'http://google.com/searchbyimage?image_url=', '#http://tineye.com/search?url=', '#http://saucenao.com/search.php?db=999&url=', '#http://3d.iqdb.org/?url=', '#http://regex.info/exif.cgi?imgurl=', '#http://imgur.com/upload?url=', '#http://ompldr.org/upload?url1='].join('\n'), time: '%m/%d/%y(%a)%H:%M', backlink: '>>%id', favicon: 'ferongr', hotkeys: { openOptions: 'ctrl+o', close: 'Esc', spoiler: 'ctrl+s', openQR: 'i', openEmptyQR: 'I', submit: 'alt+s', nextReply: 'J', previousReply: 'K', nextThread: 'n', previousThread: 'p', nextPage: 'L', previousPage: 'H', zero: '0', openThreadTab: 'o', openThread: 'O', expandThread: 'e', watch: 'w', hide: 'x', expandImages: 'm', expandAllImages: 'M', update: 'u', unreadCountTo0: 'z' }, updater: { checkbox: { 'Scrolling': [false, 'Scroll updated posts into view. Only enabled at bottom of page.'], 'Scroll BG': [false, 'Scroll background tabs'], 'Verbose': [true, 'Show countdown timer, new post count'], 'Auto Update': [true, 'Automatically fetch new posts'] }, 'Interval': 30 } }; log = typeof (_base = console.log).bind === "function" ? _base.bind(console) : void 0; conf = {}; (flatten = function(parent, obj) { var key, val, _results; if (obj.length) { if (typeof obj[0] === 'boolean') { return conf[parent] = obj[0]; } else { return conf[parent] = obj; } } else if (typeof obj === 'object') { _results = []; for (key in obj) { val = obj[key]; _results.push(flatten(key, val)); } return _results; } else { return conf[parent] = obj; } })(null, config); NAMESPACE = '4chan_x.'; VERSION = '2.24.2'; SECOND = 1000; MINUTE = 60 * SECOND; HOUR = 60 * MINUTE; DAY = 24 * HOUR; engine = /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase(); d = document; g = { callbacks: [] }; ui = { dialog: function(id, position, html) { var el, saved, _ref; el = d.createElement('div'); el.className = 'reply dialog'; el.innerHTML = html; el.id = id; el.style.cssText = (saved = localStorage["" + NAMESPACE + id + ".position"]) ? saved : position; if ((_ref = el.querySelector('div.move')) != null) { _ref.addEventListener('mousedown', ui.dragstart, false); } return el; }, dragstart: function(e) { var el, rect; e.preventDefault(); ui.el = el = this.parentNode; d.addEventListener('mousemove', ui.drag, false); d.addEventListener('mouseup', ui.dragend, false); rect = el.getBoundingClientRect(); ui.dx = e.clientX - rect.left; ui.dy = e.clientY - rect.top; ui.width = d.body.clientWidth - el.offsetWidth; return ui.height = d.body.clientHeight - el.offsetHeight; }, drag: function(e) { var bottom, left, right, style, top; left = e.clientX - ui.dx; top = e.clientY - ui.dy; left = left < 10 ? 0 : ui.width - left < 10 ? null : left; top = top < 10 ? 0 : ui.height - top < 10 ? null : top; right = left === null ? 0 : null; bottom = top === null ? 0 : null; style = ui.el.style; style.top = top; style.right = right; style.bottom = bottom; return style.left = left; }, dragend: function() { var el; el = ui.el; localStorage["" + NAMESPACE + el.id + ".position"] = el.style.cssText; d.removeEventListener('mousemove', ui.drag, false); return d.removeEventListener('mouseup', ui.dragend, false); }, hover: function(e) { var clientHeight, clientWidth, clientX, clientY, el, height, style, top, _ref; clientX = e.clientX, clientY = e.clientY; el = ui.el; style = el.style; _ref = d.body, clientHeight = _ref.clientHeight, clientWidth = _ref.clientWidth; height = el.offsetHeight; top = clientY - 120; style.top = clientHeight < height || top < 0 ? 0 : top + height > clientHeight ? clientHeight - height : top; if (clientX < clientWidth - 400) { style.left = clientX + 45; return style.right = null; } else { style.left = null; return style.right = clientWidth - clientX + 45; } }, hoverend: function() { return ui.el.parentNode.removeChild(ui.el); } }; /* loosely follows the jquery api: http://api.jquery.com/ not chainable */ $ = 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($, { ready: function(fc) { var cb; if (/interactive|complete/.test(d.readyState)) return fc(); cb = function() { $.off(d, 'DOMContentLoaded', cb); return fc(); }; return $.on(d, 'DOMContentLoaded', cb); }, id: function(id) { return d.getElementById(id); }, globalEval: function(code) { var script; script = $.el('script', { textContent: "(" + code + ")()" }); $.add(d.head, script); return $.rm(script); }, ajax: function(url, cb, opts) { var event, headers, key, r, type, val; if (opts == null) opts = {}; type = opts.type, event = opts.event, headers = opts.headers; type || (type = 'get'); event || (event = 'onload'); r = new XMLHttpRequest(); r.open(type, url, true); for (key in headers) { val = headers[key]; r.setRequestHeader(key, val); } r[event] = cb; r.send(); return r; }, cache: function(url, cb) { var req; if (req = $.cache.requests[url]) { if (req.readyState === 4) { return cb.call(req); } else { return req.callbacks.push(cb); } } else { req = $.ajax(url, (function() { var cb, _i, _len, _ref, _results; _ref = this.callbacks; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { cb = _ref[_i]; _results.push(cb.call(this)); } return _results; })); req.callbacks = [cb]; return $.cache.requests[url] = req; } }, cb: { checked: function() { $.set(this.name, this.checked); return conf[this.name] = this.checked; }, value: function() { $.set(this.name, this.value); return conf[this.name] = this.value; } }, addStyle: function(css) { var style; style = $.el('style', { textContent: css }); $.add(d.head, style); return style; }, 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); }, addClass: function(el, className) { return el.classList.add(className); }, removeClass: function(el, className) { return el.classList.remove(className); }, rm: function(el) { return el.parentNode.removeChild(el); }, add: 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; }, on: function(el, eventType, handler) { return el.addEventListener(eventType, handler, false); }, off: function(el, eventType, handler) { return el.removeEventListener(eventType, handler, false); }, isDST: function() { /* http://en.wikipedia.org/wiki/Eastern_Time_Zone Its UTC time offset is −5 hrs (UTC−05) during standard time and −4 hrs (UTC−04) during daylight saving time. Since 2007, the local time changes at 02:00 EST to 03:00 EDT on the second Sunday in March and returns at 02:00 EDT to 01:00 EST on the first Sunday in November, in the U.S. as well as in Canada. 0200 EST (UTC-05) = 0700 UTC 0200 EDT (UTC-04) = 0600 UTC */ var D, date, day, hours, month, sunday; D = new Date(); date = D.getUTCDate(); day = D.getUTCDay(); hours = D.getUTCHours(); month = D.getUTCMonth(); if (month < 2 || 10 < month) return false; if ((2 < month && month < 10)) return true; sunday = date - day; if (month === 2) { if (sunday < 8) return false; if (sunday < 15 && day === 0) { if (hours < 7) return false; return true; } return true; } if (sunday < 1) return true; if (sunday < 8 && day === 0) { if (hours < 6) return true; return false; } return false; } }); $.cache.requests = {}; $.extend($, typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null ? { "delete": function(name) { name = NAMESPACE + name; return GM_deleteValue(name); }, get: function(name, defaultValue) { var value; name = NAMESPACE + name; if (value = GM_getValue(name)) { return JSON.parse(value); } else { return defaultValue; } }, set: function(name, value) { name = NAMESPACE + name; localStorage[name] = JSON.stringify(value); return GM_setValue(name, JSON.stringify(value)); } } : { "delete": function(name) { name = NAMESPACE + name; return delete localStorage[name]; }, get: function(name, defaultValue) { var value; name = NAMESPACE + name; if (value = localStorage[name]) { return JSON.parse(value); } else { return defaultValue; } }, set: function(name, value) { name = NAMESPACE + name; return localStorage[name] = JSON.stringify(value); } }); for (key in conf) { val = conf[key]; conf[key] = $.get(key, val); } $$ = function(selector, root) { if (root == null) root = d.body; return Array.prototype.slice.call(root.querySelectorAll(selector)); }; filter = { regexps: {}, callbacks: [], init: function() { var f, filter, key, m, _i, _len; for (key in config.filter) { if (!(m = conf[key].match(/^\/.+\/\w*$/gm))) continue; this.regexps[key] = []; for (_i = 0, _len = m.length; _i < _len; _i++) { filter = m[_i]; f = filter.match(/^\/(.+)\/(\w*)$/); try { this.regexps[key].push(RegExp(f[1], f[2])); } catch (e) { alert(e.message); } } this.callbacks.push(this[key]); } return g.callbacks.push(this.node); }, node: function(root) { if (!root.className) { if (filter.callbacks.some(function(callback) { return callback(root); })) { return replyHiding.hideHide($('td:not([nowrap])', root)); } } else if (root.className === 'op' && !g.REPLY && conf['Filter OPs']) { if (filter.callbacks.some(function(callback) { return callback(root); })) { return threadHiding.hideHide(root.parentNode); } } }, test: function(key, value) { return filter.regexps[key].some(function(regexp) { return regexp.test(value); }); }, name: function(root) { var name; name = root.className === 'op' ? $('.postername', root) : $('.commentpostername', root); return filter.test('name', name.textContent); }, tripcode: function(root) { var trip; if (trip = $('.postertrip', root)) { return filter.test('tripcode', trip.textContent); } }, email: function(root) { var mail; if (mail = $('.linkmail', root)) return filter.test('email', mail.href); }, subject: function(root) { var sub; sub = root.className === 'op' ? $('.filetitle', root) : $('.replytitle', root); return filter.test('subject', sub.textContent); }, comment: function(root) { return filter.test('comment', ($.el('a', { innerHTML: $('blockquote', root).innerHTML.replace(/<br>/g, '\n') })).textContent); }, filename: function(root) { var file; if (file = $('.filesize span', root)) { return filter.test('filename', file.title); } }, filesize: function(root) { var img; if (img = $('img[md5]', root)) return filter.test('filesize', img.alt); }, md5: function(root) { var img; if (img = $('img[md5]', root)) { return filter.test('md5', img.getAttribute('md5')); } } }; strikethroughQuotes = { init: function() { return g.callbacks.push(function(root) { var el, quote, _i, _len, _ref, _results; if (root.className === 'inline') return; _ref = $$('.quotelink', root); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { quote = _ref[_i]; if (el = $.id(quote.hash.slice(1))) { if (el.parentNode.parentNode.parentNode.hidden) { _results.push($.addClass(quote, 'filtered')); } else { _results.push(void 0); } } else { _results.push(void 0); } } return _results; }); } }; expandComment = { init: function() { var a, _i, _len, _ref, _results; _ref = $$('.abbr a'); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { a = _ref[_i]; _results.push($.on(a, 'click', expandComment.expand)); } return _results; }, expand: function(e) { var a, replyID, threadID, _, _ref; e.preventDefault(); _ref = this.href.match(/(\d+)#(\d+)/), _ = _ref[0], threadID = _ref[1], replyID = _ref[2]; this.textContent = "Loading " + replyID + "..."; threadID = this.pathname.split('/').pop() || $.x('ancestor::div[@class="thread"]/div', this).id; a = this; return $.cache(this.pathname, (function() { return expandComment.parse(this, a, threadID, replyID); })); }, parse: function(req, a, threadID, replyID) { var body, bq, quote, reply, _i, _j, _len, _len2, _ref, _ref2; if (req.status !== 200) { a.textContent = "" + req.status + " " + req.statusText; return; } body = $.el('body', { innerHTML: req.responseText }); if (threadID === replyID) { bq = $('blockquote', 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; } } } _ref2 = $$('.quotelink', bq); for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { quote = _ref2[_j]; if (quote.getAttribute('href') === quote.hash) { quote.pathname = "/" + g.BOARD + "/res/" + threadID; } if (quote.hash.slice(1) === threadID) quote.innerHTML += ' (OP)'; if (conf['Quote Preview']) { $.on(quote, 'mouseover', quotePreview.mouseover); $.on(quote, 'mousemove', ui.hover); $.on(quote, 'mouseout', quotePreview.mouseout); } if (conf['Quote Inline']) $.on(quote, 'click', quoteInline.toggle); } return $.replace(a.parentNode.parentNode, bq); } }; expandThread = { init: function() { var a, span, _i, _len, _ref, _results; _ref = $$('.omittedposts'); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { span = _ref[_i]; a = $.el('a', { textContent: "+ " + span.textContent, className: 'omittedposts', href: 'javascript:;' }); $.on(a, 'click', expandThread.cb.toggle); _results.push($.replace(span, a)); } return _results; }, cb: { toggle: function() { var thread; thread = this.parentNode; return expandThread.toggle(thread); } }, toggle: function(thread) { var a, backlink, num, pathname, prev, table, threadID, _i, _len, _ref, _ref2, _results; threadID = thread.firstChild.id; pathname = "/" + g.BOARD + "/res/" + threadID; a = $('.omittedposts', thread); switch (a.textContent[0]) { case '+': if ((_ref = $('.op .container', thread)) != null) _ref.innerHTML = ''; a.textContent = a.textContent.replace('+', 'X Loading...'); return $.cache(pathname, (function() { return expandThread.parse(this, pathname, thread, a); })); case 'X': a.textContent = a.textContent.replace('X Loading...', '+'); return $.cache[pathname].abort(); case '-': a.textContent = a.textContent.replace('-', '+'); num = (function() { switch (g.BOARD) { case 'b': return 3; case 't': return 1; default: return 5; } })(); table = $.x("following::br[@clear]/preceding::table[" + num + "]", a); while ((prev = table.previousSibling) && (prev.nodeName === 'TABLE')) { $.rm(prev); } _ref2 = $$('.op a.backlink'); _results = []; for (_i = 0, _len = _ref2.length; _i < _len; _i++) { backlink = _ref2[_i]; if (!$.id(backlink.hash.slice(1))) { _results.push($.rm(backlink)); } else { _results.push(void 0); } } return _results; } }, parse: function(req, pathname, thread, a) { var body, br, frag, href, link, next, quote, reply, _i, _j, _len, _len2, _ref, _ref2; if (req.status !== 200) { a.textContent = "" + req.status + " " + req.statusText; $.off(a, 'click', expandThread.cb.toggle); return; } a.textContent = a.textContent.replace('X Loading...', '-'); body = $.el('body', { innerHTML: req.responseText }); frag = d.createDocumentFragment(); _ref = $$('.reply', body); for (_i = 0, _len = _ref.length; _i < _len; _i++) { reply = _ref[_i]; _ref2 = $$('.quotelink', reply); for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { quote = _ref2[_j]; if ((href = quote.getAttribute('href')) === quote.hash) { quote.pathname = pathname; } else if (href !== quote.href) { quote.href = "res/" + href; } } link = $('.quotejs', reply); link.href = "res/" + thread.firstChild.id + "#" + reply.id; link.nextSibling.href = "res/" + thread.firstChild.id + "#q" + reply.id; $.add(frag, reply.parentNode.parentNode.parentNode); } while ((next = a.nextSibling) && !next.clear) { $.rm(next); } br = next; return $.before(br, frag); } }; replyHiding = { init: function() { return g.callbacks.push(function(root) { var a, dd, id, reply; if (!(dd = $('.doubledash', root))) return; dd.className = 'replyhider'; a = $.el('a', { textContent: '[ - ]', href: 'javascript:;' }); $.on(a, 'click', replyHiding.cb.hide); $.replace(dd.firstChild, a); reply = dd.nextSibling; id = reply.id; if (id in g.hiddenReplies) return replyHiding.hide(reply); }); }, cb: { hide: function() { var reply; reply = this.parentNode.nextSibling; return replyHiding.hide(reply); }, show: function() { var div, table; div = this.parentNode; table = div.nextSibling; replyHiding.show(table); return $.rm(div); } }, hide: function(reply) { var id, quote, _i, _len, _ref; replyHiding.hideHide(reply); id = reply.id; _ref = $$(".quotelink[href='#" + id + "'], .backlink[href='#" + id + "']"); for (_i = 0, _len = _ref.length; _i < _len; _i++) { quote = _ref[_i]; $.addClass(quote, 'filtered'); } g.hiddenReplies[id] = Date.now(); return $.set("hiddenReplies/" + g.BOARD + "/", g.hiddenReplies); }, hideHide: function(reply) { var a, div, name, table, trip, _ref; table = reply.parentNode.parentNode.parentNode; if (table.hidden) return; table.hidden = true; if (conf['Show Stubs']) { name = $('.commentpostername', reply).textContent; trip = ((_ref = $('.postertrip', reply)) != null ? _ref.textContent : void 0) || ''; a = $.el('a', { textContent: "[ + ] " + name + " " + trip, href: 'javascript:;' }); $.on(a, 'click', replyHiding.cb.show); div = $.el('div', { className: 'stub' }); $.add(div, a); return $.before(table, div); } }, show: function(table) { var id, quote, _i, _len, _ref; table.hidden = false; id = $('td[id]', table).id; _ref = $$(".quotelink[href='#" + id + "'], .backlink[href='#" + id + "']"); for (_i = 0, _len = _ref.length; _i < _len; _i++) { quote = _ref[_i]; $.removeClass(quote, 'filtered'); } delete g.hiddenReplies[id]; return $.set("hiddenReplies/" + g.BOARD + "/", g.hiddenReplies); } }; keybinds = { init: function() { var node, _i, _len, _ref; _ref = $$('[accesskey]'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { node = _ref[_i]; node.removeAttribute('accesskey'); } return $.on(d, 'keydown', keybinds.keydown); }, keydown: function(e) { var o, range, selEnd, selStart, ta, thread, valEnd, valMid, valStart, value, _ref, _ref2, _ref3; updater.focus = true; if (((_ref = e.target.nodeName) === 'TEXTAREA' || _ref === 'INPUT') && !e.altKey && !e.ctrlKey && !(e.keyCode === 27)) { return; } if (!(key = keybinds.keyCode(e))) return; thread = nav.getThread(); switch (key) { case conf.openOptions: if (!$.id('overlay')) options.dialog(); break; case conf.close: if (o = $.id('overlay')) { $.rm(o); } else if (qr.el) { qr.close(); } break; case conf.spoiler: ta = e.target; if (ta.nodeName !== 'TEXTAREA') return; 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; ta.setSelectionRange(range, range); break; case conf.zero: window.location = "/" + g.BOARD + "/0#0"; break; case conf.openEmptyQR: keybinds.qr(thread); break; case conf.nextReply: keybinds.hl.next(thread); break; case conf.previousReply: keybinds.hl.prev(thread); break; case conf.expandAllImages: keybinds.img(thread, true); break; case conf.openThread: keybinds.open(thread); break; case conf.expandThread: expandThread.toggle(thread); break; case conf.openQR: keybinds.qr(thread, true); break; case conf.expandImages: keybinds.img(thread); break; case conf.nextThread: nav.next(); break; case conf.openThreadTab: keybinds.open(thread, true); break; case conf.previousThread: nav.prev(); break; case conf.update: updater.update(); break; case conf.watch: watcher.toggle(thread); break; case conf.hide: threadHiding.toggle(thread); break; case conf.nextPage: if ((_ref2 = $('input[value=Next]')) != null) _ref2.click(); break; case conf.previousPage: if ((_ref3 = $('input[value=Previous]')) != null) _ref3.click(); break; case conf.submit: if (qr.el) { qr.submit.call($('form', qr.el)); } else { $('.postarea form').submit(); } break; case conf.unreadCountTo0: unread.replies = []; unread.updateTitle(); Favicon.update(); break; default: return; } return e.preventDefault(); }, keyCode: function(e) { var c, kc; key = (function() { switch (kc = e.keyCode) { case 8: return ''; case 27: return 'Esc'; case 37: return 'Left'; case 38: return 'Up'; case 39: return 'Right'; case 40: return 'Down'; case 48: case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: case 65: case 66: case 67: case 68: case 69: case 70: case 71: case 72: case 73: case 74: case 75: case 76: case 77: case 78: case 79: case 80: case 81: case 82: case 83: case 84: case 85: case 86: case 87: case 88: case 89: case 90: c = String.fromCharCode(kc); if (e.shiftKey) { return c; } else { return c.toLowerCase(); } break; default: return null; } })(); if (key) { if (e.altKey) key = 'alt+' + key; if (e.ctrlKey) key = 'ctrl+' + key; } return key; }, 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.parentNode); } }, qr: function(thread, quote) { if (quote) { return qr.quote.call($('.quotejs + a', $('.replyhl', thread) || thread)); } else { if (!qr.el) qr.dialog('', thread != null ? thread.firstChild.id : void 0); return $('textarea', qr.el).focus(); } }, open: function(thread, tab) { var id, open, url; id = thread.firstChild.id; url = "http://boards.4chan.org/" + g.BOARD + "/res/" + id; if (tab) { open = GM_openInTab || window.open; return 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); if ($.x('ancestor::div[@class="thread"]', next) !== thread) return; 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: '▲', href: 'javascript:;' }); next = $.el('a', { textContent: '▼', href: 'javascript:;' }); $.on(prev, 'click', nav.prev); $.on(next, 'click', nav.next); $.add(span, prev, $.tn(' '), next); return $.add(d.body, span); }, prev: function() { return nav.scroll(-1); }, next: function() { return nav.scroll(+1); }, threads: [], getThread: function(full) { var bottom, i, rect, thread, _len, _ref; nav.threads = $$('div.thread:not([hidden])'); _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; if (g.REPLY) { if (delta === -1) { window.scrollTo(0, 0); } else { window.scrollTo(0, d.body.scrollHeight); } return; } _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) { if (g.PAGENUM === 0) { window.scrollTo(0, 0); } else { window.location = "" + (g.PAGENUM - 1) + "#0"; } return; } if (delta === +1) { if (i === nav.threads.length || (innerHeight + pageYOffset === d.body.scrollHeight)) { if ($('table.pages input[value="Next"]')) { window.location = "" + (g.PAGENUM + 1) + "#0"; 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', href: 'javascript:;' }); $.on(a, 'click', options.dialog); $.replace(home, a); home = $('#navbotr a'); a = $.el('a', { textContent: '4chan X', href: 'javascript:;' }); $.on(a, 'click', options.dialog); $.replace(home, a); if (!$.get('firstrun')) { $.set('firstrun', true); return options.dialog(); } }, dialog: function() { var arr, back, checked, description, dialog, favicon, hiddenNum, hiddenThreads, indicator, indicators, input, key, li, obj, overlay, ta, time, ul, _i, _j, _k, _len, _len2, _len3, _ref, _ref2, _ref3, _ref4; dialog = ui.dialog('options', '', '\<div id=optionsbar>\ <div id=credits>\ <a target=_blank href=http://mayhemydg.github.com/4chan-x/>4chan X</a> | ' + VERSION + '\ | <a target=_blank href=http://mayhemydg.github.com/4chan-x/#bug-report>Issues</a>\ </div>\ <div>\ <label for=main_tab>Main</label>\ | <label for=filter_tab>Filter</label>\ | <label for=flavors_tab>Sauce</label>\ | <label for=rice_tab>Rice</label>\ | <label for=keybinds_tab>Keybinds</label>\ </div>\</div>\<hr>\<div id=content>\ <input type=radio name=tab hidden id=main_tab checked>\ <div></div>\ <input type=radio name=tab hidden id=flavors_tab>\ <div>\ <div class=error><code>Sauce</code> is disabled.</div>\ <textarea name=flavors id=flavors></textarea>\ </div>\ <input type=radio name=tab hidden id=filter_tab>\ <div>\ <div class=error><code>Filter</code> is disabled.</div>\ Use <a href=https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions>regular expressions</a>, one per line.<br>\ For example, <code>/weeaboo/i</code> will filter posts containing `weeaboo` case-insensitive.\ <p>Name:<br><textarea name=name></textarea></p>\ <p>Tripcode:<br><textarea name=tripcode></textarea></p>\ <p>E-mail:<br><textarea name=email></textarea></p>\ <p>Subject:<br><textarea name=subject></textarea></p>\ <p>Comment:<br><textarea name=comment></textarea></p>\ <p>Filename:<br><textarea name=filename></textarea></p>\ <p>Filesize:<br><textarea name=filesize></textarea></p>\ <p>Image MD5:<br><textarea name=md5></textarea></p>\ </div>\ <input type=radio name=tab hidden id=rice_tab>\ <div>\ <div class=error><code>Quote Backlinks</code> are disabled.</div>\ <ul>\ Backlink formatting\ <li><input type=text name=backlink> : <span id=backlinkPreview></span></li>\ </ul>\ <div class=error><code>Time Formatting</code> is disabled.</div>\ <ul>\ Time formatting\ <li><input type=text name=time> : <span id=timePreview></span></li>\ <li>Supported <a href=http://en.wikipedia.org/wiki/Date_%28Unix%29#Formatting>format specifiers</a>:</li>\ <li>Day: %a, %A, %d, %e</li>\ <li>Month: %m, %b, %B</li>\ <li>Year: %y</li>\ <li>Hour: %k, %H, %l (lowercase L), %I (uppercase i), %p, %P</li>\ <li>Minutes: %M</li>\ </ul>\ <div class=error><code>Unread Count</code> is disabled.</div>\ Unread favicons<br>\ <select name=favicon>\ <option value=ferongr>ferongr</option>\ <option value=xat->xat-</option>\ <option value=Mayhem>Mayhem</option>\ <option value=Original>Original</option>\ <option value=None>None</option>\ </select>\ <span></span>\ </div>\ <input type=radio name=tab hidden id=keybinds_tab>\ <div>\ <div class=error><code>Keybinds</code> are disabled.</div>\ <table><tbody>\ <tr><th>Actions</th><th>Keybinds</th></tr>\ <tr><td>Open Options</td><td><input name=openOptions></td></tr>\ <tr><td>Close Options or QR</td><td><input name=close></td></tr>\ <tr><td>Quick spoiler</td><td><input name=spoiler></td></tr>\ <tr><td>Open QR with post number inserted</td><td><input name=openQR></td></tr>\ <tr><td>Open QR without post number inserted</td><td><input name=openEmptyQR></td></tr>\ <tr><td>Submit post</td><td><input name=submit></td></tr>\ <tr><td>Select next reply</td><td><input name=nextReply ></td></tr>\ <tr><td>Select previous reply</td><td><input name=previousReply></td></tr>\ <tr><td>See next thread</td><td><input name=nextThread></td></tr>\ <tr><td>See previous thread</td><td><input name=previousThread></td></tr>\ <tr><td>Jump to the next page</td><td><input name=nextPage></td></tr>\ <tr><td>Jump to the previous page</td><td><input name=previousPage></td></tr>\ <tr><td>Jump to page 0</td><td><input name=zero></td></tr>\ <tr><td>Open thread in current tab</td><td><input name=openThread></td></tr>\ <tr><td>Open thread in new tab</td><td><input name=openThreadTab></td></tr>\ <tr><td>Expand thread</td><td><input name=expandThread></td></tr>\ <tr><td>Watch thread</td><td><input name=watch></td></tr>\ <tr><td>Hide thread</td><td><input name=hide></td></tr>\ <tr><td>Expand selected image</td><td><input name=expandImages></td></tr>\ <tr><td>Expand all images</td><td><input name=expandAllImages></td></tr>\ <tr><td>Update now</td><td><input name=update></td></tr>\ <tr><td>Reset the unread count to 0</td><td><input name=unreadCountTo0></td></tr>\ </tbody></table>\ </div>\</div>'); _ref = config.main; for (key in _ref) { obj = _ref[key]; ul = $.el('ul', { textContent: key }); for (key in obj) { arr = obj[key]; checked = conf[key] ? 'checked' : ''; description = arr[1]; li = $.el('li', { innerHTML: "<label><input type=checkbox name='" + key + "' " + checked + ">" + key + "</label><span class=description>: " + description + "</span>" }); $.on($('input', li), 'click', $.cb.checked); $.add(ul, li); } $.add($('#main_tab + div', dialog), ul); } hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {}); hiddenNum = Object.keys(g.hiddenReplies).length + Object.keys(hiddenThreads).length; li = $.el('li', { innerHTML: "<button>hidden: " + hiddenNum + "</button> <span class=description>: Forget all hidden posts. Useful if you accidentally hide a post and have `Show Stubs` disabled." }); $.on($('button', li), 'click', options.clearHidden); $.add($('ul:nth-child(2)', dialog), li); _ref2 = $$('textarea', dialog); for (_i = 0, _len = _ref2.length; _i < _len; _i++) { ta = _ref2[_i]; ta.textContent = conf[ta.name]; $.on(ta, 'change', $.cb.value); } (back = $('[name=backlink]', dialog)).value = conf['backlink']; (time = $('[name=time]', dialog)).value = conf['time']; $.on(back, 'keyup', $.cb.value); $.on(back, 'keyup', options.backlink); $.on(time, 'keyup', $.cb.value); $.on(time, 'keyup', options.time); favicon = $('select', dialog); favicon.value = conf['favicon']; $.on(favicon, 'change', $.cb.value); $.on(favicon, 'change', options.favicon); _ref3 = $$('#keybinds_tab + div input', dialog); for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) { input = _ref3[_j]; input.type = 'text'; input.value = conf[input.name]; $.on(input, 'keydown', options.keybind); } indicators = {}; _ref4 = $$('.error', dialog); for (_k = 0, _len3 = _ref4.length; _k < _len3; _k++) { indicator = _ref4[_k]; key = indicator.firstChild.textContent; indicator.hidden = conf[key]; indicators[key] = indicator; $.on($("[name='" + key + "']", dialog), 'click', function() { return indicators[this.name].hidden = this.checked; }); } overlay = $.el('div', { id: 'overlay' }); $.on(overlay, 'click', function() { return $.rm(overlay); }); $.on(dialog, 'click', function(e) { return e.stopPropagation(); }); $.add(overlay, dialog); $.add(d.body, overlay); options.backlink.call(back); options.time.call(time); return options.favicon.call(favicon); }, clearHidden: function() { $["delete"]("hiddenReplies/" + g.BOARD + "/"); $["delete"]("hiddenThreads/" + g.BOARD + "/"); this.textContent = "hidden: 0"; return g.hiddenReplies = {}; }, keybind: function(e) { if (e.keyCode === 9) return; e.preventDefault(); e.stopPropagation(); if ((key = keybinds.keyCode(e)) == null) return; this.value = key; return $.cb.value.call(this); }, time: function() { Time.foo(); Time.date = new Date(); return $.id('timePreview').textContent = Time.funk(Time); }, backlink: function() { return $.id('backlinkPreview').textContent = conf['backlink'].replace(/%id/, '123456789'); }, favicon: function() { Favicon["switch"](); if (g.REPLY && conf['Unread Count']) Favicon.update(); return this.nextElementSibling.innerHTML = "<img src=" + Favicon.unreadSFW + "> <img src=" + Favicon.unreadNSFW + "> <img src=" + Favicon.unreadDead + ">"; } }; cooldown = { init: function() { var match, time, _; if (match = location.search.match(/cooldown=(\d+)/)) { _ = match[0], time = match[1]; if ($.get(g.BOARD + '/cooldown', 0) < time) { $.set(g.BOARD + '/cooldown', time); } } if (Date.now() < $.get(g.BOARD + '/cooldown', 0)) cooldown.start(); $.on(window, 'storage', function(e) { if (e.key === ("" + NAMESPACE + g.BOARD + "/cooldown")) { return cooldown.start(); } }); if (g.REPLY) return $('.postarea form').action += '?cooldown'; }, start: function() { var submit, _i, _len, _ref; cooldown.duration = Math.ceil(($.get(g.BOARD + '/cooldown', 0) - Date.now()) / 1000); if (!(cooldown.duration > 0)) return; _ref = $$('#com_submit'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { submit = _ref[_i]; submit.value = cooldown.duration; submit.disabled = true; } return setTimeout(cooldown.cb, 1000); }, cb: function() { var submit, submits, _i, _j, _len, _len2, _results; submits = $$('#com_submit'); if (--cooldown.duration) { setTimeout(cooldown.cb, 1000); _results = []; for (_i = 0, _len = submits.length; _i < _len; _i++) { submit = submits[_i]; _results.push(submit.value = cooldown.duration); } return _results; } else { for (_j = 0, _len2 = submits.length; _j < _len2; _j++) { submit = submits[_j]; submit.disabled = false; submit.value = 'Submit'; } return qr.autoPost(); } } }; qr = { init: function() { var iframe; g.callbacks.push(qr.node); $.on($('#recaptcha_challenge_field_holder'), 'DOMNodeInserted', qr.captchaNode); qr.captchaTime = Date.now(); qr.spoiler = $('.postarea label') ? '<label> [<input type=checkbox name=spoiler>Spoiler Image?]</label>' : ''; qr.acceptFiles = $('.rules').textContent.match(/: (.+) /)[1].replace(/\w+/g, function(type) { switch (type) { case 'JPG': return 'image/JPEG'; case 'PDF': return 'application/' + type; default: return 'image/' + type; } }); iframe = $.el('iframe', { name: 'iframe', hidden: true }); $.add(d.body, iframe); return $('#recaptcha_response_field').id = ''; }, attach: function() { var fileDiv; fileDiv = $.el('div', { innerHTML: "<input type=file name=upfile accept='" + qr.acceptFiles + "'><a>X</a>" }); $.on(fileDiv.firstChild, 'change', qr.validateFileSize); $.on(fileDiv.lastChild, 'click', (function() { return $.rm(this.parentNode); })); return $.add($('#files', qr.el), fileDiv); }, attachNext: function() { var file, fileDiv, oldFile; fileDiv = $.rm($('#files div', qr.el)); file = fileDiv.firstChild; oldFile = $('#qr_form input[type=file]', qr.el); return $.replace(oldFile, file); }, autoPost: function() { if (qr.el && $('#auto', qr.el).checked) { return qr.submit.call($('form', qr.el)); } }, captchaNode: function(e) { if (!qr.el) return; val = e.target.value; $('img', qr.el).src = "http://www.google.com/recaptcha/api/image?c=" + val; qr.challenge = val; return qr.captchaTime = Date.now(); }, captchaKeydown: function(e) { var captchas; if (!(e.keyCode === 13 && this.value)) return; if (cooldown.duration) $('#auto', qr.el).checked = true; captchas = $.get('captchas', []); captchas.push({ challenge: qr.challenge, response: this.value, time: qr.captchaTime }); $.set('captchas', captchas); $('#captchas', qr.el).textContent = captchas.length + ' captchas'; Recaptcha.reload(); this.value = ''; if (!$('textarea', qr.el).value && !$('input[type=file]', qr.el).files.length) { return e.preventDefault(); } }, close: function() { $.rm(qr.el); return qr.el = null; }, dialog: function(link) { var THREAD_ID, c, html, m, submitDisabled, submitValue; submitValue = $('#com_submit').value; submitDisabled = $('#com_submit').disabled ? 'disabled' : ''; THREAD_ID = g.THREAD_ID || $.x('ancestor::div[@class="thread"]/div', link).id; qr.challenge = $('#recaptcha_challenge_field').value; html = " <a id=close title=close>X</a> <input type=checkbox id=autohide title=autohide> <div class=move> <input class=inputtext type=text name=name placeholder=Name form=qr_form> Quick Reply </div> <div class=autohide> <form name=post action=http://sys.4chan.org/" + g.BOARD + "/post method=POST enctype=multipart/form-data target=iframe id=qr_form> <input type=hidden name=resto value=" + THREAD_ID + "> <input type=hidden name=mode value=regist> <input type=hidden name=recaptcha_challenge_field id=recaptcha_challenge_field> <input type=hidden name=recaptcha_response_field id=recaptcha_response_field> <div><input class=inputtext type=text name=email placeholder=E-mail>" + qr.spoiler + "</div> <div><input class=inputtext type=text name=sub placeholder=Subject><input type=submit value=" + submitValue + " id=com_submit " + submitDisabled + "><label><input type=checkbox id=auto>auto</label></div> <div><textarea class=inputtext name=com placeholder=Comment></textarea></div> <div><img src=http://www.google.com/recaptcha/api/image?c=" + qr.challenge + "></div> <div><input class=inputtext type=text autocomplete=off placeholder=Verification id=dummy><span id=captchas>" + ($.get('captchas', []).length) + " captchas</span></div> <div><input type=file name=upfile accept='" + qr.acceptFiles + "'></div> </form> <div id=files></div> <div><input class=inputtext type=password name=pwd placeholder=Password form=qr_form maxlength=8><a id=attach>attach another file</a></div> </div> <a id=error class=error></a> "; qr.el = ui.dialog('qr', 'top: 0; right: 0;', html); c = d.cookie; $('input[name=name]', qr.el).value = (m = c.match(/4chan_name=([^;]+)/)) ? decodeURIComponent(m[1]) : ''; $('input[name=email]', qr.el).value = (m = c.match(/4chan_email=([^;]+)/)) ? decodeURIComponent(m[1]) : ''; $('input[name=pwd]', qr.el).value = (m = c.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : $('input[name=pwd]').value; $.on($('input[name=name]', qr.el), 'mousedown', function(e) { return e.stopPropagation(); }); $.on($('input[name=upfile]', qr.el), 'change', qr.validateFileSize); $.on($('#close', qr.el), 'click', qr.close); $.on($('form', qr.el), 'submit', qr.submit); $.on($('#attach', qr.el), 'click', qr.attach); $.on($('img', qr.el), 'click', Recaptcha.reload); $.on($('#dummy', qr.el), 'keydown', Recaptcha.listener); $.on($('#dummy', qr.el), 'keydown', qr.captchaKeydown); return $.add(d.body, qr.el); }, message: function(data) { var duration, fileCount, tc; $('iframe[name=iframe]').src = 'about:blank'; fileCount = $('#files', qr.el).childElementCount; tc = data.textContent; if (!/successful!|uploaded!$/.test(tc)) { if (tc === void 0) { data.textContent = "Connection error with sys.4chan.org."; } $.extend($('#error', qr.el), data); $('#recaptcha_response_field', qr.el).value = ''; $('#autohide', qr.el).checked = false; if (tc === 'You seem to have mistyped the verification.') { setTimeout(qr.autoPost, 1000); } else if (tc === 'Error: Duplicate file entry detected.' && fileCount) { $('textarea', qr.el).value += '\n' + tc + ' ' + data.href; qr.attachNext(); setTimeout(qr.autoPost, 1000); } return; } if (qr.el) { if (g.REPLY && (conf['Persistent QR'] || fileCount)) { qr.refresh(); if (fileCount) qr.attachNext(); } else { qr.close(); } } if (conf['Cooldown']) { duration = qr.sage ? 60 : 30; $.set(g.BOARD + '/cooldown', Date.now() + duration * 1000); return cooldown.start(); } }, node: function(root) { var quote; quote = $('a.quotejs:not(:first-child)', root); return $.on(quote, 'click', qr.quote); }, postInvalid: function() { var captcha, captchas, content, cutoff, dummy, response; content = $('textarea', qr.el).value || $('input[type=file]', qr.el).files.length; if (!content) return 'Error: No text entered.'; /* captchas expire after 30 minutes, see window.RecaptchaState.timeout. cutoff 5 minutes before then, b/c posting takes time. */ cutoff = Date.now() - 25 * MINUTE; captchas = $.get('captchas', []); while (captcha = captchas.shift()) { if (captcha.time > cutoff) break; } $.set('captchas', captchas); $('#captchas', qr.el).textContent = captchas.length + ' captchas'; if (!captcha) { dummy = $('#dummy', qr.el); if (!(response = dummy.value)) { return 'You forgot to type in the verification'; } captcha = { challenge: qr.challenge, response: response }; dummy.value = ''; Recaptcha.reload(); } $('#recaptcha_challenge_field', qr.el).value = captcha.challenge; $('#recaptcha_response_field', qr.el).value = captcha.response; return false; }, quote: function(e) { var caretPos, id, s, selection, selectionID, ta, text, _ref; if (e) e.preventDefault(); if (qr.el) { $('#autohide', qr.el).checked = false; } else { qr.dialog(this); } id = this.textContent; text = ">>" + id + "\n"; selection = window.getSelection(); if (s = selection.toString()) { selectionID = (_ref = $.x('ancestor-or-self::blockquote/preceding-sibling::input', selection.anchorNode)) != null ? _ref.name : void 0; if (selectionID === id) { s = s.replace(/\n/g, '\n>'); text += ">" + s + "\n"; } } ta = $('textarea', qr.el); caretPos = ta.selectionStart; ta.value = ta.value.slice(0, caretPos) + text + ta.value.slice(ta.selectionEnd, ta.value.length); ta.focus(); return ta.selectionEnd = ta.selectionStart = caretPos + text.length + 1 * (engine === 'presto'); }, refresh: function() { var m, newFile, oldFile, _ref; $('[name=sub]', qr.el).value = ''; $('[name=email]', qr.el).value = (m = d.cookie.match(/4chan_email=([^;]+)/)) ? decodeURIComponent(m[1]) : ''; $('[name=com]', qr.el).value = ''; $('[name=recaptcha_response_field]', qr.el).value = ''; if (!conf['Remember Spoiler']) { if ((_ref = $('[name=spoiler]', qr.el)) != null) _ref.checked = false; } oldFile = $('[type=file]', qr.el); newFile = $.el('input', { type: 'file', name: 'upfile', accept: qr.acceptFiles }); return $.replace(oldFile, newFile); }, submit: function(e) { var id, msg, op; if (msg = qr.postInvalid()) { if (typeof e.preventDefault === "function") e.preventDefault(); alert(msg); if (msg === 'You forgot to type in the verification.') { $('#dummy', qr.el).focus(); } return; } if (conf['Auto Watch Reply'] && conf['Thread Watcher']) { if (g.REPLY && $('img.favicon').src === Favicon.empty) { watcher.watch(null, g.THREAD_ID); } else { id = $('input[name=resto]', qr.el).value; op = $.id(id); if ($('img.favicon', op).src === Favicon.empty) watcher.watch(op, id); } } if (!e) this.submit(); $('#error', qr.el).textContent = ''; if (conf['Auto Hide QR']) $('#autohide', qr.el).checked = true; return qr.sage = /sage/i.test($('input[name=email]', this).value); }, sys: function() { var c, duration, id, noko, recaptcha, sage, search, thread, url, watch, _, _ref, _ref2; if (recaptcha = $('#recaptcha_response_field')) { $.on(recaptcha, 'keydown', Recaptcha.listener); return; } /* http://code.google.com/p/chromium/issues/detail?id=20773 Let content scripts see other frames (instead of them being undefined) To access the parent, we have to break out of the sandbox and evaluate in the global context. */ $.globalEval(function() { var data, node, _ref; data = {}; if (node = (_ref = document.querySelector('td b')) != null ? _ref.firstChild : void 0) { data.textContent = node.textContent; if (node.href) data.href = node.href; } return parent.postMessage(data, '*'); }); c = (_ref = $('b')) != null ? _ref.lastChild : void 0; if (!(c && c.nodeType === 8)) return; _ref2 = c.textContent.match(/thread:(\d+),no:(\d+)/), _ = _ref2[0], thread = _ref2[1], id = _ref2[2]; search = location.search; cooldown = /cooldown/.test(search); noko = /noko/.test(search); sage = /sage/.test(search); watch = /watch/.test(search); url = "http://boards.4chan.org/" + g.BOARD; if (watch && thread === '0') { url += "/res/" + id + "?watch"; } else if (noko) { url += '/res/'; url += thread === '0' ? id : thread; } if (cooldown) { duration = Date.now() + (sage ? 60 : 30) * 1000; url += '?cooldown=' + duration; } if (noko) url += '#' + id; return window.location = url; }, validateFileSize: function(e) { var file; if (!(this.files[0].size > $('input[name=MAX_FILE_SIZE]').value)) return; file = $.el('input', { type: 'file', name: 'upfile', accept: qr.acceptFiles }); $.on(file, 'change', qr.validateFileSize); $.replace(this, file); $('#error', qr.el).textContent = 'Error: File too large.'; return alert('Error: File too large.'); } }; Recaptcha = { init: function() { var el, _i, _len, _ref; _ref = $$('#recaptcha_table a'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { el = _ref[_i]; el.tabIndex = 1; } return $.on($('#recaptcha_response_field'), 'keydown', Recaptcha.listener); }, listener: function(e) { if (e.keyCode === 8 && this.value === '') return Recaptcha.reload(); }, reload: function() { return window.location = 'javascript:Recaptcha.reload()'; } }; threading = { init: function() { return threading.thread($('body > form').firstChild); }, op: function(node) { var op; op = $.el('div', { className: 'op' }); $.before(node, op); while (node.nodeName !== 'BLOCKQUOTE') { $.add(op, node); node = op.nextSibling; } $.add(op, node); op.id = $('input', op).name; return op; }, thread: function(node) { var div; node = threading.op(node); if (g.REPLY) return; div = $.el('div', { className: 'thread' }); $.before(node, div); while (node.nodeName !== 'HR') { $.add(div, node); node = div.nextSibling; } node = node.nextElementSibling; if (!(node.align || node.nodeName === 'CENTER')) { return threading.thread(node); } } }; threadHiding = { init: function() { var a, hiddenThreads, op, thread, _i, _len, _ref, _results; hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {}); _ref = $$('.thread'); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { thread = _ref[_i]; op = thread.firstChild; a = $.el('a', { textContent: '[ - ]', href: 'javascript:;' }); $.on(a, 'click', threadHiding.cb.hide); $.prepend(op, a); if (op.id in hiddenThreads) { _results.push(threadHiding.hideHide(thread)); } else { _results.push(void 0); } } return _results; }, cb: { hide: function() { var thread; thread = this.parentNode.parentNode; return threadHiding.hide(thread); }, show: function() { var thread; thread = this.parentNode.parentNode; return threadHiding.show(thread); } }, toggle: function(thread) { if (/\bstub\b/.test(thread.className) || thread.hidden) { return threadHiding.show(thread); } else { return threadHiding.hide(thread); } }, hide: function(thread) { var hiddenThreads, id; threadHiding.hideHide(thread); id = thread.firstChild.id; hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {}); hiddenThreads[id] = Date.now(); return $.set("hiddenThreads/" + g.BOARD + "/", hiddenThreads); }, hideHide: function(thread) { var a, div, name, num, span, text, trip, _ref; if (conf['Show Stubs']) { if (/stub/.test(thread.className)) return; 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 = $('.postername', thread).textContent; trip = ((_ref = $('.postername + .postertrip', thread)) != null ? _ref.textContent : void 0) || ''; a = $.el('a', { textContent: "[ + ] " + name + trip + " (" + text + ")", href: 'javascript:;' }); $.on(a, 'click', threadHiding.cb.show); div = $.el('div', { className: 'block' }); $.add(div, a); $.add(thread, div); return $.addClass(thread, 'stub'); } else { thread.hidden = true; return thread.nextSibling.hidden = true; } }, show: function(thread) { var hiddenThreads, id; $.rm($('div.block', thread)); $.removeClass(thread, 'stub'); thread.hidden = false; thread.nextSibling.hidden = false; id = thread.firstChild.id; hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {}); delete hiddenThreads[id]; return $.set("hiddenThreads/" + g.BOARD + "/", hiddenThreads); } }; updater = { init: function() { var checkbox, checked, dialog, html, input, name, title, _i, _len, _ref; if (!$('form[name=post]')) return; if (conf['Scrolling']) { if (conf['Scroll BG']) { updater.focus = true; } else { $.on(window, 'focus', (function() { return updater.focus = true; })); $.on(window, 'blur', (function() { return updater.focus = false; })); } } html = "<div class=move><span id=count></span> <span id=timer>-" + conf['Interval'] + "</span></div>"; checkbox = config.updater.checkbox; for (name in checkbox) { title = checkbox[name][1]; checked = conf[name] ? 'checked' : ''; html += "<div><label title='" + title + "'>" + name + "<input name='" + name + "' type=checkbox " + checked + "></label></div>"; } checked = conf['Auto Update'] ? 'checked' : ''; html += " <div><label title='Controls whether *this* thread automatically updates or not'>Auto Update This<input name='Auto Update This' type=checkbox " + checked + "></label></div> <div><label>Interval (s)<input name=Interval value=" + conf['Interval'] + " type=text></label></div> <div><input value='Update Now' type=button></div>"; dialog = ui.dialog('updater', 'bottom: 0; right: 0;', html); updater.count = $('#count', dialog); updater.timer = $('#timer', dialog); updater.br = $('br[clear]'); _ref = $$('input', dialog); for (_i = 0, _len = _ref.length; _i < _len; _i++) { input = _ref[_i]; if (input.type === 'checkbox') { $.on(input, 'click', $.cb.checked); $.on(input, 'click', function() { return conf[this.name] = this.checked; }); if (input.name === 'Verbose') { $.on(input, 'click', updater.cb.verbose); updater.cb.verbose.call(input); } else if (input.name === 'Auto Update This') { $.on(input, 'click', updater.cb.autoUpdate); updater.cb.autoUpdate.call(input); } } else if (input.name === 'Interval') { $.on(input, 'change', function() { return conf['Interval'] = this.value = parseInt(this.value, 10) || conf['Interval']; }); $.on(input, 'change', $.cb.value); } else if (input.type === 'button') { $.on(input, 'click', updater.update); } } $.add(d.body, dialog); updater.retryCoef = 10; return updater.lastModified = 0; }, cb: { verbose: function() { if (conf['Verbose']) { updater.count.textContent = '+0'; return updater.timer.hidden = false; } else { $.extend(updater.count, { className: '', textContent: 'Thread Updater' }); return updater.timer.hidden = true; } }, autoUpdate: function() { if (this.checked) { return updater.timeoutID = setTimeout(updater.timeout, 1000); } else { return clearTimeout(updater.timeoutID); } }, update: function() { var body, frag, id, input, newPosts, reply, scroll, _i, _j, _len, _len2, _ref, _ref2, _ref3; if (this.status === 404) { updater.timer.textContent = ''; updater.count.textContent = 404; updater.count.className = 'error'; clearTimeout(updater.timeoutID); _ref = $$('#com_submit'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { input = _ref[_i]; input.disabled = true; input.value = 404; } d.title = d.title.match(/^.+-/)[0] + ' 404'; g.dead = true; Favicon.update(); return; } updater.retryCoef = 10; updater.timer.textContent = '-' + conf['Interval']; /* Status Code 304: Not modified By sending the `If-Modified-Since` header we get a proper status code, and no response. This saves bandwidth for both the user and the servers, avoid unnecessary computation, and won't load images and scripts when parsing the response. */ if (this.status === 304) { if (conf['Verbose']) { updater.count.textContent = '+0'; updater.count.className = null; } return; } updater.lastModified = this.getResponseHeader('Last-Modified'); body = $.el('body', { innerHTML: this.responseText }); if ($('title', body).textContent === '4chan - Banned') { updater.count.textContent = 'Banned'; updater.count.className = 'error'; return; } id = ((_ref2 = $('td[id]', updater.br.previousElementSibling)) != null ? _ref2.id : void 0) || 0; frag = d.createDocumentFragment(); _ref3 = $$('.reply', body).reverse(); for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) { reply = _ref3[_j]; if (reply.id <= id) break; $.prepend(frag, reply.parentNode.parentNode.parentNode); } newPosts = frag.childNodes.length; scroll = conf['Scrolling'] && updater.focus && newPosts && (d.body.scrollHeight - d.body.clientHeight - window.scrollY < 20); if (conf['Verbose']) { updater.count.textContent = '+' + newPosts; if (newPosts === 0) { updater.count.className = null; } else { updater.count.className = 'new'; } } $.before(updater.br, frag); if (scroll) return scrollTo(0, d.body.scrollHeight); } }, timeout: function() { var n; updater.timeoutID = setTimeout(updater.timeout, 1000); n = 1 + Number(updater.timer.textContent); if (n === 0) { return updater.update(); } else if (n === updater.retryCoef) { updater.retryCoef += 10 * (updater.retryCoef < 120); return updater.retry(); } else { return updater.timer.textContent = n; } }, retry: function() { updater.count.textContent = 'Retry'; updater.count.className = ''; return updater.update(); }, update: function() { var url, _ref; updater.timer.textContent = 0; if ((_ref = updater.request) != null) _ref.abort(); url = location.pathname + '?' + Date.now(); return updater.request = $.ajax(url, updater.cb.update, { headers: { 'If-Modified-Since': updater.lastModified } }); } }; watcher = { init: function() { var favicon, html, input, inputs, _i, _len; html = '<div class=move>Thread Watcher</div>'; watcher.dialog = ui.dialog('watcher', 'top: 50px; left: 0px;', html); $.add(d.body, watcher.dialog); inputs = $$('.op input'); for (_i = 0, _len = inputs.length; _i < _len; _i++) { input = inputs[_i]; favicon = $.el('img', { className: 'favicon' }); $.on(favicon, 'click', watcher.cb.toggle); $.before(input, favicon); } watcher.refresh(); if (conf['Auto Watch']) { if (!g.REPLY) { $('.postarea form').action += '?watch'; } else if (/watch/.test(location.search) && $('img.favicon').src === Favicon.empty) { watcher.watch(null, g.THREAD_ID); } } return $.on(window, 'storage', function(e) { if (e.key === ("" + NAMESPACE + "watched")) return watcher.refresh(); }); }, refresh: function() { var board, div, favicon, frag, id, link, props, watched, watchedBoard, x, _i, _j, _len, _len2, _ref, _ref2, _ref3, _results; watched = $.get('watched', {}); frag = d.createDocumentFragment(); for (board in watched) { _ref = watched[board]; for (id in _ref) { props = _ref[id]; x = $.el('a', { textContent: 'X', href: 'javascript:;' }); $.on(x, 'click', watcher.cb.x); link = $.el('a', props); link.title = link.textContent; div = $.el('div'); $.add(div, x, $.tn(' '), link); $.add(frag, div); } } _ref2 = $$('div:not(.move)', watcher.dialog); for (_i = 0, _len = _ref2.length; _i < _len; _i++) { div = _ref2[_i]; $.rm(div); } $.add(watcher.dialog, frag); watchedBoard = watched[g.BOARD] || {}; _ref3 = $$('img.favicon'); _results = []; for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) { favicon = _ref3[_j]; id = favicon.nextSibling.name; if (id in watchedBoard) { _results.push(favicon.src = Favicon["default"]); } else { _results.push(favicon.src = Favicon.empty); } } return _results; }, cb: { toggle: function() { return watcher.toggle(this.parentNode); }, x: function() { var board, id, _, _ref; _ref = this.nextElementSibling.getAttribute('href').slice(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, id); } else { return watcher.unwatch(g.BOARD, id); } }, unwatch: function(board, id) { var watched; watched = $.get('watched', {}); delete watched[board][id]; $.set('watched', watched); return watcher.refresh(); }, watch: function(thread, id) { var props, text, watched, _name; text = getTitle(thread); props = { href: "/" + g.BOARD + "/res/" + id, textContent: text }; watched = $.get('watched', {}); watched[_name = g.BOARD] || (watched[_name] = {}); watched[g.BOARD][id] = props; $.set('watched', watched); return watcher.refresh(); } }; anonymize = { init: function() { return g.callbacks.push(function(root) { var name, trip; name = $('.commentpostername, .postername', root); name.textContent = 'Anonymous'; if (trip = $('.postertrip', root)) { if (trip.parentNode.nodeName === 'A') { return $.rm(trip.parentNode); } else { return $.rm(trip); } } }); } }; sauce = { init: function() { if (!(sauce.prefixes = conf['flavors'].match(/^[^#].+$/gm))) return; sauce.names = sauce.prefixes.map(function(prefix) { return prefix.match(/(\w+)\./)[1]; }); return g.callbacks.push(function(root) { var i, link, prefix, span, suffix, _len, _ref, _results; if (root.className === 'inline' || !(span = $('.filesize', root))) return; suffix = $('a', span).href; _ref = sauce.prefixes; _results = []; for (i = 0, _len = _ref.length; i < _len; i++) { prefix = _ref[i]; link = $.el('a', { textContent: sauce.names[i], href: prefix + suffix, target: '_blank' }); _results.push($.add(span, $.tn(' '), link)); } return _results; }); } }; revealSpoilers = { init: function() { return g.callbacks.push(function(root) { var board, img, imgID, _, _ref; if (!(img = $('img[alt^=Spoiler]', root)) || root.className === 'inline') { return; } img.removeAttribute('height'); img.removeAttribute('width'); _ref = img.parentNode.href.match(/(\w+)\/src\/(\d+)/), _ = _ref[0], board = _ref[1], imgID = _ref[2]; return img.src = "http://0.thumbs.4chan.org/" + board + "/thumb/" + imgID + "s.jpg"; }); } }; Time = { init: function() { var chanOffset; Time.foo(); chanOffset = 5 - new Date().getTimezoneOffset() / 60; if ($.isDST()) chanOffset--; this.parse = Date.parse('10/11/11(Tue)18:53' === 1318351980000) ? function(node) { return new Date(Date.parse(node.textContent) + chanOffset * HOUR); } : function(node) { var day, hour, min, month, year, _, _ref; _ref = node.textContent.match(/(\d+)\/(\d+)\/(\d+)\(\w+\)(\d+):(\d+)/), _ = _ref[0], month = _ref[1], day = _ref[2], year = _ref[3], hour = _ref[4], min = _ref[5]; year = "20" + year; month -= 1; hour = chanOffset + Number(hour); return new Date(year, month, day, hour, min); }; return g.callbacks.push(Time.node); }, node: function(root) { var node, posttime, time; if (root.className === 'inline') return; node = (posttime = $('.posttime', root)) ? posttime : $('span[id]', root).previousSibling; Time.date = Time.parse(node); time = $.el('time', { textContent: ' ' + Time.funk(Time) + ' ' }); return $.replace(node, time); }, foo: function() { var code; code = conf['time'].replace(/%([A-Za-z])/g, function(s, c) { if (c in Time.formatters) { return "' + Time.formatters." + c + "() + '"; } else { return s; } }); return Time.funk = Function('Time', "return '" + code + "'"); }, day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], zeroPad: function(n) { if (n < 10) { return '0' + n; } else { return n; } }, formatters: { a: function() { return Time.day[Time.date.getDay()].slice(0, 3); }, A: function() { return Time.day[Time.date.getDay()]; }, b: function() { return Time.month[Time.date.getMonth()].slice(0, 3); }, B: function() { return Time.month[Time.date.getMonth()]; }, d: function() { return Time.zeroPad(Time.date.getDate()); }, e: function() { return Time.date.getDate(); }, H: function() { return Time.zeroPad(Time.date.getHours()); }, I: function() { return Time.zeroPad(Time.date.getHours() % 12 || 12); }, k: function() { return Time.date.getHours(); }, l: function() { return Time.date.getHours() % 12 || 12; }, m: function() { return Time.zeroPad(Time.date.getMonth() + 1); }, M: function() { return Time.zeroPad(Time.date.getMinutes()); }, p: function() { if (Time.date.getHours() < 12) { return 'AM'; } else { return 'PM'; } }, P: function() { if (Time.date.getHours() < 12) { return 'am'; } else { return 'pm'; } }, y: function() { return Time.date.getFullYear() - 2000; } } }; getTitle = function(thread) { var el, span; el = $('.filetitle', thread); if (!el.textContent) { el = $('blockquote', thread); if (!el.textContent) el = $('.postername', thread); } span = $.el('span', { innerHTML: el.innerHTML.replace(/<br>/g, ' ') }); return "/" + g.BOARD + "/ - " + span.textContent; }; titlePost = { init: function() { return d.title = getTitle(); } }; quoteBacklink = { init: function() { var format; format = conf['backlink'].replace(/%id/, "' + id + '"); quoteBacklink.funk = Function('id', "return'" + format + "'"); return g.callbacks.push(function(root) { var a, container, el, id, link, qid, quote, quotes, _i, _len, _ref, _results; if (/\binline\b/.test(root.className)) return; quotes = {}; _ref = $$('.quotelink', root); for (_i = 0, _len = _ref.length; _i < _len; _i++) { quote = _ref[_i]; if (qid = quote.hash.slice(1)) quotes[qid] = quote; } id = $('input', root).name; a = $.el('a', { href: "#" + id, className: root.hidden ? 'filtered backlink' : 'backlink', textContent: quoteBacklink.funk(id) }); _results = []; for (qid in quotes) { if (!(el = $.id(qid))) continue; if (el.className === 'op' && !conf['OP Backlinks']) continue; link = a.cloneNode(true); if (conf['Quote Preview']) { $.on(link, 'mouseover', quotePreview.mouseover); $.on(link, 'mousemove', ui.hover); $.on(link, 'mouseout', quotePreview.mouseout); } if (conf['Quote Inline']) $.on(link, 'click', quoteInline.toggle); if (!((container = $('.container', el)) && container.parentNode === el)) { container = $.el('span', { className: 'container' }); root = $('.reportbutton', el) || $('span[id]', el); $.after(root, container); } _results.push($.add(container, $.tn(' '), link)); } return _results; }); } }; quoteInline = { init: function() { return g.callbacks.push(function(root) { var quote, _i, _len, _ref, _results; _ref = $$('.quotelink, .backlink', root); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { quote = _ref[_i]; if (!quote.hash) continue; quote.removeAttribute('onclick'); _results.push($.on(quote, 'click', quoteInline.toggle)); } return _results; }); }, toggle: function(e) { var id; if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { return; } e.preventDefault(); id = this.hash.slice(1); if (/\binlined\b/.test(this.className)) { quoteInline.rm(this, id); } else { if ($.x("ancestor::*[@id='" + id + "']", this)) return; quoteInline.add(this, id); } return this.classList.toggle('inlined'); }, add: function(q, id) { var el, i, inline, pathname, root, threadID; root = q.parentNode.nodeName === 'FONT' ? q.parentNode : q.nextSibling ? q.nextSibling : q; if (el = $.id(id)) { inline = quoteInline.table(id, el.innerHTML); if (g.REPLY && conf['Unread Count'] && (i = unread.replies.indexOf(el.parentNode.parentNode.parentNode)) !== -1) { unread.replies.splice(i, 1); unread.updateTitle(); Favicon.update(); } if (/\bbacklink\b/.test(q.className)) { $.after(q.parentNode, inline); if (conf['Forward Hiding']) { $.addClass($.x('ancestor::table', el), 'forwarded'); } return; } return $.after(root, inline); } else { inline = $.el('td', { className: 'reply inline', id: "i" + id, innerHTML: "Loading " + id + "..." }); $.after(root, inline); pathname = q.pathname; threadID = pathname.split('/').pop(); return $.cache(pathname, (function() { return quoteInline.parse(this, pathname, id, threadID, inline); })); } }, rm: function(q, id) { var inlined, table, _i, _len, _ref; table = $.x("following::*[@id='i" + id + "']", q); $.rm(table); if (!conf['Forward Hiding']) return; _ref = $$('.backlink.inlined', table); for (_i = 0, _len = _ref.length; _i < _len; _i++) { inlined = _ref[_i]; $.removeClass($.x('ancestor::table', $.id(inlined.hash.slice(1))), 'forwarded'); } if (/\bbacklink\b/.test(q.className)) { return $.removeClass($.x('ancestor::table', $.id(id)), 'forwarded'); } }, parse: function(req, pathname, id, threadID, inline) { var body, href, html, link, newInline, op, quote, reply, _i, _j, _len, _len2, _ref, _ref2; if (!inline.parentNode) return; if (req.status !== 200) { inline.innerHTML = "" + req.status + " " + req.statusText; return; } body = $.el('body', { innerHTML: req.responseText }); if (id === threadID) { op = threading.op($('body > form', body).firstChild); html = op.innerHTML; } else { _ref = $$('td.reply', body); for (_i = 0, _len = _ref.length; _i < _len; _i++) { reply = _ref[_i]; if (reply.id === id) { html = reply.innerHTML; break; } } } newInline = quoteInline.table(id, html); _ref2 = $$('.quotelink', newInline); for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { quote = _ref2[_j]; if ((href = quote.getAttribute('href')) === quote.hash) { quote.pathname = pathname; } else if (!g.REPLY && href !== quote.href) { quote.href = "res/" + href; } } link = $('.quotejs', newInline); link.href = "" + pathname + "#" + id; link.nextSibling.href = "" + pathname + "#q" + id; $.addClass(newInline, 'crossquote'); return $.replace(inline, newInline); }, table: function(id, html) { return $.el('table', { className: 'inline', id: "i" + id, innerHTML: "<tbody><tr><td class=reply>" + html + "</td></tr></tbody>" }); } }; quotePreview = { init: function() { return g.callbacks.push(function(root) { var quote, _i, _len, _ref, _results; _ref = $$('.quotelink, .backlink', root); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { quote = _ref[_i]; if (!quote.hash) continue; $.on(quote, 'mouseover', quotePreview.mouseover); $.on(quote, 'mousemove', ui.hover); _results.push($.on(quote, 'mouseout', quotePreview.mouseout)); } return _results; }); }, mouseover: function(e) { var el, id, qp, quote, replyID, threadID, _i, _len, _ref, _results; qp = ui.el = $.el('div', { id: 'qp', className: 'reply' }); $.add(d.body, qp); id = this.hash.slice(1); if (el = $.id(id)) { qp.innerHTML = el.innerHTML; if (conf['Quote Highlighting']) $.addClass(el, 'qphl'); if (/\bbacklink\b/.test(this.className)) { replyID = $.x('preceding-sibling::input', this.parentNode).name; _ref = $$('.quotelink', qp); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { quote = _ref[_i]; if (quote.hash.slice(1) === replyID) { _results.push(quote.className = 'forwardlink'); } else { _results.push(void 0); } } return _results; } } else { qp.innerHTML = "Loading " + id + "..."; threadID = this.pathname.split('/').pop() || $.x('ancestor::div[@class="thread"]/div', this).id; $.cache(this.pathname, (function() { return quotePreview.parse(this, id, threadID); })); return ui.hover(e); } }, mouseout: function() { var el; if (el = $.id(this.hash.slice(1))) $.removeClass(el, 'qphl'); return ui.hoverend(); }, parse: function(req, id, threadID) { var body, html, op, qp, reply, _i, _len, _ref; if (!((qp = ui.el) && (qp.innerHTML === ("Loading " + id + "...")))) return; if (req.status !== 200) { qp.innerHTML = "" + req.status + " " + req.statusText; return; } body = $.el('body', { innerHTML: req.responseText }); if (id === threadID) { op = threading.op($('body > form', body).firstChild); html = op.innerHTML; } else { _ref = $$('td.reply', body); for (_i = 0, _len = _ref.length; _i < _len; _i++) { reply = _ref[_i]; if (reply.id === id) { html = reply.innerHTML; break; } } } qp.innerHTML = html; return Time.node(qp); } }; quoteOP = { init: function() { return g.callbacks.push(function(root) { var quote, tid, _i, _len, _ref, _results; if (root.className === 'inline') return; tid = g.THREAD_ID || $.x('ancestor::div[contains(@class,"thread")]/div', root).id; _ref = $$('.quotelink', root); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { quote = _ref[_i]; if (quote.hash.slice(1) === tid) { _results.push(quote.innerHTML += ' (OP)'); } else { _results.push(void 0); } } return _results; }); } }; quoteDR = { init: function() { return g.callbacks.push(function(root) { var quote, tid, _i, _len, _ref, _results; if (root.className === 'inline') return; tid = g.THREAD_ID || $.x('ancestor::div[contains(@class,"thread")]/div', root).id; _ref = $$('.quotelink', root); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { quote = _ref[_i]; if (quote.pathname.indexOf("res/" + tid) === -1 && !quote.pathname.indexOf("/" + g.BOARD + "/res")) { _results.push(quote.innerHTML += ' (Cross-thread)'); } else { _results.push(void 0); } } return _results; }); } }; reportButton = { init: function() { return g.callbacks.push(function(root) { var a, span; if (!(a = $('.reportbutton', root))) { span = $('span[id]', root); a = $.el('a', { className: 'reportbutton', innerHTML: '[ ! ]', href: 'javascript:;' }); $.after(span, a); $.after(span, $.tn(' ')); } return $.on(a, 'click', reportButton.report); }); }, report: function() { var id, set, url; url = "http://sys.4chan.org/" + g.BOARD + "/imgboard.php?mode=report&no=" + ($.x('preceding-sibling::input', this).name); id = Date.now(); set = "toolbar=0,scrollbars=0,location=0,status=1,menubar=0,resizable=1,width=685,height=200"; return window.open(url, id, set); } }; threadStats = { init: function() { var dialog, html; threadStats.posts = 1; threadStats.images = $('.op img[md5]') ? 1 : 0; html = "<div class=move><span id=postcount>" + threadStats.posts + "</span> / <span id=imagecount>" + threadStats.images + "</span></div>"; dialog = ui.dialog('stats', 'bottom: 0; left: 0;', html); dialog.className = 'dialog'; threadStats.postcountEl = $('#postcount', dialog); threadStats.imagecountEl = $('#imagecount', dialog); $.add(d.body, dialog); return g.callbacks.push(threadStats.node); }, node: function(root) { if (root.className) return; threadStats.postcountEl.textContent = ++threadStats.posts; if ($('img[md5]', root)) { threadStats.imagecountEl.textContent = ++threadStats.images; if (threadStats.images > 151) { return threadStats.imagecountEl.className = 'error'; } } } }; unread = { init: function() { d.title = '(0) ' + d.title; $.on(window, 'scroll', unread.scroll); return g.callbacks.push(unread.node); }, replies: [], node: function(root) { if (root.hidden || root.className) return; unread.replies.push(root); unread.updateTitle(); if (unread.replies.length === 1) return Favicon.update(); }, scroll: function() { var bottom, height, i, reply, _len, _ref; updater.focus = true; 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 = { init: function() { var favicon, href; favicon = $('link[rel="shortcut icon"]', d.head); favicon.type = 'image/x-icon'; href = favicon.href; this.SFW = /ws.ico$/.test(href); this["default"] = href; return this["switch"](); }, "switch": function() { switch (conf['favicon']) { case 'ferongr': this.unreadDead = 'data:unreadDead;base64,R0lGODlhEAAQAOMHAOgLAnMFAL8AAOgLAukMA/+AgP+rq////////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw=='; this.unreadSFW = 'data:unreadSFW;base64,R0lGODlhEAAQAOMHAADX8QBwfgC2zADX8QDY8nnl8qLp8v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw=='; this.unreadNSFW = 'data:unreadNSFW;base64,R0lGODlhEAAQAOMHAFT+ACh5AEncAFT+AFX/Acz/su7/5v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw=='; break; case 'xat-': this.unreadDead = 'data:unreadDead;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA2ElEQVQ4y61TQQrCMBDMQ8WDIEV6LbT2A4og2Hq0veo7fIAH04dY9N4xmyYlpGmI2MCQTWYy3Wy2DAD7B2wWAzWgcTgVeZKlZRxHNYFi2jM18oBh0IcKtC6ixf22WT4IFLs0owxswXu9egm0Ls6bwfCFfNsJYJKfqoEkd3vgUgFVLWObtzNgVKyruC+ljSzr5OEnBzjvjcQecaQhbZgBb4CmGQw+PoMkTUtdbd8VSEPakcGxPOcsoIgUKy0LecY29BmdBrqRfjIwZ93KLs5loHvBnL3cLH/jF+C/+z5dgUysAAAAAElFTkSuQmCC'; this.unreadSFW = 'data:unreadSFW;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA30lEQVQ4y2P4//8/AyWYgSoGQMF/GJ7Y11VVUVoyKTM9ey4Ig9ggMWQ1YA1IBvzXm34YjkH8mPyJB+Nqlp8FYRAbmxoMF6ArSNrw6T0Qf8Amh9cFMEWVR/7/A+L/uORxhgEIt5/+/3/2lf//5wAxiI0uj+4CBlBgxVUvOwtydgXQZpDmi2/+/7/0GmIQSAwkB1IDUkuUAZeABlx+g2zAZ9wGlAOjChba+LwAUgNSi2HA5Am9VciBhSsQQWyoWgZiovEDsdGI1QBYQiLJAGQalpSxyWEzAJYWkGm8clTJjQCZ1hkoVG0CygAAAABJRU5ErkJggg=='; this.unreadNSFW = 'data:unreadNSFW;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA4ElEQVQ4y2P4//8/AyWYgSoGQMF/GJ7YNbGqrKRiUnp21lwQBrFBYshqwBqQDPifdsYYjkH8mInxB+OWx58FYRAbmxoMF6ArKPmU9B6IP2CTw+sCmKKe/5X/gPg/LnmcYQDCs/63/1/9fzYQzwGz0eXRXcAACqy4ZfFnQc7u+V/xD6T55v+LQHwJbBBIDCQHUgNSS5QBt4Cab/2/jDDgMx4DykrKJ8FCG58XQGpAajEMmNw7uQo5sHAFIogNVctATDR+IDYasRoAS0gkGYBMw5IyNjlsBsDSAjKNV44quREAx58Mr9vt5wQAAAAASUVORK5CYII='; break; case 'Mayhem': this.unreadDead = 'data:unreadDead;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4jZ2ScWuDMBDFgw4pIkU0WsoQkWAYIkXZH4N9/+/V3dmfXSrKYIFHwt17j8vdGWNMIkgFuaDgzgQnwRs4EQs5KdolUQtagRN0givEDBTEOjgtGs0Zq8F7cKqqusVxrMQLaDUWcjBSrXkn8gs51tpJSWLk9b3HUa0aNIL5gPBR1/V4kJvR7lTwl8GmAm1Gf9+c3S+89qBHa8502AsmSrtBaEBPbIbj0ah2madlNAPEccdgJDfAtWifBjqWKShRBT6KoiH8QlEUn/qt0CCjnNdmPUwmFWzj9Oe6LpKuZXcwqq88z78Pch3aZU3dPwwc2sWlfZKCW5tWluV8kGvXClLm6dYN4/aUqfCbnEOzNDGhGZbNargvxCzvMGfRJD8UaDVvgkzo6QAAAABJRU5ErkJggg=='; this.unreadSFW = 'data:unreadSFW;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCElEQVQ4jZ2S4crCMAxF+0OGDJEPKYrIGKOsiJSx/fJRfSAfTJNyKqXfiuDg0C25N2RJjTGmEVrhTzhw7oStsIEtsVzT4o2Jo9ALThiEM8IdHIgNaHo8mjNWg6/ske8bohPo+63QOLzmooHp8fyAICBSQkVz0QKdsFQEV6WSW/D+7+BbgbIDHcb4Kp61XyjyI16zZ8JemGltQtDBSGxB4/GoN+7TpkkjDCsFArm0IYv3U0BbnYtf8BCy+JytsE0X6VyuKhPPK/GAJ14kvZZDZVV3pZIb8MZr6n4o4PDGKn0S5SdDmyq5PnXQsk+Xbhinp03FFzmHJw6xYRiWm9VxnohZ3vOcxdO8ARmXRvbWdtzQAAAAAElFTkSuQmCC'; this.unreadNSFW = 'data:unreadNSFW;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCklEQVQ4jZ2S0WrDMAxF/TBCCKWMYhZKCSGYmFJMSNjD/mhf239qJXNcjBdTWODgRLpXKJKNMaYROuFTOHEehFb4gJZYrunwxsSXMApOmIQzwgOciE1oRjyaM1aDj+yR7xuiHvT9VmgcXnPRwO/9+wWCgEgJFc1FCwzCVhFclUpuw/u3g3cFyg50GPOjePZ+ocjPeM2RCXthpbUFwQAzsQ2Nx6PeuE+bJo0w7BQI5NKGLN5XAW11LX7BQ8jia7bCLl2kc7mqTLzuxAOeeJH0Wk6VVf0oldyEN15T948CDm+sMiZRfjK0pZIbUwcd+3TphnF62lR8kXN44hAbhmG5WQNnT8zynucsnuYJhFpBfkMzqD4AAAAASUVORK5CYII='; break; case 'Original': this.unreadDead = 'data:unreadDead;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; this.unreadSFW = 'data:unreadSFW;base64,R0lGODlhEAAQAKECAAAAAC6Xw////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; this.unreadNSFW = 'data:unreadNSFW;base64,R0lGODlhEAAQAKECAAAAAGbMM////////yH5BAEKAAMALAAAAAAQABAAAAI/nI95wsqygIRxDgGCBhTrwF3Zxowg5H1cSopS6FrGQ82PU1951ckRmYKJVCXizLRC9kAnT0aIiR6lCFT1cigAADs='; break; case 'None': this.unreadDead = this.dead; this.unreadSFW = 'http://static.4chan.org/image/favicon-ws.ico'; this.unreadNSFW = 'http://static.4chan.org/image/favicon.ico'; } return this.unread = this.SFW ? this.unreadSFW : this.unreadNSFW; }, empty: 'data:image/gif;base64,R0lGODlhEAAQAJEAAAAAAP///9vb2////yH5BAEAAAMALAAAAAAQABAAAAIvnI+pq+D9DBAUoFkPFnbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==', dead: 'data:image/gif;base64,R0lGODlhEAAQAKECAAAAAP8AAP///////yH5BAEKAAIALAAAAAAQABAAAAIvlI+pq+D9DAgUoFkPDlbs7lFZKIJOJJ3MyraoB14jFpOcVMpzrnF3OKlZYsMWowAAOw==', update: function() { var clone, favicon, l; l = unread.replies.length; favicon = $('link[rel="shortcut icon"]', d.head); favicon.href = g.dead ? l ? this.unreadDead : this.dead : l ? this.unread : this["default"]; if (engine !== 'webkit') { clone = favicon.cloneNode(true); favicon.href = null; return $.replace(favicon, clone); } } }; redirect = { init: function() { var url; url = location.hostname === 'images.4chan.org' ? redirect.image(g.BOARD, location.pathname.split('/')[3]) : /^\d+$/.test(g.THREAD_ID) ? redirect.thread() : void 0; if (url) return location.href = url; }, image: function(board, filename) { switch (board) { case 'a': case 'jp': case 'm': case 'tg': case 'tv': case 'u': return "http://archive.foolz.us/" + board + "/full_image/" + filename; } }, thread: function() { switch (g.BOARD) { case 'a': case 'jp': case 'm': case 'tg': case 'tv': case 'u': return "http://archive.foolz.us/" + g.BOARD + "/thread/" + g.THREAD_ID + "/"; case 'lit': return "http://fuuka.warosu.org/" + g.BOARD + "/thread/" + g.THREAD_ID; case 'diy': case 'g': case 'sci': return "http://archive.installgentoo.net/" + g.BOARD + "/thread/" + g.THREAD_ID; case '3': case 'adv': case 'an': case 'ck': case 'co': case 'fa': case 'fit': case 'int': case 'k': case 'mu': case 'n': case 'o': case 'p': case 'po': case 'pol': case 'r9k': case 'soc': case 'sp': case 'toy': case 'trv': case 'v': case 'vp': case 'x': return "http://archive.no-ip.org/" + g.BOARD + "/thread/" + g.THREAD_ID; default: return "http://boards.4chan.org/" + g.BOARD; } } }; imgHover = { init: function() { return g.callbacks.push(function(root) { var thumb; if (!(thumb = $('img[md5]', root))) return; $.on(thumb, 'mouseover', imgHover.mouseover); $.on(thumb, 'mousemove', ui.hover); return $.on(thumb, 'mouseout', ui.hoverend); }); }, mouseover: function() { ui.el = $.el('img', { id: 'ihover', src: this.parentNode.href }); return $.add(d.body, ui.el); } }; imgPreloading = { init: function() { return g.callbacks.push(function(root) { var el, src, thumb; if (!(thumb = $('img[md5]', root))) { return; } src = thumb.parentNode.href; return el = $.el('img', { src: src }); }); } }; imgGif = { init: function() { return g.callbacks.push(function(root) { var src, thumb; if (root.hidden || !(thumb = $('img[md5]', root))) return; src = thumb.parentNode.href; if (/gif$/.test(src)) return thumb.src = src; }); } }; imgExpand = { init: function() { g.callbacks.push(imgExpand.node); return imgExpand.dialog(); }, node: function(root) { var a, thumb; if (!(thumb = $('img[md5]', root))) return; a = thumb.parentNode; $.on(a, 'click', imgExpand.cb.toggle); if (imgExpand.on && !root.hidden && root.className !== 'inline') { return imgExpand.expand(a.firstChild); } }, cb: { toggle: function(e) { if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { return; } e.preventDefault(); return imgExpand.toggle(this); }, all: function() { var thumb, _i, _j, _len, _len2, _ref, _ref2, _results, _results2; imgExpand.on = this.checked; if (imgExpand.on) { _ref = $$('.op > a > img[md5]:last-child, table:not([hidden]) img[md5]:last-child'); _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { thumb = _ref[_i]; _results.push(imgExpand.expand(thumb)); } return _results; } else { _ref2 = $$('img[md5][hidden]'); _results2 = []; for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { thumb = _ref2[_j]; _results2.push(imgExpand.contract(thumb)); } return _results2; } }, typeChange: function() { var klass; switch (this.value) { case 'full': klass = ''; break; case 'fit width': klass = 'fitwidth'; break; case 'fit height': klass = 'fitheight'; break; case 'fit screen': klass = 'fitwidth fitheight'; } $('body > form').className = klass; if (/\bfitheight\b/.test(klass)) { $.on(window, 'resize', imgExpand.resize); if (!imgExpand.style) imgExpand.style = $.addStyle(''); return imgExpand.resize(); } else if (imgExpand.style) { return $.off(window, 'resize', imgExpand.resize); } } }, toggle: function(a) { var thumb; thumb = a.firstChild; if (thumb.hidden) { return imgExpand.contract(thumb); } else { return imgExpand.expand(thumb); } }, contract: function(thumb) { thumb.hidden = false; return $.rm(thumb.nextSibling); }, expand: function(thumb, url) { var a, filesize, img, max; a = thumb.parentNode; img = $.el('img', { src: url ? url : a.href }); if (engine === 'gecko' && a.parentNode.className !== 'op') { filesize = $.x('preceding-sibling::span[@class="filesize"]', a).textContent; max = filesize.match(/(\d+)x/); img.style.maxWidth = "" + max[1] + "px"; } if (conf['404 Redirect']) $.on(img, 'error', imgExpand.error); thumb.hidden = true; return $.add(a, img); }, error: function() { var req, src, thumb, url; thumb = this.previousSibling; imgExpand.contract(thumb); src = this.src.split('/'); if (url = redirect.image(src[3], src[5])) { return imgExpand.expand(thumb, url); } else if (engine === 'webkit') { return req = $.ajax(this.src, (function() { if (this.status !== 404) { return setTimeout(imgExpand.retry, 10000, thumb); } }), { type: 'head', event: 'onreadystatechange' }); } else if (!g.dead) { return setTimeout(imgExpand.retry, 10000, thumb); } }, retry: function(thumb) { if (!thumb.hidden) return imgExpand.expand(thumb); }, dialog: function() { var controls, form, imageType, select; controls = $.el('div', { id: 'imgControls', innerHTML: "<select id=imageType name=imageType><option value=full>Full</option><option value='fit width'>Fit Width</option><option value='fit height'>Fit Height</option value='fit screen'><option value='fit screen'>Fit Screen</option></select><label>Expand Images<input type=checkbox id=imageExpand></label>" }); imageType = $.get('imageType', 'full'); select = $('select', controls); select.value = imageType; imgExpand.cb.typeChange.call(select); $.on(select, 'change', $.cb.value); $.on(select, 'change', imgExpand.cb.typeChange); $.on($('input', controls), 'click', imgExpand.cb.all); form = $('body > form'); return $.prepend(form, controls); }, resize: function() { return imgExpand.style.innerHTML = ".fitheight img[md5] + img {max-height:" + d.body.clientHeight + "px;}"; } }; Main = { init: function() { var cutoff, hiddenThreads, id, now, pathname, temp, timestamp, _ref; pathname = location.pathname.slice(1).split('/'); g.BOARD = pathname[0], temp = pathname[1]; if (temp === 'res') { g.REPLY = true; g.THREAD_ID = pathname[2]; } else { g.PAGENUM = parseInt(temp) || 0; } if (location.hostname === 'sys.4chan.org') { $.ready(qr.sys); return; } if (location.hostname === 'images.4chan.org') { $.ready(function() { if (d.title === '4chan - 404') return redirect.init(); }); return; } $.ready(options.init); $.on(window, 'message', Main.message); now = Date.now(); if (conf['Check for Updates'] && $.get('lastUpdate', 0) < now - 6 * HOUR) { $.ready(function() { return $.add(d.head, $.el('script', { src: 'https://raw.github.com/mayhemydg/4chan-x/master/latest.js' })); }); $.set('lastUpdate', now); } g.hiddenReplies = $.get("hiddenReplies/" + g.BOARD + "/", {}); if ($.get('lastChecked', 0) < now - 1 * DAY) { $.set('lastChecked', now); cutoff = now - 7 * DAY; hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {}); for (id in hiddenThreads) { timestamp = hiddenThreads[id]; if (timestamp < cutoff) delete hiddenThreads[id]; } _ref = g.hiddenReplies; for (id in _ref) { timestamp = _ref[id]; if (timestamp < cutoff) delete g.hiddenReplies[id]; } $.set("hiddenThreads/" + g.BOARD + "/", hiddenThreads); $.set("hiddenReplies/" + g.BOARD + "/", g.hiddenReplies); } if (conf['Filter']) filter.init(); if (conf['Reply Hiding']) replyHiding.init(); if (conf['Filter'] || conf['Reply Hiding']) strikethroughQuotes.init(); if (conf['Anonymize']) anonymize.init(); if (conf['Time Formatting']) Time.init(); if (conf['Sauce']) sauce.init(); if (conf['Image Auto-Gif']) imgGif.init(); if (conf['Image Hover']) imgHover.init(); if (conf['Reveal Spoilers']) revealSpoilers.init(); if (conf['Report Button']) reportButton.init(); if (conf['Quote Inline']) quoteInline.init(); if (conf['Quote Preview']) quotePreview.init(); if (conf['Quote Backlinks']) quoteBacklink.init(); if (conf['Indicate OP quote']) quoteOP.init(); if (conf['Indicate Cross-thread Quotes']) quoteDR.init(); return $.ready(Main.ready); }, ready: function() { var callback, canPost, form, node, nodes, _i, _j, _len, _len2, _ref; if (conf['404 Redirect'] && d.title === '4chan - 404') { redirect.init(); return; } if (!$.id('navtopr')) return; $.addClass(d.body, engine); $.addStyle(Main.css); threading.init(); Favicon.init(); if ((form = $('form[name=post]')) && (canPost = !!$.id('recaptcha_response_field'))) { Recaptcha.init(); if (g.REPLY && conf['Auto Watch Reply'] && conf['Thread Watcher']) { $.on(form, 'submit', function() { if ($('img.favicon').src === Favicon.empty) { return watcher.watch(null, g.THREAD_ID); } }); } } if (conf['Auto Noko'] && canPost) form.action += '?noko'; if (conf['Cooldown'] && canPost) cooldown.init(); if (conf['Image Expansion']) imgExpand.init(); if (conf['Quick Reply']) qr.init(); if (conf['Thread Watcher']) watcher.init(); if (conf['Keybinds']) keybinds.init(); if (g.REPLY) { if (conf['Thread Updater']) updater.init(); if (conf['Image Preloading']) imgPreloading.init(); if (conf['Thread Stats']) threadStats.init(); if (conf['Reply Navigation']) nav.init(); if (conf['Post in Title']) titlePost.init(); if (conf['Unread Count']) unread.init(); if (conf['Quick Reply'] && conf['Persistent QR'] && canPost) { qr.dialog(); if (conf['Auto Hide QR']) $('#autohide', qr.el).checked = true; } } else { if (conf['Thread Hiding']) threadHiding.init(); if (conf['Thread Expansion']) expandThread.init(); if (conf['Comment Expansion']) expandComment.init(); if (conf['Index Navigation']) nav.init(); } nodes = $$('.op, a + table'); _ref = g.callbacks; for (_i = 0, _len = _ref.length; _i < _len; _i++) { callback = _ref[_i]; try { for (_j = 0, _len2 = nodes.length; _j < _len2; _j++) { node = nodes[_j]; callback(node); } } catch (err) { alert(err); } } return $.on($('form[name=delform]'), 'DOMNodeInserted', Main.node); }, message: function(e) { var data, origin; origin = e.origin, data = e.data; if (origin === 'http://sys.4chan.org') { return qr.message(data); } else if (data.version && data.version !== VERSION && confirm('An updated version of 4chan X is available, would you like to install it now?')) { return window.location = "https://raw.github.com/mayhemydg/4chan-x/" + data.version + "/4chan_x.user.js"; } }, node: function(e) { var callback, target, _i, _len, _ref, _results; target = e.target; if (target.nodeName !== 'TABLE') return; _ref = g.callbacks; _results = []; for (_i = 0, _len = _ref.length; _i < _len; _i++) { callback = _ref[_i]; try { _results.push(callback(target)); } catch (err) { } } return _results; }, css: '\ /* dialog styling */\ .dialog {\ border: 1px solid rgba(0,0,0,.25);\ }\ .move {\ cursor: move;\ }\ label, a, .favicon, #qr img {\ cursor: pointer;\ }\ a[href="javascript:;"] {\ text-decoration: none;\ }\\ .thread.stub > :not(.block),\ #content > [name=tab]:not(:checked) + div,\ #updater:not(:hover) > :not(.move),\ #qp > input, #qp .inline, .forwarded {\ display: none;\ }\\ .new {\ background: lime;\ }\ .error {\ color: red;\ }\ #error {\ cursor: default;\ }\ #error[href] {\ cursor: pointer;\ }\ td.replyhider {\ vertical-align: top;\ }\\ .filesize + br + a {\ float: left;\ pointer-events: none;\ }\ img[md5], img[md5] + img {\ pointer-events: all;\ }\ .fitwidth img[md5] + img {\ max-width: 100%;\ }\ .gecko > .fitwidth img[md5] + img,\ .presto > .fitwidth img[md5] + img {\ width: 100%;\ }\\ #qp, #ihover {\ position: fixed;\ }\\ #ihover {\ max-height: 100%;\ max-width: 75%;\ }\\ #navlinks {\ font-size: 16px;\ position: fixed;\ top: 25px;\ right: 5px;\ }\\ #overlay {\ position: fixed;\ top: 0;\ right: 0;\ left: 0;\ bottom: 0;\ text-align: center;\ background: rgba(0,0,0,.5);\ z-index: 1;\ }\ #overlay::after {\ content: "";\ display: inline-block;\ height: 100%;\ vertical-align: middle;\ }\ #options {\ display: inline-block;\ padding: 5px;\ text-align: left;\ vertical-align: middle;\ width: 500px;\ }\ #credits {\ float: right;\ }\ #options ul {\ list-style: none;\ padding: 0;\ }\ #options label {\ text-decoration: underline;\ }\ #content > div {\ height: 450px;\ overflow: auto;\ }\ #content textarea {\ margin: 0;\ min-height: 100px;\ resize: vertical;\ width: 100%;\ }\ #flavors {\ height: 100%;\ }\\ #qr {\ position: fixed;\ max-height: 100%;\ overflow-x: hidden;\ overflow-y: auto;\ }\ #qr > div.move {\ text-align: right;\ }\ #qr input[name=name] {\ float: left;\ }\ #qr_form {\ clear: left;\ }\ #qr_form, #qr #com_submit, #qr input[name=upfile] {\ margin: 0;\ }\ #qr textarea {\ width: 100%;\ height: 125px;\ }\ #qr #close, #qr #autohide {\ float: right;\ }\ #qr:not(:hover) > #autohide:checked ~ .autohide {\ height: 0;\ overflow: hidden;\ }\ /* http://stackoverflow.com/questions/2610497/change-an-inputs-html5-placeholder-color-with-css */\ #qr input::-webkit-input-placeholder {\ color: grey;\ }\ #qr input:-moz-placeholder {\ color: grey;\ }\ /* qr reCAPTCHA */\ #qr img {\ border: 1px solid #AAA;\ }\\ #updater {\ position: fixed;\ text-align: right;\ }\ #updater input[type=text] {\ width: 50px;\ }\ #updater:not(:hover) {\ border: none;\ background: transparent;\ }\\ #stats {\ border: none;\ position: fixed;\ }\\ #watcher {\ position: absolute;\ }\ #watcher > div {\ overflow: hidden;\ padding-right: 5px;\ padding-left: 5px;\ text-overflow: ellipsis;\ max-width: 200px;\ white-space: nowrap;\ }\ #watcher > div.move {\ text-decoration: underline;\ padding-top: 5px;\ }\ #watcher > div:last-child {\ padding-bottom: 5px;\ }\\ #qp {\ border: 1px solid;\ padding-bottom: 5px;\ }\ .qphl {\ outline: 2px solid rgba(216, 94, 49, .7);\ }\ .inlined {\ opacity: .5;\ }\ .inline td.reply {\ background-color: rgba(255, 255, 255, 0.15);\ border: 1px solid rgba(128, 128, 128, 0.5);\ }\ .filetitle, .replytitle, .postername, .commentpostername, .postertrip {\ background: none;\ }\ .filtered {\ text-decoration: line-through;\ }\\ #files > input {\ display: block;\ }\ ' }; Main.init();}).call(this);