Merge pull request #1405 from MayhemYDG/index

Index navigation improvements - EP04
This commit is contained in:
Mayhem 2014-01-29 06:23:55 -08:00
commit 20ceb570e1
14 changed files with 287 additions and 21 deletions

View File

@ -1,3 +1,15 @@
- More index navigation improvements:
- New index mode: `catalog`
- When in catalog mode, use `Shift+Click` to hide, and `Alt+Click` to pin threads.
- Existing features affect the catalog mode such as:
<ul>
<li> Filter (hiding, highlighting)
<li> Thread Hiding
<li> Linkify
<li> Auto-GIF
<li> Image Hover
</ul>
- Support for the official catalog will be removed in the future, once the catalog mode for the index is deemed satisfactory.
- Added `Original filename` variable to Sauce panel.
- Added a `Reset Settings` button in the settings.

View File

@ -40,6 +40,7 @@ module.exports = (grunt) ->
# <--|
'src/General/Board.coffee'
'src/General/Thread.coffee'
'src/General/CatalogThread.coffee'
'src/General/Post.coffee'
'src/General/Clone.coffee'
'src/General/DataBoard.coffee'

View File

@ -401,6 +401,58 @@ a[href="javascript:;"] {
.summary {
text-decoration: none;
}
.catalog-mode {
text-align: center;
}
.catalog-thread {
display: inline-block;
vertical-align: top;
padding-top: 5px;
width: 165px;
max-height: 320px;
overflow: hidden;
word-break: break-word;
}
.catalog-thread > a {
display: inline-block;
position: relative;
}
.thumb {
max-width: 150px;
max-height: 150px;
border-radius: 2px;
box-shadow: 0 0 5px rgba(0, 0, 0, .25);
}
.thunb.spoiler-file {
width: 100px;
height: 100px;
}
.thumb.deleted-file {
width: 127px;
height: 13px;
padding: 20px 11px;
}
.thumb.no-file {
width: 77px;
height: 13px;
padding: 20px 36px;
}
.thread-icons {
position: absolute;
top: 1px;
right: 1px;
}
.thread-stats {
cursor: help;
font-size: 10px;
font-weight: 700;
line-height: .8;
margin-top: 1px;
float: none;
}
.catalog-thread .subject {
font-weight: 700;
}
/* Announcement Hiding */
:root.hide-announcement #globalMessage,
@ -597,6 +649,10 @@ a.hide-announcement {
.filter-highlight > .reply {
box-shadow: -5px 0 rgba(255, 0, 0, .5);
}
.pinned .thumb,
.filter-highlight .thumb {
border: 2px solid rgba(255, 0, 0, .5);
}
/* Thread & Reply Hiding */
.hide-thread-button,

View File

@ -0,0 +1,9 @@
<a href="/#{thread.board}/res/#{thread.ID}" target="_blank">
<img src="#{src}" class="thumb #{imgClass or ''}" #{if imgWidth then "width='#{imgWidth}'"} #{if imgHeight then "height='#{imgHeight}'"}>
<div class="thread-icons"></div>
</a>
<div class="thread-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>
</div>
#{subject}
<div class="comment">#{comment}</div>

View File

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

View File

@ -1,10 +1,10 @@
ThreadHiding =
init: ->
return if g.VIEW isnt 'index' or !Conf['Thread Hiding'] and !Conf['Thread Hiding Link']
return if g.VIEW isnt 'index'
@db = new DataBoard 'hiddenThreads'
@syncCatalog()
$.on d, 'IndexBuild', @onIndexBuild
$.on d, 'IndexRefresh', @onIndexRefresh
Thread.callbacks.push
name: 'Thread Hiding'
cb: @node
@ -15,8 +15,8 @@ ThreadHiding =
return unless Conf['Thread Hiding']
$.prepend @OP.nodes.root, ThreadHiding.makeButton @, 'hide'
onIndexBuild: ({detail: nodes}) ->
for root, i in nodes by 2
onIndexRefresh: ->
for root, i in Index.nodes by 2
thread = Get.threadFromRoot root
continue unless thread.isHidden
unless thread.stub

View File

@ -254,9 +254,69 @@ Build =
[posts, files] = if Conf['Show Replies']
[data.omitted_posts, data.omitted_images]
else
# XXX data.images is not accurate.
[data.replies, data.omitted_images + data.last_replies.filter((data) -> !!data.ext).length]
[data.replies, data.images]
nodes.push Build.summary board.ID, data.no, posts, files
$.add root, nodes
root
catalogThread: (thread) ->
{staticPath, gifIcon} = Build
data = Index.liveThreadData[Index.liveThreadIDs.indexOf thread.ID]
if data.spoiler and !Conf['Reveal Spoilers']
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'
postCount = data.replies + 1
fileCount = data.images + !!data.ext
pageCount = Math.floor Index.liveThreadIDs.indexOf(thread.ID) / Index.threadsNumPerPage
subject = if thread.OP.info.subject
"<div class='subject'>#{thread.OP.info.subject}</div>"
else
''
comment = thread.OP.nodes.comment.innerHTML.replace /(<br>){2,}/g, '<br>'
root = $.el 'div',
className: 'catalog-thread'
innerHTML: <%= importHTML('General/Thread-catalog-view') %>
root.dataset.fullID = thread.fullID
$.addClass root, 'pinned' if thread.isPinned
$.addClass root, thread.OP.highlights... if thread.OP.highlights
for quotelink in $$ '.quotelink', root.lastElementChild
$.replace quotelink, [quotelink.childNodes...]
if thread.isSticky
$.add $('.thread-icons', root), $.el 'img',
src: "#{staticPath}sticky#{gifIcon}"
className: 'stickyIcon'
title: 'Sticky'
if thread.isClosed
$.add $('.thread-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

@ -0,0 +1,14 @@
class CatalogThread
@callbacks = []
toString: -> @ID
constructor: (root, @thread) ->
@ID = @thread.ID
@board = @thread.board
@nodes =
root: root
thumb: $ '.thumb', root
postCount: $ '.post-count', root
fileCount: $ '.file-count', root
pageCount: $ '.page-count', root
@thread.catalogView = @

View File

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

View File

@ -1,7 +1,25 @@
Index =
init: ->
if g.VIEW is 'catalog'
$.ready ->
span = $.el 'a',
href: '<%= meta.repo %>blob/<%= meta.mainBranch %>/CHANGELOG.md'
textContent: 'Support for the official catalog to be removed'
title: '<%= meta.name %> now has a "catalog" Index mode.'
className: 'btn-wrap warning'
target: '_blank'
$.add $.id('info'), span
return if g.VIEW isnt 'index' or g.BOARD.ID is 'f'
@db = new DataBoard 'pinnedThreads'
Thread.callbacks.push
name: 'Thread Pinning'
cb: @threadNode
CatalogThread.callbacks.push
name: 'Catalog Features'
cb: @catalogNode
@button = $.el 'a',
className: 'index-refresh-shortcut fa fa-refresh'
title: 'Refresh Index'
@ -14,6 +32,7 @@ Index =
subEntries: [
{ el: $.el 'label', innerHTML: '<input type=radio name="Index Mode" value="paged"> Paged' }
{ el: $.el 'label', innerHTML: '<input type=radio name="Index Mode" value="all pages"> All threads' }
{ el: $.el 'label', innerHTML: '<input type=radio name="Index Mode" value="catalog"> Catalog' }
]
for label in modeEntry.subEntries
input = label.el.firstChild
@ -68,6 +87,7 @@ Index =
$.addClass doc, 'index-loading'
@update()
@root = $.el 'div', className: 'board'
Index.cb.rootClass()
@pagelist = $.el 'div',
className: 'pagelist'
hidden: true
@ -100,8 +120,44 @@ Index =
$.asap (-> $('.pagelist') or d.readyState isnt 'loading'), ->
$.replace $('.pagelist'), Index.pagelist
threadNode: ->
return unless data = Index.db.get {boardID: @board.ID, threadID: @ID}
@pin() if data.isPinned
catalogNode: ->
$.on @nodes.thumb, 'click', Index.onClick
onClick: (e) ->
return if e.button isnt 0
root = @parentNode.parentNode
thread = g.threads[root.dataset.fullID]
if e.shiftKey
$.rm root
ThreadHiding.hide thread
ThreadHiding.saveHiddenState thread
else if e.altKey
Index.togglePin thread
else
return
e.preventDefault()
togglePin: (thread) ->
if thread.isPinned
thread.unpin()
Index.db.delete
boardID: thread.board.ID
threadID: thread.ID
else
thread.pin()
Index.db.set
boardID: thread.board.ID
threadID: thread.ID
val: isPinned: thread.isPinned
Index.sort()
Index.buildIndex()
cb:
rootClass: ->
(if Conf['Index Mode'] is 'catalog' then $.addClass else $.rmClass) Index.root, 'catalog-mode'
mode: ->
Index.cb.rootClass()
Index.togglePagelist()
Index.buildIndex()
sort: ->
@ -289,6 +345,8 @@ Index =
Index.nodes.push threadRoot, $.el 'hr'
if thread = g.BOARD.threads[threadData.no]
thread.setPage Math.floor i / Index.threadsNumPerPage
thread.setCount 'post', threadData.replies + 1, threadData.bumplimit
thread.setCount 'file', threadData.images + !!threadData.ext, threadData.imagelimit
thread.setStatus 'Sticky', !!threadData.sticky
thread.setStatus 'Closed', !!threadData.closed
else
@ -334,6 +392,16 @@ Index =
Main.handleErrors errors if errors
Main.callbackNodes Post, posts
buildCatalogViews: ->
threads = Index.sortedNodes
.filter((n, i) -> !(i % 2))
.map((threadRoot) -> Get.threadFromRoot threadRoot)
.filter (thread) -> !thread.isHidden
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: ->
switch Conf['Index Sort']
when 'bump'
@ -368,16 +436,19 @@ Index =
Index.sortedNodes.splice offset++ * 2, 0, Index.sortedNodes.splice(i, 2)...
return
buildIndex: ->
if Conf['Index Mode'] is 'paged'
pageNum = Index.getCurrentPage()
nodesPerPage = Index.threadsNumPerPage * 2
nodes = Index.sortedNodes[nodesPerPage * pageNum ... nodesPerPage * (pageNum + 1)]
else
nodes = Index.sortedNodes
switch Conf['Index Mode']
when 'paged'
pageNum = Index.getCurrentPage()
nodesPerPage = Index.threadsNumPerPage * 2
nodes = Index.sortedNodes[nodesPerPage * pageNum ... nodesPerPage * (pageNum + 1)]
when 'catalog'
nodes = Index.buildCatalogViews()
else
nodes = Index.sortedNodes
$.rmAll Index.root
Index.buildReplies nodes if Conf['Show Replies']
$.event 'IndexBuild', nodes
Index.buildReplies nodes if Conf['Show Replies'] and Conf['Index Mode'] isnt 'catalog'
$.add Index.root, nodes
$.event 'IndexBuild', nodes
isSearching: false
clearSearch: ->

View File

@ -10,13 +10,21 @@ class Thread
@postLimit = false
@fileLimit = false
@OP = null
@catalogView = null
g.threads[@fullID] = board.threads[@] = @
setPage: (pageNum) ->
icon = $ '.page-num', @OP.nodes.post
for key in ['title', 'textContent']
icon[key] = icon[key].replace /\d+/, pageNum
return
@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) ->
name = "is#{type}"
return if @[name] is status
@ -25,20 +33,33 @@ class Thread
typeLC = type.toLowerCase()
unless status
$.rm $ ".#{typeLC}Icon", @OP.nodes.info
$.rm $ ".#{typeLC}Icon", @catalogView if @catalogView
return
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
title: type
className: "#{typeLC}Icon"
root = if type is 'Closed' and @isSticky
$ '.stickyIcon', @OP.nodes.info
else if g.VIEW is 'index'
$ '.page-num', @OP.nodes.info
$ '.page-num', @OP.nodes.info
else
$ '[title="Quote this post"]', @OP.nodes.info
$.after root, [$.tn(' '), icon]
return unless @catalogView
root = $ '.thread-icons', @catalogView
(if type is 'Sticky' and @isClosed then $.prepend else $.add) root, icon.cloneNode()
pin: ->
@isOnTop = @isPinned = true
$.addClass @catalogView.nodes.root, 'pinned' if @catalogView
unpin: ->
@isOnTop = @isPinned = false
$.rmClass @catalogView.nodes.root, 'pinned' if @catalogView
kill: ->
@isDead = true
@timeOfDeath = Date.now()

View File

@ -5,6 +5,9 @@ AutoGIF =
Post.callbacks.push
name: 'Auto-GIF'
cb: @node
CatalogThread.callbacks.push
name: 'Auto-GIF'
cb: @catalogNode
node: ->
return if @isClone or @isHidden or @thread.isHidden or !@file?.isImage
{thumb, URL} = @file
@ -13,8 +16,17 @@ AutoGIF =
# Revealed spoilers do not have height/width set, this fixes auto-gifs dimensions.
{style} = thumb
style.maxHeight = style.maxWidth = if @isReply then '125px' else '250px'
AutoGIF.replaceThumbnail thumb, URL
catalogNode: ->
{OP} = @thread
return unless OP.file?.isImage
{URL} = OP.file
return unless /gif$/.test URL
AutoGIF.replaceThumbnail @nodes.thumb, URL
replaceThumbnail: (thumb, URL) ->
gif = $.el 'img'
$.on gif, 'load', ->
# Replace the thumbnail once the GIF has finished loading.
thumb.src = URL
gif.src = URL

View File

@ -5,11 +5,20 @@ ImageHover =
Post.callbacks.push
name: 'Image Hover'
cb: @node
CatalogThread.callbacks.push
name: 'Image Hover'
cb: @catalogNode
node: ->
return unless @file?.isImage
$.on @file.thumb, 'mouseover', ImageHover.mouseover
catalogNode: ->
return unless @thread.OP.file?.isImage
$.on @nodes.thumb, 'mouseover', ImageHover.mouseover
mouseover: (e) ->
post = Get.postFromNode @
post = if $.hasClass @, 'thumb'
g.posts[@parentNode.parentNode.dataset.fullID]
else
Get.postFromNode @
el = $.el 'img',
id: 'ihover'
src: post.file.URL

View File

@ -72,9 +72,8 @@ ThreadWatcher =
else if Conf['Auto Watch Reply']
ThreadWatcher.add board.threads[threadID]
onIndexRefresh: ->
{db} = ThreadWatcher
boardID = g.BOARD.ID
for threadID, data of db.data.boards[boardID] when not data.isDead and threadID not of g.BOARD.threads
for threadID, data of ThreadWatcher.db.data.boards[boardID] when not data.isDead and threadID not of g.BOARD.threads
if Conf['Auto Prune']
ThreadWatcher.db.delete {boardID, threadID}
else