From 0181da694f06a54d07d8d48a553c8f14e893ce1e Mon Sep 17 00:00:00 2001 From: James Campos Date: Sat, 26 Mar 2011 20:53:14 -0700 Subject: [PATCH] rewrite qr; encapsulate, html5 postmessage --- 4chan_x.js | 411 ++++++++++++++++++++++++++++---------------------- script.coffee | 313 +++++++++++++++++++++----------------- 2 files changed, 405 insertions(+), 319 deletions(-) diff --git a/4chan_x.js b/4chan_x.js index ef3148119..3f9a982bf 100644 --- a/4chan_x.js +++ b/4chan_x.js @@ -56,7 +56,7 @@ */ (function() { - var $, $$, DAY, a, arr, as, autoWatch, autohide, b, board, callback, changeCheckbox, changeValue, clearHidden, closeQR, config, cooldown, cutoff, d, delform, down, editSauce, el, expand, expandComment, expandThread, formSubmit, g, getConfig, getThread, getTime, hide, hideReply, hideThread, href, html, i, id, iframe, iframeLoad, imageClick, imageExpand, imageExpandClick, imageHover, imageResize, imageThumb, imageToggle, imageType, imageTypeChange, img, inAfter, inBefore, input, inputs, keyModeInsert, keyModeNormal, keydown, keypress, l1, lastChecked, log, m, mv, n, navbotr, navtopr, nodeInserted, now, omitted, onloadComment, onloadThread, option, options, parseResponse, pathname, qrListener, qrText, quickReply, recaptcha, recaptchaListener, recaptchaReload, redirect, replace, replyNav, report, request, rm, scroll, scrollThread, show, showReply, showThread, slice, span, src, start, stopPropagation, temp, text, textContent, thread, threadF, threads, tn, tzOffset, ui, up, updateAuto, updateCallback, updateFavicon, updateInterval, updateNow, updateTime, updateTitle, updateVerbose, updaterMake, watch, watchX, watcher, watcherUpdate, x, zeroPad, _, _base, _i, _j, _k, _l, _len, _len2, _len3, _len4, _len5, _len6, _len7, _m, _n, _ref, _ref2, _ref3, _ref4, _ref5; + var $, $$, DAY, a, arr, as, autoWatch, callback, changeCheckbox, changeValue, clearHidden, closeQR, config, cooldown, cutoff, d, delform, down, editSauce, el, expand, expandComment, expandThread, g, getConfig, getThread, getTime, hide, hideReply, hideThread, href, html, i, id, imageClick, imageExpand, imageExpandClick, imageHover, imageResize, imageThumb, imageToggle, imageType, imageTypeChange, img, inAfter, inBefore, input, inputs, keyModeInsert, keyModeNormal, keydown, keypress, l1, lastChecked, log, m, mv, n, navbotr, navtopr, nodeInserted, now, omitted, onloadComment, onloadThread, option, options, parseResponse, pathname, qr, recaptcha, recaptchaListener, recaptchaReload, redirect, replace, replyNav, report, request, rm, scroll, scrollThread, show, showReply, showThread, slice, span, src, start, stopPropagation, temp, text, textContent, threadF, threads, tn, tzOffset, ui, up, updateAuto, updateCallback, updateFavicon, updateInterval, updateNow, updateTime, updateTitle, updateVerbose, updaterMake, watch, watchX, watcher, watcherUpdate, x, zeroPad, _i, _j, _k, _l, _len, _len2, _len3, _len4, _len5, _len6, _len7, _m, _n, _ref, _ref2, _ref3, _ref4; var __slice = Array.prototype.slice; if (typeof console != "undefined" && console !== null) { log = console.log; @@ -241,6 +241,29 @@ return object; }; $.extend($, { + addClass: function(el, className) { + return el.className += ' ' + className; + }, + removeClass: function(el, className) { + return el.className = el.className.replace(' ' + className, ''); + }, + rm: function(el) { + return el.parentNode.removeChild(el); + }, + append: function(parent, child) { + return parent.appendChild(child); + }, + before: function(root, el) { + return root.parentNode.insertBefore(el, root); + }, + el: function(tag, properties) { + var el; + el = d.createElement(tag); + if (properties) { + $.extend(el, properties); + } + return el; + }, bind: function(el, eventType, handler) { return el.addEventListener(eventType, handler, true); }, @@ -387,17 +410,6 @@ return n; } }; - autohide = function() { - var klass, qr; - qr = $('#qr'); - klass = qr.className; - if (this.checked) { - klass += ' auto'; - } else { - klass = klass.replace(' auto', ''); - } - return qr.className = klass; - }; autoWatch = function() { var autoText; autoText = $('textarea', this).value.slice(0, 25); @@ -500,28 +512,6 @@ } } }; - formSubmit = function(e) { - var recaptcha, span, _ref; - if (span = this.nextSibling) { - rm(span); - } - recaptcha = $('input[name=recaptcha_response_field]', this); - if (recaptcha.value) { - if ((_ref = $('#qr input[title=autohide]:not(:checked)')) != null) { - _ref.click(); - } - return g.sage = $('#qr input[name=email]').value === 'sage' ? true : false; - } else { - e.preventDefault(); - span = n('span', { - className: 'error', - textContent: 'You forgot to type in the verification.' - }); - mv(span, this.parentNode); - alert('You forgot to type in the verification.'); - return recaptcha.focus(); - } - }; hideReply = function(reply) { var a, div, name, p, table, trip, _ref; if (p = this.parentNode) { @@ -576,42 +566,6 @@ return inBefore(div, a); } }; - iframeLoad = function() { - var auto, error, f, qr, span, submit, _ref, _ref2; - if (g.iframe = !g.iframe) { - return; - } - $('iframe').src = 'about:blank'; - qr = $('#qr'); - if (error = GM_getValue('error')) { - span = n('span', { - textContent: error, - className: 'error' - }); - mv(span, qr); - if ((_ref = $('input[title=autohide]:checked', qr)) != null) { - _ref.click(); - } - } else if (g.REPLY && getConfig('Persistent QR')) { - $('textarea', qr).value = ''; - $('input[name=recaptcha_response_field]', qr).value = ''; - f = $('input[type=file]', qr).parentNode; - f.innerHTML = f.innerHTML; - submit = $('input[type=submit]', qr); - submit.value = g.sage ? 60 : 30; - submit.disabled = true; - window.setTimeout(cooldown, 1000); - auto = submit.previousSibling.lastChild; - if (auto.checked) { - if ((_ref2 = $('input[title=autohide]:checked', qr)) != null) { - _ref2.click(); - } - } - } else { - rm(qr); - } - return recaptchaReload(); - }; imageHover = { init: function() { var img; @@ -830,9 +784,14 @@ } } if (e.shiftKey) { - return quickReply(qrLink); + $.append(d.body, qr.dialog(qrLink)); + return $('#qr textarea').focus(); } else { - return quickReply(qrLink, qrText(qrLink)); + e = { + preventDefault: function() {}, + target: qrLink + }; + return qr.cb.quote(e); } break; case "J": @@ -926,7 +885,7 @@ } }; nodeInserted = function(e) { - var callback, qr, target, _i, _len, _ref, _results; + var callback, dialog, target, _i, _len, _ref, _results; target = e.target; if (target.nodeName === 'TABLE') { _ref = g.callbacks; @@ -936,9 +895,9 @@ _results.push(callback(target)); } return _results; - } else if (target.id === 'recaptcha_challenge_field' && (qr = $('#qr'))) { - $('#recaptcha_image img', qr).src = "http://www.google.com/recaptcha/api/image?c=" + target.value; - return $('#recaptcha_challenge_field', qr).value = target.value; + } else if (target.id === 'recaptcha_challenge_field' && (dialog = $('#qr'))) { + $('#recaptcha_image img', dialog).src = "http://www.google.com/recaptcha/api/image?c=" + target.value; + return $('#recaptcha_challenge_field', dialog).value = target.value; } }; onloadComment = function(responseText, a, href) { @@ -1029,73 +988,200 @@ opbq = $('blockquote', body); return [replies, opbq]; }; - qrListener = function(e) { - var link, text; - e.preventDefault(); - link = e.target; - text = qrText(link); - return quickReply(link, text); - }; - qrText = function(link) { - var id, s, selection, text, _ref; - text = '>>' + link.parentNode.id.match(/\d+$/)[0] + '\n'; - selection = window.getSelection(); - id = (_ref = x('preceding::span[@id][1]', selection.anchorNode)) != null ? _ref.id : void 0; - if ((s = selection.toString()) && (id === link.parentNode.id)) { - text += ">" + s; - } - return text; - }; - quickReply = function(link, text) { - var auto, autoBox, clone, form, html, input, qr, script, submit, textarea, xpath, _i, _len, _ref, _ref2; - if (!(qr = $('#qr'))) { - html = "
Quick Reply X
"; - qr = ui.dialog('qr', 'topleft', html); - $('input[title=autohide]', qr).addEventListener('click', autohide, true); - form = $('form[name=post]'); - clone = form.cloneNode(true); + qr = { + init: function() { + var iframe; + g.callbacks.push(qr.cb.node); + iframe = $.el('iframe', { + name: 'iframe' + }); + $.append(d.body, iframe); + $.bind(iframe, 'load', qr.cb.load); + $.bind(window, 'message', qr.cb.messageTop); + return $('#recaptcha_response_field').id = ''; + }, + autohide: { + set: function() { + var _ref; + return (_ref = $('#qr input[title=autohide]:not(:checked)')) != null ? _ref.click() : void 0; + }, + unset: function() { + var _ref; + return (_ref = $('#qr input[title=autohide]:checked')) != null ? _ref.click() : void 0; + } + }, + cb: { + autohide: function(e) { + var dialog; + dialog = $('#qr'); + if (this.checked) { + return $.addClass(dialog, 'auto'); + } else { + return $.removeClass(dialog, 'auto'); + } + }, + load: function(e) { + return e.target.contentWindow.postMessage('', '*'); + }, + messageIframe: function(e) { + var message; + message = $('table b').firstChild.textContent; + e.source.postMessage(message, '*'); + return window.location = 'about:blank'; + }, + messageTop: function(e) { + var data, dialog, error; + data = e.data; + dialog = $('#qr'); + if (data === 'Post successful!') { + if (dialog) { + if (getConfig('Persistent QR')) { + qr.refresh(dialog); + } else { + $.rm(dialog); + } + } + } else { + error = $.el('span', { + className: 'error', + textContent: data + }); + $.append(dialog, error); + qr.autohide.unset(); + } + return recaptchaReload(); + }, + node: function(root) { + var quote, quotes, _i, _len, _results; + quotes = $$('a.quotejs:not(:first-child)', root); + _results = []; + for (_i = 0, _len = quotes.length; _i < _len; _i++) { + quote = quotes[_i]; + _results.push($.bind(quote, 'click', qr.cb.quote)); + } + return _results; + }, + submit: function(e) { + var recaptcha, span; + if (span = this.nextSibling) { + $.rm(span); + } + recaptcha = $('input[name=recaptcha_response_field]', this); + if (recaptcha.value) { + qr.autohide.set(); + return g.sage = $('#qr input[name=email]').value === 'sage'; + } else { + e.preventDefault(); + span = $.el('span', { + className: 'error', + textContent: 'You forgot to type in the verification.' + }); + $.append(this.parentNode, span); + alert('You forgot to type in the verification.'); + return recaptcha.focus(); + } + }, + quote: function(e) { + var dialog, id, s, selection, selectionID, ta, target, text, _ref; + e.preventDefault(); + target = e.target; + if (!(dialog = $('#qr'))) { + dialog = qr.dialog(target); + } + id = target.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) { + text += ">" + s + "\n"; + } + } + ta = $('textarea', dialog); + ta.focus(); + return ta.value += text; + }, + refresh: function(dialog) { + var auto, f, submit, _ref; + $('textarea', dialog).value = ''; + $('input[name=recaptcha_response_field]', dialog).value = ''; + f = $('input[type=file]', dialog).parentNode; + f.innerHTML = f.innerHTML; + submit = $('input[type=submit]', qr); + submit.value = g.sage ? 60 : 30; + submit.disabled = true; + window.setTimeout(cooldown, 1000); + auto = submit.previousSibling.lastChild; + if (auto.checked) { + return (_ref = $('input[title=autohide]:checked', qr)) != null ? _ref.click() : void 0; + } + } + }, + dialog: function(link) { + var auto, autobox, clone, dialog, el, html, input, script, submit, xpath, _i, _len, _ref; + html = "
Quick Reply X
"; + dialog = ui.dialog('qr', { + top: '0px', + left: '0px' + }, html); + el = $('input[title=autohide]', dialog); + $.bind(el, 'click', qr.cb.autohide); + clone = $('form[name=post]').cloneNode(true); _ref = $$('script', clone); for (_i = 0, _len = _ref.length; _i < _len; _i++) { script = _ref[_i]; - rm(script); + $.rm(script); } - m($('input[name=recaptcha_response_field]', clone), { - listener: ['keydown', recaptchaListener] - }); - m(clone, { - listener: ['submit', formSubmit], - target: 'iframe' - }); + clone.target = 'iframe'; + $.bind(clone, 'submit', qr.cb.submit); + $.bind($('input[name=recaptcha_response_field]', clone), 'keydown', recaptchaListener); if (!g.REPLY) { xpath = 'preceding::span[@class="postername"][1]/preceding::input[1]'; - input = n('input', { + input = $.el('input', { type: 'hidden', name: 'resto', value: x(xpath, link).name }); - mv(input, clone); + $.append(clone, input); } else if (getConfig('Persistent QR')) { submit = $('input[type=submit]', clone); - auto = n('label', { + auto = $.el('label', { textContent: 'Auto' }); - autoBox = n('input', { + autobox = $.el('input', { type: 'checkbox' }); - mv(autoBox, auto); - inBefore(submit, auto); + $.append(auto, autobox); + $.before(submit, auto); + } + $.append(dialog, clone); + $.append(d.body, dialog); + dialog.style.width = dialog.offsetWidth; + return dialog; + }, + persist: function() { + $.append(d.body, qr.dialog()); + return qr.autohide.set(); + }, + sys: function() { + var board, html, id, recaptcha, thread, _, _base, _ref, _ref2; + $.bind(window, 'message', qr.cb.messageIframe); + if (recaptcha = $('#recaptcha_response_field')) { + $.bind(recaptcha, 'keydown', recaptchaListener); + } + if (getConfig('Auto Watch')) { + html = $('b').innerHTML; + _ref = html.match(//), _ = _ref[0], thread = _ref[1], id = _ref[2]; + if (thread === '0') { + _ref2 = $('meta', d).content.match(/4chan.org\/(\w+)\//), _ = _ref2[0], board = _ref2[1]; + (_base = g.watched)[board] || (_base[board] = []); + g.watched[board].push({ + id: id, + text: GM_getValue('autoText') + }); + return GM_setValue('watched', JSON.stringify(g.watched)); + } } - mv(clone, qr); - mv(qr, d.body); - qr.style.width = qr.offsetWidth; - } - if ((_ref2 = $('input[title=autohide]:checked', qr)) != null) { - _ref2.click(); - } - textarea = $('textarea', qr); - textarea.focus(); - if (text) { - return textarea.value += text; } }; recaptchaListener = function(e) { @@ -1484,7 +1570,6 @@ favDefault: ((_ref = $('link[rel="shortcut icon"]', d)) != null ? _ref.href : void 0) || '', favEmpty: 'http://static.4chan.org/image/favicon-dis.ico', flavors: ['http://regex.info/exif.cgi?url=', 'http://iqdb.org/?url=', 'http://saucenao.com/search.php?db=999&url=', 'http://tineye.com/search?url='].join('\n'), - iframe: false, watched: JSON.parse(GM_getValue('watched', '{}')), xhrs: [] }; @@ -1504,31 +1589,6 @@ if ($.isDST()) { g.chanOffset -= 1; } - if (location.hostname.split('.')[0] === 'sys') { - if (recaptcha = $('#recaptcha_response_field')) { - m(recaptcha, { - listener: ['keydown', recaptchaListener] - }); - } else if (b = $('table font b')) { - GM_setValue('error', b.firstChild.textContent); - } else { - GM_setValue('error', ''); - if (getConfig('Auto Watch')) { - html = $('b').innerHTML; - _ref2 = html.match(//), _ = _ref2[0], thread = _ref2[1], id = _ref2[2]; - if (thread === '0') { - board = $('meta', d).content.match(/4chan.org\/(\w+)\//)[1]; - (_base = g.watched)[board] || (_base[board] = []); - g.watched[board].push({ - id: id, - text: GM_getValue('autoText') - }); - GM_setValue('watched', JSON.stringify(g.watched)); - } - } - } - return; - } lastChecked = GM_getValue('lastChecked', 0); now = getTime(); DAY = 24 * 60 * 60; @@ -1628,6 +1688,10 @@ background: lime;\ }\ '); + if (location.hostname === 'sys.4chan.org') { + qr.sys(); + return; + } if (navtopr = $('#navtopr a')) { a = n('a', { textContent: '4chan X', @@ -1647,9 +1711,9 @@ } else { return; } - _ref3 = $$('#recaptcha_table a'); - for (_i = 0, _len = _ref3.length; _i < _len; _i++) { - el = _ref3[_i]; + _ref2 = $$('#recaptcha_table a'); + for (_i = 0, _len = _ref2.length; _i < _len; _i++) { + el = _ref2[_i]; el.tabIndex = 1; } recaptcha = $('#recaptcha_response_field'); @@ -1677,9 +1741,9 @@ innerHTML: " " }); imageType = GM_getValue('imageType', 'full'); - _ref4 = $$("option", expand); - for (_j = 0, _len2 = _ref4.length; _j < _len2; _j++) { - option = _ref4[_j]; + _ref3 = $$("option", expand); + for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) { + option = _ref3[_j]; if (option.textContent === imageType) { option.selected = true; break; @@ -1719,7 +1783,7 @@ } if (getConfig('Localize Time')) { g.callbacks.push(function(root) { - var date, day, dotw, hour, min_sec, month, s, span, spans, year, _i, _len, _ref, _results; + var date, day, dotw, hour, min_sec, month, s, span, spans, year, _, _i, _len, _ref, _results; spans = $$('span[id^=no]', root); _results = []; for (_i = 0, _len = spans.length; _i < _len; _i++) { @@ -1779,7 +1843,7 @@ } if (getConfig('Reply Hiding')) { g.callbacks.push(function(root) { - var next, obj, td, tds, _i, _len, _results; + var id, next, obj, td, tds, _i, _len, _results; tds = $$('td.doubledash', root); _results = []; for (_i = 0, _len = tds.length; _i < _len; _i++) { @@ -1807,23 +1871,7 @@ }); } if (getConfig('Quick Reply')) { - iframe = n('iframe', { - name: 'iframe', - listener: ['load', iframeLoad] - }); - hide(iframe); - mv(iframe, d.body); - g.callbacks.push(function(root) { - var quote, quotes, _i, _len, _results; - quotes = $$('a.quotejs:not(:first-child)', root); - _results = []; - for (_i = 0, _len = quotes.length; _i < _len; _i++) { - quote = quotes[_i]; - _results.push(quote.addEventListener('click', qrListener, true)); - } - return _results; - }); - recaptcha.id = ''; + qr.init(); } if (getConfig('Quick Report')) { g.callbacks.push(function(root) { @@ -1939,8 +1987,7 @@ updaterMake(); } if (getConfig('Quick Reply') && getConfig('Persistent QR')) { - quickReply(); - $('#qr input[title=autohide]').click(); + qr.persist(); } if (getConfig('Post in Title')) { if (!(text = $('span.filetitle').textContent)) { @@ -2036,9 +2083,9 @@ } } } - _ref5 = g.callbacks; - for (_n = 0, _len7 = _ref5.length; _n < _len7; _n++) { - callback = _ref5[_n]; + _ref4 = g.callbacks; + for (_n = 0, _len7 = _ref4.length; _n < _len7; _n++) { + callback = _ref4[_n]; callback(); } d.body.addEventListener('DOMNodeInserted', nodeInserted, true); diff --git a/script.coffee b/script.coffee index 08dc9d31e..06cca25e0 100644 --- a/script.coffee +++ b/script.coffee @@ -143,12 +143,26 @@ $.extend = (object, properties) -> object $.extend $, + addClass: (el, className) -> + el.className += ' ' + className + removeClass: (el, className) -> + el.className = el.className.replace ' ' + className, '' + rm: (el) -> + el.parentNode.removeChild el + append: (parent, child) -> + parent.appendChild child + before: (root, el) -> + root.parentNode.insertBefore el, root + el: (tag, properties) -> + el = d.createElement tag + $.extend el, properties if properties + el bind: (el, eventType, handler) -> el.addEventListener eventType, handler, true unbind: (el, eventType, handler) -> el.removeEventListener eventType, handler, true isDST: -> - # XXX this should be isDSTinNY + # XXX this should check for DST in NY ### http://en.wikipedia.org/wiki/Daylight_saving_time_in_the_United_States Since 2007, daylight saving time starts on the second Sunday of March @@ -249,15 +263,6 @@ zeroPad = (n) -> if n < 10 then '0' + n else n #funks -autohide = -> - qr = $ '#qr' - klass = qr.className - if @checked - klass += ' auto' - else - klass = klass.replace(' auto', '') - qr.className = klass - autoWatch = -> #TODO look for subject autoText = $('textarea', this).value.slice(0, 25) @@ -345,22 +350,6 @@ getThread = -> if bottom > 0 #we have not scrolled past return [thread, i] -formSubmit = (e) -> - if span = @nextSibling - rm span - recaptcha = $('input[name=recaptcha_response_field]', this) - if recaptcha.value - $('#qr input[title=autohide]:not(:checked)')?.click() - g.sage = if $('#qr input[name=email]').value is 'sage' then true else false - else - e.preventDefault() - span = n 'span', - className: 'error' - textContent: 'You forgot to type in the verification.' - mv span, @parentNode - alert 'You forgot to type in the verification.' - recaptcha.focus() - hideReply = (reply) -> if p = @parentNode reply = p.nextSibling @@ -406,35 +395,6 @@ hideThread = (div) -> listener: ['click', showThread] inBefore div, a -iframeLoad = -> - if g.iframe = !g.iframe - return - $('iframe').src = 'about:blank' - qr = $ '#qr' - if error = GM_getValue 'error' - span = n 'span', - textContent: error - className: 'error' - mv span, qr - $('input[title=autohide]:checked', qr)?.click() - else if g.REPLY and getConfig 'Persistent QR' - $('textarea', qr).value = '' - $('input[name=recaptcha_response_field]', qr).value = '' - # XXX file.value = '' doesn't work in opera - f = $('input[type=file]', qr).parentNode - f.innerHTML = f.innerHTML - submit = $ 'input[type=submit]', qr - submit.value = if g.sage then 60 else 30 - submit.disabled = true - window.setTimeout cooldown, 1000 - auto = submit.previousSibling.lastChild - if auto.checked - #unhide the qr so you know it's ready for the next item - $('input[title=autohide]:checked', qr)?.click() - else - rm qr - recaptchaReload() - imageHover = init: -> img = n 'img', id: 'iHover' @@ -615,9 +575,14 @@ keyModeNormal = (e) -> unless qrLink = $ 'td.replyhl span[id] a:not(:first-child)', thread qrLink = $ "span#nothread#{thread.id} a:not(:first-child)", thread if e.shiftKey - quickReply qrLink + $.append d.body, qr.dialog qrLink + $('#qr textarea').focus() else - quickReply qrLink, qrText qrLink + # qrLink.click() doesn't work, so use this hack + e = + preventDefault: -> + target: qrLink + qr.cb.quote e when "J" if e.shiftKey if not g.REPLY then [root] = getThread() @@ -687,9 +652,9 @@ nodeInserted = (e) -> if target.nodeName is 'TABLE' for callback in g.callbacks callback target - else if target.id is 'recaptcha_challenge_field' and qr = $ '#qr' - $('#recaptcha_image img', qr).src = "http://www.google.com/recaptcha/api/image?c=" + target.value - $('#recaptcha_challenge_field', qr).value = target.value + else if target.id is 'recaptcha_challenge_field' and dialog = $ '#qr' + $('#recaptcha_image img', dialog).src = "http://www.google.com/recaptcha/api/image?c=" + target.value + $('#recaptcha_challenge_field', dialog).value = target.value onloadComment = (responseText, a, href) -> [_, op, id] = href.match /(\d+)#(\d+)/ @@ -760,63 +725,169 @@ parseResponse = (responseText) -> opbq = $ 'blockquote', body return [replies, opbq] -qrListener = (e) -> - e.preventDefault() - link = e.target - text = qrText link - quickReply link, text +qr = + init: -> + g.callbacks.push qr.cb.node + iframe = $.el 'iframe', + name: 'iframe' + $.append d.body, iframe + $.bind iframe, 'load', qr.cb.load + $.bind window, 'message', qr.cb.messageTop -qrText = (link) -> - #we can't just use textContent b/c of the xxxs. goddamit moot. - text = '>>' + link.parentNode.id.match(/\d+$/)[0] + '\n' + #hack - nuke id so it doesn't grab focus when reloading + $('#recaptcha_response_field').id = '' - selection = window.getSelection() - id = x('preceding::span[@id][1]', selection.anchorNode)?.id - if (s = selection.toString()) and (id is link.parentNode.id) - text += ">#{s}" + autohide: + set: -> + $('#qr input[title=autohide]:not(:checked)')?.click() + unset: -> + $('#qr input[title=autohide]:checked')?.click() - text + cb: + autohide: (e) -> + dialog = $ '#qr' + if @checked + $.addClass dialog, 'auto' + else + $.removeClass dialog, 'auto' -quickReply = (link, text) -> - unless qr = $ '#qr' - html = "
Quick Reply X
" - qr = ui.dialog 'qr', 'topleft', html - $('input[title=autohide]', qr).addEventListener 'click', autohide, true + load: (e) -> + e.target.contentWindow.postMessage '', '*' - form = $ 'form[name=post]' - clone = form.cloneNode true - #remove recaptcha scripts + messageIframe: (e) -> + message = $('table b').firstChild.textContent + e.source.postMessage message, '*' + window.location = 'about:blank' + + messageTop: (e) -> + {data} = e + dialog = $ '#qr' + if data is 'Post successful!' + if dialog + if getConfig 'Persistent QR' + qr.refresh dialog + else + $.rm dialog + else + error = $.el 'span', + className: 'error' + textContent: data + $.append dialog, error + qr.autohide.unset() + + recaptchaReload() + + node: (root) -> + quotes = $$ 'a.quotejs:not(:first-child)', root + for quote in quotes + $.bind quote, 'click', qr.cb.quote + + submit: (e) -> + if span = @nextSibling + $.rm span + recaptcha = $('input[name=recaptcha_response_field]', this) + if recaptcha.value + qr.autohide.set() + g.sage = $('#qr input[name=email]').value == 'sage' + else + e.preventDefault() + span = $.el 'span', + className: 'error' + textContent: 'You forgot to type in the verification.' + $.append @parentNode, span + alert 'You forgot to type in the verification.' + recaptcha.focus() + + quote: (e) -> + e.preventDefault() + {target} = e + unless dialog = $ '#qr' + dialog = qr.dialog target + + id = target.textContent + text = ">>#{id}\n" + + selection = window.getSelection() + if s = selection.toString() + selectionID = x('preceding::input[@type="checkbox"][1]', selection.anchorNode)?.name + if selectionID == id + text += ">#{s}\n" + + ta = $ 'textarea', dialog + ta.focus() + ta.value += text + + refresh: (dialog) -> + $('textarea', dialog).value = '' + $('input[name=recaptcha_response_field]', dialog).value = '' + # XXX file.value = '' doesn't work in opera + f = $('input[type=file]', dialog).parentNode + f.innerHTML = f.innerHTML + submit = $ 'input[type=submit]', qr + submit.value = if g.sage then 60 else 30 + submit.disabled = true + window.setTimeout cooldown, 1000 + auto = submit.previousSibling.lastChild + if auto.checked + #unhide the qr so you know it's ready for the next item + $('input[title=autohide]:checked', qr)?.click() + + dialog: (link) -> + html = "
Quick Reply X
" + dialog = ui.dialog 'qr', top: '0px', left: '0px', html + el = $ 'input[title=autohide]', dialog + $.bind el, 'click', qr.cb.autohide + + clone = $('form[name=post]').cloneNode(true) for script in $$ 'script', clone - rm script - m $('input[name=recaptcha_response_field]', clone), - listener: ['keydown', recaptchaListener] - m clone, - listener: ['submit', formSubmit] - target: 'iframe' + $.rm script + clone.target = 'iframe' + $.bind clone, 'submit', qr.cb.submit + $.bind $('input[name=recaptcha_response_field]', clone), 'keydown', recaptchaListener + if not g.REPLY #figure out which thread we're replying to xpath = 'preceding::span[@class="postername"][1]/preceding::input[1]' - input = n 'input', + input = $.el 'input', type: 'hidden' name: 'resto' value: x(xpath, link).name - mv input, clone + $.append clone, input else if getConfig 'Persistent QR' submit = $ 'input[type=submit]', clone - auto = n 'label', + auto = $.el 'label', textContent: 'Auto' - autoBox = n 'input', + autobox = $.el 'input', type: 'checkbox' - mv autoBox, auto - inBefore submit, auto - mv clone, qr - mv qr, d.body - qr.style.width = qr.offsetWidth #lock + $.append auto, autobox + $.before submit, auto - $('input[title=autohide]:checked', qr)?.click() - textarea = $('textarea', qr) - textarea.focus() - if text then textarea.value += text + $.append dialog, clone + $.append d.body, dialog + dialog.style.width = dialog.offsetWidth # lock + + dialog + + persist: -> + $.append d.body, qr.dialog() + qr.autohide.set() + + sys: -> + $.bind window, 'message', qr.cb.messageIframe + if recaptcha = $ '#recaptcha_response_field' + # post reporting + $.bind recaptcha, 'keydown', recaptchaListener + if getConfig 'Auto Watch' + html = $('b').innerHTML + [_, thread, id] = html.match(//) + if thread is '0' + [_, board] = $('meta', d).content.match(/4chan.org\/(\w+)\//) + g.watched[board] or= [] + g.watched[board].push { + id: id, + text: GM_getValue 'autoText' + } + GM_setValue 'watched', JSON.stringify g.watched recaptchaListener = (e) -> if e.keyCode is 8 and @value is '' @@ -1127,7 +1198,6 @@ g = 'http://saucenao.com/search.php?db=999&url=' 'http://tineye.com/search?url=' ].join '\n' - iframe: false watched: JSON.parse(GM_getValue('watched', '{}')) xhrs: [] g.favHalo = if /ws/.test g.favDefault then 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAZklEQVR4XrWRQQoAIQwD+6L97j7Ih9WTQQxhDqJQCk4Mranuvqod6LgwawSqSuUmWSPw/UNlJlnDAmA2ARjABLYj8ZyCzJHHqOg+GdAKZmKPIQUzuYrxicHqEgHzP9g7M0+hj45sAnRWxtPj3zSPAAAAAElFTkSuQmCC' else 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEUAAABmzDP///8AAABet0i+AAAAAXRSTlMAQObYZgAAAExJREFUeF4tyrENgDAMAMFXKuQswQLBG3mOlBnFS1gwDfIYLpEivvjq2MlqjmYvYg5jWEzCwtDSQlwcXKCVLrpFbvLvvSf9uZJ2HusDtJAY7Tkn1oYAAAAASUVORK5CYII=' @@ -1145,26 +1215,6 @@ tzOffset = (new Date()).getTimezoneOffset() / 60 g.chanOffset = 5 - tzOffset# 4chan = EST = GMT -5 if $.isDST() then g.chanOffset -= 1 -if location.hostname.split('.')[0] is 'sys' - if recaptcha = $ '#recaptcha_response_field' - m recaptcha, listener: ['keydown', recaptchaListener] - else if b = $ 'table font b' - GM_setValue 'error', b.firstChild.textContent - else - GM_setValue 'error', '' - if getConfig 'Auto Watch' - html = $('b').innerHTML - [_, thread, id] = html.match(//) - if thread is '0' - board = $('meta', d).content.match(/4chan.org\/(\w+)\//)[1] - g.watched[board] or= [] - g.watched[board].push { - id: id, - text: GM_getValue 'autoText' - } - GM_setValue 'watched', JSON.stringify g.watched - return - lastChecked = GM_getValue('lastChecked', 0) now = getTime() DAY = 24 * 60 * 60 @@ -1263,6 +1313,9 @@ GM_addStyle ' } ' +if location.hostname is 'sys.4chan.org' + qr.sys() + return if navtopr = $ '#navtopr a' a = n 'a', textContent: '4chan X' @@ -1388,20 +1441,7 @@ if getConfig 'Reply Hiding' hideReply(next) if getConfig 'Quick Reply' - iframe = n 'iframe', - name: 'iframe' - listener: ['load', iframeLoad] - hide(iframe) - mv iframe, d.body - - g.callbacks.push (root) -> - quotes = $$('a.quotejs:not(:first-child)', root) - for quote in quotes - quote.addEventListener('click', qrListener, true) - - #hack - nuke id so it doesn't grab focus when reloading - recaptcha.id = '' - + qr.init() if getConfig 'Quick Report' g.callbacks.push (root) -> @@ -1481,8 +1521,7 @@ if g.REPLY if getConfig 'Thread Updater' updaterMake() if getConfig('Quick Reply') and getConfig 'Persistent QR' - quickReply() - $('#qr input[title=autohide]').click() + qr.persist() if getConfig 'Post in Title' unless text = $('span.filetitle').textContent text = $('blockquote').textContent