Put the old QR back; this is temporary.

This commit is contained in:
Nicolas Stepien 2011-11-26 04:25:06 +01:00
parent 847cef2ae7
commit 2bfc3e649c
2 changed files with 790 additions and 796 deletions

View File

@ -64,7 +64,7 @@
*/
(function() {
var $, $$, DAY, Favicon, HOUR, MINUTE, Main, NAMESPACE, QR, SECOND, Time, anonymize, conf, config, d, expandComment, expandThread, filter, flatten, g, getTitle, imgExpand, imgGif, imgHover, key, keybinds, log, nav, options, quoteBacklink, quoteInline, quoteOP, quotePreview, redirect, replyHiding, reportButton, revealSpoilers, sauce, strikethroughQuotes, threadHiding, threadStats, threading, titlePost, ui, unread, updater, val, watcher;
var $, $$, DAY, Favicon, HOUR, MINUTE, Main, NAMESPACE, Recaptcha, SECOND, Time, anonymize, conf, config, cooldown, d, expandComment, expandThread, filter, flatten, g, getTitle, imgExpand, imgGif, imgHover, key, keybinds, log, nav, options, qr, quoteBacklink, quoteInline, quoteOP, quotePreview, redirect, replyHiding, reportButton, revealSpoilers, sauce, strikethroughQuotes, threadHiding, threadStats, threading, titlePost, ui, unread, updater, val, watcher;
var __slice = Array.prototype.slice;
config = {
@ -104,6 +104,7 @@
'Auto Watch Reply': [false, 'Automatically watch threads that you reply to']
},
Posting: {
'Auto Noko': [true, 'Always redirect to your post'],
'Cooldown': [true, 'Prevent `flood detected` errors'],
'Quick Reply': [true, 'Reply without leaving the page'],
'Persistent QR': [false, 'Quick reply won\'t disappear after posting. Only in replies.'],
@ -894,8 +895,8 @@
case conf.close:
if (o = $('#overlay')) {
$.rm(o);
} else if (QR.qr) {
QR.close();
} else if (qr.qr) {
qr.close();
}
break;
case conf.spoiler:
@ -963,8 +964,8 @@
if ((_ref3 = $('input[value=Previous]')) != null) _ref3.click();
break;
case conf.submit:
if (QR.qr) {
QR.submit.call($('form', QR.qr));
if (qr.qr) {
qr.submit.call($('form', qr.qr));
} else {
$('.postarea form').submit();
}
@ -1060,12 +1061,12 @@
},
qr: function(thread, quote) {
if (quote) {
return QR.quote.call($('.quotejs + a', $('.replyhl', thread) || thread));
return qr.quote.call($('.quotejs + a', $('.replyhl', thread) || thread));
} else {
if (QR.qr) {
return $('textarea', QR.qr).focus();
if (qr.qr) {
return $('textarea', qr.qr).focus();
} else {
return QR.dialog('', thread != null ? thread.firstChild.id : void 0);
return qr.dialog('', thread != null ? thread.firstChild.id : void 0);
}
}
},
@ -1393,26 +1394,65 @@
}
};
QR = {
cooldown = {
init: function() {
var holder;
if (!($('form[name=post]') && $('#recaptcha_response_field'))) return;
g.callbacks.push(function(root) {
var quote;
quote = $('.quotejs + a', root);
return $.on(quote, 'click', QR.quote);
var match, time, _;
if (match = location.search.match(/cooldown=(\d+)/)) {
_ = match[0], time = match[1];
if ($.get(g.BOARD + '/cooldown', 0) < time) {
$.set(g.BOARD + '/cooldown', time);
}
}
if (Date.now() < $.get(g.BOARD + '/cooldown', 0)) cooldown.start();
$.on(window, 'storage', function(e) {
if (e.key === ("" + NAMESPACE + g.BOARD + "/cooldown")) {
return cooldown.start();
}
});
$.add(d.body, $.el('iframe', {
name: 'iframe',
hidden: true
}));
$('#recaptcha_response_field').id = '';
holder = $('#recaptcha_challenge_field_holder');
$.on(holder, 'DOMNodeInserted', QR.captchaNode);
QR.captchaNode({
target: holder.firstChild
});
QR.accept = $('.rules').textContent.match(/: (.+) /)[1].replace(/\w+/g, function(type) {
if (g.REPLY) return $('.postarea form').action += '?cooldown';
},
start: function() {
var submit, _i, _len, _ref;
cooldown.duration = Math.ceil(($.get(g.BOARD + '/cooldown', 0) - Date.now()) / 1000);
if (!(cooldown.duration > 0)) return;
_ref = $$('#com_submit');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
submit = _ref[_i];
submit.value = cooldown.duration;
submit.disabled = true;
}
return setTimeout(cooldown.cb, 1000);
},
cb: function() {
var submit, submits, _i, _j, _len, _len2, _results;
submits = $$('#com_submit');
if (--cooldown.duration) {
setTimeout(cooldown.cb, 1000);
_results = [];
for (_i = 0, _len = submits.length; _i < _len; _i++) {
submit = submits[_i];
_results.push(submit.value = cooldown.duration);
}
return _results;
} else {
for (_j = 0, _len2 = submits.length; _j < _len2; _j++) {
submit = submits[_j];
submit.disabled = false;
submit.value = 'Submit';
}
return qr.autoPost();
}
}
};
qr = {
init: function() {
var iframe;
g.callbacks.push(qr.node);
$.on($('#recaptcha_challenge_field_holder'), 'DOMNodeInserted', qr.captchaNode);
qr.captchaTime = Date.now();
qr.spoiler = $('.postarea label') ? '<label> [<input type=checkbox name=spoiler>Spoiler Image?]</label>' : '';
qr.acceptFiles = $('.rules').textContent.match(/: (.+) /)[1].replace(/\w+/g, function(type) {
switch (type) {
case 'JPG':
return 'image/JPEG';
@ -1422,315 +1462,295 @@
return 'image/' + type;
}
});
QR.MAX_FILE_SIZE = $('input[name=MAX_FILE_SIZE]').value;
QR.spoiler = $('.postarea label') ? ' <label>[<input type=checkbox name=spoiler>Spoiler Image?]</label>' : '';
if (conf['Persistent QR']) {
QR.dialog();
$('textarea', QR.qr).blur();
if (conf['Auto Hide QR']) $('#autohide', QR.qr).checked = true;
}
if (conf['Cooldown']) {
return $.on(window, 'storage', function(e) {
if (e.key === ("" + NAMESPACE + "cooldown/" + g.BOARD)) {
return QR.cooldown();
}
});
}
},
attach: function(file) {
var box, files;
files = $('#files', QR.qr);
box = $.el('li', {
innerHTML: "<img><a class=x>X</a>"
iframe = $.el('iframe', {
name: 'iframe',
hidden: true
});
$.on($('.x', box), 'click', QR.rmThumb);
$.add(box, file);
$.add(files, box);
QR.stats();
return QR.foo();
$.add(d.body, iframe);
return $('#recaptcha_response_field').id = '';
},
rmThumb: function() {
$.rm(this.parentNode);
return QR.stats();
attach: function() {
var fileDiv;
fileDiv = $.el('div', {
innerHTML: "<input type=file name=upfile accept='" + qr.acceptFiles + "'><a>X</a>"
});
$.on(fileDiv.firstChild, 'change', qr.validateFileSize);
$.on(fileDiv.lastChild, 'click', (function() {
return $.rm(this.parentNode);
}));
return $.add($('#files', qr.el), fileDiv);
},
attachNext: function() {
var file, fileDiv, oldFile;
fileDiv = $.rm($('#files div', qr.el));
file = fileDiv.firstChild;
oldFile = $('#qr_form input[type=file]', qr.el);
return $.replace(oldFile, file);
},
autoPost: function() {
if (qr.el && $('#auto', qr.el).checked) {
return qr.submit.call($('form', qr.el));
}
},
captchaNode: function(e) {
QR.captcha = {
challenge: e.target.value,
time: Date.now()
};
return QR.captchaImg();
if (!qr.el) return;
val = e.target.value;
$('img', qr.el).src = "http://www.google.com/recaptcha/api/image?c=" + val;
qr.challenge = val;
return qr.captchaTime = Date.now();
},
captchaImg: function() {
var c, qr;
qr = QR.qr;
if (!qr) return;
c = QR.captcha.challenge;
return $('#captcha img', qr).src = "http://www.google.com/recaptcha/api/image?c=" + c;
},
captchaPush: function(el) {
var captcha, captchas;
captcha = QR.captcha;
captcha.response = el.value;
captchaKeydown: function(e) {
var captchas;
if (!(e.keyCode === 13 && this.value)) return;
captchas = $.get('captchas', []);
captchas.push(captcha);
captchas.push({
challenge: qr.challenge,
response: this.value,
time: qr.captchaTime
});
$.set('captchas', captchas);
el.value = '';
QR.captchaReload();
return QR.stats(captchas);
$('#captchas', qr.el).textContent = captchas.length + ' captchas';
Recaptcha.reload();
this.value = '';
if (!$('textarea', qr.el).value && !$('input[type=file]', qr.el).files.length) {
return e.preventDefault();
}
},
captchaShift: function() {
var captcha, captchas, cutoff;
captchas = $.get('captchas', []);
close: function() {
$.rm(qr.el);
return qr.el = null;
},
dialog: function(link) {
var THREAD_ID, c, email, html, m, name, pwd, submitDisabled, submitValue;
c = d.cookie;
name = (m = c.match(/4chan_name=([^;]+)/)) ? decodeURIComponent(m[1]) : '';
email = (m = c.match(/4chan_email=([^;]+)/)) ? decodeURIComponent(m[1]) : '';
pwd = (m = c.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : $('input[name=pwd]').value;
submitValue = $('#com_submit').value;
submitDisabled = $('#com_submit').disabled ? 'disabled' : '';
THREAD_ID = g.THREAD_ID || $.x('ancestor::div[@class="thread"]/div', link).id;
qr.challenge = $('#recaptcha_challenge_field').value;
html = " <a id=close title=close>X</a> <input type=checkbox id=autohide title=autohide> <div class=move> <input class=inputtext type=text name=name value='" + name + "' placeholder=Name form=qr_form> Quick Reply </div> <div class=autohide> <form name=post action=http://sys.4chan.org/" + g.BOARD + "/post method=POST enctype=multipart/form-data target=iframe id=qr_form> <input type=hidden name=resto value=" + THREAD_ID + "> <input type=hidden name=mode value=regist> <input type=hidden name=recaptcha_challenge_field id=recaptcha_challenge_field> <input type=hidden name=recaptcha_response_field id=recaptcha_response_field> <div><input class=inputtext type=text name=email value='" + email + "' placeholder=E-mail>" + qr.spoiler + "</div> <div><input class=inputtext type=text name=sub placeholder=Subject><input type=submit value=" + submitValue + " id=com_submit " + submitDisabled + "><label><input type=checkbox id=auto>auto</label></div> <div><textarea class=inputtext name=com placeholder=Comment></textarea></div> <div><img src=http://www.google.com/recaptcha/api/image?c=" + qr.challenge + "></div> <div><input class=inputtext type=text autocomplete=off placeholder=Verification id=dummy><span id=captchas>" + ($.get('captchas', []).length) + " captchas</span></div> <div><input type=file name=upfile accept='" + qr.acceptFiles + "'></div> </form> <div id=files></div> <div><input class=inputtext type=password name=pwd value='" + pwd + "' placeholder=Password form=qr_form maxlength=8><a id=attach>attach another file</a></div> </div> <a id=error class=error></a> ";
qr.el = ui.dialog('qr', 'top: 0; left: 0;', html);
$.on($('input[name=name]', qr.el), 'mousedown', function(e) {
return e.stopPropagation();
});
$.on($('input[name=upfile]', qr.el), 'change', qr.validateFileSize);
$.on($('#close', qr.el), 'click', qr.close);
$.on($('form', qr.el), 'submit', qr.submit);
$.on($('#attach', qr.el), 'click', qr.attach);
$.on($('img', qr.el), 'click', Recaptcha.reload);
$.on($('#dummy', qr.el), 'keydown', Recaptcha.listener);
$.on($('#dummy', qr.el), 'keydown', qr.captchaKeydown);
return $.add(d.body, qr.el);
},
message: function(data) {
var duration, fileCount, tc;
$('iframe[name=iframe]').src = 'about:blank';
fileCount = $('#files', qr.el).childElementCount;
tc = data.textContent;
if (tc) {
$.extend($('#error', qr.el), data);
$('#recaptcha_response_field', qr.el).value = '';
$('#autohide', qr.el).checked = false;
if (tc === 'You seem to have mistyped the verification.') {
setTimeout(qr.autoPost, 1000);
} else if (tc === 'Error: Duplicate file entry detected.' && fileCount) {
$('textarea', qr.el).value += '\n' + tc + ' ' + data.href;
qr.attachNext();
setTimeout(qr.autoPost, 1000);
}
return;
}
if (qr.el) {
if (g.REPLY && (conf['Persistent QR'] || fileCount)) {
qr.refresh();
if (fileCount) qr.attachNext();
} else {
qr.close();
}
}
if (conf['Cooldown']) {
duration = qr.sage ? 60 : 30;
$.set(g.BOARD + '/cooldown', Date.now() + duration * 1000);
return cooldown.start();
}
},
node: function(root) {
var quote;
quote = $('a.quotejs:not(:first-child)', root);
return $.on(quote, 'click', qr.quote);
},
postInvalid: function() {
var captcha, captchas, content, cutoff, dummy, response;
content = $('textarea', qr.el).value || $('input[type=file]', qr.el).files.length;
if (!content) return 'Error: No text entered.';
/*
captchas expire after 5 hours (emperically verified). cutoff 5 minutes
before then, b/c posting takes time.
*/
cutoff = Date.now() - 5 * HOUR + 5 * MINUTE;
captchas = $.get('captchas', []);
while (captcha = captchas.shift()) {
if (captcha.time > cutoff) break;
}
$.set('captchas', captchas);
QR.stats(captchas);
return captcha;
},
stats: function(captchas) {
var images, qr;
qr = QR.qr;
captchas || (captchas = $.get('captchas', []));
images = $$('#files input', qr);
return $('#qr_stats', qr).textContent = "" + images.length + " / " + captchas.length;
},
captchaReload: function() {
return window.location = 'javascript:Recaptcha.reload()';
},
change: function(e) {
var file, fr, img;
file = this.files[0];
if (file.size > QR.MAX_FILE_SIZE) {
alert('Error: File too large.');
QR.foo(this);
return;
$('#captchas', qr.el).textContent = captchas.length + ' captchas';
if (!captcha) {
dummy = $('#dummy', qr.el);
if (!(response = dummy.value)) {
return 'You forgot to type in the verification';
}
captcha = {
challenge: qr.challenge,
response: response
};
dummy.value = '';
Recaptcha.reload();
}
if (this.parentNode.className === 'wat') QR.attach(this);
fr = new FileReader();
img = $('img', this.parentNode);
fr.onload = function(e) {
return img.src = e.target.result;
};
return fr.readAsDataURL(file);
$('#recaptcha_challenge_field', qr.el).value = captcha.challenge;
$('#recaptcha_response_field', qr.el).value = captcha.response;
return false;
},
close: function() {
$.rm(QR.qr);
return QR.qr = null;
},
cooldown: function() {
var b, cooldown, n, now;
if (!(g.REPLY && QR.qr)) return;
cooldown = $.get("cooldown/" + g.BOARD, 0);
now = Date.now();
n = Math.ceil((cooldown - now) / 1000);
b = $('form button', QR.qr);
if (n > 0) {
$.extend(b, {
textContent: n,
disabled: true
});
return setTimeout(QR.cooldown, 1000);
quote: function(e) {
var id, s, selection, selectionID, ta, text, _ref;
if (e) e.preventDefault();
if (qr.el) {
$('#autohide', qr.el).checked = false;
} else {
$.extend(b, {
textContent: 'Submit',
disabled: false
});
if ($('#autopost', QR.qr).checked) return QR.submit();
qr.dialog(this);
}
},
foo: function(old) {
var input;
input = $.el('input', {
type: 'file',
name: 'upfile',
accept: QR.accept
});
$.on(input, 'change', QR.change);
if (old) {
return $.replace(old, file);
} else {
return $.add($('.wat', QR.qr), input);
}
},
dialog: function(text, tid) {
var l, qr, ta;
if (text == null) text = '';
tid || (tid = g.THREAD_ID || '');
QR.qr = qr = ui.dialog('qr', 'top: 0; right: 0;', " <a class=close>X</a> <input type=checkbox id=autohide title=autohide> <div class=move> <span id=qr_stats></span> </div> <div class=autohide> <span class=wat><img src=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41Ljg3O4BdAAAAXUlEQVQ4T2NgoAH4DzQTHyZoJckGENJASB6nc9GdCjdo6tSptkCsCPUqVgNAmtFtxiYGUkO0QrBibOqJtWkIGYDTqTgSGOnRiGYQ3mRLKBFhjUZiNCGrIZg3aKsAAGu4rTMFLFBMAAAAAElFTkSuQmCC></span> <input form=qr_form placeholder=Name name=name> <input form=qr_form placeholder=Email name=email> <input form=qr_form placeholder=Subject name=sub> <ul id=files></ul> <form enctype=multipart/form-data method=post action=http://sys.4chan.org/" + g.BOARD + "/post target=iframe id=qr_form> <textarea placeholder=Comment name=com></textarea> <div hidden> <input name=pwd> <input name=mode value=regist> <input name=recaptcha_challenge_field id=challenge> <input name=recaptcha_response_field id=response> </div> <div id=captcha> <div><img></div> <input id=recaptcha_response_field autocomplete=off> </div> <div> <button>Submit</button> " + (g.REPLY ? "<label>[<input type=checkbox id=autopost title=autopost> Autopost]</label>" : '') + " <input form=qr_form placeholder=Thread name=resto value=" + tid + " " + (g.REPLY ? 'hidden' : '') + "> " + QR.spoiler + " </div> </form> </div> <a class=error></a> ");
QR.reset();
if (conf['Cooldown']) QR.cooldown();
QR.foo();
$.on($('.close', qr), 'click', QR.close);
$.on($('form', qr), 'submit', QR.submit);
$.on($('#recaptcha_response_field', qr), 'keydown', QR.keydown);
QR.captchaImg();
QR.stats();
$.add(d.body, qr);
ta = $('textarea', qr);
ta.value = text;
l = text.length;
ta.setSelectionRange(l, l);
return ta.focus();
},
keydown: function(e) {
var kc, v;
kc = e.keyCode;
v = this.value;
if (kc === 8 && !v) {
QR.captchaReload();
return;
}
if (!(e.keyCode === 13 && v)) return;
QR.captchaPush(this);
e.preventDefault();
return QR.submit();
},
quote: function(e, blank) {
var bq, i, id, qr, s, sel, ss, ta, text, tid, v, _base, _ref, _ref2;
if (e != null) e.preventDefault();
tid = (_ref = $.x('ancestor::div[@class="thread"]/div', this)) != null ? _ref.id : void 0;
id = this.textContent;
text = ">>" + id + "\n";
sel = getSelection();
bq = $.x('ancestor::blockquote', sel.anchorNode);
if (id === ((_ref2 = $.x('preceding-sibling::input', bq)) != null ? _ref2.name : void 0)) {
if (s = sel.toString().replace(/\n/g, '\n>')) text += ">" + s + "\n";
}
qr = QR.qr;
if (!qr) {
QR.dialog(text, tid);
return;
}
$('#autohide', qr).checked = false;
ta = $('textarea', qr);
v = ta.value;
ss = ta.selectionStart;
ta.value = v.slice(0, ss) + text + v.slice(ss);
i = ss + text.length;
ta.setSelectionRange(i, i);
ta.focus();
return (_base = $('[name=resto]', qr)).value || (_base.value = tid);
},
receive: function(data) {
var cooldown, href, qr, row, textContent, _ref, _ref2;
$('iframe[name=iframe]').src = 'about:blank';
qr = QR.qr;
row = (_ref = $('#files input[form]', qr)) != null ? _ref.parentNode : void 0;
data = JSON.parse(data);
textContent = data.textContent, href = data.href;
if (QR.op) {
window.location = href;
return;
}
if (textContent) {
$.extend($('a.error', qr), data);
if (textContent === 'Error: Duplicate file entry detected.') {
if (row) $.rm(row);
QR.stats();
setTimeout(QR.submit, 1000);
} else if (textContent === 'You seem to have mistyped the verification.') {
setTimeout(QR.submit, 1000);
selection = window.getSelection();
if (s = selection.toString()) {
selectionID = (_ref = $.x('preceding::input[@type="checkbox"][1]', selection.anchorNode)) != null ? _ref.name : void 0;
if (selectionID === id) {
s = s.replace(/\n/g, '\n>');
text += ">" + s + "\n";
}
return;
}
if (row) $.rm(row);
QR.stats();
if (conf['Persistent QR'] || ((_ref2 = $('#files input', qr)) != null ? _ref2.files.length : void 0)) {
QR.reset();
} else {
QR.close();
}
if (conf['Cooldown']) {
cooldown = Date.now() + (QR.sage ? 60 : 30) * SECOND;
$.set("cooldown/" + g.BOARD, cooldown);
return QR.cooldown();
}
ta = $('textarea', qr.el);
ta.focus();
return ta.value += text;
},
reset: function() {
var c, m, qr, _ref;
qr = QR.qr;
c = d.cookie;
$('[name=name]', qr).value = (m = c.match(/4chan_name=([^;]+)/)) ? decodeURIComponent(m[1]) : '';
$('[name=email]', qr).value = (m = c.match(/4chan_email=([^;]+)/)) ? decodeURIComponent(m[1]) : '';
$('[name=pwd]', qr).value = (m = c.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : $('input[name=pwd]').value;
$('[name=sub]', qr).value = '';
refresh: function() {
var m, newFile, oldFile, _ref;
$('[name=sub]', qr.el).value = '';
$('[name=email]', qr.el).value = (m = d.cookie.match(/4chan_email=([^;]+)/)) ? decodeURIComponent(m[1]) : '';
$('[name=com]', qr.el).value = '';
$('[name=recaptcha_response_field]', qr.el).value = '';
if (!conf['Remember Spoiler']) {
if ((_ref = $('[name=spoiler]', qr)) != null) _ref.checked = false;
if ((_ref = $('[name=spoiler]', qr.el)) != null) _ref.checked = false;
}
return $('textarea', qr).value = '';
oldFile = $('[type=file]', qr.el);
newFile = $.el('input', {
type: 'file',
name: 'upfile',
accept: qr.acceptFiles
});
return $.replace(oldFile, newFile);
},
submit: function(e) {
var captcha, challenge, el, id, input, op, qr, response;
qr = QR.qr;
if ($('textarea', qr).value || $('#files', qr).childNodes.length) {
if ($('form button', qr).disabled) {
$('#autopost', qr).checked = true;
return;
}
} else {
if (e) {
alert('Error: No text entered.');
e.preventDefault();
var id, msg, op;
if (msg = qr.postInvalid()) {
if (typeof e.preventDefault === "function") e.preventDefault();
alert(msg);
if (msg === 'You forgot to type in the verification.') {
$('#dummy', qr.el).focus();
}
return;
}
$('.error', qr).textContent = '';
if (e && (el = $('#recaptcha_response_field', qr)).value) QR.captchaPush(el);
if (!(captcha = QR.captchaShift())) {
alert('You forgot to type in the verification.');
if (e != null) e.preventDefault();
return;
}
challenge = captcha.challenge, response = captcha.response;
$('#challenge', qr).value = challenge;
$('#response', qr).value = response;
if (conf['Auto Hide QR']) $('#autohide', qr).checked = true;
if (input = $('#files input', qr)) input.setAttribute('form', 'qr_form');
if (!e) $('#qr_form', qr).submit();
QR.sage = /sage/i.test($('[name=email]', qr).value);
id = $('input[name=resto]', qr).value;
QR.op = !id;
if (QR.op) $('[name=email]', qr).value = 'noko';
if (conf['Thread Watcher'] && conf['Auto Watch Reply']) {
op = $.id(id);
if ($('img.favicon', op).src === Favicon.empty) {
return watcher.watch(op, id);
if (conf['Auto Watch Reply'] && conf['Thread Watcher']) {
if (g.REPLY && $('img.favicon').src === Favicon.empty) {
watcher.watch(null, g.THREAD_ID);
} else {
id = $('input[name=resto]', qr.el).value;
op = $.id(id);
if ($('img.favicon', op).src === Favicon.empty) watcher.watch(op, id);
}
}
if (!e) this.submit();
$('#error', qr.el).textContent = '';
if (conf['Auto Hide QR']) $('#autohide', qr.el).checked = true;
return qr.sage = /sage/i.test($('input[name=email]', this).value);
},
sys: function() {
var recaptcha;
$.off(d, 'DOMContentLoaded', QR.sys);
var c, duration, id, noko, recaptcha, sage, search, thread, url, watch, _, _ref, _ref2;
if (recaptcha = $('#recaptcha_response_field')) {
$.on(recaptcha, 'keydown', QR.keydown);
$.on(recaptcha, 'keydown', Recaptcha.listener);
return;
}
/*
http://code.google.com/p/chromium/issues/detail?id=20773
Let content scripts see other frames (instead of them being undefined)
http://code.google.com/p/chromium/issues/detail?id=20773
Let content scripts see other frames (instead of them being undefined)
To access the parent, we have to break out of the sandbox and evaluate
in the global context.
To access the parent, we have to break out of the sandbox and evaluate
in the global context.
*/
return $.globalEval(function() {
var data, href, node, textContent, _ref;
$ = function(css) {
return document.querySelector(css);
$.globalEval(function() {
var data, node, _ref;
data = {
to: 'qr.message'
};
if (node = (_ref = $('table font b')) != null ? _ref.firstChild : void 0) {
textContent = node.textContent, href = node.href;
} else {
node = $('meta');
href = node.content.match(/url=(.+)/)[1];
if (node = (_ref = document.querySelector('table font b')) != null ? _ref.firstChild : void 0) {
data.textContent = node.textContent;
data.href = node.href;
}
data = JSON.stringify({
textContent: textContent,
href: href
});
return parent.postMessage(data, '*');
});
c = (_ref = $('b')) != null ? _ref.lastChild : void 0;
if (!(c && c.nodeType === 8)) return;
_ref2 = c.textContent.match(/thread:(\d+),no:(\d+)/), _ = _ref2[0], thread = _ref2[1], id = _ref2[2];
search = location.search;
cooldown = /cooldown/.test(search);
noko = /noko/.test(search);
sage = /sage/.test(search);
watch = /watch/.test(search);
url = "http://boards.4chan.org/" + g.BOARD;
if (watch && thread === '0') {
url += "/res/" + id + "?watch";
} else if (noko) {
url += '/res/';
url += thread === '0' ? id : thread;
}
if (cooldown) {
duration = Date.now() + (sage ? 60 : 30) * 1000;
url += '?cooldown=' + duration;
}
if (noko) url += '#' + id;
return window.location = url;
},
validateFileSize: function(e) {
var file;
if (!(this.files[0].size > $('input[name=MAX_FILE_SIZE]').value)) return;
file = $.el('input', {
type: 'file',
name: 'upfile',
accept: qr.acceptFiles
});
$.on(file, 'change', qr.validateFileSize);
$.replace(this, file);
$('#error', qr.el).textContent = 'Error: File too large.';
return alert('Error: File too large.');
}
};
Recaptcha = {
init: function() {
var el, _i, _len, _ref;
_ref = $$('#recaptcha_table a');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
el = _ref[_i];
el.tabIndex = 1;
}
return $.bind($('#recaptcha_response_field'), 'keydown', Recaptcha.listener);
},
listener: function(e) {
if (e.keyCode === 8 && this.value === '') return Recaptcha.reload();
},
reload: function() {
return window.location = 'javascript:Recaptcha.reload()';
}
};
@ -2872,15 +2892,6 @@
Main = {
init: function() {
var cutoff, hiddenThreads, id, lastChecked, now, pathname, temp, timestamp, _ref;
if (location.hostname === 'sys.4chan.org') {
if (d.body) {
QR.sys();
} else {
$.on(d, 'DOMContentLoaded', QR.sys);
}
return;
}
$.on(window, 'message', Main.message);
pathname = location.pathname.substring(1).split('/');
g.BOARD = pathname[0], temp = pathname[1];
if (temp === 'res') {
@ -2889,6 +2900,15 @@
} else {
g.PAGENUM = parseInt(temp) || 0;
}
if (location.hostname === 'sys.4chan.org') {
if (d.body) {
qr.sys();
} else {
$.on(d, 'DOMContentLoaded', qr.sys);
}
return;
}
$.on(window, 'message', Main.message);
g.hiddenReplies = $.get("hiddenReplies/" + g.BOARD + "/", {});
lastChecked = $.get('lastChecked', 0);
now = Date.now();
@ -2929,7 +2949,7 @@
}
},
onLoad: function() {
var callback, node, nodes, _i, _j, _len, _len2, _ref;
var callback, canPost, form, node, nodes, _i, _j, _len, _len2, _ref;
$.off(d, 'DOMContentLoaded', Main.onLoad);
if (conf['404 Redirect'] && d.title === '4chan - 404' && /^\d+$/.test(g.THREAD_ID)) {
redirect();
@ -2939,9 +2959,21 @@
$.addStyle(Main.css);
threading.init();
Favicon.init();
if ((form = $('form[name=post]')) && (canPost = !!$('#recaptcha_response_field'))) {
Recaptcha.init();
if (g.REPLY && conf['Auto Watch Reply'] && conf['Thread Watcher']) {
$.bind(form, 'submit', function() {
if ($('img.favicon').src === Favicon.empty) {
return watcher.watch(null, g.THREAD_ID);
}
});
}
}
if (conf['Auto Noko'] && canPost) form.action += '?noko';
if (conf['Cooldown'] && canPost) cooldown.init();
if (conf['Image Expansion']) imgExpand.init();
if (conf['Reveal Spoilers'] && $('.postarea label')) revealSpoilers.init();
if (conf['Quick Reply']) QR.init();
if (conf['Quick Reply']) qr.init();
if (conf['Thread Watcher']) watcher.init();
if (conf['Keybinds']) keybinds.init();
if (g.REPLY) {
@ -2950,6 +2982,10 @@
if (conf['Reply Navigation']) nav.init();
if (conf['Post in Title']) titlePost.init();
if (conf['Unread Count']) unread.init();
if (conf['Quick Reply'] && conf['Persistent QR'] && canPost) {
qr.dialog();
if (conf['Auto Hide QR']) $('#autohide', qr.el).checked = true;
}
} else {
if (conf['Thread Hiding']) threadHiding.init();
if (conf['Thread Expansion']) expandThread.init();
@ -2975,7 +3011,7 @@
message: function(e) {
var data, origin;
origin = e.origin, data = e.data;
if (origin === 'http://sys.4chan.org') return QR.receive(data);
if (origin === 'http://sys.4chan.org') return qr.message(data);
},
node: function(e) {
var callback, target, _i, _len, _ref, _results;
@ -3001,7 +3037,7 @@
div.dialog > div.move {\
cursor: move;\
}\
label, a, .favicon {\
label, a, .favicon, #qr img {\
cursor: pointer;\
}\
\
@ -3011,6 +3047,12 @@
.error {\
color: red;\
}\
#error {\
cursor: default;\
}\
#error[href] {\
cursor: pointer;\
}\
td.replyhider {\
vertical-align: top;\
}\
@ -3091,6 +3133,47 @@
resize: vertical;\
width: 100%;\
}\
\
#qr {\
position: fixed;\
max-height: 100%;\
overflow-x: hidden;\
overflow-y: auto;\
}\
#qr > div.move {\
text-align: right;\
}\
#qr input[name=name] {\
float: left;\
}\
#qr_form {\
clear: left;\
}\
#qr_form, #qr #com_submit, #qr input[name=upfile] {\
margin: 0;\
}\
#qr textarea {\
width: 100%;\
height: 125px;\
}\
#qr #close, #qr #autohide {\
float: right;\
}\
#qr:not(:hover) > #autohide:checked ~ .autohide {\
height: 0;\
overflow: hidden;\
}\
/* http://stackoverflow.com/questions/2610497/change-an-inputs-html5-placeholder-color-with-css */\
#qr input::-webkit-input-placeholder {\
color: grey;\
}\
#qr input:-moz-placeholder {\
color: grey;\
}\
/* qr reCAPTCHA */\
#qr img {\
border: 1px solid #AAA;\
}\
\
#updater {\
position: fixed;\
@ -3163,102 +3246,6 @@
#files > input {\
display: block;\
}\
#qr {\
position: fixed;\
}\
#qr .close, #qr #autohide {\
float: right;\
}\
#qr > .move {\
text-align: right;\
}\
#qr .autohide > input {\
width: 90px;\
}\
#qr #autopost {\
width: auto;\
}\
#qr #recaptcha_response_field {\
width: 100%;\
}\
#qr form {\
margin: 0;\
}\
#qr .autohide {\
clear: both;\
}\
#qr:not(:hover) #autohide:checked ~ .autohide {\
height: 0;\
overflow: hidden;\
}\
#qr textarea {\
border: 0;\
height: 150px;\
width: 100%;\
}\
#qr #captcha {\
position: relative;\
}\
#qr #files {\
width: 300px;\
white-space: nowrap;\
overflow: auto;\
margin: 0;\
padding: 0;\
}\
#qr #files li {\
position: relative;\
display: inline-block;\
width: 100px;\
height: 100px;\
overflow: hidden;\
}\
#qr #files a {\
position: absolute;\
left: 0;\
font-size: 50px;\
color: red;\
z-index: 1;\
}\
#qr #cl {\
right: 0;\
padding: 2px;\
position: absolute;\
}\
#qr #files input {\
/* cannot use `display: none;`\
https://bugs.webkit.org/show_bug.cgi?id=58208\
http://code.google.com/p/chromium/issues/detail?id=78961\
*/\
font-size: 100px;\
opacity: 0;\
}\
#qr #files img {\
position: absolute;\
left: 0;\
max-height: 100px;\
max-width: 100px;\
}\
#qr input[name=resto] {\
width: 80px;\
}\
#qr button + input[type=file] {\
position: absolute;\
opacity: 0;\
pointer-events: none;\
}\
#qr .wat {\
display: inline-block;\
width: 16px;\
overflow: hidden;\
position: relative;\
vertical-align: text-top;\
}\
#qr .wat input {\
opacity: 0;\
position: absolute;\
left: 0;\
}\
'
};

View File

@ -31,6 +31,7 @@ config =
'Auto Watch': [true, 'Automatically watch threads that you start']
'Auto Watch Reply': [false, 'Automatically watch threads that you reply to']
Posting:
'Auto Noko': [true, 'Always redirect to your post']
'Cooldown': [true, 'Prevent `flood detected` errors']
'Quick Reply': [true, 'Reply without leaving the page']
'Persistent QR': [false, 'Quick reply won\'t disappear after posting. Only in replies.']
@ -642,8 +643,8 @@ keybinds =
when conf.close
if o = $ '#overlay'
$.rm o
else if QR.qr
QR.close()
else if qr.qr
qr.close()
when conf.spoiler
ta = e.target
return unless ta.nodeName is 'TEXTAREA'
@ -694,8 +695,8 @@ keybinds =
when conf.previousPage
$('input[value=Previous]')?.click()
when conf.submit
if QR.qr
QR.submit.call $ 'form', QR.qr
if qr.qr
qr.submit.call $ 'form', qr.qr
else
$('.postarea form').submit()
when conf.unreadCountTo0
@ -740,12 +741,12 @@ keybinds =
qr: (thread, quote) ->
if quote
QR.quote.call $ '.quotejs + a', $('.replyhl', thread) or thread
qr.quote.call $ '.quotejs + a', $('.replyhl', thread) or thread
else
if QR.qr
$('textarea', QR.qr).focus()
if qr.qr
$('textarea', qr.qr).focus()
else
QR.dialog '', thread?.firstChild.id
qr.dialog '', thread?.firstChild.id
open: (thread, tab) ->
id = thread.firstChild.id
@ -1033,25 +1034,49 @@ options =
conf['backlink'] = @value
$('#backlinkPreview').textContent = conf['backlink'].replace /%id/, '123456789'
QR =
#captcha caching for report form
#report queueing
#check if captchas can be reused on eg dup file error
cooldown =
#TODO merge into qr
init: ->
#can't reply in some stickies, recaptcha may be blocked, eg by noscript
return unless $('form[name=post]') and $('#recaptcha_response_field')
g.callbacks.push (root) ->
quote = $ '.quotejs + a', root
$.on quote, 'click', QR.quote
$.add d.body, $.el 'iframe',
name: 'iframe'
hidden: true
# nuke id so qr's field focuses on recaptcha reload, instead of normal form's
$('#recaptcha_response_field').id = ''
holder = $ '#recaptcha_challenge_field_holder'
$.on holder, 'DOMNodeInserted', QR.captchaNode
QR.captchaNode target: holder.firstChild
QR.accept = $('.rules').textContent.match(/: (.+) /)[1].replace /\w+/g, (type) ->
if match = location.search.match /cooldown=(\d+)/
[_, time] = match
$.set g.BOARD+'/cooldown', time if $.get(g.BOARD+'/cooldown', 0) < time
cooldown.start() if Date.now() < $.get g.BOARD+'/cooldown', 0
$.on window, 'storage', (e) -> cooldown.start() if e.key is "#{NAMESPACE}#{g.BOARD}/cooldown"
$('.postarea form').action += '?cooldown' if g.REPLY
start: ->
cooldown.duration = Math.ceil ($.get(g.BOARD+'/cooldown', 0) - Date.now()) / 1000
return unless cooldown.duration > 0
for submit in $$ '#com_submit'
submit.value = cooldown.duration
submit.disabled = true
setTimeout cooldown.cb, 1000
cb: ->
submits = $$ '#com_submit'
if --cooldown.duration
setTimeout cooldown.cb, 1000
for submit in submits
submit.value = cooldown.duration
else
for submit in submits
submit.disabled = false
submit.value = 'Submit'
qr.autoPost()
qr =
# TODO
# error handling / logging
# persistent captcha
# rm Recaptcha
# email reverts
init: ->
g.callbacks.push qr.node
$.on $('#recaptcha_challenge_field_holder'), 'DOMNodeInserted', qr.captchaNode
qr.captchaTime = Date.now()
qr.spoiler = if $('.postarea label') then '<label> [<input type=checkbox name=spoiler>Spoiler Image?]</label>' else ''
qr.acceptFiles = $('.rules').textContent.match(/: (.+) /)[1].replace /\w+/g, (type) ->
switch type
when 'JPG'
'image/JPEG'
@ -1059,281 +1084,295 @@ QR =
'application/' + type
else
'image/' + type
QR.MAX_FILE_SIZE = $('input[name=MAX_FILE_SIZE]').value
QR.spoiler = if $('.postarea label') then ' <label>[<input type=checkbox name=spoiler>Spoiler Image?]</label>' else ''
if conf['Persistent QR']
QR.dialog()
$('textarea', QR.qr).blur()
if conf['Auto Hide QR']
$('#autohide', QR.qr).checked = true
if conf['Cooldown']
$.on window, 'storage', (e) -> QR.cooldown() if e.key is "#{NAMESPACE}cooldown/#{g.BOARD}"
attach: (file) ->
files = $ '#files', QR.qr
box = $.el 'li',
innerHTML: "<img><a class=x>X</a>"
$.on $('.x', box), 'click', QR.rmThumb
$.add box, file
$.add files, box
QR.stats()
QR.foo()
rmThumb: ->
$.rm @parentNode
QR.stats()
iframe = $.el 'iframe',
name: 'iframe'
hidden: true
$.add d.body, iframe
#hack - nuke id so it doesn't grab focus when reloading
$('#recaptcha_response_field').id = ''
attach: ->
fileDiv = $.el 'div', innerHTML: "<input type=file name=upfile accept='#{qr.acceptFiles}'><a>X</a>"
$.on fileDiv.firstChild, 'change', qr.validateFileSize
$.on fileDiv.lastChild, 'click', (-> $.rm @parentNode)
$.add $('#files', qr.el), fileDiv
attachNext: ->
fileDiv = $.rm $('#files div', qr.el)
file = fileDiv.firstChild
oldFile = $ '#qr_form input[type=file]', qr.el
$.replace oldFile, file
autoPost: ->
if qr.el and $('#auto', qr.el).checked
qr.submit.call $ 'form', qr.el
captchaNode: (e) ->
QR.captcha =
challenge: e.target.value
time: Date.now()
QR.captchaImg()
captchaImg: ->
{qr} = QR
return unless qr
c = QR.captcha.challenge
$('#captcha img', qr).src = "http://www.google.com/recaptcha/api/image?c=#{c}"
captchaPush: (el) ->
{captcha} = QR
captcha.response = el.value
return unless qr.el
val = e.target.value
$('img', qr.el).src = "http://www.google.com/recaptcha/api/image?c=" + val
qr.challenge = val
qr.captchaTime = Date.now()
captchaKeydown: (e) ->
return unless e.keyCode is 13 and @value #enter, captcha filled
captchas = $.get 'captchas', []
captchas.push captcha
captchas.push
challenge: qr.challenge
response: @value
time: qr.captchaTime
$.set 'captchas', captchas
el.value = ''
QR.captchaReload()
QR.stats captchas
captchaShift: ->
captchas = $.get 'captchas', []
$('#captchas', qr.el).textContent = captchas.length + ' captchas'
Recaptcha.reload()
@value = ''
if !$('textarea', qr.el).value and !$('input[type=file]', qr.el).files.length
e.preventDefault()
close: ->
$.rm qr.el
qr.el = null
dialog: (link) ->
c = d.cookie
name = if m = c.match(/4chan_name=([^;]+)/) then decodeURIComponent m[1] else ''
email = if m = c.match(/4chan_email=([^;]+)/) then decodeURIComponent m[1] else ''
pwd = if m = c.match(/4chan_pass=([^;]+)/) then decodeURIComponent m[1] else $('input[name=pwd]').value
submitValue = $('#com_submit').value
submitDisabled = if $('#com_submit').disabled then 'disabled' else ''
#FIXME inlined cross-thread quotes
THREAD_ID = g.THREAD_ID or $.x('ancestor::div[@class="thread"]/div', link).id
qr.challenge = $('#recaptcha_challenge_field').value
html = "
<a id=close title=close>X</a>
<input type=checkbox id=autohide title=autohide>
<div class=move>
<input class=inputtext type=text name=name value='#{name}' placeholder=Name form=qr_form>
Quick Reply
</div>
<div class=autohide>
<form name=post action=http://sys.4chan.org/#{g.BOARD}/post method=POST enctype=multipart/form-data target=iframe id=qr_form>
<input type=hidden name=resto value=#{THREAD_ID}>
<input type=hidden name=mode value=regist>
<input type=hidden name=recaptcha_challenge_field id=recaptcha_challenge_field>
<input type=hidden name=recaptcha_response_field id=recaptcha_response_field>
<div><input class=inputtext type=text name=email value='#{email}' placeholder=E-mail>#{qr.spoiler}</div>
<div><input class=inputtext type=text name=sub placeholder=Subject><input type=submit value=#{submitValue} id=com_submit #{submitDisabled}><label><input type=checkbox id=auto>auto</label></div>
<div><textarea class=inputtext name=com placeholder=Comment></textarea></div>
<div><img src=http://www.google.com/recaptcha/api/image?c=#{qr.challenge}></div>
<div><input class=inputtext type=text autocomplete=off placeholder=Verification id=dummy><span id=captchas>#{$.get('captchas', []).length} captchas</span></div>
<div><input type=file name=upfile accept='#{qr.acceptFiles}'></div>
</form>
<div id=files></div>
<div><input class=inputtext type=password name=pwd value='#{pwd}' placeholder=Password form=qr_form maxlength=8><a id=attach>attach another file</a></div>
</div>
<a id=error class=error></a>
"
qr.el = ui.dialog 'qr', 'top: 0; left: 0;', html
$.on $('input[name=name]', qr.el), 'mousedown', (e) -> e.stopPropagation()
$.on $('input[name=upfile]', qr.el), 'change', qr.validateFileSize
$.on $('#close', qr.el), 'click', qr.close
$.on $('form', qr.el), 'submit', qr.submit
$.on $('#attach', qr.el), 'click', qr.attach
$.on $('img', qr.el), 'click', Recaptcha.reload
$.on $('#dummy', qr.el), 'keydown', Recaptcha.listener
$.on $('#dummy', qr.el), 'keydown', qr.captchaKeydown
$.add d.body, qr.el
message: (data) ->
$('iframe[name=iframe]').src = 'about:blank'
fileCount = $('#files', qr.el).childElementCount
tc = data.textContent
if tc # error message
$.extend $('#error', qr.el), data
$('#recaptcha_response_field', qr.el).value = ''
$('#autohide', qr.el).checked = false
if tc is 'You seem to have mistyped the verification.'
setTimeout qr.autoPost, 1000
else if tc is 'Error: Duplicate file entry detected.' and fileCount
$('textarea', qr.el).value += '\n' + tc + ' ' + data.href
qr.attachNext()
setTimeout qr.autoPost, 1000
return
if qr.el
if g.REPLY and (conf['Persistent QR'] or fileCount)
qr.refresh()
if fileCount
qr.attachNext()
else
qr.close()
if conf['Cooldown']
duration = if qr.sage then 60 else 30
$.set g.BOARD+'/cooldown', Date.now() + duration * 1000
cooldown.start()
node: (root) ->
quote = $ 'a.quotejs:not(:first-child)', root
$.on quote, 'click', qr.quote
postInvalid: ->
content = $('textarea', qr.el).value or $('input[type=file]', qr.el).files.length
return 'Error: No text entered.' unless content
###
captchas expire after 5 hours (emperically verified). cutoff 5 minutes
before then, b/c posting takes time.
###
cutoff = Date.now() - 5*HOUR + 5*MINUTE
captchas = $.get 'captchas', []
while captcha = captchas.shift()
if captcha.time > cutoff
break
$.set 'captchas', captchas
QR.stats captchas
captcha
stats: (captchas) ->
{qr} = QR
captchas or= $.get 'captchas', []
images = $$ '#files input', qr
$('#qr_stats', qr).textContent = "#{images.length} / #{captchas.length}"
captchaReload: ->
window.location = 'javascript:Recaptcha.reload()'
change: (e) ->
file = @files[0]
if file.size > QR.MAX_FILE_SIZE
alert 'Error: File too large.'
QR.foo @
return
if @parentNode.className is 'wat'
QR.attach @
fr = new FileReader()
img = $ 'img', @parentNode
fr.onload = (e) ->
img.src = e.target.result
fr.readAsDataURL file
close: ->
$.rm QR.qr
QR.qr = null
cooldown: ->
return unless g.REPLY and QR.qr
cooldown = $.get "cooldown/#{g.BOARD}", 0
now = Date.now()
n = Math.ceil (cooldown - now) / 1000
b = $ 'form button', QR.qr
if n > 0
$.extend b,
textContent: n
disabled: true
setTimeout QR.cooldown, 1000
$('#captchas', qr.el).textContent = captchas.length + ' captchas'
unless captcha
dummy = $ '#dummy', qr.el
return 'You forgot to type in the verification' unless response = dummy.value
captcha =
challenge: qr.challenge
response: response
dummy.value = ''
Recaptcha.reload()
$('#recaptcha_challenge_field', qr.el).value = captcha.challenge
$('#recaptcha_response_field', qr.el).value = captcha.response
false
quote: (e) ->
e.preventDefault() if e
if qr.el
$('#autohide', qr.el).checked = false
else
$.extend b,
textContent: 'Submit'
disabled: false
QR.submit() if $('#autopost', QR.qr).checked
foo: (old) ->
input = $.el 'input',
type: 'file'
name: 'upfile'
accept: QR.accept
$.on input, 'change', QR.change
if old
$.replace old, file
else
$.add $('.wat', QR.qr), input
dialog: (text='', tid) ->
tid or= g.THREAD_ID or ''
QR.qr = qr = ui.dialog 'qr', 'top: 0; right: 0;', "
<a class=close>X</a>
<input type=checkbox id=autohide title=autohide>
<div class=move>
<span id=qr_stats></span>
</div>
<div class=autohide>
<span class=wat><img src=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41Ljg3O4BdAAAAXUlEQVQ4T2NgoAH4DzQTHyZoJckGENJASB6nc9GdCjdo6tSptkCsCPUqVgNAmtFtxiYGUkO0QrBibOqJtWkIGYDTqTgSGOnRiGYQ3mRLKBFhjUZiNCGrIZg3aKsAAGu4rTMFLFBMAAAAAElFTkSuQmCC></span>
<input form=qr_form placeholder=Name name=name>
<input form=qr_form placeholder=Email name=email>
<input form=qr_form placeholder=Subject name=sub>
<ul id=files></ul>
<form enctype=multipart/form-data method=post action=http://sys.4chan.org/#{g.BOARD}/post target=iframe id=qr_form>
<textarea placeholder=Comment name=com></textarea>
<div hidden>
<input name=pwd>
<input name=mode value=regist>
<input name=recaptcha_challenge_field id=challenge>
<input name=recaptcha_response_field id=response>
</div>
<div id=captcha>
<div><img></div>
<input id=recaptcha_response_field autocomplete=off>
</div>
<div>
<button>Submit</button>
#{if g.REPLY then "<label>[<input type=checkbox id=autopost title=autopost> Autopost]</label>" else ''}
<input form=qr_form placeholder=Thread name=resto value=#{tid} #{if g.REPLY then 'hidden' else ''}>
#{QR.spoiler}
</div>
</form>
</div>
<a class=error></a>
"
#XXX use dom methods to set values instead of injecting raw user input into your html -_-;
QR.reset()
QR.cooldown() if conf['Cooldown']
QR.foo()
$.on $('.close', qr), 'click', QR.close
$.on $('form', qr), 'submit', QR.submit
$.on $('#recaptcha_response_field', qr), 'keydown', QR.keydown
QR.captchaImg()
QR.stats()
$.add d.body, qr
ta = $ 'textarea', qr
ta.value = text
l = text.length
ta.setSelectionRange l, l
ta.focus()
keydown: (e) ->
kc = e.keyCode
v = @value
if kc is 8 and not v #backspace, empty
QR.captchaReload()
return
return unless e.keyCode is 13 and v #enter, not empty
QR.captchaPush @
e.preventDefault()
QR.submit() #derpy, but prevents checking for content twice
quote: (e, blank) ->
e?.preventDefault()
tid = $.x('ancestor::div[@class="thread"]/div', @)?.id
qr.dialog @
id = @textContent
text = ">>#{id}\n"
sel = getSelection()
bq = $.x('ancestor::blockquote', sel.anchorNode)
if id == $.x('preceding-sibling::input', bq)?.name
if s = sel.toString().replace /\n/g, '\n>'
text += ">#{s}\n"
{qr} = QR
if not qr
QR.dialog text, tid
return
$('#autohide', qr).checked = false
ta = $ 'textarea', qr
v = ta.value
ss = ta.selectionStart
ta.value = v[0...ss] + text + v[ss..]
i = ss + text.length
ta.setSelectionRange i, i
ta.focus()
$('[name=resto]', qr).value or= tid
receive: (data) ->
$('iframe[name=iframe]').src = 'about:blank'
{qr} = QR
row = $('#files input[form]', qr)?.parentNode
data = JSON.parse data
{textContent, href} = data
if QR.op
window.location = href
return
if textContent
$.extend $('a.error', qr), data
if textContent is 'Error: Duplicate file entry detected.'
$.rm row if row
QR.stats()
setTimeout QR.submit, 1000
else if textContent is 'You seem to have mistyped the verification.'
setTimeout QR.submit, 1000
return
$.rm row if row
QR.stats()
if conf['Persistent QR'] or $('#files input', qr)?.files.length
QR.reset()
else
QR.close()
if conf['Cooldown']
cooldown = Date.now() + (if QR.sage then 60 else 30)*SECOND
$.set "cooldown/#{g.BOARD}", cooldown
QR.cooldown()
reset: ->
{qr} = QR
c = d.cookie
$('[name=name]', qr).value = if m = c.match(/4chan_name=([^;]+)/) then decodeURIComponent m[1] else ''
$('[name=email]', qr).value = if m = c.match(/4chan_email=([^;]+)/) then decodeURIComponent m[1] else ''
$('[name=pwd]', qr).value = if m = c.match(/4chan_pass=([^;]+)/) then decodeURIComponent m[1] else $('input[name=pwd]').value
$('[name=sub]', qr).value = ''
$('[name=spoiler]', qr)?.checked = false unless conf['Remember Spoiler']
$('textarea', qr).value = ''
submit: (e) ->
{qr} = QR
#XXX e is undefined if method is called explicitly, eg, from auto posting
if $('textarea', qr).value or $('#files', qr).childNodes.length
if $('form button', qr).disabled
$('#autopost', qr).checked = true
return
else
if e
alert 'Error: No text entered.'
e.preventDefault()
return
$('.error', qr).textContent = ''
if e and (el = $('#recaptcha_response_field', qr)).value
QR.captchaPush el
if not captcha = QR.captchaShift()
alert 'You forgot to type in the verification.'
e?.preventDefault()
return
{challenge, response} = captcha
$('#challenge', qr).value = challenge
$('#response', qr).value = response
$('#autohide', qr).checked = true if conf['Auto Hide QR']
if input = $ '#files input', qr
input.setAttribute 'form', 'qr_form'
$('#qr_form', qr).submit() if not e
QR.sage = /sage/i.test $('[name=email]', qr).value
id = $('input[name=resto]', qr).value
QR.op = not id
$('[name=email]', qr).value = 'noko' if QR.op
if conf['Thread Watcher'] and conf['Auto Watch Reply']
op = $.id id
if $('img.favicon', op).src is Favicon.empty
watcher.watch op, id
sys: ->
$.off d, 'DOMContentLoaded', QR.sys
if recaptcha = $ '#recaptcha_response_field' #post reporting
$.on recaptcha, 'keydown', QR.keydown
return
###
http://code.google.com/p/chromium/issues/detail?id=20773
Let content scripts see other frames (instead of them being undefined)
To access the parent, we have to break out of the sandbox and evaluate
in the global context.
selection = window.getSelection()
if s = selection.toString()
selectionID = $.x('preceding::input[@type="checkbox"][1]', selection.anchorNode)?.name
if selectionID == id
s = s.replace /\n/g, '\n>'
text += ">#{s}\n"
ta = $ 'textarea', qr.el
ta.focus()
ta.value += text
refresh: ->
$('[name=sub]', qr.el).value = ''
$('[name=email]', qr.el).value = if m = d.cookie.match(/4chan_email=([^;]+)/) then decodeURIComponent m[1] else ''
$('[name=com]', qr.el).value = ''
$('[name=recaptcha_response_field]', qr.el).value = ''
$('[name=spoiler]', qr.el)?.checked = false unless conf['Remember Spoiler']
# XXX opera doesn't allow resetting file inputs w/ file.value = ''
oldFile = $ '[type=file]', qr.el
newFile = $.el 'input', type: 'file', name: 'upfile', accept: qr.acceptFiles
$.replace oldFile, newFile
submit: (e) ->
#XXX `e` won't exist if we're here from `qr.submit.call form`.
if msg = qr.postInvalid()
e.preventDefault?()
alert msg
if msg is 'You forgot to type in the verification.'
$('#dummy', qr.el).focus()
return
if conf['Auto Watch Reply'] and conf['Thread Watcher']
if g.REPLY and $('img.favicon').src is Favicon.empty
watcher.watch null, g.THREAD_ID
else
id = $('input[name=resto]', qr.el).value
op = $.id id
if $('img.favicon', op).src is Favicon.empty
watcher.watch op, id
if !e then @submit()
$('#error', qr.el).textContent = ''
$('#autohide', qr.el).checked = true if conf['Auto Hide QR']
qr.sage = /sage/i.test $('input[name=email]', @).value
sys: ->
if recaptcha = $ '#recaptcha_response_field' #post reporting
$.on recaptcha, 'keydown', Recaptcha.listener
return
###
http://code.google.com/p/chromium/issues/detail?id=20773
Let content scripts see other frames (instead of them being undefined)
To access the parent, we have to break out of the sandbox and evaluate
in the global context.
###
$.globalEval ->
$ = (css) -> document.querySelector css
if node = $('table font b')?.firstChild
{textContent, href} = node
else
node = $ 'meta'
href = node.content.match(/url=(.+)/)[1]
data = JSON.stringify { textContent, href }
data = to: 'qr.message'
if node = document.querySelector('table font b')?.firstChild
data.textContent = node.textContent
data.href = node.href
parent.postMessage data, '*'
#if we're an iframe, parent will blank us
c = $('b')?.lastChild
return unless c and c.nodeType is 8 #comment node
[_, thread, id] = c.textContent.match(/thread:(\d+),no:(\d+)/)
{search} = location
cooldown = /cooldown/.test search
noko = /noko/ .test search
sage = /sage/ .test search
watch = /watch/ .test search
url = "http://boards.4chan.org/#{g.BOARD}"
if watch and thread is '0'
url += "/res/#{id}?watch"
else if noko
url += '/res/'
url += if thread is '0' then id else thread
if cooldown
duration = Date.now() + (if sage then 60 else 30) * 1000
url += '?cooldown=' + duration
if noko
url += '#' + id
window.location = url
validateFileSize: (e) ->
return unless @files[0].size > $('input[name=MAX_FILE_SIZE]').value
file = $.el 'input', type: 'file', name: 'upfile', accept: qr.acceptFiles
$.on file, 'change', qr.validateFileSize
$.replace @, file
$('#error', qr.el).textContent = 'Error: File too large.'
alert 'Error: File too large.'
Recaptcha =
init: ->
#hack to tab from comment straight to recaptcha
for el in $$ '#recaptcha_table a'
el.tabIndex = 1
$.bind $('#recaptcha_response_field'), 'keydown', Recaptcha.listener
listener: (e) ->
if e.keyCode is 8 and @value is '' # backspace to reload
Recaptcha.reload()
reload: ->
window.location = 'javascript:Recaptcha.reload()'
threading =
init: ->
@ -2179,15 +2218,6 @@ imgExpand =
Main =
init: ->
if location.hostname is 'sys.4chan.org'
if d.body
QR.sys()
else
$.on d, 'DOMContentLoaded', QR.sys
return
$.on window, 'message', Main.message
pathname = location.pathname.substring(1).split('/')
[g.BOARD, temp] = pathname
if temp is 'res'
@ -2196,6 +2226,15 @@ Main =
else
g.PAGENUM = parseInt(temp) or 0
if location.hostname is 'sys.4chan.org'
if d.body
qr.sys()
else
$.on d, 'DOMContentLoaded', qr.sys
return
$.on window, 'message', Main.message
g.hiddenReplies = $.get "hiddenReplies/#{g.BOARD}/", {}
lastChecked = $.get 'lastChecked', 0
@ -2277,8 +2316,20 @@ Main =
threading.init()
Favicon.init()
#recaptcha may be blocked, eg by noscript
if (form = $ 'form[name=post]') and (canPost = !!$ '#recaptcha_response_field')
Recaptcha.init()
if g.REPLY and conf['Auto Watch Reply'] and conf['Thread Watcher']
$.bind form, 'submit', -> if $('img.favicon').src is Favicon.empty
watcher.watch null, g.THREAD_ID
#major features
if conf['Auto Noko'] and canPost
form.action += '?noko'
if conf['Cooldown'] and canPost
cooldown.init()
if conf['Image Expansion']
imgExpand.init()
@ -2286,7 +2337,7 @@ Main =
revealSpoilers.init()
if conf['Quick Reply']
QR.init()
qr.init()
if conf['Thread Watcher']
watcher.init()
@ -2310,6 +2361,11 @@ Main =
if conf['Unread Count']
unread.init()
if conf['Quick Reply'] and conf['Persistent QR'] and canPost
qr.dialog()
if conf['Auto Hide QR']
$('#autohide', qr.el).checked = true
else #not reply
if conf['Thread Hiding']
threadHiding.init()
@ -2337,7 +2393,7 @@ Main =
message: (e) ->
{origin, data} = e
if origin is 'http://sys.4chan.org'
QR.receive data
qr.message data
node: (e) ->
{target} = e
@ -2356,7 +2412,7 @@ Main =
div.dialog > div.move {
cursor: move;
}
label, a, .favicon {
label, a, .favicon, #qr img {
cursor: pointer;
}
@ -2366,6 +2422,12 @@ Main =
.error {
color: red;
}
#error {
cursor: default;
}
#error[href] {
cursor: pointer;
}
td.replyhider {
vertical-align: top;
}
@ -2447,6 +2509,47 @@ Main =
width: 100%;
}
#qr {
position: fixed;
max-height: 100%;
overflow-x: hidden;
overflow-y: auto;
}
#qr > div.move {
text-align: right;
}
#qr input[name=name] {
float: left;
}
#qr_form {
clear: left;
}
#qr_form, #qr #com_submit, #qr input[name=upfile] {
margin: 0;
}
#qr textarea {
width: 100%;
height: 125px;
}
#qr #close, #qr #autohide {
float: right;
}
#qr:not(:hover) > #autohide:checked ~ .autohide {
height: 0;
overflow: hidden;
}
/* http://stackoverflow.com/questions/2610497/change-an-inputs-html5-placeholder-color-with-css */
#qr input::-webkit-input-placeholder {
color: grey;
}
#qr input:-moz-placeholder {
color: grey;
}
/* qr reCAPTCHA */
#qr img {
border: 1px solid #AAA;
}
#updater {
position: fixed;
text-align: right;
@ -2518,102 +2621,6 @@ Main =
#files > input {
display: block;
}
#qr {
position: fixed;
}
#qr .close, #qr #autohide {
float: right;
}
#qr > .move {
text-align: right;
}
#qr .autohide > input {
width: 90px;
}
#qr #autopost {
width: auto;
}
#qr #recaptcha_response_field {
width: 100%;
}
#qr form {
margin: 0;
}
#qr .autohide {
clear: both;
}
#qr:not(:hover) #autohide:checked ~ .autohide {
height: 0;
overflow: hidden;
}
#qr textarea {
border: 0;
height: 150px;
width: 100%;
}
#qr #captcha {
position: relative;
}
#qr #files {
width: 300px;
white-space: nowrap;
overflow: auto;
margin: 0;
padding: 0;
}
#qr #files li {
position: relative;
display: inline-block;
width: 100px;
height: 100px;
overflow: hidden;
}
#qr #files a {
position: absolute;
left: 0;
font-size: 50px;
color: red;
z-index: 1;
}
#qr #cl {
right: 0;
padding: 2px;
position: absolute;
}
#qr #files input {
/* cannot use `display: none;`
https://bugs.webkit.org/show_bug.cgi?id=58208
http://code.google.com/p/chromium/issues/detail?id=78961
*/
font-size: 100px;
opacity: 0;
}
#qr #files img {
position: absolute;
left: 0;
max-height: 100px;
max-width: 100px;
}
#qr input[name=resto] {
width: 80px;
}
#qr button + input[type=file] {
position: absolute;
opacity: 0;
pointer-events: none;
}
#qr .wat {
display: inline-block;
width: 16px;
overflow: hidden;
position: relative;
vertical-align: text-top;
}
#qr .wat input {
opacity: 0;
position: absolute;
left: 0;
}
'
Main.init()