diff --git a/CHANGELOG.md b/CHANGELOG.md index ca7726798..22d650336 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,47 @@ -Sometimes the changelog has notes (not comprehensive) acknowledging people's work. This does not mean the changes are their fault, only that their code was used. All changes to the script are chosen by and the fault of the maintainer (ccd0). +### v1.13.12 + +**v1.13.12.1** *(2017-09-29)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.1/builds/4chan-X-noupdate.crx)] +- Merge v1.13.11.5: Fix lag after settings changes. + +**v1.13.12.0** *(2017-09-28)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.12.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.11.4. +- Preliminary support for Greasemonkey 4. +- Minor custom cooldown bugfix. +- (BeltranBot) Fix 'open thread in new tab' keybind for VM/TM + +### v1.13.11 + +**v1.13.11.5** *(2017-09-29)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.5/builds/4chan-X-noupdate.crx)] +- Fix lag after settings changes. + +**v1.13.11.4** *(2017-08-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.4/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.4/builds/4chan-X-noupdate.crx)] +- Merge v1.13.10.7: Fix quote preview bug when reply is in index data but no thread object exists. #1478 + +**v1.13.11.3** *(2017-08-13)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.3/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.3/builds/4chan-X-noupdate.crx)] +- Add language setting for time formatting. + +**v1.13.11.2** *(2017-08-12)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.2/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.2/builds/4chan-X-noupdate.crx)] +- Last Long Reply order will now ignore hidden and filtered replies. + +**v1.13.11.1** *(2017-08-10)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.1/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.1/builds/4chan-X-noupdate.crx)] +- Merge v1.13.10.6: Disable 'Redirect to HTTPS' on platforms where we use localStorage for saving settings. + +**v1.13.11.0** *(2017-08-08)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.0/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.11.0/builds/4chan-X-noupdate.crx)] +- Based on v1.13.10.5. +- Support [spoiler] and [code] tags in 'Copy Text' menu item. +- Trim quoted text to text fully inside post. #1108 + ### v1.13.10 +**v1.13.10.7** *(2017-08-24)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.7/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.7/builds/4chan-X-noupdate.crx)] +- Fix quote preview bug when reply is in index data but no thread object exists. #1478 + +**v1.13.10.6** *(2017-08-10)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.6/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.6/builds/4chan-X-noupdate.crx)] +- Disable 'Redirect to HTTPS' on platforms where we use localStorage for saving settings. + **v1.13.10.5** *(2017-08-04)* - [[Userscript](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.5/builds/4chan-X-noupdate.user.js)] [[Chrome extension](https://raw.githubusercontent.com/ccd0/4chan-x/1.13.10.5/builds/4chan-X-noupdate.crx)] - Better parsing of archive links for Quote Inlining / Hover. - Add Board Tips. diff --git a/README.md b/README.md index 3f11a57b7..22fc2f280 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,12 @@ https://github.com/Nebukazar/OneeChan. ## Please note **Uninstalling**: 4chan X disables the native extension, so if you uninstall 4chan X, you'll need to re-enable it. To do this, click the `[Settings]` link in the top right corner, uncheck "`Disable the native extension`" in the panel that appears, and click the "`Save Settings`" button. If you don't see a "`Save Settings`" button, it may be being hidden by your ad blocker. -**Private browsing**: 4chan X does not yet support private browsing / incognito mode. Although it may work in this mode, browsing data recorded by 4chan X, such as your last read post in a thread and which posts are yours, will still need to be cleared manually by resetting your settings. To control what browsing data 4chan X records, use the `Remember Last Read Post` and `Remember Your Posts` options in the settings panel. - -**HTTPS**: 4chan X currently shares your settings and post history between the HTTP and HTTPS versions of 4chan. If you are concerned about protecting your privacy against a man-in-the-middle attack, you should disable 4chan X on the HTTP version of 4chan and/or install [HTTPS Everywhere](https://www.eff.org/https-everywhere). +**Private browsing**: By default, 4chan X remembers your last read post in a thread and which posts were made by you, even if you are in private browsing / incognito mode. If you want to turn this off, uncheck the `Remember Last Read Post` and `Remember Your Posts` options in the settings panel. You can clear all 4chan browsing history saved by 4chan X by resetting your settings. ## Install ### Firefox -You will first need to install a userscript manager such as [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/). Unfortunately, Firefox's transition to WebExtensions has forced a complete rewrite of Greasemonkey, leaving the current version of Greasemonkey unmaintained and unreliable. Until the WebExtensions version of Greasemonkey is complete, your options are: - -- Use [Firefox ESR](https://www.mozilla.org/en-US/firefox/organizations/), which should continue to work with the current version of [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/) until a new one is ready. -- If you're using a freedom-respecting build/fork of Firefox, you can try [this build of Greasemonkey](https://www.4chan-x.net/greasemonkey-2017.07.27.beta.xpi) based on [this pull request](https://github.com/greasemonkey/greasemonkey/pull/2507) which may fix the issues some people are having. -- Install [Violentmonkey](https://addons.mozilla.org/en-US/firefox/addon/violentmonkey/) or [Tampermonkey](https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/). These are already WebExtensions, but due to the current limitations of WebExtensions, they offer less functionality than Greasemonkey. -- Use [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/) with the current version of Firefox, but don't be surprised when things break. - -After Greasemonkey or an alternative userscript manager is installed, **[click here to install 4chan X](https://www.4chan-x.net/builds/4chan-X.user.js)**. +Install [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/), then **[click here to install 4chan X](https://www.4chan-x.net/builds/4chan-X.user.js)**. Ports of Greasemonkey are available for [SeaMonkey](https://sourceforge.net/projects/gmport/) and [Pale Moon](https://github.com/janekptacijarabaci/greasemonkey/releases/latest). diff --git a/builds/4chan-X-beta.crx b/builds/4chan-X-beta.crx index 8b79bcb9a..c8cc6364f 100644 Binary files a/builds/4chan-X-beta.crx and b/builds/4chan-X-beta.crx differ diff --git a/builds/4chan-X-beta.meta.js b/builds/4chan-X-beta.meta.js index 36c78d639..031e9987c 100644 --- a/builds/4chan-X-beta.meta.js +++ b/builds/4chan-X-beta.meta.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X beta -// @version 1.13.10.5 +// @version 1.13.12.1 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -48,6 +48,11 @@ // @grant GM_addValueChangeListener // @grant GM_openInTab // @grant GM_xmlhttpRequest +// @grant GM.getValue +// @grant GM.setValue +// @grant GM.deleteValue +// @grant GM.listValues +// @grant GM.xmlHttpRequest // @run-at document-start // @updateURL https://www.4chan-x.net/builds/4chan-X-beta.meta.js // @downloadURL https://www.4chan-x.net/builds/4chan-X-beta.user.js diff --git a/builds/4chan-X-beta.user.js b/builds/4chan-X-beta.user.js index d7987e319..7bda6c13a 100644 --- a/builds/4chan-X-beta.user.js +++ b/builds/4chan-X-beta.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X beta -// @version 1.13.10.5 +// @version 1.13.12.1 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -48,6 +48,11 @@ // @grant GM_addValueChangeListener // @grant GM_openInTab // @grant GM_xmlhttpRequest +// @grant GM.getValue +// @grant GM.setValue +// @grant GM.deleteValue +// @grant GM.listValues +// @grant GM.xmlHttpRequest // @run-at document-start // @updateURL https://www.4chan-x.net/builds/4chan-X-beta.meta.js // @downloadURL https://www.4chan-x.net/builds/4chan-X-beta.user.js @@ -153,7 +158,7 @@ docSet = function() { }; g = { - VERSION: '1.13.10.5', + VERSION: '1.13.12.1', NAMESPACE: '4chan X.', boards: {} }; @@ -425,6 +430,7 @@ Config = (function() { jsWhitelist: 'http://s.4cdn.org\nhttps://s.4cdn.org\nhttp://www.google.com\nhttps://www.google.com\nhttps://www.gstatic.com\nhttp://cdn.mathjax.org\nhttps://cdn.mathjax.org\nhttps://cdnjs.cloudflare.com\n\'self\'\n\'unsafe-inline\'\n\'unsafe-eval\'\n\n# Banner ads\n#http://s.zkcdn.net/ados.js\n#https://s.zkcdn.net/ados.js\n#http://engine.4chan-ads.org\n#https://engine.4chan-ads.org', captchaLanguage: '', time: '%m/%d/%y(%a)%H:%M:%S', + timeLocale: '', backlink: '>>%id', fileInfo: '%l %d (%p%s, %r%g)', favicon: 'ferongr', @@ -4474,7 +4480,7 @@ $ = (function() { var lastModified; lastModified = {}; return function(url, options, extra) { - var err, event, form, i, len, r, ref, ref1, type, upCallbacks, whenModified; + var err, event, form, j, len, r, ref, ref1, type, upCallbacks, whenModified; if (options == null) { options = {}; } @@ -4517,8 +4523,8 @@ $ = (function() { throw err; } ref1 = ['error', 'loadend']; - for (i = 0, len = ref1.length; i < len; i++) { - event = ref1[i]; + for (j = 0, len = ref1.length; j < len; j++) { + event = ref1[j]; r["on" + event] = options["on" + event]; $.queueTask($.event, event, null, r); } @@ -4554,7 +4560,7 @@ $ = (function() { return; } $.on(req, 'load', function(e) { - var fn1, i, len, ref; + var fn1, j, len, ref; this.evt = e; ref = this.callbacks; fn1 = (function(_this) { @@ -4564,8 +4570,8 @@ $ = (function() { }); }; })(this); - for (i = 0, len = ref.length; i < len; i++) { - cb = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + cb = ref[j]; fn1(cb); } return delete this.callbacks; @@ -4664,19 +4670,19 @@ $ = (function() { }; $.addClass = function() { - var className, classNames, el, i, len; + var className, classNames, el, j, len; el = arguments[0], classNames = 2 <= arguments.length ? slice.call(arguments, 1) : []; - for (i = 0, len = classNames.length; i < len; i++) { - className = classNames[i]; + for (j = 0, len = classNames.length; j < len; j++) { + className = classNames[j]; el.classList.add(className); } }; $.rmClass = function() { - var className, classNames, el, i, len; + var className, classNames, el, j, len; el = arguments[0], classNames = 2 <= arguments.length ? slice.call(arguments, 1) : []; - for (i = 0, len = classNames.length; i < len; i++) { - className = classNames[i]; + for (j = 0, len = classNames.length; j < len; j++) { + className = classNames[j]; el.classList.remove(className); } }; @@ -4706,13 +4712,13 @@ $ = (function() { }; $.nodes = function(nodes) { - var frag, i, len, node; + var frag, j, len, node; if (!(nodes instanceof Array)) { return nodes; } frag = $.frag(); - for (i = 0, len = nodes.length; i < len; i++) { - node = nodes[i]; + for (j = 0, len = nodes.length; j < len; j++) { + node = nodes[j]; frag.appendChild(node); } return frag; @@ -4751,19 +4757,19 @@ $ = (function() { }; $.on = function(el, events, handler) { - var event, i, len, ref; + var event, j, len, ref; ref = events.split(' '); - for (i = 0, len = ref.length; i < len; i++) { - event = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + event = ref[j]; el.addEventListener(event, handler, false); } }; $.off = function(el, events, handler) { - var event, i, len, ref; + var event, j, len, ref; ref = events.split(' '); - for (i = 0, len = ref.length; i < len; i++) { - event = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + event = ref[j]; el.removeEventListener(event, handler, false); } }; @@ -4959,213 +4965,293 @@ $ = (function() { return item; }; + $.oneItemSugar = function(fn) { + return function(key, val, cb) { + if (typeof key === 'string') { + return fn($.item(key, val), cb); + } else { + return fn(key, val); + } + }; + }; + $.syncing = {}; - $.currentValue = {}; - - $.GM_getValue = function(key) { - var err; - try { - return $.currentValue[key] = GM_getValue(key); - } catch (_error) { - err = _error; - return $.currentValue[key]; - } - }; - - $.GM_setValue = function(key, val) { - $.currentValue[key] = val; - return GM_setValue(key, val); - }; - - $.GM_deleteValue = function(key) { - delete $.currentValue[key]; - return GM_deleteValue(key); - }; - - if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { - $.getValue = $.GM_getValue; - $.listValues = function() { - return GM_listValues(); - }; - } else if ($.hasStorage) { - $.getValue = function(key) { - return localStorage[key]; - }; - $.listValues = function() { - var key, results; + if ((typeof GM !== "undefined" && GM !== null ? GM.deleteValue : void 0) != null) { + $.syncChannel = new BroadcastChannel(g.NAMESPACE + 'sync'); + $.on($.syncChannel, 'message', function(e) { + var cb, key, ref, results, val; + ref = e.data; results = []; - for (key in localStorage) { - if (key.slice(0, g.NAMESPACE.length) === g.NAMESPACE) { - results.push(key); + for (key in ref) { + val = ref[key]; + if ((cb = $.syncing[key])) { + results.push(cb(val, key)); } } return results; - }; - } else { - $.getValue = function() {}; - $.listValues = function() { - return []; - }; - } - - if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { - $.setValue = $.GM_setValue; - $.deleteValue = $.GM_deleteValue; - } else if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { - $.oldValue = {}; - $.setValue = function(key, val) { - $.GM_setValue(key, val); - if (key in $.syncing) { - $.oldValue[key] = val; - if ($.hasStorage) { - return localStorage[key] = val; - } - } - }; - $.deleteValue = function(key) { - $.GM_deleteValue(key); - if (key in $.syncing) { - delete $.oldValue[key]; - if ($.hasStorage) { - return localStorage.removeItem(key); - } - } - }; - if (!$.hasStorage) { - $.cantSync = true; - } - } else if ($.hasStorage) { - $.oldValue = {}; - $.setValue = function(key, val) { - if (key in $.syncing) { - $.oldValue[key] = val; - } - return localStorage[key] = val; - }; - $.deleteValue = function(key) { - if (key in $.syncing) { - delete $.oldValue[key]; - } - return localStorage.removeItem(key); - }; - } else { - $.setValue = function() {}; - $.deleteValue = function() {}; - $.cantSync = $.cantSet = true; - } - - if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + }); $.sync = function(key, cb) { - return $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function(key2, oldValue, newValue, remote) { - if (remote) { - if (newValue !== void 0) { - newValue = JSON.parse(newValue); - } - return cb(newValue, key); + return $.syncing[key] = cb; + }; + $.forceSync = function() {}; + $["delete"] = function(keys, cb) { + var key; + if (!(keys instanceof Array)) { + keys = [keys]; + } + return Promise.all((function() { + var j, len, results; + results = []; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + results.push(GM.deleteValue(g.NAMESPACE + key)); } + return results; + })()).then(function() { + var items, j, key, len; + items = {}; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + items[key] = void 0; + } + $.syncChannel.postMessage(items); + return typeof cb === "function" ? cb() : void 0; }); }; - $.forceSync = function() {}; - } else if ((typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) || $.hasStorage) { - $.sync = function(key, cb) { - key = g.NAMESPACE + key; - $.syncing[key] = cb; - return $.oldValue[key] = $.getValue(key); - }; - (function() { - var onChange; - onChange = function(arg) { - var cb, key, newValue; - key = arg.key, newValue = arg.newValue; - if (!(cb = $.syncing[key])) { - return; + $.get = $.oneItemSugar(function(items, cb) { + var key, keys; + keys = Object.keys(items); + return Promise.all((function() { + var j, len, results; + results = []; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + results.push(GM.getValue(g.NAMESPACE + key)); } - if (newValue != null) { - if (newValue === $.oldValue[key]) { - return; + return results; + })()).then(function(values) { + var i, j, len, val; + for (i = j = 0, len = values.length; j < len; i = ++j) { + val = values[i]; + if (val) { + items[keys[i]] = JSON.parse(val); } - $.oldValue[key] = newValue; - return cb(JSON.parse(newValue), key.slice(g.NAMESPACE.length)); - } else { - if ($.oldValue[key] == null) { - return; + } + return cb(items); + }); + }); + $.set = $.oneItemSugar(function(items, cb) { + var key, val; + return Promise.all((function() { + var results; + results = []; + for (key in items) { + val = items[key]; + results.push(GM.setValue(g.NAMESPACE + key, JSON.stringify(val))); + } + return results; + })()).then(function() { + $.syncChannel.postMessage(items); + return typeof cb === "function" ? cb() : void 0; + }); + }); + $.clear = function(cb) { + return GM.listValues.then(function(keys) { + return $["delete"](keys.map(function(key) { + return key.replace(g.NAMESPACE, ''); + }), cb); + }); + }; + } else { + $.currentValue = {}; + $.GM_getValue = function(key) { + var err; + try { + return $.currentValue[key] = GM_getValue(key); + } catch (_error) { + err = _error; + return $.currentValue[key]; + } + }; + $.GM_setValue = function(key, val) { + $.currentValue[key] = val; + return GM_setValue(key, val); + }; + $.GM_deleteValue = function(key) { + delete $.currentValue[key]; + return GM_deleteValue(key); + }; + if (typeof GM_deleteValue === "undefined" || GM_deleteValue === null) { + $.perProtocolSettings = true; + } + if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { + $.getValue = $.GM_getValue; + $.listValues = function() { + return GM_listValues(); + }; + } else if ($.hasStorage) { + $.getValue = function(key) { + return localStorage[key]; + }; + $.listValues = function() { + var key, results; + results = []; + for (key in localStorage) { + if (key.slice(0, g.NAMESPACE.length) === g.NAMESPACE) { + results.push(key); + } + } + return results; + }; + } else { + $.getValue = function() {}; + $.listValues = function() { + return []; + }; + } + if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + $.setValue = $.GM_setValue; + $.deleteValue = $.GM_deleteValue; + } else if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { + $.oldValue = {}; + $.setValue = function(key, val) { + $.GM_setValue(key, val); + if (key in $.syncing) { + $.oldValue[key] = val; + if ($.hasStorage) { + return localStorage[key] = val; } - delete $.oldValue[key]; - return cb(void 0, key.slice(g.NAMESPACE.length)); } }; - $.on(window, 'storage', onChange); - return $.forceSync = function(key) { - key = g.NAMESPACE + key; - return onChange({ - key: key, - newValue: $.getValue(key) + $.deleteValue = function(key) { + $.GM_deleteValue(key); + if (key in $.syncing) { + delete $.oldValue[key]; + if ($.hasStorage) { + return localStorage.removeItem(key); + } + } + }; + if (!$.hasStorage) { + $.cantSync = true; + } + } else if ($.hasStorage) { + $.oldValue = {}; + $.setValue = function(key, val) { + if (key in $.syncing) { + $.oldValue[key] = val; + } + return localStorage[key] = val; + }; + $.deleteValue = function(key) { + if (key in $.syncing) { + delete $.oldValue[key]; + } + return localStorage.removeItem(key); + }; + } else { + $.setValue = function() {}; + $.deleteValue = function() {}; + $.cantSync = $.cantSet = true; + } + if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + $.sync = function(key, cb) { + return $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function(key2, oldValue, newValue, remote) { + if (remote) { + if (newValue !== void 0) { + newValue = JSON.parse(newValue); + } + return cb(newValue, key); + } }); }; - })(); - } else { - $.sync = function() {}; - $.forceSync = function() {}; + $.forceSync = function() {}; + } else if ((typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) || $.hasStorage) { + $.sync = function(key, cb) { + key = g.NAMESPACE + key; + $.syncing[key] = cb; + return $.oldValue[key] = $.getValue(key); + }; + (function() { + var onChange; + onChange = function(arg) { + var cb, key, newValue; + key = arg.key, newValue = arg.newValue; + if (!(cb = $.syncing[key])) { + return; + } + if (newValue != null) { + if (newValue === $.oldValue[key]) { + return; + } + $.oldValue[key] = newValue; + return cb(JSON.parse(newValue), key.slice(g.NAMESPACE.length)); + } else { + if ($.oldValue[key] == null) { + return; + } + delete $.oldValue[key]; + return cb(void 0, key.slice(g.NAMESPACE.length)); + } + }; + $.on(window, 'storage', onChange); + return $.forceSync = function(key) { + key = g.NAMESPACE + key; + return onChange({ + key: key, + newValue: $.getValue(key) + }); + }; + })(); + } else { + $.sync = function() {}; + $.forceSync = function() {}; + } + $["delete"] = function(keys) { + var j, key, len; + if (!(keys instanceof Array)) { + keys = [keys]; + } + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + $.deleteValue(g.NAMESPACE + key); + } + }; + $.get = $.oneItemSugar(function(items, cb) { + return $.queueTask($.getSync, items, cb); + }); + $.getSync = function(items, cb) { + var key, val2; + for (key in items) { + if ((val2 = $.getValue(g.NAMESPACE + key))) { + items[key] = JSON.parse(val2); + } + } + return cb(items); + }; + $.set = $.oneItemSugar(function(items, cb) { + return $.queueTask(function() { + var key, value; + for (key in items) { + value = items[key]; + $.setValue(g.NAMESPACE + key, JSON.stringify(value)); + } + return typeof cb === "function" ? cb() : void 0; + }); + }); + $.clear = function(cb) { + $["delete"](Object.keys(Conf)); + $["delete"](['previousversion', 'QR Size', 'captchas', 'QR.persona', 'hiddenPSA']); + try { + $["delete"]($.listValues().map(function(key) { + return key.replace(g.NAMESPACE, ''); + })); + } catch (_error) {} + return typeof cb === "function" ? cb() : void 0; + }; } - $["delete"] = function(keys) { - var i, key, len; - if (!(keys instanceof Array)) { - keys = [keys]; - } - for (i = 0, len = keys.length; i < len; i++) { - key = keys[i]; - $.deleteValue(g.NAMESPACE + key); - } - }; - - $.get = function(key, val, cb) { - var items; - if (typeof cb === 'function') { - items = $.item(key, val); - } else { - items = key; - cb = val; - } - return $.queueTask($.getSync, items, cb); - }; - - $.getSync = function(items, cb) { - var key, val2; - for (key in items) { - if ((val2 = $.getValue(g.NAMESPACE + key))) { - items[key] = JSON.parse(val2); - } - } - return cb(items); - }; - - $.set = function(keys, val, cb) { - var key, value; - if (typeof keys === 'string') { - $.setValue(g.NAMESPACE + keys, JSON.stringify(val)); - } else { - for (key in keys) { - value = keys[key]; - $.setValue(g.NAMESPACE + key, JSON.stringify(value)); - } - cb = val; - } - return typeof cb === "function" ? cb() : void 0; - }; - - $.clear = function(cb) { - $["delete"](Object.keys(Conf)); - $["delete"](['previousversion', 'QR Size', 'captchas', 'QR.persona', 'hiddenPSA']); - try { - $["delete"]($.listValues().map(function(key) { - return key.replace(g.NAMESPACE, ''); - })); - } catch (_error) {} - return typeof cb === "function" ? cb() : void 0; - }; - return $; }).call(this); @@ -5231,7 +5317,7 @@ CrossOrigin = (function() { } else { options.responseType = 'arraybuffer'; } - return GM_xmlhttpRequest(options); + return ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)(options); }, file: function(url, cb) { return CrossOrigin.binary(url, function(data, contentType, contentDisposition) { @@ -5269,7 +5355,7 @@ CrossOrigin = (function() { return; } callbacks[url] = [cb]; - return GM_xmlhttpRequest({ + return ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)({ method: "GET", url: url + '', onload: function(xhr) { @@ -5550,7 +5636,6 @@ DataBoard = (function() { DataBoard.prototype.deleteIfEmpty = function(arg) { var boardID, threadID; boardID = arg.boardID, threadID = arg.threadID; - $.forceSync(this.key); if (threadID) { if (!Object.keys(this.data.boards[boardID][threadID]).length) { delete this.data.boards[boardID][threadID]; @@ -5652,13 +5737,13 @@ DataBoard = (function() { }; DataBoard.prototype.ajaxClean = function(boardID) { - return $.cache("//a.4cdn.org/" + boardID + "/threads.json", (function(_this) { + return $.cache(location.protocol + "//a.4cdn.org/" + boardID + "/threads.json", (function(_this) { return function(e1) { var ref; if ((ref = e1.target.status) !== 200 && ref !== 404) { return; } - return $.cache("//a.4cdn.org/" + boardID + "/archive.json", function(e2) { + return $.cache(location.protocol + "//a.4cdn.org/" + boardID + "/archive.json", function(e2) { var ref1; if ((ref1 = e2.target.status) !== 200 && ref1 !== 404) { return; @@ -5734,9 +5819,8 @@ Fetcher = (function() { this.insert(post); return; } - if ((post = (ref = Index.replyData) != null ? ref[this.boardID + "." + this.postID] : void 0)) { + if ((post = (ref = Index.replyData) != null ? ref[this.boardID + "." + this.postID] : void 0) && (thread = g.threads[this.boardID + "." + this.threadID])) { board = g.boards[this.boardID]; - thread = g.threads[this.boardID + "." + this.threadID]; post = new Post(Build.postFromObject(post, this.boardID), thread, board); post.isFetchedQuote = true; Main.callbackNodes('Post', [post]); @@ -5745,7 +5829,7 @@ Fetcher = (function() { } this.root.textContent = "Loading post No." + this.postID + "..."; if (this.threadID) { - $.cache("//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json", (function(_this) { + $.cache(location.protocol + "//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json", (function(_this) { return function(e, isCached) { return _this.fetchedPost(e.target, isCached); }; @@ -5812,7 +5896,7 @@ Fetcher = (function() { } if (post.no !== this.postID) { if (isCached) { - api = "//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json"; + api = location.protocol + "//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json"; $.cleanCache(function(url) { return url === api; }); @@ -6161,6 +6245,7 @@ Notice = (function() { Post = (function() { var Post, + 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; }; Post = (function() { @@ -6299,6 +6384,13 @@ Post = (function() { return this.nodesToText(bq).trim().replace(/\s+$/gm, ''); }; + Post.prototype.commentOrig = function() { + var bq; + bq = this.nodes.commentClean.cloneNode(true); + this.insertTags(bq); + return this.nodesToText(bq); + }; + Post.prototype.nodesToText = function(bq) { var i, node, nodes, text; text = ""; @@ -6344,6 +6436,20 @@ Post = (function() { return $.rm($('.fortune', bq)); }; + Post.prototype.insertTags = function(bq) { + var j, k, len, len1, node, ref, ref1; + ref = $$('s, .removed-spoiler', bq); + for (j = 0, len = ref.length; j < len; j++) { + node = ref[j]; + $.replace(node, [$.tn('[spoiler]')].concat(slice.call(node.childNodes), [$.tn('[/spoiler]')])); + } + ref1 = $$('.prettyprint', bq); + for (k = 0, len1 = ref1.length; k < len1; k++) { + node = ref1[k]; + $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); + } + }; + Post.prototype.parseQuotes = function() { var j, len, quotelink, ref; this.quotes = []; @@ -6999,7 +7105,6 @@ Redirect = (function() { { "uid": 29, "name": "Archived.Moe", "domain": "archived.moe", "http": true, "https": true, "software": "foolfuuka", "boards": [ "3", "a", "aco", "adv", "an", "asp", "b", "bant", "biz", "c", "can", "cgl", "ck", "cm", "co", "cock", "d", "diy", "e", "f", "fa", "fap", "fit", "fitlit", "g", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "int", "jp", "k", "lgbt", "lit", "m", "mlp", "mlpol", "mo", "mtv", "mu", "n", "news", "o", "out", "outsoc", "p", "po", "pol", "qa", "qst", "r", "r9k", "s", "s4s", "sci", "soc", "sp", "spa", "t", "tg", "toy", "trash", "trv", "tv", "u", "v", "vg", "vint", "vip", "vp", "vr", "w", "wg", "wsg", "wsr", "x", "y" ], "files": [ "can", "cock", "fap", "fitlit", "gd", "mlpol", "mo", "mtv", "outsoc", "po", "qst", "spa", "vint", "vip" ], "search": [ "aco", "adv", "an", "asp", "b", "bant", "c", "can", "cgl", "ck", "cm", "cock", "con", "d", "diy", "e", "f", "fap", "fitlit", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "lgbt", "lit", "mlpol", "mo", "mtv", "n", "news", "o", "out", "outsoc", "p", "po", "q", "qa", "qst", "r", "s", "soc", "spa", "trv", "u", "vint", "vip", "w", "wg", "wsg", "wsr", "x", "y" ], "reports": true }, { "uid": 30, "name": "TheBArchive.com", "domain": "thebarchive.com", "http": true, "https": true, "software": "foolfuuka", "boards": [ "b", "bant" ], "files": [ "b", "bant" ], "reports": true }, { "uid": 31, "name": "Archive Of Sins", "domain": "archiveofsins.com", "http": true, "https": true, "software": "foolfuuka", "boards": [ "h", "hc", "hm", "r", "s", "soc" ], "files": [ "h", "hc", "hm", "r", "s", "soc" ], "reports": true }, - { "uid": 32, "name": "4tan", "domain": "boards.4tan.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "3", "a", "aco", "adv", "an", "asp", "b", "bant", "biz", "c", "can", "cgl", "ck", "cm", "co", "cock", "d", "diy", "e", "f", "fa", "fap", "fit", "fitlit", "g", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "int", "jp", "k", "lgbt", "lit", "m", "mlp", "mlpol", "mo", "mtv", "mu", "n", "news", "o", "out", "outsoc", "p", "po", "pol", "qa", "qst", "r", "r9k", "s", "s4s", "sci", "soc", "sp", "spa", "t", "tg", "toy", "trash", "trv", "tv", "u", "v", "vg", "vint", "vip", "vp", "vr", "w", "wg", "wsg", "wsr", "x", "y" ], "files": [ "bant", "can", "cock", "fap", "fitlit", "mlpol", "mo", "mtv", "outsoc", "spa", "vint" ], "reports": true }, { "uid": 33, "name": "YEET Archive", "domain": "archive.yeet.net", "http": true, "https": true, "software": "foolfuuka", "boards": [ "g", "k", "qa", "s4s" ] } ], init: function() { @@ -8301,7 +8406,7 @@ BoardConfig = (function() { var now, ref; now = Date.now(); if (!((now - 2 * $.HOUR < (ref = Conf['boardConfig'].lastChecked || 0) && ref <= now))) { - return $.ajax('//a.4cdn.org/boards.json', { + return $.ajax(location.protocol + "//a.4cdn.org/boards.json", { onloadend: this.load }); } else { @@ -10206,7 +10311,7 @@ Index = (function() { location.reload(); return; } - Index.req = $.ajax("//a.4cdn.org/" + g.BOARD + "/catalog.json", { + Index.req = $.ajax(location.protocol + "//a.4cdn.org/" + g.BOARD + "/catalog.json", { onabort: Index.load, onloadend: Index.load }, { @@ -10342,6 +10447,9 @@ Index = (function() { return Index.parsedThreads[threadID].isHidden; } }, + isHiddenReply: function(threadID, replyData) { + return PostHiding.isHidden(g.BOARD.ID, threadID, replyData.no) || Filter.isHidden(Build.parseJSON(replyData, g.BOARD.ID)); + }, buildThreads: function(threadIDs, isCatalog) { var ID, OP, err, errors, isStale, k, len1, newPosts, newThreads, obj, thread, threadData, threads; threads = []; @@ -10476,10 +10584,7 @@ Index = (function() { replies = []; for (k = 0, len1 = lastReplies.length; k < len1; k++) { data = lastReplies[k]; - if (PostHiding.isHidden(g.BOARD.ID, thread.ID, data.no)) { - continue; - } - if (Filter.isHidden(Build.parseJSON(data, g.BOARD.ID))) { + if (Index.isHiddenReply(thread.ID, data)) { continue; } reply = Build.catalogReply(thread, data); @@ -10494,12 +10599,13 @@ Index = (function() { $.add(thread.OP.nodes.post, nodes.replies); }, sort: function() { - var lastlong, liveThreadData, liveThreadIDs, threadIDs; + var lastlong, lastlongD, liveThreadData, liveThreadIDs, thread, threadIDs; liveThreadIDs = Index.liveThreadIDs, liveThreadData = Index.liveThreadData; if (!liveThreadData) { return; } Index.sortedThreadIDs = (function() { + var k, len1; switch (Index.currentSort.replace(/-rev$/, '')) { case 'lastreply': return slice.call(liveThreadData).sort(function(a, b) { @@ -10520,6 +10626,9 @@ Index = (function() { ref = thread.last_replies || []; for (i = k = ref.length - 1; k >= 0; i = k += -1) { r = ref[i]; + if (Index.isHiddenReply(thread.no, r)) { + continue; + } len = r.com ? Build.parseComment(r.com).replace(/[^a-z]/ig, '').length : 0; if (len >= Index.lastLongThresholds[+(!!r.ext)]) { return r; @@ -10531,8 +10640,13 @@ Index = (function() { return thread; } }; + lastlongD = {}; + for (k = 0, len1 = liveThreadData.length; k < len1; k++) { + thread = liveThreadData[k]; + lastlongD[thread.no] = lastlong(thread).no; + } return slice.call(liveThreadData).sort(function(a, b) { - return lastlong(b).no - lastlong(a).no; + return lastlongD[b.no] - lastlongD[a.no]; }).map(function(post) { return post.no; }); @@ -11008,6 +11122,9 @@ Settings = (function() { if ($.engine !== 'gecko') { $('div[data-name="Remember QR Size"]', section).hidden = true; } + if ($.perProtocolSettings) { + $('div[data-name="Redirect to HTTPS"]', section).hidden = true; + } $.get(items, function(items) { var val; for (key in items) { @@ -11512,7 +11629,7 @@ Settings = (function() { advanced: function(section) { var applyCSS, boardSelect, customCSS, event, input, inputs, interval, items, itemsArchive, j, k, l, len, len1, len2, len3, m, name, ref, ref1, ref2, ref3, table, updateArchives, warning; $.extend(section, { - innerHTML: "
Archives
404 Redirect is disabled.
Thread redirectionPost fetchingFile redirection

Archive Lists: Each line below should be an archive list in this format or a URL to load an archive list from.
Archive properties can be overriden by another item with the same uid (or if absent, its name).
Last updated:
Captcha Language
Choose from list of language codes. Leave blank to autoselect.
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
Archive link: g-archive
Internal archive link: g-expired
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"
Index-only link: g-index
Catalog-only link: g-catalog
Index mode: g-mode:"infinite scrolling"
Index sort: g-sort:"creation date rev"
External link: external-text:"Google","http://www.google.com"
Combinations are possible: g-index-text:"Technology Index"
Full board list toggle: toggle-all

[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [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
Literal %: %%
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (4chan filename)
Filename: %n (truncated), %N (untruncated), %t (4chan filename)
Download button: %d
Quick filter MD5: %f
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Tag: %g
Literal %: %%
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: seconds
Custom Cooldown Time
Seconds:
For more information about customizing 4chan X's CSS, see the styling guide.
Javascript Whitelist
Sources from which Javascript is allowed to be loaded by Content Security Policy.
Lines starting with a # will be ignored.
" + innerHTML: "
Archives
404 Redirect is disabled.
Thread redirectionPost fetchingFile redirection

Archive Lists: Each line below should be an archive list in this format or a URL to load an archive list from.
Archive properties can be overriden by another item with the same uid (or if absent, its name).
Last updated:
Captcha Language
Choose from list of language codes. Leave blank to autoselect.
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
Archive link: g-archive
Internal archive link: g-expired
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"
Index-only link: g-index
Catalog-only link: g-catalog
Index mode: g-mode:"infinite scrolling"
Index sort: g-sort:"creation date rev"
External link: external-text:"Google","http://www.google.com"
Combinations are possible: g-index-text:"Technology Index"
Full board list toggle: toggle-all

[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [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
Literal %: %%
Language tag:
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (4chan filename)
Filename: %n (truncated), %N (untruncated), %t (4chan filename)
Download button: %d
Quick filter MD5: %f
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Tag: %g
Literal %: %%
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: seconds
Custom Cooldown Time
Seconds:
For more information about customizing 4chan X's CSS, see the styling guide.
Javascript Whitelist
Sources from which Javascript is allowed to be loaded by Content Security Policy.
Lines starting with a # will be ignored.
" }); ref = $$('.warning', section); for (j = 0, len = ref.length; j < len; j++) { @@ -11531,7 +11648,7 @@ Settings = (function() { return $.id('lastarchivecheck').textContent = 'never'; }); items = {}; - ref2 = ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist']; + ref2 = ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'timeLocale', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist']; for (l = 0, len2 = ref2.length; l < len2; l++) { name = ref2[l]; items[name] = Conf[name]; @@ -11717,6 +11834,9 @@ Settings = (function() { time: function() { return this.nextElementSibling.textContent = Time.format(this.value, new Date()); }, + timeLocale: function() { + return Settings.time.call($('[name=time]', Settings.dialog)); + }, backlink: function() { return this.nextElementSibling.textContent = this.value.replace(/%(?:id|%)/g, function(x) { return { @@ -12952,7 +13072,7 @@ ImageCommon = (function() { return cb(URL); } }; - return $.ajax("//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", { + return $.ajax(location.protocol + "//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", { onload: function() { var i, len, postObj, ref; if (this.status === 404) { @@ -14867,7 +14987,7 @@ Embedding = (function() { }, title: { api: function(uid) { - return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); + return location.protocol + "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); }, text: function(_) { return _.title; @@ -15274,7 +15394,7 @@ CopyTextLink = (function() { el: a, order: 12, open: function(post) { - CopyTextLink.text = post.info.comment; + CopyTextLink.text = (post.origin || post).commentOrig(); return true; } }); @@ -15846,7 +15966,7 @@ BoardTips = (function() { tips: { qa: [ 1, { - innerHTML: "New to /qa/?
/qa/ is NOT an effective way to contact the mods.
Use IRC or feedback instead. More details here." + innerHTML: "New to /qa/?
/qa/ is NOT an effective way to contact the mods.
Message a mod on IRC or use feedback instead. More details here." } ] }, @@ -16075,7 +16195,7 @@ ExpandComment = (function() { return; } a.textContent = "Post No." + post + " Loading..."; - return $.cache("//a.4cdn.org" + (a.pathname.split(/\/+/).splice(0, 4).join('/')) + ".json", function() { + return $.cache(location.protocol + "//a.4cdn.org" + (a.pathname.split(/\/+/).splice(0, 4).join('/')) + ".json", function() { return ExpandComment.parse(this, a, post); }); }, @@ -16218,7 +16338,7 @@ ExpandThread = (function() { var status; ExpandThread.statuses[thread] = status = {}; a.textContent = Build.summaryText.apply(Build, ['...'].concat(slice.call(a.textContent.match(/\d+/g)))); - return status.req = $.cache("//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() { + return status.req = $.cache(location.protocol + "//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() { delete status.req; return ExpandThread.parse(this, thread, a); }); @@ -17255,7 +17375,7 @@ Keybinds = (function() { } url = "/" + thread.board + "/thread/" + thread; if (tab) { - return $.open(url); + return $.open(location.origin + url); } else { return location.href = url; } @@ -17907,6 +18027,30 @@ Time = (function() { }, day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + localeFormat: function(date, options, defaultValue) { + if (Conf['timeLocale']) { + try { + return Intl.DateTimeFormat(Conf['timeLocale'], options).format(date); + } catch (_error) {} + } + return defaultValue; + }, + localeFormatPart: function(date, options, part, defaultValue) { + var parts; + if (Conf['timeLocale']) { + try { + parts = Intl.DateTimeFormat(Conf['timeLocale'], options).formatToParts(date); + return parts.map(function(x) { + if (x.type === part) { + return x.value; + } else { + return ''; + } + }).join(''); + } catch (_error) {} + } + return defaultValue; + }, zeroPad: function(n) { if (n < 10) { return "0" + n; @@ -17916,16 +18060,24 @@ Time = (function() { }, formatters: { a: function() { - return Time.day[this.getDay()].slice(0, 3); + return Time.localeFormat(this, { + weekday: 'short' + }, Time.day[this.getDay()].slice(0, 3)); }, A: function() { - return Time.day[this.getDay()]; + return Time.localeFormat(this, { + weekday: 'long' + }, Time.day[this.getDay()]); }, b: function() { - return Time.month[this.getMonth()].slice(0, 3); + return Time.localeFormat(this, { + month: 'short' + }, Time.month[this.getMonth()].slice(0, 3)); }, B: function() { - return Time.month[this.getMonth()]; + return Time.localeFormat(this, { + month: 'long' + }, Time.month[this.getMonth()]); }, d: function() { return Time.zeroPad(this.getDate()); @@ -17952,18 +18104,13 @@ Time = (function() { return Time.zeroPad(this.getMinutes()); }, p: function() { - if (this.getHours() < 12) { - return 'AM'; - } else { - return 'PM'; - } + return Time.localeFormatPart(this, { + hour: 'numeric', + hour12: true + }, 'dayperiod', (this.getHours() < 12 ? 'AM' : 'PM')); }, P: function() { - if (this.getHours() < 12) { - return 'am'; - } else { - return 'pm'; - } + return Time.formatters.p.call(this).toLowerCase(); }, S: function() { return Time.zeroPad(this.getSeconds()); @@ -18360,7 +18507,7 @@ ThreadStats = (function() { return; } ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 2 * $.MINUTE); - return $.ajax("//a.4cdn.org/" + ThreadStats.thread.board + "/threads.json", { + return $.ajax(location.protocol + "//a.4cdn.org/" + ThreadStats.thread.board + "/threads.json", { onload: ThreadStats.onThreadsLoad }, { whenModified: 'ThreadStats' @@ -18585,7 +18732,7 @@ ThreadUpdater = (function() { } break; case 404: - return $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/catalog.json", { + return $.ajax(location.protocol + "//a.4cdn.org/" + ThreadUpdater.thread.board + "/catalog.json", { onloadend: function() { var confirmed, i, k, len, len1, page, ref, ref1, thread; if (this.status === 200) { @@ -18697,7 +18844,7 @@ ThreadUpdater = (function() { if ((ref = ThreadUpdater.req) != null) { ref.abort(); } - return ThreadUpdater.req = $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json", { + return ThreadUpdater.req = $.ajax(location.protocol + "//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json", { onloadend: ThreadUpdater.cb.load, timeout: $.MINUTE }, { @@ -19185,7 +19332,7 @@ ThreadWatcher = (function() { ThreadWatcher.status.textContent = '...'; $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); } - req = $.ajax("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", { + req = $.ajax(location.protocol + "//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", { onloadend: function() { return ThreadWatcher.parseStatus.call(this, thread); }, @@ -19898,7 +20045,6 @@ Unread = (function() { if (Unread.thread.isDead && !Unread.thread.isArchived) { return; } - Unread.db.forceSync(); return Unread.db.set({ boardID: Unread.thread.board.ID, threadID: Unread.thread.ID, @@ -21589,7 +21735,7 @@ QR = (function() { } }, quote: function(e) { - var ancestor, caretPos, com, frag, insideCode, j, k, l, len, len1, len2, len3, len4, len5, n, node, o, post, q, range, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, sel, text, thread; + var ancestor, caretPos, com, frag, i, insideCode, j, k, l, len, len1, len2, len3, n, node, o, post, postRange, range, ref, ref1, ref2, ref3, ref4, ref5, ref6, root, sel, text, thread; if (e != null) { e.preventDefault(); } @@ -21598,9 +21744,21 @@ QR = (function() { } sel = d.getSelection(); post = Get.postFromNode(this); + root = post.nodes.root; + postRange = new Range(); + postRange.selectNode(root); text = post.board.ID === g.BOARD.ID ? ">>" + post + "\n" : ">>>/" + post.board + "/" + post + "\n"; - if (sel.toString().trim() && post === Get.postFromNode(sel.anchorNode)) { - range = sel.getRangeAt(0); + for (i = j = 0, ref = sel.rangeCount; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) { + range = sel.getRangeAt(i); + if (range.compareBoundaryPoints(Range.START_TO_START, postRange) < 0) { + range.setStartBefore(root); + } + if (range.compareBoundaryPoints(Range.END_TO_END, postRange) > 0) { + range.setEndAfter(root); + } + if (!range.toString().trim()) { + continue; + } frag = range.cloneContents(); ancestor = range.commonAncestorContainer; if ($.x('ancestor-or-self::*[self::s or contains(@class,"removed-spoiler")]', ancestor)) { @@ -21611,37 +21769,28 @@ QR = (function() { $.prepend(frag, $.tn('[code]')); $.add(frag, $.tn('[/code]')); } - ref = $$((insideCode ? 'br' : '.prettyprint br'), frag); - for (j = 0, len = ref.length; j < len; j++) { - node = ref[j]; + ref1 = $$((insideCode ? 'br' : '.prettyprint br'), frag); + for (k = 0, len = ref1.length; k < len; k++) { + node = ref1[k]; $.replace(node, $.tn('\n')); } - ref1 = $$('br', frag); - for (k = 0, len1 = ref1.length; k < len1; k++) { - node = ref1[k]; + ref2 = $$('br', frag); + for (l = 0, len1 = ref2.length; l < len1; l++) { + node = ref2[l]; if (node !== frag.lastChild) { $.replace(node, $.tn('\n>')); } } - ref2 = $$('s, .removed-spoiler', frag); - for (l = 0, len2 = ref2.length; l < len2; l++) { - node = ref2[l]; - $.replace(node, [$.tn('[spoiler]')].concat(slice.call(node.childNodes), [$.tn('[/spoiler]')])); - } - ref3 = $$('.prettyprint', frag); - for (n = 0, len3 = ref3.length; n < len3; n++) { + Post.prototype.insertTags(frag); + ref3 = $$('.linkify[data-original]', frag); + for (n = 0, len2 = ref3.length; n < len2; n++) { node = ref3[n]; - $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); - } - ref4 = $$('.linkify[data-original]', frag); - for (o = 0, len4 = ref4.length; o < len4; o++) { - node = ref4[o]; $.replace(node, $.tn(node.dataset.original)); } - ref5 = $$('.embedder', frag); - for (q = 0, len5 = ref5.length; q < len5; q++) { - node = ref5[q]; - if (((ref6 = node.previousSibling) != null ? ref6.nodeValue : void 0) === ' ') { + ref4 = $$('.embedder', frag); + for (o = 0, len3 = ref4.length; o < len3; o++) { + node = ref4[o]; + if (((ref5 = node.previousSibling) != null ? ref5.nodeValue : void 0) === ' ') { $.rm(node.previousSibling); } $.rm(node); @@ -21649,7 +21798,7 @@ QR = (function() { text += ">" + (frag.textContent.trim()) + "\n"; } QR.openPost(); - ref7 = QR.nodes, com = ref7.com, thread = ref7.thread; + ref6 = QR.nodes, com = ref6.com, thread = ref6.thread; if (!com.value) { thread.value = Get.threadFromNode(this); } @@ -22502,9 +22651,9 @@ QR = (function() { if ((type === 'thread') === (cooldown.threadID === cooldown.postID) && cooldown.boardID !== g.BOARD.ID) { suffix = scope === 'global' ? '_global' : ''; seconds = Math.max(seconds, QR.cooldown.delays[type + suffix] - elapsed); - } - if (QR.cooldown.customCooldown) { - seconds = Math.max(seconds, parseInt(Conf['customCooldown'], 10) - elapsed); + if (QR.cooldown.customCooldown) { + seconds = Math.max(seconds, parseInt(Conf['customCooldown'], 10) - elapsed); + } } } nCooldowns += Object.keys(cooldowns).length; @@ -24506,7 +24655,7 @@ Main = (function() { items['previousversion'] = void 0; return ($.getSync || $.get)(items, function(items) { var ref1; - if (((ref1 = items['Redirect to HTTPS']) != null ? ref1 : Conf['Redirect to HTTPS']) && location.protocol !== 'https:') { + if (!$.perProtocolSettings && ((ref1 = items['Redirect to HTTPS']) != null ? ref1 : Conf['Redirect to HTTPS']) && location.protocol !== 'https:') { location.replace('https:' + location.host + location.pathname + location.search + location.hash); return; } @@ -24814,7 +24963,7 @@ Main = (function() { threads[0].ipCount = (m = scriptData.match(/\bunique_ips *= *(\d+)\b/)) ? +m[1] : void 0; } if (g.BOARD.ID === 'f' && g.VIEW === 'thread') { - $.ajax("//a.4cdn.org/f/thread/" + g.THREADID + ".json", { + $.ajax(location.protocol + "//a.4cdn.org/f/thread/" + g.THREADID + ".json", { timeout: $.MINUTE, onloadend: function() { if (this.response && posts[0].file) { diff --git a/builds/4chan-X-noupdate.crx b/builds/4chan-X-noupdate.crx index 3fd6b5156..371b85c6f 100644 Binary files a/builds/4chan-X-noupdate.crx and b/builds/4chan-X-noupdate.crx differ diff --git a/builds/4chan-X-noupdate.user.js b/builds/4chan-X-noupdate.user.js index 8c69bde13..a757cc7a9 100644 --- a/builds/4chan-X-noupdate.user.js +++ b/builds/4chan-X-noupdate.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X -// @version 1.13.10.5 +// @version 1.13.12.1 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -48,6 +48,11 @@ // @grant GM_addValueChangeListener // @grant GM_openInTab // @grant GM_xmlhttpRequest +// @grant GM.getValue +// @grant GM.setValue +// @grant GM.deleteValue +// @grant GM.listValues +// @grant GM.xmlHttpRequest // @run-at document-start // @updateURL https://noupdate.invalid/ // @downloadURL https://noupdate.invalid/ @@ -153,7 +158,7 @@ docSet = function() { }; g = { - VERSION: '1.13.10.5', + VERSION: '1.13.12.1', NAMESPACE: '4chan X.', boards: {} }; @@ -425,6 +430,7 @@ Config = (function() { jsWhitelist: 'http://s.4cdn.org\nhttps://s.4cdn.org\nhttp://www.google.com\nhttps://www.google.com\nhttps://www.gstatic.com\nhttp://cdn.mathjax.org\nhttps://cdn.mathjax.org\nhttps://cdnjs.cloudflare.com\n\'self\'\n\'unsafe-inline\'\n\'unsafe-eval\'\n\n# Banner ads\n#http://s.zkcdn.net/ados.js\n#https://s.zkcdn.net/ados.js\n#http://engine.4chan-ads.org\n#https://engine.4chan-ads.org', captchaLanguage: '', time: '%m/%d/%y(%a)%H:%M:%S', + timeLocale: '', backlink: '>>%id', fileInfo: '%l %d (%p%s, %r%g)', favicon: 'ferongr', @@ -4474,7 +4480,7 @@ $ = (function() { var lastModified; lastModified = {}; return function(url, options, extra) { - var err, event, form, i, len, r, ref, ref1, type, upCallbacks, whenModified; + var err, event, form, j, len, r, ref, ref1, type, upCallbacks, whenModified; if (options == null) { options = {}; } @@ -4517,8 +4523,8 @@ $ = (function() { throw err; } ref1 = ['error', 'loadend']; - for (i = 0, len = ref1.length; i < len; i++) { - event = ref1[i]; + for (j = 0, len = ref1.length; j < len; j++) { + event = ref1[j]; r["on" + event] = options["on" + event]; $.queueTask($.event, event, null, r); } @@ -4554,7 +4560,7 @@ $ = (function() { return; } $.on(req, 'load', function(e) { - var fn1, i, len, ref; + var fn1, j, len, ref; this.evt = e; ref = this.callbacks; fn1 = (function(_this) { @@ -4564,8 +4570,8 @@ $ = (function() { }); }; })(this); - for (i = 0, len = ref.length; i < len; i++) { - cb = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + cb = ref[j]; fn1(cb); } return delete this.callbacks; @@ -4664,19 +4670,19 @@ $ = (function() { }; $.addClass = function() { - var className, classNames, el, i, len; + var className, classNames, el, j, len; el = arguments[0], classNames = 2 <= arguments.length ? slice.call(arguments, 1) : []; - for (i = 0, len = classNames.length; i < len; i++) { - className = classNames[i]; + for (j = 0, len = classNames.length; j < len; j++) { + className = classNames[j]; el.classList.add(className); } }; $.rmClass = function() { - var className, classNames, el, i, len; + var className, classNames, el, j, len; el = arguments[0], classNames = 2 <= arguments.length ? slice.call(arguments, 1) : []; - for (i = 0, len = classNames.length; i < len; i++) { - className = classNames[i]; + for (j = 0, len = classNames.length; j < len; j++) { + className = classNames[j]; el.classList.remove(className); } }; @@ -4706,13 +4712,13 @@ $ = (function() { }; $.nodes = function(nodes) { - var frag, i, len, node; + var frag, j, len, node; if (!(nodes instanceof Array)) { return nodes; } frag = $.frag(); - for (i = 0, len = nodes.length; i < len; i++) { - node = nodes[i]; + for (j = 0, len = nodes.length; j < len; j++) { + node = nodes[j]; frag.appendChild(node); } return frag; @@ -4751,19 +4757,19 @@ $ = (function() { }; $.on = function(el, events, handler) { - var event, i, len, ref; + var event, j, len, ref; ref = events.split(' '); - for (i = 0, len = ref.length; i < len; i++) { - event = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + event = ref[j]; el.addEventListener(event, handler, false); } }; $.off = function(el, events, handler) { - var event, i, len, ref; + var event, j, len, ref; ref = events.split(' '); - for (i = 0, len = ref.length; i < len; i++) { - event = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + event = ref[j]; el.removeEventListener(event, handler, false); } }; @@ -4959,213 +4965,293 @@ $ = (function() { return item; }; + $.oneItemSugar = function(fn) { + return function(key, val, cb) { + if (typeof key === 'string') { + return fn($.item(key, val), cb); + } else { + return fn(key, val); + } + }; + }; + $.syncing = {}; - $.currentValue = {}; - - $.GM_getValue = function(key) { - var err; - try { - return $.currentValue[key] = GM_getValue(key); - } catch (_error) { - err = _error; - return $.currentValue[key]; - } - }; - - $.GM_setValue = function(key, val) { - $.currentValue[key] = val; - return GM_setValue(key, val); - }; - - $.GM_deleteValue = function(key) { - delete $.currentValue[key]; - return GM_deleteValue(key); - }; - - if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { - $.getValue = $.GM_getValue; - $.listValues = function() { - return GM_listValues(); - }; - } else if ($.hasStorage) { - $.getValue = function(key) { - return localStorage[key]; - }; - $.listValues = function() { - var key, results; + if ((typeof GM !== "undefined" && GM !== null ? GM.deleteValue : void 0) != null) { + $.syncChannel = new BroadcastChannel(g.NAMESPACE + 'sync'); + $.on($.syncChannel, 'message', function(e) { + var cb, key, ref, results, val; + ref = e.data; results = []; - for (key in localStorage) { - if (key.slice(0, g.NAMESPACE.length) === g.NAMESPACE) { - results.push(key); + for (key in ref) { + val = ref[key]; + if ((cb = $.syncing[key])) { + results.push(cb(val, key)); } } return results; - }; - } else { - $.getValue = function() {}; - $.listValues = function() { - return []; - }; - } - - if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { - $.setValue = $.GM_setValue; - $.deleteValue = $.GM_deleteValue; - } else if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { - $.oldValue = {}; - $.setValue = function(key, val) { - $.GM_setValue(key, val); - if (key in $.syncing) { - $.oldValue[key] = val; - if ($.hasStorage) { - return localStorage[key] = val; - } - } - }; - $.deleteValue = function(key) { - $.GM_deleteValue(key); - if (key in $.syncing) { - delete $.oldValue[key]; - if ($.hasStorage) { - return localStorage.removeItem(key); - } - } - }; - if (!$.hasStorage) { - $.cantSync = true; - } - } else if ($.hasStorage) { - $.oldValue = {}; - $.setValue = function(key, val) { - if (key in $.syncing) { - $.oldValue[key] = val; - } - return localStorage[key] = val; - }; - $.deleteValue = function(key) { - if (key in $.syncing) { - delete $.oldValue[key]; - } - return localStorage.removeItem(key); - }; - } else { - $.setValue = function() {}; - $.deleteValue = function() {}; - $.cantSync = $.cantSet = true; - } - - if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + }); $.sync = function(key, cb) { - return $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function(key2, oldValue, newValue, remote) { - if (remote) { - if (newValue !== void 0) { - newValue = JSON.parse(newValue); - } - return cb(newValue, key); + return $.syncing[key] = cb; + }; + $.forceSync = function() {}; + $["delete"] = function(keys, cb) { + var key; + if (!(keys instanceof Array)) { + keys = [keys]; + } + return Promise.all((function() { + var j, len, results; + results = []; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + results.push(GM.deleteValue(g.NAMESPACE + key)); } + return results; + })()).then(function() { + var items, j, key, len; + items = {}; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + items[key] = void 0; + } + $.syncChannel.postMessage(items); + return typeof cb === "function" ? cb() : void 0; }); }; - $.forceSync = function() {}; - } else if ((typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) || $.hasStorage) { - $.sync = function(key, cb) { - key = g.NAMESPACE + key; - $.syncing[key] = cb; - return $.oldValue[key] = $.getValue(key); - }; - (function() { - var onChange; - onChange = function(arg) { - var cb, key, newValue; - key = arg.key, newValue = arg.newValue; - if (!(cb = $.syncing[key])) { - return; + $.get = $.oneItemSugar(function(items, cb) { + var key, keys; + keys = Object.keys(items); + return Promise.all((function() { + var j, len, results; + results = []; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + results.push(GM.getValue(g.NAMESPACE + key)); } - if (newValue != null) { - if (newValue === $.oldValue[key]) { - return; + return results; + })()).then(function(values) { + var i, j, len, val; + for (i = j = 0, len = values.length; j < len; i = ++j) { + val = values[i]; + if (val) { + items[keys[i]] = JSON.parse(val); } - $.oldValue[key] = newValue; - return cb(JSON.parse(newValue), key.slice(g.NAMESPACE.length)); - } else { - if ($.oldValue[key] == null) { - return; + } + return cb(items); + }); + }); + $.set = $.oneItemSugar(function(items, cb) { + var key, val; + return Promise.all((function() { + var results; + results = []; + for (key in items) { + val = items[key]; + results.push(GM.setValue(g.NAMESPACE + key, JSON.stringify(val))); + } + return results; + })()).then(function() { + $.syncChannel.postMessage(items); + return typeof cb === "function" ? cb() : void 0; + }); + }); + $.clear = function(cb) { + return GM.listValues.then(function(keys) { + return $["delete"](keys.map(function(key) { + return key.replace(g.NAMESPACE, ''); + }), cb); + }); + }; + } else { + $.currentValue = {}; + $.GM_getValue = function(key) { + var err; + try { + return $.currentValue[key] = GM_getValue(key); + } catch (_error) { + err = _error; + return $.currentValue[key]; + } + }; + $.GM_setValue = function(key, val) { + $.currentValue[key] = val; + return GM_setValue(key, val); + }; + $.GM_deleteValue = function(key) { + delete $.currentValue[key]; + return GM_deleteValue(key); + }; + if (typeof GM_deleteValue === "undefined" || GM_deleteValue === null) { + $.perProtocolSettings = true; + } + if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { + $.getValue = $.GM_getValue; + $.listValues = function() { + return GM_listValues(); + }; + } else if ($.hasStorage) { + $.getValue = function(key) { + return localStorage[key]; + }; + $.listValues = function() { + var key, results; + results = []; + for (key in localStorage) { + if (key.slice(0, g.NAMESPACE.length) === g.NAMESPACE) { + results.push(key); + } + } + return results; + }; + } else { + $.getValue = function() {}; + $.listValues = function() { + return []; + }; + } + if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + $.setValue = $.GM_setValue; + $.deleteValue = $.GM_deleteValue; + } else if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { + $.oldValue = {}; + $.setValue = function(key, val) { + $.GM_setValue(key, val); + if (key in $.syncing) { + $.oldValue[key] = val; + if ($.hasStorage) { + return localStorage[key] = val; } - delete $.oldValue[key]; - return cb(void 0, key.slice(g.NAMESPACE.length)); } }; - $.on(window, 'storage', onChange); - return $.forceSync = function(key) { - key = g.NAMESPACE + key; - return onChange({ - key: key, - newValue: $.getValue(key) + $.deleteValue = function(key) { + $.GM_deleteValue(key); + if (key in $.syncing) { + delete $.oldValue[key]; + if ($.hasStorage) { + return localStorage.removeItem(key); + } + } + }; + if (!$.hasStorage) { + $.cantSync = true; + } + } else if ($.hasStorage) { + $.oldValue = {}; + $.setValue = function(key, val) { + if (key in $.syncing) { + $.oldValue[key] = val; + } + return localStorage[key] = val; + }; + $.deleteValue = function(key) { + if (key in $.syncing) { + delete $.oldValue[key]; + } + return localStorage.removeItem(key); + }; + } else { + $.setValue = function() {}; + $.deleteValue = function() {}; + $.cantSync = $.cantSet = true; + } + if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + $.sync = function(key, cb) { + return $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function(key2, oldValue, newValue, remote) { + if (remote) { + if (newValue !== void 0) { + newValue = JSON.parse(newValue); + } + return cb(newValue, key); + } }); }; - })(); - } else { - $.sync = function() {}; - $.forceSync = function() {}; + $.forceSync = function() {}; + } else if ((typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) || $.hasStorage) { + $.sync = function(key, cb) { + key = g.NAMESPACE + key; + $.syncing[key] = cb; + return $.oldValue[key] = $.getValue(key); + }; + (function() { + var onChange; + onChange = function(arg) { + var cb, key, newValue; + key = arg.key, newValue = arg.newValue; + if (!(cb = $.syncing[key])) { + return; + } + if (newValue != null) { + if (newValue === $.oldValue[key]) { + return; + } + $.oldValue[key] = newValue; + return cb(JSON.parse(newValue), key.slice(g.NAMESPACE.length)); + } else { + if ($.oldValue[key] == null) { + return; + } + delete $.oldValue[key]; + return cb(void 0, key.slice(g.NAMESPACE.length)); + } + }; + $.on(window, 'storage', onChange); + return $.forceSync = function(key) { + key = g.NAMESPACE + key; + return onChange({ + key: key, + newValue: $.getValue(key) + }); + }; + })(); + } else { + $.sync = function() {}; + $.forceSync = function() {}; + } + $["delete"] = function(keys) { + var j, key, len; + if (!(keys instanceof Array)) { + keys = [keys]; + } + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + $.deleteValue(g.NAMESPACE + key); + } + }; + $.get = $.oneItemSugar(function(items, cb) { + return $.queueTask($.getSync, items, cb); + }); + $.getSync = function(items, cb) { + var key, val2; + for (key in items) { + if ((val2 = $.getValue(g.NAMESPACE + key))) { + items[key] = JSON.parse(val2); + } + } + return cb(items); + }; + $.set = $.oneItemSugar(function(items, cb) { + return $.queueTask(function() { + var key, value; + for (key in items) { + value = items[key]; + $.setValue(g.NAMESPACE + key, JSON.stringify(value)); + } + return typeof cb === "function" ? cb() : void 0; + }); + }); + $.clear = function(cb) { + $["delete"](Object.keys(Conf)); + $["delete"](['previousversion', 'QR Size', 'captchas', 'QR.persona', 'hiddenPSA']); + try { + $["delete"]($.listValues().map(function(key) { + return key.replace(g.NAMESPACE, ''); + })); + } catch (_error) {} + return typeof cb === "function" ? cb() : void 0; + }; } - $["delete"] = function(keys) { - var i, key, len; - if (!(keys instanceof Array)) { - keys = [keys]; - } - for (i = 0, len = keys.length; i < len; i++) { - key = keys[i]; - $.deleteValue(g.NAMESPACE + key); - } - }; - - $.get = function(key, val, cb) { - var items; - if (typeof cb === 'function') { - items = $.item(key, val); - } else { - items = key; - cb = val; - } - return $.queueTask($.getSync, items, cb); - }; - - $.getSync = function(items, cb) { - var key, val2; - for (key in items) { - if ((val2 = $.getValue(g.NAMESPACE + key))) { - items[key] = JSON.parse(val2); - } - } - return cb(items); - }; - - $.set = function(keys, val, cb) { - var key, value; - if (typeof keys === 'string') { - $.setValue(g.NAMESPACE + keys, JSON.stringify(val)); - } else { - for (key in keys) { - value = keys[key]; - $.setValue(g.NAMESPACE + key, JSON.stringify(value)); - } - cb = val; - } - return typeof cb === "function" ? cb() : void 0; - }; - - $.clear = function(cb) { - $["delete"](Object.keys(Conf)); - $["delete"](['previousversion', 'QR Size', 'captchas', 'QR.persona', 'hiddenPSA']); - try { - $["delete"]($.listValues().map(function(key) { - return key.replace(g.NAMESPACE, ''); - })); - } catch (_error) {} - return typeof cb === "function" ? cb() : void 0; - }; - return $; }).call(this); @@ -5231,7 +5317,7 @@ CrossOrigin = (function() { } else { options.responseType = 'arraybuffer'; } - return GM_xmlhttpRequest(options); + return ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)(options); }, file: function(url, cb) { return CrossOrigin.binary(url, function(data, contentType, contentDisposition) { @@ -5269,7 +5355,7 @@ CrossOrigin = (function() { return; } callbacks[url] = [cb]; - return GM_xmlhttpRequest({ + return ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)({ method: "GET", url: url + '', onload: function(xhr) { @@ -5550,7 +5636,6 @@ DataBoard = (function() { DataBoard.prototype.deleteIfEmpty = function(arg) { var boardID, threadID; boardID = arg.boardID, threadID = arg.threadID; - $.forceSync(this.key); if (threadID) { if (!Object.keys(this.data.boards[boardID][threadID]).length) { delete this.data.boards[boardID][threadID]; @@ -5652,13 +5737,13 @@ DataBoard = (function() { }; DataBoard.prototype.ajaxClean = function(boardID) { - return $.cache("//a.4cdn.org/" + boardID + "/threads.json", (function(_this) { + return $.cache(location.protocol + "//a.4cdn.org/" + boardID + "/threads.json", (function(_this) { return function(e1) { var ref; if ((ref = e1.target.status) !== 200 && ref !== 404) { return; } - return $.cache("//a.4cdn.org/" + boardID + "/archive.json", function(e2) { + return $.cache(location.protocol + "//a.4cdn.org/" + boardID + "/archive.json", function(e2) { var ref1; if ((ref1 = e2.target.status) !== 200 && ref1 !== 404) { return; @@ -5734,9 +5819,8 @@ Fetcher = (function() { this.insert(post); return; } - if ((post = (ref = Index.replyData) != null ? ref[this.boardID + "." + this.postID] : void 0)) { + if ((post = (ref = Index.replyData) != null ? ref[this.boardID + "." + this.postID] : void 0) && (thread = g.threads[this.boardID + "." + this.threadID])) { board = g.boards[this.boardID]; - thread = g.threads[this.boardID + "." + this.threadID]; post = new Post(Build.postFromObject(post, this.boardID), thread, board); post.isFetchedQuote = true; Main.callbackNodes('Post', [post]); @@ -5745,7 +5829,7 @@ Fetcher = (function() { } this.root.textContent = "Loading post No." + this.postID + "..."; if (this.threadID) { - $.cache("//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json", (function(_this) { + $.cache(location.protocol + "//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json", (function(_this) { return function(e, isCached) { return _this.fetchedPost(e.target, isCached); }; @@ -5812,7 +5896,7 @@ Fetcher = (function() { } if (post.no !== this.postID) { if (isCached) { - api = "//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json"; + api = location.protocol + "//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json"; $.cleanCache(function(url) { return url === api; }); @@ -6161,6 +6245,7 @@ Notice = (function() { Post = (function() { var Post, + 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; }; Post = (function() { @@ -6299,6 +6384,13 @@ Post = (function() { return this.nodesToText(bq).trim().replace(/\s+$/gm, ''); }; + Post.prototype.commentOrig = function() { + var bq; + bq = this.nodes.commentClean.cloneNode(true); + this.insertTags(bq); + return this.nodesToText(bq); + }; + Post.prototype.nodesToText = function(bq) { var i, node, nodes, text; text = ""; @@ -6344,6 +6436,20 @@ Post = (function() { return $.rm($('.fortune', bq)); }; + Post.prototype.insertTags = function(bq) { + var j, k, len, len1, node, ref, ref1; + ref = $$('s, .removed-spoiler', bq); + for (j = 0, len = ref.length; j < len; j++) { + node = ref[j]; + $.replace(node, [$.tn('[spoiler]')].concat(slice.call(node.childNodes), [$.tn('[/spoiler]')])); + } + ref1 = $$('.prettyprint', bq); + for (k = 0, len1 = ref1.length; k < len1; k++) { + node = ref1[k]; + $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); + } + }; + Post.prototype.parseQuotes = function() { var j, len, quotelink, ref; this.quotes = []; @@ -6999,7 +7105,6 @@ Redirect = (function() { { "uid": 29, "name": "Archived.Moe", "domain": "archived.moe", "http": true, "https": true, "software": "foolfuuka", "boards": [ "3", "a", "aco", "adv", "an", "asp", "b", "bant", "biz", "c", "can", "cgl", "ck", "cm", "co", "cock", "d", "diy", "e", "f", "fa", "fap", "fit", "fitlit", "g", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "int", "jp", "k", "lgbt", "lit", "m", "mlp", "mlpol", "mo", "mtv", "mu", "n", "news", "o", "out", "outsoc", "p", "po", "pol", "qa", "qst", "r", "r9k", "s", "s4s", "sci", "soc", "sp", "spa", "t", "tg", "toy", "trash", "trv", "tv", "u", "v", "vg", "vint", "vip", "vp", "vr", "w", "wg", "wsg", "wsr", "x", "y" ], "files": [ "can", "cock", "fap", "fitlit", "gd", "mlpol", "mo", "mtv", "outsoc", "po", "qst", "spa", "vint", "vip" ], "search": [ "aco", "adv", "an", "asp", "b", "bant", "c", "can", "cgl", "ck", "cm", "cock", "con", "d", "diy", "e", "f", "fap", "fitlit", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "lgbt", "lit", "mlpol", "mo", "mtv", "n", "news", "o", "out", "outsoc", "p", "po", "q", "qa", "qst", "r", "s", "soc", "spa", "trv", "u", "vint", "vip", "w", "wg", "wsg", "wsr", "x", "y" ], "reports": true }, { "uid": 30, "name": "TheBArchive.com", "domain": "thebarchive.com", "http": true, "https": true, "software": "foolfuuka", "boards": [ "b", "bant" ], "files": [ "b", "bant" ], "reports": true }, { "uid": 31, "name": "Archive Of Sins", "domain": "archiveofsins.com", "http": true, "https": true, "software": "foolfuuka", "boards": [ "h", "hc", "hm", "r", "s", "soc" ], "files": [ "h", "hc", "hm", "r", "s", "soc" ], "reports": true }, - { "uid": 32, "name": "4tan", "domain": "boards.4tan.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "3", "a", "aco", "adv", "an", "asp", "b", "bant", "biz", "c", "can", "cgl", "ck", "cm", "co", "cock", "d", "diy", "e", "f", "fa", "fap", "fit", "fitlit", "g", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "int", "jp", "k", "lgbt", "lit", "m", "mlp", "mlpol", "mo", "mtv", "mu", "n", "news", "o", "out", "outsoc", "p", "po", "pol", "qa", "qst", "r", "r9k", "s", "s4s", "sci", "soc", "sp", "spa", "t", "tg", "toy", "trash", "trv", "tv", "u", "v", "vg", "vint", "vip", "vp", "vr", "w", "wg", "wsg", "wsr", "x", "y" ], "files": [ "bant", "can", "cock", "fap", "fitlit", "mlpol", "mo", "mtv", "outsoc", "spa", "vint" ], "reports": true }, { "uid": 33, "name": "YEET Archive", "domain": "archive.yeet.net", "http": true, "https": true, "software": "foolfuuka", "boards": [ "g", "k", "qa", "s4s" ] } ], init: function() { @@ -8301,7 +8406,7 @@ BoardConfig = (function() { var now, ref; now = Date.now(); if (!((now - 2 * $.HOUR < (ref = Conf['boardConfig'].lastChecked || 0) && ref <= now))) { - return $.ajax('//a.4cdn.org/boards.json', { + return $.ajax(location.protocol + "//a.4cdn.org/boards.json", { onloadend: this.load }); } else { @@ -10206,7 +10311,7 @@ Index = (function() { location.reload(); return; } - Index.req = $.ajax("//a.4cdn.org/" + g.BOARD + "/catalog.json", { + Index.req = $.ajax(location.protocol + "//a.4cdn.org/" + g.BOARD + "/catalog.json", { onabort: Index.load, onloadend: Index.load }, { @@ -10342,6 +10447,9 @@ Index = (function() { return Index.parsedThreads[threadID].isHidden; } }, + isHiddenReply: function(threadID, replyData) { + return PostHiding.isHidden(g.BOARD.ID, threadID, replyData.no) || Filter.isHidden(Build.parseJSON(replyData, g.BOARD.ID)); + }, buildThreads: function(threadIDs, isCatalog) { var ID, OP, err, errors, isStale, k, len1, newPosts, newThreads, obj, thread, threadData, threads; threads = []; @@ -10476,10 +10584,7 @@ Index = (function() { replies = []; for (k = 0, len1 = lastReplies.length; k < len1; k++) { data = lastReplies[k]; - if (PostHiding.isHidden(g.BOARD.ID, thread.ID, data.no)) { - continue; - } - if (Filter.isHidden(Build.parseJSON(data, g.BOARD.ID))) { + if (Index.isHiddenReply(thread.ID, data)) { continue; } reply = Build.catalogReply(thread, data); @@ -10494,12 +10599,13 @@ Index = (function() { $.add(thread.OP.nodes.post, nodes.replies); }, sort: function() { - var lastlong, liveThreadData, liveThreadIDs, threadIDs; + var lastlong, lastlongD, liveThreadData, liveThreadIDs, thread, threadIDs; liveThreadIDs = Index.liveThreadIDs, liveThreadData = Index.liveThreadData; if (!liveThreadData) { return; } Index.sortedThreadIDs = (function() { + var k, len1; switch (Index.currentSort.replace(/-rev$/, '')) { case 'lastreply': return slice.call(liveThreadData).sort(function(a, b) { @@ -10520,6 +10626,9 @@ Index = (function() { ref = thread.last_replies || []; for (i = k = ref.length - 1; k >= 0; i = k += -1) { r = ref[i]; + if (Index.isHiddenReply(thread.no, r)) { + continue; + } len = r.com ? Build.parseComment(r.com).replace(/[^a-z]/ig, '').length : 0; if (len >= Index.lastLongThresholds[+(!!r.ext)]) { return r; @@ -10531,8 +10640,13 @@ Index = (function() { return thread; } }; + lastlongD = {}; + for (k = 0, len1 = liveThreadData.length; k < len1; k++) { + thread = liveThreadData[k]; + lastlongD[thread.no] = lastlong(thread).no; + } return slice.call(liveThreadData).sort(function(a, b) { - return lastlong(b).no - lastlong(a).no; + return lastlongD[b.no] - lastlongD[a.no]; }).map(function(post) { return post.no; }); @@ -11008,6 +11122,9 @@ Settings = (function() { if ($.engine !== 'gecko') { $('div[data-name="Remember QR Size"]', section).hidden = true; } + if ($.perProtocolSettings) { + $('div[data-name="Redirect to HTTPS"]', section).hidden = true; + } $.get(items, function(items) { var val; for (key in items) { @@ -11512,7 +11629,7 @@ Settings = (function() { advanced: function(section) { var applyCSS, boardSelect, customCSS, event, input, inputs, interval, items, itemsArchive, j, k, l, len, len1, len2, len3, m, name, ref, ref1, ref2, ref3, table, updateArchives, warning; $.extend(section, { - innerHTML: "
Archives
404 Redirect is disabled.
Thread redirectionPost fetchingFile redirection

Archive Lists: Each line below should be an archive list in this format or a URL to load an archive list from.
Archive properties can be overriden by another item with the same uid (or if absent, its name).
Last updated:
Captcha Language
Choose from list of language codes. Leave blank to autoselect.
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
Archive link: g-archive
Internal archive link: g-expired
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"
Index-only link: g-index
Catalog-only link: g-catalog
Index mode: g-mode:"infinite scrolling"
Index sort: g-sort:"creation date rev"
External link: external-text:"Google","http://www.google.com"
Combinations are possible: g-index-text:"Technology Index"
Full board list toggle: toggle-all

[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [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
Literal %: %%
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (4chan filename)
Filename: %n (truncated), %N (untruncated), %t (4chan filename)
Download button: %d
Quick filter MD5: %f
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Tag: %g
Literal %: %%
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: seconds
Custom Cooldown Time
Seconds:
For more information about customizing 4chan X's CSS, see the styling guide.
Javascript Whitelist
Sources from which Javascript is allowed to be loaded by Content Security Policy.
Lines starting with a # will be ignored.
" + innerHTML: "
Archives
404 Redirect is disabled.
Thread redirectionPost fetchingFile redirection

Archive Lists: Each line below should be an archive list in this format or a URL to load an archive list from.
Archive properties can be overriden by another item with the same uid (or if absent, its name).
Last updated:
Captcha Language
Choose from list of language codes. Leave blank to autoselect.
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
Archive link: g-archive
Internal archive link: g-expired
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"
Index-only link: g-index
Catalog-only link: g-catalog
Index mode: g-mode:"infinite scrolling"
Index sort: g-sort:"creation date rev"
External link: external-text:"Google","http://www.google.com"
Combinations are possible: g-index-text:"Technology Index"
Full board list toggle: toggle-all

[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [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
Literal %: %%
Language tag:
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (4chan filename)
Filename: %n (truncated), %N (untruncated), %t (4chan filename)
Download button: %d
Quick filter MD5: %f
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Tag: %g
Literal %: %%
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: seconds
Custom Cooldown Time
Seconds:
For more information about customizing 4chan X's CSS, see the styling guide.
Javascript Whitelist
Sources from which Javascript is allowed to be loaded by Content Security Policy.
Lines starting with a # will be ignored.
" }); ref = $$('.warning', section); for (j = 0, len = ref.length; j < len; j++) { @@ -11531,7 +11648,7 @@ Settings = (function() { return $.id('lastarchivecheck').textContent = 'never'; }); items = {}; - ref2 = ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist']; + ref2 = ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'timeLocale', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist']; for (l = 0, len2 = ref2.length; l < len2; l++) { name = ref2[l]; items[name] = Conf[name]; @@ -11717,6 +11834,9 @@ Settings = (function() { time: function() { return this.nextElementSibling.textContent = Time.format(this.value, new Date()); }, + timeLocale: function() { + return Settings.time.call($('[name=time]', Settings.dialog)); + }, backlink: function() { return this.nextElementSibling.textContent = this.value.replace(/%(?:id|%)/g, function(x) { return { @@ -12952,7 +13072,7 @@ ImageCommon = (function() { return cb(URL); } }; - return $.ajax("//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", { + return $.ajax(location.protocol + "//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", { onload: function() { var i, len, postObj, ref; if (this.status === 404) { @@ -14867,7 +14987,7 @@ Embedding = (function() { }, title: { api: function(uid) { - return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); + return location.protocol + "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); }, text: function(_) { return _.title; @@ -15274,7 +15394,7 @@ CopyTextLink = (function() { el: a, order: 12, open: function(post) { - CopyTextLink.text = post.info.comment; + CopyTextLink.text = (post.origin || post).commentOrig(); return true; } }); @@ -15846,7 +15966,7 @@ BoardTips = (function() { tips: { qa: [ 1, { - innerHTML: "New to /qa/?
/qa/ is NOT an effective way to contact the mods.
Use IRC or feedback instead. More details here." + innerHTML: "New to /qa/?
/qa/ is NOT an effective way to contact the mods.
Message a mod on IRC or use feedback instead. More details here." } ] }, @@ -16075,7 +16195,7 @@ ExpandComment = (function() { return; } a.textContent = "Post No." + post + " Loading..."; - return $.cache("//a.4cdn.org" + (a.pathname.split(/\/+/).splice(0, 4).join('/')) + ".json", function() { + return $.cache(location.protocol + "//a.4cdn.org" + (a.pathname.split(/\/+/).splice(0, 4).join('/')) + ".json", function() { return ExpandComment.parse(this, a, post); }); }, @@ -16218,7 +16338,7 @@ ExpandThread = (function() { var status; ExpandThread.statuses[thread] = status = {}; a.textContent = Build.summaryText.apply(Build, ['...'].concat(slice.call(a.textContent.match(/\d+/g)))); - return status.req = $.cache("//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() { + return status.req = $.cache(location.protocol + "//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() { delete status.req; return ExpandThread.parse(this, thread, a); }); @@ -17255,7 +17375,7 @@ Keybinds = (function() { } url = "/" + thread.board + "/thread/" + thread; if (tab) { - return $.open(url); + return $.open(location.origin + url); } else { return location.href = url; } @@ -17907,6 +18027,30 @@ Time = (function() { }, day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + localeFormat: function(date, options, defaultValue) { + if (Conf['timeLocale']) { + try { + return Intl.DateTimeFormat(Conf['timeLocale'], options).format(date); + } catch (_error) {} + } + return defaultValue; + }, + localeFormatPart: function(date, options, part, defaultValue) { + var parts; + if (Conf['timeLocale']) { + try { + parts = Intl.DateTimeFormat(Conf['timeLocale'], options).formatToParts(date); + return parts.map(function(x) { + if (x.type === part) { + return x.value; + } else { + return ''; + } + }).join(''); + } catch (_error) {} + } + return defaultValue; + }, zeroPad: function(n) { if (n < 10) { return "0" + n; @@ -17916,16 +18060,24 @@ Time = (function() { }, formatters: { a: function() { - return Time.day[this.getDay()].slice(0, 3); + return Time.localeFormat(this, { + weekday: 'short' + }, Time.day[this.getDay()].slice(0, 3)); }, A: function() { - return Time.day[this.getDay()]; + return Time.localeFormat(this, { + weekday: 'long' + }, Time.day[this.getDay()]); }, b: function() { - return Time.month[this.getMonth()].slice(0, 3); + return Time.localeFormat(this, { + month: 'short' + }, Time.month[this.getMonth()].slice(0, 3)); }, B: function() { - return Time.month[this.getMonth()]; + return Time.localeFormat(this, { + month: 'long' + }, Time.month[this.getMonth()]); }, d: function() { return Time.zeroPad(this.getDate()); @@ -17952,18 +18104,13 @@ Time = (function() { return Time.zeroPad(this.getMinutes()); }, p: function() { - if (this.getHours() < 12) { - return 'AM'; - } else { - return 'PM'; - } + return Time.localeFormatPart(this, { + hour: 'numeric', + hour12: true + }, 'dayperiod', (this.getHours() < 12 ? 'AM' : 'PM')); }, P: function() { - if (this.getHours() < 12) { - return 'am'; - } else { - return 'pm'; - } + return Time.formatters.p.call(this).toLowerCase(); }, S: function() { return Time.zeroPad(this.getSeconds()); @@ -18360,7 +18507,7 @@ ThreadStats = (function() { return; } ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 2 * $.MINUTE); - return $.ajax("//a.4cdn.org/" + ThreadStats.thread.board + "/threads.json", { + return $.ajax(location.protocol + "//a.4cdn.org/" + ThreadStats.thread.board + "/threads.json", { onload: ThreadStats.onThreadsLoad }, { whenModified: 'ThreadStats' @@ -18585,7 +18732,7 @@ ThreadUpdater = (function() { } break; case 404: - return $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/catalog.json", { + return $.ajax(location.protocol + "//a.4cdn.org/" + ThreadUpdater.thread.board + "/catalog.json", { onloadend: function() { var confirmed, i, k, len, len1, page, ref, ref1, thread; if (this.status === 200) { @@ -18697,7 +18844,7 @@ ThreadUpdater = (function() { if ((ref = ThreadUpdater.req) != null) { ref.abort(); } - return ThreadUpdater.req = $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json", { + return ThreadUpdater.req = $.ajax(location.protocol + "//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json", { onloadend: ThreadUpdater.cb.load, timeout: $.MINUTE }, { @@ -19185,7 +19332,7 @@ ThreadWatcher = (function() { ThreadWatcher.status.textContent = '...'; $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); } - req = $.ajax("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", { + req = $.ajax(location.protocol + "//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", { onloadend: function() { return ThreadWatcher.parseStatus.call(this, thread); }, @@ -19898,7 +20045,6 @@ Unread = (function() { if (Unread.thread.isDead && !Unread.thread.isArchived) { return; } - Unread.db.forceSync(); return Unread.db.set({ boardID: Unread.thread.board.ID, threadID: Unread.thread.ID, @@ -21589,7 +21735,7 @@ QR = (function() { } }, quote: function(e) { - var ancestor, caretPos, com, frag, insideCode, j, k, l, len, len1, len2, len3, len4, len5, n, node, o, post, q, range, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, sel, text, thread; + var ancestor, caretPos, com, frag, i, insideCode, j, k, l, len, len1, len2, len3, n, node, o, post, postRange, range, ref, ref1, ref2, ref3, ref4, ref5, ref6, root, sel, text, thread; if (e != null) { e.preventDefault(); } @@ -21598,9 +21744,21 @@ QR = (function() { } sel = d.getSelection(); post = Get.postFromNode(this); + root = post.nodes.root; + postRange = new Range(); + postRange.selectNode(root); text = post.board.ID === g.BOARD.ID ? ">>" + post + "\n" : ">>>/" + post.board + "/" + post + "\n"; - if (sel.toString().trim() && post === Get.postFromNode(sel.anchorNode)) { - range = sel.getRangeAt(0); + for (i = j = 0, ref = sel.rangeCount; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) { + range = sel.getRangeAt(i); + if (range.compareBoundaryPoints(Range.START_TO_START, postRange) < 0) { + range.setStartBefore(root); + } + if (range.compareBoundaryPoints(Range.END_TO_END, postRange) > 0) { + range.setEndAfter(root); + } + if (!range.toString().trim()) { + continue; + } frag = range.cloneContents(); ancestor = range.commonAncestorContainer; if ($.x('ancestor-or-self::*[self::s or contains(@class,"removed-spoiler")]', ancestor)) { @@ -21611,37 +21769,28 @@ QR = (function() { $.prepend(frag, $.tn('[code]')); $.add(frag, $.tn('[/code]')); } - ref = $$((insideCode ? 'br' : '.prettyprint br'), frag); - for (j = 0, len = ref.length; j < len; j++) { - node = ref[j]; + ref1 = $$((insideCode ? 'br' : '.prettyprint br'), frag); + for (k = 0, len = ref1.length; k < len; k++) { + node = ref1[k]; $.replace(node, $.tn('\n')); } - ref1 = $$('br', frag); - for (k = 0, len1 = ref1.length; k < len1; k++) { - node = ref1[k]; + ref2 = $$('br', frag); + for (l = 0, len1 = ref2.length; l < len1; l++) { + node = ref2[l]; if (node !== frag.lastChild) { $.replace(node, $.tn('\n>')); } } - ref2 = $$('s, .removed-spoiler', frag); - for (l = 0, len2 = ref2.length; l < len2; l++) { - node = ref2[l]; - $.replace(node, [$.tn('[spoiler]')].concat(slice.call(node.childNodes), [$.tn('[/spoiler]')])); - } - ref3 = $$('.prettyprint', frag); - for (n = 0, len3 = ref3.length; n < len3; n++) { + Post.prototype.insertTags(frag); + ref3 = $$('.linkify[data-original]', frag); + for (n = 0, len2 = ref3.length; n < len2; n++) { node = ref3[n]; - $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); - } - ref4 = $$('.linkify[data-original]', frag); - for (o = 0, len4 = ref4.length; o < len4; o++) { - node = ref4[o]; $.replace(node, $.tn(node.dataset.original)); } - ref5 = $$('.embedder', frag); - for (q = 0, len5 = ref5.length; q < len5; q++) { - node = ref5[q]; - if (((ref6 = node.previousSibling) != null ? ref6.nodeValue : void 0) === ' ') { + ref4 = $$('.embedder', frag); + for (o = 0, len3 = ref4.length; o < len3; o++) { + node = ref4[o]; + if (((ref5 = node.previousSibling) != null ? ref5.nodeValue : void 0) === ' ') { $.rm(node.previousSibling); } $.rm(node); @@ -21649,7 +21798,7 @@ QR = (function() { text += ">" + (frag.textContent.trim()) + "\n"; } QR.openPost(); - ref7 = QR.nodes, com = ref7.com, thread = ref7.thread; + ref6 = QR.nodes, com = ref6.com, thread = ref6.thread; if (!com.value) { thread.value = Get.threadFromNode(this); } @@ -22502,9 +22651,9 @@ QR = (function() { if ((type === 'thread') === (cooldown.threadID === cooldown.postID) && cooldown.boardID !== g.BOARD.ID) { suffix = scope === 'global' ? '_global' : ''; seconds = Math.max(seconds, QR.cooldown.delays[type + suffix] - elapsed); - } - if (QR.cooldown.customCooldown) { - seconds = Math.max(seconds, parseInt(Conf['customCooldown'], 10) - elapsed); + if (QR.cooldown.customCooldown) { + seconds = Math.max(seconds, parseInt(Conf['customCooldown'], 10) - elapsed); + } } } nCooldowns += Object.keys(cooldowns).length; @@ -24506,7 +24655,7 @@ Main = (function() { items['previousversion'] = void 0; return ($.getSync || $.get)(items, function(items) { var ref1; - if (((ref1 = items['Redirect to HTTPS']) != null ? ref1 : Conf['Redirect to HTTPS']) && location.protocol !== 'https:') { + if (!$.perProtocolSettings && ((ref1 = items['Redirect to HTTPS']) != null ? ref1 : Conf['Redirect to HTTPS']) && location.protocol !== 'https:') { location.replace('https:' + location.host + location.pathname + location.search + location.hash); return; } @@ -24814,7 +24963,7 @@ Main = (function() { threads[0].ipCount = (m = scriptData.match(/\bunique_ips *= *(\d+)\b/)) ? +m[1] : void 0; } if (g.BOARD.ID === 'f' && g.VIEW === 'thread') { - $.ajax("//a.4cdn.org/f/thread/" + g.THREADID + ".json", { + $.ajax(location.protocol + "//a.4cdn.org/f/thread/" + g.THREADID + ".json", { timeout: $.MINUTE, onloadend: function() { if (this.response && posts[0].file) { diff --git a/builds/4chan-X.crx b/builds/4chan-X.crx index 402fccaf4..541055496 100644 Binary files a/builds/4chan-X.crx and b/builds/4chan-X.crx differ diff --git a/builds/4chan-X.meta.js b/builds/4chan-X.meta.js index 217d8c739..f88c59775 100644 --- a/builds/4chan-X.meta.js +++ b/builds/4chan-X.meta.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X -// @version 1.13.10.5 +// @version 1.13.12.1 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -48,6 +48,11 @@ // @grant GM_addValueChangeListener // @grant GM_openInTab // @grant GM_xmlhttpRequest +// @grant GM.getValue +// @grant GM.setValue +// @grant GM.deleteValue +// @grant GM.listValues +// @grant GM.xmlHttpRequest // @run-at document-start // @updateURL https://www.4chan-x.net/builds/4chan-X.meta.js // @downloadURL https://www.4chan-x.net/builds/4chan-X.user.js diff --git a/builds/4chan-X.user.js b/builds/4chan-X.user.js index 37698e9e9..28e3a4b01 100644 --- a/builds/4chan-X.user.js +++ b/builds/4chan-X.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X -// @version 1.13.10.5 +// @version 1.13.12.1 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -48,6 +48,11 @@ // @grant GM_addValueChangeListener // @grant GM_openInTab // @grant GM_xmlhttpRequest +// @grant GM.getValue +// @grant GM.setValue +// @grant GM.deleteValue +// @grant GM.listValues +// @grant GM.xmlHttpRequest // @run-at document-start // @updateURL https://www.4chan-x.net/builds/4chan-X.meta.js // @downloadURL https://www.4chan-x.net/builds/4chan-X.user.js @@ -153,7 +158,7 @@ docSet = function() { }; g = { - VERSION: '1.13.10.5', + VERSION: '1.13.12.1', NAMESPACE: '4chan X.', boards: {} }; @@ -425,6 +430,7 @@ Config = (function() { jsWhitelist: 'http://s.4cdn.org\nhttps://s.4cdn.org\nhttp://www.google.com\nhttps://www.google.com\nhttps://www.gstatic.com\nhttp://cdn.mathjax.org\nhttps://cdn.mathjax.org\nhttps://cdnjs.cloudflare.com\n\'self\'\n\'unsafe-inline\'\n\'unsafe-eval\'\n\n# Banner ads\n#http://s.zkcdn.net/ados.js\n#https://s.zkcdn.net/ados.js\n#http://engine.4chan-ads.org\n#https://engine.4chan-ads.org', captchaLanguage: '', time: '%m/%d/%y(%a)%H:%M:%S', + timeLocale: '', backlink: '>>%id', fileInfo: '%l %d (%p%s, %r%g)', favicon: 'ferongr', @@ -4474,7 +4480,7 @@ $ = (function() { var lastModified; lastModified = {}; return function(url, options, extra) { - var err, event, form, i, len, r, ref, ref1, type, upCallbacks, whenModified; + var err, event, form, j, len, r, ref, ref1, type, upCallbacks, whenModified; if (options == null) { options = {}; } @@ -4517,8 +4523,8 @@ $ = (function() { throw err; } ref1 = ['error', 'loadend']; - for (i = 0, len = ref1.length; i < len; i++) { - event = ref1[i]; + for (j = 0, len = ref1.length; j < len; j++) { + event = ref1[j]; r["on" + event] = options["on" + event]; $.queueTask($.event, event, null, r); } @@ -4554,7 +4560,7 @@ $ = (function() { return; } $.on(req, 'load', function(e) { - var fn1, i, len, ref; + var fn1, j, len, ref; this.evt = e; ref = this.callbacks; fn1 = (function(_this) { @@ -4564,8 +4570,8 @@ $ = (function() { }); }; })(this); - for (i = 0, len = ref.length; i < len; i++) { - cb = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + cb = ref[j]; fn1(cb); } return delete this.callbacks; @@ -4664,19 +4670,19 @@ $ = (function() { }; $.addClass = function() { - var className, classNames, el, i, len; + var className, classNames, el, j, len; el = arguments[0], classNames = 2 <= arguments.length ? slice.call(arguments, 1) : []; - for (i = 0, len = classNames.length; i < len; i++) { - className = classNames[i]; + for (j = 0, len = classNames.length; j < len; j++) { + className = classNames[j]; el.classList.add(className); } }; $.rmClass = function() { - var className, classNames, el, i, len; + var className, classNames, el, j, len; el = arguments[0], classNames = 2 <= arguments.length ? slice.call(arguments, 1) : []; - for (i = 0, len = classNames.length; i < len; i++) { - className = classNames[i]; + for (j = 0, len = classNames.length; j < len; j++) { + className = classNames[j]; el.classList.remove(className); } }; @@ -4706,13 +4712,13 @@ $ = (function() { }; $.nodes = function(nodes) { - var frag, i, len, node; + var frag, j, len, node; if (!(nodes instanceof Array)) { return nodes; } frag = $.frag(); - for (i = 0, len = nodes.length; i < len; i++) { - node = nodes[i]; + for (j = 0, len = nodes.length; j < len; j++) { + node = nodes[j]; frag.appendChild(node); } return frag; @@ -4751,19 +4757,19 @@ $ = (function() { }; $.on = function(el, events, handler) { - var event, i, len, ref; + var event, j, len, ref; ref = events.split(' '); - for (i = 0, len = ref.length; i < len; i++) { - event = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + event = ref[j]; el.addEventListener(event, handler, false); } }; $.off = function(el, events, handler) { - var event, i, len, ref; + var event, j, len, ref; ref = events.split(' '); - for (i = 0, len = ref.length; i < len; i++) { - event = ref[i]; + for (j = 0, len = ref.length; j < len; j++) { + event = ref[j]; el.removeEventListener(event, handler, false); } }; @@ -4959,213 +4965,293 @@ $ = (function() { return item; }; + $.oneItemSugar = function(fn) { + return function(key, val, cb) { + if (typeof key === 'string') { + return fn($.item(key, val), cb); + } else { + return fn(key, val); + } + }; + }; + $.syncing = {}; - $.currentValue = {}; - - $.GM_getValue = function(key) { - var err; - try { - return $.currentValue[key] = GM_getValue(key); - } catch (_error) { - err = _error; - return $.currentValue[key]; - } - }; - - $.GM_setValue = function(key, val) { - $.currentValue[key] = val; - return GM_setValue(key, val); - }; - - $.GM_deleteValue = function(key) { - delete $.currentValue[key]; - return GM_deleteValue(key); - }; - - if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { - $.getValue = $.GM_getValue; - $.listValues = function() { - return GM_listValues(); - }; - } else if ($.hasStorage) { - $.getValue = function(key) { - return localStorage[key]; - }; - $.listValues = function() { - var key, results; + if ((typeof GM !== "undefined" && GM !== null ? GM.deleteValue : void 0) != null) { + $.syncChannel = new BroadcastChannel(g.NAMESPACE + 'sync'); + $.on($.syncChannel, 'message', function(e) { + var cb, key, ref, results, val; + ref = e.data; results = []; - for (key in localStorage) { - if (key.slice(0, g.NAMESPACE.length) === g.NAMESPACE) { - results.push(key); + for (key in ref) { + val = ref[key]; + if ((cb = $.syncing[key])) { + results.push(cb(val, key)); } } return results; - }; - } else { - $.getValue = function() {}; - $.listValues = function() { - return []; - }; - } - - if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { - $.setValue = $.GM_setValue; - $.deleteValue = $.GM_deleteValue; - } else if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { - $.oldValue = {}; - $.setValue = function(key, val) { - $.GM_setValue(key, val); - if (key in $.syncing) { - $.oldValue[key] = val; - if ($.hasStorage) { - return localStorage[key] = val; - } - } - }; - $.deleteValue = function(key) { - $.GM_deleteValue(key); - if (key in $.syncing) { - delete $.oldValue[key]; - if ($.hasStorage) { - return localStorage.removeItem(key); - } - } - }; - if (!$.hasStorage) { - $.cantSync = true; - } - } else if ($.hasStorage) { - $.oldValue = {}; - $.setValue = function(key, val) { - if (key in $.syncing) { - $.oldValue[key] = val; - } - return localStorage[key] = val; - }; - $.deleteValue = function(key) { - if (key in $.syncing) { - delete $.oldValue[key]; - } - return localStorage.removeItem(key); - }; - } else { - $.setValue = function() {}; - $.deleteValue = function() {}; - $.cantSync = $.cantSet = true; - } - - if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + }); $.sync = function(key, cb) { - return $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function(key2, oldValue, newValue, remote) { - if (remote) { - if (newValue !== void 0) { - newValue = JSON.parse(newValue); - } - return cb(newValue, key); + return $.syncing[key] = cb; + }; + $.forceSync = function() {}; + $["delete"] = function(keys, cb) { + var key; + if (!(keys instanceof Array)) { + keys = [keys]; + } + return Promise.all((function() { + var j, len, results; + results = []; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + results.push(GM.deleteValue(g.NAMESPACE + key)); } + return results; + })()).then(function() { + var items, j, key, len; + items = {}; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + items[key] = void 0; + } + $.syncChannel.postMessage(items); + return typeof cb === "function" ? cb() : void 0; }); }; - $.forceSync = function() {}; - } else if ((typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) || $.hasStorage) { - $.sync = function(key, cb) { - key = g.NAMESPACE + key; - $.syncing[key] = cb; - return $.oldValue[key] = $.getValue(key); - }; - (function() { - var onChange; - onChange = function(arg) { - var cb, key, newValue; - key = arg.key, newValue = arg.newValue; - if (!(cb = $.syncing[key])) { - return; + $.get = $.oneItemSugar(function(items, cb) { + var key, keys; + keys = Object.keys(items); + return Promise.all((function() { + var j, len, results; + results = []; + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + results.push(GM.getValue(g.NAMESPACE + key)); } - if (newValue != null) { - if (newValue === $.oldValue[key]) { - return; + return results; + })()).then(function(values) { + var i, j, len, val; + for (i = j = 0, len = values.length; j < len; i = ++j) { + val = values[i]; + if (val) { + items[keys[i]] = JSON.parse(val); } - $.oldValue[key] = newValue; - return cb(JSON.parse(newValue), key.slice(g.NAMESPACE.length)); - } else { - if ($.oldValue[key] == null) { - return; + } + return cb(items); + }); + }); + $.set = $.oneItemSugar(function(items, cb) { + var key, val; + return Promise.all((function() { + var results; + results = []; + for (key in items) { + val = items[key]; + results.push(GM.setValue(g.NAMESPACE + key, JSON.stringify(val))); + } + return results; + })()).then(function() { + $.syncChannel.postMessage(items); + return typeof cb === "function" ? cb() : void 0; + }); + }); + $.clear = function(cb) { + return GM.listValues.then(function(keys) { + return $["delete"](keys.map(function(key) { + return key.replace(g.NAMESPACE, ''); + }), cb); + }); + }; + } else { + $.currentValue = {}; + $.GM_getValue = function(key) { + var err; + try { + return $.currentValue[key] = GM_getValue(key); + } catch (_error) { + err = _error; + return $.currentValue[key]; + } + }; + $.GM_setValue = function(key, val) { + $.currentValue[key] = val; + return GM_setValue(key, val); + }; + $.GM_deleteValue = function(key) { + delete $.currentValue[key]; + return GM_deleteValue(key); + }; + if (typeof GM_deleteValue === "undefined" || GM_deleteValue === null) { + $.perProtocolSettings = true; + } + if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { + $.getValue = $.GM_getValue; + $.listValues = function() { + return GM_listValues(); + }; + } else if ($.hasStorage) { + $.getValue = function(key) { + return localStorage[key]; + }; + $.listValues = function() { + var key, results; + results = []; + for (key in localStorage) { + if (key.slice(0, g.NAMESPACE.length) === g.NAMESPACE) { + results.push(key); + } + } + return results; + }; + } else { + $.getValue = function() {}; + $.listValues = function() { + return []; + }; + } + if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + $.setValue = $.GM_setValue; + $.deleteValue = $.GM_deleteValue; + } else if (typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) { + $.oldValue = {}; + $.setValue = function(key, val) { + $.GM_setValue(key, val); + if (key in $.syncing) { + $.oldValue[key] = val; + if ($.hasStorage) { + return localStorage[key] = val; } - delete $.oldValue[key]; - return cb(void 0, key.slice(g.NAMESPACE.length)); } }; - $.on(window, 'storage', onChange); - return $.forceSync = function(key) { - key = g.NAMESPACE + key; - return onChange({ - key: key, - newValue: $.getValue(key) + $.deleteValue = function(key) { + $.GM_deleteValue(key); + if (key in $.syncing) { + delete $.oldValue[key]; + if ($.hasStorage) { + return localStorage.removeItem(key); + } + } + }; + if (!$.hasStorage) { + $.cantSync = true; + } + } else if ($.hasStorage) { + $.oldValue = {}; + $.setValue = function(key, val) { + if (key in $.syncing) { + $.oldValue[key] = val; + } + return localStorage[key] = val; + }; + $.deleteValue = function(key) { + if (key in $.syncing) { + delete $.oldValue[key]; + } + return localStorage.removeItem(key); + }; + } else { + $.setValue = function() {}; + $.deleteValue = function() {}; + $.cantSync = $.cantSet = true; + } + if (typeof GM_addValueChangeListener !== "undefined" && GM_addValueChangeListener !== null) { + $.sync = function(key, cb) { + return $.syncing[key] = GM_addValueChangeListener(g.NAMESPACE + key, function(key2, oldValue, newValue, remote) { + if (remote) { + if (newValue !== void 0) { + newValue = JSON.parse(newValue); + } + return cb(newValue, key); + } }); }; - })(); - } else { - $.sync = function() {}; - $.forceSync = function() {}; + $.forceSync = function() {}; + } else if ((typeof GM_deleteValue !== "undefined" && GM_deleteValue !== null) || $.hasStorage) { + $.sync = function(key, cb) { + key = g.NAMESPACE + key; + $.syncing[key] = cb; + return $.oldValue[key] = $.getValue(key); + }; + (function() { + var onChange; + onChange = function(arg) { + var cb, key, newValue; + key = arg.key, newValue = arg.newValue; + if (!(cb = $.syncing[key])) { + return; + } + if (newValue != null) { + if (newValue === $.oldValue[key]) { + return; + } + $.oldValue[key] = newValue; + return cb(JSON.parse(newValue), key.slice(g.NAMESPACE.length)); + } else { + if ($.oldValue[key] == null) { + return; + } + delete $.oldValue[key]; + return cb(void 0, key.slice(g.NAMESPACE.length)); + } + }; + $.on(window, 'storage', onChange); + return $.forceSync = function(key) { + key = g.NAMESPACE + key; + return onChange({ + key: key, + newValue: $.getValue(key) + }); + }; + })(); + } else { + $.sync = function() {}; + $.forceSync = function() {}; + } + $["delete"] = function(keys) { + var j, key, len; + if (!(keys instanceof Array)) { + keys = [keys]; + } + for (j = 0, len = keys.length; j < len; j++) { + key = keys[j]; + $.deleteValue(g.NAMESPACE + key); + } + }; + $.get = $.oneItemSugar(function(items, cb) { + return $.queueTask($.getSync, items, cb); + }); + $.getSync = function(items, cb) { + var key, val2; + for (key in items) { + if ((val2 = $.getValue(g.NAMESPACE + key))) { + items[key] = JSON.parse(val2); + } + } + return cb(items); + }; + $.set = $.oneItemSugar(function(items, cb) { + return $.queueTask(function() { + var key, value; + for (key in items) { + value = items[key]; + $.setValue(g.NAMESPACE + key, JSON.stringify(value)); + } + return typeof cb === "function" ? cb() : void 0; + }); + }); + $.clear = function(cb) { + $["delete"](Object.keys(Conf)); + $["delete"](['previousversion', 'QR Size', 'captchas', 'QR.persona', 'hiddenPSA']); + try { + $["delete"]($.listValues().map(function(key) { + return key.replace(g.NAMESPACE, ''); + })); + } catch (_error) {} + return typeof cb === "function" ? cb() : void 0; + }; } - $["delete"] = function(keys) { - var i, key, len; - if (!(keys instanceof Array)) { - keys = [keys]; - } - for (i = 0, len = keys.length; i < len; i++) { - key = keys[i]; - $.deleteValue(g.NAMESPACE + key); - } - }; - - $.get = function(key, val, cb) { - var items; - if (typeof cb === 'function') { - items = $.item(key, val); - } else { - items = key; - cb = val; - } - return $.queueTask($.getSync, items, cb); - }; - - $.getSync = function(items, cb) { - var key, val2; - for (key in items) { - if ((val2 = $.getValue(g.NAMESPACE + key))) { - items[key] = JSON.parse(val2); - } - } - return cb(items); - }; - - $.set = function(keys, val, cb) { - var key, value; - if (typeof keys === 'string') { - $.setValue(g.NAMESPACE + keys, JSON.stringify(val)); - } else { - for (key in keys) { - value = keys[key]; - $.setValue(g.NAMESPACE + key, JSON.stringify(value)); - } - cb = val; - } - return typeof cb === "function" ? cb() : void 0; - }; - - $.clear = function(cb) { - $["delete"](Object.keys(Conf)); - $["delete"](['previousversion', 'QR Size', 'captchas', 'QR.persona', 'hiddenPSA']); - try { - $["delete"]($.listValues().map(function(key) { - return key.replace(g.NAMESPACE, ''); - })); - } catch (_error) {} - return typeof cb === "function" ? cb() : void 0; - }; - return $; }).call(this); @@ -5231,7 +5317,7 @@ CrossOrigin = (function() { } else { options.responseType = 'arraybuffer'; } - return GM_xmlhttpRequest(options); + return ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)(options); }, file: function(url, cb) { return CrossOrigin.binary(url, function(data, contentType, contentDisposition) { @@ -5269,7 +5355,7 @@ CrossOrigin = (function() { return; } callbacks[url] = [cb]; - return GM_xmlhttpRequest({ + return ((typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : void 0) || GM_xmlhttpRequest)({ method: "GET", url: url + '', onload: function(xhr) { @@ -5550,7 +5636,6 @@ DataBoard = (function() { DataBoard.prototype.deleteIfEmpty = function(arg) { var boardID, threadID; boardID = arg.boardID, threadID = arg.threadID; - $.forceSync(this.key); if (threadID) { if (!Object.keys(this.data.boards[boardID][threadID]).length) { delete this.data.boards[boardID][threadID]; @@ -5652,13 +5737,13 @@ DataBoard = (function() { }; DataBoard.prototype.ajaxClean = function(boardID) { - return $.cache("//a.4cdn.org/" + boardID + "/threads.json", (function(_this) { + return $.cache(location.protocol + "//a.4cdn.org/" + boardID + "/threads.json", (function(_this) { return function(e1) { var ref; if ((ref = e1.target.status) !== 200 && ref !== 404) { return; } - return $.cache("//a.4cdn.org/" + boardID + "/archive.json", function(e2) { + return $.cache(location.protocol + "//a.4cdn.org/" + boardID + "/archive.json", function(e2) { var ref1; if ((ref1 = e2.target.status) !== 200 && ref1 !== 404) { return; @@ -5734,9 +5819,8 @@ Fetcher = (function() { this.insert(post); return; } - if ((post = (ref = Index.replyData) != null ? ref[this.boardID + "." + this.postID] : void 0)) { + if ((post = (ref = Index.replyData) != null ? ref[this.boardID + "." + this.postID] : void 0) && (thread = g.threads[this.boardID + "." + this.threadID])) { board = g.boards[this.boardID]; - thread = g.threads[this.boardID + "." + this.threadID]; post = new Post(Build.postFromObject(post, this.boardID), thread, board); post.isFetchedQuote = true; Main.callbackNodes('Post', [post]); @@ -5745,7 +5829,7 @@ Fetcher = (function() { } this.root.textContent = "Loading post No." + this.postID + "..."; if (this.threadID) { - $.cache("//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json", (function(_this) { + $.cache(location.protocol + "//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json", (function(_this) { return function(e, isCached) { return _this.fetchedPost(e.target, isCached); }; @@ -5812,7 +5896,7 @@ Fetcher = (function() { } if (post.no !== this.postID) { if (isCached) { - api = "//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json"; + api = location.protocol + "//a.4cdn.org/" + this.boardID + "/thread/" + this.threadID + ".json"; $.cleanCache(function(url) { return url === api; }); @@ -6161,6 +6245,7 @@ Notice = (function() { Post = (function() { var Post, + 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; }; Post = (function() { @@ -6299,6 +6384,13 @@ Post = (function() { return this.nodesToText(bq).trim().replace(/\s+$/gm, ''); }; + Post.prototype.commentOrig = function() { + var bq; + bq = this.nodes.commentClean.cloneNode(true); + this.insertTags(bq); + return this.nodesToText(bq); + }; + Post.prototype.nodesToText = function(bq) { var i, node, nodes, text; text = ""; @@ -6344,6 +6436,20 @@ Post = (function() { return $.rm($('.fortune', bq)); }; + Post.prototype.insertTags = function(bq) { + var j, k, len, len1, node, ref, ref1; + ref = $$('s, .removed-spoiler', bq); + for (j = 0, len = ref.length; j < len; j++) { + node = ref[j]; + $.replace(node, [$.tn('[spoiler]')].concat(slice.call(node.childNodes), [$.tn('[/spoiler]')])); + } + ref1 = $$('.prettyprint', bq); + for (k = 0, len1 = ref1.length; k < len1; k++) { + node = ref1[k]; + $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); + } + }; + Post.prototype.parseQuotes = function() { var j, len, quotelink, ref; this.quotes = []; @@ -6999,7 +7105,6 @@ Redirect = (function() { { "uid": 29, "name": "Archived.Moe", "domain": "archived.moe", "http": true, "https": true, "software": "foolfuuka", "boards": [ "3", "a", "aco", "adv", "an", "asp", "b", "bant", "biz", "c", "can", "cgl", "ck", "cm", "co", "cock", "d", "diy", "e", "f", "fa", "fap", "fit", "fitlit", "g", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "int", "jp", "k", "lgbt", "lit", "m", "mlp", "mlpol", "mo", "mtv", "mu", "n", "news", "o", "out", "outsoc", "p", "po", "pol", "qa", "qst", "r", "r9k", "s", "s4s", "sci", "soc", "sp", "spa", "t", "tg", "toy", "trash", "trv", "tv", "u", "v", "vg", "vint", "vip", "vp", "vr", "w", "wg", "wsg", "wsr", "x", "y" ], "files": [ "can", "cock", "fap", "fitlit", "gd", "mlpol", "mo", "mtv", "outsoc", "po", "qst", "spa", "vint", "vip" ], "search": [ "aco", "adv", "an", "asp", "b", "bant", "c", "can", "cgl", "ck", "cm", "cock", "con", "d", "diy", "e", "f", "fap", "fitlit", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "lgbt", "lit", "mlpol", "mo", "mtv", "n", "news", "o", "out", "outsoc", "p", "po", "q", "qa", "qst", "r", "s", "soc", "spa", "trv", "u", "vint", "vip", "w", "wg", "wsg", "wsr", "x", "y" ], "reports": true }, { "uid": 30, "name": "TheBArchive.com", "domain": "thebarchive.com", "http": true, "https": true, "software": "foolfuuka", "boards": [ "b", "bant" ], "files": [ "b", "bant" ], "reports": true }, { "uid": 31, "name": "Archive Of Sins", "domain": "archiveofsins.com", "http": true, "https": true, "software": "foolfuuka", "boards": [ "h", "hc", "hm", "r", "s", "soc" ], "files": [ "h", "hc", "hm", "r", "s", "soc" ], "reports": true }, - { "uid": 32, "name": "4tan", "domain": "boards.4tan.org", "http": true, "https": true, "software": "foolfuuka", "boards": [ "3", "a", "aco", "adv", "an", "asp", "b", "bant", "biz", "c", "can", "cgl", "ck", "cm", "co", "cock", "d", "diy", "e", "f", "fa", "fap", "fit", "fitlit", "g", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "int", "jp", "k", "lgbt", "lit", "m", "mlp", "mlpol", "mo", "mtv", "mu", "n", "news", "o", "out", "outsoc", "p", "po", "pol", "qa", "qst", "r", "r9k", "s", "s4s", "sci", "soc", "sp", "spa", "t", "tg", "toy", "trash", "trv", "tv", "u", "v", "vg", "vint", "vip", "vp", "vr", "w", "wg", "wsg", "wsr", "x", "y" ], "files": [ "bant", "can", "cock", "fap", "fitlit", "mlpol", "mo", "mtv", "outsoc", "spa", "vint" ], "reports": true }, { "uid": 33, "name": "YEET Archive", "domain": "archive.yeet.net", "http": true, "https": true, "software": "foolfuuka", "boards": [ "g", "k", "qa", "s4s" ] } ], init: function() { @@ -8301,7 +8406,7 @@ BoardConfig = (function() { var now, ref; now = Date.now(); if (!((now - 2 * $.HOUR < (ref = Conf['boardConfig'].lastChecked || 0) && ref <= now))) { - return $.ajax('//a.4cdn.org/boards.json', { + return $.ajax(location.protocol + "//a.4cdn.org/boards.json", { onloadend: this.load }); } else { @@ -10206,7 +10311,7 @@ Index = (function() { location.reload(); return; } - Index.req = $.ajax("//a.4cdn.org/" + g.BOARD + "/catalog.json", { + Index.req = $.ajax(location.protocol + "//a.4cdn.org/" + g.BOARD + "/catalog.json", { onabort: Index.load, onloadend: Index.load }, { @@ -10342,6 +10447,9 @@ Index = (function() { return Index.parsedThreads[threadID].isHidden; } }, + isHiddenReply: function(threadID, replyData) { + return PostHiding.isHidden(g.BOARD.ID, threadID, replyData.no) || Filter.isHidden(Build.parseJSON(replyData, g.BOARD.ID)); + }, buildThreads: function(threadIDs, isCatalog) { var ID, OP, err, errors, isStale, k, len1, newPosts, newThreads, obj, thread, threadData, threads; threads = []; @@ -10476,10 +10584,7 @@ Index = (function() { replies = []; for (k = 0, len1 = lastReplies.length; k < len1; k++) { data = lastReplies[k]; - if (PostHiding.isHidden(g.BOARD.ID, thread.ID, data.no)) { - continue; - } - if (Filter.isHidden(Build.parseJSON(data, g.BOARD.ID))) { + if (Index.isHiddenReply(thread.ID, data)) { continue; } reply = Build.catalogReply(thread, data); @@ -10494,12 +10599,13 @@ Index = (function() { $.add(thread.OP.nodes.post, nodes.replies); }, sort: function() { - var lastlong, liveThreadData, liveThreadIDs, threadIDs; + var lastlong, lastlongD, liveThreadData, liveThreadIDs, thread, threadIDs; liveThreadIDs = Index.liveThreadIDs, liveThreadData = Index.liveThreadData; if (!liveThreadData) { return; } Index.sortedThreadIDs = (function() { + var k, len1; switch (Index.currentSort.replace(/-rev$/, '')) { case 'lastreply': return slice.call(liveThreadData).sort(function(a, b) { @@ -10520,6 +10626,9 @@ Index = (function() { ref = thread.last_replies || []; for (i = k = ref.length - 1; k >= 0; i = k += -1) { r = ref[i]; + if (Index.isHiddenReply(thread.no, r)) { + continue; + } len = r.com ? Build.parseComment(r.com).replace(/[^a-z]/ig, '').length : 0; if (len >= Index.lastLongThresholds[+(!!r.ext)]) { return r; @@ -10531,8 +10640,13 @@ Index = (function() { return thread; } }; + lastlongD = {}; + for (k = 0, len1 = liveThreadData.length; k < len1; k++) { + thread = liveThreadData[k]; + lastlongD[thread.no] = lastlong(thread).no; + } return slice.call(liveThreadData).sort(function(a, b) { - return lastlong(b).no - lastlong(a).no; + return lastlongD[b.no] - lastlongD[a.no]; }).map(function(post) { return post.no; }); @@ -11008,6 +11122,9 @@ Settings = (function() { if ($.engine !== 'gecko') { $('div[data-name="Remember QR Size"]', section).hidden = true; } + if ($.perProtocolSettings) { + $('div[data-name="Redirect to HTTPS"]', section).hidden = true; + } $.get(items, function(items) { var val; for (key in items) { @@ -11512,7 +11629,7 @@ Settings = (function() { advanced: function(section) { var applyCSS, boardSelect, customCSS, event, input, inputs, interval, items, itemsArchive, j, k, l, len, len1, len2, len3, m, name, ref, ref1, ref2, ref3, table, updateArchives, warning; $.extend(section, { - innerHTML: "
Archives
404 Redirect is disabled.
Thread redirectionPost fetchingFile redirection

Archive Lists: Each line below should be an archive list in this format or a URL to load an archive list from.
Archive properties can be overriden by another item with the same uid (or if absent, its name).
Last updated:
Captcha Language
Choose from list of language codes. Leave blank to autoselect.
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
Archive link: g-archive
Internal archive link: g-expired
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"
Index-only link: g-index
Catalog-only link: g-catalog
Index mode: g-mode:"infinite scrolling"
Index sort: g-sort:"creation date rev"
External link: external-text:"Google","http://www.google.com"
Combinations are possible: g-index-text:"Technology Index"
Full board list toggle: toggle-all

[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [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
Literal %: %%
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (4chan filename)
Filename: %n (truncated), %N (untruncated), %t (4chan filename)
Download button: %d
Quick filter MD5: %f
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Tag: %g
Literal %: %%
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: seconds
Custom Cooldown Time
Seconds:
For more information about customizing 4chan X's CSS, see the styling guide.
Javascript Whitelist
Sources from which Javascript is allowed to be loaded by Content Security Policy.
Lines starting with a # will be ignored.
" + innerHTML: "
Archives
404 Redirect is disabled.
Thread redirectionPost fetchingFile redirection

Archive Lists: Each line below should be an archive list in this format or a URL to load an archive list from.
Archive properties can be overriden by another item with the same uid (or if absent, its name).
Last updated:
Captcha Language
Choose from list of language codes. Leave blank to autoselect.
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
Archive link: g-archive
Internal archive link: g-expired
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"
Index-only link: g-index
Catalog-only link: g-catalog
Index mode: g-mode:"infinite scrolling"
Index sort: g-sort:"creation date rev"
External link: external-text:"Google","http://www.google.com"
Combinations are possible: g-index-text:"Technology Index"
Full board list toggle: toggle-all

[ toggle-all ] [current-title] [g-title / a-title / jp-title] [x / wsg / h] [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
Literal %: %%
Language tag:
Quote Backlinks formatting is disabled.
:
File Info Formatting is disabled.
:
Link: %l (truncated), %L (untruncated), %T (4chan filename)
Filename: %n (truncated), %N (untruncated), %t (4chan filename)
Download button: %d
Quick filter MD5: %f
Spoiler indicator: %p
Size: %B (Bytes), %K (KB), %M (MB), %s (4chan default)
Resolution: %r (Displays 'PDF' for PDF files)
Tag: %g
Literal %: %%
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: seconds
Custom Cooldown Time
Seconds:
For more information about customizing 4chan X's CSS, see the styling guide.
Javascript Whitelist
Sources from which Javascript is allowed to be loaded by Content Security Policy.
Lines starting with a # will be ignored.
" }); ref = $$('.warning', section); for (j = 0, len = ref.length; j < len; j++) { @@ -11531,7 +11648,7 @@ Settings = (function() { return $.id('lastarchivecheck').textContent = 'never'; }); items = {}; - ref2 = ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist']; + ref2 = ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'timeLocale', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist']; for (l = 0, len2 = ref2.length; l < len2; l++) { name = ref2[l]; items[name] = Conf[name]; @@ -11717,6 +11834,9 @@ Settings = (function() { time: function() { return this.nextElementSibling.textContent = Time.format(this.value, new Date()); }, + timeLocale: function() { + return Settings.time.call($('[name=time]', Settings.dialog)); + }, backlink: function() { return this.nextElementSibling.textContent = this.value.replace(/%(?:id|%)/g, function(x) { return { @@ -12952,7 +13072,7 @@ ImageCommon = (function() { return cb(URL); } }; - return $.ajax("//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", { + return $.ajax(location.protocol + "//a.4cdn.org/" + post.board + "/thread/" + post.thread + ".json", { onload: function() { var i, len, postObj, ref; if (this.status === 404) { @@ -14867,7 +14987,7 @@ Embedding = (function() { }, title: { api: function(uid) { - return "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); + return location.protocol + "//soundcloud.com/oembed?format=json&url=https%3A%2F%2Fsoundcloud.com%2F" + (encodeURIComponent(uid)); }, text: function(_) { return _.title; @@ -15274,7 +15394,7 @@ CopyTextLink = (function() { el: a, order: 12, open: function(post) { - CopyTextLink.text = post.info.comment; + CopyTextLink.text = (post.origin || post).commentOrig(); return true; } }); @@ -15846,7 +15966,7 @@ BoardTips = (function() { tips: { qa: [ 1, { - innerHTML: "New to /qa/?
/qa/ is NOT an effective way to contact the mods.
Use IRC or feedback instead. More details here." + innerHTML: "New to /qa/?
/qa/ is NOT an effective way to contact the mods.
Message a mod on IRC or use feedback instead. More details here." } ] }, @@ -16075,7 +16195,7 @@ ExpandComment = (function() { return; } a.textContent = "Post No." + post + " Loading..."; - return $.cache("//a.4cdn.org" + (a.pathname.split(/\/+/).splice(0, 4).join('/')) + ".json", function() { + return $.cache(location.protocol + "//a.4cdn.org" + (a.pathname.split(/\/+/).splice(0, 4).join('/')) + ".json", function() { return ExpandComment.parse(this, a, post); }); }, @@ -16218,7 +16338,7 @@ ExpandThread = (function() { var status; ExpandThread.statuses[thread] = status = {}; a.textContent = Build.summaryText.apply(Build, ['...'].concat(slice.call(a.textContent.match(/\d+/g)))); - return status.req = $.cache("//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() { + return status.req = $.cache(location.protocol + "//a.4cdn.org/" + thread.board + "/thread/" + thread + ".json", function() { delete status.req; return ExpandThread.parse(this, thread, a); }); @@ -17255,7 +17375,7 @@ Keybinds = (function() { } url = "/" + thread.board + "/thread/" + thread; if (tab) { - return $.open(url); + return $.open(location.origin + url); } else { return location.href = url; } @@ -17907,6 +18027,30 @@ Time = (function() { }, day: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], month: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + localeFormat: function(date, options, defaultValue) { + if (Conf['timeLocale']) { + try { + return Intl.DateTimeFormat(Conf['timeLocale'], options).format(date); + } catch (_error) {} + } + return defaultValue; + }, + localeFormatPart: function(date, options, part, defaultValue) { + var parts; + if (Conf['timeLocale']) { + try { + parts = Intl.DateTimeFormat(Conf['timeLocale'], options).formatToParts(date); + return parts.map(function(x) { + if (x.type === part) { + return x.value; + } else { + return ''; + } + }).join(''); + } catch (_error) {} + } + return defaultValue; + }, zeroPad: function(n) { if (n < 10) { return "0" + n; @@ -17916,16 +18060,24 @@ Time = (function() { }, formatters: { a: function() { - return Time.day[this.getDay()].slice(0, 3); + return Time.localeFormat(this, { + weekday: 'short' + }, Time.day[this.getDay()].slice(0, 3)); }, A: function() { - return Time.day[this.getDay()]; + return Time.localeFormat(this, { + weekday: 'long' + }, Time.day[this.getDay()]); }, b: function() { - return Time.month[this.getMonth()].slice(0, 3); + return Time.localeFormat(this, { + month: 'short' + }, Time.month[this.getMonth()].slice(0, 3)); }, B: function() { - return Time.month[this.getMonth()]; + return Time.localeFormat(this, { + month: 'long' + }, Time.month[this.getMonth()]); }, d: function() { return Time.zeroPad(this.getDate()); @@ -17952,18 +18104,13 @@ Time = (function() { return Time.zeroPad(this.getMinutes()); }, p: function() { - if (this.getHours() < 12) { - return 'AM'; - } else { - return 'PM'; - } + return Time.localeFormatPart(this, { + hour: 'numeric', + hour12: true + }, 'dayperiod', (this.getHours() < 12 ? 'AM' : 'PM')); }, P: function() { - if (this.getHours() < 12) { - return 'am'; - } else { - return 'pm'; - } + return Time.formatters.p.call(this).toLowerCase(); }, S: function() { return Time.zeroPad(this.getSeconds()); @@ -18360,7 +18507,7 @@ ThreadStats = (function() { return; } ThreadStats.timeout = setTimeout(ThreadStats.fetchPage, 2 * $.MINUTE); - return $.ajax("//a.4cdn.org/" + ThreadStats.thread.board + "/threads.json", { + return $.ajax(location.protocol + "//a.4cdn.org/" + ThreadStats.thread.board + "/threads.json", { onload: ThreadStats.onThreadsLoad }, { whenModified: 'ThreadStats' @@ -18585,7 +18732,7 @@ ThreadUpdater = (function() { } break; case 404: - return $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/catalog.json", { + return $.ajax(location.protocol + "//a.4cdn.org/" + ThreadUpdater.thread.board + "/catalog.json", { onloadend: function() { var confirmed, i, k, len, len1, page, ref, ref1, thread; if (this.status === 200) { @@ -18697,7 +18844,7 @@ ThreadUpdater = (function() { if ((ref = ThreadUpdater.req) != null) { ref.abort(); } - return ThreadUpdater.req = $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json", { + return ThreadUpdater.req = $.ajax(location.protocol + "//a.4cdn.org/" + ThreadUpdater.thread.board + "/thread/" + ThreadUpdater.thread + ".json", { onloadend: ThreadUpdater.cb.load, timeout: $.MINUTE }, { @@ -19185,7 +19332,7 @@ ThreadWatcher = (function() { ThreadWatcher.status.textContent = '...'; $.addClass(ThreadWatcher.refreshButton, 'fa-spin'); } - req = $.ajax("//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", { + req = $.ajax(location.protocol + "//a.4cdn.org/" + boardID + "/thread/" + threadID + ".json", { onloadend: function() { return ThreadWatcher.parseStatus.call(this, thread); }, @@ -19898,7 +20045,6 @@ Unread = (function() { if (Unread.thread.isDead && !Unread.thread.isArchived) { return; } - Unread.db.forceSync(); return Unread.db.set({ boardID: Unread.thread.board.ID, threadID: Unread.thread.ID, @@ -21589,7 +21735,7 @@ QR = (function() { } }, quote: function(e) { - var ancestor, caretPos, com, frag, insideCode, j, k, l, len, len1, len2, len3, len4, len5, n, node, o, post, q, range, ref, ref1, ref2, ref3, ref4, ref5, ref6, ref7, sel, text, thread; + var ancestor, caretPos, com, frag, i, insideCode, j, k, l, len, len1, len2, len3, n, node, o, post, postRange, range, ref, ref1, ref2, ref3, ref4, ref5, ref6, root, sel, text, thread; if (e != null) { e.preventDefault(); } @@ -21598,9 +21744,21 @@ QR = (function() { } sel = d.getSelection(); post = Get.postFromNode(this); + root = post.nodes.root; + postRange = new Range(); + postRange.selectNode(root); text = post.board.ID === g.BOARD.ID ? ">>" + post + "\n" : ">>>/" + post.board + "/" + post + "\n"; - if (sel.toString().trim() && post === Get.postFromNode(sel.anchorNode)) { - range = sel.getRangeAt(0); + for (i = j = 0, ref = sel.rangeCount; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) { + range = sel.getRangeAt(i); + if (range.compareBoundaryPoints(Range.START_TO_START, postRange) < 0) { + range.setStartBefore(root); + } + if (range.compareBoundaryPoints(Range.END_TO_END, postRange) > 0) { + range.setEndAfter(root); + } + if (!range.toString().trim()) { + continue; + } frag = range.cloneContents(); ancestor = range.commonAncestorContainer; if ($.x('ancestor-or-self::*[self::s or contains(@class,"removed-spoiler")]', ancestor)) { @@ -21611,37 +21769,28 @@ QR = (function() { $.prepend(frag, $.tn('[code]')); $.add(frag, $.tn('[/code]')); } - ref = $$((insideCode ? 'br' : '.prettyprint br'), frag); - for (j = 0, len = ref.length; j < len; j++) { - node = ref[j]; + ref1 = $$((insideCode ? 'br' : '.prettyprint br'), frag); + for (k = 0, len = ref1.length; k < len; k++) { + node = ref1[k]; $.replace(node, $.tn('\n')); } - ref1 = $$('br', frag); - for (k = 0, len1 = ref1.length; k < len1; k++) { - node = ref1[k]; + ref2 = $$('br', frag); + for (l = 0, len1 = ref2.length; l < len1; l++) { + node = ref2[l]; if (node !== frag.lastChild) { $.replace(node, $.tn('\n>')); } } - ref2 = $$('s, .removed-spoiler', frag); - for (l = 0, len2 = ref2.length; l < len2; l++) { - node = ref2[l]; - $.replace(node, [$.tn('[spoiler]')].concat(slice.call(node.childNodes), [$.tn('[/spoiler]')])); - } - ref3 = $$('.prettyprint', frag); - for (n = 0, len3 = ref3.length; n < len3; n++) { + Post.prototype.insertTags(frag); + ref3 = $$('.linkify[data-original]', frag); + for (n = 0, len2 = ref3.length; n < len2; n++) { node = ref3[n]; - $.replace(node, [$.tn('[code]')].concat(slice.call(node.childNodes), [$.tn('[/code]')])); - } - ref4 = $$('.linkify[data-original]', frag); - for (o = 0, len4 = ref4.length; o < len4; o++) { - node = ref4[o]; $.replace(node, $.tn(node.dataset.original)); } - ref5 = $$('.embedder', frag); - for (q = 0, len5 = ref5.length; q < len5; q++) { - node = ref5[q]; - if (((ref6 = node.previousSibling) != null ? ref6.nodeValue : void 0) === ' ') { + ref4 = $$('.embedder', frag); + for (o = 0, len3 = ref4.length; o < len3; o++) { + node = ref4[o]; + if (((ref5 = node.previousSibling) != null ? ref5.nodeValue : void 0) === ' ') { $.rm(node.previousSibling); } $.rm(node); @@ -21649,7 +21798,7 @@ QR = (function() { text += ">" + (frag.textContent.trim()) + "\n"; } QR.openPost(); - ref7 = QR.nodes, com = ref7.com, thread = ref7.thread; + ref6 = QR.nodes, com = ref6.com, thread = ref6.thread; if (!com.value) { thread.value = Get.threadFromNode(this); } @@ -22502,9 +22651,9 @@ QR = (function() { if ((type === 'thread') === (cooldown.threadID === cooldown.postID) && cooldown.boardID !== g.BOARD.ID) { suffix = scope === 'global' ? '_global' : ''; seconds = Math.max(seconds, QR.cooldown.delays[type + suffix] - elapsed); - } - if (QR.cooldown.customCooldown) { - seconds = Math.max(seconds, parseInt(Conf['customCooldown'], 10) - elapsed); + if (QR.cooldown.customCooldown) { + seconds = Math.max(seconds, parseInt(Conf['customCooldown'], 10) - elapsed); + } } } nCooldowns += Object.keys(cooldowns).length; @@ -24506,7 +24655,7 @@ Main = (function() { items['previousversion'] = void 0; return ($.getSync || $.get)(items, function(items) { var ref1; - if (((ref1 = items['Redirect to HTTPS']) != null ? ref1 : Conf['Redirect to HTTPS']) && location.protocol !== 'https:') { + if (!$.perProtocolSettings && ((ref1 = items['Redirect to HTTPS']) != null ? ref1 : Conf['Redirect to HTTPS']) && location.protocol !== 'https:') { location.replace('https:' + location.host + location.pathname + location.search + location.hash); return; } @@ -24814,7 +24963,7 @@ Main = (function() { threads[0].ipCount = (m = scriptData.match(/\bunique_ips *= *(\d+)\b/)) ? +m[1] : void 0; } if (g.BOARD.ID === 'f' && g.VIEW === 'thread') { - $.ajax("//a.4cdn.org/f/thread/" + g.THREADID + ".json", { + $.ajax(location.protocol + "//a.4cdn.org/f/thread/" + g.THREADID + ".json", { timeout: $.MINUTE, onloadend: function() { if (this.response && posts[0].file) { diff --git a/builds/4chan-X.zip b/builds/4chan-X.zip index e9ee6fd27..3442d2f45 100644 Binary files a/builds/4chan-X.zip and b/builds/4chan-X.zip differ diff --git a/builds/updates-beta.xml b/builds/updates-beta.xml index adb96f810..2e6ed40c2 100644 --- a/builds/updates-beta.xml +++ b/builds/updates-beta.xml @@ -1,7 +1,7 @@ - + diff --git a/builds/updates.xml b/builds/updates.xml index 8191369e1..307246286 100644 --- a/builds/updates.xml +++ b/builds/updates.xml @@ -1,7 +1,7 @@ - + diff --git a/index.html b/index.html index eee02db01..e5771163a 100644 --- a/index.html +++ b/index.html @@ -23,8 +23,7 @@ https://github.com/Nebukazar/OneeChan.

Please note

Uninstalling: 4chan X disables the native extension, so if you uninstall 4chan X, you'll need to re-enable it. To do this, click the [Settings] link in the top right corner, uncheck "Disable the native extension" in the panel that appears, and click the "Save Settings" button. If you don't see a "Save Settings" button, it may be being hidden by your ad blocker.

-

Private browsing: 4chan X does not yet support private browsing / incognito mode. Although it may work in this mode, browsing data recorded by 4chan X, such as your last read post in a thread and which posts are yours, will still need to be cleared manually by resetting your settings. To control what browsing data 4chan X records, use the Remember Last Read Post and Remember Your Posts options in the settings panel.

-

HTTPS: 4chan X currently shares your settings and post history between the HTTP and HTTPS versions of 4chan. If you are concerned about protecting your privacy against a man-in-the-middle attack, you should disable 4chan X on the HTTP version of 4chan and/or install HTTPS Everywhere.

+

Private browsing: By default, 4chan X remembers your last read post in a thread and which posts were made by you, even if you are in private browsing / incognito mode. If you want to turn this off, uncheck the Remember Last Read Post and Remember Your Posts options in the settings panel. You can clear all 4chan browsing history saved by 4chan X by resetting your settings.

Install

You will first need to install a userscript manager such as Greasemonkey. Unfortunately, Firefox's transition to WebExtensions has forced a complete rewrite of Greasemonkey, leaving the current version of Greasemonkey unmaintained and unreliable. Until the WebExtensions version of Greasemonkey is complete, your options are:

diff --git a/package.json b/package.json index 8aa1496af..467c4e8ac 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,12 @@ "GM_listValues", "GM_addValueChangeListener", "GM_openInTab", - "GM_xmlhttpRequest" + "GM_xmlhttpRequest", + "GM.getValue", + "GM.setValue", + "GM.deleteValue", + "GM.listValues", + "GM.xmlHttpRequest" ], "min": { "chrome": "33", diff --git a/src/Archive/archives.json b/src/Archive/archives.json index 4247db9e3..1e62b9d5d 100644 --- a/src/Archive/archives.json +++ b/src/Archive/archives.json @@ -114,16 +114,6 @@ "boards": ["h", "hc", "hm", "r", "s", "soc"], "files": ["h", "hc", "hm", "r", "s", "soc"], "reports": true -}, { - "uid": 32, - "name": "4tan", - "domain": "boards.4tan.org", - "http": true, - "https": true, - "software": "foolfuuka", - "boards": ["3", "a", "aco", "adv", "an", "asp", "b", "bant", "biz", "c", "can", "cgl", "ck", "cm", "co", "cock", "d", "diy", "e", "f", "fa", "fap", "fit", "fitlit", "g", "gd", "gif", "h", "hc", "his", "hm", "hr", "i", "ic", "int", "jp", "k", "lgbt", "lit", "m", "mlp", "mlpol", "mo", "mtv", "mu", "n", "news", "o", "out", "outsoc", "p", "po", "pol", "qa", "qst", "r", "r9k", "s", "s4s", "sci", "soc", "sp", "spa", "t", "tg", "toy", "trash", "trv", "tv", "u", "v", "vg", "vint", "vip", "vp", "vr", "w", "wg", "wsg", "wsr", "x", "y"], - "files": ["bant", "can", "cock", "fap", "fitlit", "mlpol", "mo", "mtv", "outsoc", "spa", "vint"], - "reports": true }, { "uid": 33, "name": "YEET Archive", diff --git a/src/General/Index.coffee b/src/General/Index.coffee index 52d30e79d..8e5ebd73b 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -688,6 +688,9 @@ Index = else Index.parsedThreads[threadID].isHidden + isHiddenReply: (threadID, replyData) -> + PostHiding.isHidden(g.BOARD.ID, threadID, replyData.no) or Filter.isHidden(Build.parseJSON replyData, g.BOARD.ID) + buildThreads: (threadIDs, isCatalog) -> threads = [] newThreads = [] @@ -788,8 +791,7 @@ Index = replies = [] for data in lastReplies - continue if PostHiding.isHidden(g.BOARD.ID, thread.ID, data.no) - continue if Filter.isHidden(Build.parseJSON data, g.BOARD.ID) + continue if Index.isHiddenReply thread.ID, data reply = Build.catalogReply thread, data RelativeDates.update $('time', reply) $.on $('.catalog-reply-preview', reply), 'mouseover', QuotePreview.mouseover @@ -813,12 +815,16 @@ Index = when 'lastlong' lastlong = (thread) -> for r, i in (thread.last_replies or []) by -1 + continue if Index.isHiddenReply thread.no, r len = if r.com then Build.parseComment(r.com).replace(/[^a-z]/ig, '').length else 0 if len >= Index.lastLongThresholds[+!!r.ext] return r if thread.omitted_posts then thread.last_replies[0] else thread + lastlongD = {} + for thread in liveThreadData + lastlongD[thread.no] = lastlong(thread).no [liveThreadData...].sort((a, b) -> - lastlong(b).no - lastlong(a).no + lastlongD[b.no] - lastlongD[a.no] ).map (post) -> post.no when 'bump' then liveThreadIDs when 'birth' then [liveThreadIDs... ].sort (a, b) -> b - a diff --git a/src/General/Settings.coffee b/src/General/Settings.coffee index 67eea6ce0..4ebf2cba1 100644 --- a/src/General/Settings.coffee +++ b/src/General/Settings.coffee @@ -162,8 +162,12 @@ Settings = addCheckboxes fs, obj $.add section, fs addCheckboxes $('div[data-name="JSON Index"] > .suboption-list', section), Config.Index + + # Unsupported options if $.engine isnt 'gecko' - $('div[data-name="Remember QR Size"]', section).hidden = true # XXX not supported + $('div[data-name="Remember QR Size"]', section).hidden = true + if $.perProtocolSettings + $('div[data-name="Redirect to HTTPS"]', section).hidden = true $.get items, (items) -> for key, val of items @@ -489,7 +493,7 @@ Settings = $.id('lastarchivecheck').textContent = 'never' items = {} - for name in ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist'] + for name in ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'timeLocale', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist'] items[name] = Conf[name] input = inputs[name] event = if name in ['archiveLists', 'archiveAutoUpdate', 'QR.personas', 'favicon', 'usercss'] then 'change' else 'input' @@ -642,6 +646,9 @@ Settings = time: -> @nextElementSibling.textContent = Time.format @value, new Date() + timeLocale: -> + Settings.time.call $('[name=time]', Settings.dialog) + backlink: -> @nextElementSibling.textContent = @value.replace /%(?:id|%)/g, (x) -> ({'%id': '123456789', '%%': '%'})[x] diff --git a/src/General/Settings/Advanced.html b/src/General/Settings/Advanced.html index ecc0dae47..53dd2f680 100644 --- a/src/General/Settings/Advanced.html +++ b/src/General/Settings/Advanced.html @@ -64,6 +64,7 @@
Minute: %M
Second: %S
Literal %: %%
+
Language tag:
diff --git a/src/Menu/CopyTextLink.coffee b/src/Menu/CopyTextLink.coffee index f0efff265..26e2e4b47 100644 --- a/src/Menu/CopyTextLink.coffee +++ b/src/Menu/CopyTextLink.coffee @@ -12,7 +12,7 @@ CopyTextLink = el: a order: 12 open: (post) -> - CopyTextLink.text = post.info.comment + CopyTextLink.text = (post.origin or post).commentOrig() true copy: -> diff --git a/src/Miscellaneous/BoardTips.coffee b/src/Miscellaneous/BoardTips.coffee index b68d7550d..437487ef8 100644 --- a/src/Miscellaneous/BoardTips.coffee +++ b/src/Miscellaneous/BoardTips.coffee @@ -5,7 +5,7 @@ BoardTips = <%= html( 'New to /qa/?
' + '/qa/ is NOT an effective way to contact the mods.
' + - 'Use IRC or feedback instead. ' + + 'Message a mod on IRC or use feedback instead. ' + 'More details here.' ) %> ] diff --git a/src/Miscellaneous/Keybinds.coffee b/src/Miscellaneous/Keybinds.coffee index 2873915ea..ad40a274a 100644 --- a/src/Miscellaneous/Keybinds.coffee +++ b/src/Miscellaneous/Keybinds.coffee @@ -312,7 +312,7 @@ Keybinds = return if g.VIEW isnt 'index' url = "/#{thread.board}/thread/#{thread}" if tab - $.open url + $.open location.origin + url else location.href = url diff --git a/src/Miscellaneous/Time.coffee b/src/Miscellaneous/Time.coffee index f1ff39693..d1343e058 100644 --- a/src/Miscellaneous/Time.coffee +++ b/src/Miscellaneous/Time.coffee @@ -41,13 +41,26 @@ Time = 'December' ] + localeFormat: (date, options, defaultValue) -> + if Conf['timeLocale'] + try + return Intl.DateTimeFormat(Conf['timeLocale'], options).format(date) + defaultValue + + localeFormatPart: (date, options, part, defaultValue) -> + if Conf['timeLocale'] + try + parts = Intl.DateTimeFormat(Conf['timeLocale'], options).formatToParts(date) + return parts.map((x) -> if x.type is part then x.value else '').join('') + defaultValue + zeroPad: (n) -> if n < 10 then "0#{n}" else n formatters: - a: -> Time.day[@getDay()][...3] - A: -> Time.day[@getDay()] - b: -> Time.month[@getMonth()][...3] - B: -> Time.month[@getMonth()] + a: -> Time.localeFormat @, {weekday: 'short'}, Time.day[@getDay()][...3] + A: -> Time.localeFormat @, {weekday: 'long'}, Time.day[@getDay()] + b: -> Time.localeFormat @, {month: 'short'}, Time.month[@getMonth()][...3] + B: -> Time.localeFormat @, {month: 'long'}, Time.month[@getMonth()] d: -> Time.zeroPad @getDate() e: -> @getDate() H: -> Time.zeroPad @getHours() @@ -56,8 +69,8 @@ Time = l: -> @getHours() % 12 or 12 m: -> Time.zeroPad @getMonth() + 1 M: -> Time.zeroPad @getMinutes() - p: -> if @getHours() < 12 then 'AM' else 'PM' - P: -> if @getHours() < 12 then 'am' else 'pm' + p: -> Time.localeFormatPart @, {hour: 'numeric', hour12: true}, 'dayperiod', (if @getHours() < 12 then 'AM' else 'PM') + P: -> Time.formatters.p.call(@).toLowerCase() S: -> Time.zeroPad @getSeconds() y: -> @getFullYear().toString()[2..] Y: -> @getFullYear() diff --git a/src/Monitoring/Unread.coffee b/src/Monitoring/Unread.coffee index 3b8040796..bce6cd10e 100644 --- a/src/Monitoring/Unread.coffee +++ b/src/Monitoring/Unread.coffee @@ -190,7 +190,6 @@ Unread = Unread.lastReadPost = ID Unread.readCount++ return if Unread.thread.isDead and !Unread.thread.isArchived - Unread.db.forceSync() Unread.db.set boardID: Unread.thread.board.ID threadID: Unread.thread.ID diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index 744c91973..23db4df9c 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -297,9 +297,20 @@ QR = return unless QR.postingIsEnabled sel = d.getSelection() post = Get.postFromNode @ + {root} = post.nodes + postRange = new Range() + postRange.selectNode root text = if post.board.ID is g.BOARD.ID then ">>#{post}\n" else ">>>/#{post.board}/#{post}\n" - if sel.toString().trim() and post is Get.postFromNode sel.anchorNode - range = sel.getRangeAt 0 + for i in [0...sel.rangeCount] + range = sel.getRangeAt i + # Trim range to be fully inside post + if range.compareBoundaryPoints(Range.START_TO_START, postRange) < 0 + range.setStartBefore root + if range.compareBoundaryPoints(Range.END_TO_END, postRange) > 0 + range.setEndAfter root + + continue unless range.toString().trim() + frag = range.cloneContents() ancestor = range.commonAncestorContainer # Quoting the insides of a spoiler/code tag. @@ -313,10 +324,7 @@ QR = $.replace node, $.tn '\n' for node in $$ 'br', frag $.replace node, $.tn '\n>' unless node is frag.lastChild - for node in $$ 's, .removed-spoiler', frag - $.replace node, [$.tn('[spoiler]'), node.childNodes..., $.tn '[/spoiler]'] - for node in $$ '.prettyprint', frag - $.replace node, [$.tn('[code]'), node.childNodes..., $.tn '[/code]'] + Post::insertTags frag for node in $$ '.linkify[data-original]', frag $.replace node, $.tn node.dataset.original for node in $$ '.embedder', frag diff --git a/src/Posting/QR.cooldown.coffee b/src/Posting/QR.cooldown.coffee index 17e9c7eb9..6ae895d8e 100644 --- a/src/Posting/QR.cooldown.coffee +++ b/src/Posting/QR.cooldown.coffee @@ -159,9 +159,9 @@ QR.cooldown = '' seconds = Math.max seconds, QR.cooldown.delays[type + suffix] - elapsed - # If additional cooldown is enabled, add the configured seconds to the count. - if QR.cooldown.customCooldown - seconds = Math.max seconds, parseInt(Conf['customCooldown'], 10) - elapsed + # If additional cooldown is enabled, add the configured seconds to the count. + if QR.cooldown.customCooldown + seconds = Math.max seconds, parseInt(Conf['customCooldown'], 10) - elapsed nCooldowns += Object.keys(cooldowns).length diff --git a/src/classes/DataBoard.coffee b/src/classes/DataBoard.coffee index 7c89a4a8d..0654933aa 100644 --- a/src/classes/DataBoard.coffee +++ b/src/classes/DataBoard.coffee @@ -30,7 +30,6 @@ class DataBoard @save() deleteIfEmpty: ({boardID, threadID}) -> - $.forceSync @key if threadID unless Object.keys(@data.boards[boardID][threadID]).length delete @data.boards[boardID][threadID] diff --git a/src/classes/Fetcher.coffee b/src/classes/Fetcher.coffee index 81f5e15a0..d268c73d1 100644 --- a/src/classes/Fetcher.coffee +++ b/src/classes/Fetcher.coffee @@ -5,9 +5,8 @@ class Fetcher return # 4chan X catalog data - if (post = Index.replyData?["#{@boardID}.#{@postID}"]) + if (post = Index.replyData?["#{@boardID}.#{@postID}"]) and (thread = g.threads["#{@boardID}.#{@threadID}"]) board = g.boards[@boardID] - thread = g.threads["#{@boardID}.#{@threadID}"] post = new Post Build.postFromObject(post, @boardID), thread, board post.isFetchedQuote = true Main.callbackNodes 'Post', [post] diff --git a/src/classes/Post.coffee b/src/classes/Post.coffee index 8aecd9704..09f1109f0 100644 --- a/src/classes/Post.coffee +++ b/src/classes/Post.coffee @@ -129,6 +129,12 @@ class Post @cleanCommentDisplay bq @nodesToText(bq).trim().replace(/\s+$/gm, '') + commentOrig: -> + # Get the comment's text for reposting purposes. + bq = @nodes.commentClean.cloneNode true + @insertTags bq + @nodesToText bq + nodesToText: (bq) -> text = "" nodes = $.X './/br|.//text()', bq @@ -155,6 +161,13 @@ class Post $.rm b if (b = $ 'b', bq) and /^Rolled /.test(b.textContent) $.rm $('.fortune', bq) + insertTags: (bq) -> + for node in $$ 's, .removed-spoiler', bq + $.replace node, [$.tn('[spoiler]'), node.childNodes..., $.tn '[/spoiler]'] + for node in $$ '.prettyprint', bq + $.replace node, [$.tn('[code]'), node.childNodes..., $.tn '[/code]'] + return + parseQuotes: -> @quotes = [] # XXX https://github.com/4chan/4chan-JS/issues/77 diff --git a/src/config/Config.coffee b/src/config/Config.coffee index 4716c1cc3..da7a350c5 100644 --- a/src/config/Config.coffee +++ b/src/config/Config.coffee @@ -857,6 +857,7 @@ Config = captchaLanguage: '' time: '%m/%d/%y(%a)%H:%M:%S' + timeLocale: '' backlink: '>>%id' diff --git a/src/main/Main.coffee b/src/main/Main.coffee index 10eec3ece..6841ead9a 100644 --- a/src/main/Main.coffee +++ b/src/main/Main.coffee @@ -72,7 +72,7 @@ Main = items[key] = undefined for key of Conf items['previousversion'] = undefined ($.getSync or $.get) items, (items) -> - if (items['Redirect to HTTPS'] ? Conf['Redirect to HTTPS']) and location.protocol isnt 'https:' + if !$.perProtocolSettings and (items['Redirect to HTTPS'] ? Conf['Redirect to HTTPS']) and location.protocol isnt 'https:' location.replace('https:' + location.host + location.pathname + location.search + location.hash) return $.asap docSet, -> diff --git a/src/meta/jshint.json b/src/meta/jshint.json index 9c2d3e820..45f62a3eb 100644 --- a/src/meta/jshint.json +++ b/src/meta/jshint.json @@ -16,11 +16,14 @@ "globals": { "MediaError": false, "Set": false, + "Promise": false, + "BroadcastChannel": false, "GM_info": false, "cloneInto": false, "unsafeWindow": false, - "chrome": false<%= - meta.grants.map(x => `,\n "${x}": false`).join('') + "chrome": false, + "GM": false<%= + meta.grants.filter(x => !/\./.test(x)).map(x => `,\n "${x}": false`).join('') %><%= read('/tmp/declaration.js').match(/^var (.*);/)[1].split(', ').map(x => `,\n "${x}": true`).join('') %><%= diff --git a/src/platform/$.coffee b/src/platform/$.coffee index e7aef13d1..abc9c3c65 100644 --- a/src/platform/$.coffee +++ b/src/platform/$.coffee @@ -369,6 +369,13 @@ $.item = (key, val) -> item[key] = val item +$.oneItemSugar = (fn) -> + (key, val, cb) -> + if typeof key is 'string' + fn $.item(key, val), cb + else + fn key, val + $.syncing = {} <% if (type === 'crx') { %> @@ -402,14 +409,8 @@ $.crxWorking = -> $.crxWarningShown = true false -$.get = (key, val, cb) -> +$.get = $.oneItemSugar (data, cb) -> return unless $.crxWorking() - if typeof cb is 'function' - data = $.item key, val - else - data = key - cb = val - results = {} get = (area) -> chrome.storage[area].get Object.keys(data), (result) -> @@ -468,13 +469,8 @@ do -> setSync = $.debounce $.SECOND, -> setArea 'sync' - $.set = (key, val, cb) -> + $.set = $.oneItemSugar (data, cb) -> return unless $.crxWorking() - if typeof key is 'string' - data = $.item key, val - else - data = key - cb = val $.extend items.local, data setArea 'local', cb @@ -496,131 +492,167 @@ do -> # http://wiki.greasespot.net/Main_Page # https://tampermonkey.net/documentation.php -# workaround for Firefox 53 issue -$.currentValue = {} -$.GM_getValue = (key) -> - try - $.currentValue[key] = GM_getValue key - catch err - $.currentValue[key] -$.GM_setValue = (key, val) -> - $.currentValue[key] = val - GM_setValue key, val -$.GM_deleteValue = (key) -> - delete $.currentValue[key] - GM_deleteValue key +if GM?.deleteValue? -if GM_deleteValue? - $.getValue = $.GM_getValue - $.listValues = -> GM_listValues() # error when called if missing -else if $.hasStorage - $.getValue = (key) -> localStorage[key] - $.listValues = -> - key for key of localStorage when key[...g.NAMESPACE.length] is g.NAMESPACE -else - $.getValue = -> - $.listValues = -> [] + $.syncChannel = new BroadcastChannel(g.NAMESPACE + 'sync') -if GM_addValueChangeListener? - $.setValue = $.GM_setValue - $.deleteValue = $.GM_deleteValue -else if GM_deleteValue? - $.oldValue = {} - $.setValue = (key, val) -> - $.GM_setValue key, val - if key of $.syncing - $.oldValue[key] = val - localStorage[key] = val if $.hasStorage # for `storage` events - $.deleteValue = (key) -> - $.GM_deleteValue key - if key of $.syncing - delete $.oldValue[key] - localStorage.removeItem key if $.hasStorage # for `storage` events - $.cantSync = true if !$.hasStorage -else if $.hasStorage - $.oldValue = {} - $.setValue = (key, val) -> - $.oldValue[key] = val if key of $.syncing - localStorage[key] = val - $.deleteValue = (key) -> - delete $.oldValue[key] if key of $.syncing - localStorage.removeItem key -else - $.setValue = -> - $.deleteValue = -> - $.cantSync = $.cantSet = true + $.on $.syncChannel, 'message', (e) -> + for key, val of e.data when (cb = $.syncing[key]) + cb val, key -if GM_addValueChangeListener? $.sync = (key, cb) -> - $.syncing[key] = GM_addValueChangeListener g.NAMESPACE + key, (key2, oldValue, newValue, remote) -> - if remote - newValue = JSON.parse newValue unless newValue is undefined - cb newValue, key - $.forceSync = -> -else if GM_deleteValue? or $.hasStorage - $.sync = (key, cb) -> - key = g.NAMESPACE + key $.syncing[key] = cb - $.oldValue[key] = $.getValue key - do -> - onChange = ({key, newValue}) -> - return if not (cb = $.syncing[key]) - if newValue? - return if newValue is $.oldValue[key] - $.oldValue[key] = newValue - cb JSON.parse(newValue), key[g.NAMESPACE.length..] - else - return unless $.oldValue[key]? - delete $.oldValue[key] - cb undefined, key[g.NAMESPACE.length..] - $.on window, 'storage', onChange - - $.forceSync = (key) -> - # Storage events don't work across origins - # e.g. http://boards.4chan.org and https://boards.4chan.org - # so force a check for changes to avoid lost data. - key = g.NAMESPACE + key - onChange {key, newValue: $.getValue key} -else - $.sync = -> $.forceSync = -> -$.delete = (keys) -> - unless keys instanceof Array - keys = [keys] - for key in keys - $.deleteValue g.NAMESPACE + key - return + $.delete = (keys, cb) -> + unless keys instanceof Array + keys = [keys] + Promise.all(GM.deleteValue(g.NAMESPACE + key) for key in keys).then -> + items = {} + items[key] = undefined for key in keys + $.syncChannel.postMessage items + cb?() -$.get = (key, val, cb) -> - if typeof cb is 'function' - items = $.item key, val + $.get = $.oneItemSugar (items, cb) -> + keys = Object.keys items + Promise.all(GM.getValue(g.NAMESPACE + key) for key in keys).then (values) -> + for val, i in values when val + items[keys[i]] = JSON.parse val + cb items + + $.set = $.oneItemSugar (items, cb) -> + Promise.all(GM.setValue(g.NAMESPACE + key, JSON.stringify(val)) for key, val of items).then -> + $.syncChannel.postMessage items + cb?() + + $.clear = (cb) -> + GM.listValues.then (keys) -> + $.delete keys.map((key) -> key.replace g.NAMESPACE, ''), cb + +else + + # workaround for Firefox 53 issue + $.currentValue = {} + $.GM_getValue = (key) -> + try + $.currentValue[key] = GM_getValue key + catch err + $.currentValue[key] + $.GM_setValue = (key, val) -> + $.currentValue[key] = val + GM_setValue key, val + $.GM_deleteValue = (key) -> + delete $.currentValue[key] + GM_deleteValue key + + unless GM_deleteValue? + $.perProtocolSettings = true + + if GM_deleteValue? + $.getValue = $.GM_getValue + $.listValues = -> GM_listValues() # error when called if missing + else if $.hasStorage + $.getValue = (key) -> localStorage[key] + $.listValues = -> + key for key of localStorage when key[...g.NAMESPACE.length] is g.NAMESPACE else - items = key - cb = val - $.queueTask $.getSync, items, cb + $.getValue = -> + $.listValues = -> [] -$.getSync = (items, cb) -> - for key of items when (val2 = $.getValue g.NAMESPACE + key) - items[key] = JSON.parse val2 - cb items - -$.set = (keys, val, cb) -> - if typeof keys is 'string' - $.setValue(g.NAMESPACE + keys, JSON.stringify val) + if GM_addValueChangeListener? + $.setValue = $.GM_setValue + $.deleteValue = $.GM_deleteValue + else if GM_deleteValue? + $.oldValue = {} + $.setValue = (key, val) -> + $.GM_setValue key, val + if key of $.syncing + $.oldValue[key] = val + localStorage[key] = val if $.hasStorage # for `storage` events + $.deleteValue = (key) -> + $.GM_deleteValue key + if key of $.syncing + delete $.oldValue[key] + localStorage.removeItem key if $.hasStorage # for `storage` events + $.cantSync = true if !$.hasStorage + else if $.hasStorage + $.oldValue = {} + $.setValue = (key, val) -> + $.oldValue[key] = val if key of $.syncing + localStorage[key] = val + $.deleteValue = (key) -> + delete $.oldValue[key] if key of $.syncing + localStorage.removeItem key else - for key, value of keys - $.setValue(g.NAMESPACE + key, JSON.stringify value) - cb = val - cb?() + $.setValue = -> + $.deleteValue = -> + $.cantSync = $.cantSet = true + + if GM_addValueChangeListener? + $.sync = (key, cb) -> + $.syncing[key] = GM_addValueChangeListener g.NAMESPACE + key, (key2, oldValue, newValue, remote) -> + if remote + newValue = JSON.parse newValue unless newValue is undefined + cb newValue, key + $.forceSync = -> + else if GM_deleteValue? or $.hasStorage + $.sync = (key, cb) -> + key = g.NAMESPACE + key + $.syncing[key] = cb + $.oldValue[key] = $.getValue key + + do -> + onChange = ({key, newValue}) -> + return if not (cb = $.syncing[key]) + if newValue? + return if newValue is $.oldValue[key] + $.oldValue[key] = newValue + cb JSON.parse(newValue), key[g.NAMESPACE.length..] + else + return unless $.oldValue[key]? + delete $.oldValue[key] + cb undefined, key[g.NAMESPACE.length..] + $.on window, 'storage', onChange + + $.forceSync = (key) -> + # Storage events don't work across origins + # e.g. http://boards.4chan.org and https://boards.4chan.org + # so force a check for changes to avoid lost data. + key = g.NAMESPACE + key + onChange {key, newValue: $.getValue key} + else + $.sync = -> + $.forceSync = -> + + $.delete = (keys) -> + unless keys instanceof Array + keys = [keys] + for key in keys + $.deleteValue g.NAMESPACE + key + return + + $.get = $.oneItemSugar (items, cb) -> + $.queueTask $.getSync, items, cb + + $.getSync = (items, cb) -> + for key of items when (val2 = $.getValue g.NAMESPACE + key) + items[key] = JSON.parse val2 + cb items + + $.set = $.oneItemSugar (items, cb) -> + $.queueTask -> + for key, value of items + $.setValue(g.NAMESPACE + key, JSON.stringify value) + cb?() + + $.clear = (cb) -> + # XXX https://github.com/greasemonkey/greasemonkey/issues/2033 + # Also support case where GM_listValues is not defined. + $.delete Object.keys(Conf) + $.delete ['previousversion', 'QR Size', 'captchas', 'QR.persona', 'hiddenPSA'] + try + $.delete $.listValues().map (key) -> key.replace g.NAMESPACE, '' + cb?() -$.clear = (cb) -> - # XXX https://github.com/greasemonkey/greasemonkey/issues/2033 - # Also support case where GM_listValues is not defined. - $.delete Object.keys(Conf) - $.delete ['previousversion', 'QR Size', 'captchas', 'QR.persona', 'hiddenPSA'] - try - $.delete $.listValues().map (key) -> key.replace g.NAMESPACE, '' - cb?() <% } %> diff --git a/src/platform/CrossOrigin.coffee b/src/platform/CrossOrigin.coffee index fdfa1cf65..6cfffaed7 100644 --- a/src/platform/CrossOrigin.coffee +++ b/src/platform/CrossOrigin.coffee @@ -63,7 +63,7 @@ CrossOrigin = options.overrideMimeType = 'text/plain; charset=x-user-defined' else options.responseType = 'arraybuffer' - GM_xmlhttpRequest options + (GM?.xmlHttpRequest or GM_xmlhttpRequest) options <% } %> file: (url, cb) -> @@ -99,7 +99,7 @@ CrossOrigin = return callbacks[url] = [cb] <% if (type === 'userscript') { %> - GM_xmlhttpRequest + (GM?.xmlHttpRequest or GM_xmlhttpRequest) method: "GET" url: url+'' onload: (xhr) -> diff --git a/version.json b/version.json index ad672d03d..ef9ee9f42 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "version": "1.13.10.5", - "date": "2017-08-04T04:26:25.809Z" + "version": "1.13.12.1", + "date": "2017-09-29T00:42:32.947Z" } \ No newline at end of file