123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763 |
- import { getLocale, getPreferredLocale, getResourceUrl, resourceAsString, t, tp } from "../utils/index.js";
- import { clearLyricsCache, getLyricsCache } from "./lyricsCache.js";
- import { doVersionCheck } from "./versionCheck.js";
- import { getFeature, promptResetConfig } from "../config.js";
- import { FeatureInfo, type ColorLightness, type ResourceKey, type SiteSelection, type SiteSelectionOrNone } from "../types.js";
- import { emitSiteEvent } from "../siteEvents.js";
- import langMapping from "../../assets/locales.json" with { type: "json" };
- import { getAutoLikeDialog } from "../dialogs/index.js";
- import { showIconToast } from "../components/index.js";
- import { mode } from "../constants.js";
- export * from "./layout.js";
- export * from "./behavior.js";
- export * from "./input.js";
- export * from "./integrations.js";
- export * from "./lyrics.js";
- export * from "./lyricsCache.js";
- export * from "./songLists.js";
- export * from "./versionCheck.js";
- export * from "./volume.js";
- interface SelectOption<TValue = number | string> {
- value: TValue;
- label: string;
- }
- type AdornmentFunc =
- | ((...args: any[]) => Promise<string | undefined>)
- | Promise<string | undefined>;
- //#region dependencies
- /** Decoration elements that can be added next to the label */
- const adornments = {
- advanced: async () => getAdornHtml("bytm-advanced-mode-icon", t("advanced_mode"), "icon-advanced_mode"),
- experimental: async () => getAdornHtml("bytm-experimental-icon", t("experimental_feature"), "icon-experimental"),
- globe: async () => await resourceAsString("icon-globe_small") ?? "",
- alert: async (title: string) => getAdornHtml("bytm-warning-icon", title, "icon-error", "role=\"alert\""),
- reloadRequired: async () => getFeature("advancedMode") ? getAdornHtml("bytm-reload-icon", t("feature_requires_reload"), "icon-reload") : undefined,
- } satisfies Record<string, AdornmentFunc>;
- /** Order of adornment elements in the {@linkcode combineAdornments()} function */
- const adornmentOrder = new Map<AdornmentFunc, number>();
- adornmentOrder.set(adornments.alert, 0);
- adornmentOrder.set(adornments.experimental, 1);
- adornmentOrder.set(adornments.globe, 2);
- adornmentOrder.set(adornments.reloadRequired, 3);
- adornmentOrder.set(adornments.advanced, 4);
- /** Creates an HTML string for the given adornment properties */
- const getAdornHtml = async (className: string, title: string, resource: ResourceKey, extraParams?: string) =>
- `<span class="${className} bytm-adorn-icon" title="${title}" aria-label="${title}"${extraParams ? " " + extraParams : ""}>${await resourceAsString(resource) ?? ""}</span>`;
- /** Combines multiple async functions or promises that resolve with an adornment HTML string into a single string */
- const combineAdornments = (
- adornments: Array<AdornmentFunc>
- ) => new Promise<string>(
- async (resolve) => {
- const sortedAdornments = adornments.sort((a, b) => {
- const aIndex = adornmentOrder.get(a) ? adornmentOrder.get(a)! : -1;
- const bIndex = adornmentOrder.has(b) ? adornmentOrder.get(b)! : -1;
- return aIndex - bIndex;
- });
- const html = [] as string[];
- for(const adornment of sortedAdornments) {
- const val = typeof adornment === "function"
- ? await adornment()
- : await adornment;
- val && html.push(val);
- }
- resolve(html.join(""));
- }
- );
- /** Common options for config items of type "select" */
- const options = {
- siteSelection: (): SelectOption<SiteSelection>[] => [
- { value: "all", label: t("site_selection_both_sites") },
- { value: "yt", label: t("site_selection_only_yt") },
- { value: "ytm", label: t("site_selection_only_ytm") },
- ],
- siteSelectionOrNone: (): SelectOption<SiteSelectionOrNone>[] => [
- { value: "all", label: t("site_selection_both_sites") },
- { value: "yt", label: t("site_selection_only_yt") },
- { value: "ytm", label: t("site_selection_only_ytm") },
- { value: "none", label: t("site_selection_none") },
- ],
- locale: () => Object.entries(langMapping)
- .reduce((a, [locale, { name }]) => {
- return [...a, {
- value: locale,
- label: name,
- }];
- }, [] as SelectOption[])
- .sort((a, b) => a.label.localeCompare(b.label)),
- colorLightness: (): SelectOption<ColorLightness>[] => [
- { value: "darker", label: t("color_lightness_darker") },
- { value: "normal", label: t("color_lightness_normal") },
- { value: "lighter", label: t("color_lightness_lighter") },
- ],
- };
- /** Renders a long number with a thousands separator */
- function renderLongNumberValue(val: string, maximumFractionDigits = 0) {
- return Number(val).toLocaleString(
- getLocale().replace(/_/g, "-"),
- {
- style: "decimal",
- maximumFractionDigits,
- }
- );
- }
- //#region features
- /**
- * Contains all possible features with their default values and other configuration.
- *
- * **Required props:**
- * <!-------------------------------------------------------------------------------------------------------------------------------------------------------->
- * | Property | Description |
- * | :------------------- | :------------------------------------------------------------------------------------------------------------------------------- |
- * | `type` | type of the feature configuration element - use autocomplete or check `FeatureTypeProps` in `src/types.ts` |
- * | `category` | category of the feature - use autocomplete or check `FeatureCategory` in `src/types.ts` |
- * | `default` | default value of the feature - type of the value depends on the given `type` |
- * | `enable(value: any)` | (required if reloadRequired = false) - function that will be called when the feature is enabled / initialized for the first time |
- *
- *
- * **Optional props:**
- * <!-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------->
- * | Property | Description |
- * | :------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- |
- * | `disable: (newValue: any) => void` | for type `toggle` only - function that will be called when the feature is disabled - can be a synchronous or asynchronous function |
- * | `change: (key: string, prevValue: any, newValue: any)` => void | for types `number`, `select`, `slider` and `hotkey` only - function that will be called when the value is changed |
- * | `click: () => void` | for type `button` only - function that will be called when the button is clicked |
- * | `helpText: string / () => string` | function that returns an HTML string or the literal string itself that will be the help text for this feature - writing as function is useful for pluralizing or inserting values into the translation at runtime - if not set, translation with key `feature_helptext_featureKey` will be used instead, if available |
- * | `textAdornment: () => string / Promise<string>` | function that returns an HTML string that will be appended to the text in the config menu as an adornment element |
- * | `unit: string / (val: number) => string` | Only if type is `number` or `slider` - The unit text that is displayed next to the input element, i.e. " px" - a leading space need to be added too! |
- * | `min: number` | Only if type is `number` or `slider` - Overwrites the default of the `min` property of the HTML input element |
- * | `max: number` | Only if type is `number` or `slider` - Overwrites the default of the `max` property of the HTML input element |
- * | `step: number` | Only if type is `number` or `slider` - Overwrites the default of the `step` property of the HTML input element |
- * | `options: SelectOption[] / () => SelectOption[]` | Only if type is `select` - function that returns an array of objects with `value` and `label` properties |
- * | `reloadRequired: boolean` | if true (default), the page needs to be reloaded for the changes to take effect - if false, `enable()` needs to be provided |
- * | `advanced: boolean` | if true, the feature will only be shown if the advanced mode feature has been turned on |
- * | `hidden: boolean` | if true, the feature will not be shown in the settings - default is undefined (false) |
- * | `valueHidden: boolean` | If true, the value of the feature will be hidden in the settings and via the plugin interface - default is undefined (false) |
- * | `normalize: (val: any) => any` | Function that will be called to normalize the value before it is saved - useful for trimming strings or other simple operations |
- * | `renderValue: (val: string) => string` | If provided, is used to render the value's label in the config menu |
- *
- * TODO: go through all features and set as many as possible to reloadRequired = false
- */
- export const featInfo = {
- //#region layout
- watermarkEnabled: {
- type: "toggle",
- category: "layout",
- default: true,
- textAdornment: adornments.reloadRequired,
- },
- removeShareTrackingParam: {
- type: "toggle",
- category: "layout",
- default: true,
- textAdornment: adornments.reloadRequired,
- },
- removeShareTrackingParamSites: {
- type: "select",
- category: "layout",
- options: options.siteSelection,
- default: "all",
- advanced: true,
- textAdornment: () => combineAdornments([adornments.advanced, adornments.reloadRequired]),
- },
- fixSpacing: {
- type: "toggle",
- category: "layout",
- default: true,
- advanced: true,
- textAdornment: () => combineAdornments([adornments.advanced, adornments.reloadRequired]),
- },
- thumbnailOverlayBehavior: {
- type: "select",
- category: "layout",
- options: () => [
- { value: "songsOnly", label: t("thumbnail_overlay_behavior_songs_only") },
- { value: "videosOnly", label: t("thumbnail_overlay_behavior_videos_only") },
- { value: "always", label: t("thumbnail_overlay_behavior_always") },
- { value: "never", label: t("thumbnail_overlay_behavior_never") },
- ],
- default: "songsOnly",
- reloadRequired: false,
- enable: noop,
- },
- thumbnailOverlayToggleBtnShown: {
- type: "toggle",
- category: "layout",
- default: true,
- textAdornment: adornments.reloadRequired,
- },
- thumbnailOverlayShowIndicator: {
- type: "toggle",
- category: "layout",
- default: true,
- textAdornment: adornments.reloadRequired,
- },
- thumbnailOverlayIndicatorOpacity: {
- type: "slider",
- category: "layout",
- min: 5,
- max: 100,
- step: 5,
- default: 40,
- unit: "%",
- advanced: true,
- textAdornment: () => combineAdornments([adornments.advanced, adornments.reloadRequired]),
- },
- thumbnailOverlayImageFit: {
- type: "select",
- category: "layout",
- options: () => [
- { value: "cover", label: t("thumbnail_overlay_image_fit_crop") },
- { value: "contain", label: t("thumbnail_overlay_image_fit_full") },
- { value: "fill", label: t("thumbnail_overlay_image_fit_stretch") },
- ],
- default: "cover",
- advanced: true,
- textAdornment: () => combineAdornments([adornments.advanced, adornments.reloadRequired]),
- },
- hideCursorOnIdle: {
- type: "toggle",
- category: "layout",
- default: true,
- reloadRequired: false,
- enable: noop,
- },
- hideCursorOnIdleDelay: {
- type: "slider",
- category: "layout",
- min: 0.5,
- max: 10,
- step: 0.25,
- default: 2,
- unit: "s",
- advanced: true,
- textAdornment: adornments.advanced,
- reloadRequired: false,
- enable: noop,
- },
- fixHdrIssues: {
- type: "toggle",
- category: "layout",
- default: true,
- advanced: true,
- textAdornment: () => combineAdornments([adornments.advanced, adornments.reloadRequired]),
- },
- showVotes: {
- type: "toggle",
- category: "layout",
- default: true,
- textAdornment: adornments.reloadRequired,
- },
- showVotesFormat: {
- type: "select",
- category: "layout",
- options: () => [
- { value: "long", label: t("votes_format_full") },
- { value: "short", label: t("votes_format_short") },
- ],
- default: "short",
- reloadRequired: false,
- enable: noop,
- },
- // archived idea for future version
- // (shows a bar under the like/dislike buttons that shows the ratio of likes to dislikes)
- // showVoteRatio: {
- // type: "select",
- // category: "layout",
- // options: () => [
- // { value: "disabled", label: t("vote_ratio_disabled") },
- // { value: "greenRed", label: t("vote_ratio_green_red") },
- // { value: "blueGray", label: t("vote_ratio_blue_gray") },
- // ],
- // default: "disabled",
- // textAdornment: adornments.reloadRequired,
- // },
- //#region volume
- volumeSliderLabel: {
- type: "toggle",
- category: "volume",
- default: true,
- textAdornment: adornments.reloadRequired,
- },
- volumeSliderSize: {
- type: "number",
- category: "volume",
- min: 50,
- max: 500,
- step: 5,
- default: 150,
- unit: "px",
- textAdornment: adornments.reloadRequired,
- },
- volumeSliderStep: {
- type: "slider",
- category: "volume",
- min: 1,
- max: 25,
- default: 2,
- unit: "%",
- textAdornment: adornments.reloadRequired,
- },
- volumeSliderScrollStep: {
- type: "slider",
- category: "volume",
- min: 1,
- max: 25,
- default: 4,
- unit: "%",
- textAdornment: adornments.reloadRequired,
- },
- volumeSharedBetweenTabs: {
- type: "toggle",
- category: "volume",
- default: false,
- textAdornment: adornments.reloadRequired,
- },
- setInitialTabVolume: {
- type: "toggle",
- category: "volume",
- default: false,
- textAdornment: () => getFeature("volumeSharedBetweenTabs")
- ? combineAdornments([adornments.alert(t("feature_warning_setInitialTabVolume_volumeSharedBetweenTabs_incompatible").replace(/"/g, "'")), adornments.reloadRequired])
- : adornments.reloadRequired(),
- },
- initialTabVolumeLevel: {
- type: "slider",
- category: "volume",
- min: 0,
- max: 100,
- step: 1,
- default: 100,
- unit: "%",
- textAdornment: () => getFeature("volumeSharedBetweenTabs")
- ? combineAdornments([adornments.alert(t("feature_warning_setInitialTabVolume_volumeSharedBetweenTabs_incompatible").replace(/"/g, "'")), adornments.reloadRequired])
- : adornments.reloadRequired(),
- reloadRequired: false,
- enable: noop,
- },
- //#region song lists
- lyricsQueueButton: {
- type: "toggle",
- category: "songLists",
- default: true,
- textAdornment: adornments.reloadRequired,
- },
- deleteFromQueueButton: {
- type: "toggle",
- category: "songLists",
- default: true,
- textAdornment: adornments.reloadRequired,
- },
- listButtonsPlacement: {
- type: "select",
- category: "songLists",
- options: () => [
- { value: "queueOnly", label: t("list_button_placement_queue_only") },
- { value: "everywhere", label: t("list_button_placement_everywhere") },
- ],
- default: "everywhere",
- advanced: true,
- textAdornment: () => combineAdornments([adornments.advanced, adornments.reloadRequired]),
- },
- scrollToActiveSongBtn: {
- type: "toggle",
- category: "songLists",
- default: true,
- textAdornment: adornments.reloadRequired,
- },
- clearQueueBtn: {
- type: "toggle",
- category: "songLists",
- default: true,
- textAdornment: adornments.reloadRequired,
- },
- //#region behavior
- disableBeforeUnloadPopup: {
- type: "toggle",
- category: "behavior",
- default: false,
- textAdornment: adornments.reloadRequired,
- },
- closeToastsTimeout: {
- type: "number",
- category: "behavior",
- min: 0,
- max: 30,
- step: 0.5,
- default: 3,
- unit: "s",
- reloadRequired: false,
- enable: noop,
- },
- rememberSongTime: {
- type: "toggle",
- category: "behavior",
- default: true,
- helpText: () => tp("feature_helptext_rememberSongTime", getFeature("rememberSongTimeMinPlayTime"), getFeature("rememberSongTimeMinPlayTime")),
- textAdornment: adornments.reloadRequired,
- },
- rememberSongTimeSites: {
- type: "select",
- category: "behavior",
- options: options.siteSelection,
- default: "all",
- textAdornment: adornments.reloadRequired,
- },
- rememberSongTimeDuration: {
- type: "number",
- category: "behavior",
- min: 1,
- max: 60 * 60 * 24 * 7,
- step: 1,
- default: 60,
- unit: "s",
- advanced: true,
- textAdornment: adornments.advanced,
- reloadRequired: false,
- enable: noop,
- },
- rememberSongTimeReduction: {
- type: "number",
- category: "behavior",
- min: 0,
- max: 30,
- step: 0.05,
- default: 0.2,
- unit: "s",
- advanced: true,
- textAdornment: adornments.advanced,
- reloadRequired: false,
- enable: noop,
- },
- rememberSongTimeMinPlayTime: {
- type: "slider",
- category: "behavior",
- min: 3,
- max: 30,
- step: 0.5,
- default: 10,
- unit: "s",
- advanced: true,
- textAdornment: adornments.advanced,
- reloadRequired: false,
- enable: noop,
- },
- //#region input
- arrowKeySupport: {
- type: "toggle",
- category: "input",
- default: true,
- reloadRequired: false,
- enable: noop,
- },
- arrowKeySkipBy: {
- type: "number",
- category: "input",
- min: 0.5,
- max: 60,
- step: 0.5,
- default: 5,
- reloadRequired: false,
- enable: noop,
- },
- switchBetweenSites: {
- type: "toggle",
- category: "input",
- default: true,
- reloadRequired: false,
- enable: noop,
- },
- switchSitesHotkey: {
- type: "hotkey",
- category: "input",
- default: {
- code: "F9",
- shift: false,
- ctrl: false,
- alt: false,
- },
- reloadRequired: false,
- enable: noop,
- },
- anchorImprovements: {
- type: "toggle",
- category: "input",
- default: true,
- textAdornment: adornments.reloadRequired,
- },
- numKeysSkipToTime: {
- type: "toggle",
- category: "input",
- default: true,
- reloadRequired: false,
- enable: noop,
- },
- autoLikeChannels: {
- type: "toggle",
- category: "input",
- default: false,
- advanced: true,
- textAdornment: () => combineAdornments([adornments.advanced, adornments.reloadRequired]),
- },
- autoLikeChannelToggleBtn: {
- type: "toggle",
- category: "input",
- default: true,
- reloadRequired: false,
- enable: noop,
- advanced: true,
- textAdornment: adornments.advanced,
- },
- // TODO(v2.2):
- // autoLikePlayerBarToggleBtn: {
- // type: "toggle",
- // category: "input",
- // default: false,
- // textAdornment: adornments.reloadRequired,
- // },
- autoLikeTimeout: {
- type: "slider",
- category: "input",
- min: 3,
- max: 30,
- step: 0.5,
- default: 5,
- unit: "s",
- advanced: true,
- reloadRequired: false,
- enable: noop,
- textAdornment: adornments.advanced,
- },
- autoLikeShowToast: {
- type: "toggle",
- category: "input",
- default: true,
- reloadRequired: false,
- advanced: true,
- enable: noop,
- textAdornment: adornments.advanced,
- },
- autoLikeOpenMgmtDialog: {
- type: "button",
- category: "input",
- click: () => getAutoLikeDialog().then(d => d.open()),
- },
- //#region lyrics
- geniusLyrics: {
- type: "toggle",
- category: "lyrics",
- default: true,
- },
- geniUrlBase: {
- type: "text",
- category: "lyrics",
- default: "https://api.sv443.net/geniurl",
- normalize: (val: string) => val.trim().replace(/\/+$/, ""),
- advanced: true,
- textAdornment: adornments.advanced,
- reloadRequired: false,
- enable: noop,
- },
- geniUrlToken: {
- type: "text",
- valueHidden: true,
- category: "lyrics",
- default: "",
- normalize: (val: string) => val.trim(),
- advanced: true,
- textAdornment: adornments.advanced,
- reloadRequired: false,
- enable: noop,
- },
- lyricsCacheMaxSize: {
- type: "slider",
- category: "lyrics",
- default: 2000,
- min: 100,
- max: 10000,
- step: 100,
- unit: (val: number) => ` ${tp("unit_entries", val)}`,
- renderValue: renderLongNumberValue,
- advanced: true,
- textAdornment: adornments.advanced,
- reloadRequired: false,
- enable: noop,
- },
- lyricsCacheTTL: {
- type: "slider",
- category: "lyrics",
- default: 21,
- min: 1,
- max: 100,
- step: 1,
- unit: (val: number) => " " + tp("unit_days", val),
- advanced: true,
- textAdornment: adornments.advanced,
- reloadRequired: false,
- enable: noop,
- },
- clearLyricsCache: {
- type: "button",
- category: "lyrics",
- async click() {
- const entries = getLyricsCache().length;
- if(confirm(tp("lyrics_clear_cache_confirm_prompt", entries, entries))) {
- await clearLyricsCache();
- alert(t("lyrics_clear_cache_success"));
- }
- },
- advanced: true,
- textAdornment: adornments.advanced,
- },
- // advancedLyricsFilter: {
- // type: "toggle",
- // category: "lyrics",
- // default: false,
- // change: () => setTimeout(() => confirm(t("lyrics_cache_changed_clear_confirm")) && clearLyricsCache(), 200),
- // advanced: true,
- // textAdornment: adornments.experimental,
- // reloadRequired: false,
- // enable: noop,
- // },
- //#region integrations
- disableDarkReaderSites: {
- type: "select",
- category: "integrations",
- options: options.siteSelectionOrNone,
- default: "all",
- advanced: true,
- textAdornment: () => combineAdornments([adornments.advanced, adornments.reloadRequired]),
- },
- sponsorBlockIntegration: {
- type: "toggle",
- category: "integrations",
- default: true,
- textAdornment: adornments.reloadRequired,
- },
- themeSongIntegration: {
- type: "toggle",
- category: "integrations",
- default: false,
- textAdornment: adornments.reloadRequired,
- },
- themeSongLightness: {
- type: "select",
- category: "integrations",
- options: options.colorLightness,
- default: "darker",
- textAdornment: adornments.reloadRequired,
- },
- //#region general
- locale: {
- type: "select",
- category: "general",
- options: options.locale,
- default: getPreferredLocale(),
- textAdornment: () => combineAdornments([adornments.globe, adornments.reloadRequired]),
- },
- localeFallback: {
- type: "toggle",
- category: "general",
- default: true,
- advanced: true,
- textAdornment: () => combineAdornments([adornments.advanced, adornments.reloadRequired]),
- },
- versionCheck: {
- type: "toggle",
- category: "general",
- default: true,
- textAdornment: adornments.reloadRequired,
- },
- checkVersionNow: {
- type: "button",
- category: "general",
- click: () => doVersionCheck(true),
- },
- logLevel: {
- type: "select",
- category: "general",
- options: () => [
- { value: 0, label: t("log_level_debug") },
- { value: 1, label: t("log_level_info") },
- ],
- default: 1,
- textAdornment: adornments.reloadRequired,
- },
- initTimeout: {
- type: "number",
- category: "general",
- min: 3,
- max: 30,
- default: 8,
- step: 0.1,
- unit: "s",
- advanced: true,
- textAdornment: () => combineAdornments([adornments.advanced, adornments.reloadRequired]),
- },
- toastDuration: {
- type: "slider",
- category: "general",
- min: 0,
- max: 15,
- default: 3,
- step: 0.5,
- unit: "s",
- reloadRequired: false,
- advanced: true,
- textAdornment: adornments.advanced,
- enable: noop,
- change: () => showIconToast({
- message: "Example",
- iconSrc: getResourceUrl(`img-logo${mode === "development" ? "_dev" : ""}`),
- }),
- },
- showToastOnGenericError: {
- type: "toggle",
- category: "general",
- default: true,
- advanced: true,
- textAdornment: () => combineAdornments([adornments.advanced, adornments.reloadRequired]),
- },
- resetConfig: {
- type: "button",
- category: "general",
- click: promptResetConfig,
- textAdornment: adornments.reloadRequired,
- },
- advancedMode: {
- type: "toggle",
- category: "general",
- default: false,
- textAdornment: () => getFeature("advancedMode") ? adornments.advanced() : undefined,
- change: (_key, prevValue, newValue) =>
- prevValue !== newValue &&
- emitSiteEvent("recreateCfgMenu"),
- },
- } as const satisfies FeatureInfo;
- function noop() {
- void 0;
- }
- void [noop];
|