Remove the 'Open Reply in New Tab' config and make it the default behavior.

Various QR fixes.
Tiny styling adjustments.
You can now create threads when outside of the index.
Allow selection-to-quote to work on any text inside the quoted post, not just the comment. Close #789.
This commit is contained in:
Nicolas Stepien 2013-02-12 23:05:09 +01:00
parent 0f0d410209
commit a9b427f0d2
12 changed files with 385 additions and 330 deletions

File diff suppressed because one or more lines are too long

View File

@ -5,8 +5,11 @@ alpha
Access the list of boards directly from the Header.
From the Header's menu, access to:
Settings
Quick Reply
Quick Reply shortcut
Can be auto-hidden.
QR changes:
Creating threads outside of the index is now possible.
Selection-to-quote also applies to selected text inside the post, not just inside the comment.
Added touch and multi-touch support for dragging windows.
The Thread Updater will pause when offline, and resume when online.
Added Thread & Post Hiding in the Menu, with individual settings.

View File

@ -3,6 +3,9 @@
background-color: #D6DAF0;
border-color: #B7C5D9;
}
:root.burichan .field:focus {
border-color: #98E;
}
/* Header */
:root.burichan #header-bar {
@ -19,6 +22,11 @@
background-color: rgba(255, 255, 255, .14);
}
/* QR */
:root.burichan .qrpreview {
background-color: rgba(0, 0, 0, .15);
}
/* Menu */
:root.burichan .entry:not(:last-child) {
border-bottom: 1px solid #B7C5D9;

View File

@ -3,6 +3,9 @@
background-color: #F0E0D6;
border-color: #D9BFB7;
}
:root.futaba .field:focus {
border-color: #EA8;
}
/* Header */
:root.futaba #header-bar {
@ -19,6 +22,11 @@
background-color: rgba(255, 255, 255, .14);
}
/* QR */
:root.futaba .qrpreview {
background-color: rgba(0, 0, 0, .15);
}
/* Menu */
:root.futaba .entry:not(:last-child) {
border-bottom: 1px solid #D9BFB7;

View File

@ -3,6 +3,9 @@
background-color: #DDD;
border-color: #CCC;
}
:root.photon .field:focus {
border-color: #EA8;
}
/* Header */
:root.photon #header-bar {
@ -19,6 +22,11 @@
background-color: rgba(255, 255, 255, .14);
}
/* QR */
:root.photon .qrpreview {
background-color: rgba(0, 0, 0, .15);
}
/* Menu */
:root.photon .entry:not(:last-child) {
border-bottom: 1px solid #CCC;

View File

@ -5,6 +5,28 @@
display: block;
padding: 0;
}
.field {
border: 1px solid #CCC;
-moz-box-sizing: border-box;
box-sizing: border-box;
color: #333;
font: 13px sans-serif;
margin: 0;
padding: 2px 4px 3px;
outline: none;
-webkit-transition: color .25s, border-color .25s;
transition: color .25s, border-color .25s;
}
.field:-moz-placeholder,
.field:hover:-moz-placeholder {
color: #AAA !important;
}
.field:hover {
border-color: #999;
}
.field:hover, .field:focus {
color: #000;
}
.move {
cursor: move;
}
@ -79,21 +101,15 @@ a[href="javascript:;"] {
border-width: 0 0 1px;
padding: 4px;
position: relative;
transition: all .1s ease-in-out;
-o-transition: all .1s ease-in-out;
-moz-transition: all .1s ease-in-out;
-webkit-transition: all .1s ease-in-out;
transition: all .1s ease-in-out;
}
#header-bar.autohide:not(:hover) {
margin-bottom: -1em;
transform: translateY(-100%);
-o-transform: translateY(-100%);
-moz-transform: translateY(-100%);
-webkit-transform: translateY(-100%);
transition: all .75s .25s ease-in-out;
-o-transition: all .75s .25s ease-in-out;
-moz-transition: all .75s .25s ease-in-out;
transform: translateY(-100%);
-webkit-transition: all .75s .25s ease-in-out;
transition: all .75s .25s ease-in-out;
}
#toggle-header-bar {
cursor: n-resize;
@ -129,10 +145,8 @@ a[href="javascript:;"] {
width: 500px;
max-width: 100%;
position: relative;
transition: all .25s ease-in-out;
-o-transition: all .25s ease-in-out;
-moz-transition: all .25s ease-in-out;
-webkit-transition: all .25s ease-in-out;
transition: all .25s ease-in-out;
}
.notification.error {
background-color: hsla(0, 100%, 40%, .9);
@ -154,6 +168,7 @@ a[href="javascript:;"] {
position: absolute;
}
.message {
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 4px 20px;
max-height: 200px;
@ -234,8 +249,8 @@ a[href="javascript:;"] {
display: none;
}
#ihover {
box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
max-height: 100%;
max-width: 75%;
padding-bottom: 16px;
@ -270,11 +285,15 @@ a[href="javascript:;"] {
}
/* QR */
.hide-original-post-form #postForm,
.hide-original-post-form .postingMode {
display: none;
}
#qr > .move {
min-width: 300px;
overflow: hidden;
box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 0 2px;
}
#qr > .move > span {
@ -283,15 +302,16 @@ a[href="javascript:;"] {
#autohide, .close, #qr select, #dump, .remove, .captchaimg, #qr div.warning {
cursor: pointer;
}
#qr select,
#qr > form {
#qr select {
margin: 0;
}
#dump {
background: -webkit-linear-gradient(#EEE, #CCC);
background: -moz-linear-gradient(#EEE, #CCC);
background: -o-linear-gradient(#EEE, #CCC);
background: linear-gradient(#EEE, #CCC);
border: 1px solid #CCC;
margin: 0;
padding: 2px 4px 3px;
outline: none;
width: 10%;
}
.gecko #dump {
@ -299,14 +319,10 @@ a[href="javascript:;"] {
}
#dump:hover, #dump:focus {
background: -webkit-linear-gradient(#FFF, #DDD);
background: -moz-linear-gradient(#FFF, #DDD);
background: -o-linear-gradient(#FFF, #DDD);
background: linear-gradient(#FFF, #DDD);
}
#dump:active, .dump #dump:not(:hover):not(:focus) {
background: -webkit-linear-gradient(#CCC, #DDD);
background: -moz-linear-gradient(#CCC, #DDD);
background: -o-linear-gradient(#CCC, #DDD);
background: linear-gradient(#CCC, #DDD);
}
#qr:not(.dump) #replies, .dump > form > label {
@ -322,7 +338,7 @@ a[href="javascript:;"] {
user-select: none;
}
#replies > div {
counter-reset: thumbnails;
counter-reset: qrpreviews;
top: 0; right: 0; bottom: 0; left: 0;
margin: 0; padding: 0;
overflow: hidden;
@ -334,103 +350,72 @@ a[href="javascript:;"] {
overflow-x: auto;
z-index: 1;
}
.thumbnail {
background-color: rgba(0,0,0,.2) !important;
background-position: 50% 20% !important;
background-size: cover !important;
border: 1px solid #666;
box-sizing: border-box;
.qrpreview {
background-position: 50% 20%;
background-size: cover;
border: 1px solid #808080;
color: #FFF !important;
font-size: 12px;
-moz-box-sizing: border-box;
box-sizing: border-box;
cursor: move;
display: inline-block;
height: 90px; width: 90px;
margin: 5px; padding: 2px;
opacity: .5;
opacity: .6;
outline: none;
overflow: hidden;
position: relative;
text-shadow: 0 1px 1px #000;
-webkit-transition: opacity .25s ease-in-out;
-moz-transition: opacity .25s ease-in-out;
-o-transition: opacity .25s ease-in-out;
transition: opacity .25s ease-in-out;
vertical-align: top;
}
.thumbnail:hover, .thumbnail:focus {
.qrpreview:hover, .qrpreview:focus {
opacity: .9;
color: #FFF !important;
}
.thumbnail#selected {
.qrpreview#selected {
opacity: 1;
}
.thumbnail::before {
counter-increment: thumbnails;
content: counter(thumbnails);
color: #FFF;
.qrpreview::before {
counter-increment: qrpreviews;
content: counter(qrpreviews);
font-weight: 700;
padding: 3px;
text-shadow: 0 0 3px #000, 0 0 5px #000;
position: absolute;
top: 0;
right: 0;
text-shadow: 0 0 3px #000, 0 0 8px #000;
top: 3px; right: 3px;
}
.thumbnail.drag {
box-shadow: 0 0 10px rgba(0,0,0,.5);
.qrpreview.drag {
border-color: red;
border-style: dashed;
}
.thumbnail.over {
.qrpreview.over {
border-color: #FFF;
}
.thumbnail > span {
color: #FFF;
border-style: dashed;
}
.remove {
background: none;
color: #E00;
color: #E00 !important;
font-weight: 700;
padding: 3px;
}
.remove:hover::after {
content: " Remove";
content: ' Remove';
}
.thumbnail > label {
background: rgba(0,0,0,.5);
color: #FFF;
.qrpreview > label {
background: rgba(0, 0, 0, .5);
right: 0; bottom: 0; left: 0;
position: absolute;
text-align: center;
}
.thumbnail > label > input {
margin: 0;
.qrpreview > label > input {
margin: 1px 0;
vertical-align: bottom;
}
#addReply {
color: #333;
font-size: 3.5em;
line-height: 100px;
}
#addReply:hover, #addReply:focus {
color: #000;
}
.field {
border: 1px solid #CCC;
box-sizing: border-box;
-moz-box-sizing: border-box;
color: #333;
font: 13px sans-serif;
margin: 0;
padding: 2px 4px 3px;
-webkit-transition: color .25s, border .25s;
-moz-transition: color .25s, border .25s;
-o-transition: color .25s, border .25s;
transition: color .25s, border .25s;
}
.field:-moz-placeholder,
.field:hover:-moz-placeholder {
color: #AAA;
}
.field:hover, .field:focus {
border-color: #999;
color: #000;
outline: none;
}
#qr > form > div:first-child > .field:not(#dump) {
width: 30%;
}

View File

@ -3,6 +3,9 @@
background-color: #282A2E;
border-color: #111;
}
:root.tomorrow .field:focus {
border-color: #000;
}
/* Header */
:root.tomorrow #header-bar {
@ -19,6 +22,11 @@
background-color: rgba(0, 0, 0, .14);
}
/* QR */
:root.tomorrow .qrpreview {
background-color: rgba(255, 255, 255, .15);
}
/* Menu */
:root.tomorrow .entry:not(:last-child) {
border-bottom: 1px solid #111;

View File

@ -3,6 +3,9 @@
background-color: #D6DAF0;
border-color: #B7C5D9;
}
:root.yotsuba-b .field:focus {
border-color: #98E;
}
/* Header */
:root.yotsuba-b #header-bar {
@ -19,6 +22,11 @@
background-color: rgba(255, 255, 255, .14);
}
/* QR */
:root.yotsuba-b .qrpreview {
background-color: rgba(0, 0, 0, .15);
}
/* Menu */
:root.yotsuba-b .entry:not(:last-child) {
border-bottom: 1px solid #B7C5D9;

View File

@ -3,6 +3,9 @@
background-color: #F0E0D6;
border-color: #D9BFB7;
}
:root.yotsuba .field:focus {
border-color: #EA8;
}
/* Header */
:root.yotsuba #header-bar {
@ -19,6 +22,11 @@
background-color: rgba(255, 255, 255, .14);
}
/* QR */
:root.yotsuba .qrpreview {
background-color: rgba(0, 0, 0, .15);
}
/* Menu */
:root.yotsuba .entry:not(:last-child) {
border-bottom: 1px solid #D9BFB7;

View File

@ -45,7 +45,6 @@ Config =
'Quick Reply': [true, 'WMD.']
'Persistent QR': [false, 'The Quick reply won\'t disappear after posting.']
'Auto Hide QR': [false, 'Automatically hide the quick reply when posting.']
'Open Reply in New Tab': [false, 'Open replies posted from the board pages in a new tab.']
'Remember Subject': [false, 'Remember the subject field, instead of resetting after posting.']
'Remember Spoiler': [false, 'Remember the spoiler state, instead of resetting after posting.']
'Hide Original Post Form': [true, 'Replace the normal post form with a shortcut to open the QR.']

View File

@ -762,38 +762,41 @@ Recursive =
QR =
init: ->
return unless Conf['Quick Reply']
return if g.VIEW is 'catalog' or !Conf['Quick Reply']
if Conf['Hide Original Post Form']
Main.css += """
#postForm, .postingMode {
display: none;
}
"""
$.addClass doc, 'hide-original-post-form'
link = $.el 'a',
className: 'qr-shortcut'
textContent: 'Quick Reply'
href: 'javascript:;'
$.on link, 'click', ->
Header.menu.close()
QR.open()
if g.BOARD.ID is 'f'
if g.VIEW is 'index'
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',
type: 'header'
el: link
Post::callbacks.push
name: 'Quick Reply'
cb: @node
$.ready @readyInit
readyInit: ->
if Conf['Persistent QR']
QR.open()
QR.hide() if Conf['Auto Hide QR']
$.on d, 'dragover', QR.dragOver
$.on d, 'drop', QR.dropFile
$.on d, 'dragstart dragend', QR.drag
$.on d, '4chanXInitFinished', ->
return unless Conf['Persistent QR']
QR.open()
QR.hide() if Conf['Auto Hide QR']
Post::callbacks.push
name: 'Quick Reply'
cb: @node
node: ->
$.on $('a[title="Quote this post"]', @nodes.info), 'click', QR.quote
@ -802,13 +805,14 @@ QR =
if QR.el
QR.el.hidden = false
QR.unhide()
else
try
QR.dialog()
catch err
Main.handleErrors
message: 'Quick Reply dialog creation crashed.'
error: err
return
try
QR.dialog()
catch err
delete QR.el
Main.handleErrors
message: 'Quick Reply dialog creation crashed.'
error: err
close: ->
QR.el.hidden = true
QR.abort()
@ -821,7 +825,7 @@ QR =
QR.resetFileInput()
if not Conf['Remember Spoiler'] and (spoiler = $.id 'spoiler').checked
spoiler.click()
QR.cleanError()
QR.cleanNotification()
hide: ->
d.activeElement.blur()
$.addClass QR.el, 'autohide'
@ -830,7 +834,10 @@ QR =
$.rmClass QR.el, 'autohide'
$.id('autohide').checked = false
toggleHide: ->
@checked and QR.hide() or QR.unhide()
if @checked
QR.hide()
else
QR.unhide()
error: (err) ->
QR.open()
@ -844,20 +851,20 @@ QR =
$('[autocomplete]', QR.el).focus()
alert el.textContent if d.hidden
QR.lastNotification = new Notification 'warning', el
cleanError: ->
cleanNotification: ->
QR.lastNotification?.close()
delete QR.lastNotification
status: (data={}) ->
return unless QR.el
if g.dead
if g.dead # XXX
value = 404
disabled = true
QR.cooldown.auto = false
value = data.progress or QR.cooldown.seconds or value
{input} = QR.status
input.value =
if QR.cooldown.auto and Conf['Cooldown']
if QR.cooldown.auto
if value then "Auto #{value}" else 'Auto'
else
value or 'Submit'
@ -865,7 +872,6 @@ QR =
cooldown:
init: ->
return unless Conf['Cooldown']
QR.cooldown.types =
thread: switch g.BOARD
when 'q' then 86400
@ -883,12 +889,11 @@ QR =
QR.cooldown.count()
sync: (cooldowns) ->
# Add each cooldowns, don't overwrite everything in case we
# still need to purge one in the current tab to auto-post.
# still need to prune one in the current tab to auto-post.
for id of cooldowns
QR.cooldown.cooldowns[id] = cooldowns[id]
QR.cooldown.start()
set: (data) ->
return unless Conf['Cooldown']
start = Date.now()
if data.delay
cooldown = delay: data.delay
@ -926,7 +931,12 @@ QR =
QR.status()
return
if (isReply = if g.REPLY then true else QR.threadSelector.value isnt 'new')
isReply =
if g.BOARD.ID is 'f' and g.VIEW is 'thread'
true
else
QR.threadSelector.value isnt 'new'
if isReply
post = QR.replies[0]
isSage = /sage/i.test post.email
hasFile = !!post.file
@ -973,14 +983,15 @@ QR =
e?.preventDefault()
QR.open()
ta = $ 'textarea', QR.el
unless g.REPLY or ta.value
if QR.threadSelector and !ta.value and g.BOARD.ID isnt 'f'
QR.threadSelector.value = $.x('ancestor::div[parent::div[@class="board"]]', @).id[1..]
# Make sure we get the correct number, even with XXX censors
id = @previousSibling.hash[2..]
text = ">>#{id}\n"
post = Get.postFromRoot $.x 'ancestor-or-self::div[contains(@class,"postContainer")][1]', @
text = ">>#{post}\n"
sel = d.getSelection()
if (s = sel.toString().trim()) and id is $.x('ancestor-or-self::blockquote', sel.anchorNode)?.id.match(/\d+$/)[0]
selectionRoot = $.x 'ancestor-or-self::div[contains(@class,"postContainer")][1]', sel.anchorNode
if (s = sel.toString().trim()) and post.nodes.root is selectionRoot
# XXX Opera doesn't retain `\n`s?
s = s.replace /\n/g, '\n>'
text += ">#{s}\n"
@ -1019,7 +1030,7 @@ QR =
QR.fileInput.call e.dataTransfer
$.addClass QR.el, 'dump'
fileInput: ->
QR.cleanError()
QR.cleanNotification()
# Set or change current reply's file.
if @files.length is 1
file = @files[0]
@ -1063,7 +1074,7 @@ QR =
@com = null
@el = $.el 'a',
className: 'thumbnail'
className: 'qrpreview'
draggable: true
href: 'javascript:;'
innerHTML: '<a class=remove>×</a><label hidden><input type=checkbox> Spoiler</label><span></span>'
@ -1187,7 +1198,7 @@ QR =
else if @el.id is 'selected'
(QR.replies[index-1] or QR.replies[index+1]).select()
QR.replies.splice index, 1
(window.URL or window.webkitURL).revokeObjectURL? @url
(window.URL or window.webkitURL)?.revokeObjectURL @url
captcha:
init: ->
@ -1235,9 +1246,8 @@ QR =
@count captchas.length
@reload()
load: ->
# Timeout is available at RecaptchaState.timeout in seconds.
# We use 5-1 minutes to give upload some time.
@timeout = Date.now() + 4*$.MINUTE
# -1 minute to give upload some time.
@timeout = Date.now() + $.unsafeWindow.RecaptchaState.timeout * $.SECOND - $.MINUTE
challenge = @challenge.firstChild.value
@img.alt = challenge
@img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}"
@ -1252,8 +1262,8 @@ QR =
"Verification (#{count} cached captchas)"
@input.alt = count # For XTRM RICE.
reload: (focus) ->
# the "t" argument prevents the input from being focused
$.globalEval 'javascript:Recaptcha.reload("t")'
# the 't' argument prevents the input from being focused
$.unsafeWindow.Recaptcha.reload 'r'
# Focus if we meant to.
QR.captcha.input.focus() if focus
keydown: (e) ->
@ -1267,23 +1277,16 @@ QR =
e.preventDefault()
dialog: ->
QR.el = UI.dialog 'qr', 'top:0;right:0;', '
<div class=move>
Quick Reply <input type=checkbox id=autohide title=Auto-hide>
<span> <a class=close title=Close>×</a></span>
</div>
<form>
<div><input id=dump type=button title="Dump list" value=+ class=field><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 id=replies><div><a id=addReply href=javascript:; title="Add a reply">+</a></div></div>
<div class=textarea><textarea name=com title=Comment placeholder=Comment class=field></textarea><span id=charCount></span></div>
<div><input type=file title="Shift+Click to remove the selected file." multiple size=16><input type=submit></div>
<label id=spoilerLabel><input type=checkbox id=spoiler> Spoiler Image</label>
</form>'
if Conf['Remember QR size'] and $.engine is 'gecko'
$.on ta = $('textarea', QR.el), 'mouseup', ->
$.set 'QR.size', @style.cssText
ta.style.cssText = $.get 'QR.size', ''
QR.el = UI.dialog 'qr', 'top:0;right:0;', """
<div class=move>Quick Reply <input type=checkbox id=autohide title=Auto-hide><span> <a class=close title=Close>×</a></span></div>
<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 id=replies><div id=repliesList><a id=addReply href=javascript:; title="Add a reply">+</a></div></div>
<div class=textarea><textarea name=com title=Comment placeholder=Comment class=field></textarea><span id=charCount></span></div>
<div><input type=file title="Shift+Click to remove the selected file." multiple size=16><input type=submit></div>
<label id=spoilerLabel><input type=checkbox id=spoiler> Spoiler Image</label>
</form>
"""
# Allow only this board's supported files.
mimeTypes = $('ul.rules').firstElementChild.textContent.trim().match(/: (.+)/)[1].toLowerCase().replace /\w+/g, (type) ->
@ -1310,21 +1313,24 @@ QR =
QR.charaCounter = $ '#charCount', QR.el
ta = $ 'textarea', QR.el
unless g.REPLY
# Make a list with visible threads and an option to create a new one.
span = $('.move > span', QR.el)
# Make a list of visible threads.
if g.BOARD.ID is 'f'
if g.VIEW is 'index'
QR.threadSelector = $('select[name=filetag]').cloneNode true
else
QR.threadSelector = $.el 'select',
title: 'Create a new thread / Reply to a thread'
threads = '<option value=new>New thread</option>'
for thread in $$ '.thread'
id = thread.id[1..]
threads += "<option value=#{id}>Thread #{id}</option>"
QR.threadSelector =
if g.BOARD is 'f'
$('select[name=filetag]').cloneNode true
else
$.el 'select'
innerHTML: threads
title: 'Create a new thread / Reply to a thread'
$.prepend $('.move > span', QR.el), QR.threadSelector
$.on QR.threadSelector, 'mousedown', (e) -> e.stopPropagation()
for key, thread of g.BOARD.threads
threads += "<option value=#{thread.ID}>Thread No.#{thread.ID}</option>"
QR.threadSelector.innerHTML = threads
if g.VIEW is 'thread'
QR.threadSelector.value = g.THREAD
if QR.threadSelector
$.prepend span, QR.threadSelector
$.on span, 'mousedown', (e) -> e.stopPropagation()
$.on $('#autohide', QR.el), 'change', QR.toggleHide
$.on $('.close', QR.el), 'click', QR.close
$.on $('#dump', QR.el), 'click', -> QR.el.classList.toggle 'dump'
@ -1366,20 +1372,20 @@ QR =
QR.abort()
reply = QR.replies[0]
if g.BOARD is 'f' and not g.REPLY
if g.BOARD.ID is 'f' and g.VIEW is 'index'
filetag = QR.threadSelector.value
threadID = 'new'
else
threadID = g.THREAD_ID or QR.threadSelector.value
threadID = QR.threadSelector.value
# prevent errors
if threadID is 'new'
threadID = null
if g.BOARD in ['vg', 'q'] and !reply.sub
if g.BOARD.ID in ['vg', 'q'] and !reply.sub
err = 'New threads require a subject.'
else unless reply.file or textOnly = !!$ 'input[name=textonly]', $.id 'postForm'
err = 'No file selected.'
else if g.BOARD is 'f' and filetag is '9999'
else if g.BOARD.ID is 'f' and filetag is '9999'
err = 'Invalid tag specified.'
else unless reply.com or reply.file
err = 'No file selected.'
@ -1412,7 +1418,7 @@ QR =
QR.status()
QR.error err
return
QR.cleanError()
QR.cleanNotification()
# Enable auto-posting if we have stuff to post, disable it otherwise.
QR.cooldown.auto = QR.replies.length > 1
@ -1530,14 +1536,13 @@ QR =
post: reply
isReply: threadID isnt '0'
# Enable auto-posting if we have stuff to post, disable it otherwise.
QR.cooldown.auto = QR.replies.length > 1
if threadID is '0' # new thread
# auto-noko
location.pathname = "/#{g.BOARD}/res/#{postID}"
else
# Enable auto-posting if we have stuff to post, disable it otherwise.
QR.cooldown.auto = QR.replies.length > 1
if Conf['Open Reply in New Tab'] and !g.REPLY and !QR.cooldown.auto
$.open "//boards.4chan.org/#{g.BOARD}/res/#{threadID}#p#{postID}"
$.open "/#{g.BOARD}/res/#{postID}"
else if g.VIEW is 'reply' and !QR.cooldown.auto # posting from the index
$.open "//boards.4chan.org/#{g.BOARD}/res/#{threadID}#p#{postID}"
if Conf['Persistent QR'] or QR.cooldown.auto
reply.rm()
@ -2092,7 +2097,7 @@ Build =
'<br><em>' +
"<a href=#{"/#{board}/res/#{threadID}#p#{postID}"}>No.</a>" +
"<a href='#{
if g.VIEW is 'thread' and g.THREAD is threadID
if g.VIEW is 'thread' and g.THREAD is +threadID
"javascript:quote(#{postID})"
else
"/#{board}/res/#{threadID}#q#{postID}"
@ -2114,7 +2119,7 @@ Build =
"<span class='postNum desktop'>" +
"<a href=#{"/#{board}/res/#{threadID}#p#{postID}"} title='Highlight this post'>No.</a>" +
"<a href='#{
if g.VIEW is 'thread' and g.THREAD is threadID
if g.VIEW is 'thread' and g.THREAD is +threadID
"javascript:quote(#{postID})"
else
"/#{board}/res/#{threadID}#q#{postID}"

View File

@ -148,10 +148,13 @@ class Post
@kill() if that.isArchived
kill: (img) ->
now = Date.now()
if @file and !@file.isDead
@file.isDead = true
@file.timeOfDeath = now
return if img
@isDead = true
@timeOfDeath = now
$.addClass @nodes.root, 'dead'
# XXX style dead posts.
@ -325,21 +328,22 @@ Main =
initStyle: ->
# disable the mobile layout
$('link[href*=mobile]', d.head)?.disabled = true
$.addClass doc, $.engine
$.addClass doc, 'fourchan-x'
$.addStyle Main.css
style = null
style = 'yotsuba-b'
mainStyleSheet = $ 'link[title=switch]', d.head
styleSheets = $$ 'link[rel="alternate stylesheet"]', d.head
setStyle = ->
$.rmClass doc, style if style
$.rmClass doc, style
for styleSheet in styleSheets
if styleSheet.href is mainStyleSheet.href
style = styleSheet.title.toLowerCase().replace('new', '').trim().replace /\s+/g, '-'
break
$.addClass doc, style
$.addClass doc, $.engine
$.addClass doc, 'fourchan-x'
setStyle()
return unless mainStyleSheet
if MutationObserver = window.MutationObserver or window.WebKitMutationObserver or window.OMutationObserver
observer = new MutationObserver setStyle
observer.observe mainStyleSheet,
@ -351,7 +355,7 @@ Main =
initReady: ->
unless $.hasClass doc, 'fourchan-x'
# Something might go wrong!
# Something might have gone wrong!
Main.initStyle()
if d.title is '4chan - 404 Not Found'
@ -362,28 +366,31 @@ Main =
postID: location.hash
return
threads = []
posts = []
if board = $ '.board'
threads = []
posts = []
for boardChild in $('.board').children
continue unless $.hasClass boardChild, 'thread'
thread = new Thread boardChild.id[1..], g.BOARD
threads.push thread
for threadChild in boardChild.children
continue unless $.hasClass threadChild, 'postContainer'
try
posts.push new Post threadChild, thread, g.BOARD
catch err
# Skip posts that we failed to parse.
unless errors
errors = []
errors.push
message: "Parsing of Post No.#{threadChild.id.match(/\d+/)} failed. Post will be skipped."
error: err
Main.handleErrors errors if errors
for boardChild in board.children
continue unless $.hasClass boardChild, 'thread'
thread = new Thread boardChild.id[1..], g.BOARD
threads.push thread
for threadChild in boardChild.children
continue unless $.hasClass threadChild, 'postContainer'
try
posts.push new Post threadChild, thread, g.BOARD
catch err
# Skip posts that we failed to parse.
unless errors
errors = []
errors.push
message: "Parsing of Post No.#{threadChild.id.match(/\d+/)} failed. Post will be skipped."
error: err
Main.handleErrors errors if errors
Main.callbackNodes Thread, threads
Main.callbackNodes Post, posts
Main.callbackNodes Thread, threads
Main.callbackNodes Post, posts
$.event '4chanXInitFinished'
callbackNodes: (klass, nodes) ->
# get the nodes' length only once