diff --git a/CHANGELOG.md b/CHANGELOG.md index 03e370275..9211c9e77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. + + ### 3.18.1 - *2014-02-20* - Fix the QR breaking after a change with 4chan. diff --git a/css/style.css b/css/style.css index 475ae1166..852f37b59 100644 --- a/css/style.css +++ b/css/style.css @@ -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; diff --git a/src/Filtering/Filter.coffee b/src/Filtering/Filter.coffee index 62abf61c3..12dcde4ed 100644 --- a/src/Filtering/Filter.coffee +++ b/src/Filtering/Filter.coffee @@ -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 diff --git a/src/Filtering/PostHiding.coffee b/src/Filtering/PostHiding.coffee index ed621d194..094f315f3 100644 --- a/src/Filtering/PostHiding.coffee +++ b/src/Filtering/PostHiding.coffee @@ -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: '' + href: 'javascript:;' + @showButton = $.el 'a', + className: 'show-post-button' + innerHTML: '' + 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: ' This post' - replies = $.el 'label', - innerHTML: " Hide replies" - makeStub = $.el 'label', - innerHTML: " 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: ' This post' - replies = $.el 'label', - innerHTML: " 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: ' This post' + replies = + el: $.el 'label', innerHTML: " Hide replies" + makeStub = + el: $.el 'label', innerHTML: " 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: ' This post' + open: (post) -> + @el.firstChild.checked = post.isHidden + true + replies = + el: $.el 'label', innerHTML: ' 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' diff --git a/src/Filtering/Recursive.coffee b/src/Filtering/Recursive.coffee index 51d7a406b..d3151507d 100644 --- a/src/Filtering/Recursive.coffee +++ b/src/Filtering/Recursive.coffee @@ -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 diff --git a/src/Filtering/ThreadHiding.coffee b/src/Filtering/ThreadHiding.coffee deleted file mode 100644 index b1daef857..000000000 --- a/src/Filtering/ThreadHiding.coffee +++ /dev/null @@ -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: " 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: "[ #{if type is 'hide' then '-' else '+'} ]" - 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 #
- 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() diff --git a/src/General/Build.coffee b/src/General/Build.coffee index 5cb9d1dca..12a5d38e8 100644 --- a/src/General/Build.coffee +++ b/src/General/Build.coffee @@ -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'] diff --git a/src/General/Config.coffee b/src/General/Config.coffee index 426c17b37..015c137be 100644 --- a/src/General/Config.coffee +++ b/src/General/Config.coffee @@ -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 diff --git a/src/General/DataBoard.coffee b/src/General/DataBoard.coffee index d82ff1b65..36fc5d78d 100644 --- a/src/General/DataBoard.coffee +++ b/src/General/DataBoard.coffee @@ -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] diff --git a/src/General/Get.coffee b/src/General/Get.coffee index 0490b8403..e581bb504 100644 --- a/src/General/Get.coffee +++ b/src/General/Get.coffee @@ -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 diff --git a/src/General/Index.coffee b/src/General/Index.coffee index bc9dbd3cb..ee0e54614 100644 --- a/src/General/Index.coffee +++ b/src/General/Index.coffee @@ -38,15 +38,11 @@ Index = repliesEntry = el: $.el 'label', innerHTML: ' Show replies' - anchorEntry = - el: $.el 'label', - innerHTML: ' Anchor hidden threads' - title: 'Move hidden threads at the end of the index.' refNavEntry = el: $.el 'label', innerHTML: ' 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
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 = [] diff --git a/src/General/Main.coffee b/src/General/Main.coffee index 6f7a455ea..7795c47b2 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -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 diff --git a/src/General/Post.coffee b/src/General/Post.coffee index 5d5b63ed1..7591ed653 100644 --- a/src/General/Post.coffee +++ b/src/General/Post.coffee @@ -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 diff --git a/src/General/Settings.coffee b/src/General/Settings.coffee index 86c027c4e..64c9c0801 100644 --- a/src/General/Settings.coffee +++ b/src/General/Settings.coffee @@ -123,18 +123,16 @@ Settings = div = $.el 'div', innerHTML: ": 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) -> diff --git a/src/General/Thread.coffee b/src/General/Thread.coffee index b930661c8..7d285d520 100644 --- a/src/General/Thread.coffee +++ b/src/General/Thread.coffee @@ -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 diff --git a/src/General/UI.coffee b/src/General/UI.coffee index d3dd11c1c..7dac2c2eb 100644 --- a/src/General/UI.coffee +++ b/src/General/UI.coffee @@ -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 diff --git a/src/Images/ImageExpand.coffee b/src/Images/ImageExpand.coffee index 9d754da91..213543a0e 100644 --- a/src/Images/ImageExpand.coffee +++ b/src/Images/ImageExpand.coffee @@ -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. diff --git a/src/Menu/Labels.coffee b/src/Menu/Labels.coffee new file mode 100644 index 000000000..726377b7d --- /dev/null +++ b/src/Menu/Labels.coffee @@ -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: [] diff --git a/src/Miscellaneous/Keybinds.coffee b/src/Miscellaneous/Keybinds.coffee index 8bb3e466b..54d41a222 100644 --- a/src/Miscellaneous/Keybinds.coffee +++ b/src/Miscellaneous/Keybinds.coffee @@ -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() diff --git a/src/Miscellaneous/Nav.coffee b/src/Miscellaneous/Nav.coffee index 0c93cc2d5..b6132e82a 100644 --- a/src/Miscellaneous/Nav.coffee +++ b/src/Miscellaneous/Nav.coffee @@ -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' diff --git a/src/Monitoring/Unread.coffee b/src/Monitoring/Unread.coffee index 067f617a9..08a1d476b 100644 --- a/src/Monitoring/Unread.coffee +++ b/src/Monitoring/Unread.coffee @@ -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 = -> diff --git a/src/Quotelinks/QuoteStrikeThrough.coffee b/src/Quotelinks/QuoteStrikeThrough.coffee index bf3120516..884c507a9 100644 --- a/src/Quotelinks/QuoteStrikeThrough.coffee +++ b/src/Quotelinks/QuoteStrikeThrough.coffee @@ -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'