Merge branch 'master' of github.com:aeosynth/4chan-x
Conflicts: 4chan_x.user.js
This commit is contained in:
commit
0fba0ee9bb
774
4chan_x.user.js
774
4chan_x.user.js
@ -61,7 +61,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
(function() {
|
(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;
|
var __slice = Array.prototype.slice;
|
||||||
config = {
|
config = {
|
||||||
main: {
|
main: {
|
||||||
@ -948,17 +948,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
qr: function(thread, quote) {
|
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) {
|
if (quote) {
|
||||||
return qr.quote.call(qrLink);
|
return QR.quote.call($('a.quotejs + a', $('td.replyhl', thread) || thread));
|
||||||
} else {
|
} else {
|
||||||
if (!qr.el) {
|
if (QR.qr) {
|
||||||
qr.dialog(qrLink);
|
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) {
|
open: function(thread, tab) {
|
||||||
@ -1210,70 +1207,28 @@
|
|||||||
return $('#backlinkPreview').textContent = conf['backlink'].replace(/%id/, '123456789');
|
return $('#backlinkPreview').textContent = conf['backlink'].replace(/%id/, '123456789');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cooldown = {
|
QR = {
|
||||||
init: function() {
|
init: function() {
|
||||||
var match, time, _;
|
var holder;
|
||||||
if (match = location.search.match(/cooldown=(\d+)/)) {
|
if (!($('form[name=post]') && $('#recaptcha_response_field'))) {
|
||||||
_ = 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)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_ref = $$('#com_submit');
|
g.callbacks.push(function(root) {
|
||||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
var quote;
|
||||||
submit = _ref[_i];
|
quote = $('a.quotejs + a', root);
|
||||||
submit.value = cooldown.duration;
|
return $.bind(quote, 'click', QR.quote);
|
||||||
submit.disabled = true;
|
});
|
||||||
}
|
$.add(d.body, $.el('iframe', {
|
||||||
return setTimeout(cooldown.cb, 1000);
|
name: 'iframe',
|
||||||
},
|
hidden: true
|
||||||
cb: function() {
|
}));
|
||||||
var submit, submits, _i, _j, _len, _len2, _results;
|
$('#recaptcha_response_field').id = '';
|
||||||
submits = $$('#com_submit');
|
holder = $('#recaptcha_challenge_field_holder');
|
||||||
if (--cooldown.duration) {
|
$.bind(holder, 'DOMNodeInserted', QR.captchaNode);
|
||||||
setTimeout(cooldown.cb, 1000);
|
QR.captchaNode({
|
||||||
_results = [];
|
target: holder.firstChild
|
||||||
for (_i = 0, _len = submits.length; _i < _len; _i++) {
|
});
|
||||||
submit = submits[_i];
|
QR.accept = $('.rules').textContent.match(/: (.+) /)[1].replace(/\w+/g, function(type) {
|
||||||
_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) {
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'JPG':
|
case 'JPG':
|
||||||
return 'image/JPEG';
|
return 'image/JPEG';
|
||||||
@ -1283,307 +1238,334 @@
|
|||||||
return 'image/' + type;
|
return 'image/' + type;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
iframe = $.el('iframe', {
|
QR.MAX_FILE_SIZE = $('input[name=MAX_FILE_SIZE]').value;
|
||||||
name: 'iframe',
|
QR.spoiler = $('.postarea label') ? ' <label>[<input type=checkbox name=spoiler>Spoiler Image?]</label>' : '';
|
||||||
hidden: true
|
if (conf['Persistent QR']) {
|
||||||
});
|
QR.dialog();
|
||||||
$.add(d.body, iframe);
|
$('textarea', QR.qr).blur();
|
||||||
return $('#recaptcha_response_field').id = '';
|
if (conf['Auto Hide QR']) {
|
||||||
},
|
$('#autohide', QR.qr).checked = true;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (conf['Cooldown']) {
|
if (conf['Cooldown']) {
|
||||||
duration = qr.sage ? 60 : 30;
|
return $.bind(window, 'storage', function(e) {
|
||||||
$.set(g.BOARD + '/cooldown', Date.now() + duration * 1000);
|
if (e.key === ("" + NAMESPACE + "cooldown/" + g.BOARD)) {
|
||||||
return cooldown.start();
|
return QR.cooldown();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
node: function(root) {
|
attach: function() {
|
||||||
var quote;
|
var box, file, files;
|
||||||
quote = $('a.quotejs:not(:first-child)', root);
|
files = $('#files', QR.qr);
|
||||||
return $.bind(quote, 'click', qr.quote);
|
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() {
|
captchaNode: function(e) {
|
||||||
var captcha, captchas, content, cutoff, dummy, response;
|
QR.captcha = {
|
||||||
content = $('textarea', qr.el).value || $('input[type=file]', qr.el).files.length;
|
challenge: e.target.value,
|
||||||
if (!content) {
|
time: Date.now()
|
||||||
return 'Error: No text entered.';
|
};
|
||||||
|
return QR.captchaImg();
|
||||||
|
},
|
||||||
|
captchaImg: function() {
|
||||||
|
var c, qr;
|
||||||
|
qr = QR.qr;
|
||||||
|
if (!qr) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
/*
|
c = QR.captcha.challenge;
|
||||||
captchas expire after 5 hours (emperically verified). cutoff 5 minutes
|
return $('#captcha img', qr).src = "http://www.google.com/recaptcha/api/image?c=" + c;
|
||||||
before then, b/c posting takes time.
|
},
|
||||||
*/
|
captchaPush: function(el) {
|
||||||
cutoff = Date.now() - 5 * HOUR + 5 * MINUTE;
|
var captcha, captchas;
|
||||||
|
captcha = QR.captcha;
|
||||||
|
captcha.response = el.value;
|
||||||
captchas = $.get('captchas', []);
|
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()) {
|
while (captcha = captchas.shift()) {
|
||||||
if (captcha.time > cutoff) {
|
if (captcha.time > cutoff) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$.set('captchas', captchas);
|
$.set('captchas', captchas);
|
||||||
$('#captchas', qr.el).textContent = captchas.length + ' captchas';
|
QR.captchaLength(captchas);
|
||||||
if (!captcha) {
|
return 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;
|
|
||||||
},
|
},
|
||||||
quote: function(e) {
|
captchaLength: function(captchas) {
|
||||||
var id, s, selection, selectionID, ta, text, _ref;
|
captchas || (captchas = $.get('captchas', []));
|
||||||
if (e) {
|
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();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
if (qr.el) {
|
tid = (_ref = $.x('ancestor::div[@class="thread"]/div', this)) != null ? _ref.id : void 0;
|
||||||
$('#autohide', qr.el).checked = false;
|
|
||||||
} else {
|
|
||||||
qr.dialog(this);
|
|
||||||
}
|
|
||||||
id = this.textContent;
|
id = this.textContent;
|
||||||
text = ">>" + id + "\n";
|
text = ">>" + id + "\n";
|
||||||
selection = window.getSelection();
|
sel = getSelection();
|
||||||
if (s = selection.toString()) {
|
if (id === ((_ref2 = $.x('preceding::input[@type="checkbox"][1]', sel.anchorNode)) != null ? _ref2.name : void 0)) {
|
||||||
selectionID = (_ref = $.x('preceding::input[@type="checkbox"][1]', selection.anchorNode)) != null ? _ref.name : void 0;
|
if (s = sel.toString().replace(/\n/g, '\n>')) {
|
||||||
if (selectionID === id) {
|
|
||||||
s = s.replace(/\n/g, '\n>');
|
|
||||||
text += ">" + s + "\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();
|
ta.focus();
|
||||||
return ta.value += text;
|
return (_base = $('[name=resto]', qr)).value || (_base.value = tid);
|
||||||
},
|
},
|
||||||
refresh: function() {
|
receive: function(data) {
|
||||||
var m, newFile, oldFile, _ref;
|
var cooldown, qr, row, tc, _ref, _ref2;
|
||||||
$('[name=sub]', qr.el).value = '';
|
$('iframe[name=iframe]').src = 'about:blank';
|
||||||
$('[name=email]', qr.el).value = (m = d.cookie.match(/4chan_email=([^;]+)/)) ? decodeURIComponent(m[1]) : '';
|
qr = QR.qr;
|
||||||
$('[name=com]', qr.el).value = '';
|
row = (_ref = $('#files input[form]', qr)) != null ? _ref.parentNode : void 0;
|
||||||
$('[name=recaptcha_response_field]', qr.el).value = '';
|
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 (!conf['Remember Spoiler']) {
|
||||||
if ((_ref = $('[name=spoiler]', qr.el)) != null) {
|
if ((_ref = $('[name=spoiler]', QR.qr)) != null) {
|
||||||
_ref.checked = false;
|
_ref.checked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
oldFile = $('[type=file]', qr.el);
|
return $('textarea', QR.qr).value = '';
|
||||||
newFile = $.el('input', {
|
|
||||||
type: 'file',
|
|
||||||
name: 'upfile',
|
|
||||||
accept: qr.acceptFiles
|
|
||||||
});
|
|
||||||
return $.replace(oldFile, newFile);
|
|
||||||
},
|
},
|
||||||
submit: function(e) {
|
submit: function(e) {
|
||||||
var id, msg, op;
|
var captcha, challenge, el, id, input, op, qr, response, _ref;
|
||||||
if (msg = qr.postInvalid()) {
|
if ($('form button', qr).disabled) {
|
||||||
if (typeof e.preventDefault === "function") {
|
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();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
alert(msg);
|
|
||||||
if (msg === 'You forgot to type in the verification.') {
|
|
||||||
$('#dummy', qr.el).focus();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (conf['Auto Watch Reply'] && conf['Thread Watcher']) {
|
qr = QR.qr;
|
||||||
if (g.REPLY && $('img.favicon').src === Favicon.empty) {
|
$('.error', qr).textContent = '';
|
||||||
watcher.watch(null, g.THREAD_ID);
|
if (e && (el = $('#recaptcha_response_field', qr)).value) {
|
||||||
} else {
|
QR.captchaPush(el);
|
||||||
id = $('input[name=resto]', qr.el).value;
|
}
|
||||||
op = $.id(id);
|
if (!(captcha = QR.captchaShift())) {
|
||||||
if ($('img.favicon', op).src === Favicon.empty) {
|
alert('You forgot to type in the verification.');
|
||||||
watcher.watch(op, id);
|
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) {
|
if (!e) {
|
||||||
this.submit();
|
$('#qr_form', qr).submit();
|
||||||
}
|
}
|
||||||
$('#error', qr.el).textContent = '';
|
QR.sage = /sage/i.test($('[name=email]', qr).value);
|
||||||
if (conf['Auto Hide QR']) {
|
id = $('input[name=resto]', qr).value;
|
||||||
$('#autohide', qr.el).checked = true;
|
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() {
|
sys: function() {
|
||||||
var c, duration, id, noko, recaptcha, sage, search, thread, url, watch, _, _ref, _ref2;
|
var recaptcha;
|
||||||
if (recaptcha = $('#recaptcha_response_field')) {
|
if (recaptcha = $('#recaptcha_response_field')) {
|
||||||
$.bind(recaptcha, 'keydown', Recaptcha.listener);
|
$.bind(recaptcha, 'keydown', QR.keydown);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
http://code.google.com/p/chromium/issues/detail?id=20773
|
http://code.google.com/p/chromium/issues/detail?id=20773
|
||||||
Let content scripts see other frames (instead of them being undefined)
|
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
|
To access the parent, we have to break out of the sandbox and evaluate
|
||||||
in the global context.
|
in the global context.
|
||||||
*/
|
*/
|
||||||
$.globalEval(function() {
|
return $.globalEval(function() {
|
||||||
var data, href, node, textContent, _ref;
|
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;
|
textContent = node.textContent, href = node.href;
|
||||||
data = JSON.stringify({
|
data = JSON.stringify({
|
||||||
textContent: textContent,
|
textContent: textContent,
|
||||||
href: href
|
href: href
|
||||||
});
|
});
|
||||||
} else {
|
} else if (node = $('meta')) {
|
||||||
data = '';
|
data = node.content.match(/url=(.+)/)[1];
|
||||||
|
if (/#/.test(data)) {
|
||||||
|
data = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return parent.postMessage(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 = {
|
threading = {
|
||||||
@ -2594,25 +2576,6 @@
|
|||||||
}
|
}
|
||||||
return location.href = url;
|
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) {
|
nodeInserted = function(e) {
|
||||||
var callback, target, _i, _len, _ref, _results;
|
var callback, target, _i, _len, _ref, _results;
|
||||||
target = e.target;
|
target = e.target;
|
||||||
@ -2815,9 +2778,9 @@
|
|||||||
};
|
};
|
||||||
Main = {
|
Main = {
|
||||||
init: function() {
|
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') {
|
if (location.hostname === 'sys.4chan.org') {
|
||||||
qr.sys();
|
QR.sys();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (conf['404 Redirect'] && d.title === '4chan - 404' && /^\d+$/.test(g.THREAD_ID)) {
|
if (conf['404 Redirect'] && d.title === '4chan - 404' && /^\d+$/.test(g.THREAD_ID)) {
|
||||||
@ -2858,26 +2821,10 @@
|
|||||||
$.set("hiddenReplies/" + g.BOARD + "/", g.hiddenReplies);
|
$.set("hiddenReplies/" + g.BOARD + "/", g.hiddenReplies);
|
||||||
}
|
}
|
||||||
$.addStyle(Main.css);
|
$.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();
|
threading.init();
|
||||||
if (g.REPLY && (id = location.hash.slice(1)) && /\d/.test(id[0]) && !$.id(id)) {
|
if (g.REPLY && (id = location.hash.slice(1)) && /\d/.test(id[0]) && !$.id(id)) {
|
||||||
scrollTo(0, d.body.scrollHeight);
|
scrollTo(0, d.body.scrollHeight);
|
||||||
}
|
}
|
||||||
if (conf['Auto Noko'] && canPost) {
|
|
||||||
form.action += '?noko';
|
|
||||||
}
|
|
||||||
if (conf['Cooldown'] && canPost) {
|
|
||||||
cooldown.init();
|
|
||||||
}
|
|
||||||
if (conf['Image Expansion']) {
|
if (conf['Image Expansion']) {
|
||||||
imgExpand.init();
|
imgExpand.init();
|
||||||
}
|
}
|
||||||
@ -2902,8 +2849,8 @@
|
|||||||
if (conf['Reply Hiding']) {
|
if (conf['Reply Hiding']) {
|
||||||
replyHiding.init();
|
replyHiding.init();
|
||||||
}
|
}
|
||||||
if (conf['Quick Reply'] && canPost) {
|
if (conf['Quick Reply']) {
|
||||||
qr.init();
|
QR.init();
|
||||||
}
|
}
|
||||||
if (conf['Report Button']) {
|
if (conf['Report Button']) {
|
||||||
reportButton.init();
|
reportButton.init();
|
||||||
@ -2933,12 +2880,6 @@
|
|||||||
if (conf['Image Preloading']) {
|
if (conf['Image Preloading']) {
|
||||||
imgPreloading.init();
|
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']) {
|
if (conf['Post in Title']) {
|
||||||
titlePost.init();
|
titlePost.init();
|
||||||
}
|
}
|
||||||
@ -2993,7 +2934,7 @@
|
|||||||
var data, origin;
|
var data, origin;
|
||||||
origin = e.origin, data = e.data;
|
origin = e.origin, data = e.data;
|
||||||
if (origin === 'http://sys.4chan.org') {
|
if (origin === 'http://sys.4chan.org') {
|
||||||
return qr.message(data);
|
return QR.receive(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
css: '\
|
css: '\
|
||||||
@ -3004,7 +2945,7 @@
|
|||||||
div.dialog > div.move {\
|
div.dialog > div.move {\
|
||||||
cursor: move;\
|
cursor: move;\
|
||||||
}\
|
}\
|
||||||
label, a, .favicon, #qr img {\
|
label, a, .favicon {\
|
||||||
cursor: pointer;\
|
cursor: pointer;\
|
||||||
}\
|
}\
|
||||||
\
|
\
|
||||||
@ -3014,12 +2955,6 @@
|
|||||||
.error {\
|
.error {\
|
||||||
color: red;\
|
color: red;\
|
||||||
}\
|
}\
|
||||||
#error {\
|
|
||||||
cursor: default;\
|
|
||||||
}\
|
|
||||||
#error[href] {\
|
|
||||||
cursor: pointer;\
|
|
||||||
}\
|
|
||||||
td.replyhider {\
|
td.replyhider {\
|
||||||
vertical-align: top;\
|
vertical-align: top;\
|
||||||
}\
|
}\
|
||||||
@ -3097,47 +3032,6 @@
|
|||||||
margin: 0;\
|
margin: 0;\
|
||||||
width: 100%;\
|
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 {\
|
#updater {\
|
||||||
position: fixed;\
|
position: fixed;\
|
||||||
@ -3203,6 +3097,68 @@
|
|||||||
#files > input {\
|
#files > input {\
|
||||||
display: block;\
|
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) {
|
if (d.body) {
|
||||||
|
|||||||
712
script.coffee
712
script.coffee
@ -656,15 +656,13 @@ keybinds =
|
|||||||
imgExpand.toggle thumb.parentNode
|
imgExpand.toggle thumb.parentNode
|
||||||
|
|
||||||
qr: (thread, quote) ->
|
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
|
if quote
|
||||||
qr.quote.call qrLink
|
QR.quote.call $ 'a.quotejs + a', $('td.replyhl', thread) or thread
|
||||||
else
|
else
|
||||||
unless qr.el
|
if QR.qr
|
||||||
qr.dialog qrLink
|
$('textarea', QR.qr).focus()
|
||||||
$('textarea', qr.el).focus()
|
else
|
||||||
|
QR.dialog '', thread?.firstChild.id
|
||||||
|
|
||||||
open: (thread, tab) ->
|
open: (thread, tab) ->
|
||||||
id = thread.firstChild.id
|
id = thread.firstChild.id
|
||||||
@ -936,49 +934,25 @@ options =
|
|||||||
conf['backlink'] = @value
|
conf['backlink'] = @value
|
||||||
$('#backlinkPreview').textContent = conf['backlink'].replace /%id/, '123456789'
|
$('#backlinkPreview').textContent = conf['backlink'].replace /%id/, '123456789'
|
||||||
|
|
||||||
cooldown =
|
QR =
|
||||||
#TODO merge into qr
|
#captcha caching for report form
|
||||||
|
#report queueing
|
||||||
|
#check if captchas can be reused on eg dup file error
|
||||||
init: ->
|
init: ->
|
||||||
if match = location.search.match /cooldown=(\d+)/
|
#can't reply in some stickies, recaptcha may be blocked, eg by noscript
|
||||||
[_, time] = match
|
return unless $('form[name=post]') and $('#recaptcha_response_field')
|
||||||
$.set g.BOARD+'/cooldown', time if $.get(g.BOARD+'/cooldown', 0) < time
|
g.callbacks.push (root) ->
|
||||||
cooldown.start() if Date.now() < $.get g.BOARD+'/cooldown', 0
|
quote = $ 'a.quotejs + a', root
|
||||||
$.bind window, 'storage', (e) -> cooldown.start() if e.key is "#{NAMESPACE}#{g.BOARD}/cooldown"
|
$.bind quote, 'click', QR.quote
|
||||||
$('.postarea form').action += '?cooldown' if g.REPLY
|
$.add d.body, $.el 'iframe',
|
||||||
|
name: 'iframe'
|
||||||
start: ->
|
hidden: true
|
||||||
cooldown.duration = Math.ceil ($.get(g.BOARD+'/cooldown', 0) - Date.now()) / 1000
|
# nuke id so qr's field focuses on recaptcha reload, instead of normal form's
|
||||||
return unless cooldown.duration > 0
|
$('#recaptcha_response_field').id = ''
|
||||||
for submit in $$ '#com_submit'
|
holder = $ '#recaptcha_challenge_field_holder'
|
||||||
submit.value = cooldown.duration
|
$.bind holder, 'DOMNodeInserted', QR.captchaNode
|
||||||
submit.disabled = true
|
QR.captchaNode target: holder.firstChild
|
||||||
setTimeout cooldown.cb, 1000
|
QR.accept = $('.rules').textContent.match(/: (.+) /)[1].replace /\w+/g, (type) ->
|
||||||
|
|
||||||
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) ->
|
|
||||||
switch type
|
switch type
|
||||||
when 'JPG'
|
when 'JPG'
|
||||||
'image/JPEG'
|
'image/JPEG'
|
||||||
@ -986,284 +960,261 @@ qr =
|
|||||||
'application/' + type
|
'application/' + type
|
||||||
else
|
else
|
||||||
'image/' + type
|
'image/' + type
|
||||||
|
QR.MAX_FILE_SIZE = $('input[name=MAX_FILE_SIZE]').value
|
||||||
iframe = $.el 'iframe',
|
QR.spoiler = if $('.postarea label') then ' <label>[<input type=checkbox name=spoiler>Spoiler Image?]</label>' else ''
|
||||||
name: 'iframe'
|
if conf['Persistent QR']
|
||||||
hidden: true
|
QR.dialog()
|
||||||
$.add d.body, iframe
|
$('textarea', QR.qr).blur()
|
||||||
|
if conf['Auto Hide QR']
|
||||||
#hack - nuke id so it doesn't grab focus when reloading
|
$('#autohide', QR.qr).checked = true
|
||||||
$('#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()
|
|
||||||
if conf['Cooldown']
|
if conf['Cooldown']
|
||||||
duration = if qr.sage then 60 else 30
|
$.bind window, 'storage', (e) -> QR.cooldown() if e.key is "#{NAMESPACE}cooldown/#{g.BOARD}"
|
||||||
$.set g.BOARD+'/cooldown', Date.now() + duration * 1000
|
attach: ->
|
||||||
cooldown.start()
|
#$('#autopost', QR.qr).checked = true
|
||||||
|
files = $ '#files', QR.qr
|
||||||
node: (root) ->
|
box = $.el 'span',
|
||||||
quote = $ 'a.quotejs:not(:first-child)', root
|
innerHTML: "<input type=file name=upfile accept='#{QR.accept}'><img alt='click here'><a class=x>X</a>"
|
||||||
$.bind quote, 'click', qr.quote
|
file = $ 'input', box
|
||||||
|
$.bind file, 'change', QR.change
|
||||||
postInvalid: ->
|
$.bind $('img', box), 'click', -> @previousSibling.click()
|
||||||
content = $('textarea', qr.el).value or $('input[type=file]', qr.el).files.length
|
$.bind $('.x', box), 'click', -> $.rm @parentNode
|
||||||
return 'Error: No text entered.' unless content
|
$.add files, box
|
||||||
|
file.click()
|
||||||
###
|
captchaNode: (e) ->
|
||||||
captchas expire after 5 hours (emperically verified). cutoff 5 minutes
|
QR.captcha =
|
||||||
before then, b/c posting takes time.
|
challenge: e.target.value
|
||||||
###
|
time: Date.now()
|
||||||
|
QR.captchaImg()
|
||||||
cutoff = Date.now() - 5*HOUR + 5*MINUTE
|
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 = $.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()
|
while captcha = captchas.shift()
|
||||||
if captcha.time > cutoff
|
if captcha.time > cutoff
|
||||||
break
|
break
|
||||||
$.set 'captchas', captchas
|
$.set 'captchas', captchas
|
||||||
|
QR.captchaLength captchas
|
||||||
$('#captchas', qr.el).textContent = captchas.length + ' captchas'
|
captcha
|
||||||
|
captchaLength: (captchas) ->
|
||||||
unless captcha
|
captchas or= $.get 'captchas', []
|
||||||
dummy = $ '#dummy', qr.el
|
$('#cl', QR.qr).textContent = captchas.length + ' captchas'
|
||||||
return 'You forgot to type in the verification' unless response = dummy.value
|
captchaReload: ->
|
||||||
captcha =
|
window.location = 'javascript:Recaptcha.reload()'
|
||||||
challenge: qr.challenge
|
change: (e) ->
|
||||||
response: response
|
file = @files[0]
|
||||||
dummy.value = ''
|
if file.size > QR.MAX_FILE_SIZE
|
||||||
Recaptcha.reload()
|
alert 'Error: File too large.'
|
||||||
|
$.rm @parentNode
|
||||||
$('#recaptcha_challenge_field', qr.el).value = captcha.challenge
|
QR.attach()
|
||||||
$('#recaptcha_response_field', qr.el).value = captcha.response
|
return
|
||||||
|
{qr} = QR
|
||||||
false
|
fr = new FileReader()
|
||||||
|
img = @nextSibling
|
||||||
quote: (e) ->
|
fr.onload = (e) ->
|
||||||
e.preventDefault() if e
|
img.src = e.target.result
|
||||||
|
fr.readAsDataURL file
|
||||||
if qr.el
|
close: ->
|
||||||
$('#autohide', qr.el).checked = false
|
$.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
|
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
|
id = @textContent
|
||||||
text = ">>#{id}\n"
|
text = ">>#{id}\n"
|
||||||
|
sel = getSelection()
|
||||||
selection = window.getSelection()
|
if id == $.x('preceding::input[@type="checkbox"][1]', sel.anchorNode)?.name
|
||||||
if s = selection.toString()
|
if s = sel.toString().replace /\n/g, '\n>'
|
||||||
selectionID = $.x('preceding::input[@type="checkbox"][1]', selection.anchorNode)?.name
|
|
||||||
if selectionID == id
|
|
||||||
s = s.replace /\n/g, '\n>'
|
|
||||||
text += ">#{s}\n"
|
text += ">#{s}\n"
|
||||||
|
{qr} = QR
|
||||||
ta = $ 'textarea', qr.el
|
if not qr
|
||||||
ta.focus()
|
QR.dialog text, tid
|
||||||
ta.value += text
|
|
||||||
|
|
||||||
refresh: ->
|
|
||||||
$('[name=sub]', qr.el).value = ''
|
|
||||||
$('[name=email]', qr.el).value = if m = d.cookie.match(/4chan_email=([^;]+)/) then decodeURIComponent m[1] else ''
|
|
||||||
$('[name=com]', qr.el).value = ''
|
|
||||||
$('[name=recaptcha_response_field]', qr.el).value = ''
|
|
||||||
$('[name=spoiler]', qr.el)?.checked = false unless conf['Remember Spoiler']
|
|
||||||
# XXX opera doesn't allow resetting file inputs w/ file.value = ''
|
|
||||||
oldFile = $ '[type=file]', qr.el
|
|
||||||
newFile = $.el 'input', type: 'file', name: 'upfile', accept: qr.acceptFiles
|
|
||||||
$.replace oldFile, newFile
|
|
||||||
|
|
||||||
submit: (e) ->
|
|
||||||
#XXX `e` won't exist if we're here from `qr.submit.call form`.
|
|
||||||
if msg = qr.postInvalid()
|
|
||||||
e.preventDefault?()
|
|
||||||
alert msg
|
|
||||||
if msg is 'You forgot to type in the verification.'
|
|
||||||
$('#dummy', qr.el).focus()
|
|
||||||
return
|
return
|
||||||
|
$('#autohide', qr).checked = false
|
||||||
if conf['Auto Watch Reply'] and conf['Thread Watcher']
|
ta = $ 'textarea', qr
|
||||||
if g.REPLY and $('img.favicon').src is Favicon.empty
|
v = ta.value
|
||||||
watcher.watch null, g.THREAD_ID
|
ss = ta.selectionStart
|
||||||
else
|
ta.value = v[0...ss] + text + v[ss..]
|
||||||
id = $('input[name=resto]', qr.el).value
|
i = ss + text.length
|
||||||
op = $.id id
|
ta.setSelectionRange i, i
|
||||||
if $('img.favicon', op).src is Favicon.empty
|
ta.focus()
|
||||||
watcher.watch op, id
|
$('[name=resto]', qr).value or= tid
|
||||||
|
receive: (data) ->
|
||||||
if !e then @submit()
|
$('iframe[name=iframe]').src = 'about:blank'
|
||||||
$('#error', qr.el).textContent = ''
|
{qr} = QR
|
||||||
$('#autohide', qr.el).checked = true if conf['Auto Hide QR']
|
row = $('#files input[form]', qr)?.parentNode
|
||||||
qr.sage = /sage/i.test $('input[name=email]', @).value
|
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: ->
|
sys: ->
|
||||||
if recaptcha = $ '#recaptcha_response_field' #post reporting
|
if recaptcha = $ '#recaptcha_response_field' #post reporting
|
||||||
$.bind recaptcha, 'keydown', Recaptcha.listener
|
$.bind recaptcha, 'keydown', QR.keydown
|
||||||
return
|
return
|
||||||
|
|
||||||
###
|
###
|
||||||
http://code.google.com/p/chromium/issues/detail?id=20773
|
http://code.google.com/p/chromium/issues/detail?id=20773
|
||||||
Let content scripts see other frames (instead of them being undefined)
|
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
|
To access the parent, we have to break out of the sandbox and evaluate
|
||||||
in the global context.
|
in the global context.
|
||||||
###
|
###
|
||||||
$.globalEval ->
|
$.globalEval ->
|
||||||
if node = document.querySelector('table font b')?.firstChild
|
$ = (css) -> document.querySelector css
|
||||||
|
if node = $('table font b')?.firstChild
|
||||||
{textContent, href} = node
|
{textContent, href} = node
|
||||||
data = JSON.stringify {textContent, href}
|
data = JSON.stringify {textContent, href}
|
||||||
else
|
else if node = $ 'meta'
|
||||||
data = ''
|
data = node.content.match(/url=(.+)/)[1]
|
||||||
|
if /#/.test data then data = '' #not op
|
||||||
parent.postMessage data, '*'
|
parent.postMessage data, '*'
|
||||||
|
#if we're an iframe, parent will blank us
|
||||||
c = $('b')?.lastChild
|
|
||||||
|
|
||||||
return unless c and c.nodeType is 8 #comment node
|
|
||||||
|
|
||||||
[_, thread, id] = c.textContent.match(/thread:(\d+),no:(\d+)/)
|
|
||||||
|
|
||||||
{search} = location
|
|
||||||
cooldown = /cooldown/.test search
|
|
||||||
noko = /noko/ .test search
|
|
||||||
sage = /sage/ .test search
|
|
||||||
watch = /watch/ .test search
|
|
||||||
|
|
||||||
url = "http://boards.4chan.org/#{g.BOARD}"
|
|
||||||
|
|
||||||
if watch and thread is '0'
|
|
||||||
url += "/res/#{id}?watch"
|
|
||||||
else if noko
|
|
||||||
url += '/res/'
|
|
||||||
url += if thread is '0' then id else thread
|
|
||||||
if cooldown
|
|
||||||
duration = Date.now() + (if sage then 60 else 30) * 1000
|
|
||||||
url += '?cooldown=' + duration
|
|
||||||
if noko
|
|
||||||
url += '#' + id
|
|
||||||
|
|
||||||
window.location = url
|
|
||||||
|
|
||||||
validateFileSize: (e) ->
|
|
||||||
return unless @files[0].size > $('input[name=MAX_FILE_SIZE]').value
|
|
||||||
|
|
||||||
file = $.el 'input', type: 'file', name: 'upfile', accept: qr.acceptFiles
|
|
||||||
$.bind file, 'change', qr.validateFileSize
|
|
||||||
$.replace @, file
|
|
||||||
|
|
||||||
$('#error', qr.el).textContent = 'Error: File too large.'
|
|
||||||
alert 'Error: File too large.'
|
|
||||||
|
|
||||||
threading =
|
threading =
|
||||||
init: ->
|
init: ->
|
||||||
@ -1972,18 +1923,6 @@ redirect = ->
|
|||||||
url = "http://boards.4chan.org/#{g.BOARD}"
|
url = "http://boards.4chan.org/#{g.BOARD}"
|
||||||
location.href = url
|
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) ->
|
nodeInserted = (e) ->
|
||||||
{target} = e
|
{target} = e
|
||||||
if target.nodeName is 'TABLE'
|
if target.nodeName is 'TABLE'
|
||||||
@ -2187,7 +2126,7 @@ firstRun =
|
|||||||
Main =
|
Main =
|
||||||
init: ->
|
init: ->
|
||||||
if location.hostname is 'sys.4chan.org'
|
if location.hostname is 'sys.4chan.org'
|
||||||
qr.sys()
|
QR.sys()
|
||||||
return
|
return
|
||||||
if conf['404 Redirect'] and d.title is '4chan - 404' and /^\d+$/.test g.THREAD_ID
|
if conf['404 Redirect'] and d.title is '4chan - 404' and /^\d+$/.test g.THREAD_ID
|
||||||
redirect()
|
redirect()
|
||||||
@ -2224,13 +2163,6 @@ Main =
|
|||||||
|
|
||||||
$.addStyle Main.css
|
$.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
|
#major features
|
||||||
threading.init()
|
threading.init()
|
||||||
|
|
||||||
@ -2239,12 +2171,6 @@ Main =
|
|||||||
if g.REPLY and (id = location.hash[1..]) and /\d/.test(id[0]) and !$.id(id)
|
if g.REPLY and (id = location.hash[1..]) and /\d/.test(id[0]) and !$.id(id)
|
||||||
scrollTo 0, d.body.scrollHeight
|
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']
|
if conf['Image Expansion']
|
||||||
imgExpand.init()
|
imgExpand.init()
|
||||||
|
|
||||||
@ -2269,8 +2195,8 @@ Main =
|
|||||||
if conf['Reply Hiding']
|
if conf['Reply Hiding']
|
||||||
replyHiding.init()
|
replyHiding.init()
|
||||||
|
|
||||||
if conf['Quick Reply'] and canPost
|
if conf['Quick Reply']
|
||||||
qr.init()
|
QR.init()
|
||||||
|
|
||||||
if conf['Report Button']
|
if conf['Report Button']
|
||||||
reportButton.init()
|
reportButton.init()
|
||||||
@ -2300,11 +2226,6 @@ Main =
|
|||||||
if conf['Image Preloading']
|
if conf['Image Preloading']
|
||||||
imgPreloading.init()
|
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']
|
if conf['Post in Title']
|
||||||
titlePost.init()
|
titlePost.init()
|
||||||
|
|
||||||
@ -2345,7 +2266,7 @@ Main =
|
|||||||
message: (e) ->
|
message: (e) ->
|
||||||
{origin, data} = e
|
{origin, data} = e
|
||||||
if origin is 'http://sys.4chan.org'
|
if origin is 'http://sys.4chan.org'
|
||||||
qr.message data
|
QR.receive data
|
||||||
|
|
||||||
css: '
|
css: '
|
||||||
/* dialog styling */
|
/* dialog styling */
|
||||||
@ -2355,7 +2276,7 @@ Main =
|
|||||||
div.dialog > div.move {
|
div.dialog > div.move {
|
||||||
cursor: move;
|
cursor: move;
|
||||||
}
|
}
|
||||||
label, a, .favicon, #qr img {
|
label, a, .favicon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2365,12 +2286,6 @@ Main =
|
|||||||
.error {
|
.error {
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
#error {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
#error[href] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
td.replyhider {
|
td.replyhider {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
@ -2449,47 +2364,6 @@ Main =
|
|||||||
width: 100%;
|
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 {
|
#updater {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@ -2554,6 +2428,68 @@ Main =
|
|||||||
#files > input {
|
#files > input {
|
||||||
display: block;
|
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
|
if d.body
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user