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,26 +152,25 @@ 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' else if hasFile
else if hasFile 'file'
'file' else
else 'post'
'post'
cooldown = cooldown =
isReply: isReply isReply: isReply
isSage: isSage isSage: isSage
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.nodes.thread.value isnt 'new'
QR.threadSelector.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,15 +205,14 @@ 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' else if hasFile and cooldown.hasFile
else if hasFile and cooldown.hasFile 'file'
'file' else
else 'post'
'post'
elapsed = Math.floor (now - start) / 1000 elapsed = Math.floor (now - start) / 1000
if elapsed >= 0 # clock changed since then? if elapsed >= 0 # clock changed since then?
seconds = Math.max seconds, types[type] - elapsed seconds = Math.max seconds, types[type] - elapsed
@ -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 nodes.autohide, 'change', QR.toggleHide
$.on $('#autohide', QR.el), 'change', QR.toggleHide $.on nodes.close, 'click', QR.close
$.on $('.close', QR.el), 'click', QR.close $.on nodes.dump, 'click', -> QR.nodes.el.classList.toggle 'dump'
$.on $('#dump', QR.el), 'click', -> QR.el.classList.toggle 'dump' $.on nodes.addReply, 'click', -> new QR.reply().select()
$.on $('#addReply', QR.el), 'click', -> new QR.reply().select() $.on nodes.form, 'submit', QR.submit
$.on $('form', QR.el), 'submit', QR.submit $.on nodes.fileInput, 'change', QR.fileInput
$.on ta, 'input', -> QR.selected.el.lastChild.textContent = @value $.on nodes.fileInput, 'click', (e) -> if e.shiftKey then QR.selected.rmFile(); e.preventDefault()
$.on ta, 'input', QR.characterCount $.on nodes.spoiler, 'change', -> $('input', QR.selected.el).click()
$.on fileInput, 'change', QR.fileInput
$.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'
threadID = 'new' filetag = QR.nodes.flashTag.value
threadID = 'new'
else
threadID = g.THREAD
else else
threadID = QR.threadSelector.value 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,15 +803,14 @@ 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 else
else # Something must've gone terribly wrong if you get captcha errors without captchas.
# Something must've gone terribly wrong if you get captcha errors without captchas. # Don't auto-post indefinitely in that case.
# Don't auto-post indefinitely in that case. false
false
# Too many frequent mistyped captchas will auto-ban you! # Too many frequent mistyped captchas will auto-ban you!
# On connection error, the post most likely didn't go through. # On connection error, the post most likely didn't go through.
QR.cooldown.set delay: 2 QR.cooldown.set delay: 2
@ -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