From 486f64baa5b925ab6d4036f2e898e6bf5b4e4b55 Mon Sep 17 00:00:00 2001 From: ccd0 Date: Fri, 5 Dec 2014 18:09:07 -0800 Subject: [PATCH] Prepare for Recaptcha 2. --- package.json | 3 +- src/General/Config.coffee | 2 +- src/General/css/style.css | 23 +-- src/General/html/Features/QuickReply.html | 16 +- src/Posting/QR.captcha.coffee | 188 ++++++++++------------ src/Posting/QR.coffee | 30 +--- src/Posting/QR.post.coffee | 3 + 7 files changed, 111 insertions(+), 154 deletions(-) diff --git a/package.json b/package.json index 6a95375e8..d71f7d073 100755 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "oldVersions": "https://raw.githubusercontent.com/ccd0/4chan-x/", "faq": "https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions", "appid": "lacclbnghgdicfifcamcmcnilckjamag", + "recaptchaKey": "6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc", "youtubeAPIKey": "AIzaSyB5_zaen_-46Uhz1xGR-lz1YoUMHqCD6CE", "buildsPath": "builds/", "mainBranch": "master", @@ -67,4 +68,4 @@ "engines": { "node": ">=0.10" } -} \ No newline at end of file +} diff --git a/src/General/Config.coffee b/src/General/Config.coffee index 4b2479d19..790775c22 100755 --- a/src/General/Config.coffee +++ b/src/General/Config.coffee @@ -373,7 +373,7 @@ Config = ] 'Auto-load captcha': [ false - 'Automatically load the captcha when you open a thread, and reload it after you post.' + 'Automatically load the captcha in the QR even if your post is empty.' ] 'Bottom QR Link': [ true diff --git a/src/General/css/style.css b/src/General/css/style.css index 64af98308..1fd43e5f1 100755 --- a/src/General/css/style.css +++ b/src/General/css/style.css @@ -10,7 +10,6 @@ #thread-watcher { box-shadow: -1px 2px 2px rgba(0, 0, 0, 0.25); } -.captcha-img, .field { background-color: #FFF; border: 1px solid #CCC; @@ -28,7 +27,6 @@ font-size: 13px !important; opacity: 1.0 !important; } -.captch-img:hover, .field:hover { border-color: #999; } @@ -947,7 +945,7 @@ span.hide-announcement { /* QR */ :root.hide-original-post-form #togglePostFormLink, :root:not(.catalog) #togglePostFormLink, -#qr.autohide:not(.focus):not(:hover):not(:active) > form, +#qr.autohide:not(.focus):not(:hover):not(:active):not(.captcha-open) > form, :root.thread-view #qr:not(.show-new-thread-option) select[data-name="thread"], #file-n-submit:not(.has-file) #qr-filerm { display: none; @@ -961,8 +959,7 @@ span.hide-announcement { #qr select, #dump-button, #url-button, -.remove, -.captcha-img { +.remove { cursor: pointer; } #qr { @@ -1055,20 +1052,12 @@ input.field.tripped:not(:hover):not(:focus) { #qr textarea { resize: both; } -.captcha-img { - margin: 0px; +#qr .captcha-counter { + display: block; text-align: center; - background-image: #fff; - font-size: 0px; - min-height: 59px; - min-width: 302px; } -.captcha-input{ - width: 100%; - margin: 1px 0 0; -} -.captcha-input.error:focus { - border-color: rgb(255,0,0) !important; +#qr .captcha-container > div > div { + margin: auto; } .field { -moz-box-sizing: border-box; diff --git a/src/General/html/Features/QuickReply.html b/src/General/html/Features/QuickReply.html index 4ec950149..7f7ff5d1c 100755 --- a/src/General/html/Features/QuickReply.html +++ b/src/General/html/Features/QuickReply.html @@ -10,20 +10,20 @@
- - - + + +
- +
- + + +
- + No selected file @@ -33,9 +33,9 @@ - +
diff --git a/src/Posting/QR.captcha.coffee b/src/Posting/QR.captcha.coffee index 02de79e05..849cf79a0 100644 --- a/src/Posting/QR.captcha.coffee +++ b/src/Posting/QR.captcha.coffee @@ -3,104 +3,106 @@ QR.captcha = return if d.cookie.indexOf('pass_enabled=1') >= 0 return unless @isEnabled = !!$.id 'captchaContainer' - $.globalEval 'loadRecaptcha()' if Conf['Auto-load captcha'] - - imgContainer = $.el 'div', - className: 'captcha-img' - title: 'Reload reCAPTCHA' - $.extend imgContainer, <%= html('') %> - input = $.el 'input', - className: 'captcha-input field' - title: 'Verification' - autocomplete: 'off' - spellcheck: false - tabIndex: 45 - @nodes = - img: imgContainer.firstChild - input: input - - $.on input, 'blur', QR.focusout - $.on input, 'focus', QR.focusin - $.on input, 'keydown', QR.captcha.keydown.bind QR.captcha - $.on @nodes.img.parentNode, 'click', QR.captcha.reload.bind QR.captcha - - $.addClass QR.nodes.el, 'has-captcha' - $.after QR.nodes.com.parentNode, [imgContainer, input] - @captchas = [] $.get 'captchas', [], ({captchas}) -> QR.captcha.sync captchas - QR.captcha.clear() - $.sync 'captchas', @sync + $.sync 'captchas', @sync.bind @ - new MutationObserver(@afterSetup).observe $.id('captchaContainer'), childList: true - - @beforeSetup() - @afterSetup() # reCAPTCHA might have loaded before the QR. - beforeSetup: -> - {img, input} = @nodes - img.parentNode.hidden = true - input.value = '' - input.placeholder = 'Focus to load reCAPTCHA' + counter = $.el 'a', + className: 'captcha-counter' + href: 'javascript:;' + container = $.el 'div', + className: 'captcha-container' + @nodes = {counter, container} @count() - $.on input, 'focus', @setup - setup: -> - $.globalEval 'loadRecaptcha()' - afterSetup: -> - return unless challenge = $.id 'recaptcha_challenge_field_holder' - return if challenge is QR.captcha.nodes.challenge + $.addClass QR.nodes.el, 'has-captcha' + $.after QR.nodes.com.parentNode, [counter, container] - 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 + $.on counter, 'click', @toggle.bind @ + $.on window, 'captcha:success', @save.bind @ - {img, input} = QR.captcha.nodes - img.parentNode.hidden = false - input.placeholder = 'Verification' - QR.captcha.count() - $.off input, 'focus', QR.captcha.setup + timeouts: {} - QR.captcha.nodes.challenge = challenge - new MutationObserver(QR.captcha.load.bind QR.captcha).observe challenge, + needed: -> + captchaCount = @captchas.length + captchaCount++ if @nodes.container.dataset.widgetID and !@timeouts.destroy + postsCount = QR.posts.length + postsCount = 0 if postsCount is 1 and !Conf['Auto-load captcha'] and !QR.posts[0].com and !QR.posts[0].file + captchaCount < postsCount + + toggle: -> + if @nodes.container.dataset.widgetID and !@timeouts.destroy + @destroy() + else + @setup true + + setup: (force) -> + return unless @isEnabled and (@needed() or force) + $.addClass QR.nodes.el, 'captcha-open' # suppress autohide so that captcha pop-up works + if @timeouts.destroy + clearTimeout @timeouts.destroy + delete @timeouts.destroy + return @reload() + return if @nodes.container.dataset.widgetID + @observer?.disconnect() + @observer = new MutationObserver @afterSetup.bind @ + @observer.observe @nodes.container, childList: true subtree: true - attributes: true - QR.captcha.load() + $.globalEval ''' + (function() { + var container = document.querySelector("#qr .captcha-container"); + container.dataset.widgetID = window.grecaptcha.render(container, { + sitekey: '<%= meta.recaptchaKey %>', + theme: document.documentElement.classList.contains('tomorrow') ? 'dark' : 'light', + callback: function(response) { + window.dispatchEvent(new CustomEvent("captcha:success", {detail: response})); + } + }); + })(); + ''' + afterSetup: -> + return unless @nodes.container.firstElementChild?.firstElementChild + @observer.disconnect() if QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight QR.nodes.el.style.top = null QR.nodes.el.style.bottom = '0px' + destroy: -> - $.globalEval 'Recaptcha.destroy()' - @beforeSetup() + return unless @isEnabled + delete @timeouts.destroy + $.rmClass QR.nodes.el, 'captcha-open' + $.rmAll @nodes.container + # XXX https://github.com/greasemonkey/greasemonkey/issues/1571 + @nodes.container.removeAttribute 'data-widget-i-d' sync: (captchas) -> - QR.captcha.captchas = captchas - QR.captcha.count() + @captchas = captchas + @clear() + @count() getOne: -> @clear() if captcha = @captchas.shift() - {challenge, response} = captcha @count() $.set 'captchas', @captchas + captcha.response else - challenge = @nodes.img.alt - if response = @nodes.input.value - if Conf['Auto-load captcha'] then @reload() else @destroy() - {challenge, response} + null - save: -> - return unless /\S/.test(response = @nodes.input.value) - @nodes.input.value = '' + save: (e) -> + if @needed() + @reload() + else + @timeouts.destroy ?= setTimeout @destroy.bind(@), 3 * $.SECOND + $.forceSync 'captchas' @captchas.push - challenge: @nodes.img.alt - response: response - timeout: @timeout + response: e.detail + timeout: Date.now() + 2 * $.MINUTE @count() - @reload() $.set 'captchas', @captchas + @nodes.counter.focus() clear: -> return unless @captchas.length @@ -111,42 +113,18 @@ QR.captcha = @captchas = @captchas[i..] @count() $.set 'captchas', @captchas - - load: -> - return unless @nodes.challenge.firstChild - return unless challenge_image = $.id 'recaptcha_challenge_image' - # -1 minute to give upload some time. - @timeout = Date.now() + @lifetime * $.SECOND - $.MINUTE - challenge = @nodes.challenge.firstChild.value - @nodes.img.alt = challenge - @nodes.img.src = challenge_image.src - @nodes.input.value = null - @clear() + @setup() count: -> - count = if @captchas then @captchas.length else 0 - placeholder = @nodes.input.placeholder.replace /\ \(.*\)$/, '' - placeholder += switch count - when 0 - if placeholder is 'Verification' then ' (Shift + Enter to cache)' else '' - when 1 - ' (1 cached captcha)' - else - " (#{count} cached captchas)" - @nodes.input.placeholder = placeholder - @nodes.input.alt = count # For XTRM RICE. + @nodes.counter.textContent = "Captchas: #{@captchas.length}" + clearTimeout @timeouts.clear + if @captchas.length + @timeouts.clear = setTimeout @clear.bind(@), @captchas[0].timeout - Date.now() reload: (focus) -> - # Hack to prevent the input from being focused - $.globalEval 'Recaptcha.reload(); Recaptcha.should_focus = false;' - # Focus if we meant to. - @nodes.input.focus() if focus - - keydown: (e) -> - if e.keyCode is 8 and not @nodes.input.value - @reload() - else if e.keyCode is 13 and e.shiftKey - @save() - else - return - e.preventDefault() + $.globalEval ''' + (function() { + var container = document.querySelector("#qr .captcha-container"); + window.grecaptcha.reset(container.dataset.widgetID); + })(); + ''' diff --git a/src/Posting/QR.coffee b/src/Posting/QR.coffee index 8b1fe1ba8..780dab32c 100644 --- a/src/Posting/QR.coffee +++ b/src/Posting/QR.coffee @@ -95,6 +95,7 @@ QR = if QR.nodes QR.nodes.el.hidden = false QR.unhide() + QR.captcha.setup() return try QR.dialog() @@ -111,8 +112,6 @@ QR = QR.cleanNotifications() d.activeElement.blur() $.rmClass QR.nodes.el, 'dump' - unless Conf['Captcha Warning Notifications'] - $.rmClass QR.captcha.nodes.input, 'error' if QR.captcha.isEnabled if Conf['QR Shortcut'] $.toggleClass $('.qr-shortcut'), 'disabled' new QR.post true @@ -120,8 +119,7 @@ QR = post.delete() QR.cooldown.auto = false QR.status() - if QR.captcha.isEnabled and not Conf['Auto-load captcha'] - QR.captcha.destroy() + QR.captcha.destroy() focusin: -> $.addClass QR.nodes.el, 'focus' focusout: -> @@ -147,18 +145,8 @@ QR = el = err el.removeAttribute 'style' if QR.captcha.isEnabled and /captcha|verification/i.test el.textContent - if QR.captcha.captchas.length is 0 - # Focus the captcha input on captcha error. - QR.captcha.nodes.input.focus() - QR.captcha.setup() - if Conf['Captcha Warning Notifications'] and !d.hidden - QR.notify el - else - $.addClass QR.captcha.nodes.input, 'error' - $.on QR.captcha.nodes.input, 'keydown', -> - $.rmClass QR.captcha.nodes.input, 'error' - else - QR.notify el + QR.captcha.setup() + QR.notify el alert el.textContent if d.hidden notify: (el) -> @@ -539,6 +527,7 @@ QR = QR.cooldown.init() QR.captcha.init() $.add d.body, dialog + QR.captcha.setup() # Create a custom event when the QR dialog is first initialized. # Use it to extend the QR's functionalities, or for XTRM RICE. @@ -629,7 +618,7 @@ QR = err = 'Max limit of image replies has been reached.' if QR.captcha.isEnabled and !err - {challenge, response} = QR.captcha.getOne() + response = QR.captcha.getOne() err = 'No valid captcha.' unless response QR.cleanNotifications() @@ -663,8 +652,7 @@ QR = textonly: textOnly mode: 'regist' pwd: QR.persona.pwd - recaptcha_challenge_field: challenge - recaptcha_response_field: response + 'g-recaptcha-response': response options = responseType: 'document' @@ -793,7 +781,6 @@ QR = icon: Favicon.logo notif.onclick = -> QR.open() - QR.captcha.nodes.input.focus() window.focus() notif.onshow = -> setTimeout -> @@ -803,9 +790,8 @@ QR = unless Conf['Persistent QR'] or postsCount QR.close() else - if QR.posts.length > 1 and QR.captcha.isEnabled and QR.captcha.captchas.length is 0 - QR.captcha.setup() post.rm() + QR.captcha.setup() QR.cooldown.add req.uploadEndTime, threadID, postID diff --git a/src/Posting/QR.post.coffee b/src/Posting/QR.post.coffee index 643171855..b228642a1 100644 --- a/src/Posting/QR.post.coffee +++ b/src/Posting/QR.post.coffee @@ -37,6 +37,7 @@ QR.post = class prev = QR.posts[QR.posts.length - 1] QR.posts.push @ + QR.captcha.setup() @nodes.spoiler.checked = @spoiler = if prev and Conf['Remember Spoiler'] prev.spoiler else @@ -131,6 +132,7 @@ QR.post = class QR.status() when 'com' @nodes.span.textContent = @com + QR.captcha.setup() QR.characterCount() # Disable auto-posting if you're typing in the first post # during the last 5 seconds of the cooldown. @@ -159,6 +161,7 @@ QR.post = class @filename = file.name @filesize = $.bytesToString file.size @nodes.label.hidden = false if QR.spoiler + QR.captcha.setup() URL.revokeObjectURL @URL if @ is QR.selected @showFileData()