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:
commit
12bc5d1612
@ -1,6 +1,6 @@
|
||||
Anonymize =
|
||||
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'
|
||||
|
||||
Post.callbacks.push
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
Filter =
|
||||
filters: {}
|
||||
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']
|
||||
$.addClass doc, 'hide-backlinks'
|
||||
@ -100,10 +100,8 @@ Filter =
|
||||
|
||||
node: ->
|
||||
return if @isClone or @isFetchedQuote
|
||||
for key of Filter.filters
|
||||
value = Filter[key] @
|
||||
for key of Filter.filters when (value = Filter[key] @) isnt false
|
||||
# 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
|
||||
# Hide
|
||||
@ -171,7 +169,7 @@ Filter =
|
||||
|
||||
menu:
|
||||
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',
|
||||
textContent: 'Filter'
|
||||
|
||||
@ -22,10 +22,9 @@ Recursive =
|
||||
|
||||
rm: (recursive, post) ->
|
||||
return unless obj = Recursive.recursives[post.fullID]
|
||||
for rec, i in obj.recursives
|
||||
if rec is recursive
|
||||
obj.recursives.splice i, 1
|
||||
obj.args.splice i, 1
|
||||
for rec, i in obj.recursives when rec is recursive
|
||||
obj.recursives.splice i, 1
|
||||
obj.args.splice i, 1
|
||||
return
|
||||
|
||||
apply: (recursive, post, args...) ->
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
Build =
|
||||
staticPath: '//s.4cdn.org/image/'
|
||||
gifIcon: if window.devicePixelRatio >= 2 then '@2x.gif' else '.gif'
|
||||
initPixelRatio: window.devicePixelRatio
|
||||
spoilerRange: {}
|
||||
unescape: (text) ->
|
||||
return text unless text?
|
||||
text.replace(/<[^>]*>/g, '').replace /&(amp|#039|quot|lt|gt);/g, (c) ->
|
||||
{'&': '&', ''': "'", '"': '"', '<': '<', '>': '>'}[c]
|
||||
shortFilename: (filename, isReply) ->
|
||||
shortFilename: (filename) ->
|
||||
threshold = 30
|
||||
ext = filename.match(/\.?[^\.]*$/)[0]
|
||||
if filename.length - ext.length > threshold
|
||||
@ -15,8 +14,8 @@ Build =
|
||||
else
|
||||
filename
|
||||
thumbRotate: do ->
|
||||
n = 0
|
||||
-> n = (n + 1) % 2
|
||||
t = 0
|
||||
-> t = (if t then 0 else 1)
|
||||
sameThread: (boardID, threadID) ->
|
||||
g.VIEW is 'thread' and g.BOARD.ID is boardID and g.THREADID is +threadID
|
||||
postURL: (boardID, threadID, postID) ->
|
||||
@ -83,8 +82,7 @@ Build =
|
||||
name or= ''
|
||||
subject or= ''
|
||||
isOP = postID is threadID
|
||||
|
||||
retina = if Build.initPixelRatio >= 2 then '@2x' else ''
|
||||
{staticPath, gifIcon} = Build
|
||||
|
||||
### Name Block ###
|
||||
|
||||
@ -92,19 +90,19 @@ Build =
|
||||
when 'admin', 'admin_highlight'
|
||||
capcodeClass = ' capcodeAdmin'
|
||||
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'
|
||||
capcodeClass = ' capcodeMod'
|
||||
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'
|
||||
capcodeClass = ' capcodeDeveloper'
|
||||
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'
|
||||
capcodeClass = ' capcodeManager'
|
||||
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
|
||||
capcodeClass = ''
|
||||
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)
|
||||
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'
|
||||
<%= html(' <span>[<a href="/${boardID}/thread/${threadID}" class="replylink">Reply</a>]</span>') %>
|
||||
@ -181,7 +179,7 @@ Build =
|
||||
fileCont = if file?.isDeleted
|
||||
<%= html(
|
||||
'<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>'
|
||||
) %>
|
||||
else if file and boardID is 'f'
|
||||
@ -196,9 +194,9 @@ Build =
|
||||
shortFilename = 'Spoiler Image'
|
||||
if spoilerRange = Build.spoilerRange[boardID]
|
||||
# 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
|
||||
fileThumb = '//s.4cdn.org/image/spoiler.png'
|
||||
fileThumb = "#{staticPath}spoiler.png"
|
||||
file.twidth = file.theight = 100
|
||||
else
|
||||
shortFilename = Build.shortFilename file.name, !isOP
|
||||
|
||||
@ -53,7 +53,7 @@ Config =
|
||||
]
|
||||
'Comment Expansion': [
|
||||
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': [
|
||||
true
|
||||
@ -118,6 +118,11 @@ Config =
|
||||
true
|
||||
'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': [
|
||||
true
|
||||
'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.'
|
||||
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':
|
||||
'Anonymize': [
|
||||
@ -384,8 +384,8 @@ Config =
|
||||
'All-in-one form to reply, create threads, automate dumping and more.'
|
||||
]
|
||||
'QR Shortcut': [
|
||||
true,
|
||||
'Adds a small [QR] link in the header.'
|
||||
true
|
||||
'Add a shortcut to the header to toggle the QR.'
|
||||
1
|
||||
]
|
||||
'Persistent QR': [
|
||||
@ -598,7 +598,7 @@ Config =
|
||||
]
|
||||
'Auto Prune': [
|
||||
false
|
||||
'Automatically prune dead threads.'
|
||||
'Automatically remove dead threads.'
|
||||
]
|
||||
'Show Unread Count': [
|
||||
true
|
||||
|
||||
@ -9,6 +9,11 @@ g =
|
||||
FAQ: '<%= meta.faq %>'
|
||||
CHANGELOG: '<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md'
|
||||
boards: {}
|
||||
E = (text) ->
|
||||
(text+'').replace /[&"'<>]/g, (x) ->
|
||||
{'&': '&', "'": ''', '"': '"', '<': '<', '>': '>'}[x]
|
||||
|
||||
E = do ->
|
||||
str = {'&': '&', "'": ''', '"': '"', '<': '<', '>': '>'}
|
||||
r = String::replace
|
||||
regex = /[&"'<>]/g
|
||||
fn = (x) ->
|
||||
str[x]
|
||||
(text) -> r.call text, regex, fn
|
||||
|
||||
@ -3,8 +3,8 @@ Main =
|
||||
if location.hostname is 'www.google.com'
|
||||
return $.ready -> Captcha.noscript.initFrame()
|
||||
|
||||
g.threads = new SimpleDict
|
||||
g.posts = new SimpleDict
|
||||
g.threads = new SimpleDict()
|
||||
g.posts = new SimpleDict()
|
||||
|
||||
pathname = location.pathname.split '/'
|
||||
g.BOARD = new Board pathname[1]
|
||||
@ -90,6 +90,7 @@ Main =
|
||||
error: err
|
||||
# finally
|
||||
# c.timeEnd "#{name} initialization"
|
||||
|
||||
# c.timeEnd 'All initializations'
|
||||
|
||||
$.ready Main.initReady
|
||||
@ -163,19 +164,19 @@ Main =
|
||||
Settings.open()
|
||||
$.set 'previousversion', g.VERSION
|
||||
|
||||
return unless Conf['Show Support Message']
|
||||
<% if (type === 'userscript') { %>
|
||||
GMver = GM_info.version.split '.'
|
||||
for v, i in "<%= meta.min.greasemonkey %>".split '.'
|
||||
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
|
||||
break
|
||||
<% } %>
|
||||
if Conf['Show Support Message']
|
||||
<% if (type === 'userscript') { %>
|
||||
GMver = GM_info.version.split '.'
|
||||
for v, i in "<%= meta.min.greasemonkey %>".split '.'
|
||||
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
|
||||
break
|
||||
<% } %>
|
||||
|
||||
try
|
||||
localStorage.getItem '4chan-settings'
|
||||
catch err
|
||||
new Notice 'warning', 'Cookies need to be enabled on 4chan for <%= meta.name %> to operate properly.', 30
|
||||
try
|
||||
localStorage.getItem '4chan-settings'
|
||||
catch err
|
||||
new Notice 'warning', 'Cookies need to be enabled on 4chan for <%= meta.name %> to operate properly.', 30
|
||||
|
||||
initThread: ->
|
||||
if board = $ '.board'
|
||||
|
||||
@ -10,14 +10,16 @@ Settings =
|
||||
|
||||
Header.addShortcut link
|
||||
|
||||
Settings.addSection 'Main', Settings.main
|
||||
Settings.addSection 'Filter', Settings.filter
|
||||
Settings.addSection 'Sauce', Settings.sauce
|
||||
Settings.addSection 'Advanced', Settings.advanced
|
||||
Settings.addSection 'Keybinds', Settings.keybinds
|
||||
add = @addSection
|
||||
|
||||
add 'Main', @main
|
||||
add 'Filter', @filter
|
||||
add 'Sauce', @sauce
|
||||
add 'Advanced', @advanced
|
||||
add 'Keybinds', @keybinds
|
||||
|
||||
$.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']
|
||||
settings = JSON.parse(localStorage.getItem '4chan-settings') or {}
|
||||
@ -26,22 +28,22 @@ Settings =
|
||||
localStorage.setItem '4chan-settings', JSON.stringify settings
|
||||
|
||||
open: (openSection) ->
|
||||
return if Settings.dialog
|
||||
return if Settings.overlay
|
||||
$.event 'CloseMenu'
|
||||
|
||||
Settings.overlay = overlay = $.el 'div',
|
||||
id: 'overlay'
|
||||
|
||||
Settings.dialog = dialog = $.el 'div',
|
||||
id: 'fourchanx-settings'
|
||||
className: 'dialog'
|
||||
$.extend dialog, <%= importHTML('Settings/Settings') %>
|
||||
$('a[href$="/CHANGELOG.md"]', dialog).textContent = g.VERSION
|
||||
|
||||
$.on $('.export', Settings.dialog), 'click', Settings.export
|
||||
$.on $('.import', Settings.dialog), 'click', Settings.import
|
||||
$.on $('.reset', Settings.dialog), 'click', Settings.reset
|
||||
$.on $('input', Settings.dialog), 'change', Settings.onImport
|
||||
Settings.overlay = overlay = $.el 'div',
|
||||
id: 'overlay'
|
||||
|
||||
$.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 = []
|
||||
for section in Settings.sections
|
||||
@ -54,7 +56,7 @@ Settings =
|
||||
sectionToOpen = link if section.title is openSection
|
||||
links.pop()
|
||||
$.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 overlay, 'click', Settings.close
|
||||
@ -103,8 +105,9 @@ Settings =
|
||||
div = $.el 'div',
|
||||
<%= html('<label><input type="checkbox" name="${key}">${key}</label><span class="description">: ${description}</span>') %>
|
||||
input = $ 'input', div
|
||||
$.on input, 'change', $.cb.checked
|
||||
$.on input, 'change', -> @parentNode.parentNode.dataset.checked = @checked
|
||||
$.on input, 'change', ->
|
||||
@parentNode.parentNode.dataset.checked = @checked
|
||||
$.cb.checked.call @
|
||||
items[key] = Conf[key]
|
||||
inputs[key] = input
|
||||
level = arr[2] or 0
|
||||
@ -160,7 +163,7 @@ Settings =
|
||||
<% } %>
|
||||
a.click()
|
||||
import: ->
|
||||
$('input', @parentNode).click()
|
||||
$('input[type=file]', @parentNode).click()
|
||||
|
||||
onImport: ->
|
||||
return unless file = @files[0]
|
||||
@ -305,17 +308,21 @@ Settings =
|
||||
items = {}
|
||||
inputs = {}
|
||||
for name in ['boardnav', 'time', 'backlink', 'fileInfo', 'favicon', 'usercss']
|
||||
input = $ "[name=#{name}]", section
|
||||
input = $ "[name='#{name}']", section
|
||||
items[name] = Conf[name]
|
||||
inputs[name] = input
|
||||
event = if name in ['favicon', 'usercss']
|
||||
'change'
|
||||
else
|
||||
'input'
|
||||
$.on input, event, $.cb.value
|
||||
if name is 'usercss'
|
||||
$.on input, 'change', $.cb.value
|
||||
else if name is 'favicon'
|
||||
$.on input, 'change', $.cb.value
|
||||
$.on input, 'change', Settings[name]
|
||||
else
|
||||
$.on input, 'input', $.cb.value
|
||||
$.on input, 'input', Settings[name]
|
||||
|
||||
# Quick Reply Personas
|
||||
ta = $ '.personafield', section
|
||||
|
||||
$.get 'QR.personas', Conf['QR.personas'], (item) ->
|
||||
ta.value = item['QR.personas']
|
||||
$.on ta, 'change', $.cb.value
|
||||
@ -325,7 +332,6 @@ Settings =
|
||||
input = inputs[key]
|
||||
input.value = val
|
||||
continue if key is 'usercss'
|
||||
$.on input, event, Settings[key]
|
||||
Settings[key].call input
|
||||
return
|
||||
|
||||
@ -484,6 +490,7 @@ Settings =
|
||||
inputs[key] = input
|
||||
$.on input, 'keydown', Settings.keybind
|
||||
$.add tbody, tr
|
||||
|
||||
$.get items, (items) ->
|
||||
for key, val of items
|
||||
inputs[key].value = val
|
||||
|
||||
@ -65,8 +65,7 @@ UI = do ->
|
||||
|
||||
$.addClass lastToggledButton, 'active'
|
||||
|
||||
$.on d, 'click', @close
|
||||
$.on d, 'CloseMenu', @close
|
||||
$.one d, 'click scroll CloseMenu', @close
|
||||
$.add button, menu
|
||||
|
||||
# Position
|
||||
@ -121,7 +120,6 @@ UI = do ->
|
||||
$.rmClass lastToggledButton, 'active'
|
||||
currentMenu = null
|
||||
lastToggledButton = null
|
||||
$.off d, 'click CloseMenu', @close
|
||||
|
||||
findNextEntry: (entry, direction) ->
|
||||
entries = [entry.parentNode.children...]
|
||||
@ -162,6 +160,7 @@ UI = do ->
|
||||
onFocus: (e) =>
|
||||
e.stopPropagation()
|
||||
@focus e.target
|
||||
|
||||
focus: (entry) ->
|
||||
while focused = $.x 'parent::*/child::*[contains(@class,"focused")]', entry
|
||||
$.rmClass focused, 'focused'
|
||||
@ -376,7 +375,6 @@ UI = do ->
|
||||
$.add label, [input, $.tn " #{text}"]
|
||||
label
|
||||
|
||||
|
||||
return {
|
||||
dialog: dialog
|
||||
Menu: Menu
|
||||
|
||||
@ -6,7 +6,9 @@
|
||||
<a href="javascript:;" class="gal-close">×</a>
|
||||
</span>
|
||||
<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-image">
|
||||
<a href="javascript:;"><img></a>
|
||||
|
||||
@ -10,9 +10,9 @@
|
||||
</div>
|
||||
<form>
|
||||
<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=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 class=textarea>
|
||||
<textarea data-name=com placeholder=Comment class=field></textarea>
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Custom Board Navigation</span></legend>
|
||||
<legend>Custom Board Navigation</legend>
|
||||
<div><textarea name=boardnav class=field spellcheck=false></textarea></div>
|
||||
<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>
|
||||
|
||||
@ -215,7 +215,7 @@ $.one = (el, events, handler) ->
|
||||
$.event = (event, detail, root=d) ->
|
||||
<% if (type === 'userscript') { %>
|
||||
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}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ class Board
|
||||
toString: -> @ID
|
||||
|
||||
constructor: (@ID) ->
|
||||
@threads = new SimpleDict
|
||||
@posts = new SimpleDict
|
||||
@threads = new SimpleDict()
|
||||
@posts = new SimpleDict()
|
||||
|
||||
g.boards[@] = @
|
||||
g.boards[@] = @
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
class CatalogThread
|
||||
@callbacks = new Callbacks 'CatalogThread'
|
||||
@callbacks = new Callbacks 'Catalog Thread'
|
||||
toString: -> @ID
|
||||
|
||||
constructor: (root, @thread) ->
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
class Connection
|
||||
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
|
||||
|
||||
onMessage: (e) ->
|
||||
onMessage: (e) =>
|
||||
return unless e.source is @target and
|
||||
e.origin is @origin and
|
||||
typeof e.data is 'string' and
|
||||
|
||||
@ -17,7 +17,7 @@ class Fetcher
|
||||
clone = post.addClone @context, ($.hasClass @root, 'dialog')
|
||||
Main.callbackNodes Clone, [clone]
|
||||
|
||||
# Get rid of the side arrows.
|
||||
# Get rid of the side arrows/stubs.
|
||||
{nodes} = clone
|
||||
$.rmAll nodes.root
|
||||
$.add nodes.root, nodes.post
|
||||
@ -36,13 +36,14 @@ class Fetcher
|
||||
{status} = req
|
||||
unless status in [200, 304]
|
||||
# The thread can die by the time we check a quote.
|
||||
unless @archivedPost()
|
||||
$.addClass @root, 'warning'
|
||||
@root.textContent =
|
||||
if status is 404
|
||||
"Thread No.#{@threadID} 404'd."
|
||||
else
|
||||
"Error #{req.statusText} (#{req.status})."
|
||||
return if @archivedPost()
|
||||
|
||||
$.addClass @root, 'warning'
|
||||
@root.textContent =
|
||||
if status is 404
|
||||
"Thread No.#{@threadID} 404'd."
|
||||
else
|
||||
"Error #{req.statusText} (#{req.status})."
|
||||
return
|
||||
|
||||
{posts} = req.response
|
||||
@ -60,9 +61,10 @@ class Fetcher
|
||||
return
|
||||
|
||||
# The post can be deleted by the time we check a quote.
|
||||
unless @archivedPost()
|
||||
$.addClass @root, 'warning'
|
||||
@root.textContent = "Post No.#{@postID} was not found."
|
||||
return if @archivedPost()
|
||||
|
||||
$.addClass @root, 'warning'
|
||||
@root.textContent = "Post No.#{@postID} was not found."
|
||||
return
|
||||
|
||||
board = g.boards[@boardID] or
|
||||
|
||||
@ -234,10 +234,9 @@ class Post
|
||||
for clone in @clones
|
||||
clone.resurrect()
|
||||
|
||||
for quotelink in Get.allQuotelinksLinkingTo @
|
||||
if $.hasClass quotelink, 'deadlink'
|
||||
quotelink.textContent = quotelink.textContent.replace '\u00A0(Dead)', ''
|
||||
$.rmClass quotelink, 'deadlink'
|
||||
for quotelink in Get.allQuotelinksLinkingTo @ when $.hasClass quotelink, 'deadlink'
|
||||
quotelink.textContent = quotelink.textContent.replace '\u00A0(Dead)', ''
|
||||
$.rmClass quotelink, 'deadlink'
|
||||
return
|
||||
|
||||
collect: ->
|
||||
|
||||
@ -4,7 +4,7 @@ class Thread
|
||||
|
||||
constructor: (@ID, @board) ->
|
||||
@fullID = "#{@board}.#{@ID}"
|
||||
@posts = new SimpleDict
|
||||
@posts = new SimpleDict()
|
||||
@isDead = false
|
||||
@isHidden = false
|
||||
@isOnTop = false
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
ArchiveLink =
|
||||
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',
|
||||
textContent: 'Archive'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
DeleteLink =
|
||||
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',
|
||||
className: 'delete-link'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
DownloadLink =
|
||||
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',
|
||||
className: 'download-link'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
Menu =
|
||||
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',
|
||||
className: 'menu-button'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
ReportLink =
|
||||
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',
|
||||
className: 'report-link'
|
||||
|
||||
@ -16,16 +16,15 @@ Banner =
|
||||
if g.BOARD.ID isnt 'f' and g.VIEW is 'thread' and Conf['Remove Thread Excerpt']
|
||||
Banner.setTitle children[1].textContent
|
||||
|
||||
i = 0
|
||||
while child = children[i++]
|
||||
if i is 1
|
||||
for child, i in children
|
||||
if i is 0
|
||||
child.title = "Click to change"
|
||||
$.on child, 'click', Banner.cb.toggle
|
||||
|
||||
continue
|
||||
|
||||
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'
|
||||
else
|
||||
''}title"
|
||||
|
||||
@ -13,5 +13,5 @@ CustomCSS =
|
||||
|
||||
update: ->
|
||||
unless @style
|
||||
@addStyle()
|
||||
return @addStyle()
|
||||
@style.textContent = Conf['usercss']
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
ExpandComment =
|
||||
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.math if g.BOARD.ID is 'sci'
|
||||
|
||||
@ -1,28 +1,26 @@
|
||||
IDColor =
|
||||
init: ->
|
||||
return if g.VIEW not in ['index', 'thread'] or not Conf['Color User IDs']
|
||||
@ids = {}
|
||||
return unless g.VIEW in ['index', 'thread'] and Conf['Color User IDs']
|
||||
@ids = {
|
||||
Heaven: [0, 0, 0, '#fff']
|
||||
}
|
||||
|
||||
Post.callbacks.push
|
||||
name: 'Color User IDs'
|
||||
cb: @node
|
||||
|
||||
node: ->
|
||||
return if @isClone or not uid = @info.uniqueID
|
||||
span = $ '.hand', @nodes.uniqueID
|
||||
return unless span and span.nodeName is 'SPAN'
|
||||
rgb = IDColor.compute uid
|
||||
return if @isClone or !((uid = @info.uniqueID) and (span = $ 'span.hand', @nodes.uniqueID))
|
||||
|
||||
rgb = IDColor.ids[uid] or IDColor.compute uid
|
||||
|
||||
# Style the damn node.
|
||||
{style} = span
|
||||
style.color = rgb[3]
|
||||
style.backgroundColor = "rgb(#{rgb[0]},#{rgb[1]},#{rgb[2]})"
|
||||
$.addClass span, 'painted'
|
||||
span.title = 'Highlight posts by this ID'
|
||||
|
||||
compute: (uid) ->
|
||||
return IDColor.ids[uid] if IDColor.ids[uid]
|
||||
|
||||
# Convert chars to integers, bitshift and math to create a larger integer
|
||||
# Create a nice string of binary
|
||||
hash = IDColor.hash uid
|
||||
@ -35,7 +33,7 @@ IDColor =
|
||||
]
|
||||
|
||||
# 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'
|
||||
else
|
||||
'#fff'
|
||||
|
||||
@ -46,7 +46,7 @@ Keybinds =
|
||||
else if (notifications = $$ '.notification').length
|
||||
for notification in notifications
|
||||
$('.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']
|
||||
QR.hide()
|
||||
else
|
||||
@ -139,19 +139,19 @@ Keybinds =
|
||||
Index.cycleSortType()
|
||||
# Thread Navigation
|
||||
when Conf['Next thread']
|
||||
return if g.VIEW isnt 'index' or !threadRoot
|
||||
return unless g.VIEW is 'index' and threadRoot
|
||||
Nav.scroll +1
|
||||
when Conf['Previous thread']
|
||||
return if g.VIEW isnt 'index' or !threadRoot
|
||||
return unless g.VIEW is 'index' and threadRoot
|
||||
Nav.scroll -1
|
||||
when Conf['Expand thread']
|
||||
return if g.VIEW isnt 'index' or !threadRoot
|
||||
return unless g.VIEW is 'index' and threadRoot
|
||||
ExpandThread.toggle thread
|
||||
when Conf['Open thread']
|
||||
return if g.VIEW isnt 'index' or !threadRoot
|
||||
return unless g.VIEW is 'index' and threadRoot
|
||||
Keybinds.open thread
|
||||
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
|
||||
# Reply Navigation
|
||||
when Conf['Next reply']
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
Time =
|
||||
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
|
||||
name: 'Time Formatting'
|
||||
|
||||
@ -7,7 +7,7 @@ MarkNewIPs =
|
||||
|
||||
node: ->
|
||||
MarkNewIPs.ipCount = @ipCount
|
||||
MarkNewIPs.postIDs = @posts.keys.map (x) -> +x
|
||||
MarkNewIPs.postIDs = (+x for x in @posts.keys)
|
||||
$.on d, 'ThreadUpdate', MarkNewIPs.onUpdate
|
||||
|
||||
onUpdate: (e) ->
|
||||
@ -35,11 +35,7 @@ MarkNewIPs =
|
||||
suffix = if (ipCount // 10) % 10 is 1
|
||||
'th'
|
||||
else
|
||||
switch ipCount % 10
|
||||
when 1 then 'st'
|
||||
when 2 then 'nd'
|
||||
when 3 then 'rd'
|
||||
else 'th'
|
||||
['st', 'nd', 'rd'][ipCount % 10 - 1] or 'th' # fuck switches
|
||||
counter = $.el 'span',
|
||||
className: 'ip-counter'
|
||||
textContent: "(#{ipCount})"
|
||||
|
||||
@ -84,5 +84,5 @@ ThreadStats =
|
||||
ThreadStats.pageCountEl.textContent = page.page
|
||||
(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.
|
||||
ThreadStats.lastPageUpdate = new Date thread.last_modified * 1000
|
||||
ThreadStats.lastPageUpdate = new Date thread.last_modified * $.SECOND
|
||||
return
|
||||
|
||||
@ -12,7 +12,7 @@ ThreadUpdater =
|
||||
@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>') %>
|
||||
$.addClass doc, 'float'
|
||||
$.ready =>
|
||||
$.ready ->
|
||||
$.add d.body, sc
|
||||
|
||||
@checkPostCount = 0
|
||||
@ -120,11 +120,11 @@ ThreadUpdater =
|
||||
-> not d.hidden
|
||||
autoUpdate: (e) ->
|
||||
ThreadUpdater.count ThreadUpdater.isUpdating = @checked
|
||||
interval: ->
|
||||
interval: (e) ->
|
||||
val = parseInt @value, 10
|
||||
if val < 1 then val = 1
|
||||
ThreadUpdater.interval = @value = val
|
||||
$.cb.value.call @
|
||||
$.cb.value.call @ if e
|
||||
load: (e) ->
|
||||
{req} = ThreadUpdater
|
||||
switch req.status
|
||||
@ -239,6 +239,7 @@ ThreadUpdater =
|
||||
ThreadUpdater.req?.abort()
|
||||
ThreadUpdater.req = $.ajax "//a.4cdn.org/#{ThreadUpdater.thread.board}/thread/#{ThreadUpdater.thread}.json",
|
||||
onloadend: ThreadUpdater.cb.load
|
||||
timeout: $.MINUTE
|
||||
,
|
||||
whenModified: true
|
||||
|
||||
@ -344,7 +345,7 @@ ThreadUpdater =
|
||||
$.event 'ThreadUpdate',
|
||||
404: false
|
||||
threadID: ThreadUpdater.thread.fullID
|
||||
newPosts: posts.map (post) -> post.fullID
|
||||
newPosts: (post.fullID for post in posts)
|
||||
postCount: OP.replies + 1
|
||||
fileCount: OP.images + (!!ThreadUpdater.thread.OP.file and !ThreadUpdater.thread.OP.file.isDead)
|
||||
ipCount: OP.unique_ips
|
||||
|
||||
@ -14,7 +14,7 @@ ThreadWatcher =
|
||||
|
||||
@status = $ '#watcher-status', @dialog
|
||||
@list = @dialog.lastElementChild
|
||||
@refreshButton = $ '.move > .refresh', @dialog
|
||||
@refreshButton = $ '.refresh', @dialog
|
||||
@unreaddb = Unread.db or new DataBoard 'lastReadPosts'
|
||||
|
||||
$.on d, 'QRPostSuccessful', @cb.post
|
||||
@ -36,13 +36,6 @@ ThreadWatcher =
|
||||
|
||||
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'
|
||||
Menu.menu.addEntry
|
||||
el: $.el 'a', href: 'javascript:;'
|
||||
@ -60,6 +53,13 @@ ThreadWatcher =
|
||||
$.on @el, 'click', @cb
|
||||
true
|
||||
|
||||
Post.callbacks.push
|
||||
name: 'Thread Watcher'
|
||||
cb: @node
|
||||
CatalogThread.callbacks.push
|
||||
name: 'Thread Watcher'
|
||||
cb: @catalogNode
|
||||
|
||||
isWatched: (thread) ->
|
||||
ThreadWatcher.db?.get {boardID: thread.board.ID, threadID: thread.ID}
|
||||
|
||||
|
||||
@ -1,20 +1,21 @@
|
||||
Unread =
|
||||
init: ->
|
||||
return if g.VIEW isnt 'thread' or
|
||||
!Conf['Unread Count'] and
|
||||
!Conf['Unread Favicon'] and
|
||||
!Conf['Unread Line'] and
|
||||
!Conf['Scroll to Last Read Post'] and
|
||||
!Conf['Thread Watcher'] and
|
||||
!Conf['Desktop Notifications'] and
|
||||
!Conf['Quote Threading']
|
||||
return unless g.VIEW is 'thread' and (
|
||||
Conf['Unread Count'] or
|
||||
Conf['Unread Favicon'] or
|
||||
Conf['Unread Line'] or
|
||||
Conf['Scroll to Last Read Post'] or
|
||||
Conf['Thread Watcher'] or
|
||||
Conf['Desktop Notifications'] or
|
||||
Conf['Quote Threading']
|
||||
)
|
||||
|
||||
@db = new DataBoard 'lastReadPosts', @sync
|
||||
@hr = $.el 'hr',
|
||||
id: 'unread-line'
|
||||
@posts = new Set
|
||||
@postsQuotingYou = new Set
|
||||
@order = new RandomAccessList
|
||||
@posts = new Set()
|
||||
@postsQuotingYou = new Set()
|
||||
@order = new RandomAccessList()
|
||||
@position = null
|
||||
|
||||
Thread.callbacks.push
|
||||
|
||||
@ -9,6 +9,7 @@ Captcha.noscript =
|
||||
container = $.el 'div',
|
||||
className: 'captcha-img'
|
||||
title: 'Reload reCAPTCHA'
|
||||
|
||||
input = $.el 'input',
|
||||
className: 'captcha-input field'
|
||||
title: 'Verification'
|
||||
@ -103,7 +104,7 @@ Captcha.noscript =
|
||||
$.off input, 'focus click', @cb.focus
|
||||
|
||||
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'
|
||||
|
||||
destroy: ->
|
||||
|
||||
@ -13,6 +13,17 @@ QR =
|
||||
noscript = Conf['Force Noscript Captcha'] or !doc.dataset.jsEnabled
|
||||
@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']
|
||||
@shortcut = sc = $.el 'a',
|
||||
className: 'qr-shortcut fa fa-comment-o disabled'
|
||||
@ -35,17 +46,6 @@ QR =
|
||||
# Prevent unnecessary loading of fallback iframe.
|
||||
$.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: ->
|
||||
$.off d, '4chanXInitFinished', @initReady
|
||||
QR.postingIsEnabled = !!$.id 'postForm'
|
||||
@ -318,7 +318,7 @@ QR =
|
||||
QR.open()
|
||||
QR.handleFiles files
|
||||
$.addClass QR.nodes.el, 'dump'
|
||||
|
||||
|
||||
handleUrl: ->
|
||||
url = prompt 'Enter a URL:'
|
||||
return if url is null
|
||||
@ -429,7 +429,6 @@ QR =
|
||||
if (e.ctrlKey or e.metaKey) and e.type is 'click'
|
||||
$.addClass QR.nodes.filename, 'edit'
|
||||
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
|
||||
e.preventDefault()
|
||||
QR.nodes.fileInput.click()
|
||||
@ -437,11 +436,11 @@ QR =
|
||||
generatePostableThreadsList: ->
|
||||
return unless QR.nodes
|
||||
list = QR.nodes.thread
|
||||
options = [list.firstChild]
|
||||
options = [list.firstElementChild]
|
||||
for thread in g.BOARD.threads.keys
|
||||
options.push $.el 'option',
|
||||
value: thread
|
||||
textContent: "Thread No.#{thread}"
|
||||
textContent: "No.#{thread}"
|
||||
val = list.value
|
||||
$.rmAll list
|
||||
$.add list, options
|
||||
@ -548,6 +547,7 @@ QR =
|
||||
$.on nodes.urlButton, 'click', QR.handleUrl
|
||||
$.on nodes.addPost, 'click', -> new QR.post true
|
||||
$.on nodes.form, 'submit', QR.submit
|
||||
$.on nodes.filename, 'blur', -> $.rmClass @, 'edit'
|
||||
$.on nodes.fileRM, 'click', -> QR.selected.rmFile()
|
||||
$.on nodes.fileExtras, 'click', (e) -> e.stopPropagation()
|
||||
$.on nodes.spoiler, 'change', -> QR.selected.nodes.spoiler.click()
|
||||
@ -665,7 +665,7 @@ QR =
|
||||
QR.status()
|
||||
QR.error $.el 'span',
|
||||
<%= 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="${g.FAQ}#what-does-4chan-x-encountered-an-error-while-posting-please-try-again-mean" target="_blank">More info</a>]'
|
||||
) %>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user