From d8b0b74df07d4d9def01dc2d4406d23580b6a51d Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Sat, 26 Jan 2013 04:24:03 +0100 Subject: [PATCH] Add Thread Hiding in the Menu, with stub setting. Add an option to toggle Thread/Reply Hiding Buttons. Save hiding settings of individual threads, like if we want to see its stub or not when we reload the page. Add the menu buttons in stubs. Make the open function for menu entries optional. --- 4chan_x.user.js | 146 +++++++++++++++++++++++++++++++++++--------- changelog | 2 + src/config.coffee | 3 +- src/features.coffee | 105 +++++++++++++++++++++++-------- src/main.coffee | 11 +++- 5 files changed, 210 insertions(+), 57 deletions(-) diff --git a/4chan_x.user.js b/4chan_x.user.js index 0dc70fc38..1f0c22361 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-25 +/* 4chan X Alpha - Version 3.0.0 - 2013-01-26 * http://mayhemydg.github.com/4chan-x/ * * Copyright (c) 2009-2011 James Campos @@ -66,8 +66,9 @@ 'Anonymize': [false, 'Turn everyone Anonymous.'], 'Filter': [true, 'Self-moderation placebo.'], 'Recursive Hiding': [true, 'Filter replies of filtered posts, recursively.'], - 'Reply Hiding': [true, 'Hide single replies.'], 'Thread Hiding': [true, 'Hide entire threads.'], + 'Reply Hiding': [true, 'Hide single replies.'], + 'Thread/Reply Hiding Buttons': [true, 'Make buttons to hide threads / replies, in addition to menu links.'], 'Stubs': [true, 'Make stubs of hidden threads / replies.'] }, Imaging: { @@ -692,9 +693,12 @@ }); }, node: function() { - var _ref; - if (_ref = this.ID, __indexOf.call(ThreadHiding.hiddenThreads.threads, _ref) >= 0) { - ThreadHiding.hide(this); + var data; + if (data = ThreadHiding.hiddenThreads.threads[this]) { + ThreadHiding.hide(this, data.makeStub); + } + if (!Conf['Thread/Reply Hiding Buttons']) { + return; } return $.prepend(this.posts[this].nodes.root, ThreadHiding.makeButton(this, 'hide')); }, @@ -703,7 +707,7 @@ hiddenThreads = $.get("hiddenThreads." + g.BOARD); if (!hiddenThreads) { hiddenThreads = { - threads: [], + threads: {}, lastChecked: Date.now() }; $.set("hiddenThreads." + g.BOARD, hiddenThreads); @@ -711,9 +715,21 @@ return ThreadHiding.hiddenThreads = hiddenThreads; }, syncFromCatalog: function() { - var hiddenThreadsOnCatalog; + var hiddenThreadsOnCatalog, threadID, threads; hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; - ThreadHiding.hiddenThreads.threads = Object.keys(hiddenThreadsOnCatalog).map(Number); + threads = ThreadHiding.hiddenThreads.threads; + for (threadID in hiddenThreadsOnCatalog) { + if (threadID in threads) { + continue; + } + threads[threadID] = {}; + } + for (threadID in threads) { + if (threadID in threads) { + continue; + } + delete threads[threadID]; + } return $.set("hiddenThreads." + g.BOARD, ThreadHiding.hiddenThreads); }, clean: function() { @@ -724,22 +740,22 @@ if (lastChecked > now - $.DAY) { return; } - if (!hiddenThreads.threads.length) { + if (!Object.keys(hiddenThreads.threads).length) { $.set("hiddenThreads." + g.BOARD, hiddenThreads); return; } return $.ajax("//api.4chan.org/" + g.BOARD + "/catalog.json", { onload: function() { - var obj, thread, threads, _i, _j, _len, _len1, _ref, _ref1, _ref2; - threads = []; + 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 (_ref2 = thread.no, __indexOf.call(hiddenThreads.threads, _ref2) >= 0) { - threads.push(thread.no); + if (thread.no in hiddenThreads.threads) { + threads[thread.no] = hiddenThreads.threads[thread.no]; } } } @@ -748,6 +764,53 @@ } }); }, + menu: { + init: function() { + var apply, div, option; + if (g.VIEW !== 'index') { + return; + } + div = $.el('div', { + className: 'hide-thread-link', + textContent: 'Hide thread' + }); + option = $.el('label', { + innerHTML: " Make stub" + }); + apply = $.el('a', { + textContent: 'Apply', + href: 'javascript:;' + }); + $.on(apply, 'click', ThreadHiding.menu.hide); + return Menu.addEntry({ + el: div, + open: function(post) { + var thread; + thread = post.thread; + if (post.isReply || thread.isHidden) { + return false; + } + ThreadHiding.menu.thread = thread; + return true; + }, + children: [ + { + el: option + }, { + el: apply + } + ] + }); + }, + hide: function() { + var makeStub, thread; + makeStub = $('input', this.parentNode).checked; + thread = ThreadHiding.menu.thread; + ThreadHiding.hide(thread, makeStub); + ThreadHiding.saveHiddenState(thread, makeStub); + return Menu.close(); + } + }, makeButton: function(thread, type) { var a; a = $.el('a', { @@ -760,22 +823,30 @@ }); return a; }, - toggle: function(thread) { + saveHiddenState: function(thread, makeStub) { var hiddenThreads, hiddenThreadsCatalog; hiddenThreads = ThreadHiding.getHiddenThreads(); hiddenThreadsCatalog = JSON.parse(localStorage.getItem("4chan-hide-t-" + g.BOARD)) || {}; if (thread.isHidden) { - ThreadHiding.show(thread); - hiddenThreads.threads.splice(hiddenThreads.threads.indexOf(thread.ID), 1); - delete hiddenThreadsCatalog[thread]; - } else { - ThreadHiding.hide(thread); - hiddenThreads.threads.push(thread.ID); + hiddenThreads.threads[thread] = { + makeStub: makeStub + }; hiddenThreadsCatalog[thread] = true; + } else { + delete hiddenThreads.threads[thread]; + delete hiddenThreadsCatalog[thread]; } $.set("hiddenThreads." + g.BOARD, hiddenThreads); return localStorage.setItem("4chan-hide-t-" + g.BOARD, JSON.stringify(hiddenThreadsCatalog)); }, + toggle: function(thread) { + if (thread.isHidden) { + ThreadHiding.show(thread); + } else { + ThreadHiding.hide(thread); + } + return ThreadHiding.saveHiddenState(thread); + }, hide: function(thread, makeStub) { var a, numReplies, op, opInfo, span, threadRoot; if (makeStub == null) { @@ -804,6 +875,9 @@ className: 'stub' }); $.add(thread.stub, a); + if (Conf['Menu']) { + $.add(thread.stub, [$.tn(' '), Menu.makeButton(op)]); + } return $.before(threadRoot, thread.stub); }, show: function(thread) { @@ -836,6 +910,9 @@ ReplyHiding.hide(this); } } + if (!Conf['Thread/Reply Hiding Buttons']) { + return; + } return $.replace($('.sideArrows', this.nodes.root), ReplyHiding.makeButton(this, 'hide')); }, getHiddenPosts: function() { @@ -946,6 +1023,9 @@ className: 'stub' }); $.add(post.nodes.stub, a); + if (Conf['Menu']) { + $.add(post.nodes.stub, [$.tn(' '), Menu.makeButton(post)]); + } return $.prepend(post.nodes.root, post.nodes.stub); }, show: function(post) { @@ -1027,13 +1107,11 @@ }, node: function() { var a; + a = Menu.makeButton(this); if (this.isClone) { - a = $('.menu-button', this.nodes.info); - a.setAttribute('data-clone', true); - $.on(a, 'click', Menu.toggle); + $.replace($('.menu-button', this.nodes.info), a); return; } - a = Menu.makeButton(this); return $.add(this.nodes.info, [$.tn('\u00A0'), a]); }, makeButton: function(post) { @@ -1044,6 +1122,9 @@ href: 'javascript:;' }); a.setAttribute('data-postid', post.fullID); + if (post.isClone) { + a.setAttribute('data-clone', true); + } $.on(a, 'click', Menu.toggle); return a; }, @@ -1097,8 +1178,10 @@ }, insertEntry: function(entry, parent, post) { var child, submenu, _i, _len, _ref; - if (!entry.open(post)) { - return; + if (typeof entry.open === 'function') { + if (!entry.open(post)) { + return; + } } $.add(parent, entry.el); if (!entry.children) { @@ -1166,7 +1249,7 @@ }, focus: function(entry) { var bottom, eRect, focused, left, right, sRect, style, submenu, top, _i, _len, _ref; - if (focused = $.x('parent::*/child::*[contains(@class,"focused")]', entry)) { + while (focused = $.x('parent::*/child::*[contains(@class,"focused")]', entry)) { $.rmClass(focused, 'focused'); } _ref = $$('.focused', entry); @@ -3503,7 +3586,14 @@ try { ReportLink.init(); } catch (err) { - $.log(err, 'Report Link', err.stack); + $.log(err, 'Report Link'); + } + } + if (Conf['Thread Hiding']) { + try { + ThreadHiding.menu.init(); + } catch (err) { + $.log(err, 'Thread Hiding - Menu'); } } if (Conf['Delete Link']) { diff --git a/changelog b/changelog index d23d6883a..f6e40629e 100644 --- a/changelog +++ b/changelog @@ -2,6 +2,8 @@ alpha - Mayhem Added touch and multi-touch support for dragging windows. The Thread Updater will pause when offline, and resume when online. + Added Thread & Post Hiding in the Menu, with individual settings. + Thread & Post Hiding Buttons can now be disabled in the settings. Recursive Hiding will be automatically applied when manually hiding a post. Fix Chrome's install warning that 4chan X would execute on all domains. Fix Quote Backlinks not affecting inlined quotes. diff --git a/src/config.coffee b/src/config.coffee index 4d63066b4..84dfed134 100644 --- a/src/config.coffee +++ b/src/config.coffee @@ -15,8 +15,9 @@ Config = 'Anonymize': [false, 'Turn everyone Anonymous.'] 'Filter': [true, 'Self-moderation placebo.'] 'Recursive Hiding': [true, 'Filter replies of filtered posts, recursively.'] - 'Reply Hiding': [true, 'Hide single replies.'] 'Thread Hiding': [true, 'Hide entire threads.'] + 'Reply Hiding': [true, 'Hide single replies.'] + 'Thread/Reply Hiding Buttons': [true, 'Make buttons to hide threads / replies, in addition to menu links.'] 'Stubs': [true, 'Make stubs of hidden threads / replies.'] Imaging: 'Auto-GIF': [false, 'Animate GIF thumbnails.'] diff --git a/src/features.coffee b/src/features.coffee index 46ea4c976..43b7d4d1f 100644 --- a/src/features.coffee +++ b/src/features.coffee @@ -9,15 +9,16 @@ ThreadHiding = cb: @node node: -> - if @ID in ThreadHiding.hiddenThreads.threads - ThreadHiding.hide @ + if data = ThreadHiding.hiddenThreads.threads[@] + ThreadHiding.hide @, data.makeStub + return unless Conf['Thread/Reply Hiding Buttons'] $.prepend @posts[@].nodes.root, ThreadHiding.makeButton @, 'hide' getHiddenThreads: -> hiddenThreads = $.get "hiddenThreads.#{g.BOARD}" unless hiddenThreads hiddenThreads = - threads: [] + threads: {} lastChecked: Date.now() $.set "hiddenThreads.#{g.BOARD}", hiddenThreads ThreadHiding.hiddenThreads = hiddenThreads @@ -25,28 +26,72 @@ ThreadHiding = syncFromCatalog: -> # Sync hidden threads from the catalog into the index. hiddenThreadsOnCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {} - ThreadHiding.hiddenThreads.threads = Object.keys(hiddenThreadsOnCatalog).map Number + {threads} = ThreadHiding.hiddenThreads + + # Add threads that were hidden in the catalog. + for threadID of hiddenThreadsOnCatalog + continue if threadID of threads + threads[threadID] = {} + + # Remove threads that were un-hidden in the catalog. + for threadID of threads + continue if threadID of threads + delete threads[threadID] + $.set "hiddenThreads.#{g.BOARD}", ThreadHiding.hiddenThreads clean: -> {hiddenThreads} = ThreadHiding - {lastChecked} = hiddenThreads + {lastChecked} = hiddenThreads hiddenThreads.lastChecked = now = Date.now() return if lastChecked > now - $.DAY - unless hiddenThreads.threads.length + unless Object.keys(hiddenThreads.threads).length $.set "hiddenThreads.#{g.BOARD}", hiddenThreads return $.ajax "//api.4chan.org/#{g.BOARD}/catalog.json", onload: -> - threads = [] + threads = {} for obj in JSON.parse @response for thread in obj.threads - threads.push thread.no if thread.no in hiddenThreads.threads + if thread.no of hiddenThreads.threads + threads[thread.no] = hiddenThreads.threads[thread.no] hiddenThreads.threads = threads $.set "hiddenThreads.#{g.BOARD}", hiddenThreads + menu: + init: -> + return if g.VIEW isnt 'index' + div = $.el 'div', + className: 'hide-thread-link' + textContent: 'Hide thread' + + option = $.el 'label', + innerHTML: " Make stub" + + apply = $.el 'a', + textContent: 'Apply' + href: 'javascript:;' + $.on apply, 'click', ThreadHiding.menu.hide + + Menu.addEntry + el: div + open: (post) -> + {thread} = post + if post.isReply or thread.isHidden + return false + ThreadHiding.menu.thread = thread + true + children: [{el: option}, {el: apply}] + hide: -> + makeStub = $('input', @parentNode).checked + {thread} = ThreadHiding.menu + ThreadHiding.hide thread, makeStub + ThreadHiding.saveHiddenState thread, makeStub + # need to save if we made a stub or not + Menu.close() + makeButton: (thread, type) -> a = $.el 'a', className: "#{type}-thread-button" @@ -55,21 +100,26 @@ ThreadHiding = $.on a, 'click', -> ThreadHiding.toggle thread a - toggle: (thread) -> + saveHiddenState: (thread, makeStub) -> # Get fresh hidden threads. - hiddenThreads = ThreadHiding.getHiddenThreads() + hiddenThreads = ThreadHiding.getHiddenThreads() hiddenThreadsCatalog = JSON.parse(localStorage.getItem "4chan-hide-t-#{g.BOARD}") or {} if thread.isHidden - ThreadHiding.show thread - hiddenThreads.threads.splice hiddenThreads.threads.indexOf(thread.ID), 1 - delete hiddenThreadsCatalog[thread] + hiddenThreads.threads[thread] = {makeStub} + hiddenThreadsCatalog[thread] = true else - ThreadHiding.hide thread - hiddenThreads.threads.push thread.ID - hiddenThreadsCatalog[thread] = true + delete hiddenThreads.threads[thread] + delete hiddenThreadsCatalog[thread] $.set "hiddenThreads.#{g.BOARD}", hiddenThreads localStorage.setItem "4chan-hide-t-#{g.BOARD}", JSON.stringify hiddenThreadsCatalog + toggle: (thread) -> + if thread.isHidden + ThreadHiding.show thread + else + ThreadHiding.hide thread + ThreadHiding.saveHiddenState thread + hide: (thread, makeStub=Conf['Stubs']) -> return if thread.hidden op = thread.posts[thread] @@ -96,8 +146,8 @@ ThreadHiding = thread.stub = $.el 'div', className: 'stub' $.add thread.stub, a - # if Conf['Menu'] - # $.add thread.stub, [$.tn(' '), Menu.makeButton()] + if Conf['Menu'] + $.add thread.stub, [$.tn(' '), Menu.makeButton op] $.before threadRoot, thread.stub show: (thread) -> @@ -121,6 +171,7 @@ ReplyHiding = if thread = ReplyHiding.hiddenPosts.threads[@thread] if @ID in thread ReplyHiding.hide @ + return unless Conf['Thread/Reply Hiding Buttons'] $.replace $('.sideArrows', @nodes.root), ReplyHiding.makeButton @, 'hide' getHiddenPosts: -> @@ -199,8 +250,8 @@ ReplyHiding = post.nodes.stub = $.el 'div', className: 'stub' $.add post.nodes.stub, a - # if Conf['Menu'] - # $.add post.nodes.stub, [$.tn(' '), Menu.makeButton()] + if Conf['Menu'] + $.add post.nodes.stub, [$.tn(' '), Menu.makeButton post] $.prepend post.nodes.root, post.nodes.stub show: (post) -> @@ -254,12 +305,10 @@ Menu = cb: @node node: -> - if @isClone - a = $ '.menu-button', @nodes.info - a.setAttribute 'data-clone', true - $.on a, 'click', Menu.toggle - return a = Menu.makeButton @ + if @isClone + $.replace $('.menu-button', @nodes.info), a + return $.add @nodes.info, [$.tn('\u00A0'), a] makeButton: (post) -> @@ -268,6 +317,7 @@ Menu = innerHTML: '[]' href: 'javascript:;' a.setAttribute 'data-postid', post.fullID + a.setAttribute 'data-clone', true if post.isClone $.on a, 'click', Menu.toggle a @@ -329,7 +379,8 @@ Menu = menu.focus() insertEntry: (entry, parent, post) -> - return unless entry.open post + if typeof entry.open is 'function' + return unless entry.open post $.add parent, entry.el return unless entry.children @@ -379,7 +430,7 @@ Menu = e.stopPropagation() focus: (entry) -> - if focused = $.x 'parent::*/child::*[contains(@class,"focused")]', entry + while focused = $.x 'parent::*/child::*[contains(@class,"focused")]', entry $.rmClass focused, 'focused' for focused in $$ '.focused', entry $.rmClass focused, 'focused' diff --git a/src/main.coffee b/src/main.coffee index 5847b18cb..85a7fd72e 100644 --- a/src/main.coffee +++ b/src/main.coffee @@ -286,6 +286,7 @@ Main = className: 'reply' innerHTML: '
' $.ready Main.initHeaderReady + initHeaderReady: -> header = Main.header $.prepend d.body, header @@ -354,7 +355,14 @@ Main = ReportLink.init() catch err # XXX handle error - $.log err, 'Report Link', err.stack + $.log err, 'Report Link' + + if Conf['Thread Hiding'] + try + ThreadHiding.menu.init() + catch err + # XXX handle error + $.log err, 'Thread Hiding - Menu' if Conf['Delete Link'] try @@ -469,6 +477,7 @@ Main = $.log err, 'Thread Updater' $.ready Main.initFeaturesReady + initFeaturesReady: -> if d.title is '4chan - 404 Not Found' if Conf['404 Redirect'] and g.VIEW is 'thread'