diff --git a/builds/appchan-x.user.js b/builds/appchan-x.user.js
index eff44882e..9cf4abd8a 100644
--- a/builds/appchan-x.user.js
+++ b/builds/appchan-x.user.js
@@ -10053,119 +10053,183 @@
};
QR.cooldown = {
+ seconds: 0,
init: function() {
- var key, setTimers, type;
+ var delay, items, key, keys, m, scope, type, _ref, _results;
if (!Conf['Cooldown']) {
return;
}
- setTimers = (function(_this) {
- return function(e) {
- return QR.cooldown.types = 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.delays = (m = Get.scriptData().match(/\bcooldowns *= *({[^}]+})/)) ? JSON.parse(m[1]) : {
+ thread: 0,
+ reply: 0,
+ image: 0,
+ reply_intra: 0,
+ image_intra: 0
+ };
+ 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();
@@ -10244,9 +10308,9 @@
}
},
getPassword: function() {
- var input, m;
+ var input, m, _ref;
if (!QR.persona.pwd) {
- QR.persona.pwd = (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : (input = $.id('postPassword')) ? input.value : $.id('delPassword').value;
+ QR.persona.pwd = (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : (input = $.id('postPassword')) ? input.value : ((_ref = $.id('delPassword')) != null ? _ref.value : void 0) || '';
}
return QR.persona.pwd;
},
@@ -10264,7 +10328,6 @@
persona = {
name: post.name,
email: /^sage$/.test(post.email) ? persona.email : post.email,
- sub: Conf['Remember Subject'] ? post.sub : void 0,
flag: post.flag
};
return $.set('QR.persona', persona);
@@ -10275,12 +10338,14 @@
QR.post = (function() {
function _Class(select) {
this.select = __bind(this.select, this);
- var el, elm, event, prev, _i, _j, _len, _len1, _ref, _ref1;
+ var el, event, prev, _i, _len, _ref;
el = $.el('a', {
className: 'qr-preview',
draggable: true,
- href: 'javascript:;',
- innerHTML: '\uf057'
+ href: 'javascript:;'
+ });
+ $.extend(el, {
+ innerHTML: ""
});
this.nodes = {
el: el,
@@ -10289,12 +10354,6 @@
spoiler: $('input', el),
span: el.lastChild
};
- _ref = $$('*', el);
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- elm = _ref[_i];
- $.on(elm, 'blur', QR.focusout);
- $.on(elm, 'focus', QR.focusin);
- }
$.on(el, 'click', this.select);
$.on(this.nodes.rm, 'click', (function(_this) {
return function(e) {
@@ -10316,9 +10375,9 @@
};
})(this));
$.add(QR.nodes.dumpList, el);
- _ref1 = ['dragStart', 'dragEnter', 'dragLeave', 'dragOver', 'dragEnd', 'drop'];
- for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) {
- event = _ref1[_j];
+ _ref = ['dragStart', 'dragEnter', 'dragLeave', 'dragOver', 'dragEnd', 'drop'];
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ event = _ref[_i];
$.on(el, event.toLowerCase(), this[event]);
}
this.thread = g.VIEW === 'thread' ? g.THREADID : 'new';
@@ -10343,7 +10402,7 @@
}
this.unlock();
$.queueTask(function() {
- return QR.captcha.setup();
+ return QR.captcha.onNewPost();
});
}
@@ -10418,6 +10477,7 @@
node.value = this[name] || node.dataset["default"] || null;
}
QR.tripcodeHider.call(QR.nodes['name']);
+ (this.thread !== 'new' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread');
this.showFileData();
return QR.characterCount();
};
@@ -10432,6 +10492,7 @@
this[name] = input.value || input.dataset["default"] || null;
switch (name) {
case 'thread':
+ (this.thread !== 'new' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread');
return QR.status();
case 'com':
this.nodes.span.textContent = this.com;
@@ -10598,13 +10659,13 @@
};
_Class.prototype.updateFilename = function() {
- var title;
- title = "" + this.filename + " (" + this.filesize + ")\nCtrl/\u2318+click to edit filename. Shift+click to clear.";
- this.nodes.el.title = title;
+ var long;
+ long = "" + this.filename + " (" + this.filesize + ")\nCtrl/\u2318+click to edit filename. Shift+click to clear.";
+ this.nodes.el.title = long;
if (this !== QR.selected) {
return;
}
- return QR.nodes.fileContainer.title = title;
+ return QR.nodes.fileContainer.title = long;
};
_Class.prototype.showFileData = function() {
diff --git a/builds/crx/script.js b/builds/crx/script.js
index a519df541..53dd87c7a 100644
--- a/builds/crx/script.js
+++ b/builds/crx/script.js
@@ -10096,119 +10096,183 @@
};
QR.cooldown = {
+ seconds: 0,
init: function() {
- var key, setTimers, type;
+ var delay, items, key, keys, m, scope, type, _ref, _results;
if (!Conf['Cooldown']) {
return;
}
- setTimers = (function(_this) {
- return function(e) {
- return QR.cooldown.types = 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.delays = (m = Get.scriptData().match(/\bcooldowns *= *({[^}]+})/)) ? JSON.parse(m[1]) : {
+ thread: 0,
+ reply: 0,
+ image: 0,
+ reply_intra: 0,
+ image_intra: 0
+ };
+ 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();
@@ -10287,9 +10351,9 @@
}
},
getPassword: function() {
- var input, m;
+ var input, m, _ref;
if (!QR.persona.pwd) {
- QR.persona.pwd = (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : (input = $.id('postPassword')) ? input.value : $.id('delPassword').value;
+ QR.persona.pwd = (m = d.cookie.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : (input = $.id('postPassword')) ? input.value : ((_ref = $.id('delPassword')) != null ? _ref.value : void 0) || '';
}
return QR.persona.pwd;
},
@@ -10307,7 +10371,6 @@
persona = {
name: post.name,
email: /^sage$/.test(post.email) ? persona.email : post.email,
- sub: Conf['Remember Subject'] ? post.sub : void 0,
flag: post.flag
};
return $.set('QR.persona', persona);
@@ -10322,8 +10385,10 @@
el = $.el('a', {
className: 'qr-preview',
draggable: true,
- href: 'javascript:;',
- innerHTML: '\uf057'
+ href: 'javascript:;'
+ });
+ $.extend(el, {
+ innerHTML: ""
});
this.nodes = {
el: el,
@@ -10380,7 +10445,7 @@
}
this.unlock();
$.queueTask(function() {
- return QR.captcha.setup();
+ return QR.captcha.onNewPost();
});
}
@@ -10455,6 +10520,7 @@
node.value = this[name] || node.dataset["default"] || null;
}
QR.tripcodeHider.call(QR.nodes['name']);
+ (this.thread !== 'new' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread');
this.showFileData();
return QR.characterCount();
};
@@ -10469,6 +10535,7 @@
this[name] = input.value || input.dataset["default"] || null;
switch (name) {
case 'thread':
+ (this.thread !== 'new' ? $.addClass : $.rmClass)(QR.nodes.el, 'reply-to-thread');
return QR.status();
case 'com':
this.nodes.span.textContent = this.com;
@@ -10632,13 +10699,13 @@
};
_Class.prototype.updateFilename = function() {
- var title;
- title = "" + this.filename + " (" + this.filesize + ")\nCtrl/\u2318+click to edit filename. Shift+click to clear.";
- this.nodes.el.title = title;
+ var long;
+ long = "" + this.filename + " (" + this.filesize + ")\nCtrl/\u2318+click to edit filename. Shift+click to clear.";
+ this.nodes.el.title = long;
if (this !== QR.selected) {
return;
}
- return QR.nodes.fileContainer.title = title;
+ return QR.nodes.fileContainer.title = long;
};
_Class.prototype.showFileData = function() {
diff --git a/src/Posting/QR.cooldown.coffee b/src/Posting/QR.cooldown.coffee
index 7a988d501..3a92d701f 100644
--- a/src/Posting/QR.cooldown.coffee
+++ b/src/Posting/QR.cooldown.coffee
@@ -1,99 +1,143 @@
QR.cooldown =
+ seconds: 0
+
init: ->
return unless Conf['Cooldown']
- setTimers = (e) => QR.cooldown.types = e.detail
- $.on window, 'cooldown:timers', setTimers
- $.globalEval 'window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))'
- $.off window, 'cooldown:timers', setTimers
- for type of QR.cooldown.types
- QR.cooldown.types[type] = +QR.cooldown.types[type]
- key = "cooldown.#{g.BOARD}"
- $.get key, {}, (item) ->
- QR.cooldown.cooldowns = item[key]
+
+ # Read cooldown times
+ QR.cooldown.delays = if m = Get.scriptData().match /\bcooldowns *= *({[^}]+})/
+ JSON.parse m[1]
+ else
+ {thread: 0, reply: 0, image: 0, reply_intra: 0, image_intra: 0}
+
+ # The longest reply cooldown, for use in pruning old reply data
+ QR.cooldown.maxDelay = 0
+ for type, delay of QR.cooldown.delays when type isnt 'thread'
+ QR.cooldown.maxDelay = Math.max QR.cooldown.maxDelay, delay
+
+ # There is a 300 second global thread cooldown.
+ QR.cooldown.delays['thread_global'] = 300
+
+ # Retrieve recent posts and delays.
+ keys = QR.cooldown.keys =
+ local: "cooldown.#{g.BOARD}"
+ global: 'cooldown.global'
+ items = {}
+ items[key] = {} for scope, key of keys
+ $.get items, (items) ->
+ QR.cooldown[scope] = items[key] for scope, key of keys
QR.cooldown.start()
- $.sync key, QR.cooldown.sync
+ $.sync key, QR.cooldown.sync scope for scope, key of keys
+
start: ->
- return if QR.cooldown.isCounting or !Object.keys(QR.cooldown.cooldowns).length
+ return if QR.cooldown.isCounting or Object.keys(QR.cooldown.local).length + Object.keys(QR.cooldown.global).length is 0
QR.cooldown.isCounting = true
QR.cooldown.count()
- sync: (cooldowns) ->
- # Add each cooldowns, don't overwrite everything in case we
- # still need to prune one in the current tab to auto-post.
- for id of cooldowns
- QR.cooldown.cooldowns[id] = cooldowns[id]
+ sync: (scope) -> (cooldowns) ->
+ QR.cooldown[scope] = cooldowns or {}
QR.cooldown.start()
- set: (data) ->
+ add: (start, threadID, postID) ->
return unless Conf['Cooldown']
- {req, post, isReply, threadID, delay} = data
- start = if req then req.uploadEndTime else Date.now()
- if delay
- cooldown = {delay}
- else
- cooldown = {isReply, threadID}
- QR.cooldown.cooldowns[start] = cooldown
- $.set "cooldown.#{g.BOARD}", QR.cooldown.cooldowns
+ boardID = g.BOARD.ID
+ QR.cooldown.set 'local', start, {threadID, postID}
+ QR.cooldown.set 'global', start, {boardID, threadID, postID} if threadID is postID
QR.cooldown.start()
- unset: (id) ->
- delete QR.cooldown.cooldowns[id]
- if Object.keys(QR.cooldown.cooldowns).length
- $.set "cooldown.#{g.BOARD}", QR.cooldown.cooldowns
+ addDelay: (post, delay) ->
+ return unless Conf['Cooldown']
+ cooldown = QR.cooldown.categorize post
+ cooldown.delay = delay
+ QR.cooldown.set 'local', Date.now(), cooldown
+ QR.cooldown.start()
+
+ delete: (post) ->
+ return unless Conf['Cooldown'] and g.BOARD.ID is post.board.ID
+ $.forceSync QR.cooldown.keys.local
+ for id, cooldown of QR.cooldown.local
+ if !cooldown.delay? and cooldown.threadID is post.thread.ID and cooldown.postID is post.ID
+ delete QR.cooldown.local[id]
+ QR.cooldown.save 'local'
+
+ categorize: (post) ->
+ if post.thread is 'new'
+ type: 'thread'
else
- $.delete "cooldown.#{g.BOARD}"
+ type: if !!post.file then 'image' else 'reply'
+ threadID: +post.thread
+
+ set: (scope, id, value) ->
+ $.forceSync QR.cooldown.keys[scope]
+ QR.cooldown[scope][id] = value
+ $.set QR.cooldown.keys[scope], QR.cooldown[scope]
+
+ save: (scope) ->
+ if Object.keys(QR.cooldown[scope]).length
+ $.set QR.cooldown.keys[scope], QR.cooldown[scope]
+ else
+ $.delete QR.cooldown.keys[scope]
count: ->
- unless Object.keys(QR.cooldown.cooldowns).length
- $.delete "cooldown.#{g.BOARD}"
- delete QR.cooldown.isCounting
- delete QR.cooldown.seconds
- QR.status()
- return
+ now = Date.now()
+ {type, threadID} = QR.cooldown.categorize QR.posts[0]
+ seconds = 0
- clearTimeout QR.cooldown.timeout
- QR.cooldown.timeout = setTimeout QR.cooldown.count, $.SECOND
+ for scope, key of QR.cooldown.keys
+ $.forceSync key
+ save = false
- now = Date.now()
- post = QR.posts[0]
- isReply = post.thread isnt 'new'
- hasFile = !!post.file
- seconds = null
- {types, cooldowns} = QR.cooldown
-
- for start, cooldown of cooldowns
- start = +start
- if 'delay' of cooldown
- if cooldown.delay
- seconds = Math.max seconds, cooldown.delay--
- else
- seconds = Math.max seconds, 0
- QR.cooldown.unset start
- continue
-
- if isReply is cooldown.isReply
- # Only cooldowns relevant to this post can set the seconds variable:
- # reply cooldown with a reply, thread cooldown with a thread
+ for start, cooldown of QR.cooldown[scope]
+ start = +start
elapsed = (now - start) // $.SECOND
if elapsed < 0 # clock changed since then?
- QR.cooldown.unset start
+ delete QR.cooldown[scope][start]
+ save = true
continue
- type = unless isReply
- 'thread'
- else if hasFile
- 'image'
+
+ # Explicit delays from error messages
+ if cooldown.delay?
+ if cooldown.delay <= elapsed
+ delete QR.cooldown[scope][start]
+ save = true
+ else if cooldown.type is type and cooldown.threadID is threadID
+ # Delays only apply to the given post type and thread.
+ seconds = Math.max seconds, cooldown.delay - elapsed
+ continue
+
+ # Clean up expired cooldowns
+ maxDelay = if cooldown.threadID isnt cooldown.postID
+ QR.cooldown.maxDelay
else
- 'reply'
- maxTimer = Math.max types[type] or 0, types[type + '_intra'] or 0
- unless start <= now <= start + maxTimer * $.SECOND
- QR.cooldown.unset start
- type += '_intra' if isReply and +post.thread is cooldown.threadID
- seconds = Math.max seconds, types[type] - elapsed
+ QR.cooldown.delays[if scope is 'global' then 'thread_global' else 'thread']
+ if maxDelay <= elapsed
+ delete QR.cooldown[scope][start]
+ save = true
+ continue
+
+ if (type is 'thread') is (cooldown.threadID is cooldown.postID)
+ # Only cooldowns relevant to this post can set the seconds variable:
+ # reply cooldown with a reply, thread cooldown with a thread
+ suffix = if scope is 'global'
+ '_global'
+ else if type isnt 'thread' and threadID is cooldown.threadID
+ '_intra'
+ else
+ ''
+ seconds = Math.max seconds, QR.cooldown.delays[type + suffix] - elapsed
+
+ QR.cooldown.save scope if save
+
+ 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 the status when we change posting type.
# Don't get stuck at some random number.
# Don't interfere with progress status updates.
- update = seconds isnt null or !!QR.cooldown.seconds
+ update = seconds isnt QR.cooldown.seconds
QR.cooldown.seconds = seconds
QR.status() if update
QR.submit() if seconds is 0 and QR.cooldown.auto and !QR.req
diff --git a/src/Posting/QR.persona.coffee b/src/Posting/QR.persona.coffee
index 670595f93..fe1eb9863 100644
--- a/src/Posting/QR.persona.coffee
+++ b/src/Posting/QR.persona.coffee
@@ -56,7 +56,7 @@ QR.persona =
# If we're in a closed thread, #postPassword isn't available.
# And since #delPassword.value is only filled on window.onload
# we'd rather use #postPassword when we can.
- $.id('delPassword').value
+ $.id('delPassword')?.value or ''
return QR.persona.pwd
get: (cb) ->
@@ -68,6 +68,5 @@ QR.persona =
persona =
name: post.name
email: if /^sage$/.test post.email then persona.email else post.email
- sub: if Conf['Remember Subject'] then post.sub else undefined
flag: post.flag
$.set 'QR.persona', persona
diff --git a/src/Posting/QR.post.coffee b/src/Posting/QR.post.coffee
index f3f0d3d38..0cc1c547d 100644
--- a/src/Posting/QR.post.coffee
+++ b/src/Posting/QR.post.coffee
@@ -4,7 +4,7 @@ QR.post = class
className: 'qr-preview'
draggable: true
href: 'javascript:;'
- innerHTML: '\uf057'
+ $.extend el, <%= html('\uf057') %>
@nodes =
el: el
@@ -13,12 +13,6 @@ QR.post = class
spoiler: $ 'input', el
span: el.lastChild
- <% if (type === 'userscript') { %>
- # XXX Firefox lacks focusin/focusout support.
- for elm in $$ '*', el
- $.on elm, 'blur', QR.focusout
- $.on elm, 'focus', QR.focusin
- <% } %>
$.on el, 'click', @select
$.on @nodes.rm, 'click', (e) => e.stopPropagation(); @rm()
$.on @nodes.label, 'click', (e) => e.stopPropagation()
@@ -72,7 +66,7 @@ QR.post = class
@select() if select
@unlock()
# Post count temporarily off by 1 when called from QR.post.rm
- $.queueTask -> QR.captcha.setup()
+ $.queueTask -> QR.captcha.onNewPost()
rm: ->
@delete()
@@ -123,6 +117,9 @@ QR.post = class
node.value = @[name] or node.dataset.default or null
QR.tripcodeHider.call QR.nodes['name']
+
+ (if @thread isnt 'new' then $.addClass else $.rmClass) QR.nodes.el, 'reply-to-thread'
+
@showFileData()
QR.characterCount()
@@ -134,6 +131,7 @@ QR.post = class
@[name] = input.value or input.dataset.default or null
switch name
when 'thread'
+ (if @thread isnt 'new' then $.addClass else $.rmClass) QR.nodes.el, 'reply-to-thread'
QR.status()
when 'com'
@nodes.span.textContent = @com
@@ -271,10 +269,10 @@ QR.post = class
URL.revokeObjectURL @URL
updateFilename: ->
- title = "#{@filename} (#{@filesize})\nCtrl/\u2318+click to edit filename. Shift+click to clear."
- @nodes.el.title = title
+ long = "#{@filename} (#{@filesize})\nCtrl/\u2318+click to edit filename. Shift+click to clear."
+ @nodes.el.title = long
return unless @ is QR.selected
- QR.nodes.fileContainer.title = title
+ QR.nodes.fileContainer.title = long
showFileData: ->
if @file