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:
parent
0f0d410209
commit
a9b427f0d2
272
4chan_x.user.js
272
4chan_x.user.js
File diff suppressed because one or more lines are too long
@ -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.
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
149
css/style.css
149
css/style.css
@ -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%;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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.']
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user