Merge branch 'master' of github.com:aeosynth/4chan-x

Conflicts:
	4chan_x.user.js
This commit is contained in:
Nicolas Stepien 2011-09-21 22:47:47 +02:00
commit 0fba0ee9bb
2 changed files with 689 additions and 797 deletions

View File

@ -61,7 +61,7 @@
*/
(function() {
var $, $$, DAY, Favicon, HOUR, MINUTE, Main, NAMESPACE, Recaptcha, SECOND, Time, anonymize, conf, config, cooldown, d, expandComment, expandThread, firstRun, g, getTitle, imgExpand, imgGif, imgHover, imgPreloading, key, keybinds, log, nav, nodeInserted, options, pathname, qr, quoteBacklink, quoteInline, quoteOP, quotePreview, redirect, replyHiding, reportButton, revealSpoilers, sauce, temp, threadHiding, threadStats, threading, titlePost, ui, unread, updater, val, watcher;
var $, $$, DAY, Favicon, HOUR, MINUTE, Main, NAMESPACE, QR, SECOND, Time, anonymize, conf, config, d, expandComment, expandThread, firstRun, g, getTitle, imgExpand, imgGif, imgHover, imgPreloading, key, keybinds, log, nav, nodeInserted, options, pathname, quoteBacklink, quoteInline, quoteOP, quotePreview, redirect, replyHiding, reportButton, revealSpoilers, sauce, temp, threadHiding, threadStats, threading, titlePost, ui, unread, updater, val, watcher;
var __slice = Array.prototype.slice;
config = {
main: {
@ -948,17 +948,14 @@
}
},
qr: function(thread, quote) {
var qrLink;
if (!(qrLink = $('td.replyhl span[id] a:not(:first-child)', thread))) {
qrLink = $("span[id^=nothread] a:not(:first-child)", thread);
}
if (quote) {
return qr.quote.call(qrLink);
return QR.quote.call($('a.quotejs + a', $('td.replyhl', thread) || thread));
} else {
if (!qr.el) {
qr.dialog(qrLink);
if (QR.qr) {
return $('textarea', QR.qr).focus();
} else {
return QR.dialog('', thread != null ? thread.firstChild.id : void 0);
}
return $('textarea', qr.el).focus();
}
},
open: function(thread, tab) {
@ -1210,70 +1207,28 @@
return $('#backlinkPreview').textContent = conf['backlink'].replace(/%id/, '123456789');
}
};
cooldown = {
QR = {
init: function() {
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();
}
$.bind(window, 'storage', function(e) {
if (e.key === ("" + NAMESPACE + g.BOARD + "/cooldown")) {
return cooldown.start();
}
});
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)) {
var holder;
if (!($('form[name=post]') && $('#recaptcha_response_field'))) {
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);
$.bind($('#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) {
g.callbacks.push(function(root) {
var quote;
quote = $('a.quotejs + a', root);
return $.bind(quote, 'click', QR.quote);
});
$.add(d.body, $.el('iframe', {
name: 'iframe',
hidden: true
}));
$('#recaptcha_response_field').id = '';
holder = $('#recaptcha_challenge_field_holder');
$.bind(holder, 'DOMNodeInserted', QR.captchaNode);
QR.captchaNode({
target: holder.firstChild
});
QR.accept = $('.rules').textContent.match(/: (.+) /)[1].replace(/\w+/g, function(type) {
switch (type) {
case 'JPG':
return 'image/JPEG';
@ -1283,245 +1238,306 @@
return 'image/' + type;
}
});
iframe = $.el('iframe', {
name: 'iframe',
hidden: true
});
$.add(d.body, iframe);
return $('#recaptcha_response_field').id = '';
},
attach: function() {
var fileDiv;
fileDiv = $.el('div', {
innerHTML: "<input type=file name=upfile accept='" + qr.acceptFiles + "'><a>X</a>"
});
$.bind(fileDiv.firstChild, 'change', qr.validateFileSize);
$.bind(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) {
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();
},
captchaKeydown: function(e) {
var captchas;
if (!(e.keyCode === 13 && this.value)) {
return;
}
captchas = $.get('captchas', []);
captchas.push({
challenge: qr.challenge,
response: this.value,
time: qr.captchaTime
});
$.set('captchas', 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();
}
},
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);
$.bind($('input[name=name]', qr.el), 'mousedown', function(e) {
return e.stopPropagation();
});
$.bind($('input[name=upfile]', qr.el), 'change', qr.validateFileSize);
$.bind($('#close', qr.el), 'click', qr.close);
$.bind($('form', qr.el), 'submit', qr.submit);
$.bind($('#attach', qr.el), 'click', qr.attach);
$.bind($('img', qr.el), 'click', Recaptcha.reload);
$.bind($('#dummy', qr.el), 'keydown', Recaptcha.listener);
$.bind($('#dummy', qr.el), 'keydown', qr.captchaKeydown);
return $.add(d.body, qr.el);
},
message: function(data) {
var duration, fileCount;
$('iframe[name=iframe]').src = 'about:blank';
fileCount = $('#files', qr.el).childElementCount;
if (data) {
data = JSON.parse(data);
$.extend($('#error', qr.el), data);
$('#recaptcha_response_field', qr.el).value = '';
$('#autohide', qr.el).checked = false;
if (data.textContent === 'You seem to have mistyped the verification.') {
setTimeout(qr.autoPost, 1000);
} else if (data.textContent === 'Error: Duplicate file entry detected.' && fileCount) {
$('textarea', qr.el).value += '\n' + data.textContent + ' ' + 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();
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']) {
duration = qr.sage ? 60 : 30;
$.set(g.BOARD + '/cooldown', Date.now() + duration * 1000);
return cooldown.start();
return $.bind(window, 'storage', function(e) {
if (e.key === ("" + NAMESPACE + "cooldown/" + g.BOARD)) {
return QR.cooldown();
}
});
}
},
node: function(root) {
var quote;
quote = $('a.quotejs:not(:first-child)', root);
return $.bind(quote, 'click', qr.quote);
attach: function() {
var box, file, files;
files = $('#files', QR.qr);
box = $.el('span', {
innerHTML: "<input type=file name=upfile accept='" + QR.accept + "'><img alt='click here'><a class=x>X</a>"
});
file = $('input', box);
$.bind(file, 'change', QR.change);
$.bind($('img', box), 'click', function() {
return this.previousSibling.click();
});
$.bind($('.x', box), 'click', function() {
return $.rm(this.parentNode);
});
$.add(files, box);
return file.click();
},
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.';
captchaNode: function(e) {
QR.captcha = {
challenge: e.target.value,
time: Date.now()
};
return QR.captchaImg();
},
captchaImg: function() {
var c, qr;
qr = QR.qr;
if (!qr) {
return;
}
/*
captchas expire after 5 hours (emperically verified). cutoff 5 minutes
before then, b/c posting takes time.
*/
cutoff = Date.now() - 5 * HOUR + 5 * MINUTE;
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;
captchas = $.get('captchas', []);
captchas.push(captcha);
$.set('captchas', captchas);
el.value = '';
QR.captchaReload();
return QR.captchaLength(captchas);
},
captchaShift: function() {
var captcha, captchas, cutoff;
captchas = $.get('captchas', []);
cutoff = Date.now() - 5 * HOUR + 5 * MINUTE;
while (captcha = captchas.shift()) {
if (captcha.time > cutoff) {
break;
}
}
$.set('captchas', captchas);
$('#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();
}
$('#recaptcha_challenge_field', qr.el).value = captcha.challenge;
$('#recaptcha_response_field', qr.el).value = captcha.response;
return false;
QR.captchaLength(captchas);
return captcha;
},
quote: function(e) {
var id, s, selection, selectionID, ta, text, _ref;
if (e) {
captchaLength: function(captchas) {
captchas || (captchas = $.get('captchas', []));
return $('#cl', QR.qr).textContent = captchas.length + ' captchas';
},
captchaReload: function() {
return window.location = 'javascript:Recaptcha.reload()';
},
change: function(e) {
var file, fr, img, qr;
file = this.files[0];
if (file.size > QR.MAX_FILE_SIZE) {
alert('Error: File too large.');
$.rm(this.parentNode);
QR.attach();
return;
}
qr = QR.qr;
fr = new FileReader();
img = this.nextSibling;
fr.onload = function(e) {
return img.src = e.target.result;
};
return fr.readAsDataURL(file);
},
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);
} else {
$.extend(b, {
textContent: 'Submit',
disabled: false
});
if ($('#autopost', QR.qr).checked) {
return QR.submit();
}
}
},
dialog: function(text, tid) {
var c, l, m, qr, ta;
if (text == null) {
text = '';
}
tid || (tid = g.THREAD_ID || '');
QR.qr = qr = ui.dialog('qr', 'top: 0; left: 0;', " <a class=close>X</a> <input type=checkbox id=autohide title=autohide> <div class=move> <span class=click> <button>File</button> <input form=qr_form placeholder=Subject name=sub> <input form=qr_form placeholder=Name name=name> <input form=qr_form placeholder=Email name=email> </span> </div> <div class=autohide> <textarea form=qr_form placeholder=Comment name=com></textarea> <div id=files></div> <form enctype=multipart/form-data method=post action=http://sys.4chan.org/" + g.BOARD + "/post target=iframe id=qr_form> <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> <span id=cl>120 Captchas</span> <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> ");
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;
$('textarea', qr).value = text;
if (conf['Cooldown']) {
QR.cooldown();
}
$.bind($('button', qr), 'click', QR.attach);
$.bind($('.close', qr), 'click', QR.close);
$.bind($('.click', qr), 'mousedown', function(e) {
return e.stopPropagation();
});
$.bind($('form', qr), 'submit', QR.submit);
$.bind($('#recaptcha_response_field', qr), 'keydown', QR.keydown);
QR.captchaImg();
QR.captchaLength();
$.add(d.body, qr);
ta = $('textarea', qr);
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 i, id, qr, s, sel, ss, ta, text, tid, v, _base, _ref, _ref2;
if (e != null) {
e.preventDefault();
}
if (qr.el) {
$('#autohide', qr.el).checked = false;
} else {
qr.dialog(this);
}
tid = (_ref = $.x('ancestor::div[@class="thread"]/div', this)) != null ? _ref.id : void 0;
id = this.textContent;
text = ">>" + id + "\n";
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>');
sel = getSelection();
if (id === ((_ref2 = $.x('preceding::input[@type="checkbox"][1]', sel.anchorNode)) != null ? _ref2.name : void 0)) {
if (s = sel.toString().replace(/\n/g, '\n>')) {
text += ">" + s + "\n";
}
}
ta = $('textarea', qr.el);
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 ta.value += text;
return (_base = $('[name=resto]', qr)).value || (_base.value = tid);
},
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.el)) != null) {
_ref.checked = false;
receive: function(data) {
var cooldown, qr, row, tc, _ref, _ref2;
$('iframe[name=iframe]').src = 'about:blank';
qr = QR.qr;
row = (_ref = $('#files input[form]', qr)) != null ? _ref.parentNode : void 0;
if (data) {
if (QR.op) {
window.location = data;
return;
}
data = JSON.parse(data);
$.extend($('a.error', qr), data);
tc = data.textContent;
if (tc === 'Error: Duplicate file entry detected.') {
if (row) {
$.rm(row);
}
oldFile = $('[type=file]', qr.el);
newFile = $.el('input', {
type: 'file',
name: 'upfile',
accept: qr.acceptFiles
});
return $.replace(oldFile, newFile);
},
submit: function(e) {
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();
setTimeout(QR.submit, 1000);
} else if (tc === 'You seem to have mistyped the verification.') {
setTimeout(QR.submit, 1000);
}
return;
}
if (conf['Auto Watch Reply'] && conf['Thread Watcher']) {
if (g.REPLY && $('img.favicon').src === Favicon.empty) {
watcher.watch(null, g.THREAD_ID);
if (row) {
$.rm(row);
}
if (conf['Persistent QR'] || ((_ref2 = $('#files input', qr)) != null ? _ref2.files.length : void 0)) {
QR.reset();
} else {
id = $('input[name=resto]', qr.el).value;
op = $.id(id);
if ($('img.favicon', op).src === Favicon.empty) {
watcher.watch(op, id);
QR.close();
}
if (conf['Cooldown']) {
cooldown = Date.now() + (QR.sage ? 60 : 30) * SECOND;
$.set("cooldown/" + g.BOARD, cooldown);
return QR.cooldown();
}
},
reset: function() {
var _ref;
if (!conf['Remember Spoiler']) {
if ((_ref = $('[name=spoiler]', QR.qr)) != null) {
_ref.checked = false;
}
}
return $('textarea', QR.qr).value = '';
},
submit: function(e) {
var captcha, challenge, el, id, input, op, qr, response, _ref;
if ($('form button', qr).disabled) {
return;
}
if (!($('textarea', QR.qr).value || ((_ref = $('[type=file]', QR.qr)) != null ? _ref.files.length : void 0))) {
if (e) {
alert('Error: No text entered.');
e.preventDefault();
}
return;
}
qr = QR.qr;
$('.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) {
this.submit();
$('#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);
}
$('#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 c, duration, id, noko, recaptcha, sage, search, thread, url, watch, _, _ref, _ref2;
var recaptcha;
if (recaptcha = $('#recaptcha_response_field')) {
$.bind(recaptcha, 'keydown', Recaptcha.listener);
$.bind(recaptcha, 'keydown', QR.keydown);
return;
}
/*
@ -1531,59 +1547,25 @@
To access the parent, we have to break out of the sandbox and evaluate
in the global context.
*/
$.globalEval(function() {
return $.globalEval(function() {
var data, href, node, textContent, _ref;
if (node = (_ref = document.querySelector('table font b')) != null ? _ref.firstChild : void 0) {
$ = function(css) {
return document.querySelector(css);
};
if (node = (_ref = $('table font b')) != null ? _ref.firstChild : void 0) {
textContent = node.textContent, href = node.href;
data = JSON.stringify({
textContent: textContent,
href: href
});
} else {
} else if (node = $('meta')) {
data = node.content.match(/url=(.+)/)[1];
if (/#/.test(data)) {
data = '';
}
}
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
});
$.bind(file, 'change', qr.validateFileSize);
$.replace(this, file);
$('#error', qr.el).textContent = 'Error: File too large.';
return alert('Error: File too large.');
}
};
threading = {
@ -2594,25 +2576,6 @@
}
return location.href = url;
};
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()';
}
};
nodeInserted = function(e) {
var callback, target, _i, _len, _ref, _results;
target = e.target;
@ -2815,9 +2778,9 @@
};
Main = {
init: function() {
var callback, canPost, cutoff, form, hiddenThreads, id, lastChecked, now, op, table, timestamp, tzOffset, _i, _j, _k, _l, _len, _len2, _len3, _len4, _ref, _ref2, _ref3, _ref4, _ref5;
var callback, cutoff, hiddenThreads, id, lastChecked, now, op, table, timestamp, tzOffset, _i, _j, _k, _l, _len, _len2, _len3, _len4, _ref, _ref2, _ref3, _ref4, _ref5;
if (location.hostname === 'sys.4chan.org') {
qr.sys();
QR.sys();
return;
}
if (conf['404 Redirect'] && d.title === '4chan - 404' && /^\d+$/.test(g.THREAD_ID)) {
@ -2858,26 +2821,10 @@
$.set("hiddenReplies/" + g.BOARD + "/", g.hiddenReplies);
}
$.addStyle(Main.css);
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);
}
});
}
}
threading.init();
if (g.REPLY && (id = location.hash.slice(1)) && /\d/.test(id[0]) && !$.id(id)) {
scrollTo(0, d.body.scrollHeight);
}
if (conf['Auto Noko'] && canPost) {
form.action += '?noko';
}
if (conf['Cooldown'] && canPost) {
cooldown.init();
}
if (conf['Image Expansion']) {
imgExpand.init();
}
@ -2902,8 +2849,8 @@
if (conf['Reply Hiding']) {
replyHiding.init();
}
if (conf['Quick Reply'] && canPost) {
qr.init();
if (conf['Quick Reply']) {
QR.init();
}
if (conf['Report Button']) {
reportButton.init();
@ -2933,12 +2880,6 @@
if (conf['Image Preloading']) {
imgPreloading.init();
}
if (conf['Quick Reply'] && conf['Persistent QR'] && canPost) {
qr.dialog();
if (conf['Auto Hide QR']) {
$('#autohide', qr.el).checked = true;
}
}
if (conf['Post in Title']) {
titlePost.init();
}
@ -2993,7 +2934,7 @@
var data, origin;
origin = e.origin, data = e.data;
if (origin === 'http://sys.4chan.org') {
return qr.message(data);
return QR.receive(data);
}
},
css: '\
@ -3004,7 +2945,7 @@
div.dialog > div.move {\
cursor: move;\
}\
label, a, .favicon, #qr img {\
label, a, .favicon {\
cursor: pointer;\
}\
\
@ -3014,12 +2955,6 @@
.error {\
color: red;\
}\
#error {\
cursor: default;\
}\
#error[href] {\
cursor: pointer;\
}\
td.replyhider {\
vertical-align: top;\
}\
@ -3097,47 +3032,6 @@
margin: 0;\
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;\
@ -3203,6 +3097,68 @@
#files > input {\
display: block;\
}\
#qr {\
max-height: 100%;\
overflow-y: auto;\
position: fixed;\
}\
#qr #autohide, #qr .close {\
float: right;\
}\
#qr .click input {\
width: 73px;\
}\
#qr .click * {\
float: left;\
}\
#qr form {\
margin: 0;\
}\
#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;\
}\
#qr #files span {\
position: relative;\
}\
#qr #files a {\
position: absolute;\
left: 0;\
font-size: 50px;\
color: red;\
}\
#qr #cl {\
right: 0;\
padding: 2px;\
position: absolute;\
}\
#qr #recaptcha_response_field {\
display: inline;\
width: 100%;\
}\
#qr #files input {\
display: none;\
}\
#qr #files img {\
max-height: 100px;\
max-width: 100px;\
}\
#qr input[name=resto] {\
width: 80px;\
}\
'
};
if (d.body) {

View File

@ -656,15 +656,13 @@ keybinds =
imgExpand.toggle thumb.parentNode
qr: (thread, quote) ->
unless qrLink = $ 'td.replyhl span[id] a:not(:first-child)', thread
qrLink = $ "span[id^=nothread] a:not(:first-child)", thread
if quote
qr.quote.call qrLink
QR.quote.call $ 'a.quotejs + a', $('td.replyhl', thread) or thread
else
unless qr.el
qr.dialog qrLink
$('textarea', qr.el).focus()
if QR.qr
$('textarea', QR.qr).focus()
else
QR.dialog '', thread?.firstChild.id
open: (thread, tab) ->
id = thread.firstChild.id
@ -936,49 +934,25 @@ options =
conf['backlink'] = @value
$('#backlinkPreview').textContent = conf['backlink'].replace /%id/, '123456789'
cooldown =
#TODO merge into qr
QR =
#captcha caching for report form
#report queueing
#check if captchas can be reused on eg dup file error
init: ->
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
$.bind 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
$.bind $('#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) ->
#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 = $ 'a.quotejs + a', root
$.bind 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'
$.bind holder, 'DOMNodeInserted', QR.captchaNode
QR.captchaNode target: holder.firstChild
QR.accept = $('.rules').textContent.match(/: (.+) /)[1].replace /\w+/g, (type) ->
switch type
when 'JPG'
'image/JPEG'
@ -986,233 +960,244 @@ qr =
'application/' + type
else
'image/' + type
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>"
$.bind fileDiv.firstChild, 'change', qr.validateFileSize
$.bind 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) ->
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
challenge: qr.challenge
response: @value
time: qr.captchaTime
$.set 'captchas', 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
$.bind $('input[name=name]', qr.el), 'mousedown', (e) -> e.stopPropagation()
$.bind $('input[name=upfile]', qr.el), 'change', qr.validateFileSize
$.bind $('#close', qr.el), 'click', qr.close
$.bind $('form', qr.el), 'submit', qr.submit
$.bind $('#attach', qr.el), 'click', qr.attach
$.bind $('img', qr.el), 'click', Recaptcha.reload
$.bind $('#dummy', qr.el), 'keydown', Recaptcha.listener
$.bind $('#dummy', qr.el), 'keydown', qr.captchaKeydown
$.add d.body, qr.el
message: (data) ->
$('iframe[name=iframe]').src = 'about:blank'
fileCount = $('#files', qr.el).childElementCount
if data # error message
data = JSON.parse data
$.extend $('#error', qr.el), data
$('#recaptcha_response_field', qr.el).value = ''
$('#autohide', qr.el).checked = false
if data.textContent is 'You seem to have mistyped the verification.'
setTimeout qr.autoPost, 1000
else if data.textContent is 'Error: Duplicate file entry detected.' and fileCount
$('textarea', qr.el).value += '\n' + data.textContent + ' ' + 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()
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']
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
$.bind 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
$.bind window, 'storage', (e) -> QR.cooldown() if e.key is "#{NAMESPACE}cooldown/#{g.BOARD}"
attach: ->
#$('#autopost', QR.qr).checked = true
files = $ '#files', QR.qr
box = $.el 'span',
innerHTML: "<input type=file name=upfile accept='#{QR.accept}'><img alt='click here'><a class=x>X</a>"
file = $ 'input', box
$.bind file, 'change', QR.change
$.bind $('img', box), 'click', -> @previousSibling.click()
$.bind $('.x', box), 'click', -> $.rm @parentNode
$.add files, box
file.click()
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
captchas = $.get 'captchas', []
captchas.push captcha
$.set 'captchas', captchas
el.value = ''
QR.captchaReload()
QR.captchaLength captchas
captchaShift: ->
captchas = $.get 'captchas', []
cutoff = Date.now() - 5*HOUR + 5*MINUTE
while captcha = captchas.shift()
if captcha.time > cutoff
break
$.set 'captchas', captchas
$('#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
QR.captchaLength captchas
captcha
captchaLength: (captchas) ->
captchas or= $.get 'captchas', []
$('#cl', QR.qr).textContent = captchas.length + ' captchas'
captchaReload: ->
window.location = 'javascript:Recaptcha.reload()'
change: (e) ->
file = @files[0]
if file.size > QR.MAX_FILE_SIZE
alert 'Error: File too large.'
$.rm @parentNode
QR.attach()
return
{qr} = QR
fr = new FileReader()
img = @nextSibling
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
else
qr.dialog @
$.extend b,
textContent: 'Submit'
disabled: false
QR.submit() if $('#autopost', QR.qr).checked
dialog: (text='', tid) ->
tid or= g.THREAD_ID or ''
QR.qr = qr = ui.dialog 'qr', 'top: 0; left: 0;', "
<a class=close>X</a>
<input type=checkbox id=autohide title=autohide>
<div class=move>
<span class=click>
<button>File</button>
<input form=qr_form placeholder=Subject name=sub>
<input form=qr_form placeholder=Name name=name>
<input form=qr_form placeholder=Email name=email>
</span>
</div>
<div class=autohide>
<textarea form=qr_form placeholder=Comment name=com></textarea>
<div id=files></div>
<form enctype=multipart/form-data method=post action=http://sys.4chan.org/#{g.BOARD}/post target=iframe id=qr_form>
<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>
<span id=cl>120 Captchas</span>
<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 -_-;
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
$('textarea', qr).value = text
QR.cooldown() if conf['Cooldown']
$.bind $('button', qr), 'click', QR.attach
$.bind $('.close', qr), 'click', QR.close
$.bind $('.click', qr), 'mousedown', (e) -> e.stopPropagation()
$.bind $('form', qr), 'submit', QR.submit
$.bind $('#recaptcha_response_field', qr), 'keydown', QR.keydown
QR.captchaImg()
QR.captchaLength()
$.add d.body, qr
ta = $ 'textarea', qr
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
id = @textContent
text = ">>#{id}\n"
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>'
sel = getSelection()
if id == $.x('preceding::input[@type="checkbox"][1]', sel.anchorNode)?.name
if s = sel.toString().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()
{qr} = QR
if not qr
QR.dialog text, tid
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
$('#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
if data
if QR.op
window.location = data
return
data = JSON.parse data
$.extend $('a.error', qr), data
tc = data.textContent
if tc is 'Error: Duplicate file entry detected.'
$.rm row if row
setTimeout QR.submit, 1000
else if tc is 'You seem to have mistyped the verification.'
setTimeout QR.submit, 1000
return
$.rm row if row
if conf['Persistent QR'] or $('#files input', qr)?.files.length
QR.reset()
else
id = $('input[name=resto]', qr.el).value
QR.close()
if conf['Cooldown']
cooldown = Date.now() + (if QR.sage then 60 else 30)*SECOND
$.set "cooldown/#{g.BOARD}", cooldown
QR.cooldown()
reset: ->
$('[name=spoiler]', QR.qr)?.checked = false unless conf['Remember Spoiler']
$('textarea', QR.qr).value = ''
submit: (e) ->
return if $('form button', qr).disabled
#XXX e is undefined if method is called explicitly, eg, from auto posting
unless $('textarea', QR.qr).value or $('[type=file]', QR.qr)?.files.length
if e
alert 'Error: No text entered.'
e.preventDefault()
return
{qr} = QR
$('.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
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
$.bind recaptcha, 'keydown', Recaptcha.listener
$.bind 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)
@ -1221,49 +1206,15 @@ qr =
in the global context.
###
$.globalEval ->
if node = document.querySelector('table font b')?.firstChild
$ = (css) -> document.querySelector css
if node = $('table font b')?.firstChild
{textContent, href} = node
data = JSON.stringify {textContent, href}
else
data = ''
else if node = $ 'meta'
data = node.content.match(/url=(.+)/)[1]
if /#/.test data then data = '' #not op
parent.postMessage data, '*'
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
$.bind file, 'change', qr.validateFileSize
$.replace @, file
$('#error', qr.el).textContent = 'Error: File too large.'
alert 'Error: File too large.'
#if we're an iframe, parent will blank us
threading =
init: ->
@ -1972,18 +1923,6 @@ redirect = ->
url = "http://boards.4chan.org/#{g.BOARD}"
location.href = url
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()'
nodeInserted = (e) ->
{target} = e
if target.nodeName is 'TABLE'
@ -2187,7 +2126,7 @@ firstRun =
Main =
init: ->
if location.hostname is 'sys.4chan.org'
qr.sys()
QR.sys()
return
if conf['404 Redirect'] and d.title is '4chan - 404' and /^\d+$/.test g.THREAD_ID
redirect()
@ -2224,13 +2163,6 @@ Main =
$.addStyle Main.css
#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
threading.init()
@ -2239,12 +2171,6 @@ Main =
if g.REPLY and (id = location.hash[1..]) and /\d/.test(id[0]) and !$.id(id)
scrollTo 0, d.body.scrollHeight
if conf['Auto Noko'] and canPost
form.action += '?noko'
if conf['Cooldown'] and canPost
cooldown.init()
if conf['Image Expansion']
imgExpand.init()
@ -2269,8 +2195,8 @@ Main =
if conf['Reply Hiding']
replyHiding.init()
if conf['Quick Reply'] and canPost
qr.init()
if conf['Quick Reply']
QR.init()
if conf['Report Button']
reportButton.init()
@ -2300,11 +2226,6 @@ Main =
if conf['Image Preloading']
imgPreloading.init()
if conf['Quick Reply'] and conf['Persistent QR'] and canPost
qr.dialog()
if conf['Auto Hide QR']
$('#autohide', qr.el).checked = true
if conf['Post in Title']
titlePost.init()
@ -2345,7 +2266,7 @@ Main =
message: (e) ->
{origin, data} = e
if origin is 'http://sys.4chan.org'
qr.message data
QR.receive data
css: '
/* dialog styling */
@ -2355,7 +2276,7 @@ Main =
div.dialog > div.move {
cursor: move;
}
label, a, .favicon, #qr img {
label, a, .favicon {
cursor: pointer;
}
@ -2365,12 +2286,6 @@ Main =
.error {
color: red;
}
#error {
cursor: default;
}
#error[href] {
cursor: pointer;
}
td.replyhider {
vertical-align: top;
}
@ -2449,47 +2364,6 @@ 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;
@ -2554,6 +2428,68 @@ Main =
#files > input {
display: block;
}
#qr {
max-height: 100%;
overflow-y: auto;
position: fixed;
}
#qr #autohide, #qr .close {
float: right;
}
#qr .click input {
width: 73px;
}
#qr .click * {
float: left;
}
#qr form {
margin: 0;
}
#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;
}
#qr #files span {
position: relative;
}
#qr #files a {
position: absolute;
left: 0;
font-size: 50px;
color: red;
}
#qr #cl {
right: 0;
padding: 2px;
position: absolute;
}
#qr #recaptcha_response_field {
display: inline;
width: 100%;
}
#qr #files input {
display: none;
}
#qr #files img {
max-height: 100px;
max-width: 100px;
}
#qr input[name=resto] {
width: 80px;
}
'
if d.body