import {type JSX, render} from "preact"; import {extractGroups, initializeGlobals, log} from "../utilities/exports.js"; import {Feature, fromStorage, Data} from "../storage/common.js"; import { AutocompleteFeature, BackToTopFeature, JumpToNewCommentFeature, UserLabelsFeature, runAnonymizeUsernamesFeature, runHideVotesFeature, runMarkdownToolbarFeature, runThemedLogoFeature, runUsernameColorsFeature, } from "./features/exports.js"; async function initialize() { const start = window.performance.now(); initializeGlobals(); const enabledFeatures = await fromStorage(Data.EnabledFeatures); // Any features that will use the knownGroups data should be added to this // array so that when groups are changed on Tildes, TRX can still update // them without having to change the hardcoded values. const usesKnownGroups = new Set([Feature.Autocomplete]); const knownGroups = await fromStorage(Data.KnownGroups); // Only when any of the features that uses this data are enabled, try to save // the groups. if ( Array.from(usesKnownGroups).some((feature) => enabledFeatures.value.has(feature), ) ) { const extractedGroups = extractGroups(); if (extractedGroups !== undefined) { knownGroups.value = new Set(extractedGroups); await knownGroups.save(); } } const anonymizeUsernamesEnabled = enabledFeatures.value.has( Feature.AnonymizeUsernames, ); const observerFeatures: Array<() => void | Promise> = []; const observer = new window.MutationObserver(async () => { log("Page mutation detected, rerunning features."); observer.disconnect(); await Promise.all(observerFeatures.map(async (feature) => feature())); startObserver(); }); function startObserver() { observer.observe(document.body, { attributes: true, childList: true, subtree: true, }); } if (anonymizeUsernamesEnabled) { observerFeatures.push(() => { runAnonymizeUsernamesFeature(); }); } if (enabledFeatures.value.has(Feature.HideVotes)) { observerFeatures.push(async () => { const data = await fromStorage(Feature.HideVotes); runHideVotesFeature(data.value); }); } if (enabledFeatures.value.has(Feature.MarkdownToolbar)) { observerFeatures.push(() => { runMarkdownToolbarFeature(); }); } if (enabledFeatures.value.has(Feature.ThemedLogo)) { observerFeatures.push(() => { runThemedLogoFeature(); }); } if (enabledFeatures.value.has(Feature.UsernameColors)) { observerFeatures.push(async () => { const data = await fromStorage(Feature.UsernameColors); runUsernameColorsFeature(data.value, anonymizeUsernamesEnabled); }); } // Initialize all the observer-dependent features first. await Promise.all(observerFeatures.map(async (feature) => feature())); // Object to hold the active components we are going to render. const components: Record = {}; const userLabels = await fromStorage(Feature.UserLabels); if (enabledFeatures.value.has(Feature.Autocomplete)) { components.autocomplete = ( ); } if (enabledFeatures.value.has(Feature.BackToTop)) { components.backToTop = ; } if (enabledFeatures.value.has(Feature.JumpToNewComment)) { components.jumpToNewComment = ; } if (enabledFeatures.value.has(Feature.UserLabels)) { components.userLabels = ( ); } // Insert a placeholder at the end of the body first, then render the rest // and use that as the replacement element. Otherwise render() would put it // at the beginning of the body which causes a bunch of different issues. const replacement = document.createElement("div"); document.body.append(replacement); // The jump to new comment button must come right before // the back to top button. The CSS depends on them being in this order. render(
{components.jumpToNewComment} {components.backToTop} {components.autocomplete} {components.userLabels}
, document.body, replacement, ); // Start the mutation observer only when some features depend on it are enabled. if (observerFeatures.length > 0) { startObserver(); } const initializedIn = window.performance.now() - start; log(`Initialized in approximately ${initializedIn} milliseconds.`); } document.addEventListener("DOMContentLoaded", initialize);