Merge branch 'v3' of git://github.com/MayhemYDG/4chan-x into v3

Conflicts:
	CHANGELOG.md
	Gruntfile.coffee
	json/archives.json
	package.json
	src/Filtering/Anonymize.coffee
	src/General/Config.coffee
	src/General/lib/post.class
	src/Linkification/Linkify.coffee
	src/Monitoring/ThreadUpdater.coffee
	src/Posting/QuickReply.coffee
This commit is contained in:
Zixaphir 2013-09-20 05:22:42 -07:00
commit 847078db53
13 changed files with 374 additions and 246 deletions

View File

@ -1,6 +1,16 @@
**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/.
### v1.2.39
*2013-09-19*
**seaweedchan**:
- Fix thread updater bug introduced in last version

View File

@ -1,21 +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 +34,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 +48,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 +86,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

@ -5254,7 +5254,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);
});
@ -5318,12 +5319,11 @@
delay: delay
};
} else {
if (post.file) {
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;
}
hasFile = !!post.file;
cooldown = {
isReply: isReply,
hasFile: hasFile,
@ -5380,7 +5380,17 @@
if (elapsed < 0) {
continue;
}
type = !isReply ? 'thread' : hasFile ? 'image' : 'reply';
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);
@ -5393,7 +5403,7 @@
}
if (seconds && Conf['Cooldown Prediction'] && hasFile && upSpd) {
seconds -= Math.floor(post.file.size / upSpd * upSpdAccuracy);
seconds = Math.max(seconds, 0);
seconds = seconds > 0 ? seconds : 0;
}
update = seconds !== null || !!QR.cooldown.seconds;
QR.cooldown.seconds = seconds;
@ -5615,6 +5625,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();
}
@ -5646,7 +5659,7 @@
};
_Class.prototype.lock = function(lock) {
var name, _i, _len, _ref;
var name, node, _i, _len, _ref;
if (lock == null) {
lock = true;
@ -5655,10 +5668,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);
@ -5688,12 +5703,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;
}
this.showFileData();
return QR.characterCount();
@ -5707,7 +5725,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();
@ -5731,15 +5749,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);
}
};
@ -6077,7 +6098,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 class=move><label><input type=checkbox id=autohide title=Auto-hide>\n Quick Reply\n</label><a href=javascript:; class=close title=Close>×</a><select data-name=thread title='Create a new thread / Reply'><option value=new>New thread</option></select></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><a id=qr-filerm href=javascript:; title='Remove file'>×</a><a id=dump-button title='Dump list'>+</a></span></span><label id=qr-spoiler-label><input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=70></label><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>")
@ -6133,8 +6154,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,
@ -6169,16 +6197,18 @@
return QR.selected.nodes.spoiler.click();
});
$.on(nodes.fileInput, 'change', QR.handleFiles);
items = ['name', 'email', 'sub', 'com', 'filename'];
items = ['name', 'email', 'sub', 'com', 'filename', 'flag'];
i = 0;
while (name = items[i++]) {
$.on(nodes[name], 'input', function() {
return QR.selected.save(this);
});
}
$.on(nodes.thread, 'change', function() {
save = function() {
return QR.selected.save(this);
});
};
while (name = items[i++]) {
if (!(node = nodes[name])) {
continue;
}
event = node.nodeName === 'SELECT' ? 'change' : 'input';
$.on(nodes[name], event, save);
}
if (Conf['Remember QR Size']) {
$.get('QR Size', '', function(item) {
return nodes.com.style.cssText = item['QR Size'];
@ -6200,7 +6230,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();
@ -6264,7 +6294,7 @@
d.activeElement.blur();
}
post.lock();
postData = {
formData = {
resto: threadID,
name: post.name,
email: post.email,
@ -6273,6 +6303,7 @@
upfile: post.file,
filetag: filetag,
spoiler: post.spoiler,
flag: post.flag,
textonly: textOnly,
mode: 'regist',
pwd: QR.persona.pwd,
@ -6294,7 +6325,7 @@
}
};
extra = {
form: $.formData(postData),
form: $.formData(formData),
upCallbacks: {
onload: function() {
QR.req.isUploadFinished = true;
@ -6378,8 +6409,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 + ".",
@ -7494,7 +7525,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);
},
@ -7808,7 +7839,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>",
@ -7828,6 +7858,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 = [];
@ -7845,7 +7876,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
@ -7875,7 +7906,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']
}));
@ -7892,19 +7922,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;
@ -7934,9 +7963,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() {
@ -7945,17 +7972,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;
}
@ -7966,9 +7986,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();
@ -7998,9 +8018,8 @@
ThreadUpdater.set('status', text, klass);
}
if (ThreadUpdater.postID) {
ThreadUpdater.cb.checkpost();
return ThreadUpdater.cb.checkpost();
}
return delete ThreadUpdater.req;
}
},
setInterval: function() {
@ -8013,8 +8032,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;
@ -8036,20 +8054,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 {
@ -8235,27 +8262,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

View File

@ -5266,7 +5266,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);
});
@ -5330,12 +5331,11 @@
delay: delay
};
} else {
if (post.file) {
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;
}
hasFile = !!post.file;
cooldown = {
isReply: isReply,
hasFile: hasFile,
@ -5392,7 +5392,17 @@
if (elapsed < 0) {
continue;
}
type = !isReply ? 'thread' : hasFile ? 'image' : 'reply';
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);
@ -5405,7 +5415,7 @@
}
if (seconds && Conf['Cooldown Prediction'] && hasFile && upSpd) {
seconds -= Math.floor(post.file.size / upSpd * upSpdAccuracy);
seconds = Math.max(seconds, 0);
seconds = seconds > 0 ? seconds : 0;
}
update = seconds !== null || !!QR.cooldown.seconds;
QR.cooldown.seconds = seconds;
@ -5621,6 +5631,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();
}
@ -5652,7 +5665,7 @@
};
_Class.prototype.lock = function(lock) {
var name, _i, _len, _ref;
var name, node, _i, _len, _ref;
if (lock == null) {
lock = true;
@ -5661,10 +5674,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);
@ -5694,12 +5709,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;
}
this.showFileData();
return QR.characterCount();
@ -5713,7 +5731,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();
@ -5737,15 +5755,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);
}
};
@ -6081,7 +6102,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 class=move><label><input type=checkbox id=autohide title=Auto-hide>\n Quick Reply\n</label><a href=javascript:; class=close title=Close>×</a><select data-name=thread title='Create a new thread / Reply'><option value=new>New thread</option></select></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><a id=qr-filerm href=javascript:; title='Remove file'>×</a><a id=dump-button title='Dump list'>+</a></span></span><label id=qr-spoiler-label><input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=70></label><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>")
@ -6137,8 +6158,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,
@ -6167,16 +6195,18 @@
return QR.selected.nodes.spoiler.click();
});
$.on(nodes.fileInput, 'change', QR.handleFiles);
items = ['name', 'email', 'sub', 'com', 'filename'];
items = ['name', 'email', 'sub', 'com', 'filename', 'flag'];
i = 0;
while (name = items[i++]) {
$.on(nodes[name], 'input', function() {
return QR.selected.save(this);
});
}
$.on(nodes.thread, 'change', function() {
save = function() {
return QR.selected.save(this);
});
};
while (name = items[i++]) {
if (!(node = nodes[name])) {
continue;
}
event = node.nodeName === 'SELECT' ? 'change' : 'input';
$.on(nodes[name], event, save);
}
QR.persona.init();
new QR.post(true);
QR.status();
@ -6187,7 +6217,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();
@ -6251,7 +6281,7 @@
d.activeElement.blur();
}
post.lock();
postData = {
formData = {
resto: threadID,
name: post.name,
email: post.email,
@ -6260,6 +6290,7 @@
upfile: post.file,
filetag: filetag,
spoiler: post.spoiler,
flag: post.flag,
textonly: textOnly,
mode: 'regist',
pwd: QR.persona.pwd,
@ -6281,7 +6312,7 @@
}
};
extra = {
form: $.formData(postData),
form: $.formData(formData),
upCallbacks: {
onload: function() {
QR.req.isUploadFinished = true;
@ -6365,8 +6396,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 + ".",
@ -7481,7 +7512,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);
},
@ -7795,7 +7826,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>",
@ -7815,6 +7845,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 = [];
@ -7832,7 +7863,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
@ -7862,7 +7893,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']
}));
@ -7879,19 +7909,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;
@ -7921,9 +7950,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() {
@ -7932,17 +7959,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;
}
@ -7953,9 +7973,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();
@ -7985,9 +8005,8 @@
ThreadUpdater.set('status', text, klass);
}
if (ThreadUpdater.postID) {
ThreadUpdater.cb.checkpost();
return ThreadUpdater.cb.checkpost();
}
return delete ThreadUpdater.req;
}
},
setInterval: function() {
@ -8000,8 +8019,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;
@ -8023,20 +8041,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 {
@ -8222,27 +8249,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

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

@ -23,14 +23,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

@ -15,4 +15,4 @@ Anonymize =
delete @nodes.tripcode
if @info.email
$.replace email, name
delete @nodes.email
delete @nodes.email

View File

@ -453,9 +453,8 @@ Config =
#/Mod$/;highlight:mod;op:yes
# Set a custom class for moot:
#/Admin$/;highlight:moot;op:yes
"""
email: ""
"""
email: ""
subject: """
# Filter Generals on /v/:
#/general/i;boards:v;op:only

View File

@ -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

@ -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>"
@ -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']
@ -81,17 +79,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) ->
@ -110,28 +107,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()
@ -161,8 +152,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
@ -176,8 +165,7 @@ ThreadUpdater =
else
i
ThreadUpdater.set 'timer', ThreadUpdater.seconds++
clearTimeout ThreadUpdater.timeoutID
ThreadUpdater.timeout()
ThreadUpdater.count true
intervalShortcut: ->
Settings.open 'Advanced'
@ -194,14 +182,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

@ -31,14 +31,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,6 +268,7 @@ 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:
@ -307,11 +308,10 @@ QR =
if delay
cooldown = {delay}
else
if post.file
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
hasFile = !!post.file
cooldown = {isReply, hasFile, threadID}
QR.cooldown.cooldowns[start] = cooldown
$.set "cooldown.#{g.BOARD}", QR.cooldown.cooldowns
@ -358,24 +358,28 @@ QR =
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
# reply cooldown with a reply, thread cooldown with a thread
elapsed = Math.floor (now - start) / $.SECOND
continue if elapsed < 0 # clock changed since then?
type = unless isReply
'thread'
unless isReply
type = 'thread'
else if hasFile
'image'
# You can post an image reply immediately after a non-image reply.
unless cooldown.hasFile
seconds = Math.max seconds, 0
continue
type = 'image'
else
'reply'
maxTimer = Math.max types[type] or 0, types[type + '_intra'] or 0
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
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 = Math.max seconds, 0
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.
@ -565,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()
@ -586,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
@ -612,8 +622,9 @@ 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
@showFileData()
QR.characterCount()
@ -622,7 +633,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()
@ -647,8 +658,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) ->
@ -948,7 +960,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
@ -978,12 +996,15 @@ QR =
$.on nodes.fileExtras, 'click', (e) -> e.stopPropagation()
$.on nodes.spoiler, 'change', -> QR.selected.nodes.spoiler.click()
$.on nodes.fileInput, 'change', QR.handleFiles
# 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 @
$.on nodes.thread, 'change', -> QR.selected.save @
continue unless node = nodes[name]
event = if node.nodeName is 'SELECT' then 'change' else 'input'
$.on nodes[name], event, save
<% if (type === 'userscript') { %>
if Conf['Remember QR Size']
@ -1029,7 +1050,7 @@ QR =
# prevent errors
if threadID is 'new'
threadID = null
if g.BOARD.ID is 'vg' 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.'
@ -1065,7 +1086,7 @@ QR =
post.lock()
postData =
formData =
resto: threadID
name: post.name
email: post.email
@ -1074,6 +1095,7 @@ QR =
upfile: post.file
filetag: filetag
spoiler: post.spoiler
flag: post.flag
textonly: textOnly
mode: 'regist'
pwd: QR.persona.pwd
@ -1097,7 +1119,7 @@ QR =
[<a href="//4chan.org/banned" target=_blank>Banned?</a>] [<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>More info</a>]
"""
extra =
form: $.formData postData
form: $.formData formData
upCallbacks:
onload: ->
# Upload done, waiting for server response.
@ -1202,9 +1224,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}."