Merge branch 'old-captcha'; allow selection of old or new captcha.

This commit is contained in:
ccd0 2014-12-10 01:53:33 -08:00
commit c7433d9271
8 changed files with 238 additions and 15 deletions

View File

@ -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'

View File

@ -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.'

View File

@ -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;
} }

View File

@ -0,0 +1 @@
Captcha = {}

View 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()

View File

@ -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()

View File

@ -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

View File

@ -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()