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/",