Drop navigation between threads/boards with JSON

This commit is contained in:
ccd0 2014-06-01 17:40:23 -07:00
parent 5989c4b555
commit 722635d722
11 changed files with 21 additions and 392 deletions

View File

@ -37,7 +37,6 @@ module.exports = (grunt) ->
'src/Monitoring/**/*.coffee' 'src/Monitoring/**/*.coffee'
'src/Archive/**/*.coffee' 'src/Archive/**/*.coffee'
'src/Miscellaneous/**/*.coffee' 'src/Miscellaneous/**/*.coffee'
'src/General/Navigate.coffee'
'src/General/Settings.coffee' 'src/General/Settings.coffee'
'src/General/Main.coffee' 'src/General/Main.coffee'
] ]

View File

@ -3,7 +3,7 @@ Config =
'Miscellaneous': 'Miscellaneous':
'JSON Navigation' : [ 'JSON Navigation' : [
false false
'Use JSON for loading the Board Index and Threads. Also allows searching and sorting the board index and infinite scolling.' 'Use JSON for loading the Board Index. Also allows searching and sorting the board index and infinite scolling.'
] ]
'Catalog Links': [ 'Catalog Links': [
true true

View File

@ -94,8 +94,6 @@ Header =
$.ready => $.ready =>
@footer = footer = $.id 'boardNavDesktopFoot' @footer = footer = $.id 'boardNavDesktopFoot'
if Conf['JSON Navigation']
$.on a, 'click', Navigate.navigate for a in $$ 'a', footer
if a = $ "a[href*='/#{g.BOARD}/']", footer if a = $ "a[href*='/#{g.BOARD}/']", footer
a.className = 'current' a.className = 'current'
@ -137,8 +135,6 @@ Header =
id: 'board-list' id: 'board-list'
innerHTML: "<span id='custom-board-list'></span><span id='full-board-list' hidden><span class='hide-board-list-container brackets-wrap'><a href='javascript:;' class='hide-board-list-button'>&nbsp;-&nbsp;</a></span> #{fourchannav.innerHTML}</span>" innerHTML: "<span id='custom-board-list'></span><span id='full-board-list' hidden><span class='hide-board-list-container brackets-wrap'><a href='javascript:;' class='hide-board-list-button'>&nbsp;-&nbsp;</a></span> #{fourchannav.innerHTML}</span>"
for a in $$ 'a', boardList for a in $$ 'a', boardList
if Conf['JSON Navigation']
$.on a, 'click', Navigate.navigate
if a.pathname.split('/')[1] is g.BOARD.ID if a.pathname.split('/')[1] is g.BOARD.ID
a.className = 'current' a.className = 'current'
fullBoardList = $ '#full-board-list', boardList fullBoardList = $ '#full-board-list', boardList
@ -184,9 +180,6 @@ Header =
if a.textContent is board if a.textContent is board
a = a.cloneNode true a = a.cloneNode true
if Conf['JSON Navigation']
$.on a, 'click', Navigate.navigate
a.textContent = if /-title/.test(t) or /-replace/.test(t) and $.hasClass a, 'current' a.textContent = if /-title/.test(t) or /-replace/.test(t) and $.hasClass a, 'current'
a.title a.title
else if /-full/.test t else if /-full/.test t

View File

@ -1,6 +1,6 @@
Index = 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 isnt 'index' or !Conf['JSON Navigation']
@board = "#{g.BOARD}" @board = "#{g.BOARD}"
@ -71,20 +71,16 @@ Index =
@navLinks = $.el 'div', @navLinks = $.el 'div',
className: 'navLinks' className: 'navLinks'
innerHTML: <%= importHTML('Features/Index-navlinks') %> innerHTML: <%= importHTML('Features/Index-navlinks') %>
@navLinksBot = $.el 'div', $('.returnlink a', @navLinks).href = "//boards.4chan.org/#{g.BOARD}/"
className: 'navLinks navLinksBot' $('.cataloglink a', @navLinks).href = "//boards.4chan.org/#{g.BOARD}/catalog"
innerHTML: <%= importHTML('Features/Index-navlinksbot') %>
@searchInput = $ '#index-search', @navLinks @searchInput = $ '#index-search', @navLinks
@currentPage = @getCurrentPage() @currentPage = @getCurrentPage()
$.on window, 'popstate', @cb.popstate
Index.setNavLinks()
$.on d, 'scroll', Index.scroll $.on d, 'scroll', Index.scroll
$.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 $('.returnlink a', @navLinks), 'click', Navigate.navigate
$.on $('.returnlink a', @navLinksBot), 'click', Navigate.navigate
@update() if g.VIEW is 'index' @update() if g.VIEW is 'index'
$.asap (-> $('.board', doc) or d.readyState isnt 'loading'), -> $.asap (-> $('.board', doc) or d.readyState isnt 'loading'), ->
@ -100,15 +96,11 @@ Index =
# Does not work on Firefox unfortunately. bugzil.la/939713 # Does not work on Firefox unfortunately. bugzil.la/939713
d.implementation.createDocument(null, null, null).appendChild board d.implementation.createDocument(null, null, null).appendChild board
$.asap (-> $('.navLinksBot.mobile', doc) or d.readyState isnt 'loading'), -> $.rm el for el in $$ '.navLinks'
$.rm el for el in $$ '.navLinks, .navLinksBot + hr'
$.id('search-box')?.parentNode.remove() $.id('search-box')?.parentNode.remove()
topNavPos = $.id('delform').previousElementSibling topNavPos = $.id('delform').previousElementSibling
botNavPos = $ '.board' $.before topNavPos, $.el 'hr'
$.before topNavPos, $.el 'hr' if g.VIEW is 'index'
$.before topNavPos, Index.navLinks $.before topNavPos, Index.navLinks
$.after botNavPos, $.el 'hr'
$.after botNavPos, Index.navLinksBot
$.asap (-> $('.pagelist', doc) or d.readyState isnt 'loading'), -> $.asap (-> $('.pagelist', doc) or d.readyState isnt 'loading'), ->
if pagelist = $('.pagelist') if pagelist = $('.pagelist')
@ -117,10 +109,6 @@ Index =
$.after $.id('delform'), Index.pagelist $.after $.id('delform'), Index.pagelist
$.rmClass doc, 'index-loading' $.rmClass doc, 'index-loading'
setNavLinks: () ->
$('.returnlink a', Index.navLinks).href = $('.returnlink a', Index.navLinksBot).href = "//boards.4chan.org/#{g.BOARD}/"
$('.cataloglink a', Index.navLinks).href = $('.cataloglink a', Index.navLinksBot).href = "//boards.4chan.org/#{g.BOARD}/catalog"
scroll: -> scroll: ->
return if Index.req or Conf['Index Mode'] isnt 'infinite' or (window.scrollY <= doc.scrollHeight - (300 + window.innerHeight)) or g.VIEW is 'thread' return if Index.req or Conf['Index Mode'] isnt 'infinite' or (window.scrollY <= 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
@ -153,6 +141,9 @@ Index =
Index.buildThreads() Index.buildThreads()
Index.sort() Index.sort()
Index.buildIndex() Index.buildIndex()
popstate: (e) ->
pageNum = Index.getCurrentPage()
Index.pageLoad pageNum if Index.currentPage isnt pageNum
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
@ -172,7 +163,7 @@ Index =
getCurrentPage: -> getCurrentPage: ->
+window.location.pathname.split('/')[2] or 1 +window.location.pathname.split('/')[2] or 1
userPageNav: (pageNum) -> userPageNav: (pageNum) ->
Navigate.pushState if pageNum is 1 then './' else pageNum history.pushState null, '', if pageNum is 1 then './' else pageNum
if Conf['Refreshed Navigation'] and Conf['Index Mode'] isnt 'all pages' if Conf['Refreshed Navigation'] and Conf['Index Mode'] isnt 'all pages'
Index.update pageNum Index.update pageNum
else else
@ -232,9 +223,6 @@ Index =
update: (pageNum, forceReparse) -> update: (pageNum, forceReparse) ->
return unless navigator.onLine return unless navigator.onLine
if g.VIEW is 'thread'
return ThreadUpdater.update() if Conf['Thread Updater']
return
unless d.readyState is 'loading' or Index.root.parentElement unless d.readyState is 'loading' or Index.root.parentElement
$.replace $('.board'), Index.root $.replace $('.board'), Index.root
delete Index.pageNum delete Index.pageNum
@ -281,8 +269,6 @@ Index =
new Notice 'warning', err, 1 new Notice 'warning', err, 1
return return
Navigate.title()
try try
if req.status is 200 if req.status is 200
Index.parse req.response, pageNum Index.parse req.response, pageNum
@ -498,7 +484,7 @@ Index =
Index.buildIndex() Index.buildIndex()
Index.setPage() Index.setPage()
else else
Navigate.pushState if pageNum is 1 then './' else pageNum history.pushState null, '', if pageNum is 1 then './' else pageNum
Index.pageLoad pageNum Index.pageLoad pageNum
querySearch: (query) -> querySearch: (query) ->

View File

@ -345,7 +345,6 @@ Main =
['Keybinds', Keybinds] ['Keybinds', Keybinds]
['Show Dice Roll', Dice] ['Show Dice Roll', Dice]
['Banner', Banner] ['Banner', Banner]
['Navigate', Navigate]
] ]
Main.init() Main.init()

View File

@ -1,329 +0,0 @@
Navigate =
path: window.location.pathname
init: ->
return if g.VIEW is 'catalog' or g.BOARD.ID is 'f' or !Conf['JSON Navigation']
<% if (type === 'crx') { %>
# blink/webkit throw a popstate on page load. Not what we want.
popstateHack = ->
$.off window, 'popstate', popstateHack
$.on window, 'popstate', Navigate.popstate
$.on window, 'popstate', popstateHack
<% } else { %>
$.on window, 'popstate', Navigate.popstate
<% } %>
@title = -> return
Thread.callbacks.push
name: 'Navigate'
cb: @thread
Post.callbacks.push
name: 'Navigate'
cb: @post
thread: ->
return if g.VIEW is 'thread' # The reply link only exists in index view
replyLink = $ 'a.replylink', @OP.nodes.info
$.on replyLink, 'click', Navigate.navigate
post: -> # Allows us to navigate via JSON from thread to thread by hashes and quote highlights.
# We don't need to reload the thread inside the thread
unless g.VIEW is 'thread' and @thread.ID is g.THREADID
$.on $('a[title="Link to this post"]', @nodes.info), 'click', Navigate.navigate
return unless (linktype = if Conf['Quote Inlining'] and Conf['Quote Hash Navigation']
'.hashlink'
else if !Conf['Quote Inlining']
'.quotelink'
else
null
)
Navigate.quoteLink $$ linktype, @nodes.comment
quoteLink: (links) ->
for link in links
Navigate.singleQuoteLink link
return
singleQuoteLink: (link) ->
{boardID, threadID} = Get.postDataFromLink link
if g.VIEW is 'index' or boardID isnt g.BOARD.ID or threadID isnt g.THREADID
$.on link, 'click', Navigate.navigate
clean: ->
# Garbage collection
g.threads.forEach (thread) -> thread.collect()
QuoteBacklink.containers = {}
$.rmAll $ '.board'
features: [
['Thread Excerpt', ThreadExcerpt]
['Unread Count', Unread]
['Quote Threading', QuoteThreading]
['Thread Stats', ThreadStats]
['Thread Updater', ThreadUpdater]
['Thread Expansion', ExpandThread]
]
disconnect: ->
for [name, feature] in Navigate.features
try
feature.disconnect()
catch err
errors = [] unless errors
errors.push
message: "Failed to disconnect feature #{name}."
error: err
Main.handleErrors errors if errors
return
reconnect: ->
for [name, feature] in Navigate.features
try
feature.init()
catch err
errors = [] unless errors
errors.push
message: "Failed to reconnect feature #{name}."
error: err
Main.handleErrors errors if errors
return
updateContext: (view) ->
g.DEAD = false
g.THREADID = +window.location.pathname.split('/')[3] if view is 'thread'
unless view is g.VIEW
$.rmClass doc, g.VIEW
$.addClass doc, view
delete g.THREADID if view is 'index'
origFormThread = $ 'form[name="post"] input[name="resto"]'
if view is 'thread'
unless origFormThread
origFormThread = $.el 'input',
type: 'hidden'
name: 'resto'
$.after ($.id 'postPassword'), origFormThread
origFormThread.value = g.THREADID
else
$.rm origFormThread if origFormThread
if Conf['Quick Reply']
QR.link.textContent = if view is 'thread' then 'Reply to Thread' else 'Start a Thread'
QR.status() # Re-enable the QR in the case of a 404'd thread or something.
for post in QR.posts
post.thread = g.THREADID or 'new'
g.VIEW = view
updateBoard: (boardID) ->
fullBoardList = $ '#full-board-list', Header.boardList
$.rmClass current, 'current' if current = $ '.current', fullBoardList
$.addClass current, 'current' if current = $ "a[href*='/#{boardID}/']", fullBoardList
Header.generateBoardList Conf['boardnav'].replace /(\r\n|\n|\r)/g, ' '
Index.setNavLinks()
$('form[name="post"]').action = "//sys.4chan.org/#{g.BOARD}/post"
QR.flagsInput() if Conf['Quick Reply']
$.cache '//a.4cdn.org/boards.json', ->
try
return unless @status is 200
for aboard in @response.boards when aboard.board is boardID
board = aboard
break
catch err
Main.handleErrors [
message: "Navigation failed to update board name."
error: err
]
return unless board
Navigate.updateTitle board
Navigate.updateSFW !!board.ws_board
updateSFW: (sfw) ->
Favicon.el.href = Favicon.default = "//s.4cdn.org/image/favicon#{if sfw then '-ws' else ''}.ico"
# Changing the href alone doesn't update the icon on Firefox
$.add d.head, Favicon.el
return if Favicon.SFW is sfw # Board SFW status hasn't changed
Favicon.SFW = sfw
Favicon.update()
findStyle = (type, base) ->
style = d.cookie.match new RegExp "\\b#{type}_style=([^;]+)"
return ["#{type}_style", (if style then style[1] else base)]
style = if sfw
findStyle 'ws', 'Yotsuba B New'
else
findStyle 'nws', 'Yotsuba New'
$.globalEval "var style_group = #{JSON.stringify style[0]}"
$('link[title=switch]', d.head).href = $("link[title='#{style[1]}']", d.head).href
Main.setClass()
updateTitle: ({board, title}) ->
$.rm subtitle if subtitle = $ '.boardSubtitle'
$('.boardTitle').textContent = d.title = "/#{board}/ - #{title}"
navigate: (e) ->
return if @hostname isnt 'boards.4chan.org' or window.location.hostname is 'rs.4chan.org'
if e
return if e.shiftKey or e.ctrlKey or (e.type is 'click' and e.button isnt 0) # Not simply a left click
if @pathname is Navigate.path
return if @id is 'popState'
if g.VIEW is 'thread'
ThreadUpdater.update() if Conf['Thread Updater']
else
Index.update()
e?.preventDefault()
return
$.addClass Index.button, 'fa-spin'
Index.clearSearch() if Index.isSearching
[_, boardID, view, threadID] = @pathname.split '/'
return if view is 'catalog' or 'f' in [boardID, g.BOARD.ID]
e?.preventDefault()
Navigate.title = -> return
delete Index.pageNum
$.rmAll Header.hover
if threadID
view = 'thread'
else
pageNum = +view or 1 # string to number, '' to 1
view = 'index' # path is "/boardID/". See the problem?
path = @pathname
path += @hash if @hash
history.pushState null, '', path unless @id is 'popState'
Navigate.path = @pathname
unless view is 'index' and 'index' is g.VIEW and boardID is g.BOARD.ID
Navigate.disconnect()
Navigate.updateContext view
Navigate.clean()
Navigate.reconnect()
if boardID is g.BOARD.ID
Navigate.title = -> d.title = $('.boardTitle').textContent if view is 'index'
else
g.BOARD = new Board boardID
Navigate.title = -> Navigate.updateBoard boardID
Navigate.updateSFW Favicon.SFW
if view is 'index'
return Index.update pageNum, true
# Moving from index to thread or thread to thread
{load} = Navigate
Navigate.req = $.ajax "//a.4cdn.org/#{boardID}/thread/#{threadID}.json",
onabort: load
onloadend: load
setTimeout (->
if Navigate.req and !Navigate.notice
Navigate.notice = new Notice 'info', 'Loading thread...'
), 3 * $.SECOND
load: (e) ->
$.rmClass Index.button, 'fa-spin'
{req, notice} = Navigate
notice?.close()
delete Navigate.req
delete Navigate.notice
if e.type is 'abort' or req.status isnt 200
req.onloadend = null
new Notice 'warning', "Failed to load thread.#{if req.status then " #{req.status}" else ''}"
return
Navigate.title()
try
Navigate.parse req.response.posts
catch err
console.error 'Navigate failure:'
console.log err
# network error or non-JSON content for example.
if notice
notice.setType 'error'
notice.el.lastElementChild.textContent = 'Navigation Failed.'
setTimeout notice.close, 2 * $.SECOND
else
new Notice 'error', 'Navigation Failed.', 2
return
parse: (data) ->
posts = []
errors = null
board = g.BOARD
threadRoot = Build.thread board, OP = data[0], true
thread = new Thread OP.no, board
makePost = (postNode) ->
try
posts.push new Post postNode, thread, board
catch err
# Skip posts that we failed to parse.
errors = [] unless errors
errors.push
message: "Parsing of Post No.#{postNode.ID} failed. Post will be skipped."
error: err
makePost $('.opContainer', threadRoot)
i = 0
while obj = data[++i]
post = Build.postFromObject obj, board
makePost post
$.add threadRoot, post
Main.callbackNodes Thread, [thread]
Main.callbackNodes Post, posts
QuoteThreading.force() if Conf['Quote Threading'] and not Conf['Unread Count']
board = $ '.board'
$.rmAll board
$.add board, [threadRoot, $.el 'hr']
Unread.ready() if Conf['Unread Count']
QR.generatePostableThreadsList() if Conf['Quick Reply']
Header.hashScroll.call window
Main.handleErrors errors if errors
pushState: (path) ->
history.pushState null, '', path
Navigate.path = window.location.pathname
popstate: ->
a = $.el 'a',
href: window.location
id: 'popState'
Navigate.navigate.call a

View File

@ -471,8 +471,7 @@ div.center:not(.ad-cnt) {
/* 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.thread .pagelist {
display: none; display: none;
} }
#index-search { #index-search {
@ -498,15 +497,6 @@ div.center:not(.ad-cnt) {
.summary { .summary {
text-decoration: none; text-decoration: none;
} }
.index .returnlink,
.index .bottomlink,
.index .navLinksBot,
.index .navLinksBot + hr,
.thread #index-last-refresh,
.thread #index-search-clear,
.thread #index-search {
display: none;
}
/* Announcement Hiding */ /* Announcement Hiding */
:root.hide-announcement #globalMessage { :root.hide-announcement #globalMessage {

View File

@ -1,3 +0,0 @@
<span class="brackets-wrap returnlink"><a href="javascript:;">Return</a></span>
<span class="brackets-wrap cataloglink"><a href="javascript:;">Catalog</a></span>
<span class="brackets-wrap toplink"><a href="#top">Top</a></span>

View File

@ -25,13 +25,12 @@ ThreadUpdater =
$.on @timer, 'click', @update $.on @timer, 'click', @update
$.on @status, 'click', @update $.on @status, 'click', @update
unless Index.navLinksBot and $ '.updatelink', Index.navLinksBot updateLink = $.el 'span',
updateLink = $.el 'span', innerHTML: '<a href="javascript:;">Update</a>'
innerHTML: '<a href="javascript:;">Update</a>' className: 'brackets-wrap updatelink'
className: 'brackets-wrap updatelink' $.ready ->
$.ready -> $.add $('.navLinksBot'), [$.tn(' '), updateLink]
$.add (Index.navLinksBot or $('.navLinksBot')), [$.tn(' '), updateLink] $.on updateLink.firstElementChild, 'click', @update
$.on updateLink.firstElementChild, 'click', @update
subEntries = [] subEntries = []
for name, conf of Config.updater.checkbox for name, conf of Config.updater.checkbox

View File

@ -56,7 +56,7 @@ QR =
if Conf['QR Shortcut'] if Conf['QR Shortcut']
$.rmClass $('.qr-shortcut'), 'disabled' $.rmClass $('.qr-shortcut'), 'disabled'
if Conf['Bottom QR Link'] and (g.VIEW is 'thread' or Conf['JSON Navigation']) if Conf['Bottom QR Link'] and g.VIEW is 'thread'
linkBot = $.el 'div', linkBot = $.el 'div',
innerHTML: '<a href="javascript:;" class="qr-link-bottom">Reply to Thread</a>' innerHTML: '<a href="javascript:;" class="qr-link-bottom">Reply to Thread</a>'
className: "brackets-wrap qr-link-container-bottom" className: "brackets-wrap qr-link-container-bottom"
@ -68,7 +68,7 @@ QR =
if Conf['QR Shortcut'] if Conf['QR Shortcut']
$.rmClass $('.qr-shortcut'), 'disabled' $.rmClass $('.qr-shortcut'), 'disabled'
$.prepend (Index.navLinksBot or $('.navLinksBot')), linkBot $.prepend $('.navLinksBot'), linkBot
$.before $.id('togglePostFormLink'), link $.before $.id('togglePostFormLink'), link

View File

@ -43,11 +43,6 @@ QuoteBacklink =
if Conf['Quote Hash Navigation'] if Conf['Quote Hash Navigation']
hash = QuoteInline.qiQuote link, $.hasClass link, 'filtered' hash = QuoteInline.qiQuote link, $.hasClass link, 'filtered'
nodes.push hash nodes.push hash
if Conf['JSON Navigation']
if hash
Navigate.singleQuoteLink hash
else unless Conf['Quote Inlining']
Navigate.singleQuoteLink link
$.add container, nodes $.add container, nodes
return return
secondNode: -> secondNode: ->