Merge branch 'old-captcha'; allow selection of old or new captcha.
This commit is contained in:
commit
c7433d9271
@ -61,6 +61,7 @@ module.exports = (grunt) ->
|
|||||||
'src/Filtering/**/*.coffee'
|
'src/Filtering/**/*.coffee'
|
||||||
'src/Quotelinks/**/*.coffee'
|
'src/Quotelinks/**/*.coffee'
|
||||||
'src/Posting/QR.coffee'
|
'src/Posting/QR.coffee'
|
||||||
|
'src/Posting/Captcha.coffee'
|
||||||
'src/Posting/**/*.coffee'
|
'src/Posting/**/*.coffee'
|
||||||
'src/Images/**/*.coffee'
|
'src/Images/**/*.coffee'
|
||||||
'src/Linkification/**/*.coffee'
|
'src/Linkification/**/*.coffee'
|
||||||
|
|||||||
@ -371,6 +371,10 @@ Config =
|
|||||||
true
|
true
|
||||||
'Show notifications on successful post creation or file uploading.'
|
'Show notifications on successful post creation or file uploading.'
|
||||||
]
|
]
|
||||||
|
'Use Recaptcha v1': [
|
||||||
|
false
|
||||||
|
'Use the old version of Recaptcha before the introduction of the checkbox.'
|
||||||
|
]
|
||||||
'Captcha Warning Notifications': [
|
'Captcha Warning Notifications': [
|
||||||
true
|
true
|
||||||
'When disabled, shows a red border on the CAPTCHA input until a key is pressed instead of a notification.'
|
'When disabled, shows a red border on the CAPTCHA input until a key is pressed instead of a notification.'
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
#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;
|
||||||
@ -27,6 +28,7 @@
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@ -969,7 +971,8 @@ 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 {
|
||||||
@ -1062,6 +1065,23 @@ input.field.tripped:not(:hover):not(:focus) {
|
|||||||
#qr textarea {
|
#qr textarea {
|
||||||
resize: both;
|
resize: both;
|
||||||
}
|
}
|
||||||
|
/* Recaptcha v1 */
|
||||||
|
.captcha-img {
|
||||||
|
margin: 0px;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
/* Recaptcha v2 */
|
||||||
#qr .captcha-root {
|
#qr .captcha-root {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/Posting/Captcha.coffee
Normal file
1
src/Posting/Captcha.coffee
Normal file
@ -0,0 +1 @@
|
|||||||
|
Captcha = {}
|
||||||
176
src/Posting/Captcha.v1.coffee
Normal file
176
src/Posting/Captcha.v1.coffee
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
Captcha.v1 =
|
||||||
|
init: ->
|
||||||
|
return if d.cookie.indexOf('pass_enabled=1') >= 0
|
||||||
|
return unless @isEnabled = !!$.id 'g-recaptcha'
|
||||||
|
|
||||||
|
script = $.el 'script',
|
||||||
|
src: '//www.google.com/recaptcha/api/js/recaptcha_ajax.js'
|
||||||
|
$.add d.head, script
|
||||||
|
captchaContainer = $.el 'div',
|
||||||
|
id: 'captchaContainer'
|
||||||
|
hidden: true
|
||||||
|
$.add d.body, captchaContainer
|
||||||
|
|
||||||
|
@setup() 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 = []
|
||||||
|
$.get 'captchas', [], ({captchas}) ->
|
||||||
|
QR.captcha.sync captchas
|
||||||
|
QR.captcha.clear()
|
||||||
|
$.sync 'captchas', @sync
|
||||||
|
|
||||||
|
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'
|
||||||
|
@count()
|
||||||
|
$.on input, 'focus', @setup
|
||||||
|
setup: ->
|
||||||
|
$.globalEval '''
|
||||||
|
(function() {
|
||||||
|
var captchaContainer = document.getElementById("captchaContainer");
|
||||||
|
if (captchaContainer.firstChild) return;
|
||||||
|
function setup() {
|
||||||
|
if (window.Recaptcha) {
|
||||||
|
Recaptcha.create(recaptchaKey, captchaContainer, {theme: "clean"});
|
||||||
|
} else {
|
||||||
|
setTimeout(setup, 25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setup();
|
||||||
|
})()
|
||||||
|
'''
|
||||||
|
afterSetup: ->
|
||||||
|
return unless challenge = $.id 'recaptcha_challenge_field_holder'
|
||||||
|
return if challenge is QR.captcha.nodes.challenge
|
||||||
|
|
||||||
|
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,
|
||||||
|
childList: true
|
||||||
|
subtree: true
|
||||||
|
attributes: true
|
||||||
|
QR.captcha.load()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
sync: (captchas) ->
|
||||||
|
QR.captcha.captchas = captchas
|
||||||
|
QR.captcha.count()
|
||||||
|
|
||||||
|
getOne: ->
|
||||||
|
@clear()
|
||||||
|
if captcha = @captchas.shift()
|
||||||
|
{challenge, response} = captcha
|
||||||
|
@count()
|
||||||
|
$.set 'captchas', @captchas
|
||||||
|
else
|
||||||
|
challenge = @nodes.img.alt
|
||||||
|
if response = @nodes.input.value
|
||||||
|
if Conf['Auto-load captcha'] then @reload() else @destroy()
|
||||||
|
# Duplicate one-word captchas.
|
||||||
|
# Don't duplicate street numbers for now (needs testing).
|
||||||
|
if response and !/\s|^\d$/.test response
|
||||||
|
response = "#{response} #{response}"
|
||||||
|
{challenge, response}
|
||||||
|
|
||||||
|
save: ->
|
||||||
|
return unless /\S/.test(response = @nodes.input.value)
|
||||||
|
@nodes.input.value = ''
|
||||||
|
@captchas.push
|
||||||
|
challenge: @nodes.img.alt
|
||||||
|
response: response
|
||||||
|
timeout: @timeout
|
||||||
|
@count()
|
||||||
|
@reload()
|
||||||
|
$.set 'captchas', @captchas
|
||||||
|
|
||||||
|
clear: ->
|
||||||
|
return unless @captchas.length
|
||||||
|
now = Date.now()
|
||||||
|
for captcha, i in @captchas
|
||||||
|
break if captcha.timeout > now
|
||||||
|
return unless i
|
||||||
|
@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()
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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()
|
||||||
@ -1,4 +1,4 @@
|
|||||||
QR.captcha =
|
Captcha.v2 =
|
||||||
init: ->
|
init: ->
|
||||||
return if d.cookie.indexOf('pass_enabled=1') >= 0
|
return if d.cookie.indexOf('pass_enabled=1') >= 0
|
||||||
return unless @isEnabled = !!$.id 'g-recaptcha'
|
return unless @isEnabled = !!$.id 'g-recaptcha'
|
||||||
@ -117,9 +117,9 @@ QR.captcha =
|
|||||||
if captcha = @captchas.shift()
|
if captcha = @captchas.shift()
|
||||||
@count()
|
@count()
|
||||||
$.set 'captchas', @captchas
|
$.set 'captchas', @captchas
|
||||||
captcha.response
|
{response: captcha.response}
|
||||||
else
|
else
|
||||||
null
|
{}
|
||||||
|
|
||||||
save: (pasted) ->
|
save: (pasted) ->
|
||||||
reload = QR.cooldown.auto and @needed()
|
reload = QR.cooldown.auto and @needed()
|
||||||
@ -7,6 +7,8 @@ QR =
|
|||||||
@db = new DataBoard 'yourPosts'
|
@db = new DataBoard 'yourPosts'
|
||||||
@posts = []
|
@posts = []
|
||||||
|
|
||||||
|
@captcha = Captcha[if Conf['Use Recaptcha v1'] then 'v1' else 'v2']
|
||||||
|
|
||||||
if Conf['QR Shortcut']
|
if Conf['QR Shortcut']
|
||||||
sc = $.el 'a',
|
sc = $.el 'a',
|
||||||
className: "qr-shortcut fa fa-comment-o #{unless Conf['Persistent QR'] then 'disabled' else ''}"
|
className: "qr-shortcut fa fa-comment-o #{unless Conf['Persistent QR'] then 'disabled' else ''}"
|
||||||
@ -95,7 +97,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()
|
QR.captcha.setup() unless Conf['Use Recaptcha v1']
|
||||||
return
|
return
|
||||||
try
|
try
|
||||||
QR.dialog()
|
QR.dialog()
|
||||||
@ -112,6 +114,8 @@ QR =
|
|||||||
QR.cleanNotifications()
|
QR.cleanNotifications()
|
||||||
d.activeElement.blur()
|
d.activeElement.blur()
|
||||||
$.rmClass QR.nodes.el, 'dump'
|
$.rmClass QR.nodes.el, 'dump'
|
||||||
|
if Conf['Use Recaptcha v1'] and !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
|
||||||
@ -119,7 +123,8 @@ QR =
|
|||||||
post.delete()
|
post.delete()
|
||||||
QR.cooldown.auto = false
|
QR.cooldown.auto = false
|
||||||
QR.status()
|
QR.status()
|
||||||
QR.captcha.destroy()
|
if !Conf['Use Recaptcha v1'] or (QR.captcha.isEnabled and not Conf['Auto-load captcha'])
|
||||||
|
QR.captcha.destroy()
|
||||||
focusin: ->
|
focusin: ->
|
||||||
$.addClass QR.nodes.el, 'focus'
|
$.addClass QR.nodes.el, 'focus'
|
||||||
focusout: ->
|
focusout: ->
|
||||||
@ -144,9 +149,21 @@ QR =
|
|||||||
else
|
else
|
||||||
el = err
|
el = err
|
||||||
el.removeAttribute 'style'
|
el.removeAttribute 'style'
|
||||||
if QR.captcha.isEnabled and /captcha|verification/i.test el.textContent
|
captchaErr = QR.captcha.isEnabled and /captcha|verification/i.test el.textContent
|
||||||
QR.captcha.setup true
|
if captchaErr and Conf['Use Recaptcha v1']
|
||||||
QR.notify el
|
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.captcha.setup true if captchaErr and !Conf['Use Recaptcha v1']
|
||||||
|
QR.notify el
|
||||||
alert el.textContent if d.hidden
|
alert el.textContent if d.hidden
|
||||||
|
|
||||||
notify: (el) ->
|
notify: (el) ->
|
||||||
@ -549,7 +566,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()
|
QR.captcha.setup() unless Conf['Use Recaptcha v1']
|
||||||
|
|
||||||
# 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.
|
||||||
@ -640,7 +657,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
|
||||||
response = QR.captcha.getOne()
|
{challenge, response} = QR.captcha.getOne()
|
||||||
err = 'No valid captcha.' unless response
|
err = 'No valid captcha.' unless response
|
||||||
|
|
||||||
QR.cleanNotifications()
|
QR.cleanNotifications()
|
||||||
@ -674,6 +691,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
|
'g-recaptcha-response': response
|
||||||
|
|
||||||
options =
|
options =
|
||||||
@ -803,6 +821,7 @@ QR =
|
|||||||
icon: Favicon.logo
|
icon: Favicon.logo
|
||||||
notif.onclick = ->
|
notif.onclick = ->
|
||||||
QR.open()
|
QR.open()
|
||||||
|
QR.captcha.nodes.input.focus() if Conf['Use Recaptcha v1']
|
||||||
window.focus()
|
window.focus()
|
||||||
notif.onshow = ->
|
notif.onshow = ->
|
||||||
setTimeout ->
|
setTimeout ->
|
||||||
@ -812,8 +831,10 @@ QR =
|
|||||||
unless Conf['Persistent QR'] or postsCount
|
unless Conf['Persistent QR'] or postsCount
|
||||||
QR.close()
|
QR.close()
|
||||||
else
|
else
|
||||||
|
if Conf['Use Recaptcha v1'] and 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 true
|
QR.captcha.setup true unless Conf['Use Recaptcha v1']
|
||||||
|
|
||||||
QR.cooldown.add req.uploadEndTime, threadID, postID
|
QR.cooldown.add req.uploadEndTime, threadID, postID
|
||||||
|
|
||||||
|
|||||||
@ -70,7 +70,7 @@ QR.post = class
|
|||||||
@select() if select
|
@select() if select
|
||||||
@unlock()
|
@unlock()
|
||||||
# Post count temporarily off by 1 when called from QR.post.rm
|
# Post count temporarily off by 1 when called from QR.post.rm
|
||||||
$.queueTask -> QR.captcha.setup()
|
$.queueTask -> QR.captcha.setup() unless Conf['Use Recaptcha v1']
|
||||||
|
|
||||||
rm: ->
|
rm: ->
|
||||||
@delete()
|
@delete()
|
||||||
@ -133,7 +133,7 @@ QR.post = class
|
|||||||
QR.status()
|
QR.status()
|
||||||
when 'com'
|
when 'com'
|
||||||
@nodes.span.textContent = @com
|
@nodes.span.textContent = @com
|
||||||
QR.captcha.onPostChange()
|
QR.captcha.onPostChange() unless Conf['Use Recaptcha v1']
|
||||||
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.
|
||||||
@ -162,7 +162,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.onPostChange()
|
QR.captcha.onPostChange() unless Conf['Use Recaptcha v1']
|
||||||
URL.revokeObjectURL @URL
|
URL.revokeObjectURL @URL
|
||||||
if @ is QR.selected
|
if @ is QR.selected
|
||||||
@showFileData()
|
@showFileData()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user