From 8a587f515ccc66e9c1a5bc02ac18e222afe715f4 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Tue, 22 Jan 2013 13:28:25 +0100 Subject: [PATCH] Add Reply Hiding. Make Thread & Reply Hiding work with other features. Tiny fixes. --- 4chan_x.user.js | 243 +++++++++++++++++++++++++++++++++++--------- css/style.css | 9 ++ src/features.coffee | 151 ++++++++++++++++++++++++--- src/main.coffee | 43 +++----- 4 files changed, 352 insertions(+), 94 deletions(-) diff --git a/4chan_x.user.js b/4chan_x.user.js index cb1fa6641..b215c6d60 100644 --- a/4chan_x.user.js +++ b/4chan_x.user.js @@ -20,7 +20,7 @@ // @icon https://github.com/MayhemYDG/4chan-x/raw/stable/img/icon.gif // ==/UserScript== -/* 4chan X Alpha - Version 3.0.0 - 2013-01-19 +/* 4chan X Alpha - Version 3.0.0 - 2013-01-22 * http://mayhemydg.github.com/4chan-x/ * * Copyright (c) 2009-2011 James Campos @@ -43,7 +43,7 @@ */ (function() { - var $, $$, Anonymize, AutoGIF, Board, Build, Clone, Conf, Config, FileInfo, Get, ImageHover, Main, Post, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Redirect, RevealSpoilers, Sauce, Thread, ThreadHiding, ThreadUpdater, Time, UI, d, g, _base, + var $, $$, Anonymize, AutoGIF, Board, Build, Clone, Conf, Config, FileInfo, Get, ImageHover, Main, Post, QuoteBacklink, QuoteCT, QuoteInline, QuoteOP, QuotePreview, Quotify, Redirect, ReplyHiding, RevealSpoilers, Sauce, Thread, ThreadHiding, ThreadUpdater, Time, UI, d, g, _base, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; @@ -696,7 +696,7 @@ if (_ref = this.ID, __indexOf.call(ThreadHiding.hiddenThreads.threads, _ref) >= 0) { ThreadHiding.hide(this); } - return $.prepend(this.posts[this].nodes.root, ThreadHiding.button(this, '-')); + return $.prepend(this.posts[this].nodes.root, ThreadHiding.makeButton(this, '-')); }, getHiddenThreads: function() { var hiddenThreads; @@ -721,7 +721,7 @@ hiddenThreads = ThreadHiding.hiddenThreads; lastChecked = hiddenThreads.lastChecked; hiddenThreads.lastChecked = now = Date.now(); - if (!(lastChecked < now - $.DAY)) { + if (lastChecked > now - $.DAY) { return; } if (!hiddenThreads.threads.length) { @@ -748,7 +748,7 @@ } }); }, - button: function(thread, sign) { + makeButton: function(thread, sign) { var a; a = $.el('a', { className: 'hide-thread-button', @@ -764,7 +764,7 @@ var hiddenThreads, hiddenThreadsCatalog; hiddenThreads = ThreadHiding.getHiddenThreads(); hiddenThreadsCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; - if (thread.hidden) { + if (thread.isHidden) { ThreadHiding.show(thread); hiddenThreads.threads.splice(hiddenThreads.threads.indexOf(thread.ID), 1); delete hiddenThreadsCatalog[thread]; @@ -786,7 +786,7 @@ } op = thread.posts[thread]; threadRoot = op.nodes.root.parentNode; - threadRoot.hidden = thread.hidden = true; + threadRoot.hidden = thread.isHidden = true; if (!makeStub) { threadRoot.nextElementSibling.hidden = true; return; @@ -798,7 +798,7 @@ numReplies += $$('.opContainer ~ .replyContainer', threadRoot).length; numReplies = numReplies === 1 ? '1 reply' : "" + numReplies + " replies"; opInfo = Conf['Anonymize'] ? 'Anonymous' : $('.nameBlock', op.nodes.info).textContent; - a = ThreadHiding.button(thread, '+'); + a = ThreadHiding.makeButton(thread, '+'); $.add(a, $.tn("" + opInfo + " (" + numReplies + ")")); thread.stub = $.el('div'); $.add(thread.stub, a); @@ -811,7 +811,141 @@ delete thread.stub; } threadRoot = thread.posts[thread].nodes.root.parentNode; - return threadRoot.nextElementSibling.hidden = threadRoot.hidden = thread.hidden = false; + return threadRoot.nextElementSibling.hidden = threadRoot.hidden = thread.isHidden = false; + } + }; + + ReplyHiding = { + init: function() { + this.getHiddenPosts(); + this.clean(); + return Post.prototype.callbacks.push({ + name: 'Reply Hiding', + cb: this.node + }); + }, + node: function() { + var thread, _ref; + if (!this.isReply || this.isClone) { + return; + } + if (thread = ReplyHiding.hiddenPosts.threads[this.thread]) { + if (_ref = this.ID, __indexOf.call(thread, _ref) >= 0) { + ReplyHiding.hide(this); + } + } + return $.replace($('.sideArrows', this.nodes.root), ReplyHiding.makeButton(this, '-')); + }, + getHiddenPosts: function() { + var hiddenPosts; + hiddenPosts = $.get("hiddenPosts." + g.BOARD); + if (!hiddenPosts) { + hiddenPosts = { + threads: {}, + lastChecked: Date.now() + }; + $.set("hiddenPosts." + g.BOARD, hiddenPosts); + } + return ReplyHiding.hiddenPosts = hiddenPosts; + }, + clean: function() { + var hiddenPosts, lastChecked, now; + hiddenPosts = ReplyHiding.hiddenPosts; + lastChecked = hiddenPosts.lastChecked; + hiddenPosts.lastChecked = now = Date.now(); + if (lastChecked > now - $.DAY) { + return; + } + if (!Object.keys(hiddenPosts.threads).length) { + $.set("hiddenPosts." + g.BOARD, hiddenPosts); + return; + } + return $.ajax("//api.4chan.org/" + g.BOARD + "/catalog.json", { + onload: function() { + var obj, thread, threads, _i, _j, _len, _len1, _ref, _ref1; + threads = {}; + _ref = JSON.parse(this.response); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + obj = _ref[_i]; + _ref1 = obj.threads; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + thread = _ref1[_j]; + if (thread.no in hiddenPosts.threads) { + threads[thread.no] = hiddenPosts.threads[thread.no]; + } + } + } + hiddenPosts.threads = threads; + return $.set("hiddenPosts." + g.BOARD, hiddenPosts); + } + }); + }, + makeButton: function(post, sign) { + var a; + a = $.el('a', { + className: 'hide-reply-button', + innerHTML: "[ " + sign + " ]", + href: 'javascript:;' + }); + $.on(a, 'click', function() { + return ReplyHiding.toggle(post); + }); + return a; + }, + toggle: function(post) { + var hiddenPosts, quotelink, quotelinks, thread, _i, _j, _len, _len1; + hiddenPosts = ReplyHiding.getHiddenPosts(); + quotelinks = Get.allQuotelinksLinkingTo(post); + if (post.isHidden) { + ReplyHiding.show(post); + for (_i = 0, _len = quotelinks.length; _i < _len; _i++) { + quotelink = quotelinks[_i]; + $.rmClass(quotelink, 'filtered'); + } + thread = hiddenPosts.threads[post.thread]; + if (thread.length === 1) { + delete hiddenPosts.threads[post.thread]; + } else { + thread.splice(thread.indexOf(post.ID), 1); + } + } else { + ReplyHiding.hide(post); + for (_j = 0, _len1 = quotelinks.length; _j < _len1; _j++) { + quotelink = quotelinks[_j]; + $.addClass(quotelink, 'filtered'); + } + if (!(thread = hiddenPosts.threads[post.thread])) { + thread = hiddenPosts.threads[post.thread] = []; + } + thread.push(post.ID); + } + return $.set("hiddenPosts." + g.BOARD, hiddenPosts); + }, + hide: function(post, makeStub) { + var a, postInfo; + if (makeStub == null) { + makeStub = Conf['Stubs']; + } + if (post.isHidden) { + return; + } + post.nodes.root.hidden = post.isHidden = true; + if (!makeStub) { + return; + } + a = ReplyHiding.makeButton(post, '+'); + postInfo = Conf['Anonymize'] ? 'Anonymous' : $('.nameBlock', post.nodes.info).textContent; + $.add(a, $.tn(" " + postInfo)); + post.stub = $.el('div'); + $.add(post.stub, a); + return $.before(post.nodes.root, post.stub); + }, + show: function(post) { + if (post.stub) { + $.rm(post.stub); + delete post.stub; + } + return post.nodes.root.hidden = post.isHidden = false; } }; @@ -1132,7 +1266,7 @@ }, postDataFromLink: function(link) { var board, path, postID, threadID; - if (link.host === 'boards.4chan.org') { + if (link.hostname === 'boards.4chan.org') { path = link.pathname.split('/'); board = path[1]; threadID = path[3]; @@ -1148,6 +1282,39 @@ postID: +postID }; }, + allQuotelinksLinkingTo: function(post) { + var ID, fullID, quote, quotedPost, quotelinks, quoterPost, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3; + quotelinks = []; + fullID = "" + post.board + "." + post; + _ref = g.posts; + for (ID in _ref) { + quoterPost = _ref[ID]; + if (-1 !== quoterPost.quotes.indexOf(fullID)) { + _ref1 = [quoterPost].concat(quoterPost.clones); + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + quoterPost = _ref1[_i]; + quotelinks.push.apply(quotelinks, quoterPost.nodes.quotelinks); + } + } + } + if (Conf['Quote Backlinks']) { + _ref2 = post.quotes; + for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { + quote = _ref2[_j]; + quotedPost = g.posts[quote]; + _ref3 = [quotedPost].concat(quotedPost.clones); + for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) { + quotedPost = _ref3[_k]; + quotelinks.push.apply(quotelinks, Array.prototype.slice.call(quotedPost.nodes.backlinks)); + } + } + } + return quotelinks.filter(function(quotelink) { + var board, postID, _ref4; + _ref4 = Get.postDataFromLink(quotelink), board = _ref4.board, postID = _ref4.postID; + return board === post.board.ID && postID === post.ID; + }); + }, contextFromLink: function(quotelink) { return Get.postFromRoot($.x('ancestor::div[parent::div[@class="thread"]][1]', quotelink)); }, @@ -1595,7 +1762,7 @@ } a = $.el('a', { href: "/" + this.board + "/res/" + this.thread + "#p" + this, - className: 'backlink', + className: this.isHidden ? 'filtered backlink' : 'backlink', textContent: QuoteBacklink.funk(this.ID) }); _ref = this.quotes; @@ -2027,7 +2194,7 @@ }, node: function() { var URL, gif, style, thumb, _ref, _ref1; - if (this.isClone || !((_ref = this.file) != null ? _ref.isImage : void 0)) { + if (this.isClone || this.isHidden || this.thread.isHidden || !((_ref = this.file) != null ? _ref.isImage : void 0)) { return; } _ref1 = this.file, thumb = _ref1.thumb, URL = _ref1.URL; @@ -2576,7 +2743,7 @@ } Post.prototype.kill = function(img) { - var ID, board, num, post, postID, quote, quotelink, quotelinks, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _ref4; + var quotelink, _i, _len, _ref; if (this.file && !this.file.isDead) { this.file.isDead = true; } @@ -2585,41 +2752,11 @@ } this.isDead = true; $.addClass(this.nodes.root, 'dead'); - quotelinks = []; - num = "" + this.board + "." + this; - _ref = g.posts; - for (ID in _ref) { - post = _ref[ID]; - if (-1 < post.quotes.indexOf(num)) { - _ref1 = [post].concat(post.clones); - for (_i = 0, _len = _ref1.length; _i < _len; _i++) { - post = _ref1[_i]; - quotelinks.push.apply(quotelinks, post.nodes.quotelinks); - } - } - } - if (Conf['Quote Backlinks']) { - _ref2 = this.quotes; - for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { - quote = _ref2[_j]; - post = g.posts[quote]; - _ref3 = [post].concat(post.clones); - for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) { - post = _ref3[_k]; - quotelinks.push.apply(quotelinks, Array.prototype.slice.call(post.nodes.backlinks)); - } - } - } - for (_l = 0, _len3 = quotelinks.length; _l < _len3; _l++) { - quotelink = quotelinks[_l]; - if ($.hasClass(quotelink, 'deadlink')) { - continue; - } - _ref4 = Get.postDataFromLink(quotelink), board = _ref4.board, postID = _ref4.postID; - if (board === this.board.ID(postID === this.ID)) { - $.add(quotelink, $.tn('\u00A0(Dead)')); - $.addClass(quotelinks, 'deadlink'); - } + _ref = Get.allQuotelinksLinkingTo(this); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + quotelink = _ref[_i]; + $.add(quotelink, $.tn('\u00A0(Dead)')); + $.addClass(quotelink, 'deadlink'); } }; @@ -2676,6 +2813,7 @@ inlined = _ref2[_k]; $.rmClass(inlined, 'inlined'); } + root.hidden = false; $.rmClass(root, 'forwarded'); if (nodes.subject) { this.nodes.subject = $('.subject', info); @@ -2837,6 +2975,13 @@ $.log(err, 'Thread Hiding'); } } + if (Conf['Reply Hiding']) { + try { + ReplyHiding.init(); + } catch (err) { + $.log(err, 'Reply Hiding'); + } + } if (Conf['Resurrect Quotes']) { try { Quotify.init(); @@ -2996,7 +3141,7 @@ settings: function() { return alert('Here be settings'); }, - css: "/* general */\n.dialog.reply {\n display: block;\n border: 1px solid rgba(0, 0, 0, .25);\n padding: 0;\n}\n.move {\n cursor: move;\n}\nlabel {\n cursor: pointer;\n}\na[href=\"javascript:;\"] {\n text-decoration: none;\n}\n.warning {\n color: red;\n}\n\n/* 4chan style fixes */\n.opContainer, .op {\n display: block !important;\n}\n.post {\n overflow: visible !important;\n}\n\n/* fixed, z-index */\n#qp, #ihover,\n#updater, #stats,\n#boardNavDesktop.reply,\n#qr, #watcher {\n position: fixed;\n}\n#qp, #ihover {\n z-index: 100;\n}\n#updater, #stats {\n z-index: 90;\n}\n#boardNavDesktop.reply:hover {\n z-index: 80;\n}\n#qr {\n z-index: 50;\n}\n#watcher {\n z-index: 30;\n}\n#boardNavDesktop.reply {\n z-index: 10;\n}\n\n\n/* header */\nbody.fourchan_x {\n margin-top: 2.5em;\n}\n#boardNavDesktop.reply {\n border-width: 0 0 1px;\n padding: 4px;\n top: 0;\n right: 0;\n left: 0;\n transition: opacity .1s ease-in-out;\n -o-transition: opacity .1s ease-in-out;\n -moz-transition: opacity .1s ease-in-out;\n -webkit-transition: opacity .1s ease-in-out;\n}\n#boardNavDesktop.reply:not(:hover) {\n opacity: .4;\n transition: opacity 1.5s .5s ease-in-out;\n -o-transition: opacity 1.5s .5s ease-in-out;\n -moz-transition: opacity 1.5s .5s ease-in-out;\n -webkit-transition: opacity 1.5s .5s ease-in-out;\n}\n#boardNavDesktop.reply a {\n margin: -1px;\n}\n#settings {\n float: right;\n}\n\n/* thread updater */\n#updater {\n text-align: right;\n}\n#updater:not(:hover) {\n background: none;\n border: none;\n}\n#updater input[type=number] {\n width: 4em;\n}\n#updater:not(:hover) > div:not(.move) {\n display: none;\n}\n.new {\n color: limegreen;\n}\n\n/* quote */\n.quotelink.deadlink {\n text-decoration: underline !important;\n}\n.deadlink:not(.quotelink) {\n text-decoration: none !important;\n}\n.inlined {\n opacity: .5;\n}\n#qp input, .forwarded {\n display: none;\n}\n.quotelink.forwardlink,\n.backlink.forwardlink {\n text-decoration: none;\n border-bottom: 1px dashed;\n}\n.inline {\n border: 1px solid rgba(128, 128, 128, .5);\n display: table;\n margin: 2px 0;\n}\n.inline .post {\n border: 0 !important;\n display: table !important;\n margin: 0 !important;\n padding: 1px 2px !important;\n}\n#qp {\n padding: 2px 2px 5px;\n}\n#qp .post {\n border: none;\n margin: 0;\n padding: 0;\n}\n#qp img {\n max-height: 300px;\n max-width: 500px;\n}\n.qphl {\n box-shadow: 0 0 0 2px rgba(216, 94, 49, .7);\n}\n\n/* file */\n.fileText:hover .fntrunc,\n.fileText:not(:hover) .fnfull {\n display: none;\n}\n#ihover {\n box-sizing: border-box;\n -moz-box-sizing: border-box;\n max-height: 100%;\n max-width: 75%;\n padding-bottom: 16px;\n}\n\n/* thread hiding */\n.opContainer > .hide-thread-button {\n float: left;\n}" + css: "/* general */\n.dialog.reply {\n display: block;\n border: 1px solid rgba(0, 0, 0, .25);\n padding: 0;\n}\n.move {\n cursor: move;\n}\nlabel {\n cursor: pointer;\n}\na[href=\"javascript:;\"] {\n text-decoration: none;\n}\n.warning {\n color: red;\n}\n\n/* 4chan style fixes */\n.opContainer, .op {\n display: block !important;\n}\n.post {\n overflow: visible !important;\n}\n\n/* fixed, z-index */\n#qp, #ihover,\n#updater, #stats,\n#boardNavDesktop.reply,\n#qr, #watcher {\n position: fixed;\n}\n#qp, #ihover {\n z-index: 100;\n}\n#updater, #stats {\n z-index: 90;\n}\n#boardNavDesktop.reply:hover {\n z-index: 80;\n}\n#qr {\n z-index: 50;\n}\n#watcher {\n z-index: 30;\n}\n#boardNavDesktop.reply {\n z-index: 10;\n}\n\n\n/* header */\nbody.fourchan_x {\n margin-top: 2.5em;\n}\n#boardNavDesktop.reply {\n border-width: 0 0 1px;\n padding: 4px;\n top: 0;\n right: 0;\n left: 0;\n transition: opacity .1s ease-in-out;\n -o-transition: opacity .1s ease-in-out;\n -moz-transition: opacity .1s ease-in-out;\n -webkit-transition: opacity .1s ease-in-out;\n}\n#boardNavDesktop.reply:not(:hover) {\n opacity: .4;\n transition: opacity 1.5s .5s ease-in-out;\n -o-transition: opacity 1.5s .5s ease-in-out;\n -moz-transition: opacity 1.5s .5s ease-in-out;\n -webkit-transition: opacity 1.5s .5s ease-in-out;\n}\n#boardNavDesktop.reply a {\n margin: -1px;\n}\n#settings {\n float: right;\n}\n\n/* thread updater */\n#updater {\n text-align: right;\n}\n#updater:not(:hover) {\n background: none;\n border: none;\n}\n#updater input[type=number] {\n width: 4em;\n}\n#updater:not(:hover) > div:not(.move) {\n display: none;\n}\n.new {\n color: limegreen;\n}\n\n/* quote */\n.quotelink.deadlink {\n text-decoration: underline !important;\n}\n.deadlink:not(.quotelink) {\n text-decoration: none !important;\n}\n.inlined {\n opacity: .5;\n}\n#qp input, .forwarded {\n display: none;\n}\n.quotelink.forwardlink,\n.backlink.forwardlink {\n text-decoration: none;\n border-bottom: 1px dashed;\n}\n.filtered {\n text-decoration: underline line-through;\n}\n.inline {\n border: 1px solid rgba(128, 128, 128, .5);\n display: table;\n margin: 2px 0;\n}\n.inline .post {\n border: 0 !important;\n display: table !important;\n margin: 0 !important;\n padding: 1px 2px !important;\n}\n#qp {\n padding: 2px 2px 5px;\n}\n#qp .post {\n border: none;\n margin: 0;\n padding: 0;\n}\n#qp img {\n max-height: 300px;\n max-width: 500px;\n}\n.qphl {\n box-shadow: 0 0 0 2px rgba(216, 94, 49, .7);\n}\n\n/* file */\n.fileText:hover .fntrunc,\n.fileText:not(:hover) .fnfull {\n display: none;\n}\n#ihover {\n box-sizing: border-box;\n -moz-box-sizing: border-box;\n max-height: 100%;\n max-width: 75%;\n padding-bottom: 16px;\n}\n\n/* thread hiding */\n.opContainer > .hide-thread-button {\n float: left;\n}\n\n/* reply hiding */\n.replyContainer > .hide-reply-button {\n float: left;\n margin-right: 2px;\n}" }; Main.init(); diff --git a/css/style.css b/css/style.css index 29460e229..2940258de 100644 --- a/css/style.css +++ b/css/style.css @@ -117,6 +117,9 @@ body.fourchan_x { text-decoration: none; border-bottom: 1px dashed; } +.filtered { + text-decoration: underline line-through; +} .inline { border: 1px solid rgba(128, 128, 128, .5); display: table; @@ -161,3 +164,9 @@ body.fourchan_x { .opContainer > .hide-thread-button { float: left; } + +/* reply hiding */ +.replyContainer > .hide-reply-button { + float: left; + margin-right: 2px; +} diff --git a/src/features.coffee b/src/features.coffee index dc8194a10..b4320e3a8 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -11,7 +11,7 @@ ThreadHiding = node: -> if @ID in ThreadHiding.hiddenThreads.threads ThreadHiding.hide @ - $.prepend @posts[@].nodes.root, ThreadHiding.button @, '-' + $.prepend @posts[@].nodes.root, ThreadHiding.makeButton @, '-' getHiddenThreads: -> hiddenThreads = $.get "hiddenThreads.#{g.BOARD}" @@ -33,7 +33,7 @@ ThreadHiding = {lastChecked} = hiddenThreads hiddenThreads.lastChecked = now = Date.now() - return unless lastChecked < now - $.DAY + return if lastChecked > now - $.DAY unless hiddenThreads.threads.length $.set "hiddenThreads.#{g.BOARD}", hiddenThreads @@ -47,7 +47,7 @@ ThreadHiding = hiddenThreads.threads = threads $.set "hiddenThreads.#{g.BOARD}", hiddenThreads - button: (thread, sign) -> + makeButton: (thread, sign) -> a = $.el 'a', className: 'hide-thread-button' innerHTML: "[ #{sign} ] " @@ -59,7 +59,7 @@ ThreadHiding = # Get fresh hidden threads. hiddenThreads = ThreadHiding.getHiddenThreads() hiddenThreadsCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {} - if thread.hidden + if thread.isHidden ThreadHiding.show thread hiddenThreads.threads.splice hiddenThreads.threads.indexOf(thread.ID), 1 delete hiddenThreadsCatalog[thread] @@ -74,7 +74,7 @@ ThreadHiding = return if thread.hidden op = thread.posts[thread] threadRoot = op.nodes.root.parentNode - threadRoot.hidden = thread.hidden = true + threadRoot.hidden = thread.isHidden = true unless makeStub threadRoot.nextElementSibling.hidden = true #
@@ -91,13 +91,12 @@ ThreadHiding = else $('.nameBlock', op.nodes.info).textContent - a = ThreadHiding.button thread, '+' + a = ThreadHiding.makeButton thread, '+' $.add a, $.tn "#{opInfo} (#{numReplies})" thread.stub = $.el 'div' $.add thread.stub, a # if Conf['Menu'] - # menuButton = Menu.button() - # $.add thread.stub, [$.tn(' '), menuButton] + # $.add thread.stub, [$.tn(' '), Menu.makeButton()] $.before threadRoot, thread.stub show: (thread) -> @@ -106,7 +105,106 @@ ThreadHiding = delete thread.stub threadRoot = thread.posts[thread].nodes.root.parentNode threadRoot.nextElementSibling.hidden = - threadRoot.hidden = thread.hidden = false + threadRoot.hidden = thread.isHidden = false + +ReplyHiding = + init: -> + @getHiddenPosts() + @clean() + Post::callbacks.push + name: 'Reply Hiding' + cb: @node + + node: -> + return if !@isReply or @isClone + if thread = ReplyHiding.hiddenPosts.threads[@thread] + if @ID in thread + ReplyHiding.hide @ + $.replace $('.sideArrows', @nodes.root), ReplyHiding.makeButton @, '-' + + getHiddenPosts: -> + hiddenPosts = $.get "hiddenPosts.#{g.BOARD}" + unless hiddenPosts + hiddenPosts = + threads: {} + lastChecked: Date.now() + $.set "hiddenPosts.#{g.BOARD}", hiddenPosts + ReplyHiding.hiddenPosts = hiddenPosts + + clean: -> + {hiddenPosts} = ReplyHiding + {lastChecked} = hiddenPosts + hiddenPosts.lastChecked = now = Date.now() + + return if lastChecked > now - $.DAY + + unless Object.keys(hiddenPosts.threads).length + $.set "hiddenPosts.#{g.BOARD}", hiddenPosts + return + + $.ajax "//api.4chan.org/#{g.BOARD}/catalog.json", onload: -> + threads = {} + for obj in JSON.parse @response + for thread in obj.threads + if thread.no of hiddenPosts.threads + threads[thread.no] = hiddenPosts.threads[thread.no] + hiddenPosts.threads = threads + $.set "hiddenPosts.#{g.BOARD}", hiddenPosts + + makeButton: (post, sign) -> + a = $.el 'a', + className: 'hide-reply-button' + innerHTML: "[ #{sign} ]" + href: 'javascript:;' + $.on a, 'click', -> ReplyHiding.toggle post + a + + toggle: (post) -> + # Get fresh hidden posts. + hiddenPosts = ReplyHiding.getHiddenPosts() + quotelinks = Get.allQuotelinksLinkingTo post + if post.isHidden + ReplyHiding.show post + for quotelink in quotelinks + $.rmClass quotelink, 'filtered' + # XXX recursive filtering + thread = hiddenPosts.threads[post.thread] + if thread.length is 1 + delete hiddenPosts.threads[post.thread] + else + thread.splice thread.indexOf(post.ID), 1 + else + ReplyHiding.hide post + for quotelink in quotelinks + $.addClass quotelink, 'filtered' + unless thread = hiddenPosts.threads[post.thread] + thread = hiddenPosts.threads[post.thread] = [] + thread.push post.ID + $.set "hiddenPosts.#{g.BOARD}", hiddenPosts + + hide: (post, makeStub=Conf['Stubs']) -> + return if post.isHidden + post.nodes.root.hidden = post.isHidden = true + + return unless makeStub + a = ReplyHiding.makeButton post, '+' + postInfo = + if Conf['Anonymize'] + 'Anonymous' + else + $('.nameBlock', post.nodes.info).textContent + $.add a, $.tn " #{postInfo}" + post.stub = $.el 'div' + $.add post.stub, a + # if Conf['Menu'] + # $.add post.stub, [$.tn(' '), Menu.makeButton()] + $.before post.nodes.root, post.stub + + show: (post) -> + if post.stub + $.rm post.stub + delete post.stub + post.nodes.root.hidden = post.isHidden = false Redirect = image: (board, filename) -> @@ -464,7 +562,7 @@ Get = post = g.posts["#{board}.#{postID}"] if index then post.clones[index] else post postDataFromLink: (link) -> - if link.host is 'boards.4chan.org' + if link.hostname is 'boards.4chan.org' path = link.pathname.split '/' board = path[1] threadID = path[3] @@ -478,6 +576,33 @@ Get = threadID: +threadID postID: +postID } + allQuotelinksLinkingTo: (post) -> + # Get quotelinks & backlinks linking to the given post. + quotelinks = [] + fullID = "#{post.board}.#{post}" + # First: + # In every posts, + # if it did quote this post, + # get all their backlinks. + for ID, quoterPost of g.posts + if -1 isnt quoterPost.quotes.indexOf fullID + for quoterPost in [quoterPost].concat quoterPost.clones + quotelinks.push.apply quotelinks, quoterPost.nodes.quotelinks + # Second: + # If we have quote backlinks: + # in all posts this post quoted + # and their clones, + # get all of their backlinks. + if Conf['Quote Backlinks'] + for quote in post.quotes + quotedPost = g.posts[quote] + for quotedPost in [quotedPost].concat quotedPost.clones + quotelinks.push.apply quotelinks, Array::slice.call quotedPost.nodes.backlinks + # Third: + # Filter out irrelevant quotelinks. + quotelinks.filter (quotelink) -> + {board, postID} = Get.postDataFromLink quotelink + board is post.board.ID and postID is post.ID contextFromLink: (quotelink) -> Get.postFromRoot $.x 'ancestor::div[parent::div[@class="thread"]][1]', quotelink postClone: (board, threadID, postID, root, context) -> @@ -886,8 +1011,7 @@ QuoteBacklink = return if @isClone or !@quotes.length a = $.el 'a', href: "/#{@board}/res/#{@thread}#p#{@}" - # XXX className: if post.el.hidden then 'filtered backlink' else 'backlink' - className: 'backlink' + className: if @isHidden then 'filtered backlink' else 'backlink' textContent: QuoteBacklink.funk @ID for quote in @quotes containers = [QuoteBacklink.getContainer quote] @@ -1155,8 +1279,7 @@ AutoGIF = name: 'Auto-GIF' cb: @node node: -> - # XXX return if @hidden? - return if @isClone or !@file?.isImage + return if @isClone or @isHidden or @thread.isHidden or !@file?.isImage {thumb, URL} = @file return unless /gif$/.test(URL) and !/spoiler/.test thumb.src if @file.isSpoiler diff --git a/src/main.coffee b/src/main.coffee index 6768dc2f5..d41331a93 100644 --- a/src/main.coffee +++ b/src/main.coffee @@ -153,37 +153,11 @@ class Post $.addClass @nodes.root, 'dead' # XXX style dead posts. - # Get quote/backlinks to this post, + # Get quote/backlinks to this post # and paint them (Dead). - # First: - # In every posts, - # if it did quote this post, - # get all their backlinks. - # Second: - # If we have quote backlinks, - # in all posts this post quoted, - # and their clones, - # get all of their backlinks. - # Third: - # In all collected links, - # apply (Dead) if relevant. - quotelinks = [] - num = "#{@board}.#{@}" - for ID, post of g.posts - if -1 < post.quotes.indexOf num - for post in [post].concat post.clones - quotelinks.push.apply quotelinks, post.nodes.quotelinks - if Conf['Quote Backlinks'] - for quote in @quotes - post = g.posts[quote] - for post in [post].concat post.clones - quotelinks.push.apply quotelinks, Array::slice.call post.nodes.backlinks - for quotelink in quotelinks - continue if $.hasClass quotelink, 'deadlink' - {board, postID} = Get.postDataFromLink quotelink - if board is @board.ID postID is @ID - $.add quotelink, $.tn '\u00A0(Dead)' - $.addClass quotelinks, 'deadlink' + for quotelink in Get.allQuotelinksLinkingTo @ + $.add quotelink, $.tn '\u00A0(Dead)' + $.addClass quotelink, 'deadlink' return addClone: (context) -> new Clone @, context @@ -217,7 +191,7 @@ class Clone extends Post for inlined in $$ '.inlined', post $.rmClass inlined, 'inlined' - # root.hidden = false # post hiding + root.hidden = false # post hiding $.rmClass root, 'forwarded' # quote inlining # $.rmClass post, 'highlight' # keybind navigation @@ -346,6 +320,13 @@ Main = # XXX handle error $.log err, 'Thread Hiding' + if Conf['Reply Hiding'] + try + ReplyHiding.init() + catch err + # XXX handle error + $.log err, 'Reply Hiding' + if Conf['Resurrect Quotes'] try Quotify.init()