diff --git a/4chan_x.user.js b/4chan_x.user.js index 5a23ac813..153addd2d 100644 --- a/4chan_x.user.js +++ b/4chan_x.user.js @@ -71,7 +71,7 @@ */ (function() { - var $, $$, DAY, Favicon, HOUR, MINUTE, Main, NAMESPACE, SECOND, Time, VERSION, anonymize, conf, config, d, engine, expandComment, expandThread, filter, flatten, g, getTitle, imgExpand, imgGif, imgHover, key, keybinds, log, message, nav, options, qr, quoteBacklink, quoteDR, quoteInline, quoteOP, quotePreview, redirect, replyHiding, reportButton, revealSpoilers, sauce, strikethroughQuotes, threadHiding, threadStats, threading, titlePost, ui, unread, updater, val, watcher, _base, + var $, $$, DAY, Favicon, HOUR, MINUTE, Main, NAMESPACE, SECOND, Time, VERSION, anonymize, conf, config, d, engine, expandComment, expandThread, filter, flatten, g, getTitle, imgExpand, imgGif, imgHover, key, keybinds, log, nav, options, qr, quoteBacklink, quoteDR, quoteInline, quoteOP, quotePreview, redirect, replyHiding, reportButton, revealSpoilers, sauce, strikethroughQuotes, threadHiding, threadStats, threading, titlePost, ui, unread, updater, val, watcher, _base, __slice = Array.prototype.slice; config = { @@ -1191,7 +1191,7 @@ qr = { init: function() { - var form, link; + var form, iframe, link, loadChecking; if (!$.id('recaptcha_challenge_field_holder')) return; if (conf['Hide Original Post Form']) { link = $.el('h1', { @@ -1205,6 +1205,26 @@ $.before(form, link); } g.callbacks.push(this.node); + iframe = $.el('iframe', { + id: 'iframe', + hidden: true, + src: 'http://sys.4chan.org/robots.txt' + }); + $.on(iframe, 'error', function() { + return this.src = this.src; + }); + loadChecking = function(iframe) { + if (!qr.status.ready) { + iframe.src = 'about:blank'; + return setTimeout((function() { + return iframe.src = 'http://sys.4chan.org/robots.txt'; + }), 250); + } + }; + $.on(iframe, 'load', function() { + if (this.src !== 'about:blank') return setTimeout(loadChecking, 500, this); + }); + $.add(d.body, iframe); if (conf['Persistent QR']) { qr.dialog(); if (conf['Auto Hide QR']) qr.hide(); @@ -1229,9 +1249,9 @@ close: function() { var i, spoiler, _i, _len, _ref; qr.el.hidden = true; - message.send({ + qr.message.send({ req: 'abort' - }, 'sys'); + }); d.activeElement.blur(); $.removeClass(qr.el, 'dump'); _ref = qr.replies; @@ -1701,6 +1721,7 @@ qr.status(); qr.cooldown.init(); qr.captcha.init(); + qr.message.init(); $.add(d.body, qr.el); e = d.createEvent('CustomEvent'); e.initEvent('QRDialogCreation', true, false); @@ -1714,9 +1735,9 @@ qr.status(); return; } - message.send({ + qr.message.send({ req: 'abort' - }, 'sys'); + }); reply = qr.replies[0]; if (!(reply.com || reply.file)) { err = 'No file selected.'; @@ -1774,12 +1795,12 @@ file.name = reply.file.name; file.type = reply.file.type; post.upfile = file; - return message.send(post, 'sys'); + return qr.message.send(post); }; reader.readAsBinaryString(reply.file); return; } - return message.send(post, 'sys'); + return qr.message.send(post); }, response: function(html) { var b, err, node, persona, postNumber, reply, thread, _, _ref; @@ -1831,93 +1852,150 @@ qr.status(); return qr.resetFileInput(); }, - post: function(data) { - var boundary, callbacks, form, i, name, opts, parts, toBin, url, val; - url = "http://sys.4chan.org/" + data.board + "/post"; - delete data.board; - if (engine === 'gecko' && data.upfile) { - if (!data.binary) { - toBin = function(data, name, val) { - var bb, r; - bb = new MozBlobBuilder(); - bb.append(val); - r = new FileReader(); - r.onload = function() { - data[name] = r.result; - if (!--i) return qr.post(data); + message: { + init: function() { + var code, ready, script; + code = function(e) { + var data, host; + data = e.data; + if (!data.changeContext) return; + delete data.changeContext; + host = location.hostname; + if (host === 'boards.4chan.org') { + return document.getElementById('iframe').contentWindow.postMessage(data, '*'); + } else if (host === 'sys.4chan.org') { + return parent.postMessage(data, '*'); + } + }; + script = $.el('script', { + textContent: "window.addEventListener('message'," + code + ",false)" + }); + ready = function() { + $.add(d.documentElement, script); + if (location.hostname === 'sys.4chan.org') { + qr.message.send({ + req: 'status', + ready: true + }); + } + return $.rm(script); + }; + if (d.documentElement) { + return ready(); + } else { + return $.ready(ready); + } + }, + send: function(data) { + data.changeContext = true; + data.qr = true; + return postMessage(data, '*'); + }, + receive: function(data) { + var _ref; + switch (data.req) { + case 'abort': + if ((_ref = qr.ajax) != null) _ref.abort(); + return qr.message.send({ + req: 'status' + }); + case 'response': + return qr.response(data.html); + case 'status': + return qr.status(data); + default: + return qr.message.post(data); + } + }, + post: function(data) { + var boundary, callbacks, form, i, name, opts, parts, toBin, url, val; + url = "http://sys.4chan.org/" + data.board + "/post"; + delete data.board; + delete data.qr; + if (engine === 'gecko' && data.upfile) { + if (!data.binary) { + toBin = function(data, name, val) { + var bb, r; + bb = new MozBlobBuilder(); + bb.append(val); + r = new FileReader(); + r.onload = function() { + data[name] = r.result; + if (!--i) return qr.message.post(data); + }; + return r.readAsBinaryString(bb.getBlob('text/plain')); }; - return r.readAsBinaryString(bb.getBlob('text/plain')); - }; - i = Object.keys(data).length; + i = Object.keys(data).length; + for (name in data) { + val = data[name]; + if (typeof val === 'object') { + toBin(data.upfile, 'name', data.upfile.name); + } else if (typeof val === 'boolean') { + if (val) { + toBin(data, name, String(val)); + } else { + i--; + } + } else { + toBin(data, name, val); + } + } + data.board = url.split('/')[3]; + data.binary = true; + return; + } + delete data.binary; + boundary = '-------------SMCD' + Date.now(); + parts = []; + parts.push('Content-Disposition: form-data; name="upfile"; filename="' + data.upfile.name + '"\r\n' + 'Content-Type: ' + data.upfile.type + '\r\n\r\n' + data.upfile.buffer + '\r\n'); + delete data.upfile; for (name in data) { val = data[name]; - if (typeof val === 'object') { - toBin(data.upfile, 'name', data.upfile.name); - } else if (typeof val === 'boolean') { - if (val) { - toBin(data, name, String(val)); - } else { - i--; - } - } else { - toBin(data, name, val); + if (val) { + parts.push('Content-Disposition: form-data; name="' + name + '"\r\n\r\n' + val + '\r\n'); } } - data.board = url.split('/')[3]; - data.binary = true; - return; - } - delete data.binary; - boundary = '-------------SMCD' + Date.now(); - parts = []; - parts.push('Content-Disposition: form-data; name="upfile"; filename="' + data.upfile.name + '"\r\n' + 'Content-Type: ' + data.upfile.type + '\r\n\r\n' + data.upfile.buffer + '\r\n'); - delete data.upfile; - for (name in data) { - val = data[name]; - if (val) { - parts.push('Content-Disposition: form-data; name="' + name + '"\r\n\r\n' + val + '\r\n'); + form = '--' + boundary + '\r\n' + parts.join('--' + boundary + '\r\n') + '--' + boundary + '--\r\n'; + } else { + form = new FormData(); + for (name in data) { + val = data[name]; + if (val) form.append(name, val); } } - form = '--' + boundary + '\r\n' + parts.join('--' + boundary + '\r\n') + '--' + boundary + '--\r\n'; - } else { - form = new FormData(); - for (name in data) { - val = data[name]; - if (val) form.append(name, val); - } - } - callbacks = { - onload: function() { - return message.send({ - req: 'response', - html: this.response - }); - } - }; - opts = { - form: form, - type: 'post', - upCallbacks: { + callbacks = { onload: function() { - return message.send({ - req: 'status', - progress: '...' - }); - }, - onprogress: function(e) { - return message.send({ - req: 'status', - progress: "" + (Math.round(e.loaded / e.total * 100)) + "%" + return qr.message.send({ + req: 'response', + html: this.response }); } - } - }; - if (boundary) { - opts.headers = { - 'Content-Type': 'multipart/form-data;boundary=' + boundary }; + opts = { + form: form, + type: 'post', + upCallbacks: { + onload: function() { + return qr.message.send({ + req: 'status', + progress: '...' + }); + }, + onprogress: function(e) { + return qr.message.send({ + req: 'status', + progress: "" + (Math.round(e.loaded / e.total * 100)) + "%" + }); + } + } + }; + if (boundary) { + opts.headers = { + 'Content-Type': 'multipart/form-data;boundary=' + boundary + }; + } + return qr.ajax = $.ajax(url, callbacks, opts); } - return qr.ajax = $.ajax(url, callbacks, opts); } }; @@ -2352,9 +2430,9 @@ d.title = d.title.match(/^.+-/)[0] + ' 404'; } unread.update(true); - message.send({ + qr.message.send({ req: 'abort' - }, 'sys'); + }); qr.status(); Favicon.update(); return; @@ -3410,113 +3488,6 @@ } }; - message = { - init: function() { - var code, script; - $.on(window, 'message', message.receive); - $.ready(function() { - var domain, domains, iframe, _i, _len, _results; - if (location.hostname !== 'boards.4chan.org') return; - domains = []; - if (conf['Quick Reply']) domains.push('sys'); - if (conf['Image Expansion'] || conf['Sauce']) domains.push('images'); - _results = []; - for (_i = 0, _len = domains.length; _i < _len; _i++) { - domain = domains[_i]; - iframe = $.el('iframe', { - id: domain, - hidden: true, - src: "http://" + domain + ".4chan.org/robots.txt" - }); - $.on(iframe, 'error', message.loadControl); - $.on(iframe, 'load', message.loadControl); - _results.push($.add(d.body, iframe)); - } - return _results; - }); - code = function(e) { - var data; - data = e.data; - if (!data.changeContext) return; - delete data.changeContext; - if (location.hostname === 'boards.4chan.org') { - return document.getElementById(data.iframe).contentWindow.postMessage(data, '*'); - } else { - return parent.postMessage(data, '*'); - } - }; - script = $.el('script', { - textContent: "window.addEventListener('message'," + code + ",false)" - }); - return $.ready(function() { - var host; - $.add(d.documentElement, script); - host = location.hostname; - if (host === 'sys.4chan.org') { - message.send({ - req: 'status', - ready: true - }); - } - if (host !== 'boards.4chan.org') { - message.send({ - req: 'iframeLoad', - id: location.hostname.split('.')[0] - }); - } - return $.rm(script); - }); - }, - loadControl: function() { - var _this = this; - return setTimeout((function() { - if (_this.src === 'about:blank' || message[_this.id] === 'ready') return; - _this.src = 'about:blank'; - return setTimeout((function() { - return _this.src = "http://" + _this.id + ".4chan.org/robots.txt"; - }), 250); - }), 1000); - }, - receive: function(e) { - var data, version; - data = e.data; - if (data.iframe) { - if (!data.changeContext) message.handle(data); - return; - } - version = data.version; - if (version && version !== VERSION && confirm('An updated version of 4chan X is available, would you like to install it now?')) { - return window.location = "https://raw.github.com/mayhemydg/4chan-x/" + version + "/4chan_x.user.js"; - } - }, - send: function(data, iframe) { - data.changeContext = true; - data.iframe = iframe || 'boards'; - return postMessage(data, '*'); - }, - handle: function(data) { - var req, _ref; - req = data.req; - delete data.req; - delete data.iframe; - switch (req) { - case 'iframeLoad': - return message[data.id] = 'ready'; - case 'abort': - if ((_ref = qr.ajax) != null) _ref.abort(); - return message.send({ - req: 'status' - }); - case 'response': - return qr.response(data.html); - case 'status': - return qr.status(data); - default: - return qr.post(data); - } - } - }; - Main = { init: function() { var cutoff, hiddenThreads, id, now, pathname, temp, timestamp, _ref; @@ -3528,9 +3499,11 @@ } else { g.PAGENUM = parseInt(temp) || 0; } - message.init(); + $.on(window, 'message', Main.message); if (location.hostname === 'sys.4chan.org') { - if (/report/.test(location.search)) { + if (location.pathname === '/robots.txt') { + qr.message.init(); + } else if (/report/.test(location.search)) { $.ready(function() { return $.on($('#recaptcha_response_field'), 'keydown', function(e) { if (e.keyCode === 8 && !e.target.value) { @@ -3646,6 +3619,16 @@ return $.on(d, 'DOMNodeInserted', Main.addStyle); } }, + message: function(e) { + var data, version; + data = e.data; + version = data.version; + if (data.qr && !data.changeContext) { + return qr.message.receive(data); + } else if (version && version !== VERSION && confirm('An updated version of 4chan X is available, would you like to install it now?')) { + return window.location = "https://raw.github.com/mayhemydg/4chan-x/" + version + "/4chan_x.user.js"; + } + }, node: function(e) { var callback, target, _i, _len, _ref, _results; target = e.target; diff --git a/script.coffee b/script.coffee index 92def3bda..36c97154c 100644 --- a/script.coffee +++ b/script.coffee @@ -880,6 +880,19 @@ qr = $.before form, link g.callbacks.push @node + iframe = $.el 'iframe', + id: 'iframe' + hidden: true + src: 'http://sys.4chan.org/robots.txt' + $.on iframe, 'error', -> @src = @src + # Greasemonkey ghetto fix + loadChecking = (iframe) -> + unless qr.status.ready + iframe.src = 'about:blank' + setTimeout (-> iframe.src = 'http://sys.4chan.org/robots.txt'), 250 + $.on iframe, 'load', -> unless @src is 'about:blank' then setTimeout loadChecking, 500, @ + $.add d.body, iframe + if conf['Persistent QR'] qr.dialog() qr.hide() if conf['Auto Hide QR'] @@ -901,7 +914,7 @@ qr = qr.dialog() close: -> qr.el.hidden = true - message.send req: 'abort', 'sys' + qr.message.send req: 'abort' d.activeElement.blur() $.removeClass qr.el, 'dump' for i in qr.replies @@ -1274,6 +1287,7 @@ qr = qr.status() qr.cooldown.init() qr.captcha.init() + qr.message.init() $.add d.body, qr.el # Create a custom event when the QR dialog is first initialized. @@ -1288,7 +1302,7 @@ qr = qr.cooldown.auto = !qr.cooldown.auto qr.status() return - message.send req: 'abort', 'sys' + qr.message.send req: 'abort' reply = qr.replies[0] # prevent errors @@ -1357,11 +1371,11 @@ qr = file.name = reply.file.name file.type = reply.file.type post.upfile = file - message.send post, 'sys' + qr.message.send post reader.readAsBinaryString reply.file return - message.send post, 'sys' + qr.message.send post response: (html) -> unless b = $ 'td b', $.el('a', innerHTML: html) @@ -1413,78 +1427,121 @@ qr = qr.status() qr.resetFileInput() - post: (data) -> + message: + init: -> + # 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. + code = (e) -> + {data} = e + return unless data.changeContext + delete data.changeContext + host = location.hostname + if host is 'boards.4chan.org' + document.getElementById('iframe').contentWindow.postMessage data, '*' + else if host is 'sys.4chan.org' + parent.postMessage data, '*' + script = $.el 'script', textContent: "window.addEventListener('message',#{code},false)" + ready = -> + $.add d.documentElement, script + if location.hostname is 'sys.4chan.org' + qr.message.send req: 'status', ready: true + $.rm script + # Chrome can access the documentElement on document-start + if d.documentElement + ready() + # other browsers will have to wait + else $.ready ready + send: (data) -> + data.changeContext = true + data.qr = true + postMessage data, '*' + receive: (data) -> + switch data.req + when 'abort' + qr.ajax?.abort() + qr.message.send req: 'status' + when 'response' # xhr response + qr.response data.html + when 'status' + qr.status data + else + qr.message.post data # Reply object: we're posting - url = "http://sys.4chan.org/#{data.board}/post" - # Do not append that value to the form. - delete data.board + post: (data) -> - # File with filename upload fix from desuwa - if engine is 'gecko' and data.upfile - # All of this is fucking retarded. - unless data.binary - toBin = (data, name, val) -> - bb = new MozBlobBuilder() - bb.append val - r = new FileReader() - r.onload = -> - data[name] = r.result - unless --i - qr.post data - r.readAsBinaryString bb.getBlob 'text/plain' - i = Object.keys(data).length - for name, val of data - if typeof val is 'object' # File. toBin the filename. - toBin data.upfile, 'name', data.upfile.name - else if typeof val is 'boolean' - if val - toBin data, name, String val + url = "http://sys.4chan.org/#{data.board}/post" + # Do not append these values to the form. + delete data.board + delete data.qr + + # File with filename upload fix from desuwa + if engine is 'gecko' and data.upfile + # All of this is fucking retarded. + unless data.binary + toBin = (data, name, val) -> + bb = new MozBlobBuilder() + bb.append val + r = new FileReader() + r.onload = -> + data[name] = r.result + unless --i + qr.message.post data + r.readAsBinaryString bb.getBlob 'text/plain' + i = Object.keys(data).length + for name, val of data + if typeof val is 'object' # File. toBin the filename. + toBin data.upfile, 'name', data.upfile.name + else if typeof val is 'boolean' + if val + toBin data, name, String val + else + i-- else - i-- - else - toBin data, name, val - data.board = url.split('/')[3] - data.binary = true - return + toBin data, name, val + data.board = url.split('/')[3] + data.binary = true + return - delete data.binary + delete data.binary - boundary = '-------------SMCD' + Date.now(); - parts = [] - parts.push 'Content-Disposition: form-data; name="upfile"; filename="' + data.upfile.name + '"\r\n' + 'Content-Type: ' + data.upfile.type + '\r\n\r\n' + data.upfile.buffer + '\r\n' - delete data.upfile + boundary = '-------------SMCD' + Date.now(); + parts = [] + parts.push 'Content-Disposition: form-data; name="upfile"; filename="' + data.upfile.name + '"\r\n' + 'Content-Type: ' + data.upfile.type + '\r\n\r\n' + data.upfile.buffer + '\r\n' + delete data.upfile - for name, val of data - parts.push 'Content-Disposition: form-data; name="' + name + '"\r\n\r\n' + val + '\r\n' if val - form = '--' + boundary + '\r\n' + parts.join('--' + boundary + '\r\n') + '--' + boundary + '--\r\n' + for name, val of data + parts.push 'Content-Disposition: form-data; name="' + name + '"\r\n\r\n' + val + '\r\n' if val + form = '--' + boundary + '\r\n' + parts.join('--' + boundary + '\r\n') + '--' + boundary + '--\r\n' - else - form = new FormData() - for name, val of data - form.append name, val if val + else + form = new FormData() + for name, val of data + form.append name, val if val - callbacks = - onload: -> - message.send - req: 'response' - html: @response - opts = - form: form - type: 'post' - upCallbacks: + callbacks = onload: -> - message.send - req: 'status' - progress: '...' - onprogress: (e) -> - message.send - req: 'status' - progress: "#{Math.round e.loaded / e.total * 100}%" - if boundary - opts.headers = - 'Content-Type': 'multipart/form-data;boundary=' + boundary + qr.message.send + req: 'response' + html: @response + opts = + form: form + type: 'post' + upCallbacks: + onload: -> + qr.message.send + req: 'status' + progress: '...' + onprogress: (e) -> + qr.message.send + req: 'status' + progress: "#{Math.round e.loaded / e.total * 100}%" + if boundary + opts.headers = + 'Content-Type': 'multipart/form-data;boundary=' + boundary - qr.ajax = $.ajax url, callbacks, opts + qr.ajax = $.ajax url, callbacks, opts options = init: -> @@ -1864,7 +1921,7 @@ updater = else d.title = d.title.match(/^.+-/)[0] + ' 404' unread.update true - message.send req: 'abort', 'sys' + qr.message.send req: 'abort' qr.status() Favicon.update() return @@ -2656,98 +2713,6 @@ imgExpand = resize: -> imgExpand.style.innerHTML = ".fitheight img[md5] + img {max-height:#{d.body.clientHeight}px;}" -message = - init: -> - $.on window, 'message', message.receive - - $.ready -> - return if location.hostname isnt 'boards.4chan.org' - domains = [] - if conf['Quick Reply'] - domains.push 'sys' - if conf['Image Expansion'] or conf['Sauce'] - domains.push 'images' - for domain in domains - iframe = $.el 'iframe', - id: domain - hidden: true - src: "http://#{domain}.4chan.org/robots.txt" - $.on iframe, 'error', message.loadControl - $.on iframe, 'load', message.loadControl - $.add d.body, iframe - - # 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. - code = (e) -> - {data} = e - return unless data.changeContext - delete data.changeContext - if location.hostname is 'boards.4chan.org' - document.getElementById(data.iframe).contentWindow.postMessage data, '*' - else - parent.postMessage data, '*' - - script = $.el 'script', - textContent: "window.addEventListener('message',#{code},false)" - - $.ready -> - $.add d.documentElement, script - host = location.hostname - if host is 'sys.4chan.org' - message.send req: 'status', ready: true - if host isnt 'boards.4chan.org' - message.send req: 'iframeLoad', id: location.hostname.split('.')[0] - $.rm script - - loadControl: -> - # Make sure the iframe has loaded correctly. - # This is necessary for Greasemonkey users. - setTimeout (=> - return if @src is 'about:blank' or message[@id] is 'ready' - @src = 'about:blank' - setTimeout (=> - @src = "http://#{@id}.4chan.org/robots.txt" - ), 250 - ), 1000 - - receive: (e) -> - {data} = e - if data.iframe - unless data.changeContext - message.handle data - return - - {version} = data - if version and version isnt VERSION and confirm 'An updated version of 4chan X is available, would you like to install it now?' - window.location = "https://raw.github.com/mayhemydg/4chan-x/#{version}/4chan_x.user.js" - - send: (data, iframe) -> - data.changeContext = true - data.iframe = iframe or 'boards' - postMessage data, '*' - - handle: (data) -> - {req} = data - - delete data.req - delete data.iframe - - switch req - when 'iframeLoad' - message[data.id] = 'ready' - when 'abort' - qr.ajax?.abort() - message.send req: 'status' - when 'response' # xhr response - qr.response data.html - when 'status' - qr.status data - else - # Reply object: we're posting - qr.post data - Main = init: -> pathname = location.pathname[1..].split('/') @@ -2758,10 +2723,12 @@ Main = else g.PAGENUM = parseInt(temp) or 0 - message.init() + $.on window, 'message', Main.message if location.hostname is 'sys.4chan.org' - if /report/.test location.search + if location.pathname is '/robots.txt' + qr.message.init() + else if /report/.test location.search $.ready -> $.on $('#recaptcha_response_field'), 'keydown', (e) -> window.location = 'javascript:Recaptcha.reload()' if e.keyCode is 8 and not e.target.value @@ -2920,6 +2887,14 @@ Main = else # XXX fox $.on d, 'DOMNodeInserted', Main.addStyle + message: (e) -> + {data} = e + {version} = data + if data.qr and not data.changeContext + qr.message.receive data + else if version and version isnt VERSION and confirm 'An updated version of 4chan X is available, would you like to install it now?' + window.location = "https://raw.github.com/mayhemydg/4chan-x/#{version}/4chan_x.user.js" + node: (e) -> {target} = e return unless target.nodeName is 'TABLE'