diff --git a/CHANGELOG.md b/CHANGELOG.md index 11171174e..d8141dc01 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ The attributions below are for work that has been incorporated into the script a The links to individual versions below are to copies of the script with the update URL removed. If you want automatic updates, install the script from the links on the [main page](https://github.com/ccd0/4chan-x). + +### v1.9.6.0 +*2014-10-05* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.6.0/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.6.0/builds/4chan-X-noupdate.crx "Chromium version")] + +**ccd0** +- Update cooldown timers to include 4chan's new 5-minute global thread cooldown. +- Fix various cooldown bugs. +- Improve error messages for invalid filter entries. +- Fix bug causing hidden threads list to be reverted to a prior state due to syncing with native catalog. + ### v1.9.5.3 *2014-10-03* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.5.3/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.5.3/builds/4chan-X-noupdate.crx "Chromium version")] diff --git a/LICENSE b/LICENSE index f63636901..45c814f8b 100755 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ /* -* 4chan X - Version 1.9.5.3 +* 4chan X - Version 1.9.6.0 * * Licensed under the MIT license. * https://github.com/ccd0/4chan-x/blob/master/LICENSE diff --git a/builds/4chan-X-beta.crx b/builds/4chan-X-beta.crx index eb53837d3..072b78c80 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 dae481cc0..6b080bffb 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.9.5.3 +// @version 1.9.6.0 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X diff --git a/builds/4chan-X-beta.user.js b/builds/4chan-X-beta.user.js index c551d1c8f..6a4eee2e1 100644 --- a/builds/4chan-X-beta.user.js +++ b/builds/4chan-X-beta.user.js @@ -1,7 +1,7 @@ // Generated by CoffeeScript // ==UserScript== // @name 4chan X beta -// @version 1.9.5.3 +// @version 1.9.6.0 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -24,7 +24,7 @@ // ==/UserScript== /* -* 4chan X - Version 1.9.5.3 +* 4chan X - Version 1.9.6.0 * * Licensed under the MIT license. * https://github.com/ccd0/4chan-x/blob/master/LICENSE @@ -380,7 +380,7 @@ doc = d.documentElement; g = { - VERSION: '1.9.5.3', + VERSION: '1.9.6.0', NAMESPACE: '4chan X.', NAME: '4chan X', FAQ: 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions', @@ -797,8 +797,13 @@ var cb, key, newValue; key = _arg.key, newValue = _arg.newValue; if (cb = $.syncing[key]) { - oldValue[key] = newValue; - return cb(JSON.parse(newValue), key); + if (newValue != null) { + oldValue[key] = newValue; + return cb(JSON.parse(newValue), key); + } else { + delete oldValue[key]; + return cb(void 0, key); + } } }; $.on(window, 'storage', onChange); @@ -4659,7 +4664,7 @@ Filter = { filters: {}, init: function() { - var boards, err, filter, hl, key, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; + var boards, err, filter, hl, key, line, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; if (g.VIEW === 'catalog' || !Conf['Filter']) { return; } @@ -4670,14 +4675,14 @@ this.filters[key] = []; _ref = Conf[key].split('\n'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { - filter = _ref[_i]; - if (filter[0] === '#') { + line = _ref[_i]; + if (line[0] === '#') { continue; } - if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) { + if (!(regexp = line.match(/\/(.+)\/(\w*)/))) { continue; } - filter = filter.replace(regexp[0], ''); + filter = line.replace(regexp[0], ''); boards = ((_ref1 = filter.match(/boards:([^;]+)/)) != null ? _ref1[1].toLowerCase() : void 0) || 'global'; if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) { continue; @@ -4689,7 +4694,7 @@ regexp = RegExp(regexp[1], regexp[2]); } catch (_error) { err = _error; - new Notice('warning', err.message, 60); + new Notice('warning', [$.tn("Invalid " + key + " filter:"), $.el('br'), $.tn(line), $.el('br'), $.tn(err.message)], 60); continue; } } @@ -5288,16 +5293,64 @@ ThreadHiding = { init: function() { - if (g.VIEW !== 'index' || !Conf['Thread Hiding Buttons'] && !Conf['Thread Hiding Link'] && !Conf['JSON Navigation']) { + if (g.VIEW === 'thread' || !Conf['Thread Hiding Buttons'] && !Conf['Thread Hiding Link'] && !Conf['JSON Navigation']) { return; } this.db = new DataBoard('hiddenThreads'); - this.syncCatalog(); + if (g.VIEW === 'catalog') { + return this.catalogWatch(); + } + this.catalogSet(g.BOARD); return Thread.callbacks.push({ name: 'Thread Hiding', cb: this.node }); }, + catalogSet: function(board) { + var hiddenThreads, threadID; + hiddenThreads = ThreadHiding.db.get({ + boardID: board.ID, + defaultValue: {} + }); + for (threadID in hiddenThreads) { + hiddenThreads[threadID] = true; + } + return localStorage.setItem("4chan-hide-t-" + board, JSON.stringify(hiddenThreads)); + }, + catalogWatch: function() { + this.hiddenThreads = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; + return $.ready(function() { + return new MutationObserver(ThreadHiding.catalogSave).observe($.id('threads'), { + attributes: true, + subtree: true, + attributeFilter: ['style'] + }); + }); + }, + catalogSave: function() { + var hiddenThreads2, threadID; + hiddenThreads2 = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; + for (threadID in hiddenThreads2) { + if (!(threadID in ThreadHiding.hiddenThreads)) { + ThreadHiding.db.set({ + boardID: g.BOARD.ID, + threadID: threadID, + val: { + makeStub: Conf['Stubs'] + } + }); + } + } + for (threadID in ThreadHiding.hiddenThreads) { + if (!(threadID in hiddenThreads2)) { + ThreadHiding.db["delete"]({ + boardID: g.BOARD.ID, + threadID: threadID + }); + } + } + return ThreadHiding.hiddenThreads = hiddenThreads2; + }, node: function() { var data; if (data = ThreadHiding.db.get({ @@ -5321,56 +5374,6 @@ } } }, - syncCatalog: function() { - var hiddenThreads, hiddenThreadsOnCatalog, threadID; - hiddenThreads = ThreadHiding.db.get({ - boardID: g.BOARD.ID, - defaultValue: {} - }); - hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; - for (threadID in hiddenThreadsOnCatalog) { - if (!(threadID in hiddenThreads)) { - hiddenThreads[threadID] = {}; - } - } - for (threadID in hiddenThreads) { - if (!(threadID in hiddenThreadsOnCatalog)) { - delete hiddenThreads[threadID]; - } - } - if ((ThreadHiding.db.data.lastChecked || 0) > Date.now() - $.MINUTE) { - ThreadHiding.cleanCatalog(hiddenThreadsOnCatalog); - } - return ThreadHiding.db.set({ - boardID: g.BOARD.ID, - val: hiddenThreads - }); - }, - cleanCatalog: function(hiddenThreadsOnCatalog) { - return $.cache("//a.4cdn.org/" + g.BOARD + "/threads.json", function() { - var page, thread, threads, _i, _j, _len, _len1, _ref, _ref1; - if (this.status !== 200) { - return; - } - threads = {}; - _ref = this.response; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - page = _ref[_i]; - _ref1 = page.threads; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - thread = _ref1[_j]; - if (thread.no in hiddenThreadsOnCatalog) { - threads[thread.no] = hiddenThreadsOnCatalog[thread.no]; - } - } - } - if (Object.keys(threads).length) { - return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(threads)); - } else { - return localStorage.removeItem("4chan-hide-t-" + g.BOARD); - } - }); - }, menu: { init: function() { var apply, div, hideStubLink, makeStub; @@ -5501,8 +5504,6 @@ return $.prepend(root, thread.stub); }, saveHiddenState: function(thread, makeStub) { - var hiddenThreadsOnCatalog; - hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; if (thread.isHidden) { ThreadHiding.db.set({ boardID: thread.board.ID, @@ -5511,15 +5512,13 @@ makeStub: makeStub } }); - hiddenThreadsOnCatalog[thread] = true; } else { ThreadHiding.db["delete"]({ boardID: thread.board.ID, threadID: thread.ID }); - delete hiddenThreadsOnCatalog[thread]; } - return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(hiddenThreadsOnCatalog)); + return ThreadHiding.catalogSet(thread.board); }, toggle: function(thread) { if (!(thread instanceof Thread)) { @@ -7145,14 +7144,10 @@ err = 'This CAPTCHA is no longer valid because it has expired.'; } QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : err === 'Connection error with sys.4chan.org.' ? true : false; - QR.cooldown.set({ - delay: 2 - }); - } else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i))) { + QR.cooldown.addDelay(post, 2); + } else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i)) && !/duplicate/i.test(err.textContent)) { QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true; - QR.cooldown.set({ - delay: m[1] - }); + QR.cooldown.addDelay(post, +m[1]); } else { QR.cooldown.auto = false; } @@ -7213,12 +7208,7 @@ } post.rm(); } - QR.cooldown.set({ - req: req, - post: post, - isReply: isReply, - threadID: threadID - }); + QR.cooldown.add(req.uploadEndTime, threadID, postID); URL = threadID === postID ? "" + window.location.origin + "/" + g.BOARD + "/thread/" + threadID : g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab'] ? "" + window.location.origin + "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0; if (URL) { if (Conf['Open Post in New Tab']) { @@ -7455,119 +7445,184 @@ }; QR.cooldown = { + seconds: 0, init: function() { - var key, setTimers, type; + var delay, items, key, keys, scope, setTimers, type, _ref, _results; if (!Conf['Cooldown']) { return; } setTimers = (function(_this) { return function(e) { - return QR.cooldown.types = e.detail; + return QR.cooldown.delays = e.detail; }; })(this); $.on(window, 'cooldown:timers', setTimers); $.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))'); $.off(window, 'cooldown:timers', setTimers); - for (type in QR.cooldown.types) { - QR.cooldown.types[type] = +QR.cooldown.types[type]; + QR.cooldown.maxDelay = 0; + _ref = QR.cooldown.delays; + for (type in _ref) { + delay = _ref[type]; + if (type !== 'thread') { + QR.cooldown.maxDelay = Math.max(QR.cooldown.maxDelay, delay); + } } - key = "cooldown." + g.BOARD; - $.get(key, {}, function(item) { - QR.cooldown.cooldowns = item[key]; + QR.cooldown.delays['thread_global'] = 300; + keys = QR.cooldown.keys = { + local: "cooldown." + g.BOARD, + global: 'cooldown.global' + }; + items = {}; + for (scope in keys) { + key = keys[scope]; + items[key] = {}; + } + $.get(items, function(items) { + for (scope in keys) { + key = keys[scope]; + QR.cooldown[scope] = items[key]; + } return QR.cooldown.start(); }); - return $.sync(key, QR.cooldown.sync); + _results = []; + for (scope in keys) { + key = keys[scope]; + _results.push($.sync(key, QR.cooldown.sync(scope))); + } + return _results; }, start: function() { - if (QR.cooldown.isCounting || !Object.keys(QR.cooldown.cooldowns).length) { + if (QR.cooldown.isCounting || Object.keys(QR.cooldown.local).length + Object.keys(QR.cooldown.global).length === 0) { return; } QR.cooldown.isCounting = true; return QR.cooldown.count(); }, - sync: function(cooldowns) { - var id; - for (id in cooldowns) { - QR.cooldown.cooldowns[id] = cooldowns[id]; - } - return QR.cooldown.start(); + sync: function(scope) { + return function(cooldowns) { + QR.cooldown[scope] = cooldowns || {}; + return QR.cooldown.start(); + }; }, - set: function(data) { - var cooldown, delay, isReply, post, req, start, threadID; + add: function(start, threadID, postID) { + var boardID; if (!Conf['Cooldown']) { return; } - req = data.req, post = data.post, isReply = data.isReply, threadID = data.threadID, delay = data.delay; - start = req ? req.uploadEndTime : Date.now(); - if (delay) { - cooldown = { - delay: delay - }; - } else { - cooldown = { - isReply: isReply, - threadID: threadID - }; + boardID = g.BOARD.ID; + QR.cooldown.set('local', start, { + threadID: threadID, + postID: postID + }); + if (threadID === postID) { + QR.cooldown.set('global', start, { + boardID: boardID, + threadID: threadID, + postID: postID + }); } - QR.cooldown.cooldowns[start] = cooldown; - $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns); return QR.cooldown.start(); }, - unset: function(id) { - delete QR.cooldown.cooldowns[id]; - if (Object.keys(QR.cooldown.cooldowns).length) { - return $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns); + addDelay: function(post, delay) { + var cooldown; + if (!Conf['Cooldown']) { + return; + } + cooldown = QR.cooldown.categorize(post); + cooldown.delay = delay; + QR.cooldown.set('local', Date.now(), cooldown); + return QR.cooldown.start(); + }, + "delete": function(post) { + var cooldown, id, _ref; + if (!(Conf['Cooldown'] && g.BOARD.ID === post.board.ID)) { + return; + } + $.forceSync(QR.cooldown.keys.local); + _ref = QR.cooldown.local; + for (id in _ref) { + cooldown = _ref[id]; + if ((cooldown.delay == null) && cooldown.threadID === post.thread.ID && cooldown.postID === post.ID) { + delete QR.cooldown.local[id]; + } + } + return QR.cooldown.save('local'); + }, + categorize: function(post) { + if (post.thread === 'new') { + return { + type: 'thread' + }; } else { - return $["delete"]("cooldown." + g.BOARD); + return { + type: !!post.file ? 'image' : 'reply', + threadID: +post.thread + }; + } + }, + set: function(scope, id, value) { + $.forceSync(QR.cooldown.keys[scope]); + QR.cooldown[scope][id] = value; + return $.set(QR.cooldown.keys[scope], QR.cooldown[scope]); + }, + save: function(scope) { + if (Object.keys(QR.cooldown[scope]).length) { + return $.set(QR.cooldown.keys[scope], QR.cooldown[scope]); + } else { + return $["delete"](QR.cooldown.keys[scope]); } }, count: function() { - var cooldown, cooldowns, elapsed, hasFile, isReply, maxTimer, now, post, seconds, start, type, types, update, _ref; - if (!Object.keys(QR.cooldown.cooldowns).length) { - $["delete"]("cooldown." + g.BOARD); - delete QR.cooldown.isCounting; - delete QR.cooldown.seconds; - QR.status(); - return; - } - clearTimeout(QR.cooldown.timeout); - QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND); + var cooldown, elapsed, key, maxDelay, now, save, scope, seconds, start, suffix, threadID, type, update, _ref, _ref1, _ref2; now = Date.now(); - post = QR.posts[0]; - isReply = post.thread !== 'new'; - hasFile = !!post.file; - seconds = null; - _ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns; - for (start in cooldowns) { - cooldown = cooldowns[start]; - start = +start; - if ('delay' in cooldown) { - if (cooldown.delay) { - seconds = Math.max(seconds, cooldown.delay--); - } else { - seconds = Math.max(seconds, 0); - QR.cooldown.unset(start); - } - continue; - } - if (isReply === cooldown.isReply) { + _ref = QR.cooldown.categorize(QR.posts[0]), type = _ref.type, threadID = _ref.threadID; + seconds = 0; + _ref1 = QR.cooldown.keys; + for (scope in _ref1) { + key = _ref1[scope]; + $.forceSync(key); + save = false; + _ref2 = QR.cooldown[scope]; + for (start in _ref2) { + cooldown = _ref2[start]; + start = +start; elapsed = Math.floor((now - start) / $.SECOND); if (elapsed < 0) { - QR.cooldown.unset(start); + delete QR.cooldown[scope][start]; + save = true; continue; } - type = !isReply ? 'thread' : hasFile ? 'image' : 'reply'; - maxTimer = Math.max(types[type] || 0, types[type + '_intra'] || 0); - if (!((start <= now && now <= start + maxTimer * $.SECOND))) { - QR.cooldown.unset(start); + if (cooldown.delay != null) { + if (cooldown.delay <= elapsed) { + delete QR.cooldown[scope][start]; + save = true; + } else if (cooldown.type === type && cooldown.threadID === threadID) { + seconds = Math.max(seconds, cooldown.delay - elapsed); + } + continue; } - if (isReply && +post.thread === cooldown.threadID) { - type += '_intra'; + maxDelay = cooldown.threadID !== cooldown.postID ? QR.cooldown.maxDelay : QR.cooldown.delays[scope === 'global' ? 'thread_global' : 'thread']; + if (maxDelay <= elapsed) { + delete QR.cooldown[scope][start]; + save = true; + continue; } - seconds = Math.max(seconds, types[type] - elapsed); + if ((type === 'thread') === (cooldown.threadID === cooldown.postID)) { + suffix = scope === 'global' ? '_global' : type !== 'thread' && threadID === cooldown.threadID ? '_intra' : ''; + seconds = Math.max(seconds, QR.cooldown.delays[type + suffix] - elapsed); + } + } + if (save) { + QR.cooldown.save(scope); } } - update = seconds !== null || !!QR.cooldown.seconds; + if (Object.keys(QR.cooldown.local).length + Object.keys(QR.cooldown.global).length) { + clearTimeout(QR.cooldown.timeout); + QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND); + } else { + delete QR.cooldown.isCounting; + } + update = seconds !== QR.cooldown.seconds; QR.cooldown.seconds = seconds; if (update) { QR.status(); @@ -10188,6 +10243,7 @@ $.on(link, 'click', DeleteLink["delete"]); } else { if (resDoc.title === 'Updating index...') { + QR.cooldown["delete"](post); (post.origin || post).kill(fileOnly); } s = 'Deleted'; @@ -13406,7 +13462,7 @@ className: 'dialog' }); $.extend(dialog, { - innerHTML: "
" + innerHTML: "
" }); $.on($('.export', Settings.dialog), 'click', Settings["export"]); $.on($('.import', Settings.dialog), 'click', Settings["import"]); diff --git a/builds/4chan-X-noupdate.crx b/builds/4chan-X-noupdate.crx index f91ebf9ec..9ae6eb820 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 d004fe1ed..f22a34d25 100644 --- a/builds/4chan-X-noupdate.user.js +++ b/builds/4chan-X-noupdate.user.js @@ -1,7 +1,7 @@ // Generated by CoffeeScript // ==UserScript== // @name 4chan X -// @version 1.9.5.3 +// @version 1.9.6.0 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -23,7 +23,7 @@ // ==/UserScript== /* -* 4chan X - Version 1.9.5.3 +* 4chan X - Version 1.9.6.0 * * Licensed under the MIT license. * https://github.com/ccd0/4chan-x/blob/master/LICENSE @@ -379,7 +379,7 @@ doc = d.documentElement; g = { - VERSION: '1.9.5.3', + VERSION: '1.9.6.0', NAMESPACE: '4chan X.', NAME: '4chan X', FAQ: 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions', @@ -796,8 +796,13 @@ var cb, key, newValue; key = _arg.key, newValue = _arg.newValue; if (cb = $.syncing[key]) { - oldValue[key] = newValue; - return cb(JSON.parse(newValue), key); + if (newValue != null) { + oldValue[key] = newValue; + return cb(JSON.parse(newValue), key); + } else { + delete oldValue[key]; + return cb(void 0, key); + } } }; $.on(window, 'storage', onChange); @@ -4658,7 +4663,7 @@ Filter = { filters: {}, init: function() { - var boards, err, filter, hl, key, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; + var boards, err, filter, hl, key, line, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; if (g.VIEW === 'catalog' || !Conf['Filter']) { return; } @@ -4669,14 +4674,14 @@ this.filters[key] = []; _ref = Conf[key].split('\n'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { - filter = _ref[_i]; - if (filter[0] === '#') { + line = _ref[_i]; + if (line[0] === '#') { continue; } - if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) { + if (!(regexp = line.match(/\/(.+)\/(\w*)/))) { continue; } - filter = filter.replace(regexp[0], ''); + filter = line.replace(regexp[0], ''); boards = ((_ref1 = filter.match(/boards:([^;]+)/)) != null ? _ref1[1].toLowerCase() : void 0) || 'global'; if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) { continue; @@ -4688,7 +4693,7 @@ regexp = RegExp(regexp[1], regexp[2]); } catch (_error) { err = _error; - new Notice('warning', err.message, 60); + new Notice('warning', [$.tn("Invalid " + key + " filter:"), $.el('br'), $.tn(line), $.el('br'), $.tn(err.message)], 60); continue; } } @@ -5287,16 +5292,64 @@ ThreadHiding = { init: function() { - if (g.VIEW !== 'index' || !Conf['Thread Hiding Buttons'] && !Conf['Thread Hiding Link'] && !Conf['JSON Navigation']) { + if (g.VIEW === 'thread' || !Conf['Thread Hiding Buttons'] && !Conf['Thread Hiding Link'] && !Conf['JSON Navigation']) { return; } this.db = new DataBoard('hiddenThreads'); - this.syncCatalog(); + if (g.VIEW === 'catalog') { + return this.catalogWatch(); + } + this.catalogSet(g.BOARD); return Thread.callbacks.push({ name: 'Thread Hiding', cb: this.node }); }, + catalogSet: function(board) { + var hiddenThreads, threadID; + hiddenThreads = ThreadHiding.db.get({ + boardID: board.ID, + defaultValue: {} + }); + for (threadID in hiddenThreads) { + hiddenThreads[threadID] = true; + } + return localStorage.setItem("4chan-hide-t-" + board, JSON.stringify(hiddenThreads)); + }, + catalogWatch: function() { + this.hiddenThreads = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; + return $.ready(function() { + return new MutationObserver(ThreadHiding.catalogSave).observe($.id('threads'), { + attributes: true, + subtree: true, + attributeFilter: ['style'] + }); + }); + }, + catalogSave: function() { + var hiddenThreads2, threadID; + hiddenThreads2 = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; + for (threadID in hiddenThreads2) { + if (!(threadID in ThreadHiding.hiddenThreads)) { + ThreadHiding.db.set({ + boardID: g.BOARD.ID, + threadID: threadID, + val: { + makeStub: Conf['Stubs'] + } + }); + } + } + for (threadID in ThreadHiding.hiddenThreads) { + if (!(threadID in hiddenThreads2)) { + ThreadHiding.db["delete"]({ + boardID: g.BOARD.ID, + threadID: threadID + }); + } + } + return ThreadHiding.hiddenThreads = hiddenThreads2; + }, node: function() { var data; if (data = ThreadHiding.db.get({ @@ -5320,56 +5373,6 @@ } } }, - syncCatalog: function() { - var hiddenThreads, hiddenThreadsOnCatalog, threadID; - hiddenThreads = ThreadHiding.db.get({ - boardID: g.BOARD.ID, - defaultValue: {} - }); - hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; - for (threadID in hiddenThreadsOnCatalog) { - if (!(threadID in hiddenThreads)) { - hiddenThreads[threadID] = {}; - } - } - for (threadID in hiddenThreads) { - if (!(threadID in hiddenThreadsOnCatalog)) { - delete hiddenThreads[threadID]; - } - } - if ((ThreadHiding.db.data.lastChecked || 0) > Date.now() - $.MINUTE) { - ThreadHiding.cleanCatalog(hiddenThreadsOnCatalog); - } - return ThreadHiding.db.set({ - boardID: g.BOARD.ID, - val: hiddenThreads - }); - }, - cleanCatalog: function(hiddenThreadsOnCatalog) { - return $.cache("//a.4cdn.org/" + g.BOARD + "/threads.json", function() { - var page, thread, threads, _i, _j, _len, _len1, _ref, _ref1; - if (this.status !== 200) { - return; - } - threads = {}; - _ref = this.response; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - page = _ref[_i]; - _ref1 = page.threads; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - thread = _ref1[_j]; - if (thread.no in hiddenThreadsOnCatalog) { - threads[thread.no] = hiddenThreadsOnCatalog[thread.no]; - } - } - } - if (Object.keys(threads).length) { - return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(threads)); - } else { - return localStorage.removeItem("4chan-hide-t-" + g.BOARD); - } - }); - }, menu: { init: function() { var apply, div, hideStubLink, makeStub; @@ -5500,8 +5503,6 @@ return $.prepend(root, thread.stub); }, saveHiddenState: function(thread, makeStub) { - var hiddenThreadsOnCatalog; - hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; if (thread.isHidden) { ThreadHiding.db.set({ boardID: thread.board.ID, @@ -5510,15 +5511,13 @@ makeStub: makeStub } }); - hiddenThreadsOnCatalog[thread] = true; } else { ThreadHiding.db["delete"]({ boardID: thread.board.ID, threadID: thread.ID }); - delete hiddenThreadsOnCatalog[thread]; } - return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(hiddenThreadsOnCatalog)); + return ThreadHiding.catalogSet(thread.board); }, toggle: function(thread) { if (!(thread instanceof Thread)) { @@ -7144,14 +7143,10 @@ err = 'This CAPTCHA is no longer valid because it has expired.'; } QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : err === 'Connection error with sys.4chan.org.' ? true : false; - QR.cooldown.set({ - delay: 2 - }); - } else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i))) { + QR.cooldown.addDelay(post, 2); + } else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i)) && !/duplicate/i.test(err.textContent)) { QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true; - QR.cooldown.set({ - delay: m[1] - }); + QR.cooldown.addDelay(post, +m[1]); } else { QR.cooldown.auto = false; } @@ -7212,12 +7207,7 @@ } post.rm(); } - QR.cooldown.set({ - req: req, - post: post, - isReply: isReply, - threadID: threadID - }); + QR.cooldown.add(req.uploadEndTime, threadID, postID); URL = threadID === postID ? "" + window.location.origin + "/" + g.BOARD + "/thread/" + threadID : g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab'] ? "" + window.location.origin + "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0; if (URL) { if (Conf['Open Post in New Tab']) { @@ -7454,119 +7444,184 @@ }; QR.cooldown = { + seconds: 0, init: function() { - var key, setTimers, type; + var delay, items, key, keys, scope, setTimers, type, _ref, _results; if (!Conf['Cooldown']) { return; } setTimers = (function(_this) { return function(e) { - return QR.cooldown.types = e.detail; + return QR.cooldown.delays = e.detail; }; })(this); $.on(window, 'cooldown:timers', setTimers); $.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))'); $.off(window, 'cooldown:timers', setTimers); - for (type in QR.cooldown.types) { - QR.cooldown.types[type] = +QR.cooldown.types[type]; + QR.cooldown.maxDelay = 0; + _ref = QR.cooldown.delays; + for (type in _ref) { + delay = _ref[type]; + if (type !== 'thread') { + QR.cooldown.maxDelay = Math.max(QR.cooldown.maxDelay, delay); + } } - key = "cooldown." + g.BOARD; - $.get(key, {}, function(item) { - QR.cooldown.cooldowns = item[key]; + QR.cooldown.delays['thread_global'] = 300; + keys = QR.cooldown.keys = { + local: "cooldown." + g.BOARD, + global: 'cooldown.global' + }; + items = {}; + for (scope in keys) { + key = keys[scope]; + items[key] = {}; + } + $.get(items, function(items) { + for (scope in keys) { + key = keys[scope]; + QR.cooldown[scope] = items[key]; + } return QR.cooldown.start(); }); - return $.sync(key, QR.cooldown.sync); + _results = []; + for (scope in keys) { + key = keys[scope]; + _results.push($.sync(key, QR.cooldown.sync(scope))); + } + return _results; }, start: function() { - if (QR.cooldown.isCounting || !Object.keys(QR.cooldown.cooldowns).length) { + if (QR.cooldown.isCounting || Object.keys(QR.cooldown.local).length + Object.keys(QR.cooldown.global).length === 0) { return; } QR.cooldown.isCounting = true; return QR.cooldown.count(); }, - sync: function(cooldowns) { - var id; - for (id in cooldowns) { - QR.cooldown.cooldowns[id] = cooldowns[id]; - } - return QR.cooldown.start(); + sync: function(scope) { + return function(cooldowns) { + QR.cooldown[scope] = cooldowns || {}; + return QR.cooldown.start(); + }; }, - set: function(data) { - var cooldown, delay, isReply, post, req, start, threadID; + add: function(start, threadID, postID) { + var boardID; if (!Conf['Cooldown']) { return; } - req = data.req, post = data.post, isReply = data.isReply, threadID = data.threadID, delay = data.delay; - start = req ? req.uploadEndTime : Date.now(); - if (delay) { - cooldown = { - delay: delay - }; - } else { - cooldown = { - isReply: isReply, - threadID: threadID - }; + boardID = g.BOARD.ID; + QR.cooldown.set('local', start, { + threadID: threadID, + postID: postID + }); + if (threadID === postID) { + QR.cooldown.set('global', start, { + boardID: boardID, + threadID: threadID, + postID: postID + }); } - QR.cooldown.cooldowns[start] = cooldown; - $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns); return QR.cooldown.start(); }, - unset: function(id) { - delete QR.cooldown.cooldowns[id]; - if (Object.keys(QR.cooldown.cooldowns).length) { - return $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns); + addDelay: function(post, delay) { + var cooldown; + if (!Conf['Cooldown']) { + return; + } + cooldown = QR.cooldown.categorize(post); + cooldown.delay = delay; + QR.cooldown.set('local', Date.now(), cooldown); + return QR.cooldown.start(); + }, + "delete": function(post) { + var cooldown, id, _ref; + if (!(Conf['Cooldown'] && g.BOARD.ID === post.board.ID)) { + return; + } + $.forceSync(QR.cooldown.keys.local); + _ref = QR.cooldown.local; + for (id in _ref) { + cooldown = _ref[id]; + if ((cooldown.delay == null) && cooldown.threadID === post.thread.ID && cooldown.postID === post.ID) { + delete QR.cooldown.local[id]; + } + } + return QR.cooldown.save('local'); + }, + categorize: function(post) { + if (post.thread === 'new') { + return { + type: 'thread' + }; } else { - return $["delete"]("cooldown." + g.BOARD); + return { + type: !!post.file ? 'image' : 'reply', + threadID: +post.thread + }; + } + }, + set: function(scope, id, value) { + $.forceSync(QR.cooldown.keys[scope]); + QR.cooldown[scope][id] = value; + return $.set(QR.cooldown.keys[scope], QR.cooldown[scope]); + }, + save: function(scope) { + if (Object.keys(QR.cooldown[scope]).length) { + return $.set(QR.cooldown.keys[scope], QR.cooldown[scope]); + } else { + return $["delete"](QR.cooldown.keys[scope]); } }, count: function() { - var cooldown, cooldowns, elapsed, hasFile, isReply, maxTimer, now, post, seconds, start, type, types, update, _ref; - if (!Object.keys(QR.cooldown.cooldowns).length) { - $["delete"]("cooldown." + g.BOARD); - delete QR.cooldown.isCounting; - delete QR.cooldown.seconds; - QR.status(); - return; - } - clearTimeout(QR.cooldown.timeout); - QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND); + var cooldown, elapsed, key, maxDelay, now, save, scope, seconds, start, suffix, threadID, type, update, _ref, _ref1, _ref2; now = Date.now(); - post = QR.posts[0]; - isReply = post.thread !== 'new'; - hasFile = !!post.file; - seconds = null; - _ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns; - for (start in cooldowns) { - cooldown = cooldowns[start]; - start = +start; - if ('delay' in cooldown) { - if (cooldown.delay) { - seconds = Math.max(seconds, cooldown.delay--); - } else { - seconds = Math.max(seconds, 0); - QR.cooldown.unset(start); - } - continue; - } - if (isReply === cooldown.isReply) { + _ref = QR.cooldown.categorize(QR.posts[0]), type = _ref.type, threadID = _ref.threadID; + seconds = 0; + _ref1 = QR.cooldown.keys; + for (scope in _ref1) { + key = _ref1[scope]; + $.forceSync(key); + save = false; + _ref2 = QR.cooldown[scope]; + for (start in _ref2) { + cooldown = _ref2[start]; + start = +start; elapsed = Math.floor((now - start) / $.SECOND); if (elapsed < 0) { - QR.cooldown.unset(start); + delete QR.cooldown[scope][start]; + save = true; continue; } - type = !isReply ? 'thread' : hasFile ? 'image' : 'reply'; - maxTimer = Math.max(types[type] || 0, types[type + '_intra'] || 0); - if (!((start <= now && now <= start + maxTimer * $.SECOND))) { - QR.cooldown.unset(start); + if (cooldown.delay != null) { + if (cooldown.delay <= elapsed) { + delete QR.cooldown[scope][start]; + save = true; + } else if (cooldown.type === type && cooldown.threadID === threadID) { + seconds = Math.max(seconds, cooldown.delay - elapsed); + } + continue; } - if (isReply && +post.thread === cooldown.threadID) { - type += '_intra'; + maxDelay = cooldown.threadID !== cooldown.postID ? QR.cooldown.maxDelay : QR.cooldown.delays[scope === 'global' ? 'thread_global' : 'thread']; + if (maxDelay <= elapsed) { + delete QR.cooldown[scope][start]; + save = true; + continue; } - seconds = Math.max(seconds, types[type] - elapsed); + if ((type === 'thread') === (cooldown.threadID === cooldown.postID)) { + suffix = scope === 'global' ? '_global' : type !== 'thread' && threadID === cooldown.threadID ? '_intra' : ''; + seconds = Math.max(seconds, QR.cooldown.delays[type + suffix] - elapsed); + } + } + if (save) { + QR.cooldown.save(scope); } } - update = seconds !== null || !!QR.cooldown.seconds; + if (Object.keys(QR.cooldown.local).length + Object.keys(QR.cooldown.global).length) { + clearTimeout(QR.cooldown.timeout); + QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND); + } else { + delete QR.cooldown.isCounting; + } + update = seconds !== QR.cooldown.seconds; QR.cooldown.seconds = seconds; if (update) { QR.status(); @@ -10187,6 +10242,7 @@ $.on(link, 'click', DeleteLink["delete"]); } else { if (resDoc.title === 'Updating index...') { + QR.cooldown["delete"](post); (post.origin || post).kill(fileOnly); } s = 'Deleted'; @@ -13405,7 +13461,7 @@ className: 'dialog' }); $.extend(dialog, { - innerHTML: "
" + innerHTML: "
" }); $.on($('.export', Settings.dialog), 'click', Settings["export"]); $.on($('.import', Settings.dialog), 'click', Settings["import"]); diff --git a/builds/4chan-X.crx b/builds/4chan-X.crx index 5167dd71e..8fa901927 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 829ffe14e..a64e82089 100644 --- a/builds/4chan-X.meta.js +++ b/builds/4chan-X.meta.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X -// @version 1.9.5.3 +// @version 1.9.6.0 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X diff --git a/builds/4chan-X.user.js b/builds/4chan-X.user.js index d31d484c8..4206ee303 100644 --- a/builds/4chan-X.user.js +++ b/builds/4chan-X.user.js @@ -1,7 +1,7 @@ // Generated by CoffeeScript // ==UserScript== // @name 4chan X -// @version 1.9.5.3 +// @version 1.9.6.0 // @minGMVer 1.14 // @minFFVer 26 // @namespace 4chan-X @@ -24,7 +24,7 @@ // ==/UserScript== /* -* 4chan X - Version 1.9.5.3 +* 4chan X - Version 1.9.6.0 * * Licensed under the MIT license. * https://github.com/ccd0/4chan-x/blob/master/LICENSE @@ -380,7 +380,7 @@ doc = d.documentElement; g = { - VERSION: '1.9.5.3', + VERSION: '1.9.6.0', NAMESPACE: '4chan X.', NAME: '4chan X', FAQ: 'https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions', @@ -797,8 +797,13 @@ var cb, key, newValue; key = _arg.key, newValue = _arg.newValue; if (cb = $.syncing[key]) { - oldValue[key] = newValue; - return cb(JSON.parse(newValue), key); + if (newValue != null) { + oldValue[key] = newValue; + return cb(JSON.parse(newValue), key); + } else { + delete oldValue[key]; + return cb(void 0, key); + } } }; $.on(window, 'storage', onChange); @@ -4659,7 +4664,7 @@ Filter = { filters: {}, init: function() { - var boards, err, filter, hl, key, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; + var boards, err, filter, hl, key, line, op, regexp, stub, top, _i, _len, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; if (g.VIEW === 'catalog' || !Conf['Filter']) { return; } @@ -4670,14 +4675,14 @@ this.filters[key] = []; _ref = Conf[key].split('\n'); for (_i = 0, _len = _ref.length; _i < _len; _i++) { - filter = _ref[_i]; - if (filter[0] === '#') { + line = _ref[_i]; + if (line[0] === '#') { continue; } - if (!(regexp = filter.match(/\/(.+)\/(\w*)/))) { + if (!(regexp = line.match(/\/(.+)\/(\w*)/))) { continue; } - filter = filter.replace(regexp[0], ''); + filter = line.replace(regexp[0], ''); boards = ((_ref1 = filter.match(/boards:([^;]+)/)) != null ? _ref1[1].toLowerCase() : void 0) || 'global'; if (boards !== 'global' && (_ref2 = g.BOARD.ID, __indexOf.call(boards.split(','), _ref2) < 0)) { continue; @@ -4689,7 +4694,7 @@ regexp = RegExp(regexp[1], regexp[2]); } catch (_error) { err = _error; - new Notice('warning', err.message, 60); + new Notice('warning', [$.tn("Invalid " + key + " filter:"), $.el('br'), $.tn(line), $.el('br'), $.tn(err.message)], 60); continue; } } @@ -5288,16 +5293,64 @@ ThreadHiding = { init: function() { - if (g.VIEW !== 'index' || !Conf['Thread Hiding Buttons'] && !Conf['Thread Hiding Link'] && !Conf['JSON Navigation']) { + if (g.VIEW === 'thread' || !Conf['Thread Hiding Buttons'] && !Conf['Thread Hiding Link'] && !Conf['JSON Navigation']) { return; } this.db = new DataBoard('hiddenThreads'); - this.syncCatalog(); + if (g.VIEW === 'catalog') { + return this.catalogWatch(); + } + this.catalogSet(g.BOARD); return Thread.callbacks.push({ name: 'Thread Hiding', cb: this.node }); }, + catalogSet: function(board) { + var hiddenThreads, threadID; + hiddenThreads = ThreadHiding.db.get({ + boardID: board.ID, + defaultValue: {} + }); + for (threadID in hiddenThreads) { + hiddenThreads[threadID] = true; + } + return localStorage.setItem("4chan-hide-t-" + board, JSON.stringify(hiddenThreads)); + }, + catalogWatch: function() { + this.hiddenThreads = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; + return $.ready(function() { + return new MutationObserver(ThreadHiding.catalogSave).observe($.id('threads'), { + attributes: true, + subtree: true, + attributeFilter: ['style'] + }); + }); + }, + catalogSave: function() { + var hiddenThreads2, threadID; + hiddenThreads2 = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; + for (threadID in hiddenThreads2) { + if (!(threadID in ThreadHiding.hiddenThreads)) { + ThreadHiding.db.set({ + boardID: g.BOARD.ID, + threadID: threadID, + val: { + makeStub: Conf['Stubs'] + } + }); + } + } + for (threadID in ThreadHiding.hiddenThreads) { + if (!(threadID in hiddenThreads2)) { + ThreadHiding.db["delete"]({ + boardID: g.BOARD.ID, + threadID: threadID + }); + } + } + return ThreadHiding.hiddenThreads = hiddenThreads2; + }, node: function() { var data; if (data = ThreadHiding.db.get({ @@ -5321,56 +5374,6 @@ } } }, - syncCatalog: function() { - var hiddenThreads, hiddenThreadsOnCatalog, threadID; - hiddenThreads = ThreadHiding.db.get({ - boardID: g.BOARD.ID, - defaultValue: {} - }); - hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; - for (threadID in hiddenThreadsOnCatalog) { - if (!(threadID in hiddenThreads)) { - hiddenThreads[threadID] = {}; - } - } - for (threadID in hiddenThreads) { - if (!(threadID in hiddenThreadsOnCatalog)) { - delete hiddenThreads[threadID]; - } - } - if ((ThreadHiding.db.data.lastChecked || 0) > Date.now() - $.MINUTE) { - ThreadHiding.cleanCatalog(hiddenThreadsOnCatalog); - } - return ThreadHiding.db.set({ - boardID: g.BOARD.ID, - val: hiddenThreads - }); - }, - cleanCatalog: function(hiddenThreadsOnCatalog) { - return $.cache("//a.4cdn.org/" + g.BOARD + "/threads.json", function() { - var page, thread, threads, _i, _j, _len, _len1, _ref, _ref1; - if (this.status !== 200) { - return; - } - threads = {}; - _ref = this.response; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - page = _ref[_i]; - _ref1 = page.threads; - for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { - thread = _ref1[_j]; - if (thread.no in hiddenThreadsOnCatalog) { - threads[thread.no] = hiddenThreadsOnCatalog[thread.no]; - } - } - } - if (Object.keys(threads).length) { - return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(threads)); - } else { - return localStorage.removeItem("4chan-hide-t-" + g.BOARD); - } - }); - }, menu: { init: function() { var apply, div, hideStubLink, makeStub; @@ -5501,8 +5504,6 @@ return $.prepend(root, thread.stub); }, saveHiddenState: function(thread, makeStub) { - var hiddenThreadsOnCatalog; - hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; if (thread.isHidden) { ThreadHiding.db.set({ boardID: thread.board.ID, @@ -5511,15 +5512,13 @@ makeStub: makeStub } }); - hiddenThreadsOnCatalog[thread] = true; } else { ThreadHiding.db["delete"]({ boardID: thread.board.ID, threadID: thread.ID }); - delete hiddenThreadsOnCatalog[thread]; } - return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(hiddenThreadsOnCatalog)); + return ThreadHiding.catalogSet(thread.board); }, toggle: function(thread) { if (!(thread instanceof Thread)) { @@ -7145,14 +7144,10 @@ err = 'This CAPTCHA is no longer valid because it has expired.'; } QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : err === 'Connection error with sys.4chan.org.' ? true : false; - QR.cooldown.set({ - delay: 2 - }); - } else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i))) { + QR.cooldown.addDelay(post, 2); + } else if (err.textContent && (m = err.textContent.match(/wait\s+(\d+)\s+second/i)) && !/duplicate/i.test(err.textContent)) { QR.cooldown.auto = QR.captcha.isEnabled ? !!QR.captcha.captchas.length : true; - QR.cooldown.set({ - delay: m[1] - }); + QR.cooldown.addDelay(post, +m[1]); } else { QR.cooldown.auto = false; } @@ -7213,12 +7208,7 @@ } post.rm(); } - QR.cooldown.set({ - req: req, - post: post, - isReply: isReply, - threadID: threadID - }); + QR.cooldown.add(req.uploadEndTime, threadID, postID); URL = threadID === postID ? "" + window.location.origin + "/" + g.BOARD + "/thread/" + threadID : g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab'] ? "" + window.location.origin + "/" + g.BOARD + "/thread/" + threadID + "#p" + postID : void 0; if (URL) { if (Conf['Open Post in New Tab']) { @@ -7455,119 +7445,184 @@ }; QR.cooldown = { + seconds: 0, init: function() { - var key, setTimers, type; + var delay, items, key, keys, scope, setTimers, type, _ref, _results; if (!Conf['Cooldown']) { return; } setTimers = (function(_this) { return function(e) { - return QR.cooldown.types = e.detail; + return QR.cooldown.delays = e.detail; }; })(this); $.on(window, 'cooldown:timers', setTimers); $.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))'); $.off(window, 'cooldown:timers', setTimers); - for (type in QR.cooldown.types) { - QR.cooldown.types[type] = +QR.cooldown.types[type]; + QR.cooldown.maxDelay = 0; + _ref = QR.cooldown.delays; + for (type in _ref) { + delay = _ref[type]; + if (type !== 'thread') { + QR.cooldown.maxDelay = Math.max(QR.cooldown.maxDelay, delay); + } } - key = "cooldown." + g.BOARD; - $.get(key, {}, function(item) { - QR.cooldown.cooldowns = item[key]; + QR.cooldown.delays['thread_global'] = 300; + keys = QR.cooldown.keys = { + local: "cooldown." + g.BOARD, + global: 'cooldown.global' + }; + items = {}; + for (scope in keys) { + key = keys[scope]; + items[key] = {}; + } + $.get(items, function(items) { + for (scope in keys) { + key = keys[scope]; + QR.cooldown[scope] = items[key]; + } return QR.cooldown.start(); }); - return $.sync(key, QR.cooldown.sync); + _results = []; + for (scope in keys) { + key = keys[scope]; + _results.push($.sync(key, QR.cooldown.sync(scope))); + } + return _results; }, start: function() { - if (QR.cooldown.isCounting || !Object.keys(QR.cooldown.cooldowns).length) { + if (QR.cooldown.isCounting || Object.keys(QR.cooldown.local).length + Object.keys(QR.cooldown.global).length === 0) { return; } QR.cooldown.isCounting = true; return QR.cooldown.count(); }, - sync: function(cooldowns) { - var id; - for (id in cooldowns) { - QR.cooldown.cooldowns[id] = cooldowns[id]; - } - return QR.cooldown.start(); + sync: function(scope) { + return function(cooldowns) { + QR.cooldown[scope] = cooldowns || {}; + return QR.cooldown.start(); + }; }, - set: function(data) { - var cooldown, delay, isReply, post, req, start, threadID; + add: function(start, threadID, postID) { + var boardID; if (!Conf['Cooldown']) { return; } - req = data.req, post = data.post, isReply = data.isReply, threadID = data.threadID, delay = data.delay; - start = req ? req.uploadEndTime : Date.now(); - if (delay) { - cooldown = { - delay: delay - }; - } else { - cooldown = { - isReply: isReply, - threadID: threadID - }; + boardID = g.BOARD.ID; + QR.cooldown.set('local', start, { + threadID: threadID, + postID: postID + }); + if (threadID === postID) { + QR.cooldown.set('global', start, { + boardID: boardID, + threadID: threadID, + postID: postID + }); } - QR.cooldown.cooldowns[start] = cooldown; - $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns); return QR.cooldown.start(); }, - unset: function(id) { - delete QR.cooldown.cooldowns[id]; - if (Object.keys(QR.cooldown.cooldowns).length) { - return $.set("cooldown." + g.BOARD, QR.cooldown.cooldowns); + addDelay: function(post, delay) { + var cooldown; + if (!Conf['Cooldown']) { + return; + } + cooldown = QR.cooldown.categorize(post); + cooldown.delay = delay; + QR.cooldown.set('local', Date.now(), cooldown); + return QR.cooldown.start(); + }, + "delete": function(post) { + var cooldown, id, _ref; + if (!(Conf['Cooldown'] && g.BOARD.ID === post.board.ID)) { + return; + } + $.forceSync(QR.cooldown.keys.local); + _ref = QR.cooldown.local; + for (id in _ref) { + cooldown = _ref[id]; + if ((cooldown.delay == null) && cooldown.threadID === post.thread.ID && cooldown.postID === post.ID) { + delete QR.cooldown.local[id]; + } + } + return QR.cooldown.save('local'); + }, + categorize: function(post) { + if (post.thread === 'new') { + return { + type: 'thread' + }; } else { - return $["delete"]("cooldown." + g.BOARD); + return { + type: !!post.file ? 'image' : 'reply', + threadID: +post.thread + }; + } + }, + set: function(scope, id, value) { + $.forceSync(QR.cooldown.keys[scope]); + QR.cooldown[scope][id] = value; + return $.set(QR.cooldown.keys[scope], QR.cooldown[scope]); + }, + save: function(scope) { + if (Object.keys(QR.cooldown[scope]).length) { + return $.set(QR.cooldown.keys[scope], QR.cooldown[scope]); + } else { + return $["delete"](QR.cooldown.keys[scope]); } }, count: function() { - var cooldown, cooldowns, elapsed, hasFile, isReply, maxTimer, now, post, seconds, start, type, types, update, _ref; - if (!Object.keys(QR.cooldown.cooldowns).length) { - $["delete"]("cooldown." + g.BOARD); - delete QR.cooldown.isCounting; - delete QR.cooldown.seconds; - QR.status(); - return; - } - clearTimeout(QR.cooldown.timeout); - QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND); + var cooldown, elapsed, key, maxDelay, now, save, scope, seconds, start, suffix, threadID, type, update, _ref, _ref1, _ref2; now = Date.now(); - post = QR.posts[0]; - isReply = post.thread !== 'new'; - hasFile = !!post.file; - seconds = null; - _ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns; - for (start in cooldowns) { - cooldown = cooldowns[start]; - start = +start; - if ('delay' in cooldown) { - if (cooldown.delay) { - seconds = Math.max(seconds, cooldown.delay--); - } else { - seconds = Math.max(seconds, 0); - QR.cooldown.unset(start); - } - continue; - } - if (isReply === cooldown.isReply) { + _ref = QR.cooldown.categorize(QR.posts[0]), type = _ref.type, threadID = _ref.threadID; + seconds = 0; + _ref1 = QR.cooldown.keys; + for (scope in _ref1) { + key = _ref1[scope]; + $.forceSync(key); + save = false; + _ref2 = QR.cooldown[scope]; + for (start in _ref2) { + cooldown = _ref2[start]; + start = +start; elapsed = Math.floor((now - start) / $.SECOND); if (elapsed < 0) { - QR.cooldown.unset(start); + delete QR.cooldown[scope][start]; + save = true; continue; } - type = !isReply ? 'thread' : hasFile ? 'image' : 'reply'; - maxTimer = Math.max(types[type] || 0, types[type + '_intra'] || 0); - if (!((start <= now && now <= start + maxTimer * $.SECOND))) { - QR.cooldown.unset(start); + if (cooldown.delay != null) { + if (cooldown.delay <= elapsed) { + delete QR.cooldown[scope][start]; + save = true; + } else if (cooldown.type === type && cooldown.threadID === threadID) { + seconds = Math.max(seconds, cooldown.delay - elapsed); + } + continue; } - if (isReply && +post.thread === cooldown.threadID) { - type += '_intra'; + maxDelay = cooldown.threadID !== cooldown.postID ? QR.cooldown.maxDelay : QR.cooldown.delays[scope === 'global' ? 'thread_global' : 'thread']; + if (maxDelay <= elapsed) { + delete QR.cooldown[scope][start]; + save = true; + continue; } - seconds = Math.max(seconds, types[type] - elapsed); + if ((type === 'thread') === (cooldown.threadID === cooldown.postID)) { + suffix = scope === 'global' ? '_global' : type !== 'thread' && threadID === cooldown.threadID ? '_intra' : ''; + seconds = Math.max(seconds, QR.cooldown.delays[type + suffix] - elapsed); + } + } + if (save) { + QR.cooldown.save(scope); } } - update = seconds !== null || !!QR.cooldown.seconds; + if (Object.keys(QR.cooldown.local).length + Object.keys(QR.cooldown.global).length) { + clearTimeout(QR.cooldown.timeout); + QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND); + } else { + delete QR.cooldown.isCounting; + } + update = seconds !== QR.cooldown.seconds; QR.cooldown.seconds = seconds; if (update) { QR.status(); @@ -10188,6 +10243,7 @@ $.on(link, 'click', DeleteLink["delete"]); } else { if (resDoc.title === 'Updating index...') { + QR.cooldown["delete"](post); (post.origin || post).kill(fileOnly); } s = 'Deleted'; @@ -13406,7 +13462,7 @@ className: 'dialog' }); $.extend(dialog, { - innerHTML: "
" + innerHTML: "
" }); $.on($('.export', Settings.dialog), 'click', Settings["export"]); $.on($('.import', Settings.dialog), 'click', Settings["import"]); diff --git a/builds/4chan-X.zip b/builds/4chan-X.zip index dc15f6053..091e3f9ef 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 e20e92719..603c2a94f 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 3288b3711..506efcdf0 100644 --- a/builds/updates.xml +++ b/builds/updates.xml @@ -1,7 +1,7 @@ - + diff --git a/package.json b/package.json index 7070820f2..578a609a7 100755 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "Cross-browser userscript for maximum lurking on 4chan.", "meta": { "name": "4chan X", - "version": "1.9.5.3", + "version": "1.9.6.0", "repo": "https://github.com/ccd0/4chan-x/", "page": "https://github.com/ccd0/4chan-x", "downloads": "https://ccd0.github.io/4chan-x/builds/",