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,307 +1238,334 @@
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 = '';
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);
}
setTimeout(QR.submit, 1000);
} else if (tc === 'You seem to have mistyped the verification.') {
setTimeout(QR.submit, 1000);
}
return;
}
if (row) {
$.rm(row);
}
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();
}
},
reset: function() {
var _ref;
if (!conf['Remember Spoiler']) {
if ((_ref = $('[name=spoiler]', qr.el)) != null) {
if ((_ref = $('[name=spoiler]', QR.qr)) != null) {
_ref.checked = false;
}
}
oldFile = $('[type=file]', qr.el);
newFile = $.el('input', {
type: 'file',
name: 'upfile',
accept: qr.acceptFiles
});
return $.replace(oldFile, newFile);
return $('textarea', QR.qr).value = '';
},
submit: function(e) {
var id, msg, op;
if (msg = qr.postInvalid()) {
if (typeof e.preventDefault === "function") {
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();
}
alert(msg);
if (msg === 'You forgot to type in the verification.') {
$('#dummy', qr.el).focus();
}
return;
}
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);
}
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();
}
$('#error', qr.el).textContent = '';
if (conf['Auto Hide QR']) {
$('#autohide', qr.el).checked = true;
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);
}
}
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;
}
/*
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.
*/
$.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 {
data = '';
} 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,284 +960,261 @@ 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
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
$('#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
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
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)
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.
###
$.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