Merge branch 'v3'
This commit is contained in:
commit
cc7decfd34
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -32,7 +32,6 @@ Recursive =
|
|||||||
|
|
||||||
apply: (recursive, post, args...) ->
|
apply: (recursive, post, args...) ->
|
||||||
{fullID} = post
|
{fullID} = post
|
||||||
for ID, post of g.posts
|
g.posts.forEach (post) ->
|
||||||
if fullID in post.quotes
|
if fullID in post.quotes
|
||||||
recursive post, args...
|
recursive post, args...
|
||||||
return
|
|
||||||
|
|||||||
@ -40,22 +40,28 @@ Get =
|
|||||||
allQuotelinksLinkingTo: (post) ->
|
allQuotelinksLinkingTo: (post) ->
|
||||||
# Get quotelinks & backlinks linking to the given post.
|
# Get quotelinks & backlinks linking to the given post.
|
||||||
quotelinks = []
|
quotelinks = []
|
||||||
handleQuotes = (post, type) ->
|
{posts} = g
|
||||||
quotelinks.push post.nodes[type]...
|
fullID = {post}
|
||||||
quotelinks.push clone.nodes[type]... for clone in post.clones
|
handleQuotes = (qPost, type) ->
|
||||||
|
quotelinks.push qPost.nodes[type]...
|
||||||
|
quotelinks.push clone.nodes[type]... for clone in qPost.clones
|
||||||
return
|
return
|
||||||
# First:
|
# First:
|
||||||
# In every posts,
|
# In every posts,
|
||||||
# if it did quote this post,
|
# if it did quote this post,
|
||||||
# get all their backlinks.
|
# get all their backlinks.
|
||||||
handleQuotes quoterPost, 'quotelinks' for ID, quoterPost of g.posts when post.fullID in quoterPost.quotes
|
posts.forEach (qPost) ->
|
||||||
|
if fullID in qPost.quotes
|
||||||
|
handleQuotes qPost, 'quotelinks'
|
||||||
|
|
||||||
# Second:
|
# Second:
|
||||||
# If we have quote backlinks:
|
# If we have quote backlinks:
|
||||||
# in all posts this post quoted
|
# in all posts this post quoted
|
||||||
# and their clones,
|
# and their clones,
|
||||||
# get all of their backlinks.
|
# get all of their backlinks.
|
||||||
if Conf['Quote Backlinks']
|
if Conf['Quote Backlinks']
|
||||||
handleQuotes quotedPost, 'backlinks' for quote in post.quotes when quotedPost = g.posts[quote]
|
handleQuotes qPost, 'backlinks' for quote in post.quotes when qPost = posts[quote]
|
||||||
|
|
||||||
# Third:
|
# Third:
|
||||||
# Filter out irrelevant quotelinks.
|
# Filter out irrelevant quotelinks.
|
||||||
quotelinks.filter (quotelink) ->
|
quotelinks.filter (quotelink) ->
|
||||||
|
|||||||
@ -9,10 +9,7 @@ doc = d.documentElement
|
|||||||
g =
|
g =
|
||||||
VERSION: '<%= version %>'
|
VERSION: '<%= version %>'
|
||||||
NAMESPACE: '<%= meta.name.replace(' ', '_') %>.'
|
NAMESPACE: '<%= meta.name.replace(' ', '_') %>.'
|
||||||
TYPE: 'sfw'
|
|
||||||
boards: {}
|
boards: {}
|
||||||
threads: {}
|
|
||||||
posts: {}
|
|
||||||
|
|
||||||
Mascots =
|
Mascots =
|
||||||
'Akiyama_Mio':
|
'Akiyama_Mio':
|
||||||
|
|||||||
@ -2,6 +2,8 @@ Index =
|
|||||||
init: ->
|
init: ->
|
||||||
return if g.BOARD.ID is 'f' or g.VIEW is 'catalog' or !Conf['JSON Navigation']
|
return if g.BOARD.ID is 'f' or g.VIEW is 'catalog' or !Conf['JSON Navigation']
|
||||||
|
|
||||||
|
@board = "#{g.BOARD}"
|
||||||
|
|
||||||
@button = $.el 'a',
|
@button = $.el 'a',
|
||||||
className: 'index-refresh-shortcut fa'
|
className: 'index-refresh-shortcut fa'
|
||||||
title: 'Refresh Index'
|
title: 'Refresh Index'
|
||||||
@ -113,12 +115,13 @@ Index =
|
|||||||
scroll: $.debounce 100, ->
|
scroll: $.debounce 100, ->
|
||||||
return if Index.req or Conf['Index Mode'] isnt 'infinite' or (doc.scrollTop <= doc.scrollHeight - (300 + window.innerHeight)) or g.VIEW is 'thread'
|
return if Index.req or Conf['Index Mode'] isnt 'infinite' or (doc.scrollTop <= doc.scrollHeight - (300 + window.innerHeight)) or g.VIEW is 'thread'
|
||||||
Index.pageNum = Index.getCurrentPage() unless Index.pageNum? # Avoid having to pushState to keep track of the current page
|
Index.pageNum = Index.getCurrentPage() unless Index.pageNum? # Avoid having to pushState to keep track of the current page
|
||||||
|
|
||||||
pageNum = Index.pageNum++
|
pageNum = Index.pageNum++
|
||||||
return Index.endNotice() if pageNum >= Index.pagesNum
|
return Index.endNotice() if pageNum >= Index.pagesNum
|
||||||
nodesPerPage = Index.threadsNumPerPage * 2
|
|
||||||
nodes = Index.sortedNodes[nodesPerPage * pageNum ... nodesPerPage * (pageNum + 1)]
|
nodes = Index.buildSinglePage pageNum
|
||||||
Index.buildReplies nodes if Conf['Show Replies']
|
Index.buildReplies nodes if Conf['Show Replies']
|
||||||
$.add Index.root, nodes
|
Index.buildStructure nodes
|
||||||
Index.setPage pageNum
|
Index.setPage pageNum
|
||||||
|
|
||||||
endNotice: do ->
|
endNotice: do ->
|
||||||
@ -240,7 +243,7 @@ Index =
|
|||||||
onabort: onload
|
onabort: onload
|
||||||
onloadend: onload
|
onloadend: onload
|
||||||
,
|
,
|
||||||
whenModified: true
|
whenModified: Index.board is "#{g.BOARD}"
|
||||||
$.addClass Index.button, 'fa-spin'
|
$.addClass Index.button, 'fa-spin'
|
||||||
|
|
||||||
load: (e, pageNum) ->
|
load: (e, pageNum) ->
|
||||||
@ -266,13 +269,16 @@ Index =
|
|||||||
new Notice 'warning', err, 1
|
new Notice 'warning', err, 1
|
||||||
return
|
return
|
||||||
|
|
||||||
|
Navigate.title()
|
||||||
|
Index.board = "#{g.BOARD}"
|
||||||
|
|
||||||
try
|
try
|
||||||
if req.status is 200
|
if req.status is 200
|
||||||
Index.parse JSON.parse(req.response), pageNum
|
Index.parse JSON.parse(req.response), pageNum
|
||||||
else if req.status is 304 and pageNum?
|
else if req.status is 304 and pageNum?
|
||||||
Index.pageNav pageNum
|
Index.pageNav pageNum
|
||||||
catch err
|
catch err
|
||||||
c.error 'Index failure:', err
|
c.error "Index failure: #{err.message}", err.stack
|
||||||
# network error or non-JSON content for example.
|
# network error or non-JSON content for example.
|
||||||
if notice
|
if notice
|
||||||
notice.setType 'error'
|
notice.setType 'error'
|
||||||
@ -303,8 +309,8 @@ Index =
|
|||||||
Index.threadsNumPerPage = pages[0].threads.length
|
Index.threadsNumPerPage = pages[0].threads.length
|
||||||
Index.liveThreadData = pages.reduce ((arr, next) -> arr.concat next.threads), []
|
Index.liveThreadData = pages.reduce ((arr, next) -> arr.concat next.threads), []
|
||||||
Index.liveThreadIDs = Index.liveThreadData.map (data) -> data.no
|
Index.liveThreadIDs = Index.liveThreadData.map (data) -> data.no
|
||||||
for threadID, thread of g.BOARD.threads when thread.ID not in Index.liveThreadIDs
|
g.BOARD.threads.forEach (thread) ->
|
||||||
thread.collect()
|
thread.collect() unless thread.ID in Index.liveThreadIDs
|
||||||
return
|
return
|
||||||
|
|
||||||
buildThreads: ->
|
buildThreads: ->
|
||||||
@ -312,23 +318,23 @@ Index =
|
|||||||
threads = []
|
threads = []
|
||||||
posts = []
|
posts = []
|
||||||
for threadData, i in Index.liveThreadData
|
for threadData, i in Index.liveThreadData
|
||||||
threadRoot = Build.thread g.BOARD, threadData
|
|
||||||
Index.nodes.push threadRoot, $.el 'hr'
|
|
||||||
if thread = g.BOARD.threads[threadData.no]
|
|
||||||
thread.setPage Math.floor i / Index.threadsNumPerPage
|
|
||||||
thread.setStatus 'Sticky', !!threadData.sticky
|
|
||||||
thread.setStatus 'Closed', !!threadData.closed
|
|
||||||
else
|
|
||||||
thread = new Thread threadData.no, g.BOARD
|
|
||||||
threads.push thread
|
|
||||||
continue if thread.ID of thread.posts
|
|
||||||
try
|
try
|
||||||
|
threadRoot = Build.thread g.BOARD, threadData
|
||||||
|
if thread = g.BOARD.threads[threadData.no]
|
||||||
|
thread.setPage Math.floor i / Index.threadsNumPerPage
|
||||||
|
thread.setStatus 'Sticky', !!threadData.sticky
|
||||||
|
thread.setStatus 'Closed', !!threadData.closed
|
||||||
|
else
|
||||||
|
thread = new Thread threadData.no, g.BOARD
|
||||||
|
threads.push thread
|
||||||
|
Index.nodes.push threadRoot
|
||||||
|
continue if thread.ID of thread.posts
|
||||||
posts.push new Post $('.opContainer', threadRoot), thread, g.BOARD
|
posts.push new Post $('.opContainer', threadRoot), thread, g.BOARD
|
||||||
catch err
|
catch err
|
||||||
# Skip posts that we failed to parse.
|
# Skip posts that we failed to parse.
|
||||||
errors = [] unless errors
|
errors = [] unless errors
|
||||||
errors.push
|
errors.push
|
||||||
message: "Parsing of Post No.#{thread} failed. Post will be skipped."
|
message: "Parsing of Thread No.#{thread} failed. Thread will be skipped."
|
||||||
error: err
|
error: err
|
||||||
Main.handleErrors errors if errors
|
Main.handleErrors errors if errors
|
||||||
|
|
||||||
@ -340,7 +346,7 @@ Index =
|
|||||||
|
|
||||||
buildReplies: (threadRoots) ->
|
buildReplies: (threadRoots) ->
|
||||||
posts = []
|
posts = []
|
||||||
for threadRoot in threadRoots by 2
|
for threadRoot in threadRoots
|
||||||
thread = Get.threadFromRoot threadRoot
|
thread = Get.threadFromRoot threadRoot
|
||||||
i = Index.liveThreadIDs.indexOf thread.ID
|
i = Index.liveThreadIDs.indexOf thread.ID
|
||||||
continue unless lastReplies = Index.liveThreadData[i].last_replies
|
continue unless lastReplies = Index.liveThreadData[i].last_replies
|
||||||
@ -364,52 +370,89 @@ Index =
|
|||||||
Main.callbackNodes Post, posts
|
Main.callbackNodes Post, posts
|
||||||
|
|
||||||
sort: ->
|
sort: ->
|
||||||
switch Conf['Index Sort']
|
{liveThreadIDs, liveThreadData} = Index
|
||||||
when 'bump'
|
sortedThreadIDs = {
|
||||||
sortedThreadIDs = Index.liveThreadIDs
|
lastreply:
|
||||||
when 'lastreply'
|
[liveThreadData...].sort((a, b) ->
|
||||||
sortedThreadIDs = [Index.liveThreadData...].sort((a, b) ->
|
a = num[num.length - 1] if (num = a.last_replies)
|
||||||
a = a.last_replies[a.last_replies.length - 1] if 'last_replies' of a
|
b = num[num.length - 1] if (num = b.last_replies)
|
||||||
b = b.last_replies[b.last_replies.length - 1] if 'last_replies' of b
|
|
||||||
b.no - a.no
|
b.no - a.no
|
||||||
).map (data) -> data.no
|
).map (post) -> post.no
|
||||||
when 'birth'
|
bump: liveThreadIDs
|
||||||
sortedThreadIDs = [Index.liveThreadIDs...].sort (a, b) -> b - a
|
birth: [liveThreadIDs... ].sort (a, b) -> b - a
|
||||||
when 'replycount'
|
replycount: [liveThreadData...].sort((a, b) -> b.replies - a.replies).map (post) -> post.no
|
||||||
sortedThreadIDs = [Index.liveThreadData...].sort((a, b) -> b.replies - a.replies).map (data) -> data.no
|
filecount: [liveThreadData...].sort((a, b) -> b.images - a.images ).map (post) -> post.no
|
||||||
when 'filecount'
|
}[Conf['Index Sort']]
|
||||||
sortedThreadIDs = [Index.liveThreadData...].sort((a, b) -> b.images - a.images).map (data) -> data.no
|
Index.sortedNodes = sortedNodes = new RandomAccessList
|
||||||
Index.sortedNodes = []
|
{nodes} = Index
|
||||||
for threadID in sortedThreadIDs
|
for threadID in sortedThreadIDs
|
||||||
i = Index.liveThreadIDs.indexOf(threadID) * 2
|
sortedNodes.push nodes[Index.liveThreadIDs.indexOf(threadID)]
|
||||||
Index.sortedNodes.push Index.nodes[i], Index.nodes[i + 1]
|
if Index.isSearching and nodes = Index.querySearch(Index.searchInput.value)
|
||||||
if Index.isSearching
|
Index.sortedNodes = new RandomAccessList nodes
|
||||||
Index.sortedNodes = Index.querySearch(Index.searchInput.value) or Index.sortedNodes
|
items = [
|
||||||
# Sticky threads
|
# Sticky threads
|
||||||
Index.sortOnTop (thread) -> thread.isSticky
|
fn: (thread) -> thread.isSticky
|
||||||
# Highlighted threads
|
cnd: true
|
||||||
Index.sortOnTop((thread) -> thread.isOnTop) if Conf['Filter']
|
, # Highlighted threads
|
||||||
# Non-hidden threads
|
fn: (thread) -> thread.isOnTop
|
||||||
Index.sortOnTop((thread) -> !thread.isHidden) if Conf['Anchor Hidden Threads']
|
cnd: Conf['Filter']
|
||||||
|
, # Non-hidden threads
|
||||||
|
fn: (thread) -> !thread.isHidden
|
||||||
|
cnd: Conf['Anchor Hidden Threads']
|
||||||
|
]
|
||||||
|
i = 0
|
||||||
|
while item = items[i++]
|
||||||
|
{fn, cnd} = item
|
||||||
|
Index.sortOnTop fn if cnd
|
||||||
|
return
|
||||||
|
|
||||||
sortOnTop: (match) ->
|
sortOnTop: (match) ->
|
||||||
offset = 0
|
offset = 0
|
||||||
for threadRoot, i in Index.sortedNodes by 2 when match Get.threadFromRoot threadRoot
|
{sortedNodes} = Index
|
||||||
Index.sortedNodes.splice offset++ * 2, 0, Index.sortedNodes.splice(i, 2)...
|
threadRoot = sortedNodes.first
|
||||||
|
while threadRoot
|
||||||
|
if match Get.threadFromRoot threadRoot.data
|
||||||
|
target = sortedNodes.first
|
||||||
|
j = 0
|
||||||
|
while j++ < offset
|
||||||
|
target = target.next
|
||||||
|
unless threadRoot is target
|
||||||
|
offset++
|
||||||
|
sortedNodes.before target, threadRoot
|
||||||
|
threadRoot = threadRoot.next
|
||||||
return
|
return
|
||||||
|
|
||||||
buildIndex: ->
|
buildIndex: ->
|
||||||
if Conf['Index Mode'] isnt 'all pages'
|
if Conf['Index Mode'] isnt 'all pages'
|
||||||
pageNum = Index.getCurrentPage()
|
nodes = Index.buildSinglePage Index.getCurrentPage()
|
||||||
nodesPerPage = Index.threadsNumPerPage * 2
|
|
||||||
nodes = Index.sortedNodes[nodesPerPage * pageNum ... nodesPerPage * (pageNum + 1)]
|
|
||||||
else
|
else
|
||||||
nodes = Index.sortedNodes
|
nodes = [(target = Index.sortedNodes.first).data]
|
||||||
|
while target = target.next
|
||||||
|
nodes.push target.data
|
||||||
$.rmAll Index.root
|
$.rmAll Index.root
|
||||||
$.rmAll Header.hover
|
$.rmAll Header.hover
|
||||||
Index.buildReplies nodes if Conf['Show Replies']
|
Index.buildReplies nodes if Conf['Show Replies']
|
||||||
$.add Index.root, nodes
|
Index.buildStructure nodes
|
||||||
$.event 'IndexBuild', nodes
|
|
||||||
|
buildSinglePage: (pageNum) ->
|
||||||
|
nodes = []
|
||||||
|
nodesPerPage = Index.threadsNumPerPage
|
||||||
|
offset = nodesPerPage * pageNum
|
||||||
|
end = offset + nodesPerPage
|
||||||
|
target = Index.sortedNodes.order()[offset]
|
||||||
|
Index.sortedNodes
|
||||||
|
while (offset++ <= end) and target
|
||||||
|
nodes.push target.data
|
||||||
|
target = target.next
|
||||||
|
nodes
|
||||||
|
|
||||||
|
buildStructure: (nodes) ->
|
||||||
|
result = $.frag()
|
||||||
|
i = 0
|
||||||
|
$.add result, [node, $.el 'hr'] while node = nodes[i++]
|
||||||
|
$.add Index.root, result
|
||||||
|
$.rm hr for hr in $$ 'hr + hr', Index.root # Temp fix until I figure out where I fucked up
|
||||||
|
$.event 'IndexBuild', result
|
||||||
|
|
||||||
isSearching: false
|
isSearching: false
|
||||||
|
|
||||||
@ -449,11 +492,14 @@ Index =
|
|||||||
return unless keywords = query.toLowerCase().match /\S+/g
|
return unless keywords = query.toLowerCase().match /\S+/g
|
||||||
Index.search keywords
|
Index.search keywords
|
||||||
|
|
||||||
search: (keywords) ->
|
search: (keywords) ->
|
||||||
found = []
|
found = []
|
||||||
for threadRoot, i in Index.sortedNodes by 2
|
target = Index.sortedNodes.first
|
||||||
if Index.searchMatch Get.threadFromRoot(threadRoot), keywords
|
while target
|
||||||
found.push Index.sortedNodes[i], Index.sortedNodes[i + 1]
|
{data} = target
|
||||||
|
if Index.searchMatch Get.threadFromRoot(data), keywords
|
||||||
|
found.push data
|
||||||
|
target = target.next
|
||||||
found
|
found
|
||||||
|
|
||||||
searchMatch: (thread, keywords) ->
|
searchMatch: (thread, keywords) ->
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
Main =
|
Main =
|
||||||
init: ->
|
init: ->
|
||||||
|
g.threads = new SimpleDict
|
||||||
|
g.posts = new SimpleDict
|
||||||
|
|
||||||
pathname = location.pathname.split '/'
|
pathname = location.pathname.split '/'
|
||||||
g.BOARD = new Board pathname[1]
|
g.BOARD = new Board pathname[1]
|
||||||
return if g.BOARD.ID in ['z', 'fk']
|
return if g.BOARD.ID in ['z', 'fk']
|
||||||
@ -122,7 +125,7 @@ Main =
|
|||||||
'left=0,top=0,width=500,height=255,toolbar=0,resizable=0'
|
'left=0,top=0,width=500,height=255,toolbar=0,resizable=0'
|
||||||
$.before styleSelector.previousSibling, [$.tn '['; passLink, $.tn ']\u00A0\u00A0']
|
$.before styleSelector.previousSibling, [$.tn '['; passLink, $.tn ']\u00A0\u00A0']
|
||||||
|
|
||||||
if g.VIEW is 'thread' or !Conf['JSON Navigation']
|
unless Conf['JSON Navigation'] and g.VIEW is 'index'
|
||||||
Main.initThread()
|
Main.initThread()
|
||||||
else
|
else
|
||||||
$.event '4chanXInitFinished'
|
$.event '4chanXInitFinished'
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
Navigate =
|
Navigate =
|
||||||
path: window.location.pathname
|
path: window.location.pathname
|
||||||
init: ->
|
init: ->
|
||||||
return if g.VIEW is 'catalog' or g.BOARD.ID is 'f' or !Conf['JSON Navigation']
|
return if g.VIEW is 'catalog' or g.BOARD.ID is 'f' or !Conf['JSON Navigation']
|
||||||
|
|
||||||
# blink/webkit throw a popstate on page load. Not what we want.
|
# blink/webkit throw a popstate on page load. Not what we want.
|
||||||
$.ready -> $.on window, 'popstate', Navigate.popstate
|
$.ready -> $.on window, 'popstate', Navigate.popstate
|
||||||
|
|
||||||
|
@title = -> return
|
||||||
|
|
||||||
Thread.callbacks.push
|
Thread.callbacks.push
|
||||||
name: 'Navigate'
|
name: 'Navigate'
|
||||||
cb: @thread
|
cb: @thread
|
||||||
@ -34,10 +36,7 @@ Navigate =
|
|||||||
{posts, threads} = g
|
{posts, threads} = g
|
||||||
|
|
||||||
# Garbage collection
|
# Garbage collection
|
||||||
g.posts = {}
|
g.threads.forEach (thread) -> thread.collect()
|
||||||
g.threads = {}
|
|
||||||
g.BOARD.posts = {}
|
|
||||||
g.BOARD.threads = {}
|
|
||||||
|
|
||||||
QuoteBacklink.containers = {}
|
QuoteBacklink.containers = {}
|
||||||
|
|
||||||
@ -84,7 +83,7 @@ Navigate =
|
|||||||
feature() if condition
|
feature() if condition
|
||||||
catch err
|
catch err
|
||||||
error = [
|
error = [
|
||||||
message: "Quote Threading Failed."
|
message: "#{name} Failed."
|
||||||
error: err
|
error: err
|
||||||
]
|
]
|
||||||
Main.handleErrors error if error
|
Main.handleErrors error if error
|
||||||
@ -106,10 +105,13 @@ Navigate =
|
|||||||
$.off d, 'IndexRefresh', QR.generatePostableThreadsList
|
$.off d, 'IndexRefresh', QR.generatePostableThreadsList
|
||||||
|
|
||||||
updateBoard: (boardID) ->
|
updateBoard: (boardID) ->
|
||||||
g.BOARD = new Board boardID
|
|
||||||
|
|
||||||
req = null
|
req = null
|
||||||
|
|
||||||
|
fullBoardList = $ '#full-board-list', Header.boardList
|
||||||
|
$.rmClass $('.current', fullBoardList), 'current'
|
||||||
|
$.addClass $("a[href*='/#{boardID}/']", fullBoardList), 'current'
|
||||||
|
Header.generateBoardList Conf['boardnav'].replace /(\r\n|\n|\r)/g, ' '
|
||||||
|
|
||||||
onload = (e) ->
|
onload = (e) ->
|
||||||
if e.type is 'abort'
|
if e.type is 'abort'
|
||||||
req.onloadend = null
|
req.onloadend = null
|
||||||
@ -117,9 +119,10 @@ Navigate =
|
|||||||
|
|
||||||
return unless req.status is 200
|
return unless req.status is 200
|
||||||
|
|
||||||
board = do -> try
|
try
|
||||||
for board in JSON.parse(req.response).boards
|
for aboard in JSON.parse(req.response).boards when aboard.board is boardID
|
||||||
return board if board.board is boardID
|
board = aboard
|
||||||
|
break
|
||||||
|
|
||||||
catch err
|
catch err
|
||||||
Main.handleErrors [
|
Main.handleErrors [
|
||||||
@ -130,33 +133,36 @@ Navigate =
|
|||||||
|
|
||||||
return unless board
|
return unless board
|
||||||
Navigate.updateTitle board
|
Navigate.updateTitle board
|
||||||
|
Navigate.updateFavicon !!board.ws_board
|
||||||
return if Favicon.SFW is sfw = !!board.ws_board # Board SFW status hasn't changed
|
|
||||||
|
|
||||||
g.TYPE = if sfw then 'sfw' else 'nsfw'
|
|
||||||
if Conf["NSFW/SFW Mascots"]
|
|
||||||
Main.setMascotString()
|
|
||||||
MascotTools.toggle()
|
|
||||||
|
|
||||||
if Conf["NSFW/SFW Themes"]
|
|
||||||
Main.setThemeString()
|
|
||||||
theme = Themes[Conf[g.THEMESTRING] or if sfw then 'Yotsuba B' else 'Yotsuba'] or Themes[Conf[g.THEMESTRING] = if sfw then 'Yotsuba B' else 'Yotsuba']
|
|
||||||
Style.setTheme theme
|
|
||||||
|
|
||||||
Favicon.SFW = sfw
|
|
||||||
Favicon.el.href = "//s.4cdn.org/image/favicon#{if sfw then '-ws' else ''}.ico"
|
|
||||||
$.add d.head, Favicon.el # Changing the href alone doesn't update the icon on Firefox
|
|
||||||
Favicon.init()
|
|
||||||
|
|
||||||
fullBoardList = $ '#full-board-list', Header.boardList
|
|
||||||
$.rmClass $('.current', fullBoardList), 'current'
|
|
||||||
$.addClass $("a[href*='/#{boardID}/']", fullBoardList), 'current'
|
|
||||||
Header.generateBoardList Conf['boardnav'].replace /(\r\n|\n|\r)/g, ' '
|
|
||||||
|
|
||||||
req = $.ajax '//a.4cdn.org/boards.json',
|
req = $.ajax '//a.4cdn.org/boards.json',
|
||||||
onabort: onload
|
onabort: onload
|
||||||
onloadend: onload
|
onloadend: onload
|
||||||
|
|
||||||
|
updateFavicon: (sfw) ->
|
||||||
|
# TODO: think of a better name for this. Changes style, too.
|
||||||
|
Favicon.el.href = "//s.4cdn.org/image/favicon#{if sfw then '-ws' else ''}.ico"
|
||||||
|
$.add d.head, Favicon.el # Changing the href alone doesn't update the icon on Firefox
|
||||||
|
|
||||||
|
return if Favicon.SFW is sfw # Board SFW status hasn't changed
|
||||||
|
|
||||||
|
Favicon.SFW = sfw
|
||||||
|
Favicon.update()
|
||||||
|
|
||||||
|
g.TYPE = if sfw then 'sfw' else 'nsfw'
|
||||||
|
if Conf["NSFW/SFW Mascots"]
|
||||||
|
Main.setMascotString()
|
||||||
|
MascotTools.toggle()
|
||||||
|
|
||||||
|
if Conf["NSFW/SFW Themes"]
|
||||||
|
Main.setThemeString()
|
||||||
|
theme = Themes[Conf[g.THEMESTRING] or if sfw then 'Yotsuba B' else 'Yotsuba'] or Themes[Conf[g.THEMESTRING] = if sfw then 'Yotsuba B' else 'Yotsuba']
|
||||||
|
Style.setTheme theme
|
||||||
|
|
||||||
|
mainStyleSheet.href = newStyleSheet.href
|
||||||
|
|
||||||
|
Main.setClass()
|
||||||
|
|
||||||
updateTitle: ({board, title}) ->
|
updateTitle: ({board, title}) ->
|
||||||
$.rm subtitle if subtitle = $ '.boardSubtitle'
|
$.rm subtitle if subtitle = $ '.boardSubtitle'
|
||||||
$('.boardTitle').textContent = d.title = "/#{board}/ - #{title}"
|
$('.boardTitle').textContent = d.title = "/#{board}/ - #{title}"
|
||||||
@ -173,6 +179,7 @@ Navigate =
|
|||||||
|
|
||||||
return if view is 'catalog' or 'f' in [boardID, g.BOARD.ID]
|
return if view is 'catalog' or 'f' in [boardID, g.BOARD.ID]
|
||||||
e.preventDefault() if e
|
e.preventDefault() if e
|
||||||
|
Navigate.title = -> return
|
||||||
|
|
||||||
delete Index.pageNum
|
delete Index.pageNum
|
||||||
|
|
||||||
@ -196,18 +203,20 @@ Navigate =
|
|||||||
|
|
||||||
if view is 'index'
|
if view is 'index'
|
||||||
if boardID is g.BOARD.ID
|
if boardID is g.BOARD.ID
|
||||||
d.title = $('.boardTitle').textContent
|
Navigate.title = -> d.title = $('.boardTitle').textContent
|
||||||
else
|
else
|
||||||
Navigate.updateBoard boardID
|
g.BOARD = new Board boardID
|
||||||
|
Navigate.title = -> Navigate.updateBoard boardID
|
||||||
|
|
||||||
Index.update pageNum
|
Index.update pageNum
|
||||||
|
|
||||||
# Moving from index to thread or thread to thread
|
# Moving from index to thread or thread to thread
|
||||||
else
|
else
|
||||||
onload = (e) -> Navigate.load e
|
Navigate.updateFavicon Favicon.SFW
|
||||||
|
{load} = Navigate
|
||||||
Navigate.req = $.ajax "//a.4cdn.org/#{boardID}/res/#{threadID}.json",
|
Navigate.req = $.ajax "//a.4cdn.org/#{boardID}/res/#{threadID}.json",
|
||||||
onabort: onload
|
onabort: load
|
||||||
onloadend: onload
|
onloadend: load
|
||||||
|
|
||||||
setTimeout (->
|
setTimeout (->
|
||||||
if Navigate.req and !Navigate.notice
|
if Navigate.req and !Navigate.notice
|
||||||
@ -221,13 +230,13 @@ Navigate =
|
|||||||
delete Navigate.req
|
delete Navigate.req
|
||||||
delete Navigate.notice
|
delete Navigate.notice
|
||||||
|
|
||||||
if e.type is 'abort'
|
if e.type is 'abort' or req.status isnt 200
|
||||||
req.onloadend = null
|
req.onloadend = null
|
||||||
|
new Notice 'warning', "Failed to load thread.#{if req.status then " #{req.status}" else ''}"
|
||||||
return
|
return
|
||||||
|
|
||||||
try
|
try
|
||||||
if req.status is 200
|
Navigate.parse JSON.parse(req.response).posts
|
||||||
Navigate.parse JSON.parse(req.response).posts
|
|
||||||
catch err
|
catch err
|
||||||
console.error 'Navigate failure:'
|
console.error 'Navigate failure:'
|
||||||
console.log err
|
console.log err
|
||||||
@ -270,7 +279,8 @@ Navigate =
|
|||||||
Main.callbackNodes Thread, [thread]
|
Main.callbackNodes Thread, [thread]
|
||||||
Main.callbackNodes Post, posts
|
Main.callbackNodes Post, posts
|
||||||
|
|
||||||
Navigate.ready 'Quote Threading', QuoteThreading.force, Conf['Quote Threading']
|
Navigate.ready 'Quote Threading', QuoteThreading.force, Conf['Quote Threading'] and not Conf['Unread Count']
|
||||||
|
Navigate.ready 'Unread Count', Unread.ready, Conf['Unread Count']
|
||||||
|
|
||||||
Navigate.buildThread()
|
Navigate.buildThread()
|
||||||
Header.hashScroll.call window
|
Header.hashScroll.call window
|
||||||
@ -281,7 +291,6 @@ Navigate =
|
|||||||
$.add board, [Navigate.threadRoot, $.el 'hr']
|
$.add board, [Navigate.threadRoot, $.el 'hr']
|
||||||
|
|
||||||
if Conf['Unread Count']
|
if Conf['Unread Count']
|
||||||
Navigate.ready 'Unread Count', Unread.ready, not Conf['Quote Threading']
|
|
||||||
Unread.read()
|
Unread.read()
|
||||||
Unread.update()
|
Unread.update()
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ class Board
|
|||||||
toString: -> @ID
|
toString: -> @ID
|
||||||
|
|
||||||
constructor: (@ID) ->
|
constructor: (@ID) ->
|
||||||
@threads = {}
|
@threads = new SimpleDict
|
||||||
@posts = {}
|
@posts = new SimpleDict
|
||||||
|
|
||||||
g.boards[@] = @
|
g.boards[@] = @
|
||||||
@ -5,4 +5,5 @@
|
|||||||
<%= grunt.file.read('src/General/lib/clone.class') %>
|
<%= grunt.file.read('src/General/lib/clone.class') %>
|
||||||
<%= grunt.file.read('src/General/lib/databoard.class') %>
|
<%= grunt.file.read('src/General/lib/databoard.class') %>
|
||||||
<%= grunt.file.read('src/General/lib/notice.class') %>
|
<%= grunt.file.read('src/General/lib/notice.class') %>
|
||||||
<%= grunt.file.read('src/General/lib/randomaccesslist.class') %>
|
<%= grunt.file.read('src/General/lib/randomaccesslist.class') %>
|
||||||
|
<%= grunt.file.read('src/General/lib/simpledict.class') %>
|
||||||
@ -54,7 +54,7 @@ class Post
|
|||||||
@parseFile that
|
@parseFile that
|
||||||
|
|
||||||
@clones = []
|
@clones = []
|
||||||
g.posts[@fullID] = thread.posts[@] = board.posts[@] = @
|
g.posts.push @fullID, thread.posts.push @, board.posts.push @, @
|
||||||
@kill() if that.isArchived
|
@kill() if that.isArchived
|
||||||
|
|
||||||
parseComment: ->
|
parseComment: ->
|
||||||
@ -208,9 +208,9 @@ class Post
|
|||||||
|
|
||||||
collect: ->
|
collect: ->
|
||||||
@kill()
|
@kill()
|
||||||
delete g.posts[@fullID]
|
g.posts.rm @fullID
|
||||||
delete @thread.posts[@]
|
@thread.posts.rm @
|
||||||
delete @board.posts[@]
|
@board.posts.rm @
|
||||||
|
|
||||||
addClone: (context) ->
|
addClone: (context) ->
|
||||||
new Clone @, context
|
new Clone @, context
|
||||||
|
|||||||
@ -1,19 +1,36 @@
|
|||||||
class RandomAccessList
|
class RandomAccessList
|
||||||
constructor: ->
|
constructor: (items) ->
|
||||||
@length = 0
|
@length = 0
|
||||||
|
@push item for item in items if items
|
||||||
|
|
||||||
push: (item) ->
|
push: (data) ->
|
||||||
{ID} = item
|
{ID} = data
|
||||||
|
ID or= data.id
|
||||||
return if @[ID]
|
return if @[ID]
|
||||||
{last} = @
|
{last} = @
|
||||||
|
@[ID] = item =
|
||||||
|
prev: last
|
||||||
|
next: null
|
||||||
|
data: data
|
||||||
|
ID: ID
|
||||||
item.prev = last
|
item.prev = last
|
||||||
@[ID] = item
|
|
||||||
@last = if last
|
@last = if last
|
||||||
last.next = item
|
last.next = item
|
||||||
else
|
else
|
||||||
@first = item
|
@first = item
|
||||||
@length++
|
@length++
|
||||||
|
|
||||||
|
before: (root, item) ->
|
||||||
|
return if item.next is root
|
||||||
|
|
||||||
|
@rmi item
|
||||||
|
|
||||||
|
{prev} = root
|
||||||
|
root.prev = item
|
||||||
|
item.next = root
|
||||||
|
item.prev = prev
|
||||||
|
prev.next = item if prev
|
||||||
|
|
||||||
after: (root, item) ->
|
after: (root, item) ->
|
||||||
return if item.prev is root
|
return if item.prev is root
|
||||||
|
|
||||||
@ -24,7 +41,7 @@ class RandomAccessList
|
|||||||
item.prev = root
|
item.prev = root
|
||||||
item.next = next
|
item.next = next
|
||||||
next.prev = item if next
|
next.prev = item if next
|
||||||
|
|
||||||
prepend: (item) ->
|
prepend: (item) ->
|
||||||
{first} = @
|
{first} = @
|
||||||
return if item is first or not @[item.ID]
|
return if item is first or not @[item.ID]
|
||||||
@ -36,6 +53,11 @@ class RandomAccessList
|
|||||||
|
|
||||||
shift: ->
|
shift: ->
|
||||||
@rm @first.ID
|
@rm @first.ID
|
||||||
|
|
||||||
|
order: ->
|
||||||
|
order = [item = @first]
|
||||||
|
order.push item while item = item.next
|
||||||
|
order
|
||||||
|
|
||||||
rm: (ID) ->
|
rm: (ID) ->
|
||||||
item = @[ID]
|
item = @[ID]
|
||||||
|
|||||||
16
src/General/lib/simpledict.class
Normal file
16
src/General/lib/simpledict.class
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
class SimpleDict
|
||||||
|
constructor: ->
|
||||||
|
@keys = []
|
||||||
|
|
||||||
|
push: (key, data) ->
|
||||||
|
key = "#{key}"
|
||||||
|
@keys.push key unless @[key]
|
||||||
|
@[key] = data
|
||||||
|
|
||||||
|
rm: (key) ->
|
||||||
|
key = "#{key}"
|
||||||
|
if (i = @keys.indexOf key) isnt -1
|
||||||
|
@keys.splice i, 1
|
||||||
|
delete @[key]
|
||||||
|
|
||||||
|
forEach: (fn) -> fn @[key] for key in [@keys...]
|
||||||
@ -4,13 +4,13 @@ class Thread
|
|||||||
|
|
||||||
constructor: (@ID, @board) ->
|
constructor: (@ID, @board) ->
|
||||||
@fullID = "#{@board}.#{@ID}"
|
@fullID = "#{@board}.#{@ID}"
|
||||||
@posts = {}
|
@posts = new SimpleDict
|
||||||
@isSticky = false
|
@isSticky = false
|
||||||
@isClosed = false
|
@isClosed = false
|
||||||
@postLimit = false
|
@postLimit = false
|
||||||
@fileLimit = false
|
@fileLimit = false
|
||||||
|
|
||||||
g.threads[@fullID] = board.threads[@] = @
|
g.threads.push @fullID, board.threads.push @, @
|
||||||
|
|
||||||
setPage: (pageNum) ->
|
setPage: (pageNum) ->
|
||||||
icon = $ '.page-num', @OP.nodes.post
|
icon = $ '.page-num', @OP.nodes.post
|
||||||
@ -44,7 +44,6 @@ class Thread
|
|||||||
@timeOfDeath = Date.now()
|
@timeOfDeath = Date.now()
|
||||||
|
|
||||||
collect: ->
|
collect: ->
|
||||||
for postID, post in @posts
|
@posts.forEach (post) -> post.collect()
|
||||||
post.collect()
|
g.threads.rm @fullID
|
||||||
delete g.threads[@fullID]
|
@board.threads.rm @
|
||||||
delete @board.threads[@]
|
|
||||||
|
|||||||
@ -35,6 +35,14 @@ ImageExpand =
|
|||||||
ImageExpand.toggle Get.postFromNode @
|
ImageExpand.toggle Get.postFromNode @
|
||||||
toggleAll: ->
|
toggleAll: ->
|
||||||
$.event 'CloseMenu'
|
$.event 'CloseMenu'
|
||||||
|
func = (post) ->
|
||||||
|
{file} = post
|
||||||
|
return unless file and file.isImage and doc.contains post.nodes.root
|
||||||
|
if ImageExpand.on and
|
||||||
|
(!Conf['Expand spoilers'] and file.isSpoiler or
|
||||||
|
Conf['Expand from here'] and Header.getTopOf(file.thumb) < 0)
|
||||||
|
return
|
||||||
|
$.queueTask func, post
|
||||||
if ImageExpand.on = $.hasClass ImageExpand.EAI, 'expand-all-shortcut'
|
if ImageExpand.on = $.hasClass ImageExpand.EAI, 'expand-all-shortcut'
|
||||||
ImageExpand.EAI.className = 'contract-all-shortcut a-icon'
|
ImageExpand.EAI.className = 'contract-all-shortcut a-icon'
|
||||||
ImageExpand.EAI.title = 'Contract All Images'
|
ImageExpand.EAI.title = 'Contract All Images'
|
||||||
@ -43,16 +51,10 @@ ImageExpand =
|
|||||||
ImageExpand.EAI.className = 'expand-all-shortcut a-icon'
|
ImageExpand.EAI.className = 'expand-all-shortcut a-icon'
|
||||||
ImageExpand.EAI.title = 'Expand All Images'
|
ImageExpand.EAI.title = 'Expand All Images'
|
||||||
func = ImageExpand.contract
|
func = ImageExpand.contract
|
||||||
for ID, post of g.posts
|
g.posts.forEach (post) ->
|
||||||
for post in [post].concat post.clones
|
func post
|
||||||
{file} = post
|
func post for post in post.clones
|
||||||
continue unless file and file.isImage and doc.contains post.nodes.root
|
return
|
||||||
if ImageExpand.on and
|
|
||||||
(!Conf['Expand spoilers'] and file.isSpoiler or
|
|
||||||
Conf['Expand from here'] and Header.getTopOf(file.thumb) < 0)
|
|
||||||
continue
|
|
||||||
$.queueTask func, post
|
|
||||||
return
|
|
||||||
setFitness: ->
|
setFitness: ->
|
||||||
(if @checked then $.addClass else $.rmClass) doc, @name.toLowerCase().replace /\s+/g, '-'
|
(if @checked then $.addClass else $.rmClass) doc, @name.toLowerCase().replace /\s+/g, '-'
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,10 @@ ImageLoader =
|
|||||||
name: 'Image Replace'
|
name: 'Image Replace'
|
||||||
cb: @node
|
cb: @node
|
||||||
|
|
||||||
|
Thread.callbacks.push
|
||||||
|
name: 'Image Replace'
|
||||||
|
cb: @thread
|
||||||
|
|
||||||
return unless Conf['Image Prefetching'] and g.VIEW is 'thread'
|
return unless Conf['Image Prefetching'] and g.VIEW is 'thread'
|
||||||
|
|
||||||
prefetch = $.el 'label',
|
prefetch = $.el 'label',
|
||||||
@ -19,6 +23,9 @@ ImageLoader =
|
|||||||
type: 'header'
|
type: 'header'
|
||||||
el: prefetch
|
el: prefetch
|
||||||
order: 104
|
order: 104
|
||||||
|
|
||||||
|
thread: ->
|
||||||
|
ImageLoader.thread = @
|
||||||
|
|
||||||
node: ->
|
node: ->
|
||||||
return if @isClone or @isHidden or @thread.isHidden or !@file?.isImage
|
return if @isClone or @isHidden or @thread.isHidden or !@file?.isImage
|
||||||
@ -38,5 +45,5 @@ ImageLoader =
|
|||||||
toggle: ->
|
toggle: ->
|
||||||
enabled = Conf['prefetch'] = @checked
|
enabled = Conf['prefetch'] = @checked
|
||||||
if enabled
|
if enabled
|
||||||
ImageLoader.node.call post for id, post of g.threads["#{g.BOARD.ID}.#{g.THREADID}"].posts
|
ImageLoader.thread.posts.forEach ImageLoader.node.call
|
||||||
return
|
return
|
||||||
@ -19,9 +19,8 @@ ExpandThread =
|
|||||||
|
|
||||||
onIndexRefresh: ->
|
onIndexRefresh: ->
|
||||||
ExpandThread.disconnect true
|
ExpandThread.disconnect true
|
||||||
for threadID, thread of g.BOARD.threads
|
g.BOARD.threads.forEach (thread) ->
|
||||||
ExpandThread.setButton thread
|
ExpandThread.setButton thread
|
||||||
return
|
|
||||||
|
|
||||||
text: (status, posts, files) ->
|
text: (status, posts, files) ->
|
||||||
"#{status} #{posts} post#{if posts > 1 then 's' else ''}" +
|
"#{status} #{posts} post#{if posts > 1 then 's' else ''}" +
|
||||||
|
|||||||
@ -1,54 +1,63 @@
|
|||||||
Favicon =
|
Favicon =
|
||||||
init: ->
|
init: ->
|
||||||
t = 'data:image/png;base64,'
|
items = {
|
||||||
|
ferongr: [
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/ferongr/unreadDead.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/ferongr/unreadDeadY.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/ferongr/unreadSFW.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/ferongr/unreadSFWY.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/ferongr/unreadNSFW.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/ferongr/unreadNSFWY.png", {encoding: "base64"}) %>'
|
||||||
|
]
|
||||||
|
'xat-': [
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/xat-/unreadDead.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/xat-/unreadDeadY.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/xat-/unreadSFW.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/xat-/unreadSFWY.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/xat-/unreadNSFW.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/xat-/unreadNSFWY.png", {encoding: "base64"}) %>'
|
||||||
|
]
|
||||||
|
Mayhem: [
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadDead.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadDeadY.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadSFW.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadSFWY.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadNSFW.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadNSFWY.png", {encoding: "base64"}) %>'
|
||||||
|
]
|
||||||
|
'4chanJS': [
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadDead.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadDeadY.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadSFW.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadSFWY.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadNSFW.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadNSFWY.png", {encoding: "base64"}) %>'
|
||||||
|
]
|
||||||
|
Original: [
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/Original/unreadDead.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/Original/unreadDeadY.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/Original/unreadSFW.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/Original/unreadSFWY.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/Original/unreadNSFW.png", {encoding: "base64"}) %>'
|
||||||
|
'<%= grunt.file.read("src/General/img/favicons/Original/unreadNSFWY.png", {encoding: "base64"}) %>'
|
||||||
|
]
|
||||||
|
}[Conf['favicon']]
|
||||||
|
|
||||||
f = Favicon
|
f = Favicon
|
||||||
[f.unreadDead, funreadDeadY, f.unreadSFW, f.unreadSFWY, f.unreadNSFW, f.unreadNSFWY] = switch Conf['favicon']
|
t = 'data:image/png;base64,'
|
||||||
when 'ferongr' then [
|
i = 0
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadDead.png", {encoding: "base64"}) %>'
|
while items[i]
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadDeadY.png", {encoding: "base64"}) %>'
|
items[i] = t + items[i++]
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadSFW.png", {encoding: "base64"}) %>'
|
[f.unreadDead, funreadDeadY, f.unreadSFW, f.unreadSFWY, f.unreadNSFW, f.unreadNSFWY] = items
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadSFWY.png", {encoding: "base64"}) %>'
|
f.update()
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadNSFW.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/ferongr/unreadNSFWY.png", {encoding: "base64"}) %>'
|
update: ->
|
||||||
]
|
if @SFW
|
||||||
when 'xat-' then [
|
@unread = @unreadSFW
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/xat-/unreadDead.png", {encoding: "base64"}) %>'
|
@unreadY = @unreadSFWY
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/xat-/unreadDeadY.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/xat-/unreadSFW.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/xat-/unreadSFWY.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/xat-/unreadNSFW.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/xat-/unreadNSFWY.png", {encoding: "base64"}) %>'
|
|
||||||
]
|
|
||||||
when 'Mayhem' then [
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadDead.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadDeadY.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadSFW.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadSFWY.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadNSFW.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/Mayhem/unreadNSFWY.png", {encoding: "base64"}) %>'
|
|
||||||
]
|
|
||||||
when '4chanJS' then [
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadDead.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadDeadY.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadSFW.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadSFWY.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadNSFW.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/4chanJS/unreadNSFWY.png", {encoding: "base64"}) %>'
|
|
||||||
]
|
|
||||||
when 'Original' then [
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/Original/unreadDead.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/Original/unreadDeadY.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/Original/unreadSFW.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/Original/unreadSFWY.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/Original/unreadNSFW.png", {encoding: "base64"}) %>'
|
|
||||||
t + '<%= grunt.file.read("src/General/img/favicons/Original/unreadNSFWY.png", {encoding: "base64"}) %>'
|
|
||||||
]
|
|
||||||
if Favicon.SFW
|
|
||||||
Favicon.unread = Favicon.unreadSFW
|
|
||||||
Favicon.unreadY = Favicon.unreadSFWY
|
|
||||||
else
|
else
|
||||||
Favicon.unread = Favicon.unreadNSFW
|
@unread = @unreadNSFW
|
||||||
Favicon.unreadY = Favicon.unreadNSFWY
|
@unreadY = @unreadNSFWY
|
||||||
|
|
||||||
dead: 'data:image/gif;base64,<%= grunt.file.read("src/General/img/favicons/dead.gif", {encoding: "base64"}) %>'
|
dead: 'data:image/gif;base64,<%= grunt.file.read("src/General/img/favicons/dead.gif", {encoding: "base64"}) %>'
|
||||||
logo: 'data:image/png;base64,<%= grunt.file.read("src/General/img/icon128.png", {encoding: "base64"}) %>'
|
logo: 'data:image/png;base64,<%= grunt.file.read("src/General/img/icon128.png", {encoding: "base64"}) %>'
|
||||||
|
|||||||
@ -26,7 +26,7 @@ ThreadStats =
|
|||||||
node: ->
|
node: ->
|
||||||
postCount = 0
|
postCount = 0
|
||||||
fileCount = 0
|
fileCount = 0
|
||||||
for ID, post of @posts
|
@posts.forEach (post) ->
|
||||||
postCount++
|
postCount++
|
||||||
fileCount++ if post.file
|
fileCount++ if post.file
|
||||||
ThreadStats.thread = @
|
ThreadStats.thread = @
|
||||||
|
|||||||
@ -291,11 +291,11 @@ ThreadUpdater =
|
|||||||
deletedFiles = []
|
deletedFiles = []
|
||||||
|
|
||||||
# Check for deleted posts/files.
|
# Check for deleted posts/files.
|
||||||
for ID, post of ThreadUpdater.thread.posts
|
ThreadUpdater.thread.posts.forEach (post) ->
|
||||||
# XXX tmp fix for 4chan's racing condition
|
# XXX tmp fix for 4chan's racing condition
|
||||||
# giving us false-positive dead posts.
|
# giving us false-positive dead posts.
|
||||||
# continue if post.isDead
|
# continue if post.isDead
|
||||||
ID = +ID
|
ID = +post.ID
|
||||||
|
|
||||||
unless ID in index
|
unless ID in index
|
||||||
post.kill()
|
post.kill()
|
||||||
@ -306,6 +306,7 @@ ThreadUpdater =
|
|||||||
post.kill true
|
post.kill true
|
||||||
deletedFiles.push post
|
deletedFiles.push post
|
||||||
|
|
||||||
|
# Fetching your own posts after posting
|
||||||
if ThreadUpdater.postID and ThreadUpdater.postID is ID
|
if ThreadUpdater.postID and ThreadUpdater.postID is ID
|
||||||
ThreadUpdater.foundPost = true
|
ThreadUpdater.foundPost = true
|
||||||
|
|
||||||
@ -326,8 +327,7 @@ ThreadUpdater =
|
|||||||
scroll = Conf['Auto Scroll'] and ThreadUpdater.scrollBG() and
|
scroll = Conf['Auto Scroll'] and ThreadUpdater.scrollBG() and
|
||||||
ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25
|
ThreadUpdater.root.getBoundingClientRect().bottom - doc.clientHeight < 25
|
||||||
|
|
||||||
for key, post of posts
|
for post in posts
|
||||||
continue unless posts.hasOwnProperty key
|
|
||||||
root = post.nodes.root
|
root = post.nodes.root
|
||||||
if post.cb
|
if post.cb
|
||||||
unless post.cb()
|
unless post.cb()
|
||||||
|
|||||||
@ -174,7 +174,9 @@ ThreadWatcher =
|
|||||||
$.rmAll list
|
$.rmAll list
|
||||||
$.add list, nodes
|
$.add list, nodes
|
||||||
|
|
||||||
for threadID, thread of g.BOARD.threads
|
{threads} = g.BOARD
|
||||||
|
for threadID in threads.keys
|
||||||
|
thread = threads[threadID]
|
||||||
toggler = $ '.watch-thread-link', thread.OP.nodes.post
|
toggler = $ '.watch-thread-link', thread.OP.nodes.post
|
||||||
watched = ThreadWatcher.db.get {boardID: thread.board.ID, threadID}
|
watched = ThreadWatcher.db.get {boardID: thread.board.ID, threadID}
|
||||||
helper = if watched then ['addClass', 'Unwatch'] else ['rmClass', 'Watch']
|
helper = if watched then ['addClass', 'Unwatch'] else ['rmClass', 'Watch']
|
||||||
|
|||||||
@ -42,7 +42,7 @@ Unread =
|
|||||||
ready: ->
|
ready: ->
|
||||||
$.off d, '4chanXInitFinished', Unread.ready
|
$.off d, '4chanXInitFinished', Unread.ready
|
||||||
posts = []
|
posts = []
|
||||||
posts.push post for ID, post of Unread.thread.posts when post.isReply
|
Unread.thread.posts.forEach (post) -> posts.push post if post.isReply
|
||||||
Unread.addPosts posts unless Conf['Quote Threading']
|
Unread.addPosts posts unless Conf['Quote Threading']
|
||||||
QuoteThreading.force() if Conf['Quote Threading']
|
QuoteThreading.force() if Conf['Quote Threading']
|
||||||
Unread.scroll() if Conf['Scroll to Last Read Post']
|
Unread.scroll() if Conf['Scroll to Last Read Post']
|
||||||
@ -52,14 +52,15 @@ Unread =
|
|||||||
return if (hash = location.hash.match /\d+/) and hash[0] of Unread.thread.posts
|
return if (hash = location.hash.match /\d+/) and hash[0] of Unread.thread.posts
|
||||||
if post = Unread.posts.first
|
if post = Unread.posts.first
|
||||||
# Scroll to a non-hidden, non-OP post that's before the first unread post.
|
# Scroll to a non-hidden, non-OP post that's before the first unread post.
|
||||||
while root = $.x 'preceding-sibling::div[contains(@class,"replyContainer")][1]', post.nodes.root
|
while root = $.x 'preceding-sibling::div[contains(@class,"replyContainer")][1]', post.data.nodes.root
|
||||||
break unless (post = Get.postFromRoot root).isHidden
|
break unless (post = Get.postFromRoot root).isHidden
|
||||||
return unless root
|
return unless root
|
||||||
down = true
|
down = true
|
||||||
else
|
else
|
||||||
# Scroll to the last read post.
|
# Scroll to the last read post.
|
||||||
posts = Object.keys Unread.thread.posts
|
{posts} = Unread.thread
|
||||||
{root} = Unread.thread.posts[posts[posts.length - 1]].nodes
|
{keys} = posts
|
||||||
|
{root} = posts[keys[keys.length - 1]].nodes
|
||||||
|
|
||||||
# Scroll to the target unless we scrolled past it.
|
# Scroll to the target unless we scrolled past it.
|
||||||
Header.scrollTo root, down if Header.getBottomOf(root) < 0
|
Header.scrollTo root, down if Header.getBottomOf(root) < 0
|
||||||
@ -94,12 +95,11 @@ Unread =
|
|||||||
Unread.addPostQuotingYou post
|
Unread.addPostQuotingYou post
|
||||||
if Conf['Unread Line']
|
if Conf['Unread Line']
|
||||||
# Force line on visible threads if there were no unread posts previously.
|
# Force line on visible threads if there were no unread posts previously.
|
||||||
Unread.setLine Unread.posts.first in posts
|
Unread.setLine Unread.posts.first?.data in posts
|
||||||
Unread.read()
|
Unread.read()
|
||||||
Unread.update()
|
Unread.update()
|
||||||
|
|
||||||
addPostQuotingYou: (post) ->
|
addPostQuotingYou: (post) ->
|
||||||
return unless QR.db
|
|
||||||
for quotelink in post.nodes.quotelinks when QR.db.get Get.postDataFromLink quotelink
|
for quotelink in post.nodes.quotelinks when QR.db.get Get.postDataFromLink quotelink
|
||||||
Unread.postsQuotingYou.push post
|
Unread.postsQuotingYou.push post
|
||||||
Unread.openNotification post
|
Unread.openNotification post
|
||||||
@ -130,11 +130,12 @@ Unread =
|
|||||||
|
|
||||||
readSinglePost: (post) ->
|
readSinglePost: (post) ->
|
||||||
{ID} = post
|
{ID} = post
|
||||||
return unless Unread.posts[ID]
|
{posts} = Unread
|
||||||
if post is Unread.posts.first
|
return unless posts[ID]
|
||||||
|
if post is posts.first
|
||||||
Unread.lastReadPost = ID
|
Unread.lastReadPost = ID
|
||||||
Unread.saveLastReadPost()
|
Unread.saveLastReadPost()
|
||||||
Unread.posts.rm ID
|
posts.rm ID
|
||||||
if (i = Unread.postsQuotingYou.indexOf post) isnt -1
|
if (i = Unread.postsQuotingYou.indexOf post) isnt -1
|
||||||
Unread.postsQuotingYou.splice i, 1
|
Unread.postsQuotingYou.splice i, 1
|
||||||
Unread.update()
|
Unread.update()
|
||||||
@ -150,12 +151,16 @@ Unread =
|
|||||||
|
|
||||||
{posts} = Unread
|
{posts} = Unread
|
||||||
while post = posts.first
|
while post = posts.first
|
||||||
break unless Header.getBottomOf(post.nodes.root) > -1 # post is not completely read
|
break unless Header.getBottomOf(post.data.nodes.root) > -1 # post is not completely read
|
||||||
{ID} = post
|
{ID, data} = post
|
||||||
posts.rm ID
|
posts.rm ID
|
||||||
|
|
||||||
if Conf['Mark Quotes of You'] and post.info.yours
|
if Conf['Mark Quotes of You'] and QR.db.get {
|
||||||
QuoteYou.lastRead = post.nodes.root
|
boardID: data.board.ID
|
||||||
|
threadID: data.thread.ID
|
||||||
|
postID: ID
|
||||||
|
}
|
||||||
|
QuoteYou.lastRead = data.nodes.root
|
||||||
|
|
||||||
return unless ID
|
return unless ID
|
||||||
|
|
||||||
@ -174,8 +179,8 @@ Unread =
|
|||||||
setLine: (force) ->
|
setLine: (force) ->
|
||||||
return unless d.hidden or force is true
|
return unless d.hidden or force is true
|
||||||
return $.rm Unread.hr unless post = Unread.posts.first
|
return $.rm Unread.hr unless post = Unread.posts.first
|
||||||
if $.x 'preceding-sibling::div[contains(@class,"replyContainer")]', post.nodes.root # not the first reply
|
if $.x 'preceding-sibling::div[contains(@class,"replyContainer")]', post.data.nodes.root # not the first reply
|
||||||
$.before post.nodes.root, Unread.hr
|
$.before post.data.nodes.root, Unread.hr
|
||||||
|
|
||||||
update: <% if (type === 'crx') { %>(dontrepeat) <% } %>->
|
update: <% if (type === 'crx') { %>(dontrepeat) <% } %>->
|
||||||
count = Unread.posts.length
|
count = Unread.posts.length
|
||||||
|
|||||||
@ -324,7 +324,7 @@ QR =
|
|||||||
return unless QR.nodes
|
return unless QR.nodes
|
||||||
list = QR.nodes.thread
|
list = QR.nodes.thread
|
||||||
options = [list.firstChild]
|
options = [list.firstChild]
|
||||||
for thread of g.BOARD.threads
|
for thread in g.BOARD.threads.keys
|
||||||
options.push $.el 'option',
|
options.push $.el 'option',
|
||||||
value: thread
|
value: thread
|
||||||
textContent: "Thread No.#{thread}"
|
textContent: "Thread No.#{thread}"
|
||||||
|
|||||||
@ -42,15 +42,19 @@ QuoteThreading =
|
|||||||
QuoteThreading.force()
|
QuoteThreading.force()
|
||||||
|
|
||||||
force: ->
|
force: ->
|
||||||
post.cb true for ID, post of g.posts when post.cb
|
g.posts.forEach (post) ->
|
||||||
return
|
post.cb true if post.cb
|
||||||
|
|
||||||
|
if Conf['Unread Count'] and Unread.thread.OP.nodes.root.parentElement.parentElement
|
||||||
|
Unread.read()
|
||||||
|
Unread.update()
|
||||||
|
|
||||||
node: ->
|
node: ->
|
||||||
{posts} = g
|
{posts} = g
|
||||||
return if @isClone or not QuoteThreading.enabled
|
return if @isClone or not QuoteThreading.enabled
|
||||||
Unread.posts.push @ if Conf['Unread Count']
|
|
||||||
|
|
||||||
return if @thread.OP is @ or !(post = posts[@fullID]) or post.isHidden # Filtered
|
Unread.posts.push @ if Conf['Unread Count']
|
||||||
|
return if @thread.OP is @ or @isHidden # Filtered
|
||||||
|
|
||||||
keys = []
|
keys = []
|
||||||
len = g.BOARD.ID.length + 1
|
len = g.BOARD.ID.length + 1
|
||||||
@ -90,11 +94,11 @@ QuoteThreading =
|
|||||||
|
|
||||||
return true unless Conf['Unread Count']
|
return true unless Conf['Unread Count']
|
||||||
|
|
||||||
if posts[post.ID]
|
if post = posts[post.ID]
|
||||||
posts.after post, @
|
posts.after post, posts[@ID]
|
||||||
|
|
||||||
else
|
else
|
||||||
posts.prepend @
|
posts.prepend posts[@ID]
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@ -106,8 +110,10 @@ QuoteThreading =
|
|||||||
thread = $('.thread')
|
thread = $('.thread')
|
||||||
posts = []
|
posts = []
|
||||||
nodes = []
|
nodes = []
|
||||||
|
|
||||||
|
g.posts.forEach (post) ->
|
||||||
|
posts.push post unless post is post.thread.OP or post.isClone
|
||||||
|
|
||||||
posts.push post for ID, post of g.posts when not (post is post.thread.OP or post.isClone)
|
|
||||||
posts.sort (a, b) -> a.ID - b.ID
|
posts.sort (a, b) -> a.ID - b.ID
|
||||||
|
|
||||||
nodes.push post.nodes.root for post in posts
|
nodes.push post.nodes.root for post in posts
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user