diff --git a/.nvmrc b/.nvmrc index bd165d99d1..045200741c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -24.14.0 \ No newline at end of file +24.14.1 \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 5e70898951..9778f0de7b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,149 +4,197 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Overview -Trilium Notes is a hierarchical note-taking application with advanced features like synchronization, scripting, and rich text editing. It's built as a TypeScript monorepo using pnpm, with multiple applications and shared packages. +Trilium Notes is a hierarchical note-taking application with synchronization, scripting, and rich text editing. TypeScript monorepo using pnpm with multiple apps and shared packages. ## Development Commands -### Setup -- `pnpm install` - Install all dependencies -- `corepack enable` - Enable pnpm if not available +```bash +# Setup +corepack enable && pnpm install -### Running Applications -- `pnpm run server:start` - Start development server (http://localhost:8080) -- `pnpm run server:start-prod` - Run server in production mode +# Run +pnpm server:start # Dev server at http://localhost:8080 +pnpm desktop:start # Electron dev app +pnpm standalone:start # Standalone client dev -### Building -- `pnpm run client:build` - Build client application -- `pnpm run server:build` - Build server application -- `pnpm run electron:build` - Build desktop application +# Build +pnpm client:build # Frontend +pnpm server:build # Backend +pnpm desktop:build # Electron -### Testing -- `pnpm test:all` - Run all tests (parallel + sequential) -- `pnpm test:parallel` - Run tests that can run in parallel -- `pnpm test:sequential` - Run tests that must run sequentially (server, ckeditor5-mermaid, ckeditor5-math) -- `pnpm coverage` - Generate coverage reports +# Test +pnpm test:all # All tests (parallel + sequential) +pnpm test:parallel # Client + most package tests +pnpm test:sequential # Server, ckeditor5-mermaid, ckeditor5-math (shared DB) +pnpm --filter server test # Single package tests +pnpm coverage # Coverage reports -## Architecture Overview +# Lint & Format +pnpm dev:linter-check # ESLint check +pnpm dev:linter-fix # ESLint fix +pnpm dev:format-check # Format check (stricter stylistic rules) +pnpm dev:format-fix # Format fix +pnpm typecheck # TypeScript type check across all projects +``` -### Monorepo Structure -- **apps/**: Runnable applications - - `client/` - Frontend application (shared by server and desktop) - - `server/` - Node.js server with web interface - - `desktop/` - Electron desktop application - - `web-clipper/` - Browser extension for saving web content - - Additional tools: `db-compare`, `dump-db`, `edit-docs` +**Running a single test file**: `pnpm --filter server test spec/etapi/search.spec.ts` -- **packages/**: Shared libraries - - `commons/` - Shared interfaces and utilities - - `ckeditor5/` - Custom rich text editor with Trilium-specific plugins - - `codemirror/` - Code editor customizations - - `highlightjs/` - Syntax highlighting - - Custom CKEditor plugins: `ckeditor5-admonition`, `ckeditor5-footnotes`, `ckeditor5-math`, `ckeditor5-mermaid` +## Main Applications -### Core Architecture Patterns +The four main apps share `packages/trilium-core/` for business logic but differ in runtime: -#### Three-Layer Cache System -- **Becca** (Backend Cache): Server-side entity cache (`apps/server/src/becca/`) -- **Froca** (Frontend Cache): Client-side mirror of backend data (`apps/client/src/services/froca.ts`) -- **Shaca** (Share Cache): Optimized cache for shared/published notes (`apps/server/src/share/`) +- **client** (`apps/client/`): Preact frontend with jQuery widget system. Shared UI layer used by both server and desktop. +- **server** (`apps/server/`): Node.js backend (Express, better-sqlite3). Serves the client and provides REST/WebSocket APIs. +- **desktop** (`apps/desktop/`): Electron wrapper around server + client, running both in a single process. +- **standalone** (`apps/client-standalone/` + `apps/standalone-desktop/`): Runs the entire stack in the browser — server logic compiled to WASM via sql.js, executed in a service worker. No Node.js dependency at runtime. -#### Entity System -Core entities are defined in `apps/server/src/becca/entities/`: -- `BNote` - Notes with content and metadata -- `BBranch` - Hierarchical relationships between notes (allows multiple parents) -- `BAttribute` - Key-value metadata attached to notes -- `BRevision` - Note version history -- `BOption` - Application configuration +## Monorepo Structure -#### Widget-Based UI -Frontend uses a widget system (`apps/client/src/widgets/`): -- `BasicWidget` - Base class for all UI components -- `NoteContextAwareWidget` - Widgets that respond to note changes -- `RightPanelWidget` - Widgets displayed in the right panel +``` +apps/ + client/ # Preact frontend (shared by server, desktop, standalone) + server/ # Node.js backend (Express, better-sqlite3) + desktop/ # Electron (bundles server + client) + client-standalone/ # Standalone client (WASM + service workers, no Node.js) + standalone-desktop/ # Standalone desktop variant + server-e2e/ # Playwright E2E tests for server + web-clipper/ # Browser extension + website/ # Project website + db-compare/, dump-db/, edit-docs/, build-docs/, icon-pack-builder/ + +packages/ + trilium-core/ # Core business logic: entities, services, SQL, sync + commons/ # Shared interfaces and utilities + ckeditor5/ # Custom rich text editor bundle + codemirror/ # Code editor integration + highlightjs/ # Syntax highlighting + share-theme/ # Theme for shared/published notes + ckeditor5-admonition/, ckeditor5-footnotes/, ckeditor5-math/, ckeditor5-mermaid/ + ckeditor5-keyboard-marker/, express-partial-content/, pdfjs-viewer/, splitjs/ + turndown-plugin-gfm/ +``` + +Use `pnpm --filter ` to run commands in specific packages. + +## Core Architecture + +### Three-Layer Cache System + +All data access goes through cache layers — never bypass with direct DB queries: + +- **Becca** (`packages/trilium-core/src/becca/`): Server-side entity cache. Access via `becca.notes[noteId]`. +- **Froca** (`apps/client/src/services/froca.ts`): Client-side mirror synced via WebSocket. Access via `froca.getNote()`. +- **Shaca** (`apps/server/src/share/`): Optimized cache for shared/published notes. + +**Critical**: Always use cache methods, not direct DB writes. Cache methods create `EntityChange` records needed for synchronization. + +### Entity System + +Core entities live in `packages/trilium-core/src/becca/entities/` (not `apps/server/`): + +- `BNote` — Notes with content and metadata +- `BBranch` — Multi-parent tree relationships (cloning supported) +- `BAttribute` — Key-value metadata (labels and relations) +- `BRevision` — Version history +- `BOption` — Application configuration +- `BBlob` — Binary content storage + +Entities extend `AbstractBeccaEntity` with built-in change tracking, hash generation, and date management. + +### Entity Change & Sync Protocol + +Every entity modification creates an `EntityChange` record driving sync: +1. Login with HMAC authentication (document secret + timestamp) +2. Push changes → Pull changes → Push again (conflict resolution) +3. Content hash verification with retry loop + +Sync services: `packages/trilium-core/src/services/sync.ts`, `syncMutexService`, `syncUpdateService`. + +### Widget-Based UI + +Frontend widgets in `apps/client/src/widgets/`: +- `BasicWidget` / `TypedBasicWidget` — Base classes (jQuery `this.$widget` for DOM) +- `NoteContextAwareWidget` — Responds to note changes +- `RightPanelWidget` — Sidebar widgets with position ordering - Type-specific widgets in `type_widgets/` directory -#### API Architecture -- **Internal API**: REST endpoints in `apps/server/src/routes/api/` -- **ETAPI**: External API for third-party integrations (`apps/server/src/etapi/`) -- **WebSocket**: Real-time synchronization (`apps/server/src/services/ws.ts`) +**Widget lifecycle**: `doRenderBody()` for initial render, `refreshWithNote()` for note changes, `entitiesReloadedEvent({loadResults})` for entity updates. Uses jQuery — don't mix React patterns. -### Key Files for Understanding Architecture +Fluent builder pattern: `.child()`, `.class()`, `.css()` chaining with position-based ordering. -1. **Application Entry Points**: - - `apps/server/src/main.ts` - Server startup - - `apps/client/src/desktop.ts` - Client initialization +### API Architecture -2. **Core Services**: - - `apps/server/src/becca/becca.ts` - Backend data management - - `apps/client/src/services/froca.ts` - Frontend data synchronization - - `apps/server/src/services/backend_script_api.ts` - Scripting API +- **Internal API** (`apps/server/src/routes/api/`): REST endpoints, trusts frontend +- **ETAPI** (`apps/server/src/etapi/`): External API with basic auth tokens — maintain backwards compatibility +- **WebSocket** (`apps/server/src/services/ws.ts`): Real-time sync -3. **Database Schema**: - - `apps/server/src/assets/db/schema.sql` - Core database structure +### Platform Abstraction -4. **Configuration**: - - `package.json` - Project dependencies and scripts +`packages/trilium-core/src/services/platform.ts` defines `PlatformProvider` interface with implementations in `apps/desktop/`, `apps/server/`, and `apps/client-standalone/`. Singleton via `initPlatform()`/`getPlatform()`. -## Note Types and Features +**PlatformProvider** provides: +- `crash(message)` — Platform-specific fatal error handling +- `getEnv(key)` — Environment variable access (server/desktop use `process.env`, standalone maps URL query params like `?safeMode` → `TRILIUM_SAFE_MODE`) +- `isElectron`, `isMac`, `isWindows` — Platform detection flags -Trilium supports multiple note types, each with specialized widgets: -- **Text**: Rich text with CKEditor5 (markdown import/export) -- **Code**: Syntax-highlighted code editing with CodeMirror -- **File**: Binary file attachments -- **Image**: Image display with editing capabilities -- **Canvas**: Drawing/diagramming with Excalidraw -- **Mermaid**: Diagram generation -- **Relation Map**: Visual note relationship mapping -- **Web View**: Embedded web pages -- **Doc/Book**: Hierarchical documentation structure +**Critical rules for `trilium-core`**: +- **No `process.env` in core** — use `getPlatform().getEnv()` instead (not available in standalone/browser) +- **No `import path from "path"` in core** — Node's `path` module is externalized in browser builds. Use `packages/trilium-core/src/services/utils/path.ts` for `extname()`/`basename()` equivalents +- **No Node.js built-in modules in core** — core runs in both Node.js and the browser (standalone). Use platform-agnostic alternatives or platform providers +- **Platform detection via functions** — `isElectron()`, `isMac()`, `isWindows()` from `utils/index.ts` are functions (not constants) that call `getPlatform()`. They can only be called after `initializeCore()`, not at module top-level. If used in static definitions, wrap in a closure: `value: () => isWindows() ? "0.9" : "1.0"` +- **Barrel import caution** — `import { x } from "@triliumnext/core"` loads ALL core exports. Early-loading modules like `config.ts` should import specific subpaths (e.g. `@triliumnext/core/src/services/utils/index`) to avoid circular dependencies or initialization ordering issues +- **Electron IPC** — In desktop mode, client API calls use Electron IPC (not HTTP). The IPC handler in `apps/server/src/routes/electron.ts` must be registered via `utils.isElectron` from the **server's** utils (which correctly checks `process.versions["electron"]`), not from core's utils -## Development Guidelines +### Database -### Testing Strategy -- Server tests run sequentially due to shared database -- Client tests can run in parallel -- E2E tests use Playwright for both server and desktop apps -- Build validation tests check artifact integrity +SQLite via `better-sqlite3`. SQL abstraction in `packages/trilium-core/src/services/sql/` with `DatabaseProvider` interface, prepared statement caching, and transaction support. -### Scripting System -Trilium provides powerful user scripting capabilities: -- Frontend scripts run in browser context -- Backend scripts run in Node.js context with full API access -- Script API documentation available in `docs/Script API/` +- Schema: `apps/server/src/assets/db/schema.sql` +- Migrations: `apps/server/src/migrations/YYMMDD_HHMM__description.sql` -### Internationalization -- Translation files in `apps/client/src/translations/` -- Supported languages: English, German, Spanish, French, Romanian, Chinese +### Attribute Inheritance -### Security Considerations -- Per-note encryption with granular protected sessions -- CSRF protection for API endpoints -- OpenID and TOTP authentication support -- Sanitization of user-generated content +Three inheritance mechanisms: +1. **Standard**: `note.getInheritableAttributes()` walks parent tree +2. **Child prefix**: `child:label` on parent copies to children +3. **Template relation**: `#template=noteNoteId` includes template's inheritable attributes -## Common Development Tasks +Use `note.getOwnedAttribute()` for direct, `note.getAttribute()` for inherited. -### Adding New Note Types -1. Create widget in `apps/client/src/widgets/type_widgets/` -2. Register in `apps/client/src/services/note_types.ts` -3. Add backend handling in `apps/server/src/services/notes.ts` +## Important Patterns -### Extending Search -- Search expressions handled in `apps/server/src/services/search/` -- Add new search operators in search context files +- **Protected notes**: Check `note.isContentAvailable()` before accessing content; use `note.getTitleOrProtected()` for safe title access +- **Long operations**: Use `TaskContext` for progress reporting via WebSocket +- **Event system** (`packages/trilium-core/src/services/events.ts`): Events emitted in order (notes → branches → attributes) during load for referential integrity +- **Search**: Expression-based, scoring happens in-memory — cannot add SQL-level LIMIT/OFFSET without losing scoring +- **Widget cleanup**: Unsubscribe from events in `cleanup()`/`doDestroy()` to prevent memory leaks -### Custom CKEditor Plugins -- Create new package in `packages/` following existing plugin structure -- Register in `packages/ckeditor5/src/plugins.ts` +## Code Style -### Database Migrations -- Add migration scripts in `apps/server/src/migrations/` -- Update schema in `apps/server/src/assets/db/schema.sql` +- 4-space indentation, semicolons always required +- Double quotes (enforced by format config) +- Max line length: 100 characters +- Unix line endings +- Import sorting via `eslint-plugin-simple-import-sort` -## Build System Notes -- Uses pnpm for monorepo management -- Vite for fast development builds -- ESBuild for production optimization -- pnpm workspaces for dependency management -- Docker support with multi-stage builds \ No newline at end of file +## Testing + +- **Server tests** (`apps/server/spec/`): Vitest, must run sequentially (shared DB), forks pool, max 6 workers +- **Client tests** (`apps/client/src/`): Vitest with happy-dom environment, can run in parallel +- **E2E tests** (`apps/server-e2e/`): Playwright, Chromium, server started automatically on port 8082 +- **ETAPI tests** (`apps/server/spec/etapi/`): External API contract tests + +## Documentation + +- `docs/Script API/` — Auto-generated, never edit directly +- `docs/User Guide/` — Edit via `pnpm edit-docs:edit-docs`, not manually +- `docs/Developer Guide/` and `docs/Release Notes/` — Safe for direct Markdown editing + +## Key Entry Points + +- `apps/server/src/main.ts` — Server startup +- `apps/client/src/desktop.ts` — Client initialization +- `packages/trilium-core/src/becca/becca.ts` — Backend data management +- `apps/client/src/services/froca.ts` — Frontend cache +- `apps/server/src/routes/routes.ts` — API route registration +- `packages/trilium-core/src/services/sql/sql.ts` — Database abstraction diff --git a/apps/build-docs/package.json b/apps/build-docs/package.json index dd53e3a85c..70faa3a1eb 100644 --- a/apps/build-docs/package.json +++ b/apps/build-docs/package.json @@ -14,15 +14,15 @@ "keywords": [], "author": "Elian Doran ", "license": "AGPL-3.0-only", - "packageManager": "pnpm@10.32.1", + "packageManager": "pnpm@10.33.0", "devDependencies": { - "@redocly/cli": "2.24.1", + "@redocly/cli": "2.25.1", "archiver": "7.0.1", "fs-extra": "11.3.4", "js-yaml": "4.1.1", "react": "19.2.4", "react-dom": "19.2.4", - "typedoc": "0.28.17", + "typedoc": "0.28.18", "typedoc-plugin-missing-exports": "4.1.2" } } diff --git a/apps/client-standalone/package.json b/apps/client-standalone/package.json index e497b3e47e..9e72dc5357 100644 --- a/apps/client-standalone/package.json +++ b/apps/client-standalone/package.json @@ -20,10 +20,10 @@ "@fullcalendar/multimonth": "6.1.20", "@fullcalendar/timegrid": "6.1.20", "@maplibre/maplibre-gl-leaflet": "0.1.3", - "@mermaid-js/layout-elk": "0.2.0", + "@mermaid-js/layout-elk": "0.2.1", "@mind-elixir/node-menu": "5.0.1", "@popperjs/core": "2.11.8", - "@preact/signals": "2.5.1", + "@preact/signals": "2.8.2", "@sqlite.org/sqlite-wasm": "3.51.1-build2", "@triliumnext/ckeditor5": "workspace:*", "@triliumnext/codemirror": "workspace:*", @@ -32,7 +32,7 @@ "@triliumnext/highlightjs": "workspace:*", "@triliumnext/share-theme": "workspace:*", "@triliumnext/split.js": "workspace:*", - "@zumer/snapdom": "2.0.1", + "@zumer/snapdom": "2.6.0", "autocomplete.js": "0.38.1", "bootstrap": "5.3.8", "boxicons": "2.1.4", @@ -40,48 +40,48 @@ "color": "5.0.3", "debounce": "3.0.0", "draggabilly": "3.0.0", - "force-graph": "1.51.0", - "globals": "17.0.0", - "i18next": "25.7.3", + "force-graph": "1.51.2", + "globals": "17.4.0", + "i18next": "25.10.10", "i18next-http-backend": "3.0.2", - "jquery": "3.7.1", + "jquery": "4.0.0", "jquery.fancytree": "2.38.5", "js-sha1": "0.7.0", "js-sha256": "0.11.1", "js-sha512": "0.9.0", "jsplumb": "2.15.6", - "katex": "0.16.27", + "katex": "0.16.43", "knockout": "3.5.1", "leaflet": "1.9.4", "leaflet-gpx": "2.2.0", "mark.js": "8.11.1", - "marked": "17.0.1", - "mermaid": "11.12.2", - "mind-elixir": "5.4.0", + "marked": "17.0.5", + "mermaid": "11.13.0", + "mind-elixir": "5.9.3", "normalize.css": "8.0.1", - "panzoom": "9.4.3", - "preact": "10.28.2", - "react-i18next": "16.5.1", - "react-window": "2.2.3", - "reveal.js": "5.2.1", + "panzoom": "9.4.4", + "preact": "10.29.0", + "react-i18next": "16.6.6", + "react-window": "2.2.7", + "reveal.js": "6.0.0", "svg-pan-zoom": "3.6.2", - "tabulator-tables": "6.3.1", + "tabulator-tables": "6.4.0", "vanilla-js-wheel-zoom": "9.0.4" }, "devDependencies": { "@ckeditor/ckeditor5-inspector": "5.0.0", "@preact/preset-vite": "2.10.2", "@types/bootstrap": "5.2.10", - "@types/jquery": "3.5.33", + "@types/jquery": "4.0.0", "@types/leaflet": "1.9.21", "@types/leaflet-gpx": "1.3.8", "@types/mark.js": "8.11.12", "@types/reveal.js": "5.2.2", "@types/tabulator-tables": "6.3.1", - "copy-webpack-plugin": "13.0.1", + "copy-webpack-plugin": "14.0.0", "cross-env": "7.0.3", - "happy-dom": "20.0.11", + "happy-dom": "20.8.8", "script-loader": "0.7.2", - "vite-plugin-static-copy": "3.1.4" + "vite-plugin-static-copy": "3.4.0" } } \ No newline at end of file diff --git a/apps/client-standalone/src/lightweight/browser_routes.ts b/apps/client-standalone/src/lightweight/browser_routes.ts index a050692ff8..8eed93c4ea 100644 --- a/apps/client-standalone/src/lightweight/browser_routes.ts +++ b/apps/client-standalone/src/lightweight/browser_routes.ts @@ -4,7 +4,7 @@ */ import { BootstrapDefinition } from '@triliumnext/commons'; -import { entity_changes, getContext, getSharedBootstrapItems, getSql, routes, sql_init } from '@triliumnext/core'; +import { entity_changes, getContext, getPlatform, getSharedBootstrapItems, getSql, routes, sql_init } from '@triliumnext/core'; import packageJson from '../../package.json' with { type: 'json' }; import { type BrowserRequest, BrowserRouter } from './browser_router'; @@ -241,41 +241,36 @@ function bootstrapRoute(): BootstrapDefinition { const assetPath = "."; const isDbInitialized = sql_init.isDbInitialized(); - const commonItems = getSharedBootstrapItems(assetPath, isDbInitialized); + const commonItems = { + ...getSharedBootstrapItems(assetPath, isDbInitialized), + isDev: import.meta.env.DEV, + isStandalone: true, + isMainWindow: true, + isElectron: false, + hasNativeTitleBar: false, + hasBackgroundEffects: false, + triliumVersion: packageJson.version, + device: false as const, // Let the client detect device type. + appPath: assetPath, + instanceName: "standalone", + TRILIUM_SAFE_MODE: !!getPlatform().getEnv("TRILIUM_SAFE_MODE") + }; if (!isDbInitialized) { return { ...commonItems, - isStandalone: true, baseApiUrl: "../api/", + isProtectedSessionAvailable: false, }; } return { ...commonItems, - appPath: assetPath, - device: false, // Let the client detect device type. csrfToken: "dummy-csrf-token", - themeCssUrl: false, - themeUseNextAsBase: "next", - triliumVersion: packageJson.version, baseApiUrl: "../api/", headingStyle: "plain", layoutOrientation: "vertical", platform: "web", - isDev: import.meta.env.DEV, - isMainWindow: true, - isElectron: false, - isStandalone: true, - hasNativeTitleBar: false, - hasBackgroundEffects: false, - - // TODO: Fill properly - currentLocale: { id: "en", name: "English", rtl: false }, - isRtl: false, - instanceName: null, - appCssNoteIds: [], - TRILIUM_SAFE_MODE: false }; } diff --git a/apps/client-standalone/src/lightweight/platform_provider.ts b/apps/client-standalone/src/lightweight/platform_provider.ts index 9a1340eb4e..85f3bc18bc 100644 --- a/apps/client-standalone/src/lightweight/platform_provider.ts +++ b/apps/client-standalone/src/lightweight/platform_provider.ts @@ -1,6 +1,27 @@ import type { PlatformProvider } from "@triliumnext/core"; +/** Maps URL query parameter names to TRILIUM_ environment variable names. */ +const QUERY_TO_ENV: Record = { + "safeMode": "TRILIUM_SAFE_MODE", + "startNoteId": "TRILIUM_START_NOTE_ID", +}; + export default class StandalonePlatformProvider implements PlatformProvider { + readonly isElectron = false; + readonly isMac = false; + readonly isWindows = false; + + private envMap: Record = {}; + + constructor(queryString: string) { + const params = new URLSearchParams(queryString); + for (const [queryKey, envKey] of Object.entries(QUERY_TO_ENV)) { + if (params.has(queryKey)) { + this.envMap[envKey] = params.get(queryKey) || "true"; + } + } + } + crash(message: string): void { console.error("[Standalone] FATAL:", message); self.postMessage({ @@ -8,4 +29,8 @@ export default class StandalonePlatformProvider implements PlatformProvider { message }); } + + getEnv(key: string): string | undefined { + return this.envMap[key]; + } } diff --git a/apps/client-standalone/src/local-bridge.ts b/apps/client-standalone/src/local-bridge.ts index 216d6ee87b..dbd7e326d2 100644 --- a/apps/client-standalone/src/local-bridge.ts +++ b/apps/client-standalone/src/local-bridge.ts @@ -9,6 +9,7 @@ function showFatalErrorDialog(message: string) { export function startLocalServerWorker() { if (localWorker) return localWorker; localWorker = new LocalServerWorker(); + localWorker.postMessage({ type: "INIT", queryString: location.search }); // Handle worker errors during initialization localWorker.onerror = (event) => { diff --git a/apps/client-standalone/src/local-server-worker.ts b/apps/client-standalone/src/local-server-worker.ts index 86e945be10..be86cf8391 100644 --- a/apps/client-standalone/src/local-server-worker.ts +++ b/apps/client-standalone/src/local-server-worker.ts @@ -69,6 +69,7 @@ let coreModule: typeof import("@triliumnext/core") | null = null; let router: BrowserRouter | null = null; let initPromise: Promise | null = null; let initError: Error | null = null; +let queryString = ""; /** * Load all required modules using dynamic imports. @@ -153,7 +154,7 @@ async function initialize(): Promise { crypto: new BrowserCryptoProvider(), messaging: messagingProvider!, request: new FetchRequestProvider(), - platform: new StandalonePlatformProvider(), + platform: new StandalonePlatformProvider(queryString), translations: translationProvider, schema: schemaModule.default, dbConfig: { @@ -241,7 +242,14 @@ initialize().catch(err => { self.onmessage = async (event) => { const msg = event.data; - if (!msg || msg.type !== "LOCAL_REQUEST") return; + if (!msg) return; + + if (msg.type === "INIT") { + queryString = msg.queryString || ""; + return; + } + + if (msg.type !== "LOCAL_REQUEST") return; const { id, request } = msg; diff --git a/apps/client/package.json b/apps/client/package.json index 7ad5cb53d7..db99c6cb9a 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -43,7 +43,7 @@ "@univerjs/preset-sheets-note": "0.18.0", "@univerjs/preset-sheets-sort": "0.18.0", "@univerjs/presets": "0.18.0", - "@zumer/snapdom": "2.5.0", + "@zumer/snapdom": "2.6.0", "autocomplete.js": "0.38.1", "bootstrap": "5.3.8", "boxicons": "2.1.4", @@ -53,12 +53,12 @@ "draggabilly": "3.0.0", "force-graph": "1.51.2", "globals": "17.4.0", - "i18next": "25.10.3", + "i18next": "25.10.10", "i18next-http-backend": "3.0.2", "jquery": "4.0.0", "jquery.fancytree": "2.38.5", "jsplumb": "2.15.6", - "katex": "0.16.40", + "katex": "0.16.43", "leaflet": "1.9.4", "leaflet-gpx": "2.2.0", "mark.js": "8.11.1", @@ -66,9 +66,9 @@ "mermaid": "11.13.0", "mind-elixir": "5.9.3", "normalize.css": "8.0.1", - "panzoom": "9.4.3", + "panzoom": "9.4.4", "preact": "10.29.0", - "react-i18next": "16.6.0", + "react-i18next": "16.6.6", "react-window": "2.2.7", "reveal.js": "6.0.0", "rrule": "2.8.1", @@ -86,9 +86,9 @@ "@types/mark.js": "8.11.12", "@types/tabulator-tables": "6.3.1", "copy-webpack-plugin": "14.0.0", - "happy-dom": "20.8.4", + "happy-dom": "20.8.8", "lightningcss": "1.32.0", "script-loader": "0.7.2", - "vite-plugin-static-copy": "3.3.0" + "vite-plugin-static-copy": "3.4.0" } } \ No newline at end of file diff --git a/apps/client/src/services/ws.ts b/apps/client/src/services/ws.ts index e014974761..6146fc9b7e 100644 --- a/apps/client/src/services/ws.ts +++ b/apps/client/src/services/ws.ts @@ -7,16 +7,15 @@ import { t } from "./i18n.js"; import options from "./options.js"; import server from "./server.js"; import toastService from "./toast.js"; -import toast from "./toast.js"; import utils from "./utils.js"; type MessageHandler = (message: WebSocketMessage) => void; let messageHandlers: MessageHandler[] = []; let ws: WebSocket; -let lastAcceptedEntityChangeId = window.glob.maxEntityChangeIdAtLoad; -let lastAcceptedEntityChangeSyncId = window.glob.maxEntityChangeSyncIdAtLoad; -let lastProcessedEntityChangeId = window.glob.maxEntityChangeIdAtLoad; +let lastAcceptedEntityChangeId = window.glob.maxEntityChangeIdAtLoad ?? 0; +let lastAcceptedEntityChangeSyncId = window.glob.maxEntityChangeSyncIdAtLoad ?? 0; +let lastProcessedEntityChangeId = window.glob.maxEntityChangeIdAtLoad ?? 0; let lastPingTs: number; let frontendUpdateDataQueue: EntityChange[] = []; @@ -261,7 +260,7 @@ async function sendPing() { if (Date.now() - lastPingTs > 30000) { console.warn(utils.now(), "Lost websocket connection to the backend"); - toast.showPersistent({ + toastService.showPersistent({ id: "lost-websocket-connection", title: t("ws.lost-websocket-connection-title"), message: t("ws.lost-websocket-connection-message"), @@ -270,7 +269,7 @@ async function sendPing() { } if (ws.readyState === ws.OPEN) { - toast.closePersistent("lost-websocket-connection"); + toastService.closePersistent("lost-websocket-connection"); ws.send( JSON.stringify({ type: "ping", diff --git a/apps/client/src/translations/ca/translation.json b/apps/client/src/translations/ca/translation.json index 6fbcf570e3..32de8b1615 100644 --- a/apps/client/src/translations/ca/translation.json +++ b/apps/client/src/translations/ca/translation.json @@ -93,7 +93,10 @@ "digits": "dígits", "inheritable": "Heretable", "delete": "Suprimeix", - "color_type": "Color" + "color_type": "Color", + "textarea": "Text multi linia", + "date_time": "Data i hora", + "precision_title": "Quants dígits han d'estar disponibles per a coma flotant a la interfície de configuració." }, "rename_label": { "to": "Per" diff --git a/apps/client/src/translations/cn/translation.json b/apps/client/src/translations/cn/translation.json index 73af593a43..df59a5f866 100644 --- a/apps/client/src/translations/cn/translation.json +++ b/apps/client/src/translations/cn/translation.json @@ -446,7 +446,8 @@ "and_more": "... 以及另外 {{count}} 个。", "print_landscape": "导出为 PDF 时,将页面方向更改为横向而不是纵向。", "print_page_size": "导出为 PDF 时,更改页面大小。支持的值:A0A1A2A3A4A5A6LegalLetterTabloidLedger。", - "color_type": "颜色" + "color_type": "颜色", + "textarea": "多行文本" }, "attribute_editor": { "help_text_body1": "要添加标签,只需输入例如 #rock 或者如果您还想添加值,则例如 #year = 2020", @@ -2167,5 +2168,52 @@ }, "setup_form": { "more_info": "了解更多" + }, + "media": { + "play": "播放(空格)", + "pause": "暂停(空格)", + "back-10s": "后退10秒(左箭头键)", + "forward-30s": "前进30秒", + "mute": "静音(M)", + "unmute": "取消静音(M)", + "playback-speed": "播放速度", + "loop": "循环播放", + "disable-loop": "禁用循环播放", + "rotate": "旋转", + "picture-in-picture": "画中画", + "exit-picture-in-picture": "退出画中画", + "fullscreen": "全屏(F)", + "exit-fullscreen": "退出全屏", + "unsupported-format": "此文件格式不支持媒体预览:\n{{mime}}", + "zoom-to-fit": "缩放以填充", + "zoom-reset": "重置缩放以填充" + }, + "mermaid": { + "sample_diagrams": "示例图:", + "sample_flowchart": "流程图", + "sample_class": "类图", + "sample_sequence": "时序图", + "sample_entity_relationship": "实体关系图", + "sample_state": "状态图", + "sample_mindmap": "思维导图", + "sample_architecture": "架构图", + "sample_block": "模块图", + "sample_c4": "C4 图", + "sample_gantt": "甘特图", + "sample_git": "Git 流程图", + "sample_kanban": "看板图", + "sample_packet": "数据包图", + "sample_pie": "饼图", + "sample_quadrant": "象限图", + "sample_radar": "雷达图", + "sample_requirement": "需求图", + "sample_sankey": "桑基图", + "sample_timeline": "时间轴图", + "sample_treemap": "树形图", + "sample_user_journey": "用户旅程图", + "sample_xy": "散点图", + "sample_venn": "韦恩图", + "sample_ishikawa": "鱼骨图", + "placeholder": "输入你的美人鱼图的内容,或者使用下面的示例图之一。" } } diff --git a/apps/client/src/translations/de/translation.json b/apps/client/src/translations/de/translation.json index 49a5a6c90d..1275af9825 100644 --- a/apps/client/src/translations/de/translation.json +++ b/apps/client/src/translations/de/translation.json @@ -446,7 +446,8 @@ "and_more": "... und {{count}} mehr.", "print_landscape": "Beim Export als PDF, wird die Seitenausrichtung Querformat anstatt Hochformat verwendet.", "print_page_size": "Beim Export als PDF, wird die Größe der Seite angepasst. Unterstützte Größen: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger.", - "color_type": "Farbe" + "color_type": "Farbe", + "textarea": "Mehrzeilen-Text" }, "attribute_editor": { "help_text_body1": "Um ein Label hinzuzufügen, gebe einfach z.B. ein. #rock oder wenn du auch einen Wert hinzufügen möchten, dann z.B. #year = 2024", diff --git a/apps/client/src/translations/ga/translation.json b/apps/client/src/translations/ga/translation.json index 0c2c66be12..d0f41408ce 100644 --- a/apps/client/src/translations/ga/translation.json +++ b/apps/client/src/translations/ga/translation.json @@ -477,7 +477,8 @@ "and_more": "... agus {{count}} eile.", "print_landscape": "Agus é á onnmhairiú go PDF, athraítear treoshuíomh an leathanaigh go tírdhreach seachas portráid.", "print_page_size": "Agus é á easpórtáil go PDF, athraítear méid an leathanaigh. Luachanna tacaithe: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger.", - "color_type": "Dath" + "color_type": "Dath", + "textarea": "Téacs Il-líne" }, "attribute_editor": { "help_text_body1": "Chun lipéad a chur leis, clóscríobh m.sh. #rock nó más mian leat luach a chur leis freisin ansin m.sh. #year = 2020", diff --git a/apps/client/src/translations/it/translation.json b/apps/client/src/translations/it/translation.json index 2ef5526c27..ef4e0295a2 100644 --- a/apps/client/src/translations/it/translation.json +++ b/apps/client/src/translations/it/translation.json @@ -520,7 +520,7 @@ "custom_name_label": "Nome del motore di ricerca personalizzato", "custom_name_placeholder": "Personalizza il nome del motore di ricerca", "custom_url_label": "L'URL del motore di ricerca personalizzato deve includere {keyword} come segnaposto per il termine di ricerca.", - "custom_url_placeholder": "Personalizza l'URL del motore di ricerca" + "custom_url_placeholder": "Personalizza indirizzo url del motore di ricerca" }, "sql_table_schemas": { "tables": "Tabelle" @@ -917,7 +917,8 @@ "print_landscape": "Quando si esporta in PDF, cambia l'orientamento della pagina da verticale a orizzontale.", "print_page_size": "Quando si esporta in PDF, modifica le dimensioni della pagina. Valori supportati: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger.", "color_type": "Colore", - "share_root": "segna la nota che viene servita su /share root." + "share_root": "segna la nota che viene servita su /share root.", + "textarea": "Testo su più righe" }, "attribute_editor": { "help_text_body1": "Per aggiungere un'etichetta, basta digitare ad esempio #rock oppure, se si desidera aggiungere anche un valore, ad esempio #year = 2020", @@ -2197,5 +2198,52 @@ }, "setup_form": { "more_info": "Per saperne di più" + }, + "media": { + "play": "Gioca (Barra spaziatrice)", + "pause": "Pausa (Barra spaziatrice)", + "back-10s": "Indietro di 10 (tasto freccia sinistra)", + "forward-30s": "Avanti 30s", + "mute": "Muto (M)", + "unmute": "Riattiva audio (M)", + "playback-speed": "Velocità di riproduzione", + "loop": "Ciclo", + "disable-loop": "Disattiva il ciclo", + "rotate": "Ruota", + "picture-in-picture": "Immagine nell'immagine", + "exit-picture-in-picture": "Esci dalla modalità picture-in-picture", + "fullscreen": "Schermo intero (F)", + "exit-fullscreen": "Esci dalla modalità a schermo intero", + "unsupported-format": "Per questo formato di file non è disponibile l'anteprima multimediale:\n{{mime}}", + "zoom-to-fit": "Ingrandisci per riempire", + "zoom-reset": "Ripristina lo zoom a schermo intero" + }, + "mermaid": { + "placeholder": "Digita il contenuto del tuo diagramma Mermaid oppure utilizza uno dei diagrammi di esempio riportati di seguito.", + "sample_diagrams": "Esempi di diagrammi:", + "sample_flowchart": "Diagramma di flusso", + "sample_class": "Classe", + "sample_sequence": "Sequenza", + "sample_entity_relationship": "Relazioni tra entità", + "sample_state": "Stato", + "sample_mindmap": "Mappa mentale", + "sample_architecture": "Architettura", + "sample_block": "Blocco", + "sample_c4": "C4", + "sample_gantt": "Gantt", + "sample_git": "Git", + "sample_kanban": "Kanban", + "sample_packet": "Packet", + "sample_pie": "Torta", + "sample_quadrant": "Quadrante", + "sample_radar": "Radar", + "sample_requirement": "Requisito", + "sample_sankey": "Chiave", + "sample_timeline": "Cronologia", + "sample_treemap": "Treemap", + "sample_user_journey": "Percorso dell'utente", + "sample_xy": "XY", + "sample_venn": "Venn", + "sample_ishikawa": "Ishikawa" } } diff --git a/apps/client/src/translations/pl/translation.json b/apps/client/src/translations/pl/translation.json index cf1bad2b9d..3cd2d35be3 100644 --- a/apps/client/src/translations/pl/translation.json +++ b/apps/client/src/translations/pl/translation.json @@ -117,7 +117,7 @@ "no_path_to_clone_to": "Brak ścieżki do sklonowania.", "note_cloned": "Notatka \"{{clonedTitle}}\" została sklonowana do \"{{targetTitle}}\"", "help_on_links": "Pomoc dotycząca linków", - "target_parent_note": "Docelowa notatka nadrzędna" + "target_parent_note": "Docelowa notatka pierwotna" }, "help": { "title": "Ściągawka", @@ -126,7 +126,7 @@ "collapseExpand": "zwiń/rozwiń węzeł", "notSet": "nie ustawiono", "goBackForwards": "idź wstecz / do przodu w historii", - "showJumpToNoteDialog": "pokaż okno \"Przejdź do\"", + "showJumpToNoteDialog": "pokaż \"Przejdź do\"", "scrollToActiveNote": "przewiń do aktywnej notatki", "jumpToParentNote": "przejdź do notatki nadrzędnej", "collapseWholeTree": "zwiń całe drzewo notatek", @@ -402,7 +402,8 @@ "and_more": "... i {{count}} więcej.", "print_landscape": "Podczas eksportowania do PDF zmienia orientację strony na poziomą zamiast pionowej.", "print_page_size": "Podczas eksportowania do PDF zmienia rozmiar strony. Obsługiwane wartości: A0, A1, A2, A3, A4, A5, A6, Legal, Letter, Tabloid, Ledger.", - "color_type": "Kolor" + "color_type": "Kolor", + "textarea": "Wiele linii tekstu" }, "import": { "importIntoNote": "Importuj do notatki", @@ -1613,7 +1614,7 @@ "password_changed_success": "Hasło zostało zmienione. Trilium zostanie przeładowane po naciśnięciu OK." }, "multi_factor_authentication": { - "title": "Uwierzytelnianie wieloskładnikowe (MFA)", + "title": "Uwierzytelnianie wieloskładnikowe", "description": "Uwierzytelnianie wieloskładnikowe (MFA) dodaje dodatkową warstwę zabezpieczeń do Twojego konta. Zamiast tylko wpisywać hasło do logowania, MFA wymaga podania jednego lub więcej dodatkowych dowodów tożsamości. W ten sposób, nawet jeśli ktoś zdobędzie Twoje hasło, nadal nie będzie mógł uzyskać dostępu do Twojego konta bez drugiej informacji. To jak dodanie dodatkowego zamka do drzwi, utrudniającego włamanie.

Proszę postępować zgodnie z poniższymi instrukcjami, aby włączyć MFA. Jeśli nie skonfigurujesz poprawnie, logowanie powróci do samego hasła.", "mfa_enabled": "Włącz uwierzytelnianie wieloskładnikowe", "mfa_method": "Metoda MFA", @@ -1628,7 +1629,7 @@ "totp_secret_generated": "Sekret TOTP wygenerowany", "totp_secret_warning": "Proszę zapisać wygenerowany sekret w bezpiecznym miejscu. Nie zostanie pokazany ponownie.", "totp_secret_regenerate_confirm": "Czy na pewno chcesz ponownie wygenerować sekret TOTP? To unieważni poprzedni sekret TOTP i wszystkie istniejące kody odzyskiwania.", - "recovery_keys_title": "Klucze odzyskiwania logowania jednokrotnego (SSO)", + "recovery_keys_title": "Klucze odzyskiwania logowania jednokrotnego", "recovery_keys_description": "Klucze odzyskiwania logowania jednokrotnego służą do logowania w przypadku braku dostępu do kodów Authenticator.", "recovery_keys_description_warning": "Klucze odzyskiwania nie zostaną pokazane ponownie po opuszczeniu strony, przechowuj je w bezpiecznym miejscu.
Po użyciu klucza odzyskiwania nie można go użyć ponownie.", "recovery_keys_error": "Błąd generowania kodów odzyskiwania", @@ -1766,7 +1767,7 @@ "book": "Kolekcja", "mermaid-diagram": "Diagram Mermaid", "canvas": "Płótno", - "web-view": "Widok WWW", + "web-view": "Widok strony web", "mind-map": "Mapa myśli", "file": "Plik", "image": "Obraz", @@ -1815,9 +1816,9 @@ "modal_title": "Konfiguracja listy wyróżnień", "menu_configure": "Konfiguracja listy wyróżnień...", "no_highlights": "Nie znaleziono wyróżnień.", - "title_with_count_one": "{{count}} podświetlenie", - "title_with_count_few": "{{count}} podświetlenia", - "title_with_count_many": "{{count}} podświetleń" + "title_with_count_one": "{{count}} wyróżnienie", + "title_with_count_few": "{{count}} wyróżnienia", + "title_with_count_many": "{{count}} wyróżnień" }, "quick-search": { "placeholder": "Szybkie wyszukiwanie", @@ -2070,7 +2071,7 @@ "read_only_temporarily_disabled_description": "Ta notatka jest obecnie edytowalna, ale normalnie jest tylko do odczytu. Notatka powróci do trybu tylko do odczytu, gdy tylko przejdziesz do innej notatki.\n\nKliknij, aby ponownie włączyć tryb tylko do odczytu.", "shared_publicly": "Udostępniona publicznie", "shared_locally": "Udostępniona lokalnie", - "clipped_note": "Wycinek WWW", + "clipped_note": "Wycinek z sieci", "clipped_note_description": "Ta notatka została pierwotnie pobrana z {{url}}.\n\nKliknij, aby przejść do źródłowej strony internetowej.", "execute_script": "Uruchom skrypt", "execute_script_description": "Ta notatka jest notatką skryptową. Kliknij, aby wykonać skrypt.", @@ -2236,7 +2237,7 @@ "sample_c4": "C4", "sample_gantt": "Wykres Gantta", "sample_git": "Diagram Git", - "sample_kanban": "Kanban", + "sample_kanban": "Tablica Kanban", "sample_packet": "Diagram pakietów", "sample_pie": "Wykres kołowy", "sample_quadrant": "Diagram kwadrantowy", diff --git a/apps/client/src/translations/tw/translation.json b/apps/client/src/translations/tw/translation.json index 7b837fcbfb..35c8dbff1b 100644 --- a/apps/client/src/translations/tw/translation.json +++ b/apps/client/src/translations/tw/translation.json @@ -2226,7 +2226,7 @@ "sample_sankey": "桑基圖", "sample_timeline": "時間軸", "sample_treemap": "樹狀圖", - "sample_user_journey": "用戶旅程", + "sample_user_journey": "使用者旅程", "sample_xy": "XY 圖表", "sample_venn": "韋恩圖", "sample_ishikawa": "魚骨圖" diff --git a/apps/client/src/widgets/PromotedAttributes.tsx b/apps/client/src/widgets/PromotedAttributes.tsx index 6a1a8640f2..f86cbeb5cd 100644 --- a/apps/client/src/widgets/PromotedAttributes.tsx +++ b/apps/client/src/widgets/PromotedAttributes.tsx @@ -1,6 +1,6 @@ import "./PromotedAttributes.css"; -import { UpdateAttributeResponse } from "@triliumnext/commons"; +import { DefinitionObject, LabelType, UpdateAttributeResponse } from "@triliumnext/commons"; import clsx from "clsx"; import { ComponentChild, createElement, HTMLInputTypeAttribute, InputHTMLAttributes, MouseEventHandler, TargetedEvent, TargetedInputEvent } from "preact"; import { Dispatch, StateUpdater, useCallback, useEffect, useRef, useState } from "preact/hooks"; @@ -11,7 +11,7 @@ import FNote from "../entities/fnote"; import { Attribute } from "../services/attribute_parser"; import attributes from "../services/attributes"; import { t } from "../services/i18n"; -import { DefinitionObject, extractAttributeDefinitionTypeAndName, LabelType } from "../services/promoted_attribute_definition_parser"; +import { extractAttributeDefinitionTypeAndName } from "../services/promoted_attribute_definition_parser"; import server from "../services/server"; import { randomString } from "../services/utils"; import ws from "../services/ws"; diff --git a/apps/client/src/widgets/collections/board/data.spec.ts b/apps/client/src/widgets/collections/board/data.spec.ts index 83a36ac0e6..9f7bb01ed3 100644 --- a/apps/client/src/widgets/collections/board/data.spec.ts +++ b/apps/client/src/widgets/collections/board/data.spec.ts @@ -1,8 +1,9 @@ -import { it, describe, expect } from "vitest"; -import { buildNote } from "../../../test/easy-froca"; -import { getBoardData } from "./data"; +import { describe, expect, it } from "vitest"; + import FBranch from "../../../entities/fbranch"; import froca from "../../../services/froca"; +import { buildNote } from "../../../test/easy-froca"; +import { getBoardData } from "./data"; describe("Board data", () => { it("deduplicates cloned notes", async () => { diff --git a/apps/client/src/widgets/dialogs/revisions.tsx b/apps/client/src/widgets/dialogs/revisions.tsx index 66ce763a3d..6f0850651f 100644 --- a/apps/client/src/widgets/dialogs/revisions.tsx +++ b/apps/client/src/widgets/dialogs/revisions.tsx @@ -284,7 +284,7 @@ function RevisionContent({ noteContent, revisionItem, fullRevision, showDiff }: } } -function RevisionContentText({ content }: { content: string | Buffer | undefined }) { +function RevisionContentText({ content }: { content: string | Uint8Array | undefined }) { const contentRef = useRef(null); useEffect(() => { if (contentRef.current?.querySelector("span.math-tex")) { @@ -296,7 +296,7 @@ function RevisionContentText({ content }: { content: string | Buffer | undefined, + itemContent: string | Uint8Array | undefined, itemType: string }) { const contentRef = useRef(null); diff --git a/apps/db-compare/package.json b/apps/db-compare/package.json index 77002e1204..ff041aa7ea 100644 --- a/apps/db-compare/package.json +++ b/apps/db-compare/package.json @@ -5,7 +5,7 @@ "description": "Tool to compare content of Trilium databases. Useful for debugging sync problems.", "dependencies": { "colors": "1.4.0", - "diff": "8.0.3", + "diff": "8.0.4", "sqlite": "5.1.1", "sqlite3": "6.0.1" }, diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 691b9702e4..ec79ae6232 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -36,7 +36,7 @@ "@triliumnext/core": "workspace:*", "@triliumnext/server": "workspace:*", "copy-webpack-plugin": "14.0.0", - "electron": "41.0.3", + "electron": "41.0.4", "@electron-forge/cli": "7.11.1", "@electron-forge/maker-deb": "7.11.1", "@electron-forge/maker-dmg": "7.11.1", diff --git a/apps/desktop/src/platform_provider.ts b/apps/desktop/src/platform_provider.ts index e9dab7c5cf..72e9d6cb38 100644 --- a/apps/desktop/src/platform_provider.ts +++ b/apps/desktop/src/platform_provider.ts @@ -2,8 +2,16 @@ import { PlatformProvider, t } from "@triliumnext/core"; import electron from "electron"; export default class DesktopPlatformProvider implements PlatformProvider { + readonly isElectron = true; + readonly isMac = process.platform === "darwin"; + readonly isWindows = process.platform === "win32"; + crash(message: string): void { electron.dialog.showErrorBox(t("modals.error_title"), message); electron.app.exit(1); } + + getEnv(key: string): string | undefined { + return process.env[key]; + } } diff --git a/apps/desktop/tsconfig.forge.json b/apps/desktop/tsconfig.forge.json index e02509ba6f..2748eb5897 100644 --- a/apps/desktop/tsconfig.forge.json +++ b/apps/desktop/tsconfig.forge.json @@ -9,15 +9,26 @@ "node", "express" ], - "tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo" + "tsBuildInfoFile": "dist/tsconfig.forge.tsbuildinfo" }, "include": [ "**/*.ts", - "../server/src/*.d.ts" + "../server/src/*.d.ts", + "package.json" ], "exclude": [ "eslint.config.js", "eslint.config.cjs", - "eslint.config.mjs" + "eslint.config.mjs", + "scripts/**", + "dist/**" + ], + "references": [ + { + "path": "../server/tsconfig.app.json" + }, + { + "path": "../../packages/commons/tsconfig.lib.json" + } ] } diff --git a/apps/edit-docs/package.json b/apps/edit-docs/package.json index da53d66d60..04957a29a6 100644 --- a/apps/edit-docs/package.json +++ b/apps/edit-docs/package.json @@ -9,10 +9,11 @@ }, "devDependencies": { "@triliumnext/client": "workspace:*", + "@triliumnext/core": "workspace:*", "@triliumnext/desktop": "workspace:*", "@types/fs-extra": "11.0.4", "copy-webpack-plugin": "14.0.0", - "electron": "41.0.3", + "electron": "41.0.4", "fs-extra": "11.3.4" }, "scripts": { diff --git a/apps/edit-docs/src/edit-demo.ts b/apps/edit-docs/src/edit-demo.ts index 62ea490142..4f70a380e7 100644 --- a/apps/edit-docs/src/edit-demo.ts +++ b/apps/edit-docs/src/edit-demo.ts @@ -1,9 +1,9 @@ -import { extractZip, importData, initializeDatabase, startElectron } from "./utils.js"; -import { initializeTranslations } from "@triliumnext/server/src/services/i18n.js"; import debounce from "@triliumnext/client/src/services/debounce.js"; +import cls from "@triliumnext/server/src/services/cls.js"; import fs from "fs/promises"; import { join } from "path"; -import cls from "@triliumnext/server/src/services/cls.js"; + +import { extractZip, importData, startElectron } from "./utils.js"; // Paths are relative to apps/edit-docs/dist. const DEMO_ZIP_PATH = join(__dirname, "../../server/src/assets/db/demo.zip"); @@ -15,8 +15,7 @@ async function main() { setTimeout(() => registerHandlers(), 10_000); }); - await initializeTranslations(); - await initializeDatabase(true); + // TODO: Initialize core. cls.init(async () => { await importData(DEMO_ZIP_DIR_PATH); setOptions(); @@ -34,8 +33,8 @@ async function setOptions() { } async function registerHandlers() { - const events = (await import("@triliumnext/server/src/services/events.js")).default; - const eraseService = (await import("@triliumnext/server/src/services/erase.js")).default; + const { events } = await import("@triliumnext/core"); + const { erase: eraseService } = await import("@triliumnext/core"); const debouncer = debounce(async () => { console.log("Exporting data"); eraseService.eraseUnusedAttachmentsNow(); diff --git a/apps/edit-docs/src/edit-docs.ts b/apps/edit-docs/src/edit-docs.ts index 40163330d0..67a9ac06bb 100644 --- a/apps/edit-docs/src/edit-docs.ts +++ b/apps/edit-docs/src/edit-docs.ts @@ -121,11 +121,10 @@ async function main() { }, 10_000); }); - await initializeTranslations(); - await initializeDatabase(true); + // TODO: Initialize core. // Wait for becca to be loaded before importing data - const beccaLoader = await import("@triliumnext/server/src/becca/becca_loader.js"); + const { becca_loader: beccaLoader } = await import("@triliumnext/core"); await beccaLoader.beccaLoaded; cls.init(async () => { @@ -252,8 +251,8 @@ async function cleanUpMeta(outputPath: string, minify: boolean) { } async function registerHandlers() { - const events = (await import("@triliumnext/server/src/services/events.js")).default; - const eraseService = (await import("@triliumnext/server/src/services/erase.js")).default; + const { events } = await import("@triliumnext/core"); + const { erase: eraseService } = await import("@triliumnext/core"); const debouncer = debounce(async () => { eraseService.eraseUnusedAttachmentsNow(); diff --git a/apps/edit-docs/tsconfig.app.json b/apps/edit-docs/tsconfig.app.json index b9e17115a0..ab40ca633c 100644 --- a/apps/edit-docs/tsconfig.app.json +++ b/apps/edit-docs/tsconfig.app.json @@ -23,6 +23,9 @@ "eslint.config.mjs" ], "references": [ + { + "path": "../../packages/trilium-core/tsconfig.lib.json" + }, { "path": "../server/tsconfig.app.json" }, diff --git a/apps/server/package.json b/apps/server/package.json index 87b6c3f880..243a987b41 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -32,7 +32,6 @@ "dependencies": { "better-sqlite3": "12.8.0", "html-to-text": "9.0.5", - "node-html-parser": "7.1.0", "sucrase": "3.35.1" }, "devDependencies": { @@ -79,12 +78,12 @@ "debounce": "3.0.0", "debug": "4.4.3", "ejs": "5.0.1", - "electron": "41.0.3", + "electron": "41.0.4", "electron-debug": "4.1.0", "electron-window-state": "5.0.3", "express": "5.2.1", "express-http-proxy": "2.1.2", - "express-openid-connect": "2.19.4", + "express-openid-connect": "2.20.0", "express-rate-limit": "8.3.1", "express-session": "1.19.0", "file-uri-to-path": "2.0.0", @@ -94,9 +93,9 @@ "html2plaintext": "2.1.4", "http-proxy-agent": "8.0.0", "https-proxy-agent": "8.0.0", - "i18next": "25.10.3", + "i18next": "25.10.10", "i18next-fs-backend": "2.6.1", - "image-type": "6.0.0", + "image-type": "6.1.0", "ini": "6.0.0", "is-animated": "2.0.2", "is-svg": "6.1.0", @@ -117,7 +116,7 @@ "time2fa": "1.4.2", "tmp": "0.2.5", "turnish": "1.8.0", - "vite": "8.0.1", + "vite": "8.0.2", "ws": "8.20.0", "xml2js": "0.6.2", "yauzl": "3.2.1" diff --git a/apps/server/spec/setup.ts b/apps/server/spec/setup.ts index f3ea012c85..437ff8a2f8 100644 --- a/apps/server/spec/setup.ts +++ b/apps/server/spec/setup.ts @@ -1,7 +1,12 @@ import { beforeAll } from "vitest"; -import i18next from "i18next"; +import { readFileSync } from "fs"; import { join } from "path"; -import { setDayjsLocale } from "@triliumnext/commons"; +import { initializeCore } from "@triliumnext/core"; +import ClsHookedExecutionContext from "../src/cls_provider.js"; +import NodejsCryptoProvider from "../src/crypto_provider.js"; +import ServerPlatformProvider from "../src/platform_provider.js"; +import BetterSqlite3Provider from "../src/sql_provider.js"; +import { initializeTranslations } from "../src/services/i18n.js"; // Initialize environment variables. process.env.TRILIUM_DATA_DIR = join(__dirname, "db"); @@ -11,20 +16,20 @@ process.env.TRILIUM_ENV = "dev"; process.env.TRILIUM_PUBLIC_SERVER = "http://localhost:4200"; beforeAll(async () => { - // Initialize the translations manually to avoid any side effects. - const Backend = (await import("i18next-fs-backend")).default; + const dbProvider = new BetterSqlite3Provider(); + dbProvider.loadFromMemory(); - // Initialize translations - await i18next.use(Backend).init({ - lng: "en", - fallbackLng: "en", - ns: "server", - backend: { - loadPath: join(__dirname, "../src/assets/translations/{{lng}}/{{ns}}.json") + await initializeCore({ + dbConfig: { + provider: dbProvider, + isReadOnly: false, + onTransactionCommit() {}, + onTransactionRollback() {} }, - showSupportNotice: false + crypto: new NodejsCryptoProvider(), + executionContext: new ClsHookedExecutionContext(), + schema: readFileSync(require.resolve("@triliumnext/core/src/assets/schema.sql"), "utf-8"), + platform: new ServerPlatformProvider(), + translations: initializeTranslations }); - - // Initialize dayjs - await setDayjsLocale("en"); }); diff --git a/apps/server/src/assets/translations/pl/server.json b/apps/server/src/assets/translations/pl/server.json index 08d938cccd..69458369f9 100644 --- a/apps/server/src/assets/translations/pl/server.json +++ b/apps/server/src/assets/translations/pl/server.json @@ -14,7 +14,7 @@ "creating-and-moving-notes": "Tworzenie i przenoszenie notatek", "create-note-after": "Utwórz notatkę po aktywnej notatce", "create-note-into": "Utwórz notatkę jako podrzędną aktywnej notatki", - "create-note-into-inbox": "Utwórz notatkę w skrzynce odbiorczej (jeśli zdefiniowana) lub notatkę dnia", + "create-note-into-inbox": "Utwórz notatkę w skrzynce odbiorczej (jeśli zdefiniowano) lub w notatce dziennej", "delete-note": "Usuń notatkę", "move-note-up": "Przenieś notatkę w górę", "move-note-down": "Przenieś notatkę w dół", @@ -59,7 +59,7 @@ "show-backend-log": "Otwórz stronę \"Logi backendu\"", "show-help": "Otwórz wbudowany Poradnik Użytkownika", "show-cheatsheet": "Pokaż listę skrótów klawiszowych", - "text-note-operations": "Operacje na notatkach tekstowych", + "text-note-operations": "Operacje na notatkach", "add-link-to-text": "Otwórz okno dodawania linku do tekstu", "follow-link-under-cursor": "Podążaj za linkiem pod kursorem", "insert-date-and-time-to-text": "Wstaw aktualną datę i czas", diff --git a/apps/server/src/etapi/metrics.ts b/apps/server/src/etapi/metrics.ts index d460266472..05a5a41c98 100644 --- a/apps/server/src/etapi/metrics.ts +++ b/apps/server/src/etapi/metrics.ts @@ -1,13 +1,14 @@ -import type { Router, Request, Response, NextFunction } from "express"; -import eu from "./etapi_utils.js"; -import sql from "../services/sql.js"; +import type { NextFunction,Request, Response, Router } from "express"; + import appInfo from "../services/app_info.js"; +import sql from "../services/sql.js"; +import eu from "./etapi_utils.js"; interface MetricsData { version: { app: string; db: number; - node: string; + node?: string; sync: number; buildDate: string; buildRevision: string; @@ -45,7 +46,7 @@ function formatPrometheusMetrics(data: MetricsData): string { const lines: string[] = []; // Helper function to add a metric - const addMetric = (name: string, value: number | null, help: string, type: string = 'gauge', labels: Record = {}) => { + const addMetric = (name: string, value: number | null, help: string, type: string = 'gauge', labels: Record = {}) => { if (value === null) return; lines.push(`# HELP ${name} ${help}`); @@ -233,8 +234,8 @@ function register(router: Router): void { } else if (format === 'prometheus') { const prometheusText = formatPrometheusMetrics(metrics); res.status(200) - .set('Content-Type', 'text/plain; version=0.0.4; charset=utf-8') - .send(prometheusText); + .set('Content-Type', 'text/plain; version=0.0.4; charset=utf-8') + .send(prometheusText); } else { throw new eu.EtapiError(400, "INVALID_FORMAT", "Supported formats: 'prometheus' (default), 'json'"); } diff --git a/apps/server/src/etapi/notes.ts b/apps/server/src/etapi/notes.ts index 3999995f26..a0d3cf49e1 100644 --- a/apps/server/src/etapi/notes.ts +++ b/apps/server/src/etapi/notes.ts @@ -1,4 +1,4 @@ -import { NoteParams } from "@triliumnext/core"; +import { NoteParams, SearchParams } from "@triliumnext/core"; import type { Request, Router } from "express"; import type { ParsedQs } from "qs"; @@ -9,7 +9,6 @@ import zipImportService from "../services/import/zip.js"; import noteService from "../services/notes.js"; import SearchContext from "../services/search/search_context.js"; import searchService from "../services/search/services/search.js"; -import type { SearchParams } from "../services/search/services/types.js"; import TaskContext from "../services/task_context.js"; import utils from "../services/utils.js"; import eu from "./etapi_utils.js"; diff --git a/apps/server/src/platform_provider.ts b/apps/server/src/platform_provider.ts index ed9d5919e5..ae3433f47c 100644 --- a/apps/server/src/platform_provider.ts +++ b/apps/server/src/platform_provider.ts @@ -1,8 +1,16 @@ import { getLog, PlatformProvider } from "@triliumnext/core"; export default class ServerPlatformProvider implements PlatformProvider { + readonly isElectron = !!process.versions["electron"]; + readonly isMac = process.platform === "darwin"; + readonly isWindows = process.platform === "win32"; + crash(message: string): void { getLog().error(message); process.exit(1); } + + getEnv(key: string): string | undefined { + return process.env[key]; + } } diff --git a/apps/server/src/routes/api/database.ts b/apps/server/src/routes/api/database.ts index 2fe001a0b5..b73e25a22c 100644 --- a/apps/server/src/routes/api/database.ts +++ b/apps/server/src/routes/api/database.ts @@ -1,5 +1,5 @@ import { BackupDatabaseNowResponse, DatabaseCheckIntegrityResponse } from "@triliumnext/commons"; -import { becca_loader, ValidationError } from "@triliumnext/core"; +import { becca_loader, rebuildIntegrationTestDatabase as rebuildIntegrationTestDatabaseCore,ValidationError } from "@triliumnext/core"; import type { Request } from "express"; import anonymizationService from "../../services/anonymization.js"; @@ -30,7 +30,7 @@ function findAndFixConsistencyIssues() { } async function rebuildIntegrationTestDatabase() { - sql.rebuildIntegrationTestDatabase(); + rebuildIntegrationTestDatabaseCore(); sql_init.initializeDb(); becca_loader.load(); } diff --git a/apps/server/src/routes/api/files.ts b/apps/server/src/routes/api/files.ts index 8d5199385a..75cd081683 100644 --- a/apps/server/src/routes/api/files.ts +++ b/apps/server/src/routes/api/files.ts @@ -1,17 +1,14 @@ import { ValidationError } from "@triliumnext/core"; import chokidar from "chokidar"; -import type { Request, Response } from "express"; +import type { Request } from "express"; import fs from "fs"; import { Readable } from "stream"; import tmp from "tmp"; import becca from "../../becca/becca.js"; -import type BAttachment from "../../becca/entities/battachment.js"; -import type BNote from "../../becca/entities/bnote.js"; import dataDirs from "../../services/data_dir.js"; import log from "../../services/log.js"; import noteService from "../../services/notes.js"; -import protectedSessionService from "../../services/protected_session.js"; import utils from "../../services/utils.js"; import ws from "../../services/ws.js"; @@ -64,49 +61,6 @@ function updateAttachment(req: Request<{ attachmentId: string }>) { }; } -function downloadData(noteOrAttachment: BNote | BAttachment, res: Response, contentDisposition: boolean) { - if (noteOrAttachment.isProtected && !protectedSessionService.isProtectedSessionAvailable()) { - return res.status(401).send("Protected session not available"); - } - - if (contentDisposition) { - const fileName = noteOrAttachment.getFileName(); - - res.setHeader("Content-Disposition", utils.getContentDisposition(fileName)); - } - - res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - res.setHeader("Content-Type", noteOrAttachment.mime); - - res.send(noteOrAttachment.getContent()); -} - -function downloadNoteInt(noteId: string, res: Response, contentDisposition = true) { - const note = becca.getNote(noteId); - - if (!note) { - return res.setHeader("Content-Type", "text/plain").status(404).send(`Note '${noteId}' doesn't exist.`); - } - - return downloadData(note, res, contentDisposition); -} - -function downloadAttachmentInt(attachmentId: string, res: Response, contentDisposition = true) { - const attachment = becca.getAttachment(attachmentId); - - if (!attachment) { - return res.setHeader("Content-Type", "text/plain").status(404).send(`Attachment '${attachmentId}' doesn't exist.`); - } - - return downloadData(attachment, res, contentDisposition); -} - -const downloadFile = (req: Request<{ noteId: string }>, res: Response) => downloadNoteInt(req.params.noteId, res, true); -const openFile = (req: Request<{ noteId: string }>, res: Response) => downloadNoteInt(req.params.noteId, res, false); - -const downloadAttachment = (req: Request<{ attachmentId: string }>, res: Response) => downloadAttachmentInt(req.params.attachmentId, res, true); -const openAttachment = (req: Request<{ attachmentId: string }>, res: Response) => downloadAttachmentInt(req.params.attachmentId, res, false); - function fileContentProvider(req: Request<{ noteId: string }>) { // Read the file name from route params. const note = becca.getNoteOrThrow(req.params.noteId); @@ -248,13 +202,8 @@ function uploadModifiedFileToAttachment(req: Request<{ attachmentId: string }>) export default { updateFile, updateAttachment, - openFile, fileContentProvider, - downloadFile, - downloadNoteInt, saveNoteToTmpDir, - openAttachment, - downloadAttachment, saveAttachmentToTmpDir, attachmentContentProvider, uploadModifiedFileToNote, diff --git a/apps/server/src/routes/api/note_map.ts b/apps/server/src/routes/api/note_map.ts deleted file mode 100644 index fa941b884c..0000000000 --- a/apps/server/src/routes/api/note_map.ts +++ /dev/null @@ -1,383 +0,0 @@ -import { BacklinkCountResponse, BacklinksResponse } from "@triliumnext/commons"; -import type { Request } from "express"; -import { HTMLElement, parse, TextNode } from "node-html-parser"; - -import becca from "../../becca/becca.js"; -import type BAttribute from "../../becca/entities/battribute.js"; -import type BNote from "../../becca/entities/bnote.js"; - -interface TreeLink { - sourceNoteId: string; - targetNoteId: string; -} - -function buildDescendantCountMap(noteIdsToCount: string[]) { - if (!Array.isArray(noteIdsToCount)) { - throw new Error("noteIdsToCount: type error"); - } - - const noteIdToCountMap = Object.create(null); - - function getCount(noteId: string) { - if (!(noteId in noteIdToCountMap)) { - const note = becca.getNote(noteId); - if (!note) { - return; - } - - const hiddenImageNoteIds = note.getRelations("imageLink").map((rel) => rel.value); - const childNoteIds = note.children.map((child) => child.noteId); - const nonHiddenNoteIds = childNoteIds.filter((childNoteId) => !hiddenImageNoteIds.includes(childNoteId)); - - noteIdToCountMap[noteId] = nonHiddenNoteIds.length; - - for (const child of note.children) { - noteIdToCountMap[noteId] += getCount(child.noteId); - } - } - - return noteIdToCountMap[noteId]; - } - noteIdsToCount.forEach((noteId) => { - getCount(noteId); - }); - - return noteIdToCountMap; -} -function getNeighbors(note: BNote, depth: number): string[] { - if (depth === 0) { - return []; - } - - const retNoteIds: string[] = []; - - function isIgnoredRelation(relation: BAttribute) { - return ["relationMapLink", "template", "inherit", "image", "ancestor"].includes(relation.name); - } - - // forward links - for (const relation of note.getRelations()) { - if (isIgnoredRelation(relation)) { - continue; - } - - const targetNote = relation.getTargetNote(); - - if (!targetNote || targetNote.isLabelTruthy("excludeFromNoteMap")) { - continue; - } - - retNoteIds.push(targetNote.noteId); - - for (const noteId of getNeighbors(targetNote, depth - 1)) { - retNoteIds.push(noteId); - } - } - - // backward links - for (const relation of note.getTargetRelations()) { - if (isIgnoredRelation(relation)) { - continue; - } - - const sourceNote = relation.getNote(); - - if (!sourceNote || sourceNote.isLabelTruthy("excludeFromNoteMap")) { - continue; - } - - retNoteIds.push(sourceNote.noteId); - - for (const noteId of getNeighbors(sourceNote, depth - 1)) { - retNoteIds.push(noteId); - } - } - - return retNoteIds; -} - -function getLinkMap(req: Request<{ noteId: string }>) { - const mapRootNote = becca.getNoteOrThrow(req.params.noteId); - - // if the map root itself has "excludeFromNoteMap" attribute (journal typically) then there wouldn't be anything - // to display, so we'll just ignore it - const ignoreExcludeFromNoteMap = mapRootNote.isLabelTruthy("excludeFromNoteMap"); - let unfilteredNotes; - - const toSet = (data: unknown) => new Set(data instanceof Array ? data : []); - - const excludeRelations = toSet(req.body.excludeRelations); - const includeRelations = toSet(req.body.includeRelations); - - if (mapRootNote.type === "search") { - // for search notes, we want to consider the direct search results only without the descendants - unfilteredNotes = mapRootNote.getSearchResultNotes(); - } else { - unfilteredNotes = mapRootNote.getSubtree({ - includeArchived: false, - resolveSearch: true, - includeHidden: mapRootNote.isInHiddenSubtree() - }).notes; - } - - const noteIds = new Set(unfilteredNotes.filter((note) => ignoreExcludeFromNoteMap || !note.isLabelTruthy("excludeFromNoteMap")).map((note) => note.noteId)); - - if (mapRootNote.type === "search") { - noteIds.delete(mapRootNote.noteId); - } - - for (const noteId of getNeighbors(mapRootNote, 3)) { - noteIds.add(noteId); - } - - const noteIdsArray = Array.from(noteIds); - - const notes = noteIdsArray.map((noteId) => { - const note = becca.getNoteOrThrow(noteId); - - return [note.noteId, note.getTitleOrProtected(), note.type, note.getLabelValue("color")]; - }); - - const links = Object.values(becca.attributes) - .filter((rel) => { - if (rel.type !== "relation" || rel.name === "relationMapLink" || rel.name === "template" || rel.name === "inherit") { - return false; - } else if (!noteIds.has(rel.noteId) || !noteIds.has(rel.value)) { - return false; - } else if (rel.name === "imageLink") { - const parentNote = becca.getNote(rel.noteId); - if (!parentNote) { - return false; - } - - return !parentNote.getChildNotes().find((childNote) => childNote.noteId === rel.value); - } else if (includeRelations.size != 0 && !includeRelations.has(rel.name)) { - return false; - } else if (excludeRelations.has(rel.name)) { - return false; - } - return true; - - }) - .map((rel) => ({ - id: `${rel.noteId}-${rel.name}-${rel.value}`, - sourceNoteId: rel.noteId, - targetNoteId: rel.value, - name: rel.name - })); - - return { - notes, - noteIdToDescendantCountMap: buildDescendantCountMap(noteIdsArray), - links - }; -} - -function getTreeMap(req: Request<{ noteId: string }>) { - const mapRootNote = becca.getNoteOrThrow(req.params.noteId); - // if the map root itself has "excludeFromNoteMap" (journal typically) then there wouldn't be anything to display, - // so we'll just ignore it - const ignoreExcludeFromNoteMap = mapRootNote.isLabelTruthy("excludeFromNoteMap"); - const subtree = mapRootNote.getSubtree({ - includeArchived: false, - resolveSearch: true, - includeHidden: mapRootNote.isInHiddenSubtree() - }); - - const notes = subtree.notes - .filter((note) => ignoreExcludeFromNoteMap || !note.isLabelTruthy("excludeFromNoteMap")) - .filter((note) => { - if (note.type !== "image" || note.getChildNotes().length > 0) { - return true; - } - - const imageLinkRelation = note.getTargetRelations().find((rel) => rel.name === "imageLink"); - - if (!imageLinkRelation) { - return true; - } - - return !note.getParentNotes().find((parentNote) => parentNote.noteId === imageLinkRelation.noteId); - }) - .map((note) => [note.noteId, note.getTitleOrProtected(), note.type, note.getLabelValue("color")]); - - const noteIds = new Set(); - notes.forEach(([noteId]) => noteId && noteIds.add(noteId)); - - const links: TreeLink[] = []; - - for (const { parentNoteId, childNoteId } of subtree.relationships) { - if (!noteIds.has(parentNoteId) || !noteIds.has(childNoteId)) { - continue; - } - - links.push({ - sourceNoteId: parentNoteId, - targetNoteId: childNoteId - }); - } - - const noteIdToDescendantCountMap = buildDescendantCountMap(Array.from(noteIds)); - - updateDescendantCountMapForSearch(noteIdToDescendantCountMap, subtree.relationships); - - return { - notes, - noteIdToDescendantCountMap, - links - }; -} - -function updateDescendantCountMapForSearch(noteIdToDescendantCountMap: Record, relationships: { parentNoteId: string; childNoteId: string }[]) { - for (const { parentNoteId, childNoteId } of relationships) { - const parentNote = becca.notes[parentNoteId]; - if (!parentNote || parentNote.type !== "search") { - continue; - } - - noteIdToDescendantCountMap[parentNote.noteId] = noteIdToDescendantCountMap[parentNoteId] || 0; - noteIdToDescendantCountMap[parentNote.noteId] += noteIdToDescendantCountMap[childNoteId] || 1; - } -} - -function removeImages(document: HTMLElement) { - const images = document.getElementsByTagName("img"); - for (const image of images) { - image.remove(); - } -} - -const EXCERPT_CHAR_LIMIT = 200; -type ElementOrText = HTMLElement | TextNode; - -export function findExcerpts(sourceNote: BNote, referencedNoteId: string) { - const html = sourceNote.getContent(); - const document = parse(html.toString()); - - const excerpts: string[] = []; - - removeImages(document); - - for (const linkEl of document.querySelectorAll("a")) { - console.log("Got ", linkEl.innerHTML); - const href = linkEl.getAttribute("href"); - - if (!href || !href.endsWith(referencedNoteId)) { - continue; - } - - linkEl.classList.add("backlink-link"); - - let centerEl: HTMLElement = linkEl; - - while (centerEl.tagName !== "BODY" && centerEl.parentNode && (centerEl.parentNode?.textContent?.length || 0) <= EXCERPT_CHAR_LIMIT) { - centerEl = centerEl.parentNode; - } - - const excerptEls: ElementOrText[] = [centerEl]; - let excerptLength = centerEl.textContent?.length || 0; - let left: ElementOrText = centerEl; - let right: ElementOrText = centerEl; - - while (excerptLength < EXCERPT_CHAR_LIMIT) { - let added = false; - - const prev: HTMLElement | null = left.previousElementSibling; - - if (prev) { - const prevText = prev.textContent || ""; - - if (prevText.length + excerptLength > EXCERPT_CHAR_LIMIT) { - const prefix = prevText.substr(prevText.length - (EXCERPT_CHAR_LIMIT - excerptLength)); - - const textNode = new TextNode(`…${prefix}`); - excerptEls.unshift(textNode); - - break; - } - - left = prev; - excerptEls.unshift(left); - excerptLength += prevText.length; - added = true; - } - - const next: HTMLElement | null = right.nextElementSibling; - - if (next) { - const nextText = next.textContent; - - if (nextText && nextText.length + excerptLength > EXCERPT_CHAR_LIMIT) { - const suffix = nextText.substr(nextText.length - (EXCERPT_CHAR_LIMIT - excerptLength)); - - const textNode = new TextNode(`${suffix}…`); - excerptEls.push(textNode); - - break; - } - - right = next; - excerptEls.push(right); - excerptLength += nextText?.length || 0; - added = true; - } - - if (!added) { - break; - } - } - - const excerptWrapper = new HTMLElement("div", {}); - excerptWrapper.classList.add("ck-content"); - excerptWrapper.classList.add("backlink-excerpt"); - - for (const childEl of excerptEls) { - excerptWrapper.appendChild(childEl); - } - - excerpts.push(excerptWrapper.outerHTML); - } - return excerpts; -} - -// TODO: Deduplicate with core -function getFilteredBacklinks(note: BNote): BAttribute[] { - return ( - note - .getTargetRelations() - // search notes have "ancestor" relations which are not interesting - .filter((relation) => !!relation.getNote() && relation.getNote().type !== "search") - ); -} - -function getBacklinks(req: Request<{ noteId: string }>): BacklinksResponse { - const { noteId } = req.params; - const note = becca.getNoteOrThrow(noteId); - - let backlinksWithExcerptCount = 0; - - return getFilteredBacklinks(note).map((backlink) => { - const sourceNote = backlink.note; - - if (sourceNote.type !== "text" || backlinksWithExcerptCount > 50) { - return { - noteId: sourceNote.noteId, - relationName: backlink.name - } satisfies BacklinksResponse[number]; - } - - backlinksWithExcerptCount++; - - const excerpts = findExcerpts(sourceNote, noteId); - return { - noteId: sourceNote.noteId, - excerpts - } satisfies BacklinksResponse[number]; - }); -} - -export default { - getLinkMap, - getTreeMap, - getBacklinks -}; diff --git a/apps/server/src/routes/custom.ts b/apps/server/src/routes/custom.ts index 5498ef6d06..31ea0658ee 100644 --- a/apps/server/src/routes/custom.ts +++ b/apps/server/src/routes/custom.ts @@ -1,3 +1,4 @@ +import { routeHelpers,utils } from "@triliumnext/core"; import type { Request, Response, Router } from "express"; import becca from "../becca/becca.js"; @@ -6,8 +7,6 @@ import cls from "../services/cls.js"; import log from "../services/log.js"; import scriptService from "../services/script.js"; import sql from "../services/sql.js"; -import { normalizeCustomHandlerPattern,safeExtractMessageAndStackFromError } from "../services/utils.js"; -import fileService from "./api/files.js"; function handleRequest(req: Request, res: Response) { @@ -41,7 +40,7 @@ function handleRequest(req: Request, res: Response) { } // Get normalized patterns to handle both trailing slash cases - const patterns = normalizeCustomHandlerPattern(attr.value); + const patterns = utils.normalizeCustomHandlerPattern(attr.value); let match: RegExpMatchArray | null = null; try { @@ -54,7 +53,7 @@ function handleRequest(req: Request, res: Response) { } } } catch (e: unknown) { - const [errMessage, errStack] = safeExtractMessageAndStackFromError(e); + const [errMessage, errStack] = utils.safeExtractMessageAndStackFromError(e); log.error(`Testing path for label '${attr.attributeId}', regex '${attr.value}' failed with error: ${errMessage}, stack: ${errStack}`); continue; } @@ -75,12 +74,12 @@ function handleRequest(req: Request, res: Response) { res }); } catch (e: unknown) { - const [errMessage, errStack] = safeExtractMessageAndStackFromError(e); + const [errMessage, errStack] = utils.safeExtractMessageAndStackFromError(e); log.error(`Custom handler '${note.noteId}' failed with: ${errMessage}, ${errStack}`); res.setHeader("Content-Type", "text/plain").status(500).send(errMessage); } } else if (attr.name === "customResourceProvider") { - fileService.downloadNoteInt(attr.noteId, res); + routeHelpers.downloadNoteInt(attr.noteId, res); } else { throw new Error(`Unrecognized attribute name '${attr.name}'`); } diff --git a/apps/server/src/routes/index.ts b/apps/server/src/routes/index.ts index f1ce6cd8cd..65d4b3160d 100644 --- a/apps/server/src/routes/index.ts +++ b/apps/server/src/routes/index.ts @@ -10,7 +10,7 @@ import attributeService from "../services/attributes.js"; import config from "../services/config.js"; import log from "../services/log.js"; import optionService from "../services/options.js"; -import { isDev, isElectron, isMac, isWindows11 } from "../services/utils.js"; +import { isDev, isElectron, isMac, isWindows, isWindows11 } from "../services/utils.js"; import { generateCsrfToken } from "./csrf_protection.js"; type View = "desktop" | "mobile" | "print"; @@ -25,18 +25,31 @@ export function bootstrap(req: Request, res: Response) { req.session.csrfInitialized = true; } + const view = getView(req); const isDbInitialized = sql_init.isDbInitialized(); - const commonItems = getSharedBootstrapItems(assetPath, isDbInitialized); + const commonItems = { + ...getSharedBootstrapItems(assetPath, isDbInitialized), + baseApiUrl: "api/", + appPath, + isStandalone: false, + isElectron, + isDev, + triliumVersion: packageJson.version, + device: view, + TRILIUM_SAFE_MODE: !!process.env.TRILIUM_SAFE_MODE, + instanceName: config.General ? config.General.instanceName : null + }; if (!isDbInitialized) { res.send({ ...commonItems, - baseApiUrl: "api/", - componentId: "" - }); + hasNativeTitleBar: false, + hasBackgroundEffects: isElectron && (isWindows11 || isMac), + isMainWindow: true, + appCssNoteIds: [], + } satisfies BootstrapDefinition); return; } - const options = optionService.getOptionMap(); const csrfToken = generateCsrfToken(req, res, { overwrite: false, @@ -44,43 +57,27 @@ export function bootstrap(req: Request, res: Response) { }); log.info(`CSRF token generation: ${csrfToken ? "Successful" : "Failed"}`); - const view = getView(req); - const theme = options.theme; - const themeNote = attributeService.getNoteWithLabel("appTheme", theme); + const options = optionService.getOptionMap(); const nativeTitleBarVisible = options.nativeTitleBarVisible === "true"; const iconPacks = iconPackService.getIconPacks(); - const sql = getSql(); res.send({ ...commonItems, dbInitialized: true, - device: view, csrfToken, - themeCssUrl: getThemeCssUrl(theme, themeNote), - themeUseNextAsBase: themeNote?.getAttributeValue("label", "appThemeBase") as "next" | "next-light" | "next-dark", platform: process.platform, - isElectron, hasNativeTitleBar: isElectron && nativeTitleBarVisible, hasBackgroundEffects: options.backgroundEffects === "true" && isElectron && (isWindows11 || isMac) && !nativeTitleBarVisible, - maxEntityChangeIdAtLoad: sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes"), - maxEntityChangeSyncIdAtLoad: sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes WHERE isSynced = 1"), - instanceName: config.General ? config.General.instanceName : null, - appCssNoteIds: getAppCssNoteIds(), - isDev, isMainWindow: view === "mobile" ? true : !req.query.extraWindow, - triliumVersion: packageJson.version, - appPath, - baseApiUrl: 'api/', iconPackCss: iconPacks .map((p: iconPackService.ProcessedIconPack) => iconPackService.generateCss(p, p.builtin ? `${assetPath}/fonts/${p.fontAttachmentId}.${iconPackService.MIME_TO_EXTENSION_MAPPINGS[p.fontMime]}` : `api/attachments/download/${p.fontAttachmentId}`)) .filter(Boolean) .join("\n\n"), - TRILIUM_SAFE_MODE: !!process.env.TRILIUM_SAFE_MODE } satisfies BootstrapDefinition); } @@ -120,28 +117,3 @@ function getView(req: Request): View { return "desktop"; } - -function getThemeCssUrl(theme: string, themeNote: BNote | null) { - if (theme === "auto") { - return `${assetPath}/stylesheets/theme.css`; - } else if (theme === "light") { - // light theme is always loaded as baseline - return false; - } else if (theme === "dark") { - return `${assetPath}/stylesheets/theme-dark.css`; - } else if (theme === "next") { - return `${assetPath}/stylesheets/theme-next.css`; - } else if (theme === "next-light") { - return `${assetPath}/stylesheets/theme-next-light.css`; - } else if (theme === "next-dark") { - return `${assetPath}/stylesheets/theme-next-dark.css`; - } else if (!process.env.TRILIUM_SAFE_MODE && themeNote) { - return `api/notes/download/${themeNote.noteId}`; - } - // baseline light theme - return false; -} - -function getAppCssNoteIds() { - return attributeService.getNotesWithLabel("appCss").map((note) => note.noteId); -} diff --git a/apps/server/src/routes/route_api.ts b/apps/server/src/routes/route_api.ts index 10e3f078ea..fa2aabe2f8 100644 --- a/apps/server/src/routes/route_api.ts +++ b/apps/server/src/routes/route_api.ts @@ -102,8 +102,7 @@ function internalRoute

(method: HttpMethod, path: str return; } - if (result?.then) { - // promise + if (result instanceof Promise) { result.then((promiseResult: unknown) => handleResponse(resultHandler, req, res, promiseResult, start)).catch((e: unknown) => handleException(e, method, path, res)); } else { handleResponse(resultHandler, req, res, result, start); diff --git a/apps/server/src/routes/routes.ts b/apps/server/src/routes/routes.ts index d2bb246041..c1ad909d61 100644 --- a/apps/server/src/routes/routes.ts +++ b/apps/server/src/routes/routes.ts @@ -28,7 +28,6 @@ import fontsRoute from "./api/fonts.js"; import importRoute from "./api/import.js"; import loginApiRoute from "./api/login.js"; import metricsRoute from "./api/metrics.js"; -import noteMapRoute from "./api/note_map.js"; import otherRoute from "./api/other.js"; import passwordApiRoute from "./api/password.js"; import recoveryCodes from './api/recovery_codes.js'; @@ -94,7 +93,6 @@ function register(app: express.Application) { }); route(PUT, "/api/notes/:noteId/file", [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], filesRoute.updateFile, apiResultHandler); - route(GET, "/api/notes/:noteId/open", [auth.checkApiAuthOrElectron], filesRoute.openFile); asyncRoute( GET, "/api/notes/:noteId/open-partial", @@ -105,15 +103,11 @@ function register(app: express.Application) { } }) ); - route(GET, "/api/notes/:noteId/download", [auth.checkApiAuthOrElectron], filesRoute.downloadFile); - // this "hacky" path is used for easier referencing of CSS resources - route(GET, "/api/notes/download/:noteId", [auth.checkApiAuthOrElectron], filesRoute.downloadFile); apiRoute(PST, "/api/notes/:noteId/save-to-tmp-dir", filesRoute.saveNoteToTmpDir); apiRoute(PST, "/api/notes/:noteId/upload-modified-file", filesRoute.uploadModifiedFileToNote); // TODO: Bring back attachment uploading // route(PST, "/api/notes/:noteId/attachments/upload", [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], attachmentsApiRoute.uploadAttachment, apiResultHandler); - route(GET, "/api/attachments/:attachmentId/open", [auth.checkApiAuthOrElectron], filesRoute.openAttachment); asyncRoute( GET, "/api/attachments/:attachmentId/open-partial", @@ -124,9 +118,7 @@ function register(app: express.Application) { } }) ); - route(GET, "/api/attachments/:attachmentId/download", [auth.checkApiAuthOrElectron], filesRoute.downloadAttachment); - // this "hacky" path is used for easier referencing of CSS resources - route(GET, "/api/attachments/download/:attachmentId", [auth.checkApiAuthOrElectron], filesRoute.downloadAttachment); + apiRoute(PST, "/api/attachments/:attachmentId/save-to-tmp-dir", filesRoute.saveAttachmentToTmpDir); apiRoute(PST, "/api/attachments/:attachmentId/upload-modified-file", filesRoute.uploadModifiedFileToAttachment); route(PUT, "/api/attachments/:attachmentId/file", [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], filesRoute.updateAttachment, apiResultHandler); @@ -210,10 +202,6 @@ function register(app: express.Application) { apiRoute(PST, "/api/other/render-markdown", otherRoute.renderMarkdown); apiRoute(PST, "/api/other/to-markdown", otherRoute.toMarkdown); - apiRoute(PST, "/api/note-map/:noteId/tree", noteMapRoute.getTreeMap); - apiRoute(PST, "/api/note-map/:noteId/link", noteMapRoute.getLinkMap); - apiRoute(GET, "/api/note-map/:noteId/backlinks", noteMapRoute.getBacklinks); - shareRoutes.register(router); etapiAuthRoutes.register(router, [loginRateLimiter]); diff --git a/apps/server/src/services/config.ts b/apps/server/src/services/config.ts index 267b68e008..30228ec05d 100644 --- a/apps/server/src/services/config.ts +++ b/apps/server/src/services/config.ts @@ -19,12 +19,13 @@ * ╚════════════════════════════════════════════════════════════════════════════╝ */ -import ini from "ini"; +import { utils } from "@triliumnext/core"; import fs from "fs"; -import dataDir from "./data_dir.js"; +import ini from "ini"; import path from "path"; + +import dataDir from "./data_dir.js"; import resourceDir from "./resource_dir.js"; -import { envToBoolean, stringToInt } from "./utils.js"; /** * Path to the sample configuration file that serves as a template for new installations. @@ -253,7 +254,7 @@ function getIniSection(sectionName: string): IniConfigSection | undefined { */ function transformBoolean(value: unknown): boolean { // First try the standard envToBoolean function which handles "true"/"false" strings - const result = envToBoolean(String(value)); + const result = utils.envToBoolean(String(value)); if (result !== undefined) return result; // Handle numeric boolean values (both string and number types) @@ -456,7 +457,7 @@ const configMapping = { aliasEnvVars: ['TRILIUM_LOGGING_RETENTION_DAYS'], iniGetter: () => getIniSection("Logging")?.retentionDays, defaultValue: LOGGING_DEFAULT_RETENTION_DAYS, - transformer: (value: unknown) => stringToInt(String(value)) ?? LOGGING_DEFAULT_RETENTION_DAYS + transformer: (value: unknown) => utils.stringToInt(String(value)) ?? LOGGING_DEFAULT_RETENTION_DAYS } } }; diff --git a/apps/server/src/services/export/opml.ts b/apps/server/src/services/export/opml.ts index 52e60a60f0..7d83a7d6f2 100644 --- a/apps/server/src/services/export/opml.ts +++ b/apps/server/src/services/export/opml.ts @@ -1,11 +1,10 @@ -"use strict"; - -import { getContentDisposition, stripTags } from "../utils.js"; -import becca from "../../becca/becca.js"; -import type TaskContext from "../task_context.js"; -import type BBranch from "../../becca/entities/bbranch.js"; +import { utils } from "@triliumnext/core"; import type { Response } from "express"; +import becca from "../../becca/becca.js"; +import type BBranch from "../../becca/entities/bbranch.js"; +import type TaskContext from "../task_context.js"; + function exportToOpml(taskContext: TaskContext<"export">, branch: BBranch, version: string, res: Response) { if (!["1.0", "2.0"].includes(version)) { throw new Error(`Unrecognized OPML version ${version}`); @@ -59,7 +58,7 @@ function exportToOpml(taskContext: TaskContext<"export">, branch: BBranch, versi const filename = `${branch.prefix ? `${branch.prefix} - ` : ""}${note.title}.opml`; - res.setHeader("Content-Disposition", getContentDisposition(filename)); + res.setHeader("Content-Disposition", utils.getContentDisposition(filename)); res.setHeader("Content-Type", "text/x-opml"); res.write(` @@ -83,7 +82,7 @@ function exportToOpml(taskContext: TaskContext<"export">, branch: BBranch, versi function prepareText(text: string) { const newLines = text.replace(/(]*>|)/g, "\n").replace(/ /g, " "); // nbsp isn't in XML standard (only HTML) - const stripped = stripTags(newLines); + const stripped = utils.stripTags(newLines); const escaped = escapeXmlAttribute(stripped); diff --git a/apps/server/src/services/export/zip/abstract_provider.ts b/apps/server/src/services/export/zip/abstract_provider.ts index d3c5ea2220..dd2fdcafa2 100644 --- a/apps/server/src/services/export/zip/abstract_provider.ts +++ b/apps/server/src/services/export/zip/abstract_provider.ts @@ -1,4 +1,5 @@ import { NoteType } from "@triliumnext/commons"; +import { ExportFormat } from "@triliumnext/core"; import { Archiver } from "archiver"; import mimeTypes from "mime-types"; @@ -6,9 +7,9 @@ import type BBranch from "../../../becca/entities/bbranch.js"; import type BNote from "../../../becca/entities/bnote.js"; import type { default as NoteMeta, NoteMetaFile } from "../../meta/note_meta.js"; -type RewriteLinksFn = (content: string, noteMeta: NoteMeta) => string; +export type { ExportFormat, NoteMeta } from "@triliumnext/core"; -export type ExportFormat = "html" | "markdown" | "share"; +type RewriteLinksFn = (content: string, noteMeta: NoteMeta) => string; export interface AdvancedExportOptions { /** diff --git a/apps/server/src/services/hidden_subtree.spec.ts b/apps/server/src/services/hidden_subtree.spec.ts index dd3939155b..5282e5b525 100644 --- a/apps/server/src/services/hidden_subtree.spec.ts +++ b/apps/server/src/services/hidden_subtree.spec.ts @@ -1,12 +1,11 @@ import { deferred, LOCALES } from "@triliumnext/commons"; +import { becca_loader, i18n } from "@triliumnext/core"; import { beforeAll, describe, expect, it } from "vitest"; import becca from "../becca/becca.js"; -import becca_loader from "../becca/becca_loader.js"; import branches from "./branches.js"; import cls from "./cls.js"; import hiddenSubtreeService from "./hidden_subtree.js"; -import { changeLanguage } from "./i18n.js"; import notes from "./notes.js"; import sql_init from "./sql_init.js"; @@ -141,7 +140,7 @@ describe("Hidden Subtree", () => { } try { - await changeLanguage(locale.id); + await i18n.changeLanguage(locale.id); } catch (error) { done.reject(error); } diff --git a/apps/server/src/services/import/single.ts b/apps/server/src/services/import/single.ts index 2a66164c9e..7fba5024a5 100644 --- a/apps/server/src/services/import/single.ts +++ b/apps/server/src/services/import/single.ts @@ -1,10 +1,10 @@ import type { NoteType } from "@triliumnext/commons"; -import { sanitize } from "@triliumnext/core"; +import { sanitize, utils } from "@triliumnext/core"; import type BNote from "../../becca/entities/bnote.js"; import imageService from "../../services/image.js"; import noteService from "../../services/notes.js"; -import { getNoteTitle, processStringOrBuffer } from "../../services/utils.js"; +import { processStringOrBuffer } from "../../services/utils.js"; import protectedSessionService from "../protected_session.js"; import type TaskContext from "../task_context.js"; import type { File } from "./common.js"; @@ -57,7 +57,7 @@ function importFile(taskContext: TaskContext<"importNotes">, file: File, parentN const mime = mimeService.getMime(originalName) || file.mimetype; const { note } = noteService.createNewNote({ parentNoteId: parentNote.noteId, - title: getNoteTitle(originalName, mime === "application/pdf", { mime }), + title: utils.getNoteTitle(originalName, mime === "application/pdf", { mime }), content: file.buffer, isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), type: "file", @@ -72,7 +72,7 @@ function importFile(taskContext: TaskContext<"importNotes">, file: File, parentN } function importCodeNote(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote) { - const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); + const title = utils.getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); const content = processStringOrBuffer(file.buffer); const detectedMime = mimeService.getMime(file.originalname) || file.mimetype; const mime = mimeService.normalizeMimeType(detectedMime); @@ -97,7 +97,7 @@ function importCodeNote(taskContext: TaskContext<"importNotes">, file: File, par } function importCustomType(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote, type: NoteType, mime: string) { - const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); + const title = utils.getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); const content = processStringOrBuffer(file.buffer); const { note } = noteService.createNewNote({ @@ -115,7 +115,7 @@ function importCustomType(taskContext: TaskContext<"importNotes">, file: File, p } function importPlainText(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote) { - const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); + const title = utils.getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); const plainTextContent = processStringOrBuffer(file.buffer); const htmlContent = convertTextToHtml(plainTextContent); @@ -150,7 +150,7 @@ function convertTextToHtml(text: string) { } function importMarkdown(taskContext: TaskContext<"importNotes">, file: File, parentNote: BNote) { - const title = getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); + const title = utils.getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); const markdownContent = processStringOrBuffer(file.buffer); let htmlContent = markdownService.renderToHtml(markdownContent, title); @@ -179,7 +179,7 @@ function importHtml(taskContext: TaskContext<"importNotes">, file: File, parentN // Try to get title from HTML first, fall back to filename // We do this before sanitization since that turns all

s into

const htmlTitle = importUtils.extractHtmlTitle(content); - const title = htmlTitle || getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); + const title = htmlTitle || utils.getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces); content = importUtils.handleH1(content, title); diff --git a/apps/server/src/services/import/zip.ts b/apps/server/src/services/import/zip.ts index a20794305e..29fce3430a 100644 --- a/apps/server/src/services/import/zip.ts +++ b/apps/server/src/services/import/zip.ts @@ -1,7 +1,7 @@ import { ALLOWED_NOTE_TYPES, type NoteType } from "@triliumnext/commons"; -import { sanitize } from "@triliumnext/core"; +import { sanitize, utils } from "@triliumnext/core"; import path from "path"; import type { Stream } from "stream"; import yauzl from "yauzl"; @@ -14,7 +14,7 @@ import type BNote from "../../becca/entities/bnote.js"; import attributeService from "../../services/attributes.js"; import log from "../../services/log.js"; import noteService from "../../services/notes.js"; -import { getNoteTitle, newEntityId, processStringOrBuffer, removeFileExtension, unescapeHtml } from "../../services/utils.js"; +import { newEntityId, processStringOrBuffer, unescapeHtml } from "../../services/utils.js"; import type AttributeMeta from "../meta/attribute_meta.js"; import type NoteMeta from "../meta/note_meta.js"; import protectedSessionService from "../protected_session.js"; @@ -162,7 +162,7 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu // in case we lack metadata, we treat e.g. "Programming.html" and "Programming" as the same note // (one data file, the other directory for children) - const filePathNoExt = removeFileExtension(filePath); + const filePathNoExt = utils.removeFileExtension(filePath); if (filePathNoExt in createdPaths) { return createdPaths[filePathNoExt]; @@ -234,7 +234,7 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu return; } - const noteTitle = getNoteTitle(filePath, !!taskContext.data?.replaceUnderscoresWithSpaces, noteMeta); + const noteTitle = utils.getNoteTitle(filePath, !!taskContext.data?.replaceUnderscoresWithSpaces, noteMeta); const parentNoteId = getParentNoteId(filePath, parentNoteMeta); if (!parentNoteId) { @@ -467,7 +467,7 @@ async function importZip(taskContext: TaskContext<"importNotes">, fileBuffer: Bu content = processStringOrBuffer(content); } - const noteTitle = getNoteTitle(filePath, taskContext.data?.replaceUnderscoresWithSpaces || false, noteMeta); + const noteTitle = utils.getNoteTitle(filePath, taskContext.data?.replaceUnderscoresWithSpaces || false, noteMeta); content = processNoteContent(noteMeta, type, mime, content, noteTitle || "", filePath); diff --git a/apps/server/src/services/meta/attachment_meta.ts b/apps/server/src/services/meta/attachment_meta.ts index f0c50360f7..dfbb43af05 100644 --- a/apps/server/src/services/meta/attachment_meta.ts +++ b/apps/server/src/services/meta/attachment_meta.ts @@ -1,8 +1 @@ -export default interface AttachmentMeta { - attachmentId?: string; - title: string; - role: string; - mime: string; - position?: number; - dataFileName: string; -} +export type { AttachmentMeta as default } from "@triliumnext/core"; diff --git a/apps/server/src/services/meta/attribute_meta.ts b/apps/server/src/services/meta/attribute_meta.ts index aa3d36b8c2..75bd6ba922 100644 --- a/apps/server/src/services/meta/attribute_meta.ts +++ b/apps/server/src/services/meta/attribute_meta.ts @@ -1,10 +1 @@ -import type { AttributeType } from "@triliumnext/commons"; - -export default interface AttributeMeta { - noteId?: string; - type: AttributeType; - name: string; - value: string; - isInheritable?: boolean; - position?: number; -} +export type { AttributeMeta as default } from "@triliumnext/core"; diff --git a/apps/server/src/services/meta/note_meta.ts b/apps/server/src/services/meta/note_meta.ts index 7a7a9f4b7c..e661899d5c 100644 --- a/apps/server/src/services/meta/note_meta.ts +++ b/apps/server/src/services/meta/note_meta.ts @@ -1,32 +1 @@ -import type { NoteType } from "@triliumnext/commons"; -import type AttachmentMeta from "./attachment_meta.js"; -import type AttributeMeta from "./attribute_meta.js"; -import type { ExportFormat } from "../export/zip/abstract_provider.js"; - -export interface NoteMetaFile { - formatVersion: number; - appVersion: string; - files: NoteMeta[]; -} - -export default interface NoteMeta { - noteId?: string; - notePath?: string[]; - isClone?: boolean; - title?: string; - notePosition?: number; - prefix?: string | null; - isExpanded?: boolean; - type?: NoteType; - mime?: string; - /** 'html' or 'markdown', applicable to text notes only */ - format?: ExportFormat; - dataFileName?: string; - dirFileName?: string; - /** this file should not be imported (e.g., HTML navigation) */ - noImport?: boolean; - isImportRoot?: boolean; - attributes?: AttributeMeta[]; - attachments?: AttachmentMeta[]; - children?: NoteMeta[]; -} +export { type NoteMeta as default, type NoteMetaFile } from "@triliumnext/core"; diff --git a/apps/server/src/services/script_context.ts b/apps/server/src/services/script_context.ts index f6dc0f7800..25be301d19 100644 --- a/apps/server/src/services/script_context.ts +++ b/apps/server/src/services/script_context.ts @@ -1,6 +1,7 @@ -import { toObject } from "./utils.js"; -import BackendScriptApi from "./backend_script_api.js"; +import { utils } from "@triliumnext/core"; + import type BNote from "../becca/entities/bnote.js"; +import BackendScriptApi from "./backend_script_api.js"; import type { ApiParams } from "./backend_script_api_interface.js"; type Module = { @@ -16,8 +17,8 @@ class ScriptContext { constructor(allNotes: BNote[], apiParams: ApiParams) { this.allNotes = allNotes; this.modules = {}; - this.notes = toObject(allNotes, (note) => [note.noteId, note]); - this.apis = toObject(allNotes, (note) => [note.noteId, new BackendScriptApi(note, apiParams)]); + this.notes = utils.toObject(allNotes, (note) => [note.noteId, note]); + this.apis = utils.toObject(allNotes, (note) => [note.noteId, new BackendScriptApi(note, apiParams)]); } require(moduleNoteIds: string[]) { diff --git a/apps/server/src/services/utils.spec.ts b/apps/server/src/services/utils.spec.ts index 1a69d7dd17..e29c391c03 100644 --- a/apps/server/src/services/utils.spec.ts +++ b/apps/server/src/services/utils.spec.ts @@ -1,707 +1,9 @@ -import { describe, expect,it } from "vitest"; +import { describe, expect, it } from "vitest"; -import utils from "./utils.js"; - -type TestCase any> = [desc: string, fnParams: Parameters, expected: ReturnType]; - -describe("#newEntityId", () => { - it("should return a string with a length of 12", () => { - const result = utils.newEntityId(); - expect(result).toBeTypeOf("string"); - expect(result).toHaveLength(12); - }); -}); - -describe("#randomString", () => { - it("should return a string with a length as per argument", () => { - const stringLength = 5; - const result = utils.randomString(stringLength); - expect(result).toBeTypeOf("string"); - expect(result).toHaveLength(stringLength); - }); -}); - -// TriliumNextTODO: should use mocks and assert that functions get called -describe("#randomSecureToken", () => { - // base64 -> 4 * (bytes/3) length -> if padding and rounding up is ignored for simplicity - // https://stackoverflow.com/a/13378842 - const byteToBase64Length = (bytes: number) => 4 * (bytes / 3); - - it("should return a string and use 32 bytes by default", () => { - const result = utils.randomSecureToken(); - expect(result).toBeTypeOf("string"); - expect(result.length).toBeGreaterThanOrEqual(byteToBase64Length(32)); - }); - - it("should return a string and use passed byte length", () => { - const bytes = 16; - const result = utils.randomSecureToken(bytes); - expect(result).toBeTypeOf("string"); - expect(result.length).toBeGreaterThanOrEqual(byteToBase64Length(bytes)); - expect(result.length).toBeLessThan(44); // default argument uses 32 bytes -> which translates to 44 base64 legal chars - }); -}); - -// TriliumNextTODO: should use mocks and assert that functions get called -describe.todo("#md5", () => {}); - -// TriliumNextTODO: should use mocks and assert that functions get called -describe.todo("#hashedBlobId", () => {}); - -// TriliumNextTODO: should use mocks and assert that functions get called -describe.todo("#toBase64", () => {}); - -// TriliumNextTODO: should use mocks and assert that functions get called -describe.todo("#fromBase64", () => {}); - -// TriliumNextTODO: should use mocks and assert that functions get called -describe.todo("#hmac", () => {}); - -// TriliumNextTODO: should use mocks and assert that functions get called -describe.todo("#hash", () => {}); - -describe("#isEmptyOrWhitespace", () => { - const testCases: TestCase[] = [ - [ "w/ 'null' it should return true", [ null ], true ], - [ "w/ 'null' it should return true", [ null ], true ], - [ "w/ undefined it should return true", [ undefined ], true ], - [ "w/ empty string '' it should return true", [ "" ], true ], - [ "w/ single whitespace string ' ' it should return true", [ " " ], true ], - [ "w/ multiple whitespace string ' ' it should return true", [ " " ], true ], - [ "w/ non-empty string ' t ' it should return false", [ " t " ], false ] - ]; - - testCases.forEach((testCase) => { - const [ desc, fnParams, expected ] = testCase; - it(desc, () => { - const result = utils.isEmptyOrWhitespace(...fnParams); - expect(result).toStrictEqual(expected); - }); - }); -}); - -describe("#sanitizeSqlIdentifier", () => { - const testCases: TestCase[] = [ - [ "w/ 'test' it should not strip anything", [ "test" ], "test" ], - [ "w/ 'test123' it should not strip anything", [ "test123" ], "test123" ], - [ "w/ 'tEst_TeSt' it should not strip anything", [ "tEst_TeSt" ], "tEst_TeSt" ], - [ "w/ 'test_test' it should not strip '_'", [ "test_test" ], "test_test" ], - [ "w/ 'test-' it should strip the '-'", [ "test-" ], "test" ], - [ "w/ 'test-test' it should strip the '-'", [ "test-test" ], "testtest" ], - [ "w/ 'test; --test' it should strip the '; --'", [ "test; --test" ], "testtest" ], - [ "w/ 'test test' it should strip the ' '", [ "test test" ], "testtest" ] - ]; - - testCases.forEach((testCase) => { - const [ desc, fnParams, expected ] = testCase; - it(desc, () => { - const result = utils.sanitizeSqlIdentifier(...fnParams); - expect(result).toStrictEqual(expected); - }); - }); -}); - -describe("#escapeHtml", () => { - it("should re-export 'escape-html' npm module as escapeHtml", () => { - expect(utils.escapeHtml).toBeTypeOf("function"); - }); -}); - -describe("#unescapeHtml", () => { - it("should re-export 'unescape' npm module as unescapeHtml", () => { - expect(utils.unescapeHtml).toBeTypeOf("function"); - }); -}); - -describe("#toObject", () => { - it("should return an object with keys and value being set from the supplied Function", () => { - type TestListEntry = { testPropA: string; testPropB: string }; - type TestListFn = (testListEntry: TestListEntry) => [string, string]; - const testList: [TestListEntry, TestListEntry] = [ - { testPropA: "keyA", testPropB: "valueA" }, - { testPropA: "keyB", testPropB: "valueB" } - ]; - const fn: TestListFn = (testListEntry: TestListEntry) => [ `${testListEntry.testPropA }_fn`, `${testListEntry.testPropB }_fn` ]; - - const result = utils.toObject(testList, fn); - expect(result).toStrictEqual({ - keyA_fn: "valueA_fn", - keyB_fn: "valueB_fn" - }); - }); -}); - -describe("#stripTags", () => { - //prettier-ignore - const htmlWithNewlines = -`

abc -def

-

ghi

`; - - const testCases: TestCase[] = [ - [ "should strip all tags and only return the content, leaving new lines and spaces in tact", [ htmlWithNewlines ], "abc\ndef\nghi" ], - //TriliumNextTODO: should this actually insert a space between content to prevent concatenated text? - [ "should strip all tags and only return the content", [ "

abc

def

" ], "abcdef" ] - ]; - - testCases.forEach((testCase) => { - const [ desc, fnParams, expected ] = testCase; - it(desc, () => { - const result = utils.stripTags(...fnParams); - expect(result).toStrictEqual(expected); - }); - }); -}); - -describe.todo("#escapeRegExp", () => {}); - -describe.todo("#crash", () => {}); - -describe("#getContentDisposition", () => { - - const defaultFallBackDisposition = `file; filename="file"; filename*=UTF-8''file`; - const testCases: TestCase[] = [ - [ - "when passed filename is empty, it should fallback to default value 'file'", - [ " " ], - defaultFallBackDisposition - ], - [ - "when passed filename '..' would cause sanitized filename to be empty, it should fallback to default value 'file'", - [ ".." ], - defaultFallBackDisposition - ], - // COM1 is a Windows specific "illegal filename" that sanitize filename strips away - [ - "when passed filename 'COM1' would cause sanitized filename to be empty, it should fallback to default value 'file'", - [ "COM1" ], - defaultFallBackDisposition - ], - [ - "sanitized passed filename should be returned URIEncoded", - [ "test file.csv" ], - `file; filename="test%20file.csv"; filename*=UTF-8''test%20file.csv` - ] - ]; - - testCases.forEach(testCase => { - const [ desc, fnParams, expected ] = testCase; - it(desc, () => { - const result = utils.getContentDisposition(...fnParams); - expect(result).toStrictEqual(expected); - }); - }); -}); - -describe("#isStringNote", () => { - - const testCases: TestCase[] = [ - [ - "w/ 'undefined' note type, but a string mime type, it should return true", - [ undefined, "application/javascript" ], - true - ], - [ - "w/ non-string note type, it should return false", - [ "image", "image/jpeg" ], - false - ], - [ - "w/ string note type (text), it should return true", - [ "text", "text/html" ], - true - ], - [ - "w/ string note type (code), it should return true", - [ "code", "application/json" ], - true - ], - [ - "w/ non-string note type (file), but string mime type, it should return true", - [ "file", "application/json" ], - true - ], - [ - "w/ non-string note type (file), but mime type starting with 'text/', it should return true", - [ "file", "text/html" ], - true - ] - ]; - - testCases.forEach((testCase) => { - const [ desc, fnParams, expected ] = testCase; - it(desc, () => { - const result = utils.isStringNote(...fnParams); - expect(result).toStrictEqual(expected); - }); - }); -}); - -describe.todo("#quoteRegex", () => {}); - -describe.todo("#replaceAll", () => {}); - -describe("#removeFileExtension", () => { - const testCases: TestCase[] = [ - [ "w/ 'test.md' it should strip '.md'", [ "test.md" ], "test" ], - [ "w/ 'test.markdown' it should strip '.markdown'", [ "test.markdown" ], "test" ], - [ "w/ 'test.html' it should strip '.html'", [ "test.html" ], "test" ], - [ "w/ 'test.htm' it should strip '.htm'", [ "test.htm" ], "test" ], - [ "w/ 'test.zip' it should NOT strip '.zip'", [ "test.zip" ], "test.zip" ] - ]; - - testCases.forEach((testCase) => { - const [ desc, fnParams, expected ] = testCase; - it(desc, () => { - const result = utils.removeFileExtension(...fnParams); - expect(result).toStrictEqual(expected); - }); - }); -}); - -describe("#getNoteTitle", () => { - const testCases: TestCase[] = [ - [ - "when file has no spaces, and no special file extension, it should return the filename unaltered", - [ "test.json", true, undefined ], - "test.json" - ], - [ - "when replaceUnderscoresWithSpaces is false, it should keep the underscores in the title", - [ "test_file.json", false, undefined ], - "test_file.json" - ], - [ - "when replaceUnderscoresWithSpaces is true, it should replace the underscores in the title", - [ "test_file.json", true, undefined ], - "test file.json" - ], - [ - "when filePath ends with one of the extra handled endings (.md), it should strip the file extension from the title", - [ "test_file.md", false, undefined ], - "test_file" - ], - [ - "when filePath ends with one of the extra handled endings (.md) and replaceUnderscoresWithSpaces is true, it should strip the file extension from the title and replace underscores", - [ "test_file.md", true, undefined ], - "test file" - ], - [ - "when filepath contains a full path, it should only return the basename of the file", - [ "Trilium Demo/Scripting examples/Statistics/Most cloned notes/template.zip", true, undefined ], - "template.zip" - ], - [ - "when filepath contains a full path and has extra handled ending (.html), it should only return the basename of the file and strip the file extension", - [ "Trilium Demo/Scripting examples/Statistics/Most cloned notes/template.html", true, undefined ], - "template" - ], - [ - "when a noteMeta object is passed, it should use the title from the noteMeta, if present", - [ "test_file.md", true, { title: "some other title" } ], - "some other title" - ], - [ - "when a noteMeta object is passed, but the title prop is empty, it should try to handle the filename as if no noteMeta was passed", - [ "test_file.md", true, { title: "" } ], - "test file" - ], - [ - "when a noteMeta object is passed, but the title prop is empty, it should try to handle the filename as if no noteMeta was passed", - [ "test_file.json", false, { title: " " } ], - "test_file.json" - ] - ]; - - testCases.forEach(testCase => { - const [ desc, fnParams, expected ] = testCase; - it(desc, () => { - const result = utils.getNoteTitle(...fnParams); - expect(result).toStrictEqual(expected); - }); - }); - -}); - -describe("#timeLimit", () => { - it("when promise execution does NOT exceed timeout, it should resolve with promises' value", async () => { - const resolvedValue = `resolved: ${new Date().toISOString()}`; - const testPromise = new Promise((res, rej) => { - setTimeout(() => { - return res(resolvedValue); - }, 200); - //rej("rejected!"); - }); - await expect(utils.timeLimit(testPromise, 1_000)).resolves.toBe(resolvedValue); - }); - - it("when promise execution rejects within timeout, it should return the original promises' rejected value, not the custom set one", async () => { - const rejectedValue = `rejected: ${new Date().toISOString()}`; - const testPromise = new Promise((res, rej) => { - setTimeout(() => { - //return res("resolved"); - rej(rejectedValue); - }, 100); - }); - await expect(utils.timeLimit(testPromise, 200, "Custom Error")).rejects.toThrow(rejectedValue); - }); - - it("when promise execution exceeds the set timeout, and 'errorMessage' is NOT set, it should reject the promise and display default error message", async () => { - const testPromise = new Promise((res, rej) => { - setTimeout(() => { - return res("resolved"); - }, 500); - //rej("rejected!"); - }); - await expect(utils.timeLimit(testPromise, 200)).rejects.toThrow(`Process exceeded time limit 200`); - }); - - it("when promise execution exceeds the set timeout, and 'errorMessage' is set, it should reject the promise and display set error message", async () => { - const customErrorMsg = "Custom Error"; - const testPromise = new Promise((res, rej) => { - setTimeout(() => { - return res("resolved"); - }, 500); - //rej("rejected!"); - }); - await expect(utils.timeLimit(testPromise, 200, customErrorMsg)).rejects.toThrow(customErrorMsg); - }); - - // TriliumNextTODO: since TS avoids this from ever happening – do we need this check? - it("when the passed promise is not a promise but 'undefined', it should return 'undefined'", async () => { - //@ts-expect-error - passing in illegal type 'undefined' - expect(utils.timeLimit(undefined, 200)).toBe(undefined); - }); - - // TriliumNextTODO: since TS avoids this from ever happening – do we need this check? - it("when the passed promise is not a promise, it should return the passed value", async () => { - //@ts-expect-error - passing in illegal type 'object' - expect(utils.timeLimit({ test: 1 }, 200)).toStrictEqual({ test: 1 }); - }); -}); - -describe("#removeDiacritic", () => { - const testCases: TestCase[] = [ - [ "w/ 'Äpfel' it should replace the 'Ä'", [ "Äpfel" ], "Apfel" ], - [ "w/ 'Été' it should replace the 'É' and 'é'", [ "Été" ], "Ete" ], - [ "w/ 'Fête' it should replace the 'ê'", [ "Fête" ], "Fete" ], - [ "w/ 'Αλφαβήτα' it should replace the 'ή'", [ "Αλφαβήτα" ], "Αλφαβητα" ], - [ "w/ '' (empty string) it should return empty string", [ "" ], "" ] - ]; - - testCases.forEach((testCase) => { - const [ desc, fnParams, expected ] = testCase; - it(desc, () => { - const result = utils.removeDiacritic(...fnParams); - expect(result).toStrictEqual(expected); - }); - }); -}); - -describe("#normalize", () => { - const testCases: TestCase[] = [ - [ "w/ 'Äpfel' it should replace the 'Ä' and return lowercased", [ "Äpfel" ], "apfel" ], - [ "w/ 'Été' it should replace the 'É' and 'é' and return lowercased", [ "Été" ], "ete" ], - [ "w/ 'FêTe' it should replace the 'ê' and return lowercased", [ "FêTe" ], "fete" ], - [ "w/ 'ΑλΦαβήΤα' it should replace the 'ή' and return lowercased", [ "ΑλΦαβήΤα" ], "αλφαβητα" ], - [ "w/ '' (empty string) it should return empty string", [ "" ], "" ] - ]; - - testCases.forEach((testCase) => { - const [ desc, fnParams, expected ] = testCase; - it(desc, () => { - const result = utils.normalize(...fnParams); - expect(result).toStrictEqual(expected); - }); - }); -}); - -describe("#toMap", () => { - it("should return an instace of Map, with the correct size and keys, when supplied with a list and existing keys", () => { - const testList = [ { title: "test", propA: "text", propB: 123 }, { title: "test2", propA: "prop2", propB: 456 } ]; - const result = utils.toMap(testList, "title"); - expect(result).toBeInstanceOf(Map); - expect(result.size).toBe(2); - expect(Array.from(result.keys())).toStrictEqual([ "test", "test2" ]); - }); - it("should return an instace of Map, with an empty size, when the supplied list does not contain the supplied key", () => { - const testList = [ { title: "test", propA: "text", propB: 123 }, { title: "test2", propA: "prop2", propB: 456 } ]; - //@ts-expect-error - key is non-existing on supplied list type - const result = utils.toMap(testList, "nonExistingKey"); - expect(result).toBeInstanceOf(Map); - expect(result.size).toBe(0); - }); - it.fails("should correctly handle duplicate keys? (currently it will overwrite the entry, so returned size will be 1 instead of 2)", () => { - const testList = [ { title: "testDupeTitle", propA: "text", propB: 123 }, { title: "testDupeTitle", propA: "prop2", propB: 456 } ]; - const result = utils.toMap(testList, "title"); - expect(result).toBeInstanceOf(Map); - expect(result.size).toBe(2); - }); -}); - -describe("#envToBoolean", () => { - const testCases: TestCase[] = [ - [ "w/ 'true' it should return boolean 'true'", [ "true" ], true ], - [ "w/ 'True' it should return boolean 'true'", [ "True" ], true ], - [ "w/ 'TRUE' it should return boolean 'true'", [ "TRUE" ], true ], - [ "w/ 'true ' it should return boolean 'true'", [ "true " ], true ], - [ "w/ 'false' it should return boolean 'false'", [ "false" ], false ], - [ "w/ 'False' it should return boolean 'false'", [ "False" ], false ], - [ "w/ 'FALSE' it should return boolean 'false'", [ "FALSE" ], false ], - [ "w/ 'false ' it should return boolean 'false'", [ "false " ], false ], - [ "w/ 'whatever' (non-boolean string) it should return undefined", [ "whatever" ], undefined ], - [ "w/ '-' (non-boolean string) it should return undefined", [ "-" ], undefined ], - [ "w/ '' (empty string) it should return undefined", [ "" ], undefined ], - [ "w/ ' ' (white space string) it should return undefined", [ " " ], undefined ], - [ "w/ undefined it should return undefined", [ undefined ], undefined ], - //@ts-expect-error - pass wrong type as param - [ "w/ number 1 it should return undefined", [ 1 ], undefined ] - ]; - - testCases.forEach((testCase) => { - const [ desc, fnParams, expected ] = testCase; - it(desc, () => { - const result = utils.envToBoolean(...fnParams); - expect(result).toStrictEqual(expected); - }); - }); -}); - -describe.todo("#getResourceDir", () => {}); - -describe("#isElectron", () => { - it("should export a boolean", () => { - expect(utils.isElectron).toBeTypeOf("boolean"); - }); -}); - -describe("#isMac", () => { - it("should export a boolean", () => { - expect(utils.isMac).toBeTypeOf("boolean"); - }); -}); - -describe("#isWindows", () => { - it("should export a boolean", () => { - expect(utils.isWindows).toBeTypeOf("boolean"); - }); -}); +import utils from "./utils"; describe("#isDev", () => { it("should export a boolean", () => { expect(utils.isDev).toBeTypeOf("boolean"); }); }); - -describe("#safeExtractMessageAndStackFromError", () => { - it("should correctly extract the message and stack property if it gets passed an instance of an Error", () => { - const testMessage = "Test Message"; - const testError = new Error(testMessage); - const actual = utils.safeExtractMessageAndStackFromError(testError); - expect(actual[0]).toBe(testMessage); - expect(actual[1]).not.toBeUndefined(); - }); - - it("should use the fallback 'Unknown Error' message, if it gets passed anything else than an instance of an Error", () => { - const testNonError = "this is not an instance of an Error, but JS technically allows us to throw this anyways"; - const actual = utils.safeExtractMessageAndStackFromError(testNonError); - expect(actual[0]).toBe("Unknown Error"); - expect(actual[1]).toBeUndefined(); - }); -}); - -describe("#formatDownloadTitle", () => { - //prettier-ignore - const testCases: [fnValue: Parameters, expectedValue: ReturnType][] = [ - - // empty fileName tests - [ - [ "", "text", "" ], - "untitled.html" - ], - [ - [ "", "canvas", "" ], - "untitled.json" - ], - [ - [ "", null, "" ], - "untitled" - ], - - - // json extension from type tests - [ - [ "test_file", "canvas", "" ], - "test_file.json" - ], - [ - [ "test_file", "relationMap", "" ], - "test_file.json" - ], - [ - [ "test_file", "search", "" ], - "test_file.json" - ], - - - // extension based on mime type - [ - [ "test_file", null, "text/csv" ], - "test_file.csv" - ], - [ - [ "test_file_wo_ext", "image", "image/svg+xml" ], - "test_file_wo_ext.svg" - ], - [ - [ "test_file_wo_ext", "file", "application/json" ], - "test_file_wo_ext.json" - ], - [ - [ "test_file_w_fake_ext.ext", "image", "image/svg+xml" ], - "test_file_w_fake_ext.ext.svg" - ], - [ - [ "test_file_w_correct_ext.svg", "image", "image/svg+xml" ], - "test_file_w_correct_ext.svg" - ], - [ - [ "test_file_w_correct_ext.svgz", "image", "image/svg+xml" ], - "test_file_w_correct_ext.svgz" - ], - [ - [ "test_file.zip", "file", "application/zip" ], - "test_file.zip" - ], - [ - [ "test_file", "file", "application/zip" ], - "test_file.zip" - ], - - - // application/octet-stream tests - [ - [ "test_file", "file", "application/octet-stream" ], - "test_file" - ], - [ - [ "test_file.zip", "file", "application/octet-stream" ], - "test_file.zip" - ], - [ - [ "test_file.unknown", null, "application/octet-stream" ], - "test_file.unknown" - ], - - - // sanitized filename tests - [ - [ "test/file", null, "application/octet-stream" ], - "testfile" - ], - [ - [ "test:file.zip", "file", "application/zip" ], - "testfile.zip" - ], - [ - [ ":::", "file", "application/zip" ], - ".zip" - ], - [ - [ ":::a", "file", "application/zip" ], - "a.zip" - ] - ]; - - testCases.forEach((testCase) => { - const [ fnParams, expected ] = testCase; - return it(`With args '${JSON.stringify(fnParams)}', it should return '${expected}'`, () => { - const actual = utils.formatDownloadTitle(...fnParams); - expect(actual).toStrictEqual(expected); - }); - }); -}); - -describe("#normalizeUrl", () => { - const testCases: TestCase[] = [ - [ "should remove trailing slash from simple URL", [ "https://example.com/" ], "https://example.com" ], - [ "should remove trailing slash from URL with path", [ "https://example.com/path/" ], "https://example.com/path" ], - [ "should preserve URL without trailing slash", [ "https://example.com" ], "https://example.com" ], - [ "should preserve URL without trailing slash with path", [ "https://example.com/path" ], "https://example.com/path" ], - [ "should preserve protocol-only URLs", [ "https://" ], "https://" ], - [ "should preserve protocol-only URLs", [ "http://" ], "http://" ], - [ "should fix double slashes in path", [ "https://example.com//api//test" ], "https://example.com/api/test" ], - [ "should handle multiple double slashes", [ "https://example.com///api///test" ], "https://example.com/api/test" ], - [ "should handle trailing slash with double slashes", [ "https://example.com//api//" ], "https://example.com/api" ], - [ "should preserve protocol double slash", [ "https://example.com/api" ], "https://example.com/api" ], - [ "should handle empty string", [ "" ], "" ], - [ "should handle whitespace-only string", [ " " ], "" ], - [ "should trim whitespace", [ " https://example.com/ " ], "https://example.com" ], - [ "should handle null as empty", [ null as any ], null ], - [ "should handle undefined as empty", [ undefined as any ], undefined ] - ]; - - testCases.forEach((testCase) => { - const [ desc, fnParams, expected ] = testCase; - it(desc, () => { - const result = utils.normalizeUrl(...fnParams); - expect(result).toStrictEqual(expected); - }); - }); -}); - -describe("#normalizeCustomHandlerPattern", () => { - const testCases: TestCase[] = [ - [ "should handle pattern without ending - add both versions", [ "foo" ], [ "foo", "foo/" ] ], - [ "should handle pattern with trailing slash - add both versions", [ "foo/" ], [ "foo", "foo/" ] ], - [ "should handle pattern ending with $ - add optional slash", [ "foo$" ], [ "foo/?$" ] ], - [ "should handle pattern with trailing slash and $ - add both versions", [ "foo/$" ], [ "foo$", "foo/$" ] ], - [ "should preserve existing optional slash pattern", [ "foo/?$" ], [ "foo/?$" ] ], - [ "should preserve existing optional slash pattern (alternative)", [ "foo/?)" ], [ "foo/?)" ] ], - [ "should handle regex pattern with special chars", [ "api/[a-z]+$" ], [ "api/[a-z]+/?$" ] ], - [ "should handle complex regex pattern", [ "user/([0-9]+)/profile$" ], [ "user/([0-9]+)/profile/?$" ] ], - [ "should handle empty string", [ "" ], [ "" ] ], - [ "should handle whitespace-only string", [ " " ], [ "" ] ], - [ "should handle null", [ null as any ], [ null ] ], - [ "should handle undefined", [ undefined as any ], [ undefined ] ] - ]; - - testCases.forEach((testCase) => { - const [ desc, fnParams, expected ] = testCase; - it(desc, () => { - const result = utils.normalizeCustomHandlerPattern(...fnParams); - expect(result).toStrictEqual(expected); - }); - }); -}); - -describe("#slugify", () => { - it("should return a slugified string", () => { - const testString = "This is a Test String! With unicode & Special #Chars."; - const expectedSlug = "this-is-a-test-string-with-unicode-special-chars"; - const result = utils.slugify(testString); - expect(result).toBe(expectedSlug); - }); - - it("supports CJK characters without alteration", () => { - const testString = "测试中文字符"; - const expectedSlug = "测试中文字符"; - const result = utils.slugify(testString); - expect(result).toBe(expectedSlug); - }); - - it("supports Cyrillic characters without alteration", () => { - const testString = "Тестирование кириллических символов"; - const expectedSlug = "тестирование-кириллических-символов"; - const result = utils.slugify(testString); - expect(result).toBe(expectedSlug); - }); - - // preserves diacritic marks - it("preserves diacritic marks", () => { - const testString = "Café naïve façade jalapeño"; - const expectedSlug = "café-naïve-façade-jalapeño"; - const result = utils.slugify(testString); - expect(result).toBe(expectedSlug); - }); -}); diff --git a/apps/server/src/services/utils.ts b/apps/server/src/services/utils.ts index 70531a376f..7b4d6cd1d3 100644 --- a/apps/server/src/services/utils.ts +++ b/apps/server/src/services/utils.ts @@ -1,14 +1,10 @@ import { getCrypto,utils as coreUtils } from "@triliumnext/core"; import chardet from "chardet"; import crypto from "crypto"; -import { t } from "i18next"; import { release as osRelease } from "os"; import path from "path"; import stripBom from "strip-bom"; -import log from "./log.js"; -import type NoteMeta from "./meta/note_meta.js"; - const osVersion = osRelease().split('.').map(Number); export const isMac = process.platform === "darwin"; @@ -83,22 +79,6 @@ export function constantTimeCompare(a: string | null | undefined, b: string | nu return crypto.timingSafeEqual(bufA, bufB); } -export function toObject(array: T[], fn: (item: T) => [K, V]): Record { - const obj: Record = {} as Record; // TODO: unsafe? - - for (const item of array) { - const ret = fn(item); - - obj[ret[0]] = ret[1]; - } - - return obj; -} - -export function stripTags(text: string) { - return text.replace(/<(?:.|\n)*?>/gm, ""); -} - /** @deprecated */ export function getContentDisposition(filename: string) { return coreUtils.getContentDisposition(filename); @@ -124,37 +104,6 @@ export function formatDownloadTitle(fileName: string, type: string | null, mime: return coreUtils.formatDownloadTitle(fileName, type, mime); } -export function removeFileExtension(filePath: string, mime?: string) { - const extension = path.extname(filePath).toLowerCase(); - - if (mime?.startsWith("video/") || mime?.startsWith("audio/")) { - return filePath.substring(0, filePath.length - extension.length); - } - - switch (extension) { - case ".md": - case ".mdx": - case ".markdown": - case ".html": - case ".htm": - case ".excalidraw": - case ".mermaid": - case ".mmd": - case ".pdf": - return filePath.substring(0, filePath.length - extension.length); - default: - return filePath; - } -} - -export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, noteMeta?: NoteMeta) { - const trimmedNoteMeta = noteMeta?.title?.trim(); - if (trimmedNoteMeta) return trimmedNoteMeta; - - const basename = path.basename(removeFileExtension(filePath, noteMeta?.mime)); - return replaceUnderscoresWithSpaces ? basename.replace(/_/g, " ").trim() : basename; -} - /** @deprecated */ export function removeDiacritic(str: string) { return coreUtils.removeDiacritic(str); @@ -170,37 +119,6 @@ export function toMap>(list: T[], key: keyof T) { return coreUtils.toMap(list, key); } -// try to turn 'true' and 'false' strings from process.env variables into boolean values or undefined -export function envToBoolean(val: string | undefined) { - if (val === undefined || typeof val !== "string") return undefined; - - const valLc = val.toLowerCase().trim(); - - if (valLc === "true") return true; - if (valLc === "false") return false; - - return undefined; -} - -/** - * Parses a string value to an integer. If the resulting number is NaN or undefined, the result is also undefined. - * - * @param val the value to parse. - * @returns the parsed value. - */ -export function stringToInt(val: string | undefined) { - if (!val) { - return undefined; - } - - const parsed = parseInt(val, 10); - if (Number.isNaN(parsed)) { - return undefined; - } - - return parsed; -} - /** * Returns the directory for resources. On Electron builds this corresponds to the `resources` subdirectory inside the distributable package. * On development builds, this simply refers to the src directory of the application. @@ -220,48 +138,6 @@ export function getResourceDir() { return path.join(__dirname, ".."); } -// TODO: Deduplicate with src/public/app/services/utils.ts -/** - * Compares two semantic version strings. - * Returns: - * 1 if v1 is greater than v2 - * 0 if v1 is equal to v2 - * -1 if v1 is less than v2 - * - * @param v1 First version string - * @param v2 Second version string - * @returns - */ -function compareVersions(v1: string, v2: string): number { - // Remove 'v' prefix and everything after dash if present - v1 = v1.replace(/^v/, "").split("-")[0]; - v2 = v2.replace(/^v/, "").split("-")[0]; - - const v1parts = v1.split(".").map(Number); - const v2parts = v2.split(".").map(Number); - - // Pad shorter version with zeros - while (v1parts.length < 3) v1parts.push(0); - while (v2parts.length < 3) v2parts.push(0); - - // Compare major version - if (v1parts[0] !== v2parts[0]) { - return v1parts[0] > v2parts[0] ? 1 : -1; - } - - // Compare minor version - if (v1parts[1] !== v2parts[1]) { - return v1parts[1] > v2parts[1] ? 1 : -1; - } - - // Compare patch version - if (v1parts[2] !== v2parts[2]) { - return v1parts[2] > v2parts[2] ? 1 : -1; - } - - return 0; -} - /** * For buffers, they are scanned for a supported encoding and decoded (UTF-8, UTF-16). In some cases, the BOM is also stripped. * @@ -291,82 +167,6 @@ export function processStringOrBuffer(data: string | Buffer | null) { } } -/** - * Normalizes a path pattern for custom request handlers. - * Ensures both trailing slash and non-trailing slash versions are handled. - * - * @param pattern The original pattern from customRequestHandler attribute - * @returns An array of patterns to match both with and without trailing slash - */ -export function normalizeCustomHandlerPattern(pattern: string | null | undefined): (string | null | undefined)[] { - if (!pattern || typeof pattern !== 'string') { - return [pattern]; - } - - pattern = pattern.trim(); - - if (!pattern) { - return [pattern]; - } - - // If pattern already ends with optional trailing slash, return as-is - if (pattern.endsWith('/?$') || pattern.endsWith('/?)')) { - return [pattern]; - } - - // If pattern ends with $, handle it specially - if (pattern.endsWith('$')) { - const basePattern = pattern.slice(0, -1); - - // If already ends with slash, create both versions - if (basePattern.endsWith('/')) { - const withoutSlash = `${basePattern.slice(0, -1) }$`; - const withSlash = pattern; - return [withoutSlash, withSlash]; - } - // Add optional trailing slash - const withSlash = `${basePattern }/?$`; - return [withSlash]; - - } - - // For patterns without $, add both versions - if (pattern.endsWith('/')) { - const withoutSlash = pattern.slice(0, -1); - return [withoutSlash, pattern]; - } - const withSlash = `${pattern }/`; - return [pattern, withSlash]; - -} - -export function formatUtcTime(time: string) { - return time.replace("T", " ").substring(0, 19); -} - -// TODO: Deduplicate with client utils -export function formatSize(size: number | null | undefined) { - if (size === null || size === undefined) { - return ""; - } - - size = Math.max(Math.round(size / 1024), 1); - - if (size < 1024) { - return `${size} KiB`; - } - return `${Math.round(size / 102.4) / 10} MiB`; - -} - -function slugify(text: string) { - return text - .normalize("NFC") // keep composed form, preserves accents - .toLowerCase() - .replace(/[^\p{Letter}\p{Number}]+/gu, "-") // replace non-letter/number with "-" - .replace(/(^-|-$)+/g, ""); // trim dashes -} - /** @deprecated */ export const escapeHtml = coreUtils.escapeHtml; /** @deprecated */ @@ -392,15 +192,12 @@ export function waitForStreamToFinish(stream: any): Promise { } export default { - compareVersions, constantTimeCompare, - envToBoolean, escapeHtml, escapeRegExp, formatDownloadTitle, fromBase64, getContentDisposition, - getNoteTitle, getResourceDir, hashedBlobId, hmac, @@ -413,19 +210,14 @@ export default { md5, newEntityId, normalize, - normalizeCustomHandlerPattern, quoteRegex, randomSecureToken, randomString, removeDiacritic, - removeFileExtension, replaceAll, safeExtractMessageAndStackFromError, - stripTags, - slugify, toBase64, toMap, - toObject, unescapeHtml, waitForStreamToFinish }; diff --git a/apps/server/src/services/ws_messaging_provider.ts b/apps/server/src/services/ws_messaging_provider.ts index 0896e8a906..a97c66a26a 100644 --- a/apps/server/src/services/ws_messaging_provider.ts +++ b/apps/server/src/services/ws_messaging_provider.ts @@ -24,7 +24,7 @@ export default class WebSocketMessagingProvider implements MessagingProvider { init(httpServer: HttpServer, sessionParser: express.RequestHandler) { this.webSocketServer = new WebSocketServer({ verifyClient: (info, done) => { - sessionParser(info.req as express.Request, {}, () => { + sessionParser(info.req as express.Request, {} as express.Response, () => { const allowed = isElectron || (info.req as any).session.loggedIn || (config.General && config.General.noAuthentication); if (!allowed) { diff --git a/apps/server/src/sql_provider.ts b/apps/server/src/sql_provider.ts index da1604d44e..86adb5b2c9 100644 --- a/apps/server/src/sql_provider.ts +++ b/apps/server/src/sql_provider.ts @@ -44,7 +44,8 @@ export default class BetterSqlite3Provider implements DatabaseProvider { prepare(query: string): Statement { if (!this.dbConnection) throw new Error("DB not open."); - return this.dbConnection.prepare(query); + // Cast is safe: better-sqlite3 only returns bigint when safeIntegers() is enabled, which we don't use. + return this.dbConnection.prepare(query) as unknown as Statement; } transaction(func: (statement: Statement) => T): Transaction { diff --git a/apps/server/src/test/becca_easy_mocking.ts b/apps/server/src/test/becca_easy_mocking.ts index d809baa7f9..293d37a2e8 100644 --- a/apps/server/src/test/becca_easy_mocking.ts +++ b/apps/server/src/test/becca_easy_mocking.ts @@ -1,134 +1,3 @@ -import { NoteType } from "@triliumnext/commons"; - -import BAttachment from "../becca/entities/battachment.js"; -import BAttribute from "../becca/entities/battribute.js"; -import BBranch from "../becca/entities/bbranch.js"; -import BNote from "../becca/entities/bnote.js"; -import utils, { randomString } from "../services/utils.js"; - -type AttributeDefinitions = { [key in `#${string}`]: string; }; -type RelationDefinitions = { [key in `~${string}`]: string; }; - -interface NoteDefinition extends AttributeDefinitions, RelationDefinitions { - id?: string | undefined; - title?: string; - content?: string; - type?: NoteType; - mime?: string; - children?: NoteDefinition[]; - attachments?: { - title: string; - role: string; - mime: string; - }[]; -} - -/** - * Creates the given notes with the given title and optionally one or more attributes. - * - * For a label to be created, simply pass on a key prefixed with `#` and any desired value. - * - * The notes and attributes will be injected in the froca. - * - * @param notes - * @returns an array containing the IDs of the created notes. - * @example - * buildNotes([ - * { title: "A", "#startDate": "2025-05-05" }, - * { title: "B", "#startDate": "2025-05-07" } - * ]); - */ -export function buildNotes(notes: NoteDefinition[]) { - const ids: string[] = []; - - for (const noteDef of notes) { - ids.push(buildNote(noteDef).noteId); - } - - return ids; -} - -export function buildNote(noteDef: NoteDefinition) { - const note = new BNote({ - noteId: noteDef.id ?? utils.randomString(12), - title: noteDef.title ?? "New note", - type: noteDef.type ?? "text", - mime: noteDef.mime ?? "text/html", - isProtected: false, - blobId: "" - }); - - // Handle content. - if (noteDef.content !== undefined) { - note.getContent = () => noteDef.content!; - } - - // Handle children - if (noteDef.children) { - for (const childDef of noteDef.children) { - const childNote = buildNote(childDef); - new BBranch({ - noteId: childNote.noteId, - parentNoteId: note.noteId, - branchId: `${note.noteId}_${childNote.noteId}` - }); - } - } - - // Handle labels and relations. - let position = 0; - for (const [ key, value ] of Object.entries(noteDef)) { - const attributeId = utils.randomString(12); - const name = key.substring(1); - - let attribute: BAttribute | null = null; - if (key.startsWith("#")) { - attribute = new BAttribute({ - noteId: note.noteId, - attributeId, - type: "label", - name, - value, - position, - isInheritable: false - }); - } - - if (key.startsWith("~")) { - attribute = new BAttribute({ - noteId: note.noteId, - attributeId, - type: "relation", - name, - value, - position, - isInheritable: false - }); - } - - if (!attribute) { - continue; - } - - position++; - } - - // Handle attachments. - if (noteDef.attachments) { - const allAttachments: BAttachment[] = []; - for (const { title, role, mime } of noteDef.attachments) { - const attachment = new BAttachment({ - attachmentId: randomString(10), - ownerId: note.noteId, - title, - role, - mime - }); - allAttachments.push(attachment); - } - - note.getAttachmentsByRole = (role) => allAttachments.filter(a => a.role === role); - } - - return note; -} +import { becca_easy_mocking } from "@triliumnext/core"; +export const buildNote = becca_easy_mocking.buildNote; +export const buildNotes = becca_easy_mocking.buildNotes; diff --git a/apps/server/src/test/becca_mocking.ts b/apps/server/src/test/becca_mocking.ts index 34ec36c3c8..4e70eb4ff9 100644 --- a/apps/server/src/test/becca_mocking.ts +++ b/apps/server/src/test/becca_mocking.ts @@ -1,76 +1,2 @@ -import BNote from "../becca/entities/bnote.js"; -import BBranch from "../becca/entities/bbranch.js"; -import BAttribute from "../becca/entities/battribute.js"; -import becca from "../becca/becca.js"; -import randtoken from "rand-token"; -import type SearchResult from "../services/search/search_result.js"; -import type { NoteRow, NoteType } from "@triliumnext/commons"; -randtoken.generator({ source: "crypto" }); - -export function findNoteByTitle(searchResults: Array, title: string): BNote | undefined { - return searchResults.map((sr) => becca.notes[sr.noteId]).find((note) => note.title === title); -} - -export class NoteBuilder { - note: BNote; - constructor(note: BNote) { - this.note = note; - } - - label(name: string, value = "", isInheritable = false) { - new BAttribute({ - attributeId: id(), - noteId: this.note.noteId, - type: "label", - isInheritable, - name, - value - }); - - return this; - } - - relation(name: string, targetNote: BNote) { - new BAttribute({ - attributeId: id(), - noteId: this.note.noteId, - type: "relation", - name, - value: targetNote.noteId - }); - - return this; - } - - child(childNoteBuilder: NoteBuilder, prefix = "") { - new BBranch({ - branchId: id(), - noteId: childNoteBuilder.note.noteId, - parentNoteId: this.note.noteId, - prefix, - notePosition: 10 - }); - - return this; - } -} - -export function id() { - return randtoken.generate(10); -} - -export function note(title: string, extraParams: Partial = {}) { - const row = Object.assign( - { - noteId: id(), - title: title, - type: "text" as NoteType, - mime: "text/html" - }, - extraParams - ); - - const note = new BNote(row); - - return new NoteBuilder(note); -} +import { becca_mocking } from "@triliumnext/core"; +export const NoteBuilder = becca_mocking.NoteBuilder; diff --git a/apps/server/src/www.ts b/apps/server/src/www.ts index ec3c31bbcf..868b3877ad 100644 --- a/apps/server/src/www.ts +++ b/apps/server/src/www.ts @@ -1,4 +1,4 @@ -import { getMessagingProvider } from "@triliumnext/core"; +import { getMessagingProvider, utils } from "@triliumnext/core"; import type { Express } from "express"; import fs from "fs"; import http from "http"; @@ -12,7 +12,6 @@ import host from "./services/host.js"; import log from "./services/log.js"; import port from "./services/port.js"; import { getDbSize } from "./services/sql_init.js"; -import utils, { formatSize, formatUtcTime } from "./services/utils.js"; import WebSocketMessagingProvider from "./services/ws_messaging_provider.js"; const MINIMUM_NODE_VERSION = "20.0.0"; @@ -62,7 +61,7 @@ export default async function startTriliumServer() { const sessionParser = (await import("./routes/session_parser.js")).default; (getMessagingProvider() as WebSocketMessagingProvider).init(httpServer, sessionParser); - if (utils.isElectron) { + if (utils.isElectron()) { const electronRouting = await import("./routes/electron.js"); electronRouting.default(app); } @@ -71,9 +70,9 @@ export default async function startTriliumServer() { async function displayStartupMessage() { log.info(`\n${LOGO.replace("[version]", appInfo.appVersion)}`); log.info(`📦 Versions: app=${appInfo.appVersion} db=${appInfo.dbVersion} sync=${appInfo.syncVersion} clipper=${appInfo.clipperProtocolVersion}`); - log.info(`🔧 Build: ${formatUtcTime(appInfo.buildDate)} (${appInfo.buildRevision.substring(0, 10)})`); + log.info(`🔧 Build: ${utils.formatUtcTime(appInfo.buildDate)} (${appInfo.buildRevision.substring(0, 10)})`); log.info(`📂 Data dir: ${appInfo.dataDirectory}`); - log.info(`⏰ UTC time: ${formatUtcTime(appInfo.utcDateTime)}`); + log.info(`⏰ UTC time: ${utils.formatUtcTime(appInfo.utcDateTime)}`); // for perf. issues it's good to know the rough configuration const cpuInfos = (await import("os")).cpus(); @@ -82,7 +81,7 @@ async function displayStartupMessage() { const cpuModel = (cpuInfos[0].model || "").trimEnd(); log.info(`💻 CPU: ${cpuModel} (${cpuInfos.length}-core @ ${cpuInfos[0].speed} Mhz)`); } - log.info(`💾 DB size: ${formatSize(getDbSize() * 1024)}`); + log.info(`💾 DB size: ${utils.formatSize(getDbSize() * 1024)}`); log.info(""); } @@ -155,7 +154,7 @@ function startHttpServer(app: Express) { } } - if (utils.isElectron) { + if (utils.isElectron()) { import("electron").then(({ app, dialog }) => { // Not all situations require showing an error dialog. When Trilium is already open, // clicking the shortcut, the software icon, or the taskbar icon, or when creating a new window, diff --git a/apps/web-clipper/package.json b/apps/web-clipper/package.json index 966bbb10ba..59b7e07a83 100644 --- a/apps/web-clipper/package.json +++ b/apps/web-clipper/package.json @@ -13,7 +13,7 @@ "postinstall": "wxt prepare" }, "keywords": [], - "packageManager": "pnpm@10.32.1", + "packageManager": "pnpm@10.33.0", "devDependencies": { "@wxt-dev/auto-icons": "1.1.1", "wxt": "0.20.20" diff --git a/apps/website/package.json b/apps/website/package.json index 3d495ee9fa..0e54d6bc04 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -9,21 +9,21 @@ "preview": "pnpm build && vite preview" }, "dependencies": { - "i18next": "25.10.3", + "i18next": "25.10.10", "i18next-http-backend": "3.0.2", "preact": "10.29.0", "preact-iso": "2.11.1", "preact-render-to-string": "6.6.6", - "react-i18next": "16.6.0" + "react-i18next": "16.6.6" }, "devDependencies": { "@preact/preset-vite": "2.10.5", "eslint": "10.1.0", "eslint-config-preact": "2.0.0", "typescript": "5.9.3", - "user-agent-data-types": "0.4.2", - "vite": "8.0.1", - "vitest": "4.1.0" + "user-agent-data-types": "0.4.3", + "vite": "8.0.2", + "vitest": "4.1.2" }, "eslintConfig": { "extends": "preact" diff --git a/apps/website/src/translations/it/translation.json b/apps/website/src/translations/it/translation.json index 2d86a3afaa..84b60d045c 100644 --- a/apps/website/src/translations/it/translation.json +++ b/apps/website/src/translations/it/translation.json @@ -201,7 +201,7 @@ "resources": { "title": "Risorse", "icon_packs": "Pacchetti di icone", - "icon_packs_intro": "Ampliate la selezione di icone disponibili per le vostre note utilizzando un pacchetto di icone. Per ulteriori informazioni sui pacchetti di icone, consultate ladocumentazione ufficiale.", + "icon_packs_intro": "Ampliate la selezione di icone disponibili per le vostre note utilizzando un pacchetto di icone. Per ulteriori informazioni sui pacchetti di icone, consultate la documentazione ufficiale.", "download": "Scarica", "website": "Sito web" } diff --git a/apps/website/src/translations/pl/translation.json b/apps/website/src/translations/pl/translation.json index 7551a29e34..727cf42dff 100644 --- a/apps/website/src/translations/pl/translation.json +++ b/apps/website/src/translations/pl/translation.json @@ -201,7 +201,7 @@ "resources": { "title": "Zasoby", "icon_packs": "Paczki ikon", - "icon_packs_intro": "Rozszerz wybór dostępnych ikon dla swoich notatek, korzystając z pakietu ikon. Więcej informacji na temat pakietów ikon znajdziesz w dokumentacji .", + "icon_packs_intro": "Rozszerz wybór dostępnych ikon dla swoich notatek, korzystając z pakietu ikon. Więcej informacji na temat pakietów ikon znajdziesz w oficjalnej dokumentacji .", "download": "Pobieranie", "website": "Strona internetowa" } diff --git a/docs/README-pl.md b/docs/README-pl.md index 73a83f8fb0..b00670d0f2 100644 --- a/docs/README-pl.md +++ b/docs/README-pl.md @@ -48,7 +48,7 @@ wiedzy. [docs.triliumnotes.org](https://docs.triliumnotes.org/)** Nasza dokumentacja jest dostępna w wielu formatach: -- **Dokumentacja Online**: Pełna dokumentacja dostępna na +- **Dokumentacja online**: Przeglądaj pełną dokumentację pod linkiem [docs.triliumnotes.org](https://docs.triliumnotes.org/) - **Pomoc w aplikacji**: Naciśnij `F1` w Trilium, aby uzyskać dostęp do tej samej dokumentacji bezpośrednio w aplikacji diff --git a/package.json b/package.json index c20996da39..acdad6e906 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "test:all": "pnpm test:parallel && pnpm test:sequential", "test:parallel": "pnpm --filter=!server --filter=!ckeditor5-mermaid --filter=!ckeditor5-math --parallel test", "test:sequential": "pnpm --filter=server --filter=ckeditor5-mermaid --filter=ckeditor5-math --sequential test", - "typecheck": "tsc --build", + "typecheck": "tsx scripts/filter-tsc-output.mts", "dev:format-check": "eslint -c eslint.format.config.mjs .", "dev:format-fix": "eslint -c eslint.format.config.mjs . --fix", "dev:linter-check": "cross-env NODE_OPTIONS=--max_old_space_size=4096 eslint .", @@ -54,9 +54,9 @@ "@types/express": "5.0.6", "@types/js-yaml": "4.0.9", "@types/node": "24.12.0", - "@vitest/browser-webdriverio": "4.1.0", - "@vitest/coverage-v8": "4.1.0", - "@vitest/ui": "4.1.0", + "@vitest/browser-webdriverio": "4.1.2", + "@vitest/coverage-v8": "4.1.2", + "@vitest/ui": "4.1.2", "chalk": "5.6.2", "cross-env": "10.1.0", "dpdm": "4.0.1", @@ -66,7 +66,7 @@ "eslint-config-prettier": "10.1.8", "eslint-plugin-playwright": "2.10.1", "eslint-plugin-simple-import-sort": "12.1.1", - "happy-dom": "20.8.4", + "happy-dom": "20.8.8", "http-server": "14.1.1", "jiti": "2.6.1", "js-yaml": "4.1.1", @@ -76,11 +76,11 @@ "tslib": "2.8.1", "tsx": "4.21.0", "typescript": "5.9.3", - "typescript-eslint": "8.57.1", + "typescript-eslint": "8.57.2", "upath": "2.0.1", - "vite": "8.0.1", + "vite": "8.0.2", "vite-plugin-dts": "4.5.4", - "vitest": "4.1.0" + "vitest": "4.1.2" }, "license": "AGPL-3.0-only", "author": { @@ -96,14 +96,14 @@ "url": "https://github.com/TriliumNext/Trilium/issues" }, "homepage": "https://triliumnotes.org", - "packageManager": "pnpm@10.32.1", + "packageManager": "pnpm@10.33.0", "pnpm": { "patchedDependencies": { "@ckeditor/ckeditor5-mention": "patches/@ckeditor__ckeditor5-mention.patch", "@ckeditor/ckeditor5-code-block": "patches/@ckeditor__ckeditor5-code-block.patch" }, "overrides": { - "@codemirror/language": "6.12.2", + "@codemirror/language": "6.12.3", "@lezer/highlight": "1.2.3", "@lezer/common": "1.5.1", "mermaid": "11.13.0", diff --git a/packages/ckeditor5-admonition/package.json b/packages/ckeditor5-admonition/package.json index 13475ec301..57ab953242 100644 --- a/packages/ckeditor5-admonition/package.json +++ b/packages/ckeditor5-admonition/package.json @@ -24,22 +24,22 @@ "@ckeditor/ckeditor5-dev-build-tools": "55.2.0", "@ckeditor/ckeditor5-inspector": ">=4.1.0", "@ckeditor/ckeditor5-package-tools": "5.1.0", - "@typescript-eslint/eslint-plugin": "8.57.1", - "@typescript-eslint/parser": "8.57.1", - "@vitest/browser": "4.1.0", - "@vitest/coverage-istanbul": "4.1.0", + "@typescript-eslint/eslint-plugin": "8.57.2", + "@typescript-eslint/parser": "8.57.2", + "@vitest/browser": "4.1.2", + "@vitest/coverage-istanbul": "4.1.2", "ckeditor5": "47.6.1", "eslint": "10.1.0", "eslint-config-ckeditor5": ">=9.1.0", "http-server": "14.1.1", "lint-staged": "16.4.0", - "stylelint": "17.5.0", + "stylelint": "17.6.0", "stylelint-config-ckeditor5": ">=9.1.0", "ts-node": "10.9.2", "typescript": "5.9.3", "vite-plugin-svgo": "2.0.0", - "vitest": "4.1.0", - "webdriverio": "9.26.1" + "vitest": "4.1.2", + "webdriverio": "9.27.0" }, "peerDependencies": { "ckeditor5": "47.6.1" diff --git a/packages/ckeditor5-footnotes/package.json b/packages/ckeditor5-footnotes/package.json index 0fa4a2403a..1c8027d401 100644 --- a/packages/ckeditor5-footnotes/package.json +++ b/packages/ckeditor5-footnotes/package.json @@ -25,22 +25,22 @@ "@ckeditor/ckeditor5-dev-build-tools": "55.2.0", "@ckeditor/ckeditor5-inspector": ">=4.1.0", "@ckeditor/ckeditor5-package-tools": "5.1.0", - "@typescript-eslint/eslint-plugin": "8.57.1", - "@typescript-eslint/parser": "8.57.1", - "@vitest/browser": "4.1.0", - "@vitest/coverage-istanbul": "4.1.0", + "@typescript-eslint/eslint-plugin": "8.57.2", + "@typescript-eslint/parser": "8.57.2", + "@vitest/browser": "4.1.2", + "@vitest/coverage-istanbul": "4.1.2", "ckeditor5": "47.6.1", "eslint": "10.1.0", "eslint-config-ckeditor5": ">=9.1.0", "http-server": "14.1.1", "lint-staged": "16.4.0", - "stylelint": "17.5.0", + "stylelint": "17.6.0", "stylelint-config-ckeditor5": ">=9.1.0", "ts-node": "10.9.2", "typescript": "5.9.3", "vite-plugin-svgo": "2.0.0", - "vitest": "4.1.0", - "webdriverio": "9.26.1" + "vitest": "4.1.2", + "webdriverio": "9.27.0" }, "peerDependencies": { "ckeditor5": "47.6.1" diff --git a/packages/ckeditor5-keyboard-marker/package.json b/packages/ckeditor5-keyboard-marker/package.json index 435146b9ca..05b190e476 100644 --- a/packages/ckeditor5-keyboard-marker/package.json +++ b/packages/ckeditor5-keyboard-marker/package.json @@ -27,22 +27,22 @@ "@ckeditor/ckeditor5-dev-build-tools": "55.2.0", "@ckeditor/ckeditor5-inspector": ">=4.1.0", "@ckeditor/ckeditor5-package-tools": "5.1.0", - "@typescript-eslint/eslint-plugin": "8.57.1", - "@typescript-eslint/parser": "8.57.1", - "@vitest/browser": "4.1.0", - "@vitest/coverage-istanbul": "4.1.0", + "@typescript-eslint/eslint-plugin": "8.57.2", + "@typescript-eslint/parser": "8.57.2", + "@vitest/browser": "4.1.2", + "@vitest/coverage-istanbul": "4.1.2", "ckeditor5": "47.6.1", "eslint": "10.1.0", "eslint-config-ckeditor5": ">=9.1.0", "http-server": "14.1.1", "lint-staged": "16.4.0", - "stylelint": "17.5.0", + "stylelint": "17.6.0", "stylelint-config-ckeditor5": ">=9.1.0", "ts-node": "10.9.2", "typescript": "5.9.3", "vite-plugin-svgo": "2.0.0", - "vitest": "4.1.0", - "webdriverio": "9.26.1" + "vitest": "4.1.2", + "webdriverio": "9.27.0" }, "peerDependencies": { "ckeditor5": "47.6.1" diff --git a/packages/ckeditor5-math/package.json b/packages/ckeditor5-math/package.json index 12feaed834..df72ff48e5 100644 --- a/packages/ckeditor5-math/package.json +++ b/packages/ckeditor5-math/package.json @@ -27,22 +27,22 @@ "@ckeditor/ckeditor5-dev-build-tools": "55.2.0", "@ckeditor/ckeditor5-inspector": ">=4.1.0", "@ckeditor/ckeditor5-package-tools": "5.1.0", - "@typescript-eslint/eslint-plugin": "8.57.1", - "@typescript-eslint/parser": "8.57.1", - "@vitest/browser": "4.1.0", - "@vitest/coverage-istanbul": "4.1.0", + "@typescript-eslint/eslint-plugin": "8.57.2", + "@typescript-eslint/parser": "8.57.2", + "@vitest/browser": "4.1.2", + "@vitest/coverage-istanbul": "4.1.2", "ckeditor5": "47.6.1", "eslint": "10.1.0", "eslint-config-ckeditor5": ">=9.1.0", "http-server": "14.1.1", "lint-staged": "16.4.0", - "stylelint": "17.5.0", + "stylelint": "17.6.0", "stylelint-config-ckeditor5": ">=9.1.0", "ts-node": "10.9.2", "typescript": "5.9.3", "vite-plugin-svgo": "2.0.0", - "vitest": "4.1.0", - "webdriverio": "9.26.1" + "vitest": "4.1.2", + "webdriverio": "9.27.0" }, "peerDependencies": { "ckeditor5": "47.6.1" diff --git a/packages/ckeditor5-mermaid/package.json b/packages/ckeditor5-mermaid/package.json index 4af4cc741f..6d638c5a50 100644 --- a/packages/ckeditor5-mermaid/package.json +++ b/packages/ckeditor5-mermaid/package.json @@ -27,22 +27,22 @@ "@ckeditor/ckeditor5-dev-build-tools": "55.2.0", "@ckeditor/ckeditor5-inspector": ">=4.1.0", "@ckeditor/ckeditor5-package-tools": "5.1.0", - "@typescript-eslint/eslint-plugin": "8.57.1", - "@typescript-eslint/parser": "8.57.1", - "@vitest/browser": "4.1.0", - "@vitest/coverage-istanbul": "4.1.0", + "@typescript-eslint/eslint-plugin": "8.57.2", + "@typescript-eslint/parser": "8.57.2", + "@vitest/browser": "4.1.2", + "@vitest/coverage-istanbul": "4.1.2", "ckeditor5": "47.6.1", "eslint": "10.1.0", "eslint-config-ckeditor5": ">=9.1.0", "http-server": "14.1.1", "lint-staged": "16.4.0", - "stylelint": "17.5.0", + "stylelint": "17.6.0", "stylelint-config-ckeditor5": ">=9.1.0", "ts-node": "10.9.2", "typescript": "5.9.3", "vite-plugin-svgo": "2.0.0", - "vitest": "4.1.0", - "webdriverio": "9.26.1" + "vitest": "4.1.2", + "webdriverio": "9.27.0" }, "peerDependencies": { "ckeditor5": "47.6.1" diff --git a/packages/commons/src/lib/server_api.ts b/packages/commons/src/lib/server_api.ts index bf5c173cfe..cb50e57a37 100644 --- a/packages/commons/src/lib/server_api.ts +++ b/packages/commons/src/lib/server_api.ts @@ -313,9 +313,9 @@ export interface DefinitionObject { } /** - * Subset of bootstrap items that are available both in the main client and in the setup page. + * Bootstrap items that the client needs to start up. These are sent by the server in the HTML and made available as `window.glob`. */ -export interface BootstrapCommonItems { +export type BootstrapDefinition = { dbInitialized: boolean; baseApiUrl: string; assetPath: string; @@ -323,24 +323,17 @@ export interface BootstrapCommonItems { themeUseNextAsBase?: "next" | "next-light" | "next-dark"; iconPackCss: string; iconRegistry: IconRegistry; -} - -/** - * Bootstrap items that the client needs to start up. These are sent by the server in the HTML and made available as `window.glob`. - */ -export type BootstrapDefinition = BootstrapCommonItems & ({ - dbInitialized: true; device: "mobile" | "desktop" | "print" | false; - csrfToken: string; + csrfToken?: string; headingStyle: "plain" | "underline" | "markdown"; layoutOrientation: "vertical" | "horizontal"; platform?: typeof process.platform | "web"; isElectron: boolean; - isStandalone?: boolean; + isStandalone: boolean; hasNativeTitleBar: boolean; hasBackgroundEffects: boolean; - maxEntityChangeIdAtLoad: number; - maxEntityChangeSyncIdAtLoad: number; + maxEntityChangeIdAtLoad?: number; + maxEntityChangeSyncIdAtLoad?: number; instanceName: string | null; appCssNoteIds: string[]; isDev: boolean; @@ -351,9 +344,8 @@ export type BootstrapDefinition = BootstrapCommonItems & ({ currentLocale: Locale; isRtl: boolean; TRILIUM_SAFE_MODE: boolean; -} | { - dbInitialized: false; -}); + componentId?: string; +}; /** * Response for /api/setup/status. diff --git a/packages/share-theme/package.json b/packages/share-theme/package.json index b97b7a8f43..95c5cdf2df 100644 --- a/packages/share-theme/package.json +++ b/packages/share-theme/package.json @@ -25,14 +25,14 @@ "license": "Apache-2.0", "dependencies": { "fuse.js": "7.1.0", - "katex": "0.16.40", + "katex": "0.16.43", "mermaid": "11.13.0" }, "devDependencies": { "@digitak/esrun": "3.2.26", "@triliumnext/ckeditor5": "workspace:*", - "@typescript-eslint/eslint-plugin": "8.57.1", - "@typescript-eslint/parser": "8.57.1", + "@typescript-eslint/eslint-plugin": "8.57.2", + "@typescript-eslint/parser": "8.57.2", "dotenv": "17.3.1", "esbuild": "0.27.4", "eslint": "10.1.0", diff --git a/packages/trilium-core/package.json b/packages/trilium-core/package.json index 629b09e34c..2a1133b32e 100644 --- a/packages/trilium-core/package.json +++ b/packages/trilium-core/package.json @@ -9,17 +9,18 @@ "dependencies": { "@braintree/sanitize-url": "7.1.1", "@triliumnext/commons": "workspace:*", + "async-mutex": "0.5.0", "escape-html": "1.0.3", - "i18next": "25.10.3", + "i18next": "25.10.10", "mime-types": "3.0.2", + "node-html-parser": "7.1.0", "sanitize-filename": "1.6.4", "sanitize-html": "2.17.2", - "unescape": "1.0.1", - "async-mutex": "0.5.0" + "unescape": "1.0.1" }, "devDependencies": { "@types/escape-html": "1.0.4", "@types/mime-types": "3.0.1", "@types/sanitize-html": "2.16.1" } -} +} \ No newline at end of file diff --git a/packages/trilium-core/src/index.ts b/packages/trilium-core/src/index.ts index 1138fecc24..df899145e8 100644 --- a/packages/trilium-core/src/index.ts +++ b/packages/trilium-core/src/index.ts @@ -8,7 +8,7 @@ import { initRequest, RequestProvider } from "./services/request"; import { initTranslations, TranslationProvider } from "./services/i18n"; import { initSchema } from "./services/sql_init"; import appInfo from "./services/app_info"; -import PlatformProvider, { initPlatform } from "./services/platform"; +import { type PlatformProvider, initPlatform } from "./services/platform"; export { getLog } from "./services/log"; export type * from "./services/sql/types"; @@ -77,7 +77,9 @@ export type { NotePojo } from "./becca/becca-interface"; export { default as NoteSet } from "./services/search/note_set"; export { default as SearchContext } from "./services/search/search_context"; -export { default as search } from "./services/search/services/search"; +export { default as search, } from "./services/search/services/search"; +export { type default as SearchResult } from "./services/search/search_result"; +export { type SearchParams } from "./services/search/services/types"; export { default as note_service } from "./services/notes"; export type { NoteParams } from "./services/notes"; export * as sanitize from "./services/sanitizer"; @@ -94,6 +96,11 @@ export { default as setup } from "./services/setup"; export { getPlatform, type PlatformProvider } from "./services/platform"; export { t } from "i18next"; export type { RequestProvider, ExecOpts, CookieJar } from "./services/request"; +export type * from "./meta"; +export * as routeHelpers from "./routes/helpers"; + +export * as becca_easy_mocking from "./test/becca_easy_mocking"; +export * as becca_mocking from "./test/becca_mocking"; export async function initializeCore({ dbConfig, executionContext, crypto, translations, messaging, request, schema, extraAppInfo, platform }: { dbConfig: SqlServiceParams, @@ -114,7 +121,7 @@ export async function initializeCore({ dbConfig, executionContext, crypto, trans await initTranslations(translations); initCrypto(crypto); initContext(executionContext); - initSql(new SqlService(dbConfig, getLog()), dbConfig.onDatabaseNotInitialized); + initSql(new SqlService(dbConfig, getLog())); initSchema(schema); Object.assign(appInfo, extraAppInfo); if (messaging) { diff --git a/packages/trilium-core/src/meta.ts b/packages/trilium-core/src/meta.ts new file mode 100644 index 0000000000..44006bf37d --- /dev/null +++ b/packages/trilium-core/src/meta.ts @@ -0,0 +1,49 @@ +import type { AttributeType, NoteType } from "@triliumnext/commons"; + +export type ExportFormat = "html" | "markdown" | "share"; + +export interface AttachmentMeta { + attachmentId?: string; + title: string; + role: string; + mime: string; + position?: number; + dataFileName: string; +} + +export interface AttributeMeta { + noteId?: string; + type: AttributeType; + name: string; + value: string; + isInheritable?: boolean; + position?: number; +} + +export interface NoteMetaFile { + formatVersion: number; + appVersion: string; + files: NoteMeta[]; +} + +export interface NoteMeta { + noteId?: string; + notePath?: string[]; + isClone?: boolean; + title?: string; + notePosition?: number; + prefix?: string | null; + isExpanded?: boolean; + type?: NoteType; + mime?: string; + /** 'html' or 'markdown', applicable to text notes only */ + format?: ExportFormat; + dataFileName?: string; + dirFileName?: string; + /** this file should not be imported (e.g., HTML navigation) */ + noImport?: boolean; + isImportRoot?: boolean; + attributes?: AttributeMeta[]; + attachments?: AttachmentMeta[]; + children?: NoteMeta[]; +} diff --git a/packages/trilium-core/src/migrations/0233__migrate_geo_map_to_collection.spec.ts b/packages/trilium-core/src/migrations/0233__migrate_geo_map_to_collection.spec.ts index 61a89584dc..6dfba236df 100644 --- a/packages/trilium-core/src/migrations/0233__migrate_geo_map_to_collection.spec.ts +++ b/packages/trilium-core/src/migrations/0233__migrate_geo_map_to_collection.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it, beforeEach } from "vitest"; import * as cls from "../services/context.js"; -import { getSql } from "../services/sql/index.js"; +import { getSql, rebuildIntegrationTestDatabase } from "../services/sql/index.js"; import becca from "../becca/becca.js"; import becca_loader from "../becca/becca_loader.js"; import migration from "./0233__migrate_geo_map_to_collection.js"; @@ -23,7 +23,7 @@ describe("Migration 0233: Migrate geoMap to collection", () => { beforeEach(async () => { // Set up a clean in-memory database for each test - sql.rebuildIntegrationTestDatabase(); + rebuildIntegrationTestDatabase(); await new Promise((resolve) => { cls.getContext().init(() => { diff --git a/packages/trilium-core/src/routes/api/attachments.ts b/packages/trilium-core/src/routes/api/attachments.ts index 63f7293871..c74dcce1f0 100644 --- a/packages/trilium-core/src/routes/api/attachments.ts +++ b/packages/trilium-core/src/routes/api/attachments.ts @@ -46,7 +46,7 @@ function saveAttachment(req: Request<{ noteId: string }>) { function uploadAttachment(req: Request<{ noteId: string }>) { const { noteId } = req.params; - const { file } = req; + const { file } = req as any; // TODO: Add support for file upload in type definitions and remove 'as any' cast if (!file) { return { diff --git a/packages/trilium-core/src/routes/api/files.ts b/packages/trilium-core/src/routes/api/files.ts new file mode 100644 index 0000000000..397579217d --- /dev/null +++ b/packages/trilium-core/src/routes/api/files.ts @@ -0,0 +1,26 @@ +import { Request, Response } from "express"; +import becca from "../../becca/becca"; +import { downloadData, downloadNoteInt } from "../helpers"; + +const downloadFile = (req: Request<{ noteId: string }>, res: Response) => downloadNoteInt(req.params.noteId, res, true); +const openFile = (req: Request<{ noteId: string }>, res: Response) => downloadNoteInt(req.params.noteId, res, false); + +const downloadAttachment = (req: Request<{ attachmentId: string }>, res: Response) => downloadAttachmentInt(req.params.attachmentId, res, true); +const openAttachment = (req: Request<{ attachmentId: string }>, res: Response) => downloadAttachmentInt(req.params.attachmentId, res, false); + +function downloadAttachmentInt(attachmentId: string, res: Response, contentDisposition = true) { + const attachment = becca.getAttachment(attachmentId); + + if (!attachment) { + return res.setHeader("Content-Type", "text/plain").status(404).send(`Attachment '${attachmentId}' doesn't exist.`); + } + + return downloadData(attachment, res, contentDisposition); +} + +export default { + openFile, + downloadFile, + openAttachment, + downloadAttachment, +} diff --git a/packages/trilium-core/src/routes/api/image.ts b/packages/trilium-core/src/routes/api/image.ts index d359ec9ae9..2b4e0130b0 100644 --- a/packages/trilium-core/src/routes/api/image.ts +++ b/packages/trilium-core/src/routes/api/image.ts @@ -93,7 +93,7 @@ function returnAttachedImage(req: Request<{ attachmentId: string }>, res: Respon function updateImage(req: Request<{ noteId: string }>) { const { noteId } = req.params; - const { file } = req; + const { file } = req as any; // TODO: Add support for file upload in type definitions and remove 'as any' cast const _note = becca.getNoteOrThrow(noteId); diff --git a/apps/server/src/routes/api/note_map.spec.ts b/packages/trilium-core/src/routes/api/note_map.spec.ts similarity index 100% rename from apps/server/src/routes/api/note_map.spec.ts rename to packages/trilium-core/src/routes/api/note_map.spec.ts diff --git a/packages/trilium-core/src/routes/api/note_map.ts b/packages/trilium-core/src/routes/api/note_map.ts index d42e989588..82ce821ed9 100644 --- a/packages/trilium-core/src/routes/api/note_map.ts +++ b/packages/trilium-core/src/routes/api/note_map.ts @@ -1,8 +1,342 @@ -import type { Request } from "express"; import BAttribute from "../../becca/entities/battribute"; import BNote from "../../becca/entities/bnote"; import becca from "../../becca/becca"; -import type { BacklinkCountResponse } from "@triliumnext/commons"; +import type { BacklinkCountResponse, BacklinksResponse } from "@triliumnext/commons"; +import type { Request } from "express"; +import { HTMLElement, parse, TextNode } from "node-html-parser"; + +interface TreeLink { + sourceNoteId: string; + targetNoteId: string; +} + +function buildDescendantCountMap(noteIdsToCount: string[]) { + if (!Array.isArray(noteIdsToCount)) { + throw new Error("noteIdsToCount: type error"); + } + + const noteIdToCountMap: Record = Object.create(null); + + function getCount(noteId: string): number { + if (!(noteId in noteIdToCountMap)) { + const note = becca.getNote(noteId); + if (!note) { + return 0; + } + + const hiddenImageNoteIds = note.getRelations("imageLink").map((rel) => rel.value); + const childNoteIds = note.children.map((child) => child.noteId); + const nonHiddenNoteIds = childNoteIds.filter((childNoteId) => !hiddenImageNoteIds.includes(childNoteId)); + + noteIdToCountMap[noteId] = nonHiddenNoteIds.length; + + for (const child of note.children) { + noteIdToCountMap[noteId] += getCount(child.noteId); + } + } + + return noteIdToCountMap[noteId]; + } + noteIdsToCount.forEach((noteId) => { + getCount(noteId); + }); + + return noteIdToCountMap; +} +function getNeighbors(note: BNote, depth: number): string[] { + if (depth === 0) { + return []; + } + + const retNoteIds: string[] = []; + + function isIgnoredRelation(relation: BAttribute) { + return ["relationMapLink", "template", "inherit", "image", "ancestor"].includes(relation.name); + } + + // forward links + for (const relation of note.getRelations()) { + if (isIgnoredRelation(relation)) { + continue; + } + + const targetNote = relation.getTargetNote(); + + if (!targetNote || targetNote.isLabelTruthy("excludeFromNoteMap")) { + continue; + } + + retNoteIds.push(targetNote.noteId); + + for (const noteId of getNeighbors(targetNote, depth - 1)) { + retNoteIds.push(noteId); + } + } + + // backward links + for (const relation of note.getTargetRelations()) { + if (isIgnoredRelation(relation)) { + continue; + } + + const sourceNote = relation.getNote(); + + if (!sourceNote || sourceNote.isLabelTruthy("excludeFromNoteMap")) { + continue; + } + + retNoteIds.push(sourceNote.noteId); + + for (const noteId of getNeighbors(sourceNote, depth - 1)) { + retNoteIds.push(noteId); + } + } + + return retNoteIds; +} + +function getLinkMap(req: Request<{ noteId: string }>) { + const mapRootNote = becca.getNoteOrThrow(req.params.noteId); + + // if the map root itself has "excludeFromNoteMap" attribute (journal typically) then there wouldn't be anything + // to display, so we'll just ignore it + const ignoreExcludeFromNoteMap = mapRootNote.isLabelTruthy("excludeFromNoteMap"); + let unfilteredNotes; + + const toSet = (data: unknown) => new Set(data instanceof Array ? data : []); + + const excludeRelations = toSet(req.body.excludeRelations); + const includeRelations = toSet(req.body.includeRelations); + + if (mapRootNote.type === "search") { + // for search notes, we want to consider the direct search results only without the descendants + unfilteredNotes = mapRootNote.getSearchResultNotes(); + } else { + unfilteredNotes = mapRootNote.getSubtree({ + includeArchived: false, + resolveSearch: true, + includeHidden: mapRootNote.isInHiddenSubtree() + }).notes; + } + + const noteIds = new Set(unfilteredNotes.filter((note) => ignoreExcludeFromNoteMap || !note.isLabelTruthy("excludeFromNoteMap")).map((note) => note.noteId)); + + if (mapRootNote.type === "search") { + noteIds.delete(mapRootNote.noteId); + } + + for (const noteId of getNeighbors(mapRootNote, 3)) { + noteIds.add(noteId); + } + + const noteIdsArray = Array.from(noteIds); + + const notes = noteIdsArray.map((noteId) => { + const note = becca.getNoteOrThrow(noteId); + + return [note.noteId, note.getTitleOrProtected(), note.type, note.getLabelValue("color")]; + }); + + const links = Object.values(becca.attributes) + .filter((rel) => { + if (rel.type !== "relation" || rel.name === "relationMapLink" || rel.name === "template" || rel.name === "inherit") { + return false; + } else if (!noteIds.has(rel.noteId) || !noteIds.has(rel.value)) { + return false; + } else if (rel.name === "imageLink") { + const parentNote = becca.getNote(rel.noteId); + if (!parentNote) { + return false; + } + + return !parentNote.getChildNotes().find((childNote) => childNote.noteId === rel.value); + } else if (includeRelations.size != 0 && !includeRelations.has(rel.name)) { + return false; + } else if (excludeRelations.has(rel.name)) { + return false; + } + return true; + + }) + .map((rel) => ({ + id: `${rel.noteId}-${rel.name}-${rel.value}`, + sourceNoteId: rel.noteId, + targetNoteId: rel.value, + name: rel.name + })); + + return { + notes, + noteIdToDescendantCountMap: buildDescendantCountMap(noteIdsArray), + links + }; +} + +function getTreeMap(req: Request<{ noteId: string }>) { + const mapRootNote = becca.getNoteOrThrow(req.params.noteId); + // if the map root itself has "excludeFromNoteMap" (journal typically) then there wouldn't be anything to display, + // so we'll just ignore it + const ignoreExcludeFromNoteMap = mapRootNote.isLabelTruthy("excludeFromNoteMap"); + const subtree = mapRootNote.getSubtree({ + includeArchived: false, + resolveSearch: true, + includeHidden: mapRootNote.isInHiddenSubtree() + }); + + const notes = subtree.notes + .filter((note) => ignoreExcludeFromNoteMap || !note.isLabelTruthy("excludeFromNoteMap")) + .filter((note) => { + if (note.type !== "image" || note.getChildNotes().length > 0) { + return true; + } + + const imageLinkRelation = note.getTargetRelations().find((rel) => rel.name === "imageLink"); + + if (!imageLinkRelation) { + return true; + } + + return !note.getParentNotes().find((parentNote) => parentNote.noteId === imageLinkRelation.noteId); + }) + .map((note) => [note.noteId, note.getTitleOrProtected(), note.type, note.getLabelValue("color")]); + + const noteIds = new Set(); + notes.forEach(([noteId]) => noteId && noteIds.add(noteId)); + + const links: TreeLink[] = []; + + for (const { parentNoteId, childNoteId } of subtree.relationships) { + if (!noteIds.has(parentNoteId) || !noteIds.has(childNoteId)) { + continue; + } + + links.push({ + sourceNoteId: parentNoteId, + targetNoteId: childNoteId + }); + } + + const noteIdToDescendantCountMap = buildDescendantCountMap(Array.from(noteIds)); + + updateDescendantCountMapForSearch(noteIdToDescendantCountMap, subtree.relationships); + + return { + notes, + noteIdToDescendantCountMap, + links + }; +} + +function updateDescendantCountMapForSearch(noteIdToDescendantCountMap: Record, relationships: { parentNoteId: string; childNoteId: string }[]) { + for (const { parentNoteId, childNoteId } of relationships) { + const parentNote = becca.notes[parentNoteId]; + if (!parentNote || parentNote.type !== "search") { + continue; + } + + noteIdToDescendantCountMap[parentNote.noteId] = noteIdToDescendantCountMap[parentNoteId] || 0; + noteIdToDescendantCountMap[parentNote.noteId] += noteIdToDescendantCountMap[childNoteId] || 1; + } +} + +function removeImages(document: HTMLElement) { + const images = document.getElementsByTagName("img"); + for (const image of images) { + image.remove(); + } +} + +const EXCERPT_CHAR_LIMIT = 200; +type ElementOrText = HTMLElement | TextNode; + +export function findExcerpts(sourceNote: BNote, referencedNoteId: string) { + const html = sourceNote.getContent(); + const document = parse(html.toString()); + + const excerpts: string[] = []; + + removeImages(document); + + for (const linkEl of document.querySelectorAll("a")) { + const href = linkEl.getAttribute("href"); + + if (!href || !href.endsWith(referencedNoteId)) { + continue; + } + + linkEl.classList.add("backlink-link"); + + let centerEl: HTMLElement = linkEl; + + while (centerEl.tagName !== "BODY" && centerEl.parentNode && (centerEl.parentNode?.textContent?.length || 0) <= EXCERPT_CHAR_LIMIT) { + centerEl = centerEl.parentNode; + } + + const excerptEls: ElementOrText[] = [centerEl]; + let excerptLength = centerEl.textContent?.length || 0; + let left: ElementOrText = centerEl; + let right: ElementOrText = centerEl; + + while (excerptLength < EXCERPT_CHAR_LIMIT) { + let added = false; + + const prev: HTMLElement | null = left.previousElementSibling; + + if (prev) { + const prevText = prev.textContent || ""; + + if (prevText.length + excerptLength > EXCERPT_CHAR_LIMIT) { + const prefix = prevText.substr(prevText.length - (EXCERPT_CHAR_LIMIT - excerptLength)); + + const textNode = new TextNode(`…${prefix}`); + excerptEls.unshift(textNode); + + break; + } + + left = prev; + excerptEls.unshift(left); + excerptLength += prevText.length; + added = true; + } + + const next: HTMLElement | null = right.nextElementSibling; + + if (next) { + const nextText = next.textContent; + + if (nextText && nextText.length + excerptLength > EXCERPT_CHAR_LIMIT) { + const suffix = nextText.substr(nextText.length - (EXCERPT_CHAR_LIMIT - excerptLength)); + + const textNode = new TextNode(`${suffix}…`); + excerptEls.push(textNode); + + break; + } + + right = next; + excerptEls.push(right); + excerptLength += nextText?.length || 0; + added = true; + } + + if (!added) { + break; + } + } + + const excerptWrapper = new HTMLElement("div", {}); + excerptWrapper.classList.add("ck-content"); + excerptWrapper.classList.add("backlink-excerpt"); + + for (const childEl of excerptEls) { + excerptWrapper.appendChild(childEl); + } + + excerpts.push(excerptWrapper.outerHTML); + } + return excerpts; +} function getFilteredBacklinks(note: BNote): BAttribute[] { return ( @@ -13,7 +347,33 @@ function getFilteredBacklinks(note: BNote): BAttribute[] { ); } -function getBacklinkCount(req: Request) { +function getBacklinks(req: Request<{ noteId: string }>): BacklinksResponse { + const { noteId } = req.params; + const note = becca.getNoteOrThrow(noteId); + + let backlinksWithExcerptCount = 0; + + return getFilteredBacklinks(note).map((backlink) => { + const sourceNote = backlink.note; + + if (sourceNote.type !== "text" || backlinksWithExcerptCount > 50) { + return { + noteId: sourceNote.noteId, + relationName: backlink.name + } satisfies BacklinksResponse[number]; + } + + backlinksWithExcerptCount++; + + const excerpts = findExcerpts(sourceNote, noteId); + return { + noteId: sourceNote.noteId, + excerpts + } satisfies BacklinksResponse[number]; + }); +} + +function getBacklinkCount(req: Request<{ noteId: string }>): BacklinkCountResponse { const { noteId } = req.params; const note = becca.getNoteOrThrow(noteId); @@ -24,5 +384,8 @@ function getBacklinkCount(req: Request) { } export default { + getLinkMap, + getTreeMap, + getBacklinks, getBacklinkCount -} +}; diff --git a/packages/trilium-core/src/routes/api/options.ts b/packages/trilium-core/src/routes/api/options.ts index 071f4b4b00..a73deffb2d 100644 --- a/packages/trilium-core/src/routes/api/options.ts +++ b/packages/trilium-core/src/routes/api/options.ts @@ -108,7 +108,6 @@ const ALLOWED_OPTIONS = new Set([ ]); function getOptions() { - console.log("Got opts"); const optionMap = optionService.getOptionMap(); const resultMap: Record = {}; diff --git a/packages/trilium-core/src/routes/helpers.ts b/packages/trilium-core/src/routes/helpers.ts new file mode 100644 index 0000000000..d12344c097 --- /dev/null +++ b/packages/trilium-core/src/routes/helpers.ts @@ -0,0 +1,33 @@ +import { Response } from "express"; +import becca from "../becca/becca"; +import BNote from "../becca/entities/bnote"; +import protected_session from "../services/protected_session"; +import BAttachment from "../becca/entities/battachment"; +import { getContentDisposition } from "../services/utils/index"; + +export function downloadNoteInt(noteId: string, res: Response, contentDisposition = true) { + const note = becca.getNote(noteId); + + if (!note) { + return res.setHeader("Content-Type", "text/plain").status(404).send(`Note '${noteId}' doesn't exist.`); + } + + return downloadData(note, res, contentDisposition); +} + +export function downloadData(noteOrAttachment: BNote | BAttachment, res: Response, contentDisposition: boolean) { + if (noteOrAttachment.isProtected && !protected_session.isProtectedSessionAvailable()) { + return res.status(401).send("Protected session not available"); + } + + if (contentDisposition) { + const fileName = noteOrAttachment.getFileName(); + + res.setHeader("Content-Disposition", getContentDisposition(fileName)); + } + + res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + res.setHeader("Content-Type", noteOrAttachment.mime); + + res.send(noteOrAttachment.getContent()); +} diff --git a/packages/trilium-core/src/routes/index.ts b/packages/trilium-core/src/routes/index.ts index ed088aa1a6..7f2b526f92 100644 --- a/packages/trilium-core/src/routes/index.ts +++ b/packages/trilium-core/src/routes/index.ts @@ -24,6 +24,7 @@ import autocompleteApiRoute from "./api/autocomplete"; import similarNotesRoute from "./api/similar_notes"; import imageRoute from "./api/image"; import setupApiRoute from "./api/setup"; +import filesRoute from "./api/files"; // TODO: Deduplicate with routes.ts const GET = "get", @@ -166,6 +167,9 @@ export function buildSharedApiRoutes({ route, asyncRoute, apiRoute, asyncApiRout apiRoute(PST, "/api/special-notes/launchers/:parentNoteId/:launcherType", specialNotesRoute.createLauncher); apiRoute(PUT, "/api/special-notes/api-script-launcher", specialNotesRoute.createOrUpdateScriptLauncherFromApi); + apiRoute(PST, "/api/note-map/:noteId/tree", noteMapRoute.getTreeMap); + apiRoute(PST, "/api/note-map/:noteId/link", noteMapRoute.getLinkMap); + apiRoute(GET, "/api/note-map/:noteId/backlinks", noteMapRoute.getBacklinks); apiRoute(GET, "/api/note-map/:noteId/backlink-count", noteMapRoute.getBacklinkCount); apiRoute(PST, "/api/recent-notes", recentNotesRoute.addRecentNote); @@ -187,6 +191,17 @@ export function buildSharedApiRoutes({ route, asyncRoute, apiRoute, asyncApiRout asyncApiRoute(GET, "/api/similar-notes/:noteId", similarNotesRoute.getSimilarNotes); apiRoute(PST, "/api/relation-map", relationMapApiRoute.getRelationMap); apiRoute(GET, "/api/recent-changes/:ancestorNoteId", recentChangesApiRoute.getRecentChanges); + + //#region Files + route(GET, "/api/notes/:noteId/open", [checkApiAuthOrElectron], filesRoute.openFile); + route(GET, "/api/notes/:noteId/download", [checkApiAuthOrElectron], filesRoute.downloadFile); + // this "hacky" path is used for easier referencing of CSS resources + route(GET, "/api/notes/download/:noteId", [checkApiAuthOrElectron], filesRoute.downloadFile); + route(GET, "/api/attachments/:attachmentId/open", [checkApiAuthOrElectron], filesRoute.openAttachment); + route(GET, "/api/attachments/:attachmentId/download", [checkApiAuthOrElectron], filesRoute.downloadAttachment); + // this "hacky" path is used for easier referencing of CSS resources + route(GET, "/api/attachments/download/:attachmentId", [checkApiAuthOrElectron], filesRoute.downloadAttachment); + //#endregion } /** Handling common patterns. If entity is not caught, serialization to JSON will fail */ diff --git a/packages/trilium-core/src/services/backup.ts b/packages/trilium-core/src/services/backup.ts index b596ccabe5..e229020f9d 100644 --- a/packages/trilium-core/src/services/backup.ts +++ b/packages/trilium-core/src/services/backup.ts @@ -1,5 +1,5 @@ export default { - backupNow() { + backupNow(name: string) { console.warn("Backup not yet available."); } } diff --git a/packages/trilium-core/src/services/bootstrap_utils.ts b/packages/trilium-core/src/services/bootstrap_utils.ts index c541407f4a..9d857145f1 100644 --- a/packages/trilium-core/src/services/bootstrap_utils.ts +++ b/packages/trilium-core/src/services/bootstrap_utils.ts @@ -2,8 +2,11 @@ import { BootstrapDefinition } from "@triliumnext/commons"; import { getSql } from "./sql"; import protected_session from "./protected_session"; import { generateCss, generateIconRegistry, getIconPacks, MIME_TO_EXTENSION_MAPPINGS } from "./icon_packs"; -import options from "./options"; +import optionService from "./options"; import { getCurrentLocale } from "./i18n"; +import attributes from "./attributes"; +import BNote from "../becca/entities/bnote"; +import { getPlatform } from "./platform"; export default function getSharedBootstrapItems(assetPath: string, dbInitialized: boolean) { const sql = getSql(); @@ -12,26 +15,39 @@ export default function getSharedBootstrapItems(assetPath: string, dbInitialized const commonItems = { assetPath, dbInitialized, + currentLocale, + isRtl: !!currentLocale.rtl, + isProtectedSessionAvailable: false, + layoutOrientation: "vertical" as const, + headingStyle: "plain" as const, + componentId: "", ...getIconConfig(assetPath) }; + // Setup not yet finished. if (!dbInitialized) { return { ...commonItems, - themeCssUrl: false, - themeUseNextAsBase: "next" + themeCssUrl: false as const, + themeUseNextAsBase: "next" as const, + appCssNoteIds: [] }; } + // Database initialized. + const options = optionService.getOptionMap(); + const theme = options.theme; + const themeNote = attributes.getNoteWithLabel("appTheme", theme); return { ...commonItems, - headingStyle: options.getOption("headingStyle") as "plain" | "underline" | "markdown", - layoutOrientation: options.getOption("layoutOrientation") as "vertical" | "horizontal", - maxEntityChangeIdAtLoad: sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes"), - maxEntityChangeSyncIdAtLoad: sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes WHERE isSynced = 1"), + headingStyle: options.headingStyle as "plain" | "underline" | "markdown", + layoutOrientation: options.layoutOrientation as "vertical" | "horizontal", + maxEntityChangeIdAtLoad: sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes"), + maxEntityChangeSyncIdAtLoad: sql.getValue("SELECT COALESCE(MAX(id), 0) FROM entity_changes WHERE isSynced = 1"), isProtectedSessionAvailable: protected_session.isProtectedSessionAvailable(), - currentLocale, - isRtl: !!currentLocale.rtl, + themeCssUrl: getThemeCssUrl(theme, commonItems.assetPath, themeNote) as string | false, + themeUseNextAsBase: themeNote?.getAttributeValue("label", "appThemeBase") as "next" | "next-light" | "next-dark", + appCssNoteIds: getAppCssNoteIds(), } } @@ -48,3 +64,28 @@ export function getIconConfig(assetPath: string): Pick note.noteId); +} + +function getThemeCssUrl(theme: string, assetPath: string, themeNote: BNote | null) { + if (theme === "auto") { + return `${assetPath}/stylesheets/theme.css`; + } else if (theme === "light") { + // light theme is always loaded as baseline + return false; + } else if (theme === "dark") { + return `${assetPath}/stylesheets/theme-dark.css`; + } else if (theme === "next") { + return `${assetPath}/stylesheets/theme-next.css`; + } else if (theme === "next-light") { + return `${assetPath}/stylesheets/theme-next-light.css`; + } else if (theme === "next-dark") { + return `${assetPath}/stylesheets/theme-next-dark.css`; + } else if (!getPlatform().getEnv("TRILIUM_SAFE_MODE") && themeNote) { + return `api/notes/download/${themeNote.noteId}`; + } + // baseline light theme + return false; +} diff --git a/packages/trilium-core/src/services/config.ts b/packages/trilium-core/src/services/config.ts index 22ea820dbf..3a383a00b0 100644 --- a/packages/trilium-core/src/services/config.ts +++ b/packages/trilium-core/src/services/config.ts @@ -2,5 +2,10 @@ export default { General: { readOnly: false + }, + Sync: { + syncServerHost: "", + syncServerTimeout: "120000", + syncProxy: "" } }; diff --git a/packages/trilium-core/src/services/image.ts b/packages/trilium-core/src/services/image.ts index e140ca0507..605c3cec21 100644 --- a/packages/trilium-core/src/services/image.ts +++ b/packages/trilium-core/src/services/image.ts @@ -1,5 +1,14 @@ export default { saveImageToAttachment(noteId: string, imageBuffer: Uint8Array, title: string, b1: boolean, b2: boolean) { console.warn("Image save ignored", noteId, title); + + return { + attachmentId: null, + title: "" + }; + }, + + updateImage(noteId: string, imageBuffer: Uint8Array, title: string) { + console.warn("Image update ignored", noteId, title); } } diff --git a/packages/trilium-core/src/services/keyboard_actions.ts b/packages/trilium-core/src/services/keyboard_actions.ts index 704a639adb..268c0487da 100644 --- a/packages/trilium-core/src/services/keyboard_actions.ts +++ b/packages/trilium-core/src/services/keyboard_actions.ts @@ -20,7 +20,7 @@ function getDefaultKeyboardActions() { friendlyName: t("keyboard_action_names.back-in-note-history"), iconClass: "bx bxs-chevron-left", // Mac has a different history navigation shortcuts - https://github.com/zadam/trilium/issues/376 - defaultShortcuts: isMac ? ["CommandOrControl+["] : ["Alt+Left"], + defaultShortcuts: isMac() ? ["CommandOrControl+["] : ["Alt+Left"], description: t("keyboard_actions.back-in-note-history"), scope: "window" }, @@ -29,7 +29,7 @@ function getDefaultKeyboardActions() { friendlyName: t("keyboard_action_names.forward-in-note-history"), iconClass: "bx bxs-chevron-right", // Mac has a different history navigation shortcuts - https://github.com/zadam/trilium/issues/376 - defaultShortcuts: isMac ? ["CommandOrControl+]"] : ["Alt+Right"], + defaultShortcuts: isMac() ? ["CommandOrControl+]"] : ["Alt+Right"], description: t("keyboard_actions.forward-in-note-history"), scope: "window" }, @@ -153,7 +153,7 @@ function getDefaultKeyboardActions() { actionName: "moveNoteUp", friendlyName: t("keyboard_action_names.move-note-up"), iconClass: "bx bx-up-arrow-alt", - defaultShortcuts: isMac ? ["Alt+Up"] : ["CommandOrControl+Up"], + defaultShortcuts: isMac() ? ["Alt+Up"] : ["CommandOrControl+Up"], description: t("keyboard_actions.move-note-up"), scope: "note-tree" }, @@ -161,7 +161,7 @@ function getDefaultKeyboardActions() { actionName: "moveNoteDown", friendlyName: t("keyboard_action_names.move-note-down"), iconClass: "bx bx-down-arrow-alt", - defaultShortcuts: isMac ? ["Alt+Down"] : ["CommandOrControl+Down"], + defaultShortcuts: isMac() ? ["Alt+Down"] : ["CommandOrControl+Down"], description: t("keyboard_actions.move-note-down"), scope: "note-tree" }, @@ -169,7 +169,7 @@ function getDefaultKeyboardActions() { actionName: "moveNoteUpInHierarchy", friendlyName: t("keyboard_action_names.move-note-up-in-hierarchy"), iconClass: "bx bx-arrow-from-bottom", - defaultShortcuts: isMac ? ["Alt+Left"] : ["CommandOrControl+Left"], + defaultShortcuts: isMac() ? ["Alt+Left"] : ["CommandOrControl+Left"], description: t("keyboard_actions.move-note-up-in-hierarchy"), scope: "note-tree" }, @@ -177,7 +177,7 @@ function getDefaultKeyboardActions() { actionName: "moveNoteDownInHierarchy", friendlyName: t("keyboard_action_names.move-note-down-in-hierarchy"), iconClass: "bx bx-arrow-from-top", - defaultShortcuts: isMac ? ["Alt+Right"] : ["CommandOrControl+Right"], + defaultShortcuts: isMac() ? ["Alt+Right"] : ["CommandOrControl+Right"], description: t("keyboard_actions.move-note-down-in-hierarchy"), scope: "note-tree" }, @@ -282,7 +282,7 @@ function getDefaultKeyboardActions() { actionName: "openNewTab", friendlyName: t("keyboard_action_names.open-new-tab"), iconClass: "bx bx-plus", - defaultShortcuts: isElectron ? ["CommandOrControl+T"] : [], + defaultShortcuts: isElectron() ? ["CommandOrControl+T"] : [], description: t("keyboard_actions.open-new-tab"), scope: "window" }, @@ -290,7 +290,7 @@ function getDefaultKeyboardActions() { actionName: "closeActiveTab", friendlyName: t("keyboard_action_names.close-active-tab"), iconClass: "bx bx-minus", - defaultShortcuts: isElectron ? ["CommandOrControl+W"] : [], + defaultShortcuts: isElectron() ? ["CommandOrControl+W"] : [], description: t("keyboard_actions.close-active-tab"), scope: "window" }, @@ -298,7 +298,7 @@ function getDefaultKeyboardActions() { actionName: "reopenLastTab", friendlyName: t("keyboard_action_names.reopen-last-tab"), iconClass: "bx bx-undo", - defaultShortcuts: isElectron ? ["CommandOrControl+Shift+T"] : [], + defaultShortcuts: isElectron() ? ["CommandOrControl+Shift+T"] : [], isElectronOnly: true, description: t("keyboard_actions.reopen-last-tab"), scope: "window" @@ -307,7 +307,7 @@ function getDefaultKeyboardActions() { actionName: "activateNextTab", friendlyName: t("keyboard_action_names.activate-next-tab"), iconClass: "bx bx-skip-next", - defaultShortcuts: isElectron ? ["CommandOrControl+Tab", "CommandOrControl+PageDown"] : [], + defaultShortcuts: isElectron() ? ["CommandOrControl+Tab", "CommandOrControl+PageDown"] : [], description: t("keyboard_actions.activate-next-tab"), scope: "window" }, @@ -315,7 +315,7 @@ function getDefaultKeyboardActions() { actionName: "activatePreviousTab", friendlyName: t("keyboard_action_names.activate-previous-tab"), iconClass: "bx bx-skip-previous", - defaultShortcuts: isElectron ? ["CommandOrControl+Shift+Tab", "CommandOrControl+PageUp"] : [], + defaultShortcuts: isElectron() ? ["CommandOrControl+Shift+Tab", "CommandOrControl+PageUp"] : [], description: t("keyboard_actions.activate-previous-tab"), scope: "window" }, @@ -757,7 +757,7 @@ function getDefaultKeyboardActions() { actionName: "openDevTools", friendlyName: t("keyboard_action_names.open-developer-tools"), iconClass: "bx bx-bug-alt", - defaultShortcuts: isElectron ? ["CommandOrControl+Shift+I"] : [], + defaultShortcuts: isElectron() ? ["CommandOrControl+Shift+I"] : [], isElectronOnly: true, description: t("keyboard_actions.open-dev-tools"), scope: "window" @@ -766,7 +766,7 @@ function getDefaultKeyboardActions() { actionName: "findInText", friendlyName: t("keyboard_action_names.find-in-text"), iconClass: "bx bx-search", - defaultShortcuts: isElectron ? ["CommandOrControl+F"] : [], + defaultShortcuts: isElectron() ? ["CommandOrControl+F"] : [], description: t("keyboard_actions.find-in-text"), scope: "window" }, @@ -790,7 +790,7 @@ function getDefaultKeyboardActions() { actionName: "zoomOut", friendlyName: t("keyboard_action_names.zoom-out"), iconClass: "bx bx-zoom-out", - defaultShortcuts: isElectron ? ["CommandOrControl+-"] : [], + defaultShortcuts: isElectron() ? ["CommandOrControl+-"] : [], isElectronOnly: true, description: t("keyboard_actions.zoom-out"), scope: "window" @@ -800,7 +800,7 @@ function getDefaultKeyboardActions() { friendlyName: t("keyboard_action_names.zoom-in"), iconClass: "bx bx-zoom-in", description: t("keyboard_actions.zoom-in"), - defaultShortcuts: isElectron ? ["CommandOrControl+="] : [], + defaultShortcuts: isElectron() ? ["CommandOrControl+="] : [], isElectronOnly: true, scope: "window" }, @@ -809,7 +809,7 @@ function getDefaultKeyboardActions() { friendlyName: t("keyboard_action_names.reset-zoom-level"), iconClass: "bx bx-search-alt", description: t("keyboard_actions.reset-zoom-level"), - defaultShortcuts: isElectron ? ["CommandOrControl+0"] : [], + defaultShortcuts: isElectron() ? ["CommandOrControl+0"] : [], isElectronOnly: true, scope: "window" }, @@ -834,7 +834,7 @@ function getDefaultKeyboardActions() { /* * Apply macOS-specific tweaks. */ - const platformModifier = isMac ? "Meta" : "Ctrl"; + const platformModifier = isMac() ? "Meta" : "Ctrl"; for (const action of DEFAULT_KEYBOARD_ACTIONS) { if ("defaultShortcuts" in action && action.defaultShortcuts) { diff --git a/packages/trilium-core/src/services/log.ts b/packages/trilium-core/src/services/log.ts index ab3b262d73..e3cc68af2e 100644 --- a/packages/trilium-core/src/services/log.ts +++ b/packages/trilium-core/src/services/log.ts @@ -12,7 +12,8 @@ export default class LogService { console.error("ERROR: ", message); } - banner(message: string) { + banner(message: string | undefined) { + if (!message) return; const maxContent = 76; // 80 - 4 (border + padding) const words = message.split(" "); const lines: string[] = []; diff --git a/packages/trilium-core/src/services/migration.spec.ts b/packages/trilium-core/src/services/migration.spec.ts index f47803ac4f..c88bbfb824 100644 --- a/packages/trilium-core/src/services/migration.spec.ts +++ b/packages/trilium-core/src/services/migration.spec.ts @@ -1,18 +1,16 @@ import { describe, expect, it } from "vitest"; -import cls from "./cls.js"; +import { getContext } from "./context.js"; describe("Migration", () => { it("migrates from v214", async () => { await new Promise((resolve) => { - cls.init(async () => { - await import("../app.js"); - - const sql = (await (import("./sql.js"))).default; - sql.rebuildIntegrationTestDatabase("spec/db/document_v214.db"); + getContext().init(async () => { + const { getSql, rebuildIntegrationTestDatabase } = (await (import("./sql/index.js"))); + rebuildIntegrationTestDatabase("spec/db/document_v214.db"); const migration = (await import("./migration.js")).default; await migration.migrateIfNecessary(); - expect(sql.getValue("SELECT count(*) FROM blobs")).toBe(118); + expect(getSql().getValue("SELECT count(*) FROM blobs")).toBe(118); resolve(); }); }); diff --git a/packages/trilium-core/src/services/migration.ts b/packages/trilium-core/src/services/migration.ts index 34b9bc94a6..9ab5cf0cd6 100644 --- a/packages/trilium-core/src/services/migration.ts +++ b/packages/trilium-core/src/services/migration.ts @@ -24,7 +24,7 @@ async function migrate() { } // backup before attempting migration - if (!process.env.TRILIUM_INTEGRATION_TEST) { + if (!getPlatform().getEnv("TRILIUM_INTEGRATION_TEST")) { await backupService.backupNow( // creating a special backup for version 0.60.4, the changes in 0.61 are major. currentDbVersion === 214 ? `before-migration-v060` : "before-migration" @@ -123,7 +123,7 @@ function isDbUpToDate() { async function migrateIfNecessary() { const currentDbVersion = getDbVersion(); - if (currentDbVersion > appInfo.dbVersion && process.env.TRILIUM_IGNORE_DB_VERSION !== "true") { + if (currentDbVersion > appInfo.dbVersion && getPlatform().getEnv("TRILIUM_IGNORE_DB_VERSION") !== "true") { getPlatform().crash(t("migration.wrong_db_version", { version: currentDbVersion, targetVersion: appInfo.dbVersion })); } diff --git a/packages/trilium-core/src/services/notes.ts b/packages/trilium-core/src/services/notes.ts index b60e2ea86a..69f7601351 100644 --- a/packages/trilium-core/src/services/notes.ts +++ b/packages/trilium-core/src/services/notes.ts @@ -548,7 +548,7 @@ async function downloadImage(noteId: string, imageUrl: string) { }); }); } else { - imageBuffer = await request.getImage(unescapedUrl); + imageBuffer = new Uint8Array(await request.getImage(unescapedUrl)); } const parsedUrl = url.parse(unescapedUrl); diff --git a/packages/trilium-core/src/services/options_init.ts b/packages/trilium-core/src/services/options_init.ts index a1fe29b46b..730b9791a7 100644 --- a/packages/trilium-core/src/services/options_init.ts +++ b/packages/trilium-core/src/services/options_init.ts @@ -1,6 +1,7 @@ import { type KeyboardShortcutWithRequiredActionName, type OptionMap, type OptionNames, SANITIZER_DEFAULT_ALLOWED_TAGS } from "@triliumnext/commons"; import appInfo from "./app_info.js"; +import { getPlatform } from "./platform.js"; import dateUtils from "./utils/date.js"; import keyboardActions from "./keyboard_actions.js"; import { getLog } from "./log.js"; @@ -79,7 +80,7 @@ const defaultOptions: DefaultOption[] = [ { name: "revisionSnapshotNumberLimit", value: "-1", isSynced: true }, { name: "protectedSessionTimeout", value: "600", isSynced: true }, { name: "protectedSessionTimeoutTimeScale", value: "60", isSynced: true }, - { name: "zoomFactor", value: isWindows ? "0.9" : "1.0", isSynced: false }, + { name: "zoomFactor", value: () => isWindows() ? "0.9" : "1.0", isSynced: false }, { name: "overrideThemeFonts", value: "false", isSynced: false }, { name: "mainFontFamily", value: "theme", isSynced: false }, { name: "mainFontSize", value: "100", isSynced: false }, @@ -237,12 +238,12 @@ export function initStartupOptions() { } } - if (process.env.TRILIUM_START_NOTE_ID || process.env.TRILIUM_SAFE_MODE) { + if (getPlatform().getEnv("TRILIUM_START_NOTE_ID") || getPlatform().getEnv("TRILIUM_SAFE_MODE")) { optionService.setOption( "openNoteContexts", JSON.stringify([ { - notePath: process.env.TRILIUM_START_NOTE_ID || "root", + notePath: getPlatform().getEnv("TRILIUM_START_NOTE_ID") || "root", active: true } ]) diff --git a/packages/trilium-core/src/services/platform.ts b/packages/trilium-core/src/services/platform.ts index 8c5d9e0929..3a7fcfc6b8 100644 --- a/packages/trilium-core/src/services/platform.ts +++ b/packages/trilium-core/src/services/platform.ts @@ -3,6 +3,11 @@ */ export interface PlatformProvider { crash(message: string): void; + /** Returns the value of an environment variable, or undefined if not set. */ + getEnv(key: string): string | undefined; + readonly isElectron: boolean; + readonly isMac: boolean; + readonly isWindows: boolean; } let platformProvider: PlatformProvider | null = null; diff --git a/packages/trilium-core/src/services/script.ts b/packages/trilium-core/src/services/script.ts index 83dc0272b6..a6a86754f6 100644 --- a/packages/trilium-core/src/services/script.ts +++ b/packages/trilium-core/src/services/script.ts @@ -1,6 +1,6 @@ import type BNote from "../becca/entities/bnote"; -export function executeNoteNoException(script: unknown) { +export function executeNoteNoException(script: unknown, { originEntity: unknown }) { console.warn("Skipped script execution"); } diff --git a/packages/trilium-core/src/services/search/services/search.spec.ts b/packages/trilium-core/src/services/search/services/search.spec.ts index fc36d7d7cb..aaffa40aa8 100644 --- a/packages/trilium-core/src/services/search/services/search.spec.ts +++ b/packages/trilium-core/src/services/search/services/search.spec.ts @@ -3,7 +3,7 @@ import searchService from "./search.js"; import BNote from "../../../becca/entities/bnote.js"; import BBranch from "../../../becca/entities/bbranch.js"; import SearchContext from "../search_context.js"; -import dateUtils from "../../date_utils.js"; +import dateUtils from "../../utils/date.js"; import becca from "../../../becca/becca.js"; import { findNoteByTitle, note, NoteBuilder } from "../../../test/becca_mocking.js"; @@ -710,22 +710,22 @@ describe("Search", () => { // Create a moderate-sized dataset to test performance const countries = ["Austria", "Belgium", "Croatia", "Denmark", "Estonia", "Finland", "Germany", "Hungary", "Ireland", "Japan"]; const europeanCountries = note("Europe"); - + countries.forEach(country => { europeanCountries.child(note(country).label("type", "country").label("continent", "Europe")); }); - + rootNote.child(europeanCountries); const searchContext = new SearchContext(); const startTime = Date.now(); - + // Perform a search that exercises multiple features const searchResults = searchService.findResultsWithQuery("#type=country AND continent", searchContext); - + const endTime = Date.now(); const duration = endTime - startTime; - + // Search should complete in under 1 second for reasonable dataset expect(duration).toBeLessThan(1000); expect(searchResults.length).toEqual(10); @@ -748,14 +748,14 @@ describe("Search", () => { // Get note titles in result order const resultTitles = searchResults.map(r => becca.notes[r.noteId].title); - + // Find all exact matches (contain "analysis") - const exactMatchIndices = resultTitles.map((title, index) => + const exactMatchIndices = resultTitles.map((title, index) => title.toLowerCase().includes("analysis") ? index : -1 ).filter(index => index !== -1); - + // Find all fuzzy matches (contain typos) - const fuzzyMatchIndices = resultTitles.map((title, index) => + const fuzzyMatchIndices = resultTitles.map((title, index) => (title.includes("Anaylsis") || title.includes("Anlaysis")) ? index : -1 ).filter(index => index !== -1); @@ -765,7 +765,7 @@ describe("Search", () => { // CRITICAL: All exact matches must appear before all fuzzy matches const lastExactIndex = Math.max(...exactMatchIndices); const firstFuzzyIndex = Math.min(...fuzzyMatchIndices); - + expect(lastExactIndex).toBeLessThan(firstFuzzyIndex); }); diff --git a/packages/trilium-core/src/services/sql/index.ts b/packages/trilium-core/src/services/sql/index.ts index 09d06265f5..9b6516df8b 100644 --- a/packages/trilium-core/src/services/sql/index.ts +++ b/packages/trilium-core/src/services/sql/index.ts @@ -13,3 +13,7 @@ export function getSql(): SqlService { if (!sql) throw new Error("SQL not initialized"); return sql; } + +export function rebuildIntegrationTestDatabase(path?: string) { + throw new Error("Not implemented"); +} diff --git a/packages/trilium-core/src/services/sql/sql.ts b/packages/trilium-core/src/services/sql/sql.ts index 8b9ba890c0..d878936f2c 100644 --- a/packages/trilium-core/src/services/sql/sql.ts +++ b/packages/trilium-core/src/services/sql/sql.ts @@ -11,7 +11,6 @@ export interface SqlServiceParams { provider: DatabaseProvider; onTransactionRollback: () => void; onTransactionCommit: () => void; - onDatabaseNotInitialized?: () => void; isReadOnly: boolean; } diff --git a/packages/trilium-core/src/services/tree.spec.ts b/packages/trilium-core/src/services/tree.spec.ts index 7107152627..d705cba42e 100644 --- a/packages/trilium-core/src/services/tree.spec.ts +++ b/packages/trilium-core/src/services/tree.spec.ts @@ -4,8 +4,8 @@ import becca from "../becca/becca.js"; import BBranch from "../becca/entities/bbranch.js"; import BNote from "../becca/entities/bnote.js"; import tree from "./tree.js"; -import cls from "./cls.js"; import {buildNote} from "../test/becca_easy_mocking.js"; +import { getContext } from "./context.js"; describe("Tree", () => { let rootNote!: NoteBuilder; @@ -58,7 +58,7 @@ describe("Tree", () => { ], "#sorted": "", }); - cls.init(() => { + getContext().init(() => { tree.sortNotesIfNeeded(note.noteId); }); const orderedTitles = note.children.map((child) => child.title); @@ -85,7 +85,7 @@ describe("Tree", () => { // Sort a few times to ensure that the resulting order is the same. for (let i = 0; i < 5; i++) { - cls.init(() => { + getContext().init(() => { tree.sortNotesIfNeeded(rootNote.note.noteId); }); @@ -106,7 +106,7 @@ describe("Tree", () => { ], "#sorted": "" }); - cls.init(() => { + getContext().init(() => { tree.sortNotesIfNeeded(note.noteId); }); const orderedTitles = note.children.map((child) => child.title); @@ -126,7 +126,7 @@ describe("Tree", () => { "#sorted": "", "#sortDirection": "desc" }); - cls.init(() => { + getContext().init(() => { tree.sortNotesIfNeeded(note.noteId); }); const orderedTitles = note.children.map((child) => child.title); @@ -148,7 +148,7 @@ describe("Tree", () => { "#sorted": "", "#sortFoldersFirst": "" }); - cls.init(() => { + getContext().init(() => { tree.sortNotesIfNeeded(note.noteId); }); const orderedTitles = note.children.map((child) => child.title); @@ -169,7 +169,7 @@ describe("Tree", () => { "#sorted": "", "#sortNatural": "" }); - cls.init(() => { + getContext().init(() => { tree.sortNotesIfNeeded(note.noteId); }); const orderedTitles = note.children.map((child) => child.title); diff --git a/packages/trilium-core/src/services/utils/index.spec.ts b/packages/trilium-core/src/services/utils/index.spec.ts index 368a81c035..7ebbf4df0e 100644 --- a/packages/trilium-core/src/services/utils/index.spec.ts +++ b/packages/trilium-core/src/services/utils/index.spec.ts @@ -1,5 +1,5 @@ import { expect, describe, it } from "vitest"; -import { sanitizeAttributeName } from "./index"; +import * as utils from "./index"; // fn value, expected value const testCases: [fnValue: string, expectedValue: string][] = [ @@ -30,8 +30,706 @@ describe("sanitizeAttributeName unit tests", () => { testCases.forEach((testCase) => { return it(`'${testCase[0]}' should return '${testCase[1]}'`, () => { const [ value, expected ] = testCase; - const actual = sanitizeAttributeName(value); + const actual = utils.sanitizeAttributeName(value); expect(actual).toStrictEqual(expected); }); }); }); + +type TestCase any> = [desc: string, fnParams: Parameters, expected: ReturnType]; + +describe("#newEntityId", () => { + it("should return a string with a length of 12", () => { + const result = utils.newEntityId(); + expect(result).toBeTypeOf("string"); + expect(result).toHaveLength(12); + }); +}); + +describe("#randomString", () => { + it("should return a string with a length as per argument", () => { + const stringLength = 5; + const result = utils.randomString(stringLength); + expect(result).toBeTypeOf("string"); + expect(result).toHaveLength(stringLength); + }); +}); + +// TriliumNextTODO: should use mocks and assert that functions get called +describe("#randomSecureToken", () => { + // base64 -> 4 * (bytes/3) length -> if padding and rounding up is ignored for simplicity + // https://stackoverflow.com/a/13378842 + const byteToBase64Length = (bytes: number) => 4 * (bytes / 3); + + it("should return a string and use 32 bytes by default", () => { + const result = utils.randomSecureToken(); + expect(result).toBeTypeOf("string"); + expect(result.length).toBeGreaterThanOrEqual(byteToBase64Length(32)); + }); + + it("should return a string and use passed byte length", () => { + const bytes = 16; + const result = utils.randomSecureToken(bytes); + expect(result).toBeTypeOf("string"); + expect(result.length).toBeGreaterThanOrEqual(byteToBase64Length(bytes)); + expect(result.length).toBeLessThan(44); // default argument uses 32 bytes -> which translates to 44 base64 legal chars + }); +}); + +// TriliumNextTODO: should use mocks and assert that functions get called +describe.todo("#md5", () => {}); + +// TriliumNextTODO: should use mocks and assert that functions get called +describe.todo("#hashedBlobId", () => {}); + +// TriliumNextTODO: should use mocks and assert that functions get called +describe.todo("#toBase64", () => {}); + +// TriliumNextTODO: should use mocks and assert that functions get called +describe.todo("#fromBase64", () => {}); + +// TriliumNextTODO: should use mocks and assert that functions get called +describe.todo("#hmac", () => {}); + +// TriliumNextTODO: should use mocks and assert that functions get called +describe.todo("#hash", () => {}); + +describe("#isEmptyOrWhitespace", () => { + const testCases: TestCase[] = [ + [ "w/ 'null' it should return true", [ null ], true ], + [ "w/ 'null' it should return true", [ null ], true ], + [ "w/ undefined it should return true", [ undefined ], true ], + [ "w/ empty string '' it should return true", [ "" ], true ], + [ "w/ single whitespace string ' ' it should return true", [ " " ], true ], + [ "w/ multiple whitespace string ' ' it should return true", [ " " ], true ], + [ "w/ non-empty string ' t ' it should return false", [ " t " ], false ] + ]; + + testCases.forEach((testCase) => { + const [ desc, fnParams, expected ] = testCase; + it(desc, () => { + const result = utils.isEmptyOrWhitespace(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); +}); + +describe("#sanitizeSqlIdentifier", () => { + const testCases: TestCase[] = [ + [ "w/ 'test' it should not strip anything", [ "test" ], "test" ], + [ "w/ 'test123' it should not strip anything", [ "test123" ], "test123" ], + [ "w/ 'tEst_TeSt' it should not strip anything", [ "tEst_TeSt" ], "tEst_TeSt" ], + [ "w/ 'test_test' it should not strip '_'", [ "test_test" ], "test_test" ], + [ "w/ 'test-' it should strip the '-'", [ "test-" ], "test" ], + [ "w/ 'test-test' it should strip the '-'", [ "test-test" ], "testtest" ], + [ "w/ 'test; --test' it should strip the '; --'", [ "test; --test" ], "testtest" ], + [ "w/ 'test test' it should strip the ' '", [ "test test" ], "testtest" ] + ]; + + testCases.forEach((testCase) => { + const [ desc, fnParams, expected ] = testCase; + it(desc, () => { + const result = utils.sanitizeSqlIdentifier(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); +}); + +describe("#escapeHtml", () => { + it("should re-export 'escape-html' npm module as escapeHtml", () => { + expect(utils.escapeHtml).toBeTypeOf("function"); + }); +}); + +describe("#unescapeHtml", () => { + it("should re-export 'unescape' npm module as unescapeHtml", () => { + expect(utils.unescapeHtml).toBeTypeOf("function"); + }); +}); + +describe("#toObject", () => { + it("should return an object with keys and value being set from the supplied Function", () => { + type TestListEntry = { testPropA: string; testPropB: string }; + type TestListFn = (testListEntry: TestListEntry) => [string, string]; + const testList: [TestListEntry, TestListEntry] = [ + { testPropA: "keyA", testPropB: "valueA" }, + { testPropA: "keyB", testPropB: "valueB" } + ]; + const fn: TestListFn = (testListEntry: TestListEntry) => [ `${testListEntry.testPropA }_fn`, `${testListEntry.testPropB }_fn` ]; + + const result = utils.toObject(testList, fn); + expect(result).toStrictEqual({ + keyA_fn: "valueA_fn", + keyB_fn: "valueB_fn" + }); + }); +}); + +describe("#stripTags", () => { + //prettier-ignore + const htmlWithNewlines = +`

abc +def

+

ghi

`; + + const testCases: TestCase[] = [ + [ "should strip all tags and only return the content, leaving new lines and spaces in tact", [ htmlWithNewlines ], "abc\ndef\nghi" ], + //TriliumNextTODO: should this actually insert a space between content to prevent concatenated text? + [ "should strip all tags and only return the content", [ "

abc

def

" ], "abcdef" ] + ]; + + testCases.forEach((testCase) => { + const [ desc, fnParams, expected ] = testCase; + it(desc, () => { + const result = utils.stripTags(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); +}); + +describe.todo("#escapeRegExp", () => {}); + +describe.todo("#crash", () => {}); + +describe("#getContentDisposition", () => { + + const defaultFallBackDisposition = `file; filename="file"; filename*=UTF-8''file`; + const testCases: TestCase[] = [ + [ + "when passed filename is empty, it should fallback to default value 'file'", + [ " " ], + defaultFallBackDisposition + ], + [ + "when passed filename '..' would cause sanitized filename to be empty, it should fallback to default value 'file'", + [ ".." ], + defaultFallBackDisposition + ], + // COM1 is a Windows specific "illegal filename" that sanitize filename strips away + [ + "when passed filename 'COM1' would cause sanitized filename to be empty, it should fallback to default value 'file'", + [ "COM1" ], + defaultFallBackDisposition + ], + [ + "sanitized passed filename should be returned URIEncoded", + [ "test file.csv" ], + `file; filename="test%20file.csv"; filename*=UTF-8''test%20file.csv` + ] + ]; + + testCases.forEach(testCase => { + const [ desc, fnParams, expected ] = testCase; + it(desc, () => { + const result = utils.getContentDisposition(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); +}); + +describe("#isStringNote", () => { + + const testCases: TestCase[] = [ + [ + "w/ 'undefined' note type, but a string mime type, it should return true", + [ undefined, "application/javascript" ], + true + ], + [ + "w/ non-string note type, it should return false", + [ "image", "image/jpeg" ], + false + ], + [ + "w/ string note type (text), it should return true", + [ "text", "text/html" ], + true + ], + [ + "w/ string note type (code), it should return true", + [ "code", "application/json" ], + true + ], + [ + "w/ non-string note type (file), but string mime type, it should return true", + [ "file", "application/json" ], + true + ], + [ + "w/ non-string note type (file), but mime type starting with 'text/', it should return true", + [ "file", "text/html" ], + true + ] + ]; + + testCases.forEach((testCase) => { + const [ desc, fnParams, expected ] = testCase; + it(desc, () => { + const result = utils.isStringNote(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); +}); + +describe.todo("#quoteRegex", () => {}); + +describe.todo("#replaceAll", () => {}); + +describe("#removeFileExtension", () => { + const testCases: TestCase[] = [ + [ "w/ 'test.md' it should strip '.md'", [ "test.md" ], "test" ], + [ "w/ 'test.markdown' it should strip '.markdown'", [ "test.markdown" ], "test" ], + [ "w/ 'test.html' it should strip '.html'", [ "test.html" ], "test" ], + [ "w/ 'test.htm' it should strip '.htm'", [ "test.htm" ], "test" ], + [ "w/ 'test.zip' it should NOT strip '.zip'", [ "test.zip" ], "test.zip" ] + ]; + + testCases.forEach((testCase) => { + const [ desc, fnParams, expected ] = testCase; + it(desc, () => { + const result = utils.removeFileExtension(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); +}); + +describe("#getNoteTitle", () => { + const testCases: TestCase[] = [ + [ + "when file has no spaces, and no special file extension, it should return the filename unaltered", + [ "test.json", true, undefined ], + "test.json" + ], + [ + "when replaceUnderscoresWithSpaces is false, it should keep the underscores in the title", + [ "test_file.json", false, undefined ], + "test_file.json" + ], + [ + "when replaceUnderscoresWithSpaces is true, it should replace the underscores in the title", + [ "test_file.json", true, undefined ], + "test file.json" + ], + [ + "when filePath ends with one of the extra handled endings (.md), it should strip the file extension from the title", + [ "test_file.md", false, undefined ], + "test_file" + ], + [ + "when filePath ends with one of the extra handled endings (.md) and replaceUnderscoresWithSpaces is true, it should strip the file extension from the title and replace underscores", + [ "test_file.md", true, undefined ], + "test file" + ], + [ + "when filepath contains a full path, it should only return the basename of the file", + [ "Trilium Demo/Scripting examples/Statistics/Most cloned notes/template.zip", true, undefined ], + "template.zip" + ], + [ + "when filepath contains a full path and has extra handled ending (.html), it should only return the basename of the file and strip the file extension", + [ "Trilium Demo/Scripting examples/Statistics/Most cloned notes/template.html", true, undefined ], + "template" + ], + [ + "when a noteMeta object is passed, it should use the title from the noteMeta, if present", + [ "test_file.md", true, { title: "some other title" } ], + "some other title" + ], + [ + "when a noteMeta object is passed, but the title prop is empty, it should try to handle the filename as if no noteMeta was passed", + [ "test_file.md", true, { title: "" } ], + "test file" + ], + [ + "when a noteMeta object is passed, but the title prop is empty, it should try to handle the filename as if no noteMeta was passed", + [ "test_file.json", false, { title: " " } ], + "test_file.json" + ] + ]; + + testCases.forEach(testCase => { + const [ desc, fnParams, expected ] = testCase; + it(desc, () => { + const result = utils.getNoteTitle(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); + +}); + +describe("#timeLimit", () => { + it("when promise execution does NOT exceed timeout, it should resolve with promises' value", async () => { + const resolvedValue = `resolved: ${new Date().toISOString()}`; + const testPromise = new Promise((res, rej) => { + setTimeout(() => { + return res(resolvedValue); + }, 200); + //rej("rejected!"); + }); + await expect(utils.timeLimit(testPromise, 1_000)).resolves.toBe(resolvedValue); + }); + + it("when promise execution rejects within timeout, it should return the original promises' rejected value, not the custom set one", async () => { + const rejectedValue = `rejected: ${new Date().toISOString()}`; + const testPromise = new Promise((res, rej) => { + setTimeout(() => { + //return res("resolved"); + rej(rejectedValue); + }, 100); + }); + await expect(utils.timeLimit(testPromise, 200, "Custom Error")).rejects.toThrow(rejectedValue); + }); + + it("when promise execution exceeds the set timeout, and 'errorMessage' is NOT set, it should reject the promise and display default error message", async () => { + const testPromise = new Promise((res, rej) => { + setTimeout(() => { + return res("resolved"); + }, 500); + //rej("rejected!"); + }); + await expect(utils.timeLimit(testPromise, 200)).rejects.toThrow(`Process exceeded time limit 200`); + }); + + it("when promise execution exceeds the set timeout, and 'errorMessage' is set, it should reject the promise and display set error message", async () => { + const customErrorMsg = "Custom Error"; + const testPromise = new Promise((res, rej) => { + setTimeout(() => { + return res("resolved"); + }, 500); + //rej("rejected!"); + }); + await expect(utils.timeLimit(testPromise, 200, customErrorMsg)).rejects.toThrow(customErrorMsg); + }); + + // TriliumNextTODO: since TS avoids this from ever happening – do we need this check? + it("when the passed promise is not a promise but 'undefined', it should return 'undefined'", async () => { + //@ts-expect-error - passing in illegal type 'undefined' + expect(utils.timeLimit(undefined, 200)).toBe(undefined); + }); + + // TriliumNextTODO: since TS avoids this from ever happening – do we need this check? + it("when the passed promise is not a promise, it should return the passed value", async () => { + //@ts-expect-error - passing in illegal type 'object' + expect(utils.timeLimit({ test: 1 }, 200)).toStrictEqual({ test: 1 }); + }); +}); + +describe("#removeDiacritic", () => { + const testCases: TestCase[] = [ + [ "w/ 'Äpfel' it should replace the 'Ä'", [ "Äpfel" ], "Apfel" ], + [ "w/ 'Été' it should replace the 'É' and 'é'", [ "Été" ], "Ete" ], + [ "w/ 'Fête' it should replace the 'ê'", [ "Fête" ], "Fete" ], + [ "w/ 'Αλφαβήτα' it should replace the 'ή'", [ "Αλφαβήτα" ], "Αλφαβητα" ], + [ "w/ '' (empty string) it should return empty string", [ "" ], "" ] + ]; + + testCases.forEach((testCase) => { + const [ desc, fnParams, expected ] = testCase; + it(desc, () => { + const result = utils.removeDiacritic(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); +}); + +describe("#normalize", () => { + const testCases: TestCase[] = [ + [ "w/ 'Äpfel' it should replace the 'Ä' and return lowercased", [ "Äpfel" ], "apfel" ], + [ "w/ 'Été' it should replace the 'É' and 'é' and return lowercased", [ "Été" ], "ete" ], + [ "w/ 'FêTe' it should replace the 'ê' and return lowercased", [ "FêTe" ], "fete" ], + [ "w/ 'ΑλΦαβήΤα' it should replace the 'ή' and return lowercased", [ "ΑλΦαβήΤα" ], "αλφαβητα" ], + [ "w/ '' (empty string) it should return empty string", [ "" ], "" ] + ]; + + testCases.forEach((testCase) => { + const [ desc, fnParams, expected ] = testCase; + it(desc, () => { + const result = utils.normalize(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); +}); + +describe("#toMap", () => { + it("should return an instace of Map, with the correct size and keys, when supplied with a list and existing keys", () => { + const testList = [ { title: "test", propA: "text", propB: 123 }, { title: "test2", propA: "prop2", propB: 456 } ]; + const result = utils.toMap(testList, "title"); + expect(result).toBeInstanceOf(Map); + expect(result.size).toBe(2); + expect(Array.from(result.keys())).toStrictEqual([ "test", "test2" ]); + }); + it("should return an instace of Map, with an empty size, when the supplied list does not contain the supplied key", () => { + const testList = [ { title: "test", propA: "text", propB: 123 }, { title: "test2", propA: "prop2", propB: 456 } ]; + //@ts-expect-error - key is non-existing on supplied list type + const result = utils.toMap(testList, "nonExistingKey"); + expect(result).toBeInstanceOf(Map); + expect(result.size).toBe(0); + }); + it.fails("should correctly handle duplicate keys? (currently it will overwrite the entry, so returned size will be 1 instead of 2)", () => { + const testList = [ { title: "testDupeTitle", propA: "text", propB: 123 }, { title: "testDupeTitle", propA: "prop2", propB: 456 } ]; + const result = utils.toMap(testList, "title"); + expect(result).toBeInstanceOf(Map); + expect(result.size).toBe(2); + }); +}); + +describe("#envToBoolean", () => { + const testCases: TestCase[] = [ + [ "w/ 'true' it should return boolean 'true'", [ "true" ], true ], + [ "w/ 'True' it should return boolean 'true'", [ "True" ], true ], + [ "w/ 'TRUE' it should return boolean 'true'", [ "TRUE" ], true ], + [ "w/ 'true ' it should return boolean 'true'", [ "true " ], true ], + [ "w/ 'false' it should return boolean 'false'", [ "false" ], false ], + [ "w/ 'False' it should return boolean 'false'", [ "False" ], false ], + [ "w/ 'FALSE' it should return boolean 'false'", [ "FALSE" ], false ], + [ "w/ 'false ' it should return boolean 'false'", [ "false " ], false ], + [ "w/ 'whatever' (non-boolean string) it should return undefined", [ "whatever" ], undefined ], + [ "w/ '-' (non-boolean string) it should return undefined", [ "-" ], undefined ], + [ "w/ '' (empty string) it should return undefined", [ "" ], undefined ], + [ "w/ ' ' (white space string) it should return undefined", [ " " ], undefined ], + [ "w/ undefined it should return undefined", [ undefined ], undefined ], + //@ts-expect-error - pass wrong type as param + [ "w/ number 1 it should return undefined", [ 1 ], undefined ] + ]; + + testCases.forEach((testCase) => { + const [ desc, fnParams, expected ] = testCase; + it(desc, () => { + const result = utils.envToBoolean(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); +}); + +describe.todo("#getResourceDir", () => {}); + +describe("#isElectron", () => { + it("should export a boolean", () => { + expect(utils.isElectron()).toBeTypeOf("boolean"); + }); +}); + +describe("#isMac", () => { + it("should export a boolean", () => { + expect(utils.isMac()).toBeTypeOf("boolean"); + }); +}); + +describe("#isWindows", () => { + it("should export a boolean", () => { + expect(utils.isWindows()).toBeTypeOf("boolean"); + }); +}); + +describe("#safeExtractMessageAndStackFromError", () => { + it("should correctly extract the message and stack property if it gets passed an instance of an Error", () => { + const testMessage = "Test Message"; + const testError = new Error(testMessage); + const actual = utils.safeExtractMessageAndStackFromError(testError); + expect(actual[0]).toBe(testMessage); + expect(actual[1]).not.toBeUndefined(); + }); + + it("should use the fallback 'Unknown Error' message, if it gets passed anything else than an instance of an Error", () => { + const testNonError = "this is not an instance of an Error, but JS technically allows us to throw this anyways"; + const actual = utils.safeExtractMessageAndStackFromError(testNonError); + expect(actual[0]).toBe("Unknown Error"); + expect(actual[1]).toBeUndefined(); + }); +}); + +describe("#formatDownloadTitle", () => { + //prettier-ignore + const testCases: [fnValue: Parameters, expectedValue: ReturnType][] = [ + + // empty fileName tests + [ + [ "", "text", "" ], + "untitled.html" + ], + [ + [ "", "canvas", "" ], + "untitled.json" + ], + [ + [ "", null, "" ], + "untitled" + ], + + + // json extension from type tests + [ + [ "test_file", "canvas", "" ], + "test_file.json" + ], + [ + [ "test_file", "relationMap", "" ], + "test_file.json" + ], + [ + [ "test_file", "search", "" ], + "test_file.json" + ], + + + // extension based on mime type + [ + [ "test_file", null, "text/csv" ], + "test_file.csv" + ], + [ + [ "test_file_wo_ext", "image", "image/svg+xml" ], + "test_file_wo_ext.svg" + ], + [ + [ "test_file_wo_ext", "file", "application/json" ], + "test_file_wo_ext.json" + ], + [ + [ "test_file_w_fake_ext.ext", "image", "image/svg+xml" ], + "test_file_w_fake_ext.ext.svg" + ], + [ + [ "test_file_w_correct_ext.svg", "image", "image/svg+xml" ], + "test_file_w_correct_ext.svg" + ], + [ + [ "test_file_w_correct_ext.svgz", "image", "image/svg+xml" ], + "test_file_w_correct_ext.svgz" + ], + [ + [ "test_file.zip", "file", "application/zip" ], + "test_file.zip" + ], + [ + [ "test_file", "file", "application/zip" ], + "test_file.zip" + ], + + + // application/octet-stream tests + [ + [ "test_file", "file", "application/octet-stream" ], + "test_file" + ], + [ + [ "test_file.zip", "file", "application/octet-stream" ], + "test_file.zip" + ], + [ + [ "test_file.unknown", null, "application/octet-stream" ], + "test_file.unknown" + ], + + + // sanitized filename tests + [ + [ "test/file", null, "application/octet-stream" ], + "testfile" + ], + [ + [ "test:file.zip", "file", "application/zip" ], + "testfile.zip" + ], + [ + [ ":::", "file", "application/zip" ], + ".zip" + ], + [ + [ ":::a", "file", "application/zip" ], + "a.zip" + ] + ]; + + testCases.forEach((testCase) => { + const [ fnParams, expected ] = testCase; + return it(`With args '${JSON.stringify(fnParams)}', it should return '${expected}'`, () => { + const actual = utils.formatDownloadTitle(...fnParams); + expect(actual).toStrictEqual(expected); + }); + }); +}); + +describe("#normalizeUrl", () => { + const testCases: TestCase[] = [ + [ "should remove trailing slash from simple URL", [ "https://example.com/" ], "https://example.com" ], + [ "should remove trailing slash from URL with path", [ "https://example.com/path/" ], "https://example.com/path" ], + [ "should preserve URL without trailing slash", [ "https://example.com" ], "https://example.com" ], + [ "should preserve URL without trailing slash with path", [ "https://example.com/path" ], "https://example.com/path" ], + [ "should preserve protocol-only URLs", [ "https://" ], "https://" ], + [ "should preserve protocol-only URLs", [ "http://" ], "http://" ], + [ "should fix double slashes in path", [ "https://example.com//api//test" ], "https://example.com/api/test" ], + [ "should handle multiple double slashes", [ "https://example.com///api///test" ], "https://example.com/api/test" ], + [ "should handle trailing slash with double slashes", [ "https://example.com//api//" ], "https://example.com/api" ], + [ "should preserve protocol double slash", [ "https://example.com/api" ], "https://example.com/api" ], + [ "should handle empty string", [ "" ], "" ], + [ "should handle whitespace-only string", [ " " ], "" ], + [ "should trim whitespace", [ " https://example.com/ " ], "https://example.com" ], + [ "should handle null as empty", [ null as any ], null ], + [ "should handle undefined as empty", [ undefined as any ], undefined ] + ]; + + testCases.forEach((testCase) => { + const [ desc, fnParams, expected ] = testCase; + it(desc, () => { + const result = utils.normalizeUrl(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); +}); + +describe("#normalizeCustomHandlerPattern", () => { + const testCases: TestCase[] = [ + [ "should handle pattern without ending - add both versions", [ "foo" ], [ "foo", "foo/" ] ], + [ "should handle pattern with trailing slash - add both versions", [ "foo/" ], [ "foo", "foo/" ] ], + [ "should handle pattern ending with $ - add optional slash", [ "foo$" ], [ "foo/?$" ] ], + [ "should handle pattern with trailing slash and $ - add both versions", [ "foo/$" ], [ "foo$", "foo/$" ] ], + [ "should preserve existing optional slash pattern", [ "foo/?$" ], [ "foo/?$" ] ], + [ "should preserve existing optional slash pattern (alternative)", [ "foo/?)" ], [ "foo/?)" ] ], + [ "should handle regex pattern with special chars", [ "api/[a-z]+$" ], [ "api/[a-z]+/?$" ] ], + [ "should handle complex regex pattern", [ "user/([0-9]+)/profile$" ], [ "user/([0-9]+)/profile/?$" ] ], + [ "should handle empty string", [ "" ], [ "" ] ], + [ "should handle whitespace-only string", [ " " ], [ "" ] ], + [ "should handle null", [ null as any ], [ null ] ], + [ "should handle undefined", [ undefined as any ], [ undefined ] ] + ]; + + testCases.forEach((testCase) => { + const [ desc, fnParams, expected ] = testCase; + it(desc, () => { + const result = utils.normalizeCustomHandlerPattern(...fnParams); + expect(result).toStrictEqual(expected); + }); + }); +}); + +describe("#slugify", () => { + it("should return a slugified string", () => { + const testString = "This is a Test String! With unicode & Special #Chars."; + const expectedSlug = "this-is-a-test-string-with-unicode-special-chars"; + const result = utils.slugify(testString); + expect(result).toBe(expectedSlug); + }); + + it("supports CJK characters without alteration", () => { + const testString = "测试中文字符"; + const expectedSlug = "测试中文字符"; + const result = utils.slugify(testString); + expect(result).toBe(expectedSlug); + }); + + it("supports Cyrillic characters without alteration", () => { + const testString = "Тестирование кириллических символов"; + const expectedSlug = "тестирование-кириллических-символов"; + const result = utils.slugify(testString); + expect(result).toBe(expectedSlug); + }); + + // preserves diacritic marks + it("preserves diacritic marks", () => { + const testString = "Café naïve façade jalapeño"; + const expectedSlug = "café-naïve-façade-jalapeño"; + const result = utils.slugify(testString); + expect(result).toBe(expectedSlug); + }); +}); diff --git a/packages/trilium-core/src/services/utils/index.ts b/packages/trilium-core/src/services/utils/index.ts index 74f47927a4..0a73c6b8e3 100644 --- a/packages/trilium-core/src/services/utils/index.ts +++ b/packages/trilium-core/src/services/utils/index.ts @@ -1,14 +1,16 @@ import { getCrypto } from "../encryption/crypto"; +import { getPlatform } from "../platform"; import { sanitizeFileName } from "../sanitizer"; import { encodeBase64 } from "./binary"; -import mimeTypes from "mime-types"; +import { extensions as mimeToExt, types as extToMime } from "mime-types"; import escape from "escape-html"; import unescape from "unescape"; +import { basename, extname } from "./path"; +import { NoteMeta } from "../../meta"; -// TODO: Implement platform detection. -export const isElectron = false; -export const isMac = false; -export const isWindows = false; +export function isElectron() { return getPlatform().isElectron; } +export function isMac() { return getPlatform().isMac; } +export function isWindows() { return getPlatform().isWindows; } // render and book are string note in the sense that they are expected to contain empty string const STRING_NOTE_TYPES = new Set(["text", "code", "relationMap", "search", "render", "book", "mermaid", "canvas", "webView"]); @@ -159,12 +161,12 @@ export function formatDownloadTitle(fileName: string, type: string | null, mime: if (mimeLc === "application/octet-stream") return ""; // if fileName has an extension matching the mime already - reuse it - const mimeTypeFromFileName = mimeTypes.lookup(fileName); - if (mimeTypeFromFileName === mimeLc) return ""; + const ext = extname(fileName).toLowerCase().replace(".", ""); + if (ext && extToMime[ext] === mimeLc) return ""; // as last resort try to get extension from mimeType - const extensions = mimeTypes.extension(mime); - return extensions ? `.${extensions}` : ""; + const exts = mimeToExt[mime]; + return exts?.length ? `.${exts[0]}` : ""; }; return `${fileNameBase}${getExtension()}`; @@ -202,3 +204,200 @@ export function isEmptyOrWhitespace(str: string | null | undefined) { export function escapeRegExp(str: string) { return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); } + +export function removeFileExtension(filePath: string, mime?: string) { + const extension = extname(filePath).toLowerCase(); + + if (mime?.startsWith("video/") || mime?.startsWith("audio/")) { + return filePath.substring(0, filePath.length - extension.length); + } + + switch (extension) { + case ".md": + case ".mdx": + case ".markdown": + case ".html": + case ".htm": + case ".excalidraw": + case ".mermaid": + case ".mmd": + case ".pdf": + return filePath.substring(0, filePath.length - extension.length); + default: + return filePath; + } +} + +export function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, noteMeta?: NoteMeta) { + const trimmedNoteMeta = noteMeta?.title?.trim(); + if (trimmedNoteMeta) return trimmedNoteMeta; + + const fileBasename = basename(removeFileExtension(filePath, noteMeta?.mime)); + return replaceUnderscoresWithSpaces ? fileBasename.replace(/_/g, " ").trim() : fileBasename; +} + +// try to turn 'true' and 'false' strings from process.env variables into boolean values or undefined +export function envToBoolean(val: string | undefined) { + if (val === undefined || typeof val !== "string") return undefined; + + const valLc = val.toLowerCase().trim(); + + if (valLc === "true") return true; + if (valLc === "false") return false; + + return undefined; +} + +/** + * Parses a string value to an integer. If the resulting number is NaN or undefined, the result is also undefined. + * + * @param val the value to parse. + * @returns the parsed value. + */ +export function stringToInt(val: string | undefined) { + if (!val) { + return undefined; + } + + const parsed = parseInt(val, 10); + if (Number.isNaN(parsed)) { + return undefined; + } + + return parsed; +} + + +/** + * Normalizes a path pattern for custom request handlers. + * Ensures both trailing slash and non-trailing slash versions are handled. + * + * @param pattern The original pattern from customRequestHandler attribute + * @returns An array of patterns to match both with and without trailing slash + */ +export function normalizeCustomHandlerPattern(pattern: string | null | undefined): (string | null | undefined)[] { + if (!pattern || typeof pattern !== "string") { + return [pattern]; + } + + pattern = pattern.trim(); + + if (!pattern) { + return [pattern]; + } + + // If pattern already ends with optional trailing slash, return as-is + if (pattern.endsWith("/?$") || pattern.endsWith("/?)")) { + return [pattern]; + } + + // If pattern ends with $, handle it specially + if (pattern.endsWith("$")) { + const basePattern = pattern.slice(0, -1); + + // If already ends with slash, create both versions + if (basePattern.endsWith("/")) { + const withoutSlash = `${basePattern.slice(0, -1)}$`; + const withSlash = pattern; + return [withoutSlash, withSlash]; + } + // Add optional trailing slash + const withSlash = `${basePattern}/?$`; + return [withSlash]; + + } + + // For patterns without $, add both versions + if (pattern.endsWith("/")) { + const withoutSlash = pattern.slice(0, -1); + return [withoutSlash, pattern]; + } + const withSlash = `${pattern}/`; + return [pattern, withSlash]; + +} + +export function formatUtcTime(time: string) { + return time.replace("T", " ").substring(0, 19); +} + +// TODO: Deduplicate with client utils +export function formatSize(size: number | null | undefined) { + if (size === null || size === undefined) { + return ""; + } + + size = Math.max(Math.round(size / 1024), 1); + + if (size < 1024) { + return `${size} KiB`; + } + return `${Math.round(size / 102.4) / 10} MiB`; + +} + +export function slugify(text: string) { + return text + .normalize("NFC") // keep composed form, preserves accents + .toLowerCase() + .replace(/[^\p{Letter}\p{Number}]+/gu, "-") // replace non-letter/number with "-" + .replace(/(^-|-$)+/g, ""); // trim dashes +} + +export function stripTags(text: string) { + return text.replace(/<(?:.|\n)*?>/gm, ""); +} + +export function toObject(array: T[], fn: (item: T) => [K, V]): Record { + const obj: Record = {} as Record; // TODO: unsafe? + + for (const item of array) { + const ret = fn(item); + + obj[ret[0]] = ret[1]; + } + + return obj; +} + +// TODO: Deduplicate with src/public/app/services/utils.ts +/** + * Compares two semantic version strings. + * Returns: + * 1 if v1 is greater than v2 + * 0 if v1 is equal to v2 + * -1 if v1 is less than v2 + * + * @param v1 First version string + * @param v2 Second version string + * @returns + */ +export function compareVersions(v1: string, v2: string): number { + // Remove 'v' prefix and everything after dash if present + v1 = v1.replace(/^v/, "").split("-")[0]; + v2 = v2.replace(/^v/, "").split("-")[0]; + + const v1parts = v1.split(".").map(Number); + const v2parts = v2.split(".").map(Number); + + // Pad shorter version with zeros + while (v1parts.length < 3) v1parts.push(0); + while (v2parts.length < 3) v2parts.push(0); + + // Compare major version + if (v1parts[0] !== v2parts[0]) { + return v1parts[0] > v2parts[0] ? 1 : -1; + } + + // Compare minor version + if (v1parts[1] !== v2parts[1]) { + return v1parts[1] > v2parts[1] ? 1 : -1; + } + + // Compare patch version + if (v1parts[2] !== v2parts[2]) { + return v1parts[2] > v2parts[2] ? 1 : -1; + } + + return 0; +} diff --git a/packages/trilium-core/src/services/utils/path.spec.ts b/packages/trilium-core/src/services/utils/path.spec.ts new file mode 100644 index 0000000000..8543617def --- /dev/null +++ b/packages/trilium-core/src/services/utils/path.spec.ts @@ -0,0 +1,41 @@ +import { describe, it, expect } from "vitest"; +import { extname, basename } from "./path"; + +describe("#extname", () => { + const testCases: [input: string, expected: string][] = [ + ["file.txt", ".txt"], + ["file.tar.gz", ".gz"], + ["file", ""], + [".hidden", ""], + [".hidden.txt", ".txt"], + ["no-ext.", "."], + ["path/to/file.ts", ".ts"], + ["path\\to\\file.ts", ".ts"], + ["path/to/.gitignore", ""], + ["", ""], + ]; + + testCases.forEach(([input, expected]) => { + it(`'${input}' should return '${expected}'`, () => { + expect(extname(input)).toBe(expected); + }); + }); +}); + +describe("#basename", () => { + const testCases: [input: string, expected: string][] = [ + ["path/to/file.txt", "file.txt"], + ["path\\to\\file.txt", "file.txt"], + ["file.txt", "file.txt"], + ["/root/file", "file"], + ["C:\\Users\\test\\file.md", "file.md"], + ["path/to/dir/", ""], + ["", ""], + ]; + + testCases.forEach(([input, expected]) => { + it(`'${input}' should return '${expected}'`, () => { + expect(basename(input)).toBe(expected); + }); + }); +}); diff --git a/packages/trilium-core/src/services/utils/path.ts b/packages/trilium-core/src/services/utils/path.ts new file mode 100644 index 0000000000..d470b5dc16 --- /dev/null +++ b/packages/trilium-core/src/services/utils/path.ts @@ -0,0 +1,18 @@ +/** + * Browser-safe path utilities that don't depend on Node's `path` module. + * Handles both forward slashes and backslashes. + */ + +/** Returns the extension of a file path (e.g. ".txt"), or "" if none. */ +export function extname(filePath: string): string { + const base = basename(filePath); + const dotIdx = base.lastIndexOf("."); + if (dotIdx <= 0) return ""; + return base.substring(dotIdx); +} + +/** Returns the last component of a file path. */ +export function basename(filePath: string): string { + const lastSlash = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\")); + return filePath.substring(lastSlash + 1); +} diff --git a/packages/trilium-core/src/test/becca_easy_mocking.ts b/packages/trilium-core/src/test/becca_easy_mocking.ts new file mode 100644 index 0000000000..7e900d3c21 --- /dev/null +++ b/packages/trilium-core/src/test/becca_easy_mocking.ts @@ -0,0 +1,134 @@ +import { NoteType } from "@triliumnext/commons"; + +import BAttachment from "../becca/entities/battachment.js"; +import BAttribute from "../becca/entities/battribute.js"; +import BBranch from "../becca/entities/bbranch.js"; +import BNote from "../becca/entities/bnote.js"; +import { randomString } from "../services/utils/index.js"; + +type AttributeDefinitions = { [key in `#${string}`]: string; }; +type RelationDefinitions = { [key in `~${string}`]: string; }; + +interface NoteDefinition extends AttributeDefinitions, RelationDefinitions { + id?: string | undefined; + title?: string; + content?: string; + type?: NoteType; + mime?: string; + children?: NoteDefinition[]; + attachments?: { + title: string; + role: string; + mime: string; + }[]; +} + +/** + * Creates the given notes with the given title and optionally one or more attributes. + * + * For a label to be created, simply pass on a key prefixed with `#` and any desired value. + * + * The notes and attributes will be injected in the froca. + * + * @param notes + * @returns an array containing the IDs of the created notes. + * @example + * buildNotes([ + * { title: "A", "#startDate": "2025-05-05" }, + * { title: "B", "#startDate": "2025-05-07" } + * ]); + */ +export function buildNotes(notes: NoteDefinition[]) { + const ids: string[] = []; + + for (const noteDef of notes) { + ids.push(buildNote(noteDef).noteId); + } + + return ids; +} + +export function buildNote(noteDef: NoteDefinition) { + const note = new BNote({ + noteId: noteDef.id ?? randomString(12), + title: noteDef.title ?? "New note", + type: noteDef.type ?? "text", + mime: noteDef.mime ?? "text/html", + isProtected: false, + blobId: "" + }); + + // Handle content. + if (noteDef.content !== undefined) { + note.getContent = () => noteDef.content!; + } + + // Handle children + if (noteDef.children) { + for (const childDef of noteDef.children) { + const childNote = buildNote(childDef); + new BBranch({ + noteId: childNote.noteId, + parentNoteId: note.noteId, + branchId: `${note.noteId}_${childNote.noteId}` + }); + } + } + + // Handle labels and relations. + let position = 0; + for (const [ key, value ] of Object.entries(noteDef)) { + const attributeId = randomString(12); + const name = key.substring(1); + + let attribute: BAttribute | null = null; + if (key.startsWith("#")) { + attribute = new BAttribute({ + noteId: note.noteId, + attributeId, + type: "label", + name, + value, + position, + isInheritable: false + }); + } + + if (key.startsWith("~")) { + attribute = new BAttribute({ + noteId: note.noteId, + attributeId, + type: "relation", + name, + value, + position, + isInheritable: false + }); + } + + if (!attribute) { + continue; + } + + position++; + } + + // Handle attachments. + if (noteDef.attachments) { + const allAttachments: BAttachment[] = []; + for (const { title, role, mime } of noteDef.attachments) { + const attachment = new BAttachment({ + attachmentId: randomString(10), + ownerId: note.noteId, + title, + role, + mime + }); + allAttachments.push(attachment); + } + + note.getAttachmentsByRole = (role) => allAttachments.filter(a => a.role === role); + } + + return note; +} diff --git a/packages/trilium-core/src/test/becca_mocking.ts b/packages/trilium-core/src/test/becca_mocking.ts new file mode 100644 index 0000000000..b273c76e76 --- /dev/null +++ b/packages/trilium-core/src/test/becca_mocking.ts @@ -0,0 +1,76 @@ +import type { NoteRow, NoteType } from "@triliumnext/commons"; + +import becca from "../becca/becca.js"; +import BAttribute from "../becca/entities/battribute.js"; +import BBranch from "../becca/entities/bbranch.js"; +import BNote from "../becca/entities/bnote.js"; +import { randomString } from "../services/utils/index.js"; +import SearchResult from "../services/search/search_result.js"; + +export function findNoteByTitle(searchResults: Array, title: string): BNote | undefined { + return searchResults.map((sr) => becca.notes[sr.noteId]).find((note) => note.title === title); +} + +export class NoteBuilder { + note: BNote; + constructor(note: BNote) { + this.note = note; + } + + label(name: string, value = "", isInheritable = false) { + new BAttribute({ + attributeId: id(), + noteId: this.note.noteId, + type: "label", + isInheritable, + name, + value + }); + + return this; + } + + relation(name: string, targetNote: BNote) { + new BAttribute({ + attributeId: id(), + noteId: this.note.noteId, + type: "relation", + name, + value: targetNote.noteId + }); + + return this; + } + + child(childNoteBuilder: NoteBuilder, prefix = "") { + new BBranch({ + branchId: id(), + noteId: childNoteBuilder.note.noteId, + parentNoteId: this.note.noteId, + prefix, + notePosition: 10 + }); + + return this; + } +} + +export function id() { + return randomString(10); +} + +export function note(title: string, extraParams: Partial = {}) { + const row = Object.assign( + { + noteId: id(), + title, + type: "text" as NoteType, + mime: "text/html" + }, + extraParams + ); + + const note = new BNote(row); + + return new NoteBuilder(note); +} diff --git a/packages/trilium-core/tsconfig.lib.json b/packages/trilium-core/tsconfig.lib.json index b8103483a1..3ae44d7205 100644 --- a/packages/trilium-core/tsconfig.lib.json +++ b/packages/trilium-core/tsconfig.lib.json @@ -1,15 +1,19 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "module": "ESNext", + "moduleResolution": "bundler", "rootDir": "src", "outDir": "dist", "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", "emitDeclarationOnly": true, + "noEmitOnError": false, "forceConsistentCasingInFileNames": true, "types": [] }, "include": [ - "src/**/*.ts" + "src/**/*.ts", + "src/**/*.json" ], "references": [], "exclude": [ diff --git a/packages/trilium-core/tsconfig.spec.json b/packages/trilium-core/tsconfig.spec.json index 2819b04629..92448e8fa1 100644 --- a/packages/trilium-core/tsconfig.spec.json +++ b/packages/trilium-core/tsconfig.spec.json @@ -20,7 +20,8 @@ "src/**/*.spec.js", "src/**/*.test.jsx", "src/**/*.spec.jsx", - "src/**/*.d.ts" + "src/**/*.d.ts", + "src/**/*.json" ], "references": [ { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 631aaa6752..8aa762e626 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ settings: excludeLinksFromLockfile: false overrides: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@lezer/highlight': 1.2.3 '@lezer/common': 1.5.1 mermaid: 11.13.0 @@ -75,14 +75,14 @@ importers: specifier: 24.12.0 version: 24.12.0 '@vitest/browser-webdriverio': - specifier: 4.1.0 - version: 4.1.0(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.0)(webdriverio@9.26.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)) + specifier: 4.1.2 + version: 4.1.2(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.2)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) '@vitest/coverage-v8': - specifier: 4.1.0 - version: 4.1.0(@vitest/browser@4.1.0(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.0))(vitest@4.1.0) + specifier: 4.1.2 + version: 4.1.2(@vitest/browser@4.1.2(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.2))(vitest@4.1.2) '@vitest/ui': - specifier: 4.1.0 - version: 4.1.0(vitest@4.1.0) + specifier: 4.1.2 + version: 4.1.2(vitest@4.1.2) chalk: specifier: 5.6.2 version: 5.6.2 @@ -111,8 +111,8 @@ importers: specifier: 12.1.1 version: 12.1.1(eslint@10.1.0(jiti@2.6.1)) happy-dom: - specifier: 20.8.4 - version: 20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 20.8.8 + version: 20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5) http-server: specifier: 14.1.1 version: 14.1.1 @@ -130,7 +130,7 @@ importers: version: 0.18.0 rollup-plugin-webpack-stats: specifier: 3.1.0 - version: 3.1.0(rolldown@1.0.0-rc.10)(rollup@4.52.0)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 3.1.0(rolldown@1.0.0-rc.11)(rollup@4.52.0)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) tslib: specifier: 2.8.1 version: 2.8.1 @@ -141,26 +141,26 @@ importers: specifier: 5.9.3 version: 5.9.3 typescript-eslint: - specifier: 8.57.1 - version: 8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.57.2 + version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) upath: specifier: 2.0.1 version: 2.0.1 vite: - specifier: 8.0.1 - version: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + specifier: 8.0.2 + version: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) vite-plugin-dts: specifier: 4.5.4 - version: 4.5.4(@types/node@24.12.0)(rollup@4.52.0)(typescript@5.9.3)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.5.4(@types/node@24.12.0)(rollup@4.52.0)(typescript@5.9.3)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) vitest: - specifier: 4.1.0 - version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.0)(@vitest/ui@4.1.0)(happy-dom@20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: 4.1.2 + version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.2)(@vitest/ui@4.1.2)(happy-dom@20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) apps/build-docs: devDependencies: '@redocly/cli': - specifier: 2.24.1 - version: 2.24.1(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5) + specifier: 2.25.1 + version: 2.25.1(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5) archiver: specifier: 7.0.1 version: 7.0.1 @@ -177,11 +177,11 @@ importers: specifier: 19.2.4 version: 19.2.4(react@19.2.4) typedoc: - specifier: 0.28.17 - version: 0.28.17(typescript@5.9.3) + specifier: 0.28.18 + version: 0.28.18(typescript@5.9.3) typedoc-plugin-missing-exports: specifier: 4.1.2 - version: 4.1.2(typedoc@0.28.17(typescript@5.9.3)) + version: 4.1.2(typedoc@0.28.18(typescript@5.9.3)) apps/client: dependencies: @@ -267,8 +267,8 @@ importers: specifier: 0.18.0 version: 0.18.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2) '@zumer/snapdom': - specifier: 2.5.0 - version: 2.5.0 + specifier: 2.6.0 + version: 2.6.0 autocomplete.js: specifier: 0.38.1 version: 0.38.1 @@ -297,8 +297,8 @@ importers: specifier: 17.4.0 version: 17.4.0 i18next: - specifier: 25.10.3 - version: 25.10.3(typescript@5.9.3) + specifier: 25.10.10 + version: 25.10.10(typescript@5.9.3) i18next-http-backend: specifier: 3.0.2 version: 3.0.2(encoding@0.1.13) @@ -312,8 +312,8 @@ importers: specifier: 2.15.6 version: 2.15.6 katex: - specifier: 0.16.40 - version: 0.16.40 + specifier: 0.16.43 + version: 0.16.43 leaflet: specifier: 1.9.4 version: 1.9.4 @@ -336,14 +336,14 @@ importers: specifier: 8.0.1 version: 8.0.1 panzoom: - specifier: 9.4.3 - version: 9.4.3 + specifier: 9.4.4 + version: 9.4.4 preact: specifier: 10.29.0 version: 10.29.0 react-i18next: - specifier: 16.6.0 - version: 16.6.0(i18next@25.10.3(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + specifier: 16.6.6 + version: 16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) react-window: specifier: 2.2.7 version: 2.2.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -368,7 +368,7 @@ importers: version: 5.0.0 '@prefresh/vite': specifier: 2.4.12 - version: 2.4.12(preact@10.29.0)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 2.4.12(preact@10.29.0)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) '@types/bootstrap': specifier: 5.2.10 version: 5.2.10 @@ -391,8 +391,8 @@ importers: specifier: 14.0.0 version: 14.0.0(webpack@5.101.3(esbuild@0.27.4)) happy-dom: - specifier: 20.8.4 - version: 20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 20.8.8 + version: 20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5) lightningcss: specifier: 1.32.0 version: 1.32.0 @@ -400,8 +400,8 @@ importers: specifier: 0.7.2 version: 0.7.2 vite-plugin-static-copy: - specifier: 3.3.0 - version: 3.3.0(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: 3.4.0 + version: 3.4.0(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) apps/client-standalone: dependencies: @@ -430,17 +430,17 @@ importers: specifier: 0.1.3 version: 0.1.3(@types/leaflet@1.9.21)(leaflet@1.9.4)(maplibre-gl@5.6.1) '@mermaid-js/layout-elk': - specifier: 0.2.0 - version: 0.2.0(mermaid@11.13.0) + specifier: 0.2.1 + version: 0.2.1(mermaid@11.13.0) '@mind-elixir/node-menu': specifier: 5.0.1 - version: 5.0.1(mind-elixir@5.4.0) + version: 5.0.1(mind-elixir@5.9.3) '@popperjs/core': specifier: 2.11.8 version: 2.11.8 '@preact/signals': - specifier: 2.5.1 - version: 2.5.1(preact@10.29.0) + specifier: 2.8.2 + version: 2.8.2(preact@10.29.0) '@sqlite.org/sqlite-wasm': specifier: 3.51.1-build2 version: 3.51.1-build2 @@ -466,8 +466,8 @@ importers: specifier: workspace:* version: link:../../packages/splitjs '@zumer/snapdom': - specifier: 2.0.1 - version: 2.0.1 + specifier: 2.6.0 + version: 2.6.0 autocomplete.js: specifier: 0.38.1 version: 0.38.1 @@ -490,23 +490,23 @@ importers: specifier: 3.0.0 version: 3.0.0 force-graph: - specifier: 1.51.0 - version: 1.51.0 + specifier: 1.51.2 + version: 1.51.2 globals: - specifier: 17.0.0 - version: 17.0.0 + specifier: 17.4.0 + version: 17.4.0 i18next: - specifier: 25.7.3 - version: 25.7.3(typescript@5.9.3) + specifier: 25.10.10 + version: 25.10.10(typescript@5.9.3) i18next-http-backend: specifier: 3.0.2 version: 3.0.2(encoding@0.1.13) jquery: - specifier: 3.7.1 - version: 3.7.1 + specifier: 4.0.0 + version: 4.0.0 jquery.fancytree: specifier: 2.38.5 - version: 2.38.5(jquery@3.7.1) + version: 2.38.5(jquery@4.0.0) js-sha1: specifier: 0.7.0 version: 0.7.0 @@ -520,8 +520,8 @@ importers: specifier: 2.15.6 version: 2.15.6 katex: - specifier: 0.16.27 - version: 0.16.27 + specifier: 0.16.43 + version: 0.16.43 knockout: specifier: 3.5.1 version: 3.5.1 @@ -535,38 +535,38 @@ importers: specifier: 8.11.1 version: 8.11.1 marked: - specifier: 17.0.1 - version: 17.0.1 + specifier: 17.0.5 + version: 17.0.5 mermaid: specifier: 11.13.0 version: 11.13.0 mind-elixir: - specifier: 5.4.0 - version: 5.4.0 + specifier: 5.9.3 + version: 5.9.3 normalize.css: specifier: 8.0.1 version: 8.0.1 panzoom: - specifier: 9.4.3 - version: 9.4.3 + specifier: 9.4.4 + version: 9.4.4 preact: specifier: 10.29.0 version: 10.29.0 react-i18next: - specifier: 16.5.1 - version: 16.5.1(i18next@25.7.3(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + specifier: 16.6.6 + version: 16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) react-window: - specifier: 2.2.3 - version: 2.2.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 2.2.7 + version: 2.2.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) reveal.js: - specifier: 5.2.1 - version: 5.2.1 + specifier: 6.0.0 + version: 6.0.0 svg-pan-zoom: specifier: 3.6.2 version: 3.6.2 tabulator-tables: - specifier: 6.3.1 - version: 6.3.1 + specifier: 6.4.0 + version: 6.4.0 vanilla-js-wheel-zoom: specifier: 9.0.4 version: 9.0.4 @@ -576,13 +576,13 @@ importers: version: 5.0.0 '@preact/preset-vite': specifier: 2.10.2 - version: 2.10.2(@babel/core@7.29.0)(preact@10.29.0)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 2.10.2(@babel/core@7.29.0)(preact@10.29.0)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) '@types/bootstrap': specifier: 5.2.10 version: 5.2.10 '@types/jquery': - specifier: 3.5.33 - version: 3.5.33 + specifier: 4.0.0 + version: 4.0.0 '@types/leaflet': specifier: 1.9.21 version: 1.9.21 @@ -599,20 +599,20 @@ importers: specifier: 6.3.1 version: 6.3.1 copy-webpack-plugin: - specifier: 13.0.1 - version: 13.0.1(webpack@5.101.3(esbuild@0.27.4)) + specifier: 14.0.0 + version: 14.0.0(webpack@5.101.3(esbuild@0.27.4)) cross-env: specifier: 7.0.3 version: 7.0.3 happy-dom: - specifier: 20.0.11 - version: 20.0.11 + specifier: 20.8.8 + version: 20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5) script-loader: specifier: 0.7.2 version: 0.7.2 vite-plugin-static-copy: - specifier: 3.1.4 - version: 3.1.4(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: 3.4.0 + version: 3.4.0(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) apps/db-compare: dependencies: @@ -620,8 +620,8 @@ importers: specifier: 1.4.0 version: 1.4.0 diff: - specifier: 8.0.3 - version: 8.0.3 + specifier: 8.0.4 + version: 8.0.4 sqlite: specifier: 5.1.1 version: 5.1.1 @@ -633,7 +633,7 @@ importers: dependencies: '@electron/remote': specifier: 2.1.3 - version: 2.1.3(electron@41.0.3) + version: 2.1.3(electron@41.0.4) better-sqlite3: specifier: 12.8.0 version: 12.8.0 @@ -693,8 +693,8 @@ importers: specifier: 14.0.0 version: 14.0.0(webpack@5.101.3(esbuild@0.27.4)) electron: - specifier: 41.0.3 - version: 41.0.3 + specifier: 41.0.4 + version: 41.0.4 prebuild-install: specifier: 7.1.3 version: 7.1.3 @@ -739,6 +739,9 @@ importers: '@triliumnext/client': specifier: workspace:* version: link:../client + '@triliumnext/core': + specifier: workspace:* + version: link:../../packages/trilium-core '@triliumnext/desktop': specifier: workspace:* version: link:../desktop @@ -749,8 +752,8 @@ importers: specifier: 14.0.0 version: 14.0.0(webpack@5.101.3(esbuild@0.27.4)) electron: - specifier: 41.0.3 - version: 41.0.3 + specifier: 41.0.4 + version: 41.0.4 fs-extra: specifier: 11.3.4 version: 11.3.4 @@ -772,9 +775,6 @@ importers: html-to-text: specifier: 9.0.5 version: 9.0.5 - node-html-parser: - specifier: 7.1.0 - version: 7.1.0 sucrase: specifier: 3.35.1 version: 3.35.1 @@ -784,7 +784,7 @@ importers: version: 7.1.2 '@electron/remote': specifier: 2.1.3 - version: 2.1.3(electron@41.0.3) + version: 2.1.3(electron@41.0.4) '@triliumnext/commons': specifier: workspace:* version: link:../../packages/commons @@ -909,8 +909,8 @@ importers: specifier: 5.0.1 version: 5.0.1 electron: - specifier: 41.0.3 - version: 41.0.3 + specifier: 41.0.4 + version: 41.0.4 electron-debug: specifier: 4.1.0 version: 4.1.0 @@ -924,8 +924,8 @@ importers: specifier: 2.1.2 version: 2.1.2 express-openid-connect: - specifier: 2.19.4 - version: 2.19.4(express@5.2.1) + specifier: 2.20.0 + version: 2.20.0(express@5.2.1) express-rate-limit: specifier: 8.3.1 version: 8.3.1(express@5.2.1) @@ -954,14 +954,14 @@ importers: specifier: 8.0.0 version: 8.0.0 i18next: - specifier: 25.10.3 - version: 25.10.3(typescript@5.9.3) + specifier: 25.10.10 + version: 25.10.10(typescript@5.9.3) i18next-fs-backend: specifier: 2.6.1 version: 2.6.1 image-type: - specifier: 6.0.0 - version: 6.0.0 + specifier: 6.1.0 + version: 6.1.0 ini: specifier: 6.0.0 version: 6.0.0 @@ -1023,8 +1023,8 @@ importers: specifier: 1.8.0 version: 1.8.0 vite: - specifier: 8.0.1 - version: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + specifier: 8.0.2 + version: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) ws: specifier: 8.20.0 version: 8.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -1057,8 +1057,8 @@ importers: apps/website: dependencies: i18next: - specifier: 25.10.3 - version: 25.10.3(typescript@5.9.3) + specifier: 25.10.10 + version: 25.10.10(typescript@5.9.3) i18next-http-backend: specifier: 3.0.2 version: 3.0.2(encoding@0.1.13) @@ -1072,12 +1072,12 @@ importers: specifier: 6.6.6 version: 6.6.6(preact@10.29.0) react-i18next: - specifier: 16.6.0 - version: 16.6.0(i18next@25.10.3(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + specifier: 16.6.6 + version: 16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) devDependencies: '@preact/preset-vite': specifier: 2.10.5 - version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.52.0)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.52.0)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) eslint: specifier: 10.1.0 version: 10.1.0(jiti@2.6.1) @@ -1088,14 +1088,14 @@ importers: specifier: 5.9.3 version: 5.9.3 user-agent-data-types: - specifier: 0.4.2 - version: 0.4.2 + specifier: 0.4.3 + version: 0.4.3 vite: - specifier: 8.0.1 - version: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + specifier: 8.0.2 + version: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) vitest: - specifier: 4.1.0 - version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.0)(@vitest/ui@4.1.0)(happy-dom@20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: 4.1.2 + version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.2)(@vitest/ui@4.1.2)(happy-dom@20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) packages/ckeditor5: dependencies: @@ -1143,17 +1143,17 @@ importers: specifier: 5.1.0 version: 5.1.0(@babel/core@7.29.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.12.0)(bufferutil@4.0.9)(esbuild@0.27.4)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': - specifier: 8.57.1 - version: 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.57.2 + version: 8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.57.1 - version: 8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.57.2 + version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) '@vitest/browser': - specifier: 4.1.0 - version: 4.1.0(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.0) + specifier: 4.1.2 + version: 4.1.2(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.2) '@vitest/coverage-istanbul': - specifier: 4.1.0 - version: 4.1.0(vitest@4.1.0) + specifier: 4.1.2 + version: 4.1.2(vitest@4.1.2) ckeditor5: specifier: 47.6.1 version: 47.6.1 @@ -1170,11 +1170,11 @@ importers: specifier: 16.4.0 version: 16.4.0 stylelint: - specifier: 17.5.0 - version: 17.5.0(typescript@5.9.3) + specifier: 17.6.0 + version: 17.6.0(typescript@5.9.3) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(stylelint@17.5.0(typescript@5.9.3)) + version: 14.0.0(stylelint@17.6.0(typescript@5.9.3)) ts-node: specifier: 10.9.2 version: 10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.12.0)(typescript@5.9.3) @@ -1183,13 +1183,13 @@ importers: version: 5.9.3 vite-plugin-svgo: specifier: 2.0.0 - version: 2.0.0(typescript@5.9.3)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 2.0.0(typescript@5.9.3)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) vitest: - specifier: 4.1.0 - version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.0)(@vitest/ui@4.1.0)(happy-dom@20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: 4.1.2 + version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.2)(@vitest/ui@4.1.2)(happy-dom@20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) webdriverio: - specifier: 9.26.1 - version: 9.26.1(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.27.0 + version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/ckeditor5-footnotes: devDependencies: @@ -1203,17 +1203,17 @@ importers: specifier: 5.1.0 version: 5.1.0(@babel/core@7.29.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.12.0)(bufferutil@4.0.9)(esbuild@0.27.4)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': - specifier: 8.57.1 - version: 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.57.2 + version: 8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.57.1 - version: 8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.57.2 + version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) '@vitest/browser': - specifier: 4.1.0 - version: 4.1.0(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.0) + specifier: 4.1.2 + version: 4.1.2(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.2) '@vitest/coverage-istanbul': - specifier: 4.1.0 - version: 4.1.0(vitest@4.1.0) + specifier: 4.1.2 + version: 4.1.2(vitest@4.1.2) ckeditor5: specifier: 47.6.1 version: 47.6.1 @@ -1230,11 +1230,11 @@ importers: specifier: 16.4.0 version: 16.4.0 stylelint: - specifier: 17.5.0 - version: 17.5.0(typescript@5.9.3) + specifier: 17.6.0 + version: 17.6.0(typescript@5.9.3) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(stylelint@17.5.0(typescript@5.9.3)) + version: 14.0.0(stylelint@17.6.0(typescript@5.9.3)) ts-node: specifier: 10.9.2 version: 10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.12.0)(typescript@5.9.3) @@ -1243,13 +1243,13 @@ importers: version: 5.9.3 vite-plugin-svgo: specifier: 2.0.0 - version: 2.0.0(typescript@5.9.3)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 2.0.0(typescript@5.9.3)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) vitest: - specifier: 4.1.0 - version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.0)(@vitest/ui@4.1.0)(happy-dom@20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: 4.1.2 + version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.2)(@vitest/ui@4.1.2)(happy-dom@20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) webdriverio: - specifier: 9.26.1 - version: 9.26.1(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.27.0 + version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/ckeditor5-keyboard-marker: devDependencies: @@ -1263,17 +1263,17 @@ importers: specifier: 5.1.0 version: 5.1.0(@babel/core@7.29.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.12.0)(bufferutil@4.0.9)(esbuild@0.27.4)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': - specifier: 8.57.1 - version: 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.57.2 + version: 8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.57.1 - version: 8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.57.2 + version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) '@vitest/browser': - specifier: 4.1.0 - version: 4.1.0(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.0) + specifier: 4.1.2 + version: 4.1.2(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.2) '@vitest/coverage-istanbul': - specifier: 4.1.0 - version: 4.1.0(vitest@4.1.0) + specifier: 4.1.2 + version: 4.1.2(vitest@4.1.2) ckeditor5: specifier: 47.6.1 version: 47.6.1 @@ -1290,11 +1290,11 @@ importers: specifier: 16.4.0 version: 16.4.0 stylelint: - specifier: 17.5.0 - version: 17.5.0(typescript@5.9.3) + specifier: 17.6.0 + version: 17.6.0(typescript@5.9.3) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(stylelint@17.5.0(typescript@5.9.3)) + version: 14.0.0(stylelint@17.6.0(typescript@5.9.3)) ts-node: specifier: 10.9.2 version: 10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.12.0)(typescript@5.9.3) @@ -1303,13 +1303,13 @@ importers: version: 5.9.3 vite-plugin-svgo: specifier: 2.0.0 - version: 2.0.0(typescript@5.9.3)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 2.0.0(typescript@5.9.3)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) vitest: - specifier: 4.1.0 - version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.0)(@vitest/ui@4.1.0)(happy-dom@20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: 4.1.2 + version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.2)(@vitest/ui@4.1.2)(happy-dom@20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) webdriverio: - specifier: 9.26.1 - version: 9.26.1(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.27.0 + version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/ckeditor5-math: dependencies: @@ -1330,17 +1330,17 @@ importers: specifier: 5.1.0 version: 5.1.0(@babel/core@7.29.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.12.0)(bufferutil@4.0.9)(esbuild@0.27.4)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': - specifier: 8.57.1 - version: 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.57.2 + version: 8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.57.1 - version: 8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.57.2 + version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) '@vitest/browser': - specifier: 4.1.0 - version: 4.1.0(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.0) + specifier: 4.1.2 + version: 4.1.2(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.2) '@vitest/coverage-istanbul': - specifier: 4.1.0 - version: 4.1.0(vitest@4.1.0) + specifier: 4.1.2 + version: 4.1.2(vitest@4.1.2) ckeditor5: specifier: 47.6.1 version: 47.6.1 @@ -1357,11 +1357,11 @@ importers: specifier: 16.4.0 version: 16.4.0 stylelint: - specifier: 17.5.0 - version: 17.5.0(typescript@5.9.3) + specifier: 17.6.0 + version: 17.6.0(typescript@5.9.3) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(stylelint@17.5.0(typescript@5.9.3)) + version: 14.0.0(stylelint@17.6.0(typescript@5.9.3)) ts-node: specifier: 10.9.2 version: 10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.12.0)(typescript@5.9.3) @@ -1370,13 +1370,13 @@ importers: version: 5.9.3 vite-plugin-svgo: specifier: 2.0.0 - version: 2.0.0(typescript@5.9.3)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 2.0.0(typescript@5.9.3)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) vitest: - specifier: 4.1.0 - version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.0)(@vitest/ui@4.1.0)(happy-dom@20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: 4.1.2 + version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.2)(@vitest/ui@4.1.2)(happy-dom@20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) webdriverio: - specifier: 9.26.1 - version: 9.26.1(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.27.0 + version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/ckeditor5-mermaid: dependencies: @@ -1397,17 +1397,17 @@ importers: specifier: 5.1.0 version: 5.1.0(@babel/core@7.29.0)(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.12.0)(bufferutil@4.0.9)(esbuild@0.27.4)(utf-8-validate@6.0.5) '@typescript-eslint/eslint-plugin': - specifier: 8.57.1 - version: 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.57.2 + version: 8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.57.1 - version: 8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.57.2 + version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) '@vitest/browser': - specifier: 4.1.0 - version: 4.1.0(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.0) + specifier: 4.1.2 + version: 4.1.2(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.2) '@vitest/coverage-istanbul': - specifier: 4.1.0 - version: 4.1.0(vitest@4.1.0) + specifier: 4.1.2 + version: 4.1.2(vitest@4.1.2) ckeditor5: specifier: 47.6.1 version: 47.6.1 @@ -1424,11 +1424,11 @@ importers: specifier: 16.4.0 version: 16.4.0 stylelint: - specifier: 17.5.0 - version: 17.5.0(typescript@5.9.3) + specifier: 17.6.0 + version: 17.6.0(typescript@5.9.3) stylelint-config-ckeditor5: specifier: '>=9.1.0' - version: 14.0.0(stylelint@17.5.0(typescript@5.9.3)) + version: 14.0.0(stylelint@17.6.0(typescript@5.9.3)) ts-node: specifier: 10.9.2 version: 10.9.2(@swc/core@1.11.29(@swc/helpers@0.5.17))(@types/node@24.12.0)(typescript@5.9.3) @@ -1437,13 +1437,13 @@ importers: version: 5.9.3 vite-plugin-svgo: specifier: 2.0.0 - version: 2.0.0(typescript@5.9.3)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + version: 2.0.0(typescript@5.9.3)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) vitest: - specifier: 4.1.0 - version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.0)(@vitest/ui@4.1.0)(happy-dom@20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + specifier: 4.1.2 + version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.2)(@vitest/ui@4.1.2)(happy-dom@20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) webdriverio: - specifier: 9.26.1 - version: 9.26.1(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.27.0 + version: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/codemirror: dependencies: @@ -1491,85 +1491,85 @@ importers: version: 6.40.0 '@fsegurai/codemirror-theme-abcdef': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-abyss': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-android-studio': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-andromeda': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-basic-dark': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-basic-light': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-cobalt2': specifier: 6.0.3 - version: 6.0.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.0.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-forest': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-github-dark': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-github-light': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-gruvbox-dark': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-gruvbox-light': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-material-dark': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-material-light': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-monokai': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-nord': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-palenight': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-solarized-dark': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-solarized-light': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-tokyo-night-day': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-tokyo-night-storm': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-volcano': specifier: 6.2.3 - version: 6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-vscode-dark': specifier: 6.2.4 - version: 6.2.4(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.4(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@fsegurai/codemirror-theme-vscode-light': specifier: 6.2.4 - version: 6.2.4(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) + version: 6.2.4(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3) '@replit/codemirror-indentation-markers': specifier: 6.5.3 - version: 6.5.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0) + version: 6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0) '@replit/codemirror-lang-nix': specifier: 6.0.1 - version: 6.0.1(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.2) + version: 6.0.1(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.2) '@replit/codemirror-vim': specifier: 6.3.0 - version: 6.3.0(@codemirror/commands@6.10.3)(@codemirror/language@6.12.2)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0) + version: 6.3.0(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0) '@ssddanbrown/codemirror-lang-smarty': specifier: 1.0.0 version: 1.0.0 @@ -1644,8 +1644,8 @@ importers: specifier: 7.1.0 version: 7.1.0 katex: - specifier: 0.16.40 - version: 0.16.40 + specifier: 0.16.43 + version: 0.16.43 mermaid: specifier: 11.13.0 version: 11.13.0 @@ -1657,11 +1657,11 @@ importers: specifier: workspace:* version: link:../ckeditor5 '@typescript-eslint/eslint-plugin': - specifier: 8.57.1 - version: 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.57.2 + version: 8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': - specifier: 8.57.1 - version: 8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + specifier: 8.57.2 + version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) dotenv: specifier: 17.3.1 version: 17.3.1 @@ -1711,11 +1711,14 @@ importers: specifier: 1.0.3 version: 1.0.3 i18next: - specifier: 25.10.3 - version: 25.10.3(typescript@5.9.3) + specifier: 25.10.10 + version: 25.10.10(typescript@5.9.3) mime-types: specifier: 3.0.2 version: 3.0.2 + node-html-parser: + specifier: 7.1.0 + version: 7.1.0 sanitize-filename: specifier: 1.6.4 version: 1.6.4 @@ -2091,6 +2094,9 @@ packages: '@blazediff/core@1.9.1': resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==} + '@borewit/text-codec@0.2.2': + resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} + '@braintree/sanitize-url@6.0.2': resolution: {integrity: sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==} @@ -2463,8 +2469,8 @@ packages: '@codemirror/lang-xml@6.1.0': resolution: {integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==} - '@codemirror/language@6.12.2': - resolution: {integrity: sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg==} + '@codemirror/language@6.12.3': + resolution: {integrity: sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==} '@codemirror/legacy-modes@6.5.2': resolution: {integrity: sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==} @@ -3352,7 +3358,7 @@ packages: '@fsegurai/codemirror-theme-abcdef@6.2.3': resolution: {integrity: sha512-uZGUe50mfGuHFiEzovScIxgxE1jARrQRNjH/6BA7O4UtBuoCGQZhPhyFv850L3gGRxK+LnWJHAAgnxHqJxW1Yw==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3360,7 +3366,7 @@ packages: '@fsegurai/codemirror-theme-abyss@6.2.3': resolution: {integrity: sha512-yU6lXHNM9mHGexMT9cNi45ma/vaXtPAwI8pP9yQt7M6Bh8WOeycDzd3WDpOcQaBleS26fCSfeNBdixhevP3Lbg==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3368,7 +3374,7 @@ packages: '@fsegurai/codemirror-theme-android-studio@6.2.3': resolution: {integrity: sha512-rAQH1Hz78RHdXz2p90S8efqe9zq7fR8wWFfTihaCOHDCUUPTDziuTcOSZfmLM9jOL1ed6yjg4n+HAOEf6FfMDg==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3376,7 +3382,7 @@ packages: '@fsegurai/codemirror-theme-andromeda@6.2.3': resolution: {integrity: sha512-YCFCgbY9OWOajrsIwmHjnKwbB3IDS2lxs+4CetrqZehZJ9vAkSAW98Zztig1QtZbPaO3x9pdMuD4B6i0BxtDoA==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3384,7 +3390,7 @@ packages: '@fsegurai/codemirror-theme-basic-dark@6.2.3': resolution: {integrity: sha512-08d09Yn9Ic8mjCzrBQQhtws/HM+8B00bRV9FqW+GaIQwSOFmn17FsvzuLJQyervcKAkTzmKaLPjp2D3Y+2K8EQ==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3392,7 +3398,7 @@ packages: '@fsegurai/codemirror-theme-basic-light@6.2.3': resolution: {integrity: sha512-rkHCj1U3OwNAqLLi2xti47u3Fq6gDiSEKmQsAOwIADJKnnwU2LeAwCPqSEa7sUVlavFusjDvt5L/SmGjb10vWg==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3400,7 +3406,7 @@ packages: '@fsegurai/codemirror-theme-cobalt2@6.0.3': resolution: {integrity: sha512-t/vnO5NRWUgvKKews0FoN9OQxKthSg+fNDfl9tMNyC22/QL55lXuScmzV9qADRcugbLv7CT6m0tgurSBSc9QKg==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3408,7 +3414,7 @@ packages: '@fsegurai/codemirror-theme-forest@6.2.3': resolution: {integrity: sha512-cn+GMBkHtp70qLEHfvjXyR1nWdA1KDeC/hJOWWv/FmpUrFfq61oM2GWZL+yqDINrTO709lfcDu+gcAjnpOtfSg==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3416,7 +3422,7 @@ packages: '@fsegurai/codemirror-theme-github-dark@6.2.3': resolution: {integrity: sha512-EBBbSK0iZn4w3T5fNx9ZV5ur1X7N+RVfTdWzrPmz1LJ90UQshTFpalm9oYMJ/5/98zwP9p99bsLrKE1r2x6A6w==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3424,7 +3430,7 @@ packages: '@fsegurai/codemirror-theme-github-light@6.2.3': resolution: {integrity: sha512-vbwyznBoTrLQdWvQ6/vjIpoDojd7VIMK+sQnMXkKOjXbm5cGul6A3mqM2RSt9Z5NhIRikmxKkApflvWOJrDuWA==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3432,7 +3438,7 @@ packages: '@fsegurai/codemirror-theme-gruvbox-dark@6.2.3': resolution: {integrity: sha512-Z7fZ8aNkN/ib5nfT2CJHod1qqXlz5jpgCtiKV0jaQXq7g8axFY8iION94+A5AwrRk1VJbAyGxlAIDIXTqQA70g==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3440,7 +3446,7 @@ packages: '@fsegurai/codemirror-theme-gruvbox-light@6.2.3': resolution: {integrity: sha512-lmyiiU1Q9WPuhxJsSAkPLcocyx+odkYvG4FcJYi3Mbn2e/wwYSrHckS8uccDU3zJmWthM9sVbtVd0LpRxU/Jmw==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3448,7 +3454,7 @@ packages: '@fsegurai/codemirror-theme-material-dark@6.2.3': resolution: {integrity: sha512-8jw5/mLF/wIGKJ8rmxVWyXnhrRgqm+F7L49UlWd5VHgvny0XFJ45I3Tl8xD3i2kWqDm3TF+C/p86Mq9J6TjWVA==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3456,7 +3462,7 @@ packages: '@fsegurai/codemirror-theme-material-light@6.2.3': resolution: {integrity: sha512-/t29bGYA8Yyd94Ecbq/gZ/CA9QKALfr5JXF+G2p31CROEYVG3iV1F4Fp8TteTHAyP3np0dachHcSRNlNB0wftw==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3464,7 +3470,7 @@ packages: '@fsegurai/codemirror-theme-monokai@6.2.3': resolution: {integrity: sha512-kHiansKaONRuTU76zavJ9KRJnITbg6y6PXBVX0ZEUSf6WSdTAAgubYay+M6tVNMAirwAdkHnIjjZbh8GXeg01g==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3472,7 +3478,7 @@ packages: '@fsegurai/codemirror-theme-nord@6.2.3': resolution: {integrity: sha512-ZwMY2kKDlBUuDXDqH81TCArlIq37qBMuYIAJkpVm52dJ89AtOFk2VwWuRkVsJr3g0Fs76kOBQbyM0ifmMOSTPg==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3480,7 +3486,7 @@ packages: '@fsegurai/codemirror-theme-palenight@6.2.3': resolution: {integrity: sha512-SE5FmZldO2YwNsOrd8fwCV8VBekZDvt5zF7ZQa+VeGnsXf6ekhtdqBa9nPt1gbB4QOoz3lbJ2uzpUVePr33JpA==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3488,7 +3494,7 @@ packages: '@fsegurai/codemirror-theme-solarized-dark@6.2.3': resolution: {integrity: sha512-7bM3Pe2C8Pad3ejVdecrOYrIHt2MwM1N9o7PXYImuUbO/guHCsyjpvyaN7Y5J+EwOBhxYRbR4TyWMA3AiwlU7w==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3496,7 +3502,7 @@ packages: '@fsegurai/codemirror-theme-solarized-light@6.2.3': resolution: {integrity: sha512-UzXetG+rtCY3RXz1QmfPyCQaxfmCXs92UVp+bSGtbXaS+muf/GpKpXlovwMmMzIpLeLKavIHJX43uQuptZSF7g==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3504,7 +3510,7 @@ packages: '@fsegurai/codemirror-theme-tokyo-night-day@6.2.3': resolution: {integrity: sha512-IJ6gtD4T4JBowEJsiRBvRtZhCzrMGHA4K3b7nEXVUbIZbPOWFzts3kWWITR8FgfYXX0AyJz31UHu7ST/kkcF0A==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3512,7 +3518,7 @@ packages: '@fsegurai/codemirror-theme-tokyo-night-storm@6.2.3': resolution: {integrity: sha512-1Nhke42cQ2HHH50ia5Ya8LswVlRSnYjXfl5wfXcTLMSGmA4RxiiyN6OsDPHFVYjCdGJEzTXkX20N6h5HQVgO6Q==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3520,7 +3526,7 @@ packages: '@fsegurai/codemirror-theme-volcano@6.2.3': resolution: {integrity: sha512-HHspLdvKoLC12XWy/mhcfeSyGzUlpMwc9ildoDcw8/SCXB6twEpMx31Q/YHThnIVUYrHIzF3KV/xWurICMunxQ==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3528,7 +3534,7 @@ packages: '@fsegurai/codemirror-theme-vscode-dark@6.2.4': resolution: {integrity: sha512-Vjr1658LIYgvC3ALVeqMhSMLA1ljoLmQpdAq8JuFdH8HkENfCXPmLX498Lnab3UYyn/b3qZAbHtAF+wQ64iMkg==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3536,7 +3542,7 @@ packages: '@fsegurai/codemirror-theme-vscode-light@6.2.4': resolution: {integrity: sha512-to840rPueVX6UV4YLEymGhUzzyuunY15LfGgm4Vq6hY2NmCfwYhQ/KfTcJ3XrZXiE80MrHjOq1FDcXl17UgvIQ==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/highlight': 1.2.3 @@ -3582,8 +3588,8 @@ packages: '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} - '@gerrit0/mini-shiki@3.17.0': - resolution: {integrity: sha512-Bpf6WuFar20ZXL6qU6VpVl4bVQfyyYiX+6O4xrns4nkU3Mr8paeupDbS1HENpcLOYj7pN4Rkd/yCaPA0vQwKww==} + '@gerrit0/mini-shiki@3.23.0': + resolution: {integrity: sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==} '@hapi/hoek@9.3.0': resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} @@ -4166,11 +4172,6 @@ packages: '@mdn/browser-compat-data@5.7.6': resolution: {integrity: sha512-7xdrMX0Wk7grrTZQwAoy1GkvPMFoizStUoL+VmtUkAxegbCCec+3FKwOM6yc/uGU5+BEczQHXAlWiqvM8JeENg==} - '@mermaid-js/layout-elk@0.2.0': - resolution: {integrity: sha512-vjjYGnCCjYlIA/rR7M//eFi0rHM6dsMyN1JQKfckpt30DTC/esrw36hcrvA2FNPHaqh3Q/SyBWzddyaky8EtUQ==} - peerDependencies: - mermaid: 11.13.0 - '@mermaid-js/layout-elk@0.2.1': resolution: {integrity: sha512-MX9jwhMyd5zDcFsYcl3duDUkKhjVRUCGEQrdCeNV5hCIR6+3FuDDbRbFmvVbAu15K1+juzsYGG+K8MDvCY1Amg==} peerDependencies: @@ -4447,8 +4448,8 @@ packages: resolution: {integrity: sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA==} engines: {node: '>=14'} - '@oxc-project/types@0.120.0': - resolution: {integrity: sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==} + '@oxc-project/types@0.122.0': + resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} '@panva/asn1.js@1.0.0': resolution: {integrity: sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==} @@ -4586,11 +4587,6 @@ packages: '@preact/signals-core@1.14.0': resolution: {integrity: sha512-AowtCcCU/33lFlh1zRFf/u+12rfrhtNakj7UpaGEsmMwUKpKWMVvcktOGcwBBNiB4lWrZWc01LhiyyzVklJyaQ==} - '@preact/signals@2.5.1': - resolution: {integrity: sha512-VPjk5YFt7i11Fi4UK0tzaEe5xLwfhUxXL3l89ocxQ5aPz7bRo8M5+N73LjBMPklyXKYKz6YsNo4Smp8n6nplng==} - peerDependencies: - preact: 10.29.0 - '@preact/signals@2.8.2': resolution: {integrity: sha512-gym5yoa64c+0w2kL7zRAAjY548qzWXbbuOfjsK9F1nWrEqooDwyWnih5SNdonjhQSp27zUqYh7UrxIRnkCyFCA==} peerDependencies: @@ -5276,33 +5272,36 @@ packages: '@redocly/ajv@8.18.0': resolution: {integrity: sha512-F+LMD2IDIXuHxgpLJh3nkLj9+tSaEzoUWd+7fONGq5pe2169FUDjpEkOfEpoGLz1sbZni/69p07OsecNfAOpqA==} - '@redocly/cli@2.24.1': - resolution: {integrity: sha512-GTAKMPtyvO7vn3CrSp8Q5SJlMUr8q6wgMN/J4K5owphyp5gOQCZqMySyWcq+V5RPPXkTuIHZYEzgnecB6RF2bQ==} + '@redocly/cli-otel@0.1.2': + resolution: {integrity: sha512-Bg7BoO5t1x3lVK+KhA5aGPmeXpQmdf6WtTYHhelKJCsQ+tRMiJoFAQoKHoBHAoNxXrhlS3K9lKFLHGmtxsFQfA==} + + '@redocly/cli@2.25.1': + resolution: {integrity: sha512-i17Riz3CO3KMhaxMW3OPUw8ipeipnuk4wQTRu4zi2pCA/zI6GkeO/Ku7pdEwPd1LuPIR/RNBTp0YPahmyGy+2A==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} hasBin: true '@redocly/config@0.22.2': resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} - '@redocly/config@0.44.1': - resolution: {integrity: sha512-l6/ZE+/RBfNDdhzltau6cbW8+k5PgJbJBMqaBrlQlZQlmGBHMxqGyDaon4dPLj0jdi37gsMQ3yf95JBY/vaDSg==} + '@redocly/config@0.44.2': + resolution: {integrity: sha512-/77Y/C4aDCa0XkRYffISHH1K816KDmmFY1LX3TwODJ4Li+DFhvyWiMm7ofFCEssKwNjs3fzGW/ISKOsDFdirJw==} '@redocly/openapi-core@1.34.5': resolution: {integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==} engines: {node: '>=18.17.0', npm: '>=9.5.0'} - '@redocly/openapi-core@2.24.1': - resolution: {integrity: sha512-Iqc/4DI/CIQkKys8HRHkX+bpF/UosVUE7lc7tuxIOKzVIOk5QhQMglbd2yzbAYgJF7YAtCDDAKWosvXnvTTWsA==} + '@redocly/openapi-core@2.25.1': + resolution: {integrity: sha512-6n1gkzvqLhU1/rCSDvLqSrceiXHMw5YTqrao63nb/SLxPzbhBEW61D9VAvu2dn5HGsanYc9QH0j73W/D3eYsVw==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} - '@redocly/respect-core@2.24.1': - resolution: {integrity: sha512-WMeg9TmAc0ZINp6Tza+ZWhMuIBM28us6ZyLj5DKWZhkBZhKaTNhXlmTYES11uM35eie+mYZStTov4vXYL//wqg==} + '@redocly/respect-core@2.25.1': + resolution: {integrity: sha512-iF/7uP/kI5BPzdp4e2Ug3UQWU1ghYJoAPURFcqPvtRc0S4G3olyecJggOhDQqyC8iDHvN7SJNmm/O3VWn4QU4Q==} engines: {node: '>=22.12.0 || >=20.19.0 <21.0.0', npm: '>=10'} '@replit/codemirror-indentation-markers@6.5.3': resolution: {integrity: sha512-hL5Sfvw3C1vgg7GolLe/uxX5T3tmgOA3ZzqlMv47zjU1ON51pzNWiVbS22oh6crYhtVhv8b3gdXwoYp++2ilHw==} peerDependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 @@ -5310,7 +5309,7 @@ packages: resolution: {integrity: sha512-lvzjoYn9nfJzBD5qdm3Ut6G3+Or2wEacYIDJ49h9+19WSChVnxv4ojf+rNmQ78ncuxIt/bfbMvDLMeMP0xze6g==} peerDependencies: '@codemirror/autocomplete': ^6.0.0 - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': ^6.0.0 '@codemirror/view': ^6.0.0 '@lezer/common': 1.5.1 @@ -5321,108 +5320,108 @@ packages: resolution: {integrity: sha512-aTx931ULAMuJx6xLf7KQDOL7CxD+Sa05FktTDrtLaSy53uj01ll3Zf17JdKsriER248oS55GBzg0CfCTjEneAQ==} peerDependencies: '@codemirror/commands': 6.x.x - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/search': 6.x.x '@codemirror/state': 6.x.x '@codemirror/view': 6.x.x - '@rolldown/binding-android-arm64@1.0.0-rc.10': - resolution: {integrity: sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==} + '@rolldown/binding-android-arm64@1.0.0-rc.11': + resolution: {integrity: sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.10': - resolution: {integrity: sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.11': + resolution: {integrity: sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.10': - resolution: {integrity: sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==} + '@rolldown/binding-darwin-x64@1.0.0-rc.11': + resolution: {integrity: sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.10': - resolution: {integrity: sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.11': + resolution: {integrity: sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10': - resolution: {integrity: sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.11': + resolution: {integrity: sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10': - resolution: {integrity: sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.11': + resolution: {integrity: sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.10': - resolution: {integrity: sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.11': + resolution: {integrity: sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10': - resolution: {integrity: sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.11': + resolution: {integrity: sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10': - resolution: {integrity: sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==} + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.11': + resolution: {integrity: sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.10': - resolution: {integrity: sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.11': + resolution: {integrity: sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.10': - resolution: {integrity: sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.11': + resolution: {integrity: sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.10': - resolution: {integrity: sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.11': + resolution: {integrity: sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.10': - resolution: {integrity: sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.11': + resolution: {integrity: sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10': - resolution: {integrity: sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.11': + resolution: {integrity: sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.10': - resolution: {integrity: sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.11': + resolution: {integrity: sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-rc.10': - resolution: {integrity: sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==} + '@rolldown/pluginutils@1.0.0-rc.11': + resolution: {integrity: sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ==} '@rollup/plugin-buble@1.0.3': resolution: {integrity: sha512-QYD9BKkJoof0FdCFeSYYhF6/Y8e0Mnf+098xGgmWOFJ4UPHlWujjqOYeVwEm2hJPOmlR5k7HPUdAjqtOWhN64Q==} @@ -5651,17 +5650,17 @@ packages: '@selderee/plugin-htmlparser2@0.11.0': resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} - '@shikijs/engine-oniguruma@3.17.0': - resolution: {integrity: sha512-flSbHZAiOZDNTrEbULY8DLWavu/TyVu/E7RChpLB4WvKX4iHMfj80C6Hi3TjIWaQtHOW0KC6kzMcuB5TO1hZ8Q==} + '@shikijs/engine-oniguruma@3.23.0': + resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} - '@shikijs/langs@3.17.0': - resolution: {integrity: sha512-icmur2n5Ojb+HAiQu6NEcIIJ8oWDFGGEpiqSCe43539Sabpx7Y829WR3QuUW2zjTM4l6V8Sazgb3rrHO2orEAw==} + '@shikijs/langs@3.23.0': + resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} - '@shikijs/themes@3.17.0': - resolution: {integrity: sha512-/xEizMHLBmMHwtx4JuOkRf3zwhWD2bmG5BRr0IPjpcWpaq4C3mYEuTk/USAEglN0qPrTwEHwKVpSu/y2jhferA==} + '@shikijs/themes@3.23.0': + resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} - '@shikijs/types@3.17.0': - resolution: {integrity: sha512-wjLVfutYWVUnxAjsWEob98xgyaGv0dTEnMZDruU5mRjVN7szcGOfgO+997W2yR6odp+1PtSBNeSITRRTfUzK/g==} + '@shikijs/types@3.23.0': + resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -6064,8 +6063,8 @@ packages: resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} - '@tokenizer/inflate@0.2.7': - resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} + '@tokenizer/inflate@0.4.1': + resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} engines: {node: '>=18'} '@tokenizer/token@0.3.0': @@ -6354,9 +6353,6 @@ packages: '@types/ini@4.1.1': resolution: {integrity: sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==} - '@types/jquery@3.5.33': - resolution: {integrity: sha512-SeyVJXlCZpEki5F0ghuYe+L+PprQta6nRZqhONt9F13dWBtR/ftoaIbdRQ7cis7womE+X2LKhsDdDtkkDhJS6g==} - '@types/jquery@4.0.0': resolution: {integrity: sha512-Z+to+A2VkaHq1DfI2oSwsoCdhCHMpTSgjWzNcbNlRGYzksDBpPUgEcAL+RQjOBJRaLoEAOHXxqDGBVP+BblBwg==} @@ -6509,9 +6505,6 @@ packages: '@types/sinonjs__fake-timers@8.1.5': resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} - '@types/sizzle@2.3.10': - resolution: {integrity: sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==} - '@types/sockjs@0.3.36': resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==} @@ -6586,6 +6579,14 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/eslint-plugin@8.57.2': + resolution: {integrity: sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.57.2 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/parser@8.57.1': resolution: {integrity: sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6593,22 +6594,45 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/parser@8.57.2': + resolution: {integrity: sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/project-service@8.57.1': resolution: {integrity: sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/project-service@8.57.2': + resolution: {integrity: sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/scope-manager@8.57.1': resolution: {integrity: sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.57.2': + resolution: {integrity: sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.57.1': resolution: {integrity: sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/tsconfig-utils@8.57.2': + resolution: {integrity: sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/type-utils@8.57.1': resolution: {integrity: sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6616,16 +6640,33 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/type-utils@8.57.2': + resolution: {integrity: sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/types@8.57.1': resolution: {integrity: sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.57.2': + resolution: {integrity: sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.57.1': resolution: {integrity: sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/typescript-estree@8.57.2': + resolution: {integrity: sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/utils@8.57.1': resolution: {integrity: sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -6633,10 +6674,21 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/utils@8.57.2': + resolution: {integrity: sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/visitor-keys@8.57.1': resolution: {integrity: sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.57.2': + resolution: {integrity: sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} @@ -7194,64 +7246,64 @@ packages: '@upsetjs/venn.js@2.0.0': resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} - '@vitest/browser-webdriverio@4.1.0': - resolution: {integrity: sha512-CAQA8rxHvJpC4v+JoOTmuu4o8SeH+DapZ2Gk5gqaW413Etnq8b4jG0d4pEMVqyiFwgPQ3FVzYCaaLsf4TmLPMw==} + '@vitest/browser-webdriverio@4.1.2': + resolution: {integrity: sha512-5VKfMSq6ZoEAmvVu3sJGkDjEjGuxwk72tOgoNJfJYv+c+UQX1D4UqSdL8kXUMJcTQx1tKeWwQ9Zym0gRdMfyrA==} peerDependencies: - vitest: 4.1.0 + vitest: 4.1.2 webdriverio: '*' - '@vitest/browser@4.1.0': - resolution: {integrity: sha512-tG/iOrgbiHQks0ew7CdelUyNEHkv8NLrt+CqdTivIuoSnXvO7scWMn4Kqo78/UGY1NJ6Hv+vp8BvRnED/bjFdQ==} + '@vitest/browser@4.1.2': + resolution: {integrity: sha512-CwdIf90LNf1Zitgqy63ciMAzmyb4oIGs8WZ40VGYrWkssQKeEKr32EzO8MKUrDPPcPVHFI9oQ5ni2Hp24NaNRQ==} peerDependencies: - vitest: 4.1.0 + vitest: 4.1.2 - '@vitest/coverage-istanbul@4.1.0': - resolution: {integrity: sha512-0+67gA94YToxd+Pc3XgIA/2c8HN2hXNSg3T+1FI4HW7W/2gPitYCtktsY6Ke7vrt5caboMq3TUf0/vwbHRb0og==} + '@vitest/coverage-istanbul@4.1.2': + resolution: {integrity: sha512-WSz7+4a7PcMtMNvIP7AXUMffsq4JrWeJaguC8lg6fSQyGxSfaT4Rf81idqwxTT6qX5kjjZw2t9rAnCRRQobSqw==} peerDependencies: - vitest: 4.1.0 + vitest: 4.1.2 - '@vitest/coverage-v8@4.1.0': - resolution: {integrity: sha512-nDWulKeik2bL2Va/Wl4x7DLuTKAXa906iRFooIRPR+huHkcvp9QDkPQ2RJdmjOFrqOqvNfoSQLF68deE3xC3CQ==} + '@vitest/coverage-v8@4.1.2': + resolution: {integrity: sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==} peerDependencies: - '@vitest/browser': 4.1.0 - vitest: 4.1.0 + '@vitest/browser': 4.1.2 + vitest: 4.1.2 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.1.0': - resolution: {integrity: sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==} + '@vitest/expect@4.1.2': + resolution: {integrity: sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==} - '@vitest/mocker@4.1.0': - resolution: {integrity: sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==} + '@vitest/mocker@4.1.2': + resolution: {integrity: sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==} peerDependencies: msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@4.1.0': - resolution: {integrity: sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==} + '@vitest/pretty-format@4.1.2': + resolution: {integrity: sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==} - '@vitest/runner@4.1.0': - resolution: {integrity: sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==} + '@vitest/runner@4.1.2': + resolution: {integrity: sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==} - '@vitest/snapshot@4.1.0': - resolution: {integrity: sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==} + '@vitest/snapshot@4.1.2': + resolution: {integrity: sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==} - '@vitest/spy@4.1.0': - resolution: {integrity: sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==} + '@vitest/spy@4.1.2': + resolution: {integrity: sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==} - '@vitest/ui@4.1.0': - resolution: {integrity: sha512-sTSDtVM1GOevRGsCNhp1mBUHKo9Qlc55+HCreFT4fe99AHxl1QQNXSL3uj4Pkjh5yEuWZIx8E2tVC94nnBZECQ==} + '@vitest/ui@4.1.2': + resolution: {integrity: sha512-/irhyeAcKS2u6Zokagf9tqZJ0t8S6kMZq4ZG9BHZv7I+fkRrYfQX4w7geYeC2r6obThz39PDxvXQzZX+qXqGeg==} peerDependencies: - vitest: 4.1.0 + vitest: 4.1.2 - '@vitest/utils@4.1.0': - resolution: {integrity: sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==} + '@vitest/utils@4.1.2': + resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==} '@volar/language-core@2.4.13': resolution: {integrity: sha512-MnQJ7eKchJx5Oz+YdbqyFUk8BN6jasdJv31n/7r6/WwlOOv7qzvot6B66887l2ST3bUW4Mewml54euzpJWA6bg==} @@ -7285,27 +7337,27 @@ packages: '@vue/shared@3.5.14': resolution: {integrity: sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ==} - '@wdio/config@9.26.1': - resolution: {integrity: sha512-gzinrualmF0X+UN9ftSTS3s9Xfymny2bROh7VD10j+rVO+qgKqVfGaCseVdpHs+PvZjnSdHr9rDKwNiYvNa09Q==} + '@wdio/config@9.27.0': + resolution: {integrity: sha512-9y8z7ugIbU6ycKrA2SqCpKh1/hobut2rDq9CLt/BNVzSlebBBVOTMiAt1XroZzcPnA7/ZqpbkpOsbpPUaAQuNQ==} engines: {node: '>=18.20.0'} '@wdio/logger@9.18.0': resolution: {integrity: sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==} engines: {node: '>=18.20.0'} - '@wdio/protocols@9.26.1': - resolution: {integrity: sha512-PGmJvUUMAhvs2tgjAdhWSmY1qQxS71a0GCtTJff8Zw35yxlHo0FMrhFCw91BGvWgHZGygJbdTXETFlpvjAZxOw==} + '@wdio/protocols@9.27.0': + resolution: {integrity: sha512-rIk69BsY1+6uU2PEN5FiRpI6K7HJ86YHzZRFBe4iRzKXQgGNk1zWzbdVJIuNFoOWsnmYUkK42KSSOT4Le6EmiQ==} '@wdio/repl@9.16.2': resolution: {integrity: sha512-FLTF0VL6+o5BSTCO7yLSXocm3kUnu31zYwzdsz4n9s5YWt83sCtzGZlZpt7TaTzb3jVUfxuHNQDTb8UMkCu0lQ==} engines: {node: '>=18.20.0'} - '@wdio/types@9.26.1': - resolution: {integrity: sha512-U6JTbwVvDoSHBvFNuE6GbiW4fX0gl7wyrtJVsgv0vYkt4qzssVPFpE19ndBY1PZ59dLWU6llDEgyyTtIcXwSfQ==} + '@wdio/types@9.27.0': + resolution: {integrity: sha512-DQJ+OdRBqUBcQ30DN2Z651hEVh3OoxnlDUSRqlWy9An2AY6v9rYWTj825B6zsj5pLLEToYO1tfwWq0ab183pXg==} engines: {node: '>=18.20.0'} - '@wdio/utils@9.26.1': - resolution: {integrity: sha512-EfXS438cLc54+XQFcFcbcTWLJ4VSEpjtEHQ/v3QFB+mbBezJUC15rf/zEG4fFjhP1ENAAmZZtjc/l6bGEFFk2A==} + '@wdio/utils@9.27.0': + resolution: {integrity: sha512-fUasd5OKJTy2seJfWnYZ9xlxTtY0p/Kyeuh7Tbb8kcofBqmBi2fTvM3sfZlo1tGQX9yCh+IS2N7hlfyFMmuZ+w==} engines: {node: '>=18.20.0'} '@webassemblyjs/ast@1.14.1': @@ -7398,11 +7450,8 @@ packages: resolution: {integrity: sha512-0fztsk/0ryJ+2PPr9EyXS5/Co7OK8q3zY/xOoozEWaUsL5x+C0cyZ4YyMuUffOO2Dx/rAdq4JMPqW0VUtm+vzA==} engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=18.0.0'} - '@zumer/snapdom@2.0.1': - resolution: {integrity: sha512-78/qbYl2FTv4H6qaXcNfAujfIOSzdvs83NW63VbyC9QA3sqNPfPvhn4xYMO6Gy11hXwJUEhd0z65yKiNzDwy9w==} - - '@zumer/snapdom@2.5.0': - resolution: {integrity: sha512-cjk1VTc6D/tRWHdvm/UwzetR7FSxDobYJ7Db1b5uuLhVTU0br8Y8hJJ1+TzArJni4BRvHgU60Q0MR+RKhRqyDg==} + '@zumer/snapdom@2.6.0': + resolution: {integrity: sha512-JpPPkuMzozRVX6KArgCiMgLpgVW82kWgyoFk5DWGKE5msWGEshXEUdQHLLEyZRO7qioI1pI+yaBJz81tEP9gPg==} abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} @@ -8514,12 +8563,6 @@ packages: copy-anything@2.0.6: resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} - copy-webpack-plugin@13.0.1: - resolution: {integrity: sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==} - engines: {node: '>= 18.12.0'} - peerDependencies: - webpack: ^5.1.0 - copy-webpack-plugin@14.0.0: resolution: {integrity: sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==} engines: {node: '>= 20.9.0'} @@ -8550,15 +8593,6 @@ packages: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} - cosmiconfig@9.0.0: - resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true - cosmiconfig@9.0.1: resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} engines: {node: '>=14'} @@ -8673,10 +8707,6 @@ packages: resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - css-tree@3.1.0: - resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - css-tree@3.2.1: resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} @@ -9136,6 +9166,10 @@ packages: resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} engines: {node: '>=0.3.1'} + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + dir-compare@4.2.0: resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} @@ -9301,8 +9335,8 @@ packages: resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==} engines: {node: '>=8.0.0'} - electron@41.0.3: - resolution: {integrity: sha512-IDjx8liW1q+r7+MOip5W1Eo1eMwJzVObmYrd9yz2dPCkS7XlgLq3qPVMR80TpiROFp73iY30kTzMdpA6fEVs3A==} + electron@41.0.4: + resolution: {integrity: sha512-rO08CxnAsAkKPFj3OZnxFkKrlnpSL3OCOewMDj5kaohVo++7e8hIT5Sl+tNl9WkNKiLvfZSW180ueA9s5zh9dg==} engines: {node: '>= 12.20.55'} hasBin: true @@ -9375,10 +9409,6 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - entities@6.0.0: - resolution: {integrity: sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==} - engines: {node: '>=0.12'} - entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} @@ -9704,8 +9734,8 @@ packages: resolution: {integrity: sha512-FXcAcs7Nf/hF73Mzh0WDWPwaOlsEUL/fCHW3L4wU6DH79dypsaxmbnAildCLniFs7HQuuvoiR6bjNVUvGuTb5g==} engines: {node: '>=6.0.0'} - express-openid-connect@2.19.4: - resolution: {integrity: sha512-3YFPZ4MgUPhwfHbCaJKEij7uTc0vF4KpGKsuc3D1IhNMooiP6w8p1HBaaDQOE2KaAas22UghxVECxPpcC/gfOA==} + express-openid-connect@2.20.0: + resolution: {integrity: sha512-eovUMbYe40iKfcJoS/Yxrnz1SppQ8kW7QG1bxI/7wRz6Fl8MiVrCZkdiGkEmF2wwI9lL050LFCx+Mgw2/5TjmQ==} engines: {node: ^10.19.0 || >=12.0.0 < 13 || >=13.7.0 < 14 || >= 14.2.0} peerDependencies: express: '>= 4.17.0' @@ -9842,9 +9872,9 @@ packages: resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==} engines: {node: '>=10'} - file-type@20.5.0: - resolution: {integrity: sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==} - engines: {node: '>=18'} + file-type@21.3.4: + resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==} + engines: {node: '>=20'} file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -9905,12 +9935,12 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - flatted@3.4.0: resolution: {integrity: sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw==} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + float-tooltip@1.7.5: resolution: {integrity: sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==} engines: {node: '>=12'} @@ -9935,10 +9965,6 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - force-graph@1.51.0: - resolution: {integrity: sha512-aTnihCmiMA0ItLJLCbrQYS9mzriopW24goFPgUnKAAmAlPogTSmFWqoBPMXzIfPb7bs04Hur5zEI4WYgLW3Sig==} - engines: {node: '>=12'} - force-graph@1.51.2: resolution: {integrity: sha512-zZNdMqx8qIQGurgnbgYIUsdXxSfvhfRSIdncsKGv/twUOZpwCsk9hPHmdjdcme1+epATgb41G0rkIGHJ0Wydng==} engines: {node: '>=12'} @@ -10244,10 +10270,6 @@ packages: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} - globals@17.0.0: - resolution: {integrity: sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==} - engines: {node: '>=18'} - globals@17.4.0: resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} engines: {node: '>=18'} @@ -10304,12 +10326,8 @@ packages: engines: {node: '>=0.4.7'} hasBin: true - happy-dom@20.0.11: - resolution: {integrity: sha512-QsCdAUHAmiDeKeaNojb1OHOPF7NjcWPBR7obdu3NwH2a/oyQaLg5d0aaCy/9My6CdPChYF07dvz5chaXBGaD4g==} - engines: {node: '>=20.0.0'} - - happy-dom@20.8.4: - resolution: {integrity: sha512-GKhjq4OQCYB4VLFBzv8mmccUadwlAusOZOI7hC1D9xDIT5HhzkJK17c4el2f6R6C715P9xB4uiMxeKUa2nHMwQ==} + happy-dom@20.8.8: + resolution: {integrity: sha512-5/F8wxkNxYtsN0bXfMwIyNLZ9WYsoOYPbmoluqVJqv8KBUbcyKZawJ7uYK4WTX8IHBLYv+VXIwfeNDPy1oKMwQ==} engines: {node: '>=20.0.0'} has-bigints@1.1.0: @@ -10593,18 +10611,10 @@ packages: i18next-http-backend@3.0.2: resolution: {integrity: sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==} - i18next@25.10.3: - resolution: {integrity: sha512-9XCjFgF7q4wNdmy7RFcDBIx1ndSXU3QwtbmqPjOdUxYxU9gbovJzZUY5Mb3ejWmDhxMl6Wmr2OenWOU3uyy6VQ==} + i18next@25.10.10: + resolution: {integrity: sha512-cqUW2Z3EkRx7NqSyywjkgCLK7KLCL6IFVFcONG7nVYIJ3ekZ1/N5jUsihHV6Bq37NfhgtczxJcxduELtjTwkuQ==} peerDependencies: - typescript: ^5 - peerDependenciesMeta: - typescript: - optional: true - - i18next@25.7.3: - resolution: {integrity: sha512-2XaT+HpYGuc2uTExq9TVRhLsso+Dxym6PWaKpn36wfBmTI779OQ7iP/XaZHzrnGyzU4SHpFrTYLKfVyBfAhVNA==} - peerDependencies: - typescript: ^5 + typescript: ^5 || ^6 peerDependenciesMeta: typescript: optional: true @@ -10662,8 +10672,8 @@ packages: engines: {node: '>=6.9.0'} hasBin: true - image-type@6.0.0: - resolution: {integrity: sha512-efpcYd/E9A7a+oanft11ceIbO9Aw0iszfJ7Qfh4QLWl2Ulsth9nnllV/L1TmzKwlQ2O5FuT08vy5zxLnGxZe8w==} + image-type@6.1.0: + resolution: {integrity: sha512-SBKn3TelGQdOLAROwl99gN7HUWWozZCHQIH5PmvpJUA/u5+BJBCsVz7lSO+Q3Kl2K/uw9QU6GDcksA16RtuzSw==} engines: {node: '>=20'} immediate@3.0.6: @@ -11156,9 +11166,6 @@ packages: peerDependencies: jquery: '>=1.9' - jquery@3.7.1: - resolution: {integrity: sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==} - jquery@4.0.0: resolution: {integrity: sha512-TXCHVR3Lb6TZdtw1l3RTLf8RBWVGexdxL6AC8/e0xZKEpBflBsjh9/8LXw+dkNFuOyW9B7iB3O1sP7hS0Kiacg==} @@ -11317,12 +11324,8 @@ packages: engines: {node: '>= 10'} hasBin: true - katex@0.16.27: - resolution: {integrity: sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==} - hasBin: true - - katex@0.16.40: - resolution: {integrity: sha512-1DJcK/L05k1Y9Gf7wMcyuqFOL6BiY3vY0CFcAM/LPRN04NALxcl6u7lOWNsp3f/bCHWxigzQl6FbR95XJ4R84Q==} + katex@0.16.43: + resolution: {integrity: sha512-K7NL5JtGrFEglipOAjY4UYA69CnTuNmjArxeXF6+bw7h2OGySUPv6QWRjfb1gmutJ4Mw/qLeBqiROOEDULp4nA==} hasBin: true kdbush@4.0.2: @@ -11680,10 +11683,6 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.4: - resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} - engines: {node: 20 || >=22} - lru-cache@11.2.7: resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} engines: {node: 20 || >=22} @@ -11764,8 +11763,8 @@ packages: mark.js@8.11.1: resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} - markdown-it@14.1.0: - resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + markdown-it@14.1.1: + resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==} hasBin: true markdown-table@3.0.4: @@ -11776,11 +11775,6 @@ packages: engines: {node: '>= 20'} hasBin: true - marked@17.0.1: - resolution: {integrity: sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==} - engines: {node: '>= 20'} - hasBin: true - marked@17.0.5: resolution: {integrity: sha512-6hLvc0/JEbRjRgzI6wnT2P1XuM1/RrrDEX0kPt0N7jGm1133g6X7DlxFasUIx+72aKAr904GTxhSLDrd5DIlZg==} engines: {node: '>= 20'} @@ -11856,9 +11850,6 @@ packages: mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - mdn-data@2.12.2: - resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} - mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} @@ -12044,9 +12035,6 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} - mind-elixir@5.4.0: - resolution: {integrity: sha512-yxXajDWoSF6id8b2LKxlhXidxH/v6mx4JV+isrtsZ62RGCMsRbjUMFO9xOfTVH8vyxWhsbCkiAP6/i5hqbyk6w==} - mind-elixir@5.9.3: resolution: {integrity: sha512-OTTO6ofvDuzN4fxuBngqhQLJmIqModr2NgQb4OY+5DGRt54B+YNAvNnlspYwUXXGq2Rbht1DhXgeU4dr4CUy6Q==} @@ -12076,10 +12064,6 @@ packages: resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} engines: {node: '>=10'} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - minimatch@9.0.9: resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} @@ -12762,8 +12746,8 @@ packages: pako@2.1.0: resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} - panzoom@9.4.3: - resolution: {integrity: sha512-xaxCpElcRbQsUtIdwlrZA90P90+BHip4Vda2BC8MEb4tkI05PmR6cKECdqUCZ85ZvBHjpI9htJrZBxV5Gp/q/w==} + panzoom@9.4.4: + resolution: {integrity: sha512-r1KfkNZvsBw59IPq7Yy+GWZnZE1YCG/t7aG6caSkij/TBqdxTzmxNTm/lHf3h6qlVMFisIGIO+lgS2Ym23PIoA==} parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} @@ -13546,30 +13530,14 @@ packages: peerDependencies: react: ^19.2.4 - react-i18next@16.5.1: - resolution: {integrity: sha512-Hks6UIRZWW4c+qDAnx1csVsCGYeIR4MoBGQgJ+NUoNnO6qLxXuf8zu0xdcinyXUORgGzCdRsexxO1Xzv3sTdnw==} + react-i18next@16.6.6: + resolution: {integrity: sha512-ZgL2HUoW34UKUkOV7uSQFE1CDnRPD+tCR3ywSuWH7u2iapnz86U8Bi3Vrs620qNDzCf1F47NxglCEkchCTDOHw==} peerDependencies: - i18next: '>= 25.6.2' + i18next: '>= 25.10.9' react: '>= 16.8.0' react-dom: '*' react-native: '*' - typescript: ^5 - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - typescript: - optional: true - - react-i18next@16.6.0: - resolution: {integrity: sha512-bxVftl2q/pfupKVmBH80ui1rHl3ia2sdcR0Yhn6cr0PyoHfO8JLZ19fccwOIH+0dMBCIkdO5gAmEQFCTAggeQg==} - peerDependencies: - i18next: '>= 25.6.2' - react: '>= 16.8.0' - react-dom: '*' - react-native: '*' - typescript: ^5 + typescript: ^5 || ^6 peerDependenciesMeta: react-dom: optional: true @@ -13641,12 +13609,6 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' - react-window@2.2.3: - resolution: {integrity: sha512-gTRqQYC8ojbiXyd9duYFiSn2TJw0ROXCgYjenOvNKITWzK0m0eCvkUsEUM08xvydkMh7ncp+LE0uS3DeNGZxnQ==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - react-window@2.2.7: resolution: {integrity: sha512-SH5nvfUQwGHYyriDUAOt7wfPsfG9Qxd6OdzQxl5oQ4dsSsUicqQvjV7dR+NqZ4coY0fUn3w1jnC5PwzIUWEg5w==} peerDependencies: @@ -13874,10 +13836,6 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - reveal.js@5.2.1: - resolution: {integrity: sha512-r7//6mIM5p34hFiDMvYfXgyjXqGRta+/psd9YtytsgRlrpRzFv4RbH76TXd2qD+7ZPZEbpBDhdRhJaFgfQ7zNQ==} - engines: {node: '>=18.0.0'} - reveal.js@6.0.0: resolution: {integrity: sha512-RayDr1FL3Jglnf6p9xHGJ0U18va96PiuLs/JHnd1cdDOXvC+3lsXKe6ujl7PX0pvnhNW2Tpqnr6PEKpJVO2exw==} @@ -13909,8 +13867,8 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rolldown@1.0.0-rc.10: - resolution: {integrity: sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==} + rolldown@1.0.0-rc.11: + resolution: {integrity: sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -14643,10 +14601,6 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - string-width@8.1.1: - resolution: {integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==} - engines: {node: '>=20'} - string-width@8.2.0: resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} engines: {node: '>=20'} @@ -14735,6 +14689,10 @@ packages: resolution: {integrity: sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg==} engines: {node: '>=18'} + strtok3@10.3.5: + resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} + engines: {node: '>=18'} + strtok3@6.3.0: resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} engines: {node: '>=10'} @@ -14797,8 +14755,8 @@ packages: peerDependencies: stylelint: '>=16.0.0' - stylelint@17.5.0: - resolution: {integrity: sha512-o/NS6zhsPZFmgUm5tXX4pVNg1XDOZSlucLdf2qow/lVn4JIyzZIQ5b3kad1ugqUj3GSIgr2u5lQw7X8rjqw33g==} + stylelint@17.6.0: + resolution: {integrity: sha512-tokrsMIVAR9vAQ/q3UVEr7S0dGXCi7zkCezPRnS2kqPUulvUh5Vgfwngrk4EoAoW7wnrThqTdnTFN5Ra7CaxIg==} engines: {node: '>=20.19.0'} hasBin: true @@ -14899,9 +14857,6 @@ packages: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} - tabulator-tables@6.3.1: - resolution: {integrity: sha512-qFW7kfadtcaISQIibKAIy0f3eeIXUVi8242Vly1iJfMD79kfEGzfczNuPBN/80hDxHzQJXYbmJ8VipI40hQtfA==} - tabulator-tables@6.4.0: resolution: {integrity: sha512-Lxh+leFNoBo/Yyr4USs6gxqbfo8anYUaUMmoT91pfVLtoUgl/dE+qV7ahnFrKVMCYYqGG33aIMPR7FzpPBaNYA==} @@ -15009,10 +14964,6 @@ packages: tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} - tinyexec@1.0.2: - resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} - engines: {node: '>=18'} - tinyexec@1.0.4: resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} engines: {node: '>=18'} @@ -15024,8 +14975,8 @@ packages: tinyqueue@3.0.0: resolution: {integrity: sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g==} - tinyrainbow@3.0.3: - resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} tldts-core@6.1.86: @@ -15061,8 +15012,8 @@ packages: resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==} engines: {node: '>=10'} - token-types@6.0.0: - resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==} + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} engines: {node: '>=14.16'} totalist@3.0.1: @@ -15237,12 +15188,12 @@ packages: peerDependencies: typedoc: ^0.28.1 - typedoc@0.28.17: - resolution: {integrity: sha512-ZkJ2G7mZrbxrKxinTQMjFqsCoYY6a5Luwv2GKbTnBCEgV2ihYm5CflA9JnJAwH0pZWavqfYxmDkFHPt4yx2oDQ==} + typedoc@0.28.18: + resolution: {integrity: sha512-NTWTUOFRQ9+SGKKTuWKUioUkjxNwtS3JDRPVKZAXGHZy2wCA8bdv2iJiyeePn0xkmK+TCCqZFT0X7+2+FLjngA==} engines: {node: '>= 18', pnpm: '>= 10'} hasBin: true peerDependencies: - typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x + typescript: 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x || 6.0.x typescript-eslint@8.57.1: resolution: {integrity: sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==} @@ -15251,6 +15202,13 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' + typescript-eslint@8.57.2: + resolution: {integrity: sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + typescript@5.0.4: resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} engines: {node: '>=12.20'} @@ -15297,6 +15255,10 @@ packages: resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==} engines: {node: '>=18'} + ulid@2.4.0: + resolution: {integrity: sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg==} + hasBin: true + ulid@3.0.2: resolution: {integrity: sha512-yu26mwteFYzBAot7KVMqFGCVpsF6g8wXfJzQUHvu1no3+rRRSFcSV2nKeYvNPLD2J4b08jYBDhHUjeH0ygIl9w==} hasBin: true @@ -15503,8 +15465,8 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - user-agent-data-types@0.4.2: - resolution: {integrity: sha512-jXep3kO/dGNmDOkbDa8ccp4QArgxR4I76m3QVcJ1aOF0B9toc+YtSXtX5gLdDTZXyWlpQYQrABr6L1L2GZOghw==} + user-agent-data-types@0.4.3: + resolution: {integrity: sha512-Dn3fjwVgeM363mx1sEgOpOp9NNqHBj6Gn68dFbV1WoTr3s05CijKdxfH6A3Q5uzXnYNB6vdAfs/1HWFpObc7EQ==} userhome@1.0.1: resolution: {integrity: sha512-5cnLm4gseXjAclKowC4IjByaGsjtAoV6PrOQOljplNB54ReUYJP8HdAFq2muHinSDAh09PPX/uXDPfdxRHvuSA==} @@ -15600,14 +15562,8 @@ packages: vite: optional: true - vite-plugin-static-copy@3.1.4: - resolution: {integrity: sha512-iCmr4GSw4eSnaB+G8zc2f4dxSuDjbkjwpuBLLGvQYR9IW7rnDzftnUjOH5p4RYR+d4GsiBqXRvzuFhs5bnzVyw==} - engines: {node: ^18.0.0 || >=20.0.0} - peerDependencies: - vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - - vite-plugin-static-copy@3.3.0: - resolution: {integrity: sha512-XiAtZcev7nppxNFgKoD55rfL+ukVp/RtrnTJONRwRuzv/B2FK2h2ZRCYjvxhwBV/Oarse83SiyXBSxMTfeEM0Q==} + vite-plugin-static-copy@3.4.0: + resolution: {integrity: sha512-ekryzCw0ouAOE8tw4RvVL/dfqguXzumsV3FBKoKso4MQ1MUUrUXtl5RI4KpJQUNGqFEsg9kxl4EvDl02YtA9VQ==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -15663,8 +15619,8 @@ packages: yaml: optional: true - vite@8.0.1: - resolution: {integrity: sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==} + vite@8.0.2: + resolution: {integrity: sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -15706,21 +15662,21 @@ packages: yaml: optional: true - vitest@4.1.0: - resolution: {integrity: sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==} + vitest@4.1.2: + resolution: {integrity: sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.0 - '@vitest/browser-preview': 4.1.0 - '@vitest/browser-webdriverio': 4.1.0 - '@vitest/ui': 4.1.0 + '@vitest/browser-playwright': 4.1.2 + '@vitest/browser-preview': 4.1.2 + '@vitest/browser-webdriverio': 4.1.2 + '@vitest/ui': 4.1.2 happy-dom: '*' jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -15816,12 +15772,12 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} - webdriver@9.26.1: - resolution: {integrity: sha512-u5gdt4u900G0k19HM8SvPXKhyaqZZtwTqG7e8bh8dnNb2Td1EiHKEmnaSNDWBllGLCztPE5lHseXzrxUMW88cw==} + webdriver@9.27.0: + resolution: {integrity: sha512-w07ThZND48SIr0b4S7eFougYUyclmoUwdmju8yXvEJiXYjDjeYUpl8wZrYPEYRBylxpSx+sBHfEUBrPQkcTTRQ==} engines: {node: '>=18.20.0'} - webdriverio@9.26.1: - resolution: {integrity: sha512-eqW624AjSEcyO93kfwz/lbn7Uu6x5V8BG8nvPZ/cHXQWfZxvi4AVOZh2Z7k9Vd6Lh5cgdsPbezUQtqnBxzrK0g==} + webdriverio@9.27.0: + resolution: {integrity: sha512-Y4FbMf4bKBXpPB0lYpglzQ2GfDDe6uojmMZl85uPyrDx18NW7mqN84ZawGoIg/FRvcLaVhcOzc98WOPo725Rag==} engines: {node: '>=18.20.0'} peerDependencies: puppeteer-core: '>=22.x || <=24.x' @@ -16032,8 +15988,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - write-file-atomic@7.0.0: - resolution: {integrity: sha512-YnlPC6JqnZl6aO4uRc+dx5PHguiR9S6WeoLtpxNT9wIG+BDya7ZNE1q7KOjVgaA73hKhKLpVPgJ5QA9THQ5BRg==} + write-file-atomic@7.0.1: + resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==} engines: {node: ^20.17.0 || >=22.9.0} ws@7.5.10: @@ -16060,18 +16016,6 @@ packages: utf-8-validate: optional: true - ws@8.19.0: - resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.20.0: resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} engines: {node: '>=10.0.0'} @@ -16167,11 +16111,6 @@ packages: resolution: {integrity: sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==} engines: {node: '>= 6'} - yaml@2.8.1: - resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} - engines: {node: '>= 14.6'} - hasBin: true - yaml@2.8.2: resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} @@ -16297,7 +16236,7 @@ snapshots: '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.3.0 - tinyexec: 1.0.2 + tinyexec: 1.0.4 '@apidevtools/json-schema-ref-parser@9.1.2': dependencies: @@ -16882,7 +16821,7 @@ snapshots: '@babel/core': 7.28.0 '@babel/helper-module-imports': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.4 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color @@ -16905,7 +16844,7 @@ snapshots: '@babel/helpers@7.27.6': dependencies: - '@babel/template': 7.27.2 + '@babel/template': 7.28.6 '@babel/types': 7.29.0 '@babel/helpers@7.28.6': @@ -16956,7 +16895,7 @@ snapshots: '@babel/template@7.27.2': dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 '@babel/parser': 7.29.0 '@babel/types': 7.29.0 @@ -16968,11 +16907,11 @@ snapshots: '@babel/traverse@7.28.4': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 '@babel/helper-globals': 7.28.0 '@babel/parser': 7.29.0 - '@babel/template': 7.27.2 + '@babel/template': 7.28.6 '@babel/types': 7.29.0 debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: @@ -16999,6 +16938,8 @@ snapshots: '@blazediff/core@1.9.1': {} + '@borewit/text-codec@0.2.2': {} + '@braintree/sanitize-url@6.0.2': {} '@braintree/sanitize-url@7.1.1': {} @@ -17039,7 +16980,7 @@ snapshots: '@catppuccin/codemirror@1.0.3': dependencies: '@catppuccin/palette': 1.7.1 - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 @@ -17665,6 +17606,8 @@ snapshots: '@ckeditor/ckeditor5-widget': 47.6.1 ckeditor5: 47.6.1 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-icons@47.6.1': {} @@ -17809,6 +17752,8 @@ snapshots: '@ckeditor/ckeditor5-utils': 47.6.1 ckeditor5: 47.6.1 es-toolkit: 1.39.5 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-merge-fields@47.6.1': dependencies: @@ -17829,8 +17774,6 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.6.1 '@ckeditor/ckeditor5-utils': 47.6.1 ckeditor5: 47.6.1 - transitivePeerDependencies: - - supports-color '@ckeditor/ckeditor5-operations-compressor@47.6.1': dependencies: @@ -18014,7 +17957,7 @@ snapshots: '@codemirror/commands': 6.8.1 '@codemirror/lang-html': 6.4.11 '@codemirror/lang-markdown': 6.3.2 - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/theme-one-dark': 6.1.2 '@codemirror/view': 6.40.0 @@ -18192,21 +18135,21 @@ snapshots: '@codemirror/autocomplete@6.18.6': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/common': 1.5.1 '@codemirror/commands@6.10.3': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/common': 1.5.1 '@codemirror/commands@6.8.1': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/common': 1.5.1 @@ -18214,7 +18157,7 @@ snapshots: '@codemirror/lang-css@6.3.1': dependencies: '@codemirror/autocomplete': 6.18.6 - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@lezer/common': 1.5.1 '@lezer/css': 1.1.11 @@ -18224,7 +18167,7 @@ snapshots: '@codemirror/autocomplete': 6.18.6 '@codemirror/lang-css': 6.3.1 '@codemirror/lang-javascript': 6.2.5 - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/common': 1.5.1 @@ -18234,7 +18177,7 @@ snapshots: '@codemirror/lang-javascript@6.2.5': dependencies: '@codemirror/autocomplete': 6.18.6 - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/lint': 6.8.5 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 @@ -18243,14 +18186,14 @@ snapshots: '@codemirror/lang-json@6.0.2': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@lezer/json': 1.0.3 '@codemirror/lang-markdown@6.3.2': dependencies: '@codemirror/autocomplete': 6.18.6 '@codemirror/lang-html': 6.4.11 - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/common': 1.5.1 @@ -18260,7 +18203,7 @@ snapshots: dependencies: '@codemirror/autocomplete': 6.18.6 '@codemirror/lang-html': 6.4.11 - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/common': 1.5.1 @@ -18269,7 +18212,7 @@ snapshots: '@codemirror/lang-php@6.0.2': dependencies: '@codemirror/lang-html': 6.4.11 - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@lezer/common': 1.5.1 '@lezer/php': 1.0.2 @@ -18278,7 +18221,7 @@ snapshots: dependencies: '@codemirror/lang-html': 6.4.11 '@codemirror/lang-javascript': 6.2.5 - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@lezer/common': 1.5.1 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.2 @@ -18286,13 +18229,13 @@ snapshots: '@codemirror/lang-xml@6.1.0': dependencies: '@codemirror/autocomplete': 6.18.6 - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/common': 1.5.1 '@lezer/xml': 1.0.6 - '@codemirror/language@6.12.2': + '@codemirror/language@6.12.3': dependencies: '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 @@ -18303,7 +18246,7 @@ snapshots: '@codemirror/legacy-modes@6.5.2': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/lint@6.8.5': dependencies: @@ -18323,7 +18266,7 @@ snapshots: '@codemirror/theme-one-dark@6.1.2': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 @@ -18814,9 +18757,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@electron/remote@2.1.3(electron@41.0.3)': + '@electron/remote@2.1.3(electron@41.0.4)': dependencies: - electron: 41.0.3 + electron: 41.0.4 '@electron/universal@2.0.2': dependencies: @@ -19264,170 +19207,170 @@ snapshots: '@floating-ui/utils@0.2.11': {} - '@fsegurai/codemirror-theme-abcdef@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-abcdef@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-abyss@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-abyss@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-android-studio@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-android-studio@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-andromeda@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-andromeda@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-basic-dark@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-basic-dark@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-basic-light@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-basic-light@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-cobalt2@6.0.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-cobalt2@6.0.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-forest@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-forest@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-github-dark@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-github-dark@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-github-light@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-github-light@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-gruvbox-dark@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-gruvbox-dark@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-gruvbox-light@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-gruvbox-light@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-material-dark@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-material-dark@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-material-light@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-material-light@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-monokai@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-monokai@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-nord@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-nord@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-palenight@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-palenight@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-solarized-dark@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-solarized-dark@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-solarized-light@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-solarized-light@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-tokyo-night-day@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-tokyo-night-day@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-tokyo-night-storm@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-tokyo-night-storm@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-volcano@6.2.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-volcano@6.2.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-vscode-dark@6.2.4(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-vscode-dark@6.2.4(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 - '@fsegurai/codemirror-theme-vscode-light@6.2.4(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': + '@fsegurai/codemirror-theme-vscode-light@6.2.4(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/highlight@1.2.3)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/highlight': 1.2.3 @@ -19469,12 +19412,12 @@ snapshots: '@gar/promisify@1.1.3': {} - '@gerrit0/mini-shiki@3.17.0': + '@gerrit0/mini-shiki@3.23.0': dependencies: - '@shikijs/engine-oniguruma': 3.17.0 - '@shikijs/langs': 3.17.0 - '@shikijs/themes': 3.17.0 - '@shikijs/types': 3.17.0 + '@shikijs/engine-oniguruma': 3.23.0 + '@shikijs/langs': 3.23.0 + '@shikijs/themes': 3.23.0 + '@shikijs/types': 3.23.0 '@shikijs/vscode-textmate': 10.0.2 '@hapi/hoek@9.3.0': {} @@ -20145,12 +20088,6 @@ snapshots: '@mdn/browser-compat-data@5.7.6': {} - '@mermaid-js/layout-elk@0.2.0(mermaid@11.13.0)': - dependencies: - d3: 7.9.0 - elkjs: 0.9.3 - mermaid: 11.13.0 - '@mermaid-js/layout-elk@0.2.1(mermaid@11.13.0)': dependencies: d3: 7.9.0 @@ -20196,10 +20133,6 @@ snapshots: '@microsoft/tsdoc@0.15.1': {} - '@mind-elixir/node-menu@5.0.1(mind-elixir@5.4.0)': - dependencies: - mind-elixir: 5.4.0 - '@mind-elixir/node-menu@5.0.1(mind-elixir@5.9.3)': dependencies: mind-elixir: 5.9.3 @@ -20475,7 +20408,7 @@ snapshots: '@opentelemetry/semantic-conventions@1.34.0': {} - '@oxc-project/types@0.120.0': {} + '@oxc-project/types@0.122.0': {} '@panva/asn1.js@1.0.0': {} @@ -20566,35 +20499,35 @@ snapshots: '@popperjs/core@2.11.8': {} - '@preact/preset-vite@2.10.2(@babel/core@7.29.0)(preact@10.29.0)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))': + '@preact/preset-vite@2.10.2(@babel/core@7.29.0)(preact@10.29.0)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) - '@prefresh/vite': 2.4.12(preact@10.29.0)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + '@prefresh/vite': 2.4.12(preact@10.29.0)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) '@rollup/pluginutils': 4.2.1 babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.0) debug: 4.4.3(supports-color@8.1.1) picocolors: 1.1.1 - vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) - vite-prerender-plugin: 0.5.11(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + vite-prerender-plugin: 0.5.11(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) transitivePeerDependencies: - preact - supports-color - '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.52.0)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))': + '@preact/preset-vite@2.10.5(@babel/core@7.29.0)(preact@10.29.0)(rollup@4.52.0)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) - '@prefresh/vite': 2.4.12(preact@10.29.0)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + '@prefresh/vite': 2.4.12(preact@10.29.0)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) '@rollup/pluginutils': 5.1.4(rollup@4.52.0) babel-plugin-transform-hook-names: 1.0.2(@babel/core@7.29.0) debug: 4.4.3(supports-color@8.1.1) magic-string: 0.30.21 picocolors: 1.1.1 - vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) - vite-prerender-plugin: 0.5.11(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + vite: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + vite-prerender-plugin: 0.5.11(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) zimmerframe: 1.1.4 transitivePeerDependencies: - preact @@ -20603,11 +20536,6 @@ snapshots: '@preact/signals-core@1.14.0': {} - '@preact/signals@2.5.1(preact@10.29.0)': - dependencies: - '@preact/signals-core': 1.14.0 - preact: 10.29.0 - '@preact/signals@2.8.2(preact@10.29.0)': dependencies: '@preact/signals-core': 1.14.0 @@ -20621,7 +20549,7 @@ snapshots: '@prefresh/utils@1.2.1': {} - '@prefresh/vite@2.4.12(preact@10.29.0)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))': + '@prefresh/vite@2.4.12(preact@10.29.0)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@babel/core': 7.28.0 '@prefresh/babel-plugin': 0.5.2 @@ -20629,7 +20557,7 @@ snapshots: '@prefresh/utils': 1.2.1 '@rollup/pluginutils': 4.2.1 preact: 10.29.0 - vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -21287,14 +21215,19 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - '@redocly/cli@2.24.1(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)': + '@redocly/cli-otel@0.1.2': + dependencies: + ulid: 2.4.0 + + '@redocly/cli@2.25.1(@opentelemetry/api@1.9.0)(bufferutil@4.0.9)(core-js@3.46.0)(encoding@0.1.13)(utf-8-validate@6.0.5)': dependencies: '@opentelemetry/exporter-trace-otlp-http': 0.202.0(@opentelemetry/api@1.9.0) '@opentelemetry/resources': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-node': 2.0.1(@opentelemetry/api@1.9.0) '@opentelemetry/semantic-conventions': 1.34.0 - '@redocly/openapi-core': 2.24.1 - '@redocly/respect-core': 2.24.1 + '@redocly/cli-otel': 0.1.2 + '@redocly/openapi-core': 2.25.1 + '@redocly/respect-core': 2.25.1 abort-controller: 3.0.0 ajv: '@redocly/ajv@8.18.0' ajv-formats: 3.0.1(@redocly/ajv@8.18.0) @@ -21328,7 +21261,7 @@ snapshots: '@redocly/config@0.22.2': {} - '@redocly/config@0.44.1': + '@redocly/config@0.44.2': dependencies: json-schema-to-ts: 2.7.2 @@ -21346,10 +21279,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@redocly/openapi-core@2.24.1': + '@redocly/openapi-core@2.25.1': dependencies: '@redocly/ajv': 8.18.0 - '@redocly/config': 0.44.1 + '@redocly/config': 0.44.2 ajv: '@redocly/ajv@8.18.0' ajv-formats: 3.0.1(@redocly/ajv@8.18.0) colorette: 1.4.0 @@ -21359,12 +21292,12 @@ snapshots: pluralize: 8.0.0 yaml-ast-parser: 0.0.43 - '@redocly/respect-core@2.24.1': + '@redocly/respect-core@2.25.1': dependencies: '@faker-js/faker': 7.6.0 '@noble/hashes': 1.8.0 '@redocly/ajv': 8.18.0 - '@redocly/openapi-core': 2.24.1 + '@redocly/openapi-core': 2.25.1 ajv: '@redocly/ajv@8.18.0' better-ajv-errors: 1.2.0(@redocly/ajv@8.18.0) colorette: 2.0.20 @@ -21374,78 +21307,78 @@ snapshots: outdent: 0.8.0 picomatch: 4.0.3 - '@replit/codemirror-indentation-markers@6.5.3(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)': + '@replit/codemirror-indentation-markers@6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 - '@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.12.2)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.2)': + '@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.2)': dependencies: '@codemirror/autocomplete': 6.18.6 - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 '@lezer/common': 1.5.1 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.2 - '@replit/codemirror-vim@6.3.0(@codemirror/commands@6.10.3)(@codemirror/language@6.12.2)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)': + '@replit/codemirror-vim@6.3.0(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)': dependencies: '@codemirror/commands': 6.10.3 - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@codemirror/search': 6.6.0 '@codemirror/state': 6.6.0 '@codemirror/view': 6.40.0 - '@rolldown/binding-android-arm64@1.0.0-rc.10': + '@rolldown/binding-android-arm64@1.0.0-rc.11': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.10': + '@rolldown/binding-darwin-arm64@1.0.0-rc.11': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.10': + '@rolldown/binding-darwin-x64@1.0.0-rc.11': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.10': + '@rolldown/binding-freebsd-x64@1.0.0-rc.11': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.11': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.11': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.10': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.11': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10': + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.11': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10': + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.11': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.10': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.11': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.10': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.11': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.10': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.11': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.10': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.11': dependencies: '@napi-rs/wasm-runtime': 1.1.1 optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.11': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.10': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.11': optional: true - '@rolldown/pluginutils@1.0.0-rc.10': {} + '@rolldown/pluginutils@1.0.0-rc.11': {} '@rollup/plugin-buble@1.0.3(rollup@4.52.0)': dependencies: @@ -21626,20 +21559,20 @@ snapshots: domhandler: 5.0.3 selderee: 0.11.0 - '@shikijs/engine-oniguruma@3.17.0': + '@shikijs/engine-oniguruma@3.23.0': dependencies: - '@shikijs/types': 3.17.0 + '@shikijs/types': 3.23.0 '@shikijs/vscode-textmate': 10.0.2 - '@shikijs/langs@3.17.0': + '@shikijs/langs@3.23.0': dependencies: - '@shikijs/types': 3.17.0 + '@shikijs/types': 3.23.0 - '@shikijs/themes@3.17.0': + '@shikijs/themes@3.23.0': dependencies: - '@shikijs/types': 3.17.0 + '@shikijs/types': 3.23.0 - '@shikijs/types@3.17.0': + '@shikijs/types@3.23.0': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -22087,7 +22020,7 @@ snapshots: '@ssddanbrown/codemirror-lang-twig@1.0.0': dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.2 @@ -22105,7 +22038,7 @@ snapshots: - supports-color - typescript - '@stylistic/stylelint-plugin@3.1.3(stylelint@17.5.0(typescript@5.9.3))': + '@stylistic/stylelint-plugin@3.1.3(stylelint@17.6.0(typescript@5.9.3))': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 @@ -22115,7 +22048,7 @@ snapshots: postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 style-search: 0.1.0 - stylelint: 17.5.0(typescript@5.9.3) + stylelint: 17.6.0(typescript@5.9.3) '@swc/core-darwin-arm64@1.11.29': optional: true @@ -22181,11 +22114,10 @@ snapshots: dependencies: defer-to-connect: 2.0.1 - '@tokenizer/inflate@0.2.7': + '@tokenizer/inflate@0.4.1': dependencies: debug: 4.4.3(supports-color@8.1.1) - fflate: 0.8.2 - token-types: 6.0.0 + token-types: 6.1.2 transitivePeerDependencies: - supports-color @@ -22520,10 +22452,6 @@ snapshots: '@types/ini@4.1.1': {} - '@types/jquery@3.5.33': - dependencies: - '@types/sizzle': 2.3.10 - '@types/jquery@4.0.0': {} '@types/js-yaml@4.0.9': {} @@ -22690,8 +22618,6 @@ snapshots: '@types/sinonjs__fake-timers@8.1.5': {} - '@types/sizzle@2.3.10': {} - '@types/sockjs@0.3.36': dependencies: '@types/node': 24.12.0 @@ -22780,6 +22706,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/type-utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.57.2 + eslint: 10.1.0(jiti@2.6.1) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.57.1 @@ -22792,6 +22734,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.57.2 + debug: 4.4.3(supports-color@8.1.1) + eslint: 10.1.0(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/project-service@8.57.1(typescript@5.9.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3) @@ -22801,15 +22755,33 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.57.2(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.9.3) + '@typescript-eslint/types': 8.57.2 + debug: 4.4.3(supports-color@8.1.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@8.57.1': dependencies: '@typescript-eslint/types': 8.57.1 '@typescript-eslint/visitor-keys': 8.57.1 + '@typescript-eslint/scope-manager@8.57.2': + dependencies: + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/visitor-keys': 8.57.2 + '@typescript-eslint/tsconfig-utils@8.57.1(typescript@5.9.3)': dependencies: typescript: 5.9.3 + '@typescript-eslint/tsconfig-utils@8.57.2(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + '@typescript-eslint/type-utils@8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.57.1 @@ -22822,8 +22794,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/type-utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3(supports-color@8.1.1) + eslint: 10.1.0(jiti@2.6.1) + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@8.57.1': {} + '@typescript-eslint/types@8.57.2': {} + '@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3)': dependencies: '@typescript-eslint/project-service': 8.57.1(typescript@5.9.3) @@ -22839,6 +22825,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.57.2(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.57.2(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.9.3) + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/visitor-keys': 8.57.2 + debug: 4.4.3(supports-color@8.1.1) + minimatch: 10.2.4 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) @@ -22850,11 +22851,27 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.57.2 + '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) + eslint: 10.1.0(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@8.57.1': dependencies: '@typescript-eslint/types': 8.57.1 eslint-visitor-keys: 5.0.1 + '@typescript-eslint/visitor-keys@8.57.2': + dependencies: + '@typescript-eslint/types': 8.57.2 + eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.0': {} '@univerjs-pro/collaboration-client-ui@0.18.0(@types/react-dom@19.1.6(@types/react@19.1.7))(@types/react@19.1.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)': @@ -24272,27 +24289,27 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - '@vitest/browser-webdriverio@4.1.0(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.0)(webdriverio@9.26.1(bufferutil@4.0.9)(utf-8-validate@6.0.5))': + '@vitest/browser-webdriverio@4.1.2(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.2)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))': dependencies: - '@vitest/browser': 4.1.0(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.0) - vitest: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.0)(@vitest/ui@4.1.0)(happy-dom@20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) - webdriverio: 9.26.1(bufferutil@4.0.9)(utf-8-validate@6.0.5) + '@vitest/browser': 4.1.2(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.2) + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.2)(@vitest/ui@4.1.2)(happy-dom@20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + webdriverio: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@4.1.0(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.0)': + '@vitest/browser@4.1.2(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.2)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.0(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/utils': 4.1.0 + '@vitest/mocker': 4.1.2(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/utils': 4.1.2 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 - tinyrainbow: 3.0.3 - vitest: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.0)(@vitest/ui@4.1.0)(happy-dom@20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + tinyrainbow: 3.1.0 + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.2)(@vitest/ui@4.1.2)(happy-dom@20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) ws: 8.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bufferutil @@ -24300,7 +24317,7 @@ snapshots: - utf-8-validate - vite - '@vitest/coverage-istanbul@4.1.0(vitest@4.1.0)': + '@vitest/coverage-istanbul@4.1.2(vitest@4.1.2)': dependencies: '@babel/core': 7.29.0 '@istanbuljs/schema': 0.1.3 @@ -24311,15 +24328,15 @@ snapshots: istanbul-reports: 3.2.0 magicast: 0.5.2 obug: 2.1.1 - tinyrainbow: 3.0.3 - vitest: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.0)(@vitest/ui@4.1.0)(happy-dom@20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + tinyrainbow: 3.1.0 + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.2)(@vitest/ui@4.1.2)(happy-dom@20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.1.0(@vitest/browser@4.1.0(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.0))(vitest@4.1.0)': + '@vitest/coverage-v8@4.1.2(@vitest/browser@4.1.2(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.2))(vitest@4.1.2)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.0 + '@vitest/utils': 4.1.2 ast-v8-to-istanbul: 1.0.0 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 @@ -24327,63 +24344,63 @@ snapshots: magicast: 0.5.2 obug: 2.1.1 std-env: 4.0.0 - tinyrainbow: 3.0.3 - vitest: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.0)(@vitest/ui@4.1.0)(happy-dom@20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + tinyrainbow: 3.1.0 + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.2)(@vitest/ui@4.1.2)(happy-dom@20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) optionalDependencies: - '@vitest/browser': 4.1.0(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.0) + '@vitest/browser': 4.1.2(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.2) - '@vitest/expect@4.1.0': + '@vitest/expect@4.1.2': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.2 - '@vitest/spy': 4.1.0 - '@vitest/utils': 4.1.0 + '@vitest/spy': 4.1.2 + '@vitest/utils': 4.1.2 chai: 6.2.2 - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.0(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@4.1.2(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))': dependencies: - '@vitest/spy': 4.1.0 + '@vitest/spy': 4.1.2 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.7.5(@types/node@24.12.0)(typescript@5.9.3) - vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/pretty-format@4.1.0': + '@vitest/pretty-format@4.1.2': dependencies: - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 - '@vitest/runner@4.1.0': + '@vitest/runner@4.1.2': dependencies: - '@vitest/utils': 4.1.0 + '@vitest/utils': 4.1.2 pathe: 2.0.3 - '@vitest/snapshot@4.1.0': + '@vitest/snapshot@4.1.2': dependencies: - '@vitest/pretty-format': 4.1.0 - '@vitest/utils': 4.1.0 + '@vitest/pretty-format': 4.1.2 + '@vitest/utils': 4.1.2 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.0': {} + '@vitest/spy@4.1.2': {} - '@vitest/ui@4.1.0(vitest@4.1.0)': + '@vitest/ui@4.1.2(vitest@4.1.2)': dependencies: - '@vitest/utils': 4.1.0 + '@vitest/utils': 4.1.2 fflate: 0.8.2 - flatted: 3.4.0 + flatted: 3.4.2 pathe: 2.0.3 sirv: 3.0.2 tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vitest: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.0)(@vitest/ui@4.1.0)(happy-dom@20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + tinyrainbow: 3.1.0 + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.2)(@vitest/ui@4.1.2)(happy-dom@20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/utils@4.1.0': + '@vitest/utils@4.1.2': dependencies: - '@vitest/pretty-format': 4.1.0 + '@vitest/pretty-format': 4.1.2 convert-source-map: 2.0.0 - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 '@volar/language-core@2.4.13': dependencies: @@ -24432,11 +24449,11 @@ snapshots: '@vue/shared@3.5.14': {} - '@wdio/config@9.26.1': + '@wdio/config@9.27.0': dependencies: '@wdio/logger': 9.18.0 - '@wdio/types': 9.26.1 - '@wdio/utils': 9.26.1 + '@wdio/types': 9.27.0 + '@wdio/utils': 9.27.0 deepmerge-ts: 7.1.5 glob: 13.0.6 import-meta-resolve: 4.2.0 @@ -24453,21 +24470,21 @@ snapshots: safe-regex2: 5.0.0 strip-ansi: 7.1.2 - '@wdio/protocols@9.26.1': {} + '@wdio/protocols@9.27.0': {} '@wdio/repl@9.16.2': dependencies: '@types/node': 20.19.25 - '@wdio/types@9.26.1': + '@wdio/types@9.27.0': dependencies: '@types/node': 20.19.25 - '@wdio/utils@9.26.1': + '@wdio/utils@9.27.0': dependencies: '@puppeteer/browsers': 2.10.10 '@wdio/logger': 9.18.0 - '@wdio/types': 9.26.1 + '@wdio/types': 9.27.0 decamelize: 6.0.1 deepmerge-ts: 7.1.5 edgedriver: 6.1.2 @@ -24601,9 +24618,7 @@ snapshots: '@zip.js/zip.js@2.8.11': {} - '@zumer/snapdom@2.0.1': {} - - '@zumer/snapdom@2.5.0': {} + '@zumer/snapdom@2.6.0': {} abab@2.0.6: {} @@ -25665,7 +25680,7 @@ snapshots: cli-truncate@5.1.0: dependencies: slice-ansi: 7.1.2 - string-width: 8.1.1 + string-width: 8.2.0 cli-width@4.1.0: {} @@ -25707,18 +25722,18 @@ snapshots: codemirror-lang-elixir@4.0.1: dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 lezer-elixir: 1.1.2 codemirror-lang-hcl@0.1.0: dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.2 codemirror-lang-mermaid@0.5.0: dependencies: - '@codemirror/language': 6.12.2 + '@codemirror/language': 6.12.3 '@lezer/highlight': 1.2.3 '@lezer/lr': 1.4.2 @@ -25918,15 +25933,6 @@ snapshots: is-what: 3.14.1 optional: true - copy-webpack-plugin@13.0.1(webpack@5.101.3(esbuild@0.27.4)): - dependencies: - glob-parent: 6.0.2 - normalize-path: 3.0.0 - schema-utils: 4.3.2 - serialize-javascript: 6.0.2 - tinyglobby: 0.2.15 - webpack: 5.101.3(esbuild@0.27.4) - copy-webpack-plugin@14.0.0(webpack@5.101.3(esbuild@0.27.4)): dependencies: glob-parent: 6.0.2 @@ -25963,7 +25969,7 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - cosmiconfig@9.0.0(typescript@5.0.4): + cosmiconfig@9.0.1(typescript@5.0.4): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 @@ -26102,11 +26108,6 @@ snapshots: mdn-data: 2.0.30 source-map-js: 1.2.1 - css-tree@3.1.0: - dependencies: - mdn-data: 2.12.2 - source-map-js: 1.2.1 - css-tree@3.2.1: dependencies: mdn-data: 2.27.1 @@ -26576,6 +26577,8 @@ snapshots: diff@8.0.3: {} + diff@8.0.4: {} + dir-compare@4.2.0: dependencies: minimatch: 3.1.5 @@ -26827,7 +26830,7 @@ snapshots: - supports-color optional: true - electron@41.0.3: + electron@41.0.4: dependencies: '@electron/get': 2.0.3 '@types/node': 24.12.0 @@ -26921,8 +26924,6 @@ snapshots: entities@4.5.0: {} - entities@6.0.0: {} - entities@6.0.1: {} entities@7.0.1: {} @@ -27474,7 +27475,7 @@ snapshots: transitivePeerDependencies: - supports-color - express-openid-connect@2.19.4(express@5.2.1): + express-openid-connect@2.20.0(express@5.2.1): dependencies: base64url: 3.0.1 clone: 2.1.2 @@ -27696,11 +27697,11 @@ snapshots: strtok3: 6.3.0 token-types: 4.2.1 - file-type@20.5.0: + file-type@21.3.4: dependencies: - '@tokenizer/inflate': 0.2.7 - strtok3: 10.2.2 - token-types: 6.0.0 + '@tokenizer/inflate': 0.4.1 + strtok3: 10.3.5 + token-types: 6.1.2 uint8array-extras: 1.4.0 transitivePeerDependencies: - supports-color @@ -27788,10 +27789,10 @@ snapshots: flat@5.0.2: {} - flatted@3.3.3: {} - flatted@3.4.0: {} + flatted@3.4.2: {} + float-tooltip@1.7.5: dependencies: d3-selection: 3.0.0 @@ -27818,24 +27819,6 @@ snapshots: dependencies: is-callable: 1.2.7 - force-graph@1.51.0: - dependencies: - '@tweenjs/tween.js': 25.0.0 - accessor-fn: 1.5.3 - bezier-js: 6.1.4 - canvas-color-tracker: 1.3.2 - d3-array: 3.2.4 - d3-drag: 3.0.0 - d3-force-3d: 3.0.6 - d3-scale: 4.0.2 - d3-scale-chromatic: 3.1.0 - d3-selection: 3.0.0 - d3-zoom: 3.0.0 - float-tooltip: 1.7.5 - index-array-by: 1.4.2 - kapsule: 1.16.3 - lodash-es: 4.17.23 - force-graph@1.51.2: dependencies: '@tweenjs/tween.js': 25.0.0 @@ -28199,8 +28182,6 @@ snapshots: globals@16.5.0: {} - globals@17.0.0: {} - globals@17.4.0: {} globalthis@1.0.4: @@ -28263,20 +28244,14 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 - happy-dom@20.0.11: - dependencies: - '@types/node': 20.19.25 - '@types/whatwg-mimetype': 3.0.2 - whatwg-mimetype: 3.0.0 - - happy-dom@20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5): + happy-dom@20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5): dependencies: '@types/node': 24.12.0 '@types/whatwg-mimetype': 3.0.2 '@types/ws': 8.18.1 entities: 7.0.1 whatwg-mimetype: 3.0.0 - ws: 8.19.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) + ws: 8.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -28686,13 +28661,7 @@ snapshots: transitivePeerDependencies: - encoding - i18next@25.10.3(typescript@5.9.3): - dependencies: - '@babel/runtime': 7.29.2 - optionalDependencies: - typescript: 5.9.3 - - i18next@25.7.3(typescript@5.9.3): + i18next@25.10.10(typescript@5.9.3): dependencies: '@babel/runtime': 7.29.2 optionalDependencies: @@ -28743,9 +28712,9 @@ snapshots: image-size@0.7.5: optional: true - image-type@6.0.0: + image-type@6.1.0: dependencies: - file-type: 20.5.0 + file-type: 21.3.4 transitivePeerDependencies: - supports-color @@ -28896,7 +28865,7 @@ snapshots: is-fullwidth-code-point@5.1.0: dependencies: - get-east-asian-width: 1.4.0 + get-east-asian-width: 1.5.0 is-generator-function@1.1.0: dependencies: @@ -29170,16 +29139,10 @@ snapshots: jquery-hotkeys@0.2.2: {} - jquery.fancytree@2.38.5(jquery@3.7.1): - dependencies: - jquery: 3.7.1 - jquery.fancytree@2.38.5(jquery@4.0.0): dependencies: jquery: 4.0.0 - jquery@3.7.1: {} - jquery@4.0.0: {} js-levenshtein@1.1.6: {} @@ -29389,11 +29352,7 @@ snapshots: - supports-color - utf-8-validate - katex@0.16.27: - dependencies: - commander: 8.3.0 - - katex@0.16.40: + katex@0.16.43: dependencies: commander: 8.3.0 @@ -29717,7 +29676,7 @@ snapshots: dependencies: date-format: 4.0.14 debug: 4.4.3(supports-color@8.1.1) - flatted: 3.3.3 + flatted: 3.4.0 rfdc: 1.4.1 streamroller: 3.1.5 transitivePeerDependencies: @@ -29743,8 +29702,6 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.2.4: {} - lru-cache@11.2.7: {} lru-cache@5.1.1: @@ -29917,7 +29874,7 @@ snapshots: mark.js@8.11.1: {} - markdown-it@14.1.0: + markdown-it@14.1.1: dependencies: argparse: 2.0.1 entities: 4.5.0 @@ -29930,8 +29887,6 @@ snapshots: marked@16.4.2: {} - marked@17.0.1: {} - marked@17.0.5: {} marked@4.3.0: {} @@ -30085,8 +30040,6 @@ snapshots: mdn-data@2.0.30: {} - mdn-data@2.12.2: {} - mdn-data@2.27.1: {} mdurl@2.0.0: {} @@ -30135,7 +30088,7 @@ snapshots: dagre-d3-es: 7.0.14 dayjs: 1.11.20 dompurify: 3.3.3 - katex: 0.16.40 + katex: 0.16.43 khroma: 2.1.0 lodash-es: 4.17.23 marked: 16.4.2 @@ -30375,8 +30328,6 @@ snapshots: mimic-response@3.1.0: {} - mind-elixir@5.4.0: {} - mind-elixir@5.9.3: {} mini-css-extract-plugin@2.9.4(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.4)): @@ -30407,10 +30358,6 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.2 - minimatch@9.0.9: dependencies: brace-expansion: 2.0.2 @@ -30538,7 +30485,7 @@ snapshots: browser-stdout: 1.3.1 chokidar: 4.0.3 debug: 4.4.3(supports-color@8.1.1) - diff: 8.0.3 + diff: 8.0.4 escape-string-regexp: 4.0.0 find-up: 5.0.0 glob: 13.0.6 @@ -31107,7 +31054,7 @@ snapshots: is-unicode-supported: 2.1.0 log-symbols: 7.0.1 stdin-discarder: 0.2.2 - string-width: 8.1.1 + string-width: 8.2.0 os-shim@0.1.3: {} @@ -31225,7 +31172,7 @@ snapshots: pako@2.1.0: {} - panzoom@9.4.3: + panzoom@9.4.4: dependencies: amator: 1.1.0 ngraph.events: 1.2.2 @@ -31294,7 +31241,7 @@ snapshots: parse5@7.3.0: dependencies: - entities: 6.0.0 + entities: 6.0.1 parseley@0.12.1: dependencies: @@ -31325,7 +31272,7 @@ snapshots: path-scurry@2.0.0: dependencies: - lru-cache: 11.2.4 + lru-cache: 11.2.7 minipass: 7.1.3 path-scurry@2.0.2: @@ -31555,7 +31502,7 @@ snapshots: postcss-loader@8.2.0(postcss@8.5.8)(typescript@5.0.4)(webpack@5.101.3(@swc/core@1.11.29(@swc/helpers@0.5.17))(esbuild@0.27.4)): dependencies: - cosmiconfig: 9.0.0(typescript@5.0.4) + cosmiconfig: 9.0.1(typescript@5.0.4) jiti: 2.6.1 postcss: 8.5.8 semver: 7.7.4 @@ -32002,22 +31949,11 @@ snapshots: react: 19.2.4 scheduler: 0.27.0 - react-i18next@16.5.1(i18next@25.7.3(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): + react-i18next@16.6.6(i18next@25.10.10(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): dependencies: '@babel/runtime': 7.29.2 html-parse-stringify: 3.0.1 - i18next: 25.7.3(typescript@5.9.3) - react: 19.2.4 - use-sync-external-store: 1.6.0(react@19.2.4) - optionalDependencies: - react-dom: 19.2.4(react@19.2.4) - typescript: 5.9.3 - - react-i18next@16.6.0(i18next@25.10.3(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3): - dependencies: - '@babel/runtime': 7.29.2 - html-parse-stringify: 3.0.1 - i18next: 25.10.3(typescript@5.9.3) + i18next: 25.10.10(typescript@5.9.3) react: 19.2.4 use-sync-external-store: 1.6.0(react@19.2.4) optionalDependencies: @@ -32098,11 +32034,6 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - react-window@2.2.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4): - dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - react-window@2.2.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: react: 19.2.4 @@ -32404,8 +32335,6 @@ snapshots: reusify@1.1.0: {} - reveal.js@5.2.1: {} - reveal.js@6.0.0: {} rfdc@1.4.1: {} @@ -32438,45 +32367,45 @@ snapshots: robust-predicates@3.0.2: {} - rolldown@1.0.0-rc.10: + rolldown@1.0.0-rc.11: dependencies: - '@oxc-project/types': 0.120.0 - '@rolldown/pluginutils': 1.0.0-rc.10 + '@oxc-project/types': 0.122.0 + '@rolldown/pluginutils': 1.0.0-rc.11 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.10 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.10 - '@rolldown/binding-darwin-x64': 1.0.0-rc.10 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.10 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.10 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.10 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.10 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.10 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.10 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.10 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.10 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.10 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.10 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.10 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.10 + '@rolldown/binding-android-arm64': 1.0.0-rc.11 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.11 + '@rolldown/binding-darwin-x64': 1.0.0-rc.11 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.11 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.11 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.11 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.11 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.11 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.11 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.11 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.11 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.11 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.11 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.11 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.11 - rollup-plugin-stats@2.1.0(rolldown@1.0.0-rc.10)(rollup@4.52.0)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): + rollup-plugin-stats@2.1.0(rolldown@1.0.0-rc.11)(rollup@4.52.0)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): optionalDependencies: - rolldown: 1.0.0-rc.10 + rolldown: 1.0.0-rc.11 rollup: 4.52.0 - vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) rollup-plugin-svg-import@3.0.0(rollup@4.52.0): dependencies: '@rollup/pluginutils': 5.1.4(rollup@4.52.0) rollup: 4.52.0 - rollup-plugin-webpack-stats@3.1.0(rolldown@1.0.0-rc.10)(rollup@4.52.0)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): + rollup-plugin-webpack-stats@3.1.0(rolldown@1.0.0-rc.11)(rollup@4.52.0)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: - rollup-plugin-stats: 2.1.0(rolldown@1.0.0-rc.10)(rollup@4.52.0)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + rollup-plugin-stats: 2.1.0(rolldown@1.0.0-rc.11)(rollup@4.52.0)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) optionalDependencies: - rolldown: 1.0.0-rc.10 + rolldown: 1.0.0-rc.11 rollup: 4.52.0 - vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) rollup@4.52.0: dependencies: @@ -33337,11 +33266,6 @@ snapshots: get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 - string-width@8.1.1: - dependencies: - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.2 - string-width@8.2.0: dependencies: get-east-asian-width: 1.5.0 @@ -33445,6 +33369,10 @@ snapshots: '@tokenizer/token': 0.3.0 peek-readable: 7.0.0 + strtok3@10.3.5: + dependencies: + '@tokenizer/token': 0.3.0 + strtok3@6.3.0: dependencies: '@tokenizer/token': 0.3.0 @@ -33491,22 +33419,22 @@ snapshots: postcss: 8.5.8 postcss-selector-parser: 7.1.1 - stylelint-config-ckeditor5@14.0.0(stylelint@17.5.0(typescript@5.9.3)): + stylelint-config-ckeditor5@14.0.0(stylelint@17.6.0(typescript@5.9.3)): dependencies: - '@stylistic/stylelint-plugin': 3.1.3(stylelint@17.5.0(typescript@5.9.3)) - stylelint: 17.5.0(typescript@5.9.3) - stylelint-config-recommended: 16.0.0(stylelint@17.5.0(typescript@5.9.3)) - stylelint-plugin-ckeditor5-rules: 14.0.0(stylelint@17.5.0(typescript@5.9.3)) + '@stylistic/stylelint-plugin': 3.1.3(stylelint@17.6.0(typescript@5.9.3)) + stylelint: 17.6.0(typescript@5.9.3) + stylelint-config-recommended: 16.0.0(stylelint@17.6.0(typescript@5.9.3)) + stylelint-plugin-ckeditor5-rules: 14.0.0(stylelint@17.6.0(typescript@5.9.3)) - stylelint-config-recommended@16.0.0(stylelint@17.5.0(typescript@5.9.3)): + stylelint-config-recommended@16.0.0(stylelint@17.6.0(typescript@5.9.3)): dependencies: - stylelint: 17.5.0(typescript@5.9.3) + stylelint: 17.6.0(typescript@5.9.3) - stylelint-plugin-ckeditor5-rules@14.0.0(stylelint@17.5.0(typescript@5.9.3)): + stylelint-plugin-ckeditor5-rules@14.0.0(stylelint@17.6.0(typescript@5.9.3)): dependencies: - stylelint: 17.5.0(typescript@5.9.3) + stylelint: 17.6.0(typescript@5.9.3) - stylelint@17.5.0(typescript@5.9.3): + stylelint@17.6.0(typescript@5.9.3): dependencies: '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) @@ -33529,7 +33457,6 @@ snapshots: html-tags: 5.1.0 ignore: 7.0.5 import-meta-resolve: 4.2.0 - imurmurhash: 0.1.4 is-plain-object: 5.0.0 mathml-tag-names: 4.0.0 meow: 14.1.0 @@ -33544,7 +33471,7 @@ snapshots: supports-hyperlinks: 4.4.0 svg-tags: 1.0.0 table: 6.9.0 - write-file-atomic: 7.0.0 + write-file-atomic: 7.0.1 transitivePeerDependencies: - supports-color - typescript @@ -33636,7 +33563,7 @@ snapshots: dependencies: commander: 11.1.0 css-select: 5.2.2 - css-tree: 3.1.0 + css-tree: 3.2.1 css-what: 6.2.2 csso: 5.0.5 picocolors: 1.1.1 @@ -33693,8 +33620,6 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - tabulator-tables@6.3.1: {} - tabulator-tables@6.4.0: {} tailwind-merge@2.6.0: {} @@ -33833,8 +33758,6 @@ snapshots: tinycolor2@1.6.0: {} - tinyexec@1.0.2: {} - tinyexec@1.0.4: {} tinyglobby@0.2.15: @@ -33844,7 +33767,7 @@ snapshots: tinyqueue@3.0.0: {} - tinyrainbow@3.0.3: {} + tinyrainbow@3.1.0: {} tldts-core@6.1.86: optional: true @@ -33880,8 +33803,9 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 - token-types@6.0.0: + token-types@6.1.2: dependencies: + '@borewit/text-codec': 0.2.2 '@tokenizer/token': 0.3.0 ieee754: 1.2.1 @@ -33965,7 +33889,7 @@ snapshots: acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 - diff: 8.0.3 + diff: 8.0.4 make-error: 1.3.6 typescript: 5.0.4 v8-compile-cache-lib: 3.0.1 @@ -33985,7 +33909,7 @@ snapshots: acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 - diff: 8.0.3 + diff: 8.0.4 make-error: 1.3.6 typescript: 5.9.3 v8-compile-cache-lib: 3.0.1 @@ -34104,18 +34028,18 @@ snapshots: typedarray@0.0.6: {} - typedoc-plugin-missing-exports@4.1.2(typedoc@0.28.17(typescript@5.9.3)): + typedoc-plugin-missing-exports@4.1.2(typedoc@0.28.18(typescript@5.9.3)): dependencies: - typedoc: 0.28.17(typescript@5.9.3) + typedoc: 0.28.18(typescript@5.9.3) - typedoc@0.28.17(typescript@5.9.3): + typedoc@0.28.18(typescript@5.9.3): dependencies: - '@gerrit0/mini-shiki': 3.17.0 + '@gerrit0/mini-shiki': 3.23.0 lunr: 2.3.9 - markdown-it: 14.1.0 - minimatch: 9.0.5 + markdown-it: 14.1.1 + minimatch: 10.2.4 typescript: 5.9.3 - yaml: 2.8.1 + yaml: 2.8.2 typescript-eslint@8.57.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3): dependencies: @@ -34128,6 +34052,17 @@ snapshots: transitivePeerDependencies: - supports-color + typescript-eslint@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + eslint: 10.1.0(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.0.4: {} typescript@5.4.5: {} @@ -34153,6 +34088,8 @@ snapshots: uint8array-extras@1.4.0: {} + ulid@2.4.0: {} + ulid@3.0.2: {} unbox-primitive@1.1.0: @@ -34378,7 +34315,7 @@ snapshots: dependencies: react: 19.2.4 - user-agent-data-types@0.4.2: {} + user-agent-data-types@0.4.3: {} userhome@1.0.1: {} @@ -34466,7 +34403,7 @@ snapshots: - tsx - yaml - vite-plugin-dts@4.5.4(@types/node@24.12.0)(rollup@4.52.0)(typescript@5.9.3)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-dts@4.5.4(@types/node@24.12.0)(rollup@4.52.0)(typescript@5.9.3)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@microsoft/api-extractor': 7.52.8(@types/node@24.12.0) '@rollup/pluginutils': 5.1.4(rollup@4.52.0) @@ -34479,35 +34416,27 @@ snapshots: magic-string: 0.30.21 typescript: 5.9.3 optionalDependencies: - vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-static-copy@3.1.4(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-static-copy@3.4.0(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: chokidar: 3.6.0 p-map: 7.0.4 picocolors: 1.1.1 tinyglobby: 0.2.15 - vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) - vite-plugin-static-copy@3.3.0(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): - dependencies: - chokidar: 3.6.0 - p-map: 7.0.4 - picocolors: 1.1.1 - tinyglobby: 0.2.15 - vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) - - vite-plugin-svgo@2.0.0(typescript@5.9.3)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-plugin-svgo@2.0.0(typescript@5.9.3)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: svgo: 3.3.2 typescript: 5.9.3 - vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) - vite-prerender-plugin@0.5.11(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): + vite-prerender-plugin@0.5.11(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: kolorist: 1.8.0 magic-string: 0.30.21 @@ -34515,7 +34444,7 @@ snapshots: simple-code-frame: 1.3.0 source-map: 0.7.6 stack-trace: 1.0.0-pre2 - vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.32.0)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: @@ -34537,12 +34466,12 @@ snapshots: tsx: 4.21.0 yaml: 2.8.2 - vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2): + vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2): dependencies: lightningcss: 1.32.0 picomatch: 4.0.3 postcss: 8.5.8 - rolldown: 1.0.0-rc.10 + rolldown: 1.0.0-rc.11 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.12.0 @@ -34556,15 +34485,15 @@ snapshots: tsx: 4.21.0 yaml: 2.8.2 - vitest@4.1.0(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.0)(@vitest/ui@4.1.0)(happy-dom@20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): + vitest@4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.12.0)(@vitest/browser-webdriverio@4.1.2)(@vitest/ui@4.1.2)(happy-dom@20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5))(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)): dependencies: - '@vitest/expect': 4.1.0 - '@vitest/mocker': 4.1.0(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/pretty-format': 4.1.0 - '@vitest/runner': 4.1.0 - '@vitest/snapshot': 4.1.0 - '@vitest/spy': 4.1.0 - '@vitest/utils': 4.1.0 + '@vitest/expect': 4.1.2 + '@vitest/mocker': 4.1.2(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/pretty-format': 4.1.2 + '@vitest/runner': 4.1.2 + '@vitest/snapshot': 4.1.2 + '@vitest/spy': 4.1.2 + '@vitest/utils': 4.1.2 es-module-lexer: 2.0.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -34573,17 +34502,17 @@ snapshots: picomatch: 4.0.3 std-env: 4.0.0 tinybench: 2.9.0 - tinyexec: 1.0.2 + tinyexec: 1.0.4 tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + tinyrainbow: 3.1.0 + vite: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 24.12.0 - '@vitest/browser-webdriverio': 4.1.0(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.0)(webdriverio@9.26.1(bufferutil@4.0.9)(utf-8-validate@6.0.5)) - '@vitest/ui': 4.1.0(vitest@4.1.0) - happy-dom: 20.8.4(bufferutil@4.0.9)(utf-8-validate@6.0.5) + '@vitest/browser-webdriverio': 4.1.2(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.12.0)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2))(vitest@4.1.2)(webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) + '@vitest/ui': 4.1.2(vitest@4.1.2) + happy-dom: 20.8.8(bufferutil@4.0.9)(utf-8-validate@6.0.5) jsdom: 26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - msw @@ -34684,15 +34613,15 @@ snapshots: web-streams-polyfill@3.3.3: {} - webdriver@9.26.1(bufferutil@4.0.9)(utf-8-validate@6.0.5): + webdriver@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5): dependencies: '@types/node': 20.19.25 '@types/ws': 8.18.1 - '@wdio/config': 9.26.1 + '@wdio/config': 9.27.0 '@wdio/logger': 9.18.0 - '@wdio/protocols': 9.26.1 - '@wdio/types': 9.26.1 - '@wdio/utils': 9.26.1 + '@wdio/protocols': 9.27.0 + '@wdio/types': 9.27.0 + '@wdio/utils': 9.27.0 deepmerge-ts: 7.1.5 https-proxy-agent: 7.0.6 undici: 6.24.1 @@ -34703,16 +34632,16 @@ snapshots: - supports-color - utf-8-validate - webdriverio@9.26.1(bufferutil@4.0.9)(utf-8-validate@6.0.5): + webdriverio@9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5): dependencies: '@types/node': 20.19.25 '@types/sinonjs__fake-timers': 8.1.5 - '@wdio/config': 9.26.1 + '@wdio/config': 9.27.0 '@wdio/logger': 9.18.0 - '@wdio/protocols': 9.26.1 + '@wdio/protocols': 9.27.0 '@wdio/repl': 9.16.2 - '@wdio/types': 9.26.1 - '@wdio/utils': 9.26.1 + '@wdio/types': 9.27.0 + '@wdio/utils': 9.27.0 archiver: 7.0.1 aria-query: 5.3.2 cheerio: 1.2.0 @@ -34729,7 +34658,7 @@ snapshots: rgb2hex: 0.2.5 serialize-error: 12.0.0 urlpattern-polyfill: 10.1.0 - webdriver: 9.26.1(bufferutil@4.0.9)(utf-8-validate@6.0.5) + webdriver: 9.27.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bare-buffer - bufferutil @@ -35031,9 +34960,8 @@ snapshots: wrappy@1.0.2: {} - write-file-atomic@7.0.0: + write-file-atomic@7.0.1: dependencies: - imurmurhash: 0.1.4 signal-exit: 4.1.0 ws@7.5.10(bufferutil@4.0.9)(utf-8-validate@6.0.5): @@ -35046,11 +34974,6 @@ snapshots: bufferutil: 4.0.9 utf-8-validate: 6.0.5 - ws@8.19.0(bufferutil@4.0.9)(utf-8-validate@6.0.5): - optionalDependencies: - bufferutil: 4.0.9 - utf-8-validate: 6.0.5 - ws@8.20.0(bufferutil@4.0.9)(utf-8-validate@6.0.5): optionalDependencies: bufferutil: 4.0.9 @@ -35108,7 +35031,7 @@ snapshots: scule: 1.3.0 tinyglobby: 0.2.15 unimport: 5.6.0 - vite: 8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.2(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.1.3)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) vite-node: 5.3.0(@types/node@24.12.0)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.32.0)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.2) web-ext-run: 0.2.4 optionalDependencies: @@ -35174,8 +35097,6 @@ snapshots: yaml@2.0.0-1: {} - yaml@2.8.1: {} - yaml@2.8.2: {} yargs-parser@20.2.9: {} diff --git a/scripts/filter-tsc-output.mts b/scripts/filter-tsc-output.mts new file mode 100644 index 0000000000..bdc9263e4b --- /dev/null +++ b/scripts/filter-tsc-output.mts @@ -0,0 +1,46 @@ +/** + * Runs `tsc --build` and filters out noisy cascade errors (TS6305). + * Numbers each remaining error and prints a summary at the end. + */ + +import { execSync } from "child_process"; + +const SUPPRESSED_CODES = [ "TS6305" ]; +const ERROR_LINE_PATTERN = /^.+\(\d+,\d+\): error TS\d+:/; + +let output: string; +try { + output = execSync("tsc --build", { + encoding: "utf-8", + stdio: [ "inherit", "pipe", "pipe" ] + }); +} catch (err: unknown) { + const execErr = err as { stdout?: string; stderr?: string }; + output = (execErr.stdout ?? "") + (execErr.stderr ?? ""); +} + +const lines = output.split(/\r?\n/); +const filtered = lines.filter( + (line) => !SUPPRESSED_CODES.some((code) => line.includes(code)) +); + +let errorIndex = 0; +const numbered: string[] = []; + +for (const line of filtered) { + if (ERROR_LINE_PATTERN.test(line)) { + errorIndex++; + numbered.push(`[${errorIndex}] ${line}`); + } else if (line.trim()) { + // Continuation line (indented context for multi-line errors) + numbered.push(line); + } +} + +if (errorIndex > 0) { + console.log(numbered.join("\n")); + console.log(`\n${errorIndex} error(s) found.`); + process.exit(1); +} else { + console.log("No errors found."); +} diff --git a/scripts/synchronize-versions-for-standalone.ts b/scripts/synchronize-versions-for-standalone.ts new file mode 100644 index 0000000000..05840ea99c --- /dev/null +++ b/scripts/synchronize-versions-for-standalone.ts @@ -0,0 +1,19 @@ +import { readFileSync, writeFileSync } from "fs"; + +function synchronize(targetPackageJsonPath: string, sourcePackageJsonPath: string) { + const targetPackageJson = JSON.parse(readFileSync(targetPackageJsonPath, "utf-8")); + const sourcePackageJson = JSON.parse(readFileSync(sourcePackageJsonPath, "utf-8")); + + for (const prefix of ["dependencies", "devDependencies"]) { + for (const [packageName, version] of Object.entries(sourcePackageJson[prefix] || {})) { + if (targetPackageJson[prefix] && targetPackageJson[prefix][packageName]) { + targetPackageJson[prefix][packageName] = version; + } + } + } + + writeFileSync(targetPackageJsonPath, JSON.stringify(targetPackageJson, null, 2)); +} + +synchronize("packages/trilium-core/package.json", "apps/server/package.json"); +synchronize("apps/client-standalone/package.json", "apps/client/package.json");