Merge tag 'Stable' into Beta

4chan X v1.9.14.5.

Conflicts:
	LICENSE
	builds/4chan-X-beta.crx
	builds/4chan-X-beta.meta.js
	builds/4chan-X-beta.user.js
	builds/4chan-X-noupdate.crx
	builds/4chan-X-noupdate.user.js
	builds/4chan-X.crx
	builds/4chan-X.meta.js
	builds/4chan-X.user.js
	builds/4chan-X.zip
	builds/updates-beta.xml
	builds/updates.xml
	package.json
	src/Posting/QR.captcha.coffee
This commit is contained in:
ccd0 2014-12-08 15:27:19 -08:00
commit 9560ccf7b2
7 changed files with 134 additions and 146 deletions

View File

@ -42,6 +42,12 @@ Based on v1.9.14.2.
- Bug fixes. - Bug fixes.
<!-- v1.9.14.x --> <!-- v1.9.14.x -->
### v1.9.14.5
*2014-12-08* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.14.5/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.14.5/builds/4chan-X-noupdate.crx "Chromium version")]
**ccd0**
- Update 4chan X for Recaptcha v2.
### v1.9.14.4 ### v1.9.14.4
*2014-12-08* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.14.4/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.14.4/builds/4chan-X-noupdate.crx "Chromium version")] *2014-12-08* - [[Firefox](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.14.4/builds/4chan-X-noupdate.user.js "Firefox version")] [[Chromium](https://raw.githubusercontent.com/ccd0/4chan-x/1.9.14.4/builds/4chan-X-noupdate.crx "Chromium version")]

View File

@ -10,6 +10,7 @@
"oldVersions": "https://raw.githubusercontent.com/ccd0/4chan-x/", "oldVersions": "https://raw.githubusercontent.com/ccd0/4chan-x/",
"faq": "https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions", "faq": "https://github.com/ccd0/4chan-x/wiki/Frequently-Asked-Questions",
"appid": "lacclbnghgdicfifcamcmcnilckjamag", "appid": "lacclbnghgdicfifcamcmcnilckjamag",
"recaptchaKey": "6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc",
"youtubeAPIKey": "AIzaSyB5_zaen_-46Uhz1xGR-lz1YoUMHqCD6CE", "youtubeAPIKey": "AIzaSyB5_zaen_-46Uhz1xGR-lz1YoUMHqCD6CE",
"buildsPath": "builds/", "buildsPath": "builds/",
"mainBranch": "master", "mainBranch": "master",

View File

@ -377,7 +377,7 @@ Config =
] ]
'Auto-load captcha': [ 'Auto-load captcha': [
false 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': [ 'Bottom QR Link': [
true true

View File

@ -10,7 +10,6 @@
#thread-watcher { #thread-watcher {
box-shadow: -1px 2px 2px rgba(0, 0, 0, 0.25); box-shadow: -1px 2px 2px rgba(0, 0, 0, 0.25);
} }
.captcha-img,
.field { .field {
background-color: #FFF; background-color: #FFF;
border: 1px solid #CCC; border: 1px solid #CCC;
@ -28,7 +27,6 @@
font-size: 13px !important; font-size: 13px !important;
opacity: 1.0 !important; opacity: 1.0 !important;
} }
.captch-img:hover,
.field:hover { .field:hover {
border-color: #999; border-color: #999;
} }
@ -961,7 +959,7 @@ span.hide-announcement {
/* QR */ /* QR */
:root.hide-original-post-form #togglePostFormLink, :root.hide-original-post-form #togglePostFormLink,
:root:not(.catalog) #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"], :root.thread-view #qr:not(.show-new-thread-option) select[data-name="thread"],
#file-n-submit:not(.has-file) #qr-filerm { #file-n-submit:not(.has-file) #qr-filerm {
display: none; display: none;
@ -975,8 +973,7 @@ span.hide-announcement {
#qr select, #qr select,
#dump-button, #dump-button,
#url-button, #url-button,
.remove, .remove {
.captcha-img {
cursor: pointer; cursor: pointer;
} }
#qr { #qr {
@ -1069,20 +1066,28 @@ input.field.tripped:not(:hover):not(:focus) {
#qr textarea { #qr textarea {
resize: both; resize: both;
} }
.captcha-img { #qr .captcha-section {
margin: 0px; position: relative;
text-align: center;
background-image: #fff;
font-size: 0px;
min-height: 59px;
min-width: 302px;
} }
.captcha-input{ #qr .captcha-container > div > div {
margin: auto;
}
#qr .captcha-counter {
display: block;
width: 100%; width: 100%;
margin: 1px 0 0; text-align: center;
pointer-events: none;
} }
.captcha-input.error:focus { #qr.captcha-open .captcha-counter {
border-color: rgb(255,0,0) !important; position: absolute;
bottom: 3px;
}
#qr .captcha-counter > a {
pointer-events: auto;
}
#qr:not(.captcha-open) .captcha-counter > a {
display: block;
width: 100%;
} }
.field { .field {
-moz-box-sizing: border-box; -moz-box-sizing: border-box;

View File

@ -1,104 +1,115 @@
QR.captcha = QR.captcha =
init: -> init: ->
return if d.cookie.indexOf('pass_enabled=1') >= 0 return if d.cookie.indexOf('pass_enabled=1') >= 0
return unless @isEnabled = !!$.id 'captchaContainer' return unless @isEnabled = !!$.id 'g-recaptcha'
$.globalEval 'loadRecaptcha()' if Conf['Auto-load captcha']
imgContainer = $.el 'div',
className: 'captcha-img'
title: 'Reload reCAPTCHA'
$.extend imgContainer, <%= html('<img>') %>
input = $.el 'input',
className: 'captcha-input field'
title: 'Verification'
autocomplete: 'off'
spellcheck: false
@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 = [] @captchas = []
$.get 'captchas', [], ({captchas}) -> $.get 'captchas', [], ({captchas}) ->
QR.captcha.sync captchas QR.captcha.sync captchas
QR.captcha.clear() $.sync 'captchas', @sync.bind @
$.sync 'captchas', @sync
new MutationObserver(@afterSetup).observe $.id('captchaContainer'), childList: true section = $.el 'div', className: 'captcha-section'
$.extend section, <%= html(
@beforeSetup() '<div class="captcha-container"></div>' +
@afterSetup() # reCAPTCHA might have loaded before the QR. '<div class="captcha-counter"><a href="javascript:;"></a></div>'
beforeSetup: -> ) %>
{img, input} = @nodes container = $ '.captcha-container', section
img.parentNode.hidden = true counter = $ '.captcha-counter > a', section
input.value = '' @nodes = {container, counter}
input.placeholder = 'Focus to load reCAPTCHA'
@count() @count()
$.on input, 'focus', @setup $.addClass QR.nodes.el, 'has-captcha'
setup: -> $.after QR.nodes.com.parentNode, section
$.globalEval 'loadRecaptcha()'
afterSetup: ->
return unless challenge = $.id 'recaptcha_challenge_field_holder'
return if challenge is QR.captcha.nodes.challenge
setLifetime = (e) -> QR.captcha.lifetime = e.detail new MutationObserver(@afterSetup.bind @).observe container,
$.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
input.placeholder = 'Verification'
QR.captcha.count()
$.off input, 'focus', QR.captcha.setup
QR.captcha.nodes.challenge = challenge
new MutationObserver(QR.captcha.load.bind QR.captcha).observe challenge,
childList: true childList: true
subtree: true subtree: true
attributes: true
QR.captcha.load()
$.on counter, 'click', @toggle.bind @
$.on window, 'captcha:success', @save.bind @
shouldFocus: false
timeouts: {}
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
@shouldFocus = true
@setup true
setup: (force) ->
return unless @isEnabled and (@needed() or force)
$.addClass QR.nodes.el, 'captcha-open'
if @timeouts.destroy
clearTimeout @timeouts.destroy
delete @timeouts.destroy
return @reload()
return if @nodes.container.dataset.widgetID
$.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: (mutations) ->
for mutation in mutations
for node in mutation.addedNodes
iframe = node if node.nodeName is 'IFRAME'
return unless iframe
if QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight if QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight
QR.nodes.el.style.top = null QR.nodes.el.style.top = null
QR.nodes.el.style.bottom = '0px' QR.nodes.el.style.bottom = '0px'
iframe.focus() if @shouldFocus
@shouldFocus = false
destroy: -> destroy: ->
$.globalEval 'Recaptcha.destroy()' return unless @isEnabled
@beforeSetup() 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) -> sync: (captchas) ->
QR.captcha.captchas = captchas @captchas = captchas
QR.captcha.count() @clear()
@count()
getOne: -> getOne: ->
@clear() @clear()
if captcha = @captchas.shift() if captcha = @captchas.shift()
{challenge, response} = captcha
@count() @count()
$.set 'captchas', @captchas $.set 'captchas', @captchas
captcha.response
else else
challenge = @nodes.img.alt null
if response = @nodes.input.value
if Conf['Auto-load captcha'] then @reload() else @destroy()
{challenge, response}
save: -> save: (e) ->
return unless /\S/.test(response = @nodes.input.value) if @needed()
@nodes.input.value = '' @shouldFocus = true
@reload()
else
@nodes.counter.focus()
@timeouts.destroy ?= setTimeout @destroy.bind(@), 3 * $.SECOND
$.forceSync 'captchas'
@captchas.push @captchas.push
challenge: @nodes.img.alt response: e.detail
response: response timeout: Date.now() + 2 * $.MINUTE
timeout: @timeout
@count() @count()
@reload()
$.set 'captchas', @captchas $.set 'captchas', @captchas
clear: -> clear: ->
@ -110,42 +121,18 @@ QR.captcha =
@captchas = @captchas[i..] @captchas = @captchas[i..]
@count() @count()
$.set 'captchas', @captchas $.set 'captchas', @captchas
@setup()
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()
count: -> count: ->
count = if @captchas then @captchas.length else 0 @nodes.counter.textContent = "Captchas: #{@captchas.length}"
placeholder = @nodes.input.placeholder.replace /\ \(.*\)$/, '' clearTimeout @timeouts.clear
placeholder += switch count if @captchas.length
when 0 @timeouts.clear = setTimeout @clear.bind(@), @captchas[0].timeout - Date.now()
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.
reload: (focus) -> reload: (focus) ->
# Hack to prevent the input from being focused $.globalEval '''
$.globalEval 'Recaptcha.reload(); Recaptcha.should_focus = false;' (function() {
# Focus if we meant to. var container = document.querySelector("#qr .captcha-container");
@nodes.input.focus() if focus window.grecaptcha.reset(container.dataset.widgetID);
})();
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()

View File

@ -95,6 +95,7 @@ QR =
if QR.nodes if QR.nodes
QR.nodes.el.hidden = false QR.nodes.el.hidden = false
QR.unhide() QR.unhide()
QR.captcha.setup()
return return
try try
QR.dialog() QR.dialog()
@ -111,8 +112,6 @@ QR =
QR.cleanNotifications() QR.cleanNotifications()
d.activeElement.blur() d.activeElement.blur()
$.rmClass QR.nodes.el, 'dump' $.rmClass QR.nodes.el, 'dump'
unless Conf['Captcha Warning Notifications']
$.rmClass QR.captcha.nodes.input, 'error' if QR.captcha.isEnabled
if Conf['QR Shortcut'] if Conf['QR Shortcut']
$.toggleClass $('.qr-shortcut'), 'disabled' $.toggleClass $('.qr-shortcut'), 'disabled'
new QR.post true new QR.post true
@ -120,8 +119,7 @@ QR =
post.delete() post.delete()
QR.cooldown.auto = false QR.cooldown.auto = false
QR.status() QR.status()
if QR.captcha.isEnabled and not Conf['Auto-load captcha'] QR.captcha.destroy()
QR.captcha.destroy()
focusin: -> focusin: ->
$.addClass QR.nodes.el, 'focus' $.addClass QR.nodes.el, 'focus'
focusout: -> focusout: ->
@ -147,18 +145,8 @@ QR =
el = err el = err
el.removeAttribute 'style' el.removeAttribute 'style'
if QR.captcha.isEnabled and /captcha|verification/i.test el.textContent if QR.captcha.isEnabled and /captcha|verification/i.test el.textContent
if QR.captcha.captchas.length is 0 QR.captcha.setup()
# Focus the captcha input on captcha error. QR.notify el
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
alert el.textContent if d.hidden alert el.textContent if d.hidden
notify: (el) -> notify: (el) ->
@ -561,6 +549,7 @@ QR =
QR.cooldown.init() QR.cooldown.init()
QR.captcha.init() QR.captcha.init()
$.add d.body, dialog $.add d.body, dialog
QR.captcha.setup()
# Create a custom event when the QR dialog is first initialized. # Create a custom event when the QR dialog is first initialized.
# Use it to extend the QR's functionalities, or for XTRM RICE. # Use it to extend the QR's functionalities, or for XTRM RICE.
@ -651,7 +640,7 @@ QR =
err = 'Max limit of image replies has been reached.' err = 'Max limit of image replies has been reached.'
if QR.captcha.isEnabled and !err if QR.captcha.isEnabled and !err
{challenge, response} = QR.captcha.getOne() response = QR.captcha.getOne()
err = 'No valid captcha.' unless response err = 'No valid captcha.' unless response
QR.cleanNotifications() QR.cleanNotifications()
@ -685,8 +674,7 @@ QR =
textonly: textOnly textonly: textOnly
mode: 'regist' mode: 'regist'
pwd: QR.persona.pwd pwd: QR.persona.pwd
recaptcha_challenge_field: challenge 'g-recaptcha-response': response
recaptcha_response_field: response
options = options =
responseType: 'document' responseType: 'document'
@ -815,7 +803,6 @@ QR =
icon: Favicon.logo icon: Favicon.logo
notif.onclick = -> notif.onclick = ->
QR.open() QR.open()
QR.captcha.nodes.input.focus()
window.focus() window.focus()
notif.onshow = -> notif.onshow = ->
setTimeout -> setTimeout ->
@ -825,9 +812,8 @@ QR =
unless Conf['Persistent QR'] or postsCount unless Conf['Persistent QR'] or postsCount
QR.close() QR.close()
else else
if QR.posts.length > 1 and QR.captcha.isEnabled and QR.captcha.captchas.length is 0
QR.captcha.setup()
post.rm() post.rm()
QR.captcha.setup()
QR.cooldown.add req.uploadEndTime, threadID, postID QR.cooldown.add req.uploadEndTime, threadID, postID

View File

@ -37,6 +37,7 @@ QR.post = class
prev = QR.posts[QR.posts.length - 1] prev = QR.posts[QR.posts.length - 1]
QR.posts.push @ QR.posts.push @
QR.captcha.setup()
@nodes.spoiler.checked = @spoiler = if prev and Conf['Remember Spoiler'] @nodes.spoiler.checked = @spoiler = if prev and Conf['Remember Spoiler']
prev.spoiler prev.spoiler
else else
@ -131,6 +132,7 @@ QR.post = class
QR.status() QR.status()
when 'com' when 'com'
@nodes.span.textContent = @com @nodes.span.textContent = @com
QR.captcha.setup()
QR.characterCount() QR.characterCount()
# Disable auto-posting if you're typing in the first post # Disable auto-posting if you're typing in the first post
# during the last 5 seconds of the cooldown. # during the last 5 seconds of the cooldown.
@ -159,6 +161,7 @@ QR.post = class
@filename = file.name @filename = file.name
@filesize = $.bytesToString file.size @filesize = $.bytesToString file.size
@nodes.label.hidden = false if QR.spoiler @nodes.label.hidden = false if QR.spoiler
QR.captcha.setup()
URL.revokeObjectURL @URL URL.revokeObjectURL @URL
if @ is QR.selected if @ is QR.selected
@showFileData() @showFileData()