Merge branch 'catalog'

This commit is contained in:
ccd0 2014-09-20 21:17:04 -07:00
commit c47c02e95d
19 changed files with 570 additions and 119 deletions

View File

@ -113,6 +113,8 @@ Filter =
# Highlight # Highlight
$.addClass @nodes.root, result.class $.addClass @nodes.root, result.class
unless @highlights and result.class in @highlights
(@highlights or= []).push result.class
if !@isReply and result.top if !@isReply and result.top
@thread.isOnTop = true @thread.isOnTop = true

View File

@ -1,6 +1,6 @@
ThreadHiding = ThreadHiding =
init: -> init: ->
return if g.VIEW isnt 'index' or !Conf['Thread Hiding Buttons'] and !Conf['Thread Hiding Link'] return if g.VIEW isnt 'index' or !Conf['Thread Hiding Buttons'] and !Conf['Thread Hiding Link'] and !Conf['JSON Navigation']
@db = new DataBoard 'hiddenThreads' @db = new DataBoard 'hiddenThreads'
@syncCatalog() @syncCatalog()
@ -81,7 +81,7 @@ ThreadHiding =
el: div el: div
order: 20 order: 20
open: ({thread, isReply}) -> open: ({thread, isReply}) ->
if isReply or thread.isHidden if isReply or thread.isHidden or Conf['JSON Navigation'] and Conf['Index Mode'] is 'catalog'
return false return false
ThreadHiding.menu.thread = thread ThreadHiding.menu.thread = thread
true true
@ -188,6 +188,7 @@ ThreadHiding =
return if thread.isHidden return if thread.isHidden
threadRoot = thread.OP.nodes.root.parentNode threadRoot = thread.OP.nodes.root.parentNode
thread.isHidden = true thread.isHidden = true
Index.updateHideLabel() if Conf['JSON Navigation']
return threadRoot.hidden = true unless makeStub return threadRoot.hidden = true unless makeStub
@ -199,3 +200,4 @@ ThreadHiding =
delete thread.stub delete thread.stub
threadRoot = thread.OP.nodes.root.parentNode threadRoot = thread.OP.nodes.root.parentNode
threadRoot.hidden = thread.isHidden = false threadRoot.hidden = thread.isHidden = false
Index.updateHideLabel() if Conf['JSON Navigation']

View File

@ -1,4 +1,6 @@
Build = Build =
staticPath: '//s.4cdn.org/image/'
gifIcon: if window.devicePixelRatio >= 2 then '@2x.gif' else '.gif'
initPixelRatio: window.devicePixelRatio initPixelRatio: window.devicePixelRatio
spoilerRange: {} spoilerRange: {}
unescape: (text) -> unescape: (text) ->
@ -289,11 +291,91 @@ Build =
nodes = [if OP then OP.nodes.root else Build.postFromObject data, board.ID] nodes = [if OP then OP.nodes.root else Build.postFromObject data, board.ID]
if data.omitted_posts or !Conf['Show Replies'] and data.replies if data.omitted_posts or !Conf['Show Replies'] and data.replies
[posts, files] = if Conf['Show Replies'] [posts, files] = if Conf['Show Replies']
[data.omitted_posts, data.omitted_images] # XXX data.omitted_images is not accurate.
[data.omitted_posts, data.images - data.last_replies.filter((data) -> !!data.ext).length]
else else
# XXX data.images is not accurate. [data.replies, data.images]
[data.replies, data.omitted_images + data.last_replies.filter((data) -> !!data.ext).length]
nodes.push Build.summary board.ID, data.no, posts, files nodes.push Build.summary board.ID, data.no, posts, files
nodes nodes
fullThread: (board, data) -> Build.postFromObject data, board.ID fullThread: (board, data) -> Build.postFromObject data, board.ID
catalogThread: (thread) ->
{staticPath, gifIcon} = Build
data = Index.liveThreadData[Index.liveThreadIDs.indexOf thread.ID]
if data.spoiler and !Conf['Reveal Spoiler Thumbnails']
src = "#{staticPath}spoiler"
if spoilerRange = Build.spoilerRange[thread.board]
# Randomize the spoiler image.
src += "-#{thread.board}" + Math.floor 1 + spoilerRange * Math.random()
src += '.png'
imgClass = 'spoiler-file'
else if data.filedeleted
src = "#{staticPath}filedeleted-res#{gifIcon}"
imgClass = 'deleted-file'
else if thread.OP.file
src = thread.OP.file.thumbURL
max = Math.max data.tn_w, data.tn_h
imgWidth = data.tn_w * 150 / max
imgHeight = data.tn_h * 150 / max
else
src = "#{staticPath}nofile.png"
imgClass = 'no-file'
thumb = if imgClass
<%= html('<img src="${src}" class="catalog-thumb ${imgClass}">') %>
else
<%= html('<img src="${src}" class="catalog-thumb" width="${imgWidth}" height="${imgHeight}">') %>
postCount = data.replies + 1
fileCount = data.images + !!data.ext
pageCount = Index.liveThreadIDs.indexOf(thread.ID) // Index.threadsNumPerPage + 1
subject = if thread.OP.info.subject
<%= html('<div class="subject">${thread.OP.info.subject}</div>') %>
else
<%= html('') %>
root = $.el 'div',
className: 'catalog-thread'
$.extend root, <%= html(
'<a href="/${thread.board}/thread/${thread.ID}">' +
'&{thumb}' +
'</a>' +
'<div class="catalog-stats" title="Post count / File count / Page count">' +
'<span class="post-count">${postCount}</span> / <span class="file-count">${fileCount}</span> / <span class="page-count">${pageCount}</span>' +
'<span class="catalog-icons"></span>' +
'</div>' +
'&{subject}' +
'<div class="comment">&{thread.OP.info.commentHTML}</div>'
) %>
root.dataset.fullID = thread.fullID
$.addClass root, 'pinned' if thread.isPinned
$.addClass root, thread.OP.highlights... if thread.OP.highlights
for quotelink in $$ '.quotelink, .deadlink', root.lastElementChild
$.replace quotelink, [quotelink.childNodes...]
for pp in $$ '.prettyprint', root.lastElementChild
$.replace pp, $.tn pp.textContent
for br in $$ 'br', root.lastElementChild when !br.previousSibling or br.previousSibling.nodeName is 'BR'
$.rm br
if thread.isSticky
$.add $('.catalog-icons', root), $.el 'img',
src: "#{staticPath}sticky#{gifIcon}"
className: 'stickyIcon'
title: 'Sticky'
if thread.isClosed
$.add $('.catalog-icons', root), $.el 'img',
src: "#{staticPath}closed#{gifIcon}"
className: 'closedIcon'
title: 'Closed'
if data.bumplimit
$.addClass $('.post-count', root), 'warning'
if data.imagelimit
$.addClass $('.file-count', root), 'warning'
root

View File

@ -5,6 +5,10 @@ Config =
true true
'Replace the board index with a dynamically generated one supporting searching, sorting, and infinite scrolling.' 'Replace the board index with a dynamically generated one supporting searching, sorting, and infinite scrolling.'
] ]
'Use 4chan X Catalog': [
false
'Link to 4chan X\'s catalog instead of the native 4chan one.'
]
'Catalog Links': [ 'Catalog Links': [
true true
'Add toggle link in header menu to turn Navigation links into links to each board\'s catalog.' 'Add toggle link in header menu to turn Navigation links into links to each board\'s catalog.'
@ -553,6 +557,7 @@ http://iqdb.org/?url=%TURL
Index: Index:
'Index Mode': 'paged' 'Index Mode': 'paged'
'Previous Index Mode': 'paged'
'Index Sort': 'bump' 'Index Sort': 'bump'
'Show Replies': true 'Show Replies': true
'Anchor Hidden Threads': true 'Anchor Hidden Threads': true

View File

@ -197,7 +197,7 @@ Header =
if Conf['External Catalog'] if Conf['External Catalog']
a.href = CatalogLinks.external board a.href = CatalogLinks.external board
else else
a.href += 'catalog' a.href += if Conf['JSON Navigation'] and Conf['Use 4chan X Catalog'] then '#catalog' else 'catalog'
$.addClass a, 'catalog' $.addClass a, 'catalog'
$.addClass a, 'navSmall' if board is '@' $.addClass a, 'navSmall' if board is '@'

View File

@ -1,9 +1,27 @@
Index = Index =
showHiddenThreads: false
init: -> init: ->
return if g.BOARD.ID is 'f' or g.VIEW isnt 'index' or !Conf['JSON Navigation'] return if g.BOARD.ID is 'f' or !Conf['JSON Navigation']
if g.VIEW is 'thread' and Conf['Use 4chan X Catalog']
$.ready ->
for link in $$ '.navLinks.desktop a' when link.pathname is "/#{g.BOARD}/catalog"
link.href = "/#{g.BOARD}/#catalog"
return if g.VIEW isnt 'index'
@board = "#{g.BOARD}" @board = "#{g.BOARD}"
@db = new DataBoard 'pinnedThreads'
Thread.callbacks.push
name: 'Thread Pinning'
cb: @threadNode
CatalogThread.callbacks.push
name: 'Catalog Features'
cb: @catalogNode
if Conf['Use 4chan X Catalog'] and Conf['Index Mode'] is 'catalog'
Index.setMode Conf['Previous Index Mode']
@cb.popstate()
@button = $.el 'a', @button = $.el 'a',
className: 'index-refresh-shortcut fa fa-refresh' className: 'index-refresh-shortcut fa fa-refresh'
title: 'Refresh' title: 'Refresh'
@ -18,28 +36,17 @@ Index =
{ el: $.el 'label', <%= html('<input type="radio" name="Index Mode" value="paged"> Paged') %> } { el: $.el 'label', <%= html('<input type="radio" name="Index Mode" value="paged"> Paged') %> }
{ el: $.el 'label', <%= html('<input type="radio" name="Index Mode" value="infinite"> Infinite scrolling') %> } { el: $.el 'label', <%= html('<input type="radio" name="Index Mode" value="infinite"> Infinite scrolling') %> }
{ el: $.el 'label', <%= html('<input type="radio" name="Index Mode" value="all pages"> All threads') %> } { el: $.el 'label', <%= html('<input type="radio" name="Index Mode" value="all pages"> All threads') %> }
{ el: $.el 'label', <%= html('<input type="radio" name="Index Mode" value="catalog"> Catalog') %> }
] ]
open: ->
for label in @subEntries
input = label.el.firstChild
input.checked = Conf['Index Mode'] is input.value
true
for label in modeEntry.subEntries for label in modeEntry.subEntries
input = label.el.firstChild input = label.el.firstChild
input.checked = Conf['Index Mode'] is input.value
$.on input, 'change', $.cb.value
$.on input, 'change', @cb.mode $.on input, 'change', @cb.mode
sortEntry =
el: $.el 'span', textContent: 'Sort by'
subEntries: [
{ el: $.el 'label', <%= html('<input type="radio" name="Index Sort" value="bump"> Bump order') %> }
{ el: $.el 'label', <%= html('<input type="radio" name="Index Sort" value="lastreply"> Last reply') %> }
{ el: $.el 'label', <%= html('<input type="radio" name="Index Sort" value="birth"> Creation date') %> }
{ el: $.el 'label', <%= html('<input type="radio" name="Index Sort" value="replycount"> Reply count') %> }
{ el: $.el 'label', <%= html('<input type="radio" name="Index Sort" value="filecount"> File count') %> }
]
for label in sortEntry.subEntries
input = label.el.firstChild
input.checked = Conf['Index Sort'] is input.value
$.on input, 'change', $.cb.value
$.on input, 'change', @cb.sort
repliesEntry = el: UI.checkbox 'Show Replies', ' Show replies' repliesEntry = el: UI.checkbox 'Show Replies', ' Show replies'
anchorEntry = el: UI.checkbox 'Anchor Hidden Threads', ' Anchor hidden threads' anchorEntry = el: UI.checkbox 'Anchor Hidden Threads', ' Anchor hidden threads'
refNavEntry = el: UI.checkbox 'Refreshed Navigation', ' Refreshed navigation' refNavEntry = el: UI.checkbox 'Refreshed Navigation', ' Refreshed navigation'
@ -59,20 +66,20 @@ Index =
el: $.el 'span', el: $.el 'span',
textContent: 'Index Navigation' textContent: 'Index Navigation'
order: 98 order: 98
subEntries: [repliesEntry, anchorEntry, refNavEntry, modeEntry, sortEntry] subEntries: [repliesEntry, anchorEntry, refNavEntry, modeEntry]
$.addClass doc, 'index-loading' $.addClass doc, 'index-loading', "#{Conf['Index Mode'].replace /\ /g, '-'}-mode"
@root = $.el 'div', className: 'board' @root = $.el 'div', className: 'board'
@pagelist = $.el 'div', @pagelist = $.el 'div', className: 'pagelist'
className: 'pagelist'
hidden: true
$.extend @pagelist, <%= importHTML('Features/Index-pagelist') %> $.extend @pagelist, <%= importHTML('Features/Index-pagelist') %>
@navLinks = $.el 'div', $('.cataloglink a', @pagelist).href = if Conf['Use 4chan X Catalog'] then '#catalog' else "/#{g.BOARD}/catalog"
className: 'navLinks' @navLinks = $.el 'div', className: 'navLinks'
$.extend @navLinks, <%= importHTML('Features/Index-navlinks') %> $.extend @navLinks, <%= importHTML('Features/Index-navlinks') %>
$('.returnlink a', @navLinks).href = "//boards.4chan.org/#{g.BOARD}/" $('.returnlink a', @navLinks).href = if Conf['Use 4chan X Catalog'] then '#index' else "/#{g.BOARD}/"
$('.cataloglink a', @navLinks).href = "//boards.4chan.org/#{g.BOARD}/catalog" $('.cataloglink a', @navLinks).href = if Conf['Use 4chan X Catalog'] then '#catalog' else "/#{g.BOARD}/catalog"
@searchInput = $ '#index-search', @navLinks @searchInput = $ '#index-search', @navLinks
@hideLabel = $ '#hidden-label', @navLinks
@selectSort = $ '#index-sort', @navLinks
@currentPage = @getCurrentPage() @currentPage = @getCurrentPage()
$.on window, 'popstate', @cb.popstate $.on window, 'popstate', @cb.popstate
@ -80,6 +87,10 @@ Index =
$.on @pagelist, 'click', @cb.pageNav $.on @pagelist, 'click', @cb.pageNav
$.on @searchInput, 'input', @onSearchInput $.on @searchInput, 'input', @onSearchInput
$.on $('#index-search-clear', @navLinks), 'click', @clearSearch $.on $('#index-search-clear', @navLinks), 'click', @clearSearch
$.on $('#hidden-toggle a', @navLinks), 'click', @cb.toggleHiddenThreads
@selectSort.value = Conf[@selectSort.name]
$.on @selectSort, 'change', $.cb.value
$.on @selectSort, 'change', @cb.sort
@update() @update()
$.asap (-> $('.board', doc) or d.readyState isnt 'loading'), -> $.asap (-> $('.board', doc) or d.readyState isnt 'loading'), ->
@ -129,10 +140,93 @@ Index =
new Notice 'info', "Last page reached.", 2 new Notice 'info', "Last page reached.", 2
setTimeout reset, 3 * $.SECOND setTimeout reset, 3 * $.SECOND
menu:
init: ->
return if g.VIEW isnt 'index' or !Conf['JSON Navigation'] or !Conf['Menu'] or g.BOARD.ID is 'f'
Menu.menu.addEntry
el: $.el 'a', href: 'javascript:;'
order: 5
open: ({thread}) ->
return false if Conf['Index Mode'] isnt 'catalog'
@el.textContent = if thread.isHidden
'Unhide thread'
else
'Hide thread'
$.off @el, 'click', @cb if @cb
@cb = ->
$.event 'CloseMenu'
Index.toggleHide thread
$.on @el, 'click', @cb
true
Menu.menu.addEntry
el: $.el 'a', href: 'javascript:;'
order: 6
open: ({thread}) ->
return false if Conf['Index Mode'] isnt 'catalog'
@el.textContent = if thread.isPinned
'Unpin thread'
else
'Pin thread'
$.off @el, 'click', @cb if @cb
@cb = ->
$.event 'CloseMenu'
Index.togglePin thread
$.on @el, 'click', @cb
true
threadNode: ->
return unless Index.db.get {boardID: @board.ID, threadID: @ID}
@pin()
catalogNode: ->
$.on @nodes.thumb.parentNode, 'click', Index.onClick
onClick: (e) ->
return if e.button isnt 0
thread = g.threads[@parentNode.dataset.fullID]
if e.shiftKey
Index.toggleHide thread
else if e.altKey
Index.togglePin thread
else
return
e.preventDefault()
toggleHide: (thread) ->
$.rm thread.catalogView.nodes.root
if Index.showHiddenThreads
ThreadHiding.show thread
return unless ThreadHiding.db.get {boardID: thread.board.ID, threadID: thread.ID}
# Don't save when un-hiding filtered threads.
else
ThreadHiding.hide thread
ThreadHiding.saveHiddenState thread
togglePin: (thread) ->
data =
boardID: thread.board.ID
threadID: thread.ID
if thread.isPinned
thread.unpin()
Index.db.delete data
else
thread.pin()
data.val = true
Index.db.set data
Index.sort()
Index.buildIndex()
cb: cb:
mode: -> toggleHiddenThreads: ->
Index.togglePagelist() $('#hidden-toggle a', Index.navLinks).textContent = if Index.showHiddenThreads = !Index.showHiddenThreads
'Hide'
else
'Show'
Index.sort()
Index.buildIndex() Index.buildIndex()
mode: ->
Index.setMode @value
Index.pushState Conf['Index Mode'], Index.currentPage
Index.buildIndex()
Index.setPage()
sort: -> sort: ->
Index.sort() Index.sort()
Index.buildIndex() Index.buildIndex()
@ -140,9 +234,31 @@ Index =
Index.buildThreads() Index.buildThreads()
Index.sort() Index.sort()
Index.buildIndex() Index.buildIndex()
hashchange: (e) ->
switch command = location.hash[1..]
when 'paged', 'infinite', 'all-pages', 'catalog'
mode = command.replace /-/g, ' '
when 'index'
mode = Conf['Previous Index Mode']
if mode
Index.setMode mode
history.replaceState {mode}, '', if Index.currentPage is 1 then './' else Index.currentPage
if e
# hash change, not call from init
Index.buildIndex()
Index.setPage()
return
history.replaceState {mode: Conf['Index Mode']}, ''
popstate: (e) -> popstate: (e) ->
unless e?.state
# page load or hash change
return Index.cb.hashchange.call @, e
{mode} = e.state
pageNum = Index.getCurrentPage() pageNum = Index.getCurrentPage()
Index.pageLoad pageNum if Index.currentPage isnt pageNum unless Conf['Index Mode'] is mode and Index.currentPage is pageNum
Index.setMode mode
Index.buildIndex()
Index.setPage()
pageNav: (e) -> pageNav: (e) ->
return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0 return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0
switch e.target.nodeName switch e.target.nodeName
@ -158,23 +274,36 @@ Index =
Index.userPageNav +a.pathname.split('/')[2] or 1 Index.userPageNav +a.pathname.split('/')[2] or 1
scrollToIndex: -> scrollToIndex: ->
Header.scrollToIfNeeded Index.root Header.scrollToIfNeeded Index.navLinks
getCurrentPage: -> getCurrentPage: ->
+window.location.pathname.split('/')[2] or 1 if Conf['Index Mode'] in ['all pages', 'catalog']
1
else
+window.location.pathname.split('/')[2] or 1
userPageNav: (pageNum) -> userPageNav: (pageNum) ->
history.pushState null, '', if pageNum is 1 then './' else pageNum Index.pushState Conf['Index Mode'], pageNum
if Conf['Refreshed Navigation'] and Conf['Index Mode'] isnt 'all pages' if Conf['Refreshed Navigation']
Index.update pageNum Index.update pageNum
else else
return if Index.currentPage is pageNum return if Index.currentPage is pageNum
Index.pageLoad pageNum Index.pageLoad pageNum
pushState: (mode, pageNum) ->
history.pushState {mode}, '', if pageNum is 1 then './' else pageNum
pageLoad: (pageNum) -> pageLoad: (pageNum) ->
Index.currentPage = pageNum Index.currentPage = pageNum
return if Conf['Index Mode'] is 'all pages'
Index.buildIndex() Index.buildIndex()
Index.setPage() Index.setPage()
Index.scrollToIndex() Index.scrollToIndex()
setMode: (mode) ->
$.rmClass doc, "#{Conf['Index Mode'].replace /\ /g, '-'}-mode"
$.addClass doc, "#{mode.replace /\ /g, '-'}-mode"
Conf['Index Mode'] = mode
$.set 'Index Mode', mode
Index.currentPage = Index.getCurrentPage()
if mode not in ['catalog', Conf['Previous Index Mode']]
Conf['Previous Index Mode'] = mode
$.set 'Previous Index Mode', mode
getPagesNum: -> getPagesNum: ->
if Index.isSearching if Index.isSearching
@ -183,8 +312,6 @@ Index =
Index.pagesNum Index.pagesNum
getMaxPageNum: -> getMaxPageNum: ->
Math.max 1, Index.getPagesNum() Math.max 1, Index.getPagesNum()
togglePagelist: ->
Index.pagelist.hidden = Conf['Index Mode'] isnt 'paged'
buildPagelist: -> buildPagelist: ->
pagesRoot = $ '.pages', Index.pagelist pagesRoot = $ '.pages', Index.pagelist
maxPageNum = Index.getMaxPageNum() maxPageNum = Index.getMaxPageNum()
@ -197,7 +324,6 @@ Index =
nodes.push $.tn('['), a, $.tn '] ' nodes.push $.tn('['), a, $.tn '] '
$.rmAll pagesRoot $.rmAll pagesRoot
$.add pagesRoot, nodes $.add pagesRoot, nodes
Index.togglePagelist()
setPage: (pageNum) -> setPage: (pageNum) ->
pageNum or= Index.getCurrentPage() pageNum or= Index.getCurrentPage()
maxPageNum = Index.getMaxPageNum() maxPageNum = Index.getMaxPageNum()
@ -221,7 +347,21 @@ Index =
$.before a, strong $.before a, strong
$.add strong, a $.add strong, a
update: (pageNum, forceReparse) -> updateHideLabel: ->
hiddenCount = 0
for threadID, thread of g.BOARD.threads when thread.isHidden
hiddenCount++ if thread.ID in Index.liveThreadIDs
unless hiddenCount
Index.hideLabel.hidden = true
Index.cb.toggleHiddenThreads() if Index.showHiddenThreads
return
Index.hideLabel.hidden = false
$('#hidden-count', Index.navLinks).textContent = if hiddenCount is 1
'1 hidden thread'
else
"#{hiddenCount} hidden threads"
update: (pageNum) ->
return unless navigator.onLine return unless navigator.onLine
delete Index.pageNum delete Index.pageNum
Index.req?.abort() Index.req?.abort()
@ -241,7 +381,7 @@ Index =
onabort: onload onabort: onload
onloadend: onload onloadend: onload
, ,
whenModified: !forceReparse whenModified: true
$.addClass Index.button, 'fa-spin' $.addClass Index.button, 'fa-spin'
load: (e, pageNum) -> load: (e, pageNum) ->
@ -317,6 +457,8 @@ Index =
try try
threadRoot = Build.thread g.BOARD, threadData threadRoot = Build.thread g.BOARD, threadData
if thread = g.BOARD.threads[threadData.no] if thread = g.BOARD.threads[threadData.no]
thread.setCount 'post', threadData.replies + 1, threadData.bumplimit
thread.setCount 'file', threadData.images + !!threadData.ext, threadData.imagelimit
thread.setStatus 'Sticky', !!threadData.sticky thread.setStatus 'Sticky', !!threadData.sticky
thread.setStatus 'Closed', !!threadData.closed thread.setStatus 'Closed', !!threadData.closed
else else
@ -338,6 +480,7 @@ Index =
$.nodes Index.nodes $.nodes Index.nodes
Main.callbackNodes Thread, threads Main.callbackNodes Thread, threads
Main.callbackNodes Post, posts Main.callbackNodes Post, posts
Index.updateHideLabel()
$.event 'IndexRefresh' $.event 'IndexRefresh'
buildReplies: (threadRoots) -> buildReplies: (threadRoots) ->
@ -365,6 +508,16 @@ Index =
Main.handleErrors errors if errors Main.handleErrors errors if errors
Main.callbackNodes Post, posts Main.callbackNodes Post, posts
buildCatalogViews: ->
threads = Index.sortedNodes
.map((threadRoot) -> Get.threadFromRoot threadRoot)
.filter (thread) -> !thread.isHidden isnt Index.showHiddenThreads
catalogThreads = []
for thread in threads when !thread.catalogView
catalogThreads.push new CatalogThread Build.catalogThread(thread), thread
Main.callbackNodes CatalogThread, catalogThreads
threads.map (thread) -> thread.catalogView.nodes.root
sort: -> sort: ->
{liveThreadIDs, liveThreadData} = Index {liveThreadIDs, liveThreadData} = Index
sortedThreadIDs = { sortedThreadIDs = {
@ -388,7 +541,7 @@ Index =
# Sticky threads # Sticky threads
Index.sortOnTop (thread) -> thread.isSticky Index.sortOnTop (thread) -> thread.isSticky
# Highlighted threads # Highlighted threads
Index.sortOnTop((thread) -> thread.isOnTop) if Conf['Filter'] Index.sortOnTop (thread) -> thread.isOnTop or thread.isPinned
# Non-hidden threads # Non-hidden threads
Index.sortOnTop((thread) -> !thread.isHidden) if Conf['Anchor Hidden Threads'] Index.sortOnTop((thread) -> !thread.isHidden) if Conf['Anchor Hidden Threads']
@ -400,14 +553,20 @@ Index =
Index.sortedNodes = topNodes.concat(bottomNodes) Index.sortedNodes = topNodes.concat(bottomNodes)
buildIndex: -> buildIndex: ->
if Conf['Index Mode'] isnt 'all pages' switch Conf['Index Mode']
nodes = Index.buildSinglePage Index.getCurrentPage() when 'all pages'
else nodes = Index.sortedNodes
nodes = Index.sortedNodes when 'catalog'
nodes = Index.buildCatalogViews()
else
nodes = Index.buildSinglePage Index.getCurrentPage()
$.rmAll Index.root $.rmAll Index.root
$.rmAll Header.hover $.rmAll Header.hover
Index.buildReplies nodes if Conf['Show Replies'] if Conf['Index Mode'] is 'catalog'
Index.buildStructure nodes $.add Index.root, nodes
else
Index.buildReplies nodes if Conf['Show Replies']
Index.buildStructure nodes
buildSinglePage: (pageNum) -> buildSinglePage: (pageNum) ->
nodesPerPage = Index.threadsNumPerPage nodesPerPage = Index.threadsNumPerPage
@ -439,12 +598,8 @@ Index =
return unless Index.searchInput.dataset.searching return unless Index.searchInput.dataset.searching
pageNum = Index.pageBeforeSearch pageNum = Index.pageBeforeSearch
delete Index.pageBeforeSearch delete Index.pageBeforeSearch
<% if (type === 'userscript') { %>
# XXX https://github.com/greasemonkey/greasemonkey/issues/1571 # XXX https://github.com/greasemonkey/greasemonkey/issues/1571
Index.searchInput.removeAttribute 'data-searching' Index.searchInput.removeAttribute 'data-searching'
<% } else { %>
delete Index.searchInput.dataset.searching
<% } %>
Index.sort() Index.sort()
# Go to the last available page if we were past the limit. # Go to the last available page if we were past the limit.
pageNum = Math.min pageNum, Index.getMaxPageNum() if Conf['Index Mode'] isnt 'all pages' pageNum = Math.min pageNum, Index.getMaxPageNum() if Conf['Index Mode'] isnt 'all pages'
@ -453,7 +608,7 @@ Index =
Index.buildIndex() Index.buildIndex()
Index.setPage() Index.setPage()
else else
history.pushState null, '', if pageNum is 1 then './' else pageNum Index.pushState Conf['Index Mode'], pageNum
Index.pageLoad pageNum Index.pageLoad pageNum
querySearch: (query) -> querySearch: (query) ->

View File

@ -293,6 +293,7 @@ Main =
['Strike-through Quotes', QuoteStrikeThrough] ['Strike-through Quotes', QuoteStrikeThrough]
['Quick Reply', QR] ['Quick Reply', QR]
['Menu', Menu] ['Menu', Menu]
['Index Generator (Menu)', Index.menu]
['Report Link', ReportLink] ['Report Link', ReportLink]
['Thread Hiding (Menu)', ThreadHiding.menu] ['Thread Hiding (Menu)', ThreadHiding.menu]
['Reply Hiding (Menu)', PostHiding.menu] ['Reply Hiding (Menu)', PostHiding.menu]

View File

@ -471,7 +471,11 @@ hr + div.center:not(.ad-cnt):not(.topad):not(.middlead):not(.bottomad) {
/* Index */ /* Index */
:root.index-loading .navLinks, :root.index-loading .navLinks,
:root.index-loading .board, :root.index-loading .board,
:root.index-loading .pagelist { :root.index-loading .pagelist,
:root.infinite-mode .pagelist,
:root.all-pages-mode .pagelist,
:root.catalog-mode .pagelist,
:root:not(.catalog-mode) #hidden-label {
display: none; display: none;
} }
#index-search { #index-search {
@ -485,7 +489,10 @@ hr + div.center:not(.ad-cnt):not(.topad):not(.middlead):not(.bottomad) {
} }
#index-search-clear { #index-search-clear {
color: gray; color: gray;
margin-left: -1em; display: inline-block;
position: relative;
left: -1em;
width: 0;
} }
<% if (type === 'crx') { %> <% if (type === 'crx') { %>
/* ``::-webkit-*'' selectors break selector lists on Firefox. */ /* ``::-webkit-*'' selectors break selector lists on Firefox. */
@ -494,10 +501,101 @@ hr + div.center:not(.ad-cnt):not(.topad):not(.middlead):not(.bottomad) {
#index-search:not([data-searching]) + #index-search-clear { #index-search:not([data-searching]) + #index-search-clear {
display: none; display: none;
} }
#index-sort {
float: right;
}
.summary { .summary {
text-decoration: none; text-decoration: none;
} }
/* Catalog */
:root.catalog-mode .board {
text-align: center;
}
.catalog-thread {
display: -webkit-inline-flex;
display: inline-flex;
text-align: left;
-webkit-flex-direction: column;
flex-direction: column;
-webkit-align-items: center;
align-items: center;
width: 165px;
margin: 0 2px 5px;
max-height: 320px;
word-wrap: break-word;
vertical-align: top;
}
.catalog-thread > a {
flex-shrink: 0;
-webkit-flex-shrink: 0;
position: relative;
}
.catalog-thumb {
max-width: 150px;
max-height: 150px;
border-radius: 2px;
box-shadow: 0 0 5px rgba(0, 0, 0, .25);
}
.catalog-thumb:not(.deleted-file):not(.no-file) {
min-width: 30px;
min-height: 30px;
}
.catalog-thumb.spoiler-file {
width: 100px;
height: 100px;
}
.catalog-thumb.deleted-file {
width: 127px;
height: 13px;
padding: 20px 11px;
}
.catalog-thumb.no-file {
width: 77px;
height: 13px;
padding: 20px 36px;
}
.catalog-icons > img,
.catalog-stats > .menu-button {
width: 1em;
height: 1em;
margin: 0;
vertical-align: text-top;
padding-left: 2px;
}
.catalog-stats > .menu-button {
text-align: center;
font-weight: normal;
}
.catalog-stats > .menu-button > i::before {
line-height: 11px;
}
.catalog-stats {
-webkit-flex-shrink: 0;
flex-shrink: 0;
cursor: help;
font-size: 10px;
font-weight: 700;
margin-top: 2px;
}
.catalog-thread > .subject {
-webkit-flex-shrink: 0;
flex-shrink: 0;
-webkit-align-self: stretch;
align-self: stretch;
font-weight: 700;
line-height: 1;
text-align: center;
}
.catalog-thread > .comment {
-webkit-flex-shrink: 1;
flex-shrink: 1;
-webkit-align-self: stretch;
align-self: stretch;
overflow: hidden;
text-align: center;
}
/* Announcement Hiding */ /* Announcement Hiding */
:root.hide-announcement #globalMessage { :root.hide-announcement #globalMessage {
display: none; display: none;
@ -752,9 +850,21 @@ span.hide-announcement {
display: none; display: none;
} }
/* Werk Tyme */ /* Werk Tyme */
:root.werkTyme .postContainer:not(.noFile) .fileThumb { :root.werkTyme .postContainer:not(.noFile) .fileThumb,
:root.werkTyme .catalog-thumb:not(.deleted-file):not(.no-file),
:root:not(.werkTyme) .werkTyme-filename {
display: none; display: none;
} }
.werkTyme-filename {
font-weight: bold;
}
:root.werkTyme .catalog-thread > a {
text-align: center;
}
.pinned .werkTyme-filename,
.filter-highlight .werkTyme-filename {
border: 2px solid rgba(255, 0, 0, .5);
}
/* Index/Reply Navigation */ /* Index/Reply Navigation */
#navlinks { #navlinks {
@ -762,6 +872,9 @@ span.hide-announcement {
top: 25px; top: 25px;
right: 10px; right: 10px;
} }
:root.catalog-mode #navlinks {
display: none;
}
/* Filter */ /* Filter */
.opContainer.filter-highlight { .opContainer.filter-highlight {
@ -770,6 +883,10 @@ span.hide-announcement {
.filter-highlight > .reply { .filter-highlight > .reply {
box-shadow: -5px 0 rgba(255, 0, 0, .5); box-shadow: -5px 0 rgba(255, 0, 0, .5);
} }
.pinned .catalog-thumb,
.filter-highlight .catalog-thumb {
border: 2px solid rgba(255, 0, 0, .5);
}
/* Spoiler text */ /* Spoiler text */
:root.reveal-spoilers s { :root.reveal-spoilers s {

View File

@ -1,6 +1,15 @@
<span class="brackets-wrap returnlink"><a href="javascript:;">Return</a></span> <span class="brackets-wrap returnlink"><a href="./">Return</a></span>
<span class="brackets-wrap cataloglink"><a href="javascript:;">Catalog</a></span> <span class="brackets-wrap cataloglink"><a href="./catalog">Catalog</a></span>
<span class="brackets-wrap bottomlink"><a href="#bottom">Bottom</a></span> <span class="brackets-wrap bottomlink"><a href="#bottom">Bottom</a></span>
<span class="brackets-wrap" id="index-last-refresh"><time title="Last index refresh">...</time></span> <span class="brackets-wrap" id="index-last-refresh"><time title="Last index refresh">...</time></span>
<input type="search" id="index-search" class="field" placeholder="Search"> <input type="search" id="index-search" class="field" placeholder="Search">
<a id="index-search-clear" href="javascript:;" title="Clear search">×</a> <a id="index-search-clear" href="javascript:;" title="Clear search">×</a>
<span id="hidden-label" hidden> &mdash; <span id="hidden-count"></span> <span id="hidden-toggle">[<a href="javascript:;">Show</a>]</span></span>
<select id="index-sort" name="Index Sort">
<option disabled>Index Sort</option>
<option value="bump">Bump order</option>
<option value="lastreply">Last reply</option>
<option value="birth">Creation date</option>
<option value="replycount">Reply count</option>
<option value="filecount">File count</option>
</select>

View File

@ -0,0 +1,16 @@
class CatalogThread
@callbacks = new Callbacks 'CatalogThread'
toString: -> @ID
constructor: (root, @thread) ->
@ID = @thread.ID
@board = @thread.board
@nodes =
root: root
thumb: $ '.catalog-thumb', root
icons: $ '.catalog-icons', root
postCount: $ '.post-count', root
fileCount: $ '.file-count', root
pageCount: $ '.page-count', root
comment: $ '.comment', root
@thread.catalogView = @

View File

@ -1,6 +1,7 @@
<%= grunt.file.read('src/General/lib/callbacks.class') %> <%= grunt.file.read('src/General/lib/callbacks.class') %>
<%= grunt.file.read('src/General/lib/board.class') %> <%= grunt.file.read('src/General/lib/board.class') %>
<%= grunt.file.read('src/General/lib/thread.class') %> <%= grunt.file.read('src/General/lib/thread.class') %>
<%= grunt.file.read('src/General/lib/catalogthread.class') %>
<%= grunt.file.read('src/General/lib/post.class') %> <%= grunt.file.read('src/General/lib/post.class') %>
<%= 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') %>

View File

@ -1,5 +1,5 @@
class DataBoard class DataBoard
@keys = ['hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads'] @keys = ['pinnedThreads', 'hiddenThreads', 'hiddenPosts', 'lastReadPosts', 'yourPosts', 'watchedThreads']
constructor: (@key, sync, dontClean) -> constructor: (@key, sync, dontClean) ->
@data = Conf[key] @data = Conf[key]

View File

@ -78,6 +78,9 @@ class Post
@parseQuotes() @parseQuotes()
@parseFile that @parseFile that
@isDead = false
@isHidden = false
@clones = [] @clones = []
g.posts.push @fullID, thread.posts.push @, board.posts.push @, @ g.posts.push @fullID, thread.posts.push @, board.posts.push @, @
@kill() if that.isArchived @kill() if that.isArchived
@ -85,6 +88,7 @@ class Post
parseComment: -> parseComment: ->
# Merge text nodes and remove empty ones. # Merge text nodes and remove empty ones.
@nodes.comment.normalize() @nodes.comment.normalize()
# Get the comment's text. # Get the comment's text.
# <br> -> \n # <br> -> \n
# Remove: # Remove:
@ -97,7 +101,11 @@ class Post
for node in $$ '.abbr, .exif, b', bq for node in $$ '.abbr, .exif, b', bq
$.rm node $.rm node
@info.comment = @nodesToText bq @info.comment = @nodesToText bq
# Hide spoilers.
# Save cleaned comment HTML.
@info.commentHTML = <%= html('&{bq}') %>
# Get the comment's text with spoilers hidden.
spoilers = $$ 's', bq spoilers = $$ 's', bq
@info.commentSpoilered = if spoilers.length @info.commentSpoilered = if spoilers.length
for node in spoilers for node in spoilers
@ -177,17 +185,14 @@ class Post
$.rmClass node, 'desktop' $.rmClass node, 'desktop'
return return
kill: (file, now) -> kill: (file) ->
now or= new Date()
if file if file
return if @file.isDead return if @file.isDead
@file.isDead = true @file.isDead = true
@file.timeOfDeath = now
$.addClass @nodes.root, 'deleted-file' $.addClass @nodes.root, 'deleted-file'
else else
return if @isDead return if @isDead
@isDead = true @isDead = true
@timeOfDeath = now
$.addClass @nodes.root, 'deleted-post' $.addClass @nodes.root, 'deleted-post'
unless strong = $ 'strong.warning', @nodes.info unless strong = $ 'strong.warning', @nodes.info
@ -199,7 +204,7 @@ class Post
return if @isClone return if @isClone
for clone in @clones for clone in @clones
clone.kill file, now clone.kill file
return if file return if file
# Get quotelinks/backlinks to this post # Get quotelinks/backlinks to this post
@ -212,7 +217,6 @@ class Post
# giving us false-positive dead posts. # giving us false-positive dead posts.
resurrect: -> resurrect: ->
delete @isDead delete @isDead
delete @timeOfDeath
$.rmClass @nodes.root, 'deleted-post' $.rmClass @nodes.root, 'deleted-post'
strong = $ 'strong.warning', @nodes.info strong = $ 'strong.warning', @nodes.info
# no false-positive files # no false-positive files

View File

@ -5,12 +5,19 @@ class Thread
constructor: (@ID, @board) -> constructor: (@ID, @board) ->
@fullID = "#{@board}.#{@ID}" @fullID = "#{@board}.#{@ID}"
@posts = new SimpleDict @posts = new SimpleDict
@isDead = false
@isHidden = false
@isOnTop = false
@isPinned = false
@isSticky = false @isSticky = false
@isClosed = false @isClosed = false
@isArchived = false @isArchived = false
@postLimit = false @postLimit = false
@fileLimit = false @fileLimit = false
@OP = null
@catalogView = null
g.threads.push @fullID, board.threads.push @, @ g.threads.push @fullID, board.threads.push @, @
setPage: (pageNum) -> setPage: (pageNum) ->
@ -20,6 +27,12 @@ class Thread
$.after $('a[title="Reply to this post"]', info), [$.tn(' '), icon] $.after $('a[title="Reply to this post"]', info), [$.tn(' '), icon]
icon.title = "This thread is on page #{pageNum} in the original index." icon.title = "This thread is on page #{pageNum} in the original index."
icon.textContent = "[#{pageNum}]" icon.textContent = "[#{pageNum}]"
@catalogView.nodes.pageCount.textContent = pageNum if @catalogView
setCount: (type, count, reachedLimit) ->
return unless @catalogView
el = @catalogView.nodes["#{type}Count"]
el.textContent = count
(if reachedLimit then $.addClass else $.rmClass) el, 'warning'
setStatus: (type, status) -> setStatus: (type, status) ->
name = "is#{type}" name = "is#{type}"
@ -37,21 +50,31 @@ class Thread
unless status unless status
$.rm icon.previousSibling $.rm icon.previousSibling
$.rm icon $.rm icon
$.rm $ ".#{typeLC}Icon", @catalogView.nodes.icons if @catalogView
return return
icon = $.el 'img', icon = $.el 'img',
src: "//s.4cdn.org/image/#{typeLC}#{if window.devicePixelRatio >= 2 then '@2x' else ''}.gif" src: "#{Build.staticPath}#{typeLC}#{Build.gifIcon}"
alt: type alt: type
title: type title: type
className: "#{typeLC}Icon retina" className: "#{typeLC}Icon retina"
root = if type isnt 'Sticky' and @isSticky root = if type isnt 'Sticky' and @isSticky
$ '.stickyIcon', @OP.nodes.info $ '.stickyIcon', @OP.nodes.info
else else
$('.page-num', @OP.nodes.info) or $('[title="Reply to this post"]', @OP.nodes.info) $('.page-num', @OP.nodes.info) or $('[title="Reply to this post"]', @OP.nodes.info)
$.after root, [$.tn(' '), icon] $.after root, [$.tn(' '), icon]
return unless @catalogView
(if type is 'Sticky' and @isClosed then $.prepend else $.add) @catalogView.nodes.icons, icon.cloneNode()
pin: ->
@isPinned = true
$.addClass @catalogView.nodes.root, 'pinned' if @catalogView
unpin: ->
@isPinned = false
$.rmClass @catalogView.nodes.root, 'pinned' if @catalogView
kill: -> kill: ->
@isDead = true @isDead = true
@timeOfDeath = Date.now()
collect: -> collect: ->
@posts.forEach (post) -> post.collect() @posts.forEach (post) -> post.collect()

View File

@ -20,10 +20,22 @@ FappeTyme =
name: 'Fappe Tyme' name: 'Fappe Tyme'
cb: @node cb: @node
CatalogThread.callbacks.push
name: 'Werk Tyme'
cb: @catalogNode
node: -> node: ->
return if @file return if @file
$.addClass @nodes.root, "noFile" $.addClass @nodes.root, "noFile"
catalogNode: ->
{file} = @thread.OP
return if !file
filename = $.el 'div',
textContent: file.name
className: 'werkTyme-filename'
$.add @nodes.thumb.parentNode, filename
cb: cb:
set: (type) -> set: (type) ->
FappeTyme[type].checked = Conf[type] FappeTyme[type].checked = Conf[type]

View File

@ -11,18 +11,22 @@ Menu =
Post.callbacks.push Post.callbacks.push
name: 'Menu' name: 'Menu'
cb: @node cb: @node
CatalogThread.callbacks.push
name: 'Menu'
cb: @catalogNode
node: -> node: ->
if @isClone if @isClone
$.on $('.menu-button', @nodes.info), 'click', Menu.toggle Menu.makeButton @, $('.menu-button', @nodes.info)
return return
$.add @nodes.info, Menu.makeButton() $.add @nodes.info, Menu.makeButton @
makeButton: -> catalogNode: ->
clone = Menu.button.cloneNode true post = g.threads[@thread.fullID].OP
$.on clone, 'click', Menu.toggle $.after @nodes.icons, Menu.makeButton post
clone
toggle: (e) -> makeButton: (post, button) ->
post = Get.postFromNode @ button or= Menu.button.cloneNode true
Menu.menu.toggle e, @, post $.on button, 'click', (e) ->
Menu.menu.toggle e, @, post
button

View File

@ -24,14 +24,17 @@ CatalogLinks =
CatalogLinks.set @checked CatalogLinks.set @checked
set: (useCatalog) -> set: (useCatalog) ->
path = if useCatalog then 'catalog' else '' path = if useCatalog
if Conf['JSON Navigation'] and Conf['Use 4chan X Catalog'] then '#catalog' else 'catalog'
else
''
generateURL = if useCatalog and Conf['External Catalog'] generateURL = if useCatalog and Conf['External Catalog']
CatalogLinks.external CatalogLinks.external
else else
(board) -> a.href = "/#{board}/#{path}" (board) -> a.href = "/#{board}/#{path}"
for a in $$ """#board-list a:not(.catalog), #boardNavDesktopFoot a""" for a in $$ """#board-list a:not([data-only]), #boardNavDesktopFoot a"""
continue if a.hostname not in ['boards.4chan.org', 'catalog.neet.tv', '4index.gropes.us'] or continue if a.hostname not in ['boards.4chan.org', 'catalog.neet.tv', '4index.gropes.us'] or
!(board = a.pathname.split('/')[1]) or !(board = a.pathname.split('/')[1]) or
board in ['f', 'status', '4chan'] or board in ['f', 'status', '4chan'] or

View File

@ -21,7 +21,7 @@ Fourchan =
if (!jsMath) return; if (!jsMath) return;
if (jsMath.loaded) { if (jsMath.loaded) {
// process one post // process one post
jsMath.ProcessBeforeShowing(document.getElementById(e.detail)); jsMath.ProcessBeforeShowing(e.target);
} else if (jsMath.Autoload && jsMath.Autoload.checked) { } else if (jsMath.Autoload && jsMath.Autoload.checked) {
// load jsMath and process whole document // load jsMath and process whole document
jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]); jsMath.Autoload.Script.Push('ProcessBeforeShowing', [null]);
@ -32,6 +32,9 @@ Fourchan =
Post.callbacks.push Post.callbacks.push
name: 'Parse /sci/ math' name: 'Parse /sci/ math'
cb: @math cb: @math
CatalogThread.callbacks.push
name: 'Parse /sci/ math'
cb: @math
code: -> code: ->
return if @isClone return if @isClone
apply = (e) -> apply = (e) ->
@ -44,8 +47,8 @@ Fourchan =
return return
math: -> math: ->
return if (@isClone and doc.contains @origin.nodes.root) or !$ '.math', @nodes.comment return if (@isClone and doc.contains @origin.nodes.root) or !$ '.math', @nodes.comment
$.asap (=> doc.contains @nodes.post), => $.asap (=> doc.contains @nodes.comment), =>
$.event 'jsmath', @nodes.post.id, window $.event 'jsmath', null, @nodes.comment
parseThread: (threadID, offset, limit) -> parseThread: (threadID, offset, limit) ->
# Fix /sci/ # Fix /sci/
# Fix /g/ # Fix /g/

View File

@ -21,21 +21,21 @@ Keybinds =
{target} = e {target} = e
if target.nodeName in ['INPUT', 'TEXTAREA'] if target.nodeName in ['INPUT', 'TEXTAREA']
return unless /(Esc|Alt|Ctrl|Meta|Shift\+\w{2,})/.test key return unless /(Esc|Alt|Ctrl|Meta|Shift\+\w{2,})/.test key
unless g.VIEW is 'catalog' unless g.VIEW is 'catalog' or g.VIEW is 'index' and Conf['JSON Navigation'] and Conf['Index Mode'] is 'catalog'
threadRoot = Nav.getThread() threadRoot = Nav.getThread()
if op = $ '.op', threadRoot if op = $ '.op', threadRoot
thread = Get.postFromNode(op).thread thread = Get.postFromNode(op).thread
switch key switch key
# QR & Options # QR & Options
when Conf['Toggle board list'] when Conf['Toggle board list']
if Conf['Custom Board Navigation'] return unless Conf['Custom Board Navigation']
Header.toggleBoardList() Header.toggleBoardList()
when Conf['Toggle header'] when Conf['Toggle header']
Header.toggleBarVisibility() Header.toggleBarVisibility()
when Conf['Open empty QR'] when Conf['Open empty QR']
Keybinds.qr() Keybinds.qr()
when Conf['Open QR'] when Conf['Open QR']
return if g.VIEW is 'catalog' return unless threadRoot
Keybinds.qr threadRoot Keybinds.qr threadRoot
when Conf['Open settings'] when Conf['Open settings']
Settings.open() Settings.open()
@ -45,11 +45,13 @@ Keybinds =
else if (notifications = $$ '.notification').length else if (notifications = $$ '.notification').length
for notification in notifications for notification in notifications
$('.close', notification).click() $('.close', notification).click()
else if QR.nodes else if QR.nodes and !QR.nodes.el.hidden
if Conf['Persistent QR'] if Conf['Persistent QR']
QR.hide() QR.hide()
else else
QR.close() QR.close()
else
return
when Conf['Spoiler tags'] when Conf['Spoiler tags']
return if target.nodeName isnt 'TEXTAREA' return if target.nodeName isnt 'TEXTAREA'
Keybinds.tags 'spoiler', target Keybinds.tags 'spoiler', target
@ -63,25 +65,31 @@ Keybinds =
return if target.nodeName isnt 'TEXTAREA' return if target.nodeName isnt 'TEXTAREA'
Keybinds.tags 'math', target Keybinds.tags 'math', target
when Conf['Toggle sage'] when Conf['Toggle sage']
Keybinds.sage() if QR.nodes return unless QR.nodes and !QR.nodes.el.hidden
Keybinds.sage()
when Conf['Submit QR'] when Conf['Submit QR']
QR.submit() if QR.nodes and !QR.status() return unless QR.nodes and !QR.nodes.el.hidden
QR.submit() if !QR.status()
# Index/Thread related # Index/Thread related
when Conf['Update'] when Conf['Update']
switch g.VIEW switch g.VIEW
when 'thread' when 'thread'
ThreadUpdater.update() if Conf['Thread Updater'] return unless Conf['Thread Updater']
ThreadUpdater.update()
when 'index' when 'index'
if Conf['JSON Navigation'] then Index.update() return unless Conf['JSON Navigation']
Index.update()
else
return
when Conf['Watch'] when Conf['Watch']
return if g.VIEW is 'catalog' return unless thread
ThreadWatcher.toggle thread ThreadWatcher.toggle thread
# Images # Images
when Conf['Expand image'] when Conf['Expand image']
return if g.VIEW is 'catalog' return unless threadRoot
Keybinds.img threadRoot Keybinds.img threadRoot
when Conf['Expand images'] when Conf['Expand images']
return if g.VIEW is 'catalog' return unless threadRoot
Keybinds.img threadRoot, true Keybinds.img threadRoot, true
when Conf['Open Gallery'] when Conf['Open Gallery']
return if g.VIEW is 'catalog' return if g.VIEW is 'catalog'
@ -95,6 +103,10 @@ Keybinds =
# Board Navigation # Board Navigation
when Conf['Front page'] when Conf['Front page']
if Conf['JSON Navigation'] and g.VIEW is 'index' if Conf['JSON Navigation'] and g.VIEW is 'index'
if Conf['Use 4chan X Catalog'] and Conf['Index Mode'] is 'catalog'
window.location = '#index'
return
return unless Conf['Index Mode'] in ['paged', 'infinite']
Index.userPageNav 1 Index.userPageNav 1
else else
window.location = "/#{g.BOARD}/" window.location = "/#{g.BOARD}/"
@ -103,16 +115,16 @@ Keybinds =
when Conf['Next page'] when Conf['Next page']
return unless g.VIEW is 'index' return unless g.VIEW is 'index'
if Conf['JSON Navigation'] if Conf['JSON Navigation']
if Conf['Index Mode'] isnt 'all pages' return unless Conf['Index Mode'] in ['paged', 'infinite']
$('.next button', Index.pagelist).click() $('.next button', Index.pagelist).click()
else else
if form = $ '.next form' if form = $ '.next form'
window.location = form.action window.location = form.action
when Conf['Previous page'] when Conf['Previous page']
return unless g.VIEW is 'index' return unless g.VIEW is 'index'
if Conf['JSON Navigation'] if Conf['JSON Navigation']
if Conf['Index Mode'] isnt 'all pages' return unless Conf['Index Mode'] in ['paged', 'infinite']
$('.prev button', Index.pagelist).click() $('.prev button', Index.pagelist).click()
else else
if form = $ '.prev form' if form = $ '.prev form'
window.location = form.action window.location = form.action
@ -125,45 +137,45 @@ Keybinds =
if Conf['External Catalog'] if Conf['External Catalog']
window.location = CatalogLinks.external(g.BOARD.ID) window.location = CatalogLinks.external(g.BOARD.ID)
else else
window.location = "/#{g.BOARD}/catalog" window.location = "/#{g.BOARD}/" + if Conf['JSON Navigation'] and Conf['Use 4chan X Catalog'] then '#catalog' else 'catalog'
# Thread Navigation # Thread Navigation
when Conf['Next thread'] when Conf['Next thread']
return if g.VIEW isnt 'index' return if g.VIEW isnt 'index' or !threadRoot
Nav.scroll +1 Nav.scroll +1
when Conf['Previous thread'] when Conf['Previous thread']
return if g.VIEW isnt 'index' return if g.VIEW isnt 'index' or !threadRoot
Nav.scroll -1 Nav.scroll -1
when Conf['Expand thread'] when Conf['Expand thread']
return if g.VIEW isnt 'index' return if g.VIEW isnt 'index' or !threadRoot
ExpandThread.toggle thread ExpandThread.toggle thread
when Conf['Open thread'] when Conf['Open thread']
return if g.VIEW isnt 'index' return if g.VIEW isnt 'index' or !threadRoot
Keybinds.open thread Keybinds.open thread
when Conf['Open thread tab'] when Conf['Open thread tab']
return if g.VIEW isnt 'index' return if g.VIEW isnt 'index' or !threadRoot
Keybinds.open thread, true Keybinds.open thread, true
# Reply Navigation # Reply Navigation
when Conf['Next reply'] when Conf['Next reply']
return if g.VIEW is 'catalog' return unless threadRoot
Keybinds.hl +1, threadRoot Keybinds.hl +1, threadRoot
when Conf['Previous reply'] when Conf['Previous reply']
return if g.VIEW is 'catalog' return unless threadRoot
Keybinds.hl -1, threadRoot Keybinds.hl -1, threadRoot
when Conf['Deselect reply'] when Conf['Deselect reply']
return if g.VIEW is 'catalog' return unless threadRoot
Keybinds.hl 0, threadRoot Keybinds.hl 0, threadRoot
when Conf['Hide'] when Conf['Hide']
return if g.VIEW is 'catalog' return unless thread
ThreadHiding.toggle thread if ThreadHiding.db ThreadHiding.toggle thread if ThreadHiding.db
when Conf['Previous Post Quoting You'] when Conf['Previous Post Quoting You']
return if g.VIEW is 'catalog' return unless threadRoot
QuoteYou.cb.seek 'preceding' QuoteYou.cb.seek 'preceding'
when Conf['Next Post Quoting You'] when Conf['Next Post Quoting You']
return if g.VIEW is 'catalog' return unless threadRoot
QuoteYou.cb.seek 'following' QuoteYou.cb.seek 'following'
<% if (tests_enabled) { %> <% if (tests_enabled) { %>
when 't' when 't'
return if g.VIEW is 'catalog' return unless threadRoot
BuildTest.testAll() BuildTest.testAll()
<% } %> <% } %>
else else