Strengthen QR

* keyup and click event on file div (so unnecessary Mayhem button is
  unnecessary).
* TabIndex on all elements so no mistabs.
* Focus Event on every input so QR will not autohide when focused.
* More love <3
This commit is contained in:
Zixaphir 2013-04-11 12:42:30 -07:00
parent d86e7545dd
commit 419f6cff5d
4 changed files with 78 additions and 18 deletions

File diff suppressed because one or more lines are too long

View File

@ -1097,7 +1097,7 @@ unless _conf['Post Form Style'] is 'tabbed slideout'
unless _conf['Post Form Style'] is 'float' or _conf['Show Post Form Header'] unless _conf['Post Form Style'] is 'float' or _conf['Show Post Form Header']
"#qrtab { display: none; }" "#qrtab { display: none; }"
else unless _conf['Post Form Style'] is 'slideout' else unless _conf['Post Form Style'] is 'slideout'
".autohide:not(:hover) > form { display: none !important; }" ".autohide:not(:hover):not(.focus) > form { display: none !important; }"
else "" else ""
) + "#qrtab { margin-bottom: 1px; }" ) + "#qrtab { margin-bottom: 1px; }"
else ""} else ""}
@ -1253,6 +1253,7 @@ input:not([type=radio]) {
position: absolute; position: absolute;
right: 3px; right: 3px;
top: 2px; top: 2px;
z-index: 2;
} }
/* Thread Select / Spoiler Label */ /* Thread Select / Spoiler Label */
#qr-thread-select { #qr-thread-select {

View File

@ -129,6 +129,7 @@ textarea {
} }
#dump:hover, #dump:hover,
#qr-filename-container:hover, #qr-filename-container:hover,
#qr-filename-container:hover,
.selectrice:hover, .selectrice:hover,
#selectrice li:hover, #selectrice li:hover,
#selectrice li:nth-of-type(2n+1):hover, #selectrice li:nth-of-type(2n+1):hover,
@ -142,12 +143,15 @@ textarea:hover {
#dump:focus, #dump:focus,
#selectrice li:focus, #selectrice li:focus,
.selectrice:focus, .selectrice:focus,
#qr-filename-container:active,
#qr-filename-container:focus,
input:focus, input:focus,
textarea:focus, textarea:focus,
textarea.field:focus { textarea.field:focus {
background: #{theme["Focused Input Background"]}; background: #{theme["Focused Input Background"]};
border-color: #{theme["Focused Input Border"]}; border-color: #{theme["Focused Input Border"]};
color: #{theme["Inputs"]}; color: #{theme["Inputs"]};
outline: none;
} }
#mouseover, #mouseover,
#post-preview, #post-preview,

View File

@ -53,6 +53,7 @@ QR =
persist: -> persist: ->
QR.open() QR.open()
QR.hide() if Conf['Auto-Hide QR'] QR.hide() if Conf['Auto-Hide QR']
open: -> open: ->
if QR.nodes if QR.nodes
QR.nodes.el.hidden = false QR.nodes.el.hidden = false
@ -65,6 +66,7 @@ QR =
Main.handleErrors Main.handleErrors
message: 'Quick Reply dialog creation crashed.' message: 'Quick Reply dialog creation crashed.'
error: err error: err
close: -> close: ->
if QR.req if QR.req
QR.abort() QR.abort()
@ -83,9 +85,11 @@ QR =
d.activeElement.blur() d.activeElement.blur()
$.addClass QR.nodes.el, 'autohide' $.addClass QR.nodes.el, 'autohide'
QR.nodes.autohide.checked = true QR.nodes.autohide.checked = true
unhide: -> unhide: ->
$.rmClass QR.nodes.el, 'autohide' $.rmClass QR.nodes.el, 'autohide'
QR.nodes.autohide.checked = false QR.nodes.autohide.checked = false
toggleHide: -> toggleHide: ->
if @checked if @checked
QR.hide() QR.hide()
@ -104,7 +108,9 @@ QR =
QR.captcha.nodes.input.focus() QR.captcha.nodes.input.focus()
alert el.textContent if d.hidden alert el.textContent if d.hidden
QR.notifications.push new Notification 'warning', el QR.notifications.push new Notification 'warning', el
notifications: [] notifications: []
cleanNotifications: -> cleanNotifications: ->
for notification in QR.notifications for notification in QR.notifications
notification.close() notification.close()
@ -292,9 +298,11 @@ QR =
toggle = if e.type is 'dragstart' then $.off else $.on toggle = if e.type is 'dragstart' then $.off else $.on
toggle d, 'dragover', QR.dragOver toggle d, 'dragover', QR.dragOver
toggle d, 'drop', QR.dropFile toggle d, 'drop', QR.dropFile
dragOver: (e) -> dragOver: (e) ->
e.preventDefault() e.preventDefault()
e.dataTransfer.dropEffect = 'copy' # cursor feedback e.dataTransfer.dropEffect = 'copy' # cursor feedback
dropFile: (e) -> dropFile: (e) ->
# Let it only handle files from the desktop. # Let it only handle files from the desktop.
return unless e.dataTransfer.files.length return unless e.dataTransfer.files.length
@ -302,6 +310,7 @@ QR =
QR.open() QR.open()
QR.fileInput e.dataTransfer.files QR.fileInput e.dataTransfer.files
$.addClass QR.nodes.el, 'dump' $.addClass QR.nodes.el, 'dump'
paste: (e) -> paste: (e) ->
files = [] files = []
for item in e.clipboardData.items for item in e.clipboardData.items
@ -313,8 +322,12 @@ QR =
return unless files.length return unless files.length
QR.open() QR.open()
QR.fileInput files QR.fileInput files
openFileInput: ->
openFileInput: (e) ->
if e.keyCode
return unless e.keyCode is 32
QR.nodes.fileInput.click() QR.nodes.fileInput.click()
fileInput: (files) -> fileInput: (files) ->
if @ instanceof Element #or files instanceof Event # file input if @ instanceof Element #or files instanceof Event # file input
files = [@files...] files = [@files...]
@ -350,6 +363,7 @@ QR =
post = new QR.post() post = new QR.post()
post.setFile file post.setFile file
$.addClass QR.nodes.el, 'dump' $.addClass QR.nodes.el, 'dump'
resetThreadSelector: -> resetThreadSelector: ->
if g.VIEW is 'thread' if g.VIEW is 'thread'
QR.nodes.thread.value = g.THREADID QR.nodes.thread.value = g.THREADID
@ -357,6 +371,7 @@ QR =
QR.nodes.thread.value = 'new' QR.nodes.thread.value = 'new'
posts: [] posts: []
post: class post: class
constructor: (select) -> constructor: (select) ->
el = $.el 'a', el = $.el 'a',
@ -406,6 +421,7 @@ QR =
@load() if QR.selected is @ # load persona @load() if QR.selected is @ # load persona
@select() if select @select() if select
@unlock() @unlock()
rm: -> rm: ->
$.rm @nodes.el $.rm @nodes.el
index = QR.posts.indexOf @ index = QR.posts.indexOf @
@ -416,6 +432,7 @@ QR =
QR.posts.splice index, 1 QR.posts.splice index, 1
return unless window.URL return unless window.URL
URL.revokeObjectURL @URL URL.revokeObjectURL @URL
lock: (lock=true) -> lock: (lock=true) ->
@isLocked = lock @isLocked = lock
return unless @ is QR.selected return unless @ is QR.selected
@ -426,8 +443,10 @@ QR =
(if lock then $.off else $.on) QR.nodes.filename.parentNode, 'click', QR.openFileInput (if lock then $.off else $.on) QR.nodes.filename.parentNode, 'click', QR.openFileInput
@nodes.spoiler.disabled = lock @nodes.spoiler.disabled = lock
@nodes.el.draggable = !lock @nodes.el.draggable = !lock
unlock: -> unlock: ->
@lock false @lock false
select: -> select: ->
if QR.selected if QR.selected
QR.selected.nodes.el.id = null QR.selected.nodes.el.id = null
@ -440,12 +459,14 @@ QR =
rectList = @nodes.el.parentNode.getBoundingClientRect() rectList = @nodes.el.parentNode.getBoundingClientRect()
@nodes.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() @load()
load: -> load: ->
# Load this post's values. # Load this post's values.
for name in ['name', 'email', 'sub', 'com'] for name in ['name', 'email', 'sub', 'com']
QR.nodes[name].value = @[name] or null QR.nodes[name].value = @[name] or null
@showFileData() @showFileData()
QR.characterCount() QR.characterCount()
save: (input) -> save: (input) ->
{value} = input {value} = input
@[input.dataset.name] = value @[input.dataset.name] = value
@ -456,6 +477,7 @@ QR =
# during the last 5 seconds of the cooldown. # during the last 5 seconds of the cooldown.
if QR.cooldown.auto and @ is QR.posts[0] and 0 < QR.cooldown.seconds <= 5 if QR.cooldown.auto and @ is QR.posts[0] and 0 < QR.cooldown.seconds <= 5
QR.cooldown.auto = false QR.cooldown.auto = false
forceSave: -> forceSave: ->
return unless @ is QR.selected return unless @ is QR.selected
# Do this in case people use extensions # Do this in case people use extensions
@ -463,6 +485,7 @@ QR =
for name in ['name', 'email', 'sub', 'com'] for name in ['name', 'email', 'sub', 'com']
@save QR.nodes[name] @save QR.nodes[name]
return return
setFile: (@file) -> setFile: (@file) ->
@filename = "#{file.name} (#{$.bytesToString file.size})" @filename = "#{file.name} (#{$.bytesToString file.size})"
@nodes.el.title = @filename @nodes.el.title = @filename
@ -473,6 +496,7 @@ QR =
@nodes.el.style.backgroundImage = null @nodes.el.style.backgroundImage = null
return return
@setThumbnail() @setThumbnail()
setThumbnail: (fileURL) -> setThumbnail: (fileURL) ->
# XXX Opera does not support blob URL # XXX Opera does not support blob URL
# Create a redimensioned thumbnail. # Create a redimensioned thumbnail.
@ -532,6 +556,7 @@ QR =
applyBlob new Blob [ui8a], type: 'image/png' applyBlob new Blob [ui8a], type: 'image/png'
img.src = fileURL img.src = fileURL
rmFile: -> rmFile: ->
delete @file delete @file
delete @filename delete @filename
@ -541,6 +566,7 @@ QR =
@showFileData() @showFileData()
return unless window.URL return unless window.URL
URL.revokeObjectURL @URL URL.revokeObjectURL @URL
showFileData: (hide) -> showFileData: (hide) ->
if @file if @file
QR.nodes.filename.textContent = @filename QR.nodes.filename.textContent = @filename
@ -549,6 +575,7 @@ QR =
$.addClass QR.nodes.fileSubmit, 'has-file' $.addClass QR.nodes.fileSubmit, 'has-file'
else else
$.rmClass QR.nodes.fileSubmit, 'has-file' $.rmClass QR.nodes.fileSubmit, 'has-file'
pasteText: (file) -> pasteText: (file) ->
reader = new FileReader() reader = new FileReader()
reader.onload = (e) => reader.onload = (e) =>
@ -561,17 +588,23 @@ QR =
QR.nodes.com.value = @com QR.nodes.com.value = @com
@nodes.span.textContent = @com @nodes.span.textContent = @com
reader.readAsText file reader.readAsText file
dragStart: -> dragStart: ->
$.addClass @, 'drag' $.addClass @, 'drag'
dragEnd: -> dragEnd: ->
$.rmClass @, 'drag' $.rmClass @, 'drag'
dragEnter: -> dragEnter: ->
$.addClass @, 'over' $.addClass @, 'over'
dragLeave: -> dragLeave: ->
$.rmClass @, 'over' $.rmClass @, 'over'
dragOver: (e) -> dragOver: (e) ->
e.preventDefault() e.preventDefault()
e.dataTransfer.dropEffect = 'move' e.dataTransfer.dropEffect = 'move'
drop: -> drop: ->
el = $ '.drag', @parentNode el = $ '.drag', @parentNode
$.rmClass el, 'drag' # Opera doesn't fire dragEnd if we drop it on something else $.rmClass el, 'drag' # Opera doesn't fire dragEnd if we drop it on something else
@ -589,6 +622,7 @@ QR =
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'
$.asap (-> $.id 'recaptcha_challenge_field_holder'), @ready.bind @ $.asap (-> $.id 'recaptcha_challenge_field_holder'), @ready.bind @
ready: -> ready: ->
setLifetime = (e) => @lifetime = e.detail setLifetime = (e) => @lifetime = e.detail
$.on window, 'captcha:timeout', setLifetime $.on window, 'captcha:timeout', setLifetime
@ -626,8 +660,10 @@ QR =
$.addClass QR.nodes.el, 'has-captcha' $.addClass QR.nodes.el, 'has-captcha'
$.after QR.nodes.com.parentNode, [imgContainer, input] $.after QR.nodes.com.parentNode, [imgContainer, input]
sync: (@captchas) -> sync: (@captchas) ->
QR.captcha.count() QR.captcha.count()
getOne: -> getOne: ->
@clear() @clear()
if captcha = @captchas.shift() if captcha = @captchas.shift()
@ -643,6 +679,7 @@ QR =
# If there's only one word, duplicate it. # If there's only one word, duplicate it.
response = "#{response} #{response}" unless /\s/.test response response = "#{response} #{response}" unless /\s/.test response
{challenge, response} {challenge, response}
save: -> save: ->
return unless response = @nodes.input.value.trim() return unless response = @nodes.input.value.trim()
@captchas.push @captchas.push
@ -652,6 +689,7 @@ QR =
@count() @count()
@reload() @reload()
$.set 'captchas', @captchas $.set 'captchas', @captchas
clear: -> clear: ->
now = Date.now() now = Date.now()
for captcha, i in @captchas for captcha, i in @captchas
@ -660,6 +698,7 @@ QR =
@captchas = @captchas[i..] @captchas = @captchas[i..]
@count() @count()
$.set 'captchas', @captchas $.set 'captchas', @captchas
load: -> load: ->
return unless @nodes.challenge.firstChild return unless @nodes.challenge.firstChild
# -1 minute to give upload some time. # -1 minute to give upload some time.
@ -669,6 +708,7 @@ QR =
@nodes.img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}" @nodes.img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}"
@nodes.input.value = null @nodes.input.value = null
@clear() @clear()
count: -> count: ->
count = @captchas.length count = @captchas.length
@nodes.input.placeholder = switch count @nodes.input.placeholder = switch count
@ -679,11 +719,13 @@ QR =
else else
"Verification (#{count} cached captchas)" "Verification (#{count} cached captchas)"
@nodes.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
$.globalEval 'Recaptcha.reload("t")' $.globalEval 'Recaptcha.reload("t")'
# Focus if we meant to. # Focus if we meant to.
@nodes.input.focus() if focus @nodes.input.focus() if focus
keydown: (e) -> keydown: (e) ->
if e.keyCode is 8 and not @nodes.input.value if e.keyCode is 8 and not @nodes.input.value
@reload() @reload()
@ -702,26 +744,26 @@ QR =
</div> </div>
<form> <form>
<div class=persona> <div class=persona>
<input id=dump-button type=button title='Dump list' value=+> <input id=dump-button type=button title='Dump list' value=+ tabindex=0>
<input name=name data-name=name title=Name placeholder=Name class=field size=1> <input name=name data-name=name title=Name placeholder=Name class=field size=1 tabindex=10>
<input name=email data-name=email title=E-mail placeholder=E-mail class=field size=1> <input name=email data-name=email title=E-mail placeholder=E-mail class=field size=1 tabindex=20>
<input name=sub data-name=sub title=Subject placeholder=Subject class=field size=1> <input name=sub data-name=sub title=Subject placeholder=Subject class=field size=1 tabindex=30>
</div> </div>
<div class=textarea> <div class=textarea>
<textarea data-name=com title=Comment placeholder=Comment class=field></textarea> <textarea data-name=com title=Comment placeholder=Comment class=field tabindex=40></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">+</a> <a id=add-post href=javascript:; title="Add a post" tabindex=50>+</a>
</div> </div>
<div id=file-n-submit> <div id=file-n-submit>
<span id=qr-filename-container class=field> <span id=qr-filename-container class=field tabindex=60>
<span id=qr-no-file>No selected file</span> <span id=qr-no-file>No selected file</span>
<span id=qr-filename></span> <span id=qr-filename></span>
<a id=qr-filerm href=javascript:; title='Remove file'>×</a> <a id=qr-filerm href=javascript:; title='Remove file' tabindex=80>×</a>
</span> </span>
<input type=submit> <input type=submit tabindex=70>
</div> </div>
<input type=file multiple> <input type=file multiple>
<div id=qr-thread-select> <div id=qr-thread-select>
@ -730,7 +772,7 @@ QR =
</select> </select>
</div> </div>
<label id=qr-spoiler-label> <label id=qr-spoiler-label>
<input type=checkbox id=qr-file-spoiler title='Spoiler image'>Spoiler? <input type=checkbox id=qr-file-spoiler title='Spoiler image' tabindex=90>Spoiler?
</label> </label>
</form> </form>
""".replace />\s+</g, '><' # get rid of spaces between elements """.replace />\s+</g, '><' # get rid of spaces between elements
@ -801,7 +843,7 @@ QR =
$.add nodes.threadPar, nodes.thread $.add nodes.threadPar, nodes.thread
QR.resetThreadSelector() QR.resetThreadSelector()
$.on nodes.filename.parentNode, 'click', QR.openFileInput $.on nodes.filename.parentNode, 'click keyup', QR.openFileInput
$.on nodes.autohide, 'change', QR.toggleHide $.on nodes.autohide, 'change', QR.toggleHide
$.on nodes.close, 'click', QR.close $.on nodes.close, 'click', QR.close
@ -818,6 +860,8 @@ QR =
# save selected post's data # save selected post's data
for name in ['name', 'email', 'sub', 'com'] for name in ['name', 'email', 'sub', 'com']
$.on nodes[name], 'input', -> QR.selected.save @ $.on nodes[name], 'input', -> QR.selected.save @
$.on nodes[name], 'focus', -> $.addClass nodes.el, 'focus'
$.on nodes[name], 'blur', -> $.rmClass nodes.el, 'focus'
QR.status() QR.status()
QR.cooldown.init() QR.cooldown.init()