diff --git a/4chan_x.user.js b/4chan_x.user.js index b215c6d60..f3d1cde77 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-22 +/* 4chan X Alpha - Version 3.0.0 - 2013-01-24 * 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, ReplyHiding, 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, Recursive, 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; }; @@ -65,7 +65,7 @@ Filtering: { 'Anonymize': [false, 'Turn everyone Anonymous.'], 'Filter': [true, 'Self-moderation placebo.'], - 'Recursive Filtering': [true, 'Filter replies of filtered posts, recursively.'], + 'Recursive Hiding': [true, 'Filter replies of filtered posts, recursively.'], 'Reply Hiding': [true, 'Hide single replies.'], 'Thread Hiding': [true, 'Hide entire threads.'], 'Stubs': [true, 'Make stubs of hidden threads / replies.'] @@ -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.makeButton(this, '-')); + return $.prepend(this.posts[this].nodes.root, ThreadHiding.makeButton(this, 'hide')); }, getHiddenThreads: function() { var hiddenThreads; @@ -748,11 +748,11 @@ } }); }, - makeButton: function(thread, sign) { + makeButton: function(thread, type) { var a; a = $.el('a', { - className: 'hide-thread-button', - innerHTML: "[ " + sign + " ] ", + className: "" + type + "-thread-button", + innerHTML: "[ " + (type === 'hide' ? '-' : '+') + " ]", href: 'javascript:;' }); $.on(a, 'click', function() { @@ -798,9 +798,11 @@ numReplies += $$('.opContainer ~ .replyContainer', threadRoot).length; numReplies = numReplies === 1 ? '1 reply' : "" + numReplies + " replies"; opInfo = Conf['Anonymize'] ? 'Anonymous' : $('.nameBlock', op.nodes.info).textContent; - a = ThreadHiding.makeButton(thread, '+'); - $.add(a, $.tn("" + opInfo + " (" + numReplies + ")")); - thread.stub = $.el('div'); + a = ThreadHiding.makeButton(thread, 'show'); + $.add(a, $.tn(" " + opInfo + " (" + numReplies + ")")); + thread.stub = $.el('div', { + className: 'stub' + }); $.add(thread.stub, a); return $.before(threadRoot, thread.stub); }, @@ -834,7 +836,7 @@ ReplyHiding.hide(this); } } - return $.replace($('.sideArrows', this.nodes.root), ReplyHiding.makeButton(this, '-')); + return $.replace($('.sideArrows', this.nodes.root), ReplyHiding.makeButton(this, 'hide')); }, getHiddenPosts: function() { var hiddenPosts; @@ -880,11 +882,11 @@ } }); }, - makeButton: function(post, sign) { + makeButton: function(post, type) { var a; a = $.el('a', { - className: 'hide-reply-button', - innerHTML: "[ " + sign + " ]", + className: "" + type + "-reply-button", + innerHTML: "[ " + (type === 'hide' ? '-' : '+') + " ]", href: 'javascript:;' }); $.on(a, 'click', function() { @@ -893,27 +895,20 @@ return a; }, toggle: function(post) { - var hiddenPosts, quotelink, quotelinks, thread, _i, _j, _len, _len1; + var hiddenPosts, index, thread; 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); + if ((index = thread.indexOf(post.ID)) > -1) { + if (thread.length === 1) { + delete hiddenPosts.threads[post.thread]; + } else { + thread.splice(index, 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] = []; } @@ -921,31 +916,101 @@ } return $.set("hiddenPosts." + g.BOARD, hiddenPosts); }, - hide: function(post, makeStub) { - var a, postInfo; + hide: function(post, makeStub, hideRecursively) { + var a, postInfo, quotelink, _i, _len, _ref; if (makeStub == null) { makeStub = Conf['Stubs']; } + if (hideRecursively == null) { + hideRecursively = Conf['Recursive Hiding']; + } if (post.isHidden) { return; } - post.nodes.root.hidden = post.isHidden = true; + post.isHidden = true; + if (hideRecursively) { + Recursive.hide(post, makeStub, true); + } + _ref = Get.allQuotelinksLinkingTo(post); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + quotelink = _ref[_i]; + $.addClass(quotelink, 'filtered'); + } if (!makeStub) { return; } - a = ReplyHiding.makeButton(post, '+'); + a = ReplyHiding.makeButton(post, 'show'); 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); + post.nodes.stub = $.el('div', { + className: 'stub' + }); + $.add(post.nodes.stub, a); + return $.prepend(post.nodes.root, post.nodes.stub); }, show: function(post) { - if (post.stub) { - $.rm(post.stub); - delete post.stub; + var quotelink, _i, _len, _ref; + if (post.nodes.stub) { + $.rm(post.nodes.stub); + delete post.nodes.stub; + } + post.isHidden = false; + _ref = Get.allQuotelinksLinkingTo(post); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + quotelink = _ref[_i]; + $.rmClass(quotelink, 'filtered'); + } + } + }; + + Recursive = { + toHide: [], + init: function() { + return Post.prototype.callbacks.push({ + name: 'Recursive', + cb: this.node + }); + }, + node: function() { + var board, postID, quote, quotelink, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3; + if (this.isClone) { + return; + } + _ref = this.quotes; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + quote = _ref[_i]; + if (__indexOf.call(Recursive.toHide, quote) >= 0) { + ReplyHiding.hide(this, !!g.posts[quote].nodes.stub, true); + } + } + _ref1 = this.nodes.quotelinks; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + quotelink = _ref1[_j]; + _ref2 = Get.postDataFromLink(quotelink), board = _ref2.board, postID = _ref2.postID; + if ((_ref3 = g.posts["" + board + "." + postID]) != null ? _ref3.isHidden : void 0) { + $.addClass(quotelink, 'filtered'); + } + } + }, + hide: function(post, makeStub) { + var ID, fullID, quote, _i, _len, _ref, _ref1; + fullID = post.fullID; + Recursive.toHide.push(fullID); + _ref = g.posts; + for (ID in _ref) { + post = _ref[ID]; + if (!post.isReply || post.isHidden) { + continue; + } + _ref1 = post.quotes; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + quote = _ref1[_i]; + if (quote === fullID) { + ReplyHiding.hide(post, makeStub, true); + break; + } + } } - return post.nodes.root.hidden = post.isHidden = false; } }; @@ -1017,7 +1082,7 @@ to: function(data) { var board, url; board = data.board; - switch ("" + board) { + switch (board) { case 'a': case 'co': case 'jp': @@ -1283,13 +1348,12 @@ }; }, allQuotelinksLinkingTo: function(post) { - var ID, fullID, quote, quotedPost, quotelinks, quoterPost, _i, _j, _k, _len, _len1, _len2, _ref, _ref1, _ref2, _ref3; + var ID, 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)) { + if (-1 !== quoterPost.quotes.indexOf(post.fullID)) { _ref1 = [quoterPost].concat(quoterPost.clones); for (_i = 0, _len = _ref1.length; _i < _len; _i++) { quoterPost = _ref1[_i]; @@ -1301,7 +1365,9 @@ _ref2 = post.quotes; for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { quote = _ref2[_j]; - quotedPost = g.posts[quote]; + if (!(quotedPost = g.posts[quote])) { + continue; + } _ref3 = [quotedPost].concat(quotedPost.clones); for (_k = 0, _len2 = _ref3.length; _k < _len2; _k++) { quotedPost = _ref3[_k]; @@ -1584,19 +1650,20 @@ } }, toggle: function(e) { - var board, postID, threadID, _ref; + var board, context, postID, threadID, _ref; if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { return; } e.preventDefault(); _ref = Get.postDataFromLink(this), board = _ref.board, threadID = _ref.threadID, postID = _ref.postID; + context = Get.contextFromLink(this); if ($.hasClass(this, 'inlined')) { - QuoteInline.rm(this, board, threadID, postID); + QuoteInline.rm(this, board, threadID, postID, context); } else { if ($.x("ancestor::div[@id='p" + postID + "']", this)) { return; } - QuoteInline.add(this, board, threadID, postID); + QuoteInline.add(this, board, threadID, postID, context); } return this.classList.toggle('inlined'); }, @@ -1607,28 +1674,27 @@ return $.x('ancestor-or-self::*[parent::blockquote][1]', quotelink); } }, - add: function(quotelink, board, threadID, postID) { - var context, inline, isBacklink, post; + add: function(quotelink, board, threadID, postID, context) { + var inline, isBacklink, post; isBacklink = $.hasClass(quotelink, 'backlink'); inline = $.el('div', { id: "i" + postID, className: 'inline' }); - context = Get.contextFromLink(quotelink); $.after(QuoteInline.findRoot(quotelink, isBacklink), inline); Get.postClone(board, threadID, postID, inline, context); - if (context.thread !== g.threads["" + board + "." + threadID]) { + if (!((post = g.posts["" + board + "." + postID]) && context.thread === post.thread)) { return; } - post = g.posts["" + board + "." + postID]; if (isBacklink && Conf['Forward Hiding']) { $.addClass(post.nodes.root, 'forwarded'); return post.forwarded++ || (post.forwarded = 1); } }, - rm: function(quotelink, board, threadID, postID) { - var context, el, inline, post, root, _i, _len, _ref, _ref1; - root = QuoteInline.findRoot(quotelink, $.hasClass(quotelink, 'backlink')); + rm: function(quotelink, board, threadID, postID, context) { + var el, inlined, isBacklink, post, root, _ref; + isBacklink = $.hasClass(quotelink, 'backlink'); + root = QuoteInline.findRoot(quotelink, isBacklink); root = $.x("following-sibling::div[@id='i" + postID + "'][1]", root); $.rm(root); if (!(el = root.firstElementChild)) { @@ -1636,26 +1702,14 @@ } post = g.posts["" + board + "." + postID]; post.rmClone(el.dataset.clone); - context = Get.contextFromLink(quotelink); - if (Conf['Forward Hiding'] && context.thread === g.threads["" + board + "." + threadID] && $.hasClass(quotelink, 'backlink') && !--post.forwarded) { + if (Conf['Forward Hiding'] && isBacklink && context.thread === g.threads["" + board + "." + threadID] && !--post.forwarded) { delete post.forwarded; $.rmClass(post.nodes.root, 'forwarded'); } - _ref = $$('.inlined', el); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - inline = _ref[_i]; - _ref1 = Get.postDataFromLink(inline), board = _ref1.board, threadID = _ref1.threadID, postID = _ref1.postID; - root = QuoteInline.findRoot(inline, $.hasClass(inline, 'backlink')); - root = $.x("following-sibling::div[@id='i" + postID + "'][1]", root); - if (!(el = root.firstElementChild)) { - continue; - } - post = g.posts["" + board + "." + postID]; - post.rmClone(el.dataset.clone); - if (Conf['Forward Hiding'] && context.thread === g.threads["" + board + "." + threadID] && $.hasClass(inline, 'backlink') && !--post.forwarded) { - delete post.forwarded; - $.rmClass(post.nodes.root, 'forwarded'); - } + while (inlined = $('.inlined', el)) { + _ref = Get.postDataFromLink(inlined), board = _ref.board, threadID = _ref.threadID, postID = _ref.postID; + QuoteInline.rm(inlined, board, threadID, postID, context); + $.rmClass(inlined, 'inlined'); } } }; @@ -1798,7 +1852,7 @@ if (!(Conf['OP Backlinks'] || this.isReply)) { return; } - container = QuoteBacklink.getContainer("" + this.board + "." + this); + container = QuoteBacklink.getContainer(this.fullID); this.nodes.backlinkContainer = container; return $.add(this.nodes.info, container); }, @@ -1819,7 +1873,7 @@ }); }, node: function() { - var board, op, postID, quote, quotelinks, quotes, thread, _i, _j, _len, _len1, _ref, _ref1; + var board, op, postID, quote, quotelinks, quotes, _i, _j, _len, _len1, _ref; if (this.isClone && this.thread === this.context.thread) { return; } @@ -1827,20 +1881,19 @@ return; } quotelinks = this.nodes.quotelinks; - if (this.isClone && -1 < quotes.indexOf("" + this.board + "." + this.thread)) { + if (this.isClone && -1 < quotes.indexOf(this.fullID)) { for (_i = 0, _len = quotelinks.length; _i < _len; _i++) { quote = quotelinks[_i]; quote.textContent = quote.textContent.replace(QuoteOP.text, ''); } } - _ref = this.isClone ? this.context : this, board = _ref.board, thread = _ref.thread; - op = "" + board + "." + thread; + op = (this.isClone ? this.context : this).thread.fullID; if (!(-1 < quotes.indexOf(op))) { return; } for (_j = 0, _len1 = quotelinks.length; _j < _len1; _j++) { quote = quotelinks[_j]; - _ref1 = Get.postDataFromLink(quote), board = _ref1.board, postID = _ref1.postID; + _ref = Get.postDataFromLink(quote), board = _ref.board, postID = _ref.postID; if (("" + board + "." + postID) === op) { $.add(quote, $.tn(QuoteOP.text)); } @@ -2609,6 +2662,7 @@ function Thread(ID, board) { this.board = board; this.ID = +ID; + this.fullID = "" + this.board + "." + this.ID; this.posts = {}; g.threads["" + board + "." + this] = board.threads[this] = this; } @@ -2633,6 +2687,7 @@ that = {}; } this.ID = +root.id.slice(2); + this.fullID = "" + this.board + "." + this.ID; post = $('.post', root); info = $('.postInfo', post); this.nodes = { @@ -2786,7 +2841,7 @@ var file, index, info, inline, inlined, key, nodes, post, quotelink, root, val, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _ref4; this.origin = origin; this.context = context; - _ref = ['ID', 'board', 'thread', 'info', 'quotes', 'isReply']; + _ref = ['ID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply']; for (_i = 0, _len = _ref.length; _i < _len; _i++) { key = _ref[_i]; this[key] = origin[key]; @@ -2968,6 +3023,13 @@ settings.disableAll = true; localStorage.setItem('4chan-settings', JSON.stringify(settings)); } + if (Conf['Resurrect Quotes']) { + try { + Quotify.init(); + } catch (err) { + $.log(err, 'Resurrect Quotes'); + } + } if (Conf['Thread Hiding']) { try { ThreadHiding.init(); @@ -2982,12 +3044,10 @@ $.log(err, 'Reply Hiding'); } } - if (Conf['Resurrect Quotes']) { - try { - Quotify.init(); - } catch (err) { - $.log(err, 'Resurrect Quotes'); - } + try { + Recursive.init(); + } catch (err) { + $.log(err, 'Recursive'); } if (Conf['Quote Inline']) { try { @@ -3134,14 +3194,15 @@ callback.cb.call(nodes[i]); } } catch (err) { - $.log(callback.name, 'crashed. error:', err.message, nodes[i], err); + $.log(callback.name, 'crashed. error:', err.message, nodes[i]); + $.log(err.stack); } } }, 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.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}" + 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[hidden] {\n display: none !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 & reply hiding */\n.hide-thread-button,\n.hide-reply-button {\n float: left;\n margin-right: 2px;\n}\n.stub ~ .sideArrows,\n.stub ~ .hide-reply-button,\n.stub ~ .post {\n display: none !important;\n}" }; Main.init(); diff --git a/css/style.css b/css/style.css index 2940258de..794a87845 100644 --- a/css/style.css +++ b/css/style.css @@ -24,6 +24,9 @@ a[href="javascript:;"] { .post { overflow: visible !important; } +[hidden] { + display: none !important; +} /* fixed, z-index */ #qp, #ihover, @@ -160,13 +163,14 @@ body.fourchan_x { padding-bottom: 16px; } -/* thread hiding */ -.opContainer > .hide-thread-button { - float: left; -} - -/* reply hiding */ -.replyContainer > .hide-reply-button { +/* thread & reply hiding */ +.hide-thread-button, +.hide-reply-button { float: left; margin-right: 2px; } +.stub ~ .sideArrows, +.stub ~ .hide-reply-button, +.stub ~ .post { + display: none !important; +} diff --git a/src/config.coffee b/src/config.coffee index c924f4c0e..4d63066b4 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -14,7 +14,7 @@ Config = Filtering: 'Anonymize': [false, 'Turn everyone Anonymous.'] 'Filter': [true, 'Self-moderation placebo.'] - 'Recursive Filtering': [true, 'Filter replies of filtered posts, recursively.'] + 'Recursive Hiding': [true, 'Filter replies of filtered posts, recursively.'] 'Reply Hiding': [true, 'Hide single replies.'] 'Thread Hiding': [true, 'Hide entire threads.'] 'Stubs': [true, 'Make stubs of hidden threads / replies.'] diff --git a/src/features.coffee b/src/features.coffee index b4320e3a8..a5bccc5da 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.makeButton @, '-' + $.prepend @posts[@].nodes.root, ThreadHiding.makeButton @, 'hide' getHiddenThreads: -> hiddenThreads = $.get "hiddenThreads.#{g.BOARD}" @@ -47,10 +47,10 @@ ThreadHiding = hiddenThreads.threads = threads $.set "hiddenThreads.#{g.BOARD}", hiddenThreads - makeButton: (thread, sign) -> + makeButton: (thread, type) -> a = $.el 'a', - className: 'hide-thread-button' - innerHTML: "[ #{sign} ] " + className: "#{type}-thread-button" + innerHTML: "[ #{if type is 'hide' then '-' else '+'} ]" href: 'javascript:;' $.on a, 'click', -> ThreadHiding.toggle thread a @@ -91,9 +91,10 @@ ThreadHiding = else $('.nameBlock', op.nodes.info).textContent - a = ThreadHiding.makeButton thread, '+' - $.add a, $.tn "#{opInfo} (#{numReplies})" - thread.stub = $.el 'div' + a = ThreadHiding.makeButton thread, 'show' + $.add a, $.tn " #{opInfo} (#{numReplies})" + thread.stub = $.el 'div', + className: 'stub' $.add thread.stub, a # if Conf['Menu'] # $.add thread.stub, [$.tn(' '), Menu.makeButton()] @@ -120,7 +121,7 @@ ReplyHiding = if thread = ReplyHiding.hiddenPosts.threads[@thread] if @ID in thread ReplyHiding.hide @ - $.replace $('.sideArrows', @nodes.root), ReplyHiding.makeButton @, '-' + $.replace $('.sideArrows', @nodes.root), ReplyHiding.makeButton @, 'hide' getHiddenPosts: -> hiddenPosts = $.get "hiddenPosts.#{g.BOARD}" @@ -151,10 +152,10 @@ ReplyHiding = hiddenPosts.threads = threads $.set "hiddenPosts.#{g.BOARD}", hiddenPosts - makeButton: (post, sign) -> + makeButton: (post, type) -> a = $.el 'a', - className: 'hide-reply-button' - innerHTML: "[ #{sign} ]" + className: "#{type}-reply-button" + innerHTML: "[ #{if type is 'hide' then '-' else '+'} ]" href: 'javascript:;' $.on a, 'click', -> ReplyHiding.toggle post a @@ -162,49 +163,86 @@ ReplyHiding = 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 + if (index = thread.indexOf post.ID) > -1 + # Was manually hidden, not by recursion/filtering. + if thread.length is 1 + delete hiddenPosts.threads[post.thread] + else + thread.splice index, 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']) -> + hide: (post, makeStub=Conf['Stubs'], hideRecursively=Conf['Recursive Hiding']) -> return if post.isHidden - post.nodes.root.hidden = post.isHidden = true + post.isHidden = true + + Recursive.hide post, makeStub, true if hideRecursively + + for quotelink in Get.allQuotelinksLinkingTo post + $.addClass quotelink, 'filtered' return unless makeStub - a = ReplyHiding.makeButton post, '+' + a = ReplyHiding.makeButton post, 'show' postInfo = if Conf['Anonymize'] 'Anonymous' else $('.nameBlock', post.nodes.info).textContent $.add a, $.tn " #{postInfo}" - post.stub = $.el 'div' - $.add post.stub, a + post.nodes.stub = $.el 'div', + className: 'stub' + $.add post.nodes.stub, a # if Conf['Menu'] - # $.add post.stub, [$.tn(' '), Menu.makeButton()] - $.before post.nodes.root, post.stub + # $.add post.nodes.stub, [$.tn(' '), Menu.makeButton()] + $.prepend post.nodes.root, post.nodes.stub show: (post) -> - if post.stub - $.rm post.stub - delete post.stub - post.nodes.root.hidden = post.isHidden = false + if post.nodes.stub + $.rm post.nodes.stub + delete post.nodes.stub + post.isHidden = false + for quotelink in Get.allQuotelinksLinkingTo post + $.rmClass quotelink, 'filtered' + return + +Recursive = + toHide: [] + init: -> + Post::callbacks.push + name: 'Recursive' + cb: @node + + node: -> + return if @isClone + # In fetched posts: + # - Strike-through quotelinks + # - Hide recursively + for quote in @quotes + if quote in Recursive.toHide + ReplyHiding.hide @, !!g.posts[quote].nodes.stub, true + for quotelink in @nodes.quotelinks + {board, postID} = Get.postDataFromLink quotelink + if g.posts["#{board}.#{postID}"]?.isHidden + $.addClass quotelink, 'filtered' + return + + hide: (post, makeStub) -> + {fullID} = post + Recursive.toHide.push fullID + for ID, post of g.posts + continue if !post.isReply or post.isHidden + for quote in post.quotes + if quote is fullID + ReplyHiding.hide post, makeStub, true + break + return Redirect = image: (board, filename) -> @@ -239,7 +277,7 @@ Redirect = # https://github.com/eksopl/fuuka/issues/27 to: (data) -> {board} = data - switch "#{board}" + switch board when 'a', 'co', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'v', 'vg', 'wsg', 'dev', 'foolz' url = Redirect.path '//archive.foolz.us', 'foolfuuka', data when 'u', 'kuku' @@ -579,13 +617,12 @@ Get = 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 + if -1 isnt quoterPost.quotes.indexOf post.fullID for quoterPost in [quoterPost].concat quoterPost.clones quotelinks.push.apply quotelinks, quoterPost.nodes.quotelinks # Second: @@ -595,7 +632,7 @@ Get = # get all of their backlinks. if Conf['Quote Backlinks'] for quote in post.quotes - quotedPost = g.posts[quote] + continue unless quotedPost = g.posts[quote] for quotedPost in [quotedPost].concat quotedPost.clones quotelinks.push.apply quotelinks, Array::slice.call quotedPost.nodes.backlinks # Third: @@ -854,11 +891,12 @@ QuoteInline = return if e.shiftKey or e.altKey or e.ctrlKey or e.metaKey or e.button isnt 0 e.preventDefault() {board, threadID, postID} = Get.postDataFromLink @ + context = Get.contextFromLink @ if $.hasClass @, 'inlined' - QuoteInline.rm @, board, threadID, postID + QuoteInline.rm @, board, threadID, postID, context else return if $.x "ancestor::div[@id='p#{postID}']", @ - QuoteInline.add @, board, threadID, postID + QuoteInline.add @, board, threadID, postID, context @classList.toggle 'inlined' findRoot: (quotelink, isBacklink) -> @@ -866,17 +904,16 @@ QuoteInline = quotelink.parentNode.parentNode else $.x 'ancestor-or-self::*[parent::blockquote][1]', quotelink - add: (quotelink, board, threadID, postID) -> + add: (quotelink, board, threadID, postID, context) -> isBacklink = $.hasClass quotelink, 'backlink' inline = $.el 'div', id: "i#{postID}" className: 'inline' - context = Get.contextFromLink quotelink $.after QuoteInline.findRoot(quotelink, isBacklink), inline Get.postClone board, threadID, postID, inline, context - return unless context.thread is g.threads["#{board}.#{threadID}"] - post = g.posts["#{board}.#{postID}"] + return unless (post = g.posts["#{board}.#{postID}"]) and + context.thread is post.thread # Hide forward post if it's a backlink of a post in this thread. # Will only unhide if there's no inlined backlinks of it anymore. @@ -890,9 +927,10 @@ QuoteInline = # Unread.replies.splice i, 1 # Unread.update true - rm: (quotelink, board, threadID, postID) -> + rm: (quotelink, board, threadID, postID, context) -> + isBacklink = $.hasClass quotelink, 'backlink' # Select the corresponding inlined quote, and remove it. - root = QuoteInline.findRoot quotelink, $.hasClass quotelink, 'backlink' + root = QuoteInline.findRoot quotelink, isBacklink root = $.x "following-sibling::div[@id='i#{postID}'][1]", root $.rm root @@ -903,31 +941,19 @@ QuoteInline = post = g.posts["#{board}.#{postID}"] post.rmClone el.dataset.clone - context = Get.contextFromLink quotelink - # Decrease forward count and unhide. if Conf['Forward Hiding'] and + isBacklink and context.thread is g.threads["#{board}.#{threadID}"] and - $.hasClass(quotelink, 'backlink') and not --post.forwarded delete post.forwarded $.rmClass post.nodes.root, 'forwarded' # Repeat. - for inline in $$ '.inlined', el - {board, threadID, postID} = Get.postDataFromLink inline - root = QuoteInline.findRoot inline, $.hasClass inline, 'backlink' - root = $.x "following-sibling::div[@id='i#{postID}'][1]", root - continue unless el = root.firstElementChild - post = g.posts["#{board}.#{postID}"] - post.rmClone el.dataset.clone - - if Conf['Forward Hiding'] and - context.thread is g.threads["#{board}.#{threadID}"] and - $.hasClass(inline, 'backlink') and - not --post.forwarded - delete post.forwarded - $.rmClass post.nodes.root, 'forwarded' + while inlined = $ '.inlined', el + {board, threadID, postID} = Get.postDataFromLink inlined + QuoteInline.rm inlined, board, threadID, postID, context + $.rmClass inlined, 'inlined' return QuotePreview = @@ -1032,7 +1058,7 @@ QuoteBacklink = return # Don't backlink the OP. return unless Conf['OP Backlinks'] or @isReply - container = QuoteBacklink.getContainer "#{@board}.#{@}" + container = QuoteBacklink.getContainer @fullID @nodes.backlinkContainer = container $.add @nodes.info, container getContainer: (id) -> @@ -1054,12 +1080,11 @@ QuoteOP = quotelinks = @nodes.quotelinks # rm (OP) from cross-thread quotes. - if @isClone and -1 < quotes.indexOf "#{@board}.#{@thread}" + if @isClone and -1 < quotes.indexOf @fullID for quote in quotelinks quote.textContent = quote.textContent.replace QuoteOP.text, '' - {board, thread} = if @isClone then @context else @ - op = "#{board}.#{thread}" + op = (if @isClone then @context else @).thread.fullID # add (OP) to quotes quoting this context's OP. return unless -1 < quotes.indexOf op for quote in quotelinks diff --git a/src/main.coffee b/src/main.coffee index d41331a93..e37b60a64 100644 --- a/src/main.coffee +++ b/src/main.coffee @@ -12,8 +12,9 @@ class Thread toString: -> @ID constructor: (ID, @board) -> - @ID = +ID - @posts = {} + @ID = +ID + @fullID = "#{@board}.#{@ID}" + @posts = {} # XXX Can't check when parsing single posts # move to Post constructor? unless @isReply @@ -28,7 +29,8 @@ class Post toString: -> @ID constructor: (root, @thread, @board, that={}) -> - @ID = +root.id[2..] + @ID = +root.id[2..] + @fullID = "#{@board}.#{@ID}" post = $ '.post', root info = $ '.postInfo', post @@ -169,7 +171,7 @@ class Post class Clone extends Post constructor: (@origin, @context) -> - for key in ['ID', 'board', 'thread', 'info', 'quotes', 'isReply'] + for key in ['ID', 'fullID', 'board', 'thread', 'info', 'quotes', 'isReply'] # Copy or point to the origin's key value. @[key] = origin[key] @@ -313,6 +315,13 @@ Main = settings.disableAll = true localStorage.setItem '4chan-settings', JSON.stringify settings + if Conf['Resurrect Quotes'] + try + Quotify.init() + catch err + # XXX handle error + $.log err, 'Resurrect Quotes' + if Conf['Thread Hiding'] try ThreadHiding.init() @@ -327,12 +336,11 @@ Main = # XXX handle error $.log err, 'Reply Hiding' - if Conf['Resurrect Quotes'] - try - Quotify.init() - catch err - # XXX handle error - $.log err, 'Resurrect Quotes' + try + Recursive.init() + catch err + # XXX handle error + $.log err, 'Recursive' if Conf['Quote Inline'] try @@ -466,7 +474,8 @@ Main = callback.cb.call nodes[i] catch err # XXX handle error if notify - $.log callback.name, 'crashed. error:', err.message, nodes[i], err + $.log callback.name, 'crashed. error:', err.message, nodes[i] + $.log err.stack return settings: ->