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:
commit
9560ccf7b2
@ -42,6 +42,12 @@ Based on v1.9.14.2.
|
||||
- Bug fixes.
|
||||
|
||||
<!-- 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
|
||||
*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")]
|
||||
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -377,7 +377,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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -961,7 +959,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;
|
||||
@ -975,8 +973,7 @@ span.hide-announcement {
|
||||
#qr select,
|
||||
#dump-button,
|
||||
#url-button,
|
||||
.remove,
|
||||
.captcha-img {
|
||||
.remove {
|
||||
cursor: pointer;
|
||||
}
|
||||
#qr {
|
||||
@ -1069,20 +1066,28 @@ input.field.tripped:not(:hover):not(:focus) {
|
||||
#qr textarea {
|
||||
resize: both;
|
||||
}
|
||||
.captcha-img {
|
||||
margin: 0px;
|
||||
text-align: center;
|
||||
background-image: #fff;
|
||||
font-size: 0px;
|
||||
min-height: 59px;
|
||||
min-width: 302px;
|
||||
#qr .captcha-section {
|
||||
position: relative;
|
||||
}
|
||||
.captcha-input{
|
||||
#qr .captcha-container > div > div {
|
||||
margin: auto;
|
||||
}
|
||||
#qr .captcha-counter {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 1px 0 0;
|
||||
text-align: center;
|
||||
pointer-events: none;
|
||||
}
|
||||
.captcha-input.error:focus {
|
||||
border-color: rgb(255,0,0) !important;
|
||||
#qr.captcha-open .captcha-counter {
|
||||
position: absolute;
|
||||
bottom: 3px;
|
||||
}
|
||||
#qr .captcha-counter > a {
|
||||
pointer-events: auto;
|
||||
}
|
||||
#qr:not(.captcha-open) .captcha-counter > a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.field {
|
||||
-moz-box-sizing: border-box;
|
||||
|
||||
@ -1,104 +1,115 @@
|
||||
QR.captcha =
|
||||
init: ->
|
||||
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('<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]
|
||||
return unless @isEnabled = !!$.id 'g-recaptcha'
|
||||
|
||||
@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'
|
||||
section = $.el 'div', className: 'captcha-section'
|
||||
$.extend section, <%= html(
|
||||
'<div class="captcha-container"></div>' +
|
||||
'<div class="captcha-counter"><a href="javascript:;"></a></div>'
|
||||
) %>
|
||||
container = $ '.captcha-container', section
|
||||
counter = $ '.captcha-counter > a', section
|
||||
@nodes = {container, counter}
|
||||
@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, section
|
||||
|
||||
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
|
||||
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,
|
||||
new MutationObserver(@afterSetup.bind @).observe container,
|
||||
childList: 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
|
||||
QR.nodes.el.style.top = null
|
||||
QR.nodes.el.style.bottom = '0px'
|
||||
iframe.focus() if @shouldFocus
|
||||
@shouldFocus = false
|
||||
|
||||
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()
|
||||
@shouldFocus = true
|
||||
@reload()
|
||||
else
|
||||
@nodes.counter.focus()
|
||||
@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
|
||||
|
||||
clear: ->
|
||||
@ -110,42 +121,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);
|
||||
})();
|
||||
'''
|
||||
|
||||
@ -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) ->
|
||||
@ -561,6 +549,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.
|
||||
@ -651,7 +640,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()
|
||||
@ -685,8 +674,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'
|
||||
@ -815,7 +803,6 @@ QR =
|
||||
icon: Favicon.logo
|
||||
notif.onclick = ->
|
||||
QR.open()
|
||||
QR.captcha.nodes.input.focus()
|
||||
window.focus()
|
||||
notif.onshow = ->
|
||||
setTimeout ->
|
||||
@ -825,9 +812,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
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user