4chan-x/src/Filtering/ThreadHiding.js
2023-04-30 13:36:40 +02:00

288 lines
8.4 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Callbacks from "../classes/Callbacks";
import DataBoard from "../classes/DataBoard";
import Thread from "../classes/Thread";
import Index from "../General/Index";
import UI from "../General/UI";
import { g, Conf, d, doc } from "../globals/globals";
import Main from "../main/Main";
import Menu from "../Menu/Menu";
import $ from "../platform/$";
import $$ from "../platform/$$";
import { dict } from "../platform/helpers";
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
var ThreadHiding = {
init() {
if (!['index', 'catalog'].includes(g.VIEW) || (!Conf['Thread Hiding Buttons'] && !(Conf['Menu'] && Conf['Thread Hiding Link']) && !Conf['JSON Index'])) { return; }
this.db = new DataBoard('hiddenThreads');
if (g.VIEW === 'catalog') { return this.catalogWatch(); }
this.catalogSet(g.BOARD);
$.on(d, 'IndexRefreshInternal', this.onIndexRefresh);
if (Conf['Thread Hiding Buttons']) {
$.addClass(doc, 'thread-hide');
}
return Callbacks.Post.push({
name: 'Thread Hiding',
cb: this.node
});
},
catalogSet(board) {
if (!$.hasStorage || (g.SITE.software !== 'yotsuba')) { return; }
const hiddenThreads = ThreadHiding.db.get({
boardID: board.ID,
defaultValue: dict()
});
for (var threadID in hiddenThreads) { hiddenThreads[threadID] = true; }
return localStorage.setItem(`4chan-hide-t-${board}`, JSON.stringify(hiddenThreads));
},
catalogWatch() {
if (!$.hasStorage || (g.SITE.software !== 'yotsuba')) { return; }
this.hiddenThreads = JSON.parse(localStorage.getItem(`4chan-hide-t-${g.BOARD}`)) || {};
return Main.ready(() => // 4chan's catalog sets the style to "display: none;" when hiding or unhiding a thread.
new MutationObserver(ThreadHiding.catalogSave).observe($.id('threads'), {
attributes: true,
subtree: true,
attributeFilter: ['style']
}));
},
catalogSave() {
let threadID;
const hiddenThreads2 = JSON.parse(localStorage.getItem(`4chan-hide-t-${g.BOARD}`)) || {};
for (threadID in hiddenThreads2) {
if (!$.hasOwn(ThreadHiding.hiddenThreads, threadID)) {
ThreadHiding.db.set({
boardID: g.BOARD.ID,
threadID,
val: {makeStub: Conf['Stubs']}});
}
}
for (threadID in ThreadHiding.hiddenThreads) {
if (!$.hasOwn(hiddenThreads2, threadID)) {
ThreadHiding.db.delete({
boardID: g.BOARD.ID,
threadID
});
}
}
return ThreadHiding.hiddenThreads = hiddenThreads2;
},
isHidden(boardID, threadID) {
return !!(ThreadHiding.db && ThreadHiding.db.get({boardID, threadID}));
},
node() {
let data;
if (this.isReply || this.isClone || this.isFetchedQuote) { return; }
if (Conf['Thread Hiding Buttons']) {
$.prepend(this.nodes.root, ThreadHiding.makeButton(this.thread, 'hide'));
}
if (data = ThreadHiding.db.get({boardID: this.board.ID, threadID: this.ID})) {
return ThreadHiding.hide(this.thread, data.makeStub);
}
},
onIndexRefresh() {
return g.BOARD.threads.forEach(function(thread) {
const {root} = thread.nodes;
if (thread.isHidden && thread.stub && !root.contains(thread.stub)) {
return ThreadHiding.makeStub(thread, root);
}
});
},
menu: {
init() {
if ((g.VIEW !== 'index') || !Conf['Menu'] || !Conf['Thread Hiding Link']) { return; }
let div = $.el('div', {
className: 'hide-thread-link',
textContent: 'Hide'
}
);
const apply = $.el('a', {
textContent: 'Apply',
href: 'javascript:;'
}
);
$.on(apply, 'click', ThreadHiding.menu.hide);
const makeStub = UI.checkbox('Stubs', 'Make stub');
Menu.menu.addEntry({
el: div,
order: 20,
open({thread, isReply}) {
if (isReply || thread.isHidden || (Conf['JSON Index'] && (Conf['Index Mode'] === 'catalog'))) {
return false;
}
ThreadHiding.menu.thread = thread;
return true;
},
subEntries: [{el: apply}, {el: makeStub}]});
div = $.el('a', {
className: 'show-thread-link',
textContent: 'Show',
href: 'javascript:;'
}
);
$.on(div, 'click', ThreadHiding.menu.show);
Menu.menu.addEntry({
el: div,
order: 20,
open({thread, isReply}) {
if (isReply || !thread.isHidden || (Conf['JSON Index'] && (Conf['Index Mode'] === 'catalog'))) {
return false;
}
ThreadHiding.menu.thread = thread;
return true;
}
});
const hideStubLink = $.el('a', {
textContent: 'Hide stub',
href: 'javascript:;'
}
);
$.on(hideStubLink, 'click', ThreadHiding.menu.hideStub);
return Menu.menu.addEntry({
el: hideStubLink,
order: 15,
open({thread, isReply}) {
if (isReply || !thread.isHidden || (Conf['JSON Index'] && (Conf['Index Mode'] === 'catalog'))) {
return false;
}
return ThreadHiding.menu.thread = thread;
}
});
},
hide() {
const makeStub = $('input', this.parentNode).checked;
const {thread} = ThreadHiding.menu;
ThreadHiding.hide(thread, makeStub);
ThreadHiding.saveHiddenState(thread, makeStub);
return $.event('CloseMenu');
},
show() {
const {thread} = ThreadHiding.menu;
ThreadHiding.show(thread);
ThreadHiding.saveHiddenState(thread);
return $.event('CloseMenu');
},
hideStub() {
const {thread} = ThreadHiding.menu;
ThreadHiding.show(thread);
ThreadHiding.hide(thread, false);
ThreadHiding.saveHiddenState(thread, false);
$.event('CloseMenu');
}
},
makeButton(thread, type) {
const a = $.el('a', {
className: `${type}-thread-button`,
href: 'javascript:;'
}
);
$.extend(a, {textContent: type === "hide" ? '' : '' });
a.dataset.fullID = thread.fullID;
$.on(a, 'click', ThreadHiding.toggle);
return a;
},
makeStub(thread, root) {
let summary, threadDivider;
let numReplies = $$(g.SITE.selectors.replyOriginal, root).length;
if (summary = $(g.SITE.selectors.summary, root)) { numReplies += +summary.textContent.match(/\d+/); }
const a = ThreadHiding.makeButton(thread, 'show');
$.add(a, $.tn(` ${thread.OP.info.nameBlock} (${numReplies === 1 ? '1 reply' : `${numReplies} replies`})`));
thread.stub = $.el('div',
{className: 'stub'});
if (Conf['Menu']) {
$.add(thread.stub, [a, Menu.makeButton(thread.OP)]);
} else {
$.add(thread.stub, a);
}
$.prepend(root, thread.stub);
// Prevent hiding of thread divider on sites that put it inside the thread
if (threadDivider = $(g.SITE.selectors.threadDivider, root)) {
return $.addClass(threadDivider, 'threadDivider');
}
},
saveHiddenState(thread, makeStub) {
if (thread.isHidden) {
ThreadHiding.db.set({
boardID: thread.board.ID,
threadID: thread.ID,
val: {makeStub}});
} else {
ThreadHiding.db.delete({
boardID: thread.board.ID,
threadID: thread.ID
});
}
return ThreadHiding.catalogSet(thread.board);
},
toggle(thread) {
if (!(thread instanceof Thread)) {
thread = g.threads.get(this.dataset.fullID);
}
if (thread.isHidden) {
ThreadHiding.show(thread);
} else {
ThreadHiding.hide(thread);
}
return ThreadHiding.saveHiddenState(thread);
},
hide(thread, makeStub=Conf['Stubs']) {
if (thread.isHidden) { return; }
const threadRoot = thread.nodes.root;
thread.isHidden = true;
Index.updateHideLabel();
if (thread.catalogView && !Index.showHiddenThreads) {
$.rm(thread.catalogView.nodes.root);
$.event('PostsRemoved', null, Index.root);
}
if (!makeStub) { return threadRoot.hidden = true; }
return ThreadHiding.makeStub(thread, threadRoot);
},
show(thread) {
if (thread.stub) {
$.rm(thread.stub);
delete thread.stub;
}
const threadRoot = thread.nodes.root;
threadRoot.hidden = (thread.isHidden = false);
Index.updateHideLabel();
if (thread.catalogView && Index.showHiddenThreads) {
$.rm(thread.catalogView.nodes.root);
return $.event('PostsRemoved', null, Index.root);
}
}
};
export default ThreadHiding;