diff --git a/4chan_x.user.js b/4chan_x.user.js
index 2cfc53d80..e75108a62 100644
--- a/4chan_x.user.js
+++ b/4chan_x.user.js
@@ -60,7 +60,7 @@
*/
(function() {
- var $, $$, DAY, Favicon, HOUR, MINUTE, NAMESPACE, QR, SECOND, Time, anonymize, conf, config, cooldown, d, expandComment, expandThread, firstRun, g, getTitle, imgExpand, imgGif, imgHover, imgPreloading, key, keybinds, log, main, nav, nodeInserted, options, qr, quoteBacklink, quoteInline, quoteOP, quotePreview, redirect, replyHiding, reportButton, revealSpoilers, sauce, threadHiding, threadStats, threading, titlePost, ui, unread, updater, val, watcher, _ref;
+ var $, $$, DAY, Favicon, HOUR, MINUTE, NAMESPACE, QR, SECOND, Time, anonymize, conf, config, cooldown, d, expandComment, expandThread, firstRun, g, getTitle, imgExpand, imgGif, imgHover, imgPreloading, key, keybinds, log, main, nav, nodeInserted, options, quoteBacklink, quoteInline, quoteOP, quotePreview, redirect, replyHiding, reportButton, revealSpoilers, sauce, threadHiding, threadStats, threading, titlePost, ui, unread, updater, val, watcher, _ref;
var __slice = Array.prototype.slice;
config = {
main: {
@@ -1596,6 +1596,13 @@
$.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)
+
+ To access the parent, we have to break out of the sandbox and evaluate
+ in the global context.
+ */
return $.globalEval(function() {
var data, href, node, textContent, _ref;
$ = function(css) {
@@ -1615,331 +1622,6 @@
});
}
};
- qr = {
- init: function() {
- var iframe;
- g.callbacks.push(qr.node);
- $.bind(window, 'message', qr.message);
- $.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) {
- switch (type) {
- case 'JPG':
- return 'image/JPEG';
- case 'PDF':
- return 'application/' + type;
- default:
- return 'image/' + type;
- }
- });
- iframe = $.el('iframe', {
- name: 'iframe',
- hidden: true
- });
- $.add(d.body, iframe);
- return $('#recaptcha_response_field').id = '';
- },
- attach: function() {
- var fileDiv;
- fileDiv = $.el('div', {
- innerHTML: "X"
- });
- $.bind(fileDiv.firstChild, 'change', qr.validateFileSize);
- $.bind(fileDiv.lastChild, 'click', (function() {
- return $.rm(this.parentNode);
- }));
- return $.add($('#files', qr.el), fileDiv);
- },
- attachNext: function() {
- var file, fileDiv, oldFile;
- fileDiv = $.rm($('#files div', qr.el));
- file = fileDiv.firstChild;
- oldFile = $('#qr_form input[type=file]', qr.el);
- return $.replace(oldFile, file);
- },
- autoPost: function() {
- if (qr.el && $('#auto', qr.el).checked) {
- return qr.submit.call($('form', qr.el));
- }
- },
- captchaNode: function(e) {
- if (!qr.el) {
- return;
- }
- val = e.target.value;
- $('img', qr.el).src = "http://www.google.com/recaptcha/api/image?c=" + val;
- qr.challenge = val;
- return qr.captchaTime = Date.now();
- },
- captchaKeydown: function(e) {
- var captchas;
- if (!(e.keyCode === 13 && this.value)) {
- return;
- }
- captchas = $.get('captchas', []);
- captchas.push({
- challenge: qr.challenge,
- response: this.value,
- time: qr.captchaTime
- });
- $.set('captchas', captchas);
- $('#captchas', qr.el).textContent = captchas.length + ' captchas';
- Recaptcha.reload();
- this.value = '';
- if (!$('textarea', qr.el).value && !$('input[type=file]', qr.el).files.length) {
- return e.preventDefault();
- }
- },
- close: function() {
- $.rm(qr.el);
- return qr.el = null;
- },
- dialog: function(link) {
- var THREAD_ID, c, email, html, m, name, pwd, submitDisabled, submitValue;
- c = d.cookie;
- name = (m = c.match(/4chan_name=([^;]+)/)) ? decodeURIComponent(m[1]) : '';
- email = (m = c.match(/4chan_email=([^;]+)/)) ? decodeURIComponent(m[1]) : '';
- pwd = (m = c.match(/4chan_pass=([^;]+)/)) ? decodeURIComponent(m[1]) : $('input[name=pwd]').value;
- submitValue = $('#com_submit').value;
- submitDisabled = $('#com_submit').disabled ? 'disabled' : '';
- THREAD_ID = g.THREAD_ID || $.x('ancestor::div[@class="thread"]/div', link).id;
- qr.challenge = $('#recaptcha_challenge_field').value;
- html = " X
Quick Reply
";
- qr.el = ui.dialog('qr', {
- top: '0px',
- left: '0px'
- }, 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(e) {
- var data, duration, fileCount;
- $('iframe[name=iframe]').src = 'about:blank';
- fileCount = $('#files', qr.el).childElementCount;
- data = e.data;
- if (data) {
- data = JSON.parse(data);
- $.extend($('#error', qr.el), data);
- $('#recaptcha_response_field', qr.el).value = '';
- $('#autohide', qr.el).checked = false;
- if (data.textContent === 'You seem to have mistyped the verification.') {
- setTimeout(qr.autoPost, 1000);
- } else if (data.textContent === 'Error: Duplicate file entry detected.' && fileCount) {
- $('textarea', qr.el).value += '\n' + data.textContent + ' ' + data.href;
- qr.attachNext();
- setTimeout(qr.autoPost, 1000);
- }
- return;
- }
- if (qr.el) {
- if (g.REPLY && (conf['Persistent QR'] || fileCount)) {
- qr.refresh();
- if (fileCount) {
- qr.attachNext();
- }
- } else {
- qr.close();
- }
- }
- if (conf['Cooldown']) {
- duration = qr.sage ? 60 : 30;
- $.set(g.BOARD + '/cooldown', Date.now() + duration * 1000);
- return cooldown.start();
- }
- },
- node: function(root) {
- var quote;
- quote = $('a.quotejs:not(:first-child)', root);
- return $.bind(quote, 'click', qr.quote);
- },
- 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.';
- }
- /*
- captchas expire after 5 hours (emperically verified). cutoff 5 minutes
- before then, b/c posting takes time.
- */
- cutoff = Date.now() - 5 * HOUR + 5 * MINUTE;
- captchas = $.get('captchas', []);
- 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;
- },
- quote: function(e) {
- var id, s, selection, selectionID, ta, text, _ref;
- if (e) {
- e.preventDefault();
- }
- if (qr.el) {
- $('#autohide', qr.el).checked = false;
- } else {
- qr.dialog(this);
- }
- 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>');
- text += ">" + s + "\n";
- }
- }
- ta = $('textarea', qr.el);
- ta.focus();
- return ta.value += text;
- },
- 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 = '';
- if (!conf['Remember Spoiler']) {
- if ((_ref = $('[name=spoiler]', qr.el)) != null) {
- _ref.checked = false;
- }
- }
- oldFile = $('[type=file]', qr.el);
- newFile = $.el('input', {
- type: 'file',
- name: 'upfile',
- accept: qr.acceptFiles
- });
- return $.replace(oldFile, newFile);
- },
- submit: function(e) {
- var id, msg, op;
- if (msg = qr.postInvalid()) {
- if (typeof e.preventDefault === "function") {
- 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);
- }
- }
- }
- if (!e) {
- this.submit();
- }
- $('#error', qr.el).textContent = '';
- if (conf['Auto Hide QR']) {
- $('#autohide', qr.el).checked = true;
- }
- 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;
- if (recaptcha = $('#recaptcha_response_field')) {
- $.bind(recaptcha, 'keydown', Recaptcha.listener);
- return;
- }
- /*
- 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.
- */
- $.globalEval(function() {
- var data, href, node, textContent, _ref;
- if (node = (_ref = document.querySelector('table font b')) != null ? _ref.firstChild : void 0) {
- textContent = node.textContent, href = node.href;
- data = JSON.stringify({
- textContent: textContent,
- href: href
- });
- } else {
- 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 = {
init: function() {
var node;
diff --git a/script.coffee b/script.coffee
index 9318b205a..6aba38e80 100644
--- a/script.coffee
+++ b/script.coffee
@@ -1222,6 +1222,13 @@ QR =
if recaptcha = $ '#recaptcha_response_field' #post reporting
$.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)
+
+ To access the parent, we have to break out of the sandbox and evaluate
+ in the global context.
+ ###
$.globalEval ->
$ = (css) -> document.querySelector css
if node = $('table font b')?.firstChild
@@ -1234,307 +1241,6 @@ QR =
#parent will blank us on message receival;
#if we're not an iframe, we won't get blanked
-qr =
- # TODO
- # error handling / logging
- # persistent captcha
- # rm Recaptcha
- # email reverts
- init: ->
- g.callbacks.push qr.node
- $.bind window, 'message', qr.message
- $.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) ->
- switch type
- when 'JPG'
- 'image/JPEG'
- when 'PDF'
- 'application/' + type
- else
- 'image/' + type
-
- iframe = $.el 'iframe',
- name: 'iframe'
- hidden: true
- $.add d.body, iframe
-
- #hack - nuke id so it doesn't grab focus when reloading
- $('#recaptcha_response_field').id = ''
-
- attach: ->
- fileDiv = $.el 'div', innerHTML: "X"
- $.bind fileDiv.firstChild, 'change', qr.validateFileSize
- $.bind fileDiv.lastChild, 'click', (-> $.rm @parentNode)
- $.add $('#files', qr.el), fileDiv
-
- attachNext: ->
- fileDiv = $.rm $('#files div', qr.el)
- file = fileDiv.firstChild
- oldFile = $ '#qr_form input[type=file]', qr.el
- $.replace oldFile, file
-
- autoPost: ->
- if qr.el and $('#auto', qr.el).checked
- qr.submit.call $ 'form', qr.el
-
- captchaNode: (e) ->
- return unless qr.el
- val = e.target.value
- $('img', qr.el).src = "http://www.google.com/recaptcha/api/image?c=" + val
- qr.challenge = val
- qr.captchaTime = Date.now()
-
- captchaKeydown: (e) ->
- return unless e.keyCode is 13 and @value #enter, captcha filled
-
- captchas = $.get 'captchas', []
- captchas.push
- challenge: qr.challenge
- response: @value
- time: qr.captchaTime
- $.set 'captchas', captchas
- $('#captchas', qr.el).textContent = captchas.length + ' captchas'
- Recaptcha.reload()
- @value = ''
-
- if !$('textarea', qr.el).value and !$('input[type=file]', qr.el).files.length
- e.preventDefault()
-
- close: ->
- $.rm qr.el
- qr.el = null
-
- dialog: (link) ->
- c = d.cookie
- name = if m = c.match(/4chan_name=([^;]+)/) then decodeURIComponent m[1] else ''
- email = if m = c.match(/4chan_email=([^;]+)/) then decodeURIComponent m[1] else ''
- pwd = if m = c.match(/4chan_pass=([^;]+)/) then decodeURIComponent m[1] else $('input[name=pwd]').value
- submitValue = $('#com_submit').value
- submitDisabled = if $('#com_submit').disabled then 'disabled' else ''
- #FIXME inlined cross-thread quotes
- THREAD_ID = g.THREAD_ID or $.x('ancestor::div[@class="thread"]/div', link).id
- qr.challenge = $('#recaptcha_challenge_field').value
-
- html = "
- X
-
-
-
- Quick Reply
-
-
-
- "
- qr.el = ui.dialog 'qr', top: '0px', left: '0px', 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: (e) ->
- $('iframe[name=iframe]').src = 'about:blank'
- fileCount = $('#files', qr.el).childElementCount
-
- {data} = e
- if data # error message
- data = JSON.parse data
- $.extend $('#error', qr.el), data
- $('#recaptcha_response_field', qr.el).value = ''
- $('#autohide', qr.el).checked = false
- if data.textContent is 'You seem to have mistyped the verification.'
- setTimeout qr.autoPost, 1000
- else if data.textContent is 'Error: Duplicate file entry detected.' and fileCount
- $('textarea', qr.el).value += '\n' + data.textContent + ' ' + data.href
- qr.attachNext()
- setTimeout qr.autoPost, 1000
- return
-
- if qr.el
- if g.REPLY and (conf['Persistent QR'] or fileCount)
- qr.refresh()
- if fileCount
- qr.attachNext()
- else
- qr.close()
- if conf['Cooldown']
- 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
- captchas = $.get 'captchas', []
- 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
- else
- qr.dialog @
-
- 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>'
- 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()
- 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
-
- sys: ->
- if recaptcha = $ '#recaptcha_response_field' #post reporting
- $.bind recaptcha, 'keydown', Recaptcha.listener
- return
-
- ###
- 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.
- ###
- $.globalEval ->
- if node = document.querySelector('table font b')?.firstChild
- {textContent, href} = node
- data = JSON.stringify {textContent, href}
- else
- data = ''
- 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.'
-
threading =
init: ->
# don't thread image controls