From e4dde07c1164bd0c6c6aa242047f653b92fa90f9 Mon Sep 17 00:00:00 2001 From: Zixaphir Date: Fri, 9 Jan 2015 23:31:22 -0700 Subject: [PATCH] The rest of the QR --- builds/appchan-x.user.js | 259 ++++++++++++++++++++------------- builds/crx/script.js | 245 ++++++++++++++++++++----------- src/Posting/QR.cooldown.coffee | 184 ++++++++++++++--------- src/Posting/QR.persona.coffee | 3 +- src/Posting/QR.post.coffee | 20 ++- 5 files changed, 440 insertions(+), 271 deletions(-) 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