diff --git a/4chan_x.user.js b/4chan_x.user.js
index 19e322c76..ac03e00cf 100644
--- a/4chan_x.user.js
+++ b/4chan_x.user.js
@@ -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') ? '' : '';
- 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: "X"
- });
- $.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 = " X
Quick Reply
";
- 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') ? ' ' : '';
+ 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: "
X"
+ });
+ 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;', " X
");
+ 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) {
diff --git a/script.coffee b/script.coffee
index 50bb69279..75b171910 100644
--- a/script.coffee
+++ b/script.coffee
@@ -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 '' 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: "X"
- $.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 = "
- X
-
-
-
- Quick Reply
-
-
-
- "
- 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 ' ' 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: "
X"
+ 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;', "
+ X
+
+
+
+
+
+
+
+
+
+
+
+ "
+ #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