diff --git a/4chan_x.user.js b/4chan_x.user.js index 3934927d7..dc50b1fb6 100644 --- a/4chan_x.user.js +++ b/4chan_x.user.js @@ -73,9 +73,9 @@ */ (function() { - var $, $$, Anonymize, AutoGif, DAY, ExpandComment, ExpandThread, Favicon, FileInfo, Filter, GetTitle, HOUR, ImageExpand, ImageHover, Keybinds, MINUTE, Main, NAMESPACE, Nav, Options, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Redirect, ReplyHiding, ReportButton, RevealSpoilers, SECOND, Sauce, StrikethroughQuotes, ThreadHiding, ThreadStats, Threading, Time, TitlePost, Unread, Updater, VERSION, Watcher, conf, config, d, engine, flatten, g, log, qr, ui, _base; + var $, $$, Anonymize, AutoGif, Conf, Config, ExpandComment, ExpandThread, Favicon, FileInfo, Filter, GetTitle, ImageExpand, ImageHover, Keybinds, Main, Nav, Options, QR, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Redirect, ReplyHiding, ReportButton, RevealSpoilers, Sauce, StrikethroughQuotes, ThreadHiding, ThreadStats, Threading, Time, TitlePost, UI, Unread, Updater, Watcher, d, g, _base; - config = { + Config = { main: { Enhancing: { '404 Redirect': [true, 'Redirect dead threads and images'], @@ -192,76 +192,44 @@ } }; - log = typeof (_base = console.log).bind === "function" ? _base.bind(console) : void 0; - - conf = {}; - - (flatten = function(parent, obj) { - var key, val; - if (obj instanceof Array) { - conf[parent] = obj[0]; - } else if (typeof obj === 'object') { - for (key in obj) { - val = obj[key]; - flatten(key, val); - } - } else { - conf[parent] = obj; - } - })(null, config); - - NAMESPACE = '4chan_x.'; - - VERSION = '2.29.1'; - - SECOND = 1000; - - MINUTE = 60 * SECOND; - - HOUR = 60 * MINUTE; - - DAY = 24 * HOUR; - - engine = /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase(); + Conf = {}; d = document; - g = { - callbacks: [] - }; + g = {}; - ui = { + UI = { dialog: function(id, position, html) { var el, saved; el = d.createElement('div'); el.className = 'reply dialog'; el.innerHTML = html; el.id = id; - el.style.cssText = (saved = localStorage["" + NAMESPACE + id + ".position"]) ? saved : position; - el.querySelector('.move').addEventListener('mousedown', ui.dragstart, false); + el.style.cssText = (saved = localStorage["" + Main.namespace + id + ".position"]) ? saved : position; + el.querySelector('.move').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); + 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; + 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; + 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 = UI.el.style; style.top = top; style.right = right; style.bottom = bottom; @@ -269,15 +237,15 @@ }, 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); + el = UI.el; + localStorage["" + Main.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; + el = UI.el; style = el.style; _ref = d.body, clientHeight = _ref.clientHeight, clientWidth = _ref.clientWidth; height = el.offsetHeight; @@ -292,8 +260,8 @@ } }, hoverend: function() { - $.rm(ui.el); - return delete ui.el; + $.rm(UI.el); + return delete UI.el; } }; @@ -317,6 +285,12 @@ }; $.extend($, { + SECOND: 1000, + MINUTE: 1000 * 60, + HOUR: 1000 * 60 * 60, + DAY: 1000 * 60 * 60 * 24, + log: typeof (_base = console.log).bind === "function" ? _base.bind(console) : void 0, + engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase(), ready: function(fc) { var cb; if (/interactive|complete/.test(d.readyState)) return setTimeout(fc); @@ -328,7 +302,9 @@ }, sync: function(key, cb) { return $.on(window, 'storage', function(e) { - if (e.key === ("" + NAMESPACE + key)) return cb(JSON.parse(e.newValue)); + if (e.key === ("" + Main.namespace + key)) { + return cb(JSON.parse(e.newValue)); + } }); }, id: function(id) { @@ -384,11 +360,11 @@ cb: { checked: function() { $.set(this.name, this.checked); - return conf[this.name] = this.checked; + return Conf[this.name] = this.checked; }, value: function() { $.set(this.name, this.value.trim()); - return conf[this.name] = this.value; + return Conf[this.name] = this.value; } }, addStyle: function(css) { @@ -498,12 +474,12 @@ $.extend($, typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null ? { "delete": function(name) { - name = NAMESPACE + name; + name = Main.namespace + name; return GM_deleteValue(name); }, get: function(name, defaultValue) { var value; - name = NAMESPACE + name; + name = Main.namespace + name; if (value = GM_getValue(name)) { return JSON.parse(value); } else { @@ -511,24 +487,24 @@ } }, set: function(name, value) { - name = NAMESPACE + name; + name = Main.namespace + name; localStorage.setItem(name, JSON.stringify(value)); return GM_setValue(name, JSON.stringify(value)); } } : { "delete": function(name) { - return localStorage.removeItem(NAMESPACE + name); + return localStorage.removeItem(Main.namespace + name); }, get: function(name, defaultValue) { var value; - if (value = localStorage.getItem(NAMESPACE + name)) { + if (value = localStorage.getItem(Main.namespace + name)) { return JSON.parse(value); } else { return defaultValue; } }, set: function(name, value) { - return localStorage.setItem(NAMESPACE + name, JSON.stringify(value)); + return localStorage.setItem(Main.namespace + name, JSON.stringify(value)); } }); @@ -541,9 +517,9 @@ filters: {}, init: function() { var boards, filter, hl, key, op, regexp, top, _i, _len, _ref, _ref2, _ref3, _ref4, _ref5; - for (key in config.filter) { + for (key in Config.filter) { this.filters[key] = []; - _ref = conf[key].split('\n'); + _ref = Conf[key].split('\n'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { filter = _ref[_i]; if (filter[0] === '#') continue; @@ -573,7 +549,7 @@ } if (!this.filters[key].length) delete this.filters[key]; } - if (Object.keys(this.filters).length) return g.callbacks.push(this.node); + if (Object.keys(this.filters).length) return Main.callbacks.push(this.node); }, createFilter: function(regexp, op, hl, top) { var test; @@ -698,7 +674,7 @@ StrikethroughQuotes = { init: function() { - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var el, quote, _i, _len, _ref; @@ -708,7 +684,7 @@ quote = _ref[_i]; if ((el = $.id(quote.hash.slice(1))) && el.parentNode.parentNode.parentNode.hidden) { $.addClass(quote, 'filtered'); - if (conf['Recursive Filtering']) ReplyHiding.hide(post.root); + if (Conf['Recursive Filtering']) ReplyHiding.hide(post.root); } } } @@ -759,11 +735,11 @@ quotes: quotes, backlinks: [] }; - if (conf['Resurrect Quotes']) Quotify.node(post); - if (conf['Quote Preview']) QuotePreview.node(post); - if (conf['Quote Inline']) QuoteInline.node(post); - if (conf['Indicate OP quote']) QuoteOP.node(post); - if (conf['Indicate Cross-thread Quotes']) QuoteCT.node(post); + if (Conf['Resurrect Quotes']) Quotify.node(post); + if (Conf['Quote Preview']) QuotePreview.node(post); + if (Conf['Quote Inline']) QuoteInline.node(post); + if (Conf['Indicate OP quote']) QuoteOP.node(post); + if (Conf['Indicate Cross-thread Quotes']) QuoteCT.node(post); return $.replace(a.parentNode.parentNode, node.lastChild); } }; @@ -880,7 +856,7 @@ className: 'replyhider', innerHTML: '[ - ]' }); - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var td; @@ -920,7 +896,7 @@ var div, name, trip, uid, _ref, _ref2; if (table.hidden) return; table.hidden = true; - if (!conf['Show Stubs']) return; + if (!Conf['Show Stubs']) return; name = $('.commentpostername', table).textContent; uid = ((_ref = $('.posteruid', table)) != null ? _ref.textContent : void 0) || ''; trip = ((_ref2 = $('.postertrip', table)) != null ? _ref2.textContent : void 0) || ''; @@ -950,26 +926,26 @@ } thread = Nav.getThread(); switch (key) { - case conf.openQR: + case Conf.openQR: Keybinds.qr(thread, true); break; - case conf.openEmptyQR: + case Conf.openEmptyQR: Keybinds.qr(thread); break; - case conf.openOptions: + case Conf.openOptions: if (!$.id('overlay')) Options.dialog(); break; - case conf.close: + case Conf.close: if (o = $.id('overlay')) { Options.close.call(o); - } else if (qr.el) { - qr.close(); + } else if (QR.el) { + QR.close(); } break; - case conf.submit: - if (qr.el && !qr.status()) qr.submit(); + case Conf.submit: + if (QR.el && !QR.status()) QR.submit(); break; - case conf.spoiler: + case Conf.spoiler: ta = e.target; if (ta.nodeName !== 'TEXTAREA') return; value = ta.value; @@ -979,55 +955,55 @@ range = 9 + selEnd; ta.setSelectionRange(range, range); break; - case conf.watch: + case Conf.watch: Watcher.toggle(thread); break; - case conf.update: + case Conf.update: Updater.update(); break; - case conf.unreadCountTo0: + case Conf.unreadCountTo0: Unread.replies = []; Unread.update(); break; - case conf.expandImage: + case Conf.expandImage: Keybinds.img(thread); break; - case conf.expandAllImages: + case Conf.expandAllImages: Keybinds.img(thread, true); break; - case conf.zero: + case Conf.zero: window.location = "/" + g.BOARD + "/0#0"; break; - case conf.nextPage: + case Conf.nextPage: if ((_ref = $('input[value=Next]')) != null) _ref.click(); break; - case conf.previousPage: + case Conf.previousPage: if ((_ref2 = $('input[value=Previous]')) != null) _ref2.click(); break; - case conf.nextThread: + case Conf.nextThread: if (g.REPLY) return; Nav.scroll(+1); break; - case conf.previousThread: + case Conf.previousThread: if (g.REPLY) return; Nav.scroll(-1); break; - case conf.expandThread: + case Conf.expandThread: ExpandThread.toggle(thread); break; - case conf.openThread: + case Conf.openThread: Keybinds.open(thread); break; - case conf.openThreadTab: + case Conf.openThreadTab: Keybinds.open(thread, true); break; - case conf.nextReply: + case Conf.nextReply: Keybinds.hl(+1, thread); break; - case conf.previousReply: + case Conf.previousReply: Keybinds.hl(-1, thread); break; - case conf.hide: + case Conf.hide: if (/\bthread\b/.test(thread.className)) ThreadHiding.toggle(thread); break; default: @@ -1115,11 +1091,11 @@ }, qr: function(thread, quote) { if (quote) { - qr.quote.call($('.quotejs + .quotejs', $('.replyhl', thread) || thread)); + QR.quote.call($('.quotejs + .quotejs', $('.replyhl', thread) || thread)); } else { - qr.open(); + QR.open(); } - return $('textarea', qr.el).focus(); + return $('textarea', QR.el).focus(); }, open: function(thread, tab) { var id, url; @@ -1233,28 +1209,28 @@ } }; - qr = { + QR = { init: function() { if (!$.id('recaptcha_challenge_field_holder')) return; - g.callbacks.push(this.node); + Main.callbacks.push(this.node); return setTimeout(this.asyncInit); }, asyncInit: function() { var form, iframe, link, loadChecking, script; - if (conf['Hide Original Post Form']) { + if (Conf['Hide Original Post Form']) { link = $.el('h1', { innerHTML: "" + (g.REPLY ? 'Quick Reply' : 'New Thread') + "" }); $.on($('a', link), 'click', function() { - qr.open(); - if (!g.REPLY) $('select', qr.el).value = 'new'; - return $('textarea', qr.el).focus(); + QR.open(); + if (!g.REPLY) $('select', QR.el).value = 'new'; + return $('textarea', QR.el).focus(); }); form = d.forms[0]; $.before(form, link); } if (/chrome/i.test(navigator.userAgent)) { - qr.status({ + QR.status({ ready: true }); } else { @@ -1266,7 +1242,7 @@ return this.src = this.src; }); loadChecking = function(iframe) { - if (!qr.status.ready) { + if (!QR.status.ready) { iframe.src = 'about:blank'; return setTimeout((function() { return iframe.src = 'https://sys.4chan.org/robots.txt'; @@ -1285,131 +1261,131 @@ }); $.add(d.head, script); $.rm(script); - if (conf['Persistent QR']) { - qr.dialog(); - if (conf['Auto Hide QR']) qr.hide(); + if (Conf['Persistent QR']) { + QR.dialog(); + if (Conf['Auto Hide QR']) QR.hide(); } - $.on(d, 'dragover', qr.dragOver); - $.on(d, 'drop', qr.dropFile); - $.on(d, 'dragstart', qr.drag); - return $.on(d, 'dragend', qr.drag); + $.on(d, 'dragover', QR.dragOver); + $.on(d, 'drop', QR.dropFile); + $.on(d, 'dragstart', QR.drag); + return $.on(d, 'dragend', QR.drag); }, node: function(post) { - return $.on($('.quotejs + .quotejs', post.el), 'click', qr.quote); + return $.on($('.quotejs + .quotejs', post.el), 'click', QR.quote); }, open: function() { - if (qr.el) { - qr.el.hidden = false; - return qr.unhide(); + if (QR.el) { + QR.el.hidden = false; + return QR.unhide(); } else { - return qr.dialog(); + return QR.dialog(); } }, close: function() { var i, spoiler, _i, _len, _ref; - qr.el.hidden = true; - qr.message.send({ + QR.el.hidden = true; + QR.message.send({ req: 'abort' }); d.activeElement.blur(); - $.removeClass(qr.el, 'dump'); - _ref = qr.replies; + $.removeClass(QR.el, 'dump'); + _ref = QR.replies; for (_i = 0, _len = _ref.length; _i < _len; _i++) { i = _ref[_i]; - qr.replies[0].rm(); + QR.replies[0].rm(); } - qr.cooldown.auto = false; - qr.status(); - qr.resetFileInput(); - if (!conf['Remember Spoiler'] && (spoiler = $.id('spoiler')).checked) { + QR.cooldown.auto = false; + QR.status(); + QR.resetFileInput(); + if (!Conf['Remember Spoiler'] && (spoiler = $.id('spoiler')).checked) { spoiler.click(); } - return qr.cleanError(); + return QR.cleanError(); }, hide: function() { d.activeElement.blur(); - $.addClass(qr.el, 'autohide'); + $.addClass(QR.el, 'autohide'); return $.id('autohide').checked = true; }, unhide: function() { - $.removeClass(qr.el, 'autohide'); + $.removeClass(QR.el, 'autohide'); return $.id('autohide').checked = false; }, toggleHide: function() { - return this.checked && qr.hide() || qr.unhide(); + return this.checked && QR.hide() || QR.unhide(); }, error: function(err, node) { var el; - el = $('.warning', qr.el); + el = $('.warning', QR.el); el.textContent = err; if (node) $.replace(el.firstChild, node); - qr.open(); - if (/captcha|verification/i.test(err)) $('[autocomplete]', qr.el).focus(); + QR.open(); + if (/captcha|verification/i.test(err)) $('[autocomplete]', QR.el).focus(); if (d.hidden || d.oHidden || d.mozHidden || d.webkitHidden) { return alert(err); } }, cleanError: function() { - return $('.warning', qr.el).textContent = null; + return $('.warning', QR.el).textContent = null; }, status: function(data) { var disabled, input, value; if (data == null) data = {}; if (data.ready) { - qr.status.ready = true; - qr.status.banned = data.banned; - } else if (!qr.status.ready) { + QR.status.ready = true; + QR.status.banned = data.banned; + } else if (!QR.status.ready) { value = 'Loading'; disabled = true; } if (g.dead) { value = 404; disabled = true; - qr.cooldown.auto = false; - } else if (qr.status.banned) { + QR.cooldown.auto = false; + } else if (QR.status.banned) { value = 'Banned'; disabled = true; } else { - value = qr.cooldown.seconds || data.progress || value; + value = QR.cooldown.seconds || data.progress || value; } - if (!qr.el) return; - input = qr.status.input; - input.value = qr.cooldown.auto && conf['Cooldown'] ? value ? "Auto " + value : 'Auto' : value || 'Submit'; + if (!QR.el) return; + input = QR.status.input; + input.value = QR.cooldown.auto && Conf['Cooldown'] ? value ? "Auto " + value : 'Auto' : value || 'Submit'; return input.disabled = disabled || false; }, cooldown: { init: function() { - if (!conf['Cooldown']) return; - qr.cooldown.start($.get("/" + g.BOARD + "/cooldown", 0)); - return $.sync("/" + g.BOARD + "/cooldown", qr.cooldown.start); + if (!Conf['Cooldown']) return; + QR.cooldown.start($.get("/" + g.BOARD + "/cooldown", 0)); + return $.sync("/" + g.BOARD + "/cooldown", QR.cooldown.start); }, start: function(timeout) { var seconds; seconds = Math.floor((timeout - Date.now()) / 1000); - return qr.cooldown.count(seconds); + return QR.cooldown.count(seconds); }, set: function(seconds) { - if (!conf['Cooldown']) return; - qr.cooldown.count(seconds); - return $.set("/" + g.BOARD + "/cooldown", Date.now() + seconds * SECOND); + if (!Conf['Cooldown']) return; + QR.cooldown.count(seconds); + return $.set("/" + g.BOARD + "/cooldown", Date.now() + seconds * $.SECOND); }, count: function(seconds) { if (!((0 <= seconds && seconds <= 60))) return; - setTimeout(qr.cooldown.count, 1000, seconds - 1); - qr.cooldown.seconds = seconds; + setTimeout(QR.cooldown.count, 1000, seconds - 1); + QR.cooldown.seconds = seconds; if (seconds === 0) { $["delete"]("/" + g.BOARD + "/cooldown"); - if (qr.cooldown.auto) qr.submit(); + if (QR.cooldown.auto) QR.submit(); } - return qr.status(); + return QR.status(); } }, quote: function(e) { var caretPos, id, range, s, sel, ta, text, _ref; if (e != null) e.preventDefault(); - qr.open(); + QR.open(); if (!g.REPLY) { - $('select', qr.el).value = $.x('ancestor::div[@class="thread"]', this).firstChild.id; + $('select', QR.el).value = $.x('ancestor::div[@class="thread"]', this).firstChild.id; } id = this.previousElementSibling.hash.slice(1); text = ">>" + id + "\n"; @@ -1418,9 +1394,9 @@ s = s.replace(/\n/g, '\n>'); text += ">" + s + "\n"; } - ta = $('textarea', qr.el); + ta = $('textarea', QR.el); caretPos = ta.selectionStart; - qr.selected.el.lastChild.textContent = qr.selected.com = ta.value = ta.value.slice(0, caretPos) + text + ta.value.slice(ta.selectionEnd); + QR.selected.el.lastChild.textContent = QR.selected.com = ta.value = ta.value.slice(0, caretPos) + text + ta.value.slice(ta.selectionEnd); ta.focus(); ta.selectionEnd = ta.selectionStart = caretPos + text.length; range = caretPos + text.length; @@ -1429,8 +1405,8 @@ drag: function(e) { var i; i = e.type === 'dragstart' ? 'off' : 'on'; - $[i](d, 'dragover', qr.dragOver); - return $[i](d, 'drop', qr.dropFile); + $[i](d, 'dragover', QR.dragOver); + return $[i](d, 'drop', QR.dropFile); }, dragOver: function(e) { e.preventDefault(); @@ -1439,23 +1415,23 @@ dropFile: function(e) { if (!e.dataTransfer.files.length) return; e.preventDefault(); - qr.open(); - qr.fileInput.call(e.dataTransfer); - return $.addClass(qr.el, 'dump'); + QR.open(); + QR.fileInput.call(e.dataTransfer); + return $.addClass(QR.el, 'dump'); }, fileInput: function() { var file, _i, _len, _ref; - qr.cleanError(); + QR.cleanError(); if (this.files.length === 1) { file = this.files[0]; if (file.size > this.max) { - qr.error('File too large.'); - qr.resetFileInput(); - } else if (-1 === qr.mimeTypes.indexOf(file.type)) { - qr.error('Unsupported file type.'); - qr.resetFileInput(); + QR.error('File too large.'); + QR.resetFileInput(); + } else if (-1 === QR.mimeTypes.indexOf(file.type)) { + QR.error('Unsupported file type.'); + QR.resetFileInput(); } else { - qr.selected.setFile(file); + QR.selected.setFile(file); } return; } @@ -1463,23 +1439,23 @@ for (_i = 0, _len = _ref.length; _i < _len; _i++) { file = _ref[_i]; if (file.size > this.max) { - qr.error("File " + file.name + " is too large."); + QR.error("File " + file.name + " is too large."); break; - } else if (-1 === qr.mimeTypes.indexOf(file.type)) { - qr.error("" + file.name + ": Unsupported file type."); + } else if (-1 === QR.mimeTypes.indexOf(file.type)) { + QR.error("" + file.name + ": Unsupported file type."); break; } - if (!qr.replies[qr.replies.length - 1].file) { - qr.replies[qr.replies.length - 1].setFile(file); + if (!QR.replies[QR.replies.length - 1].file) { + QR.replies[QR.replies.length - 1].setFile(file); } else { - new qr.reply().setFile(file); + new QR.reply().setFile(file); } } - $.addClass(qr.el, 'dump'); - return qr.resetFileInput(); + $.addClass(QR.el, 'dump'); + return QR.resetFileInput(); }, resetFileInput: function() { - return $('[type=file]', qr.el).value = null; + return $('[type=file]', QR.el).value = null; }, replies: [], reply: (function() { @@ -1487,12 +1463,12 @@ function _Class() { var persona, prev, _this = this; - prev = qr.replies[qr.replies.length - 1]; - persona = $.get('qr.persona', {}); + prev = QR.replies[QR.replies.length - 1]; + persona = $.get('QR.persona', {}); this.name = prev ? prev.name : persona.name || null; this.email = prev && !/^sage$/.test(prev.email) ? prev.email : persona.email || null; - this.sub = prev && conf['Remember Subject'] ? prev.sub : conf['Remember Subject'] ? persona.sub : null; - this.spoiler = prev && conf['Remember Spoiler'] ? prev.spoiler : false; + this.sub = prev && Conf['Remember Subject'] ? prev.sub : Conf['Remember Subject'] ? persona.sub : null; + this.spoiler = prev && Conf['Remember Spoiler'] ? prev.spoiler : false; this.com = null; this.el = $.el('a', { className: 'preview', @@ -1517,14 +1493,14 @@ return $.id('spoiler').checked = _this.spoiler; } }); - $.before($('#addReply', qr.el), this.el); + $.before($('#addReply', QR.el), this.el); $.on(this.el, 'dragstart', this.dragStart); $.on(this.el, 'dragenter', this.dragEnter); $.on(this.el, 'dragleave', this.dragLeave); $.on(this.el, 'dragover', this.dragOver); $.on(this.el, 'dragend', this.dragEnd); $.on(this.el, 'drop', this.drop); - qr.replies.push(this); + QR.replies.push(this); } _Class.prototype.setFile = function(file) { @@ -1532,7 +1508,7 @@ _this = this; this.file = file; this.el.title = file.name; - if (qr.spoiler) $('label', this.el).hidden = false; + if (QR.spoiler) $('label', this.el).hidden = false; if (file.type === 'application/pdf') { this.el.style.backgroundImage = null; return; @@ -1576,18 +1552,18 @@ }; _Class.prototype.rmFile = function() { - qr.resetFileInput(); + QR.resetFileInput(); delete this.file; this.el.title = null; this.el.style.backgroundImage = null; - if (qr.spoiler) $('label', this.el).hidden = true; + if (QR.spoiler) $('label', this.el).hidden = true; return (window.URL || window.webkitURL).revokeObjectURL(this.url); }; _Class.prototype.select = function() { var data, rectEl, rectList, _i, _len, _ref, _ref2; - if ((_ref = qr.selected) != null) _ref.el.id = null; - qr.selected = this; + if ((_ref = QR.selected) != null) _ref.el.id = null; + QR.selected = this; this.el.id = 'selected'; rectEl = this.el.getBoundingClientRect(); rectList = this.el.parentNode.getBoundingClientRect(); @@ -1595,9 +1571,9 @@ _ref2 = ['name', 'email', 'sub', 'com']; for (_i = 0, _len = _ref2.length; _i < _len; _i++) { data = _ref2[_i]; - $("[name=" + data + "]", qr.el).value = this[data]; + $("[name=" + data + "]", QR.el).value = this[data]; } - return $('#spoiler', qr.el).checked = this.spoiler; + return $('#spoiler', QR.el).checked = this.spoiler; }; _Class.prototype.dragStart = function() { @@ -1630,8 +1606,8 @@ } else { $.before(this, el); } - reply = qr.replies.splice(oldIndex, 1)[0]; - return qr.replies.splice(newIndex, 0, reply); + reply = QR.replies.splice(oldIndex, 1)[0]; + return QR.replies.splice(newIndex, 0, reply); }; _Class.prototype.dragEnd = function() { @@ -1642,15 +1618,15 @@ _Class.prototype.rm = function() { var index; - qr.resetFileInput(); + QR.resetFileInput(); $.rm(this.el); - index = qr.replies.indexOf(this); - if (qr.replies.length === 1) { - new qr.reply().select(); + index = QR.replies.indexOf(this); + if (QR.replies.length === 1) { + new QR.reply().select(); } else if (this.el.id === 'selected') { - (qr.replies[index - 1] || qr.replies[index + 1]).select(); + (QR.replies[index - 1] || QR.replies[index + 1]).select(); } - qr.replies.splice(index, 1); + QR.replies.splice(index, 1); (window.URL || window.webkitURL).revokeObjectURL(this.url); return delete this; }; @@ -1661,8 +1637,8 @@ captcha: { init: function() { var _this = this; - this.img = $('.captcha > img', qr.el); - this.input = $('[autocomplete]', qr.el); + this.img = $('.captcha > img', QR.el); + this.input = $('[autocomplete]', QR.el); this.challenge = $.id('recaptcha_challenge_field_holder'); $.on(this.img.parentNode, 'click', this.reload); $.on(this.input, 'keydown', this.keydown); @@ -1693,7 +1669,7 @@ }, load: function() { var challenge; - this.timeout = Date.now() + 26 * MINUTE; + this.timeout = Date.now() + 26 * $.MINUTE; challenge = this.challenge.firstChild.value; this.img.alt = challenge; this.img.src = "http://www.google.com/recaptcha/api/image?c=" + challenge; @@ -1714,11 +1690,11 @@ }, reload: function(focus) { window.location = 'javascript:Recaptcha.reload()'; - if (focus) return qr.captcha.input.focus(); + if (focus) return QR.captcha.input.focus(); }, keydown: function(e) { var c; - c = qr.captcha; + c = QR.captcha; if (e.keyCode === 8 && !c.input.value) { c.reload(); } else if (e.keyCode === 13 && e.shiftKey) { @@ -1731,7 +1707,7 @@ }, dialog: function() { var e, event, fileInput, input, mimeTypes, name, spoiler, ta, thread, threads, _i, _j, _k, _len, _len2, _len3, _ref, _ref2, _ref3; - qr.el = ui.dialog('qr', 'top:0;right:0;', '\ + QR.el = UI.dialog('qr', 'top:0;right:0;', '\
\ Quick Reply \ ×\ @@ -1746,11 +1722,11 @@ \
\ '); - if (conf['Remember QR size'] && engine === 'gecko') { - $.on(ta = $('textarea', qr.el), 'mouseup', function() { - return $.set('qr.size', this.style.cssText); + if (Conf['Remember QR size'] && $.engine === 'gecko') { + $.on(ta = $('textarea', QR.el), 'mouseup', function() { + return $.set('QR.size', this.style.cssText); }); - ta.style.cssText = $.get('qr.size', ''); + ta.style.cssText = $.get('QR.size', ''); } mimeTypes = $('.rules').firstChild.textContent.match(/: (.+) /)[1].toLowerCase().replace(/\w+/g, function(type) { switch (type) { @@ -1762,14 +1738,14 @@ return "image/" + type; } }); - qr.mimeTypes = mimeTypes.split(', '); - qr.mimeTypes.push(''); - fileInput = $('[type=file]', qr.el); + QR.mimeTypes = mimeTypes.split(', '); + QR.mimeTypes.push(''); + fileInput = $('[type=file]', QR.el); fileInput.max = $('[name=MAX_FILE_SIZE]').value; fileInput.accept = mimeTypes; - qr.spoiler = !!$('#com_submit + label'); - spoiler = $('#spoilerLabel', qr.el); - spoiler.hidden = !qr.spoiler; + QR.spoiler = !!$('#com_submit + label'); + spoiler = $('#spoilerLabel', QR.el); + spoiler.hidden = !QR.spoiler; if (!g.REPLY) { threads = ''; _ref = $$('.op'); @@ -1777,82 +1753,82 @@ thread = _ref[_i]; threads += ""; } - $.prepend($('.move > span', qr.el), $.el('select', { + $.prepend($('.move > span', QR.el), $.el('select', { innerHTML: threads, title: 'Create a new thread / Reply to a thread' })); - $.on($('select', qr.el), 'mousedown', function(e) { + $.on($('select', QR.el), 'mousedown', function(e) { return e.stopPropagation(); }); } - $.on($('#autohide', qr.el), 'change', qr.toggleHide); - $.on($('.close', qr.el), 'click', qr.close); - $.on($('#dump', qr.el), 'click', function() { - return qr.el.classList.toggle('dump'); + $.on($('#autohide', QR.el), 'change', QR.toggleHide); + $.on($('.close', QR.el), 'click', QR.close); + $.on($('#dump', QR.el), 'click', function() { + return QR.el.classList.toggle('dump'); }); - $.on($('#addReply', qr.el), 'click', function() { - return new qr.reply().select(); + $.on($('#addReply', QR.el), 'click', function() { + return new QR.reply().select(); }); - $.on($('form', qr.el), 'submit', qr.submit); - $.on($('textarea', qr.el), 'keyup', function() { - return qr.selected.el.lastChild.textContent = this.value; + $.on($('form', QR.el), 'submit', QR.submit); + $.on($('textarea', QR.el), 'keyup', function() { + return QR.selected.el.lastChild.textContent = this.value; }); - $.on(fileInput, 'change', qr.fileInput); + $.on(fileInput, 'change', QR.fileInput); $.on(fileInput, 'click', function(e) { - if (e.shiftKey) return qr.selected.rmFile() || e.preventDefault(); + if (e.shiftKey) return QR.selected.rmFile() || e.preventDefault(); }); $.on(spoiler.firstChild, 'change', function() { - return $('input', qr.selected.el).click(); + return $('input', QR.selected.el).click(); }); - $.on($('.warning', qr.el), 'click', qr.cleanError); - new qr.reply().select(); + $.on($('.warning', QR.el), 'click', QR.cleanError); + new QR.reply().select(); _ref2 = ['name', 'email', 'sub', 'com']; for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { name = _ref2[_j]; - input = $("[name=" + name + "]", qr.el); + input = $("[name=" + name + "]", QR.el); _ref3 = ['input', 'keyup', 'change', 'paste']; for (_k = 0, _len3 = _ref3.length; _k < _len3; _k++) { event = _ref3[_k]; $.on(input, event, function() { - qr.selected[this.name] = this.value; - if (qr.cooldown.auto && qr.selected === qr.replies[0] && parseInt(qr.status.input.value.match(/\d+/)) < 6) { - return qr.cooldown.auto = false; + QR.selected[this.name] = this.value; + if (QR.cooldown.auto && QR.selected === QR.replies[0] && parseInt(QR.status.input.value.match(/\d+/)) < 6) { + return QR.cooldown.auto = false; } }); } } - $.sync('qr.persona', function(persona) { + $.sync('QR.persona', function(persona) { var key, val, _results; - if (!qr.el.hidden) return; + if (!QR.el.hidden) return; _results = []; for (key in persona) { val = persona[key]; - qr.selected[key] = val; - _results.push($("[name=" + key + "]", qr.el).value = val); + QR.selected[key] = val; + _results.push($("[name=" + key + "]", QR.el).value = val); } return _results; }); - qr.status.input = $('[type=submit]', qr.el); - qr.status(); - qr.cooldown.init(); - qr.captcha.init(); - $.add(d.body, qr.el); + QR.status.input = $('[type=submit]', QR.el); + QR.status(); + QR.cooldown.init(); + QR.captcha.init(); + $.add(d.body, QR.el); e = d.createEvent('CustomEvent'); e.initEvent('QRDialogCreation', true, false); - return qr.el.dispatchEvent(e); + return QR.el.dispatchEvent(e); }, submit: function(e) { var captcha, captchas, challenge, err, file, m, post, reader, reply, response, threadID; if (e != null) e.preventDefault(); - if (qr.cooldown.seconds) { - qr.cooldown.auto = !qr.cooldown.auto; - qr.status(); + if (QR.cooldown.seconds) { + QR.cooldown.auto = !QR.cooldown.auto; + QR.status(); return; } - qr.message.send({ + QR.message.send({ req: 'abort' }); - reply = qr.replies[0]; + reply = QR.replies[0]; if (!(reply.com || reply.file)) { err = 'No file selected.'; } else { @@ -1864,24 +1840,24 @@ challenge = captcha.challenge; response = captcha.response; } else { - challenge = qr.captcha.img.alt; - if (response = qr.captcha.input.value) qr.captcha.reload(); + challenge = QR.captcha.img.alt; + if (response = QR.captcha.input.value) QR.captcha.reload(); } $.set('captchas', captchas); - qr.captcha.count(captchas.length); + QR.captcha.count(captchas.length); if (!response) err = 'No valid captcha.'; } if (err) { - qr.cooldown.auto = false; - qr.status(); - qr.error(err); + QR.cooldown.auto = false; + QR.status(); + QR.error(err); return; } - qr.cleanError(); - threadID = g.THREAD_ID || $('select', qr.el).value; - qr.cooldown.auto = qr.replies.length > 1; - if (conf['Auto Hide QR'] && !qr.cooldown.auto) qr.hide(); - if (conf['Thread Watcher'] && conf['Auto Watch Reply'] && threadID !== 'new') { + QR.cleanError(); + threadID = g.THREAD_ID || $('select', QR.el).value; + QR.cooldown.auto = QR.replies.length > 1; + if (Conf['Auto Hide QR'] && !QR.cooldown.auto) QR.hide(); + if (Conf['Thread Watcher'] && Conf['Auto Watch Reply'] && threadID !== 'new') { Watcher.watch(threadID); } post = { @@ -1898,10 +1874,10 @@ recaptcha_challenge_field: challenge, recaptcha_response_field: response + ' ' }; - qr.status({ + QR.status({ progress: '...' }); - if (engine === 'gecko' && reply.file) { + if ($.engine === 'gecko' && reply.file) { file = {}; reader = new FileReader(); reader.onload = function() { @@ -1909,16 +1885,16 @@ file.name = reply.file.name; file.type = reply.file.type; post.upfile = file; - return qr.message.send(post); + return QR.message.send(post); }; reader.readAsBinaryString(reply.file); return; } if (/chrome/i.test(navigator.userAgent)) { - qr.message.post(post); + QR.message.post(post); return; } - return qr.message.send(post); + return QR.message.send(post); }, response: function(html) { var b, doc, err, node, persona, postNumber, reply, thread, _, _ref; @@ -1926,7 +1902,7 @@ innerHTML: html }); if ($('title', doc).textContent === '4chan - Banned') { - qr.status({ + QR.status({ ready: true, banned: true }); @@ -1943,58 +1919,58 @@ } if (err) { if (/captcha|verification/i.test(err) || err === 'Connection error with sys.4chan.org.') { - qr.cooldown.auto = !!$.get('captchas', []).length; - qr.cooldown.set(2); + QR.cooldown.auto = !!$.get('captchas', []).length; + QR.cooldown.set(2); } else { - qr.cooldown.auto = false; + QR.cooldown.auto = false; } - qr.status(); - qr.error(err, node); + QR.status(); + QR.error(err, node); return; } - reply = qr.replies[0]; - persona = $.get('qr.persona', {}); + reply = QR.replies[0]; + persona = $.get('QR.persona', {}); persona = { name: reply.name, email: /^sage$/.test(reply.email) ? persona.email : reply.email, - sub: conf['Remember Subject'] ? reply.sub : null + sub: Conf['Remember Subject'] ? reply.sub : null }; - $.set('qr.persona', persona); + $.set('QR.persona', persona); _ref = b.lastChild.textContent.match(/thread:(\d+),no:(\d+)/), _ = _ref[0], thread = _ref[1], postNumber = _ref[2]; if (thread === '0') { - if (conf['Thread Watcher'] && conf['Auto Watch']) { + if (Conf['Thread Watcher'] && Conf['Auto Watch']) { $.set('autoWatch', postNumber); } location.pathname = "/" + g.BOARD + "/res/" + postNumber; } else { - qr.cooldown.auto = qr.replies.length > 1; - qr.cooldown.set(/sage/i.test(reply.email) ? 60 : 30); - if (conf['Open Reply in New Tab'] && !g.REPLY && !qr.cooldown.auto) { + QR.cooldown.auto = QR.replies.length > 1; + QR.cooldown.set(/sage/i.test(reply.email) ? 60 : 30); + if (Conf['Open Reply in New Tab'] && !g.REPLY && !QR.cooldown.auto) { $.open("//boards.4chan.org/" + g.BOARD + "/res/" + thread + "#" + postNumber); } } - if (conf['Persistent QR'] || qr.cooldown.auto) { + if (Conf['Persistent QR'] || QR.cooldown.auto) { reply.rm(); } else { - qr.close(); + QR.close(); } - if (g.REPLY && (conf['Unread Count'] || conf['Unread Favicon'])) { + if (g.REPLY && (Conf['Unread Count'] || Conf['Unread Favicon'])) { Unread.foresee.push(postNumber); } - if (g.REPLY && conf['Thread Updater'] && conf['Auto Update This']) { + if (g.REPLY && Conf['Thread Updater'] && Conf['Auto Update This']) { Updater.update(); } - qr.status(); - return qr.resetFileInput(); + QR.status(); + return QR.resetFileInput(); }, message: { send: function(data) { var host, window; if (/chrome/i.test(navigator.userAgent)) { - qr.message.receive(data); + QR.message.receive(data); return; } - data.qr = true; + data.QR = true; host = location.hostname; window = host === 'boards.4chan.org' ? $.id('iframe').contentWindow : parent; return window.postMessage(data, '*'); @@ -2003,26 +1979,26 @@ var req, _ref; req = data.req; delete data.req; - delete data.qr; + delete data.QR; switch (req) { case 'abort': - if ((_ref = qr.ajax) != null) _ref.abort(); - return qr.message.send({ + if ((_ref = QR.ajax) != null) _ref.abort(); + return QR.message.send({ req: 'status' }); case 'response': - return qr.response(data.html); + return QR.response(data.html); case 'status': - return qr.status(data); + return QR.status(data); default: - return qr.message.post(data); + return QR.message.post(data); } }, post: function(data) { var boundary, callbacks, form, i, name, opts, parts, toBin, url, val; url = data.postURL; delete data.postURL; - if (engine === 'gecko' && data.upfile) { + if ($.engine === 'gecko' && data.upfile) { if (!data.binary) { toBin = function(data, name, val) { var bb, r; @@ -2031,7 +2007,7 @@ r = new FileReader(); r.onload = function() { data[name] = r.result; - if (!--i) return qr.message.post(data); + if (!--i) return QR.message.post(data); }; return r.readAsBinaryString(bb.getBlob('text/plain')); }; @@ -2075,13 +2051,13 @@ } callbacks = { onload: function() { - return qr.message.send({ + return QR.message.send({ req: 'response', html: this.response }); }, onerror: function() { - return qr.message.send({ + return QR.message.send({ req: 'status', ready: true, banned: true @@ -2093,13 +2069,13 @@ type: 'post', upCallbacks: { onload: function() { - return qr.message.send({ + return QR.message.send({ req: 'status', progress: '...' }); }, onprogress: function(e) { - return qr.message.send({ + return QR.message.send({ req: 'status', progress: "" + (Math.round(e.loaded / e.total * 100)) + "%" }); @@ -2111,7 +2087,7 @@ 'Content-Type': 'multipart/form-data;boundary=' + boundary }; } - return qr.ajax = $.ajax(url, callbacks, opts); + return QR.ajax = $.ajax(url, callbacks, opts); } } }; @@ -2142,7 +2118,7 @@ innerHTML: '
\ \
\ @@ -2244,7 +2220,7 @@
\
' }); - _ref = config.main; + _ref = Config.main; for (key in _ref) { obj = _ref[key]; ul = $.el('ul', { @@ -2252,7 +2228,7 @@ }); for (key in obj) { arr = obj[key]; - checked = conf[key] ? 'checked' : ''; + checked = Conf[key] ? 'checked' : ''; description = arr[1]; li = $.el('li', { innerHTML: ": " + description + "" @@ -2272,13 +2248,13 @@ _ref2 = $$('textarea', dialog); for (_i = 0, _len = _ref2.length; _i < _len; _i++) { ta = _ref2[_i]; - ta.textContent = conf[ta.name]; + ta.textContent = Conf[ta.name]; $.on(ta, 'change', $.cb.value); } - (back = $('[name=backlink]', dialog)).value = conf['backlink']; - (time = $('[name=time]', dialog)).value = conf['time']; - (fileInfoR = $('[name=fileInfoR]', dialog)).value = conf['fileInfoR']; - (fileInfoT = $('[name=fileInfoT]', dialog)).value = conf['fileInfoT']; + (back = $('[name=backlink]', dialog)).value = Conf['backlink']; + (time = $('[name=time]', dialog)).value = Conf['time']; + (fileInfoR = $('[name=fileInfoR]', dialog)).value = Conf['fileInfoR']; + (fileInfoT = $('[name=fileInfoT]', dialog)).value = Conf['fileInfoT']; $.on(back, 'keyup', $.cb.value); $.on(back, 'keyup', Options.backlink); $.on(time, 'keyup', $.cb.value); @@ -2288,17 +2264,17 @@ $.on(fileInfoT, 'keyup', $.cb.value); $.on(fileInfoT, 'keyup', Options.fileInfo); favicon = $('select', dialog); - favicon.value = conf['favicon']; + favicon.value = Conf['favicon']; $.on(favicon, 'change', $.cb.value); $.on(favicon, 'change', Options.favicon); - _ref3 = config.hotkeys; + _ref3 = Config.hotkeys; for (key in _ref3) { arr = _ref3[key]; tr = $.el('tr', { innerHTML: "" + arr[1] + "" }); input = $('input', tr); - input.value = conf[key]; + input.value = Conf[key]; $.on(input, 'keydown', Options.keybind); $.add($('#keybinds_tab + div tbody', dialog), tr); } @@ -2307,7 +2283,7 @@ for (_j = 0, _len2 = _ref4.length; _j < _len2; _j++) { indicator = _ref4[_j]; key = indicator.firstChild.textContent; - indicator.hidden = conf[key]; + indicator.hidden = Conf[key]; indicators[key] = indicator; $.on($("[name='" + key + "']", dialog), 'click', function() { return indicators[this.name].hidden = this.checked; @@ -2354,7 +2330,7 @@ return $.id('timePreview').textContent = Time.funk(Time); }, backlink: function() { - return $.id('backlinkPreview').textContent = conf['backlink'].replace(/%id/, '123456789'); + return $.id('backlinkPreview').textContent = Conf['backlink'].replace(/%id/, '123456789'); }, fileInfo: function() { var type; @@ -2451,7 +2427,7 @@ }, hide: function(thread) { var a, div, name, num, op, span, text, trip, uid, _ref, _ref2; - if (!conf['Show Stubs']) { + if (!Conf['Show Stubs']) { thread.hidden = true; thread.nextSibling.hidden = true; return; @@ -2488,16 +2464,16 @@ Updater = { init: function() { var checkbox, checked, dialog, html, input, name, title, _i, _len, _ref; - html = "
-" + conf['Interval'] + "
"; - checkbox = config.updater.checkbox; + html = "
-" + Conf['Interval'] + "
"; + checkbox = Config.updater.checkbox; for (name in checkbox) { title = checkbox[name][1]; - checked = conf[name] ? 'checked' : ''; + checked = Conf[name] ? 'checked' : ''; html += "
"; } - checked = conf['Auto Update'] ? 'checked' : ''; - html += "
"; - dialog = ui.dialog('updater', 'bottom: 0; right: 0;', html); + checked = Conf['Auto Update'] ? 'checked' : ''; + html += "
"; + dialog = UI.dialog('updater', 'bottom: 0; right: 0;', html); this.count = $('#count', dialog); this.timer = $('#timer', dialog); this.br = $('br[clear]'); @@ -2516,11 +2492,11 @@ } else if (input.name === 'Auto Update This') { $.on(input, 'click', this.cb.autoUpdate); this.cb.autoUpdate.call(input); - conf[input.name] = input.checked; + Conf[input.name] = input.checked; } } else if (input.name === 'Interval') { $.on(input, 'change', function() { - return conf['Interval'] = this.value = parseInt(this.value, 10) || conf['Interval']; + return Conf['Interval'] = this.value = parseInt(this.value, 10) || Conf['Interval']; }); $.on(input, 'change', $.cb.value); } else if (input.type === 'button') { @@ -2533,7 +2509,7 @@ }, cb: { verbose: function() { - if (conf['Verbose']) { + if (Conf['Verbose']) { Updater.count.textContent = '+0'; return Updater.timer.hidden = false; } else { @@ -2566,20 +2542,20 @@ Updater.count.className = 'warning'; clearTimeout(Updater.timeoutID); g.dead = true; - if (conf['Unread Count']) { + if (Conf['Unread Count']) { Unread.title = Unread.title.match(/^.+-/)[0] + ' 404'; } else { d.title = d.title.match(/^.+-/)[0] + ' 404'; } Unread.update(true); - qr.message.send({ + QR.message.send({ req: 'abort' }); - qr.status(); + QR.status(); return; } Updater.retryCoef = 10; - Updater.timer.textContent = '-' + conf['Interval']; + 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. @@ -2587,7 +2563,7 @@ and won't load images and scripts when parsing the response. */ if (this.status === 304) { - if (conf['Verbose']) { + if (Conf['Verbose']) { Updater.count.textContent = '+0'; Updater.count.className = null; } @@ -2605,8 +2581,8 @@ nodes.push(reply.parentNode.parentNode.parentNode); } newPosts = nodes.length; - scroll = conf['Scrolling'] && Updater.scrollBG() && newPosts && Updater.br.previousElementSibling.getBoundingClientRect().bottom - d.body.clientHeight < 25; - if (conf['Verbose']) { + scroll = Conf['Scrolling'] && Updater.scrollBG() && newPosts && Updater.br.previousElementSibling.getBoundingClientRect().bottom - d.body.clientHeight < 25; + if (Conf['Verbose']) { Updater.count.textContent = "+" + newPosts; Updater.count.className = newPosts ? 'new' : null; } @@ -2651,7 +2627,7 @@ init: function() { var favicon, html, input, inputs, _i, _len; html = '
Thread Watcher
'; - this.dialog = ui.dialog('watcher', 'top: 50px; left: 0px;', html); + this.dialog = UI.dialog('watcher', 'top: 50px; left: 0px;', html); $.add(d.body, this.dialog); inputs = $$('.op > input'); for (_i = 0, _len = inputs.length; _i < _len; _i++) { @@ -2748,7 +2724,7 @@ Anonymize = { init: function() { - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var name, node; @@ -2767,14 +2743,14 @@ var link, _i, _len, _ref; if (g.BOARD === 'f') return; this.links = []; - _ref = conf['sauces'].split('\n'); + _ref = Conf['sauces'].split('\n'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { link = _ref[_i]; if (link[0] === '#') continue; this.links.push(this.createSauceLink(link)); } if (!this.links.length) return; - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, createSauceLink: function(link) { var domain, el, href; @@ -2820,7 +2796,7 @@ RevealSpoilers = { init: function() { - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var img; @@ -2839,7 +2815,7 @@ 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); + 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]; @@ -2848,7 +2824,7 @@ hour = chanOffset + Number(hour); return new Date(year, month, day, hour, min); }; - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var node, time; @@ -2863,7 +2839,7 @@ }, foo: function() { var code; - code = conf['time'].replace(/%([A-Za-z])/g, function(s, c) { + code = Conf['time'].replace(/%([A-Za-z])/g, function(s, c) { if (c in Time.formatters) { return "' + Time.formatters." + c + "() + '"; } else { @@ -2942,7 +2918,7 @@ init: function() { if (g.BOARD === 'f') return; this.setFormats(); - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var data, link, node, regexp, resolution, size, span, unit, _, _ref; @@ -2967,7 +2943,7 @@ var code, format, funks, i, param; funks = []; for (i = 0; i <= 1; i++) { - format = i ? conf['fileInfoT'] : conf['fileInfoR']; + format = i ? Conf['fileInfoT'] : Conf['fileInfoR']; param = i ? /%([BKlMrs])/g : /%([BKlLMnNrs])/g; code = format.replace(param, function(s, c) { if (c in FileInfo.formatters) { @@ -3064,9 +3040,9 @@ QuoteBacklink = { init: function() { var format; - format = conf['backlink'].replace(/%id/g, "' + id + '"); + format = Conf['backlink'].replace(/%id/g, "' + id + '"); this.funk = Function('id', "return '" + format + "'"); - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var a, container, el, link, qid, quote, quotes, root, _i, _len, _ref; @@ -3083,12 +3059,12 @@ textContent: QuoteBacklink.funk(post.id) }); for (qid in quotes) { - if (!(el = $.id(qid)) || el.className === 'op' && !conf['OP Backlinks']) { + if (!(el = $.id(qid)) || el.className === 'op' && !Conf['OP Backlinks']) { continue; } link = a.cloneNode(true); - if (conf['Quote Preview']) $.on(link, 'mouseover', QuotePreview.mouseover); - if (conf['Quote Inline']) { + if (Conf['Quote Preview']) $.on(link, 'mouseover', QuotePreview.mouseover); + if (Conf['Quote Inline']) { $.on(link, 'click', QuoteInline.toggle); } else { link.setAttribute('onclick', "replyhl('" + post.id + "');"); @@ -3109,7 +3085,7 @@ QuoteInline = { init: function() { - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var quote, _i, _j, _len, _len2, _ref, _ref2; @@ -3152,7 +3128,7 @@ } if (/\bbacklink\b/.test(q.className)) { $.after(q.parentNode, inline); - if (conf['Forward Hiding']) { + if (Conf['Forward Hiding']) { table = $.x('ancestor::table', el); $.addClass(table, 'forwarded'); ++table.title || (table.title = 1); @@ -3178,7 +3154,7 @@ var inlined, table, _i, _len, _ref; table = $.x("following::*[@id='i" + id + "']", q); $.rm(table); - if (!conf['Forward Hiding']) return; + if (!Conf['Forward Hiding']) return; _ref = $$('.backlink.inlined', table); for (_i = 0, _len = _ref.length; _i < _len; _i++) { inlined = _ref[_i]; @@ -3227,7 +3203,7 @@ QuotePreview = { init: function() { - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var quote, _i, _j, _len, _len2, _ref, _ref2; @@ -3245,7 +3221,7 @@ mouseover: function(e) { var el, id, qp, quote, replyID, threadID, _i, _len, _ref; if (/\binlined\b/.test(this.className)) return; - qp = ui.el = $.el('div', { + qp = UI.el = $.el('div', { id: 'qp', className: 'reply dialog' }); @@ -3253,7 +3229,7 @@ id = this.hash.slice(1); if (el = $.id(id)) { qp.innerHTML = el.innerHTML; - if (conf['Quote Highlighting']) $.addClass(el, 'qphl'); + if (Conf['Quote Highlighting']) $.addClass(el, 'qphl'); if (/\bbacklink\b/.test(this.className)) { replyID = $.x('preceding-sibling::input', this.parentNode).name; _ref = $$('.quotelink', qp); @@ -3268,23 +3244,23 @@ $.cache(this.pathname, (function() { return QuotePreview.parse(this, id, threadID); })); - ui.hover(e); + UI.hover(e); } - $.on(this, 'mousemove', ui.hover); + $.on(this, 'mousemove', UI.hover); $.on(this, 'mouseout', QuotePreview.mouseout); return $.on(this, 'click', QuotePreview.mouseout); }, mouseout: function() { var el; if (el = $.id(this.hash.slice(1))) $.removeClass(el, 'qphl'); - ui.hoverend(); - $.off(this, 'mousemove', ui.hover); + UI.hoverend(); + $.off(this, 'mousemove', UI.hover); $.off(this, 'mouseout', QuotePreview.mouseout); return $.off(this, 'click', QuotePreview.mouseout); }, parse: function(req, id, threadID) { var doc, node, post, qp; - if (!((qp = ui.el) && qp.textContent === ("Loading " + id + "..."))) return; + if (!((qp = UI.el) && qp.textContent === ("Loading " + id + "..."))) return; if (req.status !== 200) { qp.textContent = "" + req.status + " " + req.statusText; return; @@ -3298,15 +3274,15 @@ filesize: $('.filesize', qp), img: $('img[md5]', qp) }; - if (conf['Image Auto-Gif']) AutoGif.node(post); - if (conf['Time Formatting']) Time.node(post); - if (conf['File Info Formatting']) return FileInfo.node(post); + if (Conf['Image Auto-Gif']) AutoGif.node(post); + if (Conf['Time Formatting']) Time.node(post); + if (Conf['File Info Formatting']) return FileInfo.node(post); } }; QuoteOP = { init: function() { - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var quote, _i, _len, _ref; @@ -3323,7 +3299,7 @@ QuoteCT = { init: function() { - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var path, quote, _i, _len, _ref; @@ -3342,7 +3318,7 @@ Quotify = { init: function() { - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var a, board, data, i, id, index, m, node, nodes, quote, quotes, snapshot, text, _i, _len, _ref; @@ -3386,7 +3362,7 @@ innerHTML: '[ ! ]', href: 'javascript:;' }); - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var a; @@ -3408,7 +3384,7 @@ ThreadStats = { init: function() { var dialog; - dialog = ui.dialog('stats', 'bottom: 0; left: 0;', '
0 / 0
'); + dialog = UI.dialog('stats', 'bottom: 0; left: 0;', '
0 / 0
'); dialog.className = 'dialog'; $.add(d.body, dialog); this.posts = this.images = 0; @@ -3424,7 +3400,7 @@ return 151; } })(); - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var imgcount; @@ -3444,7 +3420,7 @@ this.title = d.title; this.update(); $.on(window, 'scroll', Unread.scroll); - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, replies: [], foresee: [], @@ -3486,8 +3462,8 @@ var count; if (!g.REPLY) return; count = this.replies.length; - if (conf['Unread Count']) this.setTitle(count); - if (!(conf['Unread Favicon'] && (count < 2 || forceUpdate))) return; + if (Conf['Unread Count']) this.setTitle(count); + if (!(Conf['Unread Favicon'] && (count < 2 || forceUpdate))) return; Favicon.el.href = g.dead ? count ? Favicon.unreadDead : Favicon.dead : count ? Favicon.unread : Favicon["default"]; return $.add(d.head, Favicon.el); } @@ -3504,7 +3480,7 @@ return this["switch"](); }, "switch": function() { - switch (conf['favicon']) { + switch (Conf['favicon']) { case 'ferongr': this.unreadDead = 'data:unreadDead;base64,R0lGODlhEAAQAOMHAOgLAnMFAL8AAOgLAukMA/+AgP+rq////////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw=='; this.unreadSFW = 'data:unreadSFW;base64,R0lGODlhEAAQAOMHAADX8QBwfgC2zADX8QDY8nnl8qLp8v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw=='; @@ -3539,7 +3515,7 @@ }, image: function(href) { href = href.split('/'); - if (!conf['404 Redirect']) return; + if (!Conf['404 Redirect']) return; switch (href[3]) { case 'a': case 'jp': @@ -3554,7 +3530,7 @@ if (board == null) board = g.BOARD; if (id == null) id = g.THREAD_ID; if (mode == null) mode = 'thread'; - if (!(conf['404 Redirect'] || mode === 'post')) return; + if (!(Conf['404 Redirect'] || mode === 'post')) return; switch (g.BOARD) { case 'a': case 'jp': @@ -3606,41 +3582,41 @@ ImageHover = { init: function() { - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { if (!post.img) return; return $.on(post.img, 'mouseover', ImageHover.mouseover); }, mouseover: function() { - ui.el = $.el('img', { + UI.el = $.el('img', { id: 'ihover', src: this.parentNode.href }); - $.add(d.body, ui.el); - $.on(ui.el, 'load', ImageHover.load); - $.on(this, 'mousemove', ui.hover); + $.add(d.body, UI.el); + $.on(UI.el, 'load', ImageHover.load); + $.on(this, 'mousemove', UI.hover); return $.on(this, 'mouseout', ImageHover.mouseout); }, load: function() { var style; - if (this !== ui.el) return; + if (this !== UI.el) return; style = this.style; - return ui.hover({ + return UI.hover({ clientX: -45 + parseInt(style.left), clientY: 120 + parseInt(style.top) }); }, mouseout: function() { - ui.hoverend(); - $.off(this, 'mousemove', ui.hover); + UI.hoverend(); + $.off(this, 'mousemove', UI.hover); return $.off(this, 'mouseout', ImageHover.mouseout); } }; AutoGif = { init: function() { - return g.callbacks.push(this.node); + return Main.callbacks.push(this.node); }, node: function(post) { var img, src; @@ -3658,7 +3634,7 @@ ImageExpand = { init: function() { - g.callbacks.push(this.node); + Main.callbacks.push(this.node); return this.dialog(); }, node: function(post) { @@ -3683,7 +3659,7 @@ ImageExpand.on = this.checked; if (ImageExpand.on) { thumbs = $$('img[md5]'); - if (conf['Expand From Current']) { + if (Conf['Expand From Current']) { for (i = 0, _len = thumbs.length; i < _len; i++) { thumb = thumbs[i]; if (thumb.getBoundingClientRect().top > 0) break; @@ -3769,7 +3745,7 @@ url = href + '?' + Date.now(); } timeoutID = setTimeout(ImageExpand.expand, 10000, thumb, url); - if (!(engine === 'webkit' && url.split('/')[2] === 'images.4chan.org')) { + if (!($.engine === 'webkit' && url.split('/')[2] === 'images.4chan.org')) { return; } return $.ajax(url, { @@ -3804,24 +3780,23 @@ Main = { init: function() { var cutoff, hiddenThreads, id, key, now, path, pathname, temp, timestamp, val, _ref; + Main.flatten(null, Config); path = location.pathname; pathname = path.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; } - for (key in conf) { - val = conf[key]; - conf[key] = $.get(key, val); + for (key in Conf) { + val = Conf[key]; + Conf[key] = $.get(key, val); } $.on(window, 'message', Main.message); switch (location.hostname) { case 'sys.4chan.org': if (path === '/robots.txt') { - qr.message.send({ + QR.message.send({ req: 'status', ready: true }); @@ -3837,7 +3812,7 @@ return; case 'www.4chan.org': if (path === '/banned') { - qr.message.send({ + QR.message.send({ req: 'status', ready: true, banned: true @@ -3851,12 +3826,12 @@ return; } $.ready(Options.init); - if (conf['Quick Reply'] && conf['Hide Original Post Form'] && g.BOARD !== 'f') { + if (Conf['Quick Reply'] && Conf['Hide Original Post Form'] && g.BOARD !== 'f') { Main.css += 'form[name=post] { display: none; }'; } Main.addStyle(); now = Date.now(); - if (conf['Check for Updates'] && $.get('lastUpdate', 0) < now - 6 * HOUR) { + 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' @@ -3865,9 +3840,9 @@ $.set('lastUpdate', now); } g.hiddenReplies = $.get("hiddenReplies/" + g.BOARD + "/", {}); - if ($.get('lastChecked', 0) < now - 1 * DAY) { + if ($.get('lastChecked', 0) < now - 1 * $.DAY) { $.set('lastChecked', now); - cutoff = now - 7 * DAY; + cutoff = now - 7 * $.DAY; hiddenThreads = $.get("hiddenThreads/" + g.BOARD + "/", {}); for (id in hiddenThreads) { timestamp = hiddenThreads[id]; @@ -3881,23 +3856,23 @@ $.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['File Info Formatting']) FileInfo.init(); - if (conf['Sauce']) Sauce.init(); - if (conf['Reveal Spoilers']) RevealSpoilers.init(); - if (conf['Image Auto-Gif']) AutoGif.init(); - if (conf['Image Hover']) ImageHover.init(); - if (conf['Report Button']) ReportButton.init(); - if (conf['Resurrect Quotes']) Quotify.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']) QuoteCT.init(); + 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['File Info Formatting']) FileInfo.init(); + if (Conf['Sauce']) Sauce.init(); + if (Conf['Reveal Spoilers']) RevealSpoilers.init(); + if (Conf['Image Auto-Gif']) AutoGif.init(); + if (Conf['Image Hover']) ImageHover.init(); + if (Conf['Report Button']) ReportButton.init(); + if (Conf['Resurrect Quotes']) Quotify.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']) QuoteCT.init(); return $.ready(Main.ready); }, ready: function() { @@ -3907,8 +3882,8 @@ return; } if (!$.id('navtopr')) return; - $.addClass(d.body, "chanx_" + (VERSION.split('.')[1])); - $.addClass(d.body, engine); + $.addClass(d.body, "chanx_" + (Main.version.split('.')[1])); + $.addClass(d.body, $.engine); _ref = ['navtop', 'navbot']; for (_i = 0, _len = _ref.length; _i < _len; _i++) { nav = _ref[_i]; @@ -3917,49 +3892,49 @@ form = $('form[name=delform]'); Threading.thread(form.firstElementChild); Favicon.init(); - if (conf['Quick Reply']) qr.init(); - if (conf['Image Expansion']) ImageExpand.init(); - if (conf['Thread Watcher']) { + if (Conf['Quick Reply']) QR.init(); + if (Conf['Image Expansion']) ImageExpand.init(); + if (Conf['Thread Watcher']) { setTimeout(function() { return Watcher.init(); }); } - if (conf['Keybinds']) { + if (Conf['Keybinds']) { setTimeout(function() { return Keybinds.init(); }); } if (g.REPLY) { - if (conf['Thread Updater']) { + if (Conf['Thread Updater']) { setTimeout(function() { return Updater.init(); }); } - if (conf['Thread Stats']) ThreadStats.init(); - if (conf['Reply Navigation']) { + if (Conf['Thread Stats']) ThreadStats.init(); + if (Conf['Reply Navigation']) { setTimeout(function() { return Nav.init(); }); } - if (conf['Post in Title']) TitlePost.init(); - if (conf['Unread Count'] || conf['Unread Favicon']) Unread.init(); + if (Conf['Post in Title']) TitlePost.init(); + if (Conf['Unread Count'] || Conf['Unread Favicon']) Unread.init(); } else { - if (conf['Thread Hiding']) { + if (Conf['Thread Hiding']) { setTimeout(function() { return ThreadHiding.init(); }); } - if (conf['Thread Expansion']) { + if (Conf['Thread Expansion']) { setTimeout(function() { return ExpandThread.init(); }); } - if (conf['Comment Expansion']) { + if (Conf['Comment Expansion']) { setTimeout(function() { return ExpandComment.init(); }); } - if (conf['Index Navigation']) { + if (Conf['Index Navigation']) { setTimeout(function() { return Nav.init(); }); @@ -3982,6 +3957,19 @@ return $.on(form, 'DOMNodeInserted', Main.listener); } }, + flatten: function(parent, obj) { + var key, val; + if (obj instanceof Array) { + Conf[parent] = obj[0]; + } else if (typeof obj === 'object') { + for (key in obj) { + val = obj[key]; + Main.flatten(key, val); + } + } else { + Conf[parent] = obj; + } + }, addStyle: function() { $.off(d, 'DOMNodeInserted', Main.addStyle); if (d.head) { @@ -3993,12 +3981,12 @@ message: function(e) { var data, version; data = e.data; - if (data.qr) { - qr.message.receive(data); + if (data.QR) { + QR.message.receive(data); return; } version = data.version; - if (version && version !== VERSION && confirm('An updated version of 4chan X is available, would you like to install it now?')) { + if (version && version !== Main.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/" + version + "/4chan_x.user.js"; } }, @@ -4021,7 +4009,7 @@ }, node: function(nodes, notify) { var callback, node, _i, _j, _len, _len2, _ref; - _ref = g.callbacks; + _ref = Main.callbacks; for (_i = 0, _len = _ref.length; _i < _len; _i++) { callback = _ref[_i]; try { @@ -4031,7 +4019,7 @@ } } catch (err) { if (notify) { - alert("4chan X (" + VERSION + ") error: " + err.message + "\nhttp://mayhemydg.github.com/4chan-x/#bug-report\n\n" + err.stack); + alert("4chan X (" + Main.version + ") error: " + err.message + "\nhttp://mayhemydg.github.com/4chan-x/#bug-report\n\n" + err.stack); } } } @@ -4054,6 +4042,9 @@ target = e.target; if (target.nodeName === 'TABLE') return Main.node([Main.preParse(target)]); }, + namespace: '4chan_x.', + version: '2.29.1', + callbacks: [], css: '\ /* dialog styling */\ .dialog {\ diff --git a/script.coffee b/script.coffee index 84688eec2..82b0341fd 100644 --- a/script.coffee +++ b/script.coffee @@ -1,4 +1,4 @@ -config = +Config = main: Enhancing: '404 Redirect': [true, 'Redirect dead threads and images'] @@ -154,72 +154,49 @@ config = 'Auto Update': [true, 'Automatically fetch new posts'] 'Interval': 30 -# XXX Chrome can't into {log} = console -# XXX GreaseMonkey can't into console.log.bind -log = console.log.bind? console - -# flatten the config -conf = {} -(flatten = (parent, obj) -> - if obj instanceof Array - conf[parent] = obj[0] - else if typeof obj is 'object' - for key, val of obj - flatten key, val - else # string or number - conf[parent] = obj - return -) null, config - -NAMESPACE = '4chan_x.' -VERSION = '2.29.1' -SECOND = 1000 -MINUTE = 60*SECOND -HOUR = 60*MINUTE -DAY = 24*HOUR -engine = /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase() +Conf = {} d = document -g = callbacks: [] +g = {} -ui = +UI = dialog: (id, position, html) -> el = d.createElement 'div' el.className = 'reply dialog' el.innerHTML = html el.id = id - el.style.cssText = if saved = localStorage["#{NAMESPACE}#{id}.position"] then saved else position - el.querySelector('.move').addEventListener 'mousedown', ui.dragstart, false + el.style.cssText = if saved = localStorage["#{Main.namespace}#{id}.position"] then saved else position + el.querySelector('.move').addEventListener 'mousedown', UI.dragstart, false el dragstart: (e) -> #prevent text selection e.preventDefault() - ui.el = el = @parentNode - d.addEventListener 'mousemove', ui.drag, false - d.addEventListener 'mouseup', ui.dragend, false + UI.el = el = @parentNode + d.addEventListener 'mousemove', UI.drag, false + d.addEventListener 'mouseup', UI.dragend, false #distance from pointer to el edge is constant; calculate it here. # XXX opera reports el.offsetLeft / el.offsetTop as 0 rect = el.getBoundingClientRect() - ui.dx = e.clientX - rect.left - ui.dy = e.clientY - rect.top + UI.dx = e.clientX - rect.left + UI.dy = e.clientY - rect.top #factor out el from document dimensions - ui.width = d.body.clientWidth - el.offsetWidth - ui.height = d.body.clientHeight - el.offsetHeight + UI.width = d.body.clientWidth - el.offsetWidth + UI.height = d.body.clientHeight - el.offsetHeight drag: (e) -> - left = e.clientX - ui.dx - top = e.clientY - ui.dy + left = e.clientX - UI.dx + top = e.clientY - UI.dy left = if left < 10 then 0 - else if ui.width - left < 10 then null + else if UI.width - left < 10 then null else left top = if top < 10 then 0 - else if ui.height - top < 10 then null + else if UI.height - top < 10 then null else top right = if left is null then 0 else null bottom = if top is null then 0 else null #using null instead of '' is 4% faster #these 4 statements are 40% faster than 1 style.cssText - {style} = ui.el + {style} = UI.el style.top = top style.right = right style.bottom = bottom @@ -228,13 +205,13 @@ ui = #$ coffee -bpe '{a} = {b} = c' #var a, b; #a = (b = c.b, c).a; - {el} = ui - localStorage["#{NAMESPACE}#{el.id}.position"] = el.style.cssText - d.removeEventListener 'mousemove', ui.drag, false - d.removeEventListener 'mouseup', ui.dragend, false + {el} = UI + localStorage["#{Main.namespace}#{el.id}.position"] = el.style.cssText + d.removeEventListener 'mousemove', UI.drag, false + d.removeEventListener 'mouseup', UI.dragend, false hover: (e) -> {clientX, clientY} = e - {el} = ui + {el} = UI {style} = el {clientHeight, clientWidth} = d.body height = el.offsetHeight @@ -256,8 +233,8 @@ ui = style.right = clientWidth - clientX + 45 hoverend: -> - $.rm ui.el - delete ui.el + $.rm UI.el + delete UI.el ### loosely follows the jquery api: @@ -273,6 +250,14 @@ $.extend = (object, properties) -> return $.extend $, + SECOND: 1000 + MINUTE: 1000*60 + HOUR : 1000*60*60 + DAY : 1000*60*60*24 + log: + # XXX GreaseMonkey can't into console.log.bind + console.log.bind? console + engine: /WebKit|Presto|Gecko/.exec(navigator.userAgent)[0].toLowerCase() ready: (fc) -> if /interactive|complete/.test d.readyState # Execute the functions in parallel. @@ -284,7 +269,7 @@ $.extend $, $.on d, 'DOMContentLoaded', cb sync: (key, cb) -> $.on window, 'storage', (e) -> - cb JSON.parse e.newValue if e.key is "#{NAMESPACE}#{key}" + cb JSON.parse e.newValue if e.key is "#{Main.namespace}#{key}" id: (id) -> d.getElementById id ajax: (url, callbacks, opts={}) -> @@ -312,10 +297,10 @@ $.extend $, cb: checked: -> $.set @name, @checked - conf[@name] = @checked + Conf[@name] = @checked value: -> $.set @name, @value.trim() - conf[@name] = @value + Conf[@name] = @value addStyle: (css) -> style = $.el 'style', textContent: css @@ -423,29 +408,29 @@ $.cache.requests = {} $.extend $, if GM_deleteValue? delete: (name) -> - name = NAMESPACE + name + name = Main.namespace + name GM_deleteValue name get: (name, defaultValue) -> - name = NAMESPACE + name + name = Main.namespace + name if value = GM_getValue name JSON.parse value else defaultValue set: (name, value) -> - name = NAMESPACE + name + name = Main.namespace + name # for `storage` events localStorage.setItem name, JSON.stringify value GM_setValue name, JSON.stringify value else delete: (name) -> - localStorage.removeItem NAMESPACE + name + localStorage.removeItem Main.namespace + name get: (name, defaultValue) -> - if value = localStorage.getItem NAMESPACE + name + if value = localStorage.getItem Main.namespace + name JSON.parse value else defaultValue set: (name, value) -> - localStorage.setItem NAMESPACE + name, JSON.stringify value + localStorage.setItem Main.namespace + name, JSON.stringify value $$ = (selector, root=d.body) -> Array::slice.call root.querySelectorAll selector @@ -453,9 +438,9 @@ $$ = (selector, root=d.body) -> Filter = filters: {} init: -> - for key of config.filter + for key of Config.filter @filters[key] = [] - for filter in conf[key].split '\n' + for filter in Conf[key].split '\n' continue if filter[0] is '#' unless regexp = filter.match /\/(.+)\/(\w*)/ @@ -504,7 +489,7 @@ Filter = delete @filters[key] if Object.keys(@filters).length - g.callbacks.push @node + Main.callbacks.push @node createFilter: (regexp, op, hl, top) -> test = @@ -610,13 +595,13 @@ Filter = StrikethroughQuotes = init: -> - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> return if post.isInlined for quote in post.quotes if (el = $.id quote.hash[1..]) and el.parentNode.parentNode.parentNode.hidden $.addClass quote, 'filtered' - ReplyHiding.hide post.root if conf['Recursive Filtering'] + ReplyHiding.hide post.root if Conf['Recursive Filtering'] return ExpandComment = @@ -655,15 +640,15 @@ ExpandComment = threadId: threadID quotes: quotes backlinks: [] - if conf['Resurrect Quotes'] + if Conf['Resurrect Quotes'] Quotify.node post - if conf['Quote Preview'] + if Conf['Quote Preview'] QuotePreview.node post - if conf['Quote Inline'] + if Conf['Quote Inline'] QuoteInline.node post - if conf['Indicate OP quote'] + if Conf['Indicate OP quote'] QuoteOP.node post - if conf['Indicate Cross-thread Quotes'] + if Conf['Indicate Cross-thread Quotes'] QuoteCT.node post $.replace a.parentNode.parentNode, node.lastChild @@ -747,7 +732,7 @@ ReplyHiding = noWrap: true className: 'replyhider' innerHTML: '[ - ]' - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> return if post.class @@ -781,7 +766,7 @@ ReplyHiding = table.hidden = true - return unless conf['Show Stubs'] + return unless Conf['Show Stubs'] name = $('.commentpostername', table).textContent uid = $('.posteruid', table)?.textContent or '' @@ -806,20 +791,20 @@ Keybinds = thread = Nav.getThread() switch key # QR & Options - when conf.openQR + when Conf.openQR Keybinds.qr thread, true - when conf.openEmptyQR + when Conf.openEmptyQR Keybinds.qr thread - when conf.openOptions + when Conf.openOptions Options.dialog() unless $.id 'overlay' - when conf.close + when Conf.close if o = $.id 'overlay' Options.close.call o - else if qr.el - qr.close() - when conf.submit - qr.submit() if qr.el and !qr.status() - when conf.spoiler + else if QR.el + QR.close() + when Conf.submit + QR.submit() if QR.el and !QR.status() + when Conf.spoiler ta = e.target return if ta.nodeName isnt 'TEXTAREA' @@ -836,44 +821,44 @@ Keybinds = # Move the caret to the end of the selection. ta.setSelectionRange range, range # Thread related - when conf.watch + when Conf.watch Watcher.toggle thread - when conf.update + when Conf.update Updater.update() - when conf.unreadCountTo0 + when Conf.unreadCountTo0 Unread.replies = [] Unread.update() # Images - when conf.expandImage + when Conf.expandImage Keybinds.img thread - when conf.expandAllImages + when Conf.expandAllImages Keybinds.img thread, true # Board Navigation - when conf.zero + when Conf.zero window.location = "/#{g.BOARD}/0#0" - when conf.nextPage + when Conf.nextPage $('input[value=Next]')?.click() - when conf.previousPage + when Conf.previousPage $('input[value=Previous]')?.click() # Thread Navigation - when conf.nextThread + when Conf.nextThread return if g.REPLY Nav.scroll +1 - when conf.previousThread + when Conf.previousThread return if g.REPLY Nav.scroll -1 - when conf.expandThread + when Conf.expandThread ExpandThread.toggle thread - when conf.openThread + when Conf.openThread Keybinds.open thread - when conf.openThreadTab + when Conf.openThreadTab Keybinds.open thread, true # Reply Navigation - when conf.nextReply + when Conf.nextReply Keybinds.hl +1, thread - when conf.previousReply + when Conf.previousReply Keybinds.hl -1, thread - when conf.hide + when Conf.hide ThreadHiding.toggle thread if /\bthread\b/.test thread.className else return @@ -912,10 +897,10 @@ Keybinds = qr: (thread, quote) -> if quote - qr.quote.call $ '.quotejs + .quotejs', $('.replyhl', thread) or thread + QR.quote.call $ '.quotejs + .quotejs', $('.replyhl', thread) or thread else - qr.open() - $('textarea', qr.el).focus() + QR.open() + $('textarea', QR.el).focus() open: (thread, tab) -> id = thread.firstChild.id @@ -1014,25 +999,25 @@ Nav = {top} = Nav.threads[i]?.getBoundingClientRect() window.scrollBy 0, top -qr = +QR = init: -> return unless $.id 'recaptcha_challenge_field_holder' - g.callbacks.push @node + Main.callbacks.push @node setTimeout @asyncInit asyncInit: -> - if conf['Hide Original Post Form'] + if Conf['Hide Original Post Form'] link = $.el 'h1', innerHTML: "#{if g.REPLY then 'Quick Reply' else 'New Thread'}" $.on $('a', link), 'click', -> - qr.open() - $('select', qr.el).value = 'new' unless g.REPLY - $('textarea', qr.el).focus() + QR.open() + $('select', QR.el).value = 'new' unless g.REPLY + $('textarea', QR.el).focus() form = d.forms[0] $.before form, link # CORS is ignored for content script on Chrome, but not Safari/Oprah/Firefox. if /chrome/i.test navigator.userAgent - qr.status ready: true + QR.status ready: true else iframe = $.el 'iframe', id: 'iframe' @@ -1040,7 +1025,7 @@ qr = $.on iframe, 'error', -> @src = @src # Greasemonkey ghetto fix loadChecking = (iframe) -> - unless qr.status.ready + unless QR.status.ready iframe.src = 'about:blank' setTimeout (-> iframe.src = 'https://sys.4chan.org/robots.txt'), 100 $.on iframe, 'load', -> if @src isnt 'about:blank' then setTimeout loadChecking, 500, @ @@ -1051,79 +1036,79 @@ qr = $.add d.head, script $.rm script - if conf['Persistent QR'] - qr.dialog() - qr.hide() if conf['Auto Hide QR'] - $.on d, 'dragover', qr.dragOver - $.on d, 'drop', qr.dropFile - $.on d, 'dragstart', qr.drag - $.on d, 'dragend', qr.drag + if Conf['Persistent QR'] + QR.dialog() + QR.hide() if Conf['Auto Hide QR'] + $.on d, 'dragover', QR.dragOver + $.on d, 'drop', QR.dropFile + $.on d, 'dragstart', QR.drag + $.on d, 'dragend', QR.drag node: (post) -> - $.on $('.quotejs + .quotejs', post.el), 'click', qr.quote + $.on $('.quotejs + .quotejs', post.el), 'click', QR.quote open: -> - if qr.el - qr.el.hidden = false - qr.unhide() + if QR.el + QR.el.hidden = false + QR.unhide() else - qr.dialog() + QR.dialog() close: -> - qr.el.hidden = true - qr.message.send req: 'abort' + QR.el.hidden = true + QR.message.send req: 'abort' d.activeElement.blur() - $.removeClass qr.el, 'dump' - for i in qr.replies - qr.replies[0].rm() - qr.cooldown.auto = false - qr.status() - qr.resetFileInput() - if not conf['Remember Spoiler'] and (spoiler = $.id 'spoiler').checked + $.removeClass QR.el, 'dump' + for i in QR.replies + QR.replies[0].rm() + QR.cooldown.auto = false + QR.status() + QR.resetFileInput() + if not Conf['Remember Spoiler'] and (spoiler = $.id 'spoiler').checked spoiler.click() - qr.cleanError() + QR.cleanError() hide: -> d.activeElement.blur() - $.addClass qr.el, 'autohide' + $.addClass QR.el, 'autohide' $.id('autohide').checked = true unhide: -> - $.removeClass qr.el, 'autohide' + $.removeClass QR.el, 'autohide' $.id('autohide').checked = false toggleHide: -> - @checked and qr.hide() or qr.unhide() + @checked and QR.hide() or QR.unhide() error: (err, node) -> - el = $ '.warning', qr.el + el = $ '.warning', QR.el el.textContent = err $.replace el.firstChild, node if node - qr.open() + QR.open() if /captcha|verification/i.test err # Focus the captcha input on captcha error. - $('[autocomplete]', qr.el).focus() + $('[autocomplete]', QR.el).focus() alert err if d.hidden or d.oHidden or d.mozHidden or d.webkitHidden cleanError: -> - $('.warning', qr.el).textContent = null + $('.warning', QR.el).textContent = null status: (data={}) -> if data.ready - qr.status.ready = true - qr.status.banned = data.banned - else unless qr.status.ready + QR.status.ready = true + QR.status.banned = data.banned + else unless QR.status.ready value = 'Loading' disabled = true if g.dead value = 404 disabled = true - qr.cooldown.auto = false - else if qr.status.banned + QR.cooldown.auto = false + else if QR.status.banned value = 'Banned' disabled = true else # do not cancel `value = 'Loading'` once the cooldown is over - value = qr.cooldown.seconds or data.progress or value - return unless qr.el - {input} = qr.status + value = QR.cooldown.seconds or data.progress or value + return unless QR.el + {input} = QR.status input.value = - if qr.cooldown.auto and conf['Cooldown'] + if QR.cooldown.auto and Conf['Cooldown'] if value then "Auto #{value}" else 'Auto' else value or 'Submit' @@ -1131,30 +1116,30 @@ qr = cooldown: init: -> - return unless conf['Cooldown'] - qr.cooldown.start $.get "/#{g.BOARD}/cooldown", 0 - $.sync "/#{g.BOARD}/cooldown", qr.cooldown.start + return unless Conf['Cooldown'] + QR.cooldown.start $.get "/#{g.BOARD}/cooldown", 0 + $.sync "/#{g.BOARD}/cooldown", QR.cooldown.start start: (timeout) -> seconds = Math.floor (timeout - Date.now()) / 1000 - qr.cooldown.count seconds + QR.cooldown.count seconds set: (seconds) -> - return unless conf['Cooldown'] - qr.cooldown.count seconds - $.set "/#{g.BOARD}/cooldown", Date.now() + seconds*SECOND + return unless Conf['Cooldown'] + QR.cooldown.count seconds + $.set "/#{g.BOARD}/cooldown", Date.now() + seconds*$.SECOND count: (seconds) -> return unless 0 <= seconds <= 60 - setTimeout qr.cooldown.count, 1000, seconds-1 - qr.cooldown.seconds = seconds + setTimeout QR.cooldown.count, 1000, seconds-1 + QR.cooldown.seconds = seconds if seconds is 0 $.delete "/#{g.BOARD}/cooldown" - qr.submit() if qr.cooldown.auto - qr.status() + QR.submit() if QR.cooldown.auto + QR.status() quote: (e) -> e?.preventDefault() - qr.open() + QR.open() unless g.REPLY - $('select', qr.el).value = $.x('ancestor::div[@class="thread"]', @).firstChild.id + $('select', QR.el).value = $.x('ancestor::div[@class="thread"]', @).firstChild.id # Make sure we get the correct number, even with XXX censors id = @previousElementSibling.hash[1..] @@ -1165,12 +1150,12 @@ qr = s = s.replace /\n/g, '\n>' text += ">#{s}\n" - ta = $ 'textarea', qr.el + ta = $ 'textarea', QR.el caretPos = ta.selectionStart # Replace selection for text. # onchange event isn't triggered, save value. - qr.selected.el.lastChild.textContent = - qr.selected.com = + QR.selected.el.lastChild.textContent = + QR.selected.com = ta.value = ta.value[...caretPos] + text + ta.value[ta.selectionEnd..] ta.focus() @@ -1182,8 +1167,8 @@ qr = drag: (e) -> # Let it drag anything from the page. i = if e.type is 'dragstart' then 'off' else 'on' - $[i] d, 'dragover', qr.dragOver - $[i] d, 'drop', qr.dropFile + $[i] d, 'dragover', QR.dragOver + $[i] d, 'drop', QR.dropFile dragOver: (e) -> e.preventDefault() e.dataTransfer.dropEffect = 'copy' # cursor feedback @@ -1191,51 +1176,51 @@ qr = # Let it only handle files from the desktop. return unless e.dataTransfer.files.length e.preventDefault() - qr.open() - qr.fileInput.call e.dataTransfer - $.addClass qr.el, 'dump' + QR.open() + QR.fileInput.call e.dataTransfer + $.addClass QR.el, 'dump' fileInput: -> - qr.cleanError() + QR.cleanError() # Set or change current reply's file. if @files.length is 1 file = @files[0] if file.size > @max - qr.error 'File too large.' - qr.resetFileInput() - else if -1 is qr.mimeTypes.indexOf file.type - qr.error 'Unsupported file type.' - qr.resetFileInput() + QR.error 'File too large.' + QR.resetFileInput() + else if -1 is QR.mimeTypes.indexOf file.type + QR.error 'Unsupported file type.' + QR.resetFileInput() else - qr.selected.setFile file + QR.selected.setFile file return # Create new replies with these files. for file in @files if file.size > @max - qr.error "File #{file.name} is too large." + QR.error "File #{file.name} is too large." break - else if -1 is qr.mimeTypes.indexOf file.type - qr.error "#{file.name}: Unsupported file type." + else if -1 is QR.mimeTypes.indexOf file.type + QR.error "#{file.name}: Unsupported file type." break - unless qr.replies[qr.replies.length - 1].file + unless QR.replies[QR.replies.length - 1].file # set last reply's file - qr.replies[qr.replies.length - 1].setFile file + QR.replies[QR.replies.length - 1].setFile file else - new qr.reply().setFile file - $.addClass qr.el, 'dump' - qr.resetFileInput() # reset input + new QR.reply().setFile file + $.addClass QR.el, 'dump' + QR.resetFileInput() # reset input resetFileInput: -> - $('[type=file]', qr.el).value = null + $('[type=file]', QR.el).value = null replies: [] reply: class constructor: -> # set values, or null, to avoid 'undefined' values in inputs - prev = qr.replies[qr.replies.length-1] - persona = $.get 'qr.persona', {} + prev = QR.replies[QR.replies.length-1] + persona = $.get 'QR.persona', {} @name = if prev then prev.name else persona.name or null @email = if prev and !/^sage$/.test prev.email then prev.email else persona.email or null - @sub = if prev and conf['Remember Subject'] then prev.sub else if conf['Remember Subject'] then persona.sub else null - @spoiler = if prev and conf['Remember Spoiler'] then prev.spoiler else false + @sub = if prev and Conf['Remember Subject'] then prev.sub else if Conf['Remember Subject'] then persona.sub else null + @spoiler = if prev and Conf['Remember Spoiler'] then prev.spoiler else false @com = null @el = $.el 'a', @@ -1252,7 +1237,7 @@ qr = $.on $('input', @el), 'change', (e) => @spoiler = e.target.checked $.id('spoiler').checked = @spoiler if @el.id is 'selected' - $.before $('#addReply', qr.el), @el + $.before $('#addReply', QR.el), @el $.on @el, 'dragstart', @dragStart $.on @el, 'dragenter', @dragEnter @@ -1261,10 +1246,10 @@ qr = $.on @el, 'dragend', @dragEnd $.on @el, 'drop', @drop - qr.replies.push @ + QR.replies.push @ setFile: (@file) -> @el.title = file.name - $('label', @el).hidden = false if qr.spoiler + $('label', @el).hidden = false if QR.spoiler if file.type is 'application/pdf' @el.style.backgroundImage = null return @@ -1312,15 +1297,15 @@ qr = img.src = fileUrl rmFile: -> - qr.resetFileInput() + QR.resetFileInput() delete @file @el.title = null @el.style.backgroundImage = null - $('label', @el).hidden = true if qr.spoiler + $('label', @el).hidden = true if QR.spoiler (window.URL or window.webkitURL).revokeObjectURL @url select: -> - qr.selected?.el.id = null - qr.selected = @ + QR.selected?.el.id = null + QR.selected = @ @el.id = 'selected' # Scroll the list to center the focused reply. rectEl = @el.getBoundingClientRect() @@ -1328,8 +1313,8 @@ qr = @el.parentNode.scrollLeft += rectEl.left + rectEl.width/2 - rectList.left - rectList.width/2 # Load this reply's values. for data in ['name', 'email', 'sub', 'com'] - $("[name=#{data}]", qr.el).value = @[data] - $('#spoiler', qr.el).checked = @spoiler + $("[name=#{data}]", QR.el).value = @[data] + $('#spoiler', QR.el).checked = @spoiler dragStart: -> $.addClass @, 'drag' dragEnter: -> @@ -1348,28 +1333,28 @@ qr = $.after @, el else $.before @, el - reply = qr.replies.splice(oldIndex, 1)[0] - qr.replies.splice newIndex, 0, reply + reply = QR.replies.splice(oldIndex, 1)[0] + QR.replies.splice newIndex, 0, reply dragEnd: -> $.removeClass @, 'drag' if el = $ '.over', @parentNode $.removeClass el, 'over' rm: -> - qr.resetFileInput() + QR.resetFileInput() $.rm @el - index = qr.replies.indexOf @ - if qr.replies.length is 1 - new qr.reply().select() + index = QR.replies.indexOf @ + if QR.replies.length is 1 + new QR.reply().select() else if @el.id is 'selected' - (qr.replies[index-1] or qr.replies[index+1]).select() - qr.replies.splice index, 1 + (QR.replies[index-1] or QR.replies[index+1]).select() + QR.replies.splice index, 1 (window.URL or window.webkitURL).revokeObjectURL @url delete @ captcha: init: -> - @img = $ '.captcha > img', qr.el - @input = $ '[autocomplete]', qr.el + @img = $ '.captcha > img', QR.el + @input = $ '[autocomplete]', QR.el @challenge = $.id 'recaptcha_challenge_field_holder' $.on @img.parentNode, 'click', @reload $.on @input, 'keydown', @keydown @@ -1393,7 +1378,7 @@ qr = @reload() load: -> # Timeout is available at RecaptchaState.timeout in seconds. - @timeout = Date.now() + 26*MINUTE + @timeout = Date.now() + 26*$.MINUTE challenge = @challenge.firstChild.value @img.alt = challenge @img.src = "http://www.google.com/recaptcha/api/image?c=#{challenge}" @@ -1410,9 +1395,9 @@ qr = reload: (focus) -> window.location = 'javascript:Recaptcha.reload()' # Focus if we meant to. - qr.captcha.input.focus() if focus + QR.captcha.input.focus() if focus keydown: (e) -> - c = qr.captcha + c = QR.captcha if e.keyCode is 8 and not c.input.value c.reload() else if e.keyCode is 13 and e.shiftKey @@ -1422,7 +1407,7 @@ qr = e.preventDefault() dialog: -> - qr.el = ui.dialog 'qr', 'top:0;right:0;', ' + QR.el = UI.dialog 'qr', 'top:0;right:0;', '
Quick Reply × @@ -1438,10 +1423,10 @@ qr =
' - if conf['Remember QR size'] and engine is 'gecko' - $.on ta = $('textarea', qr.el), 'mouseup', -> - $.set 'qr.size', @style.cssText - ta.style.cssText = $.get 'qr.size', '' + if Conf['Remember QR size'] and $.engine is 'gecko' + $.on ta = $('textarea', QR.el), 'mouseup', -> + $.set 'QR.size', @style.cssText + ta.style.cssText = $.get 'QR.size', '' # Allow only this board's supported files. mimeTypes = $('.rules').firstChild.textContent.match(/: (.+) /)[1].toLowerCase().replace /\w+/g, (type) -> @@ -1452,78 +1437,78 @@ qr = 'application/pdf' else "image/#{type}" - qr.mimeTypes = mimeTypes.split ', ' + QR.mimeTypes = mimeTypes.split ', ' # Add empty mimeType to avoid errors with URLs selected in Window's file dialog. - qr.mimeTypes.push '' - fileInput = $ '[type=file]', qr.el + QR.mimeTypes.push '' + fileInput = $ '[type=file]', QR.el fileInput.max = $('[name=MAX_FILE_SIZE]').value fileInput.accept = mimeTypes - qr.spoiler = !!$ '#com_submit + label' - spoiler = $ '#spoilerLabel', qr.el - spoiler.hidden = !qr.spoiler + QR.spoiler = !!$ '#com_submit + label' + spoiler = $ '#spoilerLabel', QR.el + spoiler.hidden = !QR.spoiler unless g.REPLY # Make a list with visible threads and an option to create a new one. threads = '' for thread in $$ '.op' threads += "" - $.prepend $('.move > span', qr.el), $.el 'select' + $.prepend $('.move > span', QR.el), $.el 'select' innerHTML: threads title: 'Create a new thread / Reply to a thread' - $.on $('select', qr.el), 'mousedown', (e) -> e.stopPropagation() - $.on $('#autohide', qr.el), 'change', qr.toggleHide - $.on $('.close', qr.el), 'click', qr.close - $.on $('#dump', qr.el), 'click', -> qr.el.classList.toggle 'dump' - $.on $('#addReply', qr.el), 'click', -> new qr.reply().select() - $.on $('form', qr.el), 'submit', qr.submit - $.on $('textarea', qr.el), 'keyup', -> qr.selected.el.lastChild.textContent = @value - $.on fileInput, 'change', qr.fileInput - $.on fileInput, 'click', (e) -> if e.shiftKey then qr.selected.rmFile() or e.preventDefault() - $.on spoiler.firstChild, 'change', -> $('input', qr.selected.el).click() - $.on $('.warning', qr.el), 'click', qr.cleanError + $.on $('select', QR.el), 'mousedown', (e) -> e.stopPropagation() + $.on $('#autohide', QR.el), 'change', QR.toggleHide + $.on $('.close', QR.el), 'click', QR.close + $.on $('#dump', QR.el), 'click', -> QR.el.classList.toggle 'dump' + $.on $('#addReply', QR.el), 'click', -> new QR.reply().select() + $.on $('form', QR.el), 'submit', QR.submit + $.on $('textarea', QR.el), 'keyup', -> QR.selected.el.lastChild.textContent = @value + $.on fileInput, 'change', QR.fileInput + $.on fileInput, 'click', (e) -> if e.shiftKey then QR.selected.rmFile() or e.preventDefault() + $.on spoiler.firstChild, 'change', -> $('input', QR.selected.el).click() + $.on $('.warning', QR.el), 'click', QR.cleanError - new qr.reply().select() + new QR.reply().select() # save selected reply's data for name in ['name', 'email', 'sub', 'com'] - input = $ "[name=#{name}]", qr.el + input = $ "[name=#{name}]", QR.el for event in ['input', 'keyup', 'change', 'paste'] # The input event replaces keyup, change and paste events. # Firefox 12 will support the input event. # Oprah? $.on input, event, -> - qr.selected[@name] = @value + QR.selected[@name] = @value # Disable auto-posting if you're typing in the first reply # during the last 5 seconds of the cooldown. - if qr.cooldown.auto and qr.selected is qr.replies[0] and parseInt(qr.status.input.value.match /\d+/) < 6 - qr.cooldown.auto = false + if QR.cooldown.auto and QR.selected is QR.replies[0] and parseInt(QR.status.input.value.match /\d+/) < 6 + QR.cooldown.auto = false # sync between tabs - $.sync 'qr.persona', (persona) -> - return unless qr.el.hidden + $.sync 'QR.persona', (persona) -> + return unless QR.el.hidden for key, val of persona - qr.selected[key] = val - $("[name=#{key}]", qr.el).value = val + QR.selected[key] = val + $("[name=#{key}]", QR.el).value = val - qr.status.input = $ '[type=submit]', qr.el - qr.status() - qr.cooldown.init() - qr.captcha.init() - $.add d.body, qr.el + QR.status.input = $ '[type=submit]', QR.el + QR.status() + QR.cooldown.init() + QR.captcha.init() + $.add d.body, QR.el # Create a custom event when the QR dialog is first initialized. # Use it to extend the QR's functionalities, or for XTRM RICE. e = d.createEvent 'CustomEvent' e.initEvent 'QRDialogCreation', true, false - qr.el.dispatchEvent e + QR.el.dispatchEvent e submit: (e) -> e?.preventDefault() - if qr.cooldown.seconds - qr.cooldown.auto = !qr.cooldown.auto - qr.status() + if QR.cooldown.seconds + QR.cooldown.auto = !QR.cooldown.auto + QR.status() return - qr.message.send req: 'abort' - reply = qr.replies[0] + QR.message.send req: 'abort' + reply = QR.replies[0] # prevent errors unless reply.com or reply.file @@ -1538,28 +1523,28 @@ qr = challenge = captcha.challenge response = captcha.response else - challenge = qr.captcha.img.alt - if response = qr.captcha.input.value then qr.captcha.reload() + challenge = QR.captcha.img.alt + if response = QR.captcha.input.value then QR.captcha.reload() $.set 'captchas', captchas - qr.captcha.count captchas.length + QR.captcha.count captchas.length unless response err = 'No valid captcha.' if err # stop auto-posting - qr.cooldown.auto = false - qr.status() - qr.error err + QR.cooldown.auto = false + QR.status() + QR.error err return - qr.cleanError() + QR.cleanError() - threadID = g.THREAD_ID or $('select', qr.el).value + threadID = g.THREAD_ID or $('select', QR.el).value # Enable auto-posting if we have stuff to post, disable it otherwise. - qr.cooldown.auto = qr.replies.length > 1 - if conf['Auto Hide QR'] and not qr.cooldown.auto - qr.hide() - if conf['Thread Watcher'] and conf['Auto Watch Reply'] and threadID isnt 'new' + QR.cooldown.auto = QR.replies.length > 1 + if Conf['Auto Hide QR'] and not QR.cooldown.auto + QR.hide() + if Conf['Thread Watcher'] and Conf['Auto Watch Reply'] and threadID isnt 'new' Watcher.watch threadID post = @@ -1578,9 +1563,9 @@ qr = # Starting to upload might take some time. # Provide some feedback that we're starting to submit. - qr.status progress: '...' + QR.status progress: '...' - if engine is 'gecko' and reply.file + if $.engine is 'gecko' and reply.file # https://bugzilla.mozilla.org/show_bug.cgi?id=673742 # We plan to allow postMessaging Files and FileLists across origins, # that just needs a more in depth security review. @@ -1591,21 +1576,21 @@ qr = file.name = reply.file.name file.type = reply.file.type post.upfile = file - qr.message.send post + QR.message.send post reader.readAsBinaryString reply.file return # CORS is ignored for content script on Chrome, but not Safari/Oprah/Firefox. if /chrome/i.test navigator.userAgent - qr.message.post post + QR.message.post post return - qr.message.send post + QR.message.send post response: (html) -> doc = $.el 'a', innerHTML: html # Check for ban. if $('title', doc).textContent is '4chan - Banned' - qr.status ready: true, banned: true + QR.status ready: true, banned: true return unless b = $ 'td b', doc err = 'Connection error with sys.4chan.org.' @@ -1618,58 +1603,58 @@ qr = if err if /captcha|verification/i.test(err) or err is 'Connection error with sys.4chan.org.' # Enable auto-post if we have some cached captchas. - qr.cooldown.auto = !!$.get('captchas', []).length + QR.cooldown.auto = !!$.get('captchas', []).length # Too many frequent mistyped captchas will auto-ban you! # On connection error, the post most likely didn't go through. - qr.cooldown.set 2 + QR.cooldown.set 2 else # stop auto-posting - qr.cooldown.auto = false - qr.status() - qr.error err, node + QR.cooldown.auto = false + QR.status() + QR.error err, node return - reply = qr.replies[0] + reply = QR.replies[0] - persona = $.get 'qr.persona', {} + persona = $.get 'QR.persona', {} persona = name: reply.name email: if /^sage$/.test reply.email then persona.email else reply.email - sub: if conf['Remember Subject'] then reply.sub else null - $.set 'qr.persona', persona + sub: if Conf['Remember Subject'] then reply.sub else null + $.set 'QR.persona', persona [_, thread, postNumber] = b.lastChild.textContent.match /thread:(\d+),no:(\d+)/ if thread is '0' # new thread - if conf['Thread Watcher'] and conf['Auto Watch'] + if Conf['Thread Watcher'] and Conf['Auto Watch'] $.set 'autoWatch', postNumber # auto-noko location.pathname = "/#{g.BOARD}/res/#{postNumber}" else # Enable auto-posting if we have stuff to post, disable it otherwise. - qr.cooldown.auto = qr.replies.length > 1 - qr.cooldown.set if /sage/i.test reply.email then 60 else 30 - if conf['Open Reply in New Tab'] && !g.REPLY && !qr.cooldown.auto + QR.cooldown.auto = QR.replies.length > 1 + QR.cooldown.set if /sage/i.test reply.email then 60 else 30 + if Conf['Open Reply in New Tab'] && !g.REPLY && !QR.cooldown.auto $.open "//boards.4chan.org/#{g.BOARD}/res/#{thread}##{postNumber}" - if conf['Persistent QR'] or qr.cooldown.auto + if Conf['Persistent QR'] or QR.cooldown.auto reply.rm() else - qr.close() + QR.close() - if g.REPLY and (conf['Unread Count'] or conf['Unread Favicon']) + if g.REPLY and (Conf['Unread Count'] or Conf['Unread Favicon']) Unread.foresee.push postNumber - if g.REPLY and conf['Thread Updater'] and conf['Auto Update This'] + if g.REPLY and Conf['Thread Updater'] and Conf['Auto Update This'] Updater.update() - qr.status() - qr.resetFileInput() + QR.status() + QR.resetFileInput() message: send: (data) -> # CORS is ignored for content script on Chrome, but not Safari/Oprah/Firefox. if /chrome/i.test navigator.userAgent - qr.message.receive data + QR.message.receive data return - data.qr = true + data.QR = true host = location.hostname window = if host is 'boards.4chan.org' @@ -1680,17 +1665,17 @@ qr = receive: (data) -> req = data.req delete data.req - delete data.qr + delete data.QR switch req when 'abort' - qr.ajax?.abort() - qr.message.send req: 'status' + QR.ajax?.abort() + QR.message.send req: 'status' when 'response' # xhr response - qr.response data.html + QR.response data.html when 'status' - qr.status data + QR.status data else - qr.message.post data # Reply object: we're posting + QR.message.post data # Reply object: we're posting post: (data) -> @@ -1699,7 +1684,7 @@ qr = delete data.postURL # File with filename upload fix from desuwa - if engine is 'gecko' and data.upfile + if $.engine is 'gecko' and data.upfile # All of this is fucking retarded. unless data.binary toBin = (data, name, val) -> @@ -1709,7 +1694,7 @@ qr = r.onload = -> data[name] = r.result unless --i - qr.message.post data + QR.message.post data r.readAsBinaryString bb.getBlob 'text/plain' i = Object.keys(data).length for name, val of data @@ -1744,29 +1729,29 @@ qr = callbacks = onload: -> - qr.message.send + QR.message.send req: 'response' html: @response onerror: -> # CORS disabled error: redirecting to banned page ;_; - qr.message.send req: 'status', ready: true, banned: true + QR.message.send req: 'status', ready: true, banned: true opts = form: form type: 'post' upCallbacks: onload: -> - qr.message.send + QR.message.send req: 'status' progress: '...' onprogress: (e) -> - qr.message.send + QR.message.send req: 'status' progress: "#{Math.round e.loaded / e.total * 100}%" if boundary opts.headers = 'Content-Type': 'multipart/form-data;boundary=' + boundary - qr.ajax = $.ajax url, callbacks, opts + QR.ajax = $.ajax url, callbacks, opts Options = init: -> @@ -1787,7 +1772,7 @@ Options = innerHTML: '
@@ -1890,11 +1875,11 @@ Options =
' #main - for key, obj of config.main + for key, obj of Config.main ul = $.el 'ul', textContent: key for key, arr of obj - checked = if conf[key] then 'checked' else '' + checked = if Conf[key] then 'checked' else '' description = arr[1] li = $.el 'li', innerHTML: ": #{description}" @@ -1911,14 +1896,14 @@ Options = #filter & sauce for ta in $$ 'textarea', dialog - ta.textContent = conf[ta.name] + ta.textContent = Conf[ta.name] $.on ta, 'change', $.cb.value #rice - (back = $ '[name=backlink]', dialog).value = conf['backlink'] - (time = $ '[name=time]', dialog).value = conf['time'] - (fileInfoR = $ '[name=fileInfoR]', dialog).value = conf['fileInfoR'] - (fileInfoT = $ '[name=fileInfoT]', dialog).value = conf['fileInfoT'] + (back = $ '[name=backlink]', dialog).value = Conf['backlink'] + (time = $ '[name=time]', dialog).value = Conf['time'] + (fileInfoR = $ '[name=fileInfoR]', dialog).value = Conf['fileInfoR'] + (fileInfoT = $ '[name=fileInfoT]', dialog).value = Conf['fileInfoT'] $.on back, 'keyup', $.cb.value $.on back, 'keyup', Options.backlink $.on time, 'keyup', $.cb.value @@ -1928,16 +1913,16 @@ Options = $.on fileInfoT, 'keyup', $.cb.value $.on fileInfoT, 'keyup', Options.fileInfo favicon = $ 'select', dialog - favicon.value = conf['favicon'] + favicon.value = Conf['favicon'] $.on favicon, 'change', $.cb.value $.on favicon, 'change', Options.favicon #keybinds - for key, arr of config.hotkeys + for key, arr of Config.hotkeys tr = $.el 'tr', innerHTML: "#{arr[1]}" input = $ 'input', tr - input.value = conf[key] + input.value = Conf[key] $.on input, 'keydown', Options.keybind $.add $('#keybinds_tab + div tbody', dialog), tr @@ -1945,7 +1930,7 @@ Options = indicators = {} for indicator in $$ '.warning', dialog key = indicator.firstChild.textContent - indicator.hidden = conf[key] + indicator.hidden = Conf[key] indicators[key] = indicator $.on $("[name='#{key}']", dialog), 'click', -> indicators[@name].hidden = @checked @@ -1986,7 +1971,7 @@ Options = Time.date = new Date() $.id('timePreview').textContent = Time.funk Time backlink: -> - $.id('backlinkPreview').textContent = conf['backlink'].replace /%id/, '123456789' + $.id('backlinkPreview').textContent = Conf['backlink'].replace /%id/, '123456789' fileInfo: -> type = if @name is 'fileInfoR' then 0 else 1 FileInfo.data = @@ -2067,7 +2052,7 @@ ThreadHiding = $.set "hiddenThreads/#{g.BOARD}/", hiddenThreads hide: (thread) -> - unless conf['Show Stubs'] + unless Conf['Show Stubs'] thread.hidden = true thread.nextSibling.hidden = true return @@ -2102,20 +2087,20 @@ ThreadHiding = Updater = init: -> - html = "
-#{conf['Interval']}
" - {checkbox} = config.updater + html = "
-#{Conf['Interval']}
" + {checkbox} = Config.updater for name of checkbox title = checkbox[name][1] - checked = if conf[name] then 'checked' else '' + checked = if Conf[name] then 'checked' else '' html += "
" - checked = if conf['Auto Update'] then 'checked' else '' + checked = if Conf['Auto Update'] then 'checked' else '' html += "
-
+
" - dialog = ui.dialog 'updater', 'bottom: 0; right: 0;', html + dialog = UI.dialog 'updater', 'bottom: 0; right: 0;', html @count = $ '#count', dialog @timer = $ '#timer', dialog @@ -2134,9 +2119,9 @@ Updater = $.on input, 'click', @cb.autoUpdate @cb.autoUpdate.call input # Required for the QR's update after posting. - conf[input.name] = input.checked + Conf[input.name] = input.checked else if input.name is 'Interval' - $.on input, 'change', -> conf['Interval'] = @value = parseInt(@value, 10) or conf['Interval'] + $.on input, 'change', -> Conf['Interval'] = @value = parseInt(@value, 10) or Conf['Interval'] $.on input, 'change', $.cb.value else if input.type is 'button' $.on input, 'click', @update @@ -2148,7 +2133,7 @@ Updater = cb: verbose: -> - if conf['Verbose'] + if Conf['Verbose'] Updater.count.textContent = '+0' Updater.timer.hidden = false else @@ -2174,17 +2159,17 @@ Updater = Updater.count.className = 'warning' clearTimeout Updater.timeoutID g.dead = true - if conf['Unread Count'] + if Conf['Unread Count'] Unread.title = Unread.title.match(/^.+-/)[0] + ' 404' else d.title = d.title.match(/^.+-/)[0] + ' 404' Unread.update true - qr.message.send req: 'abort' - qr.status() + QR.message.send req: 'abort' + QR.status() return Updater.retryCoef = 10 - Updater.timer.textContent = '-' + conf['Interval'] + Updater.timer.textContent = '-' + Conf['Interval'] ### Status Code 304: Not modified @@ -2193,7 +2178,7 @@ Updater = and won't load images and scripts when parsing the response. ### if @status is 304 - if conf['Verbose'] + if Conf['Verbose'] Updater.count.textContent = '+0' Updater.count.className = null return @@ -2209,9 +2194,9 @@ Updater = nodes.push reply.parentNode.parentNode.parentNode #table newPosts = nodes.length - scroll = conf['Scrolling'] && Updater.scrollBG() && newPosts && + scroll = Conf['Scrolling'] && Updater.scrollBG() && newPosts && Updater.br.previousElementSibling.getBoundingClientRect().bottom - d.body.clientHeight < 25 - if conf['Verbose'] + if Conf['Verbose'] Updater.count.textContent = "+#{newPosts}" Updater.count.className = if newPosts then 'new' else null @@ -2247,7 +2232,7 @@ Updater = Watcher = init: -> html = '
Thread Watcher
' - @dialog = ui.dialog 'watcher', 'top: 50px; left: 0px;', html + @dialog = UI.dialog 'watcher', 'top: 50px; left: 0px;', html $.add d.body, @dialog #add watch buttons @@ -2329,7 +2314,7 @@ Watcher = Anonymize = init: -> - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> return if post.class is 'inline' name = $ '.commentpostername, .postername', post.el @@ -2342,11 +2327,11 @@ Sauce = init: -> return if g.BOARD is 'f' @links = [] - for link in conf['sauces'].split '\n' + for link in Conf['sauces'].split '\n' continue if link[0] is '#' @links.push @createSauceLink link return unless @links.length - g.callbacks.push @node + Main.callbacks.push @node createSauceLink: (link) -> domain = link.match(/(\w+)\.\w+\//)[1] @@ -2380,7 +2365,7 @@ Sauce = RevealSpoilers = init: -> - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> {img} = post if not (img and /^Spoil/.test img.alt) or post.class is 'inline' @@ -2400,7 +2385,7 @@ Time = @parse = if Date.parse('10/11/11(Tue)18:53') is 1318351980000 - (node) -> new Date Date.parse(node.textContent) + chanOffset*HOUR + (node) -> new Date Date.parse(node.textContent) + chanOffset*$.HOUR else # Firefox and Opera do not parse 4chan's time format correctly (node) -> [_, month, day, year, hour, min] = @@ -2410,7 +2395,7 @@ Time = hour = chanOffset + Number hour new Date year, month, day, hour, min - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> return if post.class is 'inline' # .posttime exists on every board except /f/ @@ -2422,7 +2407,7 @@ Time = time.setAttribute 'datetime', Time.date.toISOString() $.replace node, time foo: -> - code = conf['time'].replace /%([A-Za-z])/g, (s, c) -> + code = Conf['time'].replace /%([A-Za-z])/g, (s, c) -> if c of Time.formatters "' + Time.formatters.#{c}() + '" else @@ -2473,7 +2458,7 @@ FileInfo = init: -> return if g.BOARD is 'f' @setFormats() - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> return if post.class is 'inline' or not node = post.filesize regexp = /^File: (<.+>)-\((?:Spoiler Image, )?([\d\.]+) (\w+), (\d+x\d+|PDF)/ @@ -2493,7 +2478,7 @@ FileInfo = setFormats: -> funks = [] for i in [0..1] - format = if i then conf['fileInfoT'] else conf['fileInfoR'] + format = if i then Conf['fileInfoT'] else Conf['fileInfoR'] param = if i then /%([BKlMrs])/g else /%([BKlLMnNrs])/g code = format.replace param, (s, c) -> if c of FileInfo.formatters @@ -2550,9 +2535,9 @@ TitlePost = QuoteBacklink = init: -> - format = conf['backlink'].replace /%id/g, "' + id + '" + format = Conf['backlink'].replace /%id/g, "' + id + '" @funk = Function 'id', "return '#{format}'" - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> return if post.isInlined quotes = {} @@ -2567,11 +2552,11 @@ QuoteBacklink = textContent: QuoteBacklink.funk post.id for qid of quotes # Don't backlink the OP. - continue if !(el = $.id qid) or el.className is 'op' and !conf['OP Backlinks'] + continue if !(el = $.id qid) or el.className is 'op' and !Conf['OP Backlinks'] link = a.cloneNode true - if conf['Quote Preview'] + if Conf['Quote Preview'] $.on link, 'mouseover', QuotePreview.mouseover - if conf['Quote Inline'] + if Conf['Quote Inline'] $.on link, 'click', QuoteInline.toggle else link.setAttribute 'onclick', "replyhl('#{post.id}');" @@ -2586,7 +2571,7 @@ QuoteBacklink = QuoteInline = init: -> - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> for quote in post.quotes continue unless quote.hash @@ -2615,7 +2600,7 @@ QuoteInline = Unread.update() if /\bbacklink\b/.test q.className $.after q.parentNode, inline - if conf['Forward Hiding'] + if Conf['Forward Hiding'] table = $.x 'ancestor::table', el $.addClass table, 'forwarded' # Will only unhide if there's no inlined backlinks of it anymore. @@ -2636,7 +2621,7 @@ QuoteInline = #select the corresponding table or loading td table = $.x "following::*[@id='i#{id}']", q $.rm table - return unless conf['Forward Hiding'] + return unless Conf['Forward Hiding'] for inlined in $$ '.backlink.inlined', table table = $.x 'ancestor::table', $.id inlined.hash[1..] $.removeClass table, 'forwarded' unless --table.title @@ -2678,7 +2663,7 @@ QuoteInline = QuotePreview = init: -> - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> for quote in post.quotes $.on quote, 'mouseover', QuotePreview.mouseover if quote.hash @@ -2687,7 +2672,7 @@ QuotePreview = return mouseover: (e) -> return if /\binlined\b/.test @className - qp = ui.el = $.el 'div', + qp = UI.el = $.el 'div', id: 'qp' className: 'reply dialog' $.add d.body, qp @@ -2695,7 +2680,7 @@ QuotePreview = id = @hash[1..] if el = $.id id qp.innerHTML = el.innerHTML - $.addClass el, 'qphl' if conf['Quote Highlighting'] + $.addClass el, 'qphl' if Conf['Quote Highlighting'] if /\bbacklink\b/.test @className replyID = $.x('preceding-sibling::input', @parentNode).name for quote in $$ '.quotelink', qp @@ -2705,19 +2690,19 @@ QuotePreview = qp.textContent = "Loading #{id}..." threadID = @pathname.split('/').pop() or $.x('ancestor::div[@class="thread"]', @).firstChild.id $.cache @pathname, (-> QuotePreview.parse @, id, threadID) - ui.hover e - $.on @, 'mousemove', ui.hover + UI.hover e + $.on @, 'mousemove', UI.hover $.on @, 'mouseout', QuotePreview.mouseout $.on @, 'click', QuotePreview.mouseout mouseout: -> if el = $.id @hash[1..] $.removeClass el, 'qphl' - ui.hoverend() - $.off @, 'mousemove', ui.hover + UI.hoverend() + $.off @, 'mousemove', UI.hover $.off @, 'mouseout', QuotePreview.mouseout $.off @, 'click', QuotePreview.mouseout parse: (req, id, threadID) -> - return unless (qp = ui.el) and qp.textContent is "Loading #{id}..." + return unless (qp = UI.el) and qp.textContent is "Loading #{id}..." if req.status isnt 200 qp.textContent = "#{req.status} #{req.statusText}" @@ -2736,16 +2721,16 @@ QuotePreview = root: qp filesize: $ '.filesize', qp img: $ 'img[md5]', qp - if conf['Image Auto-Gif'] + if Conf['Image Auto-Gif'] AutoGif.node post - if conf['Time Formatting'] + if Conf['Time Formatting'] Time.node post - if conf['File Info Formatting'] + if Conf['File Info Formatting'] FileInfo.node post QuoteOP = init: -> - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> return if post.class is 'inline' for quote in post.quotes @@ -2756,7 +2741,7 @@ QuoteOP = QuoteCT = init: -> - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> return if post.class is 'inline' for quote in post.quotes @@ -2772,7 +2757,7 @@ QuoteCT = Quotify = init: -> - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> return if post.class is 'inline' @@ -2832,7 +2817,7 @@ ReportButton = className: 'reportbutton' innerHTML: '[ ! ]' href: 'javascript:;' - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> unless a = $ '.reportbutton', post.el a = ReportButton.a.cloneNode true @@ -2846,7 +2831,7 @@ ReportButton = ThreadStats = init: -> - dialog = ui.dialog 'stats', 'bottom: 0; left: 0;', '
0 / 0
' + dialog = UI.dialog 'stats', 'bottom: 0; left: 0;', '
0 / 0
' dialog.className = 'dialog' $.add d.body, dialog @posts = @images = 0 @@ -2858,7 +2843,7 @@ ThreadStats = 501 else 151 - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> return if post.isInlined $.id('postcount').textContent = ++ThreadStats.posts @@ -2873,7 +2858,7 @@ Unread = @title = d.title @update() $.on window, 'scroll', Unread.scroll - g.callbacks.push @node + Main.callbacks.push @node replies: [] foresee: [] @@ -2912,10 +2897,10 @@ Unread = count = @replies.length - if conf['Unread Count'] + if Conf['Unread Count'] @setTitle count - unless conf['Unread Favicon'] and (count < 2 or forceUpdate) + unless Conf['Unread Favicon'] and (count < 2 or forceUpdate) return Favicon.el.href = @@ -2945,7 +2930,7 @@ Favicon = @switch() switch: -> - switch conf['favicon'] + switch Conf['favicon'] when 'ferongr' @unreadDead = 'data:unreadDead;base64,R0lGODlhEAAQAOMHAOgLAnMFAL8AAOgLAukMA/+AgP+rq////////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==' @unreadSFW = 'data:unreadSFW;base64,R0lGODlhEAAQAOMHAADX8QBwfgC2zADX8QDY8nnl8qLp8v///////////////////////////////////yH5BAEKAAcALAAAAAAQABAAAARZ8MhJ6xwDWIBv+AM1fEEIBIVRlNKYrtpIECuGzuwpCLg974EYiXUYkUItjGbC6VQ4omXFiKROA6qSy0A8nAo9GS3YCswIWnOvLAi0be23Z1QtdSUaqXcviQAAOw==' @@ -2978,12 +2963,12 @@ Redirect = image: (href) -> href = href.split '/' # Do not use g.BOARD, the image url can originate from a cross-quote. - return unless conf['404 Redirect'] + return unless Conf['404 Redirect'] switch href[3] when 'a', 'jp', 'm', 'tg', 'u', 'vg' "http://archive.foolz.us/#{href[3]}/full_image/#{href[5]}" thread: (board=g.BOARD, id=g.THREAD_ID, mode='thread') -> - return unless conf['404 Redirect'] or mode is 'post' + return unless Conf['404 Redirect'] or mode is 'post' switch g.BOARD when 'a', 'jp', 'm', 'tg', 'tv', 'u', 'v', 'vg' "http://archive.foolz.us/#{board}/thread/#{id}/" @@ -3001,33 +2986,33 @@ Redirect = ImageHover = init: -> - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> return unless post.img $.on post.img, 'mouseover', ImageHover.mouseover mouseover: -> - ui.el = $.el 'img' + UI.el = $.el 'img' id: 'ihover' src: @parentNode.href - $.add d.body, ui.el - $.on ui.el, 'load', ImageHover.load - $.on @, 'mousemove', ui.hover + $.add d.body, UI.el + $.on UI.el, 'load', ImageHover.load + $.on @, 'mousemove', UI.hover $.on @, 'mouseout', ImageHover.mouseout load: -> - return if @ isnt ui.el + return if @ isnt UI.el # 'Fake' mousemove event by giving required values. {style} = @ - ui.hover + UI.hover clientX: - 45 + parseInt style.left clientY: 120 + parseInt style.top mouseout: -> - ui.hoverend() - $.off @, 'mousemove', ui.hover + UI.hoverend() + $.off @, 'mousemove', UI.hover $.off @, 'mouseout', ImageHover.mouseout AutoGif = init: -> - g.callbacks.push @node + Main.callbacks.push @node node: (post) -> return if post.root.hidden or not post.img src = post.img.parentNode.href @@ -3040,7 +3025,7 @@ AutoGif = ImageExpand = init: -> - g.callbacks.push @node + Main.callbacks.push @node @dialog() node: (post) -> @@ -3058,7 +3043,7 @@ ImageExpand = ImageExpand.on = @checked if ImageExpand.on #expand thumbs = $$ 'img[md5]' - if conf['Expand From Current'] + if Conf['Expand From Current'] for thumb, i in thumbs if thumb.getBoundingClientRect().top > 0 break @@ -3130,7 +3115,7 @@ ImageExpand = timeoutID = setTimeout ImageExpand.expand, 10000, thumb, url # Only Chrome let userscript break through cross domain requests. # Don't check it 404s in the archivers. - return unless engine is 'webkit' and url.split('/')[2] is 'images.4chan.org' + return unless $.engine is 'webkit' and url.split('/')[2] is 'images.4chan.org' $.ajax url, onreadystatechange: (-> clearTimeout timeoutID if @status is 404), type: 'head' @@ -3155,25 +3140,25 @@ ImageExpand = Main = init: -> + Main.flatten null, Config + path = location.pathname pathname = path[1..].split '/' [g.BOARD, temp] = pathname if temp is 'res' g.REPLY = true g.THREAD_ID = pathname[2] - else - g.PAGENUM = parseInt(temp) or 0 #load values from localStorage - for key, val of conf - conf[key] = $.get key, val + for key, val of Conf + Conf[key] = $.get key, val $.on window, 'message', Main.message switch location.hostname when 'sys.4chan.org' if path is '/robots.txt' - qr.message.send req: 'status', ready: true + QR.message.send req: 'status', ready: true else if /report/.test location.search $.ready -> $.on $.id('recaptcha_response_field'), 'keydown', (e) -> @@ -3181,7 +3166,7 @@ Main = return when 'www.4chan.org' if path is '/banned' - qr.message.send req: 'status', ready: true, banned: true + QR.message.send req: 'status', ready: true, banned: true return when 'images.4chan.org' $.ready -> Redirect.init() if d.title is '4chan - 404' @@ -3189,21 +3174,21 @@ Main = $.ready Options.init - if conf['Quick Reply'] and conf['Hide Original Post Form'] and g.BOARD isnt 'f' + if Conf['Quick Reply'] and Conf['Hide Original Post Form'] and g.BOARD isnt 'f' Main.css += 'form[name=post] { display: none; }' Main.addStyle() now = Date.now() - if conf['Check for Updates'] and $.get('lastUpdate', 0) < now - 6*HOUR + if Conf['Check for Updates'] and $.get('lastUpdate', 0) < now - 6*$.HOUR $.ready -> $.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 + if $.get('lastChecked', 0) < now - 1*$.DAY $.set 'lastChecked', now - cutoff = now - 7*DAY + cutoff = now - 7*$.DAY hiddenThreads = $.get "hiddenThreads/#{g.BOARD}/", {} for id, timestamp of hiddenThreads @@ -3219,55 +3204,55 @@ Main = #major features - if conf['Filter'] + if Conf['Filter'] Filter.init() - if conf['Reply Hiding'] + if Conf['Reply Hiding'] ReplyHiding.init() - if conf['Filter'] or conf['Reply Hiding'] + if Conf['Filter'] or Conf['Reply Hiding'] StrikethroughQuotes.init() - if conf['Anonymize'] + if Conf['Anonymize'] Anonymize.init() - if conf['Time Formatting'] + if Conf['Time Formatting'] Time.init() - if conf['File Info Formatting'] + if Conf['File Info Formatting'] FileInfo.init() - if conf['Sauce'] + if Conf['Sauce'] Sauce.init() - if conf['Reveal Spoilers'] + if Conf['Reveal Spoilers'] RevealSpoilers.init() - if conf['Image Auto-Gif'] + if Conf['Image Auto-Gif'] AutoGif.init() - if conf['Image Hover'] + if Conf['Image Hover'] ImageHover.init() - if conf['Report Button'] + if Conf['Report Button'] ReportButton.init() - if conf['Resurrect Quotes'] + if Conf['Resurrect Quotes'] Quotify.init() - if conf['Quote Inline'] + if Conf['Quote Inline'] QuoteInline.init() - if conf['Quote Preview'] + if Conf['Quote Preview'] QuotePreview.init() - if conf['Quote Backlinks'] + if Conf['Quote Backlinks'] QuoteBacklink.init() - if conf['Indicate OP quote'] + if Conf['Indicate OP quote'] QuoteOP.init() - if conf['Indicate Cross-thread Quotes'] + if Conf['Indicate Cross-thread Quotes'] QuoteCT.init() $.ready Main.ready @@ -3278,8 +3263,8 @@ Main = return unless $.id 'navtopr' return - $.addClass d.body, "chanx_#{VERSION.split('.')[1]}" - $.addClass d.body, engine + $.addClass d.body, "chanx_#{Main.version.split('.')[1]}" + $.addClass d.body, $.engine for nav in ['navtop', 'navbot'] $.addClass $("a[href$='/#{g.BOARD}/']", $.id nav), 'current' form = $ 'form[name=delform]' @@ -3287,45 +3272,45 @@ Main = Favicon.init() # Major features. - if conf['Quick Reply'] - qr.init() + if Conf['Quick Reply'] + QR.init() - if conf['Image Expansion'] + if Conf['Image Expansion'] ImageExpand.init() - if conf['Thread Watcher'] + if Conf['Thread Watcher'] setTimeout -> Watcher.init() - if conf['Keybinds'] + if Conf['Keybinds'] setTimeout -> Keybinds.init() if g.REPLY - if conf['Thread Updater'] + if Conf['Thread Updater'] setTimeout -> Updater.init() - if conf['Thread Stats'] + if Conf['Thread Stats'] ThreadStats.init() - if conf['Reply Navigation'] + if Conf['Reply Navigation'] setTimeout -> Nav.init() - if conf['Post in Title'] + if Conf['Post in Title'] TitlePost.init() - if conf['Unread Count'] or conf['Unread Favicon'] + if Conf['Unread Count'] or Conf['Unread Favicon'] Unread.init() else #not reply - if conf['Thread Hiding'] + if Conf['Thread Hiding'] setTimeout -> ThreadHiding.init() - if conf['Thread Expansion'] + if Conf['Thread Expansion'] setTimeout -> ExpandThread.init() - if conf['Comment Expansion'] + if Conf['Comment Expansion'] setTimeout -> ExpandComment.init() - if conf['Index Navigation'] + if Conf['Index Navigation'] setTimeout -> Nav.init() nodes = [] @@ -3341,6 +3326,16 @@ Main = else $.on form, 'DOMNodeInserted', Main.listener + flatten: (parent, obj) -> + if obj instanceof Array + Conf[parent] = obj[0] + else if typeof obj is 'object' + for key, val of obj + Main.flatten key, val + else # string or number + Conf[parent] = obj + return + addStyle: -> $.off d, 'DOMNodeInserted', Main.addStyle if d.head @@ -3350,11 +3345,11 @@ Main = message: (e) -> {data} = e - if data.qr - qr.message.receive data + if data.QR + QR.message.receive data return {version} = data - if version and version isnt VERSION and confirm 'An updated version of 4chan X is available, would you like to install it now?' + if version and version isnt Main.version and confirm 'An updated version of 4chan X is available, would you like to install it now?' window.location = "https://raw.github.com/mayhemydg/4chan-x/#{version}/4chan_x.user.js" preParse: (node) -> @@ -3372,11 +3367,11 @@ Main = post.img = if post.filesize then node.getElementsByTagName('img')[0] else false post node: (nodes, notify) -> - for callback in g.callbacks + for callback in Main.callbacks try callback node for node in nodes catch err - alert "4chan X (#{VERSION}) error: #{err.message}\nhttp://mayhemydg.github.com/4chan-x/#bug-report\n\n#{err.stack}" if notify + alert "4chan X (#{Main.version}) error: #{err.message}\nhttp://mayhemydg.github.com/4chan-x/#bug-report\n\n#{err.stack}" if notify return observer: (mutations) -> nodes = [] @@ -3388,6 +3383,9 @@ Main = {target} = e Main.node [Main.preParse target] if target.nodeName is 'TABLE' + namespace: '4chan_x.' + version: '2.29.1' + callbacks: [] css: ' /* dialog styling */ .dialog {