Merge branch 'zixaphir-merge'

This brings various changes from @zixaphir's Appchan X into this fork:
- Fix updating of the preview text in the advanced settings panel.
- Close menus on scroll (fixes post menus not scrolling with their posts).
- Add "none" option (don't open any section) to the "OpenSettings" event.
- Give "Heaven" a white ID color.
- Some settings description improvements.
- Refactoring and minor changes.

Some changes are being deferred for now, but are intended to be added in future commits:
- addition of src/Miscellaneous/Flash.coffee
- rewrite of src/Quotelinks/QuoteBacklinks.coffee
- refactoring of various quote link features into QuoteMarkers.coffee (including change in QuoteInline.qiQuote)
- adding dataset.fullID for simpler Get.threadFromRoot and Get.postFromRoot
- "Except Archives" -> "Exempt Archives" setting change

The changes to Callbacks.execute in src/General/lib/callbacks.class and
elimination of the corresponding methods in Main.coffee may be incorporated
in the future, but will need to be rewritten to preserve the existing
25-posts-at-a-time functionality to prevent hanging on long threads.

The post/thread hiding changes from
https://github.com/MayhemYDG/4chan-x/pull/1457
are being rejected, at least for now, due to breaking thread stubs.
This commit is contained in:
ccd0 2015-02-06 23:47:17 -08:00
commit 12bc5d1612
37 changed files with 187 additions and 182 deletions

View File

@ -1,6 +1,6 @@
Anonymize = Anonymize =
init: -> init: ->
return if g.VIEW not in ['index', 'thread', 'archive'] or !Conf['Anonymize'] return unless g.VIEW in ['index', 'thread', 'archive'] and Conf['Anonymize']
return @archive() if g.VIEW is 'archive' return @archive() if g.VIEW is 'archive'
Post.callbacks.push Post.callbacks.push

View File

@ -1,7 +1,7 @@
Filter = Filter =
filters: {} filters: {}
init: -> init: ->
return if g.VIEW not in ['index', 'thread'] or !Conf['Filter'] return unless g.VIEW in ['index', 'thread'] and Conf['Filter']
unless Conf['Filtered Backlinks'] unless Conf['Filtered Backlinks']
$.addClass doc, 'hide-backlinks' $.addClass doc, 'hide-backlinks'
@ -100,10 +100,8 @@ Filter =
node: -> node: ->
return if @isClone or @isFetchedQuote return if @isClone or @isFetchedQuote
for key of Filter.filters for key of Filter.filters when (value = Filter[key] @) isnt false
value = Filter[key] @
# Continue if there's nothing to filter (no tripcode for example). # Continue if there's nothing to filter (no tripcode for example).
continue if value is false
for filter in Filter.filters[key] when result = filter value, @isReply for filter in Filter.filters[key] when result = filter value, @isReply
# Hide # Hide
@ -171,7 +169,7 @@ Filter =
menu: menu:
init: -> init: ->
return if g.VIEW not in ['index', 'thread'] or !Conf['Menu'] or !Conf['Filter'] return unless g.VIEW in ['index', 'thread'] and Conf['Menu'] and Conf['Filter']
div = $.el 'div', div = $.el 'div',
textContent: 'Filter' textContent: 'Filter'

View File

@ -22,10 +22,9 @@ Recursive =
rm: (recursive, post) -> rm: (recursive, post) ->
return unless obj = Recursive.recursives[post.fullID] return unless obj = Recursive.recursives[post.fullID]
for rec, i in obj.recursives for rec, i in obj.recursives when rec is recursive
if rec is recursive obj.recursives.splice i, 1
obj.recursives.splice i, 1 obj.args.splice i, 1
obj.args.splice i, 1
return return
apply: (recursive, post, args...) -> apply: (recursive, post, args...) ->

View File

@ -1,13 +1,12 @@
Build = Build =
staticPath: '//s.4cdn.org/image/' staticPath: '//s.4cdn.org/image/'
gifIcon: if window.devicePixelRatio >= 2 then '@2x.gif' else '.gif' gifIcon: if window.devicePixelRatio >= 2 then '@2x.gif' else '.gif'
initPixelRatio: window.devicePixelRatio
spoilerRange: {} spoilerRange: {}
unescape: (text) -> unescape: (text) ->
return text unless text? return text unless text?
text.replace(/<[^>]*>/g, '').replace /&(amp|#039|quot|lt|gt);/g, (c) -> text.replace(/<[^>]*>/g, '').replace /&(amp|#039|quot|lt|gt);/g, (c) ->
{'&amp;': '&', '&#039;': "'", '&quot;': '"', '&lt;': '<', '&gt;': '>'}[c] {'&amp;': '&', '&#039;': "'", '&quot;': '"', '&lt;': '<', '&gt;': '>'}[c]
shortFilename: (filename, isReply) -> shortFilename: (filename) ->
threshold = 30 threshold = 30
ext = filename.match(/\.?[^\.]*$/)[0] ext = filename.match(/\.?[^\.]*$/)[0]
if filename.length - ext.length > threshold if filename.length - ext.length > threshold
@ -15,8 +14,8 @@ Build =
else else
filename filename
thumbRotate: do -> thumbRotate: do ->
n = 0 t = 0
-> n = (n + 1) % 2 -> t = (if t then 0 else 1)
sameThread: (boardID, threadID) -> sameThread: (boardID, threadID) ->
g.VIEW is 'thread' and g.BOARD.ID is boardID and g.THREADID is +threadID g.VIEW is 'thread' and g.BOARD.ID is boardID and g.THREADID is +threadID
postURL: (boardID, threadID, postID) -> postURL: (boardID, threadID, postID) ->
@ -83,8 +82,7 @@ Build =
name or= '' name or= ''
subject or= '' subject or= ''
isOP = postID is threadID isOP = postID is threadID
{staticPath, gifIcon} = Build
retina = if Build.initPixelRatio >= 2 then '@2x' else ''
### Name Block ### ### Name Block ###
@ -92,19 +90,19 @@ Build =
when 'admin', 'admin_highlight' when 'admin', 'admin_highlight'
capcodeClass = ' capcodeAdmin' capcodeClass = ' capcodeAdmin'
capcodeStart = <%= html(' <strong class="capcode hand id_admin" title="Highlight posts by the Administrator">## Admin</strong>') %> capcodeStart = <%= html(' <strong class="capcode hand id_admin" title="Highlight posts by the Administrator">## Admin</strong>') %>
capcodeIcon = <%= html(' <img src="//s.4cdn.org/image/adminicon${retina}.gif" alt="Admin Icon" title="This user is the 4chan Administrator." class="identityIcon retina">') %> capcodeIcon = <%= html(' <img src="${staticPath}adminicon${gifIcon}" alt="Admin Icon" title="This user is the 4chan Administrator." class="identityIcon retina">') %>
when 'mod' when 'mod'
capcodeClass = ' capcodeMod' capcodeClass = ' capcodeMod'
capcodeStart = <%= html(' <strong class="capcode hand id_mod" title="Highlight posts by Moderators">## Mod</strong>') %> capcodeStart = <%= html(' <strong class="capcode hand id_mod" title="Highlight posts by Moderators">## Mod</strong>') %>
capcodeIcon = <%= html(' <img src="//s.4cdn.org/image/modicon${retina}.gif" alt="Mod Icon" title="This user is a 4chan Moderator." class="identityIcon retina">') %> capcodeIcon = <%= html(' <img src="${staticPath}modicon${gifIcon}" alt="Mod Icon" title="This user is a 4chan Moderator." class="identityIcon retina">') %>
when 'developer' when 'developer'
capcodeClass = ' capcodeDeveloper' capcodeClass = ' capcodeDeveloper'
capcodeStart = <%= html(' <strong class="capcode hand id_developer" title="Highlight posts by Developers">## Developer</strong>') %> capcodeStart = <%= html(' <strong class="capcode hand id_developer" title="Highlight posts by Developers">## Developer</strong>') %>
capcodeIcon = <%= html(' <img src="//s.4cdn.org/image/developericon${retina}.gif" alt="Developer Icon" title="This user is a 4chan Developer." class="identityIcon retina">') %> capcodeIcon = <%= html(' <img src="${staticPath}developericon${gifIcon}" alt="Developer Icon" title="This user is a 4chan Developer." class="identityIcon retina">') %>
when 'manager' when 'manager'
capcodeClass = ' capcodeManager' capcodeClass = ' capcodeManager'
capcodeStart = <%= html(' <strong class="capcode hand id_manager" title="Highlight posts by Managers">## Manager</strong>') %> capcodeStart = <%= html(' <strong class="capcode hand id_manager" title="Highlight posts by Managers">## Manager</strong>') %>
capcodeIcon = <%= html(' <img src="//s.4cdn.org/image/managericon${retina}.gif" alt="Manager Icon" title="This user is a 4chan Manager." class="identityIcon retina">') %> capcodeIcon = <%= html(' <img src="${staticPath}managericon${gifIcon}" alt="Manager Icon" title="This user is a 4chan Manager." class="identityIcon retina">') %>
else else
capcodeClass = '' capcodeClass = ''
capcodeStart = <%= html('') %> capcodeStart = <%= html('') %>
@ -155,7 +153,7 @@ Build =
icons = for type in ['Sticky', 'Closed', 'Archived'] when o["is#{type}"] and !(type is 'Closed' and o.isArchived) icons = for type in ['Sticky', 'Closed', 'Archived'] when o["is#{type}"] and !(type is 'Closed' and o.isArchived)
typeLC = type.toLowerCase() typeLC = type.toLowerCase()
<%= html(' <img src="//s.4cdn.org/image/${typeLC}${retina}.gif" alt="${type}" title="${type}" class="${typeLC}Icon retina">') %> <%= html(' <img src="${staticPath}${typeLC}${gifIcon}" alt="${type}" title="${type}" class="${typeLC}Icon retina">') %>
replyLink = if isOP and g.VIEW is 'index' replyLink = if isOP and g.VIEW is 'index'
<%= html(' &nbsp; <span>[<a href="/${boardID}/thread/${threadID}" class="replylink">Reply</a>]</span>') %> <%= html(' &nbsp; <span>[<a href="/${boardID}/thread/${threadID}" class="replylink">Reply</a>]</span>') %>
@ -181,7 +179,7 @@ Build =
fileCont = if file?.isDeleted fileCont = if file?.isDeleted
<%= html( <%= html(
'<span class="fileThumb">' + '<span class="fileThumb">' +
'<img src="//s.4cdn.org/image/filedeleted-res${retina}.gif" alt="File deleted." class="fileDeletedRes retina">' + '<img src="${staticPath}filedeleted-res${gifIcon}" alt="File deleted." class="fileDeletedRes retina">' +
'</span>' '</span>'
) %> ) %>
else if file and boardID is 'f' else if file and boardID is 'f'
@ -196,9 +194,9 @@ Build =
shortFilename = 'Spoiler Image' shortFilename = 'Spoiler Image'
if spoilerRange = Build.spoilerRange[boardID] if spoilerRange = Build.spoilerRange[boardID]
# Randomize the spoiler image. # Randomize the spoiler image.
fileThumb = "//s.4cdn.org/image/spoiler-#{boardID}#{Math.floor 1 + spoilerRange * Math.random()}.png" fileThumb = "#{staticPath}spoiler-#{boardID}#{Math.floor 1 + spoilerRange * Math.random()}.png"
else else
fileThumb = '//s.4cdn.org/image/spoiler.png' fileThumb = "#{staticPath}spoiler.png"
file.twidth = file.theight = 100 file.twidth = file.theight = 100
else else
shortFilename = Build.shortFilename file.name, !isOP shortFilename = Build.shortFilename file.name, !isOP

View File

@ -53,7 +53,7 @@ Config =
] ]
'Comment Expansion': [ 'Comment Expansion': [
true true
'Add buttons to expand too long comments.' 'Expand Comments that are too long to display on the index. Not applicable with JSON Navigation.'
] ]
'File Info Formatting': [ 'File Info Formatting': [
true true
@ -118,6 +118,11 @@ Config =
true true
'Convert text into links where applicable.' 'Convert text into links where applicable.'
] ]
'Link Title': [
true
'Replace the link of a supported site with its actual title. Currently Supported: YouTube, Vimeo, SoundCloud, and Github gists'
1
]
'Embedding': [ 'Embedding': [
true true
'Embed supported services. Note: Some services don\'t work on HTTPS.' 'Embed supported services. Note: Some services don\'t work on HTTPS.'
@ -133,11 +138,6 @@ Config =
'Embed content in a frame that remains in place when the page is scrolled.' 'Embed content in a frame that remains in place when the page is scrolled.'
2 2
] ]
'Link Title': [
true
'Replace the link of a supported site with its actual title. Currently Supported: YouTube, Vimeo, SoundCloud, and Github gists'
1
]
'Filtering': 'Filtering':
'Anonymize': [ 'Anonymize': [
@ -384,8 +384,8 @@ Config =
'All-in-one form to reply, create threads, automate dumping and more.' 'All-in-one form to reply, create threads, automate dumping and more.'
] ]
'QR Shortcut': [ 'QR Shortcut': [
true, true
'Adds a small [QR] link in the header.' 'Add a shortcut to the header to toggle the QR.'
1 1
] ]
'Persistent QR': [ 'Persistent QR': [
@ -598,7 +598,7 @@ Config =
] ]
'Auto Prune': [ 'Auto Prune': [
false false
'Automatically prune dead threads.' 'Automatically remove dead threads.'
] ]
'Show Unread Count': [ 'Show Unread Count': [
true true

View File

@ -9,6 +9,11 @@ g =
FAQ: '<%= meta.faq %>' FAQ: '<%= meta.faq %>'
CHANGELOG: '<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md' CHANGELOG: '<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md'
boards: {} boards: {}
E = (text) ->
(text+'').replace /[&"'<>]/g, (x) -> E = do ->
{'&': '&amp;', "'": '&#039;', '"': '&quot;', '<': '&lt;', '>': '&gt;'}[x] str = {'&': '&amp;', "'": '&#039;', '"': '&quot;', '<': '&lt;', '>': '&gt;'}
r = String::replace
regex = /[&"'<>]/g
fn = (x) ->
str[x]
(text) -> r.call text, regex, fn

View File

@ -3,8 +3,8 @@ Main =
if location.hostname is 'www.google.com' if location.hostname is 'www.google.com'
return $.ready -> Captcha.noscript.initFrame() return $.ready -> Captcha.noscript.initFrame()
g.threads = new SimpleDict g.threads = new SimpleDict()
g.posts = new SimpleDict g.posts = new SimpleDict()
pathname = location.pathname.split '/' pathname = location.pathname.split '/'
g.BOARD = new Board pathname[1] g.BOARD = new Board pathname[1]
@ -90,6 +90,7 @@ Main =
error: err error: err
# finally # finally
# c.timeEnd "#{name} initialization" # c.timeEnd "#{name} initialization"
# c.timeEnd 'All initializations' # c.timeEnd 'All initializations'
$.ready Main.initReady $.ready Main.initReady
@ -163,19 +164,19 @@ Main =
Settings.open() Settings.open()
$.set 'previousversion', g.VERSION $.set 'previousversion', g.VERSION
return unless Conf['Show Support Message'] if Conf['Show Support Message']
<% if (type === 'userscript') { %> <% if (type === 'userscript') { %>
GMver = GM_info.version.split '.' GMver = GM_info.version.split '.'
for v, i in "<%= meta.min.greasemonkey %>".split '.' for v, i in "<%= meta.min.greasemonkey %>".split '.'
continue if v is GMver[i] continue if v is GMver[i]
(v < GMver[i]) or new Notice 'warning', "Your version of Greasemonkey is outdated (v#{GM_info.version} instead of v<%= meta.min.greasemonkey %> minimum) and <%= meta.name %> may not operate correctly.", 30 (v < GMver[i]) or new Notice 'warning', "Your version of Greasemonkey is outdated (v#{GM_info.version} instead of v<%= meta.min.greasemonkey %> minimum) and <%= meta.name %> may not operate correctly.", 30
break break
<% } %> <% } %>
try try
localStorage.getItem '4chan-settings' localStorage.getItem '4chan-settings'
catch err catch err
new Notice 'warning', 'Cookies need to be enabled on 4chan for <%= meta.name %> to operate properly.', 30 new Notice 'warning', 'Cookies need to be enabled on 4chan for <%= meta.name %> to operate properly.', 30
initThread: -> initThread: ->
if board = $ '.board' if board = $ '.board'

View File

@ -10,14 +10,16 @@ Settings =
Header.addShortcut link Header.addShortcut link
Settings.addSection 'Main', Settings.main add = @addSection
Settings.addSection 'Filter', Settings.filter
Settings.addSection 'Sauce', Settings.sauce add 'Main', @main
Settings.addSection 'Advanced', Settings.advanced add 'Filter', @filter
Settings.addSection 'Keybinds', Settings.keybinds add 'Sauce', @sauce
add 'Advanced', @advanced
add 'Keybinds', @keybinds
$.on d, 'AddSettingsSection', Settings.addSection $.on d, 'AddSettingsSection', Settings.addSection
$.on d, 'OpenSettings', (e) -> Settings.open e.detail $.on d, 'OpenSettings', (e) -> Settings.open e.detail
if Conf['Disable Native Extension'] if Conf['Disable Native Extension']
settings = JSON.parse(localStorage.getItem '4chan-settings') or {} settings = JSON.parse(localStorage.getItem '4chan-settings') or {}
@ -26,22 +28,22 @@ Settings =
localStorage.setItem '4chan-settings', JSON.stringify settings localStorage.setItem '4chan-settings', JSON.stringify settings
open: (openSection) -> open: (openSection) ->
return if Settings.dialog return if Settings.overlay
$.event 'CloseMenu' $.event 'CloseMenu'
Settings.overlay = overlay = $.el 'div',
id: 'overlay'
Settings.dialog = dialog = $.el 'div', Settings.dialog = dialog = $.el 'div',
id: 'fourchanx-settings' id: 'fourchanx-settings'
className: 'dialog' className: 'dialog'
$.extend dialog, <%= importHTML('Settings/Settings') %> $.extend dialog, <%= importHTML('Settings/Settings') %>
$('a[href$="/CHANGELOG.md"]', dialog).textContent = g.VERSION $('a[href$="/CHANGELOG.md"]', dialog).textContent = g.VERSION
$.on $('.export', Settings.dialog), 'click', Settings.export Settings.overlay = overlay = $.el 'div',
$.on $('.import', Settings.dialog), 'click', Settings.import id: 'overlay'
$.on $('.reset', Settings.dialog), 'click', Settings.reset
$.on $('input', Settings.dialog), 'change', Settings.onImport $.on $('.export', dialog), 'click', Settings.export
$.on $('.import', dialog), 'click', Settings.import
$.on $('.reset', dialog), 'click', Settings.reset
$.on $('input', dialog), 'change', Settings.onImport
links = [] links = []
for section in Settings.sections for section in Settings.sections
@ -54,7 +56,7 @@ Settings =
sectionToOpen = link if section.title is openSection sectionToOpen = link if section.title is openSection
links.pop() links.pop()
$.add $('.sections-list', dialog), links $.add $('.sections-list', dialog), links
(if sectionToOpen then sectionToOpen else links[0]).click() (if sectionToOpen then sectionToOpen else links[0]).click() unless openSection is 'none'
$.on $('.close', dialog), 'click', Settings.close $.on $('.close', dialog), 'click', Settings.close
$.on overlay, 'click', Settings.close $.on overlay, 'click', Settings.close
@ -103,8 +105,9 @@ Settings =
div = $.el 'div', div = $.el 'div',
<%= html('<label><input type="checkbox" name="${key}">${key}</label><span class="description">: ${description}</span>') %> <%= html('<label><input type="checkbox" name="${key}">${key}</label><span class="description">: ${description}</span>') %>
input = $ 'input', div input = $ 'input', div
$.on input, 'change', $.cb.checked $.on input, 'change', ->
$.on input, 'change', -> @parentNode.parentNode.dataset.checked = @checked @parentNode.parentNode.dataset.checked = @checked
$.cb.checked.call @
items[key] = Conf[key] items[key] = Conf[key]
inputs[key] = input inputs[key] = input
level = arr[2] or 0 level = arr[2] or 0
@ -160,7 +163,7 @@ Settings =
<% } %> <% } %>
a.click() a.click()
import: -> import: ->
$('input', @parentNode).click() $('input[type=file]', @parentNode).click()
onImport: -> onImport: ->
return unless file = @files[0] return unless file = @files[0]
@ -305,17 +308,21 @@ Settings =
items = {} items = {}
inputs = {} inputs = {}
for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss'] for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss']
input = $ "[name=#{name}]", section input = $ "[name='#{name}']", section
items[name] = Conf[name] items[name] = Conf[name]
inputs[name] = input inputs[name] = input
event = if name in ['favicon', 'usercss'] if name is 'usercss'
'change' $.on input, 'change', $.cb.value
else else if name is 'favicon'
'input' $.on input, 'change', $.cb.value
$.on input, event, $.cb.value $.on input, 'change', Settings[name]
else
$.on input, 'input', $.cb.value
$.on input, 'input', Settings[name]
# Quick Reply Personas # Quick Reply Personas
ta = $ '.personafield', section ta = $ '.personafield', section
$.get 'QR.personas', Conf['QR.personas'], (item) -> $.get 'QR.personas', Conf['QR.personas'], (item) ->
ta.value = item['QR.personas'] ta.value = item['QR.personas']
$.on ta, 'change', $.cb.value $.on ta, 'change', $.cb.value
@ -325,7 +332,6 @@ Settings =
input = inputs[key] input = inputs[key]
input.value = val input.value = val
continue if key is 'usercss' continue if key is 'usercss'
$.on input, event, Settings[key]
Settings[key].call input Settings[key].call input
return return
@ -484,6 +490,7 @@ Settings =
inputs[key] = input inputs[key] = input
$.on input, 'keydown', Settings.keybind $.on input, 'keydown', Settings.keybind
$.add tbody, tr $.add tbody, tr
$.get items, (items) -> $.get items, (items) ->
for key, val of items for key, val of items
inputs[key].value = val inputs[key].value = val

View File

@ -65,8 +65,7 @@ UI = do ->
$.addClass lastToggledButton, 'active' $.addClass lastToggledButton, 'active'
$.on d, 'click', @close $.one d, 'click scroll CloseMenu', @close
$.on d, 'CloseMenu', @close
$.add button, menu $.add button, menu
# Position # Position
@ -121,7 +120,6 @@ UI = do ->
$.rmClass lastToggledButton, 'active' $.rmClass lastToggledButton, 'active'
currentMenu = null currentMenu = null
lastToggledButton = null lastToggledButton = null
$.off d, 'click CloseMenu', @close
findNextEntry: (entry, direction) -> findNextEntry: (entry, direction) ->
entries = [entry.parentNode.children...] entries = [entry.parentNode.children...]
@ -162,6 +160,7 @@ UI = do ->
onFocus: (e) => onFocus: (e) =>
e.stopPropagation() e.stopPropagation()
@focus e.target @focus e.target
focus: (entry) -> focus: (entry) ->
while focused = $.x 'parent::*/child::*[contains(@class,"focused")]', entry while focused = $.x 'parent::*/child::*[contains(@class,"focused")]', entry
$.rmClass focused, 'focused' $.rmClass focused, 'focused'
@ -376,7 +375,6 @@ UI = do ->
$.add label, [input, $.tn " #{text}"] $.add label, [input, $.tn " #{text}"]
label label
return { return {
dialog: dialog dialog: dialog
Menu: Menu Menu: Menu

View File

@ -6,7 +6,9 @@
<a href="javascript:;" class="gal-close">×</a> <a href="javascript:;" class="gal-close">×</a>
</span> </span>
<a class="gal-name" target="_blank"></a> <a class="gal-name" target="_blank"></a>
<span class="gal-count"><span class="count"></span> / <span class="total"></span></span> <span class="gal-count">
<span class="count"></span> / <span class="total"></span>
</span>
<div class="gal-prev"></div> <div class="gal-prev"></div>
<div class="gal-image"> <div class="gal-image">
<a href="javascript:;"><img></a> <a href="javascript:;"><img></a>

View File

@ -10,9 +10,9 @@
</div> </div>
<form> <form>
<div class=persona> <div class=persona>
<input name=name data-name=name list="list-name" placeholder=Name class=field size=1> <input name=name data-name=name list="list-name" placeholder=Name class=field size=1>
<input name=email data-name=email list="list-email" placeholder=Options class=field size=1> <input name=email data-name=email list="list-email" placeholder=Options class=field size=1>
<input name=sub data-name=sub list="list-sub" placeholder=Subject class=field size=1> <input name=sub data-name=sub list="list-sub" placeholder=Subject class=field size=1>
</div> </div>
<div class=textarea> <div class=textarea>
<textarea data-name=com placeholder=Comment class=field></textarea> <textarea data-name=com placeholder=Comment class=field></textarea>

View File

@ -13,7 +13,7 @@
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>Custom Board Navigation</span></legend> <legend>Custom Board Navigation</legend>
<div><textarea name=boardnav class=field spellcheck=false></textarea></div> <div><textarea name=boardnav class=field spellcheck=false></textarea></div>
<span class=note>New lines will be converted into spaces.</span><br><br> <span class=note>New lines will be converted into spaces.</span><br><br>
<div class=note>In the following examples for /g/, <code>g</code> can be changed to a different board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Twitter link (<code>@</code>).</div> <div class=note>In the following examples for /g/, <code>g</code> can be changed to a different board ID (<code>a</code>, <code>b</code>, etc...), the current board (<code>current</code>), or the Twitter link (<code>@</code>).</div>

View File

@ -215,7 +215,7 @@ $.one = (el, events, handler) ->
$.event = (event, detail, root=d) -> $.event = (event, detail, root=d) ->
<% if (type === 'userscript') { %> <% if (type === 'userscript') { %>
if detail? and typeof cloneInto is 'function' if detail? and typeof cloneInto is 'function'
detail = cloneInto detail, document.defaultView detail = cloneInto detail, d.defaultView
<% } %> <% } %>
root.dispatchEvent new CustomEvent event, {bubbles: true, detail} root.dispatchEvent new CustomEvent event, {bubbles: true, detail}

View File

@ -2,7 +2,7 @@ class Board
toString: -> @ID toString: -> @ID
constructor: (@ID) -> constructor: (@ID) ->
@threads = new SimpleDict @threads = new SimpleDict()
@posts = new SimpleDict @posts = new SimpleDict()
g.boards[@] = @ g.boards[@] = @

View File

@ -1,5 +1,5 @@
class CatalogThread class CatalogThread
@callbacks = new Callbacks 'CatalogThread' @callbacks = new Callbacks 'Catalog Thread'
toString: -> @ID toString: -> @ID
constructor: (root, @thread) -> constructor: (root, @thread) ->

View File

@ -1,11 +1,11 @@
class Connection class Connection
constructor: (@target, @origin, @cb) -> constructor: (@target, @origin, @cb) ->
$.on window, 'message', @onMessage.bind @ $.on window, 'message', @onMessage
send: (data) -> send: (data) =>
@target.postMessage "#{g.NAMESPACE}#{JSON.stringify data}", @origin @target.postMessage "#{g.NAMESPACE}#{JSON.stringify data}", @origin
onMessage: (e) -> onMessage: (e) =>
return unless e.source is @target and return unless e.source is @target and
e.origin is @origin and e.origin is @origin and
typeof e.data is 'string' and typeof e.data is 'string' and

View File

@ -17,7 +17,7 @@ class Fetcher
clone = post.addClone @context, ($.hasClass @root, 'dialog') clone = post.addClone @context, ($.hasClass @root, 'dialog')
Main.callbackNodes Clone, [clone] Main.callbackNodes Clone, [clone]
# Get rid of the side arrows. # Get rid of the side arrows/stubs.
{nodes} = clone {nodes} = clone
$.rmAll nodes.root $.rmAll nodes.root
$.add nodes.root, nodes.post $.add nodes.root, nodes.post
@ -36,13 +36,14 @@ class Fetcher
{status} = req {status} = req
unless status in [200, 304] unless status in [200, 304]
# The thread can die by the time we check a quote. # The thread can die by the time we check a quote.
unless @archivedPost() return if @archivedPost()
$.addClass @root, 'warning'
@root.textContent = $.addClass @root, 'warning'
if status is 404 @root.textContent =
"Thread No.#{@threadID} 404'd." if status is 404
else "Thread No.#{@threadID} 404'd."
"Error #{req.statusText} (#{req.status})." else
"Error #{req.statusText} (#{req.status})."
return return
{posts} = req.response {posts} = req.response
@ -60,9 +61,10 @@ class Fetcher
return return
# The post can be deleted by the time we check a quote. # The post can be deleted by the time we check a quote.
unless @archivedPost() return if @archivedPost()
$.addClass @root, 'warning'
@root.textContent = "Post No.#{@postID} was not found." $.addClass @root, 'warning'
@root.textContent = "Post No.#{@postID} was not found."
return return
board = g.boards[@boardID] or board = g.boards[@boardID] or

View File

@ -234,10 +234,9 @@ class Post
for clone in @clones for clone in @clones
clone.resurrect() clone.resurrect()
for quotelink in Get.allQuotelinksLinkingTo @ for quotelink in Get.allQuotelinksLinkingTo @ when $.hasClass quotelink, 'deadlink'
if $.hasClass quotelink, 'deadlink' quotelink.textContent = quotelink.textContent.replace '\u00A0(Dead)', ''
quotelink.textContent = quotelink.textContent.replace '\u00A0(Dead)', '' $.rmClass quotelink, 'deadlink'
$.rmClass quotelink, 'deadlink'
return return
collect: -> collect: ->

View File

@ -4,7 +4,7 @@ class Thread
constructor: (@ID, @board) -> constructor: (@ID, @board) ->
@fullID = "#{@board}.#{@ID}" @fullID = "#{@board}.#{@ID}"
@posts = new SimpleDict @posts = new SimpleDict()
@isDead = false @isDead = false
@isHidden = false @isHidden = false
@isOnTop = false @isOnTop = false

View File

@ -1,6 +1,6 @@
ArchiveLink = ArchiveLink =
init: -> init: ->
return if g.VIEW not in ['index', 'thread'] or !Conf['Menu'] or !Conf['Archive Link'] return unless g.VIEW in ['index', 'thread'] and Conf['Menu'] and Conf['Archive Link']
div = $.el 'div', div = $.el 'div',
textContent: 'Archive' textContent: 'Archive'

View File

@ -1,6 +1,6 @@
DeleteLink = DeleteLink =
init: -> init: ->
return if g.VIEW not in ['index', 'thread'] or !Conf['Menu'] or !Conf['Delete Link'] return unless g.VIEW in ['index', 'thread'] and Conf['Menu'] and Conf['Delete Link']
div = $.el 'div', div = $.el 'div',
className: 'delete-link' className: 'delete-link'

View File

@ -1,6 +1,6 @@
DownloadLink = DownloadLink =
init: -> init: ->
return if g.VIEW not in ['index', 'thread'] or !Conf['Menu'] or !Conf['Download Link'] return unless g.VIEW in ['index', 'thread'] and Conf['Menu'] and Conf['Download Link']
a = $.el 'a', a = $.el 'a',
className: 'download-link' className: 'download-link'

View File

@ -1,6 +1,6 @@
Menu = Menu =
init: -> init: ->
return if g.VIEW not in ['index', 'thread'] or !Conf['Menu'] return unless g.VIEW in ['index', 'thread'] and Conf['Menu']
@button = $.el 'a', @button = $.el 'a',
className: 'menu-button' className: 'menu-button'

View File

@ -1,6 +1,6 @@
ReportLink = ReportLink =
init: -> init: ->
return if g.VIEW not in ['index', 'thread'] or !Conf['Menu'] or !Conf['Report Link'] return unless g.VIEW in ['index', 'thread'] and Conf['Menu'] and Conf['Report Link']
a = $.el 'a', a = $.el 'a',
className: 'report-link' className: 'report-link'

View File

@ -16,16 +16,15 @@ Banner =
if g.BOARD.ID isnt 'f' and g.VIEW is 'thread' and Conf['Remove Thread Excerpt'] if g.BOARD.ID isnt 'f' and g.VIEW is 'thread' and Conf['Remove Thread Excerpt']
Banner.setTitle children[1].textContent Banner.setTitle children[1].textContent
i = 0 for child, i in children
while child = children[i++] if i is 0
if i is 1
child.title = "Click to change" child.title = "Click to change"
$.on child, 'click', Banner.cb.toggle $.on child, 'click', Banner.cb.toggle
continue continue
if Conf['Custom Board Titles'] if Conf['Custom Board Titles']
Banner.custom(child).title = "Ctrl/\u2318+click to edit board #{if i is 3 Banner.custom(child).title = "Ctrl/\u2318+click to edit board #{if i is 2
'sub' 'sub'
else else
''}title" ''}title"

View File

@ -13,5 +13,5 @@ CustomCSS =
update: -> update: ->
unless @style unless @style
@addStyle() return @addStyle()
@style.textContent = Conf['usercss'] @style.textContent = Conf['usercss']

View File

@ -1,6 +1,6 @@
ExpandComment = ExpandComment =
init: -> init: ->
return if g.VIEW isnt 'index' or !Conf['Comment Expansion'] return if g.VIEW isnt 'index' or !Conf['Comment Expansion'] or Conf['JSON Navigation']
@callbacks.push Fourchan.code if g.BOARD.ID is 'g' @callbacks.push Fourchan.code if g.BOARD.ID is 'g'
@callbacks.push Fourchan.math if g.BOARD.ID is 'sci' @callbacks.push Fourchan.math if g.BOARD.ID is 'sci'

View File

@ -1,28 +1,26 @@
IDColor = IDColor =
init: -> init: ->
return if g.VIEW not in ['index', 'thread'] or not Conf['Color User IDs'] return unless g.VIEW in ['index', 'thread'] and Conf['Color User IDs']
@ids = {} @ids = {
Heaven: [0, 0, 0, '#fff']
}
Post.callbacks.push Post.callbacks.push
name: 'Color User IDs' name: 'Color User IDs'
cb: @node cb: @node
node: -> node: ->
return if @isClone or not uid = @info.uniqueID return if @isClone or !((uid = @info.uniqueID) and (span = $ 'span.hand', @nodes.uniqueID))
span = $ '.hand', @nodes.uniqueID
return unless span and span.nodeName is 'SPAN' rgb = IDColor.ids[uid] or IDColor.compute uid
rgb = IDColor.compute uid
# Style the damn node. # Style the damn node.
{style} = span {style} = span
style.color = rgb[3] style.color = rgb[3]
style.backgroundColor = "rgb(#{rgb[0]},#{rgb[1]},#{rgb[2]})" style.backgroundColor = "rgb(#{rgb[0]},#{rgb[1]},#{rgb[2]})"
$.addClass span, 'painted' $.addClass span, 'painted'
span.title = 'Highlight posts by this ID'
compute: (uid) -> compute: (uid) ->
return IDColor.ids[uid] if IDColor.ids[uid]
# Convert chars to integers, bitshift and math to create a larger integer # Convert chars to integers, bitshift and math to create a larger integer
# Create a nice string of binary # Create a nice string of binary
hash = IDColor.hash uid hash = IDColor.hash uid
@ -35,7 +33,7 @@ IDColor =
] ]
# Weight color luminance values, assign a font color that should be readable. # Weight color luminance values, assign a font color that should be readable.
rgb[3] = if (rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114) > 125 rgb.push if (rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114) > 125
'#000' '#000'
else else
'#fff' '#fff'

View File

@ -46,7 +46,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.nodes and !QR.nodes.el.hidden and window.getComputedStyle(QR.nodes.form).display isnt 'none' else if QR.nodes and not (QR.nodes.el.hidden or window.getComputedStyle(QR.nodes.form).display is 'none')
if Conf['Persistent QR'] if Conf['Persistent QR']
QR.hide() QR.hide()
else else
@ -139,19 +139,19 @@ Keybinds =
Index.cycleSortType() Index.cycleSortType()
# Thread Navigation # Thread Navigation
when Conf['Next thread'] when Conf['Next thread']
return if g.VIEW isnt 'index' or !threadRoot return unless g.VIEW is 'index' and threadRoot
Nav.scroll +1 Nav.scroll +1
when Conf['Previous thread'] when Conf['Previous thread']
return if g.VIEW isnt 'index' or !threadRoot return unless g.VIEW is 'index' and threadRoot
Nav.scroll -1 Nav.scroll -1
when Conf['Expand thread'] when Conf['Expand thread']
return if g.VIEW isnt 'index' or !threadRoot return unless g.VIEW is 'index' and threadRoot
ExpandThread.toggle thread ExpandThread.toggle thread
when Conf['Open thread'] when Conf['Open thread']
return if g.VIEW isnt 'index' or !threadRoot return unless g.VIEW is 'index' and threadRoot
Keybinds.open thread Keybinds.open thread
when Conf['Open thread tab'] when Conf['Open thread tab']
return if g.VIEW isnt 'index' or !threadRoot return unless g.VIEW is 'index' and threadRoot
Keybinds.open thread, true Keybinds.open thread, true
# Reply Navigation # Reply Navigation
when Conf['Next reply'] when Conf['Next reply']

View File

@ -1,6 +1,6 @@
Time = Time =
init: -> init: ->
return if g.VIEW not in ['index', 'thread'] or !Conf['Time Formatting'] return unless g.VIEW in ['index', 'thread'] and Conf['Time Formatting']
Post.callbacks.push Post.callbacks.push
name: 'Time Formatting' name: 'Time Formatting'

View File

@ -7,7 +7,7 @@ MarkNewIPs =
node: -> node: ->
MarkNewIPs.ipCount = @ipCount MarkNewIPs.ipCount = @ipCount
MarkNewIPs.postIDs = @posts.keys.map (x) -> +x MarkNewIPs.postIDs = (+x for x in @posts.keys)
$.on d, 'ThreadUpdate', MarkNewIPs.onUpdate $.on d, 'ThreadUpdate', MarkNewIPs.onUpdate
onUpdate: (e) -> onUpdate: (e) ->
@ -35,11 +35,7 @@ MarkNewIPs =
suffix = if (ipCount // 10) % 10 is 1 suffix = if (ipCount // 10) % 10 is 1
'th' 'th'
else else
switch ipCount % 10 ['st', 'nd', 'rd'][ipCount % 10 - 1] or 'th' # fuck switches
when 1 then 'st'
when 2 then 'nd'
when 3 then 'rd'
else 'th'
counter = $.el 'span', counter = $.el 'span',
className: 'ip-counter' className: 'ip-counter'
textContent: "(#{ipCount})" textContent: "(#{ipCount})"

View File

@ -84,5 +84,5 @@ ThreadStats =
ThreadStats.pageCountEl.textContent = page.page ThreadStats.pageCountEl.textContent = page.page
(if page.page is @response.length then $.addClass else $.rmClass) ThreadStats.pageCountEl, 'warning' (if page.page is @response.length then $.addClass else $.rmClass) ThreadStats.pageCountEl, 'warning'
# Thread data may be stale (modification date given < time of last post). If so, try again on next thread update. # Thread data may be stale (modification date given < time of last post). If so, try again on next thread update.
ThreadStats.lastPageUpdate = new Date thread.last_modified * 1000 ThreadStats.lastPageUpdate = new Date thread.last_modified * $.SECOND
return return

View File

@ -12,7 +12,7 @@ ThreadUpdater =
@dialog = sc = UI.dialog 'updater', 'bottom: 0px; left: 0px;', @dialog = sc = UI.dialog 'updater', 'bottom: 0px; left: 0px;',
<%= html('<div class="move"></div><span id="update-status"></span><span id="update-timer" title="Update now"></span>') %> <%= html('<div class="move"></div><span id="update-status"></span><span id="update-timer" title="Update now"></span>') %>
$.addClass doc, 'float' $.addClass doc, 'float'
$.ready => $.ready ->
$.add d.body, sc $.add d.body, sc
@checkPostCount = 0 @checkPostCount = 0
@ -120,11 +120,11 @@ ThreadUpdater =
-> not d.hidden -> not d.hidden
autoUpdate: (e) -> autoUpdate: (e) ->
ThreadUpdater.count ThreadUpdater.isUpdating = @checked ThreadUpdater.count ThreadUpdater.isUpdating = @checked
interval: -> interval: (e) ->
val = parseInt @value, 10 val = parseInt @value, 10
if val < 1 then val = 1 if val < 1 then val = 1
ThreadUpdater.interval = @value = val ThreadUpdater.interval = @value = val
$.cb.value.call @ $.cb.value.call @ if e
load: (e) -> load: (e) ->
{req} = ThreadUpdater {req} = ThreadUpdater
switch req.status switch req.status
@ -239,6 +239,7 @@ ThreadUpdater =
ThreadUpdater.req?.abort() ThreadUpdater.req?.abort()
ThreadUpdater.req = $.ajax "//a.4cdn.org/#{ThreadUpdater.thread.board}/thread/#{ThreadUpdater.thread}.json", ThreadUpdater.req = $.ajax "//a.4cdn.org/#{ThreadUpdater.thread.board}/thread/#{ThreadUpdater.thread}.json",
onloadend: ThreadUpdater.cb.load onloadend: ThreadUpdater.cb.load
timeout: $.MINUTE
, ,
whenModified: true whenModified: true
@ -344,7 +345,7 @@ ThreadUpdater =
$.event 'ThreadUpdate', $.event 'ThreadUpdate',
404: false 404: false
threadID: ThreadUpdater.thread.fullID threadID: ThreadUpdater.thread.fullID
newPosts: posts.map (post) -> post.fullID newPosts: (post.fullID for post in posts)
postCount: OP.replies + 1 postCount: OP.replies + 1
fileCount: OP.images + (!!ThreadUpdater.thread.OP.file and !ThreadUpdater.thread.OP.file.isDead) fileCount: OP.images + (!!ThreadUpdater.thread.OP.file and !ThreadUpdater.thread.OP.file.isDead)
ipCount: OP.unique_ips ipCount: OP.unique_ips

View File

@ -14,7 +14,7 @@ ThreadWatcher =
@status = $ '#watcher-status', @dialog @status = $ '#watcher-status', @dialog
@list = @dialog.lastElementChild @list = @dialog.lastElementChild
@refreshButton = $ '.move > .refresh', @dialog @refreshButton = $ '.refresh', @dialog
@unreaddb = Unread.db or new DataBoard 'lastReadPosts' @unreaddb = Unread.db or new DataBoard 'lastReadPosts'
$.on d, 'QRPostSuccessful', @cb.post $.on d, 'QRPostSuccessful', @cb.post
@ -36,13 +36,6 @@ ThreadWatcher =
ThreadWatcher.fetchAuto() ThreadWatcher.fetchAuto()
Post.callbacks.push
name: 'Thread Watcher'
cb: @node
CatalogThread.callbacks.push
name: 'Thread Watcher'
cb: @catalogNode
if g.VIEW is 'index' and Conf['JSON Navigation'] and Conf['Menu'] and g.BOARD.ID isnt 'f' if g.VIEW is 'index' and Conf['JSON Navigation'] and Conf['Menu'] and g.BOARD.ID isnt 'f'
Menu.menu.addEntry Menu.menu.addEntry
el: $.el 'a', href: 'javascript:;' el: $.el 'a', href: 'javascript:;'
@ -60,6 +53,13 @@ ThreadWatcher =
$.on @el, 'click', @cb $.on @el, 'click', @cb
true true
Post.callbacks.push
name: 'Thread Watcher'
cb: @node
CatalogThread.callbacks.push
name: 'Thread Watcher'
cb: @catalogNode
isWatched: (thread) -> isWatched: (thread) ->
ThreadWatcher.db?.get {boardID: thread.board.ID, threadID: thread.ID} ThreadWatcher.db?.get {boardID: thread.board.ID, threadID: thread.ID}

View File

@ -1,20 +1,21 @@
Unread = Unread =
init: -> init: ->
return if g.VIEW isnt 'thread' or return unless g.VIEW is 'thread' and (
!Conf['Unread Count'] and Conf['Unread Count'] or
!Conf['Unread Favicon'] and Conf['Unread Favicon'] or
!Conf['Unread Line'] and Conf['Unread Line'] or
!Conf['Scroll to Last Read Post'] and Conf['Scroll to Last Read Post'] or
!Conf['Thread Watcher'] and Conf['Thread Watcher'] or
!Conf['Desktop Notifications'] and Conf['Desktop Notifications'] or
!Conf['Quote Threading'] Conf['Quote Threading']
)
@db = new DataBoard 'lastReadPosts', @sync @db = new DataBoard 'lastReadPosts', @sync
@hr = $.el 'hr', @hr = $.el 'hr',
id: 'unread-line' id: 'unread-line'
@posts = new Set @posts = new Set()
@postsQuotingYou = new Set @postsQuotingYou = new Set()
@order = new RandomAccessList @order = new RandomAccessList()
@position = null @position = null
Thread.callbacks.push Thread.callbacks.push

View File

@ -9,6 +9,7 @@ Captcha.noscript =
container = $.el 'div', container = $.el 'div',
className: 'captcha-img' className: 'captcha-img'
title: 'Reload reCAPTCHA' title: 'Reload reCAPTCHA'
input = $.el 'input', input = $.el 'input',
className: 'captcha-input field' className: 'captcha-input field'
title: 'Verification' title: 'Verification'
@ -103,7 +104,7 @@ Captcha.noscript =
$.off input, 'focus click', @cb.focus $.off input, 'focus click', @cb.focus
if QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight if QR.nodes.el.getBoundingClientRect().bottom > doc.clientHeight
QR.nodes.el.style.top = null QR.nodes.el.style.top = ''
QR.nodes.el.style.bottom = '0px' QR.nodes.el.style.bottom = '0px'
destroy: -> destroy: ->

View File

@ -13,6 +13,17 @@ QR =
noscript = Conf['Force Noscript Captcha'] or !doc.dataset.jsEnabled noscript = Conf['Force Noscript Captcha'] or !doc.dataset.jsEnabled
@captcha = Captcha[if noscript then 'noscript' else 'v2'] @captcha = Captcha[if noscript then 'noscript' else 'v2']
$.on d, '4chanXInitFinished', @initReady
window.addEventListener 'focus', @focus, true
window.addEventListener 'blur', @focus, true
# We don't receive blur events from captcha iframe.
$.on d, 'click', @focus
Post.callbacks.push
name: 'Quick Reply'
cb: @node
if Conf['QR Shortcut'] if Conf['QR Shortcut']
@shortcut = sc = $.el 'a', @shortcut = sc = $.el 'a',
className: 'qr-shortcut fa fa-comment-o disabled' className: 'qr-shortcut fa fa-comment-o disabled'
@ -35,17 +46,6 @@ QR =
# Prevent unnecessary loading of fallback iframe. # Prevent unnecessary loading of fallback iframe.
$.onExists doc, '#postForm noscript', true, $.rm $.onExists doc, '#postForm noscript', true, $.rm
$.on d, '4chanXInitFinished', @initReady
window.addEventListener 'focus', @focus, true
window.addEventListener 'blur', @focus, true
# We don't receive blur events from captcha iframe.
$.on d, 'click', @focus
Post.callbacks.push
name: 'Quick Reply'
cb: @node
initReady: -> initReady: ->
$.off d, '4chanXInitFinished', @initReady $.off d, '4chanXInitFinished', @initReady
QR.postingIsEnabled = !!$.id 'postForm' QR.postingIsEnabled = !!$.id 'postForm'
@ -318,7 +318,7 @@ QR =
QR.open() QR.open()
QR.handleFiles files QR.handleFiles files
$.addClass QR.nodes.el, 'dump' $.addClass QR.nodes.el, 'dump'
handleUrl: -> handleUrl: ->
url = prompt 'Enter a URL:' url = prompt 'Enter a URL:'
return if url is null return if url is null
@ -429,7 +429,6 @@ QR =
if (e.ctrlKey or e.metaKey) and e.type is 'click' if (e.ctrlKey or e.metaKey) and e.type is 'click'
$.addClass QR.nodes.filename, 'edit' $.addClass QR.nodes.filename, 'edit'
QR.nodes.filename.focus() QR.nodes.filename.focus()
return $.on QR.nodes.filename, 'blur', -> $.rmClass QR.nodes.filename, 'edit'
return if e.target.nodeName is 'INPUT' or (e.keyCode and e.keyCode not in [32, 13]) or e.ctrlKey return if e.target.nodeName is 'INPUT' or (e.keyCode and e.keyCode not in [32, 13]) or e.ctrlKey
e.preventDefault() e.preventDefault()
QR.nodes.fileInput.click() QR.nodes.fileInput.click()
@ -437,11 +436,11 @@ QR =
generatePostableThreadsList: -> generatePostableThreadsList: ->
return unless QR.nodes return unless QR.nodes
list = QR.nodes.thread list = QR.nodes.thread
options = [list.firstChild] options = [list.firstElementChild]
for thread in g.BOARD.threads.keys for thread in g.BOARD.threads.keys
options.push $.el 'option', options.push $.el 'option',
value: thread value: thread
textContent: "Thread No.#{thread}" textContent: "No.#{thread}"
val = list.value val = list.value
$.rmAll list $.rmAll list
$.add list, options $.add list, options
@ -548,6 +547,7 @@ QR =
$.on nodes.urlButton, 'click', QR.handleUrl $.on nodes.urlButton, 'click', QR.handleUrl
$.on nodes.addPost, 'click', -> new QR.post true $.on nodes.addPost, 'click', -> new QR.post true
$.on nodes.form, 'submit', QR.submit $.on nodes.form, 'submit', QR.submit
$.on nodes.filename, 'blur', -> $.rmClass @, 'edit'
$.on nodes.fileRM, 'click', -> QR.selected.rmFile() $.on nodes.fileRM, 'click', -> QR.selected.rmFile()
$.on nodes.fileExtras, 'click', (e) -> e.stopPropagation() $.on nodes.fileExtras, 'click', (e) -> e.stopPropagation()
$.on nodes.spoiler, 'change', -> QR.selected.nodes.spoiler.click() $.on nodes.spoiler, 'change', -> QR.selected.nodes.spoiler.click()
@ -665,7 +665,7 @@ QR =
QR.status() QR.status()
QR.error $.el 'span', QR.error $.el 'span',
<%= html( <%= html(
'${g.NAME} encountered an error while posting. ' + meta.name + ' encountered an error while posting. ' +
'[<a href="//4chan.org/banned" target="_blank">Banned?</a>] ' + '[<a href="//4chan.org/banned" target="_blank">Banned?</a>] ' +
'[<a href="${g.FAQ}#what-does-4chan-x-encountered-an-error-while-posting-please-try-again-mean" target="_blank">More info</a>]' '[<a href="${g.FAQ}#what-does-4chan-x-encountered-an-error-while-posting-please-try-again-mean" target="_blank">More info</a>]'
) %> ) %>