diff --git a/.eslintrc.js b/.eslintrc.js index 71946ca6b8d..27bfe6de838 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -63,6 +63,7 @@ module.exports = { globalThis: false, vi: true, }, + ignorePatterns: ['**/public/*.js'], rules: { // http://eslint.org/docs/rules/ 'array-callback-return': 'warn', diff --git a/packages/desktop-client/src/components/ActualPluginsProvider.tsx b/packages/desktop-client/src/components/ActualPluginsProvider.tsx new file mode 100644 index 00000000000..5f2e85cce1e --- /dev/null +++ b/packages/desktop-client/src/components/ActualPluginsProvider.tsx @@ -0,0 +1,332 @@ +import React, { + createContext, + type ReactNode, + useCallback, + useContext, + useEffect, + useState, +} from 'react'; + +import { getDatabase } from 'loot-core/platform/server/indexeddb'; +import { type ActualPluginStored } from 'loot-core/types/models/actual-plugin-stored'; + +import { + type ActualPluginManifest, + type ActualPlugin, + type ActualPluginEntry, +} from '../../../plugins-shared/src'; +import { useFeatureFlag } from '../hooks/useFeatureFlag'; + +// Context and Provider +type ActualPluginsContextType = { + plugins: ActualPlugin[]; + pluginStore: ActualPluginStored[]; + loadPlugins: () => Promise; + refreshPluginStore: () => Promise; +}; + +const ActualPluginsContext = createContext< + ActualPluginsContextType | undefined +>(undefined); + +type ActualPluginsProviderProps = { + children: ReactNode; +}; +export function ActualPluginsProvider({ + children, +}: ActualPluginsProviderProps) { + const pluginsEnabled = useFeatureFlag('plugins'); + const [plugins, setPlugins] = useState([]); + const [pluginStore, setPluginStore] = useState([]); + + const refreshPluginStore = useCallback(async () => { + setPluginStore(await getAllPlugins()); + }, []); + + const loadPlugins = useCallback(async () => { + try { + const allPlugins = await getAllPlugins(); + + const fullPlugins = []; + for (const plugin of allPlugins) { + const loadedPlugin = await loadPluginFromRepo(plugins, plugin.url); + if (loadedPlugin) { + fullPlugins.push(loadedPlugin); + } + } + setPlugins(fullPlugins as ActualPlugin[]); + await refreshPluginStore(); + } catch (error) { + console.error('Failed to load plugins:', error); + } + }, [plugins, refreshPluginStore]); + + useEffect(() => { + if (pluginsEnabled && plugins.length === 0) { + loadPlugins(); + refreshPluginStore(); + } + }, [pluginsEnabled, plugins, loadPlugins, refreshPluginStore]); + + return ( + + {children} + + ); +} + +// Hook +export const useActualPlugins = () => { + const context = useContext(ActualPluginsContext); + if (!context) { + throw new Error( + 'useActualPlugins must be used within an ActualPluginsProvider', + ); + } + return context; +}; + +async function loadPluginScript( + scriptBlob: Blob, + manifest: ActualPluginManifest, +): Promise { + const scriptURL = URL.createObjectURL(scriptBlob); + const scriptCode = await scriptBlob.text(); + const pluginModule = await import(/* @vite-ignore */ scriptURL); + const db = await getDatabase(); + + if (pluginModule?.default) { + const pluginEntry: ActualPluginEntry = pluginModule.default; + + if (manifest.pluginType === 'client') { + const plugin = pluginEntry(React); + console.log( + `Plugin “${manifest.name}” v${manifest.version} loaded successfully.`, + ); + const transaction = db.transaction(['plugins'], 'readwrite'); + const objectStore = transaction.objectStore('plugins'); + const storedPlugin: ActualPluginStored = manifest as ActualPluginStored; + storedPlugin.plugin = scriptCode; + + objectStore.put(storedPlugin); + + return plugin; + } + + return null; + } + throw new Error('Plugin script does not export a default object.'); +} + +type GitHubAsset = { + name: string; + browser_download_url: string; +}; +export async function fetchRelease( + owner: string, + repo: string, + releasePath: string, +): Promise<{ version: string; scriptUrl: string; manifestUrl: string }> { + const apiUrl = `https://cors-anywhere.herokuapp.com/https://api.github.com/repos/${owner}/${repo}/releases/${releasePath}`; + const response = await fetch(apiUrl); + if (!response.ok) { + throw new Error(`Failed to fetch release metadata for ${repo}`); + } + + const releaseData = await response.json(); + const version = releaseData.tag_name; + const scriptUrl = releaseData.assets.filter( + (f: GitHubAsset) => f.name === 'index.es.js', + )[0]?.browser_download_url; + const manifestUrl = releaseData.assets.filter( + (f: GitHubAsset) => f.name === 'manifest.json', + )[0]?.browser_download_url; + + return { version, scriptUrl, manifestUrl }; +} + +export function parseGitHubRepoUrl( + url: string, +): { owner: string; repo: string } | null { + try { + const parsedUrl = new URL(url); + + if (!parsedUrl.hostname.includes('github.com')) { + throw new Error('Not a valid GitHub URL'); + } + + const pathParts = parsedUrl.pathname.split('/').filter(Boolean); + if (pathParts.length >= 2) { + const owner = pathParts[0]; + const repo = pathParts[1]; + return { owner, repo }; + } + throw new Error('URL does not contain owner and repository name'); + } catch (error) { + console.error(`Error parsing GitHub URL: ${url}`, error); + return null; + } +} + +async function loadPluginFromRepo( + loadedPlugins: ActualPlugin[], + repo: string, +): Promise { + try { + const parsedRepo = parseGitHubRepoUrl(repo); + if (parsedRepo == null) throw new Error(`Invalid repo ${repo}`); + + console.log(`Checking for updates for plugin ${repo}...`); + + const { + version: latestVersion, + scriptUrl, + manifestUrl, + } = await fetchRelease(parsedRepo.owner, parsedRepo.repo, 'latest'); + + let response = await fetch( + `https://cors-anywhere.herokuapp.com/${manifestUrl}`, + ); + + if (!response.ok) { + throw new Error(`Failed to download plugin manifest for ${repo}`); + } + + const manifest = (await response.json()) as ActualPluginManifest; + + const foundPlugin = loadedPlugins.find( + plugin => plugin.name === manifest.name, + ); + if (foundPlugin) return foundPlugin; + + const storedPlugin = await getStoredPlugin(manifest); + + let indexContent = null; + if (!storedPlugin || storedPlugin.version !== latestVersion) { + console.log(`Downloading plugin “${repo}” v${latestVersion}...`); + //need to change the cors proxy at some point: + response = await fetch( + `https://cors-anywhere.herokuapp.com/${scriptUrl}`, + ); + + if (!response.ok) { + throw new Error(`Failed to download plugin script for ${repo}`); + } + + indexContent = await response.text(); + } else { + indexContent = await storedPlugin.plugin; + console.log( + `Using cached version of plugin “${repo}” v${latestVersion}...`, + ); + } + + if (!indexContent) { + return null; + } + + const indexJsBlob = new Blob([indexContent], { + type: 'application/javascript', + }); + + console.log(`Plugin “${repo}” loaded successfully.`); + return await loadPluginScript(indexJsBlob, manifest); + } catch (error) { + console.error(`Error loading plugin “${repo}”:`, error); + return null; + } +} + +async function getStoredPlugin( + manifest: ActualPluginManifest, +): Promise { + const db = await getDatabase(); // Open the database + const transaction = db.transaction(['plugins'], 'readonly'); + const objectStore = transaction.objectStore('plugins'); + + return new Promise((resolve, reject) => { + const req = objectStore.get(manifest.url); + + req.onsuccess = () => { + resolve(req.result || null); // Resolve with the result + }; + + req.onerror = () => { + reject(req.error); // Reject with the error + }; + }); +} + +async function getAllPlugins(): Promise { + const db = await getDatabase(); // Open the database + const transaction = db.transaction(['plugins'], 'readonly'); + const objectStore = transaction.objectStore('plugins'); + + return new Promise((resolve, reject) => { + const req = objectStore.getAll(); + + req.onsuccess = () => { + resolve(req.result); // Resolve with the array of rows + }; + + req.onerror = () => { + reject(req.error); // Reject with the error + }; + }); +} + +export async function installPluginFromManifest( + loadedPlugins: ActualPlugin[], + manifest: ActualPluginManifest, +): Promise { + try { + const foundPlugin = loadedPlugins.find( + plugin => plugin.name === manifest.name, + ); + if (foundPlugin) return foundPlugin; + + console.log( + `Downloading plugin “${manifest.name}” v${manifest.version}...`, + ); + + const parsedRepo = parseGitHubRepoUrl(manifest.url); + if (parsedRepo == null) throw new Error(`Invalid repo ${manifest.url}`); + + const { scriptUrl } = await fetchRelease( + parsedRepo.owner, + parsedRepo.repo, + `tags/${manifest.version}`, + ); + + //need to change the cors proxy at some point: + const response = await fetch( + `https://cors-anywhere.herokuapp.com/${scriptUrl}`, + ); + + if (!response.ok) { + throw new Error(`Failed to download plugin script for ${manifest.name}`); + } + + const indexContent = await response.text(); + + if (!indexContent) { + return null; + } + + const indexJsBlob = new Blob([indexContent], { + type: 'application/javascript', + }); + + console.log(`Plugin “${manifest.name}” loaded successfully.`); + const loadedPlugin = await loadPluginScript(indexJsBlob, manifest); + if (loadedPlugin) { + loadedPlugins.push(loadedPlugin); + } + return loadedPlugin; + } catch (error) { + console.error(`Error loading plugin “${manifest.name}”:`, error); + return null; + } +} diff --git a/packages/desktop-client/src/components/App.tsx b/packages/desktop-client/src/components/App.tsx index 431bed29fb4..33a28140901 100644 --- a/packages/desktop-client/src/components/App.tsx +++ b/packages/desktop-client/src/components/App.tsx @@ -33,6 +33,7 @@ import { installPolyfills } from '../polyfills'; import { styles, hasHiddenScrollbars, ThemeStyle, useTheme } from '../style'; import { ExposeNavigate } from '../util/router-tools'; +import { ActualPluginsProvider } from './ActualPluginsProvider'; import { AppBackground } from './AppBackground'; import { BudgetMonthCountProvider } from './budget/BudgetMonthCountContext'; import { View } from './common/View'; @@ -192,47 +193,49 @@ export function App() { return ( - - - - - - - - + + + + + + + + - - {process.env.REACT_APP_REVIEW_ID && - !Platform.isPlaywright && } - - - - - + + + {process.env.REACT_APP_REVIEW_ID && + !Platform.isPlaywright && } + + + + + + - - - - - - - + + + + + + + ); } diff --git a/packages/desktop-client/src/components/FinancesApp.tsx b/packages/desktop-client/src/components/FinancesApp.tsx index 8b987636ee3..1813540cea9 100644 --- a/packages/desktop-client/src/components/FinancesApp.tsx +++ b/packages/desktop-client/src/components/FinancesApp.tsx @@ -33,6 +33,7 @@ import { MobileNavTabs } from './mobile/MobileNavTabs'; import { TransactionEdit } from './mobile/transactions/TransactionEdit'; import { Notifications } from './Notifications'; import { ManagePayeesPage } from './payees/ManagePayeesPage'; +import { Plugins } from './plugins/Plugins'; import { Reports } from './reports'; import { LoadingIndicator } from './reports/LoadingIndicator'; import { NarrowAlternate, WideComponent } from './responsive'; @@ -250,6 +251,7 @@ export function FinancesApp() { } /> } /> + } /> } /> ; + case 'select-new-plugin': + return ; + default: throw new Error('Unknown modal'); } diff --git a/packages/desktop-client/src/components/ThemeSelector.tsx b/packages/desktop-client/src/components/ThemeSelector.tsx index 7e1c8600c96..9d571950527 100644 --- a/packages/desktop-client/src/components/ThemeSelector.tsx +++ b/packages/desktop-client/src/components/ThemeSelector.tsx @@ -1,35 +1,120 @@ -import React, { useRef, useState, type CSSProperties } from 'react'; +import { + type CSSProperties, + type SVGProps, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; import { useTranslation } from 'react-i18next'; import type { Theme } from 'loot-core/src/types/prefs'; +import { type ThemeDefinition } from '../../../plugins-shared/src'; import { SvgMoonStars, SvgSun, SvgSystem } from '../icons/v2'; -import { themeOptions, useTheme } from '../style'; +import { themeOptions, themes, useTheme } from '../style'; +import { useActualPlugins } from './ActualPluginsProvider'; import { Button } from './common/Button2'; import { Menu } from './common/Menu'; import { Popover } from './common/Popover'; +import { View } from './common/View'; import { useResponsive } from './responsive/ResponsiveProvider'; type ThemeSelectorProps = { style?: CSSProperties; }; +type ThemesExtendedType = { + [key: string]: { + name: string; + colors: ThemeDefinition; + }; +}; + +type ThemesIconsType = { + [key: string]: (props: SVGProps) => JSX.Element; +}; + export function ThemeSelector({ style }: ThemeSelectorProps) { const [theme, switchTheme] = useTheme(); const [menuOpen, setMenuOpen] = useState(false); const triggerRef = useRef(null); + const [themesExtended, setThemesExtended] = + useState(themes); + const [themeOptionsExtended, setThemeOptionsExtended] = + useState(themeOptions); const { isNarrowWidth } = useResponsive(); const { t } = useTranslation(); - const themeIcons = { - light: SvgSun, - dark: SvgMoonStars, - auto: SvgSystem, - midnight: SvgMoonStars, - development: SvgMoonStars, - } as const; + const baseIcons = useMemo( + () => ({ + light: SvgSun, + dark: SvgMoonStars, + auto: SvgSystem, + midnight: SvgMoonStars, + development: SvgMoonStars, + }), + [], + ); + const [themeIcons, setThemeIcons] = useState(baseIcons); + const { plugins: loadedPlugins } = useActualPlugins(); + + useEffect(() => { + const pluginIcons = + loadedPlugins?.reduce((acc, plugin) => { + if ( + plugin && + plugin.availableThemes && + plugin.availableThemes().length > 0 + ) { + plugin.availableThemes().forEach(theme => { + acc = { + ...acc, + [theme]: (props: SVGProps) => + plugin?.getThemeIcon?.(theme, props.style) ?? , + }; + }); + } + return acc; + }, {} as ThemesIconsType) ?? ({} as ThemesIconsType); + + const customThemes = + loadedPlugins?.reduce((acc, plugin) => { + if ( + plugin && + plugin.availableThemes && + plugin.availableThemes().length > 0 + ) { + plugin + .availableThemes() + .filter(theme => theme !== undefined) + .forEach(theme => { + acc = { + ...acc, + [theme]: { + name: theme, + colors: plugin?.getThemeSchema?.(theme) ?? {}, + }, + }; + }); + } + return acc; + }, {} as ThemesExtendedType) ?? ({} as ThemesExtendedType); + + setThemeIcons({ ...baseIcons, ...pluginIcons }); + + setThemesExtended({ ...themes, ...customThemes }); + }, [loadedPlugins, baseIcons]); + + useEffect(() => { + setThemeOptionsExtended( + Object.entries(themesExtended).map( + ([key, { name }]) => [key, name] as [Theme, string], + ), + ); + }, [themesExtended]); function onMenuSelect(newTheme: Theme) { setMenuOpen(false); @@ -62,7 +147,7 @@ export function ThemeSelector({ style }: ThemeSelectorProps) { > ({ name, text }))} + items={themeOptionsExtended.map(([name, text]) => ({ name, text }))} /> diff --git a/packages/desktop-client/src/components/modals/SelectNewPluginModal.tsx b/packages/desktop-client/src/components/modals/SelectNewPluginModal.tsx new file mode 100644 index 00000000000..267ed81c201 --- /dev/null +++ b/packages/desktop-client/src/components/modals/SelectNewPluginModal.tsx @@ -0,0 +1,255 @@ +import React, { useEffect, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; + +import { type ActualPluginManifest } from '../../../../plugins-shared/src'; +import { SvgCheck } from '../../icons/v2'; +import { theme } from '../../style'; +import { + fetchRelease, + installPluginFromManifest, + parseGitHubRepoUrl, + useActualPlugins, +} from '../ActualPluginsProvider'; +import { Button } from '../common/Button2'; +import { Card } from '../common/Card'; +import { Modal, ModalCloseButton, ModalHeader } from '../common/Modal'; +import { Text } from '../common/Text'; +import { View } from '../common/View'; +import { LoadingIndicator } from '../reports/LoadingIndicator'; + +type SelectNewPluginModalProps = { + onSave: () => void; +}; + +type WhiteListPlugin = { + url: string; + version: string; + manifest?: ActualPluginManifest; + error?: string; + loading: boolean; +}; + +export function SelectNewPluginModal({ onSave }: SelectNewPluginModalProps) { + const { t } = useTranslation(); + const [pluginsListLoading, setPluginsListLoading] = useState(true); + const [whiteListPlugins, setWhiteListPlugins] = useState( + [], + ); + const [selectedPlugin, setSelectedPlugin] = useState( + null, + ); + const { plugins, refreshPluginStore, pluginStore } = useActualPlugins(); + + useEffect(() => { + const fetchPlugins = async () => { + try { + const response = await fetch( + 'https://raw.githubusercontent.com/actual-plugins/whitelist/refs/heads/main/plugins.json', + ); + if (response.ok) { + const plugins = (await response.json()) as WhiteListPlugin[]; + const updatedPlugins = await Promise.all( + plugins.map(async plugin => { + plugin.loading = true; + const parsedRepo = parseGitHubRepoUrl(plugin.url); + if (parsedRepo == null) { + throw new Error(`Invalid repo ${plugin.url}`); + } + + try { + const { manifestUrl } = await fetchRelease( + parsedRepo.owner, + parsedRepo.repo, + `tags/${plugin.version}`, + ); + const manifestResponse = await fetch( + `https://cors-anywhere.herokuapp.com/${manifestUrl}`, + ); + if (manifestResponse.ok) { + plugin.manifest = await manifestResponse.json(); + } + } catch (error) { + if (error && typeof error == 'object') { + plugin.error = error.toString(); + } else { + plugin.error = 'unknown error'; + } + } finally { + plugin.loading = false; + } + return plugin; + }), + ); + setWhiteListPlugins(updatedPlugins); + } + } catch (error) { + console.error('Error fetching plugin list:', error); + } finally { + setPluginsListLoading(false); + } + }; + + fetchPlugins(); + }, []); + + async function _onSave() { + if (selectedPlugin && selectedPlugin.manifest) { + await installPluginFromManifest(plugins, selectedPlugin.manifest); + await refreshPluginStore(); + } + onSave(); + } + + return ( + + {({ state: { close } }) => ( + <> + } + /> + {pluginsListLoading ? ( + + + Loading plugin list... + + + + ) : ( + + + {whiteListPlugins.map(plugin => ( + { + if ( + pluginStore.some(p => p.name === plugin.manifest?.name) + ) { + return; + } + + if (selectedPlugin === plugin) { + setSelectedPlugin(null); + } else if (plugin.manifest) { + setSelectedPlugin(plugin); + } + }} + style={{ + padding: 6, + minWidth: 250, + maxWidth: 250, + height: 250, + backgroundColor: pluginStore.some( + p => p.name === plugin.manifest?.name, + ) + ? theme.buttonPrimaryDisabledBackground + : selectedPlugin === plugin + ? theme.buttonNormalSelectedBackground + : theme.tableBackground, + cursor: pluginStore.some( + p => p.name === plugin.manifest?.name, + ) + ? 'inherit' + : 'pointer', + }} + > + + {plugin.loading ? ( + + + {plugin.url}:{plugin.version} + + + + ) : plugin.error ? ( + + {t('Error')}: {plugin.error} + + ) : ( + <> + + Plugin Name: + + {plugin.manifest?.name} + + Version: + + {plugin.manifest?.version} + + Description: + + {plugin.manifest?.description} + + Author: + + {plugin.manifest?.author} + + Repository: + + {plugin.manifest?.url} + + )} + + + ))} + + + {selectedPlugin && ( + + )} + + + )} + + )} + + ); +} diff --git a/packages/desktop-client/src/components/plugins/ManagePlugins.tsx b/packages/desktop-client/src/components/plugins/ManagePlugins.tsx new file mode 100644 index 00000000000..232da819256 --- /dev/null +++ b/packages/desktop-client/src/components/plugins/ManagePlugins.tsx @@ -0,0 +1,221 @@ +import { useTranslation } from 'react-i18next'; +import { useDispatch } from 'react-redux'; + +import { pushModal } from 'loot-core/client/actions'; +import { type ActualPluginStored } from 'loot-core/types/models/actual-plugin-stored'; + +import { theme } from '../../style'; +import { useActualPlugins } from '../ActualPluginsProvider'; +import { Button } from '../common/Button2'; +import { Link } from '../common/Link'; +import { SimpleTable } from '../common/SimpleTable'; +import { Stack } from '../common/Stack'; +import { Text } from '../common/Text'; +import { View } from '../common/View'; +import { Cell, Row } from '../table'; + +import { PluginsHeader } from './PluginsHeader'; + +export function ManagePlugins() { + const dispatch = useDispatch(); + const { t } = useTranslation(); + return ( + + + + + {t( + 'Manage and configure plugins to enhance functionality and efficiency.', + )}{' '} + + {t('Learn more')} + + + + + {/* */} + + + + {}} + // Hide the last border of the item in the table + style={{ marginBottom: -1 }} + > +
+ + {/* {filteredRules.length === 0 ? ( + + ) : ( + onDeleteRule(rule.id)} + /> + )} */} + + + + + {/* {selectedInst.items.size > 0 && ( + + )} */} + + + + + ); +} + +function PluginList() { + const { pluginStore, plugins } = useActualPlugins(); + return ( + <> + {pluginStore.map(plugin => ( + p.name === plugin.name)} + /> + ))} + + ); +} + +type PluginRowProps = { + plugin: ActualPluginStored; + enabled: boolean; +}; +function PluginRow({ plugin, enabled }: PluginRowProps) { + return ( + + + + {plugin.name} + + + + + {plugin.version} + + + + + {plugin.url} + + + + + {enabled ? 'Running' : ''} + + + + + {plugin.description} + + + + + + + ); +} diff --git a/packages/desktop-client/src/components/plugins/Plugins.tsx b/packages/desktop-client/src/components/plugins/Plugins.tsx new file mode 100644 index 00000000000..289a77f181a --- /dev/null +++ b/packages/desktop-client/src/components/plugins/Plugins.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Page } from '../Page'; + +import { ManagePlugins } from './ManagePlugins'; + +export function Plugins() { + const { t } = useTranslation(); + return ( + + + + ); +} diff --git a/packages/desktop-client/src/components/plugins/PluginsHeader.tsx b/packages/desktop-client/src/components/plugins/PluginsHeader.tsx new file mode 100644 index 00000000000..d9607edc944 --- /dev/null +++ b/packages/desktop-client/src/components/plugins/PluginsHeader.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Cell, TableHeader } from '../table'; + +export function PluginsHeader() { + const { t } = useTranslation(); + + return ( + + + + + + + + + ); +} diff --git a/packages/desktop-client/src/components/settings/Experimental.tsx b/packages/desktop-client/src/components/settings/Experimental.tsx index 09d46d28513..797fbaa852c 100644 --- a/packages/desktop-client/src/components/settings/Experimental.tsx +++ b/packages/desktop-client/src/components/settings/Experimental.tsx @@ -102,6 +102,12 @@ export function ExperimentalFeatures() { > OpenID authentication method + + Plugins + ) : ( (themes); + const [themeOptionsExtended, setThemeOptionsExtended] = + useState(themeOptions); + const { plugins: loadedPlugins } = useActualPlugins(); + + useEffect(() => { + const customThemes = + loadedPlugins?.reduce((acc, plugin) => { + if ( + plugin && + plugin.availableThemes && + plugin.availableThemes().length > 0 + ) { + plugin + .availableThemes() + .filter(theme => theme !== undefined) + .forEach(theme => { + acc = { + ...acc, + [theme]: { + name: theme, + colors: plugin?.getThemeSchema?.(theme) ?? {}, + }, + }; + }); + } + return acc; + }, {} as ThemesExtendedType) ?? ({} as ThemesExtendedType); + + setThemesExtended({ ...themes, ...customThemes }); + }, [loadedPlugins]); + + useEffect(() => { + setThemeOptionsExtended( + Object.entries(themesExtended).map( + ([key, { name }]) => [key, name] as [Theme, string], + ), + ); + }, [themesExtended]); return ( onChange={value => { switchTheme(value); + if ( + !Object.keys(themes).some(t => t === value) && + typeof themesExtended === 'object' + ) { + setThemeObject(JSON.stringify(themesExtended?.[value] ?? '')); + } else { + setThemeObject(null); + } }} value={theme} - options={themeOptions} + options={themeOptionsExtended} className={css({ '&[data-hovered]': { backgroundColor: themeStyle.buttonNormalBackgroundHover, diff --git a/packages/desktop-client/src/components/sidebar/PrimaryButtons.tsx b/packages/desktop-client/src/components/sidebar/PrimaryButtons.tsx index 51063cc8603..c4045c37916 100644 --- a/packages/desktop-client/src/components/sidebar/PrimaryButtons.tsx +++ b/packages/desktop-client/src/components/sidebar/PrimaryButtons.tsx @@ -2,10 +2,12 @@ import React, { useState, useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom'; +import { useFeatureFlag } from '../../hooks/useFeatureFlag'; import { SvgCheveronDown, SvgCheveronRight, SvgCog, + SvgPlugin, SvgReports, SvgStoreFront, SvgTuning, @@ -18,6 +20,7 @@ import { Item } from './Item'; import { SecondaryItem } from './SecondaryItem'; export function PrimaryButtons() { + const pluginsEnabled = useFeatureFlag('plugins'); const { t } = useTranslation(); const [isOpen, setOpen] = useState(false); const onToggle = useCallback(() => setOpen(open => !open), []); @@ -59,6 +62,14 @@ export function PrimaryButtons() { to="/rules" indent={15} /> + {pluginsEnabled && ( + + )} = { upcomingLengthAdjustment: false, contextMenus: false, openidAuth: false, + plugins: false, }; export function useFeatureFlag(name: FeatureFlag): boolean { diff --git a/packages/desktop-client/src/style/theme.tsx b/packages/desktop-client/src/style/theme.tsx index ad29777e430..9695cfe5a46 100644 --- a/packages/desktop-client/src/style/theme.tsx +++ b/packages/desktop-client/src/style/theme.tsx @@ -4,6 +4,8 @@ import { useEffect, useState } from 'react'; import { isNonProductionEnvironment } from 'loot-core/src/shared/environment'; import type { DarkTheme, Theme } from 'loot-core/src/types/prefs'; +import { type ThemeDefinition } from '../../../plugins-shared/src'; +import { useActualPlugins } from '../components/ActualPluginsProvider'; import { useGlobalPref } from '../hooks/useGlobalPref'; import * as darkTheme from './themes/dark'; @@ -11,7 +13,7 @@ import * as developmentTheme from './themes/development'; import * as lightTheme from './themes/light'; import * as midnightTheme from './themes/midnight'; -const themes = { +export const themes = { light: { name: 'Light', colors: lightTheme }, dark: { name: 'Dark', colors: darkTheme }, midnight: { name: 'Midnight', colors: midnightTheme }, @@ -32,7 +34,8 @@ export const darkThemeOptions = Object.entries({ export function useTheme() { const [theme = 'auto', setThemePref] = useGlobalPref('theme'); - return [theme, setThemePref] as const; + const [customTheme, setCustomTheme] = useGlobalPref('customTheme'); + return [theme, setThemePref, customTheme, setCustomTheme] as const; } export function usePreferredDarkTheme() { @@ -42,25 +45,47 @@ export function usePreferredDarkTheme() { } export function ThemeStyle() { - const [theme] = useTheme(); + const [theme, , customTheme] = useTheme(); + const [themesExtended, setThemesExtended] = useState(themes); + const [darkThemePreference] = usePreferredDarkTheme(); - const [themeColors, setThemeColors] = useState< - | typeof lightTheme - | typeof darkTheme - | typeof midnightTheme - | typeof developmentTheme - | undefined - >(undefined); + const [themeColors, setThemeColors] = useState( + undefined, + ); + + const { plugins: loadedPlugins } = useActualPlugins(); + + useEffect(() => { + const customThemes = + loadedPlugins?.reduce((acc, plugin) => { + if (plugin.availableThemes()?.length) { + plugin.availableThemes().forEach(theme => { + acc[theme] = { + name: theme, + colors: plugin.getThemeSchema(theme), + }; + }); + } + return acc; + }, {}) ?? {}; + + setThemesExtended({ ...themes, ...customThemes }); + }, [loadedPlugins]); useEffect(() => { + if (customTheme) { + setThemeColors(JSON.parse(customTheme).colors); + return; + } + if (theme === 'auto') { - const darkTheme = themes[darkThemePreference]; + const darkTheme = themesExtended[darkThemePreference]; function darkThemeMediaQueryListener(event: MediaQueryListEvent) { if (event.matches) { setThemeColors(darkTheme.colors); } else { - setThemeColors(themes['light'].colors); + setThemeColors(themesExtended['light'].colors); } } const darkThemeMediaQuery = window.matchMedia( @@ -75,7 +100,7 @@ export function ThemeStyle() { if (darkThemeMediaQuery.matches) { setThemeColors(darkTheme.colors); } else { - setThemeColors(themes['light'].colors); + setThemeColors(themesExtended['light'].colors); } return () => { @@ -85,9 +110,9 @@ export function ThemeStyle() { ); }; } else { - setThemeColors(themes[theme].colors); + setThemeColors(themesExtended[theme].colors); } - }, [theme, darkThemePreference]); + }, [theme, darkThemePreference, themesExtended, customTheme]); if (!themeColors) return null; diff --git a/packages/loot-core/src/client/state-types/modals.d.ts b/packages/loot-core/src/client/state-types/modals.d.ts index 8e09e5ba3e6..7363416c3f1 100644 --- a/packages/loot-core/src/client/state-types/modals.d.ts +++ b/packages/loot-core/src/client/state-types/modals.d.ts @@ -337,6 +337,9 @@ type FinanceModals = { }; 'keyboard-shortcuts': EmptyObject; 'goal-templates': EmptyObject; + 'select-new-plugin': { + onSave?: () => void; + }; }; export type PushModalAction = { diff --git a/packages/loot-core/src/platform/server/indexeddb/index.web.ts b/packages/loot-core/src/platform/server/indexeddb/index.web.ts index f7396545491..09ccaf5fe2a 100644 --- a/packages/loot-core/src/platform/server/indexeddb/index.web.ts +++ b/packages/loot-core/src/platform/server/indexeddb/index.web.ts @@ -30,6 +30,9 @@ function _openDatabase() { if (!db.objectStoreNames.contains('files')) { db.createObjectStore('files', { keyPath: 'filepath' }); } + if (!db.objectStoreNames.contains('plugins')) { + db.createObjectStore('plugins', { keyPath: 'url' }); + } }; openRequest.onblocked = e => console.log('blocked', e); diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index f4ee2c7b69d..12a9d97928f 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -1349,6 +1349,9 @@ handlers['save-global-prefs'] = async function (prefs) { if ('theme' in prefs) { await asyncStorage.setItem('theme', prefs.theme); } + if ('customTheme' in prefs) { + await asyncStorage.setItem('customTheme', prefs.customTheme); + } if ('preferredDarkTheme' in prefs) { await asyncStorage.setItem( 'preferred-dark-theme', @@ -1371,6 +1374,7 @@ handlers['load-global-prefs'] = async function () { [, documentDir], [, encryptKey], [, theme], + [, customTheme], [, preferredDarkTheme], [, serverSelfSignedCert], ] = await asyncStorage.multiGet([ @@ -1379,6 +1383,7 @@ handlers['load-global-prefs'] = async function () { 'document-dir', 'encrypt-key', 'theme', + 'customTheme', 'preferred-dark-theme', 'server-self-signed-cert', ]); @@ -1392,9 +1397,11 @@ handlers['load-global-prefs'] = async function () { theme === 'dark' || theme === 'auto' || theme === 'development' || - theme === 'midnight' + theme === 'midnight' || + customTheme ? theme : 'auto', + customTheme, preferredDarkTheme: preferredDarkTheme === 'dark' || preferredDarkTheme === 'midnight' ? preferredDarkTheme diff --git a/packages/loot-core/src/types/models/actual-plugin-stored.d.ts b/packages/loot-core/src/types/models/actual-plugin-stored.d.ts new file mode 100644 index 00000000000..5c3ca9b8e52 --- /dev/null +++ b/packages/loot-core/src/types/models/actual-plugin-stored.d.ts @@ -0,0 +1,6 @@ +import { ActualPluginManifest } from '../../../../plugins-shared/src'; + +export type ActualPluginStored = { + plugin: string; + enabled: boolean; +} & ActualPluginManifest; diff --git a/packages/loot-core/src/types/prefs.d.ts b/packages/loot-core/src/types/prefs.d.ts index 97610ce05d8..c140c64a89d 100644 --- a/packages/loot-core/src/types/prefs.d.ts +++ b/packages/loot-core/src/types/prefs.d.ts @@ -3,7 +3,8 @@ export type FeatureFlag = | 'actionTemplating' | 'upcomingLengthAdjustment' | 'contextMenus' - | 'openidAuth'; + | 'openidAuth' + | 'plugins'; /** * Cross-device preferences. These sync across devices when they are changed. @@ -71,7 +72,13 @@ export type LocalPrefs = Partial<{ 'mobile.showSpentColumn': boolean; }>; -export type Theme = 'light' | 'dark' | 'auto' | 'midnight' | 'development'; +export type Theme = + | 'light' + | 'dark' + | 'auto' + | 'midnight' + | 'development' + | string; export type DarkTheme = 'dark' | 'midnight'; export type GlobalPrefs = Partial<{ floatingSidebar: boolean; @@ -81,6 +88,7 @@ export type GlobalPrefs = Partial<{ preferredDarkTheme: DarkTheme; documentDir: string; // Electron only serverSelfSignedCert: string; // Electron only + customTheme: string | null; }>; export type AuthMethods = 'password' | 'openid'; diff --git a/packages/plugins-shared/.gitignore b/packages/plugins-shared/.gitignore new file mode 100644 index 00000000000..a547bf36d8d --- /dev/null +++ b/packages/plugins-shared/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/plugins-shared/README.md b/packages/plugins-shared/README.md new file mode 100644 index 00000000000..3d2092114bc --- /dev/null +++ b/packages/plugins-shared/README.md @@ -0,0 +1 @@ +# plugins-shared diff --git a/packages/plugins-shared/package.json b/packages/plugins-shared/package.json new file mode 100644 index 00000000000..74c358eaca1 --- /dev/null +++ b/packages/plugins-shared/package.json @@ -0,0 +1,21 @@ +{ + "name": "plugins-shared", + "version": "1.0.0", + "main": "dist/index.cjs.js", + "module": "dist/index.es.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "vite build" + }, + "devDependencies": { + "@emotion/css": "^11.13.4", + "@types/react": "^18.2.0", + "i18next": "^23.11.5", + "react": "18.2.0", + "throttleit": "^1.0.1", + "typescript": "^5.0.0", + "vite": "^5.2.14", + "vite-plugin-dts": "4.3.0" + }, + "packageManager": "yarn@4.3.1" +} diff --git a/packages/plugins-shared/src/globals.d.ts b/packages/plugins-shared/src/globals.d.ts new file mode 100644 index 00000000000..f9aba659ca9 --- /dev/null +++ b/packages/plugins-shared/src/globals.d.ts @@ -0,0 +1,8 @@ +import { type CSSObject } from '@emotion/css/dist/declarations/src/create-instance'; + +declare module 'react' { + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + interface CSSProperties extends CSSObject {} +} + +export {}; diff --git a/packages/plugins-shared/src/index.ts b/packages/plugins-shared/src/index.ts new file mode 100644 index 00000000000..30bc0ce6e22 --- /dev/null +++ b/packages/plugins-shared/src/index.ts @@ -0,0 +1,5 @@ +export type { ActualPlugin } from './interfaces/actualPlugin'; +export type { ThemeDefinition } from './interfaces/themeDefinition'; +export type { ActualPluginEntry } from './interfaces/actualPluginEntry'; + +export type { ActualPluginManifest } from './interfaces/actualPluginManifest'; diff --git a/packages/plugins-shared/src/interfaces/actualPlugin.ts b/packages/plugins-shared/src/interfaces/actualPlugin.ts new file mode 100644 index 00000000000..3b68b760280 --- /dev/null +++ b/packages/plugins-shared/src/interfaces/actualPlugin.ts @@ -0,0 +1,12 @@ +import { CSSProperties } from 'react'; + +import { ThemeDefinition } from './themeDefinition'; + +export interface ActualPlugin { + name: string; + version: string; + availableThemes?: () => string[]; + getThemeIcon?: (themeName: string, properties?: CSSProperties) => JSX.Element; + getThemeSchema?: (themeName: string) => ThemeDefinition; + uninstall: (db: IDBDatabase) => void; +} diff --git a/packages/plugins-shared/src/interfaces/actualPluginEntry.ts b/packages/plugins-shared/src/interfaces/actualPluginEntry.ts new file mode 100644 index 00000000000..b8f93a0c5fa --- /dev/null +++ b/packages/plugins-shared/src/interfaces/actualPluginEntry.ts @@ -0,0 +1,5 @@ +import React from 'react'; + +import { ActualPlugin } from './actualPlugin'; + +export type ActualPluginEntry = (react: typeof React) => ActualPlugin; diff --git a/packages/plugins-shared/src/interfaces/actualPluginManifest.ts b/packages/plugins-shared/src/interfaces/actualPluginManifest.ts new file mode 100644 index 00000000000..64c90c974fe --- /dev/null +++ b/packages/plugins-shared/src/interfaces/actualPluginManifest.ts @@ -0,0 +1,10 @@ +export interface ActualPluginManifest { + url: string; + name: string; + version: string; + description?: string; + pluginType: 'server' | 'client'; + minimumActualVersion: string; + author: string; + plugin?: string; +} diff --git a/packages/plugins-shared/src/interfaces/themeDefinition.ts b/packages/plugins-shared/src/interfaces/themeDefinition.ts new file mode 100644 index 00000000000..1f41f64f4c1 --- /dev/null +++ b/packages/plugins-shared/src/interfaces/themeDefinition.ts @@ -0,0 +1,196 @@ +export interface ThemeDefinition { + pageBackground?: string; + pageBackgroundModalActive?: string; + pageBackgroundTopLeft?: string; + pageBackgroundBottomRight?: string; + pageBackgroundLineTop?: string; + pageBackgroundLineMid?: string; + pageBackgroundLineBottom?: string; + pageText?: string; + pageTextLight?: string; + pageTextSubdued?: string; + pageTextDark?: string; + pageTextPositive?: string; + pageTextLink?: string; + pageTextLinkLight?: string; + cardBackground?: string; + cardBorder?: string; + cardShadow?: string; + tableBackground?: string; + tableRowBackgroundHover?: string; + tableText?: string; + tableTextLight?: string; + tableTextSubdued?: string; + tableTextSelected?: string; + tableTextHover?: string; + tableTextInactive?: string; + tableHeaderText?: string; + tableHeaderBackground?: string; + tableBorder?: string; + tableBorderSelected?: string; + tableBorderHover?: string; + tableBorderSeparator?: string; + tableRowBackgroundHighlight?: string; + tableRowBackgroundHighlightText?: string; + tableRowHeaderBackground?: string; + tableRowHeaderText?: string; + sidebarBackground?: string; + sidebarItemBackgroundPending?: string; + sidebarItemBackgroundPositive?: string; + sidebarItemBackgroundFailed?: string; + sidebarItemAccentSelected?: string; + sidebarItemBackgroundHover?: string; + sidebarItemText?: string; + sidebarItemTextSelected?: string; + menuBackground?: string; + menuItemBackground?: string; + menuItemBackgroundHover?: string; + menuItemText?: string; + menuItemTextHover?: string; + menuItemTextSelected?: string; + menuItemTextHeader?: string; + menuBorder?: string; + menuBorderHover?: string; + menuKeybindingText?: string; + menuAutoCompleteBackground?: string; + menuAutoCompleteBackgroundHover?: string; + menuAutoCompleteText?: string; + menuAutoCompleteTextHover?: string; + menuAutoCompleteTextHeader?: string; + menuAutoCompleteItemTextHover?: string; + menuAutoCompleteItemText?: string; + modalBackground?: string; + modalBorder?: string; + mobileHeaderBackground?: string; + mobileHeaderText?: string; + mobileHeaderTextSubdued?: string; + mobileHeaderTextHover?: string; + mobilePageBackground?: string; + mobileNavBackground?: string; + mobileNavItem?: string; + mobileNavItemSelected?: string; + mobileAccountShadow?: string; + mobileAccountText?: string; + mobileTransactionSelected?: string; + mobileViewTheme?: string; + mobileConfigServerViewTheme?: string; + markdownNormal?: string; + markdownDark?: string; + markdownLight?: string; + buttonMenuText?: string; + buttonMenuTextHover?: string; + buttonMenuBackground?: string; + buttonMenuBackgroundHover?: string; + buttonMenuBorder?: string; + buttonMenuSelectedText?: string; + buttonMenuSelectedTextHover?: string; + buttonMenuSelectedBackground?: string; + buttonMenuSelectedBackgroundHover?: string; + buttonMenuSelectedBorder?: string; + buttonPrimaryText?: string; + buttonPrimaryTextHover?: string; + buttonPrimaryBackground?: string; + buttonPrimaryBackgroundHover?: string; + buttonPrimaryBorder?: string; + buttonPrimaryShadow?: string; + buttonPrimaryDisabledText?: string; + buttonPrimaryDisabledBackground?: string; + buttonPrimaryDisabledBorder?: string; + buttonNormalText?: string; + buttonNormalTextHover?: string; + buttonNormalBackground?: string; + buttonNormalBackgroundHover?: string; + buttonNormalBorder?: string; + buttonNormalShadow?: string; + buttonNormalSelectedText?: string; + buttonNormalSelectedBackground?: string; + buttonNormalDisabledText?: string; + buttonNormalDisabledBackground?: string; + buttonNormalDisabledBorder?: string; + buttonBareText?: string; + buttonBareTextHover?: string; + buttonBareBackground?: string; + buttonBareBackgroundHover?: string; + buttonBareBackgroundActive?: string; + buttonBareDisabledText?: string; + buttonBareDisabledBackground?: string; + calendarText?: string; + calendarBackground?: string; + calendarItemText?: string; + calendarItemBackground?: string; + calendarSelectedBackground?: string; + noticeBackground?: string; + noticeBackgroundLight?: string; + noticeBackgroundDark?: string; + noticeText?: string; + noticeTextLight?: string; + noticeTextDark?: string; + noticeTextMenu?: string; + noticeTextMenuHover?: string; + noticeBorder?: string; + warningBackground?: string; + warningText?: string; + warningTextLight?: string; + warningTextDark?: string; + warningBorder?: string; + errorBackground?: string; + errorText?: string; + errorTextDark?: string; + errorTextDarker?: string; + errorTextMenu?: string; + errorBorder?: string; + upcomingBackground?: string; + upcomingText?: string; + upcomingBorder?: string; + formLabelText?: string; + formLabelBackground?: string; + formInputBackground?: string; + formInputBackgroundSelected?: string; + formInputBackgroundSelection?: string; + formInputBorder?: string; + formInputTextReadOnlySelection?: string; + formInputBorderSelected?: string; + formInputText?: string; + formInputTextSelected?: string; + formInputTextPlaceholder?: string; + formInputTextPlaceholderSelected?: string; + formInputTextSelection?: string; + formInputShadowSelected?: string; + formInputTextHighlight?: string; + checkboxText?: string; + checkboxBackgroundSelected?: string; + checkboxBorderSelected?: string; + checkboxShadowSelected?: string; + checkboxToggleBackground?: string; + checkboxToggleBackgroundSelected?: string; + checkboxToggleDisabled?: string; + pillBackground?: string; + pillBackgroundLight?: string; + pillText?: string; + pillTextHighlighted?: string; + pillBorder?: string; + pillBorderDark?: string; + pillBackgroundSelected?: string; + pillTextSelected?: string; + pillBorderSelected?: string; + pillTextSubdued?: string; + reportsRed?: string; + reportsBlue?: string; + reportsGreen?: string; + reportsGray?: string; + reportsLabel?: string; + reportsInnerLabel?: string; + noteTagBackground?: string; + noteTagBackgroundHover?: string; + noteTagText?: string; + budgetOtherMonth?: string; + budgetCurrentMonth?: string; + budgetHeaderOtherMonth?: string; + budgetHeaderCurrentMonth?: string; + floatingActionBarBackground?: string; + floatingActionBarBorder?: string; + floatingActionBarText?: string; + tooltipText?: string; + tooltipBackground?: string; + tooltipBorder?: string; +} diff --git a/packages/plugins-shared/tsconfig.json b/packages/plugins-shared/tsconfig.json new file mode 100644 index 00000000000..45a4c256942 --- /dev/null +++ b/packages/plugins-shared/tsconfig.json @@ -0,0 +1,41 @@ +{ + "references": [ + ], + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + "downlevelIteration": true, + "strictFunctionTypes": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "jsx": "preserve", + "types": ["vite/client", "jest"], + "allowJs": true, + "checkJs": false, + "outDir": "dist", + "moduleResolution": "Node10", + "module": "ES2022", + "noEmit": true, + "plugins": [ + { + "name": "typescript-strict-plugin", + "path": ["./packages"] + } + ] + }, + "include": ["packages/**/*"], + "exclude": [ + "node_modules", + "**/node_modules/*", + "**/dist/*", + ], + "ts-node": { + "compilerOptions": { + "module": "CommonJS" + } + } +} diff --git a/packages/plugins-shared/vite.config.mts b/packages/plugins-shared/vite.config.mts new file mode 100644 index 00000000000..5d6741802c5 --- /dev/null +++ b/packages/plugins-shared/vite.config.mts @@ -0,0 +1,29 @@ +import * as path from 'path'; +import { fileURLToPath } from 'url'; + +import { defineConfig } from 'vite'; +import dts from 'vite-plugin-dts'; + +export default defineConfig({ + plugins: [dts({ insertTypesEntry: true, include: ['src'] })], + build: { + lib: { + entry: path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + 'src/index.ts', + ), + name: 'Shared', + formats: ['es', 'cjs'], + fileName: format => `index.${format}.js`, + }, + rollupOptions: { + external: ['react', 'react-dom', '@emotion/css'], + output: { + globals: { + react: 'React', + 'react-dom': 'ReactDOM', + }, + }, + }, + }, +}); diff --git a/upcoming-release-notes/4001.md b/upcoming-release-notes/4001.md new file mode 100644 index 00000000000..338a1abd961 --- /dev/null +++ b/upcoming-release-notes/4001.md @@ -0,0 +1,6 @@ +--- +category: Features +authors: [lelemm] +--- + +Plugin support for Actual Budget. diff --git a/yarn.lock b/yarn.lock index f6a4ad2ab0c..8985671a5a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -428,6 +428,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-string-parser@npm:7.25.9" + checksum: 10/c28656c52bd48e8c1d9f3e8e68ecafd09d949c57755b0d353739eb4eae7ba4f7e67e92e4036f1cd43378cc1397a2c943ed7bcaf5949b04ab48607def0258b775 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.22.20, @babel/helper-validator-identifier@npm:^7.24.5, @babel/helper-validator-identifier@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-validator-identifier@npm:7.24.7" @@ -435,6 +442,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-validator-identifier@npm:7.25.9" + checksum: 10/3f9b649be0c2fd457fa1957b694b4e69532a668866b8a0d81eabfa34ba16dbf3107b39e0e7144c55c3c652bf773ec816af8df4a61273a2bb4eb3145ca9cf478e + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.23.5": version: 7.23.5 resolution: "@babel/helper-validator-option@npm:7.23.5" @@ -487,6 +501,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.25.3": + version: 7.26.3 + resolution: "@babel/parser@npm:7.26.3" + dependencies: + "@babel/types": "npm:^7.26.3" + bin: + parser: ./bin/babel-parser.js + checksum: 10/e7e3814b2dc9ee3ed605d38223471fa7d3a84cbe9474d2b5fa7ac57dc1ddf75577b1fd3a93bf7db8f41f28869bda795cddd80223f980be23623b6434bf4c88a8 + languageName: node + linkType: hard + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.23.3": version: 7.23.3 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.23.3" @@ -1500,6 +1525,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.26.3": + version: 7.26.3 + resolution: "@babel/types@npm:7.26.3" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.25.9" + checksum: 10/c31d0549630a89abfa11410bf82a318b0c87aa846fbf5f9905e47ba5e2aa44f41cc746442f105d622c519e4dc532d35a8d8080460ff4692f9fc7485fbf3a00eb + languageName: node + linkType: hard + "@bcoe/v8-coverage@npm:^0.2.3": version: 0.2.3 resolution: "@bcoe/v8-coverage@npm:0.2.3" @@ -2632,6 +2667,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 10/4ed6123217569a1484419ac53f6ea0d9f3b57e5b57ab30d7c267bdb27792a27eb0e4b08e84a2680aa55cc2f2b411ffd6ec3db01c44fdc6dc43aca4b55f8374fd + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:0.3.9": version: 0.3.9 resolution: "@jridgewell/trace-mapping@npm:0.3.9" @@ -2689,6 +2731,59 @@ __metadata: languageName: node linkType: hard +"@microsoft/api-extractor-model@npm:7.30.1": + version: 7.30.1 + resolution: "@microsoft/api-extractor-model@npm:7.30.1" + dependencies: + "@microsoft/tsdoc": "npm:~0.15.1" + "@microsoft/tsdoc-config": "npm:~0.17.1" + "@rushstack/node-core-library": "npm:5.10.1" + checksum: 10/c805be8aa4e1023da06caab5af5a931ab9b7d69522a7547df05dcb9c66083af1f68866b0bb2b734778801b0b6d342e2b64e24fe4bfae39137a29538cd4601dcc + languageName: node + linkType: hard + +"@microsoft/api-extractor@npm:^7.47.11": + version: 7.48.1 + resolution: "@microsoft/api-extractor@npm:7.48.1" + dependencies: + "@microsoft/api-extractor-model": "npm:7.30.1" + "@microsoft/tsdoc": "npm:~0.15.1" + "@microsoft/tsdoc-config": "npm:~0.17.1" + "@rushstack/node-core-library": "npm:5.10.1" + "@rushstack/rig-package": "npm:0.5.3" + "@rushstack/terminal": "npm:0.14.4" + "@rushstack/ts-command-line": "npm:4.23.2" + lodash: "npm:~4.17.15" + minimatch: "npm:~3.0.3" + resolve: "npm:~1.22.1" + semver: "npm:~7.5.4" + source-map: "npm:~0.6.1" + typescript: "npm:5.4.2" + bin: + api-extractor: bin/api-extractor + checksum: 10/18b9dc1820d6489049d9e6db48f3eeee6028521ee90e067b155b42e38fe03e1048e914279fab0d0993e6f07f0c257579afa0b04a1145ec50b54e44f21ae9736a + languageName: node + linkType: hard + +"@microsoft/tsdoc-config@npm:~0.17.1": + version: 0.17.1 + resolution: "@microsoft/tsdoc-config@npm:0.17.1" + dependencies: + "@microsoft/tsdoc": "npm:0.15.1" + ajv: "npm:~8.12.0" + jju: "npm:~1.4.0" + resolve: "npm:~1.22.2" + checksum: 10/19f57b752413916c7ad14466650f48ba1acaf674411b6a44065e93f762d391e501cb553eeb8ae3834f1f1f064ddc83a26bdbd8026c9b2c0c194fe90818078eb9 + languageName: node + linkType: hard + +"@microsoft/tsdoc@npm:0.15.1, @microsoft/tsdoc@npm:~0.15.1": + version: 0.15.1 + resolution: "@microsoft/tsdoc@npm:0.15.1" + checksum: 10/1a92612883088fe184dba596e7ba7a0daef0e6981caeca22bad6ad551d2247294f12e368537d0d8192525cf5743f7f15fcc2ad7b3b849f26a09a15ffdd89fd0c + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -4636,6 +4731,22 @@ __metadata: languageName: node linkType: hard +"@rollup/pluginutils@npm:^5.1.0": + version: 5.1.4 + resolution: "@rollup/pluginutils@npm:5.1.4" + dependencies: + "@types/estree": "npm:^1.0.0" + estree-walker: "npm:^2.0.2" + picomatch: "npm:^4.0.2" + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 10/598f628988af25541a9a6c6ef154aaf350f8be3238884e500cc0e47138684071abe490563c953f9bda9e8b113ecb1f99c11abfb9dbaf4f72cdd62e257a673fa3 + languageName: node + linkType: hard + "@rollup/rollup-android-arm-eabi@npm:4.9.4": version: 4.9.4 resolution: "@rollup/rollup-android-arm-eabi@npm:4.9.4" @@ -4752,6 +4863,64 @@ __metadata: languageName: node linkType: hard +"@rushstack/node-core-library@npm:5.10.1": + version: 5.10.1 + resolution: "@rushstack/node-core-library@npm:5.10.1" + dependencies: + ajv: "npm:~8.13.0" + ajv-draft-04: "npm:~1.0.0" + ajv-formats: "npm:~3.0.1" + fs-extra: "npm:~7.0.1" + import-lazy: "npm:~4.0.0" + jju: "npm:~1.4.0" + resolve: "npm:~1.22.1" + semver: "npm:~7.5.4" + peerDependencies: + "@types/node": "*" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/440aade872d13c4b39eeaa762bdfca713fea60d34c72f7b869db345ef9e9aed62c7f9fb175bc90684cf0d1f46e1e5cd778941baf43b01f2ee576a51f2ba2cce7 + languageName: node + linkType: hard + +"@rushstack/rig-package@npm:0.5.3": + version: 0.5.3 + resolution: "@rushstack/rig-package@npm:0.5.3" + dependencies: + resolve: "npm:~1.22.1" + strip-json-comments: "npm:~3.1.1" + checksum: 10/b58a3925a41d7a0e79f4fde7c400a379683cc7b0073c447aba6d36231529a37e7d2f4559f459be785ad862ecb01b618b2d0ff60661046e5223437356155ccb14 + languageName: node + linkType: hard + +"@rushstack/terminal@npm:0.14.4": + version: 0.14.4 + resolution: "@rushstack/terminal@npm:0.14.4" + dependencies: + "@rushstack/node-core-library": "npm:5.10.1" + supports-color: "npm:~8.1.1" + peerDependencies: + "@types/node": "*" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/aede44b4255225777acd7887b1adba7b8cca578fe9adf4283c5591a0f28dbd45fb38f6b5f04a56973828e28da6a9d951da046bcb4f576d5fa360c8893cac6861 + languageName: node + linkType: hard + +"@rushstack/ts-command-line@npm:4.23.2": + version: 4.23.2 + resolution: "@rushstack/ts-command-line@npm:4.23.2" + dependencies: + "@rushstack/terminal": "npm:0.14.4" + "@types/argparse": "npm:1.0.38" + argparse: "npm:~1.0.9" + string-argv: "npm:~0.3.1" + checksum: 10/583cd23d2d3e3e9772184764a1a7e50dcb863fe1c6affefe196a935a69c64aaaa32cc71aa3fb55fb2dfbd353cc25db3f0b9999b79bbe13b75ee476a51cb48717 + languageName: node + linkType: hard + "@sideway/address@npm:^4.1.3": version: 4.1.4 resolution: "@sideway/address@npm:4.1.4" @@ -5258,6 +5427,13 @@ __metadata: languageName: node linkType: hard +"@types/argparse@npm:1.0.38": + version: 1.0.38 + resolution: "@types/argparse@npm:1.0.38" + checksum: 10/26ed7e3f1e3595efdb883a852f5205f971b798e4c28b7e30a32c5298eee596e8b45834ce831f014d250b9730819ab05acff5b31229666d3af4ba465b4697d0eb + languageName: node + linkType: hard + "@types/aria-query@npm:^5.0.1": version: 5.0.1 resolution: "@types/aria-query@npm:5.0.1" @@ -6066,6 +6242,94 @@ __metadata: languageName: node linkType: hard +"@volar/language-core@npm:2.4.11, @volar/language-core@npm:~2.4.1": + version: 2.4.11 + resolution: "@volar/language-core@npm:2.4.11" + dependencies: + "@volar/source-map": "npm:2.4.11" + checksum: 10/cda5959642eb469ad6c57c3d7ad731503fc3b1f82bc57c3511cc6c925138fe1f6f3ef253ddf1cc6bd3bdd63d8a5943396b337e97ecd8afec8f486ac79db5f258 + languageName: node + linkType: hard + +"@volar/source-map@npm:2.4.11": + version: 2.4.11 + resolution: "@volar/source-map@npm:2.4.11" + checksum: 10/689803b2e788bdfda45a7994605798e8ed5ae7bb1116912db5e8f8c8a18000c0c66ebfae5ce33d3ac4cd3a7e171819253511232ed1eb20b1781b4aaed806d100 + languageName: node + linkType: hard + +"@volar/typescript@npm:^2.4.4": + version: 2.4.11 + resolution: "@volar/typescript@npm:2.4.11" + dependencies: + "@volar/language-core": "npm:2.4.11" + path-browserify: "npm:^1.0.1" + vscode-uri: "npm:^3.0.8" + checksum: 10/fe4be5497a8c12ddf25ee681cb47a3acc78cff4190ed6da7763c6a42f4fb5497cd99fb276ac0e849a70e49c05f9fea73127b10ca3b1a62ed3a33a67c4297215c + languageName: node + linkType: hard + +"@vue/compiler-core@npm:3.5.13": + version: 3.5.13 + resolution: "@vue/compiler-core@npm:3.5.13" + dependencies: + "@babel/parser": "npm:^7.25.3" + "@vue/shared": "npm:3.5.13" + entities: "npm:^4.5.0" + estree-walker: "npm:^2.0.2" + source-map-js: "npm:^1.2.0" + checksum: 10/22f042bb47c8a1edb9d602e24da8092ab542d5640f0459a9b99ecf35f90e96678f870209dd30f774f5340c6d817d3c5a46ca49cefb9659ee5b228bd42d1f076a + languageName: node + linkType: hard + +"@vue/compiler-dom@npm:^3.4.0": + version: 3.5.13 + resolution: "@vue/compiler-dom@npm:3.5.13" + dependencies: + "@vue/compiler-core": "npm:3.5.13" + "@vue/shared": "npm:3.5.13" + checksum: 10/5dc628c52091264a443c2d7326b759d7d3999c7e9c00078c2eb370b778e60b9f2ef258a8decf2fd97c8fa0923f895d449eabc1e5bc3d8a45d3ef99c9eb0599d7 + languageName: node + linkType: hard + +"@vue/compiler-vue2@npm:^2.7.16": + version: 2.7.16 + resolution: "@vue/compiler-vue2@npm:2.7.16" + dependencies: + de-indent: "npm:^1.0.2" + he: "npm:^1.2.0" + checksum: 10/739ad06be19206b2715707c226a070509bcf28c31b539a6fc932d220eb7b0c09109d71fded573ed0c4073429793a3513ca4a4e69ad4f7afc0c5bc3c28639e871 + languageName: node + linkType: hard + +"@vue/language-core@npm:2.1.6": + version: 2.1.6 + resolution: "@vue/language-core@npm:2.1.6" + dependencies: + "@volar/language-core": "npm:~2.4.1" + "@vue/compiler-dom": "npm:^3.4.0" + "@vue/compiler-vue2": "npm:^2.7.16" + "@vue/shared": "npm:^3.4.0" + computeds: "npm:^0.0.1" + minimatch: "npm:^9.0.3" + muggle-string: "npm:^0.4.1" + path-browserify: "npm:^1.0.1" + peerDependencies: + typescript: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/640d4af0031975620cd3a8050bb4b0f4ed333f241ded195e3bf8c4e571c720b4e3bec3947caf2b10e4e2de19deb7621982d15439de3732d510cd43e325c74a50 + languageName: node + linkType: hard + +"@vue/shared@npm:3.5.13, @vue/shared@npm:^3.4.0": + version: 3.5.13 + resolution: "@vue/shared@npm:3.5.13" + checksum: 10/5c0c24f443533392dde08c3e4272ff2e19af9762f90baeaa808850e05106537bbd9e2d2ad2081d979b8c4bc89902395b46036b67f74c60b76025924de37833b1 + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.12.1, @webassemblyjs/ast@npm:^1.12.1": version: 1.12.1 resolution: "@webassemblyjs/ast@npm:1.12.1" @@ -6419,6 +6683,32 @@ __metadata: languageName: node linkType: hard +"ajv-draft-04@npm:~1.0.0": + version: 1.0.0 + resolution: "ajv-draft-04@npm:1.0.0" + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: 10/3f11fa0e7f7359bef6608657f02ab78e9cc62b1fb7bdd860db0d00351b3863a1189c1a23b72466d2d82726cab4eb20725c76f5e7c134a89865e2bfd0e6828137 + languageName: node + linkType: hard + +"ajv-formats@npm:~3.0.1": + version: 3.0.1 + resolution: "ajv-formats@npm:3.0.1" + dependencies: + ajv: "npm:^8.0.0" + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: 10/5679b9f9ced9d0213a202a37f3aa91efcffe59a6de1a6e3da5c873344d3c161820a1f11cc29899661fee36271fd2895dd3851b6461c902a752ad661d1c1e8722 + languageName: node + linkType: hard + "ajv-keywords@npm:^3.4.1, ajv-keywords@npm:^3.5.2": version: 3.5.2 resolution: "ajv-keywords@npm:3.5.2" @@ -6440,7 +6730,19 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.6.0": +"ajv@npm:^8.0.0": + version: 8.17.1 + resolution: "ajv@npm:8.17.1" + dependencies: + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + checksum: 10/ee3c62162c953e91986c838f004132b6a253d700f1e51253b99791e2dbfdb39161bc950ebdc2f156f8568035bb5ed8be7bd78289cd9ecbf3381fe8f5b82e3f33 + languageName: node + linkType: hard + +"ajv@npm:^8.6.0, ajv@npm:~8.12.0": version: 8.12.0 resolution: "ajv@npm:8.12.0" dependencies: @@ -6452,6 +6754,18 @@ __metadata: languageName: node linkType: hard +"ajv@npm:~8.13.0": + version: 8.13.0 + resolution: "ajv@npm:8.13.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + uri-js: "npm:^4.4.1" + checksum: 10/4ada268c9a6e44be87fd295df0f0a91267a7bae8dbc8a67a2d5799c3cb459232839c99d18b035597bb6e3ffe88af6979f7daece854f590a81ebbbc2dfa80002c + languageName: node + linkType: hard + "ansi-escapes@npm:^4.2.1": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" @@ -6604,7 +6918,7 @@ __metadata: languageName: node linkType: hard -"argparse@npm:^1.0.7": +"argparse@npm:^1.0.7, argparse@npm:~1.0.9": version: 1.0.10 resolution: "argparse@npm:1.0.10" dependencies: @@ -7924,6 +8238,13 @@ __metadata: languageName: node linkType: hard +"compare-versions@npm:^6.1.1": + version: 6.1.1 + resolution: "compare-versions@npm:6.1.1" + checksum: 10/9325c0fadfba81afa0ec17e6fc2ef823ba785c693089698b8d9374e5460509f1916a88591644d4cb4045c9a58e47fafbcc0724fe8bf446d2a875a3d6eeddf165 + languageName: node + linkType: hard + "compute-scroll-into-view@npm:^2.0.4": version: 2.0.4 resolution: "compute-scroll-into-view@npm:2.0.4" @@ -7931,6 +8252,13 @@ __metadata: languageName: node linkType: hard +"computeds@npm:^0.0.1": + version: 0.0.1 + resolution: "computeds@npm:0.0.1" + checksum: 10/738625ccec6e483124d0ac79ec5474ab5c9df103ea05afc1fd840eed7d9004e3d6009b7bc806df564d66ad915c1ee1fb017bd91b2b32606a252ea9870b6a4026 + languageName: node + linkType: hard + "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -8405,6 +8733,13 @@ __metadata: languageName: node linkType: hard +"de-indent@npm:^1.0.2": + version: 1.0.2 + resolution: "de-indent@npm:1.0.2" + checksum: 10/30bf43744dca005f9252dbb34ed95dcb3c30dfe52bfed84973b89c29eccff04e27769f222a34c61a93354acf47457785e9032e6184be390ed1d324fb9ab3f427 + languageName: node + linkType: hard + "debounce@npm:^1.2.1": version: 1.2.1 resolution: "debounce@npm:1.2.1" @@ -8442,6 +8777,18 @@ __metadata: languageName: node linkType: hard +"debug@npm:^4.3.6": + version: 4.4.0 + resolution: "debug@npm:4.4.0" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10/1847944c2e3c2c732514b93d11886575625686056cd765336212dc15de2d2b29612b6cd80e1afba767bb8e1803b778caf9973e98169ef1a24a7a7009e1820367 + languageName: node + linkType: hard + "decimal.js-light@npm:^2.4.1": version: 2.5.1 resolution: "decimal.js-light@npm:2.5.1" @@ -9123,7 +9470,7 @@ __metadata: languageName: node linkType: hard -"entities@npm:^4.2.0, entities@npm:^4.4.0": +"entities@npm:^4.2.0, entities@npm:^4.4.0, entities@npm:^4.5.0": version: 4.5.0 resolution: "entities@npm:4.5.0" checksum: 10/ede2a35c9bce1aeccd055a1b445d41c75a14a2bb1cd22e242f20cf04d236cdcd7f9c859eb83f76885327bfae0c25bf03303665ee1ce3d47c5927b98b0e3e3d48 @@ -10238,6 +10585,13 @@ __metadata: languageName: node linkType: hard +"fast-uri@npm:^3.0.1": + version: 3.0.3 + resolution: "fast-uri@npm:3.0.3" + checksum: 10/92487c75848b03edc45517fca0148287d342c30818ce43d556391db774d8e01644fb6964315a3336eec5a90f301b218b21f71fb9b2528ba25757435a20392c95 + languageName: node + linkType: hard + "fastest-levenshtein@npm:^1.0.12": version: 1.0.16 resolution: "fastest-levenshtein@npm:1.0.16" @@ -10508,6 +10862,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:~7.0.1": + version: 7.0.1 + resolution: "fs-extra@npm:7.0.1" + dependencies: + graceful-fs: "npm:^4.1.2" + jsonfile: "npm:^4.0.0" + universalify: "npm:^0.1.0" + checksum: 10/3fc6e56ba2f07c00d452163f27f21a7076b72ef7da8a50fef004336d59ef4c34deda11d10ecd73fd8fbcf20e4f575f52857293090b3c9f8741d4e0598be30fea + languageName: node + linkType: hard + "fs-merger@npm:^3.2.1": version: 3.2.1 resolution: "fs-merger@npm:3.2.1" @@ -11129,6 +11494,15 @@ __metadata: languageName: node linkType: hard +"he@npm:^1.2.0": + version: 1.2.0 + resolution: "he@npm:1.2.0" + bin: + he: bin/he + checksum: 10/d09b2243da4e23f53336e8de3093e5c43d2c39f8d0d18817abfa32ce3e9355391b2edb4bb5edc376aea5d4b0b59d6a0482aab4c52bc02ef95751e4b818e847f1 + languageName: node + linkType: hard + "heimdalljs-logger@npm:^0.1.10, heimdalljs-logger@npm:^0.1.7": version: 0.1.10 resolution: "heimdalljs-logger@npm:0.1.10" @@ -11409,6 +11783,13 @@ __metadata: languageName: node linkType: hard +"import-lazy@npm:~4.0.0": + version: 4.0.0 + resolution: "import-lazy@npm:4.0.0" + checksum: 10/943309cc8eb01ada12700448c288b0384f77a1bc33c7e00fa4cb223c665f467a13ce9aaceb8d2e4cf586b07c1d2828040263dcc069873ce63cfc2ac6fd087971 + languageName: node + linkType: hard + "import-local@npm:^3.0.2": version: 3.1.0 resolution: "import-local@npm:3.1.0" @@ -11638,6 +12019,15 @@ __metadata: languageName: node linkType: hard +"is-core-module@npm:^2.16.0": + version: 2.16.0 + resolution: "is-core-module@npm:2.16.0" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10/064442b9eefb7162376a4a414aa98b1e0c6cbb471507e66966b7d6d607a3f60eb09c7da4ee401648640a389e4af0f5a770bd5b3cd9c1084853e4a57f472408f8 + languageName: node + linkType: hard + "is-data-view@npm:^1.0.1": version: 1.0.1 resolution: "is-data-view@npm:1.0.1" @@ -12848,6 +13238,13 @@ __metadata: languageName: node linkType: hard +"jju@npm:~1.4.0": + version: 1.4.0 + resolution: "jju@npm:1.4.0" + checksum: 10/1067ff8ce02221faac5a842116ed0ec79a53312a111d0bf8342a80bd02c0a3fdf0b8449694a65947db0a3e8420e8b326dffb489c7dd5866efc380c0d1708a707 + languageName: node + linkType: hard + "joi@npm:^17.4.0": version: 17.9.2 resolution: "joi@npm:17.9.2" @@ -13125,6 +13522,13 @@ __metadata: languageName: node linkType: hard +"kolorist@npm:^1.8.0": + version: 1.8.0 + resolution: "kolorist@npm:1.8.0" + checksum: 10/71d5d122951cc65f2f14c3e1d7f8fd91694b374647d4f6deec3816d018cd04a44edd9578d93e00c82c2053b925e5d30a0565746c4171f4ca9fce1a13bd5f3315 + languageName: node + linkType: hard + "language-subtag-registry@npm:^0.3.20": version: 0.3.23 resolution: "language-subtag-registry@npm:0.3.23" @@ -13305,7 +13709,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.7.0": +"lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.7.0, lodash@npm:~4.17.15": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 @@ -13512,6 +13916,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.11": + version: 0.30.17 + resolution: "magic-string@npm:0.30.17" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + checksum: 10/2f71af2b0afd78c2e9012a29b066d2c8ba45a9cd0c8070f7fd72de982fb1c403b4e3afdb1dae00691d56885ede66b772ef6bedf765e02e3a7066208fe2fec4aa + languageName: node + linkType: hard + "magic-string@npm:^0.30.3, magic-string@npm:^0.30.5": version: 0.30.5 resolution: "magic-string@npm:0.30.5" @@ -14302,6 +14715,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:~3.0.3": + version: 3.0.8 + resolution: "minimatch@npm:3.0.8" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10/6df5373cb1ea79020beb6887ff5576c58cfabcfd32c5a65c2cf58f326e4ee8eae84f129e5fa50b8a4347fa1d1e583f931285c9fb3040d984bdfb5109ef6607ec + languageName: node + linkType: hard + "minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6": version: 1.2.8 resolution: "minimist@npm:1.2.8" @@ -14485,13 +14907,20 @@ __metadata: languageName: node linkType: hard -"ms@npm:^2.0.0, ms@npm:^2.1.1": +"ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d languageName: node linkType: hard +"muggle-string@npm:^0.4.1": + version: 0.4.1 + resolution: "muggle-string@npm:0.4.1" + checksum: 10/8fa2ea08f497c04069718bd3fd1909b382114dacbad832d10967ca72690de43f5f8492d8ccfbf827d6be63868ed5fc10395e7b7c082aa95997eea498586c6620 + languageName: node + linkType: hard + "murmurhash@npm:^2.0.1": version: 2.0.1 resolution: "murmurhash@npm:2.0.1" @@ -15323,6 +15752,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.2": + version: 4.0.2 + resolution: "picomatch@npm:4.0.2" + checksum: 10/ce617b8da36797d09c0baacb96ca8a44460452c89362d7cb8f70ca46b4158ba8bc3606912de7c818eb4a939f7f9015cef3c766ec8a0c6bfc725fdc078e39c717 + languageName: node + linkType: hard + "pidtree@npm:^0.3.0": version: 0.3.1 resolution: "pidtree@npm:0.3.1" @@ -15449,6 +15885,21 @@ __metadata: languageName: node linkType: hard +"plugins-shared@workspace:packages/plugins-shared": + version: 0.0.0-use.local + resolution: "plugins-shared@workspace:packages/plugins-shared" + dependencies: + "@emotion/css": "npm:^11.13.4" + "@types/react": "npm:^18.2.0" + i18next: "npm:^23.11.5" + react: "npm:18.2.0" + throttleit: "npm:^1.0.1" + typescript: "npm:^5.0.0" + vite: "npm:^5.2.14" + vite-plugin-dts: "npm:4.3.0" + languageName: unknown + linkType: soft + "possible-typed-array-names@npm:^1.0.0": version: 1.0.0 resolution: "possible-typed-array-names@npm:1.0.0" @@ -16631,6 +17082,19 @@ __metadata: languageName: node linkType: hard +"resolve@npm:~1.22.1, resolve@npm:~1.22.2": + version: 1.22.9 + resolution: "resolve@npm:1.22.9" + dependencies: + is-core-module: "npm:^2.16.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10/787b122cffd34944e8e899dc6f934278142df9f47c1228672cf80d21791364f0a9ff17b766374e9e83b78dee1cf4ded28d8387d264343861db77dc1141c5ec78 + languageName: node + linkType: hard + "resolve@patch:resolve@npm%3A^1.10.0#optional!builtin, resolve@patch:resolve@npm%3A^1.10.1#optional!builtin, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" @@ -16657,6 +17121,19 @@ __metadata: languageName: node linkType: hard +"resolve@patch:resolve@npm%3A~1.22.1#optional!builtin, resolve@patch:resolve@npm%3A~1.22.2#optional!builtin": + version: 1.22.9 + resolution: "resolve@patch:resolve@npm%3A1.22.9#optional!builtin::version=1.22.9&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.16.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10/423e54ddf58784c85ba2382f1e982f57e55dc19967f348214e1e6bc80d2fdbdaef35453d1a6a3c31810ac5e4e87e05ad9f5b3a3b1f117d3e673de313690eb54a + languageName: node + linkType: hard + "responselike@npm:^2.0.0": version: 2.0.1 resolution: "responselike@npm:2.0.1" @@ -17003,6 +17480,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:~7.5.4": + version: 7.5.4 + resolution: "semver@npm:7.5.4" + dependencies: + lru-cache: "npm:^6.0.0" + bin: + semver: bin/semver.js + checksum: 10/985dec0d372370229a262c737063860fabd4a1c730662c1ea3200a2f649117761a42184c96df62a0e885e76fbd5dace41087d6c1ac0351b13c0df5d6bcb1b5ac + languageName: node + linkType: hard + "serialize-error@npm:^7.0.1": version: 7.0.1 resolution: "serialize-error@npm:7.0.1" @@ -17317,7 +17805,7 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.1, source-map-js@npm:^1.2.1": +"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.1, source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" checksum: 10/ff9d8c8bf096d534a5b7707e0382ef827b4dd360a577d3f34d2b9f48e12c9d230b5747974ee7c607f0df65113732711bb701fe9ece3c7edbd43cb2294d707df3 @@ -17517,7 +18005,7 @@ __metadata: languageName: node linkType: hard -"string-argv@npm:~0.3.2": +"string-argv@npm:~0.3.1, string-argv@npm:~0.3.2": version: 0.3.2 resolution: "string-argv@npm:0.3.2" checksum: 10/f9d3addf887026b4b5f997a271149e93bf71efc8692e7dc0816e8807f960b18bcb9787b45beedf0f97ff459575ee389af3f189d8b649834cac602f2e857e75af @@ -17767,7 +18255,7 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:^3.1.1": +"strip-json-comments@npm:^3.1.1, strip-json-comments@npm:~3.1.1": version: 3.1.1 resolution: "strip-json-comments@npm:3.1.1" checksum: 10/492f73e27268f9b1c122733f28ecb0e7e8d8a531a6662efbd08e22cccb3f9475e90a1b82cab06a392f6afae6d2de636f977e231296400d0ec5304ba70f166443 @@ -17842,7 +18330,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8.0.0": +"supports-color@npm:^8.0.0, supports-color@npm:~8.1.1": version: 8.1.1 resolution: "supports-color@npm:8.1.1" dependencies: @@ -18580,6 +19068,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:5.4.2": + version: 5.4.2 + resolution: "typescript@npm:5.4.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10/f8cfdc630ab1672f004e9561eb2916935b2d267792d07ce93e97fc601c7a65191af32033d5e9c0169b7dc37da7db9bf320f7432bc84527cb7697effaa4e4559d + languageName: node + linkType: hard + "typescript@npm:^4.0.2": version: 4.9.5 resolution: "typescript@npm:4.9.5" @@ -18590,6 +19088,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:^5.0.0": + version: 5.7.2 + resolution: "typescript@npm:5.7.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10/4caa3904df69db9d4a8bedc31bafc1e19ffb7b24fbde2997a1633ae1398d0de5bdbf8daf602ccf3b23faddf1aeeb9b795223a2ed9c9a4fdcaf07bfde114a401a + languageName: node + linkType: hard + "typescript@npm:^5.0.4, typescript@npm:^5.5.4": version: 5.5.4 resolution: "typescript@npm:5.5.4" @@ -18600,6 +19108,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A5.4.2#optional!builtin": + version: 5.4.2 + resolution: "typescript@patch:typescript@npm%3A5.4.2#optional!builtin::version=5.4.2&hash=5adc0c" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10/f5f9a4133c2670761f0166eae5b3bafbc4a3fc24f0f42a93c9c893d9e9d6e66ea066969c5e7483fa66b4ae0e99125592553f3b92fd3599484de8be13b0615176 + languageName: node + linkType: hard + "typescript@patch:typescript@npm%3A^4.0.2#optional!builtin": version: 4.9.5 resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin::version=4.9.5&hash=289587" @@ -18610,6 +19128,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A^5.0.0#optional!builtin": + version: 5.7.2 + resolution: "typescript@patch:typescript@npm%3A5.7.2#optional!builtin::version=5.7.2&hash=379a07" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10/ff27fc124bceb8969be722baa38af945b2505767cf794de3e2715e58f61b43780284060287d651fcbbdfb6f917f4653b20f4751991f17e0706db389b9bb3f75d + languageName: node + linkType: hard + "typescript@patch:typescript@npm%3A^5.0.4#optional!builtin, typescript@patch:typescript@npm%3A^5.5.4#optional!builtin": version: 5.5.4 resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin::version=5.5.4&hash=379a07" @@ -18914,7 +19442,7 @@ __metadata: languageName: node linkType: hard -"uri-js@npm:^4.2.2": +"uri-js@npm:^4.2.2, uri-js@npm:^4.4.1": version: 4.4.1 resolution: "uri-js@npm:4.4.1" dependencies: @@ -19192,6 +19720,29 @@ __metadata: languageName: node linkType: hard +"vite-plugin-dts@npm:4.3.0": + version: 4.3.0 + resolution: "vite-plugin-dts@npm:4.3.0" + dependencies: + "@microsoft/api-extractor": "npm:^7.47.11" + "@rollup/pluginutils": "npm:^5.1.0" + "@volar/typescript": "npm:^2.4.4" + "@vue/language-core": "npm:2.1.6" + compare-versions: "npm:^6.1.1" + debug: "npm:^4.3.6" + kolorist: "npm:^1.8.0" + local-pkg: "npm:^0.5.0" + magic-string: "npm:^0.30.11" + peerDependencies: + typescript: "*" + vite: "*" + peerDependenciesMeta: + vite: + optional: true + checksum: 10/bbf59fb9d4ab9420f3fd2be2090439a072c4a57194cf6ee987ae990d1d95e2d9b16fae1a7c26578705d6a396a81eb6346ec522216e1f7cfc4f4d891fc29bd839 + languageName: node + linkType: hard + "vite-plugin-pwa@npm:^0.20.0": version: 0.20.0 resolution: "vite-plugin-pwa@npm:0.20.0" @@ -19329,6 +19880,13 @@ __metadata: languageName: node linkType: hard +"vscode-uri@npm:^3.0.8": + version: 3.0.8 + resolution: "vscode-uri@npm:3.0.8" + checksum: 10/e882d6b679e0d053cbc042893c0951a135d899a192b62cd07f0a8924f11ae722067a8d6b1b5b147034becf57faf9fff9fb543b17b749fd0f17db1f54f783f07c + languageName: node + linkType: hard + "w3c-hr-time@npm:^1.0.2": version: 1.0.2 resolution: "w3c-hr-time@npm:1.0.2"