From 9bb9be1f67e81a4907eaf886e1ae0951672190eb Mon Sep 17 00:00:00 2001 From: ccd0 Date: Mon, 26 Sep 2016 21:37:29 -0700 Subject: [PATCH] Show excerpts of replies in catalog with full reply on hover. --- src/General/Build.coffee | 8 +++++ src/General/Index.coffee | 48 +++++++++++++++++++++++--- src/Miscellaneous/RelativeDates.coffee | 24 +++++++++---- src/classes/CatalogThread.coffee | 3 +- src/classes/Post.Clone.coffee | 1 + src/config/Config.coffee | 1 + src/css/burichan.css | 4 ++- src/css/futaba.css | 4 ++- src/css/photon.css | 4 ++- src/css/style.css | 26 ++++++++++++-- src/css/tomorrow.css | 4 ++- src/css/yotsuba-b.css | 4 ++- src/css/yotsuba.css | 4 ++- 13 files changed, 114 insertions(+), 21 deletions(-) diff --git a/src/General/Build.coffee b/src/General/Build.coffee index 56843bc6a..1408c5dde 100644 --- a/src/General/Build.coffee +++ b/src/General/Build.coffee @@ -87,6 +87,14 @@ Build = .replace(/<[^>]*>/g, '') Build.unescape html + parseCommentDisplay: (html) -> + # Hide spoilers. + unless Conf['Remove Spoilers'] or Conf['Reveal Spoilers'] + while (html2 = html.replace /(?:(?!<\/?s>).)*<\/s>/g, '[spoiler]') isnt html + html = html2 + # Remove preceding and following new lines, trailing spaces. + Build.parseComment(html).trim().replace(/\s+$/gm, '') + postFromObject: (data, boardID, suppressThumb) -> o = Build.parseJSON data, boardID Build.post o, suppressThumb diff --git a/src/General/Index.coffee b/src/General/Index.coffee index b9f1476d0..bcdf4530b 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -37,20 +37,22 @@ Index = # Header "Index Navigation" submenu repliesEntry = el: UI.checkbox 'Show Replies', 'Show replies' + hoverEntry = el: UI.checkbox 'Catalog Reply Hover', 'Catalog reply hover' sortEntry = el: UI.checkbox 'Per-Board Sort Type', 'Per-board sort type', (typeof Conf['Index Sort'] is 'object') pinEntry = el: UI.checkbox 'Pin Watched Threads', 'Pin watched threads' anchorEntry = el: UI.checkbox 'Anchor Hidden Threads', 'Anchor hidden threads' refNavEntry = el: UI.checkbox 'Refreshed Navigation', 'Refreshed navigation' + hoverEntry.el.title = 'Show full replies in catalog on mouseover of excerpts.' sortEntry.el.title = 'Set the sorting order of each board independently.' pinEntry.el.title = 'Move watched threads to the start of the index.' anchorEntry.el.title = 'Move hidden threads to the end of the index.' refNavEntry.el.title = 'Refresh index when navigating through pages.' - for label in [repliesEntry, pinEntry, anchorEntry, refNavEntry] + for label in [repliesEntry, hoverEntry, pinEntry, anchorEntry, refNavEntry] input = label.el.firstChild {name} = input $.on input, 'change', $.cb.checked switch name - when 'Show Replies' + when 'Show Replies', 'Catalog Reply Hover' $.on input, 'change', @cb.replies when 'Pin Watched Threads', 'Anchor Hidden Threads' $.on input, 'change', @cb.resort @@ -60,7 +62,7 @@ Index = el: $.el 'span', textContent: 'Index Navigation' order: 100 - subEntries: [repliesEntry, sortEntry, pinEntry, anchorEntry, refNavEntry] + subEntries: [repliesEntry, hoverEntry, sortEntry, pinEntry, anchorEntry, refNavEntry] # Navigation links at top of index @navLinks = $.el 'div', className: 'navLinks json-index' @@ -582,6 +584,9 @@ Index = thread.setCount 'file', threadData.images + !!threadData.ext, threadData.imagelimit thread.setStatus 'Sticky', !!threadData.sticky thread.setStatus 'Closed', !!threadData.closed + if thread.catalogView + $.rm thread.catalogView.nodes.replies + thread.catalogView.nodes.replies = null else thread = new Thread threadData.no, g.BOARD threads.push thread @@ -608,7 +613,7 @@ Index = buildReplies: (threads) -> posts = [] for thread in threads - continue unless lastReplies = Index.liveThreadDict[thread.ID].last_replies + continue unless (lastReplies = Index.liveThreadDict[thread.ID].last_replies) nodes = [] for data in lastReplies if (post = thread.posts[data.no]) and not post.isFetchedQuote @@ -648,6 +653,40 @@ Index = thumb.style.height = height * ratio + 'px' return + buildCatalogReplies: (threads) -> + for thread in threads + {nodes} = thread.catalogView + continue unless (lastReplies = Index.liveThreadDict[thread.ID].last_replies) + + if nodes.replies + # RelativeDates will stop updating elements if they go out of document. + RelativeDates.update timeEl for timeEl in $$ 'time', nodes.replies + continue + + replies = [] + for data in lastReplies + excerpt = '' + if data.com + excerpt = Build.parseCommentDisplay(data.com).replace(/>>\d+/g, '').trim().replace(/\n+/g, ' // ') + if data.ext + excerpt or= "#{data.filename}#{data.ext}" + if data.com + excerpt or= Build.unescape data.com.replace(//gi, ' // ') + excerpt or= '\xA0' + excerpt = "#{excerpt[...70]}..." if excerpt.length > 73 + + link = Build.postURL thread.board.ID, thread.ID, data.no + reply = $.el 'div', {className: 'catalog-reply'}, + <%= html(': ${excerpt}') %> + RelativeDates.update $('time', reply) + $.on $('a', reply), 'mouseover', QuotePreview.mouseover if Conf['Catalog Reply Hover'] + replies.push reply + + nodes.replies = $.el 'div', className: 'catalog-replies' + $.add nodes.replies, replies + $.add thread.OP.nodes.post, nodes.replies + return + sort: -> {liveThreadIDs, liveThreadData} = Index return unless liveThreadData @@ -729,6 +768,7 @@ Index = buildCatalog: (threads) -> Index.buildCatalogViews threads Index.sizeCatalogViews threads + Index.buildCatalogReplies threads if Conf['Show Replies'] nodes = [] for thread in threads unless thread.nodes.placeholder diff --git a/src/Miscellaneous/RelativeDates.coffee b/src/Miscellaneous/RelativeDates.coffee index e7b1106e2..476622f88 100644 --- a/src/Miscellaneous/RelativeDates.coffee +++ b/src/Miscellaneous/RelativeDates.coffee @@ -1,5 +1,6 @@ RelativeDates = INTERVAL: $.MINUTE / 2 + init: -> if ( g.VIEW in ['index', 'thread'] and Conf['Relative Post Dates'] and !Conf['Relative Date Title'] or @@ -28,7 +29,7 @@ RelativeDates = RelativeDates.update @ # diff is milliseconds from now. - relative: (diff, now, date) -> + relative: (diff, now, date, abbrev) -> unit = if (number = (diff / $.DAY)) >= 1 years = now.getYear() - date.getYear() months = now.getMonth() - date.getMonth() @@ -57,9 +58,13 @@ RelativeDates = 'second' rounded = Math.round number - unit += 's' if rounded isnt 1 # pluralize - "#{rounded} #{unit} ago" + if abbrev + unit = if unit is 'month' then 'mo' else unit[0] + else + unit += 's' if rounded isnt 1 # pluralize + + if abbrev then "#{rounded}#{unit}" else "#{rounded} #{unit} ago" # Changing all relative dates as soon as possible incurs many annoying # redraws and scroll stuttering. Thus, sacrifice accuracy for UX/CPU economy, @@ -92,19 +97,22 @@ RelativeDates = # and re-calls `setOwnTimeout()` to re-add `data` to the stale list later. update: (data, now) -> isPost = data instanceof Post - date = if isPost - data.info.date + if isPost + date = data.info.date + abbrev = false else - new Date +data.dataset.utc + date = new Date +data.dataset.utc + abbrev = !!data.dataset.abbrev now or= new Date() diff = now - date - relative = RelativeDates.relative diff, now, date + relative = RelativeDates.relative diff, now, date, abbrev if isPost for singlePost in [data].concat data.clones singlePost.nodes.date.firstChild.textContent = relative else data.firstChild.textContent = relative RelativeDates.setOwnTimeout diff, data + setOwnTimeout: (diff, data) -> delay = if diff < $.MINUTE $.SECOND - (diff + $.SECOND / 2) % $.SECOND @@ -115,7 +123,9 @@ RelativeDates = else $.DAY - (diff + $.DAY / 2) % $.DAY setTimeout RelativeDates.markStale, delay, data + markStale: (data) -> return if data in RelativeDates.stale # We can call RelativeDates.update() multiple times. return if data instanceof Post and !g.posts[data.fullID] # collected post. + return if data instanceof Element and !doc.contains(data) # removed catalog reply. RelativeDates.stale.push data diff --git a/src/classes/CatalogThread.coffee b/src/classes/CatalogThread.coffee index 5cbe79ab6..f3e771c3e 100644 --- a/src/classes/CatalogThread.coffee +++ b/src/classes/CatalogThread.coffee @@ -6,10 +6,11 @@ class CatalogThread @board = @thread.board {post} = @thread.OP.nodes @nodes = - root: root + root: root thumb: $ '.catalog-thumb', post icons: $ '.catalog-icons', post postCount: $ '.post-count', post fileCount: $ '.file-count', post pageCount: $ '.page-count', post + replies: null @thread.catalogView = @ diff --git a/src/classes/Post.Clone.coffee b/src/classes/Post.Clone.coffee index b031d103d..6599128d3 100644 --- a/src/classes/Post.Clone.coffee +++ b/src/classes/Post.Clone.coffee @@ -31,6 +31,7 @@ Post.Clone = class extends Post # Remove catalog stuff. $.rm $('.catalog-link', @nodes.post) $.rm $('.catalog-stats', @nodes.post) + $.rm $('.catalog-replies', @nodes.post) @parseQuotes() @quotes = [@origin.quotes...] diff --git a/src/config/Config.coffee b/src/config/Config.coffee index 57641ed54..c533c473b 100644 --- a/src/config/Config.coffee +++ b/src/config/Config.coffee @@ -732,6 +732,7 @@ Config = 'Previous Index Mode': 'paged' 'Index Size': 'small' 'Show Replies': true + 'Catalog Reply Hover': true 'Pin Watched Threads': false 'Anchor Hidden Threads': true 'Refreshed Navigation': false diff --git a/src/css/burichan.css b/src/css/burichan.css index 2d1eec4f5..a028b90df 100644 --- a/src/css/burichan.css +++ b/src/css/burichan.css @@ -34,7 +34,9 @@ :root.burichan .catalog-thread > .postContainer:hover > .post { background-color: #D6DAF0; } -:root.burichan.werkTyme .catalog-thread:not(:hover), :root.burichan .catalog-thread > .postContainer:hover > .post { +:root.burichan.werkTyme .catalog-thread:not(:hover), +:root.burichan .catalog-thread > .postContainer:hover > .post, +:root.burichan .catalog-thread > .postContainer:hover .catalog-reply { border-color: #B7C5D9; } diff --git a/src/css/futaba.css b/src/css/futaba.css index 30a8b78ce..708ff66f2 100644 --- a/src/css/futaba.css +++ b/src/css/futaba.css @@ -34,7 +34,9 @@ :root.futaba .catalog-thread > .postContainer:hover > .post { background-color: #F0E0D6; } -:root.futaba.werkTyme .catalog-thread:not(:hover), :root.futaba .catalog-thread > .postContainer:hover > .post { +:root.futaba.werkTyme .catalog-thread:not(:hover), +:root.futaba .catalog-thread > .postContainer:hover > .post, +:root.futaba .catalog-thread > .postContainer:hover .catalog-reply { border-color: #D9BFB7; } diff --git a/src/css/photon.css b/src/css/photon.css index 19ce2c89f..b0ed2b9c2 100644 --- a/src/css/photon.css +++ b/src/css/photon.css @@ -34,7 +34,9 @@ :root.photon .catalog-thread > .postContainer:hover > .post { background-color: #DDD; } -:root.photon.werkTyme .catalog-thread:not(:hover), :root.photon .catalog-thread > .postContainer:hover > .post { +:root.photon.werkTyme .catalog-thread:not(:hover), +:root.photon .catalog-thread > .postContainer:hover > .post, +:root.photon .catalog-thread > .postContainer:hover .catalog-reply { border-color: #CCC; } :root.photon .catalog-code { diff --git a/src/css/style.css b/src/css/style.css index 63c58762a..2cfb81840 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -760,7 +760,7 @@ div.catalog-thread { margin-left: -61px; margin-right: -61px; } -.catalog-thread > .postContainer:hover > .post > * { +.catalog-thread > .postContainer:hover > .post > :not(.catalog-replies) { padding-left: 2px; padding-right: 2px; } @@ -811,7 +811,7 @@ div.catalog-thread { } .catalog-thread .postMessage { margin: 0; - padding-bottom: 2px; + padding-bottom: .3em; } #delform .catalog-thread > .postContainer:not(:hover) .file, #delform .catalog-thread > .postContainer:not(:hover) .postInfo > :not(.subject), @@ -821,7 +821,9 @@ div.catalog-thread { #delform .catalog-thread .postInfo > :not(.subject):not(.nameBlock):not(.dateTime), #delform .catalog-thread .posteruid, .thread:not(.catalog-thread) .catalog-link, -.thread:not(.catalog-thread) .catalog-stats { +.thread:not(.catalog-thread) .catalog-stats, +.thread:not(.catalog-thread) .catalog-replies, +.catalog-thread > .postContainer:not(:hover) .catalog-replies { display: none; } .catalog-thread .file { @@ -860,6 +862,24 @@ div.catalog-thread { .catalog-thread > .postContainer:hover .postMessage:not(:empty) { padding-top: .3em; } +.catalog-reply { + text-align: left; + margin: -1px; + border: 1px solid transparent; +} +.catalog-reply > span { + font-style: italic; + font-weight: bold; + float: left; + padding: 3px; +} +.catalog-reply > a { + display: block; + padding: 3px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} .catalog-thread .prettyprinted { max-width: 100%; box-sizing: border-box; diff --git a/src/css/tomorrow.css b/src/css/tomorrow.css index 1bd661125..ce8f17b52 100644 --- a/src/css/tomorrow.css +++ b/src/css/tomorrow.css @@ -33,7 +33,9 @@ :root.tomorrow .catalog-thread > .postContainer:hover > .post { background-color: #282A2E; } -:root.tomorrow.werkTyme .catalog-thread:not(:hover), :root.tomorrow .catalog-thread > .postContainer:hover > .post { +:root.tomorrow.werkTyme .catalog-thread:not(:hover), +:root.tomorrow .catalog-thread > .postContainer:hover > .post, +:root.tomorrow .catalog-thread > .postContainer:hover .catalog-reply { border-color: #111; } :root.tomorrow .catalog-code { diff --git a/src/css/yotsuba-b.css b/src/css/yotsuba-b.css index 689a271ac..c7db08fd8 100644 --- a/src/css/yotsuba-b.css +++ b/src/css/yotsuba-b.css @@ -34,7 +34,9 @@ :root.yotsuba-b .catalog-thread > .postContainer:hover > .post { background-color: #D6DAF0; } -:root.yotsuba-b.werkTyme .catalog-thread:not(:hover), :root.yotsuba-b .catalog-thread > .postContainer:hover > .post { +:root.yotsuba-b.werkTyme .catalog-thread:not(:hover), +:root.yotsuba-b .catalog-thread > .postContainer:hover > .post, +:root.yotsuba-b .catalog-thread > .postContainer:hover .catalog-reply { border-color: #B7C5D9; } diff --git a/src/css/yotsuba.css b/src/css/yotsuba.css index 8a5b3fe5d..b2cf55fe0 100644 --- a/src/css/yotsuba.css +++ b/src/css/yotsuba.css @@ -34,7 +34,9 @@ :root.yotsuba .catalog-thread > .postContainer:hover > .post { background-color: #F0E0D6; } -:root.yotsuba.werkTyme .catalog-thread:not(:hover), :root.yotsuba .catalog-thread > .postContainer:hover > .post { +:root.yotsuba.werkTyme .catalog-thread:not(:hover), +:root.yotsuba .catalog-thread > .postContainer:hover > .post, +:root.yotsuba .catalog-thread > .postContainer:hover .catalog-reply { border-color: #D9BFB7; }