Merge Recaptcha v2 changes from ccd0/4chan-x
This commit is contained in:
parent
2fad771b94
commit
89cb5a6b6b
@ -3,6 +3,32 @@ module.exports = (grunt) ->
|
|||||||
importHTML = (filename) ->
|
importHTML = (filename) ->
|
||||||
"\"\"\"#{grunt.file.read("src/General/html/#{filename}.html").replace(/^\s+|\s+$</gm, '').replace(/\n/g, '')}\"\"\""
|
"\"\"\"#{grunt.file.read("src/General/html/#{filename}.html").replace(/^\s+|\s+$</gm, '').replace(/\n/g, '')}\"\"\""
|
||||||
|
|
||||||
|
html = (template) ->
|
||||||
|
parts = template.split /([\$&@]){([^}`]*)}/
|
||||||
|
parts2 = []
|
||||||
|
checkText = ''
|
||||||
|
for part, i in parts
|
||||||
|
switch i % 3
|
||||||
|
when 0
|
||||||
|
parts2.push JSON.stringify part unless part is ''
|
||||||
|
checkText += part
|
||||||
|
when 1
|
||||||
|
if /<[^>]*$/.test(checkText) and not (part is '$' and /\=['"][^"'<>]*$/.test checkText)
|
||||||
|
throw new Error "Illegal insertion into HTML template: #{template}"
|
||||||
|
parts2.push switch part
|
||||||
|
when '$' then "E(`#{parts[i+1]}`)"
|
||||||
|
when '&' then "`#{parts[i+1]}`.innerHTML"
|
||||||
|
when '@' then "`#{parts[i+1]}`.map((x) -> x.innerHTML).join('')"
|
||||||
|
unless /^(<\w+( [\w-]+(='[^"'<>]*'|="[^"'<>]*")?)*>|<\/\w+>|[^"'<>]*)*$/.test checkText
|
||||||
|
throw new Error "HTML template is ill-formed: #{template}"
|
||||||
|
output = if parts2.length is 0 then '""' else parts2.join ' + '
|
||||||
|
"(innerHTML: #{output})"
|
||||||
|
|
||||||
|
assert = (statement, objs...) ->
|
||||||
|
return '' unless grunt.config('pkg').tests_enabled
|
||||||
|
"throw new Error 'Assertion failed: ' + `#{JSON.stringify statement}` unless #{statement}"
|
||||||
|
|
||||||
|
|
||||||
# Project configuration.
|
# Project configuration.
|
||||||
grunt.initConfig
|
grunt.initConfig
|
||||||
pkg: grunt.file.readJSON 'package.json'
|
pkg: grunt.file.readJSON 'package.json'
|
||||||
@ -11,6 +37,7 @@ module.exports = (grunt) ->
|
|||||||
get: ->
|
get: ->
|
||||||
pkg = grunt.config 'pkg'
|
pkg = grunt.config 'pkg'
|
||||||
pkg.importHTML = importHTML
|
pkg.importHTML = importHTML
|
||||||
|
pkg.html = html
|
||||||
pkg
|
pkg
|
||||||
enumerable: true
|
enumerable: true
|
||||||
)
|
)
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* appchan x - Version 2.9.37 - 2014-10-01
|
* appchan x - Version 2.9.37 - 2014-12-08
|
||||||
*
|
*
|
||||||
* Licensed under the MIT license.
|
* Licensed under the MIT license.
|
||||||
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE
|
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -7,6 +7,7 @@
|
|||||||
"namespace": "zixaphir",
|
"namespace": "zixaphir",
|
||||||
"repo": "https://github.com/zixaphir/appchan-x/",
|
"repo": "https://github.com/zixaphir/appchan-x/",
|
||||||
"page": "http://zixaphir.github.com/appchan-x/",
|
"page": "http://zixaphir.github.com/appchan-x/",
|
||||||
|
"recaptchaKey": "6Ldp2bsSAAAAAAJ5uyx_lx34lJeEpTLVkP5k04qc",
|
||||||
"buildsPath": "builds/",
|
"buildsPath": "builds/",
|
||||||
"mainBranch": "master",
|
"mainBranch": "master",
|
||||||
"matches": [
|
"matches": [
|
||||||
|
|||||||
@ -9,25 +9,25 @@
|
|||||||
</div>
|
</div>
|
||||||
<form>
|
<form>
|
||||||
<div class=persona>
|
<div class=persona>
|
||||||
<input name=name data-name=name list="list-name" placeholder=Name class=field size=1 tabindex=10>
|
<input name=name data-name=name list="list-name" placeholder=Name class=field size=1>
|
||||||
<input name=email data-name=email list="list-email" placeholder=Options class=field size=1 tabindex=20>
|
<input name=email data-name=email list="list-email" placeholder=Options class=field size=1>
|
||||||
<input name=sub data-name=sub list="list-sub" placeholder=Subject class=field size=1 tabindex=30>
|
<input name=sub data-name=sub list="list-sub" placeholder=Subject class=field size=1>
|
||||||
</div>
|
</div>
|
||||||
<div class=textarea>
|
<div class=textarea>
|
||||||
<textarea data-name=com placeholder=Comment class=field tabindex=40></textarea>
|
<textarea data-name=com placeholder=Comment class=field></textarea>
|
||||||
<span id=char-count></span>
|
<span id=char-count></span>
|
||||||
</div>
|
</div>
|
||||||
<div id=dump-list-container>
|
<div id=dump-list-container>
|
||||||
<div id=dump-list></div>
|
<div id=dump-list></div>
|
||||||
<a id=add-post href=javascript:; title="Add a post" tabindex=50>+</a>
|
<a id=add-post href=javascript:; title="Add a post">+</a>
|
||||||
</div>
|
</div>
|
||||||
<div id=file-n-submit>
|
<div id=file-n-submit>
|
||||||
<span id=qr-filename-container class=field tabindex=60>
|
<span id=qr-filename-container class=field tabindex=0>
|
||||||
<span id=qr-no-file>No selected file</span>
|
<span id=qr-no-file>No selected file</span>
|
||||||
<input id="qr-filename" data-name="filename" spellcheck="false">
|
<input id="qr-filename" data-name="filename" spellcheck="false">
|
||||||
<span id=qr-extras-container>
|
<span id=qr-extras-container>
|
||||||
<label id=qr-spoiler-label>
|
<label id=qr-spoiler-label>
|
||||||
<input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=70>
|
<input type=checkbox id=qr-file-spoiler title='Spoiler image'>
|
||||||
</label>
|
</label>
|
||||||
<span class=description>Spoiler</span>
|
<span class=description>Spoiler</span>
|
||||||
<a id=url-button><i class="fa">\uf0c1</i></a>
|
<a id=url-button><i class="fa">\uf0c1</i></a>
|
||||||
@ -38,7 +38,7 @@
|
|||||||
<span class=description>Remove File</span>
|
<span class=description>Remove File</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<input type=submit tabindex=80>
|
<input type=submit>
|
||||||
</div>
|
</div>
|
||||||
<input type=file multiple>
|
<input type=file multiple>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -61,18 +61,19 @@ $.ajax = do ->
|
|||||||
r.send form
|
r.send form
|
||||||
r
|
r
|
||||||
|
|
||||||
$.cache = do ->
|
do ->
|
||||||
reqs = {}
|
reqs = {}
|
||||||
(url, cb, options) ->
|
$.cache = (url, cb, options) ->
|
||||||
if req = reqs[url]
|
if req = reqs[url]
|
||||||
if req.readyState is 4
|
if req.readyState is 4
|
||||||
cb.call req, req.evt
|
$.queueTask -> cb.call req, req.evt
|
||||||
else
|
else
|
||||||
req.callbacks.push cb
|
req.callbacks.push cb
|
||||||
|
return req
|
||||||
return
|
return
|
||||||
rm = -> delete reqs[url]
|
rm = -> delete reqs[url]
|
||||||
try
|
try
|
||||||
req = $.ajax url, options
|
return unless req = $.ajax url, options
|
||||||
catch err
|
catch err
|
||||||
return
|
return
|
||||||
$.on req, 'load', (e) ->
|
$.on req, 'load', (e) ->
|
||||||
@ -178,6 +179,11 @@ $.off = (el, events, handler) ->
|
|||||||
el.removeEventListener event, handler, false
|
el.removeEventListener event, handler, false
|
||||||
return
|
return
|
||||||
|
|
||||||
|
$.one = (el, events, handler) ->
|
||||||
|
cb = (e) ->
|
||||||
|
$.off el, events, cb
|
||||||
|
handler.call @, e
|
||||||
|
$.on el, events, cb
|
||||||
$.event = (event, detail, root=d) ->
|
$.event = (event, detail, root=d) ->
|
||||||
<% if (type === 'userscript') { %>
|
<% if (type === 'userscript') { %>
|
||||||
if detail? and typeof cloneInto is 'function'
|
if detail? and typeof cloneInto is 'function'
|
||||||
@ -270,17 +276,7 @@ $.item = (key, val) ->
|
|||||||
$.syncing = {}
|
$.syncing = {}
|
||||||
|
|
||||||
<% if (type === 'crx') { %>
|
<% if (type === 'crx') { %>
|
||||||
$.sync = do ->
|
# https://developer.chrome.com/extensions/storage.html
|
||||||
chrome.storage.onChanged.addListener (changes) ->
|
|
||||||
for key of changes
|
|
||||||
if cb = $.syncing[key]
|
|
||||||
cb changes[key].newValue, key
|
|
||||||
return
|
|
||||||
(key, cb) -> $.syncing[key] = cb
|
|
||||||
|
|
||||||
$.desync = (key) -> delete $.syncing[key]
|
|
||||||
|
|
||||||
# Chrome imposes a strict 4KB limit on synchronized extension data.
|
|
||||||
$.localKeys = [
|
$.localKeys = [
|
||||||
# filters
|
# filters
|
||||||
'name',
|
'name',
|
||||||
@ -302,6 +298,15 @@ $.localKeys = [
|
|||||||
'userThemes'
|
'userThemes'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
chrome.storage.onChanged.addListener (changes) ->
|
||||||
|
cb changes[key].newValue, key for key of changes when cb = $.syncing[key]
|
||||||
|
return
|
||||||
|
|
||||||
|
$.sync = (key, cb) -> $.syncing[key] = cb
|
||||||
|
|
||||||
|
$.forceSync = (key) -> return
|
||||||
|
|
||||||
|
$.desync = (key) -> delete $.syncing[key]
|
||||||
# https://developer.chrome.com/extensions/storage.html
|
# https://developer.chrome.com/extensions/storage.html
|
||||||
do ->
|
do ->
|
||||||
items =
|
items =
|
||||||
@ -400,11 +405,31 @@ do ->
|
|||||||
<% } else { %>
|
<% } else { %>
|
||||||
|
|
||||||
# http://wiki.greasespot.net/Main_Page
|
# http://wiki.greasespot.net/Main_Page
|
||||||
$.sync = do ->
|
$.oldValue = {}
|
||||||
$.on window, 'storage', ({key, newValue}) ->
|
|
||||||
if cb = $.syncing[key]
|
$.sync = (key, cb) ->
|
||||||
|
key = g.NAMESPACE + key
|
||||||
|
$.syncing[key] = cb
|
||||||
|
$.oldValue[key] = GM_getValue key
|
||||||
|
|
||||||
|
do ->
|
||||||
|
onChange = (key) ->
|
||||||
|
return unless cb = $.syncing[key]
|
||||||
|
newValue = GM_getValue key
|
||||||
|
return if newValue is $.oldValue[key]
|
||||||
|
if newValue?
|
||||||
|
$.oldValue[key] = newValue
|
||||||
cb JSON.parse(newValue), key
|
cb JSON.parse(newValue), key
|
||||||
(key, cb) -> $.syncing[g.NAMESPACE + key] = cb
|
else
|
||||||
|
delete $.oldValue[key]
|
||||||
|
cb undefined, key
|
||||||
|
$.on window, 'storage', ({key}) -> onChange key
|
||||||
|
|
||||||
|
$.forceSync = (key) ->
|
||||||
|
# Storage events don't work across origins
|
||||||
|
# e.g. http://boards.4chan.org and https://boards.4chan.org
|
||||||
|
# so force a check for changes to avoid lost data.
|
||||||
|
onChange g.NAMESPACE + key
|
||||||
|
|
||||||
$.desync = (key) -> delete $.syncing[g.NAMESPACE + key]
|
$.desync = (key) -> delete $.syncing[g.NAMESPACE + key]
|
||||||
|
|
||||||
@ -437,6 +462,7 @@ $.set = do ->
|
|||||||
# for `storage` events
|
# for `storage` events
|
||||||
localStorage.setItem key, val
|
localStorage.setItem key, val
|
||||||
GM_setValue key, val
|
GM_setValue key, val
|
||||||
|
|
||||||
(keys, val) ->
|
(keys, val) ->
|
||||||
if typeof keys is 'string'
|
if typeof keys is 'string'
|
||||||
set keys, val
|
set keys, val
|
||||||
|
|||||||
@ -1,112 +1,119 @@
|
|||||||
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'
|
||||||
|
|
||||||
imgContainer = $.el 'div',
|
|
||||||
className: 'captcha-img'
|
|
||||||
title: 'Reload reCAPTCHA'
|
|
||||||
innerHTML: '<div><img></div>'
|
|
||||||
|
|
||||||
input = $.el 'input',
|
|
||||||
className: 'captcha-input field'
|
|
||||||
title: 'Verification'
|
|
||||||
autocomplete: 'off'
|
|
||||||
spellcheck: false
|
|
||||||
tabIndex: 45
|
|
||||||
|
|
||||||
@nodes =
|
|
||||||
img: imgContainer.firstChild.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>' +
|
||||||
|
'<div class="captcha-counter"><a href="javascript:;"></a></div>'
|
||||||
@afterSetup() # reCAPTCHA might have loaded before the QR.
|
) %>
|
||||||
|
container = $ '.captcha-container', section
|
||||||
beforeSetup: ->
|
counter = $ '.captcha-counter > a', section
|
||||||
{img, input} = @nodes
|
@nodes = {container, counter}
|
||||||
img.parentNode.parentNode.hidden = true
|
|
||||||
input.value = ''
|
|
||||||
input.placeholder = 'Focus to load reCAPTCHA'
|
|
||||||
@count()
|
@count()
|
||||||
$.on input, 'focus', @setup
|
$.addClass QR.nodes.el, 'has-captcha'
|
||||||
|
$.after QR.nodes.com.parentNode, section
|
||||||
|
|
||||||
setup: ->
|
new MutationObserver(@afterSetup.bind @).observe container,
|
||||||
$.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
|
|
||||||
$.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.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
|
||||||
|
QR.nodes.el.style.top = null
|
||||||
|
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()
|
|
||||||
if response
|
|
||||||
response = response.trim()
|
|
||||||
# one-word-captcha:
|
|
||||||
# If there's only one word, duplicate it.
|
|
||||||
response = "#{response} #{response}" unless /\s|^\d+$/.test response
|
|
||||||
{challenge, response}
|
|
||||||
|
|
||||||
save: ->
|
save: (e) -> try
|
||||||
return unless response = @nodes.input.value.trim()
|
if @needed()
|
||||||
@nodes.input.value = ''
|
@shouldFocus = true
|
||||||
|
@reload()
|
||||||
|
else
|
||||||
|
@nodes.counter.focus()
|
||||||
|
@timeouts.destroy ?= setTimeout @destroy.bind(@), 3 * $.SECOND
|
||||||
|
console.log e.detail
|
||||||
|
$.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
|
||||||
|
catch err
|
||||||
|
console.log err
|
||||||
|
|
||||||
clear: ->
|
clear: ->
|
||||||
return unless @captchas.length
|
return unless @captchas.length
|
||||||
@ -117,43 +124,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()
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
QR =
|
QR =
|
||||||
mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash', 'video/webm']
|
mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/vnd.adobe.flash.movie', 'application/x-shockwave-flash', 'video/webm']
|
||||||
|
|
||||||
init: ->
|
init: ->
|
||||||
@db = new DataBoard 'yourPosts'
|
@db = new DataBoard 'yourPosts'
|
||||||
@posts = []
|
@posts = []
|
||||||
@ -80,6 +80,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()
|
||||||
@ -97,8 +98,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
|
||||||
@ -106,9 +105,8 @@ QR =
|
|||||||
post.delete()
|
post.delete()
|
||||||
QR.cooldown.auto = false
|
QR.cooldown.auto = false
|
||||||
QR.status()
|
QR.status()
|
||||||
|
QR.captcha.destroy()
|
||||||
|
|
||||||
if QR.captcha.isEnabled and not Conf['Auto-load captcha']
|
|
||||||
QR.captcha.destroy()
|
|
||||||
focusin: ->
|
focusin: ->
|
||||||
$.addClass QR.nodes.el, 'focus'
|
$.addClass QR.nodes.el, 'focus'
|
||||||
|
|
||||||
@ -138,18 +136,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) ->
|
||||||
@ -183,7 +171,7 @@ QR =
|
|||||||
return unless QR.nodes
|
return unless QR.nodes
|
||||||
{thread} = QR.posts[0]
|
{thread} = QR.posts[0]
|
||||||
if thread isnt 'new' and g.threads["#{g.BOARD}.#{thread}"].isDead
|
if thread isnt 'new' and g.threads["#{g.BOARD}.#{thread}"].isDead
|
||||||
value = 404
|
value = 'Dead'
|
||||||
disabled = true
|
disabled = true
|
||||||
QR.cooldown.auto = false
|
QR.cooldown.auto = false
|
||||||
|
|
||||||
@ -212,20 +200,26 @@ QR =
|
|||||||
range = sel.getRangeAt 0
|
range = sel.getRangeAt 0
|
||||||
frag = range.cloneContents()
|
frag = range.cloneContents()
|
||||||
ancestor = range.commonAncestorContainer
|
ancestor = range.commonAncestorContainer
|
||||||
if ancestor.nodeName is '#text'
|
# Quoting the insides of a spoiler/code tag.
|
||||||
# Quoting the insides of a spoiler/code tag.
|
if $.x 'ancestor-or-self::*[self::s or contains(@class,"removed-spoiler")]', ancestor
|
||||||
if $.x 'ancestor::s', ancestor
|
$.prepend frag, $.tn '[spoiler]'
|
||||||
$.prepend frag, $.tn '[spoiler]'
|
$.add frag, $.tn '[/spoiler]'
|
||||||
$.add frag, $.tn '[/spoiler]'
|
if insideCode = $.x 'ancestor-or-self::pre[contains(@class,"prettyprint")]', ancestor
|
||||||
if $.x 'ancestor::pre[contains(@class,"prettyprint")]', ancestor
|
$.prepend frag, $.tn '[code]'
|
||||||
$.prepend frag, $.tn '[code]'
|
$.add frag, $.tn '[/code]'
|
||||||
$.add frag, $.tn '[/code]'
|
for node in $$ (if insideCode then 'br' else '.prettyprint br'), frag
|
||||||
|
$.replace node, $.tn '\n'
|
||||||
for node in $$ 'br', frag
|
for node in $$ 'br', frag
|
||||||
$.replace node, $.tn '\n>' unless node is frag.lastChild
|
$.replace node, $.tn '\n>' unless node is frag.lastChild
|
||||||
for node in $$ 's', frag
|
for node in $$ 's, .removed-spoiler', frag
|
||||||
$.replace node, [$.tn('[spoiler]'), node.childNodes..., $.tn '[/spoiler]']
|
$.replace node, [$.tn('[spoiler]'), node.childNodes..., $.tn '[/spoiler]']
|
||||||
for node in $$ '.prettyprint', frag
|
for node in $$ '.prettyprint', frag
|
||||||
$.replace node, [$.tn('[code]'), node.childNodes..., $.tn '[/code]']
|
$.replace node, [$.tn('[code]'), node.childNodes..., $.tn '[/code]']
|
||||||
|
for node in $$ '.linkify[data-original]', frag
|
||||||
|
$.replace node, $.tn node.dataset.original
|
||||||
|
for node in $$ '.embedder', frag
|
||||||
|
$.rm node.previousSibling if node.previousSibling?.nodeValue is ' '
|
||||||
|
$.rm node
|
||||||
text += ">#{frag.textContent.trim()}\n"
|
text += ">#{frag.textContent.trim()}\n"
|
||||||
|
|
||||||
QR.open()
|
QR.open()
|
||||||
@ -403,7 +397,7 @@ QR =
|
|||||||
$.rmAll list
|
$.rmAll list
|
||||||
$.add list, options
|
$.add list, options
|
||||||
list.value = val
|
list.value = val
|
||||||
return unless list.value
|
return if list.value
|
||||||
# Fix the value if the option disappeared.
|
# Fix the value if the option disappeared.
|
||||||
list.value = if g.VIEW is 'thread'
|
list.value = if g.VIEW is 'thread'
|
||||||
g.THREADID
|
g.THREADID
|
||||||
@ -544,6 +538,7 @@ QR =
|
|||||||
Rice.nodes dialog
|
Rice.nodes dialog
|
||||||
|
|
||||||
$.add d.body, dialog
|
$.add d.body, dialog
|
||||||
|
QR.captcha.setup()
|
||||||
|
|
||||||
if Conf['Auto Hide QR']
|
if Conf['Auto Hide QR']
|
||||||
nodes.autohide.click()
|
nodes.autohide.click()
|
||||||
@ -622,7 +617,7 @@ QR =
|
|||||||
|
|
||||||
post = QR.posts[0]
|
post = QR.posts[0]
|
||||||
post.forceSave()
|
post.forceSave()
|
||||||
if g.BOARD.ID is 'f'
|
if g.BOARD.ID is 'f' and g.VIEW isnt 'thread'
|
||||||
filetag = QR.nodes.flashTag.value
|
filetag = QR.nodes.flashTag.value
|
||||||
threadID = post.thread
|
threadID = post.thread
|
||||||
thread = g.BOARD.threads[threadID]
|
thread = g.BOARD.threads[threadID]
|
||||||
@ -642,7 +637,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()
|
||||||
@ -676,8 +671,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'
|
||||||
@ -813,7 +807,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 ->
|
||||||
@ -823,9 +816,8 @@ QR =
|
|||||||
unless Conf['Persistent QR'] or QR.cooldown.auto
|
unless Conf['Persistent QR'] or QR.cooldown.auto
|
||||||
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.set {req, post, isReply, threadID}
|
QR.cooldown.set {req, post, isReply, threadID}
|
||||||
|
|
||||||
|
|||||||
@ -37,6 +37,7 @@ QR.post = class
|
|||||||
|
|
||||||
[..., prev] = QR.posts
|
[..., prev] = QR.posts
|
||||||
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
|
||||||
@ -135,6 +136,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.
|
||||||
@ -163,6 +165,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()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user