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.spoiler + "
" + ($.get('captchas', []).length) + " captchas
attach another file
"; - 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: "click hereX" + }); + 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
120 Captchas
" + (g.REPLY ? "" : '') + " " + QR.spoiler + "
"); + 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.spoiler}
-
-
-
-
#{$.get('captchas', []).length} captchas
-
-
-
-
attach another file
-
- - " - 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: "click hereX" + 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 + +
+ + + + + + +
+
+ +
+
+ +
+
+ 120 Captchas + +
+
+ + #{if g.REPLY then "" else ''} + + #{QR.spoiler} +
+
+
+ + " + #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