diff --git a/Gruntfile.coffee b/Gruntfile.coffee index 4f60ae3b9..6e57cf1fa 100755 --- a/Gruntfile.coffee +++ b/Gruntfile.coffee @@ -1,7 +1,8 @@ module.exports = (grunt) -> + grunt.util.linefeed = '\n' importHTML = (filename) -> - "\"\"\"#{grunt.file.read("src/General/html/#{filename}.html").replace(/^\s+|\s+$ parts = template.split /([\$&@]){([^}`]*)}/ @@ -38,6 +39,8 @@ module.exports = (grunt) -> pkg = grunt.config 'pkg' pkg.importHTML = importHTML pkg.html = html + pkg.assert = assert + pkg.tests_enabled or= false pkg enumerable: true ) @@ -52,17 +55,18 @@ module.exports = (grunt) -> 'src/General/Build.coffee' 'src/General/Get.coffee' 'src/General/UI.coffee' - 'src/Filtering/*' - 'src/Quotelinks/*' - 'src/Linkification/*' - 'src/Posting/QR.coffee' - 'src/Posting/*' - 'src/Images/*' - 'src/Menu/*' - 'src/Monitoring/*' - 'src/Archive/*' - 'src/Theming/*' - 'src/Miscellaneous/*' + 'src/General/CrossOrigin.coffee' + 'src/Filtering/**/*.coffee' + 'src/Quotelinks/**/*.coffee' + 'src/Posting/Captcha.coffee' + 'src/Posting/**/*.coffee' + 'src/Images/**/*.coffee' + 'src/Linkification/**/*.coffee' + 'src/Menu/**/*.coffee' + 'src/Monitoring/**/*.coffee' + 'src/Archive/**/*.coffee' + 'src/Miscellaneous/**/*.coffee' + 'src/Theming/**/*.coffee' 'src/General/Navigate.coffee' 'src/General/Settings.coffee' 'src/General/Main.coffee' diff --git a/builds/4chan-X.meta.js b/builds/4chan-X.meta.js deleted file mode 100755 index 972d23bba..000000000 --- a/builds/4chan-X.meta.js +++ /dev/null @@ -1,23 +0,0 @@ -// ==UserScript== -// @name 4chan X -// @version 1.7.33 -// @minGMVer 1.14 -// @minFFVer 26 -// @namespace 4chan-X -// @description Cross-browser userscript for maximum lurking on 4chan. -// @license MIT; https://github.com/ccd0/4chan-x/blob/master/LICENSE -// @match *://boards.4chan.org/* -// @match *://sys.4chan.org/* -// @match *://a.4cdn.org/* -// @match *://i.4cdn.org/* -// @grant GM_getValue -// @grant GM_setValue -// @grant GM_deleteValue -// @grant GM_listValues -// @grant GM_openInTab -// @grant GM_xmlhttpRequest -// @run-at document-start -// @updateURL https://ccd0.github.io/4chan-x/builds/4chan-X.meta.js -// @downloadURL https://ccd0.github.io/4chan-x/builds/4chan-X.user.js -// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAACVBMVEUAAGcAAABmzDNZt9VtAAAAAXRSTlMAQObYZgAAAF5JREFUeNrtkTESABAQxPD/R6tsE2dUGYUtFJvLDKf93KevHJAjpBorAQWSBIKqFASC4G0pCAkm4GfaEvgYXl0T6HBaE97f0vmnfYHbZOMLZCx9ISdKWwjOWZSC8GYm4SUGwfYgqI4AAAAASUVORK5CYII= -// ==/UserScript== diff --git a/builds/appchan-x.meta.js b/builds/appchan-x.meta.js index a92d9d95a..deec3c232 100644 --- a/builds/appchan-x.meta.js +++ b/builds/appchan-x.meta.js @@ -13,7 +13,6 @@ // @match *://i.4cdn.org/* // @exclude *://blog.4chan.org/* // @exclude *://dis.4chan.org/* -// @exclude *://a.4cdn.org/* // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue diff --git a/builds/appchan-x.user.js b/builds/appchan-x.user.js index 1240d72a4..dc9c46552 100644 --- a/builds/appchan-x.user.js +++ b/builds/appchan-x.user.js @@ -14,7 +14,6 @@ // @match *://i.4cdn.org/* // @exclude *://blog.4chan.org/* // @exclude *://dis.4chan.org/* -// @exclude *://a.4cdn.org/* // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue @@ -116,7 +115,7 @@ 'use strict'; (function() { - var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, editMascot, editTheme, g, userNavigation, + var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, editMascot, editTheme, g, userNavigation, __slice = [].slice, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __hasProp = {}.hasOwnProperty, @@ -4824,11 +4823,15 @@ this.pagelist = $.el('div', { className: 'pagelist', hidden: true, - innerHTML: "
<
>
" + innerHTML: { + innerHTML: "
\r\r<\r\r
\r
\r
\r\r>\r\r
" + } }); this.navLinks = $.el('div', { className: 'navLinks', - innerHTML: "\uf05c " + innerHTML: { + innerHTML: "\r\r\\uf05c\r \r\r\r\r\r\r\r\r" + } }); this.timeEl = $('time#index-last-refresh', this.navLinks); this.searchInput = $('#index-search', this.navLinks); @@ -6050,7 +6053,9 @@ comment = thread.OP.nodes.comment.innerHTML.replace(/(
\s*){2,}/g, '
'); root = $.el('div', { className: 'catalog-thread', - innerHTML: "
" + postCount + " / " + fileCount + " / " + pageCount + "
" + subject + "
" + comment + "
" + innerHTML: { + innerHTML: "\r
\r" + postCount + " / " + fileCount + " / " + pageCount + "\r\r
\r" + subject + "\r
" + comment + "
\r" + } }); root.dataset.fullID = thread.fullID; if (thread.isPinned) { @@ -6793,11 +6798,97 @@ }; })(); + CrossOrigin = (function() { + return { + file: (function() { + var makeBlob; + makeBlob = function(urlBlob, contentType, contentDisposition, url) { + var blob, match, mime, name, _ref, _ref1, _ref2; + name = (_ref = url.match(/([^\/]+)\/*$/)) != null ? _ref[1] : void 0; + mime = (contentType != null ? contentType.match(/[^;]*/)[0] : void 0) || 'application/octet-stream'; + match = (contentDisposition != null ? (_ref1 = contentDisposition.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)) != null ? _ref1[1] : void 0 : void 0) || (contentType != null ? (_ref2 = contentType.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)) != null ? _ref2[1] : void 0 : void 0); + if (match) { + name = match.replace(/\\"/g, '"'); + } + blob = new Blob([urlBlob], { + type: mime + }); + blob.name = name; + return blob; + }; + return function(url, cb) { + return GM_xmlhttpRequest({ + method: "GET", + url: url, + overrideMimeType: "text/plain; charset=x-user-defined", + onload: function(xhr) { + var contentDisposition, contentType, data, i, r, _ref, _ref1; + r = xhr.responseText; + data = new Uint8Array(r.length); + i = 0; + while (i < r.length) { + data[i] = r.charCodeAt(i); + i++; + } + contentType = (_ref = xhr.responseHeaders.match(/Content-Type:\s*(.*)/i)) != null ? _ref[1] : void 0; + contentDisposition = (_ref1 = xhr.responseHeaders.match(/Content-Disposition:\s*(.*)/i)) != null ? _ref1[1] : void 0; + return cb(makeBlob(data, contentType, contentDisposition, url)); + }, + onerror: function() { + return cb(null); + } + }); + }; + })(), + json: (function() { + var callbacks, responses; + callbacks = {}; + responses = {}; + return function(url, cb) { + if (responses[url]) { + cb(responses[url]); + return; + } + if (callbacks[url]) { + callbacks[url].push(cb); + return; + } + callbacks[url] = [cb]; + return GM_xmlhttpRequest({ + method: "GET", + url: url + '', + onload: function(xhr) { + var response, _i, _len, _ref; + response = JSON.parse(xhr.responseText); + _ref = callbacks[url]; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + cb = _ref[_i]; + cb(response); + } + delete callbacks[url]; + return responses[url] = response; + }, + onerror: function() { + return delete callbacks[url]; + }, + onabort: function() { + return delete callbacks[url]; + } + }); + }; + })() + }; + })(); + Anonymize = { init: function() { - if (!Conf['Anonymize']) { + var _ref; + if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread' || _ref === 'archive') && Conf['Anonymize'])) { return; } + if (g.VIEW === 'archive') { + return this.archive(); + } return Post.callbacks.push({ name: 'Anonymize', cb: this.node @@ -6820,14 +6911,31 @@ $.replace(email, name); return delete this.nodes.email; } + }, + archive: function() { + return $.ready(function() { + var name, trip, _i, _j, _len, _len1, _ref, _ref1, _results; + _ref = $$('.name'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + name = _ref[_i]; + name.textContent = 'Anonymous'; + } + _ref1 = $$('.postertrip'); + _results = []; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + trip = _ref1[_j]; + _results.push($.rm(trip)); + } + return _results; + }); } }; Filter = { filters: {}, init: function() { - var boards, err, filter, hl, key, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; - if (!Conf['Filter']) { + var boards, err, filter, hl, key, line, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6; + if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Filter'])) { return; } if (!Conf['Filtered Backlinks']) { @@ -6835,18 +6943,18 @@ } for (key in Config.filter) { this.filters[key] = []; - _ref = Conf[key].split('\n'); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - filter = _ref[_i]; - if (filter[0] === '#') { + _ref1 = Conf[key].split('\n'); + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + line = _ref1[_i]; + if (line[0] === '#') { continue; } - if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) { + if (!(regexp = line.match(/\/(.+)\/(\w*)/))) { continue; } - filter = filter.replace(regexp[0], ''); - boards = ((_ref1 = filter.match(/boards:([^;]+)/)) != null ? _ref1[1].toLowerCase() : void 0) || 'global'; - if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) { + filter = line.replace(regexp[0], ''); + boards = ((_ref2 = filter.match(/boards:([^;]+)/)) != null ? _ref2[1].toLowerCase() : void 0) || 'global'; + if (boards !== 'global' && (_ref3 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref3) < 0)) { continue; } if (key === 'uniqueID' || key === 'MD5') { @@ -6856,14 +6964,14 @@ regexp = RegExp(regexp[1], regexp[2]); } catch (_error) { err = _error; - new Notice('warning', err.message, 60); + new Notice('warning', [$.tn(("Invalid " + key + " filter: ") + line, $.el('br')), $.tn(err.message)], 60); continue; } } - op = ((_ref3 = filter.match(/[^t]op:(yes|no|only)/)) != null ? _ref3[1] : void 0) || 'yes'; + op = ((_ref4 = filter.match(/[^t]op:(yes|no|only)/)) != null ? _ref4[1] : void 0) || 'yes'; stub = (function() { - var _ref4; - switch ((_ref4 = filter.match(/stub:(yes|no)/)) != null ? _ref4[1] : void 0) { + var _ref5; + switch ((_ref5 = filter.match(/stub:(yes|no)/)) != null ? _ref5[1] : void 0) { case 'yes': return true; case 'no': @@ -6873,19 +6981,11 @@ } })(); if (hl = /highlight/.test(filter)) { - hl = ((_ref4 = filter.match(/highlight:(\w+)/)) != null ? _ref4[1] : void 0) || 'filter-highlight'; - top = ((_ref5 = filter.match(/top:(yes|no)/)) != null ? _ref5[1] : void 0) || 'yes'; + hl = ((_ref5 = filter.match(/highlight:(\w+)/)) != null ? _ref5[1] : void 0) || 'filter-highlight'; + top = ((_ref6 = filter.match(/top:(yes|no)/)) != null ? _ref6[1] : void 0) || 'yes'; top = top === 'yes'; } - this.filters[key].push({ - hide: !hl, - op: op, - stub: stub, - "class": hl, - top: top, - match: regexp, - test: typeof regexp === 'string' ? Filter.stringTest : Filter.regexpTest - }); + this.filters[key].push(this.createFilter(regexp, op, stub, hl, top)); } if (!this.filters[key].length) { delete this.filters[key]; @@ -6899,9 +6999,24 @@ cb: this.node }); }, + createFilter: function(regexp, op, stub, hl, top) { + var settings, test; + test = typeof regexp === 'string' ? Filter.stringTest : Filter.regexpTest; + settings = { + hide: !hl, + stub: stub, + "class": hl, + top: top + }; + return function(value, isReply) { + if (Filter.test(test, value, isReply)) { + return settings; + } + }; + }, node: function() { - var key, obj, value, _i, _len, _ref; - if (this.isClone) { + var filter, key, result, value, _i, _len, _ref; + if (this.isClone || this.isFetchedQuote) { return; } for (key in Filter.filters) { @@ -6911,18 +7026,18 @@ } _ref = Filter.filters[key]; for (_i = 0, _len = _ref.length; _i < _len; _i++) { - obj = _ref[_i]; - if (!Filter.test(obj, value, this.isReply)) { + filter = _ref[_i]; + if (!(result = filter(value, this.isReply))) { continue; } - if (obj.hide) { + if (result.hide) { if (!(this.isReply || g.VIEW === 'index')) { continue; } - this.hide("Hidden by filtering the " + key + ": " + obj.match, obj.stub); + this.hide("Hidden by filtering the " + key + ": " + result.match, result.stub); return; } - this.highlight("Highlighted by filtering the " + key + ": " + obj.match, obj["class"], obj.top); + this.highlight("Highlighted by filtering the " + key + ": " + result.match, result["class"], result.top); } } }, @@ -6967,12 +7082,6 @@ } return false; }, - email: function(post) { - if ('email' in post.info) { - return post.info.email; - } - return false; - }, subject: function(post) { if ('subject' in post.info) { return post.info.subject || false; @@ -7001,7 +7110,7 @@ var file; file = post.file; if (file && (file.isImage || file.isVideo)) { - return post.file.dimensions; + return file.dimensions; } return false; }, @@ -7019,8 +7128,8 @@ }, menu: { init: function() { - var div, entry, type, _i, _len, _ref; - if (!Conf['Menu'] || !Conf['Filter']) { + var div, entry, type, _i, _len, _ref, _ref1; + if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Menu'] && Conf['Filter'])) { return; } div = $.el('div', { @@ -7035,9 +7144,9 @@ }, subEntries: [] }; - _ref = [['Name', 'name'], ['Unique ID', 'uniqueID'], ['Tripcode', 'tripcode'], ['Capcode', 'capcode'], ['E-mail', 'email'], ['Subject', 'subject'], ['Comment', 'comment'], ['Flag', 'flag'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'MD5']]; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - type = _ref[_i]; + _ref1 = [['Name', 'name'], ['Unique ID', 'uniqueID'], ['Tripcode', 'tripcode'], ['Capcode', 'capcode'], ['Subject', 'subject'], ['Comment', 'comment'], ['Flag', 'flag'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'MD5']]; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + type = _ref1[_i]; entry.subEntries.push(Filter.menu.createSubEntry(type[0], type[1])); } return Menu.menu.addEntry(entry); @@ -8130,555 +8239,224 @@ } }; - Linkify = { + QR.captcha = { init: function() { - var type, _i, _len, _ref; - if (!Conf['Linkify']) { + var counter, root; + if (d.cookie.indexOf('pass_enabled=1') >= 0) { return; } - this.types = {}; - _ref = this.ordered_types; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - type = _ref[_i]; - this.types[type.key] = type; + if (!(this.isEnabled = !!$.id('g-recaptcha'))) { + return; } - if (Conf['Comment Expansion']) { - ExpandComment.callbacks.push(this.node); - } - if (Conf['Embedding'] || Conf['Link Title']) { - this.embedProcess = Function('link', 'post', "var data = this.services(link);\nif (data) {" + ((Conf['Embedding'] ? 'this.embed(data); ' : '') + (Conf['Link Title'] ? 'data.push(post); this.title(data);' : '')) + "}"); - } - return Post.callbacks.push({ - name: 'Linkify', - cb: this.node + this.captchas = []; + $.get('captchas', [], function(_arg) { + var captchas; + captchas = _arg.captchas; + return QR.captcha.sync(captchas); }); + $.sync('captchas', this.sync.bind(this)); + root = $.el('div', { + className: 'captcha-root' + }); + $.extend(root, { + innerHTML: "
" + }); + counter = $('.captcha-counter > a', root); + this.nodes = { + root: root, + counter: counter + }; + this.count(); + $.addClass(QR.nodes.el, 'has-captcha'); + $.after(QR.nodes.com.parentNode, root); + $.on(counter, 'click', this.toggle.bind(this)); + return $.on(window, 'captcha:success', (function(_this) { + return function() { + return $.queueTask(function() { + return _this.save(false); + }); + }; + })(this)); }, - events: function(post) { - var el, i, items; - i = 0; - items = $$('.embedder', post.nodes.comment); - while (el = items[i++]) { - $.on(el, 'click', Linkify.cb.toggle); - if ($.hasClass(el, 'embedded')) { - Linkify.cb.toggle.call(el); - } + shouldFocus: false, + timeouts: {}, + postsCount: 0, + needed: function() { + var captchaCount; + captchaCount = this.captchas.length; + if (this.nodes.container && !this.timeouts.destroy) { + captchaCount++; + } + this.postsCount = QR.posts.length; + if (this.postsCount === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { + this.postsCount = 0; + } + return captchaCount < this.postsCount; + }, + onPostChange: function() { + if (this.postsCount === 0) { + this.setup(); + } + if (QR.posts.length === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { + return this.postsCount = 0; } }, - node: function() { - var data, end, endNode, i, index, length, link, links, node, result, saved, snapshot, space, test, word; - if (this.isClone) { - return (Conf['Embedding'] ? Linkify.events(this) : null); + toggle: function() { + if (this.nodes.container && !this.timeouts.destroy) { + return this.destroy(); + } else { + return this.setup(true, true); } - if (!Linkify.regString.test(this.info.comment)) { + }, + setup: function(focus, force) { + if (!(this.isEnabled && (this.needed() || force))) { return; } - test = /[^\s'"]+/g; - space = /[\s'"]/; - snapshot = $.X('.//br|.//text()', this.nodes.comment); - i = 0; - links = []; - while (node = snapshot.snapshotItem(i++)) { - data = node.data; - if (!data || node.parentElement.nodeName === "A") { - continue; - } - while (result = test.exec(data)) { - index = result.index; - endNode = node; - word = result[0]; - if ((length = index + word.length) === data.length) { - test.lastIndex = 0; - while ((saved = snapshot.snapshotItem(i++))) { - if (saved.nodeName === 'BR') { - break; - } - endNode = saved; - data = saved.data; - word += data; - length = data.length; - if (end = space.exec(data)) { - test.lastIndex = length = end.index; - i--; - break; - } - } - } - if (Linkify.regString.exec(word)) { - links.push(Linkify.makeRange(node, endNode, index, length)); - } - if (!(test.lastIndex && node === endNode)) { - break; - } - } + $.addClass(QR.nodes.el, 'captcha-open'); + if (focus) { + this.shouldFocus = true; } - i = links.length; - while (i--) { - link = links[i]; - Linkify.embedProcess(Linkify.makeLink(link), this); + if (this.timeouts.destroy) { + clearTimeout(this.timeouts.destroy); + delete this.timeouts.destroy; + return this.reload(); + } + if (this.nodes.container) { + return; + } + this.nodes.container = $.el('div', { + className: 'captcha-container' + }); + $.prepend(this.nodes.root, this.nodes.container); + new MutationObserver(this.afterSetup.bind(this)).observe(this.nodes.container, { + childList: true, + subtree: true + }); + return $.globalEval('(function() {\n function render() {\n var container = document.querySelector("#qr .captcha-container");\n container.dataset.widgetID = window.grecaptcha.render(container, {\n sitekey: \'6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\',\n theme: document.documentElement.classList.contains(\'tomorrow\') ? \'dark\' : \'light\',\n callback: function(response) {\n window.dispatchEvent(new CustomEvent("captcha:success", {detail: response}));\n }\n });\n }\n if (window.grecaptcha) {\n render();\n } else {\n var cbNative = window.onRecaptchaLoaded;\n window.onRecaptchaLoaded = function() {\n render();\n cbNative();\n }\n }\n})();'); + }, + afterSetup: function(mutations) { + var iframe, mutation, node, textarea, _i, _j, _len, _len1, _ref; + for (_i = 0, _len = mutations.length; _i < _len; _i++) { + mutation = mutations[_i]; + _ref = mutation.addedNodes; + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + node = _ref[_j]; + if (iframe = $.x('./descendant-or-self::iframe', node)) { + this.setupIFrame(iframe); + } + if (textarea = $.x('./descendant-or-self::textarea', node)) { + this.setupTextArea(textarea); + } + } } }, - embedProcess: function() {}, - regString: /((https?|mailto|git|magnet|ftp|irc):([a-z\d%\/])|[-a-z\d]+[.](aero|asia|biz|cat|com|coop|info|int|jobs|mobi|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2})([:\/]|(?!.))|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|[-\w\d.@]+@[a-z\d.-]+\.[a-z\d])/i, - makeRange: function(startNode, endNode, startOffset, endOffset) { - var range; - range = document.createRange(); - range.setStart(startNode, startOffset); - range.setEnd(endNode, endOffset); - return range; + setupIFrame: function(iframe) { + this.setupTime = Date.now(); + if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) { + QR.nodes.el.style.top = null; + QR.nodes.el.style.bottom = '0px'; + } + if (this.shouldFocus) { + iframe.focus(); + } + return this.shouldFocus = false; }, - makeLink: function(range) { - var a, i, t, text; - text = range.toString(); - i = 0; - while (/[(\[{<>]/.test(text.charAt(i))) { - i++; + setupTextArea: function(textarea) { + return $.one(textarea, 'input', (function(_this) { + return function() { + return _this.save(true); + }; + })(this)); + }, + destroy: function() { + if (!this.isEnabled) { + return; } - if (i) { - text = text.slice(i); - while (range.startOffset + i >= range.startContainer.data.length) { - i--; - } - if (i) { - range.setStart(range.startContainer, range.startOffset + i); - } + delete this.timeouts.destroy; + $.rmClass(QR.nodes.el, 'captcha-open'); + if (this.nodes.container) { + $.rm(this.nodes.container); } - i = 0; - while (/[)\]}>.,]/.test(t = text.charAt(text.length - (1 + i)))) { - if (!(/[.,]/.test(t) || (text.match(/[()\[\]{}<>]/g)).length % 2)) { + return delete this.nodes.container; + }, + sync: function(captchas) { + if (captchas == null) { + captchas = []; + } + this.captchas = captchas; + this.clear(); + return this.count(); + }, + getOne: function() { + var captcha; + this.clear(); + if (captcha = this.captchas.shift()) { + this.count(); + $.set('captchas', this.captchas); + return captcha.response; + } else { + return null; + } + }, + save: function(pasted) { + var reload, _base; + $.forceSync('captchas'); + reload = (QR.cooldown.auto || Conf['Post on Captcha Completion']) && this.needed(); + this.captchas.push({ + response: $('textarea', this.nodes.container).value, + timeout: (pasted ? this.setupTime : Date.now()) + 2 * $.MINUTE + }); + this.count(); + $.set('captchas', this.captchas); + if (reload) { + this.shouldFocus = true; + this.reload(); + } else { + if (pasted) { + this.destroy(); + } else { + if ((_base = this.timeouts).destroy == null) { + _base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND); + } + } + QR.nodes.status.focus(); + } + if (Conf['Post on Captcha Completion'] && !QR.cooldown.auto) { + return QR.submit(); + } + }, + clear: function() { + var captcha, i, now, _i, _len, _ref; + if (!this.captchas.length) { + return; + } + $.forceSync('captchas'); + now = Date.now(); + _ref = this.captchas; + for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { + captcha = _ref[i]; + if (captcha.timeout > now) { break; } - i++; } - if (i) { - text = text.slice(0, -i); - while (range.endOffset - i < 0) { - i--; - } - if (i) { - range.setEnd(range.endContainer, range.endOffset - i); - } - } - if (!/(mailto:|.+:\/\/)/.test(text)) { - text = (/@/.test(text) ? 'mailto:' : 'http://') + text; - } - a = $.el('a', { - className: 'linkify', - rel: 'nofollow noreferrer', - target: '_blank', - href: text - }); - $.add(a, range.extractContents()); - range.insertNode(a); - range.detach(); - return a; - }, - services: function(link) { - var href, match, type, _i, _len, _ref; - href = link.href; - _ref = Linkify.ordered_types; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - type = _ref[_i]; - if (!(match = type.regExp.exec(href))) { - continue; - } - if (type.dummy) { - return; - } - return [type.key, match[1], match[2], link]; - } - }, - embed: function(data) { - var embed, href, key, link, name, options, post, uid, value, _ref; - key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4]; - href = link.href; - embed = $.el('a', { - className: 'embedder', - href: 'javascript:;', - textContent: '(embed)' - }); - _ref = { - key: key, - href: href, - uid: uid, - options: options - }; - for (name in _ref) { - value = _ref[name]; - embed.dataset[name] = value; - } - $.addClass(link, "" + embed.dataset.key); - $.on(embed, 'click', Linkify.cb.toggle); - $.after(link, [$.tn(' '), embed]); - if (Conf['Auto-embed']) { - return Linkify.cb.toggle.call(embed); - } - }, - title: function(data) { - var err, key, link, options, post, service, title, titles, uid; - key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4]; - if (!(service = Linkify.types[key].title)) { + if (!i) { return; } - titles = Conf['CachedTitles']; - if (title = titles[uid]) { - return link.textContent = title[0]; - } else { - try { - return $.cache(service.api(uid), (function() { - return Linkify.cb.title(this, data); - }), { - responseType: 'json' - }); - } catch (_error) { - err = _error; - link.innerHTML = 'Title Link Blocked (are you using NoScript?)'; - $.prepend(link, $.tn("[" + key + "] ")); - } + this.captchas = this.captchas.slice(i); + this.count(); + $.set('captchas', this.captchas); + return this.setup(true); + }, + count: function() { + this.nodes.counter.textContent = "Captchas: " + this.captchas.length; + clearTimeout(this.timeouts.clear); + if (this.captchas.length) { + return this.timeouts.clear = setTimeout(this.clear.bind(this), this.captchas[0].timeout - Date.now()); } }, - cb: { - toggle: function() { - if ($.hasClass(this, "embedded")) { - $.rm(this.previousElementSibling); - this.previousElementSibling.hidden = false; - this.textContent = '(embed)'; - } else { - this.previousElementSibling.hidden = true; - $.before(this, Linkify.cb.embed(this)); - this.textContent = '(unembed)'; - } - return $.toggleClass(this, 'embedded'); - }, - embed: function(a) { - var el, type; - el = (type = Linkify.types[a.dataset.key]).el(a); - el.style.cssText = type.style != null ? type.style : "border: 0; width: 640px; height: 390px"; - return el; - }, - title: function(req, data) { - var key, link, link2, options, post, post2, service, status, text, uid, _i, _j, _len, _len1, _ref, _ref1; - key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4]; - status = req.status; - service = Linkify.types[key].title; - text = "[" + key + "] " + ((function() { - switch (status) { - case 200: - case 304: - return service.text(req.response); - case 404: - return "Not Found"; - case 403: - return "Forbidden or Private"; - default: - return "" + status + "'d"; - } - })()); - link.textContent = text; - _ref = post.clones; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - post2 = _ref[_i]; - _ref1 = $$('a', post2.nodes.comment); - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - link2 = _ref1[_j]; - if (link2.href === link.href) { - link2.textContent = text; - } - } - } - } - }, - ordered_types: [ - { - key: 'audio', - regExp: /(.*\.(mp3|ogg|wav))$/, - style: '', - el: function(a) { - return $.el('audio', { - controls: true, - preload: 'auto', - src: a.dataset.uid - }); - } - }, { - key: 'gist', - regExp: /.*(?:gist.github.com.*\/)([^\/][^\/]*)$/, - el: function(a) { - var div; - return div = $.el('iframe', { - src: "http://www.purplegene.com/script?url=https://gist.github.com/" + a.dataset.uid + ".js" - }); - }, - title: { - api: function(uid) { - return "https://api.github.com/gists/" + uid; - }, - text: function(_arg) { - var file, files; - files = _arg.files; - for (file in files) { - if (files.hasOwnProperty(file)) { - return file; - } - } - } - } - }, { - key: 'image', - regExp: /(http|www).*\.(gif|png|jpg|jpeg|bmp)$/, - style: 'border: 0; width: auto; height: auto;', - el: function(a) { - var el; - el = $.el('div'); - el.innerHTML = ''; - el.firstChild.href = el.firstChild.firstChild.src = a.dataset.href; - return el; - } - }, { - key: 'InstallGentoo', - regExp: /.*(?:paste.installgentoo.com\/view\/)([0-9a-z_]+)/, - el: function(a) { - return $.el('iframe', { - src: "http://paste.installgentoo.com/view/embed/" + a.dataset.uid - }); - } - }, { - key: 'Twitter', - regExp: /.*twitter.com\/(.+\/status\/\d+)/, - el: function(a) { - return $.el('iframe', { - src: "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid - }); - } - }, { - key: 'LiveLeak', - regExp: /.*(?:liveleak.com\/view.+i=)([0-9a-z_]+)/, - el: function(a) { - var el; - el = $.el('iframe', { - width: "640", - height: "360", - src: "http://www.liveleak.com/ll_embed?i=" + a.dataset.uid, - frameborder: "0" - }); - el.setAttribute("allowfullscreen", "true"); - return el; - } - }, { - key: 'MediaCrush', - regExp: /.*(?:mediacru.sh\/)([0-9a-z_-]+)/i, - style: 'border: 0;', - el: function(a) { - var el; - el = $.el('div'); - $.cache("https://mediacru.sh/" + a.dataset.uid + ".json", function() { - var embed, ext, file, files, i, status, type, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _results, _results1; - status = this.status; - if (status !== 200 && status !== 304) { - return el.textContent = "ERROR " + status; - } - files = this.response.files; - _ref = ['video/webm', 'video/mp4', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'audio/mpeg', 'audio/ogg']; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - type = _ref[_i]; - for (_j = 0, _len1 = files.length; _j < _len1; _j++) { - file = files[_j]; - if (file.type === type) { - embed = file; - break; - } - } - if (embed) { - break; - } - } - if (!embed) { - return div.textContent = "ERROR: Not a valid filetype"; - } - switch (embed.type) { - case 'video/mp4': - case 'video/webm': - case 'video/ogv': - el.innerHTML = ''; - _ref1 = ['mp4', 'webm', 'ogv']; - _results = []; - for (i = _k = 0, _len2 = _ref1.length; _k < _len2; i = ++_k) { - ext = _ref1[i]; - _results.push(el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext); - } - return _results; - break; - case 'image/svg+xml': - case 'image/png': - case 'image/gif': - case 'image/jpeg': - el.innerHTML = ''; - el.firstChild.href = a.dataset.href; - return el.firstChild.firstChild.src = "https://mediacru.sh/" + file.file; - case 'audio/mpeg': - case 'audio/ogg': - el.innerHTML = ''; - _ref2 = ['ogg', 'mp3']; - _results1 = []; - for (i = _l = 0, _len3 = _ref2.length; _l < _len3; i = ++_l) { - ext = _ref2[i]; - _results1.push(el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext); - } - return _results1; - break; - default: - return el.textContent = "ERROR: No valid filetype."; - } - }); - return el; - } - }, { - key: 'pastebin', - regExp: /.*(?:pastebin.com\/(?!u\/))([^#\&\?]*).*/, - el: function(a) { - var div; - return div = $.el('iframe', { - src: "http://pastebin.com/embed_iframe.php?i=" + a.dataset.uid - }); - } - }, { - key: 'gfycat', - regExp: /.*gfycat.com\/(?:iframe\/)?(\S*)/, - el: function(a) { - var div; - return div = $.el('iframe', { - src: "http://gfycat.com/iframe/" + a.dataset.uid - }); - } - }, { - key: 'SoundCloud', - regExp: /.*(?:soundcloud.com\/|snd.sc\/)([^#\&\?]*).*/, - style: 'border: 0; width: 500px; height: 400px;', - el: function(a) { - return $.el('iframe', { - src: "//w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(a.dataset.uid)) - }); - }, - title: { - api: function(uid) { - return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); - }, - text: function(_) { - return _.title; - } - } - }, { - key: 'StrawPoll', - regExp: /strawpoll\.me\/(?:embed_\d+\/)?(\d+)/, - style: 'border: 0; width: 600px; height: 406px;', - el: function(a) { - return $.el('iframe', { - src: "http://strawpoll.me/embed_1/" + a.dataset.uid - }); - } - }, { - key: 'TwitchTV', - regExp: /.*(?:twitch.tv\/)([^#\&\?]*).*/, - style: "border: none; width: 640px; height: 360px;", - el: function(a) { - var channel, id, idparam, obj, result, type, _; - if (result = /(\w+)\/([bc])\/(\d+)/i.exec(a.dataset.uid)) { - _ = result[0], channel = result[1], type = result[2], id = result[3]; - idparam = { - 'b': 'archive_id', - 'c': 'chapter_id' - }; - obj = $.el('object', { - data: 'http://www.twitch.tv/widgets/archive_embed_player.swf' - }); - obj.innerHTML = ''; - obj.children[1].value = "channel=" + channel + "&start_volume=25&auto_play=false&" + idparam[type] + "=" + id; - return obj; - } else { - channel = (/(\w+)/.exec(a.dataset.uid))[0]; - obj = $.el('object', { - data: "http://www.twitch.tv/widgets/live_embed_player.swf?channel=" + channel - }); - obj.innerHTML = ''; - obj.children[1].value = "hostname=www.twitch.tv&channel=" + channel + "&auto_play=true&start_volume=25"; - return obj; - } - } - }, { - key: 'Vocaroo', - regExp: /.*(?:vocaroo.com\/)([^#\&\?]*).*/, - style: '', - el: function(a) { - return $.el('audio', { - controls: true, - preload: 'auto', - src: "http://vocaroo.com/media_command.php?media=" + (a.dataset.uid.replace(/^i\//, '')) + "&command=download_ogg" - }); - } - }, { - key: 'Vimeo', - regExp: /.*(?:vimeo.com\/)([^#\&\?]*).*/, - el: function(a) { - return $.el('iframe', { - src: "//player.vimeo.com/video/" + a.dataset.uid + "?wmode=opaque" - }); - }, - title: { - api: function(uid) { - return "https://vimeo.com/api/oembed.json?url=http://vimeo.com/" + uid; - }, - text: function(_) { - return _.title; - } - } - }, { - key: 'Vine', - regExp: /.*(?:vine.co\/)([^#\&\?]*).*/, - style: 'border: none; width: 500px; height: 500px;', - el: function(a) { - return $.el('iframe', { - src: "https://vine.co/" + a.dataset.uid + "/card" - }); - } - }, { - key: 'YouTube', - regExp: /.*(?:youtu.be\/|youtube.*v=|youtube.*\/embed\/|youtube.*\/v\/|youtube.*videos\/)([^#\&\?]*)\??(t\=.*)?/, - el: function(a) { - var el; - el = $.el('iframe', { - src: "//www.youtube.com/embed/" + a.dataset.uid + (a.dataset.option ? '#' + a.dataset.option : '') + "?wmode=opaque" - }); - el.setAttribute("allowfullscreen", "true"); - return el; - }, - title: { - api: function(uid) { - return "https://gdata.youtube.com/feeds/api/videos/" + uid + "?alt=json&fields=title/text(),yt:noembed,app:control/yt:state/@reasonCode"; - }, - text: function(data) { - return data.entry.title.$t; - } - } - }, { - key: 'Loopvid', - regExp: /.*loopvid.appspot.com\/.*/, - dummy: true - }, { - key: 'MediaFire', - regExp: /.*mediafire.com\/.*/, - dummy: true - }, { - key: 'video', - regExp: /(.*\.(ogv|webm|mp4))$/, - style: 'border: 0; width: auto; height: auto;', - el: function(a) { - return $.el('video', { - controls: 'controls', - preload: 'auto', - src: a.dataset.uid - }); - } - } - ] + reload: function(focus) { + return $.globalEval('(function() {\n var container = document.querySelector("#qr .captcha-container");\n window.grecaptcha.reset(container.dataset.widgetID);\n})();'); + } }; QR = { @@ -9146,7 +8924,9 @@ dialog: function() { var dialog, elm, event, i, items, match_max, match_min, name, node, nodes, rules, save, setNode; QR.nodes = nodes = { - el: dialog = UI.dialog('qr', 'top:0;right:0;', "
\uf00d
+
No selected fileSpoiler\uf0c1Post from URL+Dump\uf00dRemove File
") + el: dialog = UI.dialog('qr', 'top:0;right:0;', { + innerHTML: "
\r\r
\r\r
\r\\uf00d\r
\r
\r
\r\r\r \r
\r
\r\r\r
\r
\r
\r+\r
\r
\r\rNo selected file\r\r\r\rSpoiler\r\\uf0c1\rPost from URL\r+\rDump\r\\uf00d\rRemove File\r\r\r\r
\r\r
\r\r\r\r" + }) }; setNode = function(name, query) { return nodes[name] = $(query, dialog); @@ -9578,226 +9358,6 @@ } }; - QR.captcha = { - init: function() { - var counter, root; - if (d.cookie.indexOf('pass_enabled=1') >= 0) { - return; - } - if (!(this.isEnabled = !!$.id('g-recaptcha'))) { - return; - } - this.captchas = []; - $.get('captchas', [], function(_arg) { - var captchas; - captchas = _arg.captchas; - return QR.captcha.sync(captchas); - }); - $.sync('captchas', this.sync.bind(this)); - root = $.el('div', { - className: 'captcha-root' - }); - $.extend(root, { - innerHTML: "
" - }); - counter = $('.captcha-counter > a', root); - this.nodes = { - root: root, - counter: counter - }; - this.count(); - $.addClass(QR.nodes.el, 'has-captcha'); - $.after(QR.nodes.com.parentNode, root); - $.on(counter, 'click', this.toggle.bind(this)); - return $.on(window, 'captcha:success', (function(_this) { - return function() { - return $.queueTask(function() { - return _this.save(false); - }); - }; - })(this)); - }, - shouldFocus: false, - timeouts: {}, - postsCount: 0, - needed: function() { - var captchaCount; - captchaCount = this.captchas.length; - if (this.nodes.container && !this.timeouts.destroy) { - captchaCount++; - } - this.postsCount = QR.posts.length; - if (this.postsCount === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { - this.postsCount = 0; - } - return captchaCount < this.postsCount; - }, - onPostChange: function() { - if (this.postsCount === 0) { - this.setup(); - } - if (QR.posts.length === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { - return this.postsCount = 0; - } - }, - toggle: function() { - if (this.nodes.container && !this.timeouts.destroy) { - return this.destroy(); - } else { - return this.setup(true, true); - } - }, - setup: function(focus, force) { - if (!(this.isEnabled && (this.needed() || force))) { - return; - } - $.addClass(QR.nodes.el, 'captcha-open'); - if (focus) { - this.shouldFocus = true; - } - if (this.timeouts.destroy) { - clearTimeout(this.timeouts.destroy); - delete this.timeouts.destroy; - return this.reload(); - } - if (this.nodes.container) { - return; - } - this.nodes.container = $.el('div', { - className: 'captcha-container' - }); - $.prepend(this.nodes.root, this.nodes.container); - new MutationObserver(this.afterSetup.bind(this)).observe(this.nodes.container, { - childList: true, - subtree: true - }); - return $.globalEval('(function() {\n function render() {\n var container = document.querySelector("#qr .captcha-container");\n container.dataset.widgetID = window.grecaptcha.render(container, {\n sitekey: \'6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\',\n theme: document.documentElement.classList.contains(\'tomorrow\') ? \'dark\' : \'light\',\n callback: function(response) {\n window.dispatchEvent(new CustomEvent("captcha:success", {detail: response}));\n }\n });\n }\n if (window.grecaptcha) {\n render();\n } else {\n var cbNative = window.onRecaptchaLoaded;\n window.onRecaptchaLoaded = function() {\n render();\n cbNative();\n }\n }\n})();'); - }, - afterSetup: function(mutations) { - var iframe, mutation, node, textarea, _i, _j, _len, _len1, _ref; - for (_i = 0, _len = mutations.length; _i < _len; _i++) { - mutation = mutations[_i]; - _ref = mutation.addedNodes; - for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { - node = _ref[_j]; - if (iframe = $.x('./descendant-or-self::iframe', node)) { - this.setupIFrame(iframe); - } - if (textarea = $.x('./descendant-or-self::textarea', node)) { - this.setupTextArea(textarea); - } - } - } - }, - setupIFrame: function(iframe) { - this.setupTime = Date.now(); - if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) { - QR.nodes.el.style.top = null; - QR.nodes.el.style.bottom = '0px'; - } - if (this.shouldFocus) { - iframe.focus(); - } - return this.shouldFocus = false; - }, - setupTextArea: function(textarea) { - return $.one(textarea, 'input', (function(_this) { - return function() { - return _this.save(true); - }; - })(this)); - }, - destroy: function() { - if (!this.isEnabled) { - return; - } - delete this.timeouts.destroy; - $.rmClass(QR.nodes.el, 'captcha-open'); - if (this.nodes.container) { - $.rm(this.nodes.container); - } - return delete this.nodes.container; - }, - sync: function(captchas) { - if (captchas == null) { - captchas = []; - } - this.captchas = captchas; - this.clear(); - return this.count(); - }, - getOne: function() { - var captcha; - this.clear(); - if (captcha = this.captchas.shift()) { - this.count(); - $.set('captchas', this.captchas); - return captcha.response; - } else { - return null; - } - }, - save: function(pasted) { - var reload, _base; - $.forceSync('captchas'); - reload = (QR.cooldown.auto || Conf['Post on Captcha Completion']) && this.needed(); - this.captchas.push({ - response: $('textarea', this.nodes.container).value, - timeout: (pasted ? this.setupTime : Date.now()) + 2 * $.MINUTE - }); - this.count(); - $.set('captchas', this.captchas); - if (reload) { - this.shouldFocus = true; - this.reload(); - } else { - if (pasted) { - this.destroy(); - } else { - if ((_base = this.timeouts).destroy == null) { - _base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND); - } - } - QR.nodes.status.focus(); - } - if (Conf['Post on Captcha Completion'] && !QR.cooldown.auto) { - return QR.submit(); - } - }, - clear: function() { - var captcha, i, now, _i, _len, _ref; - if (!this.captchas.length) { - return; - } - $.forceSync('captchas'); - now = Date.now(); - _ref = this.captchas; - for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { - captcha = _ref[i]; - if (captcha.timeout > now) { - break; - } - } - if (!i) { - return; - } - this.captchas = this.captchas.slice(i); - this.count(); - $.set('captchas', this.captchas); - return this.setup(true); - }, - count: function() { - this.nodes.counter.textContent = "Captchas: " + this.captchas.length; - clearTimeout(this.timeouts.clear); - if (this.captchas.length) { - return this.timeouts.clear = setTimeout(this.clear.bind(this), this.captchas[0].timeout - Date.now()); - } - }, - reload: function(focus) { - return $.globalEval('(function() {\n var container = document.querySelector("#qr .captcha-container");\n window.grecaptcha.reset(container.dataset.widgetID);\n})();'); - } - }; - QR.cooldown = { init: function() { var key, setTimers, type; @@ -11534,6 +11094,557 @@ } }; + Linkify = { + init: function() { + var type, _i, _len, _ref; + if (!Conf['Linkify']) { + return; + } + this.types = {}; + _ref = this.ordered_types; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + type = _ref[_i]; + this.types[type.key] = type; + } + if (Conf['Comment Expansion']) { + ExpandComment.callbacks.push(this.node); + } + if (Conf['Embedding'] || Conf['Link Title']) { + this.embedProcess = Function('link', 'post', "var data = this.services(link);\nif (data) {" + ((Conf['Embedding'] ? 'this.embed(data); ' : '') + (Conf['Link Title'] ? 'data.push(post); this.title(data);' : '')) + "}"); + } + return Post.callbacks.push({ + name: 'Linkify', + cb: this.node + }); + }, + events: function(post) { + var el, i, items; + i = 0; + items = $$('.embedder', post.nodes.comment); + while (el = items[i++]) { + $.on(el, 'click', Linkify.cb.toggle); + if ($.hasClass(el, 'embedded')) { + Linkify.cb.toggle.call(el); + } + } + }, + node: function() { + var data, end, endNode, i, index, length, link, links, node, result, saved, snapshot, space, test, word; + if (this.isClone) { + return (Conf['Embedding'] ? Linkify.events(this) : null); + } + if (!Linkify.regString.test(this.info.comment)) { + return; + } + test = /[^\s'"]+/g; + space = /[\s'"]/; + snapshot = $.X('.//br|.//text()', this.nodes.comment); + i = 0; + links = []; + while (node = snapshot.snapshotItem(i++)) { + data = node.data; + if (!data || node.parentElement.nodeName === "A") { + continue; + } + while (result = test.exec(data)) { + index = result.index; + endNode = node; + word = result[0]; + if ((length = index + word.length) === data.length) { + test.lastIndex = 0; + while ((saved = snapshot.snapshotItem(i++))) { + if (saved.nodeName === 'BR') { + break; + } + endNode = saved; + data = saved.data; + word += data; + length = data.length; + if (end = space.exec(data)) { + test.lastIndex = length = end.index; + i--; + break; + } + } + } + if (Linkify.regString.exec(word)) { + links.push(Linkify.makeRange(node, endNode, index, length)); + } + if (!(test.lastIndex && node === endNode)) { + break; + } + } + } + i = links.length; + while (i--) { + link = links[i]; + Linkify.embedProcess(Linkify.makeLink(link), this); + } + }, + embedProcess: function() {}, + regString: /((https?|mailto|git|magnet|ftp|irc):([a-z\d%\/])|[-a-z\d]+[.](aero|asia|biz|cat|com|coop|info|int|jobs|mobi|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2})([:\/]|(?!.))|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|[-\w\d.@]+@[a-z\d.-]+\.[a-z\d])/i, + makeRange: function(startNode, endNode, startOffset, endOffset) { + var range; + range = document.createRange(); + range.setStart(startNode, startOffset); + range.setEnd(endNode, endOffset); + return range; + }, + makeLink: function(range) { + var a, i, t, text; + text = range.toString(); + i = 0; + while (/[(\[{<>]/.test(text.charAt(i))) { + i++; + } + if (i) { + text = text.slice(i); + while (range.startOffset + i >= range.startContainer.data.length) { + i--; + } + if (i) { + range.setStart(range.startContainer, range.startOffset + i); + } + } + i = 0; + while (/[)\]}>.,]/.test(t = text.charAt(text.length - (1 + i)))) { + if (!(/[.,]/.test(t) || (text.match(/[()\[\]{}<>]/g)).length % 2)) { + break; + } + i++; + } + if (i) { + text = text.slice(0, -i); + while (range.endOffset - i < 0) { + i--; + } + if (i) { + range.setEnd(range.endContainer, range.endOffset - i); + } + } + if (!/(mailto:|.+:\/\/)/.test(text)) { + text = (/@/.test(text) ? 'mailto:' : 'http://') + text; + } + a = $.el('a', { + className: 'linkify', + rel: 'nofollow noreferrer', + target: '_blank', + href: text + }); + $.add(a, range.extractContents()); + range.insertNode(a); + range.detach(); + return a; + }, + services: function(link) { + var href, match, type, _i, _len, _ref; + href = link.href; + _ref = Linkify.ordered_types; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + type = _ref[_i]; + if (!(match = type.regExp.exec(href))) { + continue; + } + if (type.dummy) { + return; + } + return [type.key, match[1], match[2], link]; + } + }, + embed: function(data) { + var embed, href, key, link, name, options, post, uid, value, _ref; + key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4]; + href = link.href; + embed = $.el('a', { + className: 'embedder', + href: 'javascript:;', + textContent: '(embed)' + }); + _ref = { + key: key, + href: href, + uid: uid, + options: options + }; + for (name in _ref) { + value = _ref[name]; + embed.dataset[name] = value; + } + $.addClass(link, "" + embed.dataset.key); + $.on(embed, 'click', Linkify.cb.toggle); + $.after(link, [$.tn(' '), embed]); + if (Conf['Auto-embed']) { + return Linkify.cb.toggle.call(embed); + } + }, + title: function(data) { + var err, key, link, options, post, service, title, titles, uid; + key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4]; + if (!(service = Linkify.types[key].title)) { + return; + } + titles = Conf['CachedTitles']; + if (title = titles[uid]) { + return link.textContent = title[0]; + } else { + try { + return $.cache(service.api(uid), (function() { + return Linkify.cb.title(this, data); + }), { + responseType: 'json' + }); + } catch (_error) { + err = _error; + link.innerHTML = 'Title Link Blocked (are you using NoScript?)'; + $.prepend(link, $.tn("[" + key + "] ")); + } + } + }, + cb: { + toggle: function() { + if ($.hasClass(this, "embedded")) { + $.rm(this.previousElementSibling); + this.previousElementSibling.hidden = false; + this.textContent = '(embed)'; + } else { + this.previousElementSibling.hidden = true; + $.before(this, Linkify.cb.embed(this)); + this.textContent = '(unembed)'; + } + return $.toggleClass(this, 'embedded'); + }, + embed: function(a) { + var el, type; + el = (type = Linkify.types[a.dataset.key]).el(a); + el.style.cssText = type.style != null ? type.style : "border: 0; width: 640px; height: 390px"; + return el; + }, + title: function(req, data) { + var key, link, link2, options, post, post2, service, status, text, uid, _i, _j, _len, _len1, _ref, _ref1; + key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4]; + status = req.status; + service = Linkify.types[key].title; + text = "[" + key + "] " + ((function() { + switch (status) { + case 200: + case 304: + return service.text(req.response); + case 404: + return "Not Found"; + case 403: + return "Forbidden or Private"; + default: + return "" + status + "'d"; + } + })()); + link.textContent = text; + _ref = post.clones; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + post2 = _ref[_i]; + _ref1 = $$('a', post2.nodes.comment); + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + link2 = _ref1[_j]; + if (link2.href === link.href) { + link2.textContent = text; + } + } + } + } + }, + ordered_types: [ + { + key: 'audio', + regExp: /(.*\.(mp3|ogg|wav))$/, + style: '', + el: function(a) { + return $.el('audio', { + controls: true, + preload: 'auto', + src: a.dataset.uid + }); + } + }, { + key: 'gist', + regExp: /.*(?:gist.github.com.*\/)([^\/][^\/]*)$/, + el: function(a) { + var div; + return div = $.el('iframe', { + src: "http://www.purplegene.com/script?url=https://gist.github.com/" + a.dataset.uid + ".js" + }); + }, + title: { + api: function(uid) { + return "https://api.github.com/gists/" + uid; + }, + text: function(_arg) { + var file, files; + files = _arg.files; + for (file in files) { + if (files.hasOwnProperty(file)) { + return file; + } + } + } + } + }, { + key: 'image', + regExp: /(http|www).*\.(gif|png|jpg|jpeg|bmp)$/, + style: 'border: 0; width: auto; height: auto;', + el: function(a) { + var el; + el = $.el('div'); + el.innerHTML = ''; + el.firstChild.href = el.firstChild.firstChild.src = a.dataset.href; + return el; + } + }, { + key: 'InstallGentoo', + regExp: /.*(?:paste.installgentoo.com\/view\/)([0-9a-z_]+)/, + el: function(a) { + return $.el('iframe', { + src: "http://paste.installgentoo.com/view/embed/" + a.dataset.uid + }); + } + }, { + key: 'Twitter', + regExp: /.*twitter.com\/(.+\/status\/\d+)/, + el: function(a) { + return $.el('iframe', { + src: "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid + }); + } + }, { + key: 'LiveLeak', + regExp: /.*(?:liveleak.com\/view.+i=)([0-9a-z_]+)/, + el: function(a) { + var el; + el = $.el('iframe', { + width: "640", + height: "360", + src: "http://www.liveleak.com/ll_embed?i=" + a.dataset.uid, + frameborder: "0" + }); + el.setAttribute("allowfullscreen", "true"); + return el; + } + }, { + key: 'MediaCrush', + regExp: /.*(?:mediacru.sh\/)([0-9a-z_-]+)/i, + style: 'border: 0;', + el: function(a) { + var el; + el = $.el('div'); + $.cache("https://mediacru.sh/" + a.dataset.uid + ".json", function() { + var embed, ext, file, files, i, status, type, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _results, _results1; + status = this.status; + if (status !== 200 && status !== 304) { + return el.textContent = "ERROR " + status; + } + files = this.response.files; + _ref = ['video/webm', 'video/mp4', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'audio/mpeg', 'audio/ogg']; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + type = _ref[_i]; + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + if (file.type === type) { + embed = file; + break; + } + } + if (embed) { + break; + } + } + if (!embed) { + return div.textContent = "ERROR: Not a valid filetype"; + } + switch (embed.type) { + case 'video/mp4': + case 'video/webm': + case 'video/ogv': + el.innerHTML = ''; + _ref1 = ['mp4', 'webm', 'ogv']; + _results = []; + for (i = _k = 0, _len2 = _ref1.length; _k < _len2; i = ++_k) { + ext = _ref1[i]; + _results.push(el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext); + } + return _results; + break; + case 'image/svg+xml': + case 'image/png': + case 'image/gif': + case 'image/jpeg': + el.innerHTML = ''; + el.firstChild.href = a.dataset.href; + return el.firstChild.firstChild.src = "https://mediacru.sh/" + file.file; + case 'audio/mpeg': + case 'audio/ogg': + el.innerHTML = ''; + _ref2 = ['ogg', 'mp3']; + _results1 = []; + for (i = _l = 0, _len3 = _ref2.length; _l < _len3; i = ++_l) { + ext = _ref2[i]; + _results1.push(el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext); + } + return _results1; + break; + default: + return el.textContent = "ERROR: No valid filetype."; + } + }); + return el; + } + }, { + key: 'pastebin', + regExp: /.*(?:pastebin.com\/(?!u\/))([^#\&\?]*).*/, + el: function(a) { + var div; + return div = $.el('iframe', { + src: "http://pastebin.com/embed_iframe.php?i=" + a.dataset.uid + }); + } + }, { + key: 'gfycat', + regExp: /.*gfycat.com\/(?:iframe\/)?(\S*)/, + el: function(a) { + var div; + return div = $.el('iframe', { + src: "http://gfycat.com/iframe/" + a.dataset.uid + }); + } + }, { + key: 'SoundCloud', + regExp: /.*(?:soundcloud.com\/|snd.sc\/)([^#\&\?]*).*/, + style: 'border: 0; width: 500px; height: 400px;', + el: function(a) { + return $.el('iframe', { + src: "//w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(a.dataset.uid)) + }); + }, + title: { + api: function(uid) { + return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); + }, + text: function(_) { + return _.title; + } + } + }, { + key: 'StrawPoll', + regExp: /strawpoll\.me\/(?:embed_\d+\/)?(\d+)/, + style: 'border: 0; width: 600px; height: 406px;', + el: function(a) { + return $.el('iframe', { + src: "http://strawpoll.me/embed_1/" + a.dataset.uid + }); + } + }, { + key: 'TwitchTV', + regExp: /.*(?:twitch.tv\/)([^#\&\?]*).*/, + style: "border: none; width: 640px; height: 360px;", + el: function(a) { + var channel, id, idparam, obj, result, type, _; + if (result = /(\w+)\/([bc])\/(\d+)/i.exec(a.dataset.uid)) { + _ = result[0], channel = result[1], type = result[2], id = result[3]; + idparam = { + 'b': 'archive_id', + 'c': 'chapter_id' + }; + obj = $.el('object', { + data: 'http://www.twitch.tv/widgets/archive_embed_player.swf' + }); + obj.innerHTML = ''; + obj.children[1].value = "channel=" + channel + "&start_volume=25&auto_play=false&" + idparam[type] + "=" + id; + return obj; + } else { + channel = (/(\w+)/.exec(a.dataset.uid))[0]; + obj = $.el('object', { + data: "http://www.twitch.tv/widgets/live_embed_player.swf?channel=" + channel + }); + obj.innerHTML = ''; + obj.children[1].value = "hostname=www.twitch.tv&channel=" + channel + "&auto_play=true&start_volume=25"; + return obj; + } + } + }, { + key: 'Vocaroo', + regExp: /.*(?:vocaroo.com\/)([^#\&\?]*).*/, + style: '', + el: function(a) { + return $.el('audio', { + controls: true, + preload: 'auto', + src: "http://vocaroo.com/media_command.php?media=" + (a.dataset.uid.replace(/^i\//, '')) + "&command=download_ogg" + }); + } + }, { + key: 'Vimeo', + regExp: /.*(?:vimeo.com\/)([^#\&\?]*).*/, + el: function(a) { + return $.el('iframe', { + src: "//player.vimeo.com/video/" + a.dataset.uid + "?wmode=opaque" + }); + }, + title: { + api: function(uid) { + return "https://vimeo.com/api/oembed.json?url=http://vimeo.com/" + uid; + }, + text: function(_) { + return _.title; + } + } + }, { + key: 'Vine', + regExp: /.*(?:vine.co\/)([^#\&\?]*).*/, + style: 'border: none; width: 500px; height: 500px;', + el: function(a) { + return $.el('iframe', { + src: "https://vine.co/" + a.dataset.uid + "/card" + }); + } + }, { + key: 'YouTube', + regExp: /.*(?:youtu.be\/|youtube.*v=|youtube.*\/embed\/|youtube.*\/v\/|youtube.*videos\/)([^#\&\?]*)\??(t\=.*)?/, + el: function(a) { + var el; + el = $.el('iframe', { + src: "//www.youtube.com/embed/" + a.dataset.uid + (a.dataset.option ? '#' + a.dataset.option : '') + "?wmode=opaque" + }); + el.setAttribute("allowfullscreen", "true"); + return el; + }, + title: { + api: function(uid) { + return "https://gdata.youtube.com/feeds/api/videos/" + uid + "?alt=json&fields=title/text(),yt:noembed,app:control/yt:state/@reasonCode"; + }, + text: function(data) { + return data.entry.title.$t; + } + } + }, { + key: 'Loopvid', + regExp: /.*loopvid.appspot.com\/.*/, + dummy: true + }, { + key: 'MediaFire', + regExp: /.*mediafire.com\/.*/, + dummy: true + }, { + key: 'video', + regExp: /(.*\.(ogv|webm|mp4))$/, + style: 'border: 0; width: auto; height: auto;', + el: function(a) { + return $.el('video', { + controls: 'controls', + preload: 'auto', + src: a.dataset.uid + }); + } + } + ] + }; + ArchiveLink = { init: function() { var div, entry, type, _i, _len, _ref; @@ -13410,7 +13521,7 @@ } return Redirect.data = o; }, - archives: [{"uid":0,"name":"Moe","domain":"archive.moe","http":false,"https":true,"software":"foolfuuka","boards":["a","biz","c","co","diy","gd","h","i","int","jp","k","m","mlp","out","po","r9k","s4s","sci","sp","tg","tv","u","v","vg","vp","vr","wsg"],"files":["a","biz","c","co","diy","gd","h","i","jp","k","m","mlp","po","s4s","sci","tg","u","v","vg","vp","vr","wsg"]},{"uid":3,"name":"4plebs Archive","domain":"archive.4plebs.org","http":true,"https":true,"software":"foolfuuka","boards":["adv","f","hr","o","pol","s4s","tg","trv","tv","x"],"files":["adv","f","hr","o","pol","s4s","tg","trv","tv","x"]},{"uid":5,"name":"Love is Over","domain":"archive.loveisover.me","http":true,"https":true,"software":"foolfuuka","boards":["c","d","e","i","lgbt","t","u","w","wg"],"files":["c","d","e","i","lgbt","t","u","w","wg"]},{"uid":8,"name":"Rebecca Black Tech","domain":"archive.rebeccablacktech.com","http":false,"https":true,"software":"fuuka","boards":["cgl","g","mu","w"],"files":["cgl","g","mu","w"]},{"uid":10,"name":"warosu","domain":"warosu.org","http":false,"https":true,"software":"fuuka","boards":["3","biz","cgl","ck","diy","fa","g","ic","jp","lit","sci","tg","vr"],"files":["3","biz","cgl","ck","diy","fa","g","ic","jp","lit","sci","tg","vr"]},{"uid":15,"name":"fgts","domain":"fgts.jp","http":true,"https":true,"software":"foolfuuka","boards":["asp","cm","h","hc","hm","n","p","r","s","soc","y"],"files":["asp","cm","h","hc","hm","n","p","r","s","soc","y"]},{"uid":17,"name":"imcute","domain":"imcute.yt","http":true,"https":false,"software":"foolfuuka","boards":["an","fit","gif","int","mlp","out","r9k","toy"],"files":["an","fit","gif","int","mlp","out","r9k","toy"]}], + archives: [{"uid":0,"name":"Moe","domain":"archive.moe","http":false,"https":true,"software":"foolfuuka","boards":["a","biz","c","co","diy","gd","h","i","int","jp","k","m","mlp","out","po","r9k","s4s","sci","tg","tv","u","v","vg","vp","vr","wsg"],"files":["a","biz","c","co","diy","gd","h","i","jp","k","m","mlp","po","s4s","sci","tg","u","v","vg","vp","vr","wsg"]},{"uid":3,"name":"4plebs Archive","domain":"archive.4plebs.org","http":true,"https":true,"software":"foolfuuka","boards":["adv","f","hr","o","pol","s4s","tg","trv","tv","x"],"files":["adv","f","hr","o","pol","s4s","tg","trv","tv","x"]},{"uid":5,"name":"Love is Over","domain":"archive.loveisover.me","http":true,"https":true,"software":"foolfuuka","boards":["c","d","e","i","lgbt","t","u","w","wg"],"files":["c","d","e","i","lgbt","t","u","w","wg"]},{"uid":8,"name":"Rebecca Black Tech","domain":"archive.rebeccablacktech.com","http":false,"https":true,"software":"fuuka","boards":["cgl","g","mu","w"],"files":["cgl","g","mu","w"]},{"uid":10,"name":"warosu","domain":"warosu.org","http":false,"https":true,"software":"fuuka","boards":["3","biz","cgl","ck","diy","fa","g","ic","jp","lit","sci","tg","vr"],"files":["3","biz","cgl","ck","diy","fa","g","ic","jp","lit","sci","tg","vr"]},{"uid":15,"name":"fgts","domain":"fgts.jp","http":true,"https":true,"software":"foolfuuka","boards":["asp","cm","h","hc","hm","n","p","r","s","soc","y"],"files":["asp","cm","h","hc","hm","n","p","r","s","soc","y"]},{"uid":21,"name":"imcute","domain":"imcute.yt","http":true,"https":false,"software":"foolfuuka","boards":["an","fit","gif","int","mlp","out","r9k","toy"],"files":["an","fit","gif","int","mlp","out","r9k","toy"],"imagehosts":["http://imcute.yt/"]}], to: function(dest, data) { var archive; archive = (dest === 'search' || dest === 'board' ? Redirect.data.thread : Redirect.data[dest])[data.boardID]; @@ -13440,9 +13551,13 @@ return "" + (Redirect.protocol(archive)) + archive.domain + "/" + path; }, post: function(archive, _arg) { - var URL, boardID, postID; + var URL, boardID, postID, protocol; boardID = _arg.boardID, postID = _arg.postID; - URL = new String("" + (Redirect.protocol(archive)) + archive.domain + "/_/api/chan/post/?board=" + boardID + "&num=" + postID); + protocol = Redirect.protocol(archive); + URL = new String("" + protocol + archive.domain + "/_/api/chan/post/?board=" + boardID + "&num=" + postID); + if (!Redirect.securityCheck(URL)) { + return ''; + } URL.archive = archive; return URL; }, @@ -13463,75 +13578,1392 @@ value = encodeURIComponent(value); path = archive.software === 'foolfuuka' ? "" + boardID + "/search/" + type + "/" + value : "" + boardID + "/?task=search2&search_" + (type === 'image' ? 'media_hash' : type) + "=" + value; return "" + (Redirect.protocol(archive)) + archive.domain + "/" + path; + }, + securityCheck: function(URL) { + return /^https:\/\//.test(URL) || location.protocol === 'http:' || Conf['Except Archives from Encryption']; + }, + navigate: function(URL, alternative) { + if (URL && (Redirect.securityCheck(URL) || confirm("Redirect to " + URL + "?\n\nYour connection will not be encrypted."))) { + return location.replace(URL); + } else if (alternative) { + return location.replace(alternative); + } } }; - [ - { - "uid": 0, - "name": "Moe", - "domain": "archive.moe", - "http": false, - "https": true, - "software": "foolfuuka", - "boards": ["a", "biz", "c", "co", "diy", "gd", "h", "i", "int", "jp", "k", "m", "mlp", "out", "po", "r9k", "s4s", "sci", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], - "files": ["a", "biz", "c", "co", "diy", "gd", "h", "i", "jp", "k", "m", "mlp", "po", "s4s", "sci", "tg", "u", "v", "vg", "vp", "vr", "wsg"] - }, { - "uid": 3, - "name": "4plebs Archive", - "domain": "archive.4plebs.org", - "http": true, - "https": true, - "software": "foolfuuka", - "boards": ["adv", "f", "hr", "o", "pol", "s4s", "tg", "trv", "tv", "x"], - "files": ["adv", "f", "hr", "o", "pol", "s4s", "tg", "trv", "tv", "x"] - }, { - "uid": 5, - "name": "Love is Over", - "domain": "archive.loveisover.me", - "http": true, - "https": true, - "software": "foolfuuka", - "boards": ["c", "d", "e", "i", "lgbt", "t", "u", "w", "wg"], - "files": ["c", "d", "e", "i", "lgbt", "t", "u", "w", "wg"] - }, { - "uid": 8, - "name": "Rebecca Black Tech", - "domain": "archive.rebeccablacktech.com", - "http": false, - "https": true, - "software": "fuuka", - "boards": ["cgl", "g", "mu", "w"], - "files": ["cgl", "g", "mu", "w"] - }, { - "uid": 10, - "name": "warosu", - "domain": "warosu.org", - "http": false, - "https": true, - "software": "fuuka", - "boards": ["3", "biz", "cgl", "ck", "diy", "fa", "g", "ic", "jp", "lit", "sci", "tg", "vr"], - "files": ["3", "biz", "cgl", "ck", "diy", "fa", "g", "ic", "jp", "lit", "sci", "tg", "vr"] - }, { - "uid": 15, - "name": "fgts", - "domain": "fgts.jp", - "http": true, - "https": true, - "software": "foolfuuka", - "boards": ["asp", "cm", "h", "hc", "hm", "n", "p", "r", "s", "soc", "y"], - "files": ["asp", "cm", "h", "hc", "hm", "n", "p", "r", "s", "soc", "y"] - }, { - "uid": 17, - "name": "imcute", - "domain": "imcute.yt", - "http": true, - "https": false, - "software": "foolfuuka", - "boards": ["an", "fit", "gif", "int", "mlp", "out", "r9k", "toy"], - "files": ["an", "fit", "gif", "int", "mlp", "out", "r9k", "toy"] + PSAHiding = { + init: function() { + if (!Conf['Announcement Hiding']) { + return; + } + $.addClass(doc, 'hide-announcement'); + return $.on(d, '4chanXInitFinished', this.setup); + }, + setup: function() { + var btn, entry, psa; + $.off(d, '4chanXInitFinished', PSAHiding.setup); + if (!(psa = $.id('globalMessage'))) { + return; + } + entry = { + el: $.el('a', { + textContent: 'Show announcement', + className: 'show-announcement', + href: 'javascript:;' + }), + order: 50, + open: function() { + return psa.hidden; + } + }; + Header.menu.addEntry(entry); + $.on(entry.el, 'click', PSAHiding.toggle); + PSAHiding.btn = btn = $.el('span', { + innerHTML: '[Dismiss]', + title: 'Mark announcement as read and hide.', + className: 'hide-announcement', + href: 'javascript:;', + textContent: '[ - ]' + }); + $.on(btn, 'click', PSAHiding.toggle); + $.get('hiddenPSA', 0, function(_arg) { + var hiddenPSA; + hiddenPSA = _arg.hiddenPSA; + PSAHiding.sync(hiddenPSA); + $.add(psa, btn); + return $.rmClass(doc, 'hide-announcement'); + }); + return $.sync('hiddenPSA', PSAHiding.sync); + }, + toggle: function(e) { + var UTC; + if ($.hasClass(this, 'hide-announcement')) { + UTC = +$.id('globalMessage').dataset.utc; + $.set('hiddenPSA', UTC); + } else { + $.event('CloseMenu'); + $["delete"]('hiddenPSA'); + } + return PSAHiding.sync(UTC); + }, + sync: function(UTC) { + var hr, psa; + psa = $.id('globalMessage'); + psa.hidden = PSAHiding.btn.hidden = UTC && UTC >= +psa.dataset.utc ? true : false; + if ((hr = psa.nextElementSibling) && hr.nodeName === 'HR') { + return hr.hidden = psa.hidden; + } } - ]; + }; + + CatalogLinks = { + init: function() { + var el, input; + if (!Conf['Catalog Links']) { + return; + } + CatalogLinks.el = el = $.el('label', { + id: 'toggleCatalog', + href: 'javascript:;', + innerHTML: " Catalog Links" + }); + input = $('input', el); + $.on(input, 'change', this.toggle); + $.sync('Header catalog links', CatalogLinks.set); + Header.menu.addEntry({ + el: el, + order: 95 + }); + return $.on(d, '4chanXInitFinished', function() { + return CatalogLinks.set(Conf['Header catalog links']); + }); + }, + toggle: function() { + $.event('CloseMenu'); + $.set('Header catalog links', this.checked); + return CatalogLinks.set(this.checked); + }, + set: function(useCatalog) { + var a, board, generateURL, path, _i, _len, _ref, _ref1; + path = useCatalog ? 'catalog' : ''; + generateURL = useCatalog && Conf['External Catalog'] ? CatalogLinks.external : function(board) { + return a.href = "/" + board + "/" + path; + }; + _ref = $$("#board-list a:not(.catalog), #boardNavDesktopFoot a"); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + a = _ref[_i]; + if (((_ref1 = a.hostname) !== 'boards.4chan.org' && _ref1 !== 'catalog.neet.tv' && _ref1 !== '4index.gropes.us') || !(board = a.pathname.split('/')[1]) || (board === 'f' || board === 'status' || board === '4chan') || $.hasClass(a, 'external')) { + continue; + } + a.href = generateURL(board); + } + return CatalogLinks.el.title = "Turn catalog links " + (useCatalog ? 'off' : 'on') + "."; + }, + external: function(board) { + if (board === 'a' || board === 'c' || board === 'g' || board === 'co' || board === 'k' || board === 'm' || board === 'o' || board === 'p' || board === 'v' || board === 'vg' || board === 'w' || board === 'cm' || board === '3' || board === 'adv' || board === 'an' || board === 'cgl' || board === 'ck' || board === 'diy' || board === 'fa' || board === 'fit' || board === 'int' || board === 'jp' || board === 'mlp' || board === 'lit' || board === 'mu' || board === 'n' || board === 'po' || board === 'sci' || board === 'toy' || board === 'trv' || board === 'tv' || board === 'vp' || board === 'x' || board === 'q') { + return "http://catalog.neet.tv/" + board; + } else { + return "/" + board + "/catalog"; + } + } + }; + + CustomCSS = { + init: function() { + if (!Conf['Custom CSS']) { + return; + } + return this.addStyle(); + }, + addStyle: function() { + return this.style = $.addStyle(Conf['usercss'], 'CustomCSS'); + }, + rmStyle: function() { + if (this.style) { + $.rm(this.style); + return delete this.style; + } + }, + update: function() { + if (!this.style) { + return this.addStyle(); + } + return this.style.textContent = Conf['usercss']; + } + }; + + Dice = { + init: function() { + if (g.BOARD.ID !== 'tg' || !Conf['Show Dice Roll']) { + return; + } + return Post.callbacks.push({ + name: 'Show Dice Roll', + cb: this.node + }); + }, + node: function() { + var dicestats, roll, _ref; + if (this.isClone || !(dicestats = (_ref = this.info.email) != null ? _ref.match(/dice[+\s](\d+)d(\d+)/) : void 0)) { + return; + } + roll = $('b', this.nodes.comment).firstChild; + return roll.data = "Rolled " + dicestats[1] + "d" + dicestats[2] + ": " + (roll.data.slice(7)); + } + }; + + ExpandComment = { + init: function() { + if (g.VIEW !== 'index' || !Conf['Comment Expansion'] || Conf['JSON Navigation']) { + return; + } + if (g.BOARD.ID === 'g') { + this.callbacks.push(Fourchan.code); + } + if (g.BOARD.ID === 'sci') { + this.callbacks.push(Fourchan.math); + } + return Post.callbacks.push({ + name: 'Comment Expansion', + cb: this.node + }); + }, + node: function() { + var a; + if (a = $('.abbr > a:not([onclick])', this.nodes.comment)) { + return $.on(a, 'click', ExpandComment.cb); + } + }, + callbacks: [], + cb: function(e) { + e.preventDefault(); + return ExpandComment.expand(Get.postFromNode(this)); + }, + expand: function(post) { + var a; + if (post.nodes.longComment && !post.nodes.longComment.parentNode) { + $.replace(post.nodes.shortComment, post.nodes.longComment); + post.nodes.comment = post.nodes.longComment; + return; + } + if (!(a = $('.abbr > a', post.nodes.comment))) { + return; + } + a.textContent = "Post No." + post + " Loading..."; + return $.cache("//a.4cdn.org" + (a.pathname.split('/').splice(0, 4).join('/')) + ".json", function() { + return ExpandComment.parse(this, a, post); + }); + }, + contract: function(post) { + var a; + if (!post.nodes.shortComment) { + return; + } + a = $('.abbr > a', post.nodes.shortComment); + a.textContent = 'here'; + $.replace(post.nodes.longComment, post.nodes.shortComment); + return post.nodes.comment = post.nodes.shortComment; + }, + parse: function(req, a, post) { + var callback, clone, comment, href, postObj, posts, quote, spoilerRange, status, _i, _j, _k, _len, _len1, _len2, _ref, _ref1; + status = req.status; + if (status !== 200 && status !== 304) { + a.textContent = "Error " + req.statusText + " (" + status + ")"; + return; + } + posts = req.response.posts; + if (spoilerRange = posts[0].custom_spoiler) { + Build.spoilerRange[g.BOARD] = spoilerRange; + } + for (_i = 0, _len = posts.length; _i < _len; _i++) { + postObj = posts[_i]; + if (postObj.no === post.ID) { + break; + } + } + if (postObj.no !== post.ID) { + a.textContent = "Post No." + post + " not found."; + return; + } + comment = post.nodes.comment; + clone = comment.cloneNode(false); + clone.innerHTML = postObj.com; + _ref = $$('.quotelink', clone); + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + quote = _ref[_j]; + href = quote.getAttribute('href'); + if (href[0] === '/') { + continue; + } + if (href[0] === '#') { + quote.href = "" + (a.pathname.split('/').splice(0, 4).join('/')) + href; + } else { + quote.href = "" + (a.pathname.split('/').splice(0, 3).join('/')) + "/" + href; + } + } + post.nodes.shortComment = comment; + $.replace(comment, clone); + post.nodes.comment = post.nodes.longComment = clone; + post.parseComment(); + post.parseQuotes(); + _ref1 = ExpandComment.callbacks; + for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { + callback = _ref1[_k]; + callback.call(post); + } + } + }; + + ExpandThread = { + statuses: {}, + init: function() { + if (g.VIEW === 'thread' || !Conf['Thread Expansion']) { + return; + } + return $.on(d, (Conf['JSON Navigation'] ? 'IndexRefresh' : '4chanXInitFinished'), this.onIndexRefresh); + }, + setButton: function(thread) { + var a, summary; + if (!(summary = $.x('following-sibling::*[contains(@class,"summary")][1]', thread.OP.nodes.root))) { + return; + } + a = $.el('a', { + textContent: ExpandThread.text.apply(ExpandThread, ['+'].concat(__slice.call(summary.textContent.match(/\d+/g)))), + href: "res/" + thread.ID, + className: 'summary' + }); + $.on(a, 'click', ExpandThread.cbToggle); + return $.replace(summary, a); + }, + disconnect: function() { + this.refresh(); + return $.off(d, 'IndexRefresh', this.onIndexRefresh); + }, + refresh: function(disconnect) { + var status, threadID, _ref, _ref1; + if (g.VIEW === 'thread' || !Conf['Thread Expansion']) { + return; + } + _ref = ExpandThread.statuses; + for (threadID in _ref) { + status = _ref[threadID]; + if ((_ref1 = status.req) != null) { + _ref1.abort(); + } + delete ExpandThread.statuses[threadID]; + } + }, + onIndexRefresh: function() { + ExpandThread.refresh(); + return g.BOARD.threads.forEach(function(thread) { + return ExpandThread.setButton(thread); + }); + }, + text: function(status, posts, files) { + return ("" + status + " " + posts + " post" + (posts > 1 ? 's' : '')) + (+files ? " and " + files + " image repl" + (files > 1 ? 'ies' : 'y') : "") + (" " + (status === '-' ? 'shown' : 'omitted') + "."); + }, + cbToggle: function(e) { + if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + return; + } + e.preventDefault(); + return ExpandThread.toggle(Get.threadFromNode(this)); + }, + toggle: function(thread) { + var a, threadRoot; + threadRoot = thread.OP.nodes.root.parentNode; + if (!(a = $('.summary', threadRoot))) { + return; + } + if (thread.ID in ExpandThread.statuses) { + return ExpandThread.contract(thread, a, threadRoot); + } else { + return ExpandThread.expand(thread, a, threadRoot); + } + }, + expand: function(thread, a, threadRoot) { + var status; + ExpandThread.statuses[thread] = status = {}; + a.textContent = ExpandThread.text.apply(ExpandThread, ['...'].concat(__slice.call(a.textContent.match(/\d+/g)))); + return status.req = $.cache("//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() { + delete status.req; + return ExpandThread.parse(this, thread, a); + }); + }, + contract: function(thread, a, threadRoot) { + var filesCount, inlined, num, postsCount, replies, reply, status, _i, _len; + status = ExpandThread.statuses[thread]; + delete ExpandThread.statuses[thread]; + if (status.req) { + status.req.abort(); + if (a) { + a.textContent = ExpandThread.text.apply(ExpandThread, ['+'].concat(__slice.call(a.textContent.match(/\d+/g)))); + } + return; + } + replies = $$('.thread > .replyContainer', threadRoot); + if (Conf['Show Replies']) { + num = (function() { + if (thread.isSticky) { + return 1; + } else { + switch (g.BOARD.ID) { + case 'b': + case 'vg': + return 3; + case 't': + return 1; + default: + return 5; + } + } + })(); + replies = replies.slice(0, -num); + } + postsCount = 0; + filesCount = 0; + for (_i = 0, _len = replies.length; _i < _len; _i++) { + reply = replies[_i]; + if (Conf['Quote Inlining']) { + while (inlined = $('.inlined', reply)) { + inlined.click(); + } + } + postsCount++; + if ('file' in Get.postFromRoot(reply)) { + filesCount++; + } + $.rm(reply); + } + return a.textContent = ExpandThread.text('+', postsCount, filesCount); + }, + parse: function(req, thread, a) { + var filesCount, post, postData, posts, postsCount, postsRoot, root, _i, _len, _ref, _ref1; + if ((_ref = req.status) !== 200 && _ref !== 304) { + a.textContent = "Error " + req.statusText + " (" + req.status + ")"; + return; + } + Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler; + posts = []; + postsRoot = []; + filesCount = 0; + _ref1 = req.response.posts; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + postData = _ref1[_i]; + if (postData.no === thread.ID) { + continue; + } + if (post = thread.posts[postData.no]) { + if ('file' in post) { + filesCount++; + } + postsRoot.push(post.nodes.root); + continue; + } + root = Build.postFromObject(postData, thread.board.ID); + post = new Post(root, thread, thread.board); + if ('file' in post) { + filesCount++; + } + posts.push(post); + postsRoot.push(root); + } + Post.callbacks.execute(posts); + $.after(a, postsRoot); + postsCount = postsRoot.length; + return a.textContent = ExpandThread.text('-', postsRoot.length, filesCount); + } + }; + + FileInfo = { + init: function() { + if (!Conf['File Info Formatting']) { + return; + } + return Post.callbacks.push({ + name: 'File Info Formatting', + cb: this.node + }); + }, + node: function() { + if (!this.file || this.isClone) { + return; + } + return this.file.text.innerHTML = "" + (FileInfo.format(Conf['fileInfo'], this)) + ""; + }, + format: function(formatString, post) { + return formatString.replace(/%([A-Za-z])/g, function(s, c) { + if (c in FileInfo.formatters) { + return FileInfo.formatters[c].call(post); + } else { + return s; + } + }); + }, + convertUnit: function(size, unit) { + var i; + if (unit === 'B') { + return "" + (size.toFixed()) + " Bytes"; + } + i = 1 + ['KB', 'MB'].indexOf(unit); + while (i--) { + size /= 1024; + } + size = unit === 'MB' ? Math.round(size * 100) / 100 : size.toFixed(); + return "" + size + " " + unit; + }, + escape: function(name) { + return name.replace(/<|>/g, function(c) { + return c === '<' && '<' || '>'; + }); + }, + formatters: { + t: function() { + return this.file.URL.match(/\d+\..+$/)[0]; + }, + T: function() { + return "" + (FileInfo.formatters.t.call(this)) + ""; + }, + l: function() { + return "" + (FileInfo.formatters.n.call(this)) + ""; + }, + L: function() { + return "" + (FileInfo.formatters.N.call(this)) + ""; + }, + n: function() { + var fullname, shortname; + fullname = this.file.name; + shortname = Build.shortFilename(this.file.name, this.isReply); + if (fullname === shortname) { + return FileInfo.escape(fullname); + } else { + return "" + (FileInfo.escape(shortname)) + "" + (FileInfo.escape(fullname)) + ""; + } + }, + N: function() { + return FileInfo.escape(this.file.name); + }, + p: function() { + if (this.file.isSpoiler) { + return 'Spoiler, '; + } else { + return ''; + } + }, + s: function() { + return this.file.size; + }, + B: function() { + return FileInfo.convertUnit(this.file.sizeInBytes, 'B'); + }, + K: function() { + return FileInfo.convertUnit(this.file.sizeInBytes, 'KB'); + }, + M: function() { + return FileInfo.convertUnit(this.file.sizeInBytes, 'MB'); + }, + r: function() { + return this.file.dimensions || 'PDF'; + } + } + }; + + Flash = { + init: function() { + if (g.BOARD.ID === 'f') { + return $.ready(Flash.initReady); + } + }, + initReady: function() { + var nav, sauceLink, swfName; + $.globalEval('SWFEmbed.init()'); + if (g.VIEW !== 'thread') { + return; + } + swfName = $('.fileText > a'); + nav = $('.navLinks.desktop'); + swfName = swfName.href.replace(/^(.*?)\/f\//g, ""); + sauceLink = $.el('a', { + textContent: 'Check Sauce on SWFCHAN', + href: "http://eye.swfchan.com/search/?q=" + swfName + }); + $.addClass(nav, 'swfSauce'); + $.rmClass(nav, 'navLinks'); + $.rmAll(nav); + return $.add(nav, [$.tn('['), sauceLink, $.tn(']')]); + } + }; + + Fourchan = { + init: function() { + var board; + board = g.BOARD.ID; + if (board === 'g') { + $.globalEval("window.addEventListener('prettyprint', function(e) {\n window.dispatchEvent(new CustomEvent('prettyprint:cb', {\n detail: prettyPrintOne(e.detail)\n }));\n}, false);"); + Post.callbacks.push({ + name: 'Parse /g/ code', + cb: this.code + }); + } + if (board === 'sci') { + $.globalEval("window.addEventListener('jsmath', function(e) {\n if (jsMath.loaded) {\n // process one post\n jsMath.ProcessBeforeShowing(document.getElementById(e.detail));\n } else {\n // load jsMath and process whole document\n jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);\n jsMath.Autoload.LoadJsMath();\n }\n}, false);"); + return Post.callbacks.push({ + name: 'Parse /sci/ math', + cb: this.math + }); + } + }, + code: function() { + var apply, pre, _i, _len, _ref; + if (this.isClone) { + return; + } + apply = function(e) { + return pre.innerHTML = e.detail; + }; + $.on(window, 'prettyprint:cb', apply); + _ref = $$('.prettyprint:not(.prettyprinted)', this.nodes.comment); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + pre = _ref[_i]; + $.event('prettyprint', pre.innerHTML, window); + } + $.off(window, 'prettyprint:cb', apply); + }, + math: function() { + if (this.isClone || !$('.math', this.nodes.comment)) { + return; + } + return $.event('jsmath', this.nodes.post.id, window); + } + }; + + IDColor = { + init: function() { + if (!Conf['Color User IDs']) { + return; + } + this.ids = { + Heaven: [0, 0, 0, '#fff'] + }; + return Post.callbacks.push({ + name: 'Color User IDs', + cb: this.node + }); + }, + node: function() { + var rgb, span, uid; + if (this.isClone || !((uid = this.info.uniqueID) && (span = $('span.hand', this.nodes.uniqueID)))) { + return; + } + rgb = IDColor.ids[uid] || IDColor.compute(uid); + span.style.color = rgb[3]; + span.style.backgroundColor = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; + $.addClass(span, 'painted'); + return span.title = 'Highlight posts by this ID'; + }, + compute: function(uid) { + var hash, i, rgb; + i = 1; + hash = uid.charCodeAt(0); + while (i < 8) { + hash = (hash << 5) - hash + uid.charCodeAt(i++); + } + rgb = [(hash >> 24) & 0xFF, (hash >> 16) & 0xFF, (hash >> 8) & 0xFF]; + rgb.push((rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114) > 125 ? '#000' : '#fff'); + return this.ids[uid] = rgb; + } + }; + + Keybinds = { + init: function() { + var hotkey, init; + if (!Conf['Keybinds']) { + return; + } + for (hotkey in Conf.hotkeys) { + $.sync(hotkey, Keybinds.sync); + } + init = function() { + var node, _i, _len, _ref; + $.off(d, '4chanXInitFinished', init); + $.on(d, 'keydown', Keybinds.keydown); + _ref = $$('[accesskey]'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + node.removeAttribute('accesskey'); + } + }; + return $.on(d, '4chanXInitFinished', init); + }, + sync: function(key, hotkey) { + return Conf[hotkey] = key; + }, + keydown: function(e) { + var form, key, notification, notifications, op, searchInput, target, thread, threadRoot, _i, _len, _ref; + if (!(key = Keybinds.keyCode(e))) { + return; + } + target = e.target; + if (target.nodeName === 'EMBED') { + return; + } + if ((_ref = target.nodeName) === 'INPUT' || _ref === 'TEXTAREA') { + if (!/(Esc|Alt|Ctrl|Meta|Shift\+\w{2,})/.test(key)) { + return; + } + } + if (g.VIEW !== 'catalog') { + threadRoot = Nav.getThread(); + if (op = $('.op', threadRoot)) { + thread = Get.postFromNode(op).thread; + } + } + switch (key) { + case Conf['Toggle board list']: + if (Conf['Custom Board Navigation']) { + Header.toggleBoardList(); + } + break; + case Conf['Toggle header']: + Header.toggleBarVisibility(); + break; + case Conf['Open empty QR']: + Keybinds.qr(); + break; + case Conf['Open QR']: + if (g.VIEW === 'catalog') { + return; + } + Keybinds.qr(threadRoot); + break; + case Conf['Open settings']: + Settings.open(); + break; + case Conf['Close']: + if ($.id('fourchanx-settings')) { + Settings.close(); + } else if ((notifications = $$('.notification')).length) { + for (_i = 0, _len = notifications.length; _i < _len; _i++) { + notification = notifications[_i]; + $('.close', notification).click(); + } + } else if (QR.nodes) { + if (Conf['Persistent QR']) { + QR.hide(); + } else { + QR.close(); + } + } + break; + case Conf['Spoiler tags']: + if (target.nodeName !== 'TEXTAREA') { + return; + } + Keybinds.tags('spoiler', target); + break; + case Conf['Code tags']: + if (target.nodeName !== 'TEXTAREA') { + return; + } + Keybinds.tags('code', target); + break; + case Conf['Eqn tags']: + if (target.nodeName !== 'TEXTAREA') { + return; + } + Keybinds.tags('eqn', target); + break; + case Conf['Math tags']: + if (target.nodeName !== 'TEXTAREA') { + return; + } + Keybinds.tags('math', target); + break; + case Conf['Toggle sage']: + if (QR.nodes) { + Keybinds.sage(); + } + break; + case Conf['Submit QR']: + if (QR.nodes && !QR.status()) { + QR.submit(); + } + break; + case Conf['Post Without Name']: + if (QR.nodes && !QR.status()) { + Keybinds.name(); + QR.submit(); + } + break; + case Conf['Update']: + switch (g.VIEW) { + case 'thread': + ThreadUpdater.update(); + break; + case 'index': + if (Conf['JSON Navigation']) { + Index.update(); + } + } + break; + case Conf['Watch']: + if (g.VIEW === 'catalog') { + return; + } + ThreadWatcher.toggle(thread); + break; + case Conf['Expand image']: + if (g.VIEW === 'catalog') { + return; + } + Keybinds.img(threadRoot); + break; + case Conf['Expand images']: + if (g.VIEW === 'catalog') { + return; + } + Keybinds.img(threadRoot, true); + break; + case Conf['Open Gallery']: + if (g.VIEW === 'catalog') { + return; + } + Gallery.cb.toggle(); + break; + case Conf['fappeTyme']: + if (g.VIEW === 'catalog') { + return; + } + FappeTyme.cb.toggle.call({ + name: 'fappe' + }); + break; + case Conf['werkTyme']: + if (g.VIEW === 'catalog') { + return; + } + FappeTyme.cb.toggle.call({ + name: 'werk' + }); + break; + case Conf['Front page']: + if (Conf['JSON Navigation'] && g.VIEW === 'index') { + Index.userPageNav(1); + } else { + window.location = "/" + g.BOARD + "/"; + } + break; + case Conf['Open front page']: + $.open("/" + g.BOARD + "/"); + break; + case Conf['Next page']: + if (g.VIEW !== 'index') { + return; + } + if (Conf['JSON Navigation']) { + if (Conf['Index Mode'] !== 'all pages') { + $('.next button', Index.pagelist).click(); + } + } else { + if (form = $('.next form')) { + window.location = form.action; + } + } + break; + case Conf['Previous page']: + if (g.VIEW !== 'index') { + return; + } + if (Conf['JSON Navigation']) { + if (Conf['Index Mode'] !== 'all pages') { + $('.prev button', Index.pagelist).click(); + } + } else { + if (form = $('.prev form')) { + window.location = form.action; + } + } + break; + case Conf['Search form']: + if (g.VIEW !== 'index') { + return; + } + searchInput = Conf['JSON Navigation'] ? Index.searchInput : $.id('search-box'); + Header.scrollToIfNeeded(searchInput); + searchInput.click(); + searchInput.focus(); + break; + case Conf['Paged mode']: + if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'paged')) { + return; + } + Index.setIndexMode('paged'); + break; + case Conf['All pages mode']: + if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'all pages')) { + return; + } + Index.setIndexMode('all pages'); + break; + case Conf['Catalog mode']: + if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'catalog')) { + return; + } + Index.setIndexMode('catalog'); + break; + case Conf['Cycle sort type']: + if (g.VIEW !== 'index') { + return; + } + Index.cycleSortType(); + break; + case Conf['Open catalog']: + if (Conf['External Catalog']) { + window.location = CatalogLinks.external(g.BOARD.ID); + } else { + if (!Conf['JSON Navigation']) { + return window.location = "/" + g.BOARD + "/catalog"; + } + if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'catalog')) { + return; + } + Index.setIndexMode('catalog'); + } + break; + case Conf['Next thread']: + if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { + return; + } + Nav.scroll(+1); + break; + case Conf['Previous thread']: + if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { + return; + } + Nav.scroll(-1); + break; + case Conf['Expand thread']: + if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { + return; + } + ExpandThread.toggle(thread); + break; + case Conf['Open thread']: + if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { + return; + } + Keybinds.open(thread); + break; + case Conf['Open thread tab']: + if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { + return; + } + Keybinds.open(thread, true); + break; + case Conf['Next reply']: + if (g.VIEW === 'catalog') { + return; + } + Keybinds.hl(+1, threadRoot); + break; + case Conf['Previous reply']: + if (g.VIEW === 'catalog') { + return; + } + Keybinds.hl(-1, threadRoot); + break; + case Conf['Deselect reply']: + if (g.VIEW === 'catalog') { + return; + } + Keybinds.hl(0, threadRoot); + break; + case Conf['Hide']: + PostHiding.toggle(thread.OP); + break; + case Conf['Previous Post Quoting You']: + QuoteMarkers.cb.seek('preceding'); + break; + case Conf['Next Post Quoting You']: + QuoteMarkers.cb.seek('following'); + break; + default: + return; + } + e.preventDefault(); + return e.stopPropagation(); + }, + keyCode: function(e) { + var kc, key; + key = (function() { + switch (kc = e.keyCode) { + case 8: + return ''; + case 13: + return 'Enter'; + case 27: + return 'Esc'; + case 37: + return 'Left'; + case 38: + return 'Up'; + case 39: + return 'Right'; + case 40: + return 'Down'; + default: + if ((48 <= kc && kc <= 57) || (65 <= kc && kc <= 90)) { + return String.fromCharCode(kc).toLowerCase(); + } else { + return null; + } + } + })(); + if (key) { + if (e.altKey) { + key = 'Alt+' + key; + } + if (e.ctrlKey) { + key = 'Ctrl+' + key; + } + if (e.metaKey) { + key = 'Meta+' + key; + } + if (e.shiftKey) { + key = 'Shift+' + key; + } + } + return key; + }, + qr: function(thread) { + if (!QR.postingIsEnabled) { + return; + } + QR.open(); + if (thread != null) { + QR.quote.call($('input', $('.post.highlight', thread) || thread)); + } + QR.nodes.com.focus(); + if (Conf['QR Shortcut']) { + return $.rmClass($('.qr-shortcut'), 'disabled'); + } + }, + tags: function(tag, ta) { + var range, selEnd, selStart, value; + value = ta.value; + selStart = ta.selectionStart; + selEnd = ta.selectionEnd; + ta.value = value.slice(0, selStart) + ("[" + tag + "]") + value.slice(selStart, selEnd) + ("[/" + tag + "]") + value.slice(selEnd); + range = ("[" + tag + "]").length + selEnd; + ta.setSelectionRange(range, range); + return $.event('input', null, ta); + }, + name: function() { + return QR.nodes.name.value = ''; + }, + sage: function() { + var isSage; + isSage = /sage/i.test(QR.nodes.email.value); + return QR.nodes.email.value = isSage ? "" : "sage"; + }, + img: function(thread, all) { + var post; + if (all) { + return ImageExpand.cb.toggleAll(); + } else { + post = Get.postFromNode($('.post.highlight', thread) || $('.op', thread)); + return ImageExpand.toggle(post); + } + }, + open: function(thread, tab) { + var url; + if (g.VIEW !== 'index') { + return; + } + url = Build.path(thread.board.ID, thread.ID); + if (tab) { + return $.open(url); + } else { + return location.href = url; + } + }, + hl: function(delta, thread) { + var axis, height, next, postEl, replies, reply, root, _i, _len; + postEl = $('.reply.highlight', thread); + if (!delta) { + if (postEl) { + $.rmClass(postEl, 'highlight'); + } + return; + } + if (postEl) { + height = postEl.getBoundingClientRect().height; + if (Header.getTopOf(postEl) >= -height && Header.getBottomOf(postEl) >= -height) { + root = postEl.parentNode; + axis = delta === +1 ? 'following' : 'preceding'; + if (!(next = $.x("" + axis + "-sibling::div[contains(@class,'replyContainer') and not(@hidden) and not(child::div[@class='stub'])][1]/child::div[contains(@class,'reply')]", root))) { + return; + } + Header.scrollToIfNeeded(next, delta === +1); + this.focus(next); + $.rmClass(postEl, 'highlight'); + return; + } + $.rmClass(postEl, 'highlight'); + } + replies = $$('.reply', thread); + if (delta === -1) { + replies.reverse(); + } + for (_i = 0, _len = replies.length; _i < _len; _i++) { + reply = replies[_i]; + if (delta === +1 && Header.getTopOf(reply) > 0 || delta === -1 && Header.getBottomOf(reply) > 0) { + this.focus(reply); + return; + } + } + }, + focus: function(post) { + return $.addClass(post, 'highlight'); + } + }; + + Nav = { + init: function() { + var next, prev; + switch (g.VIEW) { + case 'index': + if (!Conf['Index Navigation']) { + return; + } + break; + case 'thread': + if (!Conf['Reply Navigation']) { + return; + } + } + prev = $.el('a', { + href: 'javascript:;', + id: 'navPrev' + }); + next = $.el('a', { + href: 'javascript:;', + id: 'navNext' + }); + Header.addShortcut(prev, true); + Header.addShortcut(next, true); + $.on(prev, 'click', this.prev); + return $.on(next, 'click', this.next); + }, + prev: function() { + if (g.VIEW === 'thread') { + return window.scrollTo(0, 0); + } else { + return Nav.scroll(-1); + } + }, + next: function() { + if (g.VIEW === 'thread') { + return window.scrollTo(0, d.body.scrollHeight); + } else { + return Nav.scroll(+1); + } + }, + getThread: function() { + var threadRoot, _i, _len, _ref; + _ref = $$('.thread'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + threadRoot = _ref[_i]; + if (Header.getTopOf(threadRoot) >= -threadRoot.getBoundingClientRect().height) { + return threadRoot; + } + } + return $('.board'); + }, + scroll: function(delta) { + var axis, next, thread, top; + thread = Nav.getThread(); + axis = delta === +1 ? 'following' : 'preceding'; + if (next = $.x("" + axis + "-sibling::div[contains(@class,'thread') and not(@hidden)][1]", thread)) { + top = Header.getTopOf(thread); + if (delta === +1 && top < 5 || delta === -1 && top > -5) { + thread = next; + } + } + return Header.scrollTo(thread); + } + }; + + RelativeDates = { + INTERVAL: $.MINUTE / 2, + init: function() { + switch (g.VIEW) { + case 'index': + this.flush(); + $.on(d, 'visibilitychange', this.flush); + if (!Conf['Relative Post Dates']) { + return; + } + break; + case 'thread': + if (!Conf['Relative Post Dates']) { + return; + } + this.flush(); + $.on(d, 'visibilitychange ThreadUpdate', this.flush); + break; + default: + return; + } + return Post.callbacks.push({ + name: 'Relative Post Dates', + cb: this.node + }); + }, + node: function() { + var dateEl; + if (this.isClone) { + return; + } + dateEl = this.nodes.date; + dateEl.title = dateEl.textContent; + return RelativeDates.update(this); + }, + relative: function(diff, now, date) { + var days, months, number, rounded, unit, years; + unit = (number = diff / $.DAY) >= 1 ? (years = now.getYear() - date.getYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = (months + 12) % 12) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second'); + rounded = Math.round(number); + if (rounded !== 1) { + unit += 's'; + } + return "" + rounded + " " + unit + " ago"; + }, + stale: [], + flush: function() { + var data, now, _i, _len, _ref; + if (d.hidden) { + return; + } + now = new Date(); + _ref = RelativeDates.stale; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + data = _ref[_i]; + RelativeDates.update(data, now); + } + RelativeDates.stale = []; + clearTimeout(RelativeDates.timeout); + return RelativeDates.timeout = setTimeout(RelativeDates.flush, RelativeDates.INTERVAL); + }, + update: function(data, now) { + var date, diff, isPost, relative, singlePost, _i, _len, _ref; + isPost = data instanceof Post; + date = isPost ? data.info.date : new Date(+data.dataset.utc); + now || (now = new Date()); + diff = now - date; + relative = RelativeDates.relative(diff, now, date); + if (isPost) { + _ref = [data].concat(data.clones); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + singlePost = _ref[_i]; + singlePost.nodes.date.firstChild.textContent = relative; + } + } else { + data.firstChild.textContent = relative; + } + return RelativeDates.setOwnTimeout(diff, data); + }, + setOwnTimeout: function(diff, data) { + var delay; + delay = diff < $.MINUTE ? $.SECOND - (diff + $.SECOND / 2) % $.SECOND : diff < $.HOUR ? $.MINUTE - (diff + $.MINUTE / 2) % $.MINUTE : diff < $.DAY ? $.HOUR - (diff + $.HOUR / 2) % $.HOUR : $.DAY - (diff + $.DAY / 2) % $.DAY; + return setTimeout(RelativeDates.markStale, delay, data); + }, + markStale: function(data) { + if (__indexOf.call(RelativeDates.stale, data) >= 0) { + return; + } + if (data instanceof Post && !g.posts[data.fullID]) { + return; + } + return RelativeDates.stale.push(data); + } + }; + + RemoveSpoilers = { + init: function() { + if (Conf['Reveal Spoilers']) { + $.addClass(doc, 'reveal-spoilers'); + } + if (Conf['Remove Spoilers']) { + return $.addClass(doc, 'remove-spoilers'); + } + } + }; + + Report = { + init: function() { + if (!/report/.test(location.search)) { + return; + } + return $.asap((function() { + return $.id('recaptcha_response_field'); + }), Report.ready); + }, + ready: function() { + var field; + field = $.id('recaptcha_response_field'); + $.on(field, 'keydown', function(e) { + if (e.keyCode === 8 && !field.value) { + return $.globalEval('Recaptcha.reload("t")'); + } + }); + return $.on($('form'), 'submit', function(e) { + var response; + e.preventDefault(); + response = field.value.trim(); + if (!/\s|^\d+$/.test(response)) { + field.value = "" + response + " " + response; + } + return this.submit(); + }); + } + }; + + Time = { + init: function() { + if (!Conf['Time Formatting']) { + return; + } + return Post.callbacks.push({ + name: 'Time Formatting', + cb: this.node + }); + }, + node: function() { + if (this.isClone) { + return; + } + return this.nodes.date.textContent = Time.format(Conf['time'], this.info.date); + }, + format: function(formatString, date) { + return formatString.replace(/%([A-Za-z])/g, function(s, c) { + if (c in Time.formatters) { + return Time.formatters[c].call(date); + } else { + return s; + } + }); + }, + day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + zeroPad: function(n) { + if (n < 10) { + return "0" + n; + } else { + return n; + } + }, + formatters: { + a: function() { + return Time.day[this.getDay()].slice(0, 3); + }, + A: function() { + return Time.day[this.getDay()]; + }, + b: function() { + return Time.month[this.getMonth()].slice(0, 3); + }, + B: function() { + return Time.month[this.getMonth()]; + }, + d: function() { + return Time.zeroPad(this.getDate()); + }, + e: function() { + return this.getDate(); + }, + H: function() { + return Time.zeroPad(this.getHours()); + }, + I: function() { + return Time.zeroPad(this.getHours() % 12 || 12); + }, + k: function() { + return this.getHours(); + }, + l: function() { + return this.getHours() % 12 || 12; + }, + m: function() { + return Time.zeroPad(this.getMonth() + 1); + }, + M: function() { + return Time.zeroPad(this.getMinutes()); + }, + p: function() { + if (this.getHours() < 12) { + return 'AM'; + } else { + return 'PM'; + } + }, + P: function() { + if (this.getHours() < 12) { + return 'am'; + } else { + return 'pm'; + } + }, + S: function() { + return Time.zeroPad(this.getSeconds()); + }, + y: function() { + return this.getFullYear().toString().slice(2); + }, + Y: function() { + return this.getFullYear(); + } + } + }; Banner = { init: function() { @@ -15044,1380 +16476,6 @@ })(); - PSAHiding = { - init: function() { - if (!Conf['Announcement Hiding']) { - return; - } - $.addClass(doc, 'hide-announcement'); - return $.on(d, '4chanXInitFinished', this.setup); - }, - setup: function() { - var btn, entry, psa; - $.off(d, '4chanXInitFinished', PSAHiding.setup); - if (!(psa = $.id('globalMessage'))) { - return; - } - entry = { - el: $.el('a', { - textContent: 'Show announcement', - className: 'show-announcement', - href: 'javascript:;' - }), - order: 50, - open: function() { - return psa.hidden; - } - }; - Header.menu.addEntry(entry); - $.on(entry.el, 'click', PSAHiding.toggle); - PSAHiding.btn = btn = $.el('span', { - innerHTML: '[Dismiss]', - title: 'Mark announcement as read and hide.', - className: 'hide-announcement', - href: 'javascript:;', - textContent: '[ - ]' - }); - $.on(btn, 'click', PSAHiding.toggle); - $.get('hiddenPSA', 0, function(_arg) { - var hiddenPSA; - hiddenPSA = _arg.hiddenPSA; - PSAHiding.sync(hiddenPSA); - $.add(psa, btn); - return $.rmClass(doc, 'hide-announcement'); - }); - return $.sync('hiddenPSA', PSAHiding.sync); - }, - toggle: function(e) { - var UTC; - if ($.hasClass(this, 'hide-announcement')) { - UTC = +$.id('globalMessage').dataset.utc; - $.set('hiddenPSA', UTC); - } else { - $.event('CloseMenu'); - $["delete"]('hiddenPSA'); - } - return PSAHiding.sync(UTC); - }, - sync: function(UTC) { - var hr, psa; - psa = $.id('globalMessage'); - psa.hidden = PSAHiding.btn.hidden = UTC && UTC >= +psa.dataset.utc ? true : false; - if ((hr = psa.nextElementSibling) && hr.nodeName === 'HR') { - return hr.hidden = psa.hidden; - } - } - }; - - CatalogLinks = { - init: function() { - var el, input; - if (!Conf['Catalog Links']) { - return; - } - CatalogLinks.el = el = $.el('label', { - id: 'toggleCatalog', - href: 'javascript:;', - innerHTML: " Catalog Links" - }); - input = $('input', el); - $.on(input, 'change', this.toggle); - $.sync('Header catalog links', CatalogLinks.set); - Header.menu.addEntry({ - el: el, - order: 95 - }); - return $.on(d, '4chanXInitFinished', function() { - return CatalogLinks.set(Conf['Header catalog links']); - }); - }, - toggle: function() { - $.event('CloseMenu'); - $.set('Header catalog links', this.checked); - return CatalogLinks.set(this.checked); - }, - set: function(useCatalog) { - var a, board, generateURL, path, _i, _len, _ref, _ref1; - path = useCatalog ? 'catalog' : ''; - generateURL = useCatalog && Conf['External Catalog'] ? CatalogLinks.external : function(board) { - return a.href = "/" + board + "/" + path; - }; - _ref = $$("#board-list a:not(.catalog), #boardNavDesktopFoot a"); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - a = _ref[_i]; - if (((_ref1 = a.hostname) !== 'boards.4chan.org' && _ref1 !== 'catalog.neet.tv' && _ref1 !== '4index.gropes.us') || !(board = a.pathname.split('/')[1]) || (board === 'f' || board === 'status' || board === '4chan') || $.hasClass(a, 'external')) { - continue; - } - a.href = generateURL(board); - } - return CatalogLinks.el.title = "Turn catalog links " + (useCatalog ? 'off' : 'on') + "."; - }, - external: function(board) { - if (board === 'a' || board === 'c' || board === 'g' || board === 'co' || board === 'k' || board === 'm' || board === 'o' || board === 'p' || board === 'v' || board === 'vg' || board === 'w' || board === 'cm' || board === '3' || board === 'adv' || board === 'an' || board === 'cgl' || board === 'ck' || board === 'diy' || board === 'fa' || board === 'fit' || board === 'int' || board === 'jp' || board === 'mlp' || board === 'lit' || board === 'mu' || board === 'n' || board === 'po' || board === 'sci' || board === 'toy' || board === 'trv' || board === 'tv' || board === 'vp' || board === 'x' || board === 'q') { - return "http://catalog.neet.tv/" + board; - } else { - return "/" + board + "/catalog"; - } - } - }; - - CustomCSS = { - init: function() { - if (!Conf['Custom CSS']) { - return; - } - return this.addStyle(); - }, - addStyle: function() { - return this.style = $.addStyle(Conf['usercss'], 'CustomCSS'); - }, - rmStyle: function() { - if (this.style) { - $.rm(this.style); - return delete this.style; - } - }, - update: function() { - if (!this.style) { - return this.addStyle(); - } - return this.style.textContent = Conf['usercss']; - } - }; - - Dice = { - init: function() { - if (g.BOARD.ID !== 'tg' || !Conf['Show Dice Roll']) { - return; - } - return Post.callbacks.push({ - name: 'Show Dice Roll', - cb: this.node - }); - }, - node: function() { - var dicestats, roll, _ref; - if (this.isClone || !(dicestats = (_ref = this.info.email) != null ? _ref.match(/dice[+\s](\d+)d(\d+)/) : void 0)) { - return; - } - roll = $('b', this.nodes.comment).firstChild; - return roll.data = "Rolled " + dicestats[1] + "d" + dicestats[2] + ": " + (roll.data.slice(7)); - } - }; - - ExpandComment = { - init: function() { - if (g.VIEW !== 'index' || !Conf['Comment Expansion'] || Conf['JSON Navigation']) { - return; - } - if (g.BOARD.ID === 'g') { - this.callbacks.push(Fourchan.code); - } - if (g.BOARD.ID === 'sci') { - this.callbacks.push(Fourchan.math); - } - return Post.callbacks.push({ - name: 'Comment Expansion', - cb: this.node - }); - }, - node: function() { - var a; - if (a = $('.abbr > a:not([onclick])', this.nodes.comment)) { - return $.on(a, 'click', ExpandComment.cb); - } - }, - callbacks: [], - cb: function(e) { - e.preventDefault(); - return ExpandComment.expand(Get.postFromNode(this)); - }, - expand: function(post) { - var a; - if (post.nodes.longComment && !post.nodes.longComment.parentNode) { - $.replace(post.nodes.shortComment, post.nodes.longComment); - post.nodes.comment = post.nodes.longComment; - return; - } - if (!(a = $('.abbr > a', post.nodes.comment))) { - return; - } - a.textContent = "Post No." + post + " Loading..."; - return $.cache("//a.4cdn.org" + (a.pathname.split('/').splice(0, 4).join('/')) + ".json", function() { - return ExpandComment.parse(this, a, post); - }); - }, - contract: function(post) { - var a; - if (!post.nodes.shortComment) { - return; - } - a = $('.abbr > a', post.nodes.shortComment); - a.textContent = 'here'; - $.replace(post.nodes.longComment, post.nodes.shortComment); - return post.nodes.comment = post.nodes.shortComment; - }, - parse: function(req, a, post) { - var callback, clone, comment, href, postObj, posts, quote, spoilerRange, status, _i, _j, _k, _len, _len1, _len2, _ref, _ref1; - status = req.status; - if (status !== 200 && status !== 304) { - a.textContent = "Error " + req.statusText + " (" + status + ")"; - return; - } - posts = req.response.posts; - if (spoilerRange = posts[0].custom_spoiler) { - Build.spoilerRange[g.BOARD] = spoilerRange; - } - for (_i = 0, _len = posts.length; _i < _len; _i++) { - postObj = posts[_i]; - if (postObj.no === post.ID) { - break; - } - } - if (postObj.no !== post.ID) { - a.textContent = "Post No." + post + " not found."; - return; - } - comment = post.nodes.comment; - clone = comment.cloneNode(false); - clone.innerHTML = postObj.com; - _ref = $$('.quotelink', clone); - for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { - quote = _ref[_j]; - href = quote.getAttribute('href'); - if (href[0] === '/') { - continue; - } - if (href[0] === '#') { - quote.href = "" + (a.pathname.split('/').splice(0, 4).join('/')) + href; - } else { - quote.href = "" + (a.pathname.split('/').splice(0, 3).join('/')) + "/" + href; - } - } - post.nodes.shortComment = comment; - $.replace(comment, clone); - post.nodes.comment = post.nodes.longComment = clone; - post.parseComment(); - post.parseQuotes(); - _ref1 = ExpandComment.callbacks; - for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { - callback = _ref1[_k]; - callback.call(post); - } - } - }; - - ExpandThread = { - statuses: {}, - init: function() { - if (g.VIEW === 'thread' || !Conf['Thread Expansion']) { - return; - } - return $.on(d, (Conf['JSON Navigation'] ? 'IndexRefresh' : '4chanXInitFinished'), this.onIndexRefresh); - }, - setButton: function(thread) { - var a, summary; - if (!(summary = $.x('following-sibling::*[contains(@class,"summary")][1]', thread.OP.nodes.root))) { - return; - } - a = $.el('a', { - textContent: ExpandThread.text.apply(ExpandThread, ['+'].concat(__slice.call(summary.textContent.match(/\d+/g)))), - href: "res/" + thread.ID, - className: 'summary' - }); - $.on(a, 'click', ExpandThread.cbToggle); - return $.replace(summary, a); - }, - disconnect: function() { - this.refresh(); - return $.off(d, 'IndexRefresh', this.onIndexRefresh); - }, - refresh: function(disconnect) { - var status, threadID, _ref, _ref1; - if (g.VIEW === 'thread' || !Conf['Thread Expansion']) { - return; - } - _ref = ExpandThread.statuses; - for (threadID in _ref) { - status = _ref[threadID]; - if ((_ref1 = status.req) != null) { - _ref1.abort(); - } - delete ExpandThread.statuses[threadID]; - } - }, - onIndexRefresh: function() { - ExpandThread.refresh(); - return g.BOARD.threads.forEach(function(thread) { - return ExpandThread.setButton(thread); - }); - }, - text: function(status, posts, files) { - return ("" + status + " " + posts + " post" + (posts > 1 ? 's' : '')) + (+files ? " and " + files + " image repl" + (files > 1 ? 'ies' : 'y') : "") + (" " + (status === '-' ? 'shown' : 'omitted') + "."); - }, - cbToggle: function(e) { - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { - return; - } - e.preventDefault(); - return ExpandThread.toggle(Get.threadFromNode(this)); - }, - toggle: function(thread) { - var a, threadRoot; - threadRoot = thread.OP.nodes.root.parentNode; - if (!(a = $('.summary', threadRoot))) { - return; - } - if (thread.ID in ExpandThread.statuses) { - return ExpandThread.contract(thread, a, threadRoot); - } else { - return ExpandThread.expand(thread, a, threadRoot); - } - }, - expand: function(thread, a, threadRoot) { - var status; - ExpandThread.statuses[thread] = status = {}; - a.textContent = ExpandThread.text.apply(ExpandThread, ['...'].concat(__slice.call(a.textContent.match(/\d+/g)))); - return status.req = $.cache("//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() { - delete status.req; - return ExpandThread.parse(this, thread, a); - }); - }, - contract: function(thread, a, threadRoot) { - var filesCount, inlined, num, postsCount, replies, reply, status, _i, _len; - status = ExpandThread.statuses[thread]; - delete ExpandThread.statuses[thread]; - if (status.req) { - status.req.abort(); - if (a) { - a.textContent = ExpandThread.text.apply(ExpandThread, ['+'].concat(__slice.call(a.textContent.match(/\d+/g)))); - } - return; - } - replies = $$('.thread > .replyContainer', threadRoot); - if (Conf['Show Replies']) { - num = (function() { - if (thread.isSticky) { - return 1; - } else { - switch (g.BOARD.ID) { - case 'b': - case 'vg': - return 3; - case 't': - return 1; - default: - return 5; - } - } - })(); - replies = replies.slice(0, -num); - } - postsCount = 0; - filesCount = 0; - for (_i = 0, _len = replies.length; _i < _len; _i++) { - reply = replies[_i]; - if (Conf['Quote Inlining']) { - while (inlined = $('.inlined', reply)) { - inlined.click(); - } - } - postsCount++; - if ('file' in Get.postFromRoot(reply)) { - filesCount++; - } - $.rm(reply); - } - return a.textContent = ExpandThread.text('+', postsCount, filesCount); - }, - parse: function(req, thread, a) { - var filesCount, post, postData, posts, postsCount, postsRoot, root, _i, _len, _ref, _ref1; - if ((_ref = req.status) !== 200 && _ref !== 304) { - a.textContent = "Error " + req.statusText + " (" + req.status + ")"; - return; - } - Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler; - posts = []; - postsRoot = []; - filesCount = 0; - _ref1 = req.response.posts; - for (_i = 0, _len = _ref1.length; _i < _len; _i++) { - postData = _ref1[_i]; - if (postData.no === thread.ID) { - continue; - } - if (post = thread.posts[postData.no]) { - if ('file' in post) { - filesCount++; - } - postsRoot.push(post.nodes.root); - continue; - } - root = Build.postFromObject(postData, thread.board.ID); - post = new Post(root, thread, thread.board); - if ('file' in post) { - filesCount++; - } - posts.push(post); - postsRoot.push(root); - } - Post.callbacks.execute(posts); - $.after(a, postsRoot); - postsCount = postsRoot.length; - return a.textContent = ExpandThread.text('-', postsRoot.length, filesCount); - } - }; - - FileInfo = { - init: function() { - if (!Conf['File Info Formatting']) { - return; - } - return Post.callbacks.push({ - name: 'File Info Formatting', - cb: this.node - }); - }, - node: function() { - if (!this.file || this.isClone) { - return; - } - return this.file.text.innerHTML = "" + (FileInfo.format(Conf['fileInfo'], this)) + ""; - }, - format: function(formatString, post) { - return formatString.replace(/%([A-Za-z])/g, function(s, c) { - if (c in FileInfo.formatters) { - return FileInfo.formatters[c].call(post); - } else { - return s; - } - }); - }, - convertUnit: function(size, unit) { - var i; - if (unit === 'B') { - return "" + (size.toFixed()) + " Bytes"; - } - i = 1 + ['KB', 'MB'].indexOf(unit); - while (i--) { - size /= 1024; - } - size = unit === 'MB' ? Math.round(size * 100) / 100 : size.toFixed(); - return "" + size + " " + unit; - }, - escape: function(name) { - return name.replace(/<|>/g, function(c) { - return c === '<' && '<' || '>'; - }); - }, - formatters: { - t: function() { - return this.file.URL.match(/\d+\..+$/)[0]; - }, - T: function() { - return "" + (FileInfo.formatters.t.call(this)) + ""; - }, - l: function() { - return "" + (FileInfo.formatters.n.call(this)) + ""; - }, - L: function() { - return "" + (FileInfo.formatters.N.call(this)) + ""; - }, - n: function() { - var fullname, shortname; - fullname = this.file.name; - shortname = Build.shortFilename(this.file.name, this.isReply); - if (fullname === shortname) { - return FileInfo.escape(fullname); - } else { - return "" + (FileInfo.escape(shortname)) + "" + (FileInfo.escape(fullname)) + ""; - } - }, - N: function() { - return FileInfo.escape(this.file.name); - }, - p: function() { - if (this.file.isSpoiler) { - return 'Spoiler, '; - } else { - return ''; - } - }, - s: function() { - return this.file.size; - }, - B: function() { - return FileInfo.convertUnit(this.file.sizeInBytes, 'B'); - }, - K: function() { - return FileInfo.convertUnit(this.file.sizeInBytes, 'KB'); - }, - M: function() { - return FileInfo.convertUnit(this.file.sizeInBytes, 'MB'); - }, - r: function() { - return this.file.dimensions || 'PDF'; - } - } - }; - - Flash = { - init: function() { - if (g.BOARD.ID === 'f') { - return $.ready(Flash.initReady); - } - }, - initReady: function() { - var nav, sauceLink, swfName; - $.globalEval('SWFEmbed.init()'); - if (g.VIEW !== 'thread') { - return; - } - swfName = $('.fileText > a'); - nav = $('.navLinks.desktop'); - swfName = swfName.href.replace(/^(.*?)\/f\//g, ""); - sauceLink = $.el('a', { - textContent: 'Check Sauce on SWFCHAN', - href: "http://eye.swfchan.com/search/?q=" + swfName - }); - $.addClass(nav, 'swfSauce'); - $.rmClass(nav, 'navLinks'); - $.rmAll(nav); - return $.add(nav, [$.tn('['), sauceLink, $.tn(']')]); - } - }; - - Fourchan = { - init: function() { - var board; - board = g.BOARD.ID; - if (board === 'g') { - $.globalEval("window.addEventListener('prettyprint', function(e) {\n window.dispatchEvent(new CustomEvent('prettyprint:cb', {\n detail: prettyPrintOne(e.detail)\n }));\n}, false);"); - Post.callbacks.push({ - name: 'Parse /g/ code', - cb: this.code - }); - } - if (board === 'sci') { - $.globalEval("window.addEventListener('jsmath', function(e) {\n if (jsMath.loaded) {\n // process one post\n jsMath.ProcessBeforeShowing(document.getElementById(e.detail));\n } else {\n // load jsMath and process whole document\n jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);\n jsMath.Autoload.LoadJsMath();\n }\n}, false);"); - return Post.callbacks.push({ - name: 'Parse /sci/ math', - cb: this.math - }); - } - }, - code: function() { - var apply, pre, _i, _len, _ref; - if (this.isClone) { - return; - } - apply = function(e) { - return pre.innerHTML = e.detail; - }; - $.on(window, 'prettyprint:cb', apply); - _ref = $$('.prettyprint:not(.prettyprinted)', this.nodes.comment); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pre = _ref[_i]; - $.event('prettyprint', pre.innerHTML, window); - } - $.off(window, 'prettyprint:cb', apply); - }, - math: function() { - if (this.isClone || !$('.math', this.nodes.comment)) { - return; - } - return $.event('jsmath', this.nodes.post.id, window); - } - }; - - IDColor = { - init: function() { - if (!Conf['Color User IDs']) { - return; - } - this.ids = { - Heaven: [0, 0, 0, '#fff'] - }; - return Post.callbacks.push({ - name: 'Color User IDs', - cb: this.node - }); - }, - node: function() { - var rgb, span, uid; - if (this.isClone || !((uid = this.info.uniqueID) && (span = $('span.hand', this.nodes.uniqueID)))) { - return; - } - rgb = IDColor.ids[uid] || IDColor.compute(uid); - span.style.color = rgb[3]; - span.style.backgroundColor = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; - $.addClass(span, 'painted'); - return span.title = 'Highlight posts by this ID'; - }, - compute: function(uid) { - var hash, i, rgb; - i = 1; - hash = uid.charCodeAt(0); - while (i < 8) { - hash = (hash << 5) - hash + uid.charCodeAt(i++); - } - rgb = [(hash >> 24) & 0xFF, (hash >> 16) & 0xFF, (hash >> 8) & 0xFF]; - rgb.push((rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114) > 125 ? '#000' : '#fff'); - return this.ids[uid] = rgb; - } - }; - - Keybinds = { - init: function() { - var hotkey, init; - if (!Conf['Keybinds']) { - return; - } - for (hotkey in Conf.hotkeys) { - $.sync(hotkey, Keybinds.sync); - } - init = function() { - var node, _i, _len, _ref; - $.off(d, '4chanXInitFinished', init); - $.on(d, 'keydown', Keybinds.keydown); - _ref = $$('[accesskey]'); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - node = _ref[_i]; - node.removeAttribute('accesskey'); - } - }; - return $.on(d, '4chanXInitFinished', init); - }, - sync: function(key, hotkey) { - return Conf[hotkey] = key; - }, - keydown: function(e) { - var form, key, notification, notifications, op, searchInput, target, thread, threadRoot, _i, _len, _ref; - if (!(key = Keybinds.keyCode(e))) { - return; - } - target = e.target; - if (target.nodeName === 'EMBED') { - return; - } - if ((_ref = target.nodeName) === 'INPUT' || _ref === 'TEXTAREA') { - if (!/(Esc|Alt|Ctrl|Meta|Shift\+\w{2,})/.test(key)) { - return; - } - } - if (g.VIEW !== 'catalog') { - threadRoot = Nav.getThread(); - if (op = $('.op', threadRoot)) { - thread = Get.postFromNode(op).thread; - } - } - switch (key) { - case Conf['Toggle board list']: - if (Conf['Custom Board Navigation']) { - Header.toggleBoardList(); - } - break; - case Conf['Toggle header']: - Header.toggleBarVisibility(); - break; - case Conf['Open empty QR']: - Keybinds.qr(); - break; - case Conf['Open QR']: - if (g.VIEW === 'catalog') { - return; - } - Keybinds.qr(threadRoot); - break; - case Conf['Open settings']: - Settings.open(); - break; - case Conf['Close']: - if ($.id('fourchanx-settings')) { - Settings.close(); - } else if ((notifications = $$('.notification')).length) { - for (_i = 0, _len = notifications.length; _i < _len; _i++) { - notification = notifications[_i]; - $('.close', notification).click(); - } - } else if (QR.nodes) { - if (Conf['Persistent QR']) { - QR.hide(); - } else { - QR.close(); - } - } - break; - case Conf['Spoiler tags']: - if (target.nodeName !== 'TEXTAREA') { - return; - } - Keybinds.tags('spoiler', target); - break; - case Conf['Code tags']: - if (target.nodeName !== 'TEXTAREA') { - return; - } - Keybinds.tags('code', target); - break; - case Conf['Eqn tags']: - if (target.nodeName !== 'TEXTAREA') { - return; - } - Keybinds.tags('eqn', target); - break; - case Conf['Math tags']: - if (target.nodeName !== 'TEXTAREA') { - return; - } - Keybinds.tags('math', target); - break; - case Conf['Toggle sage']: - if (QR.nodes) { - Keybinds.sage(); - } - break; - case Conf['Submit QR']: - if (QR.nodes && !QR.status()) { - QR.submit(); - } - break; - case Conf['Post Without Name']: - if (QR.nodes && !QR.status()) { - Keybinds.name(); - QR.submit(); - } - break; - case Conf['Update']: - switch (g.VIEW) { - case 'thread': - ThreadUpdater.update(); - break; - case 'index': - if (Conf['JSON Navigation']) { - Index.update(); - } - } - break; - case Conf['Watch']: - if (g.VIEW === 'catalog') { - return; - } - ThreadWatcher.toggle(thread); - break; - case Conf['Expand image']: - if (g.VIEW === 'catalog') { - return; - } - Keybinds.img(threadRoot); - break; - case Conf['Expand images']: - if (g.VIEW === 'catalog') { - return; - } - Keybinds.img(threadRoot, true); - break; - case Conf['Open Gallery']: - if (g.VIEW === 'catalog') { - return; - } - Gallery.cb.toggle(); - break; - case Conf['fappeTyme']: - if (g.VIEW === 'catalog') { - return; - } - FappeTyme.cb.toggle.call({ - name: 'fappe' - }); - break; - case Conf['werkTyme']: - if (g.VIEW === 'catalog') { - return; - } - FappeTyme.cb.toggle.call({ - name: 'werk' - }); - break; - case Conf['Front page']: - if (Conf['JSON Navigation'] && g.VIEW === 'index') { - Index.userPageNav(1); - } else { - window.location = "/" + g.BOARD + "/"; - } - break; - case Conf['Open front page']: - $.open("/" + g.BOARD + "/"); - break; - case Conf['Next page']: - if (g.VIEW !== 'index') { - return; - } - if (Conf['JSON Navigation']) { - if (Conf['Index Mode'] !== 'all pages') { - $('.next button', Index.pagelist).click(); - } - } else { - if (form = $('.next form')) { - window.location = form.action; - } - } - break; - case Conf['Previous page']: - if (g.VIEW !== 'index') { - return; - } - if (Conf['JSON Navigation']) { - if (Conf['Index Mode'] !== 'all pages') { - $('.prev button', Index.pagelist).click(); - } - } else { - if (form = $('.prev form')) { - window.location = form.action; - } - } - break; - case Conf['Search form']: - if (g.VIEW !== 'index') { - return; - } - searchInput = Conf['JSON Navigation'] ? Index.searchInput : $.id('search-box'); - Header.scrollToIfNeeded(searchInput); - searchInput.click(); - searchInput.focus(); - break; - case Conf['Paged mode']: - if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'paged')) { - return; - } - Index.setIndexMode('paged'); - break; - case Conf['All pages mode']: - if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'all pages')) { - return; - } - Index.setIndexMode('all pages'); - break; - case Conf['Catalog mode']: - if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'catalog')) { - return; - } - Index.setIndexMode('catalog'); - break; - case Conf['Cycle sort type']: - if (g.VIEW !== 'index') { - return; - } - Index.cycleSortType(); - break; - case Conf['Open catalog']: - if (Conf['External Catalog']) { - window.location = CatalogLinks.external(g.BOARD.ID); - } else { - if (!Conf['JSON Navigation']) { - return window.location = "/" + g.BOARD + "/catalog"; - } - if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'catalog')) { - return; - } - Index.setIndexMode('catalog'); - } - break; - case Conf['Next thread']: - if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { - return; - } - Nav.scroll(+1); - break; - case Conf['Previous thread']: - if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { - return; - } - Nav.scroll(-1); - break; - case Conf['Expand thread']: - if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { - return; - } - ExpandThread.toggle(thread); - break; - case Conf['Open thread']: - if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { - return; - } - Keybinds.open(thread); - break; - case Conf['Open thread tab']: - if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { - return; - } - Keybinds.open(thread, true); - break; - case Conf['Next reply']: - if (g.VIEW === 'catalog') { - return; - } - Keybinds.hl(+1, threadRoot); - break; - case Conf['Previous reply']: - if (g.VIEW === 'catalog') { - return; - } - Keybinds.hl(-1, threadRoot); - break; - case Conf['Deselect reply']: - if (g.VIEW === 'catalog') { - return; - } - Keybinds.hl(0, threadRoot); - break; - case Conf['Hide']: - PostHiding.toggle(thread.OP); - break; - case Conf['Previous Post Quoting You']: - QuoteMarkers.cb.seek('preceding'); - break; - case Conf['Next Post Quoting You']: - QuoteMarkers.cb.seek('following'); - break; - default: - return; - } - e.preventDefault(); - return e.stopPropagation(); - }, - keyCode: function(e) { - var kc, key; - key = (function() { - switch (kc = e.keyCode) { - case 8: - return ''; - case 13: - return 'Enter'; - case 27: - return 'Esc'; - case 37: - return 'Left'; - case 38: - return 'Up'; - case 39: - return 'Right'; - case 40: - return 'Down'; - default: - if ((48 <= kc && kc <= 57) || (65 <= kc && kc <= 90)) { - return String.fromCharCode(kc).toLowerCase(); - } else { - return null; - } - } - })(); - if (key) { - if (e.altKey) { - key = 'Alt+' + key; - } - if (e.ctrlKey) { - key = 'Ctrl+' + key; - } - if (e.metaKey) { - key = 'Meta+' + key; - } - if (e.shiftKey) { - key = 'Shift+' + key; - } - } - return key; - }, - qr: function(thread) { - if (!QR.postingIsEnabled) { - return; - } - QR.open(); - if (thread != null) { - QR.quote.call($('input', $('.post.highlight', thread) || thread)); - } - QR.nodes.com.focus(); - if (Conf['QR Shortcut']) { - return $.rmClass($('.qr-shortcut'), 'disabled'); - } - }, - tags: function(tag, ta) { - var range, selEnd, selStart, value; - value = ta.value; - selStart = ta.selectionStart; - selEnd = ta.selectionEnd; - ta.value = value.slice(0, selStart) + ("[" + tag + "]") + value.slice(selStart, selEnd) + ("[/" + tag + "]") + value.slice(selEnd); - range = ("[" + tag + "]").length + selEnd; - ta.setSelectionRange(range, range); - return $.event('input', null, ta); - }, - name: function() { - return QR.nodes.name.value = ''; - }, - sage: function() { - var isSage; - isSage = /sage/i.test(QR.nodes.email.value); - return QR.nodes.email.value = isSage ? "" : "sage"; - }, - img: function(thread, all) { - var post; - if (all) { - return ImageExpand.cb.toggleAll(); - } else { - post = Get.postFromNode($('.post.highlight', thread) || $('.op', thread)); - return ImageExpand.toggle(post); - } - }, - open: function(thread, tab) { - var url; - if (g.VIEW !== 'index') { - return; - } - url = Build.path(thread.board.ID, thread.ID); - if (tab) { - return $.open(url); - } else { - return location.href = url; - } - }, - hl: function(delta, thread) { - var axis, height, next, postEl, replies, reply, root, _i, _len; - postEl = $('.reply.highlight', thread); - if (!delta) { - if (postEl) { - $.rmClass(postEl, 'highlight'); - } - return; - } - if (postEl) { - height = postEl.getBoundingClientRect().height; - if (Header.getTopOf(postEl) >= -height && Header.getBottomOf(postEl) >= -height) { - root = postEl.parentNode; - axis = delta === +1 ? 'following' : 'preceding'; - if (!(next = $.x("" + axis + "-sibling::div[contains(@class,'replyContainer') and not(@hidden) and not(child::div[@class='stub'])][1]/child::div[contains(@class,'reply')]", root))) { - return; - } - Header.scrollToIfNeeded(next, delta === +1); - this.focus(next); - $.rmClass(postEl, 'highlight'); - return; - } - $.rmClass(postEl, 'highlight'); - } - replies = $$('.reply', thread); - if (delta === -1) { - replies.reverse(); - } - for (_i = 0, _len = replies.length; _i < _len; _i++) { - reply = replies[_i]; - if (delta === +1 && Header.getTopOf(reply) > 0 || delta === -1 && Header.getBottomOf(reply) > 0) { - this.focus(reply); - return; - } - } - }, - focus: function(post) { - return $.addClass(post, 'highlight'); - } - }; - - Nav = { - init: function() { - var next, prev; - switch (g.VIEW) { - case 'index': - if (!Conf['Index Navigation']) { - return; - } - break; - case 'thread': - if (!Conf['Reply Navigation']) { - return; - } - } - prev = $.el('a', { - href: 'javascript:;', - id: 'navPrev' - }); - next = $.el('a', { - href: 'javascript:;', - id: 'navNext' - }); - Header.addShortcut(prev, true); - Header.addShortcut(next, true); - $.on(prev, 'click', this.prev); - return $.on(next, 'click', this.next); - }, - prev: function() { - if (g.VIEW === 'thread') { - return window.scrollTo(0, 0); - } else { - return Nav.scroll(-1); - } - }, - next: function() { - if (g.VIEW === 'thread') { - return window.scrollTo(0, d.body.scrollHeight); - } else { - return Nav.scroll(+1); - } - }, - getThread: function() { - var threadRoot, _i, _len, _ref; - _ref = $$('.thread'); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - threadRoot = _ref[_i]; - if (Header.getTopOf(threadRoot) >= -threadRoot.getBoundingClientRect().height) { - return threadRoot; - } - } - return $('.board'); - }, - scroll: function(delta) { - var axis, next, thread, top; - thread = Nav.getThread(); - axis = delta === +1 ? 'following' : 'preceding'; - if (next = $.x("" + axis + "-sibling::div[contains(@class,'thread') and not(@hidden)][1]", thread)) { - top = Header.getTopOf(thread); - if (delta === +1 && top < 5 || delta === -1 && top > -5) { - thread = next; - } - } - return Header.scrollTo(thread); - } - }; - - RelativeDates = { - INTERVAL: $.MINUTE / 2, - init: function() { - switch (g.VIEW) { - case 'index': - this.flush(); - $.on(d, 'visibilitychange', this.flush); - if (!Conf['Relative Post Dates']) { - return; - } - break; - case 'thread': - if (!Conf['Relative Post Dates']) { - return; - } - this.flush(); - $.on(d, 'visibilitychange ThreadUpdate', this.flush); - break; - default: - return; - } - return Post.callbacks.push({ - name: 'Relative Post Dates', - cb: this.node - }); - }, - node: function() { - var dateEl; - if (this.isClone) { - return; - } - dateEl = this.nodes.date; - dateEl.title = dateEl.textContent; - return RelativeDates.update(this); - }, - relative: function(diff, now, date) { - var days, months, number, rounded, unit, years; - unit = (number = diff / $.DAY) >= 1 ? (years = now.getYear() - date.getYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = (months + 12) % 12) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second'); - rounded = Math.round(number); - if (rounded !== 1) { - unit += 's'; - } - return "" + rounded + " " + unit + " ago"; - }, - stale: [], - flush: function() { - var data, now, _i, _len, _ref; - if (d.hidden) { - return; - } - now = new Date(); - _ref = RelativeDates.stale; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - data = _ref[_i]; - RelativeDates.update(data, now); - } - RelativeDates.stale = []; - clearTimeout(RelativeDates.timeout); - return RelativeDates.timeout = setTimeout(RelativeDates.flush, RelativeDates.INTERVAL); - }, - update: function(data, now) { - var date, diff, isPost, relative, singlePost, _i, _len, _ref; - isPost = data instanceof Post; - date = isPost ? data.info.date : new Date(+data.dataset.utc); - now || (now = new Date()); - diff = now - date; - relative = RelativeDates.relative(diff, now, date); - if (isPost) { - _ref = [data].concat(data.clones); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - singlePost = _ref[_i]; - singlePost.nodes.date.firstChild.textContent = relative; - } - } else { - data.firstChild.textContent = relative; - } - return RelativeDates.setOwnTimeout(diff, data); - }, - setOwnTimeout: function(diff, data) { - var delay; - delay = diff < $.MINUTE ? $.SECOND - (diff + $.SECOND / 2) % $.SECOND : diff < $.HOUR ? $.MINUTE - (diff + $.MINUTE / 2) % $.MINUTE : diff < $.DAY ? $.HOUR - (diff + $.HOUR / 2) % $.HOUR : $.DAY - (diff + $.DAY / 2) % $.DAY; - return setTimeout(RelativeDates.markStale, delay, data); - }, - markStale: function(data) { - if (__indexOf.call(RelativeDates.stale, data) >= 0) { - return; - } - if (data instanceof Post && !g.posts[data.fullID]) { - return; - } - return RelativeDates.stale.push(data); - } - }; - - RemoveSpoilers = { - init: function() { - if (Conf['Reveal Spoilers']) { - $.addClass(doc, 'reveal-spoilers'); - } - if (Conf['Remove Spoilers']) { - return $.addClass(doc, 'remove-spoilers'); - } - } - }; - - Report = { - init: function() { - if (!/report/.test(location.search)) { - return; - } - return $.asap((function() { - return $.id('recaptcha_response_field'); - }), Report.ready); - }, - ready: function() { - var field; - field = $.id('recaptcha_response_field'); - $.on(field, 'keydown', function(e) { - if (e.keyCode === 8 && !field.value) { - return $.globalEval('Recaptcha.reload("t")'); - } - }); - return $.on($('form'), 'submit', function(e) { - var response; - e.preventDefault(); - response = field.value.trim(); - if (!/\s|^\d+$/.test(response)) { - field.value = "" + response + " " + response; - } - return this.submit(); - }); - } - }; - - Time = { - init: function() { - if (!Conf['Time Formatting']) { - return; - } - return Post.callbacks.push({ - name: 'Time Formatting', - cb: this.node - }); - }, - node: function() { - if (this.isClone) { - return; - } - return this.nodes.date.textContent = Time.format(Conf['time'], this.info.date); - }, - format: function(formatString, date) { - return formatString.replace(/%([A-Za-z])/g, function(s, c) { - if (c in Time.formatters) { - return Time.formatters[c].call(date); - } else { - return s; - } - }); - }, - day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], - month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], - zeroPad: function(n) { - if (n < 10) { - return "0" + n; - } else { - return n; - } - }, - formatters: { - a: function() { - return Time.day[this.getDay()].slice(0, 3); - }, - A: function() { - return Time.day[this.getDay()]; - }, - b: function() { - return Time.month[this.getMonth()].slice(0, 3); - }, - B: function() { - return Time.month[this.getMonth()]; - }, - d: function() { - return Time.zeroPad(this.getDate()); - }, - e: function() { - return this.getDate(); - }, - H: function() { - return Time.zeroPad(this.getHours()); - }, - I: function() { - return Time.zeroPad(this.getHours() % 12 || 12); - }, - k: function() { - return this.getHours(); - }, - l: function() { - return this.getHours() % 12 || 12; - }, - m: function() { - return Time.zeroPad(this.getMonth() + 1); - }, - M: function() { - return Time.zeroPad(this.getMinutes()); - }, - p: function() { - if (this.getHours() < 12) { - return 'AM'; - } else { - return 'PM'; - } - }, - P: function() { - if (this.getHours() < 12) { - return 'am'; - } else { - return 'pm'; - } - }, - S: function() { - return Time.zeroPad(this.getSeconds()); - }, - y: function() { - return this.getFullYear().toString().slice(2); - }, - Y: function() { - return this.getFullYear(); - } - } - }; - Navigate = { path: window.location.pathname, init: function() { @@ -16910,7 +16968,9 @@ Settings.dialog = dialog = $.el('div', { id: 'appchanx-settings', "class": 'dialog', - innerHTML: "
" + innerHTML: { + innerHTML: "\r
\r
\r" + } }); Settings.overlay = overlay = $.el('div', { id: 'overlay' @@ -17102,7 +17162,9 @@ }, filter: function(section) { var select; - section.innerHTML = "
"; + section.innerHTML = { + innerHTML: "\r
" + }; select = $('select', section); $.on(select, 'change', Settings.selectFilter); return Settings.selectFilter.call(select); @@ -17124,11 +17186,15 @@ $.add(div, ta); return; } - return div.innerHTML = "
Filter is disabled.

Use regular expressions, one per line.
Lines starting with a # will be ignored.
For example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
MD5 filtering uses exact string matching, not regular expressions.

"; + return div.innerHTML = { + innerHTML: "
Filter is disabled.
\r

\rUse regular expressions, one per line.
\rLines starting with a # will be ignored.
\rFor example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
\rMD5 filtering uses exact string matching, not regular expressions.\r

\r\r" + }; }, sauce: function(section) { var ta; - section.innerHTML = "
Sauce is disabled.
Lines starting with a # will be ignored.
You can specify a display text by appending ;text:[text] to the URL.
"; + section.innerHTML = { + innerHTML: "
Sauce is disabled.
\r
Lines starting with a # will be ignored.
\r
You can specify a display text by appending ;text:[text] to the URL.
\r\r\r" + }; ta = $('textarea', section); $.get('sauces', Conf['sauces'], function(item) { return ta.value = item['sauces'].replace(/\$\d/g, function(c) { @@ -17150,7 +17216,9 @@ }, advanced: function(section) { var archBoards, boardID, boardOptions, boardSelect, boards, event, files, i, input, inputs, item, items, name, o, row, rows, software, ta, table, withCredentials, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _m, _n, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; - section.innerHTML = "
Archiver
404 Redirect is disabled.
Thread redirectionPost fetchingFile redirection
Disabled selections indicate that only one archive is available for that board and redirection type.
Custom Board Navigation
New lines will be converted into spaces.

In the following examples for /g/, g can be changed to a different board ID (a, b, etc...), the current board (current), or the Twitter link (@).
Board link: g
Title link: g-title
Board link (Replace with title when on that board): g-replace
Full text link: g-full
Custom text link: g-text:\"Install Gentoo\"
External link: external-text:\"Google\",\"http://www.google.com\"
Index mode: g-mode:\"type\" where type is paged, all threads or catalog
Index sort: g-sort:\"type\" where type is bump order, last reply, creation date, reply count or file countCombinations are possible: g-text:\"VIP Catalog\"-mode:\"catalog\"-sort:\"creation date\"
Full board list toggle: toggle-all

[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h-mode:\"catalog\"-sort:\"file count\"] [t-text:\"Piracy\"]
will give you
[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
if you are on /g/.
Time Formatting is disabled.
:
Supported format specifiers:
Day: %a, %A, %d, %e
Month: %m, %b, %B
Year: %y, %Y
Hour: %k, %H, %l, %I, %p, %P
Minute: %M
Second: %S
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Quick Reply Personas

One item per line.
Items will be added in the relevant input's auto-completion list.
Password items will always be used, since there is no password input.
Lines starting with a # will be ignored.

Unread Favicon is disabled.
Thread Updater is disabled.
Interval:
Custom CSS
"; + section.innerHTML = { + innerHTML: "
\rArchiver\r
404 Redirect is disabled.
\r
\r\r\r\r\r\r\r\r
Thread redirectionPost fetchingFile redirection
\rDisabled selections indicate that only one archive is available for that board and redirection type.\r
\r
\rCustom Board Navigation\r
\rNew lines will be converted into spaces.

\r
In the following examples for /g/, g can be changed to a different board ID (a, b, etc...), the current board (current), or the Twitter link (@).
\r
Board link: g
\r
Title link: g-title
\r
Board link (Replace with title when on that board): g-replace
\r
Full text link: g-full
\r
Custom text link: g-text:\"Install Gentoo\"
\r
External link: external-text:\"Google\",\"http://www.google.com\"
\r
Index mode: g-mode:\"type\" where type is paged, all threads or catalog
\r
Index sort: g-sort:\"type\" where type is bump order, last reply, creation date, reply count or file countCombinations are possible: g-text:\"VIP Catalog\"-mode:\"catalog\"-sort:\"creation date\"
\r
Full board list toggle: toggle-all
\r
\r
\r[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h-mode:\"catalog\"-sort:\"file count\"] [t-text:\"Piracy\"]
\rwill give you
\r[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
\rif you are on /g/.\r
\r
\r
\rTime Formatting is disabled.\r
:
\r
Supported format specifiers:
\r
Day: %a, %A, %d, %e
\r
Month: %m, %b, %B
\r
Year: %y, %Y
\r
Hour: %k, %H, %l, %I, %p, %P
\r
Minute: %M
\r
Second: %S
\r
\r
\rQuote Backlinks formatting is disabled.\r
:
\r
\r
\rFile Info Formatting is disabled.\r
:
\r
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
\r
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
\r
Spoiler indicator: %p
\r
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
\r
Resolution: %r (Displays 'PDF' for PDF files)
\r
\r
\rQuick Reply Personas\r\r

\rOne item per line.
\rItems will be added in the relevant input's auto-completion list.
\rPassword items will always be used, since there is no password input.
\rLines starting with a # will be ignored.\r

\r\r
\r
\rUnread Favicon is disabled.\r\r\r
\r
\rThread Updater is disabled.\r
\rInterval: \r
\r
\r
\r Custom CSS\r
\r\r\r
\r
\r" + }; items = {}; inputs = {}; _ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss']; @@ -17345,7 +17413,9 @@ }, keybinds: function(section) { var arr, input, inputs, items, key, tbody, tr, _ref; - section.innerHTML = "
Keybinds are disabled.
Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
Press Backspace to disable a keybind.
ActionsKeybinds
"; + section.innerHTML = { + innerHTML: "
Keybinds are disabled.
\r
Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
\r
Press Backspace to disable a keybind.
\r\r\r
ActionsKeybinds
" + }; tbody = $('tbody', section); items = {}; inputs = {}; diff --git a/builds/crx.crx b/builds/crx.crx deleted file mode 100644 index a908dabcc..000000000 Binary files a/builds/crx.crx and /dev/null differ diff --git a/builds/crx/manifest.json b/builds/crx/manifest.json index df7271072..4e297c4f4 100644 --- a/builds/crx/manifest.json +++ b/builds/crx/manifest.json @@ -15,7 +15,7 @@ "run_at": "document_start" }], "homepage_url": "http://zixaphir.github.com/appchan-x/", - "minimum_chrome_version": "33", + "minimum_chrome_version": "32", "permissions": [ "storage", "http://*/", diff --git a/builds/crx/script.js b/builds/crx/script.js index d43f1470c..52f8afc28 100644 --- a/builds/crx/script.js +++ b/builds/crx/script.js @@ -88,7 +88,7 @@ 'use strict'; (function() { - var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, editMascot, editTheme, g, userNavigation, + var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CrossOrigin, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, editMascot, editTheme, g, userNavigation, __slice = [].slice, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __hasProp = {}.hasOwnProperty, @@ -4852,11 +4852,15 @@ this.pagelist = $.el('div', { className: 'pagelist', hidden: true, - innerHTML: "
<
>
" + innerHTML: { + innerHTML: "
\r\r<\r\r
\r
\r
\r\r>\r\r
" + } }); this.navLinks = $.el('div', { className: 'navLinks', - innerHTML: "\uf05c " + innerHTML: { + innerHTML: "\r\r\\uf05c\r \r\r\r\r\r\r\r\r" + } }); this.timeEl = $('time#index-last-refresh', this.navLinks); this.searchInput = $('#index-search', this.navLinks); @@ -6078,7 +6082,9 @@ comment = thread.OP.nodes.comment.innerHTML.replace(/(
\s*){2,}/g, '
'); root = $.el('div', { className: 'catalog-thread', - innerHTML: "
" + postCount + " / " + fileCount + " / " + pageCount + "
" + subject + "
" + comment + "
" + innerHTML: { + innerHTML: "\r
\r" + postCount + " / " + fileCount + " / " + pageCount + "\r\r
\r" + subject + "\r
" + comment + "
\r" + } }); root.dataset.fullID = thread.fullID; if (thread.isPinned) { @@ -6814,11 +6820,120 @@ }; })(); + CrossOrigin = (function() { + var eventPageRequest; + eventPageRequest = (function() { + var callbacks; + callbacks = []; + chrome.runtime.onMessage.addListener(function(data) { + callbacks[data.id](data); + return delete callbacks[data.id]; + }); + return function(url, responseType, cb) { + return chrome.runtime.sendMessage({ + url: url, + responseType: responseType + }, function(id) { + return callbacks[id] = cb; + }); + }; + })(); + return { + file: (function() { + var makeBlob; + makeBlob = function(urlBlob, contentType, contentDisposition, url) { + var blob, match, mime, name, _ref, _ref1, _ref2; + name = (_ref = url.match(/([^\/]+)\/*$/)) != null ? _ref[1] : void 0; + mime = (contentType != null ? contentType.match(/[^;]*/)[0] : void 0) || 'application/octet-stream'; + match = (contentDisposition != null ? (_ref1 = contentDisposition.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)) != null ? _ref1[1] : void 0 : void 0) || (contentType != null ? (_ref2 = contentType.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)) != null ? _ref2[1] : void 0 : void 0); + if (match) { + name = match.replace(/\\"/g, '"'); + } + blob = new Blob([urlBlob], { + type: mime + }); + blob.name = name; + return blob; + }; + return function(url, cb) { + if (/^https:\/\//.test(url) || location.protocol === 'http:') { + return $.ajax(url, { + responseType: 'blob', + onload: function() { + var contentDisposition, contentType; + if (!(this.readyState === this.DONE && this.status === 200)) { + return cb(null); + } + contentType = this.getResponseHeader('Content-Type'); + contentDisposition = this.getResponseHeader('Content-Disposition'); + return cb(makeBlob(this.response, contentType, contentDisposition, url)); + }, + onerror: function() { + return cb(null); + } + }); + } else { + return eventPageRequest(url, 'arraybuffer', function(_arg) { + var contentDisposition, contentType, error, response; + response = _arg.response, contentType = _arg.contentType, contentDisposition = _arg.contentDisposition, error = _arg.error; + if (error) { + return cb(null); + } + return cb(makeBlob(new Uint8Array(response), contentType, contentDisposition, url)); + }); + } + }; + })(), + json: (function() { + var callbacks, responses; + callbacks = {}; + responses = {}; + return function(url, cb) { + if (/^https:\/\//.test(url) || location.protocol === 'http:') { + return $.cache(url, (function() { + return cb(this.response); + }), { + responseType: 'json' + }); + } + if (responses[url]) { + cb(responses[url]); + return; + } + if (callbacks[url]) { + callbacks[url].push(cb); + return; + } + callbacks[url] = [cb]; + return eventPageRequest(url, 'json', function(_arg) { + var error, response, _i, _len, _ref; + response = _arg.response, error = _arg.error; + if (error) { + return delete callbacks[url]; + } else { + _ref = callbacks[url]; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + cb = _ref[_i]; + cb(response); + } + delete callbacks[url]; + return responses[url] = response; + } + }); + }; + })() + }; + })(); + Anonymize = { init: function() { - if (!Conf['Anonymize']) { + var _ref; + if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread' || _ref === 'archive') && Conf['Anonymize'])) { return; } + if (g.VIEW === 'archive') { + return this.archive(); + } return Post.callbacks.push({ name: 'Anonymize', cb: this.node @@ -6841,14 +6956,31 @@ $.replace(email, name); return delete this.nodes.email; } + }, + archive: function() { + return $.ready(function() { + var name, trip, _i, _j, _len, _len1, _ref, _ref1, _results; + _ref = $$('.name'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + name = _ref[_i]; + name.textContent = 'Anonymous'; + } + _ref1 = $$('.postertrip'); + _results = []; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + trip = _ref1[_j]; + _results.push($.rm(trip)); + } + return _results; + }); } }; Filter = { filters: {}, init: function() { - var boards, err, filter, hl, key, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; - if (!Conf['Filter']) { + var boards, err, filter, hl, key, line, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _ref6; + if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Filter'])) { return; } if (!Conf['Filtered Backlinks']) { @@ -6856,18 +6988,18 @@ } for (key in Config.filter) { this.filters[key] = []; - _ref = Conf[key].split('\n'); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - filter = _ref[_i]; - if (filter[0] === '#') { + _ref1 = Conf[key].split('\n'); + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + line = _ref1[_i]; + if (line[0] === '#') { continue; } - if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) { + if (!(regexp = line.match(/\/(.+)\/(\w*)/))) { continue; } - filter = filter.replace(regexp[0], ''); - boards = ((_ref1 = filter.match(/boards:([^;]+)/)) != null ? _ref1[1].toLowerCase() : void 0) || 'global'; - if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) { + filter = line.replace(regexp[0], ''); + boards = ((_ref2 = filter.match(/boards:([^;]+)/)) != null ? _ref2[1].toLowerCase() : void 0) || 'global'; + if (boards !== 'global' && (_ref3 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref3) < 0)) { continue; } if (key === 'uniqueID' || key === 'MD5') { @@ -6877,14 +7009,14 @@ regexp = RegExp(regexp[1], regexp[2]); } catch (_error) { err = _error; - new Notice('warning', err.message, 60); + new Notice('warning', [$.tn(("Invalid " + key + " filter: ") + line, $.el('br')), $.tn(err.message)], 60); continue; } } - op = ((_ref3 = filter.match(/[^t]op:(yes|no|only)/)) != null ? _ref3[1] : void 0) || 'yes'; + op = ((_ref4 = filter.match(/[^t]op:(yes|no|only)/)) != null ? _ref4[1] : void 0) || 'yes'; stub = (function() { - var _ref4; - switch ((_ref4 = filter.match(/stub:(yes|no)/)) != null ? _ref4[1] : void 0) { + var _ref5; + switch ((_ref5 = filter.match(/stub:(yes|no)/)) != null ? _ref5[1] : void 0) { case 'yes': return true; case 'no': @@ -6894,19 +7026,11 @@ } })(); if (hl = /highlight/.test(filter)) { - hl = ((_ref4 = filter.match(/highlight:(\w+)/)) != null ? _ref4[1] : void 0) || 'filter-highlight'; - top = ((_ref5 = filter.match(/top:(yes|no)/)) != null ? _ref5[1] : void 0) || 'yes'; + hl = ((_ref5 = filter.match(/highlight:(\w+)/)) != null ? _ref5[1] : void 0) || 'filter-highlight'; + top = ((_ref6 = filter.match(/top:(yes|no)/)) != null ? _ref6[1] : void 0) || 'yes'; top = top === 'yes'; } - this.filters[key].push({ - hide: !hl, - op: op, - stub: stub, - "class": hl, - top: top, - match: regexp, - test: typeof regexp === 'string' ? Filter.stringTest : Filter.regexpTest - }); + this.filters[key].push(this.createFilter(regexp, op, stub, hl, top)); } if (!this.filters[key].length) { delete this.filters[key]; @@ -6920,9 +7044,24 @@ cb: this.node }); }, + createFilter: function(regexp, op, stub, hl, top) { + var settings, test; + test = typeof regexp === 'string' ? Filter.stringTest : Filter.regexpTest; + settings = { + hide: !hl, + stub: stub, + "class": hl, + top: top + }; + return function(value, isReply) { + if (Filter.test(test, value, isReply)) { + return settings; + } + }; + }, node: function() { - var key, obj, value, _i, _len, _ref; - if (this.isClone) { + var filter, key, result, value, _i, _len, _ref; + if (this.isClone || this.isFetchedQuote) { return; } for (key in Filter.filters) { @@ -6932,18 +7071,18 @@ } _ref = Filter.filters[key]; for (_i = 0, _len = _ref.length; _i < _len; _i++) { - obj = _ref[_i]; - if (!Filter.test(obj, value, this.isReply)) { + filter = _ref[_i]; + if (!(result = filter(value, this.isReply))) { continue; } - if (obj.hide) { + if (result.hide) { if (!(this.isReply || g.VIEW === 'index')) { continue; } - this.hide("Hidden by filtering the " + key + ": " + obj.match, obj.stub); + this.hide("Hidden by filtering the " + key + ": " + result.match, result.stub); return; } - this.highlight("Highlighted by filtering the " + key + ": " + obj.match, obj["class"], obj.top); + this.highlight("Highlighted by filtering the " + key + ": " + result.match, result["class"], result.top); } } }, @@ -6988,12 +7127,6 @@ } return false; }, - email: function(post) { - if ('email' in post.info) { - return post.info.email; - } - return false; - }, subject: function(post) { if ('subject' in post.info) { return post.info.subject || false; @@ -7022,7 +7155,7 @@ var file; file = post.file; if (file && (file.isImage || file.isVideo)) { - return post.file.dimensions; + return file.dimensions; } return false; }, @@ -7040,8 +7173,8 @@ }, menu: { init: function() { - var div, entry, type, _i, _len, _ref; - if (!Conf['Menu'] || !Conf['Filter']) { + var div, entry, type, _i, _len, _ref, _ref1; + if (!(((_ref = g.VIEW) === 'index' || _ref === 'thread') && Conf['Menu'] && Conf['Filter'])) { return; } div = $.el('div', { @@ -7056,9 +7189,9 @@ }, subEntries: [] }; - _ref = [['Name', 'name'], ['Unique ID', 'uniqueID'], ['Tripcode', 'tripcode'], ['Capcode', 'capcode'], ['E-mail', 'email'], ['Subject', 'subject'], ['Comment', 'comment'], ['Flag', 'flag'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'MD5']]; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - type = _ref[_i]; + _ref1 = [['Name', 'name'], ['Unique ID', 'uniqueID'], ['Tripcode', 'tripcode'], ['Capcode', 'capcode'], ['Subject', 'subject'], ['Comment', 'comment'], ['Flag', 'flag'], ['Filename', 'filename'], ['Image dimensions', 'dimensions'], ['Filesize', 'filesize'], ['Image MD5', 'MD5']]; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + type = _ref1[_i]; entry.subEntries.push(Filter.menu.createSubEntry(type[0], type[1])); } return Menu.menu.addEntry(entry); @@ -8151,555 +8284,224 @@ } }; - Linkify = { + QR.captcha = { init: function() { - var type, _i, _len, _ref; - if (!Conf['Linkify']) { + var counter, root; + if (d.cookie.indexOf('pass_enabled=1') >= 0) { return; } - this.types = {}; - _ref = this.ordered_types; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - type = _ref[_i]; - this.types[type.key] = type; + if (!(this.isEnabled = !!$.id('g-recaptcha'))) { + return; } - if (Conf['Comment Expansion']) { - ExpandComment.callbacks.push(this.node); - } - if (Conf['Embedding'] || Conf['Link Title']) { - this.embedProcess = Function('link', 'post', "var data = this.services(link);\nif (data) {" + ((Conf['Embedding'] ? 'this.embed(data); ' : '') + (Conf['Link Title'] ? 'data.push(post); this.title(data);' : '')) + "}"); - } - return Post.callbacks.push({ - name: 'Linkify', - cb: this.node + this.captchas = []; + $.get('captchas', [], function(_arg) { + var captchas; + captchas = _arg.captchas; + return QR.captcha.sync(captchas); }); + $.sync('captchas', this.sync.bind(this)); + root = $.el('div', { + className: 'captcha-root' + }); + $.extend(root, { + innerHTML: "
" + }); + counter = $('.captcha-counter > a', root); + this.nodes = { + root: root, + counter: counter + }; + this.count(); + $.addClass(QR.nodes.el, 'has-captcha'); + $.after(QR.nodes.com.parentNode, root); + $.on(counter, 'click', this.toggle.bind(this)); + return $.on(window, 'captcha:success', (function(_this) { + return function() { + return $.queueTask(function() { + return _this.save(false); + }); + }; + })(this)); }, - events: function(post) { - var el, i, items; - i = 0; - items = $$('.embedder', post.nodes.comment); - while (el = items[i++]) { - $.on(el, 'click', Linkify.cb.toggle); - if ($.hasClass(el, 'embedded')) { - Linkify.cb.toggle.call(el); - } + shouldFocus: false, + timeouts: {}, + postsCount: 0, + needed: function() { + var captchaCount; + captchaCount = this.captchas.length; + if (this.nodes.container && !this.timeouts.destroy) { + captchaCount++; + } + this.postsCount = QR.posts.length; + if (this.postsCount === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { + this.postsCount = 0; + } + return captchaCount < this.postsCount; + }, + onPostChange: function() { + if (this.postsCount === 0) { + this.setup(); + } + if (QR.posts.length === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { + return this.postsCount = 0; } }, - node: function() { - var data, end, endNode, i, index, length, link, links, node, result, saved, snapshot, space, test, word; - if (this.isClone) { - return (Conf['Embedding'] ? Linkify.events(this) : null); + toggle: function() { + if (this.nodes.container && !this.timeouts.destroy) { + return this.destroy(); + } else { + return this.setup(true, true); } - if (!Linkify.regString.test(this.info.comment)) { + }, + setup: function(focus, force) { + if (!(this.isEnabled && (this.needed() || force))) { return; } - test = /[^\s'"]+/g; - space = /[\s'"]/; - snapshot = $.X('.//br|.//text()', this.nodes.comment); - i = 0; - links = []; - while (node = snapshot.snapshotItem(i++)) { - data = node.data; - if (!data || node.parentElement.nodeName === "A") { - continue; - } - while (result = test.exec(data)) { - index = result.index; - endNode = node; - word = result[0]; - if ((length = index + word.length) === data.length) { - test.lastIndex = 0; - while ((saved = snapshot.snapshotItem(i++))) { - if (saved.nodeName === 'BR') { - break; - } - endNode = saved; - data = saved.data; - word += data; - length = data.length; - if (end = space.exec(data)) { - test.lastIndex = length = end.index; - i--; - break; - } - } - } - if (Linkify.regString.exec(word)) { - links.push(Linkify.makeRange(node, endNode, index, length)); - } - if (!(test.lastIndex && node === endNode)) { - break; - } - } + $.addClass(QR.nodes.el, 'captcha-open'); + if (focus) { + this.shouldFocus = true; } - i = links.length; - while (i--) { - link = links[i]; - Linkify.embedProcess(Linkify.makeLink(link), this); + if (this.timeouts.destroy) { + clearTimeout(this.timeouts.destroy); + delete this.timeouts.destroy; + return this.reload(); + } + if (this.nodes.container) { + return; + } + this.nodes.container = $.el('div', { + className: 'captcha-container' + }); + $.prepend(this.nodes.root, this.nodes.container); + new MutationObserver(this.afterSetup.bind(this)).observe(this.nodes.container, { + childList: true, + subtree: true + }); + return $.globalEval('(function() {\n function render() {\n var container = document.querySelector("#qr .captcha-container");\n container.dataset.widgetID = window.grecaptcha.render(container, {\n sitekey: \'6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\',\n theme: document.documentElement.classList.contains(\'tomorrow\') ? \'dark\' : \'light\',\n callback: function(response) {\n window.dispatchEvent(new CustomEvent("captcha:success", {detail: response}));\n }\n });\n }\n if (window.grecaptcha) {\n render();\n } else {\n var cbNative = window.onRecaptchaLoaded;\n window.onRecaptchaLoaded = function() {\n render();\n cbNative();\n }\n }\n})();'); + }, + afterSetup: function(mutations) { + var iframe, mutation, node, textarea, _i, _j, _len, _len1, _ref; + for (_i = 0, _len = mutations.length; _i < _len; _i++) { + mutation = mutations[_i]; + _ref = mutation.addedNodes; + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + node = _ref[_j]; + if (iframe = $.x('./descendant-or-self::iframe', node)) { + this.setupIFrame(iframe); + } + if (textarea = $.x('./descendant-or-self::textarea', node)) { + this.setupTextArea(textarea); + } + } } }, - embedProcess: function() {}, - regString: /((https?|mailto|git|magnet|ftp|irc):([a-z\d%\/])|[-a-z\d]+[.](aero|asia|biz|cat|com|coop|info|int|jobs|mobi|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2})([:\/]|(?!.))|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|[-\w\d.@]+@[a-z\d.-]+\.[a-z\d])/i, - makeRange: function(startNode, endNode, startOffset, endOffset) { - var range; - range = document.createRange(); - range.setStart(startNode, startOffset); - range.setEnd(endNode, endOffset); - return range; + setupIFrame: function(iframe) { + this.setupTime = Date.now(); + if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) { + QR.nodes.el.style.top = null; + QR.nodes.el.style.bottom = '0px'; + } + if (this.shouldFocus) { + iframe.focus(); + } + return this.shouldFocus = false; }, - makeLink: function(range) { - var a, i, t, text; - text = range.toString(); - i = 0; - while (/[(\[{<>]/.test(text.charAt(i))) { - i++; + setupTextArea: function(textarea) { + return $.one(textarea, 'input', (function(_this) { + return function() { + return _this.save(true); + }; + })(this)); + }, + destroy: function() { + if (!this.isEnabled) { + return; } - if (i) { - text = text.slice(i); - while (range.startOffset + i >= range.startContainer.data.length) { - i--; - } - if (i) { - range.setStart(range.startContainer, range.startOffset + i); - } + delete this.timeouts.destroy; + $.rmClass(QR.nodes.el, 'captcha-open'); + if (this.nodes.container) { + $.rm(this.nodes.container); } - i = 0; - while (/[)\]}>.,]/.test(t = text.charAt(text.length - (1 + i)))) { - if (!(/[.,]/.test(t) || (text.match(/[()\[\]{}<>]/g)).length % 2)) { + return delete this.nodes.container; + }, + sync: function(captchas) { + if (captchas == null) { + captchas = []; + } + this.captchas = captchas; + this.clear(); + return this.count(); + }, + getOne: function() { + var captcha; + this.clear(); + if (captcha = this.captchas.shift()) { + this.count(); + $.set('captchas', this.captchas); + return captcha.response; + } else { + return null; + } + }, + save: function(pasted) { + var reload, _base; + $.forceSync('captchas'); + reload = (QR.cooldown.auto || Conf['Post on Captcha Completion']) && this.needed(); + this.captchas.push({ + response: $('textarea', this.nodes.container).value, + timeout: (pasted ? this.setupTime : Date.now()) + 2 * $.MINUTE + }); + this.count(); + $.set('captchas', this.captchas); + if (reload) { + this.shouldFocus = true; + this.reload(); + } else { + if (pasted) { + this.destroy(); + } else { + if ((_base = this.timeouts).destroy == null) { + _base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND); + } + } + QR.nodes.status.focus(); + } + if (Conf['Post on Captcha Completion'] && !QR.cooldown.auto) { + return QR.submit(); + } + }, + clear: function() { + var captcha, i, now, _i, _len, _ref; + if (!this.captchas.length) { + return; + } + $.forceSync('captchas'); + now = Date.now(); + _ref = this.captchas; + for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { + captcha = _ref[i]; + if (captcha.timeout > now) { break; } - i++; } - if (i) { - text = text.slice(0, -i); - while (range.endOffset - i < 0) { - i--; - } - if (i) { - range.setEnd(range.endContainer, range.endOffset - i); - } - } - if (!/(mailto:|.+:\/\/)/.test(text)) { - text = (/@/.test(text) ? 'mailto:' : 'http://') + text; - } - a = $.el('a', { - className: 'linkify', - rel: 'nofollow noreferrer', - target: '_blank', - href: text - }); - $.add(a, range.extractContents()); - range.insertNode(a); - range.detach(); - return a; - }, - services: function(link) { - var href, match, type, _i, _len, _ref; - href = link.href; - _ref = Linkify.ordered_types; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - type = _ref[_i]; - if (!(match = type.regExp.exec(href))) { - continue; - } - if (type.dummy) { - return; - } - return [type.key, match[1], match[2], link]; - } - }, - embed: function(data) { - var embed, href, key, link, name, options, post, uid, value, _ref; - key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4]; - href = link.href; - embed = $.el('a', { - className: 'embedder', - href: 'javascript:;', - textContent: '(embed)' - }); - _ref = { - key: key, - href: href, - uid: uid, - options: options - }; - for (name in _ref) { - value = _ref[name]; - embed.dataset[name] = value; - } - $.addClass(link, "" + embed.dataset.key); - $.on(embed, 'click', Linkify.cb.toggle); - $.after(link, [$.tn(' '), embed]); - if (Conf['Auto-embed']) { - return Linkify.cb.toggle.call(embed); - } - }, - title: function(data) { - var err, key, link, options, post, service, title, titles, uid; - key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4]; - if (!(service = Linkify.types[key].title)) { + if (!i) { return; } - titles = Conf['CachedTitles']; - if (title = titles[uid]) { - return link.textContent = title[0]; - } else { - try { - return $.cache(service.api(uid), (function() { - return Linkify.cb.title(this, data); - }), { - responseType: 'json' - }); - } catch (_error) { - err = _error; - link.innerHTML = 'Title Link Blocked (are you using NoScript?)'; - $.prepend(link, $.tn("[" + key + "] ")); - } + this.captchas = this.captchas.slice(i); + this.count(); + $.set('captchas', this.captchas); + return this.setup(true); + }, + count: function() { + this.nodes.counter.textContent = "Captchas: " + this.captchas.length; + clearTimeout(this.timeouts.clear); + if (this.captchas.length) { + return this.timeouts.clear = setTimeout(this.clear.bind(this), this.captchas[0].timeout - Date.now()); } }, - cb: { - toggle: function() { - if ($.hasClass(this, "embedded")) { - $.rm(this.previousElementSibling); - this.previousElementSibling.hidden = false; - this.textContent = '(embed)'; - } else { - this.previousElementSibling.hidden = true; - $.before(this, Linkify.cb.embed(this)); - this.textContent = '(unembed)'; - } - return $.toggleClass(this, 'embedded'); - }, - embed: function(a) { - var el, type; - el = (type = Linkify.types[a.dataset.key]).el(a); - el.style.cssText = type.style != null ? type.style : "border: 0; width: 640px; height: 390px"; - return el; - }, - title: function(req, data) { - var key, link, link2, options, post, post2, service, status, text, uid, _i, _j, _len, _len1, _ref, _ref1; - key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4]; - status = req.status; - service = Linkify.types[key].title; - text = "[" + key + "] " + ((function() { - switch (status) { - case 200: - case 304: - return service.text(req.response); - case 404: - return "Not Found"; - case 403: - return "Forbidden or Private"; - default: - return "" + status + "'d"; - } - })()); - link.textContent = text; - _ref = post.clones; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - post2 = _ref[_i]; - _ref1 = $$('a', post2.nodes.comment); - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - link2 = _ref1[_j]; - if (link2.href === link.href) { - link2.textContent = text; - } - } - } - } - }, - ordered_types: [ - { - key: 'audio', - regExp: /(.*\.(mp3|ogg|wav))$/, - style: '', - el: function(a) { - return $.el('audio', { - controls: true, - preload: 'auto', - src: a.dataset.uid - }); - } - }, { - key: 'gist', - regExp: /.*(?:gist.github.com.*\/)([^\/][^\/]*)$/, - el: function(a) { - var div; - return div = $.el('iframe', { - src: "http://www.purplegene.com/script?url=https://gist.github.com/" + a.dataset.uid + ".js" - }); - }, - title: { - api: function(uid) { - return "https://api.github.com/gists/" + uid; - }, - text: function(_arg) { - var file, files; - files = _arg.files; - for (file in files) { - if (files.hasOwnProperty(file)) { - return file; - } - } - } - } - }, { - key: 'image', - regExp: /(http|www).*\.(gif|png|jpg|jpeg|bmp)$/, - style: 'border: 0; width: auto; height: auto;', - el: function(a) { - var el; - el = $.el('div'); - el.innerHTML = ''; - el.firstChild.href = el.firstChild.firstChild.src = a.dataset.href; - return el; - } - }, { - key: 'InstallGentoo', - regExp: /.*(?:paste.installgentoo.com\/view\/)([0-9a-z_]+)/, - el: function(a) { - return $.el('iframe', { - src: "http://paste.installgentoo.com/view/embed/" + a.dataset.uid - }); - } - }, { - key: 'Twitter', - regExp: /.*twitter.com\/(.+\/status\/\d+)/, - el: function(a) { - return $.el('iframe', { - src: "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid - }); - } - }, { - key: 'LiveLeak', - regExp: /.*(?:liveleak.com\/view.+i=)([0-9a-z_]+)/, - el: function(a) { - var el; - el = $.el('iframe', { - width: "640", - height: "360", - src: "http://www.liveleak.com/ll_embed?i=" + a.dataset.uid, - frameborder: "0" - }); - el.setAttribute("allowfullscreen", "true"); - return el; - } - }, { - key: 'MediaCrush', - regExp: /.*(?:mediacru.sh\/)([0-9a-z_-]+)/i, - style: 'border: 0;', - el: function(a) { - var el; - el = $.el('div'); - $.cache("https://mediacru.sh/" + a.dataset.uid + ".json", function() { - var embed, ext, file, files, i, status, type, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _results, _results1; - status = this.status; - if (status !== 200 && status !== 304) { - return el.textContent = "ERROR " + status; - } - files = this.response.files; - _ref = ['video/webm', 'video/mp4', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'audio/mpeg', 'audio/ogg']; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - type = _ref[_i]; - for (_j = 0, _len1 = files.length; _j < _len1; _j++) { - file = files[_j]; - if (file.type === type) { - embed = file; - break; - } - } - if (embed) { - break; - } - } - if (!embed) { - return div.textContent = "ERROR: Not a valid filetype"; - } - switch (embed.type) { - case 'video/mp4': - case 'video/webm': - case 'video/ogv': - el.innerHTML = ''; - _ref1 = ['mp4', 'webm', 'ogv']; - _results = []; - for (i = _k = 0, _len2 = _ref1.length; _k < _len2; i = ++_k) { - ext = _ref1[i]; - _results.push(el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext); - } - return _results; - break; - case 'image/svg+xml': - case 'image/png': - case 'image/gif': - case 'image/jpeg': - el.innerHTML = ''; - el.firstChild.href = a.dataset.href; - return el.firstChild.firstChild.src = "https://mediacru.sh/" + file.file; - case 'audio/mpeg': - case 'audio/ogg': - el.innerHTML = ''; - _ref2 = ['ogg', 'mp3']; - _results1 = []; - for (i = _l = 0, _len3 = _ref2.length; _l < _len3; i = ++_l) { - ext = _ref2[i]; - _results1.push(el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext); - } - return _results1; - break; - default: - return el.textContent = "ERROR: No valid filetype."; - } - }); - return el; - } - }, { - key: 'pastebin', - regExp: /.*(?:pastebin.com\/(?!u\/))([^#\&\?]*).*/, - el: function(a) { - var div; - return div = $.el('iframe', { - src: "http://pastebin.com/embed_iframe.php?i=" + a.dataset.uid - }); - } - }, { - key: 'gfycat', - regExp: /.*gfycat.com\/(?:iframe\/)?(\S*)/, - el: function(a) { - var div; - return div = $.el('iframe', { - src: "http://gfycat.com/iframe/" + a.dataset.uid - }); - } - }, { - key: 'SoundCloud', - regExp: /.*(?:soundcloud.com\/|snd.sc\/)([^#\&\?]*).*/, - style: 'border: 0; width: 500px; height: 400px;', - el: function(a) { - return $.el('iframe', { - src: "//w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(a.dataset.uid)) - }); - }, - title: { - api: function(uid) { - return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); - }, - text: function(_) { - return _.title; - } - } - }, { - key: 'StrawPoll', - regExp: /strawpoll\.me\/(?:embed_\d+\/)?(\d+)/, - style: 'border: 0; width: 600px; height: 406px;', - el: function(a) { - return $.el('iframe', { - src: "http://strawpoll.me/embed_1/" + a.dataset.uid - }); - } - }, { - key: 'TwitchTV', - regExp: /.*(?:twitch.tv\/)([^#\&\?]*).*/, - style: "border: none; width: 640px; height: 360px;", - el: function(a) { - var channel, id, idparam, obj, result, type, _; - if (result = /(\w+)\/([bc])\/(\d+)/i.exec(a.dataset.uid)) { - _ = result[0], channel = result[1], type = result[2], id = result[3]; - idparam = { - 'b': 'archive_id', - 'c': 'chapter_id' - }; - obj = $.el('object', { - data: 'http://www.twitch.tv/widgets/archive_embed_player.swf' - }); - obj.innerHTML = ''; - obj.children[1].value = "channel=" + channel + "&start_volume=25&auto_play=false&" + idparam[type] + "=" + id; - return obj; - } else { - channel = (/(\w+)/.exec(a.dataset.uid))[0]; - obj = $.el('object', { - data: "http://www.twitch.tv/widgets/live_embed_player.swf?channel=" + channel - }); - obj.innerHTML = ''; - obj.children[1].value = "hostname=www.twitch.tv&channel=" + channel + "&auto_play=true&start_volume=25"; - return obj; - } - } - }, { - key: 'Vocaroo', - regExp: /.*(?:vocaroo.com\/)([^#\&\?]*).*/, - style: '', - el: function(a) { - return $.el('audio', { - controls: true, - preload: 'auto', - src: "http://vocaroo.com/media_command.php?media=" + (a.dataset.uid.replace(/^i\//, '')) + "&command=download_ogg" - }); - } - }, { - key: 'Vimeo', - regExp: /.*(?:vimeo.com\/)([^#\&\?]*).*/, - el: function(a) { - return $.el('iframe', { - src: "//player.vimeo.com/video/" + a.dataset.uid + "?wmode=opaque" - }); - }, - title: { - api: function(uid) { - return "https://vimeo.com/api/oembed.json?url=http://vimeo.com/" + uid; - }, - text: function(_) { - return _.title; - } - } - }, { - key: 'Vine', - regExp: /.*(?:vine.co\/)([^#\&\?]*).*/, - style: 'border: none; width: 500px; height: 500px;', - el: function(a) { - return $.el('iframe', { - src: "https://vine.co/" + a.dataset.uid + "/card" - }); - } - }, { - key: 'YouTube', - regExp: /.*(?:youtu.be\/|youtube.*v=|youtube.*\/embed\/|youtube.*\/v\/|youtube.*videos\/)([^#\&\?]*)\??(t\=.*)?/, - el: function(a) { - var el; - el = $.el('iframe', { - src: "//www.youtube.com/embed/" + a.dataset.uid + (a.dataset.option ? '#' + a.dataset.option : '') + "?wmode=opaque" - }); - el.setAttribute("allowfullscreen", "true"); - return el; - }, - title: { - api: function(uid) { - return "https://gdata.youtube.com/feeds/api/videos/" + uid + "?alt=json&fields=title/text(),yt:noembed,app:control/yt:state/@reasonCode"; - }, - text: function(data) { - return data.entry.title.$t; - } - } - }, { - key: 'Loopvid', - regExp: /.*loopvid.appspot.com\/.*/, - dummy: true - }, { - key: 'MediaFire', - regExp: /.*mediafire.com\/.*/, - dummy: true - }, { - key: 'video', - regExp: /(.*\.(ogv|webm|mp4))$/, - style: 'border: 0; width: auto; height: auto;', - el: function(a) { - return $.el('video', { - controls: 'controls', - preload: 'auto', - src: a.dataset.uid - }); - } - } - ] + reload: function(focus) { + return $.globalEval('(function() {\n var container = document.querySelector("#qr .captcha-container");\n window.grecaptcha.reset(container.dataset.widgetID);\n})();'); + } }; QR = { @@ -9177,7 +8979,9 @@ dialog: function() { var dialog, elm, event, i, items, match_max, match_min, name, node, nodes, rules, save, setNode; QR.nodes = nodes = { - el: dialog = UI.dialog('qr', 'top:0;right:0;', "
\uf00d
+
No selected fileSpoiler\uf0c1Post from URL+Dump\uf00dRemove File
") + el: dialog = UI.dialog('qr', 'top:0;right:0;', { + innerHTML: "
\r\r
\r\r
\r\\uf00d\r
\r
\r
\r\r\r \r
\r
\r\r\r
\r
\r
\r+\r
\r
\r\rNo selected file\r\r\r\rSpoiler\r\\uf0c1\rPost from URL\r+\rDump\r\\uf00d\rRemove File\r\r\r\r
\r\r
\r\r\r\r" + }) }; setNode = function(name, query) { return nodes[name] = $(query, dialog); @@ -9598,226 +9402,6 @@ } }; - QR.captcha = { - init: function() { - var counter, root; - if (d.cookie.indexOf('pass_enabled=1') >= 0) { - return; - } - if (!(this.isEnabled = !!$.id('g-recaptcha'))) { - return; - } - this.captchas = []; - $.get('captchas', [], function(_arg) { - var captchas; - captchas = _arg.captchas; - return QR.captcha.sync(captchas); - }); - $.sync('captchas', this.sync.bind(this)); - root = $.el('div', { - className: 'captcha-root' - }); - $.extend(root, { - innerHTML: "
" - }); - counter = $('.captcha-counter > a', root); - this.nodes = { - root: root, - counter: counter - }; - this.count(); - $.addClass(QR.nodes.el, 'has-captcha'); - $.after(QR.nodes.com.parentNode, root); - $.on(counter, 'click', this.toggle.bind(this)); - return $.on(window, 'captcha:success', (function(_this) { - return function() { - return $.queueTask(function() { - return _this.save(false); - }); - }; - })(this)); - }, - shouldFocus: false, - timeouts: {}, - postsCount: 0, - needed: function() { - var captchaCount; - captchaCount = this.captchas.length; - if (this.nodes.container && !this.timeouts.destroy) { - captchaCount++; - } - this.postsCount = QR.posts.length; - if (this.postsCount === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { - this.postsCount = 0; - } - return captchaCount < this.postsCount; - }, - onPostChange: function() { - if (this.postsCount === 0) { - this.setup(); - } - if (QR.posts.length === 1 && !Conf['Auto-load captcha'] && !QR.posts[0].com && !QR.posts[0].file) { - return this.postsCount = 0; - } - }, - toggle: function() { - if (this.nodes.container && !this.timeouts.destroy) { - return this.destroy(); - } else { - return this.setup(true, true); - } - }, - setup: function(focus, force) { - if (!(this.isEnabled && (this.needed() || force))) { - return; - } - $.addClass(QR.nodes.el, 'captcha-open'); - if (focus) { - this.shouldFocus = true; - } - if (this.timeouts.destroy) { - clearTimeout(this.timeouts.destroy); - delete this.timeouts.destroy; - return this.reload(); - } - if (this.nodes.container) { - return; - } - this.nodes.container = $.el('div', { - className: 'captcha-container' - }); - $.prepend(this.nodes.root, this.nodes.container); - new MutationObserver(this.afterSetup.bind(this)).observe(this.nodes.container, { - childList: true, - subtree: true - }); - return $.globalEval('(function() {\n function render() {\n var container = document.querySelector("#qr .captcha-container");\n container.dataset.widgetID = window.grecaptcha.render(container, {\n sitekey: \'6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc\',\n theme: document.documentElement.classList.contains(\'tomorrow\') ? \'dark\' : \'light\',\n callback: function(response) {\n window.dispatchEvent(new CustomEvent("captcha:success", {detail: response}));\n }\n });\n }\n if (window.grecaptcha) {\n render();\n } else {\n var cbNative = window.onRecaptchaLoaded;\n window.onRecaptchaLoaded = function() {\n render();\n cbNative();\n }\n }\n})();'); - }, - afterSetup: function(mutations) { - var iframe, mutation, node, textarea, _i, _j, _len, _len1, _ref; - for (_i = 0, _len = mutations.length; _i < _len; _i++) { - mutation = mutations[_i]; - _ref = mutation.addedNodes; - for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { - node = _ref[_j]; - if (iframe = $.x('./descendant-or-self::iframe', node)) { - this.setupIFrame(iframe); - } - if (textarea = $.x('./descendant-or-self::textarea', node)) { - this.setupTextArea(textarea); - } - } - } - }, - setupIFrame: function(iframe) { - this.setupTime = Date.now(); - if (QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight) { - QR.nodes.el.style.top = null; - QR.nodes.el.style.bottom = '0px'; - } - if (this.shouldFocus) { - iframe.focus(); - } - return this.shouldFocus = false; - }, - setupTextArea: function(textarea) { - return $.one(textarea, 'input', (function(_this) { - return function() { - return _this.save(true); - }; - })(this)); - }, - destroy: function() { - if (!this.isEnabled) { - return; - } - delete this.timeouts.destroy; - $.rmClass(QR.nodes.el, 'captcha-open'); - if (this.nodes.container) { - $.rm(this.nodes.container); - } - return delete this.nodes.container; - }, - sync: function(captchas) { - if (captchas == null) { - captchas = []; - } - this.captchas = captchas; - this.clear(); - return this.count(); - }, - getOne: function() { - var captcha; - this.clear(); - if (captcha = this.captchas.shift()) { - this.count(); - $.set('captchas', this.captchas); - return captcha.response; - } else { - return null; - } - }, - save: function(pasted) { - var reload, _base; - $.forceSync('captchas'); - reload = (QR.cooldown.auto || Conf['Post on Captcha Completion']) && this.needed(); - this.captchas.push({ - response: $('textarea', this.nodes.container).value, - timeout: (pasted ? this.setupTime : Date.now()) + 2 * $.MINUTE - }); - this.count(); - $.set('captchas', this.captchas); - if (reload) { - this.shouldFocus = true; - this.reload(); - } else { - if (pasted) { - this.destroy(); - } else { - if ((_base = this.timeouts).destroy == null) { - _base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND); - } - } - QR.nodes.status.focus(); - } - if (Conf['Post on Captcha Completion'] && !QR.cooldown.auto) { - return QR.submit(); - } - }, - clear: function() { - var captcha, i, now, _i, _len, _ref; - if (!this.captchas.length) { - return; - } - $.forceSync('captchas'); - now = Date.now(); - _ref = this.captchas; - for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { - captcha = _ref[i]; - if (captcha.timeout > now) { - break; - } - } - if (!i) { - return; - } - this.captchas = this.captchas.slice(i); - this.count(); - $.set('captchas', this.captchas); - return this.setup(true); - }, - count: function() { - this.nodes.counter.textContent = "Captchas: " + this.captchas.length; - clearTimeout(this.timeouts.clear); - if (this.captchas.length) { - return this.timeouts.clear = setTimeout(this.clear.bind(this), this.captchas[0].timeout - Date.now()); - } - }, - reload: function(focus) { - return $.globalEval('(function() {\n var container = document.querySelector("#qr .captcha-container");\n window.grecaptcha.reset(container.dataset.widgetID);\n})();'); - } - }; - QR.cooldown = { init: function() { var key, setTimers, type; @@ -11523,6 +11107,557 @@ } }; + Linkify = { + init: function() { + var type, _i, _len, _ref; + if (!Conf['Linkify']) { + return; + } + this.types = {}; + _ref = this.ordered_types; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + type = _ref[_i]; + this.types[type.key] = type; + } + if (Conf['Comment Expansion']) { + ExpandComment.callbacks.push(this.node); + } + if (Conf['Embedding'] || Conf['Link Title']) { + this.embedProcess = Function('link', 'post', "var data = this.services(link);\nif (data) {" + ((Conf['Embedding'] ? 'this.embed(data); ' : '') + (Conf['Link Title'] ? 'data.push(post); this.title(data);' : '')) + "}"); + } + return Post.callbacks.push({ + name: 'Linkify', + cb: this.node + }); + }, + events: function(post) { + var el, i, items; + i = 0; + items = $$('.embedder', post.nodes.comment); + while (el = items[i++]) { + $.on(el, 'click', Linkify.cb.toggle); + if ($.hasClass(el, 'embedded')) { + Linkify.cb.toggle.call(el); + } + } + }, + node: function() { + var data, end, endNode, i, index, length, link, links, node, result, saved, snapshot, space, test, word; + if (this.isClone) { + return (Conf['Embedding'] ? Linkify.events(this) : null); + } + if (!Linkify.regString.test(this.info.comment)) { + return; + } + test = /[^\s'"]+/g; + space = /[\s'"]/; + snapshot = $.X('.//br|.//text()', this.nodes.comment); + i = 0; + links = []; + while (node = snapshot.snapshotItem(i++)) { + data = node.data; + if (!data || node.parentElement.nodeName === "A") { + continue; + } + while (result = test.exec(data)) { + index = result.index; + endNode = node; + word = result[0]; + if ((length = index + word.length) === data.length) { + test.lastIndex = 0; + while ((saved = snapshot.snapshotItem(i++))) { + if (saved.nodeName === 'BR') { + break; + } + endNode = saved; + data = saved.data; + word += data; + length = data.length; + if (end = space.exec(data)) { + test.lastIndex = length = end.index; + i--; + break; + } + } + } + if (Linkify.regString.exec(word)) { + links.push(Linkify.makeRange(node, endNode, index, length)); + } + if (!(test.lastIndex && node === endNode)) { + break; + } + } + } + i = links.length; + while (i--) { + link = links[i]; + Linkify.embedProcess(Linkify.makeLink(link), this); + } + }, + embedProcess: function() {}, + regString: /((https?|mailto|git|magnet|ftp|irc):([a-z\d%\/])|[-a-z\d]+[.](aero|asia|biz|cat|com|coop|info|int|jobs|mobi|museum|name|net|org|post|pro|tel|travel|xxx|edu|gov|mil|[a-z]{2})([:\/]|(?!.))|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|[-\w\d.@]+@[a-z\d.-]+\.[a-z\d])/i, + makeRange: function(startNode, endNode, startOffset, endOffset) { + var range; + range = document.createRange(); + range.setStart(startNode, startOffset); + range.setEnd(endNode, endOffset); + return range; + }, + makeLink: function(range) { + var a, i, t, text; + text = range.toString(); + i = 0; + while (/[(\[{<>]/.test(text.charAt(i))) { + i++; + } + if (i) { + text = text.slice(i); + while (range.startOffset + i >= range.startContainer.data.length) { + i--; + } + if (i) { + range.setStart(range.startContainer, range.startOffset + i); + } + } + i = 0; + while (/[)\]}>.,]/.test(t = text.charAt(text.length - (1 + i)))) { + if (!(/[.,]/.test(t) || (text.match(/[()\[\]{}<>]/g)).length % 2)) { + break; + } + i++; + } + if (i) { + text = text.slice(0, -i); + while (range.endOffset - i < 0) { + i--; + } + if (i) { + range.setEnd(range.endContainer, range.endOffset - i); + } + } + if (!/(mailto:|.+:\/\/)/.test(text)) { + text = (/@/.test(text) ? 'mailto:' : 'http://') + text; + } + a = $.el('a', { + className: 'linkify', + rel: 'nofollow noreferrer', + target: '_blank', + href: text + }); + $.add(a, range.extractContents()); + range.insertNode(a); + range.detach(); + return a; + }, + services: function(link) { + var href, match, type, _i, _len, _ref; + href = link.href; + _ref = Linkify.ordered_types; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + type = _ref[_i]; + if (!(match = type.regExp.exec(href))) { + continue; + } + if (type.dummy) { + return; + } + return [type.key, match[1], match[2], link]; + } + }, + embed: function(data) { + var embed, href, key, link, name, options, post, uid, value, _ref; + key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4]; + href = link.href; + embed = $.el('a', { + className: 'embedder', + href: 'javascript:;', + textContent: '(embed)' + }); + _ref = { + key: key, + href: href, + uid: uid, + options: options + }; + for (name in _ref) { + value = _ref[name]; + embed.dataset[name] = value; + } + $.addClass(link, "" + embed.dataset.key); + $.on(embed, 'click', Linkify.cb.toggle); + $.after(link, [$.tn(' '), embed]); + if (Conf['Auto-embed']) { + return Linkify.cb.toggle.call(embed); + } + }, + title: function(data) { + var err, key, link, options, post, service, title, titles, uid; + key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4]; + if (!(service = Linkify.types[key].title)) { + return; + } + titles = Conf['CachedTitles']; + if (title = titles[uid]) { + return link.textContent = title[0]; + } else { + try { + return $.cache(service.api(uid), (function() { + return Linkify.cb.title(this, data); + }), { + responseType: 'json' + }); + } catch (_error) { + err = _error; + link.innerHTML = 'Title Link Blocked (are you using NoScript?)'; + $.prepend(link, $.tn("[" + key + "] ")); + } + } + }, + cb: { + toggle: function() { + if ($.hasClass(this, "embedded")) { + $.rm(this.previousElementSibling); + this.previousElementSibling.hidden = false; + this.textContent = '(embed)'; + } else { + this.previousElementSibling.hidden = true; + $.before(this, Linkify.cb.embed(this)); + this.textContent = '(unembed)'; + } + return $.toggleClass(this, 'embedded'); + }, + embed: function(a) { + var el, type; + el = (type = Linkify.types[a.dataset.key]).el(a); + el.style.cssText = type.style != null ? type.style : "border: 0; width: 640px; height: 390px"; + return el; + }, + title: function(req, data) { + var key, link, link2, options, post, post2, service, status, text, uid, _i, _j, _len, _len1, _ref, _ref1; + key = data[0], uid = data[1], options = data[2], link = data[3], post = data[4]; + status = req.status; + service = Linkify.types[key].title; + text = "[" + key + "] " + ((function() { + switch (status) { + case 200: + case 304: + return service.text(req.response); + case 404: + return "Not Found"; + case 403: + return "Forbidden or Private"; + default: + return "" + status + "'d"; + } + })()); + link.textContent = text; + _ref = post.clones; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + post2 = _ref[_i]; + _ref1 = $$('a', post2.nodes.comment); + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + link2 = _ref1[_j]; + if (link2.href === link.href) { + link2.textContent = text; + } + } + } + } + }, + ordered_types: [ + { + key: 'audio', + regExp: /(.*\.(mp3|ogg|wav))$/, + style: '', + el: function(a) { + return $.el('audio', { + controls: true, + preload: 'auto', + src: a.dataset.uid + }); + } + }, { + key: 'gist', + regExp: /.*(?:gist.github.com.*\/)([^\/][^\/]*)$/, + el: function(a) { + var div; + return div = $.el('iframe', { + src: "http://www.purplegene.com/script?url=https://gist.github.com/" + a.dataset.uid + ".js" + }); + }, + title: { + api: function(uid) { + return "https://api.github.com/gists/" + uid; + }, + text: function(_arg) { + var file, files; + files = _arg.files; + for (file in files) { + if (files.hasOwnProperty(file)) { + return file; + } + } + } + } + }, { + key: 'image', + regExp: /(http|www).*\.(gif|png|jpg|jpeg|bmp)$/, + style: 'border: 0; width: auto; height: auto;', + el: function(a) { + var el; + el = $.el('div'); + el.innerHTML = ''; + el.firstChild.href = el.firstChild.firstChild.src = a.dataset.href; + return el; + } + }, { + key: 'InstallGentoo', + regExp: /.*(?:paste.installgentoo.com\/view\/)([0-9a-z_]+)/, + el: function(a) { + return $.el('iframe', { + src: "http://paste.installgentoo.com/view/embed/" + a.dataset.uid + }); + } + }, { + key: 'Twitter', + regExp: /.*twitter.com\/(.+\/status\/\d+)/, + el: function(a) { + return $.el('iframe', { + src: "https://twitframe.com/show?url=https://twitter.com/" + a.dataset.uid + }); + } + }, { + key: 'LiveLeak', + regExp: /.*(?:liveleak.com\/view.+i=)([0-9a-z_]+)/, + el: function(a) { + var el; + el = $.el('iframe', { + width: "640", + height: "360", + src: "http://www.liveleak.com/ll_embed?i=" + a.dataset.uid, + frameborder: "0" + }); + el.setAttribute("allowfullscreen", "true"); + return el; + } + }, { + key: 'MediaCrush', + regExp: /.*(?:mediacru.sh\/)([0-9a-z_-]+)/i, + style: 'border: 0;', + el: function(a) { + var el; + el = $.el('div'); + $.cache("https://mediacru.sh/" + a.dataset.uid + ".json", function() { + var embed, ext, file, files, i, status, type, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _results, _results1; + status = this.status; + if (status !== 200 && status !== 304) { + return el.textContent = "ERROR " + status; + } + files = this.response.files; + _ref = ['video/webm', 'video/mp4', 'video/ogv', 'image/svg+xml', 'image/png', 'image/gif', 'image/jpeg', 'audio/mpeg', 'audio/ogg']; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + type = _ref[_i]; + for (_j = 0, _len1 = files.length; _j < _len1; _j++) { + file = files[_j]; + if (file.type === type) { + embed = file; + break; + } + } + if (embed) { + break; + } + } + if (!embed) { + return div.textContent = "ERROR: Not a valid filetype"; + } + switch (embed.type) { + case 'video/mp4': + case 'video/webm': + case 'video/ogv': + el.innerHTML = ''; + _ref1 = ['mp4', 'webm', 'ogv']; + _results = []; + for (i = _k = 0, _len2 = _ref1.length; _k < _len2; i = ++_k) { + ext = _ref1[i]; + _results.push(el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext); + } + return _results; + break; + case 'image/svg+xml': + case 'image/png': + case 'image/gif': + case 'image/jpeg': + el.innerHTML = ''; + el.firstChild.href = a.dataset.href; + return el.firstChild.firstChild.src = "https://mediacru.sh/" + file.file; + case 'audio/mpeg': + case 'audio/ogg': + el.innerHTML = ''; + _ref2 = ['ogg', 'mp3']; + _results1 = []; + for (i = _l = 0, _len3 = _ref2.length; _l < _len3; i = ++_l) { + ext = _ref2[i]; + _results1.push(el.firstChild.children[i].src = "https://mediacru.sh/" + a.dataset.uid + "." + ext); + } + return _results1; + break; + default: + return el.textContent = "ERROR: No valid filetype."; + } + }); + return el; + } + }, { + key: 'pastebin', + regExp: /.*(?:pastebin.com\/(?!u\/))([^#\&\?]*).*/, + el: function(a) { + var div; + return div = $.el('iframe', { + src: "http://pastebin.com/embed_iframe.php?i=" + a.dataset.uid + }); + } + }, { + key: 'gfycat', + regExp: /.*gfycat.com\/(?:iframe\/)?(\S*)/, + el: function(a) { + var div; + return div = $.el('iframe', { + src: "http://gfycat.com/iframe/" + a.dataset.uid + }); + } + }, { + key: 'SoundCloud', + regExp: /.*(?:soundcloud.com\/|snd.sc\/)([^#\&\?]*).*/, + style: 'border: 0; width: 500px; height: 400px;', + el: function(a) { + return $.el('iframe', { + src: "//w.soundcloud.com/player/?visual=true&show_comments=false&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(a.dataset.uid)) + }); + }, + title: { + api: function(uid) { + return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); + }, + text: function(_) { + return _.title; + } + } + }, { + key: 'StrawPoll', + regExp: /strawpoll\.me\/(?:embed_\d+\/)?(\d+)/, + style: 'border: 0; width: 600px; height: 406px;', + el: function(a) { + return $.el('iframe', { + src: "http://strawpoll.me/embed_1/" + a.dataset.uid + }); + } + }, { + key: 'TwitchTV', + regExp: /.*(?:twitch.tv\/)([^#\&\?]*).*/, + style: "border: none; width: 640px; height: 360px;", + el: function(a) { + var channel, id, idparam, obj, result, type, _; + if (result = /(\w+)\/([bc])\/(\d+)/i.exec(a.dataset.uid)) { + _ = result[0], channel = result[1], type = result[2], id = result[3]; + idparam = { + 'b': 'archive_id', + 'c': 'chapter_id' + }; + obj = $.el('object', { + data: 'http://www.twitch.tv/widgets/archive_embed_player.swf' + }); + obj.innerHTML = ''; + obj.children[1].value = "channel=" + channel + "&start_volume=25&auto_play=false&" + idparam[type] + "=" + id; + return obj; + } else { + channel = (/(\w+)/.exec(a.dataset.uid))[0]; + obj = $.el('object', { + data: "http://www.twitch.tv/widgets/live_embed_player.swf?channel=" + channel + }); + obj.innerHTML = ''; + obj.children[1].value = "hostname=www.twitch.tv&channel=" + channel + "&auto_play=true&start_volume=25"; + return obj; + } + } + }, { + key: 'Vocaroo', + regExp: /.*(?:vocaroo.com\/)([^#\&\?]*).*/, + style: '', + el: function(a) { + return $.el('audio', { + controls: true, + preload: 'auto', + src: "http://vocaroo.com/media_command.php?media=" + (a.dataset.uid.replace(/^i\//, '')) + "&command=download_ogg" + }); + } + }, { + key: 'Vimeo', + regExp: /.*(?:vimeo.com\/)([^#\&\?]*).*/, + el: function(a) { + return $.el('iframe', { + src: "//player.vimeo.com/video/" + a.dataset.uid + "?wmode=opaque" + }); + }, + title: { + api: function(uid) { + return "https://vimeo.com/api/oembed.json?url=http://vimeo.com/" + uid; + }, + text: function(_) { + return _.title; + } + } + }, { + key: 'Vine', + regExp: /.*(?:vine.co\/)([^#\&\?]*).*/, + style: 'border: none; width: 500px; height: 500px;', + el: function(a) { + return $.el('iframe', { + src: "https://vine.co/" + a.dataset.uid + "/card" + }); + } + }, { + key: 'YouTube', + regExp: /.*(?:youtu.be\/|youtube.*v=|youtube.*\/embed\/|youtube.*\/v\/|youtube.*videos\/)([^#\&\?]*)\??(t\=.*)?/, + el: function(a) { + var el; + el = $.el('iframe', { + src: "//www.youtube.com/embed/" + a.dataset.uid + (a.dataset.option ? '#' + a.dataset.option : '') + "?wmode=opaque" + }); + el.setAttribute("allowfullscreen", "true"); + return el; + }, + title: { + api: function(uid) { + return "https://gdata.youtube.com/feeds/api/videos/" + uid + "?alt=json&fields=title/text(),yt:noembed,app:control/yt:state/@reasonCode"; + }, + text: function(data) { + return data.entry.title.$t; + } + } + }, { + key: 'Loopvid', + regExp: /.*loopvid.appspot.com\/.*/, + dummy: true + }, { + key: 'MediaFire', + regExp: /.*mediafire.com\/.*/, + dummy: true + }, { + key: 'video', + regExp: /(.*\.(ogv|webm|mp4))$/, + style: 'border: 0; width: auto; height: auto;', + el: function(a) { + return $.el('video', { + controls: 'controls', + preload: 'auto', + src: a.dataset.uid + }); + } + } + ] + }; + ArchiveLink = { init: function() { var div, entry, type, _i, _len, _ref; @@ -13398,7 +13533,7 @@ } return Redirect.data = o; }, - archives: [{"uid":0,"name":"Moe","domain":"archive.moe","http":false,"https":true,"software":"foolfuuka","boards":["a","biz","c","co","diy","gd","h","i","int","jp","k","m","mlp","out","po","r9k","s4s","sci","sp","tg","tv","u","v","vg","vp","vr","wsg"],"files":["a","biz","c","co","diy","gd","h","i","jp","k","m","mlp","po","s4s","sci","tg","u","v","vg","vp","vr","wsg"]},{"uid":3,"name":"4plebs Archive","domain":"archive.4plebs.org","http":true,"https":true,"software":"foolfuuka","boards":["adv","f","hr","o","pol","s4s","tg","trv","tv","x"],"files":["adv","f","hr","o","pol","s4s","tg","trv","tv","x"]},{"uid":5,"name":"Love is Over","domain":"archive.loveisover.me","http":true,"https":true,"software":"foolfuuka","boards":["c","d","e","i","lgbt","t","u","w","wg"],"files":["c","d","e","i","lgbt","t","u","w","wg"]},{"uid":8,"name":"Rebecca Black Tech","domain":"archive.rebeccablacktech.com","http":false,"https":true,"software":"fuuka","boards":["cgl","g","mu","w"],"files":["cgl","g","mu","w"]},{"uid":10,"name":"warosu","domain":"warosu.org","http":false,"https":true,"software":"fuuka","boards":["3","biz","cgl","ck","diy","fa","g","ic","jp","lit","sci","tg","vr"],"files":["3","biz","cgl","ck","diy","fa","g","ic","jp","lit","sci","tg","vr"]},{"uid":15,"name":"fgts","domain":"fgts.jp","http":true,"https":true,"software":"foolfuuka","boards":["asp","cm","h","hc","hm","n","p","r","s","soc","y"],"files":["asp","cm","h","hc","hm","n","p","r","s","soc","y"]},{"uid":17,"name":"imcute","domain":"imcute.yt","http":true,"https":false,"software":"foolfuuka","boards":["an","fit","gif","int","mlp","out","r9k","toy"],"files":["an","fit","gif","int","mlp","out","r9k","toy"]}], + archives: [{"uid":0,"name":"Moe","domain":"archive.moe","http":false,"https":true,"software":"foolfuuka","boards":["a","biz","c","co","diy","gd","h","i","int","jp","k","m","mlp","out","po","r9k","s4s","sci","tg","tv","u","v","vg","vp","vr","wsg"],"files":["a","biz","c","co","diy","gd","h","i","jp","k","m","mlp","po","s4s","sci","tg","u","v","vg","vp","vr","wsg"]},{"uid":3,"name":"4plebs Archive","domain":"archive.4plebs.org","http":true,"https":true,"software":"foolfuuka","boards":["adv","f","hr","o","pol","s4s","tg","trv","tv","x"],"files":["adv","f","hr","o","pol","s4s","tg","trv","tv","x"]},{"uid":5,"name":"Love is Over","domain":"archive.loveisover.me","http":true,"https":true,"software":"foolfuuka","boards":["c","d","e","i","lgbt","t","u","w","wg"],"files":["c","d","e","i","lgbt","t","u","w","wg"]},{"uid":8,"name":"Rebecca Black Tech","domain":"archive.rebeccablacktech.com","http":false,"https":true,"software":"fuuka","boards":["cgl","g","mu","w"],"files":["cgl","g","mu","w"]},{"uid":10,"name":"warosu","domain":"warosu.org","http":false,"https":true,"software":"fuuka","boards":["3","biz","cgl","ck","diy","fa","g","ic","jp","lit","sci","tg","vr"],"files":["3","biz","cgl","ck","diy","fa","g","ic","jp","lit","sci","tg","vr"]},{"uid":15,"name":"fgts","domain":"fgts.jp","http":true,"https":true,"software":"foolfuuka","boards":["asp","cm","h","hc","hm","n","p","r","s","soc","y"],"files":["asp","cm","h","hc","hm","n","p","r","s","soc","y"]},{"uid":21,"name":"imcute","domain":"imcute.yt","http":true,"https":false,"software":"foolfuuka","boards":["an","fit","gif","int","mlp","out","r9k","toy"],"files":["an","fit","gif","int","mlp","out","r9k","toy"],"imagehosts":["http://imcute.yt/"]}], to: function(dest, data) { var archive; archive = (dest === 'search' || dest === 'board' ? Redirect.data.thread : Redirect.data[dest])[data.boardID]; @@ -13428,9 +13563,13 @@ return "" + (Redirect.protocol(archive)) + archive.domain + "/" + path; }, post: function(archive, _arg) { - var URL, boardID, postID; + var URL, boardID, postID, protocol; boardID = _arg.boardID, postID = _arg.postID; - URL = new String("" + (Redirect.protocol(archive)) + archive.domain + "/_/api/chan/post/?board=" + boardID + "&num=" + postID); + protocol = Redirect.protocol(archive); + URL = new String("" + protocol + archive.domain + "/_/api/chan/post/?board=" + boardID + "&num=" + postID); + if (!Redirect.securityCheck(URL)) { + return ''; + } URL.archive = archive; return URL; }, @@ -13451,75 +13590,1392 @@ value = encodeURIComponent(value); path = archive.software === 'foolfuuka' ? "" + boardID + "/search/" + type + "/" + value : "" + boardID + "/?task=search2&search_" + (type === 'image' ? 'media_hash' : type) + "=" + value; return "" + (Redirect.protocol(archive)) + archive.domain + "/" + path; + }, + securityCheck: function(URL) { + return /^https:\/\//.test(URL) || location.protocol === 'http:' || Conf['Except Archives from Encryption']; + }, + navigate: function(URL, alternative) { + if (URL && (Redirect.securityCheck(URL) || confirm("Redirect to " + URL + "?\n\nYour connection will not be encrypted."))) { + return location.replace(URL); + } else if (alternative) { + return location.replace(alternative); + } } }; - [ - { - "uid": 0, - "name": "Moe", - "domain": "archive.moe", - "http": false, - "https": true, - "software": "foolfuuka", - "boards": ["a", "biz", "c", "co", "diy", "gd", "h", "i", "int", "jp", "k", "m", "mlp", "out", "po", "r9k", "s4s", "sci", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], - "files": ["a", "biz", "c", "co", "diy", "gd", "h", "i", "jp", "k", "m", "mlp", "po", "s4s", "sci", "tg", "u", "v", "vg", "vp", "vr", "wsg"] - }, { - "uid": 3, - "name": "4plebs Archive", - "domain": "archive.4plebs.org", - "http": true, - "https": true, - "software": "foolfuuka", - "boards": ["adv", "f", "hr", "o", "pol", "s4s", "tg", "trv", "tv", "x"], - "files": ["adv", "f", "hr", "o", "pol", "s4s", "tg", "trv", "tv", "x"] - }, { - "uid": 5, - "name": "Love is Over", - "domain": "archive.loveisover.me", - "http": true, - "https": true, - "software": "foolfuuka", - "boards": ["c", "d", "e", "i", "lgbt", "t", "u", "w", "wg"], - "files": ["c", "d", "e", "i", "lgbt", "t", "u", "w", "wg"] - }, { - "uid": 8, - "name": "Rebecca Black Tech", - "domain": "archive.rebeccablacktech.com", - "http": false, - "https": true, - "software": "fuuka", - "boards": ["cgl", "g", "mu", "w"], - "files": ["cgl", "g", "mu", "w"] - }, { - "uid": 10, - "name": "warosu", - "domain": "warosu.org", - "http": false, - "https": true, - "software": "fuuka", - "boards": ["3", "biz", "cgl", "ck", "diy", "fa", "g", "ic", "jp", "lit", "sci", "tg", "vr"], - "files": ["3", "biz", "cgl", "ck", "diy", "fa", "g", "ic", "jp", "lit", "sci", "tg", "vr"] - }, { - "uid": 15, - "name": "fgts", - "domain": "fgts.jp", - "http": true, - "https": true, - "software": "foolfuuka", - "boards": ["asp", "cm", "h", "hc", "hm", "n", "p", "r", "s", "soc", "y"], - "files": ["asp", "cm", "h", "hc", "hm", "n", "p", "r", "s", "soc", "y"] - }, { - "uid": 17, - "name": "imcute", - "domain": "imcute.yt", - "http": true, - "https": false, - "software": "foolfuuka", - "boards": ["an", "fit", "gif", "int", "mlp", "out", "r9k", "toy"], - "files": ["an", "fit", "gif", "int", "mlp", "out", "r9k", "toy"] + PSAHiding = { + init: function() { + if (!Conf['Announcement Hiding']) { + return; + } + $.addClass(doc, 'hide-announcement'); + return $.on(d, '4chanXInitFinished', this.setup); + }, + setup: function() { + var btn, entry, psa; + $.off(d, '4chanXInitFinished', PSAHiding.setup); + if (!(psa = $.id('globalMessage'))) { + return; + } + entry = { + el: $.el('a', { + textContent: 'Show announcement', + className: 'show-announcement', + href: 'javascript:;' + }), + order: 50, + open: function() { + return psa.hidden; + } + }; + Header.menu.addEntry(entry); + $.on(entry.el, 'click', PSAHiding.toggle); + PSAHiding.btn = btn = $.el('span', { + innerHTML: '[Dismiss]', + title: 'Mark announcement as read and hide.', + className: 'hide-announcement', + href: 'javascript:;', + textContent: '[ - ]' + }); + $.on(btn, 'click', PSAHiding.toggle); + $.get('hiddenPSA', 0, function(_arg) { + var hiddenPSA; + hiddenPSA = _arg.hiddenPSA; + PSAHiding.sync(hiddenPSA); + $.add(psa, btn); + return $.rmClass(doc, 'hide-announcement'); + }); + return $.sync('hiddenPSA', PSAHiding.sync); + }, + toggle: function(e) { + var UTC; + if ($.hasClass(this, 'hide-announcement')) { + UTC = +$.id('globalMessage').dataset.utc; + $.set('hiddenPSA', UTC); + } else { + $.event('CloseMenu'); + $["delete"]('hiddenPSA'); + } + return PSAHiding.sync(UTC); + }, + sync: function(UTC) { + var hr, psa; + psa = $.id('globalMessage'); + psa.hidden = PSAHiding.btn.hidden = UTC && UTC >= +psa.dataset.utc ? true : false; + if ((hr = psa.nextElementSibling) && hr.nodeName === 'HR') { + return hr.hidden = psa.hidden; + } } - ]; + }; + + CatalogLinks = { + init: function() { + var el, input; + if (!Conf['Catalog Links']) { + return; + } + CatalogLinks.el = el = $.el('label', { + id: 'toggleCatalog', + href: 'javascript:;', + innerHTML: " Catalog Links" + }); + input = $('input', el); + $.on(input, 'change', this.toggle); + $.sync('Header catalog links', CatalogLinks.set); + Header.menu.addEntry({ + el: el, + order: 95 + }); + return $.on(d, '4chanXInitFinished', function() { + return CatalogLinks.set(Conf['Header catalog links']); + }); + }, + toggle: function() { + $.event('CloseMenu'); + $.set('Header catalog links', this.checked); + return CatalogLinks.set(this.checked); + }, + set: function(useCatalog) { + var a, board, generateURL, path, _i, _len, _ref, _ref1; + path = useCatalog ? 'catalog' : ''; + generateURL = useCatalog && Conf['External Catalog'] ? CatalogLinks.external : function(board) { + return a.href = "/" + board + "/" + path; + }; + _ref = $$("#board-list a:not(.catalog), #boardNavDesktopFoot a"); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + a = _ref[_i]; + if (((_ref1 = a.hostname) !== 'boards.4chan.org' && _ref1 !== 'catalog.neet.tv' && _ref1 !== '4index.gropes.us') || !(board = a.pathname.split('/')[1]) || (board === 'f' || board === 'status' || board === '4chan') || $.hasClass(a, 'external')) { + continue; + } + a.href = generateURL(board); + } + return CatalogLinks.el.title = "Turn catalog links " + (useCatalog ? 'off' : 'on') + "."; + }, + external: function(board) { + if (board === 'a' || board === 'c' || board === 'g' || board === 'co' || board === 'k' || board === 'm' || board === 'o' || board === 'p' || board === 'v' || board === 'vg' || board === 'w' || board === 'cm' || board === '3' || board === 'adv' || board === 'an' || board === 'cgl' || board === 'ck' || board === 'diy' || board === 'fa' || board === 'fit' || board === 'int' || board === 'jp' || board === 'mlp' || board === 'lit' || board === 'mu' || board === 'n' || board === 'po' || board === 'sci' || board === 'toy' || board === 'trv' || board === 'tv' || board === 'vp' || board === 'x' || board === 'q') { + return "http://catalog.neet.tv/" + board; + } else { + return "/" + board + "/catalog"; + } + } + }; + + CustomCSS = { + init: function() { + if (!Conf['Custom CSS']) { + return; + } + return this.addStyle(); + }, + addStyle: function() { + return this.style = $.addStyle(Conf['usercss'], 'CustomCSS'); + }, + rmStyle: function() { + if (this.style) { + $.rm(this.style); + return delete this.style; + } + }, + update: function() { + if (!this.style) { + return this.addStyle(); + } + return this.style.textContent = Conf['usercss']; + } + }; + + Dice = { + init: function() { + if (g.BOARD.ID !== 'tg' || !Conf['Show Dice Roll']) { + return; + } + return Post.callbacks.push({ + name: 'Show Dice Roll', + cb: this.node + }); + }, + node: function() { + var dicestats, roll, _ref; + if (this.isClone || !(dicestats = (_ref = this.info.email) != null ? _ref.match(/dice[+\s](\d+)d(\d+)/) : void 0)) { + return; + } + roll = $('b', this.nodes.comment).firstChild; + return roll.data = "Rolled " + dicestats[1] + "d" + dicestats[2] + ": " + (roll.data.slice(7)); + } + }; + + ExpandComment = { + init: function() { + if (g.VIEW !== 'index' || !Conf['Comment Expansion'] || Conf['JSON Navigation']) { + return; + } + if (g.BOARD.ID === 'g') { + this.callbacks.push(Fourchan.code); + } + if (g.BOARD.ID === 'sci') { + this.callbacks.push(Fourchan.math); + } + return Post.callbacks.push({ + name: 'Comment Expansion', + cb: this.node + }); + }, + node: function() { + var a; + if (a = $('.abbr > a:not([onclick])', this.nodes.comment)) { + return $.on(a, 'click', ExpandComment.cb); + } + }, + callbacks: [], + cb: function(e) { + e.preventDefault(); + return ExpandComment.expand(Get.postFromNode(this)); + }, + expand: function(post) { + var a; + if (post.nodes.longComment && !post.nodes.longComment.parentNode) { + $.replace(post.nodes.shortComment, post.nodes.longComment); + post.nodes.comment = post.nodes.longComment; + return; + } + if (!(a = $('.abbr > a', post.nodes.comment))) { + return; + } + a.textContent = "Post No." + post + " Loading..."; + return $.cache("//a.4cdn.org" + (a.pathname.split('/').splice(0, 4).join('/')) + ".json", function() { + return ExpandComment.parse(this, a, post); + }); + }, + contract: function(post) { + var a; + if (!post.nodes.shortComment) { + return; + } + a = $('.abbr > a', post.nodes.shortComment); + a.textContent = 'here'; + $.replace(post.nodes.longComment, post.nodes.shortComment); + return post.nodes.comment = post.nodes.shortComment; + }, + parse: function(req, a, post) { + var callback, clone, comment, href, postObj, posts, quote, spoilerRange, status, _i, _j, _k, _len, _len1, _len2, _ref, _ref1; + status = req.status; + if (status !== 200 && status !== 304) { + a.textContent = "Error " + req.statusText + " (" + status + ")"; + return; + } + posts = req.response.posts; + if (spoilerRange = posts[0].custom_spoiler) { + Build.spoilerRange[g.BOARD] = spoilerRange; + } + for (_i = 0, _len = posts.length; _i < _len; _i++) { + postObj = posts[_i]; + if (postObj.no === post.ID) { + break; + } + } + if (postObj.no !== post.ID) { + a.textContent = "Post No." + post + " not found."; + return; + } + comment = post.nodes.comment; + clone = comment.cloneNode(false); + clone.innerHTML = postObj.com; + _ref = $$('.quotelink', clone); + for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { + quote = _ref[_j]; + href = quote.getAttribute('href'); + if (href[0] === '/') { + continue; + } + if (href[0] === '#') { + quote.href = "" + (a.pathname.split('/').splice(0, 4).join('/')) + href; + } else { + quote.href = "" + (a.pathname.split('/').splice(0, 3).join('/')) + "/" + href; + } + } + post.nodes.shortComment = comment; + $.replace(comment, clone); + post.nodes.comment = post.nodes.longComment = clone; + post.parseComment(); + post.parseQuotes(); + _ref1 = ExpandComment.callbacks; + for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { + callback = _ref1[_k]; + callback.call(post); + } + } + }; + + ExpandThread = { + statuses: {}, + init: function() { + if (g.VIEW === 'thread' || !Conf['Thread Expansion']) { + return; + } + return $.on(d, (Conf['JSON Navigation'] ? 'IndexRefresh' : '4chanXInitFinished'), this.onIndexRefresh); + }, + setButton: function(thread) { + var a, summary; + if (!(summary = $.x('following-sibling::*[contains(@class,"summary")][1]', thread.OP.nodes.root))) { + return; + } + a = $.el('a', { + textContent: ExpandThread.text.apply(ExpandThread, ['+'].concat(__slice.call(summary.textContent.match(/\d+/g)))), + href: "res/" + thread.ID, + className: 'summary' + }); + $.on(a, 'click', ExpandThread.cbToggle); + return $.replace(summary, a); + }, + disconnect: function() { + this.refresh(); + return $.off(d, 'IndexRefresh', this.onIndexRefresh); + }, + refresh: function(disconnect) { + var status, threadID, _ref, _ref1; + if (g.VIEW === 'thread' || !Conf['Thread Expansion']) { + return; + } + _ref = ExpandThread.statuses; + for (threadID in _ref) { + status = _ref[threadID]; + if ((_ref1 = status.req) != null) { + _ref1.abort(); + } + delete ExpandThread.statuses[threadID]; + } + }, + onIndexRefresh: function() { + ExpandThread.refresh(); + return g.BOARD.threads.forEach(function(thread) { + return ExpandThread.setButton(thread); + }); + }, + text: function(status, posts, files) { + return ("" + status + " " + posts + " post" + (posts > 1 ? 's' : '')) + (+files ? " and " + files + " image repl" + (files > 1 ? 'ies' : 'y') : "") + (" " + (status === '-' ? 'shown' : 'omitted') + "."); + }, + cbToggle: function(e) { + if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + return; + } + e.preventDefault(); + return ExpandThread.toggle(Get.threadFromNode(this)); + }, + toggle: function(thread) { + var a, threadRoot; + threadRoot = thread.OP.nodes.root.parentNode; + if (!(a = $('.summary', threadRoot))) { + return; + } + if (thread.ID in ExpandThread.statuses) { + return ExpandThread.contract(thread, a, threadRoot); + } else { + return ExpandThread.expand(thread, a, threadRoot); + } + }, + expand: function(thread, a, threadRoot) { + var status; + ExpandThread.statuses[thread] = status = {}; + a.textContent = ExpandThread.text.apply(ExpandThread, ['...'].concat(__slice.call(a.textContent.match(/\d+/g)))); + return status.req = $.cache("//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() { + delete status.req; + return ExpandThread.parse(this, thread, a); + }); + }, + contract: function(thread, a, threadRoot) { + var filesCount, inlined, num, postsCount, replies, reply, status, _i, _len; + status = ExpandThread.statuses[thread]; + delete ExpandThread.statuses[thread]; + if (status.req) { + status.req.abort(); + if (a) { + a.textContent = ExpandThread.text.apply(ExpandThread, ['+'].concat(__slice.call(a.textContent.match(/\d+/g)))); + } + return; + } + replies = $$('.thread > .replyContainer', threadRoot); + if (Conf['Show Replies']) { + num = (function() { + if (thread.isSticky) { + return 1; + } else { + switch (g.BOARD.ID) { + case 'b': + case 'vg': + return 3; + case 't': + return 1; + default: + return 5; + } + } + })(); + replies = replies.slice(0, -num); + } + postsCount = 0; + filesCount = 0; + for (_i = 0, _len = replies.length; _i < _len; _i++) { + reply = replies[_i]; + if (Conf['Quote Inlining']) { + while (inlined = $('.inlined', reply)) { + inlined.click(); + } + } + postsCount++; + if ('file' in Get.postFromRoot(reply)) { + filesCount++; + } + $.rm(reply); + } + return a.textContent = ExpandThread.text('+', postsCount, filesCount); + }, + parse: function(req, thread, a) { + var filesCount, post, postData, posts, postsCount, postsRoot, root, _i, _len, _ref, _ref1; + if ((_ref = req.status) !== 200 && _ref !== 304) { + a.textContent = "Error " + req.statusText + " (" + req.status + ")"; + return; + } + Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler; + posts = []; + postsRoot = []; + filesCount = 0; + _ref1 = req.response.posts; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + postData = _ref1[_i]; + if (postData.no === thread.ID) { + continue; + } + if (post = thread.posts[postData.no]) { + if ('file' in post) { + filesCount++; + } + postsRoot.push(post.nodes.root); + continue; + } + root = Build.postFromObject(postData, thread.board.ID); + post = new Post(root, thread, thread.board); + if ('file' in post) { + filesCount++; + } + posts.push(post); + postsRoot.push(root); + } + Post.callbacks.execute(posts); + $.after(a, postsRoot); + postsCount = postsRoot.length; + return a.textContent = ExpandThread.text('-', postsRoot.length, filesCount); + } + }; + + FileInfo = { + init: function() { + if (!Conf['File Info Formatting']) { + return; + } + return Post.callbacks.push({ + name: 'File Info Formatting', + cb: this.node + }); + }, + node: function() { + if (!this.file || this.isClone) { + return; + } + return this.file.text.innerHTML = "" + (FileInfo.format(Conf['fileInfo'], this)) + ""; + }, + format: function(formatString, post) { + return formatString.replace(/%([A-Za-z])/g, function(s, c) { + if (c in FileInfo.formatters) { + return FileInfo.formatters[c].call(post); + } else { + return s; + } + }); + }, + convertUnit: function(size, unit) { + var i; + if (unit === 'B') { + return "" + (size.toFixed()) + " Bytes"; + } + i = 1 + ['KB', 'MB'].indexOf(unit); + while (i--) { + size /= 1024; + } + size = unit === 'MB' ? Math.round(size * 100) / 100 : size.toFixed(); + return "" + size + " " + unit; + }, + escape: function(name) { + return name.replace(/<|>/g, function(c) { + return c === '<' && '<' || '>'; + }); + }, + formatters: { + t: function() { + return this.file.URL.match(/\d+\..+$/)[0]; + }, + T: function() { + return "" + (FileInfo.formatters.t.call(this)) + ""; + }, + l: function() { + return "" + (FileInfo.formatters.n.call(this)) + ""; + }, + L: function() { + return "" + (FileInfo.formatters.N.call(this)) + ""; + }, + n: function() { + var fullname, shortname; + fullname = this.file.name; + shortname = Build.shortFilename(this.file.name, this.isReply); + if (fullname === shortname) { + return FileInfo.escape(fullname); + } else { + return "" + (FileInfo.escape(shortname)) + "" + (FileInfo.escape(fullname)) + ""; + } + }, + N: function() { + return FileInfo.escape(this.file.name); + }, + p: function() { + if (this.file.isSpoiler) { + return 'Spoiler, '; + } else { + return ''; + } + }, + s: function() { + return this.file.size; + }, + B: function() { + return FileInfo.convertUnit(this.file.sizeInBytes, 'B'); + }, + K: function() { + return FileInfo.convertUnit(this.file.sizeInBytes, 'KB'); + }, + M: function() { + return FileInfo.convertUnit(this.file.sizeInBytes, 'MB'); + }, + r: function() { + return this.file.dimensions || 'PDF'; + } + } + }; + + Flash = { + init: function() { + if (g.BOARD.ID === 'f') { + return $.ready(Flash.initReady); + } + }, + initReady: function() { + var nav, sauceLink, swfName; + $.globalEval('SWFEmbed.init()'); + if (g.VIEW !== 'thread') { + return; + } + swfName = $('.fileText > a'); + nav = $('.navLinks.desktop'); + swfName = swfName.href.replace(/^(.*?)\/f\//g, ""); + sauceLink = $.el('a', { + textContent: 'Check Sauce on SWFCHAN', + href: "http://eye.swfchan.com/search/?q=" + swfName + }); + $.addClass(nav, 'swfSauce'); + $.rmClass(nav, 'navLinks'); + $.rmAll(nav); + return $.add(nav, [$.tn('['), sauceLink, $.tn(']')]); + } + }; + + Fourchan = { + init: function() { + var board; + board = g.BOARD.ID; + if (board === 'g') { + $.globalEval("window.addEventListener('prettyprint', function(e) {\n window.dispatchEvent(new CustomEvent('prettyprint:cb', {\n detail: prettyPrintOne(e.detail)\n }));\n}, false);"); + Post.callbacks.push({ + name: 'Parse /g/ code', + cb: this.code + }); + } + if (board === 'sci') { + $.globalEval("window.addEventListener('jsmath', function(e) {\n if (jsMath.loaded) {\n // process one post\n jsMath.ProcessBeforeShowing(document.getElementById(e.detail));\n } else {\n // load jsMath and process whole document\n jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);\n jsMath.Autoload.LoadJsMath();\n }\n}, false);"); + return Post.callbacks.push({ + name: 'Parse /sci/ math', + cb: this.math + }); + } + }, + code: function() { + var apply, pre, _i, _len, _ref; + if (this.isClone) { + return; + } + apply = function(e) { + return pre.innerHTML = e.detail; + }; + $.on(window, 'prettyprint:cb', apply); + _ref = $$('.prettyprint:not(.prettyprinted)', this.nodes.comment); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + pre = _ref[_i]; + $.event('prettyprint', pre.innerHTML, window); + } + $.off(window, 'prettyprint:cb', apply); + }, + math: function() { + if (this.isClone || !$('.math', this.nodes.comment)) { + return; + } + return $.event('jsmath', this.nodes.post.id, window); + } + }; + + IDColor = { + init: function() { + if (!Conf['Color User IDs']) { + return; + } + this.ids = { + Heaven: [0, 0, 0, '#fff'] + }; + return Post.callbacks.push({ + name: 'Color User IDs', + cb: this.node + }); + }, + node: function() { + var rgb, span, uid; + if (this.isClone || !((uid = this.info.uniqueID) && (span = $('span.hand', this.nodes.uniqueID)))) { + return; + } + rgb = IDColor.ids[uid] || IDColor.compute(uid); + span.style.color = rgb[3]; + span.style.backgroundColor = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; + $.addClass(span, 'painted'); + return span.title = 'Highlight posts by this ID'; + }, + compute: function(uid) { + var hash, i, rgb; + i = 1; + hash = uid.charCodeAt(0); + while (i < 8) { + hash = (hash << 5) - hash + uid.charCodeAt(i++); + } + rgb = [(hash >> 24) & 0xFF, (hash >> 16) & 0xFF, (hash >> 8) & 0xFF]; + rgb.push((rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114) > 125 ? '#000' : '#fff'); + return this.ids[uid] = rgb; + } + }; + + Keybinds = { + init: function() { + var hotkey, init; + if (!Conf['Keybinds']) { + return; + } + for (hotkey in Conf.hotkeys) { + $.sync(hotkey, Keybinds.sync); + } + init = function() { + var node, _i, _len, _ref; + $.off(d, '4chanXInitFinished', init); + $.on(d, 'keydown', Keybinds.keydown); + _ref = $$('[accesskey]'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + node = _ref[_i]; + node.removeAttribute('accesskey'); + } + }; + return $.on(d, '4chanXInitFinished', init); + }, + sync: function(key, hotkey) { + return Conf[hotkey] = key; + }, + keydown: function(e) { + var form, key, notification, notifications, op, searchInput, target, thread, threadRoot, _i, _len, _ref; + if (!(key = Keybinds.keyCode(e))) { + return; + } + target = e.target; + if (target.nodeName === 'EMBED') { + return; + } + if ((_ref = target.nodeName) === 'INPUT' || _ref === 'TEXTAREA') { + if (!/(Esc|Alt|Ctrl|Meta|Shift\+\w{2,})/.test(key)) { + return; + } + } + if (g.VIEW !== 'catalog') { + threadRoot = Nav.getThread(); + if (op = $('.op', threadRoot)) { + thread = Get.postFromNode(op).thread; + } + } + switch (key) { + case Conf['Toggle board list']: + if (Conf['Custom Board Navigation']) { + Header.toggleBoardList(); + } + break; + case Conf['Toggle header']: + Header.toggleBarVisibility(); + break; + case Conf['Open empty QR']: + Keybinds.qr(); + break; + case Conf['Open QR']: + if (g.VIEW === 'catalog') { + return; + } + Keybinds.qr(threadRoot); + break; + case Conf['Open settings']: + Settings.open(); + break; + case Conf['Close']: + if ($.id('fourchanx-settings')) { + Settings.close(); + } else if ((notifications = $$('.notification')).length) { + for (_i = 0, _len = notifications.length; _i < _len; _i++) { + notification = notifications[_i]; + $('.close', notification).click(); + } + } else if (QR.nodes) { + if (Conf['Persistent QR']) { + QR.hide(); + } else { + QR.close(); + } + } + break; + case Conf['Spoiler tags']: + if (target.nodeName !== 'TEXTAREA') { + return; + } + Keybinds.tags('spoiler', target); + break; + case Conf['Code tags']: + if (target.nodeName !== 'TEXTAREA') { + return; + } + Keybinds.tags('code', target); + break; + case Conf['Eqn tags']: + if (target.nodeName !== 'TEXTAREA') { + return; + } + Keybinds.tags('eqn', target); + break; + case Conf['Math tags']: + if (target.nodeName !== 'TEXTAREA') { + return; + } + Keybinds.tags('math', target); + break; + case Conf['Toggle sage']: + if (QR.nodes) { + Keybinds.sage(); + } + break; + case Conf['Submit QR']: + if (QR.nodes && !QR.status()) { + QR.submit(); + } + break; + case Conf['Post Without Name']: + if (QR.nodes && !QR.status()) { + Keybinds.name(); + QR.submit(); + } + break; + case Conf['Update']: + switch (g.VIEW) { + case 'thread': + ThreadUpdater.update(); + break; + case 'index': + if (Conf['JSON Navigation']) { + Index.update(); + } + } + break; + case Conf['Watch']: + if (g.VIEW === 'catalog') { + return; + } + ThreadWatcher.toggle(thread); + break; + case Conf['Expand image']: + if (g.VIEW === 'catalog') { + return; + } + Keybinds.img(threadRoot); + break; + case Conf['Expand images']: + if (g.VIEW === 'catalog') { + return; + } + Keybinds.img(threadRoot, true); + break; + case Conf['Open Gallery']: + if (g.VIEW === 'catalog') { + return; + } + Gallery.cb.toggle(); + break; + case Conf['fappeTyme']: + if (g.VIEW === 'catalog') { + return; + } + FappeTyme.cb.toggle.call({ + name: 'fappe' + }); + break; + case Conf['werkTyme']: + if (g.VIEW === 'catalog') { + return; + } + FappeTyme.cb.toggle.call({ + name: 'werk' + }); + break; + case Conf['Front page']: + if (Conf['JSON Navigation'] && g.VIEW === 'index') { + Index.userPageNav(1); + } else { + window.location = "/" + g.BOARD + "/"; + } + break; + case Conf['Open front page']: + $.open("/" + g.BOARD + "/"); + break; + case Conf['Next page']: + if (g.VIEW !== 'index') { + return; + } + if (Conf['JSON Navigation']) { + if (Conf['Index Mode'] !== 'all pages') { + $('.next button', Index.pagelist).click(); + } + } else { + if (form = $('.next form')) { + window.location = form.action; + } + } + break; + case Conf['Previous page']: + if (g.VIEW !== 'index') { + return; + } + if (Conf['JSON Navigation']) { + if (Conf['Index Mode'] !== 'all pages') { + $('.prev button', Index.pagelist).click(); + } + } else { + if (form = $('.prev form')) { + window.location = form.action; + } + } + break; + case Conf['Search form']: + if (g.VIEW !== 'index') { + return; + } + searchInput = Conf['JSON Navigation'] ? Index.searchInput : $.id('search-box'); + Header.scrollToIfNeeded(searchInput); + searchInput.click(); + searchInput.focus(); + break; + case Conf['Paged mode']: + if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'paged')) { + return; + } + Index.setIndexMode('paged'); + break; + case Conf['All pages mode']: + if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'all pages')) { + return; + } + Index.setIndexMode('all pages'); + break; + case Conf['Catalog mode']: + if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'catalog')) { + return; + } + Index.setIndexMode('catalog'); + break; + case Conf['Cycle sort type']: + if (g.VIEW !== 'index') { + return; + } + Index.cycleSortType(); + break; + case Conf['Open catalog']: + if (Conf['External Catalog']) { + window.location = CatalogLinks.external(g.BOARD.ID); + } else { + if (!Conf['JSON Navigation']) { + return window.location = "/" + g.BOARD + "/catalog"; + } + if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'catalog')) { + return; + } + Index.setIndexMode('catalog'); + } + break; + case Conf['Next thread']: + if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { + return; + } + Nav.scroll(+1); + break; + case Conf['Previous thread']: + if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { + return; + } + Nav.scroll(-1); + break; + case Conf['Expand thread']: + if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { + return; + } + ExpandThread.toggle(thread); + break; + case Conf['Open thread']: + if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { + return; + } + Keybinds.open(thread); + break; + case Conf['Open thread tab']: + if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { + return; + } + Keybinds.open(thread, true); + break; + case Conf['Next reply']: + if (g.VIEW === 'catalog') { + return; + } + Keybinds.hl(+1, threadRoot); + break; + case Conf['Previous reply']: + if (g.VIEW === 'catalog') { + return; + } + Keybinds.hl(-1, threadRoot); + break; + case Conf['Deselect reply']: + if (g.VIEW === 'catalog') { + return; + } + Keybinds.hl(0, threadRoot); + break; + case Conf['Hide']: + PostHiding.toggle(thread.OP); + break; + case Conf['Previous Post Quoting You']: + QuoteMarkers.cb.seek('preceding'); + break; + case Conf['Next Post Quoting You']: + QuoteMarkers.cb.seek('following'); + break; + default: + return; + } + e.preventDefault(); + return e.stopPropagation(); + }, + keyCode: function(e) { + var kc, key; + key = (function() { + switch (kc = e.keyCode) { + case 8: + return ''; + case 13: + return 'Enter'; + case 27: + return 'Esc'; + case 37: + return 'Left'; + case 38: + return 'Up'; + case 39: + return 'Right'; + case 40: + return 'Down'; + default: + if ((48 <= kc && kc <= 57) || (65 <= kc && kc <= 90)) { + return String.fromCharCode(kc).toLowerCase(); + } else { + return null; + } + } + })(); + if (key) { + if (e.altKey) { + key = 'Alt+' + key; + } + if (e.ctrlKey) { + key = 'Ctrl+' + key; + } + if (e.metaKey) { + key = 'Meta+' + key; + } + if (e.shiftKey) { + key = 'Shift+' + key; + } + } + return key; + }, + qr: function(thread) { + if (!QR.postingIsEnabled) { + return; + } + QR.open(); + if (thread != null) { + QR.quote.call($('input', $('.post.highlight', thread) || thread)); + } + QR.nodes.com.focus(); + if (Conf['QR Shortcut']) { + return $.rmClass($('.qr-shortcut'), 'disabled'); + } + }, + tags: function(tag, ta) { + var range, selEnd, selStart, value; + value = ta.value; + selStart = ta.selectionStart; + selEnd = ta.selectionEnd; + ta.value = value.slice(0, selStart) + ("[" + tag + "]") + value.slice(selStart, selEnd) + ("[/" + tag + "]") + value.slice(selEnd); + range = ("[" + tag + "]").length + selEnd; + ta.setSelectionRange(range, range); + return $.event('input', null, ta); + }, + name: function() { + return QR.nodes.name.value = ''; + }, + sage: function() { + var isSage; + isSage = /sage/i.test(QR.nodes.email.value); + return QR.nodes.email.value = isSage ? "" : "sage"; + }, + img: function(thread, all) { + var post; + if (all) { + return ImageExpand.cb.toggleAll(); + } else { + post = Get.postFromNode($('.post.highlight', thread) || $('.op', thread)); + return ImageExpand.toggle(post); + } + }, + open: function(thread, tab) { + var url; + if (g.VIEW !== 'index') { + return; + } + url = Build.path(thread.board.ID, thread.ID); + if (tab) { + return $.open(url); + } else { + return location.href = url; + } + }, + hl: function(delta, thread) { + var axis, height, next, postEl, replies, reply, root, _i, _len; + postEl = $('.reply.highlight', thread); + if (!delta) { + if (postEl) { + $.rmClass(postEl, 'highlight'); + } + return; + } + if (postEl) { + height = postEl.getBoundingClientRect().height; + if (Header.getTopOf(postEl) >= -height && Header.getBottomOf(postEl) >= -height) { + root = postEl.parentNode; + axis = delta === +1 ? 'following' : 'preceding'; + if (!(next = $.x("" + axis + "-sibling::div[contains(@class,'replyContainer') and not(@hidden) and not(child::div[@class='stub'])][1]/child::div[contains(@class,'reply')]", root))) { + return; + } + Header.scrollToIfNeeded(next, delta === +1); + this.focus(next); + $.rmClass(postEl, 'highlight'); + return; + } + $.rmClass(postEl, 'highlight'); + } + replies = $$('.reply', thread); + if (delta === -1) { + replies.reverse(); + } + for (_i = 0, _len = replies.length; _i < _len; _i++) { + reply = replies[_i]; + if (delta === +1 && Header.getTopOf(reply) > 0 || delta === -1 && Header.getBottomOf(reply) > 0) { + this.focus(reply); + return; + } + } + }, + focus: function(post) { + return $.addClass(post, 'highlight'); + } + }; + + Nav = { + init: function() { + var next, prev; + switch (g.VIEW) { + case 'index': + if (!Conf['Index Navigation']) { + return; + } + break; + case 'thread': + if (!Conf['Reply Navigation']) { + return; + } + } + prev = $.el('a', { + href: 'javascript:;', + id: 'navPrev' + }); + next = $.el('a', { + href: 'javascript:;', + id: 'navNext' + }); + Header.addShortcut(prev, true); + Header.addShortcut(next, true); + $.on(prev, 'click', this.prev); + return $.on(next, 'click', this.next); + }, + prev: function() { + if (g.VIEW === 'thread') { + return window.scrollTo(0, 0); + } else { + return Nav.scroll(-1); + } + }, + next: function() { + if (g.VIEW === 'thread') { + return window.scrollTo(0, d.body.scrollHeight); + } else { + return Nav.scroll(+1); + } + }, + getThread: function() { + var threadRoot, _i, _len, _ref; + _ref = $$('.thread'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + threadRoot = _ref[_i]; + if (Header.getTopOf(threadRoot) >= -threadRoot.getBoundingClientRect().height) { + return threadRoot; + } + } + return $('.board'); + }, + scroll: function(delta) { + var axis, next, thread, top; + thread = Nav.getThread(); + axis = delta === +1 ? 'following' : 'preceding'; + if (next = $.x("" + axis + "-sibling::div[contains(@class,'thread') and not(@hidden)][1]", thread)) { + top = Header.getTopOf(thread); + if (delta === +1 && top < 5 || delta === -1 && top > -5) { + thread = next; + } + } + return Header.scrollTo(thread); + } + }; + + RelativeDates = { + INTERVAL: $.MINUTE / 2, + init: function() { + switch (g.VIEW) { + case 'index': + this.flush(); + $.on(d, 'visibilitychange', this.flush); + if (!Conf['Relative Post Dates']) { + return; + } + break; + case 'thread': + if (!Conf['Relative Post Dates']) { + return; + } + this.flush(); + $.on(d, 'visibilitychange ThreadUpdate', this.flush); + break; + default: + return; + } + return Post.callbacks.push({ + name: 'Relative Post Dates', + cb: this.node + }); + }, + node: function() { + var dateEl; + if (this.isClone) { + return; + } + dateEl = this.nodes.date; + dateEl.title = dateEl.textContent; + return RelativeDates.update(this); + }, + relative: function(diff, now, date) { + var days, months, number, rounded, unit, years; + unit = (number = diff / $.DAY) >= 1 ? (years = now.getYear() - date.getYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = (months + 12) % 12) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second'); + rounded = Math.round(number); + if (rounded !== 1) { + unit += 's'; + } + return "" + rounded + " " + unit + " ago"; + }, + stale: [], + flush: function() { + var data, now, _i, _len, _ref; + if (d.hidden) { + return; + } + now = new Date(); + _ref = RelativeDates.stale; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + data = _ref[_i]; + RelativeDates.update(data, now); + } + RelativeDates.stale = []; + clearTimeout(RelativeDates.timeout); + return RelativeDates.timeout = setTimeout(RelativeDates.flush, RelativeDates.INTERVAL); + }, + update: function(data, now) { + var date, diff, isPost, relative, singlePost, _i, _len, _ref; + isPost = data instanceof Post; + date = isPost ? data.info.date : new Date(+data.dataset.utc); + now || (now = new Date()); + diff = now - date; + relative = RelativeDates.relative(diff, now, date); + if (isPost) { + _ref = [data].concat(data.clones); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + singlePost = _ref[_i]; + singlePost.nodes.date.firstChild.textContent = relative; + } + } else { + data.firstChild.textContent = relative; + } + return RelativeDates.setOwnTimeout(diff, data); + }, + setOwnTimeout: function(diff, data) { + var delay; + delay = diff < $.MINUTE ? $.SECOND - (diff + $.SECOND / 2) % $.SECOND : diff < $.HOUR ? $.MINUTE - (diff + $.MINUTE / 2) % $.MINUTE : diff < $.DAY ? $.HOUR - (diff + $.HOUR / 2) % $.HOUR : $.DAY - (diff + $.DAY / 2) % $.DAY; + return setTimeout(RelativeDates.markStale, delay, data); + }, + markStale: function(data) { + if (__indexOf.call(RelativeDates.stale, data) >= 0) { + return; + } + if (data instanceof Post && !g.posts[data.fullID]) { + return; + } + return RelativeDates.stale.push(data); + } + }; + + RemoveSpoilers = { + init: function() { + if (Conf['Reveal Spoilers']) { + $.addClass(doc, 'reveal-spoilers'); + } + if (Conf['Remove Spoilers']) { + return $.addClass(doc, 'remove-spoilers'); + } + } + }; + + Report = { + init: function() { + if (!/report/.test(location.search)) { + return; + } + return $.asap((function() { + return $.id('recaptcha_response_field'); + }), Report.ready); + }, + ready: function() { + var field; + field = $.id('recaptcha_response_field'); + $.on(field, 'keydown', function(e) { + if (e.keyCode === 8 && !field.value) { + return $.globalEval('Recaptcha.reload("t")'); + } + }); + return $.on($('form'), 'submit', function(e) { + var response; + e.preventDefault(); + response = field.value.trim(); + if (!/\s|^\d+$/.test(response)) { + field.value = "" + response + " " + response; + } + return this.submit(); + }); + } + }; + + Time = { + init: function() { + if (!Conf['Time Formatting']) { + return; + } + return Post.callbacks.push({ + name: 'Time Formatting', + cb: this.node + }); + }, + node: function() { + if (this.isClone) { + return; + } + return this.nodes.date.textContent = Time.format(Conf['time'], this.info.date); + }, + format: function(formatString, date) { + return formatString.replace(/%([A-Za-z])/g, function(s, c) { + if (c in Time.formatters) { + return Time.formatters[c].call(date); + } else { + return s; + } + }); + }, + day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + zeroPad: function(n) { + if (n < 10) { + return "0" + n; + } else { + return n; + } + }, + formatters: { + a: function() { + return Time.day[this.getDay()].slice(0, 3); + }, + A: function() { + return Time.day[this.getDay()]; + }, + b: function() { + return Time.month[this.getMonth()].slice(0, 3); + }, + B: function() { + return Time.month[this.getMonth()]; + }, + d: function() { + return Time.zeroPad(this.getDate()); + }, + e: function() { + return this.getDate(); + }, + H: function() { + return Time.zeroPad(this.getHours()); + }, + I: function() { + return Time.zeroPad(this.getHours() % 12 || 12); + }, + k: function() { + return this.getHours(); + }, + l: function() { + return this.getHours() % 12 || 12; + }, + m: function() { + return Time.zeroPad(this.getMonth() + 1); + }, + M: function() { + return Time.zeroPad(this.getMinutes()); + }, + p: function() { + if (this.getHours() < 12) { + return 'AM'; + } else { + return 'PM'; + } + }, + P: function() { + if (this.getHours() < 12) { + return 'am'; + } else { + return 'pm'; + } + }, + S: function() { + return Time.zeroPad(this.getSeconds()); + }, + y: function() { + return this.getFullYear().toString().slice(2); + }, + Y: function() { + return this.getFullYear(); + } + } + }; Banner = { init: function() { @@ -15038,1380 +16494,6 @@ })(); - PSAHiding = { - init: function() { - if (!Conf['Announcement Hiding']) { - return; - } - $.addClass(doc, 'hide-announcement'); - return $.on(d, '4chanXInitFinished', this.setup); - }, - setup: function() { - var btn, entry, psa; - $.off(d, '4chanXInitFinished', PSAHiding.setup); - if (!(psa = $.id('globalMessage'))) { - return; - } - entry = { - el: $.el('a', { - textContent: 'Show announcement', - className: 'show-announcement', - href: 'javascript:;' - }), - order: 50, - open: function() { - return psa.hidden; - } - }; - Header.menu.addEntry(entry); - $.on(entry.el, 'click', PSAHiding.toggle); - PSAHiding.btn = btn = $.el('span', { - innerHTML: '[Dismiss]', - title: 'Mark announcement as read and hide.', - className: 'hide-announcement', - href: 'javascript:;', - textContent: '[ - ]' - }); - $.on(btn, 'click', PSAHiding.toggle); - $.get('hiddenPSA', 0, function(_arg) { - var hiddenPSA; - hiddenPSA = _arg.hiddenPSA; - PSAHiding.sync(hiddenPSA); - $.add(psa, btn); - return $.rmClass(doc, 'hide-announcement'); - }); - return $.sync('hiddenPSA', PSAHiding.sync); - }, - toggle: function(e) { - var UTC; - if ($.hasClass(this, 'hide-announcement')) { - UTC = +$.id('globalMessage').dataset.utc; - $.set('hiddenPSA', UTC); - } else { - $.event('CloseMenu'); - $["delete"]('hiddenPSA'); - } - return PSAHiding.sync(UTC); - }, - sync: function(UTC) { - var hr, psa; - psa = $.id('globalMessage'); - psa.hidden = PSAHiding.btn.hidden = UTC && UTC >= +psa.dataset.utc ? true : false; - if ((hr = psa.nextElementSibling) && hr.nodeName === 'HR') { - return hr.hidden = psa.hidden; - } - } - }; - - CatalogLinks = { - init: function() { - var el, input; - if (!Conf['Catalog Links']) { - return; - } - CatalogLinks.el = el = $.el('label', { - id: 'toggleCatalog', - href: 'javascript:;', - innerHTML: " Catalog Links" - }); - input = $('input', el); - $.on(input, 'change', this.toggle); - $.sync('Header catalog links', CatalogLinks.set); - Header.menu.addEntry({ - el: el, - order: 95 - }); - return $.on(d, '4chanXInitFinished', function() { - return CatalogLinks.set(Conf['Header catalog links']); - }); - }, - toggle: function() { - $.event('CloseMenu'); - $.set('Header catalog links', this.checked); - return CatalogLinks.set(this.checked); - }, - set: function(useCatalog) { - var a, board, generateURL, path, _i, _len, _ref, _ref1; - path = useCatalog ? 'catalog' : ''; - generateURL = useCatalog && Conf['External Catalog'] ? CatalogLinks.external : function(board) { - return a.href = "/" + board + "/" + path; - }; - _ref = $$("#board-list a:not(.catalog), #boardNavDesktopFoot a"); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - a = _ref[_i]; - if (((_ref1 = a.hostname) !== 'boards.4chan.org' && _ref1 !== 'catalog.neet.tv' && _ref1 !== '4index.gropes.us') || !(board = a.pathname.split('/')[1]) || (board === 'f' || board === 'status' || board === '4chan') || $.hasClass(a, 'external')) { - continue; - } - a.href = generateURL(board); - } - return CatalogLinks.el.title = "Turn catalog links " + (useCatalog ? 'off' : 'on') + "."; - }, - external: function(board) { - if (board === 'a' || board === 'c' || board === 'g' || board === 'co' || board === 'k' || board === 'm' || board === 'o' || board === 'p' || board === 'v' || board === 'vg' || board === 'w' || board === 'cm' || board === '3' || board === 'adv' || board === 'an' || board === 'cgl' || board === 'ck' || board === 'diy' || board === 'fa' || board === 'fit' || board === 'int' || board === 'jp' || board === 'mlp' || board === 'lit' || board === 'mu' || board === 'n' || board === 'po' || board === 'sci' || board === 'toy' || board === 'trv' || board === 'tv' || board === 'vp' || board === 'x' || board === 'q') { - return "http://catalog.neet.tv/" + board; - } else { - return "/" + board + "/catalog"; - } - } - }; - - CustomCSS = { - init: function() { - if (!Conf['Custom CSS']) { - return; - } - return this.addStyle(); - }, - addStyle: function() { - return this.style = $.addStyle(Conf['usercss'], 'CustomCSS'); - }, - rmStyle: function() { - if (this.style) { - $.rm(this.style); - return delete this.style; - } - }, - update: function() { - if (!this.style) { - return this.addStyle(); - } - return this.style.textContent = Conf['usercss']; - } - }; - - Dice = { - init: function() { - if (g.BOARD.ID !== 'tg' || !Conf['Show Dice Roll']) { - return; - } - return Post.callbacks.push({ - name: 'Show Dice Roll', - cb: this.node - }); - }, - node: function() { - var dicestats, roll, _ref; - if (this.isClone || !(dicestats = (_ref = this.info.email) != null ? _ref.match(/dice[+\s](\d+)d(\d+)/) : void 0)) { - return; - } - roll = $('b', this.nodes.comment).firstChild; - return roll.data = "Rolled " + dicestats[1] + "d" + dicestats[2] + ": " + (roll.data.slice(7)); - } - }; - - ExpandComment = { - init: function() { - if (g.VIEW !== 'index' || !Conf['Comment Expansion'] || Conf['JSON Navigation']) { - return; - } - if (g.BOARD.ID === 'g') { - this.callbacks.push(Fourchan.code); - } - if (g.BOARD.ID === 'sci') { - this.callbacks.push(Fourchan.math); - } - return Post.callbacks.push({ - name: 'Comment Expansion', - cb: this.node - }); - }, - node: function() { - var a; - if (a = $('.abbr > a:not([onclick])', this.nodes.comment)) { - return $.on(a, 'click', ExpandComment.cb); - } - }, - callbacks: [], - cb: function(e) { - e.preventDefault(); - return ExpandComment.expand(Get.postFromNode(this)); - }, - expand: function(post) { - var a; - if (post.nodes.longComment && !post.nodes.longComment.parentNode) { - $.replace(post.nodes.shortComment, post.nodes.longComment); - post.nodes.comment = post.nodes.longComment; - return; - } - if (!(a = $('.abbr > a', post.nodes.comment))) { - return; - } - a.textContent = "Post No." + post + " Loading..."; - return $.cache("//a.4cdn.org" + (a.pathname.split('/').splice(0, 4).join('/')) + ".json", function() { - return ExpandComment.parse(this, a, post); - }); - }, - contract: function(post) { - var a; - if (!post.nodes.shortComment) { - return; - } - a = $('.abbr > a', post.nodes.shortComment); - a.textContent = 'here'; - $.replace(post.nodes.longComment, post.nodes.shortComment); - return post.nodes.comment = post.nodes.shortComment; - }, - parse: function(req, a, post) { - var callback, clone, comment, href, postObj, posts, quote, spoilerRange, status, _i, _j, _k, _len, _len1, _len2, _ref, _ref1; - status = req.status; - if (status !== 200 && status !== 304) { - a.textContent = "Error " + req.statusText + " (" + status + ")"; - return; - } - posts = req.response.posts; - if (spoilerRange = posts[0].custom_spoiler) { - Build.spoilerRange[g.BOARD] = spoilerRange; - } - for (_i = 0, _len = posts.length; _i < _len; _i++) { - postObj = posts[_i]; - if (postObj.no === post.ID) { - break; - } - } - if (postObj.no !== post.ID) { - a.textContent = "Post No." + post + " not found."; - return; - } - comment = post.nodes.comment; - clone = comment.cloneNode(false); - clone.innerHTML = postObj.com; - _ref = $$('.quotelink', clone); - for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) { - quote = _ref[_j]; - href = quote.getAttribute('href'); - if (href[0] === '/') { - continue; - } - if (href[0] === '#') { - quote.href = "" + (a.pathname.split('/').splice(0, 4).join('/')) + href; - } else { - quote.href = "" + (a.pathname.split('/').splice(0, 3).join('/')) + "/" + href; - } - } - post.nodes.shortComment = comment; - $.replace(comment, clone); - post.nodes.comment = post.nodes.longComment = clone; - post.parseComment(); - post.parseQuotes(); - _ref1 = ExpandComment.callbacks; - for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { - callback = _ref1[_k]; - callback.call(post); - } - } - }; - - ExpandThread = { - statuses: {}, - init: function() { - if (g.VIEW === 'thread' || !Conf['Thread Expansion']) { - return; - } - return $.on(d, (Conf['JSON Navigation'] ? 'IndexRefresh' : '4chanXInitFinished'), this.onIndexRefresh); - }, - setButton: function(thread) { - var a, summary; - if (!(summary = $.x('following-sibling::*[contains(@class,"summary")][1]', thread.OP.nodes.root))) { - return; - } - a = $.el('a', { - textContent: ExpandThread.text.apply(ExpandThread, ['+'].concat(__slice.call(summary.textContent.match(/\d+/g)))), - href: "res/" + thread.ID, - className: 'summary' - }); - $.on(a, 'click', ExpandThread.cbToggle); - return $.replace(summary, a); - }, - disconnect: function() { - this.refresh(); - return $.off(d, 'IndexRefresh', this.onIndexRefresh); - }, - refresh: function(disconnect) { - var status, threadID, _ref, _ref1; - if (g.VIEW === 'thread' || !Conf['Thread Expansion']) { - return; - } - _ref = ExpandThread.statuses; - for (threadID in _ref) { - status = _ref[threadID]; - if ((_ref1 = status.req) != null) { - _ref1.abort(); - } - delete ExpandThread.statuses[threadID]; - } - }, - onIndexRefresh: function() { - ExpandThread.refresh(); - return g.BOARD.threads.forEach(function(thread) { - return ExpandThread.setButton(thread); - }); - }, - text: function(status, posts, files) { - return ("" + status + " " + posts + " post" + (posts > 1 ? 's' : '')) + (+files ? " and " + files + " image repl" + (files > 1 ? 'ies' : 'y') : "") + (" " + (status === '-' ? 'shown' : 'omitted') + "."); - }, - cbToggle: function(e) { - if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { - return; - } - e.preventDefault(); - return ExpandThread.toggle(Get.threadFromNode(this)); - }, - toggle: function(thread) { - var a, threadRoot; - threadRoot = thread.OP.nodes.root.parentNode; - if (!(a = $('.summary', threadRoot))) { - return; - } - if (thread.ID in ExpandThread.statuses) { - return ExpandThread.contract(thread, a, threadRoot); - } else { - return ExpandThread.expand(thread, a, threadRoot); - } - }, - expand: function(thread, a, threadRoot) { - var status; - ExpandThread.statuses[thread] = status = {}; - a.textContent = ExpandThread.text.apply(ExpandThread, ['...'].concat(__slice.call(a.textContent.match(/\d+/g)))); - return status.req = $.cache("//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() { - delete status.req; - return ExpandThread.parse(this, thread, a); - }); - }, - contract: function(thread, a, threadRoot) { - var filesCount, inlined, num, postsCount, replies, reply, status, _i, _len; - status = ExpandThread.statuses[thread]; - delete ExpandThread.statuses[thread]; - if (status.req) { - status.req.abort(); - if (a) { - a.textContent = ExpandThread.text.apply(ExpandThread, ['+'].concat(__slice.call(a.textContent.match(/\d+/g)))); - } - return; - } - replies = $$('.thread > .replyContainer', threadRoot); - if (Conf['Show Replies']) { - num = (function() { - if (thread.isSticky) { - return 1; - } else { - switch (g.BOARD.ID) { - case 'b': - case 'vg': - return 3; - case 't': - return 1; - default: - return 5; - } - } - })(); - replies = replies.slice(0, -num); - } - postsCount = 0; - filesCount = 0; - for (_i = 0, _len = replies.length; _i < _len; _i++) { - reply = replies[_i]; - if (Conf['Quote Inlining']) { - while (inlined = $('.inlined', reply)) { - inlined.click(); - } - } - postsCount++; - if ('file' in Get.postFromRoot(reply)) { - filesCount++; - } - $.rm(reply); - } - return a.textContent = ExpandThread.text('+', postsCount, filesCount); - }, - parse: function(req, thread, a) { - var filesCount, post, postData, posts, postsCount, postsRoot, root, _i, _len, _ref, _ref1; - if ((_ref = req.status) !== 200 && _ref !== 304) { - a.textContent = "Error " + req.statusText + " (" + req.status + ")"; - return; - } - Build.spoilerRange[thread.board] = req.response.posts[0].custom_spoiler; - posts = []; - postsRoot = []; - filesCount = 0; - _ref1 = req.response.posts; - for (_i = 0, _len = _ref1.length; _i < _len; _i++) { - postData = _ref1[_i]; - if (postData.no === thread.ID) { - continue; - } - if (post = thread.posts[postData.no]) { - if ('file' in post) { - filesCount++; - } - postsRoot.push(post.nodes.root); - continue; - } - root = Build.postFromObject(postData, thread.board.ID); - post = new Post(root, thread, thread.board); - if ('file' in post) { - filesCount++; - } - posts.push(post); - postsRoot.push(root); - } - Post.callbacks.execute(posts); - $.after(a, postsRoot); - postsCount = postsRoot.length; - return a.textContent = ExpandThread.text('-', postsRoot.length, filesCount); - } - }; - - FileInfo = { - init: function() { - if (!Conf['File Info Formatting']) { - return; - } - return Post.callbacks.push({ - name: 'File Info Formatting', - cb: this.node - }); - }, - node: function() { - if (!this.file || this.isClone) { - return; - } - return this.file.text.innerHTML = "" + (FileInfo.format(Conf['fileInfo'], this)) + ""; - }, - format: function(formatString, post) { - return formatString.replace(/%([A-Za-z])/g, function(s, c) { - if (c in FileInfo.formatters) { - return FileInfo.formatters[c].call(post); - } else { - return s; - } - }); - }, - convertUnit: function(size, unit) { - var i; - if (unit === 'B') { - return "" + (size.toFixed()) + " Bytes"; - } - i = 1 + ['KB', 'MB'].indexOf(unit); - while (i--) { - size /= 1024; - } - size = unit === 'MB' ? Math.round(size * 100) / 100 : size.toFixed(); - return "" + size + " " + unit; - }, - escape: function(name) { - return name.replace(/<|>/g, function(c) { - return c === '<' && '<' || '>'; - }); - }, - formatters: { - t: function() { - return this.file.URL.match(/\d+\..+$/)[0]; - }, - T: function() { - return "" + (FileInfo.formatters.t.call(this)) + ""; - }, - l: function() { - return "" + (FileInfo.formatters.n.call(this)) + ""; - }, - L: function() { - return "" + (FileInfo.formatters.N.call(this)) + ""; - }, - n: function() { - var fullname, shortname; - fullname = this.file.name; - shortname = Build.shortFilename(this.file.name, this.isReply); - if (fullname === shortname) { - return FileInfo.escape(fullname); - } else { - return "" + (FileInfo.escape(shortname)) + "" + (FileInfo.escape(fullname)) + ""; - } - }, - N: function() { - return FileInfo.escape(this.file.name); - }, - p: function() { - if (this.file.isSpoiler) { - return 'Spoiler, '; - } else { - return ''; - } - }, - s: function() { - return this.file.size; - }, - B: function() { - return FileInfo.convertUnit(this.file.sizeInBytes, 'B'); - }, - K: function() { - return FileInfo.convertUnit(this.file.sizeInBytes, 'KB'); - }, - M: function() { - return FileInfo.convertUnit(this.file.sizeInBytes, 'MB'); - }, - r: function() { - return this.file.dimensions || 'PDF'; - } - } - }; - - Flash = { - init: function() { - if (g.BOARD.ID === 'f') { - return $.ready(Flash.initReady); - } - }, - initReady: function() { - var nav, sauceLink, swfName; - $.globalEval('SWFEmbed.init()'); - if (g.VIEW !== 'thread') { - return; - } - swfName = $('.fileText > a'); - nav = $('.navLinks.desktop'); - swfName = swfName.href.replace(/^(.*?)\/f\//g, ""); - sauceLink = $.el('a', { - textContent: 'Check Sauce on SWFCHAN', - href: "http://eye.swfchan.com/search/?q=" + swfName - }); - $.addClass(nav, 'swfSauce'); - $.rmClass(nav, 'navLinks'); - $.rmAll(nav); - return $.add(nav, [$.tn('['), sauceLink, $.tn(']')]); - } - }; - - Fourchan = { - init: function() { - var board; - board = g.BOARD.ID; - if (board === 'g') { - $.globalEval("window.addEventListener('prettyprint', function(e) {\n window.dispatchEvent(new CustomEvent('prettyprint:cb', {\n detail: prettyPrintOne(e.detail)\n }));\n}, false);"); - Post.callbacks.push({ - name: 'Parse /g/ code', - cb: this.code - }); - } - if (board === 'sci') { - $.globalEval("window.addEventListener('jsmath', function(e) {\n if (jsMath.loaded) {\n // process one post\n jsMath.ProcessBeforeShowing(document.getElementById(e.detail));\n } else {\n // load jsMath and process whole document\n jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);\n jsMath.Autoload.LoadJsMath();\n }\n}, false);"); - return Post.callbacks.push({ - name: 'Parse /sci/ math', - cb: this.math - }); - } - }, - code: function() { - var apply, pre, _i, _len, _ref; - if (this.isClone) { - return; - } - apply = function(e) { - return pre.innerHTML = e.detail; - }; - $.on(window, 'prettyprint:cb', apply); - _ref = $$('.prettyprint:not(.prettyprinted)', this.nodes.comment); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - pre = _ref[_i]; - $.event('prettyprint', pre.innerHTML, window); - } - $.off(window, 'prettyprint:cb', apply); - }, - math: function() { - if (this.isClone || !$('.math', this.nodes.comment)) { - return; - } - return $.event('jsmath', this.nodes.post.id, window); - } - }; - - IDColor = { - init: function() { - if (!Conf['Color User IDs']) { - return; - } - this.ids = { - Heaven: [0, 0, 0, '#fff'] - }; - return Post.callbacks.push({ - name: 'Color User IDs', - cb: this.node - }); - }, - node: function() { - var rgb, span, uid; - if (this.isClone || !((uid = this.info.uniqueID) && (span = $('span.hand', this.nodes.uniqueID)))) { - return; - } - rgb = IDColor.ids[uid] || IDColor.compute(uid); - span.style.color = rgb[3]; - span.style.backgroundColor = "rgb(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + ")"; - $.addClass(span, 'painted'); - return span.title = 'Highlight posts by this ID'; - }, - compute: function(uid) { - var hash, i, rgb; - i = 1; - hash = uid.charCodeAt(0); - while (i < 8) { - hash = (hash << 5) - hash + uid.charCodeAt(i++); - } - rgb = [(hash >> 24) & 0xFF, (hash >> 16) & 0xFF, (hash >> 8) & 0xFF]; - rgb.push((rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114) > 125 ? '#000' : '#fff'); - return this.ids[uid] = rgb; - } - }; - - Keybinds = { - init: function() { - var hotkey, init; - if (!Conf['Keybinds']) { - return; - } - for (hotkey in Conf.hotkeys) { - $.sync(hotkey, Keybinds.sync); - } - init = function() { - var node, _i, _len, _ref; - $.off(d, '4chanXInitFinished', init); - $.on(d, 'keydown', Keybinds.keydown); - _ref = $$('[accesskey]'); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - node = _ref[_i]; - node.removeAttribute('accesskey'); - } - }; - return $.on(d, '4chanXInitFinished', init); - }, - sync: function(key, hotkey) { - return Conf[hotkey] = key; - }, - keydown: function(e) { - var form, key, notification, notifications, op, searchInput, target, thread, threadRoot, _i, _len, _ref; - if (!(key = Keybinds.keyCode(e))) { - return; - } - target = e.target; - if (target.nodeName === 'EMBED') { - return; - } - if ((_ref = target.nodeName) === 'INPUT' || _ref === 'TEXTAREA') { - if (!/(Esc|Alt|Ctrl|Meta|Shift\+\w{2,})/.test(key)) { - return; - } - } - if (g.VIEW !== 'catalog') { - threadRoot = Nav.getThread(); - if (op = $('.op', threadRoot)) { - thread = Get.postFromNode(op).thread; - } - } - switch (key) { - case Conf['Toggle board list']: - if (Conf['Custom Board Navigation']) { - Header.toggleBoardList(); - } - break; - case Conf['Toggle header']: - Header.toggleBarVisibility(); - break; - case Conf['Open empty QR']: - Keybinds.qr(); - break; - case Conf['Open QR']: - if (g.VIEW === 'catalog') { - return; - } - Keybinds.qr(threadRoot); - break; - case Conf['Open settings']: - Settings.open(); - break; - case Conf['Close']: - if ($.id('fourchanx-settings')) { - Settings.close(); - } else if ((notifications = $$('.notification')).length) { - for (_i = 0, _len = notifications.length; _i < _len; _i++) { - notification = notifications[_i]; - $('.close', notification).click(); - } - } else if (QR.nodes) { - if (Conf['Persistent QR']) { - QR.hide(); - } else { - QR.close(); - } - } - break; - case Conf['Spoiler tags']: - if (target.nodeName !== 'TEXTAREA') { - return; - } - Keybinds.tags('spoiler', target); - break; - case Conf['Code tags']: - if (target.nodeName !== 'TEXTAREA') { - return; - } - Keybinds.tags('code', target); - break; - case Conf['Eqn tags']: - if (target.nodeName !== 'TEXTAREA') { - return; - } - Keybinds.tags('eqn', target); - break; - case Conf['Math tags']: - if (target.nodeName !== 'TEXTAREA') { - return; - } - Keybinds.tags('math', target); - break; - case Conf['Toggle sage']: - if (QR.nodes) { - Keybinds.sage(); - } - break; - case Conf['Submit QR']: - if (QR.nodes && !QR.status()) { - QR.submit(); - } - break; - case Conf['Post Without Name']: - if (QR.nodes && !QR.status()) { - Keybinds.name(); - QR.submit(); - } - break; - case Conf['Update']: - switch (g.VIEW) { - case 'thread': - ThreadUpdater.update(); - break; - case 'index': - if (Conf['JSON Navigation']) { - Index.update(); - } - } - break; - case Conf['Watch']: - if (g.VIEW === 'catalog') { - return; - } - ThreadWatcher.toggle(thread); - break; - case Conf['Expand image']: - if (g.VIEW === 'catalog') { - return; - } - Keybinds.img(threadRoot); - break; - case Conf['Expand images']: - if (g.VIEW === 'catalog') { - return; - } - Keybinds.img(threadRoot, true); - break; - case Conf['Open Gallery']: - if (g.VIEW === 'catalog') { - return; - } - Gallery.cb.toggle(); - break; - case Conf['fappeTyme']: - if (g.VIEW === 'catalog') { - return; - } - FappeTyme.cb.toggle.call({ - name: 'fappe' - }); - break; - case Conf['werkTyme']: - if (g.VIEW === 'catalog') { - return; - } - FappeTyme.cb.toggle.call({ - name: 'werk' - }); - break; - case Conf['Front page']: - if (Conf['JSON Navigation'] && g.VIEW === 'index') { - Index.userPageNav(1); - } else { - window.location = "/" + g.BOARD + "/"; - } - break; - case Conf['Open front page']: - $.open("/" + g.BOARD + "/"); - break; - case Conf['Next page']: - if (g.VIEW !== 'index') { - return; - } - if (Conf['JSON Navigation']) { - if (Conf['Index Mode'] !== 'all pages') { - $('.next button', Index.pagelist).click(); - } - } else { - if (form = $('.next form')) { - window.location = form.action; - } - } - break; - case Conf['Previous page']: - if (g.VIEW !== 'index') { - return; - } - if (Conf['JSON Navigation']) { - if (Conf['Index Mode'] !== 'all pages') { - $('.prev button', Index.pagelist).click(); - } - } else { - if (form = $('.prev form')) { - window.location = form.action; - } - } - break; - case Conf['Search form']: - if (g.VIEW !== 'index') { - return; - } - searchInput = Conf['JSON Navigation'] ? Index.searchInput : $.id('search-box'); - Header.scrollToIfNeeded(searchInput); - searchInput.click(); - searchInput.focus(); - break; - case Conf['Paged mode']: - if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'paged')) { - return; - } - Index.setIndexMode('paged'); - break; - case Conf['All pages mode']: - if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'all pages')) { - return; - } - Index.setIndexMode('all pages'); - break; - case Conf['Catalog mode']: - if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'catalog')) { - return; - } - Index.setIndexMode('catalog'); - break; - case Conf['Cycle sort type']: - if (g.VIEW !== 'index') { - return; - } - Index.cycleSortType(); - break; - case Conf['Open catalog']: - if (Conf['External Catalog']) { - window.location = CatalogLinks.external(g.BOARD.ID); - } else { - if (!Conf['JSON Navigation']) { - return window.location = "/" + g.BOARD + "/catalog"; - } - if (!(g.VIEW === 'index' && Conf['Index Mode'] !== 'catalog')) { - return; - } - Index.setIndexMode('catalog'); - } - break; - case Conf['Next thread']: - if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { - return; - } - Nav.scroll(+1); - break; - case Conf['Previous thread']: - if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { - return; - } - Nav.scroll(-1); - break; - case Conf['Expand thread']: - if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { - return; - } - ExpandThread.toggle(thread); - break; - case Conf['Open thread']: - if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { - return; - } - Keybinds.open(thread); - break; - case Conf['Open thread tab']: - if (g.VIEW !== 'index' || Conf['Index Mode'] === 'catalog') { - return; - } - Keybinds.open(thread, true); - break; - case Conf['Next reply']: - if (g.VIEW === 'catalog') { - return; - } - Keybinds.hl(+1, threadRoot); - break; - case Conf['Previous reply']: - if (g.VIEW === 'catalog') { - return; - } - Keybinds.hl(-1, threadRoot); - break; - case Conf['Deselect reply']: - if (g.VIEW === 'catalog') { - return; - } - Keybinds.hl(0, threadRoot); - break; - case Conf['Hide']: - PostHiding.toggle(thread.OP); - break; - case Conf['Previous Post Quoting You']: - QuoteMarkers.cb.seek('preceding'); - break; - case Conf['Next Post Quoting You']: - QuoteMarkers.cb.seek('following'); - break; - default: - return; - } - e.preventDefault(); - return e.stopPropagation(); - }, - keyCode: function(e) { - var kc, key; - key = (function() { - switch (kc = e.keyCode) { - case 8: - return ''; - case 13: - return 'Enter'; - case 27: - return 'Esc'; - case 37: - return 'Left'; - case 38: - return 'Up'; - case 39: - return 'Right'; - case 40: - return 'Down'; - default: - if ((48 <= kc && kc <= 57) || (65 <= kc && kc <= 90)) { - return String.fromCharCode(kc).toLowerCase(); - } else { - return null; - } - } - })(); - if (key) { - if (e.altKey) { - key = 'Alt+' + key; - } - if (e.ctrlKey) { - key = 'Ctrl+' + key; - } - if (e.metaKey) { - key = 'Meta+' + key; - } - if (e.shiftKey) { - key = 'Shift+' + key; - } - } - return key; - }, - qr: function(thread) { - if (!QR.postingIsEnabled) { - return; - } - QR.open(); - if (thread != null) { - QR.quote.call($('input', $('.post.highlight', thread) || thread)); - } - QR.nodes.com.focus(); - if (Conf['QR Shortcut']) { - return $.rmClass($('.qr-shortcut'), 'disabled'); - } - }, - tags: function(tag, ta) { - var range, selEnd, selStart, value; - value = ta.value; - selStart = ta.selectionStart; - selEnd = ta.selectionEnd; - ta.value = value.slice(0, selStart) + ("[" + tag + "]") + value.slice(selStart, selEnd) + ("[/" + tag + "]") + value.slice(selEnd); - range = ("[" + tag + "]").length + selEnd; - ta.setSelectionRange(range, range); - return $.event('input', null, ta); - }, - name: function() { - return QR.nodes.name.value = ''; - }, - sage: function() { - var isSage; - isSage = /sage/i.test(QR.nodes.email.value); - return QR.nodes.email.value = isSage ? "" : "sage"; - }, - img: function(thread, all) { - var post; - if (all) { - return ImageExpand.cb.toggleAll(); - } else { - post = Get.postFromNode($('.post.highlight', thread) || $('.op', thread)); - return ImageExpand.toggle(post); - } - }, - open: function(thread, tab) { - var url; - if (g.VIEW !== 'index') { - return; - } - url = Build.path(thread.board.ID, thread.ID); - if (tab) { - return $.open(url); - } else { - return location.href = url; - } - }, - hl: function(delta, thread) { - var axis, height, next, postEl, replies, reply, root, _i, _len; - postEl = $('.reply.highlight', thread); - if (!delta) { - if (postEl) { - $.rmClass(postEl, 'highlight'); - } - return; - } - if (postEl) { - height = postEl.getBoundingClientRect().height; - if (Header.getTopOf(postEl) >= -height && Header.getBottomOf(postEl) >= -height) { - root = postEl.parentNode; - axis = delta === +1 ? 'following' : 'preceding'; - if (!(next = $.x("" + axis + "-sibling::div[contains(@class,'replyContainer') and not(@hidden) and not(child::div[@class='stub'])][1]/child::div[contains(@class,'reply')]", root))) { - return; - } - Header.scrollToIfNeeded(next, delta === +1); - this.focus(next); - $.rmClass(postEl, 'highlight'); - return; - } - $.rmClass(postEl, 'highlight'); - } - replies = $$('.reply', thread); - if (delta === -1) { - replies.reverse(); - } - for (_i = 0, _len = replies.length; _i < _len; _i++) { - reply = replies[_i]; - if (delta === +1 && Header.getTopOf(reply) > 0 || delta === -1 && Header.getBottomOf(reply) > 0) { - this.focus(reply); - return; - } - } - }, - focus: function(post) { - return $.addClass(post, 'highlight'); - } - }; - - Nav = { - init: function() { - var next, prev; - switch (g.VIEW) { - case 'index': - if (!Conf['Index Navigation']) { - return; - } - break; - case 'thread': - if (!Conf['Reply Navigation']) { - return; - } - } - prev = $.el('a', { - href: 'javascript:;', - id: 'navPrev' - }); - next = $.el('a', { - href: 'javascript:;', - id: 'navNext' - }); - Header.addShortcut(prev, true); - Header.addShortcut(next, true); - $.on(prev, 'click', this.prev); - return $.on(next, 'click', this.next); - }, - prev: function() { - if (g.VIEW === 'thread') { - return window.scrollTo(0, 0); - } else { - return Nav.scroll(-1); - } - }, - next: function() { - if (g.VIEW === 'thread') { - return window.scrollTo(0, d.body.scrollHeight); - } else { - return Nav.scroll(+1); - } - }, - getThread: function() { - var threadRoot, _i, _len, _ref; - _ref = $$('.thread'); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - threadRoot = _ref[_i]; - if (Header.getTopOf(threadRoot) >= -threadRoot.getBoundingClientRect().height) { - return threadRoot; - } - } - return $('.board'); - }, - scroll: function(delta) { - var axis, next, thread, top; - thread = Nav.getThread(); - axis = delta === +1 ? 'following' : 'preceding'; - if (next = $.x("" + axis + "-sibling::div[contains(@class,'thread') and not(@hidden)][1]", thread)) { - top = Header.getTopOf(thread); - if (delta === +1 && top < 5 || delta === -1 && top > -5) { - thread = next; - } - } - return Header.scrollTo(thread); - } - }; - - RelativeDates = { - INTERVAL: $.MINUTE / 2, - init: function() { - switch (g.VIEW) { - case 'index': - this.flush(); - $.on(d, 'visibilitychange', this.flush); - if (!Conf['Relative Post Dates']) { - return; - } - break; - case 'thread': - if (!Conf['Relative Post Dates']) { - return; - } - this.flush(); - $.on(d, 'visibilitychange ThreadUpdate', this.flush); - break; - default: - return; - } - return Post.callbacks.push({ - name: 'Relative Post Dates', - cb: this.node - }); - }, - node: function() { - var dateEl; - if (this.isClone) { - return; - } - dateEl = this.nodes.date; - dateEl.title = dateEl.textContent; - return RelativeDates.update(this); - }, - relative: function(diff, now, date) { - var days, months, number, rounded, unit, years; - unit = (number = diff / $.DAY) >= 1 ? (years = now.getYear() - date.getYear(), months = now.getMonth() - date.getMonth(), days = now.getDate() - date.getDate(), years > 1 ? (number = years - (months < 0 || months === 0 && days < 0), 'year') : years === 1 && (months > 0 || months === 0 && days >= 0) ? (number = years, 'year') : (months = (months + 12) % 12) > 1 ? (number = months - (days < 0), 'month') : months === 1 && days >= 0 ? (number = months, 'month') : 'day') : (number = diff / $.HOUR) >= 1 ? 'hour' : (number = diff / $.MINUTE) >= 1 ? 'minute' : (number = Math.max(0, diff) / $.SECOND, 'second'); - rounded = Math.round(number); - if (rounded !== 1) { - unit += 's'; - } - return "" + rounded + " " + unit + " ago"; - }, - stale: [], - flush: function() { - var data, now, _i, _len, _ref; - if (d.hidden) { - return; - } - now = new Date(); - _ref = RelativeDates.stale; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - data = _ref[_i]; - RelativeDates.update(data, now); - } - RelativeDates.stale = []; - clearTimeout(RelativeDates.timeout); - return RelativeDates.timeout = setTimeout(RelativeDates.flush, RelativeDates.INTERVAL); - }, - update: function(data, now) { - var date, diff, isPost, relative, singlePost, _i, _len, _ref; - isPost = data instanceof Post; - date = isPost ? data.info.date : new Date(+data.dataset.utc); - now || (now = new Date()); - diff = now - date; - relative = RelativeDates.relative(diff, now, date); - if (isPost) { - _ref = [data].concat(data.clones); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - singlePost = _ref[_i]; - singlePost.nodes.date.firstChild.textContent = relative; - } - } else { - data.firstChild.textContent = relative; - } - return RelativeDates.setOwnTimeout(diff, data); - }, - setOwnTimeout: function(diff, data) { - var delay; - delay = diff < $.MINUTE ? $.SECOND - (diff + $.SECOND / 2) % $.SECOND : diff < $.HOUR ? $.MINUTE - (diff + $.MINUTE / 2) % $.MINUTE : diff < $.DAY ? $.HOUR - (diff + $.HOUR / 2) % $.HOUR : $.DAY - (diff + $.DAY / 2) % $.DAY; - return setTimeout(RelativeDates.markStale, delay, data); - }, - markStale: function(data) { - if (__indexOf.call(RelativeDates.stale, data) >= 0) { - return; - } - if (data instanceof Post && !g.posts[data.fullID]) { - return; - } - return RelativeDates.stale.push(data); - } - }; - - RemoveSpoilers = { - init: function() { - if (Conf['Reveal Spoilers']) { - $.addClass(doc, 'reveal-spoilers'); - } - if (Conf['Remove Spoilers']) { - return $.addClass(doc, 'remove-spoilers'); - } - } - }; - - Report = { - init: function() { - if (!/report/.test(location.search)) { - return; - } - return $.asap((function() { - return $.id('recaptcha_response_field'); - }), Report.ready); - }, - ready: function() { - var field; - field = $.id('recaptcha_response_field'); - $.on(field, 'keydown', function(e) { - if (e.keyCode === 8 && !field.value) { - return $.globalEval('Recaptcha.reload("t")'); - } - }); - return $.on($('form'), 'submit', function(e) { - var response; - e.preventDefault(); - response = field.value.trim(); - if (!/\s|^\d+$/.test(response)) { - field.value = "" + response + " " + response; - } - return this.submit(); - }); - } - }; - - Time = { - init: function() { - if (!Conf['Time Formatting']) { - return; - } - return Post.callbacks.push({ - name: 'Time Formatting', - cb: this.node - }); - }, - node: function() { - if (this.isClone) { - return; - } - return this.nodes.date.textContent = Time.format(Conf['time'], this.info.date); - }, - format: function(formatString, date) { - return formatString.replace(/%([A-Za-z])/g, function(s, c) { - if (c in Time.formatters) { - return Time.formatters[c].call(date); - } else { - return s; - } - }); - }, - day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], - month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], - zeroPad: function(n) { - if (n < 10) { - return "0" + n; - } else { - return n; - } - }, - formatters: { - a: function() { - return Time.day[this.getDay()].slice(0, 3); - }, - A: function() { - return Time.day[this.getDay()]; - }, - b: function() { - return Time.month[this.getMonth()].slice(0, 3); - }, - B: function() { - return Time.month[this.getMonth()]; - }, - d: function() { - return Time.zeroPad(this.getDate()); - }, - e: function() { - return this.getDate(); - }, - H: function() { - return Time.zeroPad(this.getHours()); - }, - I: function() { - return Time.zeroPad(this.getHours() % 12 || 12); - }, - k: function() { - return this.getHours(); - }, - l: function() { - return this.getHours() % 12 || 12; - }, - m: function() { - return Time.zeroPad(this.getMonth() + 1); - }, - M: function() { - return Time.zeroPad(this.getMinutes()); - }, - p: function() { - if (this.getHours() < 12) { - return 'AM'; - } else { - return 'PM'; - } - }, - P: function() { - if (this.getHours() < 12) { - return 'am'; - } else { - return 'pm'; - } - }, - S: function() { - return Time.zeroPad(this.getSeconds()); - }, - y: function() { - return this.getFullYear().toString().slice(2); - }, - Y: function() { - return this.getFullYear(); - } - } - }; - Navigate = { path: window.location.pathname, init: function() { @@ -16909,7 +16991,9 @@ Settings.dialog = dialog = $.el('div', { id: 'appchanx-settings', "class": 'dialog', - innerHTML: "
" + innerHTML: { + innerHTML: "\r
\r
\r" + } }); Settings.overlay = overlay = $.el('div', { id: 'overlay' @@ -17099,7 +17183,9 @@ }, filter: function(section) { var select; - section.innerHTML = "
"; + section.innerHTML = { + innerHTML: "\r
" + }; select = $('select', section); $.on(select, 'change', Settings.selectFilter); return Settings.selectFilter.call(select); @@ -17121,11 +17207,15 @@ $.add(div, ta); return; } - return div.innerHTML = "
Filter is disabled.

Use regular expressions, one per line.
Lines starting with a # will be ignored.
For example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
MD5 filtering uses exact string matching, not regular expressions.

"; + return div.innerHTML = { + innerHTML: "
Filter is disabled.
\r

\rUse regular expressions, one per line.
\rLines starting with a # will be ignored.
\rFor example, /weeaboo/i will filter posts containing the string `weeaboo`, case-insensitive.
\rMD5 filtering uses exact string matching, not regular expressions.\r

\r\r" + }; }, sauce: function(section) { var ta; - section.innerHTML = "
Sauce is disabled.
Lines starting with a # will be ignored.
You can specify a display text by appending ;text:[text] to the URL.
"; + section.innerHTML = { + innerHTML: "
Sauce is disabled.
\r
Lines starting with a # will be ignored.
\r
You can specify a display text by appending ;text:[text] to the URL.
\r\r\r" + }; ta = $('textarea', section); $.get('sauces', Conf['sauces'], function(item) { return ta.value = item['sauces'].replace(/\$\d/g, function(c) { @@ -17147,7 +17237,9 @@ }, advanced: function(section) { var archBoards, boardID, boardOptions, boardSelect, boards, event, files, i, input, inputs, item, items, name, o, row, rows, software, ta, table, withCredentials, _i, _j, _k, _l, _len, _len1, _len2, _len3, _len4, _len5, _m, _n, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; - section.innerHTML = "
Archiver
404 Redirect is disabled.
Thread redirectionPost fetchingFile redirection
Disabled selections indicate that only one archive is available for that board and redirection type.
Custom Board Navigation
New lines will be converted into spaces.

In the following examples for /g/, g can be changed to a different board ID (a, b, etc...), the current board (current), or the Twitter link (@).
Board link: g
Title link: g-title
Board link (Replace with title when on that board): g-replace
Full text link: g-full
Custom text link: g-text:\"Install Gentoo\"
External link: external-text:\"Google\",\"http://www.google.com\"
Index mode: g-mode:\"type\" where type is paged, all threads or catalog
Index sort: g-sort:\"type\" where type is bump order, last reply, creation date, reply count or file countCombinations are possible: g-text:\"VIP Catalog\"-mode:\"catalog\"-sort:\"creation date\"
Full board list toggle: toggle-all

[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h-mode:\"catalog\"-sort:\"file count\"] [t-text:\"Piracy\"]
will give you
[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
if you are on /g/.
Time Formatting is disabled.
:
Supported format specifiers:
Day: %a, %A, %d, %e
Month: %m, %b, %B
Year: %y, %Y
Hour: %k, %H, %l, %I, %p, %P
Minute: %M
Second: %S
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Quick Reply Personas

One item per line.
Items will be added in the relevant input's auto-completion list.
Password items will always be used, since there is no password input.
Lines starting with a # will be ignored.

Unread Favicon is disabled.
Thread Updater is disabled.
Interval:
Custom CSS
"; + section.innerHTML = { + innerHTML: "
\rArchiver\r
404 Redirect is disabled.
\r
\r\r\r\r\r\r\r\r
Thread redirectionPost fetchingFile redirection
\rDisabled selections indicate that only one archive is available for that board and redirection type.\r
\r
\rCustom Board Navigation\r
\rNew lines will be converted into spaces.

\r
In the following examples for /g/, g can be changed to a different board ID (a, b, etc...), the current board (current), or the Twitter link (@).
\r
Board link: g
\r
Title link: g-title
\r
Board link (Replace with title when on that board): g-replace
\r
Full text link: g-full
\r
Custom text link: g-text:\"Install Gentoo\"
\r
External link: external-text:\"Google\",\"http://www.google.com\"
\r
Index mode: g-mode:\"type\" where type is paged, all threads or catalog
\r
Index sort: g-sort:\"type\" where type is bump order, last reply, creation date, reply count or file countCombinations are possible: g-text:\"VIP Catalog\"-mode:\"catalog\"-sort:\"creation date\"
\r
Full board list toggle: toggle-all
\r
\r
\r[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h-mode:\"catalog\"-sort:\"file count\"] [t-text:\"Piracy\"]
\rwill give you
\r[ + ] [Technology] [Technology / Anime & Manga / Otaku Culture] [x / wsg / h] [Piracy]
\rif you are on /g/.\r
\r
\r
\rTime Formatting is disabled.\r
:
\r
Supported format specifiers:
\r
Day: %a, %A, %d, %e
\r
Month: %m, %b, %B
\r
Year: %y, %Y
\r
Hour: %k, %H, %l, %I, %p, %P
\r
Minute: %M
\r
Second: %S
\r
\r
\rQuote Backlinks formatting is disabled.\r
:
\r
\r
\rFile Info Formatting is disabled.\r
:
\r
Link: %l (truncated), %L (untruncated), %T (Unix timestamp)
\r
Original file name: %n (truncated), %N (untruncated), %t (Unix timestamp)
\r
Spoiler indicator: %p
\r
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
\r
Resolution: %r (Displays 'PDF' for PDF files)
\r
\r
\rQuick Reply Personas\r\r

\rOne item per line.
\rItems will be added in the relevant input's auto-completion list.
\rPassword items will always be used, since there is no password input.
\rLines starting with a # will be ignored.\r

\r\r
\r
\rUnread Favicon is disabled.\r\r\r
\r
\rThread Updater is disabled.\r
\rInterval: \r
\r
\r
\r Custom CSS\r
\r\r\r
\r
\r" + }; items = {}; inputs = {}; _ref = ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss']; @@ -17342,7 +17434,9 @@ }, keybinds: function(section) { var arr, input, inputs, items, key, tbody, tr, _ref; - section.innerHTML = "
Keybinds are disabled.
Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
Press Backspace to disable a keybind.
ActionsKeybinds
"; + section.innerHTML = { + innerHTML: "
Keybinds are disabled.
\r
Allowed keys: a-z, 0-9, Ctrl, Shift, Alt, Meta, Enter, Esc, Up, Down, Right, Left.
\r
Press Backspace to disable a keybind.
\r\r\r
ActionsKeybinds
" + }; tbody = $('tbody', section); items = {}; inputs = {}; diff --git a/builds/updates.xml b/builds/updates.xml deleted file mode 100644 index 786a7fdf3..000000000 --- a/builds/updates.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/package.json b/package.json index 37f04506e..75b92a439 100644 --- a/package.json +++ b/package.json @@ -19,15 +19,14 @@ ], "excludes": [ "*://blog.4chan.org/*", - "*://dis.4chan.org/*", - "*://a.4cdn.org/*" + "*://dis.4chan.org/*" ], "files": { "metajs": "appchan-x.meta.js", "userjs": "appchan-x.user.js" }, "min": { - "chrome": "33", + "chrome": "32", "firefox": "26", "greasemonkey": "1.14" } @@ -53,7 +52,9 @@ "author": "Zixaphir ", "contributors": [ "Nicolas Stepien ", - "James Campos " + "James Campos ", + "seaweedchan ", + "ccd0" ], "license": "MIT", "readmeFilename": "README.md", diff --git a/src/Archive/Redirect.coffee b/src/Archive/Redirect.coffee index c058a61d2..892078ef3 100755 --- a/src/Archive/Redirect.coffee +++ b/src/Archive/Redirect.coffee @@ -53,7 +53,10 @@ Redirect = post: (archive, {boardID, postID}) -> # For fuuka-based archives: # https://github.com/eksopl/fuuka/issues/27 - URL = new String "#{Redirect.protocol archive}#{archive.domain}/_/api/chan/post/?board=#{boardID}&num=#{postID}" + protocol = Redirect.protocol archive + URL = new String "#{protocol}#{archive.domain}/_/api/chan/post/?board=#{boardID}&num=#{postID}" + return '' unless Redirect.securityCheck URL + URL.archive = archive URL @@ -76,3 +79,17 @@ Redirect = else "#{boardID}/?task=search2&search_#{if type is 'image' then 'media_hash' else type}=#{value}" "#{Redirect.protocol archive}#{archive.domain}/#{path}" + + securityCheck: (URL) -> + /^https:\/\//.test(URL) or + location.protocol is 'http:' or + Conf['Except Archives from Encryption'] + + navigate: (URL, alternative) -> + if URL and ( + Redirect.securityCheck(URL) or + confirm "Redirect to #{URL}?\n\nYour connection will not be encrypted." + ) + location.replace URL + else if alternative + location.replace alternative \ No newline at end of file diff --git a/src/Archive/archives.json b/src/Archive/archives.json index a9f08fbd1..571df5359 100644 --- a/src/Archive/archives.json +++ b/src/Archive/archives.json @@ -5,7 +5,7 @@ "http": false, "https": true, "software": "foolfuuka", - "boards": ["a", "biz", "c", "co", "diy", "gd", "h", "i", "int", "jp", "k", "m", "mlp", "out", "po", "r9k", "s4s", "sci", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], + "boards": ["a", "biz", "c", "co", "diy", "gd", "h", "i", "int", "jp", "k", "m", "mlp", "out", "po", "r9k", "s4s", "sci", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"], "files": ["a", "biz", "c", "co", "diy", "gd", "h", "i", "jp", "k", "m", "mlp", "po", "s4s", "sci", "tg", "u", "v", "vg", "vp", "vr", "wsg"] }, { "uid": 3, @@ -53,12 +53,13 @@ "boards": ["asp", "cm", "h", "hc", "hm", "n", "p", "r", "s", "soc", "y"], "files": ["asp", "cm", "h", "hc", "hm", "n", "p", "r", "s", "soc", "y"] }, { - "uid": 17, + "uid": 21, "name": "imcute", "domain": "imcute.yt", "http": true, "https": false, "software": "foolfuuka", "boards": ["an", "fit", "gif", "int", "mlp", "out", "r9k", "toy"], - "files": ["an", "fit", "gif", "int", "mlp", "out", "r9k", "toy"] + "files": ["an", "fit", "gif", "int", "mlp", "out", "r9k", "toy"], + "imagehosts": ["http://imcute.yt/"] }] diff --git a/src/Filtering/Anonymize.coffee b/src/Filtering/Anonymize.coffee index 5b9f4e6a1..915361dc6 100755 --- a/src/Filtering/Anonymize.coffee +++ b/src/Filtering/Anonymize.coffee @@ -1,10 +1,12 @@ Anonymize = init: -> - return if !Conf['Anonymize'] + return unless g.VIEW in ['index', 'thread', 'archive'] and Conf['Anonymize'] + return @archive() if g.VIEW is 'archive' Post.callbacks.push name: 'Anonymize' cb: @node + node: -> return if @info.capcode or @isClone {name, tripcode, email} = @nodes @@ -16,3 +18,8 @@ Anonymize = if @info.email $.replace email, name delete @nodes.email + + archive: -> + $.ready -> + name.textContent = 'Anonymous' for name in $$ '.name' + $.rm trip for trip in $$ '.postertrip' \ No newline at end of file diff --git a/src/Filtering/Filter.coffee b/src/Filtering/Filter.coffee index 7231f44f0..e7bcd4808 100755 --- a/src/Filtering/Filter.coffee +++ b/src/Filtering/Filter.coffee @@ -1,21 +1,21 @@ Filter = filters: {} init: -> - return if !Conf['Filter'] + return unless g.VIEW in ['index', 'thread'] and Conf['Filter'] unless Conf['Filtered Backlinks'] $.addClass doc, 'hide-backlinks' for key of Config.filter @filters[key] = [] - for filter in Conf[key].split '\n' - continue if filter[0] is '#' + for line in Conf[key].split '\n' + continue if line[0] is '#' - unless regexp = filter.match /\/(.+)\/(\w*)/ + unless regexp = line.match /\/(.+)\/(\w*)/ continue # Don't mix up filter flags with the regular expression. - filter = filter.replace regexp[0], '' + filter = line.replace regexp[0], '' # Do not add this filter to the list if it's not a global one # and it's not specifically applicable to the current board. @@ -33,7 +33,11 @@ Filter = regexp = RegExp regexp[1], regexp[2] catch err # I warned you, bro. - new Notice 'warning', err.message, 60 + new Notice 'warning', [ + $.tn "Invalid #{key} filter: " + line, + $.el 'br' + $.tn err.message + ], 60 continue # Filter OPs along with their threads, replies only, or both. @@ -60,18 +64,7 @@ Filter = top = filter.match(/top:(yes|no)/)?[1] or 'yes' top = top is 'yes' # Turn it into a boolean - @filters[key].push { - hide: !hl - op: op - stub: stub - class: hl - top: top - match: regexp - test: if typeof regexp is 'string' - Filter.stringTest # MD5 checking - else - Filter.regexpTest - } + @filters[key].push @createFilter regexp, op, stub, hl, top # Only execute filter types that contain valid filters. unless @filters[key].length @@ -82,25 +75,41 @@ Filter = name: 'Filter' cb: @node + createFilter: (regexp, op, stub, hl, top) -> + test = + if typeof regexp is 'string' + # MD5 checking + Filter.stringTest + else + Filter.regexpTest + + settings = + hide: !hl + stub: stub + class: hl + top: top + + (value, isReply) -> return settings if Filter.test(test, value, isReply) + node: -> - return if @isClone + return if @isClone or @isFetchedQuote for key of Filter.filters value = Filter[key] @ # Continue if there's nothing to filter (no tripcode for example). continue if value is false - for obj in Filter.filters[key] - unless Filter.test obj, value, @isReply + for filter in Filter.filters[key] + unless result = filter value, @isReply continue # Hide - if obj.hide + if result.hide continue unless @isReply or g.VIEW is 'index' - @hide "Hidden by filtering the #{key}: #{obj.match}", obj.stub + @hide "Hidden by filtering the #{key}: #{result.match}", result.stub return # Highlight - @highlight "Highlighted by filtering the #{key}: #{obj.match}", obj.class, obj.top + @highlight "Highlighted by filtering the #{key}: #{result.match}", result.class, result.top stringTest: (string, value) -> string is value @@ -112,6 +121,7 @@ Filter = unless test match, value return false true + name: (post) -> if 'name' of post.info return post.info.name @@ -128,10 +138,6 @@ Filter = if 'capcode' of post.info return post.info.capcode false - email: (post) -> - if 'email' of post.info - return post.info.email - false subject: (post) -> if 'subject' of post.info return post.info.subject or false @@ -151,7 +157,7 @@ Filter = dimensions: (post) -> {file} = post if file and (file.isImage or file.isVideo) - return post.file.dimensions + return file.dimensions false filesize: (post) -> if post.file @@ -164,7 +170,7 @@ Filter = menu: init: -> - return if !Conf['Menu'] or !Conf['Filter'] + return unless g.VIEW in ['index', 'thread'] and Conf['Menu'] and Conf['Filter'] div = $.el 'div', textContent: 'Filter' @@ -182,7 +188,6 @@ Filter = ['Unique ID', 'uniqueID'] ['Tripcode', 'tripcode'] ['Capcode', 'capcode'] - ['E-mail', 'email'] ['Subject', 'subject'] ['Comment', 'comment'] ['Flag', 'flag']