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/Archive/**/*.coffee'
'src/Miscellaneous/**/*.coffee'
'src/General/Navigate.coffee'
'src/General/Settings.coffee'
'src/General/Main.coffee'
]

View File

@ -3,7 +3,7 @@ Config =
'Miscellaneous':
'JSON Navigation' : [
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': [
true

View File

@ -94,8 +94,6 @@ Header =
$.ready =>
@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
a.className = 'current'
@ -137,8 +135,6 @@ Header =
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>"
for a in $$ 'a', boardList
if Conf['JSON Navigation']
$.on a, 'click', Navigate.navigate
if a.pathname.split('/')[1] is g.BOARD.ID
a.className = 'current'
fullBoardList = $ '#full-board-list', boardList
@ -184,9 +180,6 @@ Header =
if a.textContent is board
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.title
else if /-full/.test t

View File

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

View File

@ -345,7 +345,6 @@ Main =
['Keybinds', Keybinds]
['Show Dice Roll', Dice]
['Banner', Banner]
['Navigate', Navigate]
]
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 */
:root.index-loading .navLinks,
:root.index-loading .board,
:root.index-loading .pagelist,
:root.thread .pagelist {
:root.index-loading .pagelist {
display: none;
}
#index-search {
@ -498,15 +497,6 @@ div.center:not(.ad-cnt) {
.summary {
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 */
: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 @status, 'click', @update
unless Index.navLinksBot and $ '.updatelink', Index.navLinksBot
updateLink = $.el 'span',
innerHTML: '<a href="javascript:;">Update</a>'
className: 'brackets-wrap updatelink'
$.ready ->
$.add (Index.navLinksBot or $('.navLinksBot')), [$.tn(' '), updateLink]
$.on updateLink.firstElementChild, 'click', @update
updateLink = $.el 'span',
innerHTML: '<a href="javascript:;">Update</a>'
className: 'brackets-wrap updatelink'
$.ready ->
$.add $('.navLinksBot'), [$.tn(' '), updateLink]
$.on updateLink.firstElementChild, 'click', @update
subEntries = []
for name, conf of Config.updater.checkbox

View File

@ -56,7 +56,7 @@ QR =
if Conf['QR Shortcut']
$.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',
innerHTML: '<a href="javascript:;" class="qr-link-bottom">Reply to Thread</a>'
className: "brackets-wrap qr-link-container-bottom"
@ -68,7 +68,7 @@ QR =
if Conf['QR Shortcut']
$.rmClass $('.qr-shortcut'), 'disabled'
$.prepend (Index.navLinksBot or $('.navLinksBot')), linkBot
$.prepend $('.navLinksBot'), linkBot
$.before $.id('togglePostFormLink'), link

View File

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