Some cleaning in the QR code.

Use toBlob method when available (only Firefox supports it for now).
Fix %MD5 sauces.
This commit is contained in:
Nicolas Stepien 2013-02-24 05:09:16 +01:00
parent ffa24e9d5f
commit 38d90627c6
4 changed files with 473 additions and 418 deletions

File diff suppressed because one or more lines are too long

View File

@ -593,7 +593,7 @@ a[href="javascript:;"] {
min-height: 160px; min-height: 160px;
min-width: 100%; min-width: 100%;
} }
#qr.captcha textarea.field { #qr.has-captcha textarea.field {
min-height: 120px; min-height: 120px;
} }
.textarea { .textarea {

View File

@ -1466,7 +1466,7 @@ Keybinds =
else if (notifications = $$ '.notification').length else if (notifications = $$ '.notification').length
for notification in notifications for notification in notifications
$('.close', notification).click() $('.close', notification).click()
else if QR.el else if QR.nodes
QR.close() QR.close()
when Conf['Spoiler tags'] when Conf['Spoiler tags']
return if target.nodeName isnt 'TEXTAREA' return if target.nodeName isnt 'TEXTAREA'
@ -1478,7 +1478,7 @@ Keybinds =
return if target.nodeName isnt 'TEXTAREA' return if target.nodeName isnt 'TEXTAREA'
Keybinds.tags 'math', target Keybinds.tags 'math', target
when Conf['Submit QR'] when Conf['Submit QR']
QR.submit() if QR.el and !QR.status() QR.submit() if QR.nodes and !QR.status()
# Thread related # Thread related
when Conf['Watch'] when Conf['Watch']
ThreadWatcher.toggle thread ThreadWatcher.toggle thread
@ -1558,7 +1558,7 @@ Keybinds =
QR.open() QR.open()
if quote if quote
QR.quote.call $ 'input', $('.post.highlight', thread) or thread QR.quote.call $ 'input', $('.post.highlight', thread) or thread
$('textarea', QR.el).focus() QR.nodes.com.focus()
tags: (tag, ta) -> tags: (tag, ta) ->
value = ta.value value = ta.value
@ -2880,7 +2880,7 @@ Sauce =
name: 'Sauce' name: 'Sauce'
cb: @node cb: @node
createSauceLink: (link) -> createSauceLink: (link) ->
link = link.replace /%(t?url|md5|board)/g, (parameter) -> link = link.replace /%(t?url|MD5|board)/g, (parameter) ->
switch parameter switch parameter
when '%turl' when '%turl'
"' + post.file.thumbURL + '" "' + post.file.thumbURL + '"

View File

@ -15,14 +15,8 @@ QR =
$.on link, 'click', -> $.on link, 'click', ->
$.event 'CloseMenu' $.event 'CloseMenu'
QR.open() QR.open()
if g.BOARD.ID is 'f' QR.resetThreadSelector()
if g.VIEW is 'index' QR.nodes.com.focus()
QR.threadSelector.value = '9999'
else if g.VIEW is 'thread'
QR.threadSelector.value = g.THREAD
else
QR.threadSelector.value = 'new'
$('textarea', QR.el).focus()
$.event 'AddMenuEntry', $.event 'AddMenuEntry',
type: 'header' type: 'header'
el: link el: link
@ -49,37 +43,37 @@ QR =
QR.open() QR.open()
QR.hide() if Conf['Auto Hide QR'] QR.hide() if Conf['Auto Hide QR']
open: -> open: ->
if QR.el if QR.nodes
QR.el.hidden = false QR.nodes.el.hidden = false
QR.unhide() QR.unhide()
return return
try try
QR.dialog() QR.dialog()
catch err catch err
delete QR.el delete QR.nodes
Main.handleErrors Main.handleErrors
message: 'Quick Reply dialog creation crashed.' message: 'Quick Reply dialog creation crashed.'
error: err error: err
close: -> close: ->
QR.el.hidden = true QR.nodes.el.hidden = true
QR.abort() QR.abort()
d.activeElement.blur() d.activeElement.blur()
$.rmClass QR.el, 'dump' $.rmClass QR.nodes.el, 'dump'
for i in QR.replies for i in QR.replies
QR.replies[0].rm() QR.replies[0].rm()
QR.cooldown.auto = false QR.cooldown.auto = false
QR.status() QR.status()
QR.resetFileInput() QR.resetFileInput()
if not Conf['Remember Spoiler'] and (spoiler = $.id 'spoiler').checked if !Conf['Remember Spoiler'] and QR.nodes.spoiler.checked
spoiler.click() QR.nodes.spoiler.click()
QR.cleanNotifications() QR.cleanNotifications()
hide: -> hide: ->
d.activeElement.blur() d.activeElement.blur()
$.addClass QR.el, 'autohide' $.addClass QR.nodes.el, 'autohide'
$.id('autohide').checked = true QR.nodes.autohide.checked = true
unhide: -> unhide: ->
$.rmClass QR.el, 'autohide' $.rmClass QR.nodes.el, 'autohide'
$.id('autohide').checked = false QR.nodes.autohide.checked = false
toggleHide: -> toggleHide: ->
if @checked if @checked
QR.hide() QR.hide()
@ -102,29 +96,29 @@ QR =
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
# Focus the captcha input on captcha error. # Focus the captcha input on captcha error.
$('[autocomplete]', QR.el).focus() QR.captcha.nodes.input.focus()
alert el.textContent if d.hidden alert el.textContent if d.hidden
QR.lastNotifications.push new Notification 'warning', el QR.notifications.push new Notification 'warning', el
lastNotifications: [] notifications: []
cleanNotifications: -> cleanNotifications: ->
for notification in QR.lastNotifications for notification in QR.notifications
notification.close() notification.close()
QR.lastNotification = [] QR.notifications = []
status: (data={}) -> status: (data={}) ->
return unless QR.el return unless QR.nodes
if g.DEAD if g.DEAD
value = 404 value = 404
disabled = true disabled = true
QR.cooldown.auto = false QR.cooldown.auto = false
value = data.progress or QR.cooldown.seconds or value value = data.progress or QR.cooldown.seconds or value
{input} = QR.status {status} = QR.nodes
input.value = status.value =
if QR.cooldown.auto if QR.cooldown.auto
if value then "Auto #{value}" else 'Auto' if value then "Auto #{value}" else 'Auto'
else else
value or 'Submit' value or 'Submit'
input.disabled = disabled or false status.disabled = disabled or false
cooldown: cooldown:
init: -> init: ->
@ -137,9 +131,9 @@ QR =
sage: if board is 'q' then 600 else 60 sage: if board is 'q' then 600 else 60
file: if board is 'q' then 300 else 30 file: if board is 'q' then 300 else 30
post: if board is 'q' then 60 else 30 post: if board is 'q' then 60 else 30
QR.cooldown.cooldowns = $.get "#{board}.cooldown", {} QR.cooldown.cooldowns = $.get "cooldown.#{board}", {}
QR.cooldown.start() QR.cooldown.start()
$.sync "#{board}.cooldown", QR.cooldown.sync $.sync "cooldown.#{board}", QR.cooldown.sync
start: -> start: ->
return if QR.cooldown.isCounting return if QR.cooldown.isCounting
QR.cooldown.isCounting = true QR.cooldown.isCounting = true
@ -158,8 +152,7 @@ QR =
isSage = /sage/i.test data.post.email isSage = /sage/i.test data.post.email
hasFile = !!data.post.file hasFile = !!data.post.file
isReply = data.isReply isReply = data.isReply
type = type = unless isReply
unless isReply
'thread' 'thread'
else if isSage else if isSage
'sage' 'sage'
@ -173,11 +166,11 @@ QR =
hasFile: hasFile hasFile: hasFile
timeout: start + QR.cooldown.types[type] * $.SECOND timeout: start + QR.cooldown.types[type] * $.SECOND
QR.cooldown.cooldowns[start] = cooldown QR.cooldown.cooldowns[start] = cooldown
$.set "#{g.BOARD}.cooldown", QR.cooldown.cooldowns $.set "cooldown.#{g.BOARD}", QR.cooldown.cooldowns
QR.cooldown.start() QR.cooldown.start()
unset: (id) -> unset: (id) ->
delete QR.cooldown.cooldowns[id] delete QR.cooldown.cooldowns[id]
$.set "#{g.BOARD}.cooldown", QR.cooldown.cooldowns $.set "cooldown.#{g.BOARD}", QR.cooldown.cooldowns
count: -> count: ->
if Object.keys(QR.cooldown.cooldowns).length if Object.keys(QR.cooldown.cooldowns).length
setTimeout QR.cooldown.count, 1000 setTimeout QR.cooldown.count, 1000
@ -188,11 +181,10 @@ QR =
QR.status() QR.status()
return return
isReply = isReply = if g.BOARD.ID is 'f'
if g.BOARD.ID is 'f' and g.VIEW is 'thread' g.VIEW is 'thread'
true
else else
QR.threadSelector.value isnt 'new' QR.nodes.thread.value isnt 'new'
if isReply if isReply
post = QR.replies[0] post = QR.replies[0]
isSage = /sage/i.test post.email isSage = /sage/i.test post.email
@ -213,8 +205,7 @@ QR =
if isReply is cooldown.isReply if isReply is cooldown.isReply
# Only cooldowns relevant to this post can set the seconds value. # Only cooldowns relevant to this post can set the seconds value.
# Unset outdated cooldowns that can no longer impact us. # Unset outdated cooldowns that can no longer impact us.
type = type = unless isReply
unless isReply
'thread' 'thread'
else if isSage and cooldown.isSage else if isSage and cooldown.isSage
'sage' 'sage'
@ -243,23 +234,23 @@ QR =
sel = d.getSelection() sel = d.getSelection()
selectionRoot = $.x 'ancestor::div[contains(@class,"postContainer")][1]', sel.anchorNode selectionRoot = $.x 'ancestor::div[contains(@class,"postContainer")][1]', sel.anchorNode
post = Get.postFromNode @ post = Get.postFromNode @
thread = g.BOARD.posts[Get.contextFromLink(@).thread] {OP} = Get.contextFromLink(@).thread
if (s = sel.toString().trim()) and post.nodes.root is selectionRoot if (s = sel.toString().trim()) and post.nodes.root is selectionRoot
# XXX Opera doesn't retain `\n`s? # XXX Opera doesn't retain `\n`s?
s = s.replace /\n/g, '\n>' s = s.replace /\n/g, '\n>'
text += ">#{s}\n" text += ">#{s}\n"
text = if !text and post is thread and (!QR.el or QR.el.hidden) text = if !text and post is OP and (!QR.nodes or QR.nodes.el.hidden)
# Don't quote the OP unless the QR was already opened once. # Don't quote the OP unless the QR was already opened once.
"" ""
else else
">>#{post}\n#{text}" ">>#{post}\n#{text}"
QR.open() QR.open()
ta = $ 'textarea', QR.el ta = QR.nodes.com
if QR.threadSelector and !ta.value and g.BOARD.ID isnt 'f' if QR.threadSelector and !ta.value and g.BOARD.ID isnt 'f'
QR.threadSelector.value = thread.ID QR.threadSelector.value = OP.ID
caretPos = ta.selectionStart caretPos = ta.selectionStart
# Replace selection for text. # Replace selection for text.
@ -270,11 +261,11 @@ QR =
ta.focus() ta.focus()
# Fire the 'input' event # Fire the 'input' event
ta.dispatchEvent new Event 'input' $.event 'input', null, ta
characterCount: -> characterCount: ->
counter = QR.charaCounter counter = QR.nodes.charCount
count = @textLength count = QR.nodes.com.textLength
counter.textContent = count counter.textContent = count
counter.hidden = count < 1000 counter.hidden = count < 1000
(if count > 1500 then $.addClass else $.rmClass) counter, 'warning' (if count > 1500 then $.addClass else $.rmClass) counter, 'warning'
@ -292,15 +283,20 @@ QR =
return unless e.dataTransfer.files.length return unless e.dataTransfer.files.length
e.preventDefault() e.preventDefault()
QR.open() QR.open()
QR.fileInput.call e.dataTransfer QR.fileInput e.dataTransfer.files
$.addClass QR.el, 'dump' $.addClass QR.nodes.el, 'dump'
fileInput: -> fileInput: (files) ->
unless files instanceof FileList
files = @files
{length} = files
return unless length
max = QR.nodes.fileInput.max
QR.cleanNotifications() QR.cleanNotifications()
# Set or change current reply's file. # Set or change current reply's file.
if @files.length is 1 if length is 1
file = @files[0] file = files[0]
if file.size > @max if file.size > max
QR.error "File too large (file: #{$.bytesToString file.size}, max: #{$.bytesToString @max})." QR.error "File too large (file: #{$.bytesToString file.size}, max: #{$.bytesToString max})."
QR.resetFileInput() QR.resetFileInput()
else unless file.type in QR.mimeTypes else unless file.type in QR.mimeTypes
QR.error 'Unsupported file type.' QR.error 'Unsupported file type.'
@ -309,9 +305,9 @@ QR =
QR.selected.setFile file QR.selected.setFile file
return return
# Create new replies with these files. # Create new replies with these files.
for file in @files for file in files
if file.size > @max if file.size > max
QR.error "File #{file.name} is too large (file: #{$.bytesToString file.size}, max: #{$.bytesToString @max})." QR.error "#{file.name}: File too large (file: #{$.bytesToString file.size}, max: #{$.bytesToString max})."
else unless file.type in QR.mimeTypes else unless file.type in QR.mimeTypes
QR.error "#{file.name}: Unsupported file type." QR.error "#{file.name}: Unsupported file type."
unless QR.replies[QR.replies.length - 1].file unless QR.replies[QR.replies.length - 1].file
@ -319,16 +315,24 @@ QR =
QR.replies[QR.replies.length - 1].setFile file QR.replies[QR.replies.length - 1].setFile file
else else
new QR.reply().setFile file new QR.reply().setFile file
$.addClass QR.el, 'dump' $.addClass QR.nodes.el, 'dump'
QR.resetFileInput() # reset input QR.resetFileInput() # reset input
resetFileInput: -> resetFileInput: ->
$('[type=file]', QR.el).value = null QR.nodes.fileInput.value = null
resetThreadSelector: ->
if g.BOARD.ID is 'f'
if g.VIEW is 'index'
QR.nodes.flashTag.value = '9999'
else if g.VIEW is 'thread'
QR.nodes.thread.value = g.THREAD
else
QR.nodes.thread.value = 'new'
replies: [] replies: []
reply: class reply: class
constructor: -> constructor: ->
# set values, or null, to avoid 'undefined' values in inputs # set values, or null, to avoid 'undefined' values in inputs
prev = QR.replies[QR.replies.length-1] prev = QR.replies[QR.replies.length - 1]
persona = $.get 'QR.persona', {} persona = $.get 'QR.persona', {}
@name = if prev then prev.name else persona.name or null @name = if prev then prev.name else persona.name or null
@email = if prev and !/^sage$/.test prev.email then prev.email else persona.email or null @email = if prev and !/^sage$/.test prev.email then prev.email else persona.email or null
@ -336,33 +340,36 @@ QR =
@spoiler = if prev and Conf['Remember Spoiler'] then prev.spoiler else false @spoiler = if prev and Conf['Remember Spoiler'] then prev.spoiler else false
@com = null @com = null
@el = $.el 'a', el = $.el 'a',
className: 'qrpreview' className: 'qrpreview'
draggable: true draggable: true
href: 'javascript:;' href: 'javascript:;'
innerHTML: '<a class=remove>×</a><label hidden><input type=checkbox> Spoiler</label><span></span>' innerHTML: '<a class=remove>×</a><label hidden><input type=checkbox> Spoiler</label><span></span>'
$('input', @el).checked = @spoiler
$.on @el, 'click', => @select()
$.on $('.remove', @el), 'click', (e) =>
e.stopPropagation()
@rm()
$.on $('label', @el), 'click', (e) => e.stopPropagation()
$.on $('input', @el), 'change', (e) =>
@spoiler = e.target.checked
$.id('spoiler').checked = @spoiler if @el.id is 'selected'
$.before $('#addReply', QR.el), @el
$.on @el, 'dragstart', @dragStart @nodes =
$.on @el, 'dragenter', @dragEnter el: el
$.on @el, 'dragleave', @dragLeave rm: el.firstChild
$.on @el, 'dragover', @dragOver label: $ 'label', el
$.on @el, 'dragend', @dragEnd spoiler: $ 'input', el
$.on @el, 'drop', @drop span: el.lastChild
@nodes.spoiler.checked = @spoiler
$.on el, 'click', @select.bind @
$.on @nodes.rm, 'click', (e) => e.stopPropagation(); @rm()
$.on @nodes.label, 'click', (e) => e.stopPropagation()
$.on @nodes.spoiler, 'change', (e) =>
@spoiler = e.target.checked
QR.nodes.spoiler.checked = @spoiler if @ is QR.selected
$.before QR.nodes.addReply, el
for event in ['dragStart', 'dragEnter', 'dragLeave', 'dragOver', 'dragEnd', 'drop']
$.on el, event.toLowerCase(), @[event]
QR.replies.push @ QR.replies.push @
setFile: (@file) -> setFile: (@file) ->
@el.title = "#{file.name} (#{$.bytesToString file.size})" @nodes.el.title = "#{file.name} (#{$.bytesToString file.size})"
$('label', @el).hidden = false if QR.spoiler @nodes.label.hidden = false if QR.spoiler
unless /^image/.test file.type unless /^image/.test file.type
@el.style.backgroundImage = null @el.style.backgroundImage = null
return return
@ -382,7 +389,7 @@ QR =
s = 90*3 s = 90*3
if img.height < s or img.width < s if img.height < s or img.width < s
@url = fileURL @url = fileURL
@el.style.backgroundImage = "url(#{@url})" @nodes.el.style.backgroundImage = "url(#{@url})"
return return
if img.height <= img.width if img.height <= img.width
img.width = s / img.height * img.width img.width = s / img.height * img.width
@ -394,7 +401,13 @@ QR =
c.height = img.height c.height = img.height
c.width = img.width c.width = img.width
c.getContext('2d').drawImage img, 0, 0, img.width, img.height c.getContext('2d').drawImage img, 0, 0, img.width, img.height
# Support for toBlob fucking when? applyBlob = (blob) =>
@url = URL.createObjectURL blob
@nodes.el.style.backgroundImage = "url(#{@url})"
URL.revokeObjectURL fileURL
if c.toBlob
c.toBlob applyBlob
return
data = atob c.toDataURL().split(',')[1] data = atob c.toDataURL().split(',')[1]
# DataUrl to Binary code from Aeosynth's 4chan X repo # DataUrl to Binary code from Aeosynth's 4chan X repo
@ -403,39 +416,47 @@ QR =
for i in [0...l] for i in [0...l]
ui8a[i] = data.charCodeAt i ui8a[i] = data.charCodeAt i
@url = URL.createObjectURL new Blob [ui8a], type: 'image/png' applyBlob new Blob [ui8a], type: 'image/png'
@el.style.backgroundImage = "url(#{@url})"
URL.revokeObjectURL fileURL
img.src = fileURL img.src = fileURL
rmFile: -> rmFile: ->
QR.resetFileInput() QR.resetFileInput()
delete @file delete @file
@el.title = null @nodes.el.title = null
@el.style.backgroundImage = null @nodes.el.style.backgroundImage = null
$('label', @el).hidden = true if QR.spoiler @nodes.label.hidden = true if QR.spoiler
return unless window.URL return unless window.URL
URL.revokeObjectURL @url URL.revokeObjectURL @url
select: -> select: ->
if QR.selected if QR.selected
QR.selected.el.id = null QR.selected.nodes.el.id = null
QR.selected.forceSave() QR.selected.forceSave()
QR.selected = @ QR.selected = @
@el.id = 'selected' @nodes.el.id = 'selected'
# Scroll the list to center the focused reply. # Scroll the list to center the focused reply.
rectEl = @el.getBoundingClientRect() rectEl = @nodes.el.getBoundingClientRect()
rectList = @el.parentNode.getBoundingClientRect() rectList = @nodes.el.parentNode.getBoundingClientRect()
@el.parentNode.scrollLeft += rectEl.left + rectEl.width/2 - rectList.left - rectList.width/2 @nodes.el.parentNode.scrollLeft += rectEl.left + rectEl.width/2 - rectList.left - rectList.width/2
# Load this reply's values. # Load this reply's values.
for name in ['name', 'email', 'sub', 'com'] for name in ['name', 'email', 'sub', 'com']
$("[name=#{name}]", QR.el).value = @[name] QR.nodes[name].value = @[name]
QR.characterCount.call $ 'textarea', QR.el QR.characterCount()
$('#spoiler', QR.el).checked = @spoiler QR.nodes.spoiler.checked = @spoiler
save: (input) ->
{value} = input
@[input.name] = value
return if input.nodeName isnt 'TEXTAREA'
@nodes.span.textContent = value
QR.characterCount()
# Disable auto-posting if you're typing in the first reply
# during the last 5 seconds of the cooldown.
if QR.cooldown.auto and @ is QR.replies[0] and 0 < QR.cooldown.seconds <= 5
QR.cooldown.auto = false
forceSave: -> forceSave: ->
# Do this in case people use extensions # Do this in case people use extensions
# that do not trigger the `input` event. # that do not trigger the `input` event.
for name in ['name', 'email', 'sub', 'com'] for name in ['name', 'email', 'sub', 'com']
@[name] = $("[name=#{name}]", QR.el).value @save QR.nodes[name]
return return
dragStart: -> dragStart: ->
$.addClass @, 'drag' $.addClass @, 'drag'
@ -463,7 +484,7 @@ QR =
$.rmClass el, 'over' $.rmClass el, 'over'
rm: -> rm: ->
QR.resetFileInput() QR.resetFileInput()
$.rm @el $.rm @nodes.el
index = QR.replies.indexOf @ index = QR.replies.indexOf @
if QR.replies.length is 1 if QR.replies.length is 1
new QR.reply().select() new QR.reply().select()
@ -475,84 +496,80 @@ QR =
captcha: captcha:
init: -> init: ->
# XXX CoffeeScrit's indexOf doesn't wanna work here ???
# return if 'pass_enabled=1' in d.cookie
return if d.cookie.indexOf('pass_enabled=1') >= 0 return if d.cookie.indexOf('pass_enabled=1') >= 0
return unless @isEnabled = !!$.id 'captchaFormPart' return unless @isEnabled = !!$.id 'captchaFormPart'
if $.id 'recaptcha_challenge_field_holder' $.asap (-> $.id 'recaptcha_challenge_field_holder'), @ready.bind @
@ready()
else
@onready = => @ready()
$.on $.id('recaptcha_widget_div'), 'DOMNodeInserted', @onready
ready: -> ready: ->
if @challenge = $.id 'recaptcha_challenge_field_holder' imgContainer = $.el 'div',
$.off $.id('recaptcha_widget_div'), 'DOMNodeInserted', @onready
delete @onready
else
return
$.addClass QR.el, 'captcha'
$.after $('.textarea', QR.el), $.el 'div',
className: 'captchaimg' className: 'captchaimg'
title: 'Reload' title: 'Reload'
innerHTML: '<img>' innerHTML: '<img>'
$.after $('.captchaimg', QR.el), $.el 'div', inputContainer = $.el 'div',
className: 'captchainput' className: 'captchainput'
innerHTML: '<input title=Verification class=field autocomplete=off size=1>' innerHTML: '<input title=Verification class=field autocomplete=off size=1>'
@img = $ '.captchaimg > img', QR.el @nodes =
@input = $ '.captchainput > input', QR.el challenge: $.id 'recaptcha_challenge_field_holder'
$.on @img.parentNode, 'click', @reload imgContainer: imgContainer
$.on @input, 'keydown', @keydown inputContainer: inputContainer
$.on @challenge, 'DOMNodeInserted', => @load() img: imgContainer.firstChild
$.sync 'captchas', (arr) => @count arr.length input: inputContainer.firstChild
@count $.get('captchas', []).length
$.on imgContainer, 'click', @reload.bind @
$.on @nodes.input, 'keydown', @keydown.bind @
$.on @nodes.challenge, 'DOMNodeInserted', @load.bind @
$.sync 'captchas', @count.bind @
@count $.get 'captchas', []
# start with an uncached captcha # start with an uncached captcha
@reload() @reload()
$.addClass QR.nodes.el, 'has-captcha'
$.after QR.nodes.com.parentNode, [imgContainer, inputContainer]
save: -> save: ->
return unless response = @input.value return unless response = @nodes.input.value.trim()
captchas = $.get 'captchas', [] captchas = $.get 'captchas', []
# Remove old captchas. # Remove old captchas.
while (captcha = captchas[0]) and captcha.time < Date.now() while (captcha = captchas[0]) and captcha.time < Date.now()
captchas.shift() captchas.shift()
captchas.push captchas.push
challenge: @challenge.firstChild.value challenge: @nodes.challenge.firstChild.value
response: response response: response
time: @timeout time: @timeout
$.set 'captchas', captchas $.set 'captchas', captchas
@count captchas.length @count captchas
@reload() @reload()
load: -> load: ->
# -1 minute to give upload some time. # -1 minute to give upload some time.
@timeout = Date.now() + $.unsafeWindow.RecaptchaState.timeout * $.SECOND - $.MINUTE @timeout = Date.now() + $.unsafeWindow.RecaptchaState.timeout * $.SECOND - $.MINUTE
challenge = @challenge.firstChild.value challenge = @nodes.challenge.firstChild.value
@img.alt = challenge @nodes.img.alt = challenge
@img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}" @nodes.img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}"
@input.value = null @nodes.input.value = null
count: (count) -> count: (arr) ->
@input.placeholder = switch count count = arr.length
@nodes.input.placeholder = switch count
when 0 when 0
'Verification (Shift + Enter to cache)' 'Verification (Shift + Enter to cache)'
when 1 when 1
'Verification (1 cached captcha)' 'Verification (1 cached captcha)'
else else
"Verification (#{count} cached captchas)" "Verification (#{count} cached captchas)"
@input.alt = count # For XTRM RICE. @nodes.input.alt = count # For XTRM RICE.
reload: (focus) -> reload: (focus) ->
# the 't' argument prevents the input from being focused # the 't' argument prevents the input from being focused
$.unsafeWindow.Recaptcha.reload 't' $.unsafeWindow.Recaptcha.reload 't'
# Focus if we meant to. # Focus if we meant to.
QR.captcha.input.focus() if focus @nodes.input.focus() if focus
keydown: (e) -> keydown: (e) ->
c = QR.captcha if e.keyCode is 8 and not @nodes.input.value
if e.keyCode is 8 and not c.input.value @reload()
c.reload()
else if e.keyCode is 13 and e.shiftKey else if e.keyCode is 13 and e.shiftKey
c.save() @save()
else else
return return
e.preventDefault() e.preventDefault()
dialog: -> dialog: ->
QR.el = UI.dialog 'qr', 'top:0;right:0;', """ dialog = UI.dialog 'qr', 'top:0;right:0;', """
<div class=move>Quick Reply <input type=checkbox id=autohide title=Auto-hide><span> <a href=javascript:; class=close title=Close>×</a></span></div> <div class=move>Quick Reply <input type=checkbox id=autohide title=Auto-hide><span> <a href=javascript:; class=close title=Close>×</a></span></div>
<form> <form>
<div class=persona><input id=dump type=button title='Dump list' value=+><input name=name title=Name placeholder=Name class=field size=1><input name=email title=E-mail placeholder=E-mail class=field size=1><input name=sub title=Subject placeholder=Subject class=field size=1></div> <div class=persona><input id=dump type=button title='Dump list' value=+><input name=name title=Name placeholder=Name class=field size=1><input name=email title=E-mail placeholder=E-mail class=field size=1><input name=sub title=Subject placeholder=Subject class=field size=1></div>
@ -563,8 +580,27 @@ QR =
</form> </form>
""" """
QR.nodes = nodes =
el: dialog
move: $ '.move', dialog
autohide: $ '#autohide', dialog
close: $ '.close', dialog
form: $ 'form', dialog
dump: $ '#dump', dialog
name: $ '[name=name]', dialog
email: $ '[name=email]', dialog
sub: $ '[name=sub]', dialog
com: $ '[name=com]', dialog
replies: $ '#replies', dialog
repliesList: $ '#repliesList', dialog
addReply: $ '#addReply', dialog
charCount: $ '#charCount', dialog
fileInput: $ '[type=file]', dialog
spoiler: $ '#spoiler', dialog
status: $ '[type=submit]', dialog
# Allow only this board's supported files. # Allow only this board's supported files.
mimeTypes = $('ul.rules').firstElementChild.textContent.trim().match(/: (.+)/)[1].toLowerCase().replace /\w+/g, (type) -> mimeTypes = $('ul.rules > li').textContent.trim().match(/: (.+)/)[1].toLowerCase().replace /\w+/g, (type) ->
switch type switch type
when 'jpg' when 'jpg'
'image/jpeg' 'image/jpeg'
@ -577,66 +613,50 @@ QR =
QR.mimeTypes = mimeTypes.split ', ' QR.mimeTypes = mimeTypes.split ', '
# Add empty mimeType to avoid errors with URLs selected in Window's file dialog. # Add empty mimeType to avoid errors with URLs selected in Window's file dialog.
QR.mimeTypes.push '' QR.mimeTypes.push ''
fileInput = $ 'input[type=file]', QR.el nodes.fileInput.max = $('input[name=MAX_FILE_SIZE]').value
fileInput.max = $('input[name=MAX_FILE_SIZE]').value nodes.fileInput.accept = mimeTypes if $.engine isnt 'presto' # Opera's accept attribute is fucked up
fileInput.accept = mimeTypes if $.engine isnt 'presto' # Opera's accept attribute is fucked up
QR.spoiler = !!$ 'input[name=spoiler]' QR.spoiler = !!$ 'input[name=spoiler]'
spoiler = $ '#spoilerLabel', QR.el nodes.spoiler.parentNode.hidden = !QR.spoiler
spoiler.hidden = !QR.spoiler
QR.charaCounter = $ '#charCount', QR.el span = nodes.autohide.nextElementSibling
ta = $ 'textarea', QR.el
span = $('.move > span', QR.el)
# Make a list of visible threads.
if g.BOARD.ID is 'f' if g.BOARD.ID is 'f'
if g.VIEW is 'index' if g.VIEW is 'index'
QR.threadSelector = $('select[name=filetag]').cloneNode true nodes.flashTag = $('select[name=filetag]').cloneNode true
else $.prepend span, nodes.flashTag
QR.threadSelector = $.el 'select', else # Make a list of visible threads.
title: 'Create a new thread / Reply to a thread' nodes.thread = $.el 'select',
title: 'Create a new thread / Reply'
threads = '<option value=new>New thread</option>' threads = '<option value=new>New thread</option>'
for key, thread of g.BOARD.threads for key, thread of g.BOARD.threads
threads += "<option value=#{thread.ID}>Thread No.#{thread.ID}</option>" threads += "<option value=#{thread.ID}>Thread No.#{thread.ID}</option>"
QR.threadSelector.innerHTML = threads nodes.thread.innerHTML = threads
if g.VIEW is 'thread' $.prepend span, nodes.thread
QR.threadSelector.value = g.THREAD QR.resetThreadSelector()
if QR.threadSelector
$.prepend span, QR.threadSelector
$.on span, 'mousedown', (e) -> e.stopPropagation() $.on span, 'mousedown', (e) -> e.stopPropagation()
$.on $('#autohide', QR.el), 'change', QR.toggleHide $.on nodes.autohide, 'change', QR.toggleHide
$.on $('.close', QR.el), 'click', QR.close $.on nodes.close, 'click', QR.close
$.on $('#dump', QR.el), 'click', -> QR.el.classList.toggle 'dump' $.on nodes.dump, 'click', -> QR.nodes.el.classList.toggle 'dump'
$.on $('#addReply', QR.el), 'click', -> new QR.reply().select() $.on nodes.addReply, 'click', -> new QR.reply().select()
$.on $('form', QR.el), 'submit', QR.submit $.on nodes.form, 'submit', QR.submit
$.on ta, 'input', -> QR.selected.el.lastChild.textContent = @value $.on nodes.fileInput, 'change', QR.fileInput
$.on ta, 'input', QR.characterCount $.on nodes.fileInput, 'click', (e) -> if e.shiftKey then QR.selected.rmFile(); e.preventDefault()
$.on fileInput, 'change', QR.fileInput $.on nodes.spoiler, 'change', -> $('input', QR.selected.el).click()
$.on fileInput, 'click', (e) -> if e.shiftKey then QR.selected.rmFile() or e.preventDefault()
$.on spoiler.firstChild, 'change', -> $('input', QR.selected.el).click()
new QR.reply().select() new QR.reply().select()
# save selected reply's data # save selected reply's data
for name in ['name', 'email', 'sub', 'com'] for name in ['name', 'email', 'sub', 'com']
# The input event replaces keyup, change and paste events. $.on nodes[name], 'input', -> QR.selected.save @
$.on $("[name=#{name}]", QR.el), 'input', ->
QR.selected[@name] = @value
# Disable auto-posting if you're typing in the first reply
# during the last 5 seconds of the cooldown.
if QR.cooldown.auto and QR.selected is QR.replies[0] and 0 < QR.cooldown.seconds <= 5
QR.cooldown.auto = false
QR.status.input = $ 'input[type=submit]', QR.el
QR.status() QR.status()
QR.cooldown.init() QR.cooldown.init()
QR.captcha.init() QR.captcha.init()
$.add d.body, QR.el $.add d.body, dialog
# 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.
$.event 'QRDialogCreation', null, QR.el $.event 'QRDialogCreation', null, dialog
submit: (e) -> submit: (e) ->
e?.preventDefault() e?.preventDefault()
@ -651,11 +671,14 @@ QR =
reply = QR.replies[0] reply = QR.replies[0]
reply.forceSave() if reply is QR.selected reply.forceSave() if reply is QR.selected
if g.BOARD.ID is 'f' and g.VIEW is 'index' if g.BOARD.ID is 'f'
filetag = QR.threadSelector.value if g.VIEW is 'index'
filetag = QR.nodes.flashTag.value
threadID = 'new' threadID = 'new'
else else
threadID = QR.threadSelector.value threadID = g.THREAD
else
threadID = QR.nodes.thread.value
# prevent errors # prevent errors
if threadID is 'new' if threadID is 'new'
@ -684,7 +707,7 @@ QR =
challenge = QR.captcha.img.alt challenge = QR.captcha.img.alt
if response = QR.captcha.input.value then QR.captcha.reload() if response = QR.captcha.input.value then QR.captcha.reload()
$.set 'captchas', captchas $.set 'captchas', captchas
QR.captcha.count captchas.length QR.captcha.count captchas
unless response unless response
err = 'No valid captcha.' err = 'No valid captcha.'
else else
@ -780,8 +803,7 @@ QR =
if /mistyped/i.test err.textContent if /mistyped/i.test err.textContent
err = 'Error: You seem to have mistyped the CAPTCHA.' err = 'Error: You seem to have mistyped the CAPTCHA.'
# Enable auto-post if we have some cached captchas. # Enable auto-post if we have some cached captchas.
QR.cooldown.auto = QR.cooldown.auto = if QR.captcha.isEnabled
if QR.captcha.isEnabled
!!$.get('captchas', []).length !!$.get('captchas', []).length
else if err is 'Connection error with sys.4chan.org.' else if err is 'Connection error with sys.4chan.org.'
true true
@ -800,7 +822,7 @@ QR =
h1 = $ 'h1', tmpDoc h1 = $ 'h1', tmpDoc
QR.cleanNotifications() QR.cleanNotifications()
QR.lastNotifications.push new Notification 'success', h1.textContent, 5 QR.notifications.push new Notification 'success', h1.textContent, 5
reply = QR.replies[0] reply = QR.replies[0]
@ -823,7 +845,7 @@ QR =
board: g.BOARD board: g.BOARD
threadID threadID
postID postID
}, QR.el }, QR.nodes.el
QR.cooldown.set QR.cooldown.set
post: reply post: reply