From 0fe89115d13b84d3e74b16fbf5775ac8c0fa12f0 Mon Sep 17 00:00:00 2001 From: perf3ct Date: Tue, 17 Jun 2025 19:37:40 +0000 Subject: [PATCH 01/21] feat(server): lint for trailing slashes in sync URL and extra slashes in customRequestHandler --- apps/server/src/routes/custom.ts | 26 +++++--- apps/server/src/services/sync_options.ts | 6 +- apps/server/src/services/utils.ts | 81 ++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 10 deletions(-) diff --git a/apps/server/src/routes/custom.ts b/apps/server/src/routes/custom.ts index e4494a6cd..e667a371b 100644 --- a/apps/server/src/routes/custom.ts +++ b/apps/server/src/routes/custom.ts @@ -5,7 +5,7 @@ import cls from "../services/cls.js"; import sql from "../services/sql.js"; import becca from "../becca/becca.js"; import type { Request, Response, Router } from "express"; -import { safeExtractMessageAndStackFromError } from "../services/utils.js"; +import { safeExtractMessageAndStackFromError, normalizeCustomHandlerPattern } from "../services/utils.js"; function handleRequest(req: Request, res: Response) { @@ -38,15 +38,23 @@ function handleRequest(req: Request, res: Response) { continue; } - const regex = new RegExp(`^${attr.value}$`); - let match; + // Get normalized patterns to handle both trailing slash cases + const patterns = normalizeCustomHandlerPattern(attr.value); + let match = null; - try { - match = path.match(regex); - } catch (e: unknown) { - const [errMessage, errStack] = safeExtractMessageAndStackFromError(e); - log.error(`Testing path for label '${attr.attributeId}', regex '${attr.value}' failed with error: ${errMessage}, stack: ${errStack}`); - continue; + // Try each pattern until we find a match + for (const pattern of patterns) { + try { + const regex = new RegExp(`^${pattern}$`); + match = path.match(regex); + if (match) { + break; // Found a match, exit pattern loop + } + } catch (e: unknown) { + const [errMessage, errStack] = safeExtractMessageAndStackFromError(e); + log.error(`Testing path for label '${attr.attributeId}', regex '${pattern}' failed with error: ${errMessage}, stack: ${errStack}`); + continue; + } } if (!match) { diff --git a/apps/server/src/services/sync_options.ts b/apps/server/src/services/sync_options.ts index 289323b54..657c1b214 100644 --- a/apps/server/src/services/sync_options.ts +++ b/apps/server/src/services/sync_options.ts @@ -2,6 +2,7 @@ import optionService from "./options.js"; import config from "./config.js"; +import { normalizeUrl } from "./utils.js"; /* * Primary configuration for sync is in the options (document), but we allow to override @@ -17,7 +18,10 @@ function get(name: keyof typeof config.Sync) { export default { // env variable is the easiest way to guarantee we won't overwrite prod data during development // after copying prod document/data directory - getSyncServerHost: () => get("syncServerHost"), + getSyncServerHost: () => { + const host = get("syncServerHost"); + return host ? normalizeUrl(host) : host; + }, isSyncSetup: () => { const syncServerHost = get("syncServerHost"); diff --git a/apps/server/src/services/utils.ts b/apps/server/src/services/utils.ts index 89aad1bbb..d33e54879 100644 --- a/apps/server/src/services/utils.ts +++ b/apps/server/src/services/utils.ts @@ -375,6 +375,85 @@ export function safeExtractMessageAndStackFromError(err: unknown): [errMessage: return (err instanceof Error) ? [err.message, err.stack] as const : ["Unknown Error", undefined] as const; } +/** + * Normalizes URL by removing trailing slashes and fixing double slashes. + * Preserves the protocol (http://, https://) but removes trailing slashes from the rest. + * + * @param url The URL to normalize + * @returns The normalized URL without trailing slashes + */ +export function normalizeUrl(url: string): string { + if (!url || typeof url !== 'string') { + return url; + } + + // Trim whitespace + url = url.trim(); + + if (!url) { + return url; + } + + // Remove trailing slash, but preserve protocol + if (url.endsWith('/') && !url.match(/^https?:\/\/$/)) { + url = url.slice(0, -1); + } + + // Fix double slashes (except in protocol) + url = url.replace(/([^:]\/)\/+/g, '$1'); + + return url; +} + +/** + * 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): string[] { + 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]; + } else { + // 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]; + } else { + const withSlash = pattern + '/'; + return [pattern, withSlash]; + } +} + export default { compareVersions, @@ -400,6 +479,8 @@ export default { md5, newEntityId, normalize, + normalizeCustomHandlerPattern, + normalizeUrl, quoteRegex, randomSecureToken, randomString, From b47180a21984ec1d40917e6d00cca287409e1709 Mon Sep 17 00:00:00 2001 From: perf3ct Date: Tue, 17 Jun 2025 21:32:27 +0000 Subject: [PATCH 02/21] feat(server): create unit tests for normalizing server URL, and fix logic based on feedback --- apps/server/src/services/utils.spec.ts | 53 ++++++++++++++++++++++++++ apps/server/src/services/utils.ts | 6 +-- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/apps/server/src/services/utils.spec.ts b/apps/server/src/services/utils.spec.ts index f6b21a8cd..6e027b7bd 100644 --- a/apps/server/src/services/utils.spec.ts +++ b/apps/server/src/services/utils.spec.ts @@ -628,3 +628,56 @@ describe("#formatDownloadTitle", () => { }); }); }); + +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); + }); + }); +}); diff --git a/apps/server/src/services/utils.ts b/apps/server/src/services/utils.ts index d33e54879..c3afd0ef0 100644 --- a/apps/server/src/services/utils.ts +++ b/apps/server/src/services/utils.ts @@ -394,14 +394,14 @@ export function normalizeUrl(url: string): string { return url; } + // Fix double slashes (except in protocol) first + url = url.replace(/([^:]\/)\/+/g, '$1'); + // Remove trailing slash, but preserve protocol if (url.endsWith('/') && !url.match(/^https?:\/\/$/)) { url = url.slice(0, -1); } - // Fix double slashes (except in protocol) - url = url.replace(/([^:]\/)\/+/g, '$1'); - return url; } From fbd6b7d22be3868d006206fbf52915bff0c5ead2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 06:11:31 +0000 Subject: [PATCH 03/21] chore(deps): update vitest monorepo to v3.2.4 --- _regroup/package.json | 2 +- pnpm-lock.yaml | 164 ++++++++++++++++++++++++------------------ 2 files changed, 97 insertions(+), 69 deletions(-) diff --git a/_regroup/package.json b/_regroup/package.json index b9f2dad6a..d423baef2 100644 --- a/_regroup/package.json +++ b/_regroup/package.json @@ -40,7 +40,7 @@ "@types/express": "5.0.3", "@types/node": "22.15.31", "@types/yargs": "17.0.33", - "@vitest/coverage-v8": "3.2.3", + "@vitest/coverage-v8": "3.2.4", "eslint": "9.29.0", "eslint-plugin-simple-import-sort": "12.1.1", "esm": "3.2.25", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 91bb6ee8d..75e1afc42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -64,7 +64,7 @@ importers: version: 21.2.0(@babel/traverse@7.27.0)(@playwright/test@1.53.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(@zkochan/js-yaml@0.0.7)(eslint@9.29.0(jiti@2.4.2))(nx@21.2.0(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.8.3) '@nx/vite': specifier: 21.2.0 - version: 21.2.0(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.2.0(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.3) + version: 21.2.0(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.2.0(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4) '@nx/web': specifier: 21.2.0 version: 21.2.0(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.2.0(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))) @@ -82,10 +82,10 @@ importers: version: 22.15.31 '@vitest/coverage-v8': specifier: ^3.0.5 - version: 3.2.3(vitest@3.2.3) + version: 3.2.4(vitest@3.2.4) '@vitest/ui': specifier: ^3.0.0 - version: 3.2.3(vitest@3.2.3) + version: 3.2.4(vitest@3.2.4) chalk: specifier: 5.4.1 version: 5.4.1 @@ -148,7 +148,7 @@ importers: version: 4.5.4(@types/node@22.15.31)(rollup@4.40.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0)) vitest: specifier: ^3.0.0 - version: 3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(@vitest/ui@3.2.3)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.31)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0) apps/client: dependencies: @@ -5611,11 +5611,11 @@ packages: peerDependencies: vitest: 3.2.0 - '@vitest/coverage-v8@3.2.3': - resolution: {integrity: sha512-D1QKzngg8PcDoCE8FHSZhREDuEy+zcKmMiMafYse41RZpBE5EDJyKOTdqK3RQfsV2S2nyKor5KCs8PyPRFqKPg==} + '@vitest/coverage-v8@3.2.4': + resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} peerDependencies: - '@vitest/browser': 3.2.3 - vitest: 3.2.3 + '@vitest/browser': 3.2.4 + vitest: 3.2.4 peerDependenciesMeta: '@vitest/browser': optional: true @@ -5623,8 +5623,8 @@ packages: '@vitest/expect@3.2.0': resolution: {integrity: sha512-0v4YVbhDKX3SKoy0PHWXpKhj44w+3zZkIoVES9Ex2pq+u6+Bijijbi2ua5kE+h3qT6LBWFTNZSCOEU37H8Y5sA==} - '@vitest/expect@3.2.3': - resolution: {integrity: sha512-W2RH2TPWVHA1o7UmaFKISPvdicFJH+mjykctJFoAkUw+SPTJTGjUNdKscFBrqM7IPnCVu6zihtKYa7TkZS1dkQ==} + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} '@vitest/mocker@3.2.0': resolution: {integrity: sha512-HFcW0lAMx3eN9vQqis63H0Pscv0QcVMo1Kv8BNysZbxcmHu3ZUYv59DS6BGYiGQ8F5lUkmsfMMlPm4DJFJdf/A==} @@ -5637,8 +5637,8 @@ packages: vite: optional: true - '@vitest/mocker@3.2.3': - resolution: {integrity: sha512-cP6fIun+Zx8he4rbWvi+Oya6goKQDZK+Yq4hhlggwQBbrlOQ4qtZ+G4nxB6ZnzI9lyIb+JnvyiJnPC2AGbKSPA==} + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: msw: ^2.4.9 vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 @@ -5651,42 +5651,42 @@ packages: '@vitest/pretty-format@3.2.0': resolution: {integrity: sha512-gUUhaUmPBHFkrqnOokmfMGRBMHhgpICud9nrz/xpNV3/4OXCn35oG+Pl8rYYsKaTNd/FAIrqRHnwpDpmYxCYZw==} - '@vitest/pretty-format@3.2.3': - resolution: {integrity: sha512-yFglXGkr9hW/yEXngO+IKMhP0jxyFw2/qys/CK4fFUZnSltD+MU7dVYGrH8rvPcK/O6feXQA+EU33gjaBBbAng==} + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} '@vitest/runner@3.2.0': resolution: {integrity: sha512-bXdmnHxuB7fXJdh+8vvnlwi/m1zvu+I06i1dICVcDQFhyV4iKw2RExC/acavtDn93m/dRuawUObKsrNE1gJacA==} - '@vitest/runner@3.2.3': - resolution: {integrity: sha512-83HWYisT3IpMaU9LN+VN+/nLHVBCSIUKJzGxC5RWUOsK1h3USg7ojL+UXQR3b4o4UBIWCYdD2fxuzM7PQQ1u8w==} + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} '@vitest/snapshot@3.2.0': resolution: {integrity: sha512-z7P/EneBRMe7hdvWhcHoXjhA6at0Q4ipcoZo6SqgxLyQQ8KSMMCmvw1cSt7FHib3ozt0wnRHc37ivuUMbxzG/A==} - '@vitest/snapshot@3.2.3': - resolution: {integrity: sha512-9gIVWx2+tysDqUmmM1L0hwadyumqssOL1r8KJipwLx5JVYyxvVRfxvMq7DaWbZZsCqZnu/dZedaZQh4iYTtneA==} + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} '@vitest/spy@3.2.0': resolution: {integrity: sha512-s3+TkCNUIEOX99S0JwNDfsHRaZDDZZR/n8F0mop0PmsEbQGKZikCGpTGZ6JRiHuONKew3Fb5//EPwCP+pUX9cw==} - '@vitest/spy@3.2.3': - resolution: {integrity: sha512-JHu9Wl+7bf6FEejTCREy+DmgWe+rQKbK+y32C/k5f4TBIAlijhJbRBIRIOCEpVevgRsCQR2iHRUH2/qKVM/plw==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} '@vitest/ui@3.2.0': resolution: {integrity: sha512-cYFZZSl1usgzsHoGF66GHfYXlEwc06ggapS1TaSLMKCzhTPWBPI9b/t1RvKIsLSjdKUakpSPf33jQMvRjMvvlQ==} peerDependencies: vitest: 3.2.0 - '@vitest/ui@3.2.3': - resolution: {integrity: sha512-9aR2tY/WT7GRHGEH/9sSIipJqeA21Eh3C6xmiOVmfyBCFmezUSUFLalpaSmRHlRzWCKQU10yz3AHhKuYcdnZGQ==} + '@vitest/ui@3.2.4': + resolution: {integrity: sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==} peerDependencies: - vitest: 3.2.3 + vitest: 3.2.4 '@vitest/utils@3.2.0': resolution: {integrity: sha512-gXXOe7Fj6toCsZKVQouTRLJftJwmvbhH5lKOBR6rlP950zUq9AitTUjnFoXS/CqjBC2aoejAztLPzzuva++XBw==} - '@vitest/utils@3.2.3': - resolution: {integrity: sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} '@volar/language-core@2.4.13': resolution: {integrity: sha512-MnQJ7eKchJx5Oz+YdbqyFUk8BN6jasdJv31n/7r6/WwlOOv7qzvot6B66887l2ST3bUW4Mewml54euzpJWA6bg==} @@ -9870,6 +9870,9 @@ packages: loupe@3.1.3: resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + loupe@3.1.4: + resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} + lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} @@ -13007,6 +13010,10 @@ packages: resolution: {integrity: sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==} engines: {node: ^18.0.0 || >=20.0.0} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + tinyrainbow@2.0.0: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} @@ -13491,8 +13498,8 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true - vite-node@3.2.3: - resolution: {integrity: sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true @@ -13593,16 +13600,16 @@ packages: jsdom: optional: true - vitest@3.2.3: - resolution: {integrity: sha512-E6U2ZFXe3N/t4f5BwUaVCKRLHqUpk1CBWeMh78UT4VaTPH/2dyvH6ALl29JTovEPu9dVKr/K/J4PkXgrMbw4Ww==} + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/debug': ^4.1.12 '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.3 - '@vitest/ui': 3.2.3 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -18341,7 +18348,7 @@ snapshots: - typescript - verdaccio - '@nx/vite@21.2.0(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.2.0(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.3)': + '@nx/vite@21.2.0(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.2.0(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17)))(typescript@5.8.3)(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0))(vitest@3.2.4)': dependencies: '@nx/devkit': 21.2.0(nx@21.2.0(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))) '@nx/js': 21.2.0(patch_hash=7201af3a8fb4840b046e4e18cc2758fa67ee3d0cf11d0783869dc828cfc79fc7)(@babel/traverse@7.27.0)(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))(nx@21.2.0(@swc-node/register@1.10.10(@swc/core@1.11.29(@swc/helpers@0.5.17))(@swc/types@0.1.21)(typescript@5.8.3))(@swc/core@1.11.29(@swc/helpers@0.5.17))) @@ -18353,7 +18360,7 @@ snapshots: semver: 7.7.2 tsconfig-paths: 4.2.0 vite: 6.3.5(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0) - vitest: 3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(@vitest/ui@3.2.3)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.31)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -20233,6 +20240,23 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/eslint-plugin@8.34.0(@typescript-eslint/parser@8.34.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.34.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.34.0 + '@typescript-eslint/type-utils': 8.34.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.34.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.34.0 + eslint: 9.28.0(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 7.0.4 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/eslint-plugin@8.34.0(@typescript-eslint/parser@8.34.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -20468,7 +20492,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@3.2.3(vitest@3.2.3)': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4)': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -20483,7 +20507,7 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(@vitest/ui@3.2.3)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.31)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - supports-color @@ -20495,11 +20519,11 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/expect@3.2.3': + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.2 - '@vitest/spy': 3.2.3 - '@vitest/utils': 3.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.2.0 tinyrainbow: 2.0.0 @@ -20512,9 +20536,9 @@ snapshots: msw: 2.7.5(@types/node@22.15.31)(typescript@5.8.3) vite: 6.3.5(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0) - '@vitest/mocker@3.2.3(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0))': + '@vitest/mocker@3.2.4(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0))': dependencies: - '@vitest/spy': 3.2.3 + '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: @@ -20525,7 +20549,7 @@ snapshots: dependencies: tinyrainbow: 2.0.0 - '@vitest/pretty-format@3.2.3': + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 @@ -20534,9 +20558,9 @@ snapshots: '@vitest/utils': 3.2.0 pathe: 2.0.3 - '@vitest/runner@3.2.3': + '@vitest/runner@3.2.4': dependencies: - '@vitest/utils': 3.2.3 + '@vitest/utils': 3.2.4 pathe: 2.0.3 strip-literal: 3.0.0 @@ -20546,9 +20570,9 @@ snapshots: magic-string: 0.30.17 pathe: 2.0.3 - '@vitest/snapshot@3.2.3': + '@vitest/snapshot@3.2.4': dependencies: - '@vitest/pretty-format': 3.2.3 + '@vitest/pretty-format': 3.2.4 magic-string: 0.30.17 pathe: 2.0.3 @@ -20556,7 +20580,7 @@ snapshots: dependencies: tinyspy: 4.0.3 - '@vitest/spy@3.2.3': + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.3 @@ -20572,16 +20596,16 @@ snapshots: vitest: 3.2.0(@types/debug@4.1.12)(@types/node@22.15.31)(@vitest/browser@3.2.0)(@vitest/ui@3.2.0)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0) optional: true - '@vitest/ui@3.2.3(vitest@3.2.3)': + '@vitest/ui@3.2.4(vitest@3.2.4)': dependencies: - '@vitest/utils': 3.2.3 + '@vitest/utils': 3.2.4 fflate: 0.8.2 flatted: 3.3.3 pathe: 2.0.3 sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(@vitest/ui@3.2.3)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.31)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0) '@vitest/utils@3.2.0': dependencies: @@ -20589,10 +20613,10 @@ snapshots: loupe: 3.1.3 tinyrainbow: 2.0.0 - '@vitest/utils@3.2.3': + '@vitest/utils@3.2.4': dependencies: - '@vitest/pretty-format': 3.2.3 - loupe: 3.1.3 + '@vitest/pretty-format': 3.2.4 + loupe: 3.1.4 tinyrainbow: 2.0.0 '@volar/language-core@2.4.13': @@ -21547,7 +21571,7 @@ snapshots: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.3 + loupe: 3.1.4 pathval: 2.0.0 chalk@2.4.2: @@ -25736,6 +25760,8 @@ snapshots: loupe@3.1.3: {} + loupe@3.1.4: {} + lowercase-keys@2.0.0: {} lru-cache@10.4.3: {} @@ -26003,7 +26029,7 @@ snapshots: minimatch@9.0.5: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 minimist@1.2.8: {} @@ -28508,7 +28534,7 @@ snapshots: socket.io-parser@4.2.4: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.1 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -29312,6 +29338,8 @@ snapshots: tinypool@1.1.0: {} + tinypool@1.1.1: {} + tinyrainbow@2.0.0: {} tinyspy@4.0.3: {} @@ -29568,7 +29596,7 @@ snapshots: typescript-eslint@8.34.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.34.0(@typescript-eslint/parser@8.34.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.34.0(@typescript-eslint/parser@8.34.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) '@typescript-eslint/parser': 8.34.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) '@typescript-eslint/utils': 8.34.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.28.0(jiti@2.4.2) @@ -29825,7 +29853,7 @@ snapshots: - tsx - yaml - vite-node@3.2.3(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0): + vite-node@3.2.4(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: cac: 6.7.14 debug: 4.4.1(supports-color@6.0.0) @@ -29952,16 +29980,16 @@ snapshots: - tsx - yaml - vitest@3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(@vitest/ui@3.2.3)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.31)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.4.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 - '@vitest/expect': 3.2.3 - '@vitest/mocker': 3.2.3(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0)) - '@vitest/pretty-format': 3.2.3 - '@vitest/runner': 3.2.3 - '@vitest/snapshot': 3.2.3 - '@vitest/spy': 3.2.3 - '@vitest/utils': 3.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(msw@2.7.5(@types/node@22.15.31)(typescript@5.8.3))(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.2.0 debug: 4.4.1(supports-color@6.0.0) expect-type: 1.2.1 @@ -29972,15 +30000,15 @@ snapshots: tinybench: 2.9.0 tinyexec: 0.3.2 tinyglobby: 0.2.14 - tinypool: 1.1.0 + tinypool: 1.1.1 tinyrainbow: 2.0.0 vite: 6.3.5(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0) - vite-node: 3.2.3(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@22.15.31)(jiti@2.4.2)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.87.0)(sass@1.87.0)(stylus@0.64.0)(sugarss@4.0.1(postcss@8.5.3))(terser@5.39.0)(tsx@4.20.3)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 '@types/node': 22.15.31 - '@vitest/ui': 3.2.3(vitest@3.2.3) + '@vitest/ui': 3.2.4(vitest@3.2.4) happy-dom: 18.0.1 jsdom: 26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: From acd68817e9e6900049be81b68ad0d81be7e79de4 Mon Sep 17 00:00:00 2001 From: perf3ct Date: Wed, 18 Jun 2025 20:46:11 +0000 Subject: [PATCH 04/21] feat(server): fix lint type errors for normalizing server URLs --- apps/server/src/routes/custom.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/server/src/routes/custom.ts b/apps/server/src/routes/custom.ts index e667a371b..b27a710f2 100644 --- a/apps/server/src/routes/custom.ts +++ b/apps/server/src/routes/custom.ts @@ -40,21 +40,21 @@ function handleRequest(req: Request, res: Response) { // Get normalized patterns to handle both trailing slash cases const patterns = normalizeCustomHandlerPattern(attr.value); - let match = null; + let match: RegExpMatchArray | null = null; - // Try each pattern until we find a match - for (const pattern of patterns) { - try { + try { + // Try each pattern until we find a match + for (const pattern of patterns) { const regex = new RegExp(`^${pattern}$`); match = path.match(regex); if (match) { break; // Found a match, exit pattern loop } - } catch (e: unknown) { - const [errMessage, errStack] = safeExtractMessageAndStackFromError(e); - log.error(`Testing path for label '${attr.attributeId}', regex '${pattern}' failed with error: ${errMessage}, stack: ${errStack}`); - continue; } + } catch (e: unknown) { + const [errMessage, errStack] = safeExtractMessageAndStackFromError(e); + log.error(`Testing path for label '${attr.attributeId}', regex '${attr.value}' failed with error: ${errMessage}, stack: ${errStack}`); + continue; } if (!match) { From 2704b1546b4c328f33e46cb87d90140c4ce08060 Mon Sep 17 00:00:00 2001 From: perf3ct Date: Wed, 18 Jun 2025 21:07:12 +0000 Subject: [PATCH 05/21] feat(server): fix lint type errors for normalizing server URLs --- apps/server/src/services/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/src/services/utils.ts b/apps/server/src/services/utils.ts index c3afd0ef0..5d5559d25 100644 --- a/apps/server/src/services/utils.ts +++ b/apps/server/src/services/utils.ts @@ -382,7 +382,7 @@ export function safeExtractMessageAndStackFromError(err: unknown): [errMessage: * @param url The URL to normalize * @returns The normalized URL without trailing slashes */ -export function normalizeUrl(url: string): string { +export function normalizeUrl(url: string | null | undefined): string | null | undefined { if (!url || typeof url !== 'string') { return url; } @@ -412,7 +412,7 @@ export function normalizeUrl(url: string): string { * @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): string[] { +export function normalizeCustomHandlerPattern(pattern: string | null | undefined): (string | null | undefined)[] { if (!pattern || typeof pattern !== 'string') { return [pattern]; } From b809137c9308c3761797e305cf21d146d93c1752 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 19 Jun 2025 18:47:41 +0300 Subject: [PATCH 06/21] refactor(text): move license key management to config --- .../src/widgets/type_widgets/ckeditor/config.ts | 11 +++++++++++ .../client/src/widgets/type_widgets/editable_text.ts | 12 +----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/ckeditor/config.ts b/apps/client/src/widgets/type_widgets/ckeditor/config.ts index 03bd5fc07..771a0ea33 100644 --- a/apps/client/src/widgets/type_widgets/ckeditor/config.ts +++ b/apps/client/src/widgets/type_widgets/ckeditor/config.ts @@ -16,6 +16,7 @@ const TEXT_FORMATTING_GROUP = { export async function buildConfig(): Promise { return { + licenseKey: getLicenseKey(), image: { styles: { options: [ @@ -276,3 +277,13 @@ export function buildFloatingToolbar() { ] }; } + +function getLicenseKey() { + const premiumLicenseKey = import.meta.env.VITE_CKEDITOR_KEY; + if (!premiumLicenseKey) { + logError("CKEditor license key is not set, premium features will not be available."); + return "GPL"; + } + + return premiumLicenseKey; +} diff --git a/apps/client/src/widgets/type_widgets/editable_text.ts b/apps/client/src/widgets/type_widgets/editable_text.ts index 28094f2a2..0b047d54c 100644 --- a/apps/client/src/widgets/type_widgets/editable_text.ts +++ b/apps/client/src/widgets/type_widgets/editable_text.ts @@ -201,8 +201,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { styles: true, classes: true, attributes: true - }, - licenseKey: getLicenseKey() + } }; const contentLanguage = this.note?.getLabelValue("language"); @@ -656,12 +655,3 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { } -function getLicenseKey() { - const premiumLicenseKey = import.meta.env.VITE_CKEDITOR_KEY; - if (!premiumLicenseKey) { - logError("CKEditor license key is not set, premium features will not be available."); - return "GPL"; - } - - return premiumLicenseKey; -} From e280968271e253e5cded3f6dc872405c44f97c09 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 19 Jun 2025 18:53:39 +0300 Subject: [PATCH 07/21] feat(ckeditor): allow use of GPL license --- .../widgets/type_widgets/ckeditor/config.ts | 21 ++++++++++++++++--- .../Note Types/Text/Premium features.md | 4 +++- packages/ckeditor5/src/index.ts | 2 +- packages/ckeditor5/src/plugins.ts | 7 +++---- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/ckeditor/config.ts b/apps/client/src/widgets/type_widgets/ckeditor/config.ts index 771a0ea33..809354e22 100644 --- a/apps/client/src/widgets/type_widgets/ckeditor/config.ts +++ b/apps/client/src/widgets/type_widgets/ckeditor/config.ts @@ -8,6 +8,9 @@ import utils from "../../../services/utils.js"; import emojiDefinitionsUrl from "@triliumnext/ckeditor5/emoji_definitions/en.json?url"; import { copyTextWithToast } from "../../../services/clipboard_ext.js"; import getTemplates from "./snippets.js"; +import { PREMIUM_PLUGINS } from "../../../../../../packages/ckeditor5/src/plugins.js"; + +const OPEN_SOURCE_LICENSE_KEY = "GPL"; const TEXT_FORMATTING_GROUP = { label: "Text formatting", @@ -15,8 +18,11 @@ const TEXT_FORMATTING_GROUP = { }; export async function buildConfig(): Promise { - return { - licenseKey: getLicenseKey(), + const licenseKey = getLicenseKey(); + const hasPremiumLicense = (licenseKey !== OPEN_SOURCE_LICENSE_KEY); + + const config: EditorConfig = { + licenseKey, image: { styles: { options: [ @@ -134,6 +140,15 @@ export async function buildConfig(): Promise { // This value must be kept in sync with the language defined in webpack.config.js. language: "en" }; + + // Enable premium plugins. + if (hasPremiumLicense) { + config.extraPlugins = [ + ...PREMIUM_PLUGINS + ]; + } + + return config; } export function buildToolbarConfig(isClassicToolbar: boolean) { @@ -282,7 +297,7 @@ function getLicenseKey() { const premiumLicenseKey = import.meta.env.VITE_CKEDITOR_KEY; if (!premiumLicenseKey) { logError("CKEditor license key is not set, premium features will not be available."); - return "GPL"; + return OPEN_SOURCE_LICENSE_KEY; } return premiumLicenseKey; diff --git a/docs/User Guide/User Guide/Note Types/Text/Premium features.md b/docs/User Guide/User Guide/Note Types/Text/Premium features.md index 1e2dd43e2..5777f59c0 100644 --- a/docs/User Guide/User Guide/Note Types/Text/Premium features.md +++ b/docs/User Guide/User Guide/Note Types/Text/Premium features.md @@ -9,4 +9,6 @@ The license key is stored in the application and it enables the use of the previ ## Can I opt out of these features? -At this moment there is no way to disable this features, apart from manually modifying the source code. If this is a problem, [let us know](../../Troubleshooting/Reporting%20issues.md). \ No newline at end of file +At this moment there is no way to disable these features, apart from manually modifying the source code. If this is a problem, [let us know](../../Troubleshooting/Reporting%20issues.md). + +If you have the possibility of rebuilding the source code (e.g. if a package maintainer), then modify `VITE_CKEDITOR_KEY` in `apps/client/.env` to be `GPL`. \ No newline at end of file diff --git a/packages/ckeditor5/src/index.ts b/packages/ckeditor5/src/index.ts index 932c12d1d..1bdaed5be 100644 --- a/packages/ckeditor5/src/index.ts +++ b/packages/ckeditor5/src/index.ts @@ -1,6 +1,6 @@ import "ckeditor5/ckeditor5.css"; import "./theme/code_block_toolbar.css"; -import { COMMON_PLUGINS, CORE_PLUGINS, POPUP_EDITOR_PLUGINS } from "./plugins"; +import { COMMON_PLUGINS, CORE_PLUGINS, POPUP_EDITOR_PLUGINS, PREMIUM_PLUGINS } from "./plugins"; import { BalloonEditor, DecoupledEditor, FindAndReplaceEditing, FindCommand } from "ckeditor5"; import "./translation_overrides.js"; export { EditorWatchdog } from "ckeditor5"; diff --git a/packages/ckeditor5/src/plugins.ts b/packages/ckeditor5/src/plugins.ts index 1911c9e64..fae78f40b 100644 --- a/packages/ckeditor5/src/plugins.ts +++ b/packages/ckeditor5/src/plugins.ts @@ -1,4 +1,4 @@ -import { Autoformat, AutoLink, BlockQuote, BlockToolbar, Bold, CKFinderUploadAdapter, Clipboard, Code, CodeBlock, Enter, FindAndReplace, Font, FontBackgroundColor, FontColor, GeneralHtmlSupport, Heading, HeadingButtonsUI, HorizontalLine, Image, ImageCaption, ImageInline, ImageResize, ImageStyle, ImageToolbar, ImageUpload, Alignment, Indent, IndentBlock, Italic, Link, List, ListProperties, Mention, PageBreak, Paragraph, ParagraphButtonUI, PasteFromOffice, PictureEditing, RemoveFormat, SelectAll, ShiftEnter, SpecialCharacters, SpecialCharactersEssentials, Strikethrough, Style, Subscript, Superscript, Table, TableCaption, TableCellProperties, TableColumnResize, TableProperties, TableSelection, TableToolbar, TextPartLanguage, TextTransformation, TodoList, Typing, Underline, Undo, Bookmark, Emoji } from "ckeditor5"; +import { Autoformat, AutoLink, BlockQuote, BlockToolbar, Bold, CKFinderUploadAdapter, Clipboard, Code, CodeBlock, Enter, FindAndReplace, Font, FontBackgroundColor, FontColor, GeneralHtmlSupport, Heading, HeadingButtonsUI, HorizontalLine, Image, ImageCaption, ImageInline, ImageResize, ImageStyle, ImageToolbar, ImageUpload, Alignment, Indent, IndentBlock, Italic, Link, List, ListProperties, Mention, PageBreak, Paragraph, ParagraphButtonUI, PasteFromOffice, PictureEditing, RemoveFormat, SelectAll, ShiftEnter, SpecialCharacters, SpecialCharactersEssentials, Strikethrough, Style, Subscript, Superscript, Table, TableCaption, TableCellProperties, TableColumnResize, TableProperties, TableSelection, TableToolbar, TextPartLanguage, TextTransformation, TodoList, Typing, Underline, Undo, Bookmark, Emoji, Notification } from "ckeditor5"; import { SlashCommand, Template } from "ckeditor5-premium-features"; import type { Plugin } from "ckeditor5"; import CutToNotePlugin from "./plugins/cuttonote.js"; @@ -148,8 +148,7 @@ export const COMMON_PLUGINS: typeof Plugin[] = [ Emoji, ...TRILIUM_PLUGINS, - ...EXTERNAL_PLUGINS, - ...PREMIUM_PLUGINS + ...EXTERNAL_PLUGINS ]; /** @@ -157,5 +156,5 @@ export const COMMON_PLUGINS: typeof Plugin[] = [ */ export const POPUP_EDITOR_PLUGINS: typeof Plugin[] = [ ...COMMON_PLUGINS, - BlockToolbar + BlockToolbar, ]; From 0325bee425e8315b7522b1ea454f6ef24254807f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 19 Jun 2025 19:38:10 +0300 Subject: [PATCH 08/21] feat(ckeditor): fallback to GPL if license key fails --- .../widgets/type_widgets/ckeditor/config.ts | 220 ++++++------------ .../{config.spec.ts => toolbar.spec.ts} | 2 +- .../widgets/type_widgets/ckeditor/toolbar.ts | 149 ++++++++++++ .../src/widgets/type_widgets/editable_text.ts | 113 ++------- apps/client/vite.config.mts | 3 + 5 files changed, 253 insertions(+), 234 deletions(-) rename apps/client/src/widgets/type_widgets/ckeditor/{config.spec.ts => toolbar.spec.ts} (94%) create mode 100644 apps/client/src/widgets/type_widgets/ckeditor/toolbar.ts diff --git a/apps/client/src/widgets/type_widgets/ckeditor/config.ts b/apps/client/src/widgets/type_widgets/ckeditor/config.ts index 809354e22..4d4e1a27c 100644 --- a/apps/client/src/widgets/type_widgets/ckeditor/config.ts +++ b/apps/client/src/widgets/type_widgets/ckeditor/config.ts @@ -4,25 +4,64 @@ import { buildExtraCommands, type EditorConfig } from "@triliumnext/ckeditor5"; import { getHighlightJsNameForMime } from "../../../services/mime_types.js"; import options from "../../../services/options.js"; import { ensureMimeTypesForHighlighting, isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js"; -import utils from "../../../services/utils.js"; import emojiDefinitionsUrl from "@triliumnext/ckeditor5/emoji_definitions/en.json?url"; import { copyTextWithToast } from "../../../services/clipboard_ext.js"; import getTemplates from "./snippets.js"; import { PREMIUM_PLUGINS } from "../../../../../../packages/ckeditor5/src/plugins.js"; +import { t } from "../../../services/i18n.js"; +import { getMermaidConfig } from "../../../services/mermaid.js"; +import noteAutocompleteService, { type Suggestion } from "../../../services/note_autocomplete.js"; +import mimeTypesService from "../../../services/mime_types.js"; +import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons"; +import { buildToolbarConfig } from "./toolbar.js"; -const OPEN_SOURCE_LICENSE_KEY = "GPL"; +export const OPEN_SOURCE_LICENSE_KEY = "GPL"; -const TEXT_FORMATTING_GROUP = { - label: "Text formatting", - icon: "text" -}; +export interface BuildEditorOptions { + forceGplLicense: boolean; + isClassicEditor: boolean; + contentLanguage: string | null; +} -export async function buildConfig(): Promise { - const licenseKey = getLicenseKey(); +export async function buildConfig(opts: BuildEditorOptions): Promise { + const licenseKey = (opts.forceGplLicense ? OPEN_SOURCE_LICENSE_KEY : getLicenseKey()); const hasPremiumLicense = (licenseKey !== OPEN_SOURCE_LICENSE_KEY); const config: EditorConfig = { licenseKey, + placeholder: t("editable_text.placeholder"), + mention: { + feeds: [ + { + marker: "@", + feed: (queryText: string) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText), + itemRenderer: (item) => { + const itemElement = document.createElement("button"); + + itemElement.innerHTML = `${(item as Suggestion).highlightedNotePathTitle} `; + + return itemElement; + }, + minimumCharacters: 0 + } + ], + }, + codeBlock: { + languages: buildListOfLanguages() + }, + math: { + engine: "katex", + outputType: "span", // or script + lazyLoad: async () => { + (window as any).katex = (await import("../../../services/math.js")).default + }, + forceOutputType: false, // forces output to use outputType + enablePreview: true // Enable preview view + }, + mermaid: { + lazyLoad: async () => (await import("mermaid")).default, // FIXME + config: getMermaidConfig() + }, image: { styles: { options: [ @@ -137,10 +176,22 @@ export async function buildConfig(): Promise { template: { definitions: await getTemplates() }, + htmlSupport: { + allow: JSON.parse(options.get("allowedHtmlTags")) + }, // This value must be kept in sync with the language defined in webpack.config.js. language: "en" }; + // Set up content language. + const { contentLanguage } = opts; + if (contentLanguage) { + config.language = { + ui: (typeof config.language === "string" ? config.language : "en"), + content: contentLanguage + } + } + // Enable premium plugins. if (hasPremiumLicense) { config.extraPlugins = [ @@ -148,149 +199,28 @@ export async function buildConfig(): Promise { ]; } - return config; -} - -export function buildToolbarConfig(isClassicToolbar: boolean) { - if (utils.isMobile()) { - return buildMobileToolbar(); - } else if (isClassicToolbar) { - const multilineToolbar = utils.isDesktop() && options.get("textNoteEditorMultilineToolbar") === "true"; - return buildClassicToolbar(multilineToolbar); - } else { - return buildFloatingToolbar(); - } -} - -export function buildMobileToolbar() { - const classicConfig = buildClassicToolbar(false); - const items: string[] = []; - - for (const item of classicConfig.toolbar.items) { - if (typeof item === "object" && "items" in item) { - for (const subitem of item.items) { - items.push(subitem); - } - } else { - items.push(item); - } - } - return { - ...classicConfig, - toolbar: { - ...classicConfig.toolbar, - items - } + ...config, + ...buildToolbarConfig(opts.isClassicEditor) }; } -export function buildClassicToolbar(multilineToolbar: boolean) { - // For nested toolbars, refer to https://ckeditor.com/docs/ckeditor5/latest/getting-started/setup/toolbar.html#grouping-toolbar-items-in-dropdowns-nested-toolbars. - return { - toolbar: { - items: [ - "heading", - "fontSize", - "|", - "bold", - "italic", - { - ...TEXT_FORMATTING_GROUP, - items: ["underline", "strikethrough", "|", "superscript", "subscript", "|", "kbd"] - }, - "|", - "fontColor", - "fontBackgroundColor", - "removeFormat", - "|", - "bulletedList", - "numberedList", - "todoList", - "|", - "blockQuote", - "admonition", - "insertTable", - "|", - "code", - "codeBlock", - "|", - "footnote", - { - label: "Insert", - icon: "plus", - items: ["imageUpload", "|", "link", "bookmark", "internallink", "includeNote", "|", "specialCharacters", "emoji", "math", "mermaid", "horizontalLine", "pageBreak", "dateTime"] - }, - "|", - "alignment", - "outdent", - "indent", - "|", - "insertTemplate", - "markdownImport", - "cuttonote", - "findAndReplace" - ], - shouldNotGroupWhenFull: multilineToolbar - } - }; -} +function buildListOfLanguages() { + const userLanguages = mimeTypesService + .getMimeTypes() + .filter((mt) => mt.enabled) + .map((mt) => ({ + language: normalizeMimeTypeForCKEditor(mt.mime), + label: mt.title + })); -export function buildFloatingToolbar() { - return { - toolbar: { - items: [ - "fontSize", - "bold", - "italic", - "underline", - { - ...TEXT_FORMATTING_GROUP, - items: [ "strikethrough", "|", "superscript", "subscript", "|", "kbd" ] - }, - "|", - "fontColor", - "fontBackgroundColor", - "|", - "code", - "link", - "bookmark", - "removeFormat", - "internallink", - "cuttonote" - ] + return [ + { + language: mimeTypesService.MIME_TYPE_AUTO, + label: t("editable-text.auto-detect-language") }, - - blockToolbar: [ - "heading", - "|", - "bulletedList", - "numberedList", - "todoList", - "|", - "blockQuote", - "admonition", - "codeBlock", - "insertTable", - "footnote", - { - label: "Insert", - icon: "plus", - items: ["link", "bookmark", "internallink", "includeNote", "|", "math", "mermaid", "horizontalLine", "pageBreak", "dateTime"] - }, - "|", - "alignment", - "outdent", - "indent", - "|", - "insertTemplate", - "imageUpload", - "markdownImport", - "specialCharacters", - "emoji", - "findAndReplace" - ] - }; + ...userLanguages + ]; } function getLicenseKey() { diff --git a/apps/client/src/widgets/type_widgets/ckeditor/config.spec.ts b/apps/client/src/widgets/type_widgets/ckeditor/toolbar.spec.ts similarity index 94% rename from apps/client/src/widgets/type_widgets/ckeditor/config.spec.ts rename to apps/client/src/widgets/type_widgets/ckeditor/toolbar.spec.ts index 3bce30d9b..0f17ce348 100644 --- a/apps/client/src/widgets/type_widgets/ckeditor/config.spec.ts +++ b/apps/client/src/widgets/type_widgets/ckeditor/toolbar.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { buildClassicToolbar, buildFloatingToolbar } from "./config.js"; +import { buildClassicToolbar, buildFloatingToolbar } from "./toolbar.js"; type ToolbarConfig = string | "|" | { items: ToolbarConfig[] }; diff --git a/apps/client/src/widgets/type_widgets/ckeditor/toolbar.ts b/apps/client/src/widgets/type_widgets/ckeditor/toolbar.ts new file mode 100644 index 000000000..ad83baab6 --- /dev/null +++ b/apps/client/src/widgets/type_widgets/ckeditor/toolbar.ts @@ -0,0 +1,149 @@ +import utils from "../../../services/utils.js"; +import options from "../../../services/options.js"; + +const TEXT_FORMATTING_GROUP = { + label: "Text formatting", + icon: "text" +}; + +export function buildToolbarConfig(isClassicToolbar: boolean) { + if (utils.isMobile()) { + return buildMobileToolbar(); + } else if (isClassicToolbar) { + const multilineToolbar = utils.isDesktop() && options.get("textNoteEditorMultilineToolbar") === "true"; + return buildClassicToolbar(multilineToolbar); + } else { + return buildFloatingToolbar(); + } +} + +export function buildMobileToolbar() { + const classicConfig = buildClassicToolbar(false); + const items: string[] = []; + + for (const item of classicConfig.toolbar.items) { + if (typeof item === "object" && "items" in item) { + for (const subitem of item.items) { + items.push(subitem); + } + } else { + items.push(item); + } + } + + return { + ...classicConfig, + toolbar: { + ...classicConfig.toolbar, + items + } + }; +} + +export function buildClassicToolbar(multilineToolbar: boolean) { + // For nested toolbars, refer to https://ckeditor.com/docs/ckeditor5/latest/getting-started/setup/toolbar.html#grouping-toolbar-items-in-dropdowns-nested-toolbars. + return { + toolbar: { + items: [ + "heading", + "fontSize", + "|", + "bold", + "italic", + { + ...TEXT_FORMATTING_GROUP, + items: ["underline", "strikethrough", "|", "superscript", "subscript", "|", "kbd"] + }, + "|", + "fontColor", + "fontBackgroundColor", + "removeFormat", + "|", + "bulletedList", + "numberedList", + "todoList", + "|", + "blockQuote", + "admonition", + "insertTable", + "|", + "code", + "codeBlock", + "|", + "footnote", + { + label: "Insert", + icon: "plus", + items: ["imageUpload", "|", "link", "bookmark", "internallink", "includeNote", "|", "specialCharacters", "emoji", "math", "mermaid", "horizontalLine", "pageBreak", "dateTime"] + }, + "|", + "alignment", + "outdent", + "indent", + "|", + "insertTemplate", + "markdownImport", + "cuttonote", + "findAndReplace" + ], + shouldNotGroupWhenFull: multilineToolbar + } + }; +} + +export function buildFloatingToolbar() { + return { + toolbar: { + items: [ + "fontSize", + "bold", + "italic", + "underline", + { + ...TEXT_FORMATTING_GROUP, + items: [ "strikethrough", "|", "superscript", "subscript", "|", "kbd" ] + }, + "|", + "fontColor", + "fontBackgroundColor", + "|", + "code", + "link", + "bookmark", + "removeFormat", + "internallink", + "cuttonote" + ] + }, + + blockToolbar: [ + "heading", + "|", + "bulletedList", + "numberedList", + "todoList", + "|", + "blockQuote", + "admonition", + "codeBlock", + "insertTable", + "footnote", + { + label: "Insert", + icon: "plus", + items: ["link", "bookmark", "internallink", "includeNote", "|", "math", "mermaid", "horizontalLine", "pageBreak", "dateTime"] + }, + "|", + "alignment", + "outdent", + "indent", + "|", + "insertTemplate", + "imageUpload", + "markdownImport", + "specialCharacters", + "emoji", + "findAndReplace" + ] + }; +} diff --git a/apps/client/src/widgets/type_widgets/editable_text.ts b/apps/client/src/widgets/type_widgets/editable_text.ts index 0b047d54c..8db9f3164 100644 --- a/apps/client/src/widgets/type_widgets/editable_text.ts +++ b/apps/client/src/widgets/type_widgets/editable_text.ts @@ -1,6 +1,3 @@ -import { t } from "../../services/i18n.js"; -import noteAutocompleteService, { type Suggestion } from "../../services/note_autocomplete.js"; -import mimeTypesService from "../../services/mime_types.js"; import utils, { hasTouchBar } from "../../services/utils.js"; import keyboardActionService from "../../services/keyboard_actions.js"; import froca from "../../services/froca.js"; @@ -12,29 +9,12 @@ import dialogService from "../../services/dialog.js"; import options from "../../services/options.js"; import toast from "../../services/toast.js"; import { buildSelectedBackgroundColor } from "../../components/touch_bar.js"; -import { buildConfig, buildToolbarConfig } from "./ckeditor/config.js"; +import { buildConfig, BuildEditorOptions, OPEN_SOURCE_LICENSE_KEY } from "./ckeditor/config.js"; import type FNote from "../../entities/fnote.js"; -import { getMermaidConfig } from "../../services/mermaid.js"; -import { PopupEditor, ClassicEditor, EditorWatchdog, type CKTextEditor, type MentionFeed, type WatchdogConfig } from "@triliumnext/ckeditor5"; +import { PopupEditor, ClassicEditor, EditorWatchdog, type CKTextEditor, type MentionFeed, type WatchdogConfig, EditorConfig } from "@triliumnext/ckeditor5"; import "@triliumnext/ckeditor5/index.css"; -import { normalizeMimeTypeForCKEditor } from "@triliumnext/commons"; import { updateTemplateCache } from "./ckeditor/snippets.js"; -const mentionSetup: MentionFeed[] = [ - { - marker: "@", - feed: (queryText: string) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText), - itemRenderer: (item) => { - const itemElement = document.createElement("button"); - - itemElement.innerHTML = `${(item as Suggestion).highlightedNotePathTitle} `; - - return itemElement; - }, - minimumCharacters: 0 - } -]; - const TPL = /*html*/`