Safer property access.
This commit is contained in:
parent
cba171b9d0
commit
e75700f5d9
@ -15,11 +15,11 @@ Redirect =
|
||||
|
||||
selectArchives: ->
|
||||
o =
|
||||
thread: {}
|
||||
post: {}
|
||||
file: {}
|
||||
thread: $.dict()
|
||||
post: $.dict()
|
||||
file: $.dict()
|
||||
|
||||
archives = {}
|
||||
archives = $.dict()
|
||||
for data in Conf['archives']
|
||||
for key in ['boards', 'files']
|
||||
data[key] = [] unless data[key] instanceof Array
|
||||
@ -32,7 +32,7 @@ Redirect =
|
||||
o.file[boardID] = data unless boardID of o.file or boardID not in files
|
||||
|
||||
for boardID, record of Conf['selectedArchives']
|
||||
for type, id of record when (archive = archives[JSON.stringify id])
|
||||
for type, id of record when (archive = archives[JSON.stringify id]) and $.hasOwn(o, type)
|
||||
boards = if type is 'file' then archive.files else archive.boards
|
||||
o[type][boardID] = archive if boardID in boards
|
||||
|
||||
@ -76,14 +76,14 @@ Redirect =
|
||||
|
||||
parse: (responses, cb) ->
|
||||
archives = []
|
||||
archiveUIDs = {}
|
||||
archiveUIDs = $.dict()
|
||||
for response in responses
|
||||
for data in response
|
||||
uid = JSON.stringify(data.uid ? data.name)
|
||||
if uid of archiveUIDs
|
||||
$.extend archiveUIDs[uid], data
|
||||
else
|
||||
archiveUIDs[uid] = data
|
||||
archiveUIDs[uid] = $.dict.clone data
|
||||
archives.push data
|
||||
items = {archives, lastarchivecheck: Date.now()}
|
||||
$.set items
|
||||
@ -98,7 +98,7 @@ Redirect =
|
||||
|
||||
protocol: (archive) ->
|
||||
protocol = location.protocol
|
||||
unless archive[protocol[0...-1]]
|
||||
unless $.getOwn(archive, protocol[0...-1])
|
||||
protocol = if protocol is 'https:' then 'http:' else 'https:'
|
||||
"#{protocol}//"
|
||||
|
||||
@ -146,10 +146,10 @@ Redirect =
|
||||
type
|
||||
if type is 'capcode'
|
||||
# https://github.com/pleebe/FoolFuuka/blob/bf4224eed04637a4d0bd4411c2bf5f9945dfec0b/src/Model/Search.php#L363
|
||||
value = {
|
||||
value = $.getOwn({
|
||||
'Developer': 'dev'
|
||||
'Verified': 'ver'
|
||||
}[value] or value.toLowerCase()
|
||||
}, value) or value.toLowerCase()
|
||||
else if type is 'image'
|
||||
value = value.replace /[+/=]/g, (c) -> ({'+': '-', '/': '_', '=': ''})[c]
|
||||
value = encodeURIComponent value
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
Filter =
|
||||
filters: {}
|
||||
results: {}
|
||||
filters: $.dict()
|
||||
results: $.dict()
|
||||
init: ->
|
||||
return unless g.VIEW in ['index', 'thread', 'catalog'] and Conf['Filter']
|
||||
return if g.VIEW is 'catalog' and not Conf['Filter in Native Catalog']
|
||||
@ -44,11 +44,11 @@ Filter =
|
||||
|
||||
# Filter OPs along with their threads or replies only.
|
||||
op = filter.match(/(?:^|;)\s*op:(no|only)/)?[1] or ''
|
||||
mask = {'no': 1, 'only': 2}[op] or 0
|
||||
mask = $.getOwn({'no': 1, 'only': 2}, op) or 0
|
||||
|
||||
# Filter only posts with/without files.
|
||||
file = filter.match(/(?:^|;)\s*file:(no|only)/)?[1] or ''
|
||||
mask = mask | ({'no': 4, 'only': 8}[file] or 0)
|
||||
mask = mask | ($.getOwn({'no': 4, 'only': 8}, file) or 0)
|
||||
|
||||
# Overrule the `Show Stubs` setting.
|
||||
# Defaults to stub showing.
|
||||
@ -102,7 +102,7 @@ Filter =
|
||||
parseBoards: (boardsRaw) ->
|
||||
return false unless boardsRaw
|
||||
return boards if (boards = Filter.parseBoardsMemo[boardsRaw])
|
||||
boards = {}
|
||||
boards = $.dict()
|
||||
siteFilter = ''
|
||||
for boardID in boardsRaw.split(',')
|
||||
if ':' in boardID
|
||||
@ -116,7 +116,7 @@ Filter =
|
||||
Filter.parseBoardsMemo[boardsRaw] = boards
|
||||
boards
|
||||
|
||||
parseBoardsMemo: {}
|
||||
parseBoardsMemo: $.dict()
|
||||
|
||||
test: (post, hideable=true) ->
|
||||
return post.filterResults if post.filterResults
|
||||
@ -172,7 +172,7 @@ Filter =
|
||||
|
||||
catalog: ->
|
||||
return unless (url = g.SITE.urls.catalogJSON?(g.BOARD))
|
||||
Filter.catalogData = {}
|
||||
Filter.catalogData = $.dict()
|
||||
$.ajax url,
|
||||
onloadend: Filter.catalogParse
|
||||
Callbacks.CatalogThreadNative.push
|
||||
@ -225,17 +225,18 @@ Filter =
|
||||
MD5: (post) -> post.files.map((f) -> f.MD5)
|
||||
|
||||
values: (key, post) ->
|
||||
if key of Filter.valueF
|
||||
if $.hasOwn(Filter.valueF, key)
|
||||
Filter.valueF[key](post).filter((v) -> v?)
|
||||
else
|
||||
[key.split('+').map((k) ->
|
||||
if (f=Filter.valueF[k])
|
||||
if (f = $.getOwn(Filter.valueF, k))
|
||||
f(post).map((v) -> v or '').join('\n')
|
||||
else
|
||||
''
|
||||
).join('\n')]
|
||||
|
||||
addFilter: (type, re, cb) ->
|
||||
return unless $.hasOwn(Config.filter, type)
|
||||
$.get type, Conf[type], (item) ->
|
||||
save = item[type]
|
||||
# Add a new line before the regexp unless the text is empty.
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
Recursive =
|
||||
recursives: {}
|
||||
recursives: $.dict()
|
||||
init: ->
|
||||
return unless g.VIEW in ['index', 'thread']
|
||||
Callbacks.Post.push
|
||||
|
||||
@ -15,7 +15,7 @@ ThreadHiding =
|
||||
return unless $.hasStorage and g.SITE.software is 'yotsuba'
|
||||
hiddenThreads = ThreadHiding.db.get
|
||||
boardID: board.ID
|
||||
defaultValue: {}
|
||||
defaultValue: $.dict()
|
||||
hiddenThreads[threadID] = true for threadID of hiddenThreads
|
||||
localStorage.setItem "4chan-hide-t-#{board}", JSON.stringify hiddenThreads
|
||||
|
||||
@ -32,12 +32,12 @@ ThreadHiding =
|
||||
|
||||
catalogSave: ->
|
||||
hiddenThreads2 = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {}
|
||||
for threadID of hiddenThreads2 when !(threadID of ThreadHiding.hiddenThreads)
|
||||
for threadID of hiddenThreads2 when !$.hasOwn(ThreadHiding.hiddenThreads, threadID)
|
||||
ThreadHiding.db.set
|
||||
boardID: g.BOARD.ID
|
||||
threadID: threadID
|
||||
val: {makeStub: Conf['Stubs']}
|
||||
for threadID of ThreadHiding.hiddenThreads when !(threadID of hiddenThreads2)
|
||||
for threadID of ThreadHiding.hiddenThreads when !$.hasOwn(hiddenThreads2, threadID)
|
||||
ThreadHiding.db.delete
|
||||
boardID: g.BOARD.ID
|
||||
threadID: threadID
|
||||
@ -176,7 +176,7 @@ ThreadHiding =
|
||||
|
||||
toggle: (thread) ->
|
||||
unless thread instanceof Thread
|
||||
thread = g.threads[@dataset.fullID]
|
||||
thread = g.threads.get(@dataset.fullID)
|
||||
if thread.isHidden
|
||||
ThreadHiding.show thread
|
||||
else
|
||||
|
||||
@ -13,7 +13,7 @@ BoardConfig =
|
||||
|
||||
load: ->
|
||||
if @status is 200 and @response and @response.boards
|
||||
boards = {}
|
||||
boards = $.dict()
|
||||
for board in @response.boards
|
||||
boards[board.board] = board
|
||||
{troll_flags} = @response
|
||||
|
||||
@ -13,14 +13,14 @@ Get =
|
||||
threadFromRoot: (root) ->
|
||||
return null unless root?
|
||||
{board} = root.dataset
|
||||
g.threads["#{if board then encodeURIComponent(board) else g.BOARD.ID}.#{root.id.match(/\d*$/)[0]}"]
|
||||
g.threads.get("#{if board then encodeURIComponent(board) else g.BOARD.ID}.#{root.id.match(/\d*$/)[0]}")
|
||||
threadFromNode: (node) ->
|
||||
Get.threadFromRoot $.x "ancestor-or-self::#{g.SITE.xpath.thread}", node
|
||||
postFromRoot: (root) ->
|
||||
return null unless root?
|
||||
post = g.posts[root.dataset.fullID]
|
||||
post = g.posts.get(root.dataset.fullID)
|
||||
index = root.dataset.clone
|
||||
if index then post.clones[index] else post
|
||||
if index then post.clones[+index] else post
|
||||
postFromNode: (root) ->
|
||||
Get.postFromRoot $.x "ancestor-or-self::#{g.SITE.xpath.postContainer}[1]", root
|
||||
postDataFromLink: (link) ->
|
||||
@ -59,7 +59,7 @@ Get =
|
||||
# and their clones,
|
||||
# get all of their backlinks.
|
||||
if Conf['Quote Backlinks']
|
||||
handleQuotes qPost, 'backlinks' for quote in post.quotes when qPost = posts[quote]
|
||||
handleQuotes qPost, 'backlinks' for quote in post.quotes when qPost = posts.get(quote)
|
||||
|
||||
# Third:
|
||||
# Filter out irrelevant quotelinks.
|
||||
|
||||
@ -52,7 +52,7 @@ Index =
|
||||
|
||||
# Header "Index Navigation" submenu
|
||||
entries = []
|
||||
@inputs = inputs = {}
|
||||
@inputs = inputs = $.dict()
|
||||
for name, arr of Config.Index when arr instanceof Array
|
||||
label = UI.checkbox name, "#{name[0]}#{name[1..].toLowerCase()}"
|
||||
label.title = arr[1]
|
||||
@ -66,7 +66,7 @@ Index =
|
||||
$.on inputs['Anchor Hidden Threads'], 'change', @cb.resort
|
||||
|
||||
watchSettings = (e) ->
|
||||
if (input = inputs[e.target.name])
|
||||
if (input = $.getOwn(inputs, e.target.name))
|
||||
input.checked = e.target.checked
|
||||
$.event 'change', null, input
|
||||
$.on d, 'OpenSettings', ->
|
||||
@ -283,10 +283,10 @@ Index =
|
||||
Index.pageLoad false unless e?.detail?.deferred
|
||||
|
||||
perBoardSort: ->
|
||||
Conf['Index Sort'] = if @checked then {} else ''
|
||||
Conf['Index Sort'] = if @checked then $.dict() else ''
|
||||
Index.saveSort()
|
||||
for i in [0...2]
|
||||
Conf["Last Long Reply Thresholds #{i}"] = if @checked then {} else ''
|
||||
Conf["Last Long Reply Thresholds #{i}"] = if @checked then $.dict() else ''
|
||||
Index.saveLastLongThresholds i
|
||||
return
|
||||
|
||||
@ -412,12 +412,12 @@ Index =
|
||||
commands = hash[1..].split '/'
|
||||
leftover = []
|
||||
for command in commands
|
||||
if (mode = Index.hashCommands.mode[command])
|
||||
if (mode = $.getOwn(Index.hashCommands.mode, command))
|
||||
state.mode = mode
|
||||
else if command is 'index'
|
||||
state.mode = Conf['Previous Index Mode']
|
||||
state.page = 1
|
||||
else if (sort = Index.hashCommands.sort[command.replace(/-rev$/, '')])
|
||||
else if (sort = $.getOwn(Index.hashCommands.sort, command.replace(/-rev$/, '')))
|
||||
state.sort = sort
|
||||
state.sort += '-rev' if /-rev$/.test(command)
|
||||
else if /^s=/.test command
|
||||
@ -659,10 +659,10 @@ Index =
|
||||
Index.threadsNumPerPage = pages[0]?.threads.length or 1
|
||||
Index.liveThreadData = pages.reduce ((arr, next) -> arr.concat next.threads), []
|
||||
Index.liveThreadIDs = Index.liveThreadData.map (data) -> data.no
|
||||
Index.liveThreadDict = {}
|
||||
Index.threadPosition = {}
|
||||
Index.parsedThreads = {}
|
||||
Index.replyData = {}
|
||||
Index.liveThreadDict = $.dict()
|
||||
Index.threadPosition = $.dict()
|
||||
Index.parsedThreads = $.dict()
|
||||
Index.replyData = $.dict()
|
||||
for data, i in Index.liveThreadData
|
||||
Index.liveThreadDict[data.no] = data
|
||||
Index.threadPosition[data.no] = i
|
||||
@ -682,7 +682,7 @@ Index =
|
||||
return
|
||||
|
||||
isHidden: (threadID) ->
|
||||
if (thread = g.BOARD.threads[threadID]) and thread.OP and not thread.OP.isFetchedQuote
|
||||
if (thread = g.BOARD.threads.get(threadID)) and thread.OP and not thread.OP.isFetchedQuote
|
||||
thread.isHidden
|
||||
else
|
||||
Index.parsedThreads[threadID].isHidden
|
||||
@ -698,7 +698,7 @@ Index =
|
||||
try
|
||||
threadData = Index.liveThreadDict[ID]
|
||||
|
||||
if (thread = g.BOARD.threads[ID])
|
||||
if (thread = g.BOARD.threads.get(ID))
|
||||
isStale = (thread.json isnt threadData) and (JSON.stringify(thread.json) isnt JSON.stringify(threadData))
|
||||
if isStale
|
||||
thread.setCount 'post', threadData.replies + 1, threadData.bumplimit
|
||||
@ -751,7 +751,7 @@ Index =
|
||||
continue if not (lastReplies = Index.liveThreadDict[thread.ID].last_replies)
|
||||
nodes = []
|
||||
for data in lastReplies
|
||||
if (post = thread.posts[data.no]) and not post.isFetchedQuote
|
||||
if (post = thread.posts.get(data.no)) and not post.isFetchedQuote
|
||||
nodes.push post.nodes.root
|
||||
continue
|
||||
nodes.push node = g.SITE.Build.postFromObject data, thread.board.ID
|
||||
@ -822,7 +822,7 @@ Index =
|
||||
if len >= Index.lastLongThresholds[+!!r.ext]
|
||||
return r
|
||||
if thread.omitted_posts then thread.last_replies[0] else thread
|
||||
lastlongD = {}
|
||||
lastlongD = $.dict()
|
||||
for thread in liveThreadData
|
||||
lastlongD[thread.no] = lastlong(thread).no
|
||||
[liveThreadData...].sort((a, b) ->
|
||||
|
||||
@ -129,8 +129,8 @@ Settings =
|
||||
warning addWarning
|
||||
$.add section, warnings
|
||||
|
||||
items = {}
|
||||
inputs = {}
|
||||
items = $.dict()
|
||||
inputs = $.dict()
|
||||
addCheckboxes = (root, obj) ->
|
||||
containers = [root]
|
||||
for key, arr of obj when arr instanceof Array
|
||||
@ -177,7 +177,7 @@ Settings =
|
||||
div = $.el 'div',
|
||||
<%= html('<button></button><span class="description">: Clear manually-hidden threads and posts on all boards. Reload the page to apply.') %>
|
||||
button = $ 'button', div
|
||||
$.get {hiddenThreads: {}, hiddenPosts: {}}, ({hiddenThreads, hiddenPosts}) ->
|
||||
$.get {hiddenThreads: $.dict(), hiddenPosts: $.dict()}, ({hiddenThreads, hiddenPosts}) ->
|
||||
hiddenNum = 0
|
||||
for ID, site of hiddenThreads when ID isnt 'boards'
|
||||
for ID, board of site.boards
|
||||
@ -194,7 +194,7 @@ Settings =
|
||||
button.textContent = "Hidden: #{hiddenNum}"
|
||||
$.on button, 'click', ->
|
||||
@textContent = 'Hidden: 0'
|
||||
$.get 'hiddenThreads', {}, ({hiddenThreads}) ->
|
||||
$.get 'hiddenThreads', $.dict(), ({hiddenThreads}) ->
|
||||
if $.hasStorage and g.SITE.software is 'yotsuba'
|
||||
for boardID of hiddenThreads.boards
|
||||
localStorage.removeItem "4chan-hide-t-#{boardID}"
|
||||
@ -203,7 +203,7 @@ Settings =
|
||||
|
||||
export: ->
|
||||
# Make sure to export the most recent data, but don't overwrite existing `Conf` object.
|
||||
Conf2 = {}
|
||||
Conf2 = $.dict()
|
||||
$.extend Conf2, Conf
|
||||
$.get Conf2, (Conf2) ->
|
||||
# Don't export cached JSON data.
|
||||
@ -235,7 +235,7 @@ Settings =
|
||||
reader = new FileReader()
|
||||
reader.onload = (e) ->
|
||||
try
|
||||
Settings.loadSettings JSON.parse(e.target.result), (err) ->
|
||||
Settings.loadSettings $.dict.json(e.target.result), (err) ->
|
||||
if err
|
||||
output.textContent = 'Import failed due to an error.'
|
||||
else if confirm 'Import successful. Reload now?'
|
||||
@ -327,14 +327,14 @@ Settings =
|
||||
data.Conf[key] = data.Conf[key].replace(/ctrl|alt|meta/g, (s) -> "#{s[0].toUpperCase()}#{s[1..]}").replace /(^|.+\+)[A-Z]$/g, (s) ->
|
||||
"Shift+#{s[0...-1]}#{s[-1..].toLowerCase()}"
|
||||
if data.WatchedThreads
|
||||
data.Conf['watchedThreads'] = boards: {}
|
||||
data.Conf['watchedThreads'] = boards: $.dict()
|
||||
for boardID, threads of data.WatchedThreads
|
||||
for threadID, threadData of threads
|
||||
(data.Conf['watchedThreads'].boards[boardID] or= {})[threadID] = excerpt: threadData.textContent
|
||||
(data.Conf['watchedThreads'].boards[boardID] or= $.dict())[threadID] = excerpt: threadData.textContent
|
||||
data
|
||||
|
||||
upgrade: (data, version) ->
|
||||
changes = {}
|
||||
changes = $.dict()
|
||||
set = (key, value) ->
|
||||
data[key] = changes[key] = value
|
||||
setD = (key, value) ->
|
||||
@ -371,7 +371,7 @@ Settings =
|
||||
if data['selectedArchives']?
|
||||
uids = {"Moe":0,"4plebs Archive":3,"Nyafuu Archive":4,"Love is Over":5,"Rebecca Black Tech":8,"warosu":10,"fgts":15,"not4plebs":22,"DesuStorage":23,"fireden.net":24,"disabled":null}
|
||||
for boardID, record of data['selectedArchives']
|
||||
for type, name of record when name of uids
|
||||
for type, name of record when $.hasOwn(uids, name)
|
||||
record[type] = uids[name]
|
||||
set 'selectedArchives', data['selectedArchives']
|
||||
if compareString < '00001.00011.00016.00000'
|
||||
@ -468,7 +468,7 @@ Settings =
|
||||
delete data[db].lastChecked
|
||||
set db, data[db]
|
||||
if data['siteSoftware']? and not data['siteProperties']?
|
||||
siteProperties = {}
|
||||
siteProperties = $.dict()
|
||||
for line in data['siteSoftware'].split('\n')
|
||||
[hostname, software] = line.split(' ')
|
||||
siteProperties[hostname] = {software}
|
||||
@ -523,6 +523,7 @@ Settings =
|
||||
selectFilter: ->
|
||||
div = @nextElementSibling
|
||||
if (name = @value) isnt 'guide'
|
||||
return unless $.hasOwn(Config.filter, name)
|
||||
$.rmAll div
|
||||
ta = $.el 'textarea',
|
||||
name: name
|
||||
@ -551,7 +552,7 @@ Settings =
|
||||
$.extend section, <%= readHTML('Advanced.html') %>
|
||||
warning.hidden = Conf[warning.dataset.feature] for warning in $$ '.warning', section
|
||||
|
||||
inputs = {}
|
||||
inputs = $.dict()
|
||||
for input in $$ '[name]', section
|
||||
inputs[input.name] = input
|
||||
|
||||
@ -560,7 +561,7 @@ Settings =
|
||||
Conf['lastarchivecheck'] = 0
|
||||
$.id('lastarchivecheck').textContent = 'never'
|
||||
|
||||
items = {}
|
||||
items = $.dict()
|
||||
for name, input of inputs when name not in ['captchaServiceKey', 'Interval', 'Custom CSS']
|
||||
items[name] = Conf[name]
|
||||
event = if (
|
||||
@ -602,7 +603,7 @@ Settings =
|
||||
$.on customCSS, 'change', Settings.togglecss
|
||||
$.on applyCSS, 'click', -> CustomCSS.update()
|
||||
|
||||
itemsArchive = {}
|
||||
itemsArchive = $.dict()
|
||||
itemsArchive[name] = Conf[name] for name in ['archives', 'selectedArchives', 'lastarchivecheck']
|
||||
$.get itemsArchive, (itemsArchive) ->
|
||||
$.extend Conf, itemsArchive
|
||||
@ -634,7 +635,7 @@ Settings =
|
||||
$.rmAll boardSelect
|
||||
$.rmAll tbody
|
||||
|
||||
archBoards = {}
|
||||
archBoards = $.dict()
|
||||
for {uid, name, boards, files, software} in Conf['archives']
|
||||
continue unless software in ['fuuka', 'foolfuuka']
|
||||
for boardID in boards
|
||||
@ -715,7 +716,7 @@ Settings =
|
||||
|
||||
saveSelectedArchive: ->
|
||||
$.get 'selectedArchives', Conf['selectedArchives'], ({selectedArchives}) =>
|
||||
(selectedArchives[@dataset.boardid] or= {})[@dataset.type] = JSON.parse @value
|
||||
(selectedArchives[@dataset.boardid] or= $.dict())[@dataset.type] = JSON.parse @value
|
||||
$.set 'selectedArchives', selectedArchives
|
||||
Conf['selectedArchives'] = selectedArchives
|
||||
Redirect.selectArchives()
|
||||
@ -732,7 +733,7 @@ Settings =
|
||||
Conf['captchaServiceKey'][domain] = value
|
||||
$.get 'captchaServiceKey', Conf['captchaServiceKey'], ({captchaServiceKey}) ->
|
||||
captchaServiceKey[domain] = value
|
||||
delete captchaServiceKey[domain] unless value or (domain of Config['captchaServiceKey'][0])
|
||||
delete captchaServiceKey[domain] unless value or $.hasOwn(Config['captchaServiceKey'][0], domain)
|
||||
Conf['captchaServiceKey'] = captchaServiceKey
|
||||
$.set 'captchaServiceKey', captchaServiceKey
|
||||
Settings.captchaServiceDomainList()
|
||||
@ -794,8 +795,8 @@ Settings =
|
||||
$('.warning', section).hidden = Conf['Keybinds']
|
||||
|
||||
tbody = $ 'tbody', section
|
||||
items = {}
|
||||
inputs = {}
|
||||
items = $.dict()
|
||||
inputs = $.dict()
|
||||
for key, arr of Config.hotkeys
|
||||
tr = $.el 'tr',
|
||||
<%= html('<td>${arr[1]}</td><td><input class="field"></td>') %>
|
||||
|
||||
@ -127,7 +127,7 @@ Test =
|
||||
|
||||
cb:
|
||||
testOne: ->
|
||||
Test.testOne g.posts[@dataset.fullID]
|
||||
Test.testOne g.posts.get(@dataset.fullID)
|
||||
Menu.menu.close()
|
||||
|
||||
testAll: ->
|
||||
|
||||
@ -25,7 +25,7 @@ FappeTyme =
|
||||
textContent: type[0]
|
||||
title: "#{type} Tyme active"
|
||||
$.on indicator, 'click', ->
|
||||
check = FappeTyme.nodes[@parentNode.id.replace('shortcut-', '')]
|
||||
check = $.getOwn(FappeTyme.nodes, @parentNode.id.replace('shortcut-', ''))
|
||||
check.checked = !check.checked
|
||||
$.event 'change', null, check
|
||||
Header.addShortcut lc, indicator, 410
|
||||
|
||||
@ -38,7 +38,7 @@ Gallery =
|
||||
|
||||
Gallery.images = []
|
||||
nodes = Gallery.nodes = {}
|
||||
Gallery.fileIDs = {}
|
||||
Gallery.fileIDs = $.dict()
|
||||
Gallery.slideshow = false
|
||||
|
||||
nodes.el = dialog = $.el 'div',
|
||||
@ -133,7 +133,7 @@ Gallery =
|
||||
|
||||
load: (thumb, errorCB) ->
|
||||
ext = thumb.href.match /\w*$/
|
||||
elType = {'webm': 'video', 'mp4': 'video', 'pdf': 'iframe'}[ext] or 'img'
|
||||
elType = $.getOwn({'webm': 'video', 'mp4': 'video', 'pdf': 'iframe'}, ext) or 'img'
|
||||
file = $.el elType
|
||||
$.extend file.dataset, thumb.dataset
|
||||
$.on file, 'error', errorCB
|
||||
@ -185,7 +185,7 @@ Gallery =
|
||||
Gallery.cb.stop()
|
||||
|
||||
# Scroll to post
|
||||
if Conf['Scroll to Post'] and (post = g.posts[file.dataset.post])
|
||||
if Conf['Scroll to Post'] and (post = g.posts.get(file.dataset.post))
|
||||
Header.scrollTo post.nodes.root
|
||||
|
||||
# Preload next image
|
||||
@ -196,11 +196,11 @@ Gallery =
|
||||
if @error?.code is MediaError.MEDIA_ERR_DECODE
|
||||
return new Notice 'error', 'Corrupt or unplayable video', 30
|
||||
return if ImageCommon.isFromArchive @
|
||||
post = g.posts[@dataset.post]
|
||||
file = post.files[@dataset.file]
|
||||
post = g.posts.get(@dataset.post)
|
||||
file = post.files[+@dataset.file]
|
||||
ImageCommon.error @, post, file, null, (url) =>
|
||||
return unless url
|
||||
Gallery.images[@dataset.id].href = url
|
||||
Gallery.images[+@dataset.id].href = url
|
||||
(@src = url if Gallery.nodes.current is @)
|
||||
|
||||
cacheError: ->
|
||||
@ -341,7 +341,7 @@ Gallery =
|
||||
{current, frame} = Gallery.nodes
|
||||
{style} = current
|
||||
|
||||
if Conf['Stretch to Fit'] and (dim = g.posts[current.dataset.post]?.file.dimensions)
|
||||
if Conf['Stretch to Fit'] and (dim = g.posts.get(current.dataset.post)?.file.dimensions)
|
||||
[width, height] = dim.split 'x'
|
||||
containerWidth = frame.clientWidth
|
||||
containerHeight = doc.clientHeight - 25
|
||||
|
||||
@ -24,7 +24,7 @@ Metadata =
|
||||
$.rmClass @parentNode, 'error'
|
||||
$.addClass @parentNode, 'loading'
|
||||
{index} = @parentNode.dataset
|
||||
CrossOrigin.binary Get.postFromNode(@).files[index].url, (data) =>
|
||||
CrossOrigin.binary Get.postFromNode(@).files[+index].url, (data) =>
|
||||
$.rmClass @parentNode, 'loading'
|
||||
if data?
|
||||
title = Metadata.parse data
|
||||
|
||||
@ -18,7 +18,7 @@ Sauce =
|
||||
|
||||
parseLink: (link) ->
|
||||
return null if not (link = link.trim())
|
||||
parts = {}
|
||||
parts = $.dict()
|
||||
for part, i in link.split /;(?=(?:text|boards|types|regexp|sandbox):?)/
|
||||
if i is 0
|
||||
parts['url'] = part
|
||||
@ -47,7 +47,7 @@ Sauce =
|
||||
|
||||
createSauceLink: (link, post, file) ->
|
||||
ext = file.url.match(/[^.]*$/)[0]
|
||||
parts = {}
|
||||
parts = $.dict()
|
||||
$.extend parts, link
|
||||
|
||||
return null unless !parts['boards'] or parts['boards']["#{post.siteID}/#{post.boardID}"] or parts['boards']["#{post.siteID}/*"]
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
Embedding =
|
||||
init: ->
|
||||
return unless g.VIEW in ['index', 'thread', 'archive'] and Conf['Linkify'] and (Conf['Embedding'] or Conf['Link Title'] or Conf['Cover Preview'])
|
||||
@types = {}
|
||||
@types = $.dict()
|
||||
@types[type.key] = type for type in @ordered_types
|
||||
|
||||
if Conf['Embedding'] and g.VIEW isnt 'archive'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
DeleteLink =
|
||||
auto: [{}, {}]
|
||||
auto: [$.dict(), $.dict()]
|
||||
|
||||
init: ->
|
||||
return unless g.VIEW in ['index', 'thread'] and Conf['Menu'] and Conf['Delete Link']
|
||||
@ -77,7 +77,7 @@ DeleteLink =
|
||||
mode: 'usrdel'
|
||||
onlyimgdel: fileOnly
|
||||
pwd: QR.persona.getPassword()
|
||||
form[post.ID] = 'delete'
|
||||
form[+post.ID] = 'delete'
|
||||
|
||||
$.ajax $.id('delform').action.replace("/#{g.BOARD}/", "/#{post.board}/"),
|
||||
responseType: 'document'
|
||||
@ -110,7 +110,7 @@ DeleteLink =
|
||||
link.textContent = 'Deleted' if post.fullID is DeleteLink.post.fullID
|
||||
|
||||
cooldown:
|
||||
seconds: {}
|
||||
seconds: $.dict()
|
||||
|
||||
start: (post, seconds) ->
|
||||
# Already counting.
|
||||
|
||||
@ -75,7 +75,7 @@ Banner =
|
||||
boardID: g.BOARD.ID
|
||||
threadID: @className
|
||||
|
||||
original: {}
|
||||
original: $.dict()
|
||||
|
||||
custom: (child) ->
|
||||
{className} = child
|
||||
|
||||
@ -81,12 +81,12 @@ CatalogLinks =
|
||||
return
|
||||
|
||||
externalParse: ->
|
||||
CatalogLinks.externalList = {}
|
||||
CatalogLinks.externalList = $.dict()
|
||||
for line in Conf['externalCatalogURLs'].split '\n'
|
||||
continue if line[0] is '#'
|
||||
url = line.split(';')[0]
|
||||
boards = Filter.parseBoards(line.match(/;boards:([^;]+)/)?[1] or '*')
|
||||
excludes = Filter.parseBoards(line.match(/;exclude:([^;]+)/)?[1]) or {}
|
||||
excludes = Filter.parseBoards(line.match(/;exclude:([^;]+)/)?[1]) or $.dict()
|
||||
for board of boards
|
||||
unless excludes[board] or excludes[board.split('/')[0] + '/*']
|
||||
CatalogLinks.externalList[board] = url
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
ExpandThread =
|
||||
statuses: {}
|
||||
statuses: $.dict()
|
||||
init: ->
|
||||
return if not (g.VIEW is 'index' and Conf['Thread Expansion'])
|
||||
if Conf['JSON Index']
|
||||
@ -96,7 +96,7 @@ ExpandThread =
|
||||
filesCount = 0
|
||||
for postData in req.response.posts
|
||||
continue if postData.no is thread.ID
|
||||
if (post = thread.posts[postData.no]) and not post.isFetchedQuote
|
||||
if (post = thread.posts.get(postData.no)) and not post.isFetchedQuote
|
||||
filesCount++ if 'file' of post
|
||||
{root} = post.nodes
|
||||
postsRoot.push root
|
||||
|
||||
@ -26,7 +26,7 @@ FileInfo =
|
||||
format: (formatString, post, outputNode) ->
|
||||
output = []
|
||||
formatString.replace /%(.)|[^%]+/g, (s, c) ->
|
||||
output.push if c of FileInfo.formatters
|
||||
output.push if $.hasOwn(FileInfo.formatters, c)
|
||||
FileInfo.formatters[c].call post
|
||||
else
|
||||
<%= html('${s}') %>
|
||||
|
||||
@ -7,8 +7,8 @@ Fourchan =
|
||||
initBoard: ->
|
||||
if g.BOARD.config.code_tags
|
||||
$.on window, 'prettyprint:cb', (e) ->
|
||||
return if not (post = g.posts[e.detail.ID])
|
||||
return if not (pre = $$('.prettyprint', post.nodes.comment)[e.detail.i])
|
||||
return if not (post = g.posts.get(e.detail.ID))
|
||||
return if not (pre = $$('.prettyprint', post.nodes.comment)[+e.detail.i])
|
||||
unless $.hasClass pre, 'prettyprinted'
|
||||
pre.innerHTML = e.detail.html
|
||||
$.addClass pre, 'prettyprinted'
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
IDColor =
|
||||
init: ->
|
||||
return unless g.VIEW in ['index', 'thread'] and Conf['Color User IDs']
|
||||
@ids = {
|
||||
Heaven: [0, 0, 0, '#fff']
|
||||
}
|
||||
@ids = $.dict()
|
||||
@ids['Heaven'] = [0, 0, 0, '#fff']
|
||||
|
||||
Callbacks.Post.push
|
||||
name: 'Color User IDs'
|
||||
|
||||
@ -6,11 +6,11 @@ ModContact =
|
||||
cb: @node
|
||||
|
||||
node: ->
|
||||
return if @isClone or !ModContact.specific[@info.capcode]
|
||||
return if @isClone or !$.hasOwn(ModContact.specific, @info.capcode)
|
||||
links = $.el 'span', className: 'contact-links brackets-wrap'
|
||||
$.extend links, ModContact.template(@info.capcode)
|
||||
$.after @nodes.capcode, links
|
||||
if (moved = @info.comment.match /This thread was moved to >>>\/(\w+)\//) and ModContact.moveNote[moved[1]]
|
||||
if (moved = @info.comment.match /This thread was moved to >>>\/(\w+)\//) and $.hasOwn(ModContact.moveNote, moved[1])
|
||||
moveNote = $.el 'div', className: 'move-note'
|
||||
$.extend moveNote, ModContact.moveNote[moved[1]]
|
||||
$.add @nodes.post, moveNote
|
||||
|
||||
@ -39,7 +39,7 @@ Nav =
|
||||
Nav.scroll +1
|
||||
|
||||
getThread: ->
|
||||
return g.threads["#{g.BOARD}.#{g.THREADID}"].nodes.root if g.VIEW is 'thread'
|
||||
return g.threads.get("#{g.BOARD}.#{g.THREADID}").nodes.root if g.VIEW is 'thread'
|
||||
return if $.hasClass doc, 'catalog-mode'
|
||||
for threadRoot in $$ g.SITE.selectors.thread
|
||||
thread = Get.threadFromRoot threadRoot
|
||||
|
||||
@ -126,6 +126,6 @@ RelativeDates =
|
||||
|
||||
markStale: (data) ->
|
||||
return if data in RelativeDates.stale # We can call RelativeDates.update() multiple times.
|
||||
return if data instanceof Post and !g.posts[data.fullID] # collected post.
|
||||
return if data instanceof Post and !g.posts.get(data.fullID) # collected post.
|
||||
return if data instanceof Element and !doc.contains(data) # removed catalog reply.
|
||||
RelativeDates.stale.push data
|
||||
|
||||
@ -13,7 +13,7 @@ Time =
|
||||
|
||||
format: (formatString, date) ->
|
||||
formatString.replace /%(.)/g, (s, c) ->
|
||||
if c of Time.formatters
|
||||
if $.hasOwn(Time.formatters, c)
|
||||
Time.formatters[c].call(date)
|
||||
else
|
||||
s
|
||||
|
||||
@ -59,7 +59,8 @@ Favicon =
|
||||
'<%= readBase64('Metro.unreadNSFW.png') %>'
|
||||
'<%= readBase64('Metro.unreadNSFWY.png') %>'
|
||||
]
|
||||
}[Conf['favicon']]
|
||||
}
|
||||
items = $.getOwn(items, Conf['favicon'])
|
||||
|
||||
f = Favicon
|
||||
t = 'data:image/png;base64,'
|
||||
|
||||
@ -18,10 +18,10 @@ MarkNewIPs =
|
||||
when postCount - MarkNewIPs.postCount + deletedPosts.length
|
||||
i = MarkNewIPs.ipCount
|
||||
for fullID in newPosts
|
||||
MarkNewIPs.markNew g.posts[fullID], ++i
|
||||
MarkNewIPs.markNew g.posts.get(fullID), ++i
|
||||
when -deletedPosts.length
|
||||
for fullID in newPosts
|
||||
MarkNewIPs.markOld g.posts[fullID]
|
||||
MarkNewIPs.markOld g.posts.get(fullID)
|
||||
MarkNewIPs.ipCount = ipCount
|
||||
MarkNewIPs.postCount = postCount
|
||||
|
||||
|
||||
@ -88,7 +88,7 @@ ReplyPruning =
|
||||
return if e.detail[404]
|
||||
for fullID in e.detail.newPosts
|
||||
ReplyPruning.total++
|
||||
ReplyPruning.totalFiles++ if g.posts[fullID].file
|
||||
ReplyPruning.totalFiles++ if g.posts.get(fullID).file
|
||||
return
|
||||
|
||||
update: ->
|
||||
@ -105,7 +105,7 @@ ReplyPruning =
|
||||
|
||||
if ReplyPruning.hidden < hidden2
|
||||
while ReplyPruning.hidden < hidden2 and ReplyPruning.position < posts.keys.length
|
||||
post = posts[posts.keys[ReplyPruning.position++]]
|
||||
post = posts.get(posts.keys[ReplyPruning.position++])
|
||||
if post.isReply and not post.isFetchedQuote
|
||||
$.add ReplyPruning.container, node while (node = ReplyPruning.summary.nextSibling) and node isnt post.nodes.root
|
||||
$.add ReplyPruning.container, post.nodes.root
|
||||
@ -115,7 +115,7 @@ ReplyPruning =
|
||||
else if ReplyPruning.hidden > hidden2
|
||||
frag = $.frag()
|
||||
while ReplyPruning.hidden > hidden2 and ReplyPruning.position > 0
|
||||
post = posts[posts.keys[--ReplyPruning.position]]
|
||||
post = posts.get(posts.keys[--ReplyPruning.position])
|
||||
if post.isReply and not post.isFetchedQuote
|
||||
$.prepend frag, node while (node = ReplyPruning.container.lastChild) and node isnt post.nodes.root
|
||||
$.prepend frag, post.nodes.root
|
||||
|
||||
@ -55,7 +55,7 @@ ThreadStats =
|
||||
{posts} = ThreadStats.thread
|
||||
n = posts.keys.length
|
||||
for i in [ThreadStats.postIndex...n] by 1
|
||||
post = posts[posts.keys[i]]
|
||||
post = posts.get(posts.keys[i])
|
||||
unless post.isFetchedQuote
|
||||
ThreadStats.postCount++
|
||||
ThreadStats.fileCount += post.files.length
|
||||
@ -132,7 +132,7 @@ ThreadStats =
|
||||
ThreadStats.showPage and
|
||||
ThreadStats.pageCountEl.textContent isnt '1' and
|
||||
!g.SITE.threadModTimeIgnoresSage and
|
||||
ThreadStats.thread.posts[ThreadStats.thread.lastPost].info.date > ThreadStats.lastPageUpdate
|
||||
ThreadStats.thread.posts.get(ThreadStats.thread.lastPost).info.date > ThreadStats.lastPageUpdate
|
||||
)
|
||||
clearTimeout ThreadStats.timeout
|
||||
ThreadStats.timeout = setTimeout ThreadStats.fetchPage, 5 * $.SECOND
|
||||
|
||||
@ -266,7 +266,7 @@ ThreadUpdater =
|
||||
|
||||
# XXX Reject updates that falsely delete the last post.
|
||||
return if postObjects[postObjects.length-1].no < lastPost and
|
||||
new Date(req.getResponseHeader('Last-Modified')) - thread.posts[lastPost].info.date < 30 * $.SECOND
|
||||
new Date(req.getResponseHeader('Last-Modified')) - thread.posts.get(lastPost).info.date < 30 * $.SECOND
|
||||
|
||||
g.SITE.Build.spoilerRange[board] = OP.custom_spoiler
|
||||
thread.setStatus 'Archived', !!OP.archived
|
||||
@ -291,7 +291,7 @@ ThreadUpdater =
|
||||
continue if ID <= lastPost
|
||||
|
||||
# XXX Resurrect wrongly deleted posts.
|
||||
if (post = thread.posts[ID]) and not post.isFetchedQuote
|
||||
if (post = thread.posts.get(ID)) and not post.isFetchedQuote
|
||||
post.resurrect()
|
||||
continue
|
||||
|
||||
@ -304,14 +304,14 @@ ThreadUpdater =
|
||||
# Check for deleted posts.
|
||||
deletedPosts = []
|
||||
for ID in ThreadUpdater.postIDs when ID not in index
|
||||
thread.posts[ID].kill()
|
||||
thread.posts.get(ID).kill()
|
||||
deletedPosts.push "#{board}.#{ID}"
|
||||
ThreadUpdater.postIDs = index
|
||||
|
||||
# Check for deleted files.
|
||||
deletedFiles = []
|
||||
for ID in ThreadUpdater.fileIDs when not (ID in files or "#{board}.#{ID}" in deletedPosts)
|
||||
thread.posts[ID].kill true
|
||||
thread.posts.get(ID).kill true
|
||||
deletedFiles.push "#{board}.#{ID}"
|
||||
ThreadUpdater.fileIDs = files
|
||||
|
||||
|
||||
@ -150,7 +150,7 @@ ThreadWatcher =
|
||||
if Conf['Auto Watch']
|
||||
ThreadWatcher.addRaw boardID, threadID, {}, cb
|
||||
else if Conf['Auto Watch Reply']
|
||||
ThreadWatcher.add (g.threads[boardID + '.' + threadID] or new Thread(threadID, g.boards[boardID] or new Board(boardID))), cb
|
||||
ThreadWatcher.add (g.threads.get(boardID + '.' + threadID) or new Thread(threadID, g.boards[boardID] or new Board(boardID))), cb
|
||||
onIndexUpdate: (e) ->
|
||||
{db} = ThreadWatcher
|
||||
siteID = g.SITE.ID
|
||||
@ -169,7 +169,7 @@ ThreadWatcher =
|
||||
nKilled++
|
||||
ThreadWatcher.refresh() if nKilled
|
||||
onThreadRefresh: (e) ->
|
||||
thread = g.threads[e.detail.threadID]
|
||||
thread = g.threads.get(e.detail.threadID)
|
||||
return unless e.detail[404] and ThreadWatcher.isWatched thread
|
||||
# Update dead status.
|
||||
ThreadWatcher.add thread
|
||||
@ -215,7 +215,7 @@ ThreadWatcher =
|
||||
ThreadWatcher.clearRequests()
|
||||
|
||||
initLastModified: ->
|
||||
lm = ($.lastModified['ThreadWatcher'] or= {})
|
||||
lm = ($.lastModified['ThreadWatcher'] or= $.dict())
|
||||
for siteID, boards of ThreadWatcher.dbLM.data
|
||||
for boardID, data of boards.boards
|
||||
if ThreadWatcher.db.get {siteID, boardID}
|
||||
@ -287,7 +287,7 @@ ThreadWatcher =
|
||||
{siteID, boardID} = board[0]
|
||||
lmDate = @getResponseHeader('Last-Modified')
|
||||
ThreadWatcher.dbLM.extend {siteID, boardID, val: $.item(url, lmDate)}
|
||||
threads = {}
|
||||
threads = $.dict()
|
||||
pageLength = 0
|
||||
nThreads = 0
|
||||
oldest = null
|
||||
@ -449,7 +449,7 @@ ThreadWatcher =
|
||||
div
|
||||
|
||||
setPrefixes: (threads) ->
|
||||
prefixes = {}
|
||||
prefixes = $.dict()
|
||||
for {siteID} in threads
|
||||
continue if siteID of prefixes
|
||||
len = 0
|
||||
@ -474,7 +474,7 @@ ThreadWatcher =
|
||||
ThreadWatcher.setPrefixes threads
|
||||
for {siteID, boardID, threadID, data} in threads
|
||||
# Add missing excerpt for threads added by Auto Watch
|
||||
if not data.excerpt? and siteID is g.SITE.ID and (thread = g.threads["#{boardID}.#{threadID}"]) and thread.OP
|
||||
if not data.excerpt? and siteID is g.SITE.ID and (thread = g.threads.get("#{boardID}.#{threadID}")) and thread.OP
|
||||
ThreadWatcher.db.extend {boardID, threadID, val: {excerpt: Get.threadExcerpt thread}}
|
||||
nodes.push ThreadWatcher.makeLine siteID, boardID, threadID, data
|
||||
{list} = ThreadWatcher
|
||||
@ -554,7 +554,7 @@ ThreadWatcher =
|
||||
ThreadWatcher.addRaw boardID, threadID, data, cb
|
||||
|
||||
addRaw: (boardID, threadID, data, cb) ->
|
||||
oldData = ThreadWatcher.db.get {boardID, threadID, defaultValue: {}}
|
||||
oldData = ThreadWatcher.db.get {boardID, threadID, defaultValue: $.dict()}
|
||||
delete oldData.last
|
||||
delete oldData.modified
|
||||
$.extend oldData, data
|
||||
@ -594,7 +594,7 @@ ThreadWatcher =
|
||||
$.rmClass entryEl, rmClass
|
||||
entryEl.textContent = text
|
||||
true
|
||||
$.on entryEl, 'click', -> ThreadWatcher.toggle g.threads["#{g.BOARD}.#{g.THREADID}"]
|
||||
$.on entryEl, 'click', -> ThreadWatcher.toggle g.threads.get("#{g.BOARD}.#{g.THREADID}")
|
||||
|
||||
addMenuEntries: ->
|
||||
entries = []
|
||||
|
||||
@ -131,7 +131,7 @@ Unread =
|
||||
postIDs = Unread.thread.posts.keys
|
||||
for i in [Unread.readCount...postIDs.length] by 1
|
||||
ID = +postIDs[i]
|
||||
unless Unread.thread.posts[ID].isFetchedQuote
|
||||
unless Unread.thread.posts.get(ID).isFetchedQuote
|
||||
break if ID > Unread.lastReadPost
|
||||
Unread.posts.delete ID
|
||||
Unread.postsQuotingYou.delete ID
|
||||
@ -217,7 +217,7 @@ Unread =
|
||||
postIDs = Unread.thread.posts.keys
|
||||
for i in [Unread.readCount...postIDs.length] by 1
|
||||
ID = +postIDs[i]
|
||||
unless Unread.thread.posts[ID].isFetchedQuote
|
||||
unless Unread.thread.posts.get(ID).isFetchedQuote
|
||||
break if Unread.posts.has ID
|
||||
Unread.lastReadPost = ID
|
||||
Unread.readCount++
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
UnreadIndex =
|
||||
lastReadPost: {}
|
||||
hr: {}
|
||||
markReadLink: {}
|
||||
lastReadPost: $.dict()
|
||||
hr: $.dict()
|
||||
markReadLink: $.dict()
|
||||
|
||||
init: ->
|
||||
return unless g.VIEW is 'index' and Conf['Remember Last Read Post'] and Conf['Unread Line in Index']
|
||||
@ -27,7 +27,7 @@ UnreadIndex =
|
||||
onIndexRefresh: (e) ->
|
||||
return if e.detail.isCatalog
|
||||
for threadID in e.detail.threadIDs
|
||||
thread = g.threads[threadID]
|
||||
thread = g.threads.get(threadID)
|
||||
UnreadIndex.update thread
|
||||
|
||||
onPostsInserted: (e) ->
|
||||
|
||||
@ -154,7 +154,7 @@ Captcha.fixes =
|
||||
else if n isnt 9 and (i = @imageKeys16.indexOf key) >= 0 and i % 4 < w and (img = @images[n - (4 - i//4)*w + (i % 4)])
|
||||
img.click()
|
||||
verify.focus()
|
||||
else if dx = {'Up': n, 'Down': w, 'Left': last, 'Right': 1}[key]
|
||||
else if dx = $.getOwn({'Up': n, 'Down': w, 'Left': last, 'Right': 1}, key)
|
||||
x = (x + dx) % (n + w)
|
||||
if n < x < last
|
||||
x = if dx is last then n else last
|
||||
|
||||
@ -112,7 +112,7 @@ QR =
|
||||
statusCheck: ->
|
||||
return unless QR.nodes
|
||||
{thread} = QR.posts[0]
|
||||
if thread isnt 'new' and g.threads["#{g.BOARD}.#{thread}"].isDead
|
||||
if thread isnt 'new' and g.threads.get("#{g.BOARD}.#{thread}").isDead
|
||||
QR.abort()
|
||||
else
|
||||
QR.status()
|
||||
@ -258,7 +258,7 @@ QR =
|
||||
status: ->
|
||||
return unless QR.nodes
|
||||
{thread} = QR.posts[0]
|
||||
if thread isnt 'new' and g.threads["#{g.BOARD}.#{thread}"].isDead
|
||||
if thread isnt 'new' and g.threads.get("#{g.BOARD}.#{thread}").isDead
|
||||
value = 'Dead'
|
||||
disabled = true
|
||||
QR.cooldown.auto = false
|
||||
@ -402,7 +402,7 @@ QR =
|
||||
if file
|
||||
{type} = file
|
||||
blob = new Blob [file], {type}
|
||||
blob.name = "#{Conf['pastedname']}.#{QR.extensionFromType[type] or 'jpg'}"
|
||||
blob.name = "#{Conf['pastedname']}.#{$.getOwn(QR.extensionFromType, type) or 'jpg'}"
|
||||
QR.open()
|
||||
QR.handleFiles [blob]
|
||||
$.addClass QR.nodes.el, 'dump'
|
||||
@ -651,7 +651,7 @@ QR =
|
||||
post = QR.posts[0]
|
||||
post.forceSave()
|
||||
threadID = post.thread
|
||||
thread = g.BOARD.threads[threadID]
|
||||
thread = g.BOARD.threads.get(threadID)
|
||||
if g.BOARD.ID is 'f' and threadID is 'new'
|
||||
filetag = QR.nodes.flashTag.value
|
||||
|
||||
@ -662,7 +662,7 @@ QR =
|
||||
err = 'New threads require a subject.'
|
||||
else unless !!g.BOARD.config.text_only or post.file
|
||||
err = 'No file selected.'
|
||||
else if g.BOARD.threads[threadID].isClosed
|
||||
else if g.BOARD.threads.get(threadID).isClosed
|
||||
err = 'You can\'t reply to this thread anymore.'
|
||||
else unless post.com or post.file
|
||||
err = 'No comment or file.'
|
||||
|
||||
@ -7,7 +7,7 @@ QR.cooldown =
|
||||
init: ->
|
||||
return unless Conf['Quick Reply']
|
||||
@data = Conf['cooldowns']
|
||||
@changes = {}
|
||||
@changes = $.dict()
|
||||
$.sync 'cooldowns', @sync
|
||||
|
||||
# Called from QR
|
||||
@ -35,7 +35,7 @@ QR.cooldown =
|
||||
QR.cooldown.count()
|
||||
|
||||
sync: (data) ->
|
||||
QR.cooldown.data = data or {}
|
||||
QR.cooldown.data = data or $.dict()
|
||||
QR.cooldown.start()
|
||||
|
||||
add: (threadID, postID) ->
|
||||
@ -63,7 +63,7 @@ QR.cooldown =
|
||||
|
||||
delete: (post) ->
|
||||
return unless QR.cooldown.data
|
||||
cooldowns = (QR.cooldown.data[post.board.ID] or= {})
|
||||
cooldowns = (QR.cooldown.data[post.board.ID] or= $.dict())
|
||||
for id, cooldown of cooldowns
|
||||
if !cooldown.delay? and cooldown.threadID is post.thread.ID and cooldown.postID is post.ID
|
||||
QR.cooldown.set post.board.ID, id, null
|
||||
@ -71,7 +71,7 @@ QR.cooldown =
|
||||
|
||||
secondsDeletion: (post) ->
|
||||
return 0 unless QR.cooldown.data and Conf['Cooldown']
|
||||
cooldowns = QR.cooldown.data[post.board.ID] or {}
|
||||
cooldowns = QR.cooldown.data[post.board.ID] or $.dict()
|
||||
for start, cooldown of cooldowns
|
||||
if !cooldown.delay? and cooldown.threadID is post.thread.ID and cooldown.postID is post.ID
|
||||
seconds = QR.cooldown.delays.deletion - (Date.now() - start) // $.SECOND
|
||||
@ -87,25 +87,25 @@ QR.cooldown =
|
||||
|
||||
mergeChange: (data, scope, id, value) ->
|
||||
if value
|
||||
(data[scope] or= {})[id] = value
|
||||
(data[scope] or= $.dict())[id] = value
|
||||
else if scope of data
|
||||
delete data[scope][id]
|
||||
delete data[scope] if Object.keys(data[scope]).length is 0
|
||||
|
||||
set: (scope, id, value) ->
|
||||
QR.cooldown.mergeChange QR.cooldown.data, scope, id, value
|
||||
(QR.cooldown.changes[scope] or= {})[id] = value
|
||||
(QR.cooldown.changes[scope] or= $.dict())[id] = value
|
||||
|
||||
save: ->
|
||||
{changes} = QR.cooldown
|
||||
return unless Object.keys(changes).length
|
||||
$.get 'cooldowns', {}, ({cooldowns}) ->
|
||||
$.get 'cooldowns', $.dict(), ({cooldowns}) ->
|
||||
for scope of QR.cooldown.changes
|
||||
for id, value of QR.cooldown.changes[scope]
|
||||
QR.cooldown.mergeChange cooldowns, scope, id, value
|
||||
QR.cooldown.data = cooldowns
|
||||
$.set 'cooldowns', cooldowns, ->
|
||||
QR.cooldown.changes = {}
|
||||
QR.cooldown.changes = $.dict()
|
||||
|
||||
update: ->
|
||||
return unless QR.cooldown.isCounting
|
||||
@ -117,7 +117,7 @@ QR.cooldown =
|
||||
seconds = 0
|
||||
|
||||
if Conf['Cooldown'] then for scope in [g.BOARD.ID, 'global']
|
||||
cooldowns = (QR.cooldown.data[scope] or= {})
|
||||
cooldowns = (QR.cooldown.data[scope] or= $.dict())
|
||||
|
||||
for start, cooldown of cooldowns
|
||||
start = +start
|
||||
|
||||
@ -122,6 +122,7 @@ QR.post = class
|
||||
@spoiler = input.checked
|
||||
return
|
||||
{name} = input.dataset
|
||||
return unless name in ['thread', 'name', 'email', 'sub', 'com', 'filename', 'flag']
|
||||
prev = @[name]
|
||||
@[name] = input.value or input.dataset.default or null
|
||||
switch name
|
||||
@ -340,7 +341,7 @@ QR.post = class
|
||||
@file.newName = (@filename or '').replace /[/\\]/g, '-'
|
||||
unless QR.validExtension.test @filename
|
||||
# 4chan will truncate the filename if it has no extension.
|
||||
@file.newName += ".#{QR.extensionFromType[@file.type] or 'jpg'}"
|
||||
@file.newName += ".#{$.getOwn(QR.extensionFromType, @file.type) or 'jpg'}"
|
||||
|
||||
updateFilename: ->
|
||||
long = "#{@filename} (#{@filesize})"
|
||||
|
||||
@ -10,7 +10,7 @@ QuoteBacklink =
|
||||
# Second callback adds relevant containers into posts.
|
||||
# This is is so that fetched posts can get their backlinks,
|
||||
# and that as much backlinks are appended in the background as possible.
|
||||
containers: {}
|
||||
containers: $.dict()
|
||||
init: ->
|
||||
return if g.VIEW not in ['index', 'thread'] or !Conf['Quote Backlinks']
|
||||
|
||||
@ -35,7 +35,7 @@ QuoteBacklink =
|
||||
$.add a, QuoteYou.mark.cloneNode(true) if markYours
|
||||
for quote in @quotes
|
||||
containers = [QuoteBacklink.getContainer quote]
|
||||
if (post = g.posts[quote]) and post.nodes.backlinkContainer
|
||||
if (post = g.posts.get(quote)) and post.nodes.backlinkContainer
|
||||
# Don't add OP clones when OP Backlinks is disabled,
|
||||
# as the clones won't have the backlink containers.
|
||||
for clone in post.clones
|
||||
|
||||
@ -33,7 +33,7 @@ QuoteInline =
|
||||
return if $.modifiedClick e
|
||||
|
||||
{boardID, threadID, postID} = Get.postDataFromLink @
|
||||
return if Conf['Inline Cross-thread Quotes Only'] and g.VIEW is 'thread' and g.posts["#{boardID}.#{postID}"]?.nodes.root.offsetParent # exists and not hidden
|
||||
return if Conf['Inline Cross-thread Quotes Only'] and g.VIEW is 'thread' and g.posts.get("#{boardID}.#{postID}")?.nodes.root.offsetParent # exists and not hidden
|
||||
return if $.hasClass(doc, 'catalog-mode')
|
||||
|
||||
e.preventDefault()
|
||||
@ -66,7 +66,7 @@ QuoteInline =
|
||||
new Fetcher boardID, threadID, postID, inline, quoter
|
||||
|
||||
return if not (
|
||||
(post = g.posts["#{boardID}.#{postID}"]) and
|
||||
(post = g.posts.get("#{boardID}.#{postID}")) and
|
||||
context.thread is post.thread
|
||||
)
|
||||
|
||||
@ -98,13 +98,13 @@ QuoteInline =
|
||||
return if not (el = root.firstElementChild)
|
||||
|
||||
# Dereference clone.
|
||||
post = g.posts["#{boardID}.#{postID}"]
|
||||
post = g.posts.get("#{boardID}.#{postID}")
|
||||
post.rmClone el.dataset.clone
|
||||
|
||||
# Decrease forward count and unhide.
|
||||
if Conf['Forward Hiding'] and
|
||||
isBacklink and
|
||||
context.thread is g.threads["#{boardID}.#{threadID}"] and
|
||||
context.thread is g.threads.get("#{boardID}.#{threadID}") and
|
||||
not --post.forwarded
|
||||
delete post.forwarded
|
||||
$.rmClass post.nodes.root, 'forwarded'
|
||||
|
||||
@ -40,7 +40,7 @@ QuotePreview =
|
||||
endEvents: 'mouseout click'
|
||||
cb: QuotePreview.mouseout
|
||||
|
||||
if Conf['Quote Highlighting'] and (origin = g.posts["#{boardID}.#{postID}"])
|
||||
if Conf['Quote Highlighting'] and (origin = g.posts.get("#{boardID}.#{postID}"))
|
||||
posts = [origin].concat origin.clones
|
||||
# Remove the clone that's in the qp from the array.
|
||||
posts.pop()
|
||||
|
||||
@ -11,6 +11,6 @@ QuoteStrikeThrough =
|
||||
return if @isClone
|
||||
for quotelink in @nodes.quotelinks
|
||||
{boardID, postID} = Get.postDataFromLink quotelink
|
||||
if g.posts["#{boardID}.#{postID}"]?.isHidden
|
||||
if g.posts.get("#{boardID}.#{postID}")?.isHidden
|
||||
$.addClass quotelink, 'filtered'
|
||||
return
|
||||
|
||||
@ -34,9 +34,9 @@ QuoteThreading =
|
||||
name: 'Quote Threading'
|
||||
cb: @node
|
||||
|
||||
parent: {}
|
||||
children: {}
|
||||
inserted: {}
|
||||
parent: $.dict()
|
||||
children: $.dict()
|
||||
inserted: $.dict()
|
||||
|
||||
toggleThreading: ->
|
||||
@setThreadingState !Conf['Thread Quotes']
|
||||
@ -65,7 +65,7 @@ QuoteThreading =
|
||||
|
||||
parents = new Set()
|
||||
lastParent = null
|
||||
for quote in @quotes when parent = g.posts[quote]
|
||||
for quote in @quotes when parent = g.posts.get(quote)
|
||||
if not parent.isFetchedQuote and parent.isReply and parent.ID < @ID
|
||||
parents.add parent.ID
|
||||
lastParent = parent if not lastParent or parent.ID > lastParent.ID
|
||||
@ -141,7 +141,7 @@ QuoteThreading =
|
||||
else
|
||||
nodes = []
|
||||
Unread.order = new RandomAccessList()
|
||||
QuoteThreading.inserted = {}
|
||||
QuoteThreading.inserted = $.dict()
|
||||
posts.forEach (post) ->
|
||||
return if post.isFetchedQuote
|
||||
Unread.order.push post
|
||||
|
||||
@ -54,7 +54,7 @@ Quotify =
|
||||
@board.ID
|
||||
quoteID = "#{boardID}.#{postID}"
|
||||
|
||||
if post = g.posts[quoteID]
|
||||
if post = g.posts.get(quoteID)
|
||||
unless post.isDead
|
||||
# Don't (Dead) when quotifying in an archived post,
|
||||
# and we know the post still exists.
|
||||
|
||||
@ -9,4 +9,4 @@ class CatalogThreadNative
|
||||
@boardID = @nodes.thumb.parentNode.pathname.split(/\/+/)[1]
|
||||
@board = g.boards[@boardID] or new Board(@boardID)
|
||||
@ID = @threadID = +(root.dataset.id or root.id).match(/\d*$/)[0]
|
||||
@thread = @board.threads[@ID] or new Thread(@ID, @board)
|
||||
@thread = @board.threads.get(@ID) or new Thread(@ID, @board)
|
||||
|
||||
@ -17,6 +17,6 @@ class Connection
|
||||
typeof e.data is 'string' and
|
||||
e.data[...g.NAMESPACE.length] is g.NAMESPACE
|
||||
data = JSON.parse e.data[g.NAMESPACE.length..]
|
||||
for type, value of data
|
||||
@cb[type]? value
|
||||
for type, value of data when $.hasOwn(@cb, type)
|
||||
@cb[type] value
|
||||
return
|
||||
|
||||
@ -19,14 +19,14 @@ class DataBoard
|
||||
@data['4chan.org'] = {boards, lastChecked}
|
||||
delete @data.boards
|
||||
delete @data.lastChecked
|
||||
@data[g.SITE.ID] or= boards: {}
|
||||
@data[g.SITE.ID] or= boards: $.dict()
|
||||
|
||||
changes: []
|
||||
|
||||
save: (change, cb) ->
|
||||
change()
|
||||
@changes.push change
|
||||
$.get @key, {boards: {}}, (items) =>
|
||||
$.get @key, {boards: $.dict()}, (items) =>
|
||||
return unless @changes.length
|
||||
needSync = ((items[@key].version or 0) > (@data.version or 0))
|
||||
if needSync
|
||||
@ -39,7 +39,7 @@ class DataBoard
|
||||
cb?()
|
||||
|
||||
forceSync: (cb) ->
|
||||
$.get @key, {boards: {}}, (items) =>
|
||||
$.get @key, {boards: $.dict()}, (items) =>
|
||||
if (items[@key].version or 0) > (@data.version or 0)
|
||||
@initData items[@key]
|
||||
change() for change in @changes
|
||||
@ -78,17 +78,17 @@ class DataBoard
|
||||
|
||||
setUnsafe: ({siteID, boardID, threadID, postID, val}) ->
|
||||
siteID or= g.SITE.ID
|
||||
@data[siteID] or= boards: {}
|
||||
@data[siteID] or= boards: $.dict()
|
||||
if postID isnt undefined
|
||||
((@data[siteID].boards[boardID] or= {})[threadID] or= {})[postID] = val
|
||||
((@data[siteID].boards[boardID] or= $.dict())[threadID] or= $.dict())[postID] = val
|
||||
else if threadID isnt undefined
|
||||
(@data[siteID].boards[boardID] or= {})[threadID] = val
|
||||
(@data[siteID].boards[boardID] or= $.dict())[threadID] = val
|
||||
else
|
||||
@data[siteID].boards[boardID] = val
|
||||
|
||||
extend: ({siteID, boardID, threadID, postID, val}, cb) ->
|
||||
@save =>
|
||||
oldVal = @get {siteID, boardID, threadID, postID, defaultValue: {}}
|
||||
oldVal = @get {siteID, boardID, threadID, postID, defaultValue: $.dict()}
|
||||
for key, subVal of val
|
||||
if typeof subVal is 'undefined'
|
||||
delete oldVal[key]
|
||||
@ -147,7 +147,7 @@ class DataBoard
|
||||
ajaxCleanParse: (boardID, response1, response2) ->
|
||||
siteID = g.SITE.ID
|
||||
return if not (board = @data[siteID].boards[boardID])
|
||||
threads = {}
|
||||
threads = $.dict()
|
||||
if response1
|
||||
for page in response1
|
||||
for thread in page.threads
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
class Fetcher
|
||||
constructor: (@boardID, @threadID, @postID, @root, @quoter) ->
|
||||
if post = g.posts["#{@boardID}.#{@postID}"]
|
||||
if post = g.posts.get("#{@boardID}.#{@postID}")
|
||||
@insert post
|
||||
return
|
||||
|
||||
# 4chan X catalog data
|
||||
if (post = Index.replyData?["#{@boardID}.#{@postID}"]) and (thread = g.threads["#{@boardID}.#{@threadID}"])
|
||||
if (post = Index.replyData?["#{@boardID}.#{@postID}"]) and (thread = g.threads.get("#{@boardID}.#{@threadID}"))
|
||||
board = g.boards[@boardID]
|
||||
post = new Post g.SITE.Build.postFromObject(post, @boardID), thread, board, {isFetchedQuote: true}
|
||||
Main.callbackNodes 'Post', [post]
|
||||
@ -53,7 +53,7 @@ class Fetcher
|
||||
fetchedPost: (req, isCached) ->
|
||||
# In case of multiple callbacks for the same request,
|
||||
# don't parse the same original post more than once.
|
||||
if post = g.posts["#{@boardID}.#{@postID}"]
|
||||
if post = g.posts.get("#{@boardID}.#{@postID}")
|
||||
@insert post
|
||||
return
|
||||
|
||||
@ -96,7 +96,7 @@ class Fetcher
|
||||
|
||||
board = g.boards[@boardID] or
|
||||
new Board @boardID
|
||||
thread = g.threads["#{@boardID}.#{@threadID}"] or
|
||||
thread = g.threads.get("#{@boardID}.#{@threadID}") or
|
||||
new Thread @threadID, board
|
||||
post = new Post g.SITE.Build.postFromObject(post, @boardID), thread, board, {isFetchedQuote: true}
|
||||
Main.callbackNodes 'Post', [post]
|
||||
@ -115,7 +115,7 @@ class Fetcher
|
||||
for key of media when /_link$/.test key
|
||||
# Image/thumbnail URLs loaded over HTTP can be modified in transit.
|
||||
# Require them to be from an HTTP host so that no referrer is sent to them from an HTTPS page.
|
||||
delete media[key] unless media[key]?.match /^http:\/\//
|
||||
delete media[key] unless $.getOwn(media, key)?.match /^http:\/\//
|
||||
that.parseArchivedPost @response, url, archive
|
||||
return true
|
||||
return false
|
||||
@ -123,7 +123,7 @@ class Fetcher
|
||||
parseArchivedPost: (data, url, archive) ->
|
||||
# In case of multiple callbacks for the same request,
|
||||
# don't parse the same original post more than once.
|
||||
if post = g.posts["#{@boardID}.#{@postID}"]
|
||||
if post = g.posts.get("#{@boardID}.#{@postID}")
|
||||
@insert post
|
||||
return
|
||||
|
||||
@ -210,7 +210,7 @@ class Fetcher
|
||||
|
||||
board = g.boards[@boardID] or
|
||||
new Board @boardID
|
||||
thread = g.threads["#{@boardID}.#{@threadID}"] or
|
||||
thread = g.threads.get("#{@boardID}.#{@threadID}") or
|
||||
new Thread @threadID, board
|
||||
post = new Post g.SITE.Build.post(o), thread, board, {isFetchedQuote: true}
|
||||
post.kill()
|
||||
|
||||
@ -59,9 +59,9 @@ class Post
|
||||
<% if (readJSON('/.tests_enabled')) { %>
|
||||
return if @forBuildTest
|
||||
<% } %>
|
||||
if g.posts[@fullID]
|
||||
if g.posts.get(@fullID)
|
||||
@isRebuilt = true
|
||||
@clones = g.posts[@fullID].clones
|
||||
@clones = g.posts.get(@fullID).clones
|
||||
clone.origin = @ for clone in @clones
|
||||
|
||||
@thread.lastPost = @ID if !@isFetchedQuote and @ID > @thread.lastPost
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
class ShimSet
|
||||
constructor: ->
|
||||
@elements = {}
|
||||
@elements = $.dict()
|
||||
@size = 0
|
||||
has: (value) ->
|
||||
value of @elements
|
||||
|
||||
@ -16,3 +16,9 @@ class SimpleDict
|
||||
forEach: (fn) ->
|
||||
fn @[key] for key in [@keys...]
|
||||
return
|
||||
|
||||
get: (key) ->
|
||||
if key is 'keys'
|
||||
undefined
|
||||
else
|
||||
$.getOwn(@, key)
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
class Thread
|
||||
toString: -> @ID
|
||||
|
||||
constructor: (@ID, @board) ->
|
||||
constructor: (ID, @board) ->
|
||||
@ID = +ID
|
||||
@threadID = @ID
|
||||
@boardID = @board.ID
|
||||
@siteID = g.SITE.ID
|
||||
|
||||
@ -27,7 +27,7 @@ sub: function(css) {
|
||||
var sel = variables;
|
||||
for (var i = 0; i < words.length; i++) {
|
||||
if (typeof sel !== 'object') return ':not(*)';
|
||||
sel = sel[words[i]];
|
||||
sel = $.getOwn(sel, words[i]);
|
||||
}
|
||||
if (typeof sel !== 'string') return ':not(*)';
|
||||
return sel;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
var Conf, E, c, d, doc, docSet, g;
|
||||
|
||||
Conf = {};
|
||||
Conf = Object.create(null);
|
||||
c = console;
|
||||
d = document;
|
||||
doc = d.documentElement;
|
||||
@ -13,8 +13,8 @@ docSet = function() {
|
||||
g = {
|
||||
VERSION: '<%= readJSON('/version.json').version %>',
|
||||
NAMESPACE: '<%= meta.name %>.',
|
||||
sites: {},
|
||||
boards: {}
|
||||
sites: Object.create(null),
|
||||
boards: Object.create(null)
|
||||
};
|
||||
|
||||
E = (function() {
|
||||
|
||||
@ -33,7 +33,7 @@ Main =
|
||||
# Flatten default values from Config into Conf
|
||||
flatten = (parent, obj) ->
|
||||
if obj instanceof Array
|
||||
Conf[parent] = obj[0]
|
||||
Conf[parent] = $.dict.clone(obj[0])
|
||||
else if typeof obj is 'object'
|
||||
for key, val of obj
|
||||
flatten key, val
|
||||
@ -57,15 +57,15 @@ Main =
|
||||
flatten null, Config
|
||||
|
||||
for db in DataBoard.keys
|
||||
Conf[db] = {}
|
||||
Conf['customTitles'] = {'4chan.org': {boards: {'qa': {'boardTitle': {orig: '/qa/ - Question & Answer', title: '/qa/ - 2D / Random'}}}}}
|
||||
Conf['boardConfig'] = boards: {}
|
||||
Conf[db] = $.dict()
|
||||
Conf['customTitles'] = $.dict.clone {'4chan.org': {boards: {'qa': {'boardTitle': {orig: '/qa/ - Question & Answer', title: '/qa/ - 2D / Random'}}}}}
|
||||
Conf['boardConfig'] = boards: $.dict()
|
||||
Conf['archives'] = Redirect.archives
|
||||
Conf['selectedArchives'] = {}
|
||||
Conf['cooldowns'] = {}
|
||||
Conf['Index Sort'] = {}
|
||||
Conf["Last Long Reply Thresholds #{i}"] = {} for i in [0...2]
|
||||
Conf['siteProperties'] = {}
|
||||
Conf['selectedArchives'] = $.dict()
|
||||
Conf['cooldowns'] = $.dict()
|
||||
Conf['Index Sort'] = $.dict()
|
||||
Conf["Last Long Reply Thresholds #{i}"] = $.dict() for i in [0...2]
|
||||
Conf['siteProperties'] = $.dict()
|
||||
|
||||
# XXX old key names
|
||||
Conf['Except Archives from Encryption'] = false
|
||||
@ -84,7 +84,7 @@ Main =
|
||||
$.addCSP "script-src #{jsWhitelist.replace(/^#.*$/mg, '').replace(/[\s;]+/g, ' ').trim()}"
|
||||
|
||||
# Get saved values as items
|
||||
items = {}
|
||||
items = $.dict()
|
||||
items[key] = undefined for key of Conf
|
||||
items['previousversion'] = undefined
|
||||
($.getSync or $.get) items, (items) ->
|
||||
@ -363,7 +363,7 @@ Main =
|
||||
else
|
||||
g.BOARD
|
||||
threadID = +threadRoot.id.match(/\d*$/)[0]
|
||||
return if !threadID or boardObj.threads[threadID]?.nodes.root
|
||||
return if !threadID or boardObj.threads.get(threadID)?.nodes.root
|
||||
thread = new Thread threadID, boardObj
|
||||
thread.nodes.root = threadRoot
|
||||
threads.push thread
|
||||
|
||||
@ -40,6 +40,32 @@ $.extend = (object, properties) ->
|
||||
object[key] = val
|
||||
return
|
||||
|
||||
$.dict = ->
|
||||
Object.create(null)
|
||||
|
||||
$.dict.clone = (obj) ->
|
||||
if typeof obj isnt 'object' or obj is null
|
||||
obj
|
||||
else if obj instanceof Array
|
||||
arr = []
|
||||
for i in [0...obj.length] by 1
|
||||
arr.push $.dict.clone(obj[i])
|
||||
arr
|
||||
else
|
||||
map = Object.create(null)
|
||||
for key, val of obj
|
||||
map[key] = $.dict.clone(val)
|
||||
map
|
||||
|
||||
$.dict.json = (str) ->
|
||||
$.dict.clone(JSON.parse(str))
|
||||
|
||||
$.hasOwn = (obj, key) ->
|
||||
Object::hasOwnProperty.call(obj, key)
|
||||
|
||||
$.getOwn = (obj, key) ->
|
||||
if Object::hasOwnProperty.call(obj, key) then obj[key] else undefined
|
||||
|
||||
$.ajax = do ->
|
||||
if window.wrappedJSObject and not XMLHttpRequest.wrappedJSObject
|
||||
pageXHR = XPCNativeWrapper window.wrappedJSObject.XMLHttpRequest
|
||||
@ -85,7 +111,7 @@ $.ajax = do ->
|
||||
# XXX https://bugs.chromium.org/p/chromium/issues/detail?id=920638
|
||||
do ->
|
||||
requestID = 0
|
||||
requests = {}
|
||||
requests = $.dict()
|
||||
|
||||
$.ajaxPageInit = ->
|
||||
if Conf['Chromium CORB Bug'] and g.SITE.software is 'yotsuba'
|
||||
@ -97,7 +123,7 @@ do ->
|
||||
$.set 'Chromium CORB Bug', (Conf['Chromium CORB Bug'] = false)
|
||||
|
||||
$.global ->
|
||||
window.FCX.requests = {}
|
||||
window.FCX.requests = Object.create(null)
|
||||
|
||||
document.addEventListener '4chanXAjax', (e) ->
|
||||
{url, timeout, responseType, withCredentials, type, onprogress, form, headers, id} = e.detail
|
||||
@ -144,7 +170,8 @@ do ->
|
||||
return unless (req = requests[e.detail.id])
|
||||
delete requests[e.detail.id]
|
||||
if e.detail.status
|
||||
$.extend req, e.detail
|
||||
for key in ['status', 'statusText', 'response', 'responseHeaderString']
|
||||
req[key] = e.detail[key]
|
||||
if req.responseType is 'document'
|
||||
req.response = new DOMParser().parseFromString(e.detail.response, 'text/html')
|
||||
req.onloadend()
|
||||
@ -165,7 +192,7 @@ do ->
|
||||
# Status Code 304: Not modified
|
||||
# With the `If-Modified-Since` header we only receive the HTTP headers and no body for 304 responses.
|
||||
# This saves a lot of bandwidth and CPU time for both the users and the servers.
|
||||
$.lastModified = {}
|
||||
$.lastModified = $.dict()
|
||||
$.whenModified = (url, bucket, cb, options={}) ->
|
||||
{timeout, ajax} = options
|
||||
params = []
|
||||
@ -174,12 +201,12 @@ $.whenModified = (url, bucket, cb, options={}) ->
|
||||
params.push "t=#{Date.now()}" if url.split('/')[2] is 'a.4cdn.org'
|
||||
url0 = url
|
||||
url += '?' + params.join('&') if params.length
|
||||
headers = {}
|
||||
headers = $.dict()
|
||||
if (t = $.lastModified[bucket]?[url0])?
|
||||
headers['If-Modified-Since'] = t
|
||||
r = (ajax or $.ajax) url, {
|
||||
onloadend: ->
|
||||
($.lastModified[bucket] or= {})[url0] = @getResponseHeader('Last-Modified')
|
||||
($.lastModified[bucket] or= $.dict())[url0] = @getResponseHeader('Last-Modified')
|
||||
cb.call @
|
||||
timeout
|
||||
headers
|
||||
@ -187,7 +214,7 @@ $.whenModified = (url, bucket, cb, options={}) ->
|
||||
r
|
||||
|
||||
do ->
|
||||
reqs = {}
|
||||
reqs = $.dict()
|
||||
$.cache = (url, cb, options={}) ->
|
||||
{ajax} = options
|
||||
if (req = reqs[url])
|
||||
@ -212,11 +239,13 @@ do ->
|
||||
|
||||
$.cb =
|
||||
checked: ->
|
||||
$.set @name, @checked
|
||||
Conf[@name] = @checked
|
||||
if $.hasOwn(Conf, @name)
|
||||
$.set @name, @checked
|
||||
Conf[@name] = @checked
|
||||
value: ->
|
||||
$.set @name, @value.trim()
|
||||
Conf[@name] = @value
|
||||
if $.hasOwn(Conf, @name)
|
||||
$.set @name, @value.trim()
|
||||
Conf[@name] = @value
|
||||
|
||||
$.asap = (test, cb) ->
|
||||
if test()
|
||||
@ -478,14 +507,14 @@ $.platform = '<%= type %>';
|
||||
|
||||
$.hasStorage = do ->
|
||||
try
|
||||
return true if localStorage[g.NAMESPACE + 'hasStorage'] is 'true'
|
||||
localStorage[g.NAMESPACE + 'hasStorage'] = 'true'
|
||||
return localStorage[g.NAMESPACE + 'hasStorage'] is 'true'
|
||||
return true if localStorage.getItem(g.NAMESPACE + 'hasStorage') is 'true'
|
||||
localStorage.setItem(g.NAMESPACE + 'hasStorage', 'true')
|
||||
return localStorage.getItem(g.NAMESPACE + 'hasStorage') is 'true'
|
||||
catch
|
||||
false
|
||||
|
||||
$.item = (key, val) ->
|
||||
item = {}
|
||||
item = $.dict()
|
||||
item[key] = val
|
||||
item
|
||||
|
||||
@ -496,7 +525,7 @@ $.oneItemSugar = (fn) ->
|
||||
else
|
||||
fn key, val
|
||||
|
||||
$.syncing = {}
|
||||
$.syncing = $.dict()
|
||||
|
||||
$.securityCheck = (data) ->
|
||||
if location.protocol isnt 'https:'
|
||||
@ -505,13 +534,13 @@ $.securityCheck = (data) ->
|
||||
<% if (type === 'crx') { %>
|
||||
# https://developer.chrome.com/extensions/storage.html
|
||||
$.oldValue =
|
||||
local: {}
|
||||
sync: {}
|
||||
local: $.dict()
|
||||
sync: $.dict()
|
||||
|
||||
chrome.storage.onChanged.addListener (changes, area) ->
|
||||
for key of changes
|
||||
oldValue = $.oldValue.local[key] ? $.oldValue.sync[key]
|
||||
$.oldValue[area][key] = changes[key].newValue
|
||||
$.oldValue[area][key] = $.dict.clone(changes[key].newValue)
|
||||
newValue = $.oldValue.local[key] ? $.oldValue.sync[key]
|
||||
cb = $.syncing[key]
|
||||
if cb and JSON.stringify(newValue) isnt JSON.stringify(oldValue)
|
||||
@ -542,11 +571,12 @@ $.get = $.oneItemSugar (data, cb) ->
|
||||
if $.engine is 'gecko' and area is 'sync' and keys.length > 3
|
||||
keys = null
|
||||
chrome.storage[area].get keys, (result) ->
|
||||
result = $.dict.clone(result)
|
||||
if chrome.runtime.lastError
|
||||
c.error chrome.runtime.lastError.message
|
||||
if keys is null
|
||||
result2 = {}
|
||||
result2[key] = val for key, val of result when key of data
|
||||
result2 = $.dict()
|
||||
result2[key] = val for key, val of result when $.hasOwn(data, key)
|
||||
result = result2
|
||||
for key of data
|
||||
$.oldValue[area][key] = result[key]
|
||||
@ -560,8 +590,8 @@ $.get = $.oneItemSugar (data, cb) ->
|
||||
|
||||
do ->
|
||||
items =
|
||||
local: {}
|
||||
sync: {}
|
||||
local: $.dict()
|
||||
sync: $.dict()
|
||||
|
||||
exceedsQuota = (key, value) ->
|
||||
# bytes in UTF-8
|
||||
@ -579,7 +609,7 @@ do ->
|
||||
|
||||
timeout = {}
|
||||
setArea = (area, cb) ->
|
||||
data = {}
|
||||
data = $.dict()
|
||||
$.extend data, items[area]
|
||||
return if !Object.keys(data).length or timeout[area] > Date.now()
|
||||
chrome.storage[area].set data, ->
|
||||
@ -609,8 +639,8 @@ do ->
|
||||
|
||||
$.clear = (cb) ->
|
||||
return unless $.crxWorking()
|
||||
items.local = {}
|
||||
items.sync = {}
|
||||
items.local = $.dict()
|
||||
items.sync = $.dict()
|
||||
count = 2
|
||||
err = null
|
||||
done = ->
|
||||
@ -631,7 +661,7 @@ if GM?.deleteValue? and window.BroadcastChannel and not GM_addValueChangeListene
|
||||
|
||||
$.on $.syncChannel, 'message', (e) ->
|
||||
for key, val of e.data when (cb = $.syncing[key])
|
||||
cb JSON.parse(JSON.stringify(val)), key
|
||||
cb $.dict.json(JSON.stringify(val)), key
|
||||
|
||||
$.sync = (key, cb) ->
|
||||
$.syncing[key] = cb
|
||||
@ -642,7 +672,7 @@ if GM?.deleteValue? and window.BroadcastChannel and not GM_addValueChangeListene
|
||||
unless keys instanceof Array
|
||||
keys = [keys]
|
||||
Promise.all(GM.deleteValue(g.NAMESPACE + key) for key in keys).then ->
|
||||
items = {}
|
||||
items = $.dict()
|
||||
items[key] = undefined for key in keys
|
||||
$.syncChannel.postMessage items
|
||||
cb?()
|
||||
@ -651,7 +681,7 @@ if GM?.deleteValue? and window.BroadcastChannel and not GM_addValueChangeListene
|
||||
keys = Object.keys items
|
||||
Promise.all(GM.getValue(g.NAMESPACE + key) for key in keys).then (values) ->
|
||||
for val, i in values when val
|
||||
items[keys[i]] = JSON.parse val
|
||||
items[keys[i]] = $.dict.json val
|
||||
cb items
|
||||
|
||||
$.set = $.oneItemSugar (items, cb) ->
|
||||
@ -675,7 +705,7 @@ else
|
||||
$.getValue = GM_getValue
|
||||
$.listValues = -> GM_listValues() # error when called if missing
|
||||
else if $.hasStorage
|
||||
$.getValue = (key) -> localStorage[key]
|
||||
$.getValue = (key) -> localStorage.getItem(key)
|
||||
$.listValues = ->
|
||||
key for key of localStorage when key[...g.NAMESPACE.length] is g.NAMESPACE
|
||||
else
|
||||
@ -686,12 +716,12 @@ else
|
||||
$.setValue = GM_setValue
|
||||
$.deleteValue = GM_deleteValue
|
||||
else if GM_deleteValue?
|
||||
$.oldValue = {}
|
||||
$.oldValue = $.dict()
|
||||
$.setValue = (key, val) ->
|
||||
GM_setValue key, val
|
||||
if key of $.syncing
|
||||
$.oldValue[key] = val
|
||||
localStorage[key] = val if $.hasStorage # for `storage` events
|
||||
localStorage.setItem(key, val) if $.hasStorage # for `storage` events
|
||||
$.deleteValue = (key) ->
|
||||
GM_deleteValue key
|
||||
if key of $.syncing
|
||||
@ -699,10 +729,10 @@ else
|
||||
localStorage.removeItem key if $.hasStorage # for `storage` events
|
||||
$.cantSync = true if !$.hasStorage
|
||||
else if $.hasStorage
|
||||
$.oldValue = {}
|
||||
$.oldValue = $.dict()
|
||||
$.setValue = (key, val) ->
|
||||
$.oldValue[key] = val if key of $.syncing
|
||||
localStorage[key] = val
|
||||
localStorage.setItem(key, val)
|
||||
$.deleteValue = (key) ->
|
||||
delete $.oldValue[key] if key of $.syncing
|
||||
localStorage.removeItem key
|
||||
@ -715,7 +745,7 @@ else
|
||||
$.sync = (key, cb) ->
|
||||
$.syncing[key] = GM_addValueChangeListener g.NAMESPACE + key, (key2, oldValue, newValue, remote) ->
|
||||
if remote
|
||||
newValue = JSON.parse newValue unless newValue is undefined
|
||||
newValue = $.dict.json newValue unless newValue is undefined
|
||||
cb newValue, key
|
||||
$.forceSync = ->
|
||||
else if GM_deleteValue? or $.hasStorage
|
||||
@ -730,7 +760,7 @@ else
|
||||
if newValue?
|
||||
return if newValue is $.oldValue[key]
|
||||
$.oldValue[key] = newValue
|
||||
cb JSON.parse(newValue), key[g.NAMESPACE.length..]
|
||||
cb $.dict.json(newValue), key[g.NAMESPACE.length..]
|
||||
else
|
||||
return unless $.oldValue[key]?
|
||||
delete $.oldValue[key]
|
||||
@ -760,7 +790,7 @@ else
|
||||
$.getSync = (items, cb) ->
|
||||
for key of items when (val2 = $.getValue g.NAMESPACE + key)
|
||||
try
|
||||
items[key] = JSON.parse val2
|
||||
items[key] = $.dict.json val2
|
||||
catch err
|
||||
# XXX https://github.com/ccd0/4chan-x/issues/2218
|
||||
unless /^(?:undefined)*$/.test(val2)
|
||||
|
||||
@ -10,7 +10,7 @@ eventPageRequest = do ->
|
||||
|
||||
<% } %>
|
||||
CrossOrigin =
|
||||
binary: (url, cb, headers={}) ->
|
||||
binary: (url, cb, headers=$.dict()) ->
|
||||
# XXX https://forums.lanik.us/viewtopic.php?f=64&t=24173&p=78310
|
||||
url = url.replace /^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, '$1//adv/'
|
||||
<% if (type === 'crx') { %>
|
||||
@ -73,7 +73,7 @@ CrossOrigin =
|
||||
name = match.replace /\\"/g, '"'
|
||||
if /^text\/plain;\s*charset=x-user-defined$/i.test(mime)
|
||||
# In JS Blocker (Safari) content type comes back as 'text/plain; charset=x-user-defined'; guess from filename instead.
|
||||
mime = QR.typeFromExtension[name.match(/[^.]*$/)[0].toLowerCase()] or 'application/octet-stream'
|
||||
mime = $.getOwn(QR.typeFromExtension, name.match(/[^.]*$/)[0].toLowerCase()) or 'application/octet-stream'
|
||||
blob = new Blob([data], {type: mime})
|
||||
blob.name = name
|
||||
cb blob
|
||||
@ -85,13 +85,13 @@ CrossOrigin =
|
||||
responseHeaderString: null
|
||||
getResponseHeader: (headerName) ->
|
||||
if !@responseHeaders? and @responseHeaderString?
|
||||
@responseHeaders = {}
|
||||
@responseHeaders = $.dict()
|
||||
for header in @responseHeaderString.split('\r\n')
|
||||
if (i = header.indexOf(':')) >= 0
|
||||
key = header[...i].trim().toLowerCase()
|
||||
val = header[i+1..].trim()
|
||||
@responseHeaders[key] = val
|
||||
(@responseHeaders or {})[headerName.toLowerCase()] ? null
|
||||
@responseHeaders?[headerName.toLowerCase()] ? null
|
||||
abort: ->
|
||||
onloadend: ->
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ SW.tinyboard =
|
||||
detect: ->
|
||||
for script in $$ 'script:not([src])', d.head
|
||||
if (m = script.textContent.match(/\bvar configRoot=(".*?")/))
|
||||
properties = {}
|
||||
properties = $.dict()
|
||||
try
|
||||
root = JSON.parse m[1]
|
||||
if root[0] is '/'
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
Build =
|
||||
staticPath: '//s.4cdn.org/image/'
|
||||
gifIcon: if window.devicePixelRatio >= 2 then '@2x.gif' else '.gif'
|
||||
spoilerRange: {}
|
||||
spoilerRange: $.dict()
|
||||
|
||||
shortFilename: (filename) ->
|
||||
ext = filename.match(/\.?[^\.]*$/)[0]
|
||||
@ -69,8 +69,9 @@ Build =
|
||||
o.file = SW.yotsuba.Build.parseJSONFile(data, {siteID, boardID})
|
||||
o.files.push o.file
|
||||
# Temporary JSON properties for events such as April 1 / Halloween
|
||||
o.extra = $.dict()
|
||||
for key of data when key[0] is 'x'
|
||||
o[key] = data[key]
|
||||
o.extra[key] = data[key]
|
||||
o
|
||||
|
||||
parseJSONFile: (data, {siteID, boardID}) ->
|
||||
@ -133,7 +134,7 @@ Build =
|
||||
capcodePlural = 'Verified Users'
|
||||
capcodeDescription = ''
|
||||
else
|
||||
capcodeLong = {'Admin': 'Administrator', 'Mod': 'Moderator'}[capcode] or capcode
|
||||
capcodeLong = $.getOwn({'Admin': 'Administrator', 'Mod': 'Moderator'}, capcode) or capcode
|
||||
capcodePlural = "#{capcodeLong}s"
|
||||
capcodeDescription = "a 4chan #{capcodeLong}"
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
?{email}{<a href="mailto:${encodeURIComponent(email).replace(/%40/g, "@")}" class="useremail">}
|
||||
<span class="name?{capcode}{ capcode}">${name}</span>
|
||||
?{tripcode}{ <span class="postertrip">${tripcode}</span>}
|
||||
?{o.xa19s}{ <span class="like-score">${o.xa19s}</span>}
|
||||
?{o.extra.xa19s}{ <span class="like-score">${o.extra.xa19s}</span>}
|
||||
?{pass}{ <span title="Pass user since ${pass}" class="n-pu"></span>}
|
||||
?{capcode}{ <strong class="capcode hand id_${capcodeLC}" title="Highlight posts by ${capcodePlural}">## ${capcode}</strong>}
|
||||
?{email}{</a>}
|
||||
@ -19,7 +19,7 @@
|
||||
<span class="postNum?{!(boardID === "f" && !o.isReply)}{ desktop}">
|
||||
<a href="${postLink}" title="Link to this post">No.</a>
|
||||
<a href="${quoteLink}" title="Reply to this post">${ID}</a>
|
||||
?{o.xa19l && o.isReply}{ <a data-cmd="like-post" href="#" class="like-btn">Like! ×${o.xa19l}</a>}
|
||||
?{o.extra.xa19l && o.isReply}{ <a data-cmd="like-post" href="#" class="like-btn">Like! ×${o.extra.xa19l}</a>}
|
||||
?{o.isSticky}{ <img src="${staticPath}sticky${gifIcon}" alt="Sticky" title="Sticky"?{boardID === "f"}{ style="height: 18px; width: 18px;"}{ class="stickyIcon retina"}>}
|
||||
?{o.isClosed && !o.isArchived}{ <img src="${staticPath}closed${gifIcon}" alt="Closed" title="Closed"?{boardID === "f"}{ style="height: 18px; width: 18px;"}{ class="closedIcon retina"}>}
|
||||
?{o.isArchived}{ <img src="${staticPath}archived${gifIcon}" alt="Archived" title="Archived" class="archivedIcon retina">}
|
||||
|
||||
@ -7,14 +7,14 @@ Site =
|
||||
init: (cb) ->
|
||||
$.extend Conf['siteProperties'], Site.defaultProperties
|
||||
hostname = Site.resolve()
|
||||
if hostname and Conf['siteProperties'][hostname].software of SW
|
||||
if hostname and $.hasOwn(SW, Conf['siteProperties'][hostname].software)
|
||||
@set hostname
|
||||
cb()
|
||||
$.onExists doc, 'body', =>
|
||||
for software of SW when (changes = SW[software].detect?())
|
||||
changes.software = software
|
||||
hostname = location.hostname.replace(/^www\./, '')
|
||||
properties = (Conf['siteProperties'][hostname] or= {})
|
||||
properties = (Conf['siteProperties'][hostname] or= $.dict())
|
||||
changed = 0
|
||||
for key of changes when properties[key] isnt changes[key]
|
||||
properties[key] = changes[key]
|
||||
@ -29,7 +29,7 @@ Site =
|
||||
|
||||
resolve: (url=location) ->
|
||||
{hostname} = url
|
||||
while hostname and hostname not of Conf['siteProperties']
|
||||
while hostname and not $.hasOwn(Conf['siteProperties'], hostname)
|
||||
hostname = hostname.replace(/^[^.]*\.?/, '')
|
||||
if hostname
|
||||
hostname = canonical if (canonical = Conf['siteProperties'][hostname].canonical)
|
||||
@ -43,7 +43,7 @@ Site =
|
||||
for ID, properties of Conf['siteProperties']
|
||||
continue if properties.canonical
|
||||
software = properties.software
|
||||
continue unless software and SW[software]
|
||||
continue unless software and $.hasOwn(SW, software)
|
||||
g.sites[ID] = site = Object.create SW[software]
|
||||
$.extend site, {ID, siteID: ID, properties, software}
|
||||
g.SITE = g.sites[hostname]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user