Merge pull request #1457 from MayhemYDG/hide

Hiding enhancements
This commit is contained in:
Mayhem 2014-02-22 02:31:06 +01:00
commit d9a1b2844e
22 changed files with 384 additions and 460 deletions

View File

@ -1,3 +1,13 @@
- Thread and post hiding changes:
- The posts' menu now has a label entry listing the reasons why a post got hidden or highlighted.
- `Thread Hiding` and `Reply Hiding` settings are merged into one: `Post Hiding`.
- `Thread Hiding Link` and `Reply Hiding Link` settings are merged into one: `Post Hiding Link`.
- Hiding a thread removes it from the index in `Paged` or `All threads` modes.
<ul>
<li> Hidden threads can be seen by clicking the `[Show]` button the the top of the index.
<li> The `Anchor Hidden Threads` setting has been removed.
</ul>
### 3.18.1 - *2014-02-20*
- Fix the QR breaking after a change with 4chan.

View File

@ -368,7 +368,6 @@ a[href="javascript:;"] {
:root.index-loading .navLinks,
:root.index-loading .board,
:root.index-loading .pagelist,
:root:not(.catalog-mode) #hidden-toggle,
:root:not(.catalog-mode) #index-size {
display: none;
}
@ -706,14 +705,16 @@ a.hide-announcement {
border: 2px solid rgba(255, 0, 0, .5);
}
/* Thread & Reply Hiding */
.hide-thread-button,
.hide-reply-button {
float: left;
margin-right: 2px;
/* Post Hiding */
.hide-post-button,
.show-post-button {
font-size: 14px;
line-height: 12px; /* Prevent the floating effect from affecting the thumbnail too */
}
.stub ~ * {
display: none !important;
.opContainer > .show-post-button,
.hide-post-button {
float: left;
margin-right: 3px;
}
.stub input {
display: inline-block;

View File

@ -57,7 +57,18 @@ Filter =
top = filter.match(/top:(yes|no)/)?[1] or 'yes'
top = top is 'yes' # Turn it into a boolean
@filters[key].push @createFilter regexp, op, stub, hl, top
@filters[key].push {
hide: !hl
op: op
stub: stub
class: hl
top: top
match: regexp
test: if typeof regexp is 'string'
Filter.stringTest # MD5 checking
else
Filter.regexpTest
}
# Only execute filter types that contain valid filters.
unless @filters[key].length
@ -68,25 +79,6 @@ Filter =
name: 'Filter'
cb: @node
createFilter: (regexp, op, stub, hl, top) ->
test =
if typeof regexp is 'string'
# MD5 checking
(value) -> regexp is value
else
(value) -> regexp.test value
settings =
hide: !hl
stub: stub
class: hl
top: top
(value, isReply) ->
if isReply and op is 'only' or !isReply and op is 'no'
return false
unless test value
return false
settings
node: ->
return if @isClone
for key of Filter.filters
@ -94,27 +86,29 @@ Filter =
# Continue if there's nothing to filter (no tripcode for example).
continue if value is false
for filter in Filter.filters[key]
unless result = filter value, @isReply
for obj in Filter.filters[key]
unless Filter.test obj, value, @isReply
continue
# Hide
if result.hide
if @isReply
PostHiding.hide @, result.stub
else if g.VIEW is 'index'
ThreadHiding.hide @thread, result.stub
else
continue
if obj.hide
continue unless @isReply or g.VIEW is 'index'
@hide "Hidden by filtering the #{key}: #{obj.match}", obj.stub
return
# 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
@highlight "Highlighted by filtering the #{key}: #{obj.match}", obj.class, obj.top
stringTest: (string, value) ->
string is value
regexpTest: (regexp, value) ->
regexp.test value
test: ({test, match, op}, value, isReply) ->
if isReply and op is 'only' or !isReply and op is 'no'
return false
unless test match, value
return false
true
name: (post) ->
if 'name' of post.info
return post.info.name

View File

@ -1,186 +1,184 @@
PostHiding =
init: ->
return if !Conf['Reply Hiding'] and !Conf['Reply Hiding Link']
@db = new DataBoard 'hiddenPosts'
@hideButton = $.el 'a',
className: 'hide-post-button'
innerHTML: '<i class="fa fa-minus-square-o"></i>'
href: 'javascript:;'
@showButton = $.el 'a',
className: 'show-post-button'
innerHTML: '<i class="fa fa-plus-square-o"></i>'
href: 'javascript:;'
Post.callbacks.push
name: 'Reply Hiding'
name: 'Post Hiding'
cb: @node
# XXX tmp conversion
$.get 'hiddenThreads', null, ({hiddenThreads}) ->
return unless hiddenThreads
for boardID, board of hiddenThreads.boards
for threadID, val of board
((PostHiding.db.data.boards[boardID] or= {})[threadID] or= {})[threadID] = val
PostHiding.db.save()
$.delete 'hiddenThreads'
node: ->
return if !@isReply or @isClone
return if !@isReply and g.VIEW isnt 'index' or @isClone
if data = PostHiding.db.get {boardID: @board.ID, threadID: @thread.ID, postID: @ID}
if data.thisPost
PostHiding.hide @, data.makeStub, data.hideRecursively
if data.thisPost is false
label = "Recursively hidden for quoting No.#{@}"
Recursive.apply 'hide', @, label, data.makeStub, true
Recursive.add 'hide', @, label, data.makeStub, true
else
Recursive.apply PostHiding.hide, @, data.makeStub, true
Recursive.add PostHiding.hide, @, data.makeStub, true
return unless Conf['Reply Hiding']
$.replace $('.sideArrows', @nodes.root), PostHiding.makeButton @, 'hide'
@hide 'Manually hidden', data.makeStub, data.hideRecursively
menu:
init: ->
return if !Conf['Menu'] or !Conf['Reply Hiding Link']
return unless Conf['Post Hiding']
if @isReply
a = PostHiding.makeButton true
a.hidden = true if @isHidden
$.replace $('.sideArrows', @nodes.root), a
else
$.prepend @nodes.root, PostHiding.makeButton !@isHidden
# Hide
div = $.el 'div',
className: 'hide-reply-link'
textContent: 'Hide reply'
apply = $.el 'a',
textContent: 'Apply'
href: 'javascript:;'
$.on apply, 'click', PostHiding.menu.hide
thisPost = $.el 'label',
innerHTML: '<input type=checkbox name=thisPost checked> This post'
replies = $.el 'label',
innerHTML: "<input type=checkbox name=replies checked=#{Conf['Recursive Hiding']}> Hide replies"
makeStub = $.el 'label',
innerHTML: "<input type=checkbox name=makeStub checked=#{Conf['Stubs']}> Make stub"
$.event 'AddMenuEntry',
type: 'post'
el: div
order: 20
open: (post) ->
if !post.isReply or post.isClone or post.isHidden
return false
PostHiding.menu.post = post
true
subEntries: [{el: apply}, {el: thisPost}, {el: replies}, {el: makeStub}]
# Show
div = $.el 'div',
className: 'show-reply-link'
textContent: 'Show reply'
apply = $.el 'a',
textContent: 'Apply'
href: 'javascript:;'
$.on apply, 'click', PostHiding.menu.show
thisPost = $.el 'label',
innerHTML: '<input type=checkbox name=thisPost> This post'
replies = $.el 'label',
innerHTML: "<input type=checkbox name=replies> Show replies"
$.event 'AddMenuEntry',
type: 'post'
el: div
order: 20
open: (post) ->
if !post.isReply or post.isClone or !post.isHidden
return false
unless data = PostHiding.db.get {boardID: post.board.ID, threadID: post.thread.ID, postID: post.ID}
return false
PostHiding.menu.post = post
thisPost.firstChild.checked = post.isHidden
replies.firstChild.checked = if data?.hideRecursively? then data.hideRecursively else Conf['Recursive Hiding']
true
subEntries: [{el: apply}, {el: thisPost}, {el: replies}]
hide: ->
parent = @parentNode
thisPost = $('input[name=thisPost]', parent).checked
replies = $('input[name=replies]', parent).checked
makeStub = $('input[name=makeStub]', parent).checked
{post} = PostHiding.menu
if thisPost
PostHiding.hide post, makeStub, replies
else if replies
Recursive.apply PostHiding.hide, post, makeStub, true
Recursive.add PostHiding.hide, post, makeStub, true
else
return
PostHiding.saveHiddenState post, true, thisPost, makeStub, replies
$.event 'CloseMenu'
show: ->
parent = @parentNode
thisPost = $('input[name=thisPost]', parent).checked
replies = $('input[name=replies]', parent).checked
{post} = PostHiding.menu
if thisPost
PostHiding.show post, replies
else if replies
Recursive.apply PostHiding.show, post, true
Recursive.rm PostHiding.hide, post, true
else
return
if data = PostHiding.db.get {boardID: post.board.ID, threadID: post.thread.ID, postID: post.ID}
PostHiding.saveHiddenState post, !(thisPost and replies), !thisPost, data.makeStub, !replies
$.event 'CloseMenu'
makeButton: (post, type) ->
span = $.el 'span',
textContent: "[\u00A0#{if type is 'hide' then '-' else '+'}\u00A0]"
a = $.el 'a',
className: "#{type}-reply-button"
href: 'javascript:;'
$.add a, span
$.on a, 'click', PostHiding.toggle
makeButton: (hide) ->
a = (if hide then PostHiding.hideButton else PostHiding.showButton).cloneNode true
$.on a, 'click', PostHiding.onToggleClick
a
saveHiddenState: (post, isHiding, thisPost, makeStub, hideRecursively) ->
onToggleClick: ->
PostHiding.toggle if $.x 'ancestor::div[contains(@class,"postContainer")][1]', @
Get.postFromNode @
else
Get.threadFromNode(@).OP
toggle: (post) ->
if post.isHidden
post.show()
else
post.hide 'Manually hidden'
PostHiding.saveHiddenState post
return if post.isReply
Index.updateHideLabel()
Index.sort()
Index.buildIndex()
saveHiddenState: (post, val) ->
data =
boardID: post.board.ID
threadID: post.thread.ID
postID: post.ID
if isHiding
data.val =
thisPost: thisPost isnt false # undefined -> true
makeStub: makeStub
hideRecursively: hideRecursively
if post.isHidden or val and !val.thisPost
data.val = val or {}
PostHiding.db.set data
else
else if PostHiding.db.get data # unhiding a filtered post f.e.
PostHiding.db.delete data
toggle: ->
post = Get.postFromNode @
if post.isHidden
PostHiding.show post
else
PostHiding.hide post
PostHiding.saveHiddenState post, post.isHidden
menu:
init: ->
return if !Conf['Menu'] or !Conf['Post Hiding Link']
hide: (post, makeStub=Conf['Stubs'], hideRecursively=Conf['Recursive Hiding']) ->
return if post.isHidden
post.isHidden = true
# Hide
apply =
el: $.el 'a', textContent: 'Apply', href: 'javascript:;'
open: (post) ->
$.off @el, 'click', @cb if @cb
@cb = -> PostHiding.menu.hide post
$.on @el, 'click', @cb
true
thisPost =
el: $.el 'label', innerHTML: '<input type=checkbox name=thisPost checked> This post'
replies =
el: $.el 'label', innerHTML: "<input type=checkbox name=replies checked=#{Conf['Recursive Hiding']}> Hide replies"
makeStub =
el: $.el 'label', innerHTML: "<input type=checkbox name=makeStub checked=#{Conf['Stubs']}> Make stub"
if hideRecursively
Recursive.apply PostHiding.hide, post, makeStub, true
Recursive.add PostHiding.hide, post, makeStub, true
$.event 'AddMenuEntry',
type: 'post'
el: $.el 'div',
textContent: 'Hide post'
className: 'hide-post-link'
order: 20
open: (post) -> !(post.isHidden or !post.isReply or post.isClone)
subEntries: [apply, thisPost, replies, makeStub]
for quotelink in Get.allQuotelinksLinkingTo post
$.addClass quotelink, 'filtered'
# Show
apply =
el: $.el 'a', textContent: 'Apply', href: 'javascript:;'
open: (post) ->
$.off @el, 'click', @cb if @cb
@cb = -> PostHiding.menu.show post
$.on @el, 'click', @cb
true
thisPost =
el: $.el 'label', innerHTML: '<input type=checkbox name=thisPost> This post'
open: (post) ->
@el.firstChild.checked = post.isHidden
true
replies =
el: $.el 'label', innerHTML: '<input type=checkbox name=replies> Unhide replies'
open: (post) ->
data = PostHiding.db.get {boardID: post.board.ID, threadID: post.thread.ID, postID: post.ID}
@el.firstChild.checked = if 'hideRecursively' of data then data.hideRecursively else Conf['Recursive Hiding']
true
unless makeStub
post.nodes.root.hidden = true
return
$.event 'AddMenuEntry',
type: 'post'
el: $.el 'div',
textContent: 'Unhide post'
className: 'show-post-link'
order: 20
open: (post) ->
if !post.isHidden or !post.isReply or post.isClone
return false
unless PostHiding.db.get {boardID: post.board.ID, threadID: post.thread.ID, postID: post.ID}
return false
true
subEntries: [apply, thisPost, replies]
a = PostHiding.makeButton post, 'show'
postInfo =
if Conf['Anonymize']
'Anonymous'
return if g.VIEW isnt 'index'
$.event 'AddMenuEntry',
type: 'post'
el: $.el 'a', href: 'javascript:;'
order: 20
open: (post) ->
@el.textContent = if post.isHidden
'Unhide thread'
else
'Hide thread'
$.off @el, 'click', @cb if @cb
@cb = ->
$.event 'CloseMenu'
PostHiding.toggle post
$.on @el, 'click', @cb
true
hide: (post) ->
parent = @parentNode
thisPost = $('input[name=thisPost]', parent).checked
replies = $('input[name=replies]', parent).checked
makeStub = $('input[name=makeStub]', parent).checked
label = 'Manually hidden'
if thisPost
post.hide label, makeStub, replies
else if replies
Recursive.apply 'hide', post, label, makeStub, true
Recursive.add 'hide', post, label, makeStub, true
else
$('.nameBlock', post.nodes.info).textContent
$.add a, $.tn " #{postInfo}"
post.nodes.stub = $.el 'div',
className: 'stub'
$.add post.nodes.stub, a
if Conf['Menu']
$.add post.nodes.stub, Menu.makeButton()
$.prepend post.nodes.root, post.nodes.stub
show: (post, showRecursively=Conf['Recursive Hiding']) ->
if post.nodes.stub
$.rm post.nodes.stub
delete post.nodes.stub
else
post.nodes.root.hidden = false
post.isHidden = false
if showRecursively
Recursive.apply PostHiding.show, post, true
Recursive.rm PostHiding.hide, post
for quotelink in Get.allQuotelinksLinkingTo post
$.rmClass quotelink, 'filtered'
return
return
PostHiding.saveHiddenState post, {thisPost, hideRecursively: replies, makeStub}
$.event 'CloseMenu'
show: (post) ->
parent = @parentNode
thisPost = $('input[name=thisPost]', parent).checked
replies = $('input[name=replies]', parent).checked
if thisPost
post.show replies
else if replies
Recursive.apply 'show', post, true
Recursive.rm 'hide', post, true
else
return
val = {thisPost: !thisPost, hideRecursively: !replies, makeStub: !!post.nodes.stub}
PostHiding.saveHiddenState post, val
$.event 'CloseMenu'

View File

@ -7,10 +7,9 @@ Recursive =
node: ->
return if @isClone
for quote in @quotes
if obj = Recursive.recursives[quote]
for recursive, i in obj.recursives
recursive @, obj.args[i]...
for quote in @quotes when obj = Recursive.recursives[quote]
for recursive, i in obj.recursives
@[recursive] obj.args[i]...
return
add: (recursive, post, args...) ->
@ -22,15 +21,13 @@ Recursive =
rm: (recursive, post) ->
return unless obj = Recursive.recursives[post.fullID]
for rec, i in obj.recursives
if rec is recursive
obj.recursives.splice i, 1
obj.args.splice i, 1
for rec, i in obj.recursives when rec is recursive
obj.recursives.splice i, 1
obj.args.splice i, 1
return
apply: (recursive, post, args...) ->
{fullID} = post
for ID, post of g.posts
if fullID in post.quotes
recursive post, args...
for ID, post of g.posts when fullID in post.quotes
post[recursive] args...
return

View File

@ -1,126 +0,0 @@
ThreadHiding =
init: ->
return if g.VIEW isnt 'index'
@db = new DataBoard 'hiddenThreads'
$.on d, 'IndexRefresh', @onIndexRefresh
Thread.callbacks.push
name: 'Thread Hiding'
cb: @node
node: ->
if data = ThreadHiding.db.get {boardID: @board.ID, threadID: @ID}
ThreadHiding.hide @, data.makeStub
return unless Conf['Thread Hiding']
$.prepend @OP.nodes.root, ThreadHiding.makeButton @, 'hide'
onIndexRefresh: ->
for root, i in Index.nodes by 2
thread = Get.threadFromRoot root
continue unless thread.isHidden
unless thread.stub
Index.nodes[i + 1].hidden = true
else unless root.contains thread.stub
# When we come back to a page, the stub is already there.
ThreadHiding.makeStub thread, root
return
menu:
init: ->
return if g.VIEW isnt 'index' or !Conf['Menu'] or !Conf['Thread Hiding Link']
div = $.el 'div',
className: 'hide-thread-link'
textContent: 'Hide thread'
apply = $.el 'a',
textContent: 'Apply'
href: 'javascript:;'
$.on apply, 'click', ThreadHiding.menu.hide
makeStub = $.el 'label',
innerHTML: "<input type=checkbox checked=#{Conf['Stubs']}> Make stub"
$.event 'AddMenuEntry',
type: 'post'
el: div
order: 20
open: ({thread, isReply}) ->
if isReply or thread.isHidden or Conf['Index Mode'] is 'catalog'
return false
ThreadHiding.menu.thread = thread
true
subEntries: [el: apply; el: makeStub]
hide: ->
makeStub = $('input', @parentNode).checked
{thread} = ThreadHiding.menu
ThreadHiding.hide thread, makeStub
ThreadHiding.saveHiddenState thread, makeStub
$.event 'CloseMenu'
makeButton: (thread, type) ->
a = $.el 'a',
className: "#{type}-thread-button"
innerHTML: "<span>[&nbsp;#{if type is 'hide' then '-' else '+'}&nbsp;]</span>"
href: 'javascript:;'
a.dataset.fullID = thread.fullID
$.on a, 'click', ThreadHiding.toggle
a
makeStub: (thread, root) ->
numReplies = $$('.thread > .replyContainer', root).length
numReplies += +summary.textContent.match /\d+/ if summary = $ '.summary', root
opInfo = if Conf['Anonymize']
'Anonymous'
else
$('.nameBlock', thread.OP.nodes.info).textContent
a = ThreadHiding.makeButton thread, 'show'
$.add a, $.tn " #{opInfo} (#{if numReplies is 1 then '1 reply' else "#{numReplies} replies"})"
thread.stub = $.el 'div',
className: 'stub'
if Conf['Menu']
$.add thread.stub, [a, Menu.makeButton()]
else
$.add thread.stub, a
$.prepend root, thread.stub
saveHiddenState: (thread, makeStub) ->
if thread.isHidden
ThreadHiding.db.set
boardID: thread.board.ID
threadID: thread.ID
val: {makeStub}
else
ThreadHiding.db.delete
boardID: thread.board.ID
threadID: thread.ID
toggle: (thread) ->
unless thread instanceof Thread
thread = g.threads[@dataset.fullID]
if thread.isHidden
ThreadHiding.show thread
else
ThreadHiding.hide thread
ThreadHiding.saveHiddenState thread
hide: (thread, makeStub=Conf['Stubs']) ->
return if thread.isHidden
threadRoot = thread.OP.nodes.root.parentNode
thread.isHidden = true
Index.updateHideLabel()
unless makeStub
threadRoot.hidden = threadRoot.nextElementSibling.hidden = true # <hr>
return
ThreadHiding.makeStub thread, threadRoot
show: (thread) ->
if thread.stub
$.rm thread.stub
delete thread.stub
threadRoot = thread.OP.nodes.root.parentNode
threadRoot.nextElementSibling.hidden =
threadRoot.hidden = thread.isHidden = false
Index.updateHideLabel()

View File

@ -244,20 +244,20 @@ Build =
if (OP = board.posts[data.no]) and root = OP.nodes.root.parentNode
$.rmAll root
$.add root, OP.nodes.root
else
root = $.el 'div',
className: 'thread'
id: "t#{data.no}"
$.add root, 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
[posts, files] = if Conf['Show Replies']
[data.omitted_posts, data.omitted_images]
else
[data.replies, data.images]
nodes.push Build.summary board.ID, data.no, posts, files
$.add root, Build.summary board.ID, data.no, posts, files
$.add root, nodes
root
catalogThread: (thread) ->
{staticPath, gifIcon} = Build
@ -279,7 +279,7 @@ Build =
root.dataset.fullID = thread.fullID
$.addClass root, 'pinned' if thread.isPinned
$.addClass root, thread.OP.highlights... if thread.OP.highlights
$.addClass root, thread.OP.highlights... if thread.OP.highlights.length
thumb = root.firstElementChild
if data.spoiler and !Conf['Reveal Spoilers']

View File

@ -17,10 +17,9 @@ Config =
'Filtering':
'Anonymize': [false, 'Make everyone Anonymous.']
'Filter': [true, 'Self-moderation placebo.']
'Post Hiding': [true, 'Add buttons to hide threads and replies.']
'Stubs': [true, 'Show stubs of hidden posts.']
'Recursive Hiding': [true, 'Hide replies of hidden posts, recursively.']
'Thread Hiding': [true, 'Add buttons to hide entire threads.']
'Reply Hiding': [true, 'Add buttons to hide single replies.']
'Stubs': [true, 'Show stubs of hidden threads / replies.']
'Images':
'Auto-GIF': [false, 'Animate GIF thumbnails (disabled on /gif/, /wsg/).']
'Image Expansion': [true, 'Expand images inline.']
@ -31,8 +30,7 @@ Config =
'Menu':
'Menu': [true, 'Add a drop-down menu to posts.']
'Report Link': [true, 'Add a report link to the menu.']
'Thread Hiding Link': [true, 'Add a link to hide entire threads.']
'Reply Hiding Link': [true, 'Add a link to hide single replies.']
'Post Hiding Link': [true, 'Add a link to hide threads and replies.']
'Delete Link': [true, 'Add post and image deletion links to the menu.']
<% if (type === 'crx') { %>
'Download Link': [true, 'Add a download with original filename link to the menu.']
@ -143,7 +141,6 @@ Config =
'Threads per Page': 0
'Open threads in a new tab': false
'Show Replies': true
'Anchor Hidden Threads': true
'Refreshed Navigation': false
Header:
'Header auto-hide': false

View File

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

View File

@ -3,8 +3,7 @@ Get =
{OP} = thread
excerpt = OP.info.subject?.trim() or
OP.info.comment.replace(/\n+/g, ' // ') or
Conf['Anonymize'] and 'Anonymous' or
$('.nameBlock', OP.nodes.info).textContent.trim()
OP.getNameBlock()
if excerpt.length > 70
excerpt = "#{excerpt[...67]}..."
"/#{thread.board}/ - #{excerpt}"
@ -233,6 +232,6 @@ Get =
thread = g.threads["#{boardID}.#{threadID}"] or
new Thread threadID, board
post = new Post Build.post(o, true), thread, board, {isArchived: true}
$('.page-num', post.nodes.info).hidden = true
$('.page-num', post.nodes.info)?.hidden = true
Main.callbackNodes Post, [post]
Get.insert post, root, context

View File

@ -38,15 +38,11 @@ Index =
repliesEntry =
el: $.el 'label',
innerHTML: '<input type=checkbox name="Show Replies"> Show replies'
anchorEntry =
el: $.el 'label',
innerHTML: '<input type=checkbox name="Anchor Hidden Threads"> Anchor hidden threads'
title: 'Move hidden threads at the end of the index.'
refNavEntry =
el: $.el 'label',
innerHTML: '<input type=checkbox name="Refreshed Navigation"> Refreshed navigation'
title: 'Refresh index when navigating through pages.'
for label in [targetEntry, repliesEntry, anchorEntry, refNavEntry]
for label in [targetEntry, repliesEntry, refNavEntry]
input = label.el.firstChild
{name} = input
input.checked = Conf[name]
@ -56,15 +52,13 @@ Index =
$.on input, 'change', @cb.target
when 'Show Replies'
$.on input, 'change', @cb.replies
when 'Anchor Hidden Threads'
$.on input, 'change', @cb.sort
$.event 'AddMenuEntry',
type: 'header'
el: $.el 'span',
textContent: 'Index Navigation'
order: 90
subEntries: [threadNumEntry, targetEntry, repliesEntry, anchorEntry, refNavEntry]
subEntries: [threadNumEntry, targetEntry, repliesEntry, refNavEntry]
$.addClass doc, 'index-loading'
@update()
@ -124,24 +118,7 @@ Index =
$.event 'AddMenuEntry',
type: 'post'
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
$.event 'AddMenuEntry',
type: 'post'
el: $.el 'a', href: 'javascript:;'
order: 6
order: 19
open: ({thread}) ->
return false if Conf['Index Mode'] isnt 'catalog'
@el.textContent = if thread.isPinned
@ -166,7 +143,7 @@ Index =
return if e.button isnt 0
thread = g.threads[@parentNode.dataset.fullID]
if e.shiftKey
Index.toggleHide thread
PostHiding.toggle thread.OP
else if e.altKey
Index.togglePin thread
else
@ -194,15 +171,6 @@ Index =
offsetX: 15
offsetY: -20
setTimeout (-> el.hidden = false if el.parentNode), .25 * $.SECOND
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
@ -259,7 +227,10 @@ Index =
else
'Show'
Index.sort()
Index.buildIndex()
if Conf['Index Mode'] is 'paged' and Index.getCurrentPage() > 0
Index.pageNav 0
else
Index.buildIndex()
mode: (e) ->
Index.cb.toggleCatalogMode()
Index.togglePagelist()
@ -289,7 +260,6 @@ Index =
Index.buildIndex() if e
threadsNum: ->
return unless Conf['Index Mode'] is 'paged'
Index.buildPagelist()
Index.buildIndex()
target: ->
for threadID, thread of g.BOARD.threads when thread.catalogView
@ -367,7 +337,6 @@ Index =
Index.currentPage = pageNum
return if Conf['Index Mode'] isnt 'paged'
Index.buildIndex()
Index.setPage()
Index.scrollToIndex()
getThreadsNumPerPage: ->
@ -376,11 +345,7 @@ Index =
else
Index.threadsNumPerPage
getPagesNum: ->
numThreads = if Index.isSearching
Index.sortedNodes.length / 2
else
Index.liveThreadIDs.length
Math.ceil numThreads / Index.getThreadsNumPerPage()
Math.ceil Index.sortedThreads.length / Index.getThreadsNumPerPage()
getMaxPageNum: ->
Math.max 0, Index.getPagesNum() - 1
togglePagelist: ->
@ -430,7 +395,7 @@ Index =
Index.cb.toggleHiddenThreads() if Index.showHiddenThreads
return
Index.hideLabel.hidden = false
$('#hidden-count', Index.navLinks).textContent = if hiddenCount is 1
$('#hidden-count', Index.hideLabel).textContent = if hiddenCount is 1
'1 hidden thread'
else
"#{hiddenCount} hidden threads"
@ -508,12 +473,10 @@ Index =
Index.parseThreadList pages
Index.buildThreads()
Index.sort()
Index.buildPagelist()
if pageNum?
Index.pageNav pageNum
return
Index.buildIndex()
Index.setPage()
parseThreadList: (pages) ->
Index.threadsNumPerPage = pages[0].threads.length
Index.liveThreadData = pages.reduce ((arr, next) -> arr.concat next.threads), []
@ -527,7 +490,7 @@ Index =
posts = []
for threadData, i in Index.liveThreadData
threadRoot = Build.thread g.BOARD, threadData
Index.nodes.push threadRoot, $.el 'hr'
Index.nodes.push threadRoot
if thread = g.BOARD.threads[threadData.no]
thread.setPage i // Index.threadsNumPerPage
thread.setCount 'post', threadData.replies + 1, threadData.bumplimit
@ -548,16 +511,18 @@ Index =
error: err
Main.handleErrors errors if errors
# Add the threads and <hr>s in a container to make sure all features work.
$.nodes Index.nodes
Main.callbackNodes Thread, threads
Main.callbackNodes Post, posts
Index.updateHideLabel()
$.event 'IndexRefresh'
buildReplies: (threadRoots) ->
buildHRs: (threadRoots) ->
for i in [0...threadRoots.length] by 1
threadRoots.splice (i * 2) + 1, 0, $.el 'hr'
return
buildReplies: (threads) ->
return unless Conf['Show Replies']
posts = []
for threadRoot in threadRoots by 2
thread = Get.threadFromRoot threadRoot
for thread in threads
i = Index.liveThreadIDs.indexOf thread.ID
continue unless lastReplies = Index.liveThreadData[i].last_replies
nodes = []
@ -574,20 +539,16 @@ Index =
errors.push
message: "Parsing of Post No.#{data.no} failed. Post will be skipped."
error: err
$.add threadRoot, nodes
$.add thread.OP.nodes.root.parentNode, nodes
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 isnt Index.showHiddenThreads
catalogThreads = []
for thread in threads when !thread.catalogView
for thread in Index.sortedThreads when !thread.catalogView
catalogThreads.push new CatalogThread Build.catalogThread(thread), thread
Main.callbackNodes CatalogThread, catalogThreads
threads.map (thread) -> thread.catalogView.nodes.root
Index.sortedThreads.map (thread) -> thread.catalogView.nodes.root
sizeCatalogViews: (nodes) ->
# XXX When browsers support CSS3 attr(), use it instead.
size = if Conf['Index Size'] is 'small' then 150 else 250
@ -615,36 +576,43 @@ Index =
sortedThreadIDs = [Index.liveThreadData...].sort((a, b) -> b.replies - a.replies).map (data) -> data.no
when 'filecount'
sortedThreadIDs = [Index.liveThreadData...].sort((a, b) -> b.images - a.images).map (data) -> data.no
Index.sortedNodes = []
for threadID in sortedThreadIDs
i = Index.liveThreadIDs.indexOf(threadID) * 2
Index.sortedNodes.push Index.nodes[i], Index.nodes[i + 1]
Index.sortedThreads = sortedThreadIDs
.map (threadID) -> Get.threadFromRoot Index.nodes[Index.liveThreadIDs.indexOf threadID]
.filter (thread) -> thread.isHidden is Index.showHiddenThreads
if Index.isSearching
Index.sortedNodes = Index.querySearch(Index.searchInput.value) or Index.sortedNodes
Index.sortedThreads = Index.querySearch(Index.searchInput.value) or Index.sortedThreads
# Sticky threads
Index.sortOnTop (thread) -> thread.isSticky
# Highlighted threads
Index.sortOnTop (thread) -> thread.isOnTop or thread.isPinned
# Non-hidden threads
Index.sortOnTop((thread) -> !thread.isHidden) if Conf['Anchor Hidden Threads']
sortOnTop: (match) ->
offset = 0
for threadRoot, i in Index.sortedNodes by 2 when match Get.threadFromRoot threadRoot
Index.sortedNodes.splice offset++ * 2, 0, Index.sortedNodes.splice(i, 2)...
for thread, i in Index.sortedThreads when match thread
Index.sortedThreads.splice offset++, 0, Index.sortedThreads.splice(i, 1)[0]
return
buildIndex: ->
switch Conf['Index Mode']
when 'paged'
pageNum = Index.getCurrentPage()
nodesPerPage = Index.getThreadsNumPerPage() * 2
nodes = Index.sortedNodes[nodesPerPage * pageNum ... nodesPerPage * (pageNum + 1)]
if pageNum > Index.getMaxPageNum()
# Go to the last available page if we were past the limit.
Index.pageNav Index.getMaxPageNum()
return
threadsPerPage = Index.getThreadsNumPerPage()
threads = Index.sortedThreads[threadsPerPage * pageNum ... threadsPerPage * (pageNum + 1)]
nodes = threads.map (thread) -> thread.OP.nodes.root.parentNode
Index.buildReplies threads
Index.buildHRs nodes
Index.buildPagelist()
Index.setPage()
when 'catalog'
nodes = Index.buildCatalogViews()
Index.sizeCatalogViews nodes
else
nodes = Index.sortedNodes
nodes = Index.sortedThreads.map (thread) -> thread.OP.nodes.root.parentNode
Index.buildReplies Index.sortedThreads
Index.buildHRs nodes
$.rmAll Index.root
Index.buildReplies nodes if Conf['Show Replies'] and Conf['Index Mode'] isnt 'catalog'
$.add Index.root, nodes
$.event 'IndexBuild', nodes
@ -672,23 +640,17 @@ Index =
delete Index.searchInput.dataset.searching
<% } %>
Index.sort()
# Go to the last available page if we were past the limit.
pageNum = Math.min pageNum, Index.getMaxPageNum() if Conf['Index Mode'] is 'paged'
Index.buildPagelist()
if Index.currentPage is pageNum
Index.buildIndex()
Index.setPage()
else
if Conf['Index Mode'] is 'paged' and Index.currentPage isnt Math.min pageNum, Index.getMaxPageNum()
# Go to the last available page if we were past the limit.
Index.pageNav pageNum
else
Index.buildIndex()
querySearch: (query) ->
return unless keywords = query.toLowerCase().match /\S+/g
Index.search keywords
search: (keywords) ->
found = []
for threadRoot, i in Index.sortedNodes by 2
if Index.searchMatch Get.threadFromRoot(threadRoot), keywords
found.push Index.sortedNodes[i], Index.sortedNodes[i + 1]
found
Index.sortedThreads.filter (thread) ->
Index.searchMatch thread, keywords
searchMatch: (thread, keywords) ->
{info, file} = thread.OP
text = []

View File

@ -79,19 +79,18 @@ Main =
initFeature 'Redirect', Redirect
initFeature 'Resurrect Quotes', Quotify
initFeature 'Filter', Filter
initFeature 'Thread Hiding', ThreadHiding
initFeature 'Reply Hiding', PostHiding
initFeature 'Post Hiding', PostHiding
initFeature 'Recursive', Recursive
initFeature 'Strike-through Quotes', QuoteStrikeThrough
initFeature 'Quick Reply', QR
initFeature 'Menu', Menu
initFeature 'Index Generator (Menu)', Index.menu
initFeature 'Report Link', ReportLink
initFeature 'Thread Hiding (Menu)', ThreadHiding.menu
initFeature 'Reply Hiding (Menu)', PostHiding.menu
initFeature 'Post Hiding (Menu)', PostHiding.menu
initFeature 'Delete Link', DeleteLink
initFeature 'Filter (Menu)', Filter.menu
initFeature 'Download Link', DownloadLink
initFeature 'Labels list', Labels
initFeature 'Archive Link', ArchiveLink
initFeature 'Quote Inlining', QuoteInline
initFeature 'Quote Previewing', QuotePreview

View File

@ -52,6 +52,8 @@ class Post
@parseQuotes()
@parseFile that
@labels = []
@highlights = []
@isDead = false
@isHidden = false
@ -106,7 +108,7 @@ class Post
# ES6 Set when?
fullID = "#{match[1]}.#{match[2]}"
@quotes.push fullID if @quotes.indexOf(fullID) is -1
@quotes.push fullID unless fullID in @quotes
parseFile: (that) ->
return unless (fileEl = $ '.file', @nodes.post) and thumb = $ 'img[data-md5]', fileEl
@ -151,6 +153,76 @@ class Post
$.rmClass node, 'desktop'
return
getNameBlock: ->
if Conf['Anonymize']
'Anonymous'
else
$('.nameBlock', @nodes.info).textContent.trim()
hide: (label, makeStub=Conf['Stubs'], hideRecursively=Conf['Recursive Hiding']) ->
@labels.push label unless label in @labels
return if @isHidden
@isHidden = true
for quotelink in Get.allQuotelinksLinkingTo @
$.addClass quotelink, 'filtered'
if hideRecursively
label = "Recursively hidden for quoting No.#{@}"
Recursive.apply 'hide', @, label, makeStub, true
Recursive.add 'hide', @, label, makeStub, true
if !@isReply
@thread.hide()
return
unless makeStub
@nodes.root.hidden = true
return
@nodes.post.hidden = true
@nodes.post.previousElementSibling.hidden = true
@nodes.stub = $.el 'div',
className: 'stub'
$.add @nodes.stub, [
PostHiding.makeButton false
$.tn " #{@getNameBlock()}"
]
$.add @nodes.stub, Menu.makeButton() if Conf['Menu']
$.prepend @nodes.root, @nodes.stub
show: (showRecursively=Conf['Recursive Hiding']) ->
return if !@isHidden
@isHidden = false
@labels = @labels.filter (label) ->
# This is lame.
!/^(Manually hidden|Recursively hidden|Hidden by)/.test label
for quotelink in Get.allQuotelinksLinkingTo @
$.rmClass quotelink, 'filtered'
if showRecursively
Recursive.apply 'show', @, true
Recursive.rm 'hide', @
if !@isReply
@thread.show()
return
unless @nodes.stub
@nodes.root.hidden = false
return
@nodes.post.hidden = false
@nodes.post.previousElementSibling.hidden = false
$.rm @nodes.stub
delete @nodes.stub
highlight: (label, highlight, top) ->
@labels.push label
unless highlight in @highlights
@highlights.push highlight
$.addClass @nodes.root, highlight
if !@isReply and top
@thread.isOnTop = true
kill: (file) ->
if file
return if @file.isDead

View File

@ -123,18 +123,16 @@ Settings =
div = $.el 'div',
innerHTML: "<button></button><span class=description>: Clear manually-hidden threads and posts on all boards. Reload the page to apply."
button = $ 'button', div
$.get {hiddenThreads: {}, hiddenPosts: {}}, ({hiddenThreads, hiddenPosts}) ->
$.get 'hiddenPosts', {}, ({hiddenPosts}) ->
hiddenNum = 0
for ID, board of hiddenThreads.boards
hiddenNum += Object.keys(board).length
for ID, board of hiddenPosts.boards
for ID, thread of board
hiddenNum += Object.keys(thread).length
button.textContent = "Hidden: #{hiddenNum}"
$.on button, 'click', ->
@textContent = 'Hidden: 0'
$.delete ['hiddenThreads', 'hiddenPosts']
$.after $('input[name="Stubs"]', section).parentNode.parentNode, div
$.delete 'hiddenPosts'
$.after $('input[name="Recursive Hiding"]', section).parentNode.parentNode, div
export: ->
# Make sure to export the most recent data.
$.get Conf, (Conf) ->

View File

@ -62,6 +62,17 @@ class Thread
@isPinned = false
$.rmClass @catalogView.nodes.root, 'pinned' if @catalogView
hide: ->
return if @isHidden
@isHidden = true
if button = $ '.hide-post-button', @OP.nodes.root
$.replace button, PostHiding.makeButton false
show: ->
return if !@isHidden
@isHidden = false
if button = $ '.show-post-button', @OP.nodes.root
$.replace button, PostHiding.makeButton true
kill: ->
@isDead = true

View File

@ -84,7 +84,9 @@ UI = do ->
insertEntry: (entry, parent, data) ->
if typeof entry.open is 'function'
return unless entry.open data
return unless entry.open data, (subEntry) =>
@parseEntry subEntry
entry.subEntries.push subEntry
$.add parent, entry.el
return unless entry.subEntries

View File

@ -43,7 +43,7 @@ ImageExpand =
for post in [post].concat post.clones
{file} = post
continue unless file and file.isImage and doc.contains post.nodes.root
if ImageExpand.on and
if ImageExpand.on and !post.isHidden and
(!Conf['Expand spoilers'] and file.isSpoiler or
Conf['Expand from here'] and Header.getTopOf(file.thumb) < 0)
continue
@ -76,7 +76,7 @@ ImageExpand =
expand: (post, src) ->
# Do not expand images of hidden/filtered replies, or already expanded pictures.
{thumb} = post.file
return if post.isHidden or post.file.isExpanded or $.hasClass thumb, 'expanding'
return if post.file.isExpanded or $.hasClass thumb, 'expanding'
$.addClass thumb, 'expanding'
if post.file.fullImage
# Expand already-loaded/ing picture.

16
src/Menu/Labels.coffee Normal file
View File

@ -0,0 +1,16 @@
Labels =
init: ->
return if !Conf['Menu']
$.event 'AddMenuEntry',
type: 'post'
el: $.el 'div', textContent: 'Labels'
order: 60
open: (post, addSubEntry) ->
{labels} = post.origin or post
return false unless labels.length
@subEntries.length = 0
for label in labels
addSubEntry el: $.el 'div', textContent: label
true
subEntries: []

View File

@ -120,7 +120,7 @@ Keybinds =
when Conf['Deselect reply']
Keybinds.hl 0, threadRoot
when Conf['Hide']
ThreadHiding.toggle thread if ThreadHiding.db
PostHiding.toggle thread.OP
else
return
e.preventDefault()

View File

@ -38,8 +38,6 @@ Nav =
getThread: ->
for threadRoot in $$ '.thread'
thread = Get.threadFromRoot threadRoot
continue if thread.isHidden and !thread.stub
if Header.getTopOf(threadRoot) >= -threadRoot.getBoundingClientRect().height # not scrolled past
return threadRoot
return $ '.board'

View File

@ -92,11 +92,7 @@ Unread =
return
openNotification: (post) ->
return unless Header.areNotificationsEnabled
name = if Conf['Anonymize']
'Anonymous'
else
$('.nameBlock', post.nodes.info).textContent.trim()
notif = new Notification "#{name} replied to you",
notif = new Notification "#{post.getNameBlock()} replied to you",
body: post.info.comment
icon: Favicon.logo
notif.onclick = ->

View File

@ -1,6 +1,6 @@
QuoteStrikeThrough =
init: ->
return if !Conf['Reply Hiding'] and !Conf['Reply Hiding Link'] and !Conf['Filter']
return if !Conf['Post Hiding'] and !Conf['Post Hiding Link'] and !Conf['Filter']
Post.callbacks.push
name: 'Strike-through Quotes'