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'