Merge branch 'v3'

Conflicts:
	CHANGELOG.md
	Gruntfile.coffee
	LICENSE
	builds/4chan-X.meta.js
	builds/4chan-X.user.js
	builds/crx/manifest.json
	builds/crx/script.js
	latest.js
	package.json
	src/General/css/style.css
	src/Posting/QuickReply.coffee
This commit is contained in:
Zixaphir 2013-09-24 14:46:24 -07:00
commit 7812001d96
23 changed files with 1833 additions and 582 deletions

View File

@ -1,3 +1,27 @@
**MayhemYDG**:
- Tiny posting cooldown adjustment:
* You can post an image reply immediately after a non-image reply.
- Update posting cooldown timers to match 4chan settings:
* Cooldown may vary between inter-thread and intra-thread replies.
* Cooldown may vary when posting a file or not.
* Cooldown does not take sageing into account anymore.
* Timers vary across boards.
- Updated post and deletion cooldown timers to match 4chan changes: they are now twice longer.
- Added support for the flag selector on /pol/.
**seaweedchan**:
- Fix thread updater bug introduced in last version
- Just some small fixes.
**zixaphir**:
- Some changes to mascots
* Silhouette mascots are now generated dynamically with the silhouette filter
* Images are now compressed client side before being uploaded via the upload interface (this is only for mascots, not posts)
- Fix an issue with Linkifier linkifying replaced spoilers
- Fix an issue with "fit height" in the gallery on Chrome
- Small thread updater fixes
### v2.3.10
*2013-08-31*

View File

@ -1,22 +1,14 @@
module.exports = (grunt) ->
concatOptions =
process: Object.create(null, data:
get: -> grunt.config 'pkg'
enumerable: true
)
shellOptions =
stdout: true
stderr: true
failOnError: true
# Project configuration.
grunt.initConfig
pkg: grunt.file.readJSON 'package.json'
concat:
options: process: Object.create(null, data:
get: -> grunt.config 'pkg'
enumerable: true
)
coffee:
options: concatOptions
src: [
'src/General/Config.coffee'
'src/General/Globals.coffee'
@ -41,13 +33,11 @@ module.exports = (grunt) ->
dest: 'tmp-<%= pkg.type %>/script.coffee'
meta:
options: concatOptions
files:
'LICENSE': 'src/General/meta/banner.js',
'latest.js': 'src/General/meta/latest.js'
crx:
options: concatOptions
files:
'builds/crx/manifest.json': 'src/General/meta/manifest.json'
'builds/crx/script.js': [
@ -57,7 +47,6 @@ module.exports = (grunt) ->
'tmp-<%= pkg.type %>/script.js'
]
userscript:
options: concatOptions
files:
'builds/<%= pkg.name %>.meta.js': 'src/General/meta/metadata.js'
'builds/<%= pkg.name %>.user.js': [
@ -96,22 +85,23 @@ module.exports = (grunt) ->
push: false
shell:
options:
stdout: true
stderr: true
failOnError: true
commit:
options: shellOptions
command: [
'git commit -am "Release <%= pkg.meta.name %> v<%= pkg.version %>."'
'git tag -a <%= pkg.version %> -m "<%= pkg.meta.name %> v<%= pkg.version %>."'
'git tag -af stable -m "<%= pkg.meta.name %> v<%= pkg.version %>."'
].join ' && '
push:
options: shellOptions
command: 'git push origin --tags -f && git push origin --all'
watch:
options:
interrupt: true
all:
options:
interrupt: true
files: [
'Gruntfile.coffee'
'package.json'

View File

@ -1,5 +1,5 @@
/*
* appchan x - Version 2.3.10 - 2013-09-20
* appchan x - Version 2.3.10 - 2013-09-24
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE

View File

@ -20,7 +20,7 @@
// ==/UserScript==
/*
* appchan x - Version 2.3.10 - 2013-09-20
* appchan x - Version 2.3.10 - 2013-09-24
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@ -335,7 +335,7 @@
uniqueID: "# Filter a specific ID:\n#/Txhvk1Tl/",
tripcode: "# Filter any tripfag\n#/^!/",
capcode: "# Set a custom class for mods:\n#/Mod$/;highlight:mod;op:yes\n# Set a custom class for moot:\n#/Admin$/;highlight:moot;op:yes",
email: "# Filter any e-mails that are not `sage` on /a/ and /jp/:\n#/^(?!sage$)/;boards:a,jp",
email: "",
subject: "# Filter Generals on /v/:\n#/general/i;boards:v;op:only",
comment: "# Filter Stallman copypasta on /g/:\n#/what you\'re refer+ing to as linux/i;boards:g",
flag: '',
@ -3320,7 +3320,7 @@
this.nodes.comment.normalize();
bq = this.nodes.comment.cloneNode(true);
nodes = $$('.abbr, .capcodeReplies, .exif, b', bq);
nodes = $$('.abbr, .exif, b', bq);
i = 0;
while (node = nodes[i++]) {
$.rm(node);
@ -3352,7 +3352,7 @@
return;
}
this.nodes.quotelinks.push(quotelink);
if (this.isClone || !this.isReply && $.hasClass(quotelink.parentNode.parentNode, 'capcodeReplies')) {
if (this.isClone) {
return;
}
fullID = "" + match[1] + "." + match[2];
@ -4224,7 +4224,6 @@
date: data.now,
dateUTC: data.time,
comment: data.com,
capcodeReplies: data.capcode_replies,
isSticky: !!data.sticky,
isClosed: !!data.closed
};
@ -4252,9 +4251,9 @@
@license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
*/
var a, boardID, capcode, capcodeClass, capcodeReplies, capcodeStart, closed, comment, container, date, dateUTC, email, emailEnd, emailStart, ext, file, fileDims, fileHTML, fileInfo, fileSize, fileThumb, filename, flag, flagCode, flagName, href, imgSrc, isClosed, isOP, isSticky, name, postID, quote, shortFilename, spoilerRange, staticPath, sticky, subject, threadID, tripcode, uniqueID, userID, _i, _len, _ref;
var a, boardID, capcode, capcodeClass, capcodeStart, closed, comment, container, date, dateUTC, email, emailEnd, emailStart, ext, file, fileDims, fileHTML, fileInfo, fileSize, fileThumb, filename, flag, flagCode, flagName, href, imgSrc, isClosed, isOP, isSticky, name, postID, quote, shortFilename, spoilerRange, staticPath, sticky, subject, threadID, tripcode, uniqueID, userID, _i, _len, _ref;
postID = o.postID, threadID = o.threadID, boardID = o.boardID, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flagCode = o.flagCode, flagName = o.flagName, date = o.date, dateUTC = o.dateUTC, isSticky = o.isSticky, isClosed = o.isClosed, comment = o.comment, capcodeReplies = o.capcodeReplies, file = o.file;
postID = o.postID, threadID = o.threadID, boardID = o.boardID, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flagCode = o.flagCode, flagName = o.flagName, date = o.date, dateUTC = o.dateUTC, isSticky = o.isSticky, isClosed = o.isClosed, comment = o.comment, file = o.file;
isOP = postID === threadID;
staticPath = '//static.4chan.org/image/';
if (email) {
@ -4331,7 +4330,7 @@
container = $.el('div', {
id: "pc" + postID,
className: "postContainer " + (isOP ? 'op' : 'reply') + "Container",
innerHTML: "" + (isOP ? '' : "<div class=sideArrows id=sa" + postID + ">&gt;&gt;</div>") + "<div id=p" + postID + " class='post " + (isOP ? 'op' : 'reply') + (capcode === 'admin_highlight' ? ' highlightPost' : '') + "'><div class='postInfoM mobile' id=pim" + postID + "><span class='nameBlock" + capcodeClass + "'><span class=name>" + (name || '') + "</span>" + (tripcode + capcodeStart + capcode + userID + flag + sticky + closed) + "<br>" + subject + "</span><span class='dateTime postNum' data-utc=" + dateUTC + ">" + date + "<a href=" + ("/" + boardID + "/res/" + threadID + "#p" + postID) + ">No.</a><a href='" + (g.VIEW === 'thread' && g.THREADID === +threadID ? "javascript:quote(" + postID + ")" : "/" + boardID + "/res/" + threadID + "#q" + postID) + "'>" + postID + "</a></span></div>" + (isOP ? fileHTML : '') + "<div class='postInfo desktop' id=pi" + postID + "><input type=checkbox name=" + postID + " value=delete>" + subject + "&nbsp;<span class='nameBlock" + capcodeClass + "'>" + emailStart + "<span class=name>" + (name || '') + "</span>" + (tripcode + capcodeStart + emailEnd + capcode + userID + flag + sticky + closed) + "</span>" + " " + "<span class=dateTime data-utc=" + dateUTC + ">" + date + "</span>" + " " + "<span class='postNum desktop'><a href=" + ("/" + boardID + "/res/" + threadID + "#p" + postID) + " title='Highlight this post'>No.</a><a href='" + (g.VIEW === 'thread' && g.THREADID === +threadID ? "javascript:quote(" + postID + ")" : "/" + boardID + "/res/" + threadID + "#q" + postID) + "' title='Quote this post'>" + postID + "</a></span></div>" + (isOP ? '' : fileHTML) + "<blockquote class=postMessage id=m" + postID + ">" + (comment || '') + "</blockquote>" + " " + "</div>"
innerHTML: "" + (isOP ? '' : "<div class=sideArrows id=sa" + postID + ">&gt;&gt;</div>") + "<div id=p" + postID + " class='post " + (isOP ? 'op' : 'reply') + (capcode === 'admin_highlight' ? ' highlightPost' : '') + "'><div class='postInfoM mobile' id=pim" + postID + "><span class='nameBlock" + capcodeClass + "'><span class=name>" + (name || '') + "</span>" + (tripcode + capcodeStart + capcode + userID + flag + sticky + closed) + "<br>" + subject + "</span><span class='dateTime postNum' data-utc=" + dateUTC + ">" + date + "<a href=" + ("/" + boardID + "/res/" + threadID + "#p" + postID) + ">No.</a><a href='" + (g.VIEW === 'thread' && g.THREADID === +threadID ? "javascript:quote(" + postID + ")" : "/" + boardID + "/res/" + threadID + "#q" + postID) + "'>" + postID + "</a></span></div>" + (isOP ? fileHTML : '') + "<div class='postInfo desktop' id=pi" + postID + "><input type=checkbox name=" + postID + " value=delete>&nbsp;" + subject + "&nbsp;<span class='nameBlock" + capcodeClass + "'>" + emailStart + "<span class=name>" + (name || '') + "</span>" + (tripcode + capcodeStart + emailEnd + capcode + userID + flag + sticky + closed) + "</span>" + " " + "<span class=dateTime data-utc=" + dateUTC + ">" + date + "</span>" + " " + "<span class='postNum desktop'><a href=" + ("/" + boardID + "/res/" + threadID + "#p" + postID) + " title='Highlight this post'>No.</a><a href='" + (g.VIEW === 'thread' && g.THREADID === +threadID ? "javascript:quote(" + postID + ")" : "/" + boardID + "/res/" + threadID + "#q" + postID) + "' title='Quote this post'>" + postID + "</a></span></div>" + (isOP ? '' : fileHTML) + "<blockquote class=postMessage id=m" + postID + ">" + (comment || '') + "</blockquote>" + " " + "</div>"
});
_ref = $$('.quotelink', container);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
@ -4342,47 +4341,7 @@
}
quote.href = "/" + boardID + "/res/" + href;
}
Build.capcodeReplies({
boardID: boardID,
threadID: threadID,
root: container,
capcodeReplies: capcodeReplies
});
return container;
},
capcodeReplies: function(_arg) {
var array, boardID, bq, capcodeReplies, capcodeType, generateCapcodeReplies, html, root, threadID;
boardID = _arg.boardID, threadID = _arg.threadID, bq = _arg.bq, root = _arg.root, capcodeReplies = _arg.capcodeReplies;
if (!capcodeReplies) {
return;
}
generateCapcodeReplies = function(capcodeType, array) {
return "<span class=smaller><span class=bold>" + ((function() {
switch (capcodeType) {
case 'admin':
return 'Administrator';
case 'mod':
return 'Moderator';
case 'developer':
return 'Developer';
}
})()) + " Repl" + (array.length > 1 ? 'ies' : 'y') + ":</span> " + (array.map(function(ID) {
return "<a href='/" + boardID + "/res/" + threadID + "#p" + ID + "' class=quotelink>&gt;&gt;" + ID + "</a>";
}).join(' ')) + "</span><br>";
};
html = [];
for (capcodeType in capcodeReplies) {
array = capcodeReplies[capcodeType];
html.push(generateCapcodeReplies(capcodeType, array));
}
bq || (bq = $('blockquote', root));
return $.add(bq, [
$.el('br'), $.el('br'), $.el('span', {
className: 'capcodeReplies',
innerHTML: html.join('')
})
]);
}
};
@ -5109,12 +5068,8 @@
delete this.nodes.tripcode;
}
if (this.info.email) {
if (/sage/i.test(this.info.email)) {
return email.href = 'mailto:sage';
} else {
$.replace(email, name);
return delete this.nodes.email;
}
$.replace(email, name);
return delete this.nodes.email;
}
}
};
@ -5678,7 +5633,7 @@
return;
}
a = PostHiding.makeButton(post, 'show');
postInfo = Conf['Anonymize'] ? 'Anonymous' : post.info.name;
postInfo = Conf['Anonymize'] ? 'Anonymous' : $('.nameBlock', post.nodes.info).textContent;
$.add(a, $.tn(" " + postInfo));
post.nodes.stub = $.el('div', {
className: 'stub'
@ -6026,7 +5981,7 @@
}
numReplies = ((span = $('.summary', threadRoot)) ? +span.textContent.match(/\d+/) : 0) + $$('.opContainer ~ .replyContainer', threadRoot).length;
numReplies = numReplies === 1 ? '1 reply' : "" + (numReplies || 'no') + " replies";
opInfo = Conf['Anonymize'] ? 'Anonymous' : OP.info.name;
opInfo = Conf['Anonymize'] ? 'Anonymous' : $('.nameBlock', OP.nodes.info).textContent;
a = ThreadHiding.makeButton(thread, 'show');
$.add(a, $.tn(" " + opInfo + " (" + numReplies + ")"));
thread.stub = $.el('div', {
@ -7581,7 +7536,8 @@
persona = {
name: post.name,
email: /^sage$/.test(post.email) ? persona.email : post.email,
sub: Conf['Remember Subject'] ? post.sub : void 0
sub: Conf['Remember Subject'] ? post.sub : void 0,
flag: post.flag
};
return $.set('QR.persona', persona);
});
@ -7589,36 +7545,30 @@
},
cooldown: {
init: function() {
var board;
var key, setTimers, type, _base,
_this = this;
if (!Conf['Cooldown']) {
return;
}
board = g.BOARD.ID;
QR.cooldown.types = {
thread: (function() {
switch (board) {
case 'q':
return 86400;
case 'b':
case 'soc':
case 'r9k':
return 600;
default:
return 300;
}
})(),
sage: board === 'q' ? 600 : 60,
file: board === 'q' ? 300 : 30,
post: board === 'q' ? 150 : 30
setTimers = function(e) {
return QR.cooldown.types = e.detail;
};
$.on(window, 'cooldown:timers', setTimers);
$.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))');
(_base = QR.cooldown).types || (_base.types = {});
$.off(window, 'cooldown:timers', setTimers);
for (type in QR.cooldown.types) {
QR.cooldown.types[type] = +QR.cooldown.types[type];
}
QR.cooldown.upSpd = 0;
QR.cooldown.upSpdAccuracy = .5;
$.get("cooldown." + board, {}, function(item) {
QR.cooldown.cooldowns = item["cooldown." + board];
key = "cooldown." + g.BOARD;
$.get(key, {}, function(item) {
QR.cooldown.cooldowns = item[key];
return QR.cooldown.start();
});
return $.sync("cooldown." + board, QR.cooldown.sync);
return $.sync(key, QR.cooldown.sync);
},
start: function() {
if (!Conf['Cooldown']) {
@ -7639,31 +7589,27 @@
return QR.cooldown.start();
},
set: function(data) {
var cooldown, delay, hasFile, isReply, isSage, post, req, start, type, upSpd;
var cooldown, delay, hasFile, isReply, post, req, start, threadID, upSpd;
if (!Conf['Cooldown']) {
return;
}
req = data.req, post = data.post, isReply = data.isReply, delay = data.delay;
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 {
if (post.file) {
upSpd = post.file.size / ((req.uploadEndTime - req.uploadStartTime) / $.SECOND);
if (hasFile = !!post.file) {
upSpd = post.file.size / ((start - req.uploadStartTime) / $.SECOND);
QR.cooldown.upSpdAccuracy = ((upSpd > QR.cooldown.upSpd * .9) + QR.cooldown.upSpdAccuracy) / 2;
QR.cooldown.upSpd = upSpd;
}
isSage = /sage/i.test(post.email);
hasFile = !!post.file;
type = !isReply ? 'thread' : isSage ? 'sage' : hasFile ? 'file' : 'post';
cooldown = {
isReply: isReply,
isSage: isSage,
hasFile: hasFile,
timeout: start + QR.cooldown.types[type] * $.SECOND
threadID: threadID
};
}
QR.cooldown.cooldowns[start] = cooldown;
@ -7679,7 +7625,7 @@
}
},
count: function() {
var cooldown, cooldowns, elapsed, hasFile, isReply, isSage, now, post, seconds, start, type, types, upSpd, upSpdAccuracy, update, _ref;
var cooldown, cooldowns, elapsed, hasFile, isReply, maxTimer, now, post, seconds, start, type, types, upSpd, upSpdAccuracy, update, _ref;
if (!Object.keys(QR.cooldown.cooldowns).length) {
$["delete"]("" + g.BOARD + ".cooldown");
@ -7688,11 +7634,11 @@
QR.status();
return;
}
setTimeout(QR.cooldown.count, $.SECOND);
clearTimeout(QR.cooldown.timeout);
QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND);
now = Date.now();
post = QR.posts[0];
isReply = post.thread !== 'new';
isSage = /sage/i.test(post.email);
hasFile = !!post.file;
seconds = null;
_ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns, upSpd = _ref.upSpd, upSpdAccuracy = _ref.upSpdAccuracy;
@ -7707,20 +7653,39 @@
}
continue;
}
if (isReply === cooldown.isReply) {
type = !isReply ? 'thread' : isSage && cooldown.isSage ? 'sage' : hasFile && cooldown.hasFile ? 'file' : 'post';
elapsed = Math.floor((now - start) / $.SECOND);
if (elapsed >= 0) {
seconds = Math.max(seconds, types[type] - elapsed);
if (Conf['Cooldown Prediction'] && hasFile && upSpd) {
seconds -= Math.floor(post.file.size / upSpd * upSpdAccuracy);
seconds = Math.max(seconds, 0);
}
}
}
if (!((start <= now && now <= cooldown.timeout))) {
if ('timeout' in cooldown) {
QR.cooldown.unset(start);
continue;
}
if (isReply === cooldown.isReply) {
elapsed = Math.floor((now - start) / $.SECOND);
if (elapsed < 0) {
continue;
}
if (!isReply) {
type = 'thread';
} else if (hasFile) {
if (!cooldown.hasFile) {
seconds = Math.max(seconds, 0);
continue;
}
type = 'image';
} else {
type = 'reply';
}
maxTimer = Math.max(types[type] || 0, types[type + '_intra'] || 0);
if (!((start <= now && now <= start + maxTimer * $.SECOND))) {
QR.cooldown.unset(start);
}
if (isReply && +post.thread === cooldown.threadID) {
type += '_intra';
}
seconds = Math.max(seconds, types[type] - elapsed);
}
}
if (seconds && Conf['Cooldown Prediction'] && hasFile && upSpd) {
seconds -= Math.floor(post.file.size / upSpd * upSpdAccuracy);
seconds = seconds > 0 ? seconds : 0;
}
update = seconds !== null || !!QR.cooldown.seconds;
QR.cooldown.seconds = seconds;
@ -7939,6 +7904,9 @@
_this.name = 'name' in QR.persona.always ? QR.persona.always.name : prev ? prev.name : persona.name;
_this.email = 'email' in QR.persona.always ? QR.persona.always.email : prev && !/^sage$/.test(prev.email) ? prev.email : persona.email;
_this.sub = 'sub' in QR.persona.always ? QR.persona.always.sub : Conf['Remember Subject'] ? prev ? prev.sub : persona.sub : '';
if (QR.nodes.flag) {
_this.flag = prev ? prev.flag : persona.flag;
}
if (QR.selected === _this) {
return _this.load();
}
@ -7970,7 +7938,7 @@
};
_Class.prototype.lock = function(lock) {
var name, _i, _len, _ref;
var name, node, _i, _len, _ref;
if (lock == null) {
lock = true;
@ -7979,10 +7947,12 @@
if (this !== QR.selected) {
return;
}
_ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler'];
_ref = ['thread', 'name', 'email', 'sub', 'com', 'fileButton', 'filename', 'spoiler', 'flag'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
name = _ref[_i];
QR.nodes[name].disabled = lock;
if (node = QR.nodes[name]) {
node.disabled = lock;
}
}
this.nodes.rm.style.visibility = lock ? 'hidden' : '';
(lock ? $.off : $.on)(QR.nodes.filename.previousElementSibling, 'click', QR.openFileInput);
@ -8012,12 +7982,15 @@
};
_Class.prototype.load = function() {
var name, _i, _len, _ref;
var name, node, _i, _len, _ref;
_ref = ['thread', 'name', 'email', 'sub', 'com', 'filename'];
_ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
name = _ref[_i];
QR.nodes[name].value = this[name] || null;
if (!(node = QR.nodes[name])) {
continue;
}
node.value = this[name] || node.dataset["default"] || null;
}
QR.tripcodeHider.call(QR.nodes['name']);
this.showFileData();
@ -8032,7 +8005,7 @@
return;
}
name = input.dataset.name;
this[name] = input.value;
this[name] = input.value || input.dataset["default"] || null;
switch (name) {
case 'thread':
return QR.status();
@ -8056,15 +8029,18 @@
};
_Class.prototype.forceSave = function() {
var name, _i, _len, _ref;
var name, node, _i, _len, _ref;
if (this !== QR.selected) {
return;
}
_ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler'];
_ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler', 'flag'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
name = _ref[_i];
this.save(QR.nodes[name]);
if (!(node = QR.nodes[name])) {
continue;
}
this.save(node);
}
};
@ -8402,7 +8378,7 @@
}
},
dialog: function() {
var check, dialog, elm, i, items, key, mimeTypes, name, nodes, thread, value, _ref;
var check, dialog, elm, event, flagSelector, i, items, key, mimeTypes, name, node, nodes, save, thread, value, _ref;
QR.nodes = nodes = {
el: dialog = UI.dialog('qr', 'top:0;right:0;', "<div id=qrtab class=move><input type=checkbox id=autohide title=Auto-hide><div id=qr-thread-select><select data-name=thread title='Create a new thread / Reply'><option value=new>New thread</option></select></div><a href=javascript:; class=close title=Close>✖</a></div><form><div class=persona><input name=name data-name=name list=\"list-name\" placeholder=Name class=field size=1 tabindex=10><input name=email data-name=email list=\"list-email\" placeholder=E-mail class=field size=1 tabindex=20><input name=sub data-name=sub list=\"list-sub\" placeholder=Subject class=field size=1 tabindex=30></div><div class=textarea><textarea data-name=com placeholder=Comment class=field tabindex=40></textarea><span id=char-count></span></div><div id=dump-list-container><div id=dump-list></div><a id=add-post href=javascript:; title=\"Add a post\" tabindex=50>+</a></div><div id=file-n-submit><span id=qr-filename-container class=field tabindex=60><span id=qr-no-file>No selected file</span><input id=\"qr-filename\" data-name=\"filename\" spellcheck=\"false\"><span id=qr-extras-container><label id=qr-spoiler-label><input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=70></label><span class=description>Spoiler</span><a id=dump-button title='Dump list'>+</a><span class=description>Dump</span><a id=qr-filerm href=javascript:; title='Remove file'>✖</a><span class=description>Remove File</span></span></span><input type=submit tabindex=80></div><input type=file multiple></form><datalist id=\"list-name\"></datalist><datalist id=\"list-email\"></datalist><datalist id=\"list-sub\"></datalist>")
@ -8462,8 +8438,15 @@
name: 'filetag',
innerHTML: "<option value=0>Hentai</option>\n<option value=6>Porn</option>\n<option value=1>Japanese</option>\n<option value=2>Anime</option>\n<option value=3>Game</option>\n<option value=5>Loop</option>\n<option value=4 selected>Other</option>"
});
nodes.flashTag.dataset["default"] = '4';
$.add(nodes.form, nodes.flashTag);
}
if (flagSelector = $('.flagSelector')) {
nodes.flag = flagSelector.cloneNode(true);
nodes.flag.dataset.name = 'flag';
nodes.flag.dataset["default"] = '0';
$.add(nodes.form, nodes.flag);
}
for (thread in g.BOARD.threads) {
$.add(nodes.thread, $.el('option', {
value: thread,
@ -8506,12 +8489,17 @@
while (name = items[i++]) {
$.on(nodes[name], 'mouseover', QR.mouseover);
}
items = ['name', 'email', 'sub', 'com', 'filename'];
items = ['name', 'email', 'sub', 'com', 'filename', 'flag'];
i = 0;
save = function() {
return QR.selected.save(this);
};
while (name = items[i++]) {
$.on(nodes[name], 'input', function() {
return QR.selected.save(this);
});
if (!(node = nodes[name])) {
continue;
}
event = node.nodeName === 'SELECT' ? 'change' : 'input';
$.on(nodes[name], event, save);
}
$.on(nodes['name'], 'blur', QR.tripcodeHider);
$.on(nodes.thread, 'change', function() {
@ -8552,7 +8540,7 @@
},
preSubmitHooks: [],
submit: function(e) {
var challenge, err, extra, filetag, hook, options, post, postData, response, textOnly, thread, threadID, _i, _len, _ref, _ref1;
var challenge, err, extra, filetag, formData, hook, options, post, response, textOnly, thread, threadID, _i, _len, _ref, _ref1;
if (e != null) {
e.preventDefault();
@ -8575,7 +8563,7 @@
thread = g.BOARD.threads[threadID];
if (threadID === 'new') {
threadID = null;
if (['vg', 'q'].contains(g.BOARD.ID) && !post.sub) {
if (g.BOARD.ID === 'vg' && !post.sub) {
err = 'New threads require a subject.';
} else if (!(post.file || (textOnly = !!$('input[name=textonly]', $.id('postForm'))))) {
err = 'No file selected.';
@ -8616,7 +8604,7 @@
d.activeElement.blur();
}
post.lock();
postData = {
formData = {
resto: threadID,
name: post.name,
email: post.email,
@ -8625,6 +8613,7 @@
upfile: post.file,
filetag: filetag,
spoiler: post.spoiler,
flag: post.flag,
textonly: textOnly,
mode: 'regist',
pwd: QR.persona.pwd,
@ -8646,7 +8635,7 @@
}
};
extra = {
form: $.formData(postData),
form: $.formData(formData),
upCallbacks: {
onload: function() {
QR.req.isUploadFinished = true;
@ -8730,8 +8719,8 @@
threadID: threadID,
postID: postID
});
postsCount = QR.posts.length;
QR.cooldown.auto = postsCount > 1 && isReply;
postsCount = QR.posts.length - 1;
QR.cooldown.auto = postsCount && isReply;
if (QR.cooldown.auto && QR.captcha.isEnabled && (captchasCount = QR.captcha.captchas.length) < 3 && captchasCount < postsCount) {
notif = new Notification('Quick reply warning', {
body: "You are running low on cached captchas. Cache count: " + captchasCount + ".",
@ -8756,7 +8745,8 @@
QR.cooldown.set({
req: req,
post: post,
isReply: isReply
isReply: isReply,
threadID: threadID
});
URL = !isReply ? "/" + g.BOARD + "/res/" + threadID : g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab'] ? "/" + g.BOARD + "/res/" + threadID + "#p" + postID : void 0;
if (URL) {
@ -9787,7 +9777,7 @@
open: function(post) {
var node;
if (post.isDead || post.board.ID === 'q') {
if (post.isDead) {
return false;
}
DeleteLink.post = post;
@ -9862,7 +9852,7 @@
return;
}
DeleteLink.cooldown.counting = post;
length = 30;
length = 60;
seconds = Math.ceil((length * $.SECOND - (Date.now() - post.info.date)) / $.SECOND);
return DeleteLink.cooldown.count(post, seconds, length, node);
},
@ -10158,7 +10148,6 @@
if (g.VIEW !== 'thread' || !Conf['Thread Updater']) {
return;
}
checked = Conf['Auto Update'] ? 'checked' : '';
if (Conf['Updater and Stats in Header']) {
this.dialog = sc = $.el('span', {
innerHTML: "[<span id=update-status></span><span id=update-timer title='Update now'></span>]\u00A0",
@ -10178,6 +10167,7 @@
this.checkPostCount = 0;
this.timer = $('#update-timer', sc);
this.status = $('#update-status', sc);
this.isUpdating = Conf['Auto Update'];
$.on(this.timer, 'click', ThreadUpdater.update);
$.on(this.status, 'click', ThreadUpdater.update);
subEntries = [];
@ -10195,7 +10185,7 @@
$.on(input, 'change', ThreadUpdater.cb.scrollBG);
ThreadUpdater.cb.scrollBG();
} else if (input.name === 'Auto Update') {
$.on(input, 'change', ThreadUpdater.update);
$.on(input, 'change', ThreadUpdater.cb.update);
}
subEntries.push({
el: el
@ -10225,7 +10215,6 @@
ThreadUpdater.thread = this;
ThreadUpdater.root = this.OP.nodes.root.parentNode;
ThreadUpdater.lastPost = +ThreadUpdater.root.lastElementChild.id.match(/\d+/)[0];
ThreadUpdater.outdateCount = 0;
ThreadUpdater.cb.interval.call($.el('input', {
value: Conf['Interval']
}));
@ -10243,19 +10232,18 @@
beep: 'data:audio/wav;base64,UklGRjQDAABXQVZFZm10IBAAAAABAAEAgD4AAIA+AAABAAgAc21wbDwAAABBAAADAAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkYXRhzAIAAGMms8em0tleMV4zIpLVo8nhfSlcPR102Ki+5JspVEkdVtKzs+K1NEhUIT7DwKrcy0g6WygsrM2k1NpiLl0zIY/WpMrjgCdbPhxw2Kq+5Z4qUkkdU9K1s+K5NkVTITzBwqnczko3WikrqM+l1NxlLF0zIIvXpsnjgydZPhxs2ay95aIrUEkdUdC3suK8N0NUIjq+xKrcz002WioppdGm091pK1w0IIjYp8jkhydXPxxq2K295aUrTkoeTs65suK+OUFUIzi7xqrb0VA0WSoootKm0t5tKlo1H4TYqMfkiydWQBxm16+85actTEseS8y7seHAPD9TIza5yKra01QyWSson9On0d5wKVk2H4DYqcfkjidUQB1j1rG75KsvSkseScu8seDCPz1TJDW2yara1FYxWSwnm9Sn0N9zKVg2H33ZqsXkkihSQR1g1bK65K0wSEsfR8i+seDEQTxUJTOzy6rY1VowWC0mmNWoz993KVc3H3rYq8TklSlRQh1d1LS647AyR0wgRMbAsN/GRDpTJTKwzKrX1l4vVy4lldWpzt97KVY4IXbUr8LZljVPRCxhw7W3z6ZISkw1VK+4sMWvXEhSPk6buay9sm5JVkZNiLWqtrJ+TldNTnquqbCwilZXU1BwpKirrpNgWFhTaZmnpquZbFlbVmWOpaOonHZcXlljhaGhpZ1+YWBdYn2cn6GdhmdhYGN3lp2enIttY2Jjco+bnJuOdGZlZXCImJqakHpoZ2Zug5WYmZJ/bGlobX6RlpeSg3BqaW16jZSVkoZ0bGtteImSk5KIeG5tbnaFkJKRinxxbm91gY2QkIt/c3BwdH6Kj4+LgnZxcXR8iI2OjIR5c3J0e4WLjYuFe3VzdHmCioyLhn52dHR5gIiKioeAeHV1eH+GiYqHgXp2dnh9hIiJh4J8eHd4fIKHiIeDfXl4eHyBhoeHhH96eHmA',
cb: {
online: function() {
if (ThreadUpdater.online = navigator.onLine) {
if (navigator.onLine) {
ThreadUpdater.outdateCount = 0;
ThreadUpdater.setInterval();
ThreadUpdater.update();
ThreadUpdater.set('status', null, null);
} else {
ThreadUpdater.set('timer', null);
ThreadUpdater.set('status', 'Offline', 'warning');
}
return ThreadUpdater.cb.autoUpdate();
return ThreadUpdater.count(true);
},
post: function(e) {
if (e.detail.threadID !== ThreadUpdater.thread.ID) {
if (!(ThreadUpdater.isUpdating && e.detail.threadID === ThreadUpdater.thread.ID)) {
return;
}
ThreadUpdater.outdateCount = 0;
@ -10285,9 +10273,7 @@
return;
}
ThreadUpdater.outdateCount = 0;
if (ThreadUpdater.seconds > ThreadUpdater.interval) {
return ThreadUpdater.setInterval();
}
return ThreadUpdater.seconds = Math.min(ThreadUpdater.seconds, ThreadUpdater.interval);
},
scrollBG: function() {
return ThreadUpdater.scrollBG = Conf['Scroll BG'] ? function() {
@ -10296,17 +10282,10 @@
return !d.hidden;
};
},
autoUpdate: function() {
if (ThreadUpdater.online) {
return ThreadUpdater.timeout();
} else {
return clearTimeout(ThreadUpdater.timeoutID);
}
},
interval: function() {
var val;
val = +this.value;
val = parseInt(this.value, 10);
if (val < 1) {
val = 1;
}
@ -10317,9 +10296,9 @@
var klass, req, text, _ref;
req = ThreadUpdater.req;
delete ThreadUpdater.req;
if (e.type !== 'loadend') {
req.onloadend = null;
delete ThreadUpdater.req;
if (e.type === 'timeout') {
ThreadUpdater.set('status', 'Retrying', null);
ThreadUpdater.update();
@ -10349,9 +10328,8 @@
ThreadUpdater.set('status', text, klass);
}
if (ThreadUpdater.postID) {
ThreadUpdater.cb.checkpost();
return ThreadUpdater.cb.checkpost();
}
return delete ThreadUpdater.req;
}
},
setInterval: function() {
@ -10364,8 +10342,7 @@
}
ThreadUpdater.seconds = Conf['Optional Increase'] ? (cur = [0, 5, 10, 15, 20, 30, 60, 90, 120, 240, 300][j] > i) ? cur : i : i;
ThreadUpdater.set('timer', ThreadUpdater.seconds++);
clearTimeout(ThreadUpdater.timeoutID);
return ThreadUpdater.timeout();
return ThreadUpdater.count(true);
},
intervalShortcut: function() {
var settings;
@ -10387,20 +10364,29 @@
return el.className = klass;
}
},
count: function(start) {
clearTimeout(ThreadUpdater.timeoutID);
if (start && ThreadUpdater.isUpdating && navigator.onLine) {
return ThreadUpdater.timeout();
}
},
timeout: function() {
var sec;
ThreadUpdater.timeoutID = setTimeout(ThreadUpdater.timeout, 1000);
ThreadUpdater.set('timer', --ThreadUpdater.seconds);
if (ThreadUpdater.seconds <= 0) {
sec = ThreadUpdater.seconds--;
ThreadUpdater.set('timer', sec);
if (sec <= 0) {
return ThreadUpdater.update();
}
},
update: function() {
var url;
if (!ThreadUpdater.online) {
if (!navigator.onLine) {
return;
}
clearTimeout(ThreadUpdater.timeoutID);
ThreadUpdater.count();
if (Conf['Auto Update']) {
ThreadUpdater.set('timer', '...');
} else {
@ -10574,27 +10560,6 @@
ThreadWatcher.fetchAllStatus();
this.db.save();
}
$.get('WatchedThreads', null, function(_arg) {
var WatchedThreads, boardID, data, threadID, threads, _ref;
WatchedThreads = _arg.WatchedThreads;
if (!WatchedThreads) {
return;
}
_ref = ThreadWatcher.convert(WatchedThreads);
for (boardID in _ref) {
threads = _ref[boardID];
for (threadID in threads) {
data = threads[threadID];
ThreadWatcher.db.set({
boardID: boardID,
threadID: threadID,
val: data
});
}
}
return $["delete"]('WatchedThreads');
});
return Thread.prototype.callbacks.push({
name: 'Thread Watcher',
cb: this.node
@ -11375,6 +11340,7 @@
'4plebs': {
domain: 'archive.4plebs.org',
http: true,
https: true,
software: 'foolfuuka',
boards: ['hr', 'tg', 'tv', 'x'],
files: ['hr', 'tg', 'tv', 'x']
@ -11382,10 +11348,10 @@
'fap archive': {
domain: 'fuuka.worldathleticproject.org',
http: true,
https: false,
https: true,
software: 'foolfuuka',
boards: ['b', 'e', 'h', 'hc', 'p', 's', 'u'],
files: ['b', 'e', 'h', 'hc', 'p', 's', 'u']
files: ['b', 'e', 'h', 'hc', 'p', 's', 'soc', 'sp', 'u']
},
'Foolz': {
domain: 'archive.foolz.us',
@ -11409,8 +11375,8 @@
https: true,
withCredentials: true,
software: 'foolfuuka',
boards: ['a', 'co', 'gd', 'h', 'jp', 'm', 'mlp', 'q', 'sp', 'tg', 'tv', 'u', 'v', 'vg', 'vp', 'vr', 'wsg'],
files: ['a', 'gd', 'h', 'jp', 'm', 'q', 'tg', 'u', 'vg', 'vp', 'vr', 'wsg']
boards: ['a', 'co', 'd', 'gd', 'h', 'jp', 'm', 'mlp', 'q', 'sp', 'tg', 'tv', 'u', 'v', 'vg', 'vp', 'vr', 'wsg'],
files: ['a', 'd', 'gd', 'h', 'jp', 'm', 'q', 'tg', 'u', 'vg', 'vp', 'vr', 'wsg']
},
'Heinessen': {
domain: 'archive.heinessen.com',
@ -11500,14 +11466,10 @@
return "" + (Redirect.protocol(archive)) + archive.domain + "/" + path;
},
post: function(archive, _arg) {
var URL, boardID, postID, protocol;
var URL, boardID, postID;
boardID = _arg.boardID, postID = _arg.postID;
protocol = Redirect.protocol(archive);
if (['Foolz', 'NSFW Foolz'].contains(archive.name)) {
protocol = 'https://';
}
URL = new String("" + protocol + archive.domain + "/_/api/chan/post/?board=" + boardID + "&num=" + postID);
URL = new String("" + (Redirect.protocol(archive)) + archive.domain + "/_/api/chan/post/?board=" + boardID + "&num=" + postID);
URL.archive = archive;
return URL;
},
@ -13418,12 +13380,6 @@
}
quote.href = "/" + post.board + "/res/" + href;
}
Build.capcodeReplies({
boardID: post.board.ID,
threadID: post.thread.ID,
bq: clone,
capcodeReplies: postObj.capcode_replies
});
post.nodes.shortComment = comment;
$.replace(comment, clone);
post.nodes.comment = post.nodes.longComment = clone;
@ -13509,7 +13465,6 @@
switch (g.BOARD.ID) {
case 'b':
case 'vg':
case 'q':
return 3;
case 't':
return 1;

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript
/*
* appchan x - Version 2.3.10 - 2013-09-20
* appchan x - Version 2.3.10 - 2013-09-24
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@ -317,7 +317,7 @@
uniqueID: "# Filter a specific ID:\n#/Txhvk1Tl/",
tripcode: "# Filter any tripfag\n#/^!/",
capcode: "# Set a custom class for mods:\n#/Mod$/;highlight:mod;op:yes\n# Set a custom class for moot:\n#/Admin$/;highlight:moot;op:yes",
email: "# Filter any e-mails that are not `sage` on /a/ and /jp/:\n#/^(?!sage$)/;boards:a,jp",
email: "",
subject: "# Filter Generals on /v/:\n#/general/i;boards:v;op:only",
comment: "# Filter Stallman copypasta on /g/:\n#/what you\'re refer+ing to as linux/i;boards:g",
flag: '',
@ -3332,7 +3332,7 @@
this.nodes.comment.normalize();
bq = this.nodes.comment.cloneNode(true);
nodes = $$('.abbr, .capcodeReplies, .exif, b', bq);
nodes = $$('.abbr, .exif, b', bq);
i = 0;
while (node = nodes[i++]) {
$.rm(node);
@ -3364,7 +3364,7 @@
return;
}
this.nodes.quotelinks.push(quotelink);
if (this.isClone || !this.isReply && $.hasClass(quotelink.parentNode.parentNode, 'capcodeReplies')) {
if (this.isClone) {
return;
}
fullID = "" + match[1] + "." + match[2];
@ -4241,7 +4241,6 @@
date: data.now,
dateUTC: data.time,
comment: data.com,
capcodeReplies: data.capcode_replies,
isSticky: !!data.sticky,
isClosed: !!data.closed
};
@ -4269,9 +4268,9 @@
@license: https://github.com/4chan/4chan-JS/blob/master/LICENSE
*/
var a, boardID, capcode, capcodeClass, capcodeReplies, capcodeStart, closed, comment, container, date, dateUTC, email, emailEnd, emailStart, ext, file, fileDims, fileHTML, fileInfo, fileSize, fileThumb, filename, flag, flagCode, flagName, href, imgSrc, isClosed, isOP, isSticky, name, postID, quote, shortFilename, spoilerRange, staticPath, sticky, subject, threadID, tripcode, uniqueID, userID, _i, _len, _ref;
var a, boardID, capcode, capcodeClass, capcodeStart, closed, comment, container, date, dateUTC, email, emailEnd, emailStart, ext, file, fileDims, fileHTML, fileInfo, fileSize, fileThumb, filename, flag, flagCode, flagName, href, imgSrc, isClosed, isOP, isSticky, name, postID, quote, shortFilename, spoilerRange, staticPath, sticky, subject, threadID, tripcode, uniqueID, userID, _i, _len, _ref;
postID = o.postID, threadID = o.threadID, boardID = o.boardID, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flagCode = o.flagCode, flagName = o.flagName, date = o.date, dateUTC = o.dateUTC, isSticky = o.isSticky, isClosed = o.isClosed, comment = o.comment, capcodeReplies = o.capcodeReplies, file = o.file;
postID = o.postID, threadID = o.threadID, boardID = o.boardID, name = o.name, capcode = o.capcode, tripcode = o.tripcode, uniqueID = o.uniqueID, email = o.email, subject = o.subject, flagCode = o.flagCode, flagName = o.flagName, date = o.date, dateUTC = o.dateUTC, isSticky = o.isSticky, isClosed = o.isClosed, comment = o.comment, file = o.file;
isOP = postID === threadID;
staticPath = '//static.4chan.org/image/';
if (email) {
@ -4348,7 +4347,7 @@
container = $.el('div', {
id: "pc" + postID,
className: "postContainer " + (isOP ? 'op' : 'reply') + "Container",
innerHTML: "" + (isOP ? '' : "<div class=sideArrows id=sa" + postID + ">&gt;&gt;</div>") + "<div id=p" + postID + " class='post " + (isOP ? 'op' : 'reply') + (capcode === 'admin_highlight' ? ' highlightPost' : '') + "'><div class='postInfoM mobile' id=pim" + postID + "><span class='nameBlock" + capcodeClass + "'><span class=name>" + (name || '') + "</span>" + (tripcode + capcodeStart + capcode + userID + flag + sticky + closed) + "<br>" + subject + "</span><span class='dateTime postNum' data-utc=" + dateUTC + ">" + date + "<a href=" + ("/" + boardID + "/res/" + threadID + "#p" + postID) + ">No.</a><a href='" + (g.VIEW === 'thread' && g.THREADID === +threadID ? "javascript:quote(" + postID + ")" : "/" + boardID + "/res/" + threadID + "#q" + postID) + "'>" + postID + "</a></span></div>" + (isOP ? fileHTML : '') + "<div class='postInfo desktop' id=pi" + postID + "><input type=checkbox name=" + postID + " value=delete>" + subject + "&nbsp;<span class='nameBlock" + capcodeClass + "'>" + emailStart + "<span class=name>" + (name || '') + "</span>" + (tripcode + capcodeStart + emailEnd + capcode + userID + flag + sticky + closed) + "</span>" + " " + "<span class=dateTime data-utc=" + dateUTC + ">" + date + "</span>" + " " + "<span class='postNum desktop'><a href=" + ("/" + boardID + "/res/" + threadID + "#p" + postID) + " title='Highlight this post'>No.</a><a href='" + (g.VIEW === 'thread' && g.THREADID === +threadID ? "javascript:quote(" + postID + ")" : "/" + boardID + "/res/" + threadID + "#q" + postID) + "' title='Quote this post'>" + postID + "</a></span></div>" + (isOP ? '' : fileHTML) + "<blockquote class=postMessage id=m" + postID + ">" + (comment || '') + "</blockquote>" + " " + "</div>"
innerHTML: "" + (isOP ? '' : "<div class=sideArrows id=sa" + postID + ">&gt;&gt;</div>") + "<div id=p" + postID + " class='post " + (isOP ? 'op' : 'reply') + (capcode === 'admin_highlight' ? ' highlightPost' : '') + "'><div class='postInfoM mobile' id=pim" + postID + "><span class='nameBlock" + capcodeClass + "'><span class=name>" + (name || '') + "</span>" + (tripcode + capcodeStart + capcode + userID + flag + sticky + closed) + "<br>" + subject + "</span><span class='dateTime postNum' data-utc=" + dateUTC + ">" + date + "<a href=" + ("/" + boardID + "/res/" + threadID + "#p" + postID) + ">No.</a><a href='" + (g.VIEW === 'thread' && g.THREADID === +threadID ? "javascript:quote(" + postID + ")" : "/" + boardID + "/res/" + threadID + "#q" + postID) + "'>" + postID + "</a></span></div>" + (isOP ? fileHTML : '') + "<div class='postInfo desktop' id=pi" + postID + "><input type=checkbox name=" + postID + " value=delete>&nbsp;" + subject + "&nbsp;<span class='nameBlock" + capcodeClass + "'>" + emailStart + "<span class=name>" + (name || '') + "</span>" + (tripcode + capcodeStart + emailEnd + capcode + userID + flag + sticky + closed) + "</span>" + " " + "<span class=dateTime data-utc=" + dateUTC + ">" + date + "</span>" + " " + "<span class='postNum desktop'><a href=" + ("/" + boardID + "/res/" + threadID + "#p" + postID) + " title='Highlight this post'>No.</a><a href='" + (g.VIEW === 'thread' && g.THREADID === +threadID ? "javascript:quote(" + postID + ")" : "/" + boardID + "/res/" + threadID + "#q" + postID) + "' title='Quote this post'>" + postID + "</a></span></div>" + (isOP ? '' : fileHTML) + "<blockquote class=postMessage id=m" + postID + ">" + (comment || '') + "</blockquote>" + " " + "</div>"
});
_ref = $$('.quotelink', container);
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
@ -4359,47 +4358,7 @@
}
quote.href = "/" + boardID + "/res/" + href;
}
Build.capcodeReplies({
boardID: boardID,
threadID: threadID,
root: container,
capcodeReplies: capcodeReplies
});
return container;
},
capcodeReplies: function(_arg) {
var array, boardID, bq, capcodeReplies, capcodeType, generateCapcodeReplies, html, root, threadID;
boardID = _arg.boardID, threadID = _arg.threadID, bq = _arg.bq, root = _arg.root, capcodeReplies = _arg.capcodeReplies;
if (!capcodeReplies) {
return;
}
generateCapcodeReplies = function(capcodeType, array) {
return "<span class=smaller><span class=bold>" + ((function() {
switch (capcodeType) {
case 'admin':
return 'Administrator';
case 'mod':
return 'Moderator';
case 'developer':
return 'Developer';
}
})()) + " Repl" + (array.length > 1 ? 'ies' : 'y') + ":</span> " + (array.map(function(ID) {
return "<a href='/" + boardID + "/res/" + threadID + "#p" + ID + "' class=quotelink>&gt;&gt;" + ID + "</a>";
}).join(' ')) + "</span><br>";
};
html = [];
for (capcodeType in capcodeReplies) {
array = capcodeReplies[capcodeType];
html.push(generateCapcodeReplies(capcodeType, array));
}
bq || (bq = $('blockquote', root));
return $.add(bq, [
$.el('br'), $.el('br'), $.el('span', {
className: 'capcodeReplies',
innerHTML: html.join('')
})
]);
}
};
@ -5119,12 +5078,8 @@
delete this.nodes.tripcode;
}
if (this.info.email) {
if (/sage/i.test(this.info.email)) {
return email.href = 'mailto:sage';
} else {
$.replace(email, name);
return delete this.nodes.email;
}
$.replace(email, name);
return delete this.nodes.email;
}
}
};
@ -5688,7 +5643,7 @@
return;
}
a = PostHiding.makeButton(post, 'show');
postInfo = Conf['Anonymize'] ? 'Anonymous' : post.info.name;
postInfo = Conf['Anonymize'] ? 'Anonymous' : $('.nameBlock', post.nodes.info).textContent;
$.add(a, $.tn(" " + postInfo));
post.nodes.stub = $.el('div', {
className: 'stub'
@ -6036,7 +5991,7 @@
}
numReplies = ((span = $('.summary', threadRoot)) ? +span.textContent.match(/\d+/) : 0) + $$('.opContainer ~ .replyContainer', threadRoot).length;
numReplies = numReplies === 1 ? '1 reply' : "" + (numReplies || 'no') + " replies";
opInfo = Conf['Anonymize'] ? 'Anonymous' : OP.info.name;
opInfo = Conf['Anonymize'] ? 'Anonymous' : $('.nameBlock', OP.nodes.info).textContent;
a = ThreadHiding.makeButton(thread, 'show');
$.add(a, $.tn(" " + opInfo + " (" + numReplies + ")"));
thread.stub = $.el('div', {
@ -7596,7 +7551,8 @@
persona = {
name: post.name,
email: /^sage$/.test(post.email) ? persona.email : post.email,
sub: Conf['Remember Subject'] ? post.sub : void 0
sub: Conf['Remember Subject'] ? post.sub : void 0,
flag: post.flag
};
return $.set('QR.persona', persona);
});
@ -7604,36 +7560,30 @@
},
cooldown: {
init: function() {
var board;
var key, setTimers, type, _base,
_this = this;
if (!Conf['Cooldown']) {
return;
}
board = g.BOARD.ID;
QR.cooldown.types = {
thread: (function() {
switch (board) {
case 'q':
return 86400;
case 'b':
case 'soc':
case 'r9k':
return 600;
default:
return 300;
}
})(),
sage: board === 'q' ? 600 : 60,
file: board === 'q' ? 300 : 30,
post: board === 'q' ? 150 : 30
setTimers = function(e) {
return QR.cooldown.types = e.detail;
};
$.on(window, 'cooldown:timers', setTimers);
$.globalEval('window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))');
(_base = QR.cooldown).types || (_base.types = {});
$.off(window, 'cooldown:timers', setTimers);
for (type in QR.cooldown.types) {
QR.cooldown.types[type] = +QR.cooldown.types[type];
}
QR.cooldown.upSpd = 0;
QR.cooldown.upSpdAccuracy = .5;
$.get("cooldown." + board, {}, function(item) {
QR.cooldown.cooldowns = item["cooldown." + board];
key = "cooldown." + g.BOARD;
$.get(key, {}, function(item) {
QR.cooldown.cooldowns = item[key];
return QR.cooldown.start();
});
return $.sync("cooldown." + board, QR.cooldown.sync);
return $.sync(key, QR.cooldown.sync);
},
start: function() {
if (!Conf['Cooldown']) {
@ -7654,31 +7604,27 @@
return QR.cooldown.start();
},
set: function(data) {
var cooldown, delay, hasFile, isReply, isSage, post, req, start, type, upSpd;
var cooldown, delay, hasFile, isReply, post, req, start, threadID, upSpd;
if (!Conf['Cooldown']) {
return;
}
req = data.req, post = data.post, isReply = data.isReply, delay = data.delay;
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 {
if (post.file) {
upSpd = post.file.size / ((req.uploadEndTime - req.uploadStartTime) / $.SECOND);
if (hasFile = !!post.file) {
upSpd = post.file.size / ((start - req.uploadStartTime) / $.SECOND);
QR.cooldown.upSpdAccuracy = ((upSpd > QR.cooldown.upSpd * .9) + QR.cooldown.upSpdAccuracy) / 2;
QR.cooldown.upSpd = upSpd;
}
isSage = /sage/i.test(post.email);
hasFile = !!post.file;
type = !isReply ? 'thread' : isSage ? 'sage' : hasFile ? 'file' : 'post';
cooldown = {
isReply: isReply,
isSage: isSage,
hasFile: hasFile,
timeout: start + QR.cooldown.types[type] * $.SECOND
threadID: threadID
};
}
QR.cooldown.cooldowns[start] = cooldown;
@ -7694,7 +7640,7 @@
}
},
count: function() {
var cooldown, cooldowns, elapsed, hasFile, isReply, isSage, now, post, seconds, start, type, types, upSpd, upSpdAccuracy, update, _ref;
var cooldown, cooldowns, elapsed, hasFile, isReply, maxTimer, now, post, seconds, start, type, types, upSpd, upSpdAccuracy, update, _ref;
if (!Object.keys(QR.cooldown.cooldowns).length) {
$["delete"]("" + g.BOARD + ".cooldown");
@ -7703,11 +7649,11 @@
QR.status();
return;
}
setTimeout(QR.cooldown.count, $.SECOND);
clearTimeout(QR.cooldown.timeout);
QR.cooldown.timeout = setTimeout(QR.cooldown.count, $.SECOND);
now = Date.now();
post = QR.posts[0];
isReply = post.thread !== 'new';
isSage = /sage/i.test(post.email);
hasFile = !!post.file;
seconds = null;
_ref = QR.cooldown, types = _ref.types, cooldowns = _ref.cooldowns, upSpd = _ref.upSpd, upSpdAccuracy = _ref.upSpdAccuracy;
@ -7722,20 +7668,39 @@
}
continue;
}
if (isReply === cooldown.isReply) {
type = !isReply ? 'thread' : isSage && cooldown.isSage ? 'sage' : hasFile && cooldown.hasFile ? 'file' : 'post';
elapsed = Math.floor((now - start) / $.SECOND);
if (elapsed >= 0) {
seconds = Math.max(seconds, types[type] - elapsed);
if (Conf['Cooldown Prediction'] && hasFile && upSpd) {
seconds -= Math.floor(post.file.size / upSpd * upSpdAccuracy);
seconds = Math.max(seconds, 0);
}
}
}
if (!((start <= now && now <= cooldown.timeout))) {
if ('timeout' in cooldown) {
QR.cooldown.unset(start);
continue;
}
if (isReply === cooldown.isReply) {
elapsed = Math.floor((now - start) / $.SECOND);
if (elapsed < 0) {
continue;
}
if (!isReply) {
type = 'thread';
} else if (hasFile) {
if (!cooldown.hasFile) {
seconds = Math.max(seconds, 0);
continue;
}
type = 'image';
} else {
type = 'reply';
}
maxTimer = Math.max(types[type] || 0, types[type + '_intra'] || 0);
if (!((start <= now && now <= start + maxTimer * $.SECOND))) {
QR.cooldown.unset(start);
}
if (isReply && +post.thread === cooldown.threadID) {
type += '_intra';
}
seconds = Math.max(seconds, types[type] - elapsed);
}
}
if (seconds && Conf['Cooldown Prediction'] && hasFile && upSpd) {
seconds -= Math.floor(post.file.size / upSpd * upSpdAccuracy);
seconds = seconds > 0 ? seconds : 0;
}
update = seconds !== null || !!QR.cooldown.seconds;
QR.cooldown.seconds = seconds;
@ -7948,6 +7913,9 @@
_this.name = 'name' in QR.persona.always ? QR.persona.always.name : prev ? prev.name : persona.name;
_this.email = 'email' in QR.persona.always ? QR.persona.always.email : prev && !/^sage$/.test(prev.email) ? prev.email : persona.email;
_this.sub = 'sub' in QR.persona.always ? QR.persona.always.sub : Conf['Remember Subject'] ? prev ? prev.sub : persona.sub : '';
if (QR.nodes.flag) {
_this.flag = prev ? prev.flag : persona.flag;
}
if (QR.selected === _this) {
return _this.load();
}
@ -7979,7 +7947,7 @@
};
_Class.prototype.lock = function(lock) {
var name, _i, _len, _ref;
var name, node, _i, _len, _ref;
if (lock == null) {
lock = true;
@ -7988,10 +7956,12 @@
if (this !== QR.selected) {
return;
}
_ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler'];
_ref = ['thread', 'name', 'email', 'sub', 'com', 'fileButton', 'filename', 'spoiler', 'flag'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
name = _ref[_i];
QR.nodes[name].disabled = lock;
if (node = QR.nodes[name]) {
node.disabled = lock;
}
}
this.nodes.rm.style.visibility = lock ? 'hidden' : '';
(lock ? $.off : $.on)(QR.nodes.filename.previousElementSibling, 'click', QR.openFileInput);
@ -8021,12 +7991,15 @@
};
_Class.prototype.load = function() {
var name, _i, _len, _ref;
var name, node, _i, _len, _ref;
_ref = ['thread', 'name', 'email', 'sub', 'com', 'filename'];
_ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
name = _ref[_i];
QR.nodes[name].value = this[name] || null;
if (!(node = QR.nodes[name])) {
continue;
}
node.value = this[name] || node.dataset["default"] || null;
}
QR.tripcodeHider.call(QR.nodes['name']);
this.showFileData();
@ -8041,7 +8014,7 @@
return;
}
name = input.dataset.name;
this[name] = input.value;
this[name] = input.value || input.dataset["default"] || null;
switch (name) {
case 'thread':
return QR.status();
@ -8065,15 +8038,18 @@
};
_Class.prototype.forceSave = function() {
var name, _i, _len, _ref;
var name, node, _i, _len, _ref;
if (this !== QR.selected) {
return;
}
_ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler'];
_ref = ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler', 'flag'];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
name = _ref[_i];
this.save(QR.nodes[name]);
if (!(node = QR.nodes[name])) {
continue;
}
this.save(node);
}
};
@ -8409,7 +8385,7 @@
}
},
dialog: function() {
var check, dialog, i, items, key, mimeTypes, name, nodes, thread, value, _ref;
var check, dialog, event, flagSelector, i, items, key, mimeTypes, name, node, nodes, save, thread, value, _ref;
QR.nodes = nodes = {
el: dialog = UI.dialog('qr', 'top:0;right:0;', "<div id=qrtab class=move><input type=checkbox id=autohide title=Auto-hide><div id=qr-thread-select><select data-name=thread title='Create a new thread / Reply'><option value=new>New thread</option></select></div><a href=javascript:; class=close title=Close>✖</a></div><form><div class=persona><input name=name data-name=name list=\"list-name\" placeholder=Name class=field size=1 tabindex=10><input name=email data-name=email list=\"list-email\" placeholder=E-mail class=field size=1 tabindex=20><input name=sub data-name=sub list=\"list-sub\" placeholder=Subject class=field size=1 tabindex=30></div><div class=textarea><textarea data-name=com placeholder=Comment class=field tabindex=40></textarea><span id=char-count></span></div><div id=dump-list-container><div id=dump-list></div><a id=add-post href=javascript:; title=\"Add a post\" tabindex=50>+</a></div><div id=file-n-submit><span id=qr-filename-container class=field tabindex=60><span id=qr-no-file>No selected file</span><input id=\"qr-filename\" data-name=\"filename\" spellcheck=\"false\"><span id=qr-extras-container><label id=qr-spoiler-label><input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=70></label><span class=description>Spoiler</span><a id=dump-button title='Dump list'>+</a><span class=description>Dump</span><a id=qr-filerm href=javascript:; title='Remove file'>✖</a><span class=description>Remove File</span></span></span><input type=submit tabindex=80></div><input type=file multiple></form><datalist id=\"list-name\"></datalist><datalist id=\"list-email\"></datalist><datalist id=\"list-sub\"></datalist>")
@ -8469,8 +8445,15 @@
name: 'filetag',
innerHTML: "<option value=0>Hentai</option>\n<option value=6>Porn</option>\n<option value=1>Japanese</option>\n<option value=2>Anime</option>\n<option value=3>Game</option>\n<option value=5>Loop</option>\n<option value=4 selected>Other</option>"
});
nodes.flashTag.dataset["default"] = '4';
$.add(nodes.form, nodes.flashTag);
}
if (flagSelector = $('.flagSelector')) {
nodes.flag = flagSelector.cloneNode(true);
nodes.flag.dataset.name = 'flag';
nodes.flag.dataset["default"] = '0';
$.add(nodes.form, nodes.flag);
}
for (thread in g.BOARD.threads) {
$.add(nodes.thread, $.el('option', {
value: thread,
@ -8507,12 +8490,17 @@
while (name = items[i++]) {
$.on(nodes[name], 'mouseover', QR.mouseover);
}
items = ['name', 'email', 'sub', 'com', 'filename'];
items = ['name', 'email', 'sub', 'com', 'filename', 'flag'];
i = 0;
save = function() {
return QR.selected.save(this);
};
while (name = items[i++]) {
$.on(nodes[name], 'input', function() {
return QR.selected.save(this);
});
if (!(node = nodes[name])) {
continue;
}
event = node.nodeName === 'SELECT' ? 'change' : 'input';
$.on(nodes[name], event, save);
}
$.on(nodes['name'], 'blur', QR.tripcodeHider);
$.on(nodes.thread, 'change', function() {
@ -8542,7 +8530,7 @@
},
preSubmitHooks: [],
submit: function(e) {
var challenge, err, extra, filetag, hook, options, post, postData, response, textOnly, thread, threadID, _i, _len, _ref, _ref1;
var challenge, err, extra, filetag, formData, hook, options, post, response, textOnly, thread, threadID, _i, _len, _ref, _ref1;
if (e != null) {
e.preventDefault();
@ -8565,7 +8553,7 @@
thread = g.BOARD.threads[threadID];
if (threadID === 'new') {
threadID = null;
if (['vg', 'q'].contains(g.BOARD.ID) && !post.sub) {
if (g.BOARD.ID === 'vg' && !post.sub) {
err = 'New threads require a subject.';
} else if (!(post.file || (textOnly = !!$('input[name=textonly]', $.id('postForm'))))) {
err = 'No file selected.';
@ -8606,7 +8594,7 @@
d.activeElement.blur();
}
post.lock();
postData = {
formData = {
resto: threadID,
name: post.name,
email: post.email,
@ -8615,6 +8603,7 @@
upfile: post.file,
filetag: filetag,
spoiler: post.spoiler,
flag: post.flag,
textonly: textOnly,
mode: 'regist',
pwd: QR.persona.pwd,
@ -8636,7 +8625,7 @@
}
};
extra = {
form: $.formData(postData),
form: $.formData(formData),
upCallbacks: {
onload: function() {
QR.req.isUploadFinished = true;
@ -8720,8 +8709,8 @@
threadID: threadID,
postID: postID
});
postsCount = QR.posts.length;
QR.cooldown.auto = postsCount > 1 && isReply;
postsCount = QR.posts.length - 1;
QR.cooldown.auto = postsCount && isReply;
if (QR.cooldown.auto && QR.captcha.isEnabled && (captchasCount = QR.captcha.captchas.length) < 3 && captchasCount < postsCount) {
notif = new Notification('Quick reply warning', {
body: "You are running low on cached captchas. Cache count: " + captchasCount + ".",
@ -8746,7 +8735,8 @@
QR.cooldown.set({
req: req,
post: post,
isReply: isReply
isReply: isReply,
threadID: threadID
});
URL = !isReply ? "/" + g.BOARD + "/res/" + threadID : g.VIEW === 'index' && !QR.cooldown.auto && Conf['Open Post in New Tab'] ? "/" + g.BOARD + "/res/" + threadID + "#p" + postID : void 0;
if (URL) {
@ -9777,7 +9767,7 @@
open: function(post) {
var node;
if (post.isDead || post.board.ID === 'q') {
if (post.isDead) {
return false;
}
DeleteLink.post = post;
@ -9852,7 +9842,7 @@
return;
}
DeleteLink.cooldown.counting = post;
length = 30;
length = 60;
seconds = Math.ceil((length * $.SECOND - (Date.now() - post.info.date)) / $.SECOND);
return DeleteLink.cooldown.count(post, seconds, length, node);
},
@ -10148,7 +10138,6 @@
if (g.VIEW !== 'thread' || !Conf['Thread Updater']) {
return;
}
checked = Conf['Auto Update'] ? 'checked' : '';
if (Conf['Updater and Stats in Header']) {
this.dialog = sc = $.el('span', {
innerHTML: "[<span id=update-status></span><span id=update-timer title='Update now'></span>]\u00A0",
@ -10168,6 +10157,7 @@
this.checkPostCount = 0;
this.timer = $('#update-timer', sc);
this.status = $('#update-status', sc);
this.isUpdating = Conf['Auto Update'];
$.on(this.timer, 'click', ThreadUpdater.update);
$.on(this.status, 'click', ThreadUpdater.update);
subEntries = [];
@ -10185,7 +10175,7 @@
$.on(input, 'change', ThreadUpdater.cb.scrollBG);
ThreadUpdater.cb.scrollBG();
} else if (input.name === 'Auto Update') {
$.on(input, 'change', ThreadUpdater.update);
$.on(input, 'change', ThreadUpdater.cb.update);
}
subEntries.push({
el: el
@ -10215,7 +10205,6 @@
ThreadUpdater.thread = this;
ThreadUpdater.root = this.OP.nodes.root.parentNode;
ThreadUpdater.lastPost = +ThreadUpdater.root.lastElementChild.id.match(/\d+/)[0];
ThreadUpdater.outdateCount = 0;
ThreadUpdater.cb.interval.call($.el('input', {
value: Conf['Interval']
}));
@ -10233,19 +10222,18 @@
beep: 'data:audio/wav;base64,UklGRjQDAABXQVZFZm10IBAAAAABAAEAgD4AAIA+AAABAAgAc21wbDwAAABBAAADAAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkYXRhzAIAAGMms8em0tleMV4zIpLVo8nhfSlcPR102Ki+5JspVEkdVtKzs+K1NEhUIT7DwKrcy0g6WygsrM2k1NpiLl0zIY/WpMrjgCdbPhxw2Kq+5Z4qUkkdU9K1s+K5NkVTITzBwqnczko3WikrqM+l1NxlLF0zIIvXpsnjgydZPhxs2ay95aIrUEkdUdC3suK8N0NUIjq+xKrcz002WioppdGm091pK1w0IIjYp8jkhydXPxxq2K295aUrTkoeTs65suK+OUFUIzi7xqrb0VA0WSoootKm0t5tKlo1H4TYqMfkiydWQBxm16+85actTEseS8y7seHAPD9TIza5yKra01QyWSson9On0d5wKVk2H4DYqcfkjidUQB1j1rG75KsvSkseScu8seDCPz1TJDW2yara1FYxWSwnm9Sn0N9zKVg2H33ZqsXkkihSQR1g1bK65K0wSEsfR8i+seDEQTxUJTOzy6rY1VowWC0mmNWoz993KVc3H3rYq8TklSlRQh1d1LS647AyR0wgRMbAsN/GRDpTJTKwzKrX1l4vVy4lldWpzt97KVY4IXbUr8LZljVPRCxhw7W3z6ZISkw1VK+4sMWvXEhSPk6buay9sm5JVkZNiLWqtrJ+TldNTnquqbCwilZXU1BwpKirrpNgWFhTaZmnpquZbFlbVmWOpaOonHZcXlljhaGhpZ1+YWBdYn2cn6GdhmdhYGN3lp2enIttY2Jjco+bnJuOdGZlZXCImJqakHpoZ2Zug5WYmZJ/bGlobX6RlpeSg3BqaW16jZSVkoZ0bGtteImSk5KIeG5tbnaFkJKRinxxbm91gY2QkIt/c3BwdH6Kj4+LgnZxcXR8iI2OjIR5c3J0e4WLjYuFe3VzdHmCioyLhn52dHR5gIiKioeAeHV1eH+GiYqHgXp2dnh9hIiJh4J8eHd4fIKHiIeDfXl4eHyBhoeHhH96eHmA',
cb: {
online: function() {
if (ThreadUpdater.online = navigator.onLine) {
if (navigator.onLine) {
ThreadUpdater.outdateCount = 0;
ThreadUpdater.setInterval();
ThreadUpdater.update();
ThreadUpdater.set('status', null, null);
} else {
ThreadUpdater.set('timer', null);
ThreadUpdater.set('status', 'Offline', 'warning');
}
return ThreadUpdater.cb.autoUpdate();
return ThreadUpdater.count(true);
},
post: function(e) {
if (e.detail.threadID !== ThreadUpdater.thread.ID) {
if (!(ThreadUpdater.isUpdating && e.detail.threadID === ThreadUpdater.thread.ID)) {
return;
}
ThreadUpdater.outdateCount = 0;
@ -10275,9 +10263,7 @@
return;
}
ThreadUpdater.outdateCount = 0;
if (ThreadUpdater.seconds > ThreadUpdater.interval) {
return ThreadUpdater.setInterval();
}
return ThreadUpdater.seconds = Math.min(ThreadUpdater.seconds, ThreadUpdater.interval);
},
scrollBG: function() {
return ThreadUpdater.scrollBG = Conf['Scroll BG'] ? function() {
@ -10286,17 +10272,10 @@
return !d.hidden;
};
},
autoUpdate: function() {
if (ThreadUpdater.online) {
return ThreadUpdater.timeout();
} else {
return clearTimeout(ThreadUpdater.timeoutID);
}
},
interval: function() {
var val;
val = +this.value;
val = parseInt(this.value, 10);
if (val < 1) {
val = 1;
}
@ -10307,9 +10286,9 @@
var klass, req, text, _ref;
req = ThreadUpdater.req;
delete ThreadUpdater.req;
if (e.type !== 'loadend') {
req.onloadend = null;
delete ThreadUpdater.req;
if (e.type === 'timeout') {
ThreadUpdater.set('status', 'Retrying', null);
ThreadUpdater.update();
@ -10339,9 +10318,8 @@
ThreadUpdater.set('status', text, klass);
}
if (ThreadUpdater.postID) {
ThreadUpdater.cb.checkpost();
return ThreadUpdater.cb.checkpost();
}
return delete ThreadUpdater.req;
}
},
setInterval: function() {
@ -10354,8 +10332,7 @@
}
ThreadUpdater.seconds = Conf['Optional Increase'] ? (cur = [0, 5, 10, 15, 20, 30, 60, 90, 120, 240, 300][j] > i) ? cur : i : i;
ThreadUpdater.set('timer', ThreadUpdater.seconds++);
clearTimeout(ThreadUpdater.timeoutID);
return ThreadUpdater.timeout();
return ThreadUpdater.count(true);
},
intervalShortcut: function() {
var settings;
@ -10377,20 +10354,29 @@
return el.className = klass;
}
},
count: function(start) {
clearTimeout(ThreadUpdater.timeoutID);
if (start && ThreadUpdater.isUpdating && navigator.onLine) {
return ThreadUpdater.timeout();
}
},
timeout: function() {
var sec;
ThreadUpdater.timeoutID = setTimeout(ThreadUpdater.timeout, 1000);
ThreadUpdater.set('timer', --ThreadUpdater.seconds);
if (ThreadUpdater.seconds <= 0) {
sec = ThreadUpdater.seconds--;
ThreadUpdater.set('timer', sec);
if (sec <= 0) {
return ThreadUpdater.update();
}
},
update: function() {
var url;
if (!ThreadUpdater.online) {
if (!navigator.onLine) {
return;
}
clearTimeout(ThreadUpdater.timeoutID);
ThreadUpdater.count();
if (Conf['Auto Update']) {
ThreadUpdater.set('timer', '...');
} else {
@ -10564,27 +10550,6 @@
ThreadWatcher.fetchAllStatus();
this.db.save();
}
$.get('WatchedThreads', null, function(_arg) {
var WatchedThreads, boardID, data, threadID, threads, _ref;
WatchedThreads = _arg.WatchedThreads;
if (!WatchedThreads) {
return;
}
_ref = ThreadWatcher.convert(WatchedThreads);
for (boardID in _ref) {
threads = _ref[boardID];
for (threadID in threads) {
data = threads[threadID];
ThreadWatcher.db.set({
boardID: boardID,
threadID: threadID,
val: data
});
}
}
return $["delete"]('WatchedThreads');
});
return Thread.prototype.callbacks.push({
name: 'Thread Watcher',
cb: this.node
@ -11371,6 +11336,7 @@
'4plebs': {
domain: 'archive.4plebs.org',
http: true,
https: true,
software: 'foolfuuka',
boards: ['hr', 'tg', 'tv', 'x'],
files: ['hr', 'tg', 'tv', 'x']
@ -11378,10 +11344,10 @@
'fap archive': {
domain: 'fuuka.worldathleticproject.org',
http: true,
https: false,
https: true,
software: 'foolfuuka',
boards: ['b', 'e', 'h', 'hc', 'p', 's', 'u'],
files: ['b', 'e', 'h', 'hc', 'p', 's', 'u']
files: ['b', 'e', 'h', 'hc', 'p', 's', 'soc', 'sp', 'u']
},
'Foolz': {
domain: 'archive.foolz.us',
@ -11405,8 +11371,8 @@
https: true,
withCredentials: true,
software: 'foolfuuka',
boards: ['a', 'co', 'gd', 'h', 'jp', 'm', 'mlp', 'q', 'sp', 'tg', 'tv', 'u', 'v', 'vg', 'vp', 'vr', 'wsg'],
files: ['a', 'gd', 'h', 'jp', 'm', 'q', 'tg', 'u', 'vg', 'vp', 'vr', 'wsg']
boards: ['a', 'co', 'd', 'gd', 'h', 'jp', 'm', 'mlp', 'q', 'sp', 'tg', 'tv', 'u', 'v', 'vg', 'vp', 'vr', 'wsg'],
files: ['a', 'd', 'gd', 'h', 'jp', 'm', 'q', 'tg', 'u', 'vg', 'vp', 'vr', 'wsg']
},
'Heinessen': {
domain: 'archive.heinessen.com',
@ -11496,14 +11462,10 @@
return "" + (Redirect.protocol(archive)) + archive.domain + "/" + path;
},
post: function(archive, _arg) {
var URL, boardID, postID, protocol;
var URL, boardID, postID;
boardID = _arg.boardID, postID = _arg.postID;
protocol = Redirect.protocol(archive);
if (['Foolz', 'NSFW Foolz'].contains(archive.name)) {
protocol = 'https://';
}
URL = new String("" + protocol + archive.domain + "/_/api/chan/post/?board=" + boardID + "&num=" + postID);
URL = new String("" + (Redirect.protocol(archive)) + archive.domain + "/_/api/chan/post/?board=" + boardID + "&num=" + postID);
URL.archive = archive;
return URL;
},
@ -13414,12 +13376,6 @@
}
quote.href = "/" + post.board + "/res/" + href;
}
Build.capcodeReplies({
boardID: post.board.ID,
threadID: post.thread.ID,
bq: clone,
capcodeReplies: postObj.capcode_replies
});
post.nodes.shortComment = comment;
$.replace(comment, clone);
post.nodes.comment = post.nodes.longComment = clone;
@ -13505,7 +13461,6 @@
switch (g.BOARD.ID) {
case 'b':
case 'vg':
case 'q':
return 3;
case 't':
return 1;

View File

@ -581,7 +581,7 @@ a.hide-announcement {
align-self: stretch;
flex: 1;
}
#qr select {
#qr select[data-name=thread] {
margin: 0;
-webkit-appearance: none;
-moz-appearance: none;
@ -794,7 +794,6 @@ a.hide-announcement {
}
#file-n-submit-container input {
margin: 0;
padding: 0;
}
#file-n-submit input[type='submit'] {
order: 1;

110
json/archives.json Normal file
View File

@ -0,0 +1,110 @@
[{
"uid": 0,
"name": "Foolz",
"domain": "archive.foolz.us",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["a", "co", "gd", "jp", "m", "q", "sp", "tg", "tv", "v", "vg", "vp", "vr", "wsg"],
"files": ["a", "gd", "jp", "m", "q", "tg", "vg", "vp", "vr", "wsg"]
}, {
"uid": 1,
"name": "NSFW Foolz",
"domain": "nsfw.foolz.us",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["u"],
"files": ["u"]
}, {
"uid": 2,
"name": "The Dark Cave",
"domain": "archive.thedarkcave.org",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["c", "int", "out", "po"],
"files": ["c", "po"]
}, {
"uid": 3,
"name": "4plebs",
"domain": "archive.4plebs.org",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["hr", "tg", "tv", "x"],
"files": ["hr", "tg", "tv", "x"]
}, {
"uid": 4,
"name": "Nyafuu",
"domain": "archive.nyafuu.org",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["c", "w", "wg"],
"files": ["c", "w", "wg"]
}, {
"uid": 11,
"name": "Foolz a Shit",
"domain": "archive.foolzashit.com",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["adv", "asp", "cm", "d", "e", "i", "lgbt", "n", "o", "p", "pol", "s", "s4s", "t", "trv", "y"],
"files": ["cm", "d", "e", "i", "n", "o", "p", "s", "trv", "y"]
}, {
"uid": 12,
"name": "FapArchive",
"domain": "fuuka.worldathleticproject.org",
"http": true,
"https": true,
"software": "foolfuuka",
"boards": ["b", "e", "h", "hc", "p", "s", "soc", "sp", "u"],
"files": ["b", "e", "h", "hc", "p", "s", "soc", "sp", "u"]
}, {
"uid": 7,
"name": "Install Gentoo",
"domain": "archive.installgentoo.net",
"http": false,
"https": true,
"software": "fuuka",
"boards": ["diy", "g", "sci"],
"files": []
}, {
"uid": 8,
"name": "Rebecca Black Tech",
"domain": "rbt.asia",
"http": true,
"https": true,
"software": "fuuka",
"boards": ["cgl", "g", "mu", "w"],
"files": ["cgl", "g", "mu", "w"]
}, {
"uid": 9,
"name": "Heinessen",
"domain": "archive.heinessen.com",
"http": true,
"https": false,
"software": "fuuka",
"boards": ["an", "fit", "k", "mlp", "r9k", "toy"],
"files": ["an", "fit", "k", "r9k", "toy"]
}, {
"uid": 10,
"name": "warosu",
"domain": "fuuka.warosu.org",
"http": true,
"https": true,
"software": "fuuka",
"boards": ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "q", "tg", "vr"],
"files": ["3", "cgl", "ck", "fa", "ic", "jp", "lit", "q", "tg", "vr"]
}, {
"uid": 13,
"name": "Foolz Beta",
"domain": "beta.foolz.us",
"http": true,
"https": true,
"withCredentials": true,
"software": "foolfuuka",
"boards": ["a", "co", "d", "gd", "h", "jp", "m", "mlp", "q", "sp", "tg", "tv", "u", "v", "vg", "vp", "vr", "wsg"],
"files": ["a", "d", "gd", "h", "jp", "m", "q", "tg", "u", "vg", "vp", "vr", "wsg"]
}]

View File

@ -22,14 +22,14 @@
"font-awesome": "git://github.com/MayhemYDG/Font-Awesome.git#df4285951124f9ca1f3907438462e5ba9e464bcb",
"grunt": "~0.4.1",
"grunt-bump": "~0.0.11",
"grunt-concurrent": "~0.3.0",
"grunt-concurrent": "~0.3.1",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-coffee": "~0.7.0",
"grunt-contrib-compress": "~0.5.2",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-watch": "~0.5.0",
"grunt-shell": "~0.3.1",
"grunt-contrib-watch": "~0.5.3",
"grunt-shell": "~0.4.0",
"load-grunt-tasks": "~0.1.0"
},
"repository": {

View File

@ -24,7 +24,8 @@ Redirect =
archives:
'4plebs':
domain: 'archive.4plebs.org'
http: true
http: true
https: true
software: 'foolfuuka'
boards: ['hr', 'tg', 'tv', 'x']
files: ['hr', 'tg', 'tv', 'x']
@ -32,10 +33,10 @@ Redirect =
'fap archive':
domain: 'fuuka.worldathleticproject.org'
http: true
https: false
https: true
software: 'foolfuuka'
boards: ['b', 'e', 'h', 'hc', 'p', 's', 'u']
files: ['b', 'e', 'h', 'hc', 'p', 's', 'u']
files: ['b', 'e', 'h', 'hc', 'p', 's', 'soc', 'sp', 'u']
'Foolz':
domain: 'archive.foolz.us'
@ -59,8 +60,8 @@ Redirect =
https: true
withCredentials: true
software: 'foolfuuka'
boards: ['a', 'co', 'gd', 'h', 'jp', 'm', 'mlp', 'q', 'sp', 'tg', 'tv', 'u', 'v', 'vg', 'vp', 'vr', 'wsg'],
files: ['a', 'gd', 'h', 'jp', 'm', 'q', 'tg', 'u', 'vg', 'vp', 'vr', 'wsg']
boards: ['a', 'co', 'd', 'gd', 'h', 'jp', 'm', 'mlp', 'q', 'sp', 'tg', 'tv', 'u', 'v', 'vg', 'vp', 'vr', 'wsg'],
files: ['a', 'd', 'gd', 'h', 'jp', 'm', 'q', 'tg', 'u', 'vg', 'vp', 'vr', 'wsg']
'Heinessen':
domain: 'archive.heinessen.com'
@ -146,12 +147,7 @@ Redirect =
post: (archive, {boardID, postID}) ->
# For fuuka-based archives:
# https://github.com/eksopl/fuuka/issues/27
protocol = Redirect.protocol archive
# XXX foolz had HSTS set for 120 days, which broke XHR+CORS+Redirection when on HTTP.
# Remove necessary HTTPS procotol in September 2013.
if ['Foolz', 'NSFW Foolz'].contains archive.name
protocol = 'https://'
URL = new String "#{protocol}#{archive.domain}/_/api/chan/post/?board=#{boardID}&num=#{postID}"
URL = new String "#{Redirect.protocol archive}#{archive.domain}/_/api/chan/post/?board=#{boardID}&num=#{postID}"
URL.archive = archive
URL

View File

@ -14,8 +14,5 @@ Anonymize =
$.rm tripcode
delete @nodes.tripcode
if @info.email
if /sage/i.test @info.email
email.href = 'mailto:sage'
else
$.replace email, name
delete @nodes.email
$.replace email, name
delete @nodes.email

View File

@ -184,7 +184,7 @@ PostHiding =
if Conf['Anonymize']
'Anonymous'
else
post.info.name
$('.nameBlock', post.nodes.info).textContent
$.add a, $.tn " #{postInfo}"
post.nodes.stub = $.el 'div',
className: 'stub'

View File

@ -185,7 +185,7 @@ ThreadHiding =
if Conf['Anonymize']
'Anonymous'
else
OP.info.name
$('.nameBlock', OP.nodes.info).textContent
a = ThreadHiding.makeButton thread, 'show'
$.add a, $.tn " #{opInfo} (#{numReplies})"

View File

@ -27,7 +27,6 @@ Build =
date: data.now
dateUTC: data.time
comment: data.com
capcodeReplies: data.capcode_replies
# thread status
isSticky: !!data.sticky
isClosed: !!data.closed
@ -59,7 +58,7 @@ Build =
postID, threadID, boardID
name, capcode, tripcode, uniqueID, email, subject, flagCode, flagName, date, dateUTC
isSticky, isClosed
comment, capcodeReplies
comment
file
} = o
isOP = postID is threadID
@ -201,36 +200,4 @@ Build =
continue if href[0] is '/' # Cross-board quote, or board link
quote.href = "/#{boardID}/res/#{href}" # Fix pathnames
Build.capcodeReplies {boardID, threadID, root: container, capcodeReplies}
container
capcodeReplies: ({boardID, threadID, bq, root, capcodeReplies}) ->
return unless capcodeReplies
generateCapcodeReplies = (capcodeType, array) ->
"<span class=smaller><span class=bold>#{
switch capcodeType
when 'admin'
'Administrator'
when 'mod'
'Moderator'
when 'developer'
'Developer'
} Repl#{if array.length > 1 then 'ies' else 'y'}:</span> #{
array.map (ID) ->
"<a href='/#{boardID}/res/#{threadID}#p#{ID}' class=quotelink>&gt;&gt;#{ID}</a>"
.join ' '
}</span><br>"
html = []
for capcodeType, array of capcodeReplies
html.push generateCapcodeReplies capcodeType, array
bq or= $ 'blockquote', root
$.add bq, [
$.el 'br'
$.el 'br'
$.el 'span',
className: 'capcodeReplies'
innerHTML: html.join ''
]

View File

@ -799,12 +799,8 @@ Config =
#/Mod$/;highlight:mod;op:yes
# Set a custom class for moot:
#/Admin$/;highlight:moot;op:yes
"""
email: """
# Filter any e-mails that are not `sage` on /a/ and /jp/:
#/^(?!sage$)/;boards:a,jp
"""
"""
email: ""
subject: """
# Filter Generals on /v/:
#/general/i;boards:v;op:only

1271
src/General/css/style.css Executable file

File diff suppressed because it is too large Load Diff

View File

@ -34,7 +34,7 @@
<div class='postInfo desktop' id=pi#{postID}>
<input type=checkbox name=#{postID} value=delete>
#{subject}&nbsp;
&nbsp;#{subject}&nbsp;
<span class='nameBlock#{capcodeClass}'>
#{emailStart}
<span class=name>#{name or ''}</span>

View File

@ -68,13 +68,12 @@ class Post
# <br> -> \n
# Remove:
# 'Comment too long'...
# Admin/Mod/Dev replies. (/q/)
# EXIF data. (/p/)
# Rolls. (/tg/)
# Preceding and following new lines.
# Trailing spaces.
bq = @nodes.comment.cloneNode true
nodes = $$ '.abbr, .capcodeReplies, .exif, b', bq
nodes = $$ '.abbr, .exif, b', bq
i = 0
while node = nodes[i++]
$.rm node
@ -108,8 +107,7 @@ class Post
@nodes.quotelinks.push quotelink
# Don't count capcode replies as quotes in OPs. (Admin/Mod/Dev Replies: ...)
return if @isClone or !@isReply and $.hasClass quotelink.parentNode.parentNode, 'capcodeReplies'
return if @isClone
# ES6 Set when?
fullID = "#{match[1]}.#{match[2]}"

View File

@ -31,7 +31,7 @@ DeleteLink =
el: div
order: 40
open: (post) ->
return false if post.isDead or post.board.ID is 'q'
return false if post.isDead
DeleteLink.post = post
node = div.firstChild
node.textContent = 'Delete'
@ -84,7 +84,7 @@ DeleteLink =
delete DeleteLink.cooldown.counting
return
DeleteLink.cooldown.counting = post
length = 30
length = 60
seconds = Math.ceil (length * $.SECOND - (Date.now() - post.info.date)) / $.SECOND
DeleteLink.cooldown.count post, seconds, length, node
count: (post, seconds, length, node) ->

View File

@ -60,11 +60,6 @@ ExpandComment =
href = quote.getAttribute 'href'
continue if href[0] is '/' # Cross-board quote, or board link
quote.href = "/#{post.board}/res/#{href}" # Fix pathnames
Build.capcodeReplies
boardID: post.board.ID
threadID: post.thread.ID
bq: clone
capcodeReplies: postObj.capcode_replies
post.nodes.shortComment = comment
$.replace comment, clone
post.nodes.comment = post.nodes.longComment = clone

View File

@ -54,7 +54,7 @@ ExpandThread =
1
else switch g.BOARD.ID
# XXX boards config
when 'b', 'vg', 'q' then 3
when 'b', 'vg' then 3
when 't' then 1
else 5
posts = $$ ".thread > .replyContainer", threadRoot

View File

@ -2,8 +2,6 @@ ThreadUpdater =
init: ->
return if g.VIEW isnt 'thread' or !Conf['Thread Updater']
checked = if Conf['Auto Update'] then 'checked' else ''
if Conf['Updater and Stats in Header']
@dialog = sc = $.el 'span',
innerHTML: "[<span id=update-status></span><span id=update-timer title='Update now'></span>]\u00A0"
@ -16,12 +14,13 @@ ThreadUpdater =
$.addClass doc, 'float'
$.ready =>
$.addClass doc, 'float'
$.add d.body, sc
$.add d.body, sc
@checkPostCount = 0
@timer = $ '#update-timer', sc
@status = $ '#update-status', sc
@isUpdating = Conf['Auto Update']
$.on @timer, 'click', ThreadUpdater.update
$.on @status, 'click', ThreadUpdater.update
@ -38,7 +37,7 @@ ThreadUpdater =
$.on input, 'change', ThreadUpdater.cb.scrollBG
ThreadUpdater.cb.scrollBG()
else if input.name is 'Auto Update'
$.on input, 'change', ThreadUpdater.update
$.on input, 'change', ThreadUpdater.cb.update
subEntries.push el: el
settings = $.el 'span',
@ -63,7 +62,6 @@ ThreadUpdater =
ThreadUpdater.thread = @
ThreadUpdater.root = @OP.nodes.root.parentNode
ThreadUpdater.lastPost = +ThreadUpdater.root.lastElementChild.id.match(/\d+/)[0]
ThreadUpdater.outdateCount = 0
ThreadUpdater.cb.interval.call $.el 'input', value: Conf['Interval']
@ -82,17 +80,16 @@ ThreadUpdater =
cb:
online: ->
if ThreadUpdater.online = navigator.onLine
if navigator.onLine
ThreadUpdater.outdateCount = 0
ThreadUpdater.setInterval()
ThreadUpdater.update()
ThreadUpdater.set 'status', null, null
else
ThreadUpdater.set 'timer', null
ThreadUpdater.set 'status', 'Offline', 'warning'
ThreadUpdater.cb.autoUpdate()
ThreadUpdater.count true
post: (e) ->
return unless e.detail.threadID is ThreadUpdater.thread.ID
return unless ThreadUpdater.isUpdating and e.detail.threadID is ThreadUpdater.thread.ID
ThreadUpdater.outdateCount = 0
setTimeout ThreadUpdater.update, 1000 if ThreadUpdater.seconds > 2
checkpost: (e) ->
@ -111,28 +108,22 @@ ThreadUpdater =
return if d.hidden
# Reset the counter when we focus this tab.
ThreadUpdater.outdateCount = 0
if ThreadUpdater.seconds > ThreadUpdater.interval
ThreadUpdater.setInterval()
ThreadUpdater.seconds = Math.min ThreadUpdater.seconds, ThreadUpdater.interval
scrollBG: ->
ThreadUpdater.scrollBG = if Conf['Scroll BG']
-> true
else
-> not d.hidden
autoUpdate: ->
if ThreadUpdater.online
ThreadUpdater.timeout()
else
clearTimeout ThreadUpdater.timeoutID
interval: ->
val = +@value
val = parseInt @value, 10
if val < 1 then val = 1
ThreadUpdater.interval = @value = val
$.cb.value.call @
load: (e) ->
{req} = ThreadUpdater
delete ThreadUpdater.req
if e.type isnt 'loadend' # timeout or abort
req.onloadend = null
delete ThreadUpdater.req
if e.type is 'timeout'
ThreadUpdater.set 'status', 'Retrying', null
ThreadUpdater.update()
@ -162,8 +153,6 @@ ThreadUpdater =
if ThreadUpdater.postID
ThreadUpdater.cb.checkpost()
delete ThreadUpdater.req
setInterval: ->
i = ThreadUpdater.interval
# Math.min/max is provably slow: http://jsperf.com/math-s-min-max-vs-homemade/5
@ -177,8 +166,7 @@ ThreadUpdater =
else
i
ThreadUpdater.set 'timer', ThreadUpdater.seconds++
clearTimeout ThreadUpdater.timeoutID
ThreadUpdater.timeout()
ThreadUpdater.count true
intervalShortcut: ->
Settings.open 'Advanced'
@ -195,14 +183,19 @@ ThreadUpdater =
el.textContent = text
el.className = klass if klass isnt undefined
count: (start) ->
clearTimeout ThreadUpdater.timeoutID
ThreadUpdater.timeout() if start and ThreadUpdater.isUpdating and navigator.onLine
timeout: ->
ThreadUpdater.timeoutID = setTimeout ThreadUpdater.timeout, 1000
ThreadUpdater.set 'timer', --ThreadUpdater.seconds
ThreadUpdater.update() if ThreadUpdater.seconds <= 0
sec = ThreadUpdater.seconds--
ThreadUpdater.set 'timer', sec
ThreadUpdater.update() if sec <= 0
update: ->
return unless ThreadUpdater.online
clearTimeout ThreadUpdater.timeoutID
return unless navigator.onLine
ThreadUpdater.count()
if Conf['Auto Update']
ThreadUpdater.set 'timer', '...'
else

View File

@ -19,14 +19,6 @@ ThreadWatcher =
ThreadWatcher.fetchAllStatus()
@db.save()
# XXX tmp conversion from old to new format
$.get 'WatchedThreads', null, ({WatchedThreads}) ->
return unless WatchedThreads
for boardID, threads of ThreadWatcher.convert WatchedThreads
for threadID, data of threads
ThreadWatcher.db.set {boardID, threadID, val: data}
$.delete 'WatchedThreads'
Thread::callbacks.push
name: 'Thread Watcher'
cb: @node

View File

@ -268,27 +268,26 @@ QR =
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
cooldown:
init: ->
return unless Conf['Cooldown']
board = g.BOARD.ID
QR.cooldown.types =
thread: switch board
when 'q' then 86400
when 'b', 'soc', 'r9k' then 600
else 300
sage: if board is 'q' then 600 else 60
file: if board is 'q' then 300 else 30
post: if board is 'q' then 150 else 30
setTimers = (e) => QR.cooldown.types = e.detail
$.on window, 'cooldown:timers', setTimers
$.globalEval 'window.dispatchEvent(new CustomEvent("cooldown:timers", {detail: cooldowns}))'
QR.cooldown.types or= {} # XXX tmp workaround until all pages and the catalogs get the cooldowns var.
$.off window, 'cooldown:timers', setTimers
for type of QR.cooldown.types
QR.cooldown.types[type] = +QR.cooldown.types[type]
QR.cooldown.upSpd = 0
QR.cooldown.upSpdAccuracy = .5
$.get "cooldown.#{board}", {}, (item) ->
QR.cooldown.cooldowns = item["cooldown.#{board}"]
key = "cooldown.#{g.BOARD}"
$.get key, {}, (item) ->
QR.cooldown.cooldowns = item[key]
QR.cooldown.start()
$.sync "cooldown.#{board}", QR.cooldown.sync
$.sync key, QR.cooldown.sync
start: ->
return unless Conf['Cooldown']
return if QR.cooldown.isCounting
@ -304,30 +303,16 @@ QR =
set: (data) ->
return unless Conf['Cooldown']
{req, post, isReply, delay} = data
{req, post, isReply, threadID, delay} = data
start = if req then req.uploadEndTime else Date.now()
if delay
cooldown = {delay}
else
if post.file
upSpd = post.file.size / ((req.uploadEndTime - req.uploadStartTime) / $.SECOND)
if hasFile = !!post.file
upSpd = post.file.size / ((start - req.uploadStartTime) / $.SECOND)
QR.cooldown.upSpdAccuracy = ((upSpd > QR.cooldown.upSpd * .9) + QR.cooldown.upSpdAccuracy) / 2
QR.cooldown.upSpd = upSpd
isSage = /sage/i.test post.email
hasFile = !!post.file
type = unless isReply
'thread'
else if isSage
'sage'
else if hasFile
'file'
else
'post'
cooldown =
isReply: isReply
isSage: isSage
hasFile: hasFile
timeout: start + QR.cooldown.types[type] * $.SECOND
cooldown = {isReply, hasFile, threadID}
QR.cooldown.cooldowns[start] = cooldown
$.set "cooldown.#{g.BOARD}", QR.cooldown.cooldowns
QR.cooldown.start()
@ -347,12 +332,12 @@ QR =
QR.status()
return
setTimeout QR.cooldown.count, $.SECOND
clearTimeout QR.cooldown.timeout
QR.cooldown.timeout = setTimeout QR.cooldown.count, $.SECOND
now = Date.now()
post = QR.posts[0]
now = Date.now()
post = QR.posts[0]
isReply = post.thread isnt 'new'
isSage = /sage/i.test post.email
hasFile = !!post.file
seconds = null
{types, cooldowns, upSpd, upSpdAccuracy} = QR.cooldown
@ -366,26 +351,35 @@ QR =
QR.cooldown.unset start
continue
if isReply is cooldown.isReply
# Only cooldowns relevant to this post can set the seconds value.
# Unset outdated cooldowns that can no longer impact us.
type = unless isReply
'thread'
else if isSage and cooldown.isSage
'sage'
else if hasFile and cooldown.hasFile
'file'
else
'post'
elapsed = Math.floor (now - start) / $.SECOND
if elapsed >= 0 # clock changed since then?
seconds = Math.max seconds, types[type] - elapsed
if Conf['Cooldown Prediction'] and hasFile and upSpd
seconds -= Math.floor post.file.size / upSpd * upSpdAccuracy
seconds = Math.max seconds, 0
unless start <= now <= cooldown.timeout
if 'timeout' of cooldown
# XXX tmp conversion from previous cooldowns
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
elapsed = Math.floor (now - start) / $.SECOND
continue if elapsed < 0 # clock changed since then?
unless isReply
type = 'thread'
else if hasFile
# You can post an image reply immediately after a non-image reply.
unless cooldown.hasFile
seconds = Math.max seconds, 0
continue
type = 'image'
else
type = '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
if seconds and Conf['Cooldown Prediction'] and hasFile and upSpd
seconds -= Math.floor post.file.size / upSpd * upSpdAccuracy
seconds = if seconds > 0 then seconds else 0
# Update the status when we change posting type.
# Don't get stuck at some random number.
# Don't interfere with progress status updates.
@ -575,6 +569,12 @@ QR =
if prev then prev.sub else persona.sub
else
''
if QR.nodes.flag
@flag = if prev
prev.flag
else
persona.flag
@load() if QR.selected is @ # load persona
@select() if select
@unlock()
@ -596,8 +596,8 @@ QR =
lock: (lock=true) ->
@isLocked = lock
return unless @ is QR.selected
for name in ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler']
QR.nodes[name].disabled = lock
for name in ['thread', 'name', 'email', 'sub', 'com', 'fileButton', 'filename', 'spoiler', 'flag'] when node = QR.nodes[name]
node.disabled = lock
@nodes.rm.style.visibility = if lock then 'hidden' else ''
(if lock then $.off else $.on) QR.nodes.filename.previousElementSibling, 'click', QR.openFileInput
@nodes.spoiler.disabled = lock
@ -623,8 +623,10 @@ QR =
load: ->
# Load this post's values.
for name in ['thread', 'name', 'email', 'sub', 'com', 'filename']
QR.nodes[name].value = @[name] or null
for name in ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag']
continue unless node = QR.nodes[name]
node.value = @[name] or node.dataset.default or null
QR.tripcodeHider.call QR.nodes['name']
@showFileData()
@ -635,7 +637,7 @@ QR =
@spoiler = input.checked
return
{name} = input.dataset
@[name] = input.value
@[name] = input.value or input.dataset.default or null
switch name
when 'thread'
QR.status()
@ -660,8 +662,9 @@ QR =
return unless @ is QR.selected
# Do this in case people use extensions
# that do not trigger the `input` event.
for name in ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler']
@save QR.nodes[name]
for name in ['thread', 'name', 'email', 'sub', 'com', 'filename', 'spoiler', 'flag']
continue unless node = QR.nodes[name]
@save node
return
setFile: (@file) ->
@ -966,7 +969,13 @@ QR =
<option value=5>Loop</option>
<option value=4 selected>Other</option>
"""
nodes.flashTag.dataset.default = '4'
$.add nodes.form, nodes.flashTag
if flagSelector = $ '.flagSelector'
nodes.flag = flagSelector.cloneNode true
nodes.flag.dataset.name = 'flag'
nodes.flag.dataset.default = '0'
$.add nodes.form, nodes.flag
# Make a list of threads.
for thread of g.BOARD.threads
@ -1006,10 +1015,13 @@ QR =
$.on nodes[name], 'mouseover', QR.mouseover
# save selected post's data
items = ['name', 'email', 'sub', 'com', 'filename']
items = ['name', 'email', 'sub', 'com', 'filename', 'flag']
i = 0
save = -> QR.selected.save @
while name = items[i++]
$.on nodes[name], 'input', -> QR.selected.save @
continue unless node = nodes[name]
event = if node.nodeName is 'SELECT' then 'change' else 'input'
$.on nodes[name], event, save
$.on nodes['name'], 'blur', QR.tripcodeHider
$.on nodes.thread, 'change', -> QR.selected.save @
@ -1068,7 +1080,7 @@ QR =
# prevent errors
if threadID is 'new'
threadID = null
if ['vg', 'q'].contains(g.BOARD.ID) and !post.sub
if g.BOARD.ID is 'vg' and !post.sub
err = 'New threads require a subject.'
else unless post.file or textOnly = !!$ 'input[name=textonly]', $.id 'postForm'
err = 'No file selected.'
@ -1104,7 +1116,7 @@ QR =
post.lock()
postData =
formData =
resto: threadID
name: post.name
email: post.email
@ -1113,6 +1125,7 @@ QR =
upfile: post.file
filetag: filetag
spoiler: post.spoiler
flag: post.flag
textonly: textOnly
mode: 'regist'
pwd: QR.persona.pwd
@ -1136,7 +1149,7 @@ QR =
[<a href="https://github.com/seaweedchan/4chan-x/wiki/Frequently-Asked-Questions#what-does-4chan-x-encountered-an-error-while-posting-please-try-again-mean" target=_blank>?</a>]
"""
extra =
form: $.formData postData
form: $.formData formData
upCallbacks:
onload: ->
# Upload done, waiting for server response.
@ -1241,9 +1254,9 @@ QR =
postID
}
# Enable auto-posting if we have stuff to post, disable it otherwise.
postsCount = QR.posts.length
QR.cooldown.auto = postsCount > 1 and isReply
# Enable auto-posting if we have stuff left to post, disable it otherwise.
postsCount = QR.posts.length - 1
QR.cooldown.auto = postsCount and isReply
if QR.cooldown.auto and QR.captcha.isEnabled and (captchasCount = QR.captcha.captchas.length) < 3 and captchasCount < postsCount
notif = new Notification 'Quick reply warning',
body: "You are running low on cached captchas. Cache count: #{captchasCount}."
@ -1262,7 +1275,7 @@ QR =
else
post.rm()
QR.cooldown.set {req, post, isReply}
QR.cooldown.set {req, post, isReply, threadID}
URL = unless isReply # new thread
"/#{g.BOARD}/res/#{threadID}"