diff --git a/LICENSE b/LICENSE index 9d1ef9871..64d784bab 100755 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ /* -* 4chan X - Version 1.3.2 - 2014-01-12 +* 4chan X - Version 1.3.3 - 2014-02-09 * * Licensed under the MIT license. * https://github.com/seaweedchan/4chan-x/blob/master/LICENSE diff --git a/builds/4chan-X.meta.js b/builds/4chan-X.meta.js index 3364a720d..df8af8ecb 100755 --- a/builds/4chan-X.meta.js +++ b/builds/4chan-X.meta.js @@ -1,6 +1,6 @@ // ==UserScript== // @name 4chan X -// @version 1.3.2 +// @version 1.3.3 // @minGMVer 1.13 // @minFFVer 26 // @namespace 4chan-X diff --git a/builds/4chan-X.user.js b/builds/4chan-X.user.js index fa89ece47..3563d85cc 100644 --- a/builds/4chan-X.user.js +++ b/builds/4chan-X.user.js @@ -1,7 +1,7 @@ // Generated by CoffeeScript // ==UserScript== // @name 4chan X -// @version 1.3.2 +// @version 1.3.3 // @minGMVer 1.13 // @minFFVer 26 // @namespace 4chan-X @@ -22,7 +22,7 @@ // ==/UserScript== /* -* 4chan X - Version 1.3.2 - 2014-01-12 +* 4chan X - Version 1.3.3 - 2014-02-09 * * Licensed under the MIT license. * https://github.com/seaweedchan/4chan-x/blob/master/LICENSE @@ -358,7 +358,7 @@ doc = d.documentElement; g = { - VERSION: '1.3.2', + VERSION: '1.3.3', NAMESPACE: '4chan X.', boards: {}, threads: {}, @@ -6113,64 +6113,73 @@ QR.captcha = { init: function() { + var container, imgContainer, input; if (d.cookie.indexOf('pass_enabled=1') >= 0) { return; } - if (!(this.isEnabled = !!$.id('captchaFormPart'))) { + container = $.id('captchaContainer'); + if (!(this.isEnabled = !!container)) { return; } - return $.asap((function() { - return $.id('recaptcha_challenge_field_holder'); - }), this.ready.bind(this)); - }, - ready: function() { - var imgContainer, input, setLifetime, - _this = this; - setLifetime = function(e) { - return _this.lifetime = e.detail; - }; - $.on(window, 'captcha:timeout', setLifetime); - $.globalEval('window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))'); - $.off(window, 'captcha:timeout', setLifetime); imgContainer = $.el('div', { className: 'captcha-img', title: 'Reload reCAPTCHA', - innerHTML: '' + innerHTML: '', + hidden: true }); input = $.el('input', { className: 'captcha-input field', title: 'Verification', + placeholder: 'Focus to load reCAPTCHA', autocomplete: 'off', - spellcheck: false, - tabIndex: 55 + spellcheck: false }); this.nodes = { - challenge: $.id('recaptcha_challenge_field_holder'), img: imgContainer.firstChild, input: input }; - new MutationObserver(this.load.bind(this)).observe(this.nodes.challenge, { - childList: true - }); - $.on(imgContainer, 'click', this.reload.bind(this)); - $.on(input, 'keydown', this.keydown.bind(this)); - $.on(input, 'focus', function() { - return $.addClass(QR.nodes.el, 'focus'); - }); - $.on(input, 'blur', function() { - return $.rmClass(QR.nodes.el, 'focus'); - }); - $.get('captchas', [], function(_arg) { - var captchas; - captchas = _arg.captchas; - return _this.sync(captchas); - }); - $.sync('captchas', this.sync); - this.reload(); + $.on(input, 'focus', this.setup); $.on(input, 'blur', QR.focusout); $.on(input, 'focus', QR.focusin); $.addClass(QR.nodes.el, 'has-captcha'); - return $.after(QR.nodes.com.parentNode, [imgContainer, input]); + $.after(QR.nodes.com.parentNode, [imgContainer, input]); + this.setupObserver = new MutationObserver(this.afterSetup); + return this.setupObserver.observe(container, { + childList: true + }); + }, + setup: function() { + return $.globalEval('loadRecaptcha()'); + }, + afterSetup: function() { + var challenge, img, input, setLifetime, _ref; + if (!(challenge = $.id('recaptcha_challenge_field_holder'))) { + return; + } + QR.captcha.setupObserver.disconnect(); + delete QR.captcha.setupObserver; + setLifetime = function(e) { + return QR.captcha.lifetime = e.detail; + }; + $.on(window, 'captcha:timeout', setLifetime); + $.globalEval('window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))'); + $.off(window, 'captcha:timeout', setLifetime); + _ref = QR.captcha.nodes, img = _ref.img, input = _ref.input; + img.parentNode.hidden = false; + $.off(input, 'focus', QR.captcha.setup); + $.on(input, 'keydown', QR.captcha.keydown.bind(QR.captcha)); + $.on(img.parentNode, 'click', QR.captcha.reload.bind(QR.captcha)); + $.get('captchas', [], function(_arg) { + var captchas; + captchas = _arg.captchas; + return QR.captcha.sync(captchas); + }); + $.sync('captchas', QR.captcha.sync); + QR.captcha.nodes.challenge = challenge; + new MutationObserver(QR.captcha.load.bind(QR.captcha)).observe(challenge, { + childList: true + }); + return QR.captcha.load(); }, sync: function(captchas) { QR.captcha.captchas = captchas; @@ -6216,6 +6225,9 @@ }, clear: function() { var captcha, i, now, _i, _len, _ref; + if (!this.captchas.length) { + return; + } now = Date.now(); _ref = this.captchas; for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { diff --git a/builds/crx/manifest.json b/builds/crx/manifest.json index ebc75edde..17518ddd3 100755 --- a/builds/crx/manifest.json +++ b/builds/crx/manifest.json @@ -1,6 +1,6 @@ { "name": "4chan X", - "version": "1.3.2", + "version": "1.3.3", "manifest_version": 2, "description": "Cross-browser userscript for maximum lurking on 4chan.", "icons": { diff --git a/builds/crx/script.js b/builds/crx/script.js index cd6b00c39..471422d34 100644 --- a/builds/crx/script.js +++ b/builds/crx/script.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript /* -* 4chan X - Version 1.3.2 - 2014-01-12 +* 4chan X - Version 1.3.3 - 2014-02-09 * * Licensed under the MIT license. * https://github.com/seaweedchan/4chan-x/blob/master/LICENSE @@ -336,7 +336,7 @@ doc = d.documentElement; g = { - VERSION: '1.3.2', + VERSION: '1.3.3', NAMESPACE: '4chan X.', boards: {}, threads: {}, @@ -6104,62 +6104,71 @@ QR.captcha = { init: function() { + var container, imgContainer, input; if (d.cookie.indexOf('pass_enabled=1') >= 0) { return; } - if (!(this.isEnabled = !!$.id('captchaFormPart'))) { + container = $.id('captchaContainer'); + if (!(this.isEnabled = !!container)) { return; } - return $.asap((function() { - return $.id('recaptcha_challenge_field_holder'); - }), this.ready.bind(this)); - }, - ready: function() { - var imgContainer, input, setLifetime, - _this = this; - setLifetime = function(e) { - return _this.lifetime = e.detail; - }; - $.on(window, 'captcha:timeout', setLifetime); - $.globalEval('window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))'); - $.off(window, 'captcha:timeout', setLifetime); imgContainer = $.el('div', { className: 'captcha-img', title: 'Reload reCAPTCHA', - innerHTML: '' + innerHTML: '', + hidden: true }); input = $.el('input', { className: 'captcha-input field', title: 'Verification', + placeholder: 'Focus to load reCAPTCHA', autocomplete: 'off', - spellcheck: false, - tabIndex: 55 + spellcheck: false }); this.nodes = { - challenge: $.id('recaptcha_challenge_field_holder'), img: imgContainer.firstChild, input: input }; - new MutationObserver(this.load.bind(this)).observe(this.nodes.challenge, { + $.on(input, 'focus', this.setup); + $.addClass(QR.nodes.el, 'has-captcha'); + $.after(QR.nodes.com.parentNode, [imgContainer, input]); + this.setupObserver = new MutationObserver(this.afterSetup); + return this.setupObserver.observe(container, { childList: true }); - $.on(imgContainer, 'click', this.reload.bind(this)); - $.on(input, 'keydown', this.keydown.bind(this)); - $.on(input, 'focus', function() { - return $.addClass(QR.nodes.el, 'focus'); - }); - $.on(input, 'blur', function() { - return $.rmClass(QR.nodes.el, 'focus'); - }); + }, + setup: function() { + return $.globalEval('loadRecaptcha()'); + }, + afterSetup: function() { + var challenge, img, input, setLifetime, _ref; + if (!(challenge = $.id('recaptcha_challenge_field_holder'))) { + return; + } + QR.captcha.setupObserver.disconnect(); + delete QR.captcha.setupObserver; + setLifetime = function(e) { + return QR.captcha.lifetime = e.detail; + }; + $.on(window, 'captcha:timeout', setLifetime); + $.globalEval('window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))'); + $.off(window, 'captcha:timeout', setLifetime); + _ref = QR.captcha.nodes, img = _ref.img, input = _ref.input; + img.parentNode.hidden = false; + $.off(input, 'focus', QR.captcha.setup); + $.on(input, 'keydown', QR.captcha.keydown.bind(QR.captcha)); + $.on(img.parentNode, 'click', QR.captcha.reload.bind(QR.captcha)); $.get('captchas', [], function(_arg) { var captchas; captchas = _arg.captchas; - return _this.sync(captchas); + return QR.captcha.sync(captchas); }); - $.sync('captchas', this.sync); - this.reload(); - $.addClass(QR.nodes.el, 'has-captcha'); - return $.after(QR.nodes.com.parentNode, [imgContainer, input]); + $.sync('captchas', QR.captcha.sync); + QR.captcha.nodes.challenge = challenge; + new MutationObserver(QR.captcha.load.bind(QR.captcha)).observe(challenge, { + childList: true + }); + return QR.captcha.load(); }, sync: function(captchas) { QR.captcha.captchas = captchas; @@ -6205,6 +6214,9 @@ }, clear: function() { var captcha, i, now, _i, _len, _ref; + if (!this.captchas.length) { + return; + } now = Date.now(); _ref = this.captchas; for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { diff --git a/latest.js b/latest.js index ac5695ab4..acb612661 100755 --- a/latest.js +++ b/latest.js @@ -1 +1 @@ -postMessage({version:'1.3.2'},'*') +postMessage({version:'1.3.3'},'*') diff --git a/package.json b/package.json index 05106b7a2..1314d757b 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "4chan-X", - "version": "1.3.2", + "version": "1.3.3", "description": "Cross-browser userscript for maximum lurking on 4chan.", "meta": { "name": "4chan X", diff --git a/src/Posting/QR.captcha.coffee b/src/Posting/QR.captcha.coffee index 671ae233f..4809ebba4 100644 --- a/src/Posting/QR.captcha.coffee +++ b/src/Posting/QR.captcha.coffee @@ -1,43 +1,25 @@ QR.captcha = init: -> return if d.cookie.indexOf('pass_enabled=1') >= 0 - return unless @isEnabled = !!$.id 'captchaFormPart' - $.asap (-> $.id 'recaptcha_challenge_field_holder'), @ready.bind @ - - ready: -> - setLifetime = (e) => @lifetime = e.detail - $.on window, 'captcha:timeout', setLifetime - $.globalEval 'window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))' - $.off window, 'captcha:timeout', setLifetime + container = $.id 'captchaContainer' + return unless @isEnabled = !!container imgContainer = $.el 'div', className: 'captcha-img' title: 'Reload reCAPTCHA' innerHTML: '' + hidden: true input = $.el 'input', className: 'captcha-input field' title: 'Verification' + placeholder: 'Focus to load reCAPTCHA' autocomplete: 'off' spellcheck: false - tabIndex: 55 @nodes = - challenge: $.id 'recaptcha_challenge_field_holder' - img: imgContainer.firstChild - input: input + img: imgContainer.firstChild + input: input - new MutationObserver(@load.bind @).observe @nodes.challenge, - childList: true - - $.on imgContainer, 'click', @reload.bind @ - $.on input, 'keydown', @keydown.bind @ - $.on input, 'focus', -> $.addClass QR.nodes.el, 'focus' - $.on input, 'blur', -> $.rmClass QR.nodes.el, 'focus' - - $.get 'captchas', [], ({captchas}) => - @sync captchas - $.sync 'captchas', @sync - # start with an uncached captcha - @reload() + $.on input, 'focus', @setup <% if (type === 'userscript') { %> # XXX Firefox lacks focusin/focusout support. @@ -48,10 +30,37 @@ QR.captcha = $.addClass QR.nodes.el, 'has-captcha' $.after QR.nodes.com.parentNode, [imgContainer, input] + @setupObserver = new MutationObserver @afterSetup + @setupObserver.observe container, childList: true + setup: -> + $.globalEval 'loadRecaptcha()' + afterSetup: -> + return unless challenge = $.id 'recaptcha_challenge_field_holder' + QR.captcha.setupObserver.disconnect() + delete QR.captcha.setupObserver + + setLifetime = (e) -> QR.captcha.lifetime = e.detail + $.on window, 'captcha:timeout', setLifetime + $.globalEval 'window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))' + $.off window, 'captcha:timeout', setLifetime + + {img, input} = QR.captcha.nodes + img.parentNode.hidden = false + $.off input, 'focus', QR.captcha.setup + $.on input, 'keydown', QR.captcha.keydown.bind QR.captcha + $.on img.parentNode, 'click', QR.captcha.reload.bind QR.captcha + + $.get 'captchas', [], ({captchas}) -> + QR.captcha.sync captchas + $.sync 'captchas', QR.captcha.sync + + QR.captcha.nodes.challenge = challenge + new MutationObserver(QR.captcha.load.bind QR.captcha).observe challenge, + childList: true + QR.captcha.load() sync: (captchas) -> QR.captcha.captchas = captchas QR.captcha.count() - getOne: -> @clear() if captcha = @captchas.shift() @@ -67,7 +76,6 @@ QR.captcha = # If there's only one word, duplicate it. response = "#{response} #{response}" unless /\s/.test response {challenge, response} - save: -> return unless response = @nodes.input.value.trim() @captchas.push @@ -77,8 +85,8 @@ QR.captcha = @count() @reload() $.set 'captchas', @captchas - clear: -> + return unless @captchas.length now = Date.now() for captcha, i in @captchas break if captcha.timeout > now @@ -86,7 +94,6 @@ QR.captcha = @captchas = @captchas[i..] @count() $.set 'captchas', @captchas - load: -> return unless @nodes.challenge.firstChild # -1 minute to give upload some time. @@ -96,7 +103,6 @@ QR.captcha = @nodes.img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}" @nodes.input.value = null @clear() - count: -> count = @captchas.length @nodes.input.placeholder = switch count @@ -107,13 +113,11 @@ QR.captcha = else "Verification (#{count} cached captchas)" @nodes.input.alt = count # For XTRM RICE. - reload: (focus) -> # the 't' argument prevents the input from being focused $.globalEval 'Recaptcha.reload("t")' # Focus if we meant to. @nodes.input.focus() if focus - keydown: (e) -> if e.keyCode is 8 and not @nodes.input.value @reload() @@ -121,4 +125,4 @@ QR.captcha = @save() else return - e.preventDefault() \ No newline at end of file + e.preventDefault()