4chan-x/src/Filtering/ThreadHiding.js
Tuxedo Takodachi 567a1fe45e Squash and cleanup of the following:
- cca085e60090ca21edf0dee6aa012fc4c949809a
  start of import/export

- f816da146c32f010476872d15b58ec8301b9fdf2
  start of changing stuff until I can get a bundle

- c92adde147792356ff206107b2311590e8b2c054
  first bundle without errors

- e652dd2b785e355e0ac33566da7eaaaa19c7c539
  Bundling works with ts files

- 60fdb2539a757ca2f66258b21adf81246873893f
  meta info in compilation

- 8ccae783cbf65ac186d5669dedd9f945f7608694
  new build doesn't cause errors on page load as userscript

- 6fa11c42a05572779870f94b7ef4ea8dac373450
  work in progress: load userscript in browser and fix bugs

- b15c557d483de544a38a28cb78f25139d1d8421f
  migrated yotsuba templates to plain js

  the old templates caused some variable be in a wrong scope after
decaffeinate, causing them to be unreadable from the old template

  the old templates caused some variable be in a wrong scope after
decaffeinate, causing them to be unreadable from the old template

- 9d763e852fde74808ca14d5a8d6be45f51ae2765
  update readme

- 924eda8268bcfc4f1c0a83062ecd1d0d65bd92aa
  added more imports, and now the circular dependencies are haunting me

- ddd2d23315d801c7deaa28313833e667698aadd3
  jsx templates for escaped strings,
  more bug fixed from circular dependencies

- fee484dd447820d908c77b1e9d31235ab95a481c
  some fixes, clarify jsx

- e1d01d02eba5db2f604a5df786c525e95f32a2f9
  Unpacked extension
  more fixes

- 97d9090b712d20f7d851c82af84c65060f1a9c6e
  fixed class on post that caused catalog to appear empty

- 96a2c7b4a1e69f5812d1e53b2e4c90f6d8447b02
  A child class that's not supposed to run the parents constructor? That needs a workaround in es6 classes.

- fc06b4e1b2769550d4c69377b84d3ccacdb2e013
  changed jsx to make the tests pass

- 7b317b2a0feabe8caa547c76baf0c908b21592f1
  revert archive and banners to json
2023-03-15 21:01:03 +01:00

288 lines
8.5 KiB
JavaScript

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, {innerHTML: "<span class=\"fa fa-" + ((type === "hide") ? "minus" : "plus") + "-square\"></span>"});
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;