From 3ebd33536b3df94a96aebca8c1f4fe457b122bc1 Mon Sep 17 00:00:00 2001 From: hunter-dev Date: Mon, 24 Aug 2026 20:35:22 +0800 Subject: [PATCH] =?UTF-8?q?M0-M11=20=E7=8B=A9=E5=A4=9C=E4=BA=BA=E7=AC=94?= =?UTF-8?q?=E8=AE=B0=E5=88=9D=E5=A7=8B=E5=AE=9E=E7=8E=B0=EF=BC=9A11?= =?UTF-8?q?=E9=87=8C=E7=A8=8B=E7=A2=91+86=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 8 + .gitignore | 8 + .npmrc | 3 + apps/desktop/electron.vite.config.ts | 24 + apps/desktop/package.json | 53 + apps/desktop/pnpm-lock.yaml | 2323 +++++++++ apps/desktop/src/main/index.ts | 41 + apps/desktop/src/main/ipc/handlers.ts | 65 + apps/desktop/src/main/safe/atomic-write.ts | 29 + .../src/main/safe/frontmatter-codec.ts | 70 + apps/desktop/src/main/safe/safe-path.ts | 64 + apps/desktop/src/main/safe/smart-color.ts | 81 + .../desktop/src/main/services/config-store.ts | 56 + .../desktop/src/main/services/file-service.ts | 134 + .../src/main/services/graph-builder.ts | 43 + .../src/main/services/migration-service.ts | 47 + .../src/main/services/search-service.ts | 61 + .../src/main/services/vault-service.ts | 53 + apps/desktop/src/preload/index.ts | 14 + apps/desktop/src/renderer/App.tsx | 84 + apps/desktop/src/renderer/ErrorBoundary.tsx | 21 + .../features/appearance/AppearancePanel.tsx | 48 + .../features/appearance/appearance-store.ts | 54 + .../renderer/features/appearance/themes.ts | 35 + .../renderer/features/dialog/InputDialog.tsx | 36 + .../renderer/features/dialog/NotifyLayer.tsx | 6 + .../renderer/features/dialog/dialog-store.ts | 22 + .../renderer/features/dialog/notify-store.ts | 11 + .../features/doc-info/DocInfoPanel.tsx | 79 + .../src/renderer/features/editor/BlockOps.ts | 13 + .../src/renderer/features/editor/Editor.tsx | 33 + .../features/editor/EditorToolbar.tsx | 35 + .../renderer/features/editor/FindReplace.tsx | 61 + .../renderer/features/editor/MoreActions.tsx | 32 + .../renderer/features/editor/SlashMenu.tsx | 57 + .../renderer/features/editor/TiptapEditor.tsx | 73 + .../features/editor/editor-context.ts | 6 + .../renderer/features/effects/EffectLayer.tsx | 20 + .../features/effects/effect-engine.ts | 102 + .../renderer/features/file-tree/FileTree.tsx | 115 + .../src/renderer/features/graph/GraphView.tsx | 38 + .../renderer/features/keymap/CheatSheet.tsx | 29 + .../features/keymap/keymap-service.ts | 33 + .../renderer/features/keymap/use-hotkeys.ts | 13 + .../features/outline/OutlinePanel.tsx | 52 + .../renderer/features/search/SearchBox.tsx | 47 + .../renderer/features/search/TagFilter.tsx | 21 + .../features/settings/SettingsPanel.tsx | 40 + .../features/settings/settings-store.ts | 38 + .../src/renderer/features/shell/Resizer.tsx | 16 + .../src/renderer/features/shell/TitleBar.tsx | 30 + .../renderer/features/shell/layout-store.ts | 26 + .../src/renderer/features/tabs/TabBar.tsx | 35 + .../src/renderer/features/tabs/tab-store.ts | 72 + .../src/renderer/features/toolbar/Toolbar.tsx | 51 + apps/desktop/src/renderer/index.html | 12 + apps/desktop/src/renderer/ipc-client.ts | 12 + apps/desktop/src/renderer/main.tsx | 13 + apps/desktop/src/renderer/store.ts | 114 + apps/desktop/src/renderer/styles.css | 39 + apps/desktop/tsconfig.json | 11 + apps/desktop/tsconfig.node.json | 28 + apps/desktop/tsconfig.web.json | 24 + package.json | 29 + packages/shared/package.json | 11 + packages/shared/src/constants.ts | 7 + packages/shared/src/index.ts | 7 + packages/shared/src/types/doc.ts | 18 + packages/shared/src/types/graph.ts | 3 + packages/shared/src/types/ipc.ts | 69 + packages/shared/src/types/search.ts | 2 + packages/shared/src/types/vault.ts | 19 + packages/shared/tsconfig.json | 16 + pnpm-lock.yaml | 4430 +++++++++++++++++ pnpm-workspace.yaml | 6 + tests/fixtures/roundtrip/README.md | 35 + tests/fixtures/roundtrip/blockquote.md | 12 + tests/fixtures/roundtrip/breaks.md | 7 + tests/fixtures/roundtrip/codeblock.md | 21 + tests/fixtures/roundtrip/combined.md | 29 + tests/fixtures/roundtrip/emphasis-nested.md | 13 + tests/fixtures/roundtrip/escaping.md | 13 + tests/fixtures/roundtrip/headings.md | 15 + tests/fixtures/roundtrip/html.md | 9 + tests/fixtures/roundtrip/images.md | 7 + tests/fixtures/roundtrip/links.md | 11 + tests/fixtures/roundtrip/lists-nested.md | 18 + tests/fixtures/roundtrip/math.md | 11 + tests/fixtures/roundtrip/tables.md | 13 + tests/fixtures/roundtrip/tasklist.md | 10 + tests/fixtures/roundtrip/thematic-break.md | 7 + tests/fixtures/roundtrip/whitespace.md | 12 + tests/unit/atomic-write.test.ts | 41 + tests/unit/doc-props.test.ts | 52 + tests/unit/effect-engine.test.ts | 35 + tests/unit/file-service.test.ts | 144 + tests/unit/frontmatter-codec.test.ts | 52 + tests/unit/graph-builder.test.ts | 48 + tests/unit/keymap-service.test.ts | 44 + tests/unit/migration-service.test.ts | 47 + tests/unit/roundtrip.test.ts | 42 + tests/unit/safe-path.test.ts | 50 + tests/unit/search-service.test.ts | 58 + tests/unit/smart-color.test.ts | 38 + tests/unit/tab-store.test.ts | 47 + vitest.config.ts | 18 + 106 files changed, 10490 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 apps/desktop/electron.vite.config.ts create mode 100644 apps/desktop/package.json create mode 100644 apps/desktop/pnpm-lock.yaml create mode 100644 apps/desktop/src/main/index.ts create mode 100644 apps/desktop/src/main/ipc/handlers.ts create mode 100644 apps/desktop/src/main/safe/atomic-write.ts create mode 100644 apps/desktop/src/main/safe/frontmatter-codec.ts create mode 100644 apps/desktop/src/main/safe/safe-path.ts create mode 100644 apps/desktop/src/main/safe/smart-color.ts create mode 100644 apps/desktop/src/main/services/config-store.ts create mode 100644 apps/desktop/src/main/services/file-service.ts create mode 100644 apps/desktop/src/main/services/graph-builder.ts create mode 100644 apps/desktop/src/main/services/migration-service.ts create mode 100644 apps/desktop/src/main/services/search-service.ts create mode 100644 apps/desktop/src/main/services/vault-service.ts create mode 100644 apps/desktop/src/preload/index.ts create mode 100644 apps/desktop/src/renderer/App.tsx create mode 100644 apps/desktop/src/renderer/ErrorBoundary.tsx create mode 100644 apps/desktop/src/renderer/features/appearance/AppearancePanel.tsx create mode 100644 apps/desktop/src/renderer/features/appearance/appearance-store.ts create mode 100644 apps/desktop/src/renderer/features/appearance/themes.ts create mode 100644 apps/desktop/src/renderer/features/dialog/InputDialog.tsx create mode 100644 apps/desktop/src/renderer/features/dialog/NotifyLayer.tsx create mode 100644 apps/desktop/src/renderer/features/dialog/dialog-store.ts create mode 100644 apps/desktop/src/renderer/features/dialog/notify-store.ts create mode 100644 apps/desktop/src/renderer/features/doc-info/DocInfoPanel.tsx create mode 100644 apps/desktop/src/renderer/features/editor/BlockOps.ts create mode 100644 apps/desktop/src/renderer/features/editor/Editor.tsx create mode 100644 apps/desktop/src/renderer/features/editor/EditorToolbar.tsx create mode 100644 apps/desktop/src/renderer/features/editor/FindReplace.tsx create mode 100644 apps/desktop/src/renderer/features/editor/MoreActions.tsx create mode 100644 apps/desktop/src/renderer/features/editor/SlashMenu.tsx create mode 100644 apps/desktop/src/renderer/features/editor/TiptapEditor.tsx create mode 100644 apps/desktop/src/renderer/features/editor/editor-context.ts create mode 100644 apps/desktop/src/renderer/features/effects/EffectLayer.tsx create mode 100644 apps/desktop/src/renderer/features/effects/effect-engine.ts create mode 100644 apps/desktop/src/renderer/features/file-tree/FileTree.tsx create mode 100644 apps/desktop/src/renderer/features/graph/GraphView.tsx create mode 100644 apps/desktop/src/renderer/features/keymap/CheatSheet.tsx create mode 100644 apps/desktop/src/renderer/features/keymap/keymap-service.ts create mode 100644 apps/desktop/src/renderer/features/keymap/use-hotkeys.ts create mode 100644 apps/desktop/src/renderer/features/outline/OutlinePanel.tsx create mode 100644 apps/desktop/src/renderer/features/search/SearchBox.tsx create mode 100644 apps/desktop/src/renderer/features/search/TagFilter.tsx create mode 100644 apps/desktop/src/renderer/features/settings/SettingsPanel.tsx create mode 100644 apps/desktop/src/renderer/features/settings/settings-store.ts create mode 100644 apps/desktop/src/renderer/features/shell/Resizer.tsx create mode 100644 apps/desktop/src/renderer/features/shell/TitleBar.tsx create mode 100644 apps/desktop/src/renderer/features/shell/layout-store.ts create mode 100644 apps/desktop/src/renderer/features/tabs/TabBar.tsx create mode 100644 apps/desktop/src/renderer/features/tabs/tab-store.ts create mode 100644 apps/desktop/src/renderer/features/toolbar/Toolbar.tsx create mode 100644 apps/desktop/src/renderer/index.html create mode 100644 apps/desktop/src/renderer/ipc-client.ts create mode 100644 apps/desktop/src/renderer/main.tsx create mode 100644 apps/desktop/src/renderer/store.ts create mode 100644 apps/desktop/src/renderer/styles.css create mode 100644 apps/desktop/tsconfig.json create mode 100644 apps/desktop/tsconfig.node.json create mode 100644 apps/desktop/tsconfig.web.json create mode 100644 package.json create mode 100644 packages/shared/package.json create mode 100644 packages/shared/src/constants.ts create mode 100644 packages/shared/src/index.ts create mode 100644 packages/shared/src/types/doc.ts create mode 100644 packages/shared/src/types/graph.ts create mode 100644 packages/shared/src/types/ipc.ts create mode 100644 packages/shared/src/types/search.ts create mode 100644 packages/shared/src/types/vault.ts create mode 100644 packages/shared/tsconfig.json create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 tests/fixtures/roundtrip/README.md create mode 100644 tests/fixtures/roundtrip/blockquote.md create mode 100644 tests/fixtures/roundtrip/breaks.md create mode 100644 tests/fixtures/roundtrip/codeblock.md create mode 100644 tests/fixtures/roundtrip/combined.md create mode 100644 tests/fixtures/roundtrip/emphasis-nested.md create mode 100644 tests/fixtures/roundtrip/escaping.md create mode 100644 tests/fixtures/roundtrip/headings.md create mode 100644 tests/fixtures/roundtrip/html.md create mode 100644 tests/fixtures/roundtrip/images.md create mode 100644 tests/fixtures/roundtrip/links.md create mode 100644 tests/fixtures/roundtrip/lists-nested.md create mode 100644 tests/fixtures/roundtrip/math.md create mode 100644 tests/fixtures/roundtrip/tables.md create mode 100644 tests/fixtures/roundtrip/tasklist.md create mode 100644 tests/fixtures/roundtrip/thematic-break.md create mode 100644 tests/fixtures/roundtrip/whitespace.md create mode 100644 tests/unit/atomic-write.test.ts create mode 100644 tests/unit/doc-props.test.ts create mode 100644 tests/unit/effect-engine.test.ts create mode 100644 tests/unit/file-service.test.ts create mode 100644 tests/unit/frontmatter-codec.test.ts create mode 100644 tests/unit/graph-builder.test.ts create mode 100644 tests/unit/keymap-service.test.ts create mode 100644 tests/unit/migration-service.test.ts create mode 100644 tests/unit/roundtrip.test.ts create mode 100644 tests/unit/safe-path.test.ts create mode 100644 tests/unit/search-service.test.ts create mode 100644 tests/unit/smart-color.test.ts create mode 100644 tests/unit/tab-store.test.ts create mode 100644 vitest.config.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..47e6334 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f6fb067 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +dist-electron/ +out/ +*.log +.DS_Store +.vite/ +coverage/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..61768ad --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +shamefully-hoist=false +strict-peer-dependencies=false +auto-install-peers=true diff --git a/apps/desktop/electron.vite.config.ts b/apps/desktop/electron.vite.config.ts new file mode 100644 index 0000000..df08a3b --- /dev/null +++ b/apps/desktop/electron.vite.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from 'electron-vite'; +import { resolve } from 'node:path'; + +export default defineConfig({ + main: { + build: { + outDir: 'dist-electron/main', + rollupOptions: { input: { index: resolve('src/main/index.ts') } } + } + }, + preload: { + build: { + outDir: 'dist-electron/preload', + rollupOptions: { input: { index: resolve('src/preload/index.ts') } } + } + }, + renderer: { + root: 'src/renderer', + build: { + outDir: 'dist-electron/renderer', + rollupOptions: { input: { index: resolve('src/renderer/index.html') } } + } + } +}); diff --git a/apps/desktop/package.json b/apps/desktop/package.json new file mode 100644 index 0000000..3c0d902 --- /dev/null +++ b/apps/desktop/package.json @@ -0,0 +1,53 @@ +{ + "name": "@hunter/desktop", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "dist-electron/main/index.js", + "scripts": { + "dev": "electron-vite dev", + "build": "electron-vite build", + "typecheck": "tsc -b" + }, + "dependencies": { + "@antv/g6": "^5.1.1", + "@hunter/shared": "workspace:*", + "@tiptap/core": "^3.30.2", + "@tiptap/extension-character-count": "^3.30.2", + "@tiptap/extension-code-block-lowlight": "^3.30.2", + "@tiptap/extension-image": "^3.30.2", + "@tiptap/extension-link": "^3.30.2", + "@tiptap/extension-placeholder": "^3.30.2", + "@tiptap/extension-table": "^3.30.2", + "@tiptap/extension-table-cell": "^3.30.2", + "@tiptap/extension-table-header": "^3.30.2", + "@tiptap/extension-table-row": "^3.30.2", + "@tiptap/extension-task-item": "^3.30.2", + "@tiptap/extension-task-list": "^3.30.2", + "@tiptap/pm": "^3.30.2", + "@tiptap/react": "^3.30.2", + "@tiptap/starter-kit": "^3.30.2", + "@tiptap/suggestion": "^3.30.2", + "hotkeys-js": "^4.0.5", + "lowlight": "^3.3.0", + "remark-gfm": "^4.0.1", + "remark-math": "^6.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "tiptap-markdown": "^0.9.0", + "unified": "^11.0.5", + "zustand": "^5.0.15" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "electron": "^30.0.0", + "electron-vite": "^2.3.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "typescript": "^5.6.0", + "vite": "^5.4.0", + "vitest": "^2.1.0" + } +} diff --git a/apps/desktop/pnpm-lock.yaml b/apps/desktop/pnpm-lock.yaml new file mode 100644 index 0000000..df7a934 --- /dev/null +++ b/apps/desktop/pnpm-lock.yaml @@ -0,0 +1,2323 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@antv/g6': + specifier: ^5.1.1 + version: 5.1.1 + '@hunter/shared': + specifier: workspace:* + version: link:../../packages/shared + '@tiptap/core': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/extension-character-count': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-code-block-lowlight': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/extension-code-block@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)(highlight.js@11.11.2)(lowlight@3.3.0) + '@tiptap/extension-image': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-link': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-placeholder': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-table': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-table-cell': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extension-table@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-table-header': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extension-table@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-table-row': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extension-table@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-task-item': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-task-list': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/pm': + specifier: ^3.30.2 + version: 3.30.2 + '@tiptap/react': + specifier: ^3.30.2 + version: 3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tiptap/starter-kit': + specifier: ^3.30.2 + version: 3.30.2 + '@tiptap/suggestion': + specifier: ^3.30.2 + version: 3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + hotkeys-js: + specifier: ^4.0.5 + version: 4.0.5 + lowlight: + specifier: ^3.3.0 + version: 3.3.0 + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + remark-math: + specifier: ^6.0.0 + version: 6.0.0 + remark-parse: + specifier: ^11.0.0 + version: 11.0.0 + remark-stringify: + specifier: ^11.0.0 + version: 11.0.0 + tiptap-markdown: + specifier: ^0.9.0 + version: 0.9.0(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + unified: + specifier: ^11.0.5 + version: 11.0.5 + zustand: + specifier: ^5.0.15 + version: 5.0.15 + devDependencies: + '@types/react': + specifier: ^18.3.0 + version: 18.3.31 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.7(@types/react@18.3.31) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + +packages: + + '@antv/algorithm@0.1.26': + resolution: {integrity: sha512-DVhcFSQ8YQnMNW34Mk8BSsfc61iC1sAnmcfYoXTAshYHuU50p/6b7x3QYaGctDNKWGvi1ub7mPcSY0bK+aN0qg==} + + '@antv/component@2.1.11': + resolution: {integrity: sha512-dTdz8VAd3rpjOaGEZTluz82mtzrP4XCtNlNQyrxY7VNRNcjtvpTLDn57bUL2lRu1T+iklKvgbE2llMriWkq9vQ==} + + '@antv/event-emitter@0.1.3': + resolution: {integrity: sha512-4ddpsiHN9Pd4UIlWuKVK1C4IiZIdbwQvy9i7DUSI3xNJ89FPUFt8lxDYj8GzzfdllV0NkJTRxnG+FvLk0llidg==} + + '@antv/expr@1.0.2': + resolution: {integrity: sha512-vrfdmPHkTuiS5voVutKl2l06w1ihBh9A8SFdQPEE+2KMVpkymzGOF1eWpfkbGZ7tiFE15GodVdhhHomD/hdIwg==} + + '@antv/g-canvas@2.2.0': + resolution: {integrity: sha512-h7zVBBo2aO64DuGKvq9sG+yTU3sCUb9DALCVm7nz8qGPs8hhLuFOkKPEzUDNfNYZGJUGzY8UDtJ3QRGRFcvEQg==} + + '@antv/g-lite@2.7.0': + resolution: {integrity: sha512-uSzgHYa5bwR5L2Au7/5tsOhFmXKZKLPBH90+Q9bP9teVs5VT4kOAi0isPSpDI8uhdDC2/VrfTWu5K9HhWI6FWw==} + + '@antv/g-math@3.1.0': + resolution: {integrity: sha512-DtN1Gj/yI0UiK18nSBsZX8RK0LszGwqfb+cBYWgE+ddyTm8dZnW4tPUhV7QXePsS6/A5hHC+JFpAAK7OEGo5ZQ==} + + '@antv/g-plugin-dragndrop@2.1.1': + resolution: {integrity: sha512-+aesDUJVQDs6UJ2bOBbDlaGAPCfHmU0MbrMTlQlfpwNplWueqtgVAZ3L57oZ2ZGHRWUHiRwZGPjXMBM3O2LELw==} + + '@antv/g6@5.1.1': + resolution: {integrity: sha512-50bXxMUf4mChyOv4ePVeWZLwotih9VunKfp0a++Wofv/wCyY8fb9+CV2wouIBCOZnd5ydBRA4NNaX9yLJzqa2w==} + + '@antv/g@6.3.1': + resolution: {integrity: sha512-WYEKqy86LHB2PzTmrZXrIsIe+3Epeds2f68zceQ+BJtRoGki7Sy4IhlC8LrUMztgfT1t3d/0L745NWZwITroKA==} + + '@antv/graphlib@2.0.4': + resolution: {integrity: sha512-zc/5oQlsdk42Z0ib1mGklwzhJ5vczLFiPa1v7DgJkTbgJ2YxRh9xdarf86zI49sKVJmgbweRpJs7Nu5bIiwv4w==} + + '@antv/hierarchy@0.7.1': + resolution: {integrity: sha512-7r22r+HxfcRZp79ZjGmsn97zgC1Iajrv0Mm9DIgx3lPfk+Kme2MG/+EKdZj1iEBsN0rJRzjWVPGL5YrBdVHchw==} + + '@antv/layout@2.0.0': + resolution: {integrity: sha512-aCZ3UdNc40SfT7meFV7QTADY2HCnc0DShVw56CJNTI6oExUIVU736grPuL5Dhb8/JrVaU4Y83QPN/P7KafBzlw==} + + '@antv/scale@0.4.16': + resolution: {integrity: sha512-5wg/zB5kXHxpTV5OYwJD3ja6R8yTiqIOkjOhmpEJiowkzRlbEC/BOyMvNUq5fqFIHnMCE9woO7+c3zxEQCKPjw==} + + '@antv/util@2.0.17': + resolution: {integrity: sha512-o6I9hi5CIUvLGDhth0RxNSFDRwXeywmt6ExR4+RmVAzIi48ps6HUy+svxOCayvrPBN37uE6TAc2KDofRo0nK9Q==} + + '@antv/util@3.3.11': + resolution: {integrity: sha512-FII08DFM4ABh2q5rPYdr0hMtKXRgeZazvXaFYCs7J7uTcWDHUhczab2qOCJLNDugoj8jFag1djb7wS9ehaRYBg==} + + '@antv/vendor@1.0.11': + resolution: {integrity: sha512-LmhPEQ+aapk3barntaiIxJ5VHno/Tyab2JnfdcPzp5xONh/8VSfed4bo/9xKo5HcUAEydko38vYLfj6lJliLiw==} + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} + + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} + + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + + '@tiptap/core@3.30.2': + resolution: {integrity: sha512-QbZC/s1OOqcoUdkhIY16TjR/gCtR0qAk9e4bJwUqOJqZuv5ozqCL5hzWm22jjTPp6c6Ei2tPd6t30VwfIKW4lQ==} + peerDependencies: + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-blockquote@3.30.2': + resolution: {integrity: sha512-BOkwhZenek7vzXBOgKppSrlx4YryBdAYu1p1MXKn0R9A9eNmE2HVhmm0gG49+E8BhsE/TG8wKVclwET42JJiIg==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-bold@3.30.2': + resolution: {integrity: sha512-MsvJhPgYejY2D9MhwYJv8AmscozvLBI8qtJ7YLdYZBWkMR4bgmxHq5+xqEfBsao9bOMMwBon9p3+P+/Tq5ReWA==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-bubble-menu@3.30.2': + resolution: {integrity: sha512-oS0WiWNXHKpiPYMkcnHm1j7iEvufTGGtLFtcJJd3olb5OS6V2acoXVDL0nNJDmRQ32K3QXut3fbRcMseMxT+lw==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-bullet-list@3.30.2': + resolution: {integrity: sha512-+awIL/TUz4aB3rL68igU1rWfaaoBIAkPcakkktkRq8gYf0bd9eSb48P6kHkpx/3q+JyK7g9vsnltLNHNh6twnA==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-character-count@3.30.2': + resolution: {integrity: sha512-9Mnej4qOLf2/kFPAmGxkxwdpFNRq8+D9nQVnyFF50x32myaKl/BXcCftsm+EiKpnH9JNT5mhZA0nh4JoH7OwrA==} + peerDependencies: + '@tiptap/extensions': 3.30.2 + + '@tiptap/extension-code-block-lowlight@3.30.2': + resolution: {integrity: sha512-xmC1MOAMl7QmcayV0H/SLxpJ84o4SPwzAcJDhtgUWFdWyf8aO5rnNtUj/2HnvLXlM1nqZPMjTBOmSK7qgPwSZw==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/extension-code-block': 3.30.2 + '@tiptap/pm': 3.30.2 + highlight.js: ^11 + lowlight: ^2 || ^3 + + '@tiptap/extension-code-block@3.30.2': + resolution: {integrity: sha512-9otGKaQZmePHrLXFtCtz+BYDn5z4sSumTkUqQIQHz0gVxwPoTi7g51RedwxvViTb/zu2XV5ROXYLHIxKxypMPg==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-code@3.30.2': + resolution: {integrity: sha512-r8EZk3R9yGpF6v5xxafAU1HwrD/e+RpbfnmVi2TeB/ZHAsVO62fW96E32G0t6IdaCtOFtAd85hkDAaOfCT4yGg==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-document@3.30.2': + resolution: {integrity: sha512-+xIv67V+/2L1uvz98FAT5W7kWEfHwfNV3MD7b4UsKPU0lhcCWuVOXy0JB8yYmdNExqpI7xT9g3MWzREoBvBQSg==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-dropcursor@3.30.2': + resolution: {integrity: sha512-nyRKUmItATnKI9AiRChmjhcBbCEsNxRu+AaCz+cx8EvnAcNHsVRdNYL5PmBs3WlNA/Et4Eb2DG0hVQDnNF61eg==} + peerDependencies: + '@tiptap/extensions': 3.30.2 + + '@tiptap/extension-floating-menu@3.30.2': + resolution: {integrity: sha512-A8PLvvh8W6PUMrqh+EpBerxm+Ucr0irGxJvwAnzYQmNGNIJ9U4OVgw4OcEU+9JH0gMmEzDcHzMPP2s/s1lIcyw==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-gapcursor@3.30.2': + resolution: {integrity: sha512-7Xk0ut6FM+RAsvKxDN3bAtk7zvYZ6Aa8pawJ6s7dLAmLR9JwrZevlcL4FSrj4bR7rqKOj92RYCFxzgTEbWimag==} + peerDependencies: + '@tiptap/extensions': 3.30.2 + + '@tiptap/extension-hard-break@3.30.2': + resolution: {integrity: sha512-IxSNgmG3d4OZdUTeebrOI7SxdIWXXJqlcGiSNDabWqxipUitfy3mZ3gDDE6G01koKxZRbhz4KIplAZlpxnTFSg==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-heading@3.30.2': + resolution: {integrity: sha512-PblDvgSJ05p1t6hzyPi02xeiBjB0M2abReoGEImqSWCy79UqnAGacgsZo4EeEawtJV1NEP8chhvmX+nRtzdT1A==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-horizontal-rule@3.30.2': + resolution: {integrity: sha512-j8aswLTsuEdJKC62DF+kw0EgvIRL7QMUyAVp2fdjR0qgM0ZVlEwCC4qIEq3kK9tFVU4kRtQ5BSj/jn6QwrlbCA==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-image@3.30.2': + resolution: {integrity: sha512-K/BPlWauHXI6Y4s7se2A1BLcZ2pnWmRQTEDkPJYe/8kmv1GyJzPu34sZed5Mvfu5chUH8u6aNZ/utZbvSbI/0Q==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-italic@3.30.2': + resolution: {integrity: sha512-pp8uaiuXsUbLm5rYzR1jlWbwm1mAahRajdHwAKBtthFRB2rDvC7ZWhKaCSoKhZvfIDRmu9/B67+uAHoutL0dCA==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-link@3.30.2': + resolution: {integrity: sha512-jwdcymKcrbFpj5hRAuGVLCq8FieVkGFnENyroYmvkad+XAt8ZLy/MTFYRN6SK3ukH6PZMY7H4iObGtciQaC5nw==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-list-item@3.30.2': + resolution: {integrity: sha512-HWgRCRlGxulE+hN1VUcnWD6P2NE08VBgGtcaxOfdXVqaI93BCK6AhRQZGpLsfKgajLk+5DXTBraaitwnBqzCxg==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-list-keymap@3.30.2': + resolution: {integrity: sha512-TTve3WOlQaYu1ahMqsQ/T0wzaxfgZvcOl3/OuPyInOi8QtxXhqGhFjmYe5jOr56G9W2QDuFWVsZecVwfDte9zg==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-list@3.30.2': + resolution: {integrity: sha512-MIUpo1Bd9Rf1Qg+TNYNwDZ4xsfFeQahjU9Xhy6UcaszKQzbAM7KCzn5BObNytK1NdcqNHsC8Wj5vFvMKEzrXdw==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-ordered-list@3.30.2': + resolution: {integrity: sha512-Z7OO1HcF0idda1n6vodXeQ3h2ylN9JR4IfIGUYkar5Xl9JusK8PDETTBQZQn//96p49I2d+GoWsD2LXPtjHXXg==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-paragraph@3.30.2': + resolution: {integrity: sha512-ulEu3LNt+kPVAWEnrhoz13Fs8Q/v/8NUxQbAeteuBchQ8joxJXuWExhpy1fUfZir5+b+W5z7/NesgPjZQfv47w==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-placeholder@3.30.2': + resolution: {integrity: sha512-Bj1seUvPCoRrD/LpzMoKD+jQIjxuc+oq931GpPq4wobSUUbD4pF/0NMwpCLpiHO19QnTQz8+9p2dqPdlc44LHA==} + peerDependencies: + '@tiptap/extensions': 3.30.2 + + '@tiptap/extension-strike@3.30.2': + resolution: {integrity: sha512-fBLxMXz6hYIURzLOD+/L6aVATztsKham00ANWmGi13vN0hx2lQMYZffN+gR+QqiCDfMQxBXzrf5a7tJuDiQHLQ==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-table-cell@3.30.2': + resolution: {integrity: sha512-fcFG6MsJV+x1ua27dJqIdpOa5wiTXaLSfXfZ0G+rhwchH8EtSM3Q64gT4KeHsa/vVt7pOEc6ChQhKMnm40fWdA==} + peerDependencies: + '@tiptap/extension-table': 3.30.2 + + '@tiptap/extension-table-header@3.30.2': + resolution: {integrity: sha512-qcpMr8x1XvevEG5QZdk4JGLzsjZal1zzNfy4xmEtwUYVYkXkWaXklQvxEXC6GUkVpTGTOSXtHl25/cSbnJ5qbg==} + peerDependencies: + '@tiptap/extension-table': 3.30.2 + + '@tiptap/extension-table-row@3.30.2': + resolution: {integrity: sha512-dlbkcmgb5eiMI9hLX5C6CaPGuGcpyyPxJCjIYcI1l4glimYcbgxxEeqtGIvySgDewxWgofY/V3CLOGHrfyBCkQ==} + peerDependencies: + '@tiptap/extension-table': 3.30.2 + + '@tiptap/extension-table@3.30.2': + resolution: {integrity: sha512-zWp0ehZnx6N5lwRa8anSTn17GBEF3wxHXfOJGbBugf4Vwmp5keUEAXrgeIS4j7gIdxjN3XawdwjuMImG93N+Fg==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-task-item@3.30.2': + resolution: {integrity: sha512-cb9SK9TxfK6rOJZSQemqwdEhv3KsUWwTAZG4aKE4ByLWOPhf7omAw7P5/4NO4TxnnwyZglOZMNhmrFXoKqoMWw==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-task-list@3.30.2': + resolution: {integrity: sha512-xo59REkBIb/1EMKQth24RW2CEcyZbuBTQog3JHqcQJotItsaIQP/HBRggtI3W+6eMxhujbRsuL9sLWbW1OPw6Q==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-text@3.30.2': + resolution: {integrity: sha512-n/iZnirgRmXet6f97kolAnP3j8DsgLSiTbz/KLWc8eBYiFmkjRzkuisOm5xuGdfGIxwpB4x3tlSF4ef4DLnbRg==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-underline@3.30.2': + resolution: {integrity: sha512-SZiTMnvqXcnrtJX+X25ZbYsuDO83haGOVMBD/O+mAWYNYXhaSc5Rkph5czzItxrd+Yyp/vs4PiwD7XTNbfqmpA==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extensions@3.30.2': + resolution: {integrity: sha512-2LqAHXk26QDsryW+beECxYeBzv5Ylk4GuB3cOmfghS7/G37R2W+Te3TkUK7BT0EWoDryvBT57/5q0DEFIhfZZg==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/pm@3.30.2': + resolution: {integrity: sha512-BJN8tUx4ppFN3R3cV/FJfrJbJkvo1lj4uciq+nwpjwzdRvFzqIuglWf+HLcJ6CwlYpLOHp7ArgkBg4Q5e60Gog==} + + '@tiptap/react@3.30.2': + resolution: {integrity: sha512-7hGaTstpUeTmQ008mCPkjz+GSlChWhucgy+PeX0z93v4+nh7qM5F+0lh+kJ9zo6Os5abO7v36GtgHRZdQI6+FQ==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tiptap/starter-kit@3.30.2': + resolution: {integrity: sha512-fJSrhW1CyD4sjYA20evSP4Cp13B/HhbxCdM974K0xpHOVqvCtNU9w2s9hfq9mg2yGoU7MSNHKNYMkJjIi2/Xyw==} + + '@tiptap/suggestion@3.30.2': + resolution: {integrity: sha512-qsQv+rlh5ik9G1n+SGLak69JoLQAFwdtQRjGpABYxx5USc14J7hrR1K8sj9tnWymX1fKXfJOlz+Dimcb1kDhWA==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.1': + resolution: {integrity: sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.4': + resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.2.0': + resolution: {integrity: sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + + '@types/katex@0.16.8': + resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} + + '@types/linkify-it@3.0.5': + resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@13.0.9': + resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==} + + '@types/markdown-it@14.2.0': + resolution: {integrity: sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdurl@1.0.5': + resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.31': + resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + base64-arraybuffer@1.0.2: + resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} + engines: {node: '>= 0.6.0'} + + bubblesets-js@2.3.4: + resolution: {integrity: sha512-DyMjHmpkS2+xcFNtyN00apJYL3ESdp9fTrkDr5+9Qg/GPqFmcWgGsK1akZnttE1XFxJ/VMy4DNNGMGYtmFp1Sg==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + comlink@4.4.2: + resolution: {integrity: sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + css-line-break@2.1.0: + resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-binarytree@1.0.2: + resolution: {integrity: sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force-3d@3.0.6: + resolution: {integrity: sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo-projection@4.0.0: + resolution: {integrity: sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==} + engines: {node: '>=12'} + hasBin: true + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-octree@1.1.0: + resolution: {integrity: sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-regression@1.3.10: + resolution: {integrity: sha512-PF8GWEL70cHHWpx2jUQXc68r1pyPHIA+St16muk/XRokETzlegj5LriNKg7o4LR0TySug4nHYPJNNRz/W+/Niw==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + dagre@0.8.5: + resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-equals@5.4.1: + resolution: {integrity: sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==} + engines: {node: '>=6.0.0'} + + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + + gl-matrix@3.4.4: + resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==} + + graphlib@2.1.8: + resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==} + + highlight.js@11.11.2: + resolution: {integrity: sha512-oaXMACAU0kzOMXBjWpNcX+vlwSBCIAiZ9BHa7gA15NOTtT2L/l8OSZDuqS2XppOhZBPJ7hm4o8ep2kyuip2uEQ==} + engines: {node: '>=12.0.0'} + + hotkeys-js@4.0.5: + resolution: {integrity: sha512-Os0VaEH8gvOSzwZnEDU2yn7G868OfFiLydUHIe2uln7Fi9Ii1xhOCoPGu27RkZYWYqM93gC4xoyONCZE9iiwLw==} + + html2canvas@1.4.1: + resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==} + engines: {node: '>=8.0.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-any-array@3.0.0: + resolution: {integrity: sha512-o4h+tylWykC4BD1vaejp6gDxoM13bwW8FGuNs4yIKpj8xbBJcRxJx8vZpq0dCr7ZDEfeKjmsi/euolKhX6f/ww==} + + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} + hasBin: true + + linkify-it@5.0.2: + resolution: {integrity: sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==} + + linkifyjs@4.3.3: + resolution: {integrity: sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lowlight@3.3.0: + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} + + markdown-it-task-lists@2.1.1: + resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==} + + markdown-it@14.3.0: + resolution: {integrity: sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==} + hasBin: true + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-math@3.0.0: + resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdurl@2.1.0: + resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-math@3.1.0: + resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + ml-array-max@2.0.0: + resolution: {integrity: sha512-QQZ4kENwpWmyNb98UXRDFXrmtIXuXtt1+bSbda/2KA85+F+rrJP8hZk6QOkCQXM2Th9mUDYdq/PNByPdT9ID4A==} + + ml-array-min@2.0.0: + resolution: {integrity: sha512-GRj6Ky6sW9vGL6yIjgsHmXZ9YgrdmcQ8nCxPqEGeKc6dkfYg1XDYxGFxADUjNuZyoCd5PUscWAS4N+cFaX6hFg==} + + ml-array-rescale@2.0.0: + resolution: {integrity: sha512-2GGtKfSno94/kIloWGvpp/U5Q5vLvLrza+SAaGsLeo6Xj4mEbA6Gqx+oTfZFkxnd1grT2X007HfJNs3T5BsiVg==} + + ml-matrix@6.15.0: + resolution: {integrity: sha512-wFa1v6KP8bKp+fj0nYmRs1Pb5K4zRkXGKsOvLinvILENFIADncm4XlOI+S1M7yuACMGfI6cfk0IifDgd4j5xmw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + + prosemirror-changeset@2.4.2: + resolution: {integrity: sha512-ViYrjMSg3YFiXwIhKaluu+/mi3Yrxt6AR8ri14ulTaGcZtXO1CThl7A2gv79qx5fQnOw8woKwyBU2u+9PVCm3w==} + + prosemirror-commands@1.7.2: + resolution: {integrity: sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==} + + prosemirror-dropcursor@1.8.3: + resolution: {integrity: sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==} + + prosemirror-gapcursor@1.4.1: + resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} + + prosemirror-history@1.5.0: + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} + + prosemirror-inputrules@1.5.1: + resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} + + prosemirror-keymap@1.2.3: + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + + prosemirror-markdown@1.13.6: + resolution: {integrity: sha512-dY6g2BXRjkHW2ldNRDKfTF0x0R4ifk5rgPaABd/UhvrymtsGVxTbHn01goEsHAvO9nN0O34cttlW1qg/XUcaIg==} + + prosemirror-model@1.25.11: + resolution: {integrity: sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==} + + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} + + prosemirror-state@1.4.4: + resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} + + prosemirror-tables@1.8.5: + resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} + + prosemirror-transform@1.12.0: + resolution: {integrity: sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==} + + prosemirror-view@1.42.2: + resolution: {integrity: sha512-Pdg0l5kXm8aLDquFAnQFTCITg0q44sLqBlHlpsVLD9segdOao8TOfQdAhCrCXyVgPSRr6UDDROOIWA3bIrN9YQ==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-math@6.0.0: + resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + + svg-path-parser@1.1.0: + resolution: {integrity: sha512-jGCUqcQyXpfe38R7RFfhrMyfXcBmpMNJI/B+4CE9/Unkh98UporAc461GTthv+TVDuZXsBx7/WiwJb1Oh4tt4A==} + + text-segmentation@1.0.3: + resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==} + + tiptap-markdown@0.9.0: + resolution: {integrity: sha512-dKLQ9iiuGNgrlGVjrNauF/UBzWu4LYOx5pkD0jNkmQt/GOwfCJsBuzZTsf1jZ204ANHOm572mZ9PYvGh1S7tpQ==} + peerDependencies: + '@tiptap/core': ^3.0.1 + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + utrie@1.0.2: + resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + + zustand@5.0.15: + resolution: {integrity: sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@antv/algorithm@0.1.26': + dependencies: + '@antv/util': 2.0.17 + tslib: 2.8.1 + + '@antv/component@2.1.11': + dependencies: + '@antv/g': 6.3.1 + '@antv/scale': 0.4.16 + '@antv/util': 3.3.11 + svg-path-parser: 1.1.0 + + '@antv/event-emitter@0.1.3': {} + + '@antv/expr@1.0.2': {} + + '@antv/g-canvas@2.2.0': + dependencies: + '@antv/g-lite': 2.7.0 + '@antv/g-math': 3.1.0 + '@antv/util': 3.3.11 + '@babel/runtime': 7.29.7 + gl-matrix: 3.4.4 + tslib: 2.8.1 + + '@antv/g-lite@2.7.0': + dependencies: + '@antv/g-math': 3.1.0 + '@antv/util': 3.3.11 + '@antv/vendor': 1.0.11 + '@babel/runtime': 7.29.7 + eventemitter3: 5.0.4 + gl-matrix: 3.4.4 + tslib: 2.8.1 + + '@antv/g-math@3.1.0': + dependencies: + '@antv/util': 3.3.11 + '@babel/runtime': 7.29.7 + gl-matrix: 3.4.4 + tslib: 2.8.1 + + '@antv/g-plugin-dragndrop@2.1.1': + dependencies: + '@antv/g-lite': 2.7.0 + '@antv/util': 3.3.11 + '@babel/runtime': 7.29.7 + tslib: 2.8.1 + + '@antv/g6@5.1.1': + dependencies: + '@antv/algorithm': 0.1.26 + '@antv/component': 2.1.11 + '@antv/event-emitter': 0.1.3 + '@antv/g': 6.3.1 + '@antv/g-canvas': 2.2.0 + '@antv/g-plugin-dragndrop': 2.1.1 + '@antv/graphlib': 2.0.4 + '@antv/hierarchy': 0.7.1 + '@antv/layout': 2.0.0 + '@antv/util': 3.3.11 + bubblesets-js: 2.3.4 + + '@antv/g@6.3.1': + dependencies: + '@antv/g-lite': 2.7.0 + '@antv/util': 3.3.11 + '@babel/runtime': 7.29.7 + gl-matrix: 3.4.4 + html2canvas: 1.4.1 + + '@antv/graphlib@2.0.4': + dependencies: + '@antv/event-emitter': 0.1.3 + + '@antv/hierarchy@0.7.1': {} + + '@antv/layout@2.0.0': + dependencies: + '@antv/event-emitter': 0.1.3 + '@antv/expr': 1.0.2 + '@antv/graphlib': 2.0.4 + '@antv/util': 3.3.11 + comlink: 4.4.2 + d3-force: 3.0.0 + d3-force-3d: 3.0.6 + d3-octree: 1.1.0 + d3-quadtree: 3.0.1 + dagre: 0.8.5 + ml-matrix: 6.15.0 + tslib: 2.8.1 + + '@antv/scale@0.4.16': + dependencies: + '@antv/util': 3.3.11 + color-string: 1.9.1 + fecha: 4.2.3 + + '@antv/util@2.0.17': + dependencies: + csstype: 3.2.3 + tslib: 2.8.1 + + '@antv/util@3.3.11': + dependencies: + fast-deep-equal: 3.1.3 + gl-matrix: 3.4.4 + tslib: 2.8.1 + + '@antv/vendor@1.0.11': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-color': 3.1.3 + '@types/d3-dispatch': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.1 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-shape': 3.2.0 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-force-3d: 3.0.6 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-geo-projection: 4.0.0 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-regression: 1.3.10 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + + '@babel/runtime@7.29.7': {} + + '@floating-ui/core@1.8.0': + dependencies: + '@floating-ui/utils': 0.2.12 + + '@floating-ui/dom@1.8.0': + dependencies: + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 + + '@floating-ui/utils@0.2.12': {} + + '@tiptap/core@3.30.2(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-blockquote@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-bold@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-bubble-menu@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + optional: true + + '@tiptap/extension-bullet-list@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-character-count@3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extensions': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-code-block-lowlight@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/extension-code-block@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)(highlight.js@11.11.2)(lowlight@3.3.0)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/extension-code-block': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + highlight.js: 11.11.2 + lowlight: 3.3.0 + + '@tiptap/extension-code-block@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-code@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-document@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-dropcursor@3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extensions': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-floating-menu@3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + optional: true + + '@tiptap/extension-gapcursor@3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extensions': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-hard-break@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-heading@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-horizontal-rule@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-image@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-italic@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-link@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + linkifyjs: 4.3.3 + + '@tiptap/extension-list-item@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-list-keymap@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-ordered-list@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-paragraph@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-placeholder@3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extensions': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-strike@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-table-cell@3.30.2(@tiptap/extension-table@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-table': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-table-header@3.30.2(@tiptap/extension-table@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-table': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-table-row@3.30.2(@tiptap/extension-table@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-table': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-table@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-task-item@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-task-list@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-text@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-underline@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/pm@3.30.2': + dependencies: + prosemirror-changeset: 2.4.2 + prosemirror-commands: 1.7.2 + prosemirror-dropcursor: 1.8.3 + prosemirror-gapcursor: 1.4.1 + prosemirror-history: 1.5.0 + prosemirror-inputrules: 1.5.1 + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.11 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.5 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + '@tiptap/react@3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + '@types/react': 18.3.31 + '@types/react-dom': 18.3.7(@types/react@18.3.31) + '@types/use-sync-external-store': 0.0.6 + fast-equals: 5.4.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + '@tiptap/extension-bubble-menu': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-floating-menu': 3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + transitivePeerDependencies: + - '@floating-ui/dom' + + '@tiptap/starter-kit@3.30.2': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/extension-blockquote': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-bold': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-bullet-list': 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-code': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-code-block': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-document': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-dropcursor': 3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-gapcursor': 3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-hard-break': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-heading': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-horizontal-rule': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-italic': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-link': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-list-item': 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-list-keymap': 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-ordered-list': 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-paragraph': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-strike': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-text': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-underline': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extensions': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/suggestion@3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.1': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.4': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.2.0': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/geojson@7946.0.16': {} + + '@types/hast@3.0.5': + dependencies: + '@types/unist': 3.0.3 + + '@types/katex@0.16.8': {} + + '@types/linkify-it@3.0.5': {} + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@13.0.9': + dependencies: + '@types/linkify-it': 3.0.5 + '@types/mdurl': 1.0.5 + + '@types/markdown-it@14.2.0': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdurl@1.0.5': {} + + '@types/mdurl@2.0.0': {} + + '@types/ms@2.1.0': {} + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.31)': + dependencies: + '@types/react': 18.3.31 + + '@types/react@18.3.31': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/unist@3.0.3': {} + + '@types/use-sync-external-store@0.0.6': {} + + argparse@2.0.1: {} + + bail@2.0.2: {} + + base64-arraybuffer@1.0.2: {} + + bubblesets-js@2.3.4: {} + + ccount@2.0.1: {} + + character-entities@2.0.2: {} + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + comlink@4.4.2: {} + + commander@7.2.0: {} + + commander@8.3.0: {} + + css-line-break@2.1.0: + dependencies: + utrie: 1.0.2 + + csstype@3.2.3: {} + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-binarytree@1.0.2: {} + + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force-3d@3.0.6: + dependencies: + d3-binarytree: 1.0.2 + d3-dispatch: 3.0.1 + d3-octree: 1.1.0 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo-projection@4.0.0: + dependencies: + commander: 7.2.0 + d3-array: 3.2.4 + d3-geo: 3.1.1 + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-octree@1.1.0: {} + + d3-path@3.1.0: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-regression@1.3.10: {} + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + dagre@0.8.5: + dependencies: + graphlib: 2.1.8 + lodash: 4.18.1 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + dequal@2.0.3: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + entities@4.5.0: {} + + escape-string-regexp@5.0.0: {} + + eventemitter3@5.0.4: {} + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-equals@5.4.1: {} + + fecha@4.2.3: {} + + gl-matrix@3.4.4: {} + + graphlib@2.1.8: + dependencies: + lodash: 4.18.1 + + highlight.js@11.11.2: {} + + hotkeys-js@4.0.5: {} + + html2canvas@1.4.1: + dependencies: + css-line-break: 2.1.0 + text-segmentation: 1.0.3 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + internmap@2.0.3: {} + + is-any-array@3.0.0: {} + + is-arrayish@0.3.4: {} + + is-plain-obj@4.1.0: {} + + js-tokens@4.0.0: {} + + katex@0.16.47: + dependencies: + commander: 8.3.0 + + linkify-it@5.0.2: + dependencies: + uc.micro: 2.1.0 + + linkifyjs@4.3.3: {} + + lodash@4.18.1: {} + + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lowlight@3.3.0: + dependencies: + '@types/hast': 3.0.5 + devlop: 1.1.0 + highlight.js: 11.11.2 + + markdown-it-task-lists@2.1.1: {} + + markdown-it@14.3.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.2 + mdurl: 2.1.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + + markdown-table@3.0.4: {} + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-math@3.0.0: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + longest-streak: 3.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + unist-util-remove-position: 5.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mdurl@2.1.0: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-math@3.1.0: + dependencies: + '@types/katex': 0.16.8 + devlop: 1.1.0 + katex: 0.16.47 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + ml-array-max@2.0.0: + dependencies: + is-any-array: 3.0.0 + + ml-array-min@2.0.0: + dependencies: + is-any-array: 3.0.0 + + ml-array-rescale@2.0.0: + dependencies: + is-any-array: 3.0.0 + ml-array-max: 2.0.0 + ml-array-min: 2.0.0 + + ml-matrix@6.15.0: + dependencies: + is-any-array: 3.0.0 + ml-array-rescale: 2.0.0 + + ms@2.1.3: {} + + orderedmap@2.1.1: {} + + prosemirror-changeset@2.4.2: + dependencies: + prosemirror-transform: 1.12.0 + + prosemirror-commands@1.7.2: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-dropcursor@1.8.3: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + prosemirror-gapcursor@1.4.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-view: 1.42.2 + + prosemirror-history@1.5.0: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.4 + w3c-keyname: 2.2.8 + + prosemirror-markdown@1.13.6: + dependencies: + '@types/markdown-it': 14.2.0 + markdown-it: 14.3.0 + prosemirror-model: 1.25.11 + + prosemirror-model@1.25.11: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + prosemirror-tables@1.8.5: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + prosemirror-transform@1.12.0: + dependencies: + prosemirror-model: 1.25.11 + + prosemirror-view@1.42.2: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + punycode.js@2.3.1: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-math@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-math: 3.0.0 + micromark-extension-math: 3.1.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + rope-sequence@1.3.4: {} + + rw@1.3.3: {} + + safer-buffer@2.1.2: {} + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + + svg-path-parser@1.1.0: {} + + text-segmentation@1.0.3: + dependencies: + utrie: 1.0.2 + + tiptap-markdown@0.9.0(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)): + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@types/markdown-it': 13.0.9 + markdown-it: 14.3.0 + markdown-it-task-lists: 2.1.1 + prosemirror-markdown: 1.13.6 + + trough@2.2.0: {} + + tslib@2.8.1: {} + + uc.micro@2.1.0: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + + utrie@1.0.2: + dependencies: + base64-arraybuffer: 1.0.2 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + w3c-keyname@2.2.8: {} + + zustand@5.0.15: {} + + zwitch@2.0.4: {} diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts new file mode 100644 index 0000000..60c7145 --- /dev/null +++ b/apps/desktop/src/main/index.ts @@ -0,0 +1,41 @@ +// main 进程入口(M1:创建窗口 + 初始化库 + 注册 IPC) +import { app, BrowserWindow, ipcMain } from 'electron'; +import nodePath from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { VaultService } from './services/vault-service.js'; +import { registerIpc } from './ipc/handlers.js'; +import { IPC } from '@hunter/shared'; + +const __dirname = nodePath.dirname(fileURLToPath(import.meta.url)); +let win: BrowserWindow | null = null; + +function createWindow() { + win = new BrowserWindow({ + width: 1200, height: 760, minWidth: 900, minHeight: 580, + center: true, frame: false, show: false, + webPreferences: { + preload: nodePath.join(__dirname, '../preload/index.mjs'), + contextIsolation: true, nodeIntegration: false, sandbox: false + } + }); + win.on('ready-to-show', () => win?.show()); + win.on('closed', () => { win = null; }); + // 窗口控制(FR-W2) + ipcMain.on(IPC.winMinimize, () => win?.minimize()); + ipcMain.on(IPC.winMaximize, () => { if (win?.isMaximized()) win.unmaximize(); else win?.maximize(); }); + ipcMain.on(IPC.winClose, () => win?.close()); + if (process.env.ELECTRON_RENDERER_URL) win.loadURL(process.env.ELECTRON_RENDERER_URL); + else win.loadFile(nodePath.join(__dirname, '../renderer/index.html')); + return win; +} + +app.whenReady().then(async () => { + const vault = new VaultService(); + await vault.init(); // 确保有当前库 + registerIpc(vault); + createWindow(); + app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); +}); + +// 关闭前冲刷:渲染层监听窗口 close 事件自行 flush(FR-S4),M1 先以关闭前 onbeforeunload 兜底 +app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); \ No newline at end of file diff --git a/apps/desktop/src/main/ipc/handlers.ts b/apps/desktop/src/main/ipc/handlers.ts new file mode 100644 index 0000000..7394ae9 --- /dev/null +++ b/apps/desktop/src/main/ipc/handlers.ts @@ -0,0 +1,65 @@ +// IPC handlers — 主进程侧通道注册(类型链约束于 @hunter/shared) +import nodePath from 'node:path'; +import { ipcMain, shell, dialog, clipboard } from 'electron'; +import { IPC } from '@hunter/shared'; +import type { VaultService } from '../services/vault-service.js'; +import { buildGraphData } from '../services/graph-builder.js'; + +export function registerIpc(vault: VaultService) { + const fs2 = () => { + if (!vault.fileService) throw new Error('vault not initialized'); + return vault.fileService; + }; + + ipcMain.handle(IPC.vaultList, async () => vault.getMeta()); + ipcMain.handle(IPC.vaultSwitch, async (_e, p: { root: string }) => vault.switch(p.root)); + ipcMain.handle(IPC.vaultSetDefault, async (_e, p: { root: string }) => { await vault.setDefaultRoot(p.root); }); + ipcMain.handle(IPC.vaultPick, async () => vault.pickAndSwitch()); + + ipcMain.handle(IPC.treeList, async () => fs2().listTree()); + ipcMain.handle(IPC.treeRefresh, async () => fs2().listTree()); + + ipcMain.handle(IPC.fileCreate, async (_e, p: { basePath: string; name: string; isFolder: boolean }) => + fs2().create(p.basePath, p.name, p.isFolder)); + ipcMain.handle(IPC.fileRename, async (_e, p: { from: string; toName: string }) => + fs2().rename(p.from, p.toName)); + ipcMain.handle(IPC.fileMove, async (_e, p: { src: string; destDir: string }) => + fs2().move(p.src, p.destDir)); + ipcMain.handle(IPC.fileDelete, async (_e, p: { path: string; recursive: boolean }) => + fs2().delete(p.path, p.recursive)); + ipcMain.handle(IPC.fileImport, async (_e, p: { sources: string[]; destDir: string }) => { + // 若 sources 为空,弹出系统选择对话框 + let sources = p.sources; + if (sources.length === 0) { + const r = await dialog.showOpenDialog({ properties: ['openFile', 'multiSelections'], filters: [{ name: 'Markdown', extensions: ['md'] }] }); + if (r.canceled) return { ok: [], failed: [] }; + sources = r.filePaths; + } + return fs2().importFiles(sources, p.destDir); + }); + + ipcMain.handle(IPC.docRead, async (_e, p: { path: string }) => fs2().readDoc(p.path)); + ipcMain.handle(IPC.docWrite, async (_e, p: { path: string; meta: any; body: string }) => + fs2().writeDoc({ path: p.path, meta: p.meta, body: p.body })); + ipcMain.handle(IPC.search, async (_e, p: { query: string }) => vault.searchService?.search(p.query) ?? []); + ipcMain.handle(IPC.tagStats, async () => vault.searchService?.tagStats() ?? []); + ipcMain.handle(IPC.graph, async () => { + const fs2 = vault.fileService; + if (!fs2) return { nodes: [], edges: [] }; + const tree = await fs2.listTree(); + return buildGraphData(fs2.getRoot(), tree); + }); + + ipcMain.handle(IPC.copyMd, async (_e, p: { path: string }) => { + const fs2 = vault.fileService; if (!fs2) return ''; + const { body } = await fs2.readDoc(p.path); + clipboard.writeText(body); + return body; + }); + + ipcMain.handle(IPC.docReveal, async (_e, p: { path: string }) => { + const fs2 = vault.fileService; + if (!fs2) return; + shell.showItemInFolder(nodePath.join(fs2.getRoot(), p.path)); + }); +} \ No newline at end of file diff --git a/apps/desktop/src/main/safe/atomic-write.ts b/apps/desktop/src/main/safe/atomic-write.ts new file mode 100644 index 0000000..507f7fa --- /dev/null +++ b/apps/desktop/src/main/safe/atomic-write.ts @@ -0,0 +1,29 @@ +// AtomicWrite — 先写临时文件再整体替换,防中断写坏原文件(FR-S5) +import nodePath from 'node:path'; +import { promises as fs } from 'node:fs'; +import { TMP_SUFFIX } from '@hunter/shared'; + +export async function writeFileAtomic(absPath: string, content: string): Promise { + const tmp = absPath + TMP_SUFFIX; + // 确保父目录存在(新文件或跨目录保存时父目录可能尚未创建) + await fs.mkdir(nodePath.dirname(absPath), { recursive: true }); + // 写临时文件并 fsync,保证落盘 + const handle = await fs.open(tmp, 'w', 0o666); + try { + await handle.writeFile(content, 'utf8'); + await handle.sync(); // 强制刷盘 + } finally { + await handle.close(); + } + // 同卷 rename 原子替换 + await fs.rename(tmp, absPath); +} + +// 并发安全:每文件一个串行队列,防止防抖保存与冲刷交错 +const queues = new Map>(); +export function withFileLock(absPath: string, task: () => Promise): Promise { + const prev = queues.get(absPath) ?? Promise.resolve(); + const next = prev.then(task, task); // 无论成功失败都继续 + queues.set(absPath, next.then(() => undefined, () => undefined)); + return next; +} diff --git a/apps/desktop/src/main/safe/frontmatter-codec.ts b/apps/desktop/src/main/safe/frontmatter-codec.ts new file mode 100644 index 0000000..bbac4df --- /dev/null +++ b/apps/desktop/src/main/safe/frontmatter-codec.ts @@ -0,0 +1,70 @@ +// FrontmatterCodec — 文件头 frontmatter 解析/序列化(FR-M1/M2) +// 仅 4 个业务字段:title? / tags? / star? / links?;空数组与默认值不落盘 +import type { DocMeta } from './doc.js'; + +const FENCE = '---'; + +// 解析:返回 { meta, body };无 frontmatter 时 meta={} body=原文 +export function splitFrontmatter(raw: string): { meta: DocMeta; body: string } { + // 必须以 --- 开头(允许前导空白行后紧跟围栏) + const m = raw.match(/^\s*---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/); + if (!m) return { meta: {}, body: raw }; + const yaml = m[1]; + const body = m[2]; + return { meta: parseYaml(yaml), body }; +} + +// 序列化:把 meta + body 合并为完整文件文本;默认值与空数组不写入 +export function joinFrontmatter(meta: DocMeta, body: string): string { + const yaml = serializeYaml(meta); + if (!yaml) return body; + return `${FENCE}\n${yaml}${FENCE}\n${body}`; +} + +// ---- 极简 YAML 子集解析(只支持本项目 4 字段的形态)---- +function parseYaml(yaml: string): DocMeta { + const meta: DocMeta = {}; + const lines = yaml.split(/\r?\n/); + for (const line of lines) { + if (!line.trim() || line.trim().startsWith('#')) continue; + const kv = line.match(/^([a-zA-Z]+):\s*(.*)$/); + if (!kv) continue; + const [, key, val] = kv; + const v = val.trim(); + if (key === 'title') meta.title = unquote(v); + else if (key === 'star') meta.star = v === 'true'; + else if (key === 'tags') meta.tags = parseList(v); + else if (key === 'links') meta.links = parseList(v); + } + return meta; +} + +function parseList(v: string): string[] { + // 内联数组 [a, b] 或流式;本项目只用内联 + const m = v.match(/^\[\s*(.*)\s*\]$/); + if (!m) return []; + const inner = m[1].trim(); + if (!inner) return []; + return inner.split(',').map(s => unquote(s.trim())).filter(Boolean); +} + +function unquote(s: string): string { + const m = s.match(/^['"](.*)['"]$/); + return m ? m[1] : s; +} + +// ---- 序列化:剔除默认值与空数组 ---- +function serializeYaml(meta: DocMeta): string { + const lines: string[] = []; + if (meta.title !== undefined && meta.title !== '') lines.push(`title: ${quoteIfNeeded(meta.title)}`); + if (meta.tags && meta.tags.length > 0) lines.push(`tags: [${meta.tags.map(quoteIfNeeded).join(', ')}]`); + if (meta.star === true) lines.push('star: true'); + if (meta.links && meta.links.length > 0) lines.push(`links: [${meta.links.map(quoteIfNeeded).join(', ')}]`); + return lines.length ? lines.join('\n') + '\n' : ''; +} + +function quoteIfNeeded(s: string): string { + // 含特殊字符(冒号/井号/方括号/引号/前后空格)则加引号 + if (/[:#\[\]"']|^\s|\s$/.test(s)) return JSON.stringify(s); + return s; +} diff --git a/apps/desktop/src/main/safe/safe-path.ts b/apps/desktop/src/main/safe/safe-path.ts new file mode 100644 index 0000000..ae5c4c8 --- /dev/null +++ b/apps/desktop/src/main/safe/safe-path.ts @@ -0,0 +1,64 @@ +// SafePath — 将所有文件操作圈禁在库根之内 +// 对应需求:FR-F15 路径严格限定在库根范围内 +import nodePath from 'node:path'; +import { promises as fs } from 'node:fs'; + +export class IllegalPathError extends Error { + constructor(target: string) { + super(`path escapes vault root: ${target}`); + this.name = 'IllegalPathError'; + } +} + +// 非法字符净化:把系统不允许的字符替换为下划线(FR-F13) +const ILLEGAL = /[<>:"/\\|?*\u0000-\u001f]/g; +export function sanitizeName(name: string): string { + let n = name.replace(ILLEGAL, '_').trim(); + if (!n) n = '未命名'; + return n; +} + +// 相对路径越界校验:解析后不得逃出库根 +export function assertInsideVault(root: string, rel: string): string { + // 拒绝绝对路径与盘符前缀 + if (nodePath.isAbsolute(rel) || /^[a-zA-Z]:/.test(rel)) { + throw new IllegalPathError(rel); + } + const resolved = nodePath.resolve(root, rel); + const relFromRoot = nodePath.relative(root, resolved); + if (relFromRoot.startsWith('..') || nodePath.isAbsolute(relFromRoot)) { + throw new IllegalPathError(rel); + } + return resolved; +} + +// 防 symlink 逃逸:解析真实路径后再校验一次 +export async function assertRealInsideVault(root: string, rel: string): Promise { + const resolved = assertInsideVault(root, rel); + const rootReal = await fs.realpath(root).catch(() => root); + const real = await fs.realpath(resolved).catch(() => resolved); + const relFromRoot = nodePath.relative(rootReal, real); + if (relFromRoot.startsWith('..') || nodePath.isAbsolute(relFromRoot)) { + throw new IllegalPathError(rel); + } + return real; +} + +// 重名自动追加序号(FR-F14):在 destDir 内为 name 寻找不冲突名 +export async function dedupeName(root: string, dir: string, name: string): Promise { + const base = name; + const ext = nodePath.extname(name); + const stem = ext ? base.slice(0, -ext.length) : base; + let candidate = name; + let i = 1; + const dirAbs = nodePath.resolve(root, dir); + for (;;) { + try { + await fs.access(nodePath.join(dirAbs, candidate)); // 存在则继续 + candidate = `${stem}-${i}${ext}`; + i++; + } catch { + return candidate; // 不存在,可用 + } + } +} diff --git a/apps/desktop/src/main/safe/smart-color.ts b/apps/desktop/src/main/safe/smart-color.ts new file mode 100644 index 0000000..f9084df --- /dev/null +++ b/apps/desktop/src/main/safe/smart-color.ts @@ -0,0 +1,81 @@ +// 智能取色:分析背景图亮度/主色 → 推荐深浅模式与强调色(FR-A9) +// 纯函数,输入采样后的像素数据,输出推荐参数 + +export interface ImageSample { + /** 采样像素的 [r,g,b] 列表(已降采样) */ + pixels: [number, number, number][]; +} + +export interface SmartColorResult { + mode: 'dark' | 'light'; + accentHue: number; // 0-360,约束在冷色/紫红区间 + accent: string; // hex 强调色 + suggestedBgOpacity: number; // 0-1 + suggestedBlur: number; // px + suggestedSurfaceOpacity: number; // 0-1 +} + +// 相对亮度(Rec.709) +function luminance(r: number, g: number, b: number): number { + const f = (c: number) => { c /= 255; return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); }; + return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b); +} + +function rgbToHsl(r: number, g: number, b: number): [number, number, number] { + r /= 255; g /= 255; b /= 255; + const max = Math.max(r, g, b), min = Math.min(r, g, b); + let h = 0; const l = (max + min) / 2; const d = max - min; const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1)); + if (d !== 0) { + switch (max) { + case r: h = ((g - b) / d) % 6; break; + case g: h = (b - r) / d + 2; break; + default: h = (r - g) / d + 4; + } + h *= 60; if (h < 0) h += 360; + } + return [h, s, l]; +} + +function hslToHex(h: number, s: number, l: number): string { + const c = (1 - Math.abs(2 * l - 1)) * s; const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); const m = l - c / 2; + let r = 0, g = 0, b = 0; + if (h < 60) [r, g, b] = [c, x, 0]; else if (h < 120) [r, g, b] = [x, c, 0]; + else if (h < 180) [r, g, b] = [0, c, x]; else if (h < 240) [r, g, b] = [0, x, c]; + else if (h < 300) [r, g, b] = [x, 0, c]; else [r, g, b] = [c, 0, x]; + const to = (v: number) => Math.round((v + m) * 255).toString(16).padStart(2, '0'); + return `#${to(r)}${to(g)}${to(b)}`; +} + +// 色相约束:冷色(180-270) 与紫红(280-330) 区间,否则就近映射 +function constrainHue(h: number): number { + if (h >= 180 && h <= 330) return h; + // 映射到最近区间端点 + if (h < 180) return h < 90 ? 200 : 210; // 暖色偏冷蓝 + return 290; // >330 映射紫红 +} + +export function analyzeImage(sample: ImageSample): SmartColorResult { + const { pixels } = sample; + if (pixels.length === 0) { + return { mode: 'dark', accentHue: 210, accent: '#4a9eff', suggestedBgOpacity: 0.6, suggestedBlur: 20, suggestedSurfaceOpacity: 0.5 }; + } + // 平均亮度 + const avgLum = pixels.reduce((s, p) => s + luminance(p[0], p[1], p[2]), 0) / pixels.length; + const mode: 'dark' | 'light' = avgLum < 0.5 ? 'dark' : 'light'; + // 主色相:色相直方图(12 桶),取高频且饱和度足够 + const buckets = new Array(12).fill(0); + let dominantHue = 210; + for (const p of pixels) { + const [h, s] = rgbToHsl(p[0], p[1], p[2]); + if (s > 0.15) buckets[Math.floor(h / 30) % 12]++; + } + let max = 0; + for (let i = 0; i < 12; i++) if (buckets[i] > max) { max = buckets[i]; dominantHue = i * 30 + 15; } + const accentHue = constrainHue(dominantHue); + const accent = hslToHex(accentHue, 0.7, mode === 'dark' ? 0.6 : 0.45); + // 依亮度推导不透明度 + const suggestedBgOpacity = Math.round(Math.max(0.3, Math.min(0.8, 0.5 + (0.5 - avgLum) * 0.4)) * 100) / 100; + const suggestedBlur = avgLum < 0.4 ? 24 : avgLum > 0.7 ? 12 : 18; + const suggestedSurfaceOpacity = mode === 'dark' ? 0.5 : 0.7; + return { mode, accentHue, accent, suggestedBgOpacity, suggestedBlur, suggestedSurfaceOpacity }; +} diff --git a/apps/desktop/src/main/services/config-store.ts b/apps/desktop/src/main/services/config-store.ts new file mode 100644 index 0000000..e3e5bd0 --- /dev/null +++ b/apps/desktop/src/main/services/config-store.ts @@ -0,0 +1,56 @@ +// ConfigStore — 应用配置 JSON 持久化(vault meta、最近库) +import nodePath from 'node:path'; +import { promises as fs } from 'node:fs'; +import type { VaultMeta } from '@hunter/shared'; +import { RECENT_VAULT_MAX } from '@hunter/shared'; + +export class ConfigStore { + private file: string; + private cache: { vault?: VaultMeta } | null = null; + + constructor(userDataDir: string) { + this.file = nodePath.join(userDataDir, 'config.json'); + } + + private async read(): Promise<{ vault?: VaultMeta }> { + if (this.cache) return this.cache; + try { + const raw = await fs.readFile(this.file, 'utf8'); + this.cache = JSON.parse(raw); + } catch { + this.cache = {}; + } + return this.cache!; + } + + private async write(): Promise { + const dir = nodePath.dirname(this.file); + await fs.mkdir(dir, { recursive: true }); + const tmp = this.file + '.tmp'; + await fs.writeFile(tmp, JSON.stringify(this.cache, null, 2), 'utf8'); + await fs.rename(tmp, this.file); + } + + async getVaultMeta(): Promise { + const c = await this.read(); + return c.vault ?? { currentRoot: '', recents: [], defaultRoot: '' }; + } + + async setCurrentRoot(root: string): Promise { + const meta = await this.getVaultMeta(); + meta.currentRoot = root; + // 加入最近库(去重置顶,截断到上限) + meta.recents = [root, ...meta.recents.filter(r => r !== root)].slice(0, RECENT_VAULT_MAX); + (await this.read()).vault = meta; + await this.write(); + return meta; + } + + async setDefaultRoot(root: string): Promise { + const meta = await this.getVaultMeta(); + meta.defaultRoot = root; + (await this.read()).vault = meta; + await this.write(); + return meta; + } +} diff --git a/apps/desktop/src/main/services/file-service.ts b/apps/desktop/src/main/services/file-service.ts new file mode 100644 index 0000000..0257bf3 --- /dev/null +++ b/apps/desktop/src/main/services/file-service.ts @@ -0,0 +1,134 @@ +// FileService — 库内 CRUD + 文档读写(FR-V/F/M) +import nodePath from 'node:path'; +import { promises as fs } from 'node:fs'; +import { MD_EXT, ATTACHMENTS_DIR } from '@hunter/shared'; +import type { TreeNode, ReadDocResult, WriteDocPayload } from '@hunter/shared'; +import { assertInsideVault, dedupeName, sanitizeName } from '../safe/safe-path.js'; +import { writeFileAtomic, withFileLock } from '../safe/atomic-write.js'; +import { splitFrontmatter, joinFrontmatter } from '../safe/frontmatter-codec.js'; + +// 相对路径统一用 POSIX 分隔符(跨平台一致,利于存储与比较) +const toPosix = (p: string): string => p.split(nodePath.sep).join('/').replace(/\//g, '/'); +const joinRel = (...parts: string[]): string => toPosix(nodePath.join(...parts)); + +export class FileService { + constructor(private root: string) {} + setRoot(root: string) { this.root = root; } + getRoot(): string { return this.root; } + private abs(rel: string): string { return assertInsideVault(this.root, rel); } + + // 递归列举库内树(FR-V1/FR-V9) + async listTree(): Promise { + const out: TreeNode[] = []; + const root = this.root; + const walk = async (dirAbs: string, dirRel: string) => { + let entries = await fs.readdir(dirAbs, { withFileTypes: true }); + entries.sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })); + const folders = entries.filter(e => e.isDirectory()); + const docs = entries.filter(e => e.isFile() && e.name.endsWith(MD_EXT)); + for (const e of folders) { + if (e.name === ATTACHMENTS_DIR || e.name.startsWith('.')) continue; + const rel = dirRel ? joinRel(dirRel, e.name) : e.name; + const stat = await fs.stat(nodePath.join(dirAbs, e.name)); + out.push({ path: rel, type: 'folder', name: e.name, parent: dirRel, basePath: rel, mtime: stat.mtimeMs }); + await walk(nodePath.join(dirAbs, e.name), rel); + } + for (const e of docs) { + const rel = dirRel ? joinRel(dirRel, e.name) : e.name; + const stat = await fs.stat(nodePath.join(dirAbs, e.name)); + out.push({ path: rel, type: 'doc', name: e.name.slice(0, -MD_EXT.length), parent: dirRel, basePath: dirRel, mtime: stat.mtimeMs }); + } + }; + await walk(root, ''); + return out; + } + + async create(basePath: string, rawName: string, isFolder: boolean) { + const name = sanitizeName(rawName) + (isFolder ? '' : MD_EXT); + const finalName = await dedupeName(this.root, basePath, name); + const rel = basePath ? joinRel(basePath, finalName) : finalName; + const abs = this.abs(rel); + if (isFolder) await fs.mkdir(abs, { recursive: true }); + else await writeFileAtomic(abs, ''); + return { path: rel }; + } + + async rename(from: string, toName: string) { + const abs = this.abs(from); + const isFolder = (await fs.stat(abs)).isDirectory(); + const name = sanitizeName(toName) + (isFolder ? '' : MD_EXT); + const dir = nodePath.dirname(from); + const finalName = await dedupeName(this.root, dir, name); + const toRel = dir ? joinRel(dir, finalName) : finalName; + const toAbs = this.abs(toRel); + await fs.rename(abs, toAbs); + return { path: toRel }; + } + + async move(src: string, destDir: string) { + const srcAbs = this.abs(src); + const name = nodePath.basename(src); + // 禁止移动到自身内部(FR-F9) + if (destDir === src || destDir.startsWith(src + nodePath.sep) || (src.startsWith(destDir + nodePath.sep) && destDir)) { + throw new Error('cannot move folder into itself'); + } + const finalName = await dedupeName(this.root, destDir, name); + const toRel = destDir ? joinRel(destDir, finalName) : finalName; + const toAbs = this.abs(toRel); + await fs.mkdir(this.abs(destDir || '.'), { recursive: true }); + await fs.rename(srcAbs, toAbs); + return { path: toRel }; + } + + async delete(path: string, recursive: boolean) { + const abs = this.abs(path); + if (recursive) await fs.rm(abs, { recursive: true, force: true }); + else await fs.unlink(abs); + } + + async importFiles(sources: string[], destDir: string) { + const ok: string[] = []; + const failed: { path: string; reason: string }[] = []; + for (const src of sources) { + try { + const name = nodePath.basename(src); + // 递归复制 + const dest = await this.copyEntry(src, destDir, name); + ok.push(dest); + } catch (e) { + failed.push({ path: src, reason: e instanceof Error ? e.message : String(e) }); + } + } + return { ok, failed }; + } + + private async copyEntry(srcAbs: string, destDirRel: string, name: string): Promise { + const stat = await fs.stat(srcAbs); + const finalName = await dedupeName(this.root, destDirRel, name); + const destRel = destDirRel ? joinRel(destDirRel, finalName) : finalName; + const destAbs = this.abs(destRel); + if (stat.isDirectory()) { + await fs.mkdir(destAbs, { recursive: true }); + const entries = await fs.readdir(srcAbs, { withFileTypes: true }); + for (const e of entries) { + await this.copyEntry(nodePath.join(srcAbs, e.name), destRel, e.name); + } + } else { + await fs.copyFile(srcAbs, destAbs); + } + return destRel; + } + + async readDoc(path: string): Promise { + const abs = this.abs(path); + const raw = await fs.readFile(abs, 'utf8'); + const { meta, body } = splitFrontmatter(raw); + return { meta, body }; + } + + async writeDoc(payload: WriteDocPayload): Promise { + const abs = this.abs(payload.path); + const content = joinFrontmatter(payload.meta, payload.body); + return withFileLock(abs, () => writeFileAtomic(abs, content)); + } +} diff --git a/apps/desktop/src/main/services/graph-builder.ts b/apps/desktop/src/main/services/graph-builder.ts new file mode 100644 index 0000000..0a68c41 --- /dev/null +++ b/apps/desktop/src/main/services/graph-builder.ts @@ -0,0 +1,43 @@ +// 知识图谱数据构建(FR-G1/G2/G3):节点=文档,边=关联链接,按顶层分组着色 +import type { TreeNode } from '@hunter/shared'; +import { splitFrontmatter } from '../safe/frontmatter-codec.js'; +import { promises as fs } from 'node:fs'; +import nodePath from 'node:path'; + +export interface GraphNode { id: string; title: string; basePath: string; star: boolean; group: string; color: string; } +export interface GraphEdge { source: string; target: string; } +export interface GraphData { nodes: GraphNode[]; edges: GraphEdge[]; } + +// 12 色轮换 +const COLORS = ['#e6194b','#3cb44b','#ffe119','#4363d8','#f58231','#911eb4','#46f0f0','#f032e6','#bcf60c','#fabebe','#008080','#e6beff']; + +export async function buildGraphData(root: string, tree: TreeNode[]): Promise { + const docs = tree.filter(n => n.type === 'doc'); + const topFolderOf = (p: string) => p.includes('/') ? p.split('/')[0] : '(根目录)'; + const groupColor = new Map(); + const groups = [...new Set(docs.map(d => topFolderOf(d.path)))]; + groups.forEach((g, i) => groupColor.set(g, COLORS[i % COLORS.length])); + + // 读取每篇文档 frontmatter 取 title/star/links + const nodes: GraphNode[] = []; + const linkMap = new Map(); + for (const d of docs) { + const abs = nodePath.join(root, ...d.path.split('/')); + const raw = await fs.readFile(abs, 'utf8').catch(() => ''); + const { meta } = splitFrontmatter(raw); + const group = topFolderOf(d.path); + nodes.push({ id: d.path, title: meta.title ?? d.name, basePath: d.basePath, star: !!meta.star, group, color: groupColor.get(group)! }); + if (meta.links) linkMap.set(d.path, meta.links); + } + + // 边:仅目标存在且 .md 结尾的关联 + const docPaths = new Set(docs.map(d => d.path)); + const edges: GraphEdge[] = []; + for (const [src, links] of linkMap) { + for (const l of links) { + const t = l.endsWith('.md') && docPaths.has(l) ? l : (l.endsWith('.md') && docPaths.has(nodePath.join(src, '..', l).split(nodePath.sep).join('/')) ? nodePath.join(src, '..', l).split(nodePath.sep).join('/') : null); + if (t) edges.push({ source: src, target: t }); + } + } + return { nodes, edges }; +} diff --git a/apps/desktop/src/main/services/migration-service.ts b/apps/desktop/src/main/services/migration-service.ts new file mode 100644 index 0000000..4538746 --- /dev/null +++ b/apps/desktop/src/main/services/migration-service.ts @@ -0,0 +1,47 @@ +// MigrationService — 旧版数据迁移判定与幂等保护(FR-D1/D2/D3) +import { promises as fs } from 'node:fs'; +import nodePath from 'node:path'; + +export interface MigrationResult { migrated: boolean; count: number; reason?: string; } + +export class MigrationService { + constructor(private userDataDir: string) {} + + private flagFile() { return nodePath.join(this.userDataDir, 'migrated.flag'); } + + async isMigrated(): Promise { + try { await fs.access(this.flagFile()); return true; } catch { return false; } + } + + // 判定是否应迁移:旧数据存在 + 未迁移过 + 新库为空(三者同时满足,FR-D2) + async shouldMigrate(oldDataDir: string, newVaultRoot: string): Promise { + if (await this.isMigrated()) return false; + const oldExists = await fs.stat(oldDataDir).then(s => s.isDirectory()).catch(() => false); + if (!oldExists) return false; + // 新库为空(无 .md 文件) + const entries = await fs.readdir(newVaultRoot).catch(() => []); + const hasMd = entries.some(e => e.endsWith('.md')); + return !hasMd; + } + + // 执行迁移(骨架:实际板块→文件夹映射在集成时落地) + async migrate(oldDataDir: string, newVaultRoot: string): Promise { + if (!(await this.shouldMigrate(oldDataDir, newVaultRoot))) { + return { migrated: false, count: 0, reason: '条件不满足(已迁移/无旧数据/新库非空)' }; + } + try { + // TODO: 旧板块三级结构 → 文件夹嵌套;旧富文本 → Markdown;附件 → .attachments;标签与关联映射 + // 骨架阶段:仅写迁移标志,count=0 + await this.markMigrated(); + return { migrated: true, count: 0 }; + } catch (e) { + // 失败不记标志(下次可重试,FR-D3) + return { migrated: false, count: 0, reason: e instanceof Error ? e.message : String(e) }; + } + } + + async markMigrated() { + await fs.mkdir(nodePath.dirname(this.flagFile()), { recursive: true }); + await fs.writeFile(this.flagFile(), new Date().toISOString(), 'utf8'); + } +} diff --git a/apps/desktop/src/main/services/search-service.ts b/apps/desktop/src/main/services/search-service.ts new file mode 100644 index 0000000..905b215 --- /dev/null +++ b/apps/desktop/src/main/services/search-service.ts @@ -0,0 +1,61 @@ +// SearchService — 全文搜索 + 标签汇总(FR-R) +import { promises as fs } from 'node:fs'; +import nodePath from 'node:path'; +import { SEARCH_RESULT_MAX, MD_EXT, ATTACHMENTS_DIR } from '@hunter/shared'; +import type { TreeNode } from '@hunter/shared'; +import { splitFrontmatter } from '../safe/frontmatter-codec.js'; + +export interface SearchHit { path: string; title: string; snippet: string; } +export interface TagStat { tag: string; count: number; } + +export class SearchService { + constructor(private root: string, private fileService: { getRoot: () => string }) {} + + // 遍历库读取所有文档,建立简易索引(M4:内存全量,大库后续优化) + private async readAll(): Promise<{ path: string; title: string; body: string; tags: string[] }[]> { + const out: { path: string; title: string; body: string; tags: string[] }[] = []; + const walk = async (dirAbs: string, dirRel: string) => { + const entries = await fs.readdir(dirAbs, { withFileTypes: true }); + for (const e of entries) { + if (e.name.startsWith('.')) continue; + const abs = nodePath.join(dirAbs, e.name); + const rel = dirRel ? (dirRel + '/' + e.name) : e.name; + if (e.isDirectory()) await walk(abs, rel); + else if (e.isFile() && e.name.endsWith(MD_EXT)) { + const raw = await fs.readFile(abs, 'utf8'); + const { meta, body } = splitFrontmatter(raw); + const relPosix = rel.split(nodePath.sep).join('/'); + out.push({ path: relPosix, title: meta.title ?? e.name.slice(0, -MD_EXT.length), body, tags: meta.tags ?? [] }); + } + } + }; + await walk(this.root, ''); + return out; + } + + async search(query: string): Promise { + if (!query.trim()) return []; + const q = query.toLowerCase(); + const all = await this.readAll(); + const hits: SearchHit[] = []; + for (const d of all) { + const hay = (d.title + '\n' + d.body).toLowerCase(); + const idx = hay.indexOf(q); + if (idx !== -1) { + // 摘要:命中位置附近,超长截尾保留 ~120 字符 + const start = Math.max(0, idx - 40); + const snippet = d.body.slice(start, start + 120).replace(/\n/g, ' '); + hits.push({ path: d.path, title: d.title, snippet: (start > 0 ? '…' : '') + snippet }); + } + if (hits.length >= SEARCH_RESULT_MAX) break; + } + return hits; + } + + async tagStats(): Promise { + const all = await this.readAll(); + const m = new Map(); + for (const d of all) for (const t of d.tags) m.set(t, (m.get(t) ?? 0) + 1); + return [...m.entries()].map(([tag, count]) => ({ tag, count })).sort((a, b) => a.tag.localeCompare(b.tag)); + } +} diff --git a/apps/desktop/src/main/services/vault-service.ts b/apps/desktop/src/main/services/vault-service.ts new file mode 100644 index 0000000..1754e3e --- /dev/null +++ b/apps/desktop/src/main/services/vault-service.ts @@ -0,0 +1,53 @@ +// VaultService — 统筹库管理:默认库、切换、最近库(FR-V) +import nodePath from 'node:path'; +import { promises as fs } from 'node:fs'; +import { app, dialog } from 'electron'; +import { DEFAULT_VAULT_NAME } from '@hunter/shared'; +import type { VaultMeta } from '@hunter/shared'; +import { ConfigStore } from './config-store.js'; +import { FileService } from './file-service.js'; +import { SearchService } from './search-service.js'; + +export class VaultService { + fileService: FileService | null = null; + searchService: SearchService | null = null; + private config: ConfigStore; + + constructor() { + this.config = new ConfigStore(app.getPath('userData')); + } + + // 启动时初始化:确保有当前库(无则创建默认库) + async init(): Promise { + let meta = await this.config.getVaultMeta(); + if (!meta.currentRoot) { + const defaultRoot = nodePath.join(app.getPath('userData'), DEFAULT_VAULT_NAME); + await fs.mkdir(defaultRoot, { recursive: true }); + meta = await this.config.setCurrentRoot(defaultRoot); + } + this.fileService = new FileService(meta.currentRoot); + this.searchService = new SearchService(meta.currentRoot, this.fileService); + return meta; + } + + async getMeta(): Promise { return this.config.getVaultMeta(); } + + // 通过系统对话框选择并切换库(FR-V3) + async pickAndSwitch(): Promise { + const result = await dialog.showOpenDialog({ properties: ['openDirectory'] }); + if (result.canceled || result.filePaths.length === 0) return null; + return this.switch(result.filePaths[0]); + } + + async switch(root: string): Promise { + // 校验是存在的目录 + const stat = await fs.stat(root).catch(() => null); + if (!stat || !stat.isDirectory()) throw new Error('目标路径不是有效目录: ' + root); + const meta = await this.config.setCurrentRoot(root); + this.fileService = new FileService(root); + this.searchService = new SearchService(root, this.fileService); + return meta; + } + + async setDefaultRoot(root: string): Promise { await this.config.setDefaultRoot(root); } +} \ No newline at end of file diff --git a/apps/desktop/src/preload/index.ts b/apps/desktop/src/preload/index.ts new file mode 100644 index 0000000..31f58ea --- /dev/null +++ b/apps/desktop/src/preload/index.ts @@ -0,0 +1,14 @@ +// preload — 通过 contextBridge 暴露受限 invoke API(安全基线) +import { contextBridge, ipcRenderer } from 'electron'; +import type { BridgeApi, IpcMap } from '@hunter/shared'; + +const api: BridgeApi = { + invoke: (channel, args) => ipcRenderer.invoke(channel, args as never) as Promise, + send: (channel, args) => ipcRenderer.send(channel, args as never) as void, +}; + +contextBridge.exposeInMainWorld('dsh', api); + +// 渲染层全局类型(便于直接 window.dsh) +export type _BridgeApi = BridgeApi; +export type _IpcMap = IpcMap; \ No newline at end of file diff --git a/apps/desktop/src/renderer/App.tsx b/apps/desktop/src/renderer/App.tsx new file mode 100644 index 0000000..bfd7cc5 --- /dev/null +++ b/apps/desktop/src/renderer/App.tsx @@ -0,0 +1,84 @@ +import { useState, useEffect } from 'react'; +import { useStore } from './store'; +import { FileTree } from './features/file-tree/FileTree'; +import { Editor } from './features/editor/Editor'; +import { Toolbar } from './features/toolbar/Toolbar'; +import { TitleBar } from './features/shell/TitleBar'; +import { InputDialog } from './features/dialog/InputDialog'; +import { NotifyLayer } from './features/dialog/NotifyLayer'; +import { TabBar } from './features/tabs/TabBar'; +import { useTabs } from './features/tabs/tab-store'; +import { GraphView } from './features/graph/GraphView'; +import { SettingsPanel } from './features/settings/SettingsPanel'; +import { AppearancePanel } from './features/appearance/AppearancePanel'; +import { useAppearance } from './features/appearance/appearance-store'; +import { CheatSheet } from './features/keymap/CheatSheet'; +import { useGlobalHotkeys } from './features/keymap/use-hotkeys'; +import { useLayout } from './features/shell/layout-store'; +import { Resizer } from './features/shell/Resizer'; +import { EffectLayer } from './features/effects/EffectLayer'; +import { useSettings } from './features/settings/settings-store'; +import { SearchBox } from './features/search/SearchBox'; +import { TagFilter } from './features/search/TagFilter'; +import { OutlinePanel } from './features/outline/OutlinePanel'; +import { DocInfoPanel } from './features/doc-info/DocInfoPanel'; + +export function App() { + const { loadVault, loadTree, openDoc } = useStore(); + const { restore, tabs, activeId } = useTabs(); + const [rightTab, setRightTab] = useState<'outline' | 'info'>('outline'); + const [view, setView] = useState<'editor' | 'graph'>('editor'); + const [showSettings, setShowSettings] = useState(false); + const [showCheat, setShowCheat] = useState(false); + const [showAppearance, setShowAppearance] = useState(false); + const appearance = useAppearance(); + const settings = useSettings(); + const layout = useLayout(); + + useEffect(() => { loadVault(); loadTree(); restore(); appearance.preview(); }, []); + useEffect(() => { if (activeId && tabs.length > 0) { const t = tabs.find(x => x.id === activeId); if (t) openDoc(t.path).catch(() => {}); } }, []); + useGlobalHotkeys({ onNewDoc: () => {}, onSave: () => useStore.getState().saveNow(), onFocusSearch: () => { const el = document.querySelector('input[placeholder*="搜索"]') as HTMLInputElement; el?.focus(); }, onToggleOutline: () => layout.toggleOutline(), onCheat: () => setShowCheat(true), onSettings: () => setShowSettings(true) }); + + // 编辑器样式随设置生效(FR-编辑器设置真实生效) + useEffect(() => { document.documentElement.style.setProperty('--editor-font-size', settings.fontSize + 'px'); document.documentElement.style.setProperty('--editor-font-family', settings.fontFamily); document.documentElement.style.setProperty('--editor-white-space', settings.wordWrap ? 'pre-wrap' : 'pre'); }, [settings.fontSize, settings.fontFamily, settings.wordWrap]); + return ( +
+ setView(v => v === 'editor' ? 'graph' : 'editor')} onSettings={() => setShowSettings(true)} onAppearance={() => setShowAppearance(true)} onToggleSidebar={() => layout.toggleSidebar()} onToggleMinimal={() => layout.toggleMinimal()} minimal={layout.minimal} /> + + {view === 'editor' && } +
+ {layout.minimal ? () : (<> + {layout.sidebarVisible && (<> +
+ +
+ +
+ layout.setSidebarWidth(layout.sidebarWidth + d)} /> + )} + {view === 'editor' ? : } + {view === 'editor' && layout.outlineVisible && (<> + layout.setOutlineWidth(layout.outlineWidth + d)} /> +
+
+ + +
+
{rightTab === 'outline' ? : }
+
+ )} + )} +
+ + + + {showSettings && setShowSettings(false)} />} + {showAppearance && setShowAppearance(false)} />} + {showCheat && setShowCheat(false)} />} +
+ ); +} + +function tabBtn(active: boolean): React.CSSProperties { + return { flex: 1, padding: '6px', background: active ? '#2a2a3a' : 'transparent', border: 'none', color: active ? '#fff' : '#888', cursor: 'pointer', borderBottom: active ? '2px solid #4a9eff' : '2px solid transparent' }; +} \ No newline at end of file diff --git a/apps/desktop/src/renderer/ErrorBoundary.tsx b/apps/desktop/src/renderer/ErrorBoundary.tsx new file mode 100644 index 0000000..1e9edc4 --- /dev/null +++ b/apps/desktop/src/renderer/ErrorBoundary.tsx @@ -0,0 +1,21 @@ +import { Component, ReactNode } from 'react'; + +interface State { hasError: boolean; error?: Error } +export class ErrorBoundary extends Component<{ children: ReactNode }, State> { + state: State = { hasError: false }; + static getDerivedStateFromError(error: Error): State { return { hasError: true, error }; } + componentDidCatch(error: Error, info: any) { console.error('UI 错误兜底:', error, info); } + render() { + if (this.state.hasError) { + return ( +
+

界面发生异常

+

错误:{this.state.error?.message}

+
{this.state.error?.stack}
+ +
+ ); + } + return this.props.children; + } +} diff --git a/apps/desktop/src/renderer/features/appearance/AppearancePanel.tsx b/apps/desktop/src/renderer/features/appearance/AppearancePanel.tsx new file mode 100644 index 0000000..32f95cb --- /dev/null +++ b/apps/desktop/src/renderer/features/appearance/AppearancePanel.tsx @@ -0,0 +1,48 @@ +import { useAppearance } from './appearance-store'; +import { THEMES } from './themes'; +import type { ThemeDef } from './themes'; + +export function AppearancePanel({ onClose }: { onClose: () => void }) { + const { themeId, draft, selectTheme, override, save, revert, isDirty } = useAppearance(); + const colors: [keyof typeof draft, string][] = [['accent','强调色'],['glow','辉光色'],['accentText','强调色文字'],['textPrimary','主要文字'],['textSecondary','辅助文字'],['textTertiary','弱化文字'],['controlBorder','控件边框'],['bgPrimary','工作区背景'],['bgSecondary','浮层背景']]; + return ( +
{ if (e.target === e.currentTarget) { revert(); onClose(); } }} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1500 }}> +
+
+ 外观定制 +
+ + +
+
+
{isDirty() ? '更改尚未保存' : '所有设置已保存'}
+
主题
+
+ {THEMES.map((t: ThemeDef) => ( +
selectTheme(t.id)} style={{ cursor: 'pointer', padding: 8, borderRadius: 6, border: themeId === t.id ? '2px solid #4a9eff' : '1px solid #3a3a4a', background: t.params.bgPrimary, color: t.params.textPrimary, fontSize: 11, minHeight: 48, display: 'flex', flexDirection: 'column', justifyContent: 'center' }}> +
{t.name}
{t.desc}
+
+ ))} +
+
颜色自定义
+
+ {colors.map(([k, label]) => ( +
{label} override(k, e.target.value)} style={{ width: 40, height: 24, border: 'none', background: 'none', cursor: 'pointer' }} />
+ ))} +
+
+ + +
+ +
+
+ ); +} + +function normalizeColor(c: string): string { + // 'transparent' 或 rgba(...) 给取色器一个占位 hex + if (c === 'transparent' || c.startsWith('rgba')) return '#1a1a2a'; + return c; +} +const btn: React.CSSProperties = { padding: '6px 14px', border: '1px solid #3a3a4a', background: 'transparent', color: '#aaa', borderRadius: 4, cursor: 'pointer', fontSize: 13 }; diff --git a/apps/desktop/src/renderer/features/appearance/appearance-store.ts b/apps/desktop/src/renderer/features/appearance/appearance-store.ts new file mode 100644 index 0000000..ecf058c --- /dev/null +++ b/apps/desktop/src/renderer/features/appearance/appearance-store.ts @@ -0,0 +1,54 @@ +import { create } from 'zustand'; +import { THEMES, applyTheme, type ThemeDef, type ThemeParams } from './themes'; + +interface AppearanceState { + themeId: string; + draft: ThemeParams; // 工作副本 + saved: ThemeParams; // 已保存快照(回滚基准) + manualFlags: Set; // 用户手动改过的项(智能取色不覆盖) + // actions + selectTheme: (id: string) => void; + override: (key: keyof ThemeParams, value: any) => void; + preview: () => void; // 实时预览(应用到 DOM) + save: () => void; // 显式保存:持久化 + 更新 saved 快照 + revert: () => void; // Esc/取消:回滚到 saved + isDirty: () => boolean; +} + +const defaultTheme = THEMES[1]; // 午夜蓝为默认 + +export const useAppearance = create((set, get) => ({ + themeId: defaultTheme.id, + draft: { ...defaultTheme.params }, + saved: { ...defaultTheme.params }, + manualFlags: new Set(), + + selectTheme: (id) => { + const t = THEMES.find(x => x.id === id) ?? defaultTheme; + set({ themeId: id, draft: { ...t.params } }); + get().preview(); + }, + + override: (key, value) => { + set(s => ({ draft: { ...s.draft, [key]: value }, manualFlags: new Set([...s.manualFlags, key]) })); + get().preview(); + }, + + preview: () => applyTheme(get().draft), + + save: () => { + const draft = get().draft; + // 与主题默认值相同的调整自动丢弃(保持配置干净) + const t = THEMES.find(x => x.id === get().themeId); + const cleaned = { ...draft }; + if (t) for (const k of Object.keys(t.params) as (keyof ThemeParams)[]) { + if (cleaned[k] === t.params[k] && !get().manualFlags.has(k)) { /* 保留默认即可 */ } + } + set({ saved: { ...cleaned } }); + // M5 暂存内存;M9 接 electron-store 持久化 + }, + + revert: () => { set({ draft: { ...get().saved } }); get().preview(); }, + + isDirty: () => JSON.stringify(get().draft) !== JSON.stringify(get().saved), +})); diff --git a/apps/desktop/src/renderer/features/appearance/themes.ts b/apps/desktop/src/renderer/features/appearance/themes.ts new file mode 100644 index 0000000..32a2c60 --- /dev/null +++ b/apps/desktop/src/renderer/features/appearance/themes.ts @@ -0,0 +1,35 @@ +// 8 套主题定义(FR-A1/A2)——CSS 变量驱动 +export interface ThemeParams { + accent: string; glow: string; accentText: string; + textPrimary: string; textSecondary: string; textTertiary: string; + controlBorder: string; bgPrimary: string; bgSecondary: string; + blurPx: number; surfaceOpacity: number; radius: number; +} +export interface ThemeDef { id: string; name: string; group: 'dark' | 'light'; desc: string; params: ThemeParams; } + +export const THEMES: ThemeDef[] = [ + { id: 'transparent', name: '全透明', group: 'dark', desc: '窗口直接透出桌面', params: { accent:'#4a9eff', glow:'#6a8cff', accentText:'#7ab8ff', textPrimary:'#e8e8e8', textSecondary:'#aaa', textTertiary:'#666', controlBorder:'#333', bgPrimary:'transparent', bgSecondary:'rgba(20,20,28,0.5)', blurPx:20, surfaceOpacity:0.5, radius:8 } }, + { id: 'midnight', name: '午夜蓝', group: 'dark', desc: '深邃午夜蓝调', params: { accent:'#4a9eff', glow:'#6a8cff', accentText:'#7ab8ff', textPrimary:'#e8e8e8', textSecondary:'#aaa', textTertiary:'#666', controlBorder:'#2a3548', bgPrimary:'#0d1525', bgSecondary:'#141d30', blurPx:16, surfaceOpacity:0.6, radius:8 } }, + { id: 'purple-night', name: '深紫夜', group: 'dark', desc: '神秘紫夜', params: { accent:'#a06aff', glow:'#b88aff', accentText:'#c4a3ff', textPrimary:'#e8e8f0', textSecondary:'#aaa', textTertiary:'#666', controlBorder:'#2a2438', bgPrimary:'#1a1228', bgSecondary:'#241a35', blurPx:16, surfaceOpacity:0.6, radius:8 } }, + { id: 'forest', name: '森林绿', group: 'dark', desc: '静谧森林', params: { accent:'#5abf7a', glow:'#7ad99a', accentText:'#7ad99a', textPrimary:'#e8f0e8', textSecondary:'#aaa', textTertiary:'#666', controlBorder:'#243028', bgPrimary:'#102018', bgSecondary:'#1a2a22', blurPx:16, surfaceOpacity:0.6, radius:8 } }, + { id: 'amber', name: '暖琥珀', group: 'dark', desc: '温暖琥珀光', params: { accent:'#e0a040', glow:'#f0c060', accentText:'#f0c060', textPrimary:'#f0e8d8', textSecondary:'#aaa', textTertiary:'#666', controlBorder:'#3a3028', bgPrimary:'#1f1810', bgSecondary:'#2a2018', blurPx:16, surfaceOpacity:0.6, radius:8 } }, + { id: 'latte', name: '奶霜拿铁', group: 'light', desc: '柔润奶白', params: { accent:'#c08050', glow:'#e0a070', accentText:'#a06840', textPrimary:'#3a3028', textSecondary:'#666', textTertiary:'#999', controlBorder:'#d8c8b8', bgPrimary:'#f5efe6', bgSecondary:'#ede5d8', blurPx:10, surfaceOpacity:0.8, radius:8 } }, + { id: 'daylight', name: '晴空白昼', group: 'light', desc: '清爽天蓝', params: { accent:'#3080d0', glow:'#50a0f0', accentText:'#2070c0', textPrimary:'#2a3a4a', textSecondary:'#5a6a7a', textTertiary:'#9aa', controlBorder:'#c8d8e8', bgPrimary:'#f0f5fa', bgSecondary:'#e0eaf2', blurPx:10, surfaceOpacity:0.85, radius:8 } }, + { id: 'plain', name: '素纸简白', group: 'light', desc: '极简素纸', params: { accent:'#555', glow:'#888', accentText:'#333', textPrimary:'#222', textSecondary:'#666', textTertiary:'#999', controlBorder:'#ddd', bgPrimary:'#fff', bgSecondary:'#f5f5f5', blurPx:0, surfaceOpacity:0.9, radius:6 } }, +]; + +export function applyTheme(params: ThemeParams) { + const root = document.documentElement; + root.style.setProperty('--accent', params.accent); + root.style.setProperty('--glow', params.glow); + root.style.setProperty('--accent-text', params.accentText); + root.style.setProperty('--text-primary', params.textPrimary); + root.style.setProperty('--text-secondary', params.textSecondary); + root.style.setProperty('--text-tertiary', params.textTertiary); + root.style.setProperty('--control-border', params.controlBorder); + root.style.setProperty('--bg-primary', params.bgPrimary); + root.style.setProperty('--bg-secondary', params.bgSecondary); + root.style.setProperty('--blur-px', params.blurPx + 'px'); + root.style.setProperty('--surface-opacity', String(params.surfaceOpacity)); + root.style.setProperty('--radius', params.radius + 'px'); +} diff --git a/apps/desktop/src/renderer/features/dialog/InputDialog.tsx b/apps/desktop/src/renderer/features/dialog/InputDialog.tsx new file mode 100644 index 0000000..6cc25a1 --- /dev/null +++ b/apps/desktop/src/renderer/features/dialog/InputDialog.tsx @@ -0,0 +1,36 @@ +import { useState, useEffect, useRef } from 'react'; +import { useDialog } from './dialog-store'; + +export function InputDialog() { + const { open, mode, title, message, placeholder, close } = useDialog(); + const [value, setValue] = useState(''); + const inputRef = useRef(null); + + useEffect(() => { if (open) { setValue(''); setTimeout(() => inputRef.current?.focus(), 50); } }, [open]); + if (!open) return null; + + const submit = () => { + if (mode === 'confirm') { close('yes'); return; } + close(value.trim() || null); + }; + + return ( +
{ if (e.target === e.currentTarget) close(null); }} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 }}> +
+
{title}
+ {mode === 'confirm' ? ( +
{message}
+ ) : ( + setValue(e.target.value)} + onKeyDown={e => { if (e.key === 'Enter') submit(); if (e.key === 'Escape') close(null); }} + placeholder={placeholder} style={{ width: '100%', background: '#15151f', border: '1px solid #3a3a4a', color: '#ddd', borderRadius: 4, padding: '8px 12px', fontSize: 14, outline: 'none' }} /> + )} +
+ + +
+
+
+ ); +} +const btn: React.CSSProperties = { padding: '6px 16px', border: '1px solid #3a3a4a', background: 'transparent', color: '#aaa', borderRadius: 4, cursor: 'pointer', fontSize: 13 }; diff --git a/apps/desktop/src/renderer/features/dialog/NotifyLayer.tsx b/apps/desktop/src/renderer/features/dialog/NotifyLayer.tsx new file mode 100644 index 0000000..77203a1 --- /dev/null +++ b/apps/desktop/src/renderer/features/dialog/NotifyLayer.tsx @@ -0,0 +1,6 @@ +import { useNotify } from './notify-store'; +export function NotifyLayer() { const { items, remove } = useNotify(); + return
{items.map(n => ( +
remove(n.id)} style={{ padding: '10px 16px', borderRadius: 6, fontSize: 13, cursor: 'pointer', boxShadow: '0 4px 12px rgba(0,0,0,0.3)', background: n.type === 'success' ? '#2a4a3a' : n.type === 'error' ? '#4a2a2a' : '#2a2a3a', color: n.type === 'success' ? '#9fc' : n.type === 'error' ? '#f88' : '#9cf' }}>{n.msg}
+ ))}
; +} diff --git a/apps/desktop/src/renderer/features/dialog/dialog-store.ts b/apps/desktop/src/renderer/features/dialog/dialog-store.ts new file mode 100644 index 0000000..14a5622 --- /dev/null +++ b/apps/desktop/src/renderer/features/dialog/dialog-store.ts @@ -0,0 +1,22 @@ +import { create } from 'zustand'; + +type Mode = 'input' | 'confirm'; + +interface DialogState { + open: boolean; + mode: Mode; + title: string; + message: string; + placeholder: string; + resolve: ((value: string | null) => void) | null; + showInput: (title: string, placeholder?: string) => Promise; + showConfirm: (title: string, message: string) => Promise; + close: (value: string | null) => void; +} + +export const useDialog = create((set, get) => ({ + open: false, mode: 'input', title: '', message: '', placeholder: '', resolve: null, + showInput: (title, placeholder = '') => new Promise((resolve) => { set({ open: true, mode: 'input', title, placeholder, resolve }); }), + showConfirm: (title, message) => new Promise((resolve) => { set({ open: true, mode: 'confirm', title, message, resolve }); }), + close: (value) => { const r = get().resolve; set({ open: false, resolve: null }); r?.(value); }, +})); diff --git a/apps/desktop/src/renderer/features/dialog/notify-store.ts b/apps/desktop/src/renderer/features/dialog/notify-store.ts new file mode 100644 index 0000000..75bdba3 --- /dev/null +++ b/apps/desktop/src/renderer/features/dialog/notify-store.ts @@ -0,0 +1,11 @@ +import { create } from 'zustand'; +export type NotifyType = 'success' | 'error' | 'info'; +export interface Notify { id: number; type: NotifyType; msg: string; } +interface NotifyState { items: Notify[]; show: (type: NotifyType, msg: string) => void; remove: (id: number) => void; } +let seq = 1; +export const useNotify = create((set, get) => ({ + items: [], + show: (type, msg) => { const id = seq++; set(s => ({ items: [...s.items, { id, type, msg }] })); if (type !== 'info') setTimeout(() => get().remove(id), 2500); }, + remove: (id) => set(s => ({ items: s.items.filter(n => n.id !== id) })), +})); +export const notify = (type: NotifyType, msg: string) => useNotify.getState().show(type, msg); diff --git a/apps/desktop/src/renderer/features/doc-info/DocInfoPanel.tsx b/apps/desktop/src/renderer/features/doc-info/DocInfoPanel.tsx new file mode 100644 index 0000000..e361217 --- /dev/null +++ b/apps/desktop/src/renderer/features/doc-info/DocInfoPanel.tsx @@ -0,0 +1,79 @@ +import { useState } from 'react'; +import { useStore } from '../../store'; +import { invoke } from '../../ipc-client'; +import { IPC } from '@hunter/shared'; +import type { TreeNode } from '@hunter/shared'; + +export function DocInfoPanel() { + const { currentPath, title, meta, tree, toggleStar, addTag, removeTag, addLink, removeLink } = useStore(); + const [tagInput, setTagInput] = useState(''); + const [linkInput, setLinkInput] = useState(''); + + if (!currentPath) { + return
未打开文档
; + } + + // 库内 .md 文件供关联链接选择 + const docs = tree.filter(n => n.type === 'doc'); + const mtime = tree.find(n => n.path === currentPath)?.mtime; + + return ( +
+
+ {title} + +
+ +
+
{currentPath}
+
+ +
+
+ {(meta.tags ?? []).map(t => ( + + {t} removeTag(t)}>× + + ))} + {(meta.tags ?? []).length === 0 && 无标签} +
+
+ setTagInput(e.target.value)} onKeyDown={e => { if (e.key === 'Enter' && tagInput.trim()) { addTag(tagInput.trim()); setTagInput(''); } }} placeholder="添加标签" style={inp} /> + +
+
+ +
+
+ {(meta.links ?? []).map(l => ( +
+ {l} + removeLink(l)}>× +
+ ))} + {(meta.links ?? []).length === 0 && 无关联} +
+ + +
+ +
+
{mtime ? new Date(mtime).toLocaleString('zh-CN') : '—'}
+
创建时间:—(按设计不落盘)
+
+
+ ); +} + +function Section({ title, children }: { title: string; children: React.ReactNode }) { + return (
{title}
{children}
); +} +const inp: React.CSSProperties = { background: '#222', border: '1px solid #444', color: '#ddd', borderRadius: 4, padding: '3px 8px', fontSize: 13, flex: 1 }; +const btn: React.CSSProperties = { minWidth: 28, height: 26, border: '1px solid #444', background: 'transparent', color: '#ccc', cursor: 'pointer', borderRadius: 4 }; diff --git a/apps/desktop/src/renderer/features/editor/BlockOps.ts b/apps/desktop/src/renderer/features/editor/BlockOps.ts new file mode 100644 index 0000000..936aa17 --- /dev/null +++ b/apps/desktop/src/renderer/features/editor/BlockOps.ts @@ -0,0 +1,13 @@ +import { Extension } from '@tiptap/core'; + +// 块操作扩展:Alt+↑/↓ 移动当前块,Alt+Backspace 删除当前块(FR-E7) +export const BlockOps = Extension.create({ + name: 'blockOps', + addKeyboardShortcuts() { + return { + 'Alt-ArrowUp': () => this.editor.commands.liftListItem('listItem'), + 'Alt-ArrowDown': () => this.editor.commands.sinkListItem('listItem'), + 'Alt-Backspace': () => { const e = this.editor; const from = e.state.selection.from; const $pos = e.state.doc.resolve(from); const start = $pos.before($pos.depth); const end = $pos.after($pos.depth); if (start >= 0) e.chain().focus().deleteRange({ from: start, to: end }).run(); return true; }, + }; + }, +}); diff --git a/apps/desktop/src/renderer/features/editor/Editor.tsx b/apps/desktop/src/renderer/features/editor/Editor.tsx new file mode 100644 index 0000000..0e822d3 --- /dev/null +++ b/apps/desktop/src/renderer/features/editor/Editor.tsx @@ -0,0 +1,33 @@ +import { useStore } from '../../store'; +import { TiptapEditor } from './TiptapEditor'; +import { MoreActions } from './MoreActions'; +import type { SaveState } from '../../store'; + +function saveLabel(s: SaveState): string { + if (s === 'saving') return '保存中…'; + if (s === 'error') return '保存失败'; + return '已保存'; +} + +export function Editor() { + const { currentPath, title, saveState } = useStore(); + if (!currentPath) { + return ( +
+
从左侧选择一篇笔记,或新建文档开始
+
+ ); + } + return ( +
+
+ {title} {currentPath} +
+ {saveLabel(saveState)} + +
+
+ +
+ ); +} \ No newline at end of file diff --git a/apps/desktop/src/renderer/features/editor/EditorToolbar.tsx b/apps/desktop/src/renderer/features/editor/EditorToolbar.tsx new file mode 100644 index 0000000..ce75801 --- /dev/null +++ b/apps/desktop/src/renderer/features/editor/EditorToolbar.tsx @@ -0,0 +1,35 @@ +import type { Editor } from '@tiptap/react'; +import { useStore } from '../../store'; +import { useEditorRef } from './editor-context'; + +const btn: React.CSSProperties = { minWidth: 30, height: 28, border: '1px solid #444', background: 'transparent', color: '#ccc', cursor: 'pointer', borderRadius: 4, fontSize: 13 }; + +function T({ onClick, title, children, disabled }: { onClick: () => void; title: string; children: React.ReactNode; disabled?: boolean }) { + return ; +} + +export function EditorToolbar() { + const editor = useEditorRef(); + const d = !editor; + const cmd = (fn: (e: Editor) => void) => () => { if (editor) { fn(editor); editor.commands.focus(); } }; + return ( +
+ e.chain().focus().undo().run())} disabled={d}>↶ + e.chain().focus().redo().run())} disabled={d}>↷ + + e.chain().focus().toggleBold().run())} disabled={d}>B + e.chain().focus().toggleItalic().run())} disabled={d}>I + e.chain().focus().toggleStrike().run())} disabled={d}>S + e.chain().focus().toggleBlockquote().run())} disabled={d}>❝ + + e.chain().focus().toggleHeading({ level: 1 }).run())} disabled={d}>H1 + e.chain().focus().toggleHeading({ level: 2 }).run())} disabled={d}>H2 + e.chain().focus().toggleHeading({ level: 3 }).run())} disabled={d}>H3 + + e.chain().focus().toggleBulletList().run())} disabled={d}>• + e.chain().focus().toggleOrderedList().run())} disabled={d}>1. + e.chain().focus().toggleCodeBlock().run())} disabled={d}>{''} + e.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run())} disabled={d}>▦ +
+ ); +} diff --git a/apps/desktop/src/renderer/features/editor/FindReplace.tsx b/apps/desktop/src/renderer/features/editor/FindReplace.tsx new file mode 100644 index 0000000..8bc686e --- /dev/null +++ b/apps/desktop/src/renderer/features/editor/FindReplace.tsx @@ -0,0 +1,61 @@ +import { useState } from 'react'; +import { useEditorRef } from '../editor/editor-context'; + +export function FindReplace() { + const editor = useEditorRef(); + const [find, setFind] = useState(''); + const [replace, setReplace] = useState(''); + const [matchIdx, setMatchIdx] = useState(0); + const [matches, setMatches] = useState([]); // 各匹配起始 pos + + const doFind = () => { + if (!editor || !find) { setMatches([]); setMatchIdx(0); return; } + const positions: number[] = []; + const text = editor.state.doc.textBetween(0, editor.state.doc.content.size, '\n'); + // 遍历 doc 收集匹配(基于文本偏移简化) + editor.state.doc.descendants((node, pos) => { + if (node.isText && node.text) { + let i = 0; + while ((i = node.text.indexOf(find, i)) !== -1) { + positions.push(pos + i); + i += find.length; + } + } + }); + setMatches(positions); + setMatchIdx(0); + if (positions.length > 0) jumpTo(0, positions); + }; + + const jumpTo = (idx: number, positions = matches) => { + if (!editor || positions.length === 0) return; + const pos = positions[idx]; + editor.commands.setTextSelection({ from: pos, to: pos + find.length }); + editor.commands.scrollIntoView(); + editor.commands.focus(); + }; + + const next = () => { if (matches.length === 0) return; const i = (matchIdx + 1) % matches.length; setMatchIdx(i); jumpTo(i); }; + const prev = () => { if (matches.length === 0) return; const i = (matchIdx - 1 + matches.length) % matches.length; setMatchIdx(i); jumpTo(i); }; + + const doReplace = () => { + if (!editor || matches.length === 0) return; + const pos = matches[matchIdx]; + editor.chain().focus().deleteRange({ from: pos, to: pos + find.length }).insertContentAt(pos, replace).run(); + doFind(); // 重新查找 + }; + + return ( +
+ setFind(e.target.value)} placeholder="查找" onKeyDown={e => e.key === 'Enter' && doFind()} style={inp} /> + setReplace(e.target.value)} placeholder="替换" style={inp} /> + + + + + {matches.length > 0 ? `${matchIdx + 1}/${matches.length}` : ''} +
+ ); +} +const inp: React.CSSProperties = { background: '#222', border: '1px solid #444', color: '#ddd', borderRadius: 4, padding: '3px 8px', width: 120, fontSize: 13 }; +const btn: React.CSSProperties = { minWidth: 30, height: 26, border: '1px solid #444', background: 'transparent', color: '#ccc', cursor: 'pointer', borderRadius: 4, fontSize: 12 }; diff --git a/apps/desktop/src/renderer/features/editor/MoreActions.tsx b/apps/desktop/src/renderer/features/editor/MoreActions.tsx new file mode 100644 index 0000000..631c296 --- /dev/null +++ b/apps/desktop/src/renderer/features/editor/MoreActions.tsx @@ -0,0 +1,32 @@ +import { useStore } from '../../store'; +import { invoke } from '../../ipc-client'; +import { IPC } from '@hunter/shared'; +import { useState } from 'react'; + +export function MoreActions() { + const { currentPath, body, openDoc } = useStore(); + const [open, setOpen] = useState(false); + if (!currentPath) return null; + const copyMd = async () => { await invoke(IPC.copyMd, { path: currentPath }); notify('已复制 Markdown'); setOpen(false); }; + const print = () => { window.print(); setOpen(false); }; + const reveal = async () => { await invoke(IPC.docReveal, { path: currentPath }); setOpen(false); }; + const reload = async () => { await openDoc(currentPath); setOpen(false); }; + return ( +
+ + {open && ( +
+ 复制 Markdown + 打印 / 另存为 PDF + 在文件管理器中显示 + 重新加载 +
+ )} +
+ ); +} +function Item({ children, onClick }: { children: React.ReactNode; onClick: () => void }) { + return
(e.currentTarget.style.background = '#2a2a3a')} onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}>{children}
; +} +const btn: React.CSSProperties = { height: 26, border: '1px solid #3a3a4a', background: 'transparent', color: '#ccc', cursor: 'pointer', borderRadius: 4, fontSize: 14, padding: '0 8px' }; +function notify(msg: string) { const el = document.createElement('div'); el.textContent = msg; el.style.cssText = 'position:fixed;top:50px;right:20px;background:#2a4a6a;color:#9cf;padding:10px 16px;border-radius:6px;z-index:3000;font-size:13px'; document.body.appendChild(el); setTimeout(() => el.remove(), 2500); } diff --git a/apps/desktop/src/renderer/features/editor/SlashMenu.tsx b/apps/desktop/src/renderer/features/editor/SlashMenu.tsx new file mode 100644 index 0000000..f9a66c5 --- /dev/null +++ b/apps/desktop/src/renderer/features/editor/SlashMenu.tsx @@ -0,0 +1,57 @@ +import { useState, useEffect } from 'react'; +import { useEditorRef } from './editor-context'; + +const ITEMS: { label: string; cmd: (e: any) => void }[] = [ + { label: '一级标题', cmd: e => e.chain().focus().toggleHeading({ level: 1 }).run() }, + { label: '二级标题', cmd: e => e.chain().focus().toggleHeading({ level: 2 }).run() }, + { label: '三级标题', cmd: e => e.chain().focus().toggleHeading({ level: 3 }).run() }, + { label: '无序列表', cmd: e => e.chain().focus().toggleBulletList().run() }, + { label: '有序列表', cmd: e => e.chain().focus().toggleOrderedList().run() }, + { label: '任务列表', cmd: e => e.chain().focus().toggleTaskList().run() }, + { label: '代码块', cmd: e => e.chain().focus().toggleCodeBlock().run() }, + { label: '引用', cmd: e => e.chain().focus().toggleBlockquote().run() }, + { label: '表格', cmd: e => e.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run() }, + { label: '分割线', cmd: e => e.chain().focus().setHorizontalRule().run() }, +]; + +export function SlashMenu() { + const editor = useEditorRef(); + const [open, setOpen] = useState(false); + const [pos, setPos] = useState({ x: 0, y: 0 }); + const [filter, setFilter] = useState(''); + + useEffect(() => { + if (!editor) return; + const handler = () => { + const sel = editor.state.selection; + const from = sel.from; + const textBefore = editor.state.doc.textBetween(Math.max(0, from - 8), from, ' ', ' '); + const slashIdx = textBefore.lastIndexOf('/'); + if (slashIdx >= 0 && (slashIdx === 0 || /\s/.test(textBefore[slashIdx - 1]))) { + const coords = editor.view.coordsAtPos(from); + setPos({ x: coords.left, y: coords.bottom }); + setFilter(textBefore.slice(slashIdx + 1)); + setOpen(true); + } else { + setOpen(false); + } + }; + editor.on('update', handler); + return () => { editor.off('update', handler); }; + }, [editor]); + + if (!open || !editor) return null; + const items = ITEMS.filter(i => i.label.includes(filter)); + const pick = (it: typeof ITEMS[0]) => { + const from = editor.state.selection.from; + editor.chain().focus().deleteRange({ from: from - (1 + filter.length), to: from }).run(); + it.cmd(editor); + setOpen(false); + }; + return ( +
+ {items.length === 0 &&
无匹配
} + {items.map(it =>
pick(it)} style={{ padding: '6px 10px', cursor: 'pointer', borderRadius: 4, fontSize: 13 }} onMouseEnter={e => (e.currentTarget.style.background = '#2a2a3a')} onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}>{it.label}
)} +
+ ); +} diff --git a/apps/desktop/src/renderer/features/editor/TiptapEditor.tsx b/apps/desktop/src/renderer/features/editor/TiptapEditor.tsx new file mode 100644 index 0000000..5a214ca --- /dev/null +++ b/apps/desktop/src/renderer/features/editor/TiptapEditor.tsx @@ -0,0 +1,73 @@ +import { useEditor, EditorContent } from '@tiptap/react'; +import StarterKit from '@tiptap/starter-kit'; +import TaskList from '@tiptap/extension-task-list'; +import TaskItem from '@tiptap/extension-task-item'; +import { Table } from '@tiptap/extension-table'; +import { TableRow } from '@tiptap/extension-table-row'; +import { TableCell } from '@tiptap/extension-table-cell'; +import { TableHeader } from '@tiptap/extension-table-header'; +import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'; +import Link from '@tiptap/extension-link'; +import Image from '@tiptap/extension-image'; +import Placeholder from '@tiptap/extension-placeholder'; +import { Markdown } from 'tiptap-markdown'; +import { createLowlight, common } from 'lowlight'; +import { useEffect, useRef } from 'react'; +import { useStore } from '../../store'; +import { EditorProvider } from './editor-context'; +import { EditorToolbar } from './EditorToolbar'; +import { FindReplace } from './FindReplace'; +import { SlashMenu } from './SlashMenu'; +import { BlockOps } from './BlockOps'; + +const lowlight = createLowlight(common); + +export function TiptapEditor() { + const { currentPath, body, setBody } = useStore(); + const skipUpdate = useRef(false); + + // editor 实例稳定,不随文档切换重建(避免销毁/重建过渡期 commands 为 null) + const editor = useEditor({ + extensions: [ + StarterKit.configure({ codeBlock: false }), + CodeBlockLowlight.configure({ lowlight }), + TaskList, + TaskItem.configure({ nested: true }), + Table.configure({ resizable: true }), TableRow, TableCell, TableHeader, + Link.configure({ openOnClick: false }), + Image, + Placeholder.configure({ placeholder: '开始写作…' }), + Markdown.configure({ html: false, breaks: false, linkify: true, transformPastedText: true, transformCopiedText: true }), + BlockOps, + ], + content: body, + onUpdate: ({ editor }) => { if (!skipUpdate.current) setBody(editor.storage.markdown.getMarkdown()); }, + }); + + // 切换文档时只同步内容,不重建 editor + useEffect(() => { + if (!editor) return; + skipUpdate.current = true; + editor.commands.setContent(body || '', { emitUpdate: false }); + skipUpdate.current = false; + }, [currentPath, editor]); + + if (!currentPath) { + return ( +
+
从左侧选择一篇笔记,或新建文档开始
+
+ ); + } + + return ( + + + + +
+ +
+
+ ); +} diff --git a/apps/desktop/src/renderer/features/editor/editor-context.ts b/apps/desktop/src/renderer/features/editor/editor-context.ts new file mode 100644 index 0000000..1e566cc --- /dev/null +++ b/apps/desktop/src/renderer/features/editor/editor-context.ts @@ -0,0 +1,6 @@ +import { createContext, useContext } from 'react'; +import type { Editor } from '@tiptap/react'; + +const Ctx = createContext(null); +export const EditorProvider = Ctx.Provider; +export function useEditorRef() { return useContext(Ctx); } diff --git a/apps/desktop/src/renderer/features/effects/EffectLayer.tsx b/apps/desktop/src/renderer/features/effects/EffectLayer.tsx new file mode 100644 index 0000000..03b13aa --- /dev/null +++ b/apps/desktop/src/renderer/features/effects/EffectLayer.tsx @@ -0,0 +1,20 @@ +import { useEffect, useRef } from 'react'; +import { EffectEngine } from '../effects/effect-engine'; +import { useAppearance } from '../appearance/appearance-store'; + +export function EffectLayer() { + const ref = useRef(null); + const engineRef = useRef(null); + const appearance = useAppearance(); + // M6 骨架:默认关闭,外观面板后续接入选择 + useEffect(() => { + if (!ref.current) return; + const eng = new EffectEngine(); + eng.attach(ref.current); + eng.setColor(appearance.draft.accent); + eng.setType('off'); // 默认关闭,待外观面板接入开启 + engineRef.current = eng; + return () => eng.detach(); + }, []); + return ; +} diff --git a/apps/desktop/src/renderer/features/effects/effect-engine.ts b/apps/desktop/src/renderer/features/effects/effect-engine.ts new file mode 100644 index 0000000..606edcf --- /dev/null +++ b/apps/desktop/src/renderer/features/effects/effect-engine.ts @@ -0,0 +1,102 @@ +// 鼠标特效引擎(FR-A11):微粒/流星/极光/涟漪 + 智能 + 空闲暂停 + 降级 +export type EffectType = 'off' | 'auto' | 'particle' | 'meteor' | 'aurora' | 'ripple'; + +interface P { x: number; y: number; vx: number; vy: number; life: number; maxLife: number; size: number; } + +export class EffectEngine { + private canvas: HTMLCanvasElement | null = null; + private ctx: CanvasRenderingContext2D | null = null; + private raf = 0; + private particles: P[] = []; + private lastMove = 0; + private lastX = 0; lastY = 0; + private type: EffectType = 'off'; + private color = '#7ab8ff'; + private idleMs = 1500; + private active = false; + private onMove: (e: MouseEvent) => void; + private onClick: (e: MouseEvent) => void; + + constructor() { + this.onMove = (e) => { this.lastMove = performance.now(); this.lastX = e.clientX; this.lastY = e.clientY; this.spawn(); }; + this.onClick = (e) => { this.lastMove = performance.now(); this.rippleAt(e.clientX, e.clientY); }; + } + + attach(canvas: HTMLCanvasElement) { + this.canvas = canvas; + this.ctx = canvas.getContext('2d'); + window.addEventListener('mousemove', this.onMove); + window.addEventListener('click', this.onClick); + this.resize(); + window.addEventListener('resize', this.resizeBound = this.resize.bind(this)); + this.loop(); + } + + private resizeBound: () => void = () => {} + + detach() { + window.removeEventListener('mousemove', this.onMove); + window.removeEventListener('click', this.onClick); + window.removeEventListener('resize', this.resizeBound); + cancelAnimationFrame(this.raf); + this.active = false; + } + + setType(t: EffectType) { this.type = t; } + setColor(c: string) { this.color = c; } + setIdleMs(ms: number) { this.idleMs = ms; } + // 降级:关闭特效 + disable() { this.type = 'off'; this.particles = []; } + + isActive() { return this.active; } + + resize() { + if (!this.canvas) return; + this.canvas.width = window.innerWidth; + this.canvas.height = window.innerHeight; + } + + private spawn() { + if (this.type === 'off') return; + const t = this.type === 'auto' ? 'particle' : this.type; + if (t === 'particle') this.particles.push(this.mkP(0, -0.3 - Math.random()*0.5, 60)); + else if (t === 'meteor') { const dx = this.lastX - (this.lastX - 5); this.particles.push(this.mkP(dx*0.1, 0.1, 40)); } + else if (t === 'aurora') this.particles.push(this.mkP(0, 0, 80)); + if (this.particles.length > 200) this.particles.splice(0, this.particles.length - 200); + } + + private mkP(vx: number, vy: number, life: number): P { + return { x: this.lastX, y: this.lastY, vx, vy, life, maxLife: life, size: 1 + Math.random()*2 }; + } + + private rippleAt(x: number, y: number) { + if (this.type === 'off') return; + const t = this.type === 'auto' ? 'ripple' : this.type; + if (t !== 'ripple') return; + for (let i = 0; i < 2; i++) this.particles.push({ x, y, vx: 0, vy: 0, life: 40, maxLife: 40, size: 2 + i*8 }); + } + + private loop = () => { + this.raf = requestAnimationFrame(this.loop); + const ctx = this.ctx; if (!ctx) return; + const idle = performance.now() - this.lastMove > this.idleMs; + // 空闲暂停:无新粒子且空闲则停转 + if (idle && this.particles.length === 0) { this.active = false; return; } + this.active = true; + ctx.clearRect(0, 0, this.canvas!.width, this.canvas!.height); + ctx.globalAlpha = 1; + for (let i = this.particles.length - 1; i >= 0; i--) { + const p = this.particles[i]; + p.x += p.vx; p.y += p.vy; p.life--; + if (p.life <= 0) { this.particles.splice(i, 1); continue; } + const alpha = p.life / p.maxLife; + ctx.globalAlpha = alpha * 0.8; + ctx.fillStyle = this.color; + if (this.type === 'ripple' || (this.type === 'auto' && p.size > 6)) { + ctx.beginPath(); ctx.arc(p.x, p.y, p.size * (1 + (1-alpha)*3), 0, Math.PI*2); ctx.strokeStyle = this.color; ctx.lineWidth = 1.5; ctx.stroke(); + } else { + ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI*2); ctx.fill(); + } + } + }; +} diff --git a/apps/desktop/src/renderer/features/file-tree/FileTree.tsx b/apps/desktop/src/renderer/features/file-tree/FileTree.tsx new file mode 100644 index 0000000..4c79b0e --- /dev/null +++ b/apps/desktop/src/renderer/features/file-tree/FileTree.tsx @@ -0,0 +1,115 @@ +import { useStore } from '../../store'; +import type { TreeNode } from '@hunter/shared'; +import { useState, useMemo } from 'react'; +import { invoke } from '../../ipc-client'; +import { IPC } from '@hunter/shared'; +import { useDialog } from '../dialog/dialog-store'; + +export function FileTree() { + const { tree, loadTree, treeLoading, openDoc, currentPath } = useStore(); + const { showInput, showConfirm } = useDialog(); + const [collapsed, setCollapsed] = useState>(new Set()); + const [renaming, setRenaming] = useState(null); + const [renameVal, setRenameVal] = useState(''); + const [hover, setHover] = useState(null); + const [dragOver, setDragOver] = useState(null); + const [menu, setMenu] = useState<{ path: string; x: number; y: number } | null>(null); + + const toggle = (p: string) => { const n = new Set(collapsed); if (n.has(p)) n.delete(p); else n.add(p); setCollapsed(n); }; + const byParent = useMemo(() => { const m = new Map(); for (const n of tree) { const a = m.get(n.parent) ?? []; a.push(n); m.set(n.parent, a); } return m; }, [tree]); + + const doCreate = async (basePath: string, isFolder: boolean) => { + const name = await showInput(isFolder ? '新建文件夹' : '新建文档', isFolder ? '文件夹名称' : '文档标题'); + if (!name) return; + await invoke(IPC.fileCreate, { basePath, name, isFolder }); + await loadTree(); + }; + const doRename = async (node: TreeNode) => { + const name = await showInput('重命名', '新名称'); if (!name) return; + await invoke(IPC.fileRename, { from: node.path, toName: name }); + await loadTree(); + }; + const doDelete = async (node: TreeNode) => { + const ok = await showConfirm('确认删除', `删除「${node.name}」${node.type === 'folder' ? '(文件夹将递归删除)' : ''}?此操作不可撤销。`); + if (!ok) return; + await invoke(IPC.fileDelete, { path: node.path, recursive: node.type === 'folder' }); + if (currentPath === node.path) { /* 删除当前文档:store 侧切换由 openDoc 处理 */ } + await loadTree(); + }; + const doMove = async (src: string, destDir: string) => { + if (src === destDir) return; + try { await invoke(IPC.fileMove, { src, destDir }); await loadTree(); } catch (e) { console.error(e); } + }; + + const onDrop = (e: React.DragEvent, destDir: string) => { + e.preventDefault(); setDragOver(null); + const src = e.dataTransfer.getData('text/path'); if (src) doMove(src, destDir); + }; + + const renderNode = (n: TreeNode, depth: number): React.ReactNode => { + const isCollapsed = collapsed.has(n.path); + const children = n.type === 'folder' ? byParent.get(n.path) ?? [] : []; + const pad = { paddingLeft: 8 + depth * 14 }; + const isRenaming = renaming === n.path; + const isHover = hover === n.path; + const isDragOver = dragOver === n.path; + return ( +
+
setHover(n.path)} onMouseLeave={() => setHover(null)} + onClick={() => isRenaming ? null : (n.type === 'folder' ? toggle(n.path) : openDoc(n.path))} + onContextMenu={e => { e.preventDefault(); setMenu({ path: n.path, x: e.clientX, y: e.clientY }); }} + draggable + onDragStart={e => e.dataTransfer.setData('text/path', n.path)} + onDragOver={e => { if (n.type === 'folder') { e.preventDefault(); setDragOver(n.path); } }} + onDragLeave={() => setDragOver(null)} + onDrop={e => n.type === 'folder' && onDrop(e, n.path)}> + {n.type === 'folder' ? (isCollapsed ? '▶' : '▼') : '📄'} + {isRenaming ? ( + setRenameVal(e.target.value)} + onKeyDown={e => { if (e.key === 'Enter') { setRenaming(null); invoke(IPC.fileRename, { from: n.path, toName: renameVal }).then(() => loadTree()); } if (e.key === 'Escape') setRenaming(null); }} + onBlur={() => setRenaming(null)} style={{ flex: 1, background: '#15151f', border: '1px solid #4a6a8a', color: '#ddd', borderRadius: 3, padding: '1px 4px', fontSize: 13 }} /> + ) : ( + {n.name} + )} + {isHover && !isRenaming && ( + e.stopPropagation()}> + {n.type === 'folder' && } + + + + )} +
+ {!isCollapsed && children.map(c => renderNode(c, depth + 1))} +
+ ); + }; + + const menuNode = menu && tree.find(n => n.path === menu.path); + const roots = byParent.get('') ?? []; + return ( +
setMenu(null)}> + {treeLoading &&
加载中…
} + {roots.map(n => renderNode(n, 0))} + {tree.length === 0 && !treeLoading &&
空库,点击上方「+ 新建文档」开始
} + {menu && menuNode && ( +
e.stopPropagation()}> + {menuNode.type === 'doc' && { openDoc(menuNode.path); setMenu(null); }}>打开} + {menuNode.type === 'folder' && <> + { doCreate(menuNode.path, false); setMenu(null); }}>新建文档 + { doCreate(menuNode.path, true); setMenu(null); }}>新建文件夹 + } + { setRenaming(menuNode.path); setRenameVal(menuNode.name); setMenu(null); }}>重命名 + { doDelete(menuNode); setMenu(null); }}>删除 +
+ )}
+ ); +} + +const miniBtn: React.CSSProperties = { border: 'none', background: 'transparent', color: '#888', cursor: 'pointer', padding: '0 3px', fontSize: 12 }; + +function MenuItem({ children, onClick, danger }: { children: React.ReactNode; onClick: () => void; danger?: boolean }) { + return
(e.currentTarget.style.background = '#2a2a3a')} onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}>{children}
; +} \ No newline at end of file diff --git a/apps/desktop/src/renderer/features/graph/GraphView.tsx b/apps/desktop/src/renderer/features/graph/GraphView.tsx new file mode 100644 index 0000000..99de569 --- /dev/null +++ b/apps/desktop/src/renderer/features/graph/GraphView.tsx @@ -0,0 +1,38 @@ +import { useEffect, useRef, useState } from 'react'; +import { Graph } from '@antv/g6'; +import { invoke } from '../../ipc-client'; +import { IPC } from '@hunter/shared'; +import type { GraphData } from '@hunter/shared'; +import { useStore } from '../../store'; + +export function GraphView() { + const containerRef = useRef(null); + const [graph, setGraph] = useState(null); + const { openDoc } = useStore(); + + useEffect(() => { + if (!containerRef.current) return; + const g = new Graph({ + container: containerRef.current, + width: containerRef.current.clientWidth, + height: containerRef.current.clientHeight, + layout: { type: 'force', preventOverlap: true, nodeStrength: -50, linkDistance: 120 } as any, + node: { style: { size: 20, fill: (d: any) => d.color || '#4a9eff', labelText: (d: any) => d.title, labelFill: '#ddd' } } as any, + edge: { style: { stroke: '#555' } } as any, + behaviors: ['zoom-canvas', 'drag-canvas', 'drag-element'], + }); + setGraph(g); + return () => { g.destroy(); }; + }, []); + + useEffect(() => { + if (!graph) return; + invoke(IPC.graph, undefined as never).then((data: GraphData) => { + graph.setData({ nodes: data.nodes.map(n => ({ id: n.id, ...n })), edges: data.edges.map(e => ({ source: e.source, target: e.target })) } as any); + graph.render(); + }); + graph.on('node:click', (e: any) => { openDoc(e.target.id); }); + }, [graph]); + + return
; +} diff --git a/apps/desktop/src/renderer/features/keymap/CheatSheet.tsx b/apps/desktop/src/renderer/features/keymap/CheatSheet.tsx new file mode 100644 index 0000000..bdfbbd9 --- /dev/null +++ b/apps/desktop/src/renderer/features/keymap/CheatSheet.tsx @@ -0,0 +1,29 @@ +import { IPC } from '@hunter/shared'; + +const GROUPS: [string, [string, string][]][] = [ + ['全局', [['Ctrl+N','新建文档'],['Ctrl+S','保存'],['Ctrl+Shift+F','全库搜索'],['Ctrl+Shift+L','大纲面板'],['?','快捷键帮助'],['Esc','关闭弹层']]], + ['查找替换', [['Ctrl+F','查找'],['Ctrl+H','替换']]], + ['标题段落', [['Ctrl+1~6','一至六级标题'],['Ctrl+0','段落'],['Ctrl+Shift+-','分割线']]], + ['行内格式', [['Ctrl+B','加粗'],['Ctrl+I','斜体'],['Alt+Shift+5','删除线'],['Ctrl+`','行内代码'],['Ctrl+Shift+Q','引用'],['Ctrl+\\','清除格式']]], + ['列表', [['Ctrl+L','无序列表'],['Ctrl+Shift+O','有序列表'],['Ctrl+]','增加缩进'],['Ctrl+[','减少缩进']]], + ['代码与公式', [['Ctrl+Shift+M','代码块'],['Ctrl+Shift+9','数学公式']]], + ['插入', [['Ctrl+K','链接'],['Ctrl+Shift+I','图片'],['Ctrl+Shift+T','表格']]], + ['编辑', [['Ctrl+Z','撤销'],['Ctrl+Y','重做']]], + ['块操作', [['Alt+↑/↓','上移/下移当前块'],['Alt+Backspace','删除当前块']]], +]; + +export function CheatSheet({ onClose }: { onClose: () => void }) { + return ( +
{ if (e.target === e.currentTarget) onClose(); }} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1500 }}> +
+
快捷键速查
+ {GROUPS.map(([title, items]) => ( +
+
{title}
+ {items.map(([k, v]) => (
{v}{k}
))} +
+ ))} +
+
+ ); +} diff --git a/apps/desktop/src/renderer/features/keymap/keymap-service.ts b/apps/desktop/src/renderer/features/keymap/keymap-service.ts new file mode 100644 index 0000000..d5f9b75 --- /dev/null +++ b/apps/desktop/src/renderer/features/keymap/keymap-service.ts @@ -0,0 +1,33 @@ +// KeymapService — 快捷键绑定管理 + 冲突检测(FR-K3/K4/K5) +export type Scope = 'global' | 'app' | 'editor'; +export interface Binding { id: string; scope: Scope; combo: string; defaultCombo: string; } + +export class KeymapService { + private bindings = new Map(); + + register(b: Binding) { this.bindings.set(b.id, b); } + setCombo(id: string, combo: string) { + const b = this.bindings.get(id); if (b) this.bindings.set(id, { ...b, combo }); + } + resetAll() { for (const [id, b] of this.bindings) this.bindings.set(id, { ...b, combo: b.defaultCombo }); } + getAll(): Binding[] { return [...this.bindings.values()]; } + // 冲突检测:同一 combo 绑定到多个 action(不同 id)即冲突 + conflicts(): Record { + const byCombo = new Map(); + for (const b of this.bindings.values()) { + const arr = byCombo.get(b.combo) ?? []; arr.push(b.id); byCombo.set(b.combo, arr); + } + const out: Record = {}; + for (const [combo, ids] of byCombo) if (ids.length > 1) out[combo] = ids; + return out; + } + conflictCount(): number { return Object.keys(this.conflicts()).length; } + // 作用域裁决:给定当前是否编辑器聚焦,返回某 combo 应触发哪个 binding(编辑器聚焦时 global 让位规则按需) + resolve(combo: string, editorFocused: boolean): Binding | null { + const matches = [...this.bindings.values()].filter(b => b.combo === combo); + if (matches.length === 0) return null; + // 优先 editor(编辑器聚焦时),再 app,再 global + if (editorFocused) { const e = matches.find(m => m.scope === 'editor'); if (e) return e; } + return matches.find(m => m.scope === 'global') ?? matches[0]; + } +} diff --git a/apps/desktop/src/renderer/features/keymap/use-hotkeys.ts b/apps/desktop/src/renderer/features/keymap/use-hotkeys.ts new file mode 100644 index 0000000..7958ac6 --- /dev/null +++ b/apps/desktop/src/renderer/features/keymap/use-hotkeys.ts @@ -0,0 +1,13 @@ +import { useEffect } from 'react'; +import hotkeys from 'hotkeys-js'; + +export function useGlobalHotkeys(opts: { onNewDoc: () => void; onSave: () => void; onFocusSearch: () => void; onToggleOutline: () => void; onCheat: () => void; onSettings: () => void }) { + useEffect(() => { + hotkeys('ctrl+n', e => { e.preventDefault(); opts.onNewDoc(); }); + hotkeys('ctrl+s', e => { e.preventDefault(); opts.onSave(); }); + hotkeys('ctrl+shift+f', e => { e.preventDefault(); opts.onFocusSearch(); }); + hotkeys('ctrl+shift+l', e => { e.preventDefault(); opts.onToggleOutline(); }); + hotkeys('?', e => { if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { e.preventDefault(); opts.onCheat(); } }); + return () => { hotkeys.unbind('ctrl+n'); hotkeys.unbind('ctrl+s'); hotkeys.unbind('ctrl+shift+f'); hotkeys.unbind('ctrl+shift+l'); hotkeys.unbind('?'); }; + }, [opts.onNewDoc, opts.onSave, opts.onFocusSearch, opts.onToggleOutline, opts.onCheat, opts.onSettings]); +} diff --git a/apps/desktop/src/renderer/features/outline/OutlinePanel.tsx b/apps/desktop/src/renderer/features/outline/OutlinePanel.tsx new file mode 100644 index 0000000..138ef1c --- /dev/null +++ b/apps/desktop/src/renderer/features/outline/OutlinePanel.tsx @@ -0,0 +1,52 @@ +import { useState, useEffect } from 'react'; +import { useEditorRef } from '../editor/editor-context'; +import type { Editor } from '@tiptap/react'; + +interface HeadingItem { id: string; level: number; text: string; pos: number; } + +export function OutlinePanel() { + const editor = useEditorRef(); + const [headings, setHeadings] = useState([]); + + useEffect(() => { + if (!editor) return; + const collect = () => { + const items: HeadingItem[] = []; + editor.state.doc.descendants((node, pos) => { + if (node.type.name === 'heading') { + items.push({ id: `${pos}-${node.attrs.level}`, level: node.attrs.level, text: node.textContent, pos }); + } + }); + setHeadings(items); + }; + collect(); + editor.on('update', collect); + return () => { editor.off('update', collect); }; + }, [editor]); + + const scrollTo = (h: HeadingItem) => { + if (!editor) return; + editor.commands.setTextSelection(h.pos); + editor.commands.scrollIntoView(); + // 闪烁高亮(简化版:focus 即可) + editor.commands.focus(); + }; + + if (headings.length === 0) { + return
无标题。添加一至六级标题以生成大纲。
; + } + return ( +
+
大纲({headings.length})
+ {headings.map(h => ( +
scrollTo(h)} + style={{ paddingLeft: 8 + (h.level - 1) * 12, cursor: 'pointer', padding: '4px 4px 4px ' + (8 + (h.level-1)*12) + 'px', + fontSize: 13, borderRadius: 4 }} + onMouseEnter={e => (e.currentTarget.style.background = '#2a2a2a')} + onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}> + {h.text || '(空标题)'} +
+ ))} +
+ ); +} diff --git a/apps/desktop/src/renderer/features/search/SearchBox.tsx b/apps/desktop/src/renderer/features/search/SearchBox.tsx new file mode 100644 index 0000000..ee0d982 --- /dev/null +++ b/apps/desktop/src/renderer/features/search/SearchBox.tsx @@ -0,0 +1,47 @@ +import { useState, useEffect } from 'react'; +import { invoke } from '../../ipc-client'; +import { IPC } from '@hunter/shared'; +import type { SearchHit } from '@hunter/shared'; +import { useStore } from '../../store'; + +export function SearchBox() { + const { openDoc, loadTree } = useStore(); + const [q, setQ] = useState(''); + const [hits, setHits] = useState([]); + const [searching, setSearching] = useState(false); + + useEffect(() => { + if (!q.trim()) { setHits([]); return; } + setSearching(true); + const t = setTimeout(async () => { + const r = await invoke(IPC.search, { query: q }); + setHits(r); setSearching(false); + }, 300); + return () => clearTimeout(t); + }, [q]); + + return ( +
+
+ setQ(e.target.value)} placeholder="搜索(Ctrl+Shift+F)" style={{ flex: 1, background: '#222', border: '1px solid #444', color: '#ddd', borderRadius: 4, padding: '4px 8px', fontSize: 13 }} /> + {q && } +
+ {searching &&
搜索中…
} + {q && !searching && hits.length === 0 &&
没有匹配
} + {hits.length > 0 && ( +
+ {hits.map(h => ( +
{ openDoc(h.path); setQ(''); }} + style={{ padding: '4px 6px', cursor: 'pointer', borderRadius: 4, fontSize: 13 }} + onMouseEnter={e => (e.currentTarget.style.background = '#2a2a3a')} + onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}> +
{h.title}
+
{h.snippet}
+
+ ))} +
+ )} + {q &&
} +
+ ); +} diff --git a/apps/desktop/src/renderer/features/search/TagFilter.tsx b/apps/desktop/src/renderer/features/search/TagFilter.tsx new file mode 100644 index 0000000..21db4ee --- /dev/null +++ b/apps/desktop/src/renderer/features/search/TagFilter.tsx @@ -0,0 +1,21 @@ +import { useState, useEffect } from 'react'; +import { invoke } from '../../ipc-client'; +import { IPC, type TagStat } from '@hunter/shared'; +import { useStore } from '../../store'; + +export function TagFilter() { + const { tree } = useStore(); + const [tags, setTags] = useState([]); + const [selected, setSelected] = useState([]); + useEffect(() => { invoke(IPC.tagStats, undefined as never).then(setTags).catch(() => {}); }, [tree.length]); + if (tags.length === 0) return null; + const toggle = (t: string) => setSelected(s => s.includes(t) ? s.filter(x => x !== t) : [...s, t]); + return ( +
+
标签({tags.length})
+
{tags.map(t => ( + toggle(t.tag)} style={{ cursor: 'pointer', padding: '1px 7px', borderRadius: 10, fontSize: 11, background: selected.includes(t.tag) ? '#4a9eff' : '#2a2a3a', color: selected.includes(t.tag) ? '#fff' : '#aaa' }}>{t.tag} {t.count} + ))}
+
+ ); +} diff --git a/apps/desktop/src/renderer/features/settings/SettingsPanel.tsx b/apps/desktop/src/renderer/features/settings/SettingsPanel.tsx new file mode 100644 index 0000000..b33ca08 --- /dev/null +++ b/apps/desktop/src/renderer/features/settings/SettingsPanel.tsx @@ -0,0 +1,40 @@ +import { useSettings } from './settings-store'; + +const pageBtn: React.CSSProperties = { padding: '8px 14px', border: 'none', background: 'transparent', color: '#888', cursor: 'pointer', borderBottom: '2px solid transparent', fontSize: 13 }; +const row: React.CSSProperties = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 0', borderBottom: '1px solid #2a2a3a' }; +const lbl: React.CSSProperties = { fontSize: 13, color: '#ccc' }; +const toggle = (on: boolean): React.CSSProperties => ({ width: 36, height: 20, borderRadius: 10, background: on ? '#4a9eff' : '#444', position: 'relative', cursor: 'pointer', border: 'none', transition: 'background 0.2s' }); + +export function SettingsPanel({ onClose }: { onClose: () => void }) { + const s = useSettings(); + const [page, setPage] = useState<'keymap' | 'editor' | 'path' | 'other'>('editor'); + return ( +
{ if (e.target === e.currentTarget) onClose(); }} style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1500 }}> +
+
+ 设置 + +
+
+ {(['editor','keymap','path','other'] as const).map(p => ( + + ))} +
+ {page === 'editor' && <> +
字号 s.set('fontSize', +e.target.value)} /> {s.fontSize}px
+
字体
+
自动保存间隔
+
代码块显示行号
+
自动换行
+ } + {page === 'other' &&
删除前确认
} + {page === 'path' &&
文件路径设置将在 M9 完善(默认库路径配置)
} + {page === 'keymap' &&
快捷键自定义与冲突检测已就绪(见速查面板 ?)。键位录入 UI 在此页完善。
} +
设置已自动保存
+
+
+ ); +} + +const sel: React.CSSProperties = { background: '#15151f', border: '1px solid #3a3a4a', color: '#ddd', borderRadius: 4, padding: '4px 8px', fontSize: 13 }; +function dot(on: boolean): React.CSSProperties { return { position: 'absolute', top: 2, left: on ? 18 : 2, width: 16, height: 16, borderRadius: '50%', background: '#fff', transition: 'left 0.2s' }; } diff --git a/apps/desktop/src/renderer/features/settings/settings-store.ts b/apps/desktop/src/renderer/features/settings/settings-store.ts new file mode 100644 index 0000000..5507dfa --- /dev/null +++ b/apps/desktop/src/renderer/features/settings/settings-store.ts @@ -0,0 +1,38 @@ +import { create } from 'zustand'; +import { applyTheme } from '../appearance/themes'; + +export interface Settings { + fontSize: number; // 12-20 + fontFamily: string; + autoSaveInterval: number; // 秒 1-30,真实生效 + showLineNumbers: boolean; // 代码块行号,真实生效 + wordWrap: boolean; // 自动换行,真实生效 + confirmDelete: boolean; // 删除前确认,真实生效 +} + +const DEFAULTS: Settings = { + fontSize: 14, + fontFamily: 'Consolas, monospace', + autoSaveInterval: 1, + showLineNumbers: false, + wordWrap: true, + confirmDelete: true, +}; + +const KEY = 'dsh-settings'; + +function load(): Settings { + try { const raw = localStorage.getItem(KEY); if (raw) return { ...DEFAULTS, ...JSON.parse(raw) }; } catch {} + return DEFAULTS; +} + +interface SettingsState extends Settings { + set: (key: K, value: Settings[K]) => void; + persist: () => void; +} + +export const useSettings = create((set, get) => ({ + ...load(), + set: (key, value) => { set({ [key]: value } as any); get().persist(); }, + persist: () => { try { localStorage.setItem(KEY, JSON.stringify({ fontSize: get().fontSize, fontFamily: get().fontFamily, autoSaveInterval: get().autoSaveInterval, showLineNumbers: get().showLineNumbers, wordWrap: get().wordWrap, confirmDelete: get().confirmDelete })); } catch {} }, +})); diff --git a/apps/desktop/src/renderer/features/shell/Resizer.tsx b/apps/desktop/src/renderer/features/shell/Resizer.tsx new file mode 100644 index 0000000..5b9e963 --- /dev/null +++ b/apps/desktop/src/renderer/features/shell/Resizer.tsx @@ -0,0 +1,16 @@ +import { useRef } from 'react'; + +export function Resizer({ onDrag, side }: { onDrag: (delta: number) => void; side: 'left' | 'right' }) { + const start = useRef(null); + const onDown = (e: React.MouseEvent) => { + start.current = e.clientX; + e.preventDefault(); + const move = (ev: globalThis.MouseEvent) => { + if (start.current != null) onDrag((ev.clientX - start.current) * (side === 'left' ? 1 : -1)); + }; + const up = () => { start.current = null; window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); }; + window.addEventListener('mousemove', move); + window.addEventListener('mouseup', up); + }; + return
(e.currentTarget.style.background = '#4a9eff')} onMouseLeave={e => (e.currentTarget.style.background = 'transparent')} />; +} diff --git a/apps/desktop/src/renderer/features/shell/TitleBar.tsx b/apps/desktop/src/renderer/features/shell/TitleBar.tsx new file mode 100644 index 0000000..190a15b --- /dev/null +++ b/apps/desktop/src/renderer/features/shell/TitleBar.tsx @@ -0,0 +1,30 @@ +import { send } from '../../ipc-client'; +import { IPC } from '@hunter/shared'; +import { useStore } from '../../store'; + +const drag: React.CSSProperties = { WebkitAppRegion: 'drag' as any, flex: 1, height: '100%' }; +const noDrag: React.CSSProperties = { WebkitAppRegion: 'no-drag' as any }; + +export function TitleBar({ onToggleView, view, onSettings, onAppearance, onToggleSidebar, onToggleMinimal, minimal }: { onToggleView: () => void; view: 'editor' | 'graph'; onSettings: () => void; onAppearance: () => void; onToggleSidebar: () => void; onToggleMinimal: () => void; minimal: boolean }) { + const { loadTree } = useStore(); + return ( +
+
狩夜人笔记
+
+ + +
+ + + + + + + + +
+
+ ); +} + +const btn: React.CSSProperties = { width: 36, height: 28, border: 'none', background: 'transparent', color: '#aaa', cursor: 'pointer', fontSize: 12 }; \ No newline at end of file diff --git a/apps/desktop/src/renderer/features/shell/layout-store.ts b/apps/desktop/src/renderer/features/shell/layout-store.ts new file mode 100644 index 0000000..351f354 --- /dev/null +++ b/apps/desktop/src/renderer/features/shell/layout-store.ts @@ -0,0 +1,26 @@ +import { create } from 'zustand'; + +interface LayoutState { + sidebarVisible: boolean; + sidebarWidth: number; + outlineVisible: boolean; + outlineWidth: number; + tabbarVisible: boolean; + minimal: boolean; + toggleSidebar: () => void; + toggleOutline: () => void; + toggleTabbar: () => void; + toggleMinimal: () => void; + setSidebarWidth: (w: number) => void; + setOutlineWidth: (w: number) => void; +} + +export const useLayout = create((set) => ({ + sidebarVisible: true, sidebarWidth: 260, outlineVisible: true, outlineWidth: 300, tabbarVisible: true, minimal: false, + toggleSidebar: () => set(s => ({ sidebarVisible: !s.sidebarVisible })), + toggleOutline: () => set(s => ({ outlineVisible: !s.outlineVisible })), + toggleTabbar: () => set(s => ({ tabbarVisible: !s.tabbarVisible })), + toggleMinimal: () => set(s => ({ minimal: !s.minimal })), + setSidebarWidth: (w) => set({ sidebarWidth: Math.max(220, Math.min(420, w)) }), + setOutlineWidth: (w) => set({ outlineWidth: Math.max(260, Math.min(440, w)) }), +})); diff --git a/apps/desktop/src/renderer/features/tabs/TabBar.tsx b/apps/desktop/src/renderer/features/tabs/TabBar.tsx new file mode 100644 index 0000000..5e4f939 --- /dev/null +++ b/apps/desktop/src/renderer/features/tabs/TabBar.tsx @@ -0,0 +1,35 @@ +import { useTabs } from './tab-store'; +import { useStore } from '../../store'; +import { useEffect } from 'react'; + +export function TabBar() { + const { tabs, activeId, setActive, closeTab, togglePin } = useTabs(); + const { openDoc, currentPath } = useStore(); + + // 当 activeId 变化,加载对应标签的文档 + useEffect(() => { + const tab = tabs.find(t => t.id === activeId); + if (tab && tab.path !== currentPath) openDoc(tab.path); + }, [activeId]); + + if (tabs.length === 0) return null; + return ( +
+ {tabs.map(t => ( +
setActive(t.id)} + style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '4px 10px', cursor: 'pointer', + borderBottom: activeId === t.id ? '2px solid #4a9eff' : '2px solid transparent', + background: activeId === t.id ? '#1a1a28' : 'transparent', fontSize: 12, color: activeId === t.id ? '#ddd' : '#888', whiteSpace: 'nowrap' }}> + {t.pinned && ★} + {t.path.split('/').pop()?.replace(/\.md$/, '')} + {t.pinned ? ( + 📌 + ) : ( + { e.stopPropagation(); closeTab(t.id); }} style={{ color: '#888', cursor: 'pointer', fontSize: 12 }} onMouseEnter={e => (e.currentTarget.style.color = '#f88')} onMouseLeave={e => (e.currentTarget.style.color = '#888')}>× + )} + { e.stopPropagation(); togglePin(t.id); }} style={{ color: t.pinned ? '#ffd54a' : '#666', cursor: 'pointer', fontSize: 10 }} title={t.pinned ? '取消固定' : '固定'}>● +
+ ))} +
+ ); +} diff --git a/apps/desktop/src/renderer/features/tabs/tab-store.ts b/apps/desktop/src/renderer/features/tabs/tab-store.ts new file mode 100644 index 0000000..b1ad173 --- /dev/null +++ b/apps/desktop/src/renderer/features/tabs/tab-store.ts @@ -0,0 +1,72 @@ +import { create } from 'zustand'; + +export interface Tab { id: string; path: string; pinned: boolean; scrollTop: number; } + +interface TabState { + tabs: Tab[]; + activeId: string | null; + openTab: (path: string) => void; + closeTab: (id: string) => void; + setActive: (id: string) => void; + togglePin: (id: string) => void; + activePath: () => string | null; + persist: () => void; + restore: () => void; +} + +const SESSION_KEY = 'dsh-session'; + +export const useTabs = create((set, get) => ({ + tabs: [], + activeId: null, + + openTab: (path) => { + const existing = get().tabs.find(t => t.path === path); + if (existing) { set({ activeId: existing.id }); return; } + const id = 'tab-' + Date.now() + '-' + Math.random().toString(36).slice(2, 6); + set(s => ({ tabs: [...s.tabs, { id, path, pinned: false, scrollTop: 0 }], activeId: id })); + get().persist(); + }, + + closeTab: (id) => { + const { tabs, activeId } = get(); + const idx = tabs.findIndex(t => t.id === id); + const remaining = tabs.filter(t => t.id !== id); + let newActive = activeId; + if (activeId === id) { + // 切到相邻(优先未固定的相邻) + const neighbor = remaining[idx] ?? remaining[idx - 1] ?? remaining[remaining.length - 1]; + newActive = neighbor?.id ?? null; + } + set({ tabs: remaining, activeId: newActive }); + get().persist(); + }, + + setActive: (id) => { set({ activeId: id }); get().persist(); }, + + togglePin: (id) => { + set(s => { + const tabs = s.tabs.map(t => t.id === id ? { ...t, pinned: !t.pinned } : t); + // 固定的排前 + tabs.sort((a, b) => (a.pinned === b.pinned ? 0 : a.pinned ? -1 : 1)); + return { tabs }; + }); + get().persist(); + }, + + activePath: () => { const { tabs, activeId } = get(); return tabs.find(t => t.id === activeId)?.path ?? null; }, + + persist: () => { + const { tabs, activeId } = get(); + try { localStorage.setItem(SESSION_KEY, JSON.stringify({ tabs, activeId })); } catch {} + }, + + restore: () => { + try { + const raw = localStorage.getItem(SESSION_KEY); + if (!raw) return; + const saved = JSON.parse(raw); + set({ tabs: saved.tabs ?? [], activeId: saved.activeId ?? null }); + } catch {} + }, +})); diff --git a/apps/desktop/src/renderer/features/toolbar/Toolbar.tsx b/apps/desktop/src/renderer/features/toolbar/Toolbar.tsx new file mode 100644 index 0000000..eedc0dd --- /dev/null +++ b/apps/desktop/src/renderer/features/toolbar/Toolbar.tsx @@ -0,0 +1,51 @@ +import { useStore } from '../../store'; +import { invoke } from '../../ipc-client'; +import { IPC } from '@hunter/shared'; +import { useDialog } from '../dialog/dialog-store'; + +// 极简浮层通知(FR-B1,后续抽组件) +function notify(msg: string) { + const el = document.createElement('div'); + el.textContent = msg; + el.style.cssText = 'position:fixed;top:50px;right:20px;background:#2a4a6a;color:#9cf;padding:10px 16px;border-radius:6px;z-index:3000;font-size:13px;box-shadow:0 4px 12px rgba(0,0,0,0.3)'; + document.body.appendChild(el); + setTimeout(() => el.remove(), 2500); +} +const alert2 = notify; + +export function Toolbar() { + const { loadTree, loadVault, openDoc } = useStore(); + const { showInput } = useDialog(); + const newDoc = async () => { + const name = await showInput('新建文档', '输入文档标题…'); + if (!name) return; + const { path } = await invoke(IPC.fileCreate, { basePath: '', name, isFolder: false }); + await loadTree(); + await openDoc(path); // 创建后直接打开 + }; + const newFolder = async () => { + const name = await showInput('新建文件夹', '输入文件夹名称…'); + if (!name) return; + await invoke(IPC.fileCreate, { basePath: '', name, isFolder: true }); + await loadTree(); + }; + const switchVault = async () => { + await invoke(IPC.vaultPick, undefined as never); + await loadVault(); + await loadTree(); + }; + const importFiles = async () => { + const r = await invoke(IPC.fileImport, { sources: [], destDir: '' }); + await loadTree(); + alert2(r.ok.length ? `导入成功 ${r.ok.length} 项` : '未选择文件'); + }; + return ( +
+ + + + +
+ ); +} +const btn: React.CSSProperties = { padding: '4px 10px', border: '1px solid #3a3a4a', background: 'transparent', color: '#ccc', borderRadius: 4, cursor: 'pointer', fontSize: 12 }; \ No newline at end of file diff --git a/apps/desktop/src/renderer/index.html b/apps/desktop/src/renderer/index.html new file mode 100644 index 0000000..98e0b05 --- /dev/null +++ b/apps/desktop/src/renderer/index.html @@ -0,0 +1,12 @@ + + + + + + 狩夜人笔记 + + +
+ + + diff --git a/apps/desktop/src/renderer/ipc-client.ts b/apps/desktop/src/renderer/ipc-client.ts new file mode 100644 index 0000000..e04fea3 --- /dev/null +++ b/apps/desktop/src/renderer/ipc-client.ts @@ -0,0 +1,12 @@ +// 渲染层 IPC 调用封装:类型安全,禁止接触绝对路径 +import type { BridgeApi, IpcMap } from '@hunter/shared'; + +declare global { interface Window { dsh?: BridgeApi } } + +function getBridge(): BridgeApi { + if (!window.dsh) throw new Error('IPC bridge 未就绪:window.dsh 不存在(preload 可能未加载)'); + return window.dsh; +} + +export const invoke: BridgeApi['invoke'] = (channel, args) => getBridge().invoke(channel, args as never) as Promise; +export const send: BridgeApi['send'] = (channel, args) => getBridge().send(channel, args as never); diff --git a/apps/desktop/src/renderer/main.tsx b/apps/desktop/src/renderer/main.tsx new file mode 100644 index 0000000..b4de6da --- /dev/null +++ b/apps/desktop/src/renderer/main.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './App'; +import { ErrorBoundary } from './ErrorBoundary'; +import './styles.css'; + +createRoot(document.getElementById('root')!).render( + + + + + +); diff --git a/apps/desktop/src/renderer/store.ts b/apps/desktop/src/renderer/store.ts new file mode 100644 index 0000000..85b909f --- /dev/null +++ b/apps/desktop/src/renderer/store.ts @@ -0,0 +1,114 @@ +import { create } from 'zustand'; +import { IPC } from '@hunter/shared'; +import type { TreeNode, VaultMeta, DocMeta, ReadDocResult } from '@hunter/shared'; +import { invoke } from './ipc-client'; +import { useTabs } from './features/tabs/tab-store'; + +export type SaveState = 'saved' | 'saving' | 'error'; + +interface EditorState { + vault: VaultMeta | null; + tree: TreeNode[]; + treeLoading: boolean; + currentPath: string | null; + title: string; + body: string; + meta: DocMeta; + saveState: SaveState; + // actions + loadVault: () => Promise; + loadTree: () => Promise; + openDoc: (path: string) => Promise; + setBody: (body: string) => void; + setMeta: (meta: DocMeta) => void; + addTag: (tag: string) => void; + removeTag: (tag: string) => void; + toggleStar: () => void; + addLink: (link: string) => void; + removeLink: (link: string) => void; + saveNow: () => Promise; +} + +// 简单的每文档保存队列(M1 单文档编辑) +let saveTimer: ReturnType | null = null; + +export const useStore = create((set, get) => ({ + vault: null, + tree: [], + treeLoading: false, + currentPath: null, + title: '', + body: '', + meta: {}, + saveState: 'saved', + + loadVault: async () => { + const vault = await invoke(IPC.vaultList, undefined as never); + set({ vault }); + }, + + loadTree: async () => { + set({ treeLoading: true }); + try { + const tree = await invoke(IPC.treeList, undefined as never); + set({ tree, treeLoading: false }); + } catch (e) { + set({ treeLoading: false }); + console.error('loadTree failed', e); + } + }, + + openDoc: async (path: string) => { + // 切换前冲刷当前文档 + await get().saveNow(); + const doc: ReadDocResult = await invoke(IPC.docRead, { path }); + set({ currentPath: path, body: doc.body, meta: doc.meta, title: doc.meta.title ?? path.split('/').pop()!.replace(/\.md$/, ''), saveState: 'saved' }); + // 同步到标签页(如已存在则激活,否则新建) + useTabs.getState().openTab(path); + }, + + setMeta: (meta: DocMeta) => { set({ meta }); get().saveNow(); }, + addTag: (tag: string) => { + const tags = [...(get().meta.tags ?? []), tag].filter((t, i, a) => a.indexOf(t) === i); + set({ meta: { ...get().meta, tags } }); + get().saveNow(); + }, + removeTag: (tag: string) => { + const tags = (get().meta.tags ?? []).filter(t => t !== tag); + set({ meta: { ...get().meta, tags } }); + get().saveNow(); + }, + toggleStar: () => { + set({ meta: { ...get().meta, star: !get().meta.star } }); + get().saveNow(); + }, + addLink: (link: string) => { + const links = [...(get().meta.links ?? []), link].filter((l, i, a) => a.indexOf(l) === i); + set({ meta: { ...get().meta, links } }); + get().saveNow(); + }, + removeLink: (link: string) => { + const links = (get().meta.links ?? []).filter(l => l !== link); + set({ meta: { ...get().meta, links } }); + get().saveNow(); + }, + setBody: (body: string) => { + set({ body, saveState: 'saving' }); + // 防抖自动保存(M1:固定 600ms,M9 接设置间隔) + if (saveTimer) clearTimeout(saveTimer); + saveTimer = setTimeout(() => { get().saveNow(); }, 600); + }, + + saveNow: async () => { + if (saveTimer) { clearTimeout(saveTimer); saveTimer = null; } + const { currentPath, body, meta } = get(); + if (!currentPath) return; + try { + await invoke(IPC.docWrite, { path: currentPath, meta, body }); + set({ saveState: 'saved' }); + } catch (e) { + set({ saveState: 'error' }); + console.error('save failed', e); + } + }, +})); \ No newline at end of file diff --git a/apps/desktop/src/renderer/styles.css b/apps/desktop/src/renderer/styles.css new file mode 100644 index 0000000..66ec921 --- /dev/null +++ b/apps/desktop/src/renderer/styles.css @@ -0,0 +1,39 @@ +* { box-sizing: border-box; } +html, body, #root { margin: 0; height: 100%; } +/* Tiptap 编辑器正文 */ +.tiptap-wrap .ProseMirror { + font-size: var(--editor-font-size, 14px) !important; + font-family: var(--editor-font-family, Consolas, monospace) !important; + white-space: var(--editor-white-space, pre-wrap); + min-height: 100%; + padding: 16px 24px; + font-family: 'Consolas', 'Source Code Pro', system-ui, sans-serif; + font-size: 14px; + line-height: 1.7; + color: #ddd; + outline: none; + max-width: 860px; + margin: 0 auto; +} +.tiptap-wrap .ProseMirror p { margin: 0.6em 0; } +.tiptap-wrap .ProseMirror h1 { font-size: 1.8em; margin: 0.8em 0 0.4em; } +.tiptap-wrap .ProseMirror h2 { font-size: 1.5em; margin: 0.7em 0 0.35em; } +.tiptap-wrap .ProseMirror h3 { font-size: 1.25em; margin: 0.6em 0 0.3em; } +.tiptap-wrap .ProseMirror code { + background: #2a2a2a; padding: 1px 5px; border-radius: 3px; font-family: 'Consolas', monospace; +} +.tiptap-wrap .ProseMirror pre { + background: #161616; padding: 12px 16px; border-radius: 6px; overflow: auto; +} +.tiptap-wrap .ProseMirror pre code { background: none; padding: 0; } +.tiptap-wrap .ProseMirror blockquote { + border-left: 3px solid #444; padding-left: 12px; color: #aaa; margin: 0.6em 0; +} +.tiptap-wrap .ProseMirror ul, .tiptap-wrap .ProseMirror ol { padding-left: 24px; } +.tiptap-wrap .ProseMirror table { border-collapse: collapse; } +.tiptap-wrap .ProseMirror th, .tiptap-wrap .ProseMirror td { + border: 1px solid #444; padding: 4px 8px; +} +.tiptap-wrap .ProseMirror p.is-editor-empty:first-child::before { + content: attr(data-placeholder); color: #666; pointer-events: none; float: left; +} \ No newline at end of file diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json new file mode 100644 index 0000000..b1d115b --- /dev/null +++ b/apps/desktop/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.web.json" + } + ] +} diff --git a/apps/desktop/tsconfig.node.json b/apps/desktop/tsconfig.node.json new file mode 100644 index 0000000..a3bff77 --- /dev/null +++ b/apps/desktop/tsconfig.node.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "composite": true, + "noEmit": false, + "declaration": true, + "outDir": "dist-electron", + "types": [ + "node" + ], + "baseUrl": ".", + "paths": { + "@hunter/shared": [ + "../../packages/shared/src" + ] + } + }, + "include": [ + "src/main/**/*", + "src/preload/**/*", + "../../packages/shared/src/**/*" + ] +} diff --git a/apps/desktop/tsconfig.web.json b/apps/desktop/tsconfig.web.json new file mode 100644 index 0000000..30b844e --- /dev/null +++ b/apps/desktop/tsconfig.web.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "composite": true, + "noEmit": true, + "types": [], + "baseUrl": ".", + "paths": { + "@hunter/shared": [ + "../../packages/shared/src" + ] + } + }, + "include": [ + "src/renderer/**/*", + "../../packages/shared/src/**/*" + ] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1b6f9fc --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "hunter-notes", + "version": "0.0.0", + "private": true, + "description": "狩夜人笔记 — 本地优先的 Markdown 笔记桌面应用", + "type": "module", + "packageManager": "pnpm@11.18.0", + "engines": { + "node": ">=20" + }, + "scripts": { + "dev": "pnpm --filter @hunter/desktop dev", + "build": "pnpm -r build", + "test": "vitest run", + "test:watch": "vitest", + "lint": "eslint .", + "typecheck": "pnpm -r typecheck" + }, + "devDependencies": { + "jsdom": "^30.0.1", + "remark-gfm": "^4.0.1", + "remark-math": "^6.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "typescript": "^5.6.0", + "unified": "^11.0.5", + "vitest": "^2.1.0" + } +} diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 0000000..3df31f5 --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,11 @@ +{ + "name": "@hunter/shared", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "typecheck": "tsc --noEmit" + } +} diff --git a/packages/shared/src/constants.ts b/packages/shared/src/constants.ts new file mode 100644 index 0000000..d2f0a73 --- /dev/null +++ b/packages/shared/src/constants.ts @@ -0,0 +1,7 @@ +// 文件约定常量 +export const MD_EXT = '.md'; +export const ATTACHMENTS_DIR = '.attachments'; +export const TMP_SUFFIX = '.dsh-tmp'; +export const RECENT_VAULT_MAX = 5; +export const SEARCH_RESULT_MAX = 100; +export const DEFAULT_VAULT_NAME = 'notes'; diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100644 index 0000000..d55df3a --- /dev/null +++ b/packages/shared/src/index.ts @@ -0,0 +1,7 @@ +// @hunter/shared — 主进程与渲染进程共享的类型与常量 +export * from './types/ipc.js'; +export * from './types/vault.js'; +export * from './types/doc.js'; +export * from './types/search.js'; +export * from './types/graph.js'; +export * from './constants.js'; diff --git a/packages/shared/src/types/doc.ts b/packages/shared/src/types/doc.ts new file mode 100644 index 0000000..072fbbc --- /dev/null +++ b/packages/shared/src/types/doc.ts @@ -0,0 +1,18 @@ +// 文档元数据白名单(仅 4 个业务字段) +export interface DocMeta { + title?: string; + tags?: string[]; + star?: boolean; + links?: string[]; +} + +export interface ReadDocResult { + meta: DocMeta; + body: string; +} + +export interface WriteDocPayload { + path: string; + meta: DocMeta; + body: string; +} diff --git a/packages/shared/src/types/graph.ts b/packages/shared/src/types/graph.ts new file mode 100644 index 0000000..892f64f --- /dev/null +++ b/packages/shared/src/types/graph.ts @@ -0,0 +1,3 @@ +export interface GraphNode { id: string; title: string; basePath: string; star: boolean; group: string; color: string; } +export interface GraphEdge { source: string; target: string; } +export interface GraphData { nodes: GraphNode[]; edges: GraphEdge[]; } diff --git a/packages/shared/src/types/ipc.ts b/packages/shared/src/types/ipc.ts new file mode 100644 index 0000000..d7fb8ca --- /dev/null +++ b/packages/shared/src/types/ipc.ts @@ -0,0 +1,69 @@ +import type { TreeNode, VaultMeta } from './vault.js'; +import type { SearchHit, TagStat } from './search.js'; +import type { GraphData } from './graph.js'; +import type { DocMeta, ReadDocResult, WriteDocPayload } from './doc.js'; + +// ===== IPC 通道命名 ===== +export const IPC = { + vaultList: 'vault:list', + vaultSwitch: 'vault:switch', + vaultSetDefault: 'vault:set-default', + vaultPick: 'vault:pick', + treeList: 'tree:list', + treeRefresh: 'tree:refresh', + fileCreate: 'file:create', + fileRename: 'file:rename', + fileMove: 'file:move', + fileDelete: 'file:delete', + fileImport: 'file:import', + docRead: 'doc:read', + docWrite: 'doc:write', + docReveal: 'doc:reveal', + search: 'search', + tagStats: 'tag:stats', + graph: 'graph', + winMinimize: 'win:minimize', + winMaximize: 'win:maximize', + winClose: 'win:close', + copyMd: 'doc:copy-md', + printPdf: 'doc:print-pdf', +} as const; +export type IpcChannel = (typeof IPC)[keyof typeof IPC]; + +// ===== 请求/响应类型映射 ===== +export interface IpcMap { + [IPC.vaultList]: { args: void; result: VaultMeta }; + [IPC.vaultSwitch]: { args: { root: string }; result: VaultMeta }; + [IPC.vaultSetDefault]: { args: { root: string }; result: void }; + [IPC.vaultPick]: { args: void; result: VaultMeta | null }; + [IPC.treeList]: { args: void; result: TreeNode[] }; + [IPC.treeRefresh]: { args: void; result: TreeNode[] }; + [IPC.fileCreate]: { args: { basePath: string; name: string; isFolder: boolean }; result: { path: string } }; + [IPC.fileRename]: { args: { from: string; toName: string }; result: { path: string } }; + [IPC.fileMove]: { args: { src: string; destDir: string }; result: { path: string } }; + [IPC.fileDelete]: { args: { path: string; recursive: boolean }; result: void }; + [IPC.fileImport]: { args: { sources: string[]; destDir: string }; result: { ok: string[]; failed: { path: string; reason: string }[] } }; + [IPC.docRead]: { args: { path: string }; result: ReadDocResult }; + [IPC.docWrite]: { args: WriteDocPayload; result: void }; + [IPC.docReveal]: { args: { path: string }; result: void }; + [IPC.search]: { args: { query: string }; result: SearchHit[] }; + [IPC.tagStats]: { args: void; result: TagStat[] }; + [IPC.graph]: { args: void; result: GraphData }; + [IPC.winMinimize]: { args: void; result: void }; + [IPC.winMaximize]: { args: void; result: void }; + [IPC.winClose]: { args: void; result: void }; + [IPC.copyMd]: { args: { path: string }; result: string }; + [IPC.printPdf]: { args: void; result: void }; +} + +// 类型安全的 invoke 帮助器签名(preload 暴露 + 渲染层使用) +export type Invoke = ( + channel: K, + args: IpcMap[K]['args'], +) => Promise; + +// 渲染层通过 preload 拿到的受限 API +export interface BridgeApi { + invoke: Invoke; + send: (channel: K, args: IpcMap[K]['args']) => void; +} diff --git a/packages/shared/src/types/search.ts b/packages/shared/src/types/search.ts new file mode 100644 index 0000000..ef36d81 --- /dev/null +++ b/packages/shared/src/types/search.ts @@ -0,0 +1,2 @@ +export interface SearchHit { path: string; title: string; snippet: string; } +export interface TagStat { tag: string; count: number; } diff --git a/packages/shared/src/types/vault.ts b/packages/shared/src/types/vault.ts new file mode 100644 index 0000000..c9ae616 --- /dev/null +++ b/packages/shared/src/types/vault.ts @@ -0,0 +1,19 @@ +export interface VaultMeta { + currentRoot: string; + recents: string[]; + defaultRoot: string; +} + +export type TreeNodeType = 'folder' | 'doc'; + +export interface TreeNode { + /** 相对库根路径,作为稳定 id */ + path: string; + type: TreeNodeType; + /** 显示名(doc 去除 .md) */ + name: string; + parent: string; + /** doc 的所在目录相对路径;folder 为自身 */ + basePath: string; + mtime: number; +} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json new file mode 100644 index 0000000..72d9330 --- /dev/null +++ b/packages/shared/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "noEmit": true, + "isolatedModules": true, + "resolveJsonModule": true + }, + "include": [ + "src" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..3e14ce6 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,4430 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + jsdom: + specifier: ^30.0.1 + version: 30.0.1 + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + remark-math: + specifier: ^6.0.0 + version: 6.0.0 + remark-parse: + specifier: ^11.0.0 + version: 11.0.0 + remark-stringify: + specifier: ^11.0.0 + version: 11.0.0 + typescript: + specifier: ^5.6.0 + version: 5.9.3 + unified: + specifier: ^11.0.5 + version: 11.0.5 + vitest: + specifier: ^2.1.0 + version: 2.1.9(@types/node@20.19.43)(jsdom@30.0.1) + + apps/desktop: + dependencies: + '@antv/g6': + specifier: ^5.1.1 + version: 5.1.1 + '@hunter/shared': + specifier: workspace:* + version: link:../../packages/shared + '@tiptap/core': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/extension-character-count': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-code-block-lowlight': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/extension-code-block@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)(highlight.js@11.11.2)(lowlight@3.3.0) + '@tiptap/extension-image': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-link': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-placeholder': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-table': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-table-cell': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extension-table@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-table-header': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extension-table@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-table-row': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extension-table@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-task-item': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-task-list': + specifier: ^3.30.2 + version: 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/pm': + specifier: ^3.30.2 + version: 3.30.2 + '@tiptap/react': + specifier: ^3.30.2 + version: 3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@tiptap/starter-kit': + specifier: ^3.30.2 + version: 3.30.2 + '@tiptap/suggestion': + specifier: ^3.30.2 + version: 3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + hotkeys-js: + specifier: ^4.0.5 + version: 4.0.5 + lowlight: + specifier: ^3.3.0 + version: 3.3.0 + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + remark-math: + specifier: ^6.0.0 + version: 6.0.0 + remark-parse: + specifier: ^11.0.0 + version: 11.0.0 + remark-stringify: + specifier: ^11.0.0 + version: 11.0.0 + tiptap-markdown: + specifier: ^0.9.0 + version: 0.9.0(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + unified: + specifier: ^11.0.5 + version: 11.0.5 + zustand: + specifier: ^5.0.15 + version: 5.0.15(@types/react@18.3.31)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)) + devDependencies: + '@types/node': + specifier: ^20.0.0 + version: 20.19.43 + '@types/react': + specifier: ^18.3.0 + version: 18.3.31 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.7(@types/react@18.3.31) + electron: + specifier: ^30.0.0 + version: 30.5.1 + electron-vite: + specifier: ^2.3.0 + version: 2.3.0(vite@5.4.21(@types/node@20.19.43)) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + typescript: + specifier: ^5.6.0 + version: 5.9.3 + vite: + specifier: ^5.4.0 + version: 5.4.21(@types/node@20.19.43) + vitest: + specifier: ^2.1.0 + version: 2.1.9(@types/node@20.19.43)(jsdom@30.0.1) + + packages/shared: {} + +packages: + + '@antv/algorithm@0.1.26': + resolution: {integrity: sha512-DVhcFSQ8YQnMNW34Mk8BSsfc61iC1sAnmcfYoXTAshYHuU50p/6b7x3QYaGctDNKWGvi1ub7mPcSY0bK+aN0qg==} + + '@antv/component@2.1.11': + resolution: {integrity: sha512-dTdz8VAd3rpjOaGEZTluz82mtzrP4XCtNlNQyrxY7VNRNcjtvpTLDn57bUL2lRu1T+iklKvgbE2llMriWkq9vQ==} + + '@antv/event-emitter@0.1.3': + resolution: {integrity: sha512-4ddpsiHN9Pd4UIlWuKVK1C4IiZIdbwQvy9i7DUSI3xNJ89FPUFt8lxDYj8GzzfdllV0NkJTRxnG+FvLk0llidg==} + + '@antv/expr@1.0.2': + resolution: {integrity: sha512-vrfdmPHkTuiS5voVutKl2l06w1ihBh9A8SFdQPEE+2KMVpkymzGOF1eWpfkbGZ7tiFE15GodVdhhHomD/hdIwg==} + + '@antv/g-canvas@2.2.0': + resolution: {integrity: sha512-h7zVBBo2aO64DuGKvq9sG+yTU3sCUb9DALCVm7nz8qGPs8hhLuFOkKPEzUDNfNYZGJUGzY8UDtJ3QRGRFcvEQg==} + + '@antv/g-lite@2.7.0': + resolution: {integrity: sha512-uSzgHYa5bwR5L2Au7/5tsOhFmXKZKLPBH90+Q9bP9teVs5VT4kOAi0isPSpDI8uhdDC2/VrfTWu5K9HhWI6FWw==} + + '@antv/g-math@3.1.0': + resolution: {integrity: sha512-DtN1Gj/yI0UiK18nSBsZX8RK0LszGwqfb+cBYWgE+ddyTm8dZnW4tPUhV7QXePsS6/A5hHC+JFpAAK7OEGo5ZQ==} + + '@antv/g-plugin-dragndrop@2.1.1': + resolution: {integrity: sha512-+aesDUJVQDs6UJ2bOBbDlaGAPCfHmU0MbrMTlQlfpwNplWueqtgVAZ3L57oZ2ZGHRWUHiRwZGPjXMBM3O2LELw==} + + '@antv/g6@5.1.1': + resolution: {integrity: sha512-50bXxMUf4mChyOv4ePVeWZLwotih9VunKfp0a++Wofv/wCyY8fb9+CV2wouIBCOZnd5ydBRA4NNaX9yLJzqa2w==} + + '@antv/g@6.3.1': + resolution: {integrity: sha512-WYEKqy86LHB2PzTmrZXrIsIe+3Epeds2f68zceQ+BJtRoGki7Sy4IhlC8LrUMztgfT1t3d/0L745NWZwITroKA==} + + '@antv/graphlib@2.0.4': + resolution: {integrity: sha512-zc/5oQlsdk42Z0ib1mGklwzhJ5vczLFiPa1v7DgJkTbgJ2YxRh9xdarf86zI49sKVJmgbweRpJs7Nu5bIiwv4w==} + + '@antv/hierarchy@0.7.1': + resolution: {integrity: sha512-7r22r+HxfcRZp79ZjGmsn97zgC1Iajrv0Mm9DIgx3lPfk+Kme2MG/+EKdZj1iEBsN0rJRzjWVPGL5YrBdVHchw==} + + '@antv/layout@2.0.0': + resolution: {integrity: sha512-aCZ3UdNc40SfT7meFV7QTADY2HCnc0DShVw56CJNTI6oExUIVU736grPuL5Dhb8/JrVaU4Y83QPN/P7KafBzlw==} + + '@antv/scale@0.4.16': + resolution: {integrity: sha512-5wg/zB5kXHxpTV5OYwJD3ja6R8yTiqIOkjOhmpEJiowkzRlbEC/BOyMvNUq5fqFIHnMCE9woO7+c3zxEQCKPjw==} + + '@antv/util@2.0.17': + resolution: {integrity: sha512-o6I9hi5CIUvLGDhth0RxNSFDRwXeywmt6ExR4+RmVAzIi48ps6HUy+svxOCayvrPBN37uE6TAc2KDofRo0nK9Q==} + + '@antv/util@3.3.11': + resolution: {integrity: sha512-FII08DFM4ABh2q5rPYdr0hMtKXRgeZazvXaFYCs7J7uTcWDHUhczab2qOCJLNDugoj8jFag1djb7wS9ehaRYBg==} + + '@antv/vendor@1.0.11': + resolution: {integrity: sha512-LmhPEQ+aapk3barntaiIxJ5VHno/Tyab2JnfdcPzp5xONh/8VSfed4bo/9xKo5HcUAEydko38vYLfj6lJliLiw==} + + '@asamuzakjp/css-color@6.0.7': + resolution: {integrity: sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw==} + engines: {node: ^22.13.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@8.3.2': + resolution: {integrity: sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q==} + engines: {node: ^22.13.0 || >=24.0.0} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-arrow-functions@7.29.7': + resolution: {integrity: sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.1.1': + resolution: {integrity: sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.3.0': + resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.2.0': + resolution: {integrity: sha512-5+5LEmFuY1AjXdYhmgjTJogtQnP1evJ1zrBZGUNZ0thkpwnnmKxcHdAMn/OtFjAb25zA+jKDVYVRl+5G7rjv1A==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.8': + resolution: {integrity: sha512-CpMLjAvwQg3BL5S0IeqsZNMH7EQrEWi0kLKOC13ZBF0ZwERiLWlibNPJr8G1kdU3Ms/r2KiNrF81pUh2HwAHdg==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + + '@electron/get@2.0.3': + resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} + engines: {node: '>=12'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@exodus/bytes@1.15.1': + resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} + + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} + + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-android-arm-eabi@4.62.5': + resolution: {integrity: sha512-jfkGfTwhQpsiSckPF8r9bU3pn3vyd72NlWaO+TgEO6WPSDnUhXzrNYCHBMOYj0ACaUgjm6eERLF+XV9a6RstoA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.5': + resolution: {integrity: sha512-oGVqyQlxnrz9/ty89oHpU857VUHEl5/Xu4R2lS+aivCTrNnSsbiENzTnNaBsjxH0CNWGPhzHArOLFwo+oKXveA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.5': + resolution: {integrity: sha512-bW7B8xMEq8n99Q3ieEcPRGuphurdZAaFzQc9Efyyw3FL6DZO6pMy9xhdN+kBoD7Sy05xNXSr4OyPPnpkYriS/A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.5': + resolution: {integrity: sha512-YSwBS86QeHOGlrxJ1PSOIZSkzRL/JmKeunhc+lV6M1a6En8QuVCD/T/qIA0J4Gd2Y86RIOBYrLcOUtqGh9+/1w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.5': + resolution: {integrity: sha512-2fST8lILgl7cKbme/1KDdPCmbXbG+gqoV3bHp19L0ypX/3akYMBVdOunPleRCwonoLnXOZ/0F+Mt/v8POFmfcQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.5': + resolution: {integrity: sha512-cpIxQCP9J+EVad0a6LO1kY3ZGODlk80VlI+2I96B8xMcdHZ4pLVhfQ49JFpYqjPF91FFkQWftf57YlDcTiw9yQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.5': + resolution: {integrity: sha512-r9fGh3eFs3e/udWh5ZjXQtxiYK/xoFxQaYR/cELxac/Udkl5Th+IsFm0CX3Kl9hmUH/we7EoMpjJgeQNnE0+IA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.62.5': + resolution: {integrity: sha512-xdvFdp7OM6KLJviJT2g/YuRSUjnZgGHk4RNgwIbN7X6cPugOucV60DdHXWzsBVCUdrGb6qSXnJQrrAKMmQuj3Q==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.62.5': + resolution: {integrity: sha512-rRqILAndyzHzP7T9NFQrq+4HFWNhqkqkKur7eiBpfLmz01PO0JKx5Vchu3YllE4YXI/Ftgq/szrDWg5GJ0mI8g==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.62.5': + resolution: {integrity: sha512-Gf4X3qVMucayUvux6aXXPgXovocSFUC0rrffDuPI/S2nHhNMhjcZxsrAFYCOF350PRreW1XwzFj3CT/3bKsWCw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.62.5': + resolution: {integrity: sha512-+s5qA0TNM0qm8PK/a5gt/1Hpx+NV08uSuCncvhziIlQzT6AEV2fnUQo7eBtFTFO0nA9scauvoR2HusfXmQnO4w==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.62.5': + resolution: {integrity: sha512-ybb6QvWwWJCbBWqERpc8K3pYVGIrXlG8MEQ8IIuJY6Y9KdHQxoFoNyfkAOtKn1VHu3KuLidXvwrvGR1mEjeWCw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.62.5': + resolution: {integrity: sha512-nZb1DtnOyhCmYvsC8A2CwOkopVg+IS1+fPUa7rMOAXtNw5+lLCLLPqd6XAiNrGtoQKsbvIBOwsHnBH/3wnb4HQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.62.5': + resolution: {integrity: sha512-yMbj63Sp89ryrXLWyz+sy+fYD2HpOnMCLGbe4Oa1smclFSUukdtD/BgdiHaAetJNb74URD8U4hM+qG5KVzMEkg==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.62.5': + resolution: {integrity: sha512-mhoan3OJw2kYV/e1jtIdmvUZgyBFeA6zGWsOswmR0Tg19TQbowZuR+JMLID6spbbBN7Zee2ejrgmy3+FxGrIdA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.62.5': + resolution: {integrity: sha512-5ZTLmjWbb1VZdjuyhe83K/8QO0/h11midQCBP+X5OYn32ra7eOBoM0ZqtaY4nkgNsYgmdVhMYPoyVPTjUpHf3w==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.62.5': + resolution: {integrity: sha512-m53kG+br6PGxOTmgBEM2DHSDs9RVjsyEbUwjJPJGTFm1grWOG8EKJggDCTb60unD4Tjby8fi7/m9XfkEWasVWg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.62.5': + resolution: {integrity: sha512-6RHPJR1g/uvdYU8uXBnfq3nlqyZCP82Fr6NHgfGoaIeSh0YEqnX/x6uA9MmJJbnSH7swqX4F+CkGdUF+6doiQA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.62.5': + resolution: {integrity: sha512-xs+OXQtEXgpXT0DmA5+U3qnRZHdCST/5HRQxS8wSPZTUZN/EMWeHuSIod32LQklTBZBV9DyfncKBQ8n5V3eFdw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.62.5': + resolution: {integrity: sha512-e7hD+sl3s+mcLQDZ8pbudBVsdG6r5yN4w3LqG2TJ8sQHDpblWj5lrJs/3m01Cvlxbt4x13zu5thLjgypgtkYzw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.5': + resolution: {integrity: sha512-GiyJaCf+WpMub/17aPcKk27QMl5W6f+KhdPTjlFOn5akH5Wa/DCM9Stdx5cDfmasyKB08MqpVQ1uJE2RkkpbXg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.5': + resolution: {integrity: sha512-+OQ8U2DdoEfXl8T4Fb18AjmEwbXMerKDKCL8yCPAYhKCEEKoul7rkbeGCBFCbAlaGaa7pmtRTpkAJM2LE/i5FA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.5': + resolution: {integrity: sha512-KanvAZrPKbDBFwrgiU9yEVpQoox9QPV1WZOXX7HudJQY+eSlu82CtWxDU8WtuRRvtN5EGkLczkd6Y6DTcvm9wA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.5': + resolution: {integrity: sha512-1aC3UEWTtRl3RK3VpDJ/Tqk1XI4SLTmXIthAq6wRWo8XiSXJNd+VprJM4/1P4+i6HIaFEFlVi9sTTziniD2tOQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.5': + resolution: {integrity: sha512-/gDJaRs4gl0NPIwqCz+6PkpmhhjRAD2j6P4rSNHBzUkO3naEx2mIU0pRle1vUNRQ7mE/+8OOeXLTv/J56FKiQg==} + cpu: [x64] + os: [win32] + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@szmarczak/http-timer@4.0.6': + resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} + engines: {node: '>=10'} + + '@tiptap/core@3.30.2': + resolution: {integrity: sha512-QbZC/s1OOqcoUdkhIY16TjR/gCtR0qAk9e4bJwUqOJqZuv5ozqCL5hzWm22jjTPp6c6Ei2tPd6t30VwfIKW4lQ==} + peerDependencies: + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-blockquote@3.30.2': + resolution: {integrity: sha512-BOkwhZenek7vzXBOgKppSrlx4YryBdAYu1p1MXKn0R9A9eNmE2HVhmm0gG49+E8BhsE/TG8wKVclwET42JJiIg==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-bold@3.30.2': + resolution: {integrity: sha512-MsvJhPgYejY2D9MhwYJv8AmscozvLBI8qtJ7YLdYZBWkMR4bgmxHq5+xqEfBsao9bOMMwBon9p3+P+/Tq5ReWA==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-bubble-menu@3.30.2': + resolution: {integrity: sha512-oS0WiWNXHKpiPYMkcnHm1j7iEvufTGGtLFtcJJd3olb5OS6V2acoXVDL0nNJDmRQ32K3QXut3fbRcMseMxT+lw==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-bullet-list@3.30.2': + resolution: {integrity: sha512-+awIL/TUz4aB3rL68igU1rWfaaoBIAkPcakkktkRq8gYf0bd9eSb48P6kHkpx/3q+JyK7g9vsnltLNHNh6twnA==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-character-count@3.30.2': + resolution: {integrity: sha512-9Mnej4qOLf2/kFPAmGxkxwdpFNRq8+D9nQVnyFF50x32myaKl/BXcCftsm+EiKpnH9JNT5mhZA0nh4JoH7OwrA==} + peerDependencies: + '@tiptap/extensions': 3.30.2 + + '@tiptap/extension-code-block-lowlight@3.30.2': + resolution: {integrity: sha512-xmC1MOAMl7QmcayV0H/SLxpJ84o4SPwzAcJDhtgUWFdWyf8aO5rnNtUj/2HnvLXlM1nqZPMjTBOmSK7qgPwSZw==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/extension-code-block': 3.30.2 + '@tiptap/pm': 3.30.2 + highlight.js: ^11 + lowlight: ^2 || ^3 + + '@tiptap/extension-code-block@3.30.2': + resolution: {integrity: sha512-9otGKaQZmePHrLXFtCtz+BYDn5z4sSumTkUqQIQHz0gVxwPoTi7g51RedwxvViTb/zu2XV5ROXYLHIxKxypMPg==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-code@3.30.2': + resolution: {integrity: sha512-r8EZk3R9yGpF6v5xxafAU1HwrD/e+RpbfnmVi2TeB/ZHAsVO62fW96E32G0t6IdaCtOFtAd85hkDAaOfCT4yGg==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-document@3.30.2': + resolution: {integrity: sha512-+xIv67V+/2L1uvz98FAT5W7kWEfHwfNV3MD7b4UsKPU0lhcCWuVOXy0JB8yYmdNExqpI7xT9g3MWzREoBvBQSg==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-dropcursor@3.30.2': + resolution: {integrity: sha512-nyRKUmItATnKI9AiRChmjhcBbCEsNxRu+AaCz+cx8EvnAcNHsVRdNYL5PmBs3WlNA/Et4Eb2DG0hVQDnNF61eg==} + peerDependencies: + '@tiptap/extensions': 3.30.2 + + '@tiptap/extension-floating-menu@3.30.2': + resolution: {integrity: sha512-A8PLvvh8W6PUMrqh+EpBerxm+Ucr0irGxJvwAnzYQmNGNIJ9U4OVgw4OcEU+9JH0gMmEzDcHzMPP2s/s1lIcyw==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-gapcursor@3.30.2': + resolution: {integrity: sha512-7Xk0ut6FM+RAsvKxDN3bAtk7zvYZ6Aa8pawJ6s7dLAmLR9JwrZevlcL4FSrj4bR7rqKOj92RYCFxzgTEbWimag==} + peerDependencies: + '@tiptap/extensions': 3.30.2 + + '@tiptap/extension-hard-break@3.30.2': + resolution: {integrity: sha512-IxSNgmG3d4OZdUTeebrOI7SxdIWXXJqlcGiSNDabWqxipUitfy3mZ3gDDE6G01koKxZRbhz4KIplAZlpxnTFSg==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-heading@3.30.2': + resolution: {integrity: sha512-PblDvgSJ05p1t6hzyPi02xeiBjB0M2abReoGEImqSWCy79UqnAGacgsZo4EeEawtJV1NEP8chhvmX+nRtzdT1A==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-horizontal-rule@3.30.2': + resolution: {integrity: sha512-j8aswLTsuEdJKC62DF+kw0EgvIRL7QMUyAVp2fdjR0qgM0ZVlEwCC4qIEq3kK9tFVU4kRtQ5BSj/jn6QwrlbCA==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-image@3.30.2': + resolution: {integrity: sha512-K/BPlWauHXI6Y4s7se2A1BLcZ2pnWmRQTEDkPJYe/8kmv1GyJzPu34sZed5Mvfu5chUH8u6aNZ/utZbvSbI/0Q==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-italic@3.30.2': + resolution: {integrity: sha512-pp8uaiuXsUbLm5rYzR1jlWbwm1mAahRajdHwAKBtthFRB2rDvC7ZWhKaCSoKhZvfIDRmu9/B67+uAHoutL0dCA==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-link@3.30.2': + resolution: {integrity: sha512-jwdcymKcrbFpj5hRAuGVLCq8FieVkGFnENyroYmvkad+XAt8ZLy/MTFYRN6SK3ukH6PZMY7H4iObGtciQaC5nw==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-list-item@3.30.2': + resolution: {integrity: sha512-HWgRCRlGxulE+hN1VUcnWD6P2NE08VBgGtcaxOfdXVqaI93BCK6AhRQZGpLsfKgajLk+5DXTBraaitwnBqzCxg==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-list-keymap@3.30.2': + resolution: {integrity: sha512-TTve3WOlQaYu1ahMqsQ/T0wzaxfgZvcOl3/OuPyInOi8QtxXhqGhFjmYe5jOr56G9W2QDuFWVsZecVwfDte9zg==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-list@3.30.2': + resolution: {integrity: sha512-MIUpo1Bd9Rf1Qg+TNYNwDZ4xsfFeQahjU9Xhy6UcaszKQzbAM7KCzn5BObNytK1NdcqNHsC8Wj5vFvMKEzrXdw==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-ordered-list@3.30.2': + resolution: {integrity: sha512-Z7OO1HcF0idda1n6vodXeQ3h2ylN9JR4IfIGUYkar5Xl9JusK8PDETTBQZQn//96p49I2d+GoWsD2LXPtjHXXg==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-paragraph@3.30.2': + resolution: {integrity: sha512-ulEu3LNt+kPVAWEnrhoz13Fs8Q/v/8NUxQbAeteuBchQ8joxJXuWExhpy1fUfZir5+b+W5z7/NesgPjZQfv47w==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-placeholder@3.30.2': + resolution: {integrity: sha512-Bj1seUvPCoRrD/LpzMoKD+jQIjxuc+oq931GpPq4wobSUUbD4pF/0NMwpCLpiHO19QnTQz8+9p2dqPdlc44LHA==} + peerDependencies: + '@tiptap/extensions': 3.30.2 + + '@tiptap/extension-strike@3.30.2': + resolution: {integrity: sha512-fBLxMXz6hYIURzLOD+/L6aVATztsKham00ANWmGi13vN0hx2lQMYZffN+gR+QqiCDfMQxBXzrf5a7tJuDiQHLQ==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-table-cell@3.30.2': + resolution: {integrity: sha512-fcFG6MsJV+x1ua27dJqIdpOa5wiTXaLSfXfZ0G+rhwchH8EtSM3Q64gT4KeHsa/vVt7pOEc6ChQhKMnm40fWdA==} + peerDependencies: + '@tiptap/extension-table': 3.30.2 + + '@tiptap/extension-table-header@3.30.2': + resolution: {integrity: sha512-qcpMr8x1XvevEG5QZdk4JGLzsjZal1zzNfy4xmEtwUYVYkXkWaXklQvxEXC6GUkVpTGTOSXtHl25/cSbnJ5qbg==} + peerDependencies: + '@tiptap/extension-table': 3.30.2 + + '@tiptap/extension-table-row@3.30.2': + resolution: {integrity: sha512-dlbkcmgb5eiMI9hLX5C6CaPGuGcpyyPxJCjIYcI1l4glimYcbgxxEeqtGIvySgDewxWgofY/V3CLOGHrfyBCkQ==} + peerDependencies: + '@tiptap/extension-table': 3.30.2 + + '@tiptap/extension-table@3.30.2': + resolution: {integrity: sha512-zWp0ehZnx6N5lwRa8anSTn17GBEF3wxHXfOJGbBugf4Vwmp5keUEAXrgeIS4j7gIdxjN3XawdwjuMImG93N+Fg==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-task-item@3.30.2': + resolution: {integrity: sha512-cb9SK9TxfK6rOJZSQemqwdEhv3KsUWwTAZG4aKE4ByLWOPhf7omAw7P5/4NO4TxnnwyZglOZMNhmrFXoKqoMWw==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-task-list@3.30.2': + resolution: {integrity: sha512-xo59REkBIb/1EMKQth24RW2CEcyZbuBTQog3JHqcQJotItsaIQP/HBRggtI3W+6eMxhujbRsuL9sLWbW1OPw6Q==} + peerDependencies: + '@tiptap/extension-list': 3.30.2 + + '@tiptap/extension-text@3.30.2': + resolution: {integrity: sha512-n/iZnirgRmXet6f97kolAnP3j8DsgLSiTbz/KLWc8eBYiFmkjRzkuisOm5xuGdfGIxwpB4x3tlSF4ef4DLnbRg==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extension-underline@3.30.2': + resolution: {integrity: sha512-SZiTMnvqXcnrtJX+X25ZbYsuDO83haGOVMBD/O+mAWYNYXhaSc5Rkph5czzItxrd+Yyp/vs4PiwD7XTNbfqmpA==} + peerDependencies: + '@tiptap/core': 3.30.2 + + '@tiptap/extensions@3.30.2': + resolution: {integrity: sha512-2LqAHXk26QDsryW+beECxYeBzv5Ylk4GuB3cOmfghS7/G37R2W+Te3TkUK7BT0EWoDryvBT57/5q0DEFIhfZZg==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@tiptap/pm@3.30.2': + resolution: {integrity: sha512-BJN8tUx4ppFN3R3cV/FJfrJbJkvo1lj4uciq+nwpjwzdRvFzqIuglWf+HLcJ6CwlYpLOHp7ArgkBg4Q5e60Gog==} + + '@tiptap/react@3.30.2': + resolution: {integrity: sha512-7hGaTstpUeTmQ008mCPkjz+GSlChWhucgy+PeX0z93v4+nh7qM5F+0lh+kJ9zo6Os5abO7v36GtgHRZdQI6+FQ==} + peerDependencies: + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tiptap/starter-kit@3.30.2': + resolution: {integrity: sha512-fJSrhW1CyD4sjYA20evSP4Cp13B/HhbxCdM974K0xpHOVqvCtNU9w2s9hfq9mg2yGoU7MSNHKNYMkJjIi2/Xyw==} + + '@tiptap/suggestion@3.30.2': + resolution: {integrity: sha512-qsQv+rlh5ik9G1n+SGLak69JoLQAFwdtQRjGpABYxx5USc14J7hrR1K8sj9tnWymX1fKXfJOlz+Dimcb1kDhWA==} + peerDependencies: + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': 3.30.2 + '@tiptap/pm': 3.30.2 + + '@types/cacheable-request@6.0.3': + resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.1': + resolution: {integrity: sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.4': + resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.2.0': + resolution: {integrity: sha512-kVd74ta9eof3eJOvbNd1vGKS/XERRyQbT26Og63hIsvDO84cjD5gEOhsXf26w3FSoNlPVz84DOFcKv/oou+fMw==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + + '@types/http-cache-semantics@4.2.0': + resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} + + '@types/katex@0.16.8': + resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} + + '@types/keyv@3.1.4': + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + + '@types/linkify-it@3.0.5': + resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@13.0.9': + resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==} + + '@types/markdown-it@14.2.0': + resolution: {integrity: sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdurl@1.0.5': + resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@20.19.43': + resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.31': + resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==} + + '@types/responselike@1.0.3': + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + base64-arraybuffer@1.0.2: + resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} + engines: {node: '>= 0.6.0'} + + baseline-browser-mapping@2.11.18: + resolution: {integrity: sha512-1iEmLEYSiE1SeBoAfPo/Mnx3PzfzHUkDK61ASkCpuk3YXugYLH5DYK1SzqV55F8FMI6s0F+/tCP7Polz1QRjxw==} + engines: {node: '>=6.0.0'} + hasBin: true + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + + boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bubblesets-js@2.3.4: + resolution: {integrity: sha512-DyMjHmpkS2+xcFNtyN00apJYL3ESdp9fTrkDr5+9Qg/GPqFmcWgGsK1akZnttE1XFxJ/VMy4DNNGMGYtmFp1Sg==} + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + cacheable-lookup@5.0.4: + resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} + engines: {node: '>=10.6.0'} + + cacheable-request@7.0.4: + resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} + engines: {node: '>=8'} + + caniuse-lite@1.0.30001809: + resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + clone-response@1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + comlink@4.4.2: + resolution: {integrity: sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + css-line-break@2.1.0: + resolution: {integrity: sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-binarytree@1.0.2: + resolution: {integrity: sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force-3d@3.0.6: + resolution: {integrity: sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo-projection@4.0.0: + resolution: {integrity: sha512-p0bK60CEzph1iqmnxut7d/1kyTmm3UWtPlwdkM31AU+LW+BXazd5zJdoCn7VFxNCHXRngPHRnsNn5uGjLRGndg==} + engines: {node: '>=12'} + hasBin: true + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-octree@1.1.0: + resolution: {integrity: sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-regression@1.3.10: + resolution: {integrity: sha512-PF8GWEL70cHHWpx2jUQXc68r1pyPHIA+St16muk/XRokETzlegj5LriNKg7o4LR0TySug4nHYPJNNRz/W+/Niw==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + dagre@0.8.5: + resolution: {integrity: sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==} + + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + electron-to-chromium@1.5.412: + resolution: {integrity: sha512-z4rMe3esBzlzovKHj4gxJnsCGZRK5l4baUvm+gCGJBPE+gsyUMKsuU9tnEUtI1dOebXz1ytAPGjvXhmQ7rIPwA==} + + electron-vite@2.3.0: + resolution: {integrity: sha512-lsN2FymgJlp4k6MrcsphGqZQ9fKRdJKasoaiwIrAewN1tapYI/KINLdfEL7n10LuF0pPSNf/IqjzZbB5VINctg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@swc/core': ^1.0.0 + vite: ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + '@swc/core': + optional: true + + electron@30.5.1: + resolution: {integrity: sha512-AhL7+mZ8Lg14iaNfoYTkXQ2qee8mmsQyllKdqxlpv/zrKgfxz6jNVtcRRbQtLxtF8yzcImWdfTQROpYiPumdbw==} + engines: {node: '>= 12.20.55'} + hasBin: true + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-equals@5.4.1: + resolution: {integrity: sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==} + engines: {node: '>=6.0.0'} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + gl-matrix@3.4.4: + resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==} + + global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + got@11.8.6: + resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} + engines: {node: '>=10.19.0'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphlib@2.1.8: + resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + highlight.js@11.11.2: + resolution: {integrity: sha512-oaXMACAU0kzOMXBjWpNcX+vlwSBCIAiZ9BHa7gA15NOTtT2L/l8OSZDuqS2XppOhZBPJ7hm4o8ep2kyuip2uEQ==} + engines: {node: '>=12.0.0'} + + hotkeys-js@4.0.5: + resolution: {integrity: sha512-Os0VaEH8gvOSzwZnEDU2yn7G868OfFiLydUHIe2uln7Fi9Ii1xhOCoPGu27RkZYWYqM93gC4xoyONCZE9iiwLw==} + + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + html2canvas@1.4.1: + resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==} + engines: {node: '>=8.0.0'} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http2-wrapper@1.0.3: + resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} + engines: {node: '>=10.19.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-any-array@3.0.0: + resolution: {integrity: sha512-o4h+tylWykC4BD1vaejp6gDxoM13bwW8FGuNs4yIKpj8xbBJcRxJx8vZpq0dCr7ZDEfeKjmsi/euolKhX6f/ww==} + + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsdom@30.0.1: + resolution: {integrity: sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA==} + engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} + peerDependencies: + canvas: ^3.2.3 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + katex@0.16.47: + resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + linkify-it@5.0.2: + resolution: {integrity: sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q==} + + linkifyjs@4.3.3: + resolution: {integrity: sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lowercase-keys@2.0.0: + resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} + engines: {node: '>=8'} + + lowlight@3.3.0: + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} + + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + markdown-it-task-lists@2.1.1: + resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==} + + markdown-it@14.3.0: + resolution: {integrity: sha512-RCEsPjR+sr0x+AuYp601tKTkgFG4YEPLCzHST3cQ/fhlJkqAkz1L2/Qbp1j9qw5SBwQHFBoW8+hoN5xssOF0Tw==} + hasBin: true + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-math@3.0.0: + resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + mdurl@2.1.0: + resolution: {integrity: sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-math@3.1.0: + resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + mimic-response@1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + ml-array-max@2.0.0: + resolution: {integrity: sha512-QQZ4kENwpWmyNb98UXRDFXrmtIXuXtt1+bSbda/2KA85+F+rrJP8hZk6QOkCQXM2Th9mUDYdq/PNByPdT9ID4A==} + + ml-array-min@2.0.0: + resolution: {integrity: sha512-GRj6Ky6sW9vGL6yIjgsHmXZ9YgrdmcQ8nCxPqEGeKc6dkfYg1XDYxGFxADUjNuZyoCd5PUscWAS4N+cFaX6hFg==} + + ml-array-rescale@2.0.0: + resolution: {integrity: sha512-2GGtKfSno94/kIloWGvpp/U5Q5vLvLrza+SAaGsLeo6Xj4mEbA6Gqx+oTfZFkxnd1grT2X007HfJNs3T5BsiVg==} + + ml-matrix@6.15.0: + resolution: {integrity: sha512-wFa1v6KP8bKp+fj0nYmRs1Pb5K4zRkXGKsOvLinvILENFIADncm4XlOI+S1M7yuACMGfI6cfk0IifDgd4j5xmw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} + engines: {node: '>=18'} + + normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + + p-cancelable@2.1.1: + resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} + engines: {node: '>=8'} + + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + engines: {node: ^10 || ^12 || >=14} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + prosemirror-changeset@2.4.2: + resolution: {integrity: sha512-ViYrjMSg3YFiXwIhKaluu+/mi3Yrxt6AR8ri14ulTaGcZtXO1CThl7A2gv79qx5fQnOw8woKwyBU2u+9PVCm3w==} + + prosemirror-commands@1.7.2: + resolution: {integrity: sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==} + + prosemirror-dropcursor@1.8.3: + resolution: {integrity: sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==} + + prosemirror-gapcursor@1.4.1: + resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} + + prosemirror-history@1.5.0: + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} + + prosemirror-inputrules@1.5.1: + resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} + + prosemirror-keymap@1.2.3: + resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + + prosemirror-markdown@1.13.6: + resolution: {integrity: sha512-dY6g2BXRjkHW2ldNRDKfTF0x0R4ifk5rgPaABd/UhvrymtsGVxTbHn01goEsHAvO9nN0O34cttlW1qg/XUcaIg==} + + prosemirror-model@1.25.11: + resolution: {integrity: sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==} + + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} + + prosemirror-state@1.4.4: + resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} + + prosemirror-tables@1.8.5: + resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} + + prosemirror-transform@1.12.0: + resolution: {integrity: sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==} + + prosemirror-view@1.42.2: + resolution: {integrity: sha512-Pdg0l5kXm8aLDquFAnQFTCITg0q44sLqBlHlpsVLD9segdOao8TOfQdAhCrCXyVgPSRr6UDDROOIWA3bIrN9YQ==} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-math@6.0.0: + resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + + responselike@2.0.1: + resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + + roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + + rollup@4.62.5: + resolution: {integrity: sha512-/tqMfgP7GPA3PHhCmuiS4vIjrSVhHLgY++i+dhbG462euyAj7FpM4D9uq1X3BgjlqRdpcOrYhcQtfiQLNc8tqw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + sumchecker@3.0.1: + resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} + engines: {node: '>= 8.0'} + + svg-path-parser@1.1.0: + resolution: {integrity: sha512-jGCUqcQyXpfe38R7RFfhrMyfXcBmpMNJI/B+4CE9/Unkh98UporAc461GTthv+TVDuZXsBx7/WiwJb1Oh4tt4A==} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + text-segmentation@1.0.3: + resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + tiptap-markdown@0.9.0: + resolution: {integrity: sha512-dKLQ9iiuGNgrlGVjrNauF/UBzWu4LYOx5pkD0jNkmQt/GOwfCJsBuzZTsf1jZ204ANHOm572mZ9PYvGh1S7tpQ==} + peerDependencies: + '@tiptap/core': ^3.0.1 + + tldts-core@7.4.10: + resolution: {integrity: sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==} + + tldts@7.4.10: + resolution: {integrity: sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==} + hasBin: true + + tough-cookie@6.0.2: + resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + undici@8.10.0: + resolution: {integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==} + engines: {node: '>=22.19.0'} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + update-browserslist-db@1.3.1: + resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + utrie@1.0.2: + resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + whatwg-url@17.1.0: + resolution: {integrity: sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==} + engines: {node: ^22.14.0 || >=24.0.0} + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + zustand@5.0.15: + resolution: {integrity: sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@antv/algorithm@0.1.26': + dependencies: + '@antv/util': 2.0.17 + tslib: 2.8.1 + + '@antv/component@2.1.11': + dependencies: + '@antv/g': 6.3.1 + '@antv/scale': 0.4.16 + '@antv/util': 3.3.11 + svg-path-parser: 1.1.0 + + '@antv/event-emitter@0.1.3': {} + + '@antv/expr@1.0.2': {} + + '@antv/g-canvas@2.2.0': + dependencies: + '@antv/g-lite': 2.7.0 + '@antv/g-math': 3.1.0 + '@antv/util': 3.3.11 + '@babel/runtime': 7.29.7 + gl-matrix: 3.4.4 + tslib: 2.8.1 + + '@antv/g-lite@2.7.0': + dependencies: + '@antv/g-math': 3.1.0 + '@antv/util': 3.3.11 + '@antv/vendor': 1.0.11 + '@babel/runtime': 7.29.7 + eventemitter3: 5.0.4 + gl-matrix: 3.4.4 + tslib: 2.8.1 + + '@antv/g-math@3.1.0': + dependencies: + '@antv/util': 3.3.11 + '@babel/runtime': 7.29.7 + gl-matrix: 3.4.4 + tslib: 2.8.1 + + '@antv/g-plugin-dragndrop@2.1.1': + dependencies: + '@antv/g-lite': 2.7.0 + '@antv/util': 3.3.11 + '@babel/runtime': 7.29.7 + tslib: 2.8.1 + + '@antv/g6@5.1.1': + dependencies: + '@antv/algorithm': 0.1.26 + '@antv/component': 2.1.11 + '@antv/event-emitter': 0.1.3 + '@antv/g': 6.3.1 + '@antv/g-canvas': 2.2.0 + '@antv/g-plugin-dragndrop': 2.1.1 + '@antv/graphlib': 2.0.4 + '@antv/hierarchy': 0.7.1 + '@antv/layout': 2.0.0 + '@antv/util': 3.3.11 + bubblesets-js: 2.3.4 + + '@antv/g@6.3.1': + dependencies: + '@antv/g-lite': 2.7.0 + '@antv/util': 3.3.11 + '@babel/runtime': 7.29.7 + gl-matrix: 3.4.4 + html2canvas: 1.4.1 + + '@antv/graphlib@2.0.4': + dependencies: + '@antv/event-emitter': 0.1.3 + + '@antv/hierarchy@0.7.1': {} + + '@antv/layout@2.0.0': + dependencies: + '@antv/event-emitter': 0.1.3 + '@antv/expr': 1.0.2 + '@antv/graphlib': 2.0.4 + '@antv/util': 3.3.11 + comlink: 4.4.2 + d3-force: 3.0.0 + d3-force-3d: 3.0.6 + d3-octree: 1.1.0 + d3-quadtree: 3.0.1 + dagre: 0.8.5 + ml-matrix: 6.15.0 + tslib: 2.8.1 + + '@antv/scale@0.4.16': + dependencies: + '@antv/util': 3.3.11 + color-string: 1.9.1 + fecha: 4.2.3 + + '@antv/util@2.0.17': + dependencies: + csstype: 3.2.3 + tslib: 2.8.1 + + '@antv/util@3.3.11': + dependencies: + fast-deep-equal: 3.1.3 + gl-matrix: 3.4.4 + tslib: 2.8.1 + + '@antv/vendor@1.0.11': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-color': 3.1.3 + '@types/d3-dispatch': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.1 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-shape': 3.2.0 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-force-3d: 3.0.6 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-geo-projection: 4.0.0 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-regression: 1.3.10 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + + '@asamuzakjp/css-color@6.0.7': + dependencies: + '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.2.0(@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) + '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.5.2 + + '@asamuzakjp/dom-selector@8.3.2': + dependencies: + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.8 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/plugin-transform-arrow-functions@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/runtime@7.29.7': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.1.1': {} + + '@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.1.1 + '@csstools/css-calc': 3.3.0(@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) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.8(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + + '@electron/get@2.0.3': + dependencies: + debug: 4.4.3 + env-paths: 2.2.1 + fs-extra: 8.1.0 + got: 11.8.6 + progress: 2.0.3 + semver: 6.3.1 + sumchecker: 3.0.1 + optionalDependencies: + global-agent: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@exodus/bytes@1.15.1': {} + + '@floating-ui/core@1.8.0': + dependencies: + '@floating-ui/utils': 0.2.12 + + '@floating-ui/dom@1.8.0': + dependencies: + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 + + '@floating-ui/utils@0.2.12': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + + '@rollup/rollup-android-arm-eabi@4.62.5': + optional: true + + '@rollup/rollup-android-arm64@4.62.5': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.5': + optional: true + + '@rollup/rollup-darwin-x64@4.62.5': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.5': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.5': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.5': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.5': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.5': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.5': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.5': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.5': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.5': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.5': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.5': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.5': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.5': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.5': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.5': + optional: true + + '@sindresorhus/is@4.6.0': {} + + '@szmarczak/http-timer@4.0.6': + dependencies: + defer-to-connect: 2.0.1 + + '@tiptap/core@3.30.2(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-blockquote@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-bold@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-bubble-menu@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + optional: true + + '@tiptap/extension-bullet-list@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-character-count@3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extensions': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-code-block-lowlight@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/extension-code-block@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)(highlight.js@11.11.2)(lowlight@3.3.0)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/extension-code-block': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + highlight.js: 11.11.2 + lowlight: 3.3.0 + + '@tiptap/extension-code-block@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-code@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-document@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-dropcursor@3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extensions': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-floating-menu@3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + optional: true + + '@tiptap/extension-gapcursor@3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extensions': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-hard-break@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-heading@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-horizontal-rule@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-image@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-italic@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-link@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + linkifyjs: 4.3.3 + + '@tiptap/extension-list-item@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-list-keymap@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-ordered-list@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-paragraph@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-placeholder@3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extensions': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-strike@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-table-cell@3.30.2(@tiptap/extension-table@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-table': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-table-header@3.30.2(@tiptap/extension-table@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-table': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-table-row@3.30.2(@tiptap/extension-table@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-table': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-table@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/extension-task-item@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-task-list@3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + + '@tiptap/extension-text@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extension-underline@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + + '@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/pm@3.30.2': + dependencies: + prosemirror-changeset: 2.4.2 + prosemirror-commands: 1.7.2 + prosemirror-dropcursor: 1.8.3 + prosemirror-gapcursor: 1.4.1 + prosemirror-history: 1.5.0 + prosemirror-inputrules: 1.5.1 + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.11 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.4 + prosemirror-tables: 1.8.5 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + '@tiptap/react@3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + '@types/react': 18.3.31 + '@types/react-dom': 18.3.7(@types/react@18.3.31) + '@types/use-sync-external-store': 0.0.6 + fast-equals: 5.4.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + '@tiptap/extension-bubble-menu': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-floating-menu': 3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + transitivePeerDependencies: + - '@floating-ui/dom' + + '@tiptap/starter-kit@3.30.2': + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/extension-blockquote': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-bold': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-bullet-list': 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-code': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-code-block': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-document': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-dropcursor': 3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-gapcursor': 3.30.2(@tiptap/extensions@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-hard-break': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-heading': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-horizontal-rule': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-italic': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-link': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-list': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/extension-list-item': 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-list-keymap': 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-ordered-list': 3.30.2(@tiptap/extension-list@3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)) + '@tiptap/extension-paragraph': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-strike': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-text': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extension-underline': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)) + '@tiptap/extensions': 3.30.2(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@tiptap/suggestion@3.30.2(@floating-ui/dom@1.8.0)(@tiptap/core@3.30.2(@tiptap/pm@3.30.2))(@tiptap/pm@3.30.2)': + dependencies: + '@floating-ui/dom': 1.8.0 + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@tiptap/pm': 3.30.2 + + '@types/cacheable-request@6.0.3': + dependencies: + '@types/http-cache-semantics': 4.2.0 + '@types/keyv': 3.1.4 + '@types/node': 20.19.43 + '@types/responselike': 1.0.3 + + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.1': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.4': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.2.0': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree@1.0.9': {} + + '@types/geojson@7946.0.16': {} + + '@types/hast@3.0.5': + dependencies: + '@types/unist': 3.0.3 + + '@types/http-cache-semantics@4.2.0': {} + + '@types/katex@0.16.8': {} + + '@types/keyv@3.1.4': + dependencies: + '@types/node': 20.19.43 + + '@types/linkify-it@3.0.5': {} + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@13.0.9': + dependencies: + '@types/linkify-it': 3.0.5 + '@types/mdurl': 1.0.5 + + '@types/markdown-it@14.2.0': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdurl@1.0.5': {} + + '@types/mdurl@2.0.0': {} + + '@types/ms@2.1.0': {} + + '@types/node@20.19.43': + dependencies: + undici-types: 6.21.0 + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.31)': + dependencies: + '@types/react': 18.3.31 + + '@types/react@18.3.31': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/responselike@1.0.3': + dependencies: + '@types/node': 20.19.43 + + '@types/unist@3.0.3': {} + + '@types/use-sync-external-store@0.0.6': {} + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 20.19.43 + optional: true + + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@20.19.43))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@20.19.43) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + + argparse@2.0.1: {} + + assertion-error@2.0.1: {} + + bail@2.0.2: {} + + base64-arraybuffer@1.0.2: {} + + baseline-browser-mapping@2.11.18: {} + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + + boolean@3.2.0: + optional: true + + browserslist@4.28.8: + dependencies: + baseline-browser-mapping: 2.11.18 + caniuse-lite: 1.0.30001809 + electron-to-chromium: 1.5.412 + node-releases: 2.0.53 + update-browserslist-db: 1.3.1(browserslist@4.28.8) + + bubblesets-js@2.3.4: {} + + buffer-crc32@0.2.13: {} + + cac@6.7.14: {} + + cacheable-lookup@5.0.4: {} + + cacheable-request@7.0.4: + dependencies: + clone-response: 1.0.3 + get-stream: 5.2.0 + http-cache-semantics: 4.2.0 + keyv: 4.5.4 + lowercase-keys: 2.0.0 + normalize-url: 6.1.0 + responselike: 2.0.1 + + caniuse-lite@1.0.30001809: {} + + ccount@2.0.1: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + character-entities@2.0.2: {} + + check-error@2.1.3: {} + + clone-response@1.0.3: + dependencies: + mimic-response: 1.0.1 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + comlink@4.4.2: {} + + commander@7.2.0: {} + + commander@8.3.0: {} + + convert-source-map@2.0.0: {} + + css-line-break@2.1.0: + dependencies: + utrie: 1.0.2 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + csstype@3.2.3: {} + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-binarytree@1.0.2: {} + + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force-3d@3.0.6: + dependencies: + d3-binarytree: 1.0.2 + d3-dispatch: 3.0.1 + d3-octree: 1.1.0 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo-projection@4.0.0: + dependencies: + commander: 7.2.0 + d3-array: 3.2.4 + d3-geo: 3.1.1 + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-octree@1.1.0: {} + + d3-path@3.1.0: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-regression@1.3.10: {} + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + dagre@0.8.5: + dependencies: + graphlib: 2.1.8 + lodash: 4.18.1 + + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-eql@5.0.2: {} + + defer-to-connect@2.0.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + optional: true + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + optional: true + + dequal@2.0.3: {} + + detect-node@2.1.0: + optional: true + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + electron-to-chromium@1.5.412: {} + + electron-vite@2.3.0(vite@5.4.21(@types/node@20.19.43)): + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7) + cac: 6.7.14 + esbuild: 0.21.5 + magic-string: 0.30.21 + picocolors: 1.1.1 + vite: 5.4.21(@types/node@20.19.43) + transitivePeerDependencies: + - supports-color + + electron@30.5.1: + dependencies: + '@electron/get': 2.0.3 + '@types/node': 20.19.43 + extract-zip: 2.0.1 + transitivePeerDependencies: + - supports-color + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + entities@4.5.0: {} + + entities@8.0.0: {} + + env-paths@2.2.1: {} + + es-define-property@1.0.1: + optional: true + + es-errors@1.3.0: + optional: true + + es-module-lexer@1.7.0: {} + + es6-error@4.1.1: + optional: true + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: + optional: true + + escape-string-regexp@5.0.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + eventemitter3@5.0.4: {} + + expect-type@1.4.0: {} + + extend@3.0.2: {} + + extract-zip@2.0.1: + dependencies: + debug: 4.4.3 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-equals@5.4.1: {} + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + fecha@4.2.3: {} + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-stream@5.2.0: + dependencies: + pump: 3.0.4 + + gl-matrix@3.4.4: {} + + global-agent@3.0.0: + dependencies: + boolean: 3.2.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.8.5 + serialize-error: 7.0.1 + optional: true + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + optional: true + + gopd@1.2.0: + optional: true + + got@11.8.6: + dependencies: + '@sindresorhus/is': 4.6.0 + '@szmarczak/http-timer': 4.0.6 + '@types/cacheable-request': 6.0.3 + '@types/responselike': 1.0.3 + cacheable-lookup: 5.0.4 + cacheable-request: 7.0.4 + decompress-response: 6.0.0 + http2-wrapper: 1.0.3 + lowercase-keys: 2.0.0 + p-cancelable: 2.1.1 + responselike: 2.0.1 + + graceful-fs@4.2.11: {} + + graphlib@2.1.8: + dependencies: + lodash: 4.18.1 + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + optional: true + + highlight.js@11.11.2: {} + + hotkeys-js@4.0.5: {} + + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.1 + transitivePeerDependencies: + - '@noble/hashes' + + html2canvas@1.4.1: + dependencies: + css-line-break: 2.1.0 + text-segmentation: 1.0.3 + + http-cache-semantics@4.2.0: {} + + http2-wrapper@1.0.3: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + internmap@2.0.3: {} + + is-any-array@3.0.0: {} + + is-arrayish@0.3.4: {} + + is-plain-obj@4.1.0: {} + + is-potential-custom-element-name@1.0.1: {} + + js-tokens@4.0.0: {} + + jsdom@30.0.1: + dependencies: + '@asamuzakjp/css-color': 6.0.7 + '@asamuzakjp/dom-selector': 8.3.2 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.8(css-tree@3.2.1) + '@exodus/bytes': 1.15.1 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.5.2 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.2 + undici: 8.10.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 17.1.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-stringify-safe@5.0.1: + optional: true + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + katex@0.16.47: + dependencies: + commander: 8.3.0 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + linkify-it@5.0.2: + dependencies: + uc.micro: 2.1.0 + + linkifyjs@4.3.3: {} + + lodash@4.18.1: {} + + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + loupe@3.2.1: {} + + lowercase-keys@2.0.0: {} + + lowlight@3.3.0: + dependencies: + '@types/hast': 3.0.5 + devlop: 1.1.0 + highlight.js: 11.11.2 + + lru-cache@11.5.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + markdown-it-task-lists@2.1.1: {} + + markdown-it@14.3.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.2 + mdurl: 2.1.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + + markdown-table@3.0.4: {} + + matcher@3.0.0: + dependencies: + escape-string-regexp: 4.0.0 + optional: true + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-math@3.0.0: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + longest-streak: 3.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + unist-util-remove-position: 5.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mdn-data@2.27.1: {} + + mdurl@2.1.0: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-math@3.1.0: + dependencies: + '@types/katex': 0.16.8 + devlop: 1.1.0 + katex: 0.16.47 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + mimic-response@1.0.1: {} + + mimic-response@3.1.0: {} + + ml-array-max@2.0.0: + dependencies: + is-any-array: 3.0.0 + + ml-array-min@2.0.0: + dependencies: + is-any-array: 3.0.0 + + ml-array-rescale@2.0.0: + dependencies: + is-any-array: 3.0.0 + ml-array-max: 2.0.0 + ml-array-min: 2.0.0 + + ml-matrix@6.15.0: + dependencies: + is-any-array: 3.0.0 + ml-array-rescale: 2.0.0 + + ms@2.1.3: {} + + nanoid@3.3.18: {} + + node-releases@2.0.53: {} + + normalize-url@6.1.0: {} + + object-keys@1.1.1: + optional: true + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + orderedmap@2.1.1: {} + + p-cancelable@2.1.1: {} + + parse5@8.0.1: + dependencies: + entities: 8.0.0 + + pathe@1.1.2: {} + + pathval@2.0.1: {} + + pend@1.2.0: {} + + picocolors@1.1.1: {} + + postcss@8.5.26: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + progress@2.0.3: {} + + prosemirror-changeset@2.4.2: + dependencies: + prosemirror-transform: 1.12.0 + + prosemirror-commands@1.7.2: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-dropcursor@1.8.3: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + prosemirror-gapcursor@1.4.1: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-view: 1.42.2 + + prosemirror-history@1.5.0: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.1: + dependencies: + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-keymap@1.2.3: + dependencies: + prosemirror-state: 1.4.4 + w3c-keyname: 2.2.8 + + prosemirror-markdown@1.13.6: + dependencies: + '@types/markdown-it': 14.2.0 + markdown-it: 14.3.0 + prosemirror-model: 1.25.11 + + prosemirror-model@1.25.11: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + prosemirror-state@1.4.4: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + prosemirror-tables@1.8.5: + dependencies: + prosemirror-keymap: 1.2.3 + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + prosemirror-view: 1.42.2 + + prosemirror-transform@1.12.0: + dependencies: + prosemirror-model: 1.25.11 + + prosemirror-view@1.42.2: + dependencies: + prosemirror-model: 1.25.11 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode.js@2.3.1: {} + + punycode@2.3.1: {} + + quick-lru@5.1.1: {} + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-math@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-math: 3.0.0 + micromark-extension-math: 3.1.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + require-from-string@2.0.2: {} + + resolve-alpn@1.2.1: {} + + responselike@2.0.1: + dependencies: + lowercase-keys: 2.0.0 + + roarr@2.15.4: + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.4 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + optional: true + + rollup@4.62.5: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.62.5 + '@rollup/rollup-android-arm64': 4.62.5 + '@rollup/rollup-darwin-arm64': 4.62.5 + '@rollup/rollup-darwin-x64': 4.62.5 + '@rollup/rollup-freebsd-arm64': 4.62.5 + '@rollup/rollup-freebsd-x64': 4.62.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.5 + '@rollup/rollup-linux-arm-musleabihf': 4.62.5 + '@rollup/rollup-linux-arm64-gnu': 4.62.5 + '@rollup/rollup-linux-arm64-musl': 4.62.5 + '@rollup/rollup-linux-loong64-gnu': 4.62.5 + '@rollup/rollup-linux-loong64-musl': 4.62.5 + '@rollup/rollup-linux-ppc64-gnu': 4.62.5 + '@rollup/rollup-linux-ppc64-musl': 4.62.5 + '@rollup/rollup-linux-riscv64-gnu': 4.62.5 + '@rollup/rollup-linux-riscv64-musl': 4.62.5 + '@rollup/rollup-linux-s390x-gnu': 4.62.5 + '@rollup/rollup-linux-x64-gnu': 4.62.5 + '@rollup/rollup-linux-x64-musl': 4.62.5 + '@rollup/rollup-openbsd-x64': 4.62.5 + '@rollup/rollup-openharmony-arm64': 4.62.5 + '@rollup/rollup-win32-arm64-msvc': 4.62.5 + '@rollup/rollup-win32-ia32-msvc': 4.62.5 + '@rollup/rollup-win32-x64-gnu': 4.62.5 + '@rollup/rollup-win32-x64-msvc': 4.62.5 + fsevents: 2.3.3 + + rope-sequence@1.3.4: {} + + rw@1.3.3: {} + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + semver-compare@1.0.0: + optional: true + + semver@6.3.1: {} + + semver@7.8.5: + optional: true + + serialize-error@7.0.1: + dependencies: + type-fest: 0.13.1 + optional: true + + siginfo@2.0.0: {} + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + + source-map-js@1.2.1: {} + + sprintf-js@1.1.3: + optional: true + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + sumchecker@3.0.1: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + svg-path-parser@1.1.0: {} + + symbol-tree@3.2.4: {} + + text-segmentation@1.0.3: + dependencies: + utrie: 1.0.2 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + tiptap-markdown@0.9.0(@tiptap/core@3.30.2(@tiptap/pm@3.30.2)): + dependencies: + '@tiptap/core': 3.30.2(@tiptap/pm@3.30.2) + '@types/markdown-it': 13.0.9 + markdown-it: 14.3.0 + markdown-it-task-lists: 2.1.1 + prosemirror-markdown: 1.13.6 + + tldts-core@7.4.10: {} + + tldts@7.4.10: + dependencies: + tldts-core: 7.4.10 + + tough-cookie@6.0.2: + dependencies: + tldts: 7.4.10 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + trough@2.2.0: {} + + tslib@2.8.1: {} + + type-fest@0.13.1: + optional: true + + typescript@5.9.3: {} + + uc.micro@2.1.0: {} + + undici-types@6.21.0: {} + + undici@8.10.0: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + universalify@0.1.2: {} + + update-browserslist-db@1.3.1(browserslist@4.28.8): + dependencies: + browserslist: 4.28.8 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + + utrie@1.0.2: + dependencies: + base64-arraybuffer: 1.0.2 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite-node@2.1.9(@types/node@20.19.43): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@20.19.43) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.21(@types/node@20.19.43): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.26 + rollup: 4.62.5 + optionalDependencies: + '@types/node': 20.19.43 + fsevents: 2.3.3 + + vitest@2.1.9(@types/node@20.19.43)(jsdom@30.0.1): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@20.19.43)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.4.0 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@20.19.43) + vite-node: 2.1.9(@types/node@20.19.43) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.19.43 + jsdom: 30.0.1 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + w3c-keyname@2.2.8: {} + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + whatwg-url@17.1.0: + dependencies: + '@exodus/bytes': 1.15.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wrappy@1.0.2: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yallist@3.1.1: {} + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + zustand@5.0.15(@types/react@18.3.31)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)): + optionalDependencies: + '@types/react': 18.3.31 + react: 18.3.1 + use-sync-external-store: 1.6.0(react@18.3.1) + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..9cdf0cd --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +packages: + - apps/* + - packages/* +allowBuilds: + electron: true + esbuild: true diff --git a/tests/fixtures/roundtrip/README.md b/tests/fixtures/roundtrip/README.md new file mode 100644 index 0000000..4061680 --- /dev/null +++ b/tests/fixtures/roundtrip/README.md @@ -0,0 +1,35 @@ +# 往返 fixture 集(Round-trip Corpus) + +> 用途:狩夜人笔记编辑器 Markdown 往返回归基线。每个 .md 是一条输入语料;断言策略见《去风险验证任务书》A.5。 +> 判据:mdast 等价(输入 md 与往返结果 md 各自 parse 成 mdast 后 deep-equal),非字节相等。 + +## 文件清单与期望 + +| 文件 | 类别 | 期望 | +|---|---|---| +| emphasis-nested.md | 嵌套强调 | mdast-equal | +| escaping.md | 反斜杠转义 | mdast-equal | +| html.md | 裸 HTML | fenced-preserve(spike 决断) | +| whitespace.md | 空白/空行/硬换行 | mdast-equal | +| tables.md | GFM 表格与对齐 | mdast-equal | +| codeblock.md | 代码块/嵌套围栏/语言 | mdast-equal | +| tasklist.md | GFM 任务列表 | mdast-equal | +| math.md | 数学公式 inline/block | mdast-equal(依赖 remark-math) | +| links.md | 链接 inline/reference/autolink | mdast-equal | +| images.md | 图片与图片链接 | mdast-equal | +| blockquote.md | 嵌套引用 | mdast-equal | +| lists-nested.md | 有序/无序嵌套、松紧 | mdast-equal | +| headings.md | 一至六级标题 | mdast-equal | +| breaks.md | 硬换行 | mdast-equal | +| thematic-break.md | 分割线 | mdast-equal | +| combined.md | 综合混合文档 | mdast-equal | + +## 扩展规则 + +- 凡报告的往返丢字 bug,先转成新 fixture 再修,回归集只增不减。 +- 新增 fixture 在本表追加一行,并标注期望。 + +## 运行方式(spike/M2 落地后) + +- 框架:Vitest;遍历本目录所有 .md(排除 README),逐条 md→PM→md 后按 mdast 等价断言。 +- mdast 解析统一用:unified + remark-parse + remark-gfm + remark-math。 \ No newline at end of file diff --git a/tests/fixtures/roundtrip/blockquote.md b/tests/fixtures/roundtrip/blockquote.md new file mode 100644 index 0000000..972861d --- /dev/null +++ b/tests/fixtures/roundtrip/blockquote.md @@ -0,0 +1,12 @@ +# 引用 + +> 一层引用 + +> > 二层嵌套引用 + +> 引用内含列表: +> - 项一 +> - 项二 + +> 引用内含代码: +> `inline code` \ No newline at end of file diff --git a/tests/fixtures/roundtrip/breaks.md b/tests/fixtures/roundtrip/breaks.md new file mode 100644 index 0000000..ca88697 --- /dev/null +++ b/tests/fixtures/roundtrip/breaks.md @@ -0,0 +1,7 @@ +# 硬换行 + +双空格硬换行第一行 +第二行。 + +反斜杠硬换行第一行\ +第二行。 \ No newline at end of file diff --git a/tests/fixtures/roundtrip/codeblock.md b/tests/fixtures/roundtrip/codeblock.md new file mode 100644 index 0000000..961233e --- /dev/null +++ b/tests/fixtures/roundtrip/codeblock.md @@ -0,0 +1,21 @@ +# 代码块 + +```js +const x = 1; // 行内注释 +``` + +无语言代码块: + +``` +plain text +``` + +嵌套围栏:外层 4 反引号包裹内层 3 反引号: + +```` +```js +inner code +``` +```` + +行内代码 `含 * 星号不解析`。 \ No newline at end of file diff --git a/tests/fixtures/roundtrip/combined.md b/tests/fixtures/roundtrip/combined.md new file mode 100644 index 0000000..cea3f24 --- /dev/null +++ b/tests/fixtures/roundtrip/combined.md @@ -0,0 +1,29 @@ +# 综合文档 + +一段含 **粗**、*斜*、`code` 与 [链接](https://example.com) 的引言。 + +## 列表与任务 + +- [x] 项一 + - 子项 +- [ ] 项二 + +## 代码 + +```ts +function add(a: number, b: number) { + return a + b; +} +``` + +## 表格 + +| A | B | +|---|---| +| 1 | 2 | + +> 引用收尾 + +--- + +$$E=mc^2$$ \ No newline at end of file diff --git a/tests/fixtures/roundtrip/emphasis-nested.md b/tests/fixtures/roundtrip/emphasis-nested.md new file mode 100644 index 0000000..44a8738 --- /dev/null +++ b/tests/fixtures/roundtrip/emphasis-nested.md @@ -0,0 +1,13 @@ +# 嵌套强调 + +**外层粗体 *内层斜体* 收尾** + +***粗斜合一*** + +*斜体中含 **粗体** 嵌套* + +**粗体中含 *斜体* 嵌套** + +两端星号与下划线混用:_下划线斜体_ 与 *星号斜体*。 + +不配对的孤立星号 * 不影响其余文本。 \ No newline at end of file diff --git a/tests/fixtures/roundtrip/escaping.md b/tests/fixtures/roundtrip/escaping.md new file mode 100644 index 0000000..b26cf35 --- /dev/null +++ b/tests/fixtures/roundtrip/escaping.md @@ -0,0 +1,13 @@ +# 反斜杠转义 + +这里 \*不是斜体\* 而是 literal 星号。 + +这里 \`不是代码\` 而是 literal 反引号。 + +反斜杠本身:\\ 输出一个反斜杠。 + +反斜杠转义井号:\# 不是标题。 + +反斜杠转义句点:1\. 不是有序列表。 + +反斜杠转义管道:a \| b 在表格外应保留。 \ No newline at end of file diff --git a/tests/fixtures/roundtrip/headings.md b/tests/fixtures/roundtrip/headings.md new file mode 100644 index 0000000..69bfc68 --- /dev/null +++ b/tests/fixtures/roundtrip/headings.md @@ -0,0 +1,15 @@ +# 一级标题 + +## 二级标题 + +### 三级标题 + +#### 四级标题 + +##### 五级标题 + +###### 六级标题 + +带尾随井号的标题 ## + +> 仅 ATX 标题;Setext 是否支持由 spike 决断。 \ No newline at end of file diff --git a/tests/fixtures/roundtrip/html.md b/tests/fixtures/roundtrip/html.md new file mode 100644 index 0000000..f65a601 --- /dev/null +++ b/tests/fixtures/roundtrip/html.md @@ -0,0 +1,9 @@ +# 裸 HTML 处置 + +这是一段含 HTML 的文本。 + +
块级 HTML
+ + + +> 期望:schema 禁用裸 HTML;spike 决断为转义为文本、代码围栏保留或透传。本 fixture 用于锁定该决断的往返行为。 \ No newline at end of file diff --git a/tests/fixtures/roundtrip/images.md b/tests/fixtures/roundtrip/images.md new file mode 100644 index 0000000..b95b85d --- /dev/null +++ b/tests/fixtures/roundtrip/images.md @@ -0,0 +1,7 @@ +# 图片 + +![alt 文本](https://example.com/x.png "标题") + +无 title 图片 ![alt](https://example.com/y.png) + +图片链接 [![alt](https://example.com/thumb.png)](https://example.com/full) \ No newline at end of file diff --git a/tests/fixtures/roundtrip/links.md b/tests/fixtures/roundtrip/links.md new file mode 100644 index 0000000..221f845 --- /dev/null +++ b/tests/fixtures/roundtrip/links.md @@ -0,0 +1,11 @@ +# 链接 + +行内链接 [文本](https://example.com "标题")。 + +无 title 链接 [文本](https://example.com)。 + +引用链接 [文本][ref] 与定义 [ref]: https://example.com "标题"。 + +自动链接 。 + +含特殊字符的链接 [a/b](https://example.com/a/b)。 \ No newline at end of file diff --git a/tests/fixtures/roundtrip/lists-nested.md b/tests/fixtures/roundtrip/lists-nested.md new file mode 100644 index 0000000..5cc84d6 --- /dev/null +++ b/tests/fixtures/roundtrip/lists-nested.md @@ -0,0 +1,18 @@ +# 嵌套列表 + +- 无序一 + - 子项 a + - 孙项 i + - 子项 b +- 无序二 + +1. 有序一 + 1. 子序 1 + 2. 子序 2 +2. 有序二 + +松散列表(项间空行): + +- 项一 + +- 项二 \ No newline at end of file diff --git a/tests/fixtures/roundtrip/math.md b/tests/fixtures/roundtrip/math.md new file mode 100644 index 0000000..ca610ad --- /dev/null +++ b/tests/fixtures/roundtrip/math.md @@ -0,0 +1,11 @@ +# 数学公式 + +行内公式 $E = mc^2$ 嵌于文本。 + +块级公式: + +$$ +\int_0^1 x^2 \, dx = \frac{1}{3} +$$ + +> 依赖 remark-math;spike 确认序列化保留 $ 与 $$ 围栏。 \ No newline at end of file diff --git a/tests/fixtures/roundtrip/tables.md b/tests/fixtures/roundtrip/tables.md new file mode 100644 index 0000000..32d9d65 --- /dev/null +++ b/tests/fixtures/roundtrip/tables.md @@ -0,0 +1,13 @@ +# GFM 表格 + +| 左对齐 | 居中 | 右对齐 | +|:---|:---:|---:| +| a | b | c | +| d | e | f | + +含特殊字符的单元格: + +| 符号 | 值 | +|---|---| +| pipe \| 转义 | 1 | +| *斜* 单元 | 2 | \ No newline at end of file diff --git a/tests/fixtures/roundtrip/tasklist.md b/tests/fixtures/roundtrip/tasklist.md new file mode 100644 index 0000000..ba74a73 --- /dev/null +++ b/tests/fixtures/roundtrip/tasklist.md @@ -0,0 +1,10 @@ +# 任务列表 + +- [x] 已完成项 +- [ ] 未完成项 +- [ ] 另一个未完成 + +有序任务: + +1. [x] 第一 +2. [ ] 第二 \ No newline at end of file diff --git a/tests/fixtures/roundtrip/thematic-break.md b/tests/fixtures/roundtrip/thematic-break.md new file mode 100644 index 0000000..c87297e --- /dev/null +++ b/tests/fixtures/roundtrip/thematic-break.md @@ -0,0 +1,7 @@ +# 分割线 + +--- + +*** + +___ \ No newline at end of file diff --git a/tests/fixtures/roundtrip/whitespace.md b/tests/fixtures/roundtrip/whitespace.md new file mode 100644 index 0000000..b897502 --- /dev/null +++ b/tests/fixtures/roundtrip/whitespace.md @@ -0,0 +1,12 @@ +# 空白与空行 + +一段文本。 + + + +连续多个空行后的段落应保留为单一段落分隔。 + +尾部带两个空格的行 +应为硬换行延续到本行。 + +行首缩进三空格不影响段落。 \ No newline at end of file diff --git a/tests/unit/atomic-write.test.ts b/tests/unit/atomic-write.test.ts new file mode 100644 index 0000000..e3457a9 --- /dev/null +++ b/tests/unit/atomic-write.test.ts @@ -0,0 +1,41 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import nodePath from 'node:path'; +import os from 'node:os'; +import { promises as fs } from 'node:fs'; +import { writeFileAtomic, withFileLock } from '../../apps/desktop/src/main/safe/atomic-write'; + +const dir = nodePath.join(os.tmpdir(), 'dsh-atomic-test'); +afterEach(async () => { await fs.rm(dir, { recursive: true, force: true }); }); + +describe('writeFileAtomic (FR-S5)', () => { + it('写入内容并回读一致', async () => { + const p = nodePath.join(dir, 'a.md'); + await writeFileAtomic(p, '# 标题\n内容'); + expect(await fs.readFile(p, 'utf8')).toBe('# 标题\n内容'); + }); + it('不残留临时文件', async () => { + const p = nodePath.join(dir, 'b.md'); + await writeFileAtomic(p, 'x'); + const files = await fs.readdir(dir); + expect(files).toEqual(['b.md']); + }); + it('覆盖已有文件', async () => { + const p = nodePath.join(dir, 'c.md'); + await writeFileAtomic(p, 'old'); + await writeFileAtomic(p, 'new'); + expect(await fs.readFile(p, 'utf8')).toBe('new'); + }); +}); + +describe('withFileLock 并发安全', () => { + it('同文件请求串行执行', async () => { + const p = nodePath.join(dir, 'lock.md'); + const order: number[] = []; + const t = (id: number, delay: number) => withFileLock(p, async () => { + order.push(id); + await new Promise(r => setTimeout(r, delay)); + }); + await Promise.all([t(1, 20), t(2, 1), t(3, 1)]); + expect(order).toEqual([1, 2, 3]); + }); +}); diff --git a/tests/unit/doc-props.test.ts b/tests/unit/doc-props.test.ts new file mode 100644 index 0000000..b5386dd --- /dev/null +++ b/tests/unit/doc-props.test.ts @@ -0,0 +1,52 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import nodePath from 'node:path'; +import os from 'node:os'; +import { promises as fs } from 'node:fs'; +import { FileService } from '../../apps/desktop/src/main/services/file-service'; + +const tmp = nodePath.join(os.tmpdir(), 'dsh-m3-test'); +beforeEach(async () => { await fs.rm(tmp, { recursive: true, force: true }); await fs.mkdir(tmp, { recursive: true }); }); +afterEach(async () => { await fs.rm(tmp, { recursive: true, force: true }); }); + +describe('M3 文档属性契约(FR-M3/M4/M5/M6)', () => { + it('标签增删后落盘往返一致', async () => { + const s = new FileService(tmp); + await s.create('', 'd', false); + await s.writeDoc({ path: 'd.md', meta: { tags: ['x', 'y'] }, body: '正文' }); + let r = await s.readDoc('d.md'); + expect(r.meta.tags).toEqual(['x', 'y']); + // 删一个 + await s.writeDoc({ path: 'd.md', meta: { tags: ['x'] }, body: '正文' }); + r = await s.readDoc('d.md'); + expect(r.meta.tags).toEqual(['x']); + }); + + it('星标切换落盘:true 写入、false 不写', async () => { + const s = new FileService(tmp); + await s.create('', 'd', false); + await s.writeDoc({ path: 'd.md', meta: { star: true }, body: 'b' }); + expect((await s.readDoc('d.md')).meta.star).toBe(true); + // 取消星标 -> star 字段不落盘 + await s.writeDoc({ path: 'd.md', meta: { star: false }, body: 'b' }); + const r = await s.readDoc('d.md'); + expect(r.meta.star).toBeFalsy(); + // 文件内不应出现 star: 字样 + const raw = await fs.readFile(nodePath.join(tmp, 'd.md'), 'utf8'); + expect(raw).not.toContain('star'); + }); + + it('关联链接落盘往返', async () => { + const s = new FileService(tmp); + await s.create('', 'd', false); + await s.writeDoc({ path: 'd.md', meta: { links: ['a/b.md', 'c.md'] }, body: 'b' }); + expect((await s.readDoc('d.md')).meta.links).toEqual(['a/b.md', 'c.md']); + }); + + it('文档信息:标题缺省回退文件名', async () => { + const s = new FileService(tmp); + await s.create('', '我的笔记', false); + await s.writeDoc({ path: '我的笔记.md', meta: {}, body: '内容' }); + const r = await s.readDoc('我的笔记.md'); + expect(r.meta.title).toBeUndefined(); // 缺省不落盘,UI 侧回退文件名 + }); +}); diff --git a/tests/unit/effect-engine.test.ts b/tests/unit/effect-engine.test.ts new file mode 100644 index 0000000..a3ccdc0 --- /dev/null +++ b/tests/unit/effect-engine.test.ts @@ -0,0 +1,35 @@ +/** @vitest-environment jsdom */ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { EffectEngine } from '../../apps/desktop/src/renderer/features/effects/effect-engine'; + +// stub requestAnimationFrame +beforeEach(() => { + globalThis.requestAnimationFrame = ((cb: FrameRequestCallback) => { return 0 as any; }) as any; + globalThis.cancelAnimationFrame = (() => {}) as any; +}); + +describe('M6 EffectEngine 配置与降级 (FR-A11)', () => { + it('setType/setColor 改变状态', () => { + const e = new EffectEngine(); + e.setType('particle'); e.setColor('#ff0000'); + e.disable(); + // disable 后 type 为 off + expect(e.isActive()).toBe(false); + }); + + it('attach 到 canvas 不抛错', () => { + const canvas = document.createElement('canvas'); + document.body.appendChild(canvas); + const e = new EffectEngine(); + expect(() => e.attach(canvas)).not.toThrow(); + e.detach(); + }); + + it('detach 后 isActive 为 false', () => { + const canvas = document.createElement('canvas'); + const e = new EffectEngine(); + e.attach(canvas); + e.detach(); + expect(e.isActive()).toBe(false); + }); +}); diff --git a/tests/unit/file-service.test.ts b/tests/unit/file-service.test.ts new file mode 100644 index 0000000..e7b7ca3 --- /dev/null +++ b/tests/unit/file-service.test.ts @@ -0,0 +1,144 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import nodePath from 'node:path'; +import os from 'node:os'; +import { promises as fs } from 'node:fs'; +import { FileService } from '../../apps/desktop/src/main/services/file-service'; +import { ConfigStore } from '../../apps/desktop/src/main/services/config-store'; + +const tmp = nodePath.join(os.tmpdir(), 'dsh-file-service-test'); + +beforeEach(async () => { await fs.rm(tmp, { recursive: true, force: true }); await fs.mkdir(tmp, { recursive: true }); }); +afterEach(async () => { await fs.rm(tmp, { recursive: true, force: true }); }); + +describe('FileService CRUD (FR-F)', () => { + it('create 文档生成 .md', async () => { + const s = new FileService(tmp); + const { path } = await s.create('', '笔记', false); + expect(path).toBe('笔记.md'); + expect(await fs.readFile(nodePath.join(tmp, '笔记.md'), 'utf8')).toBe(''); + }); + + it('create 文件夹', async () => { + const s = new FileService(tmp); + const { path } = await s.create('', '目录', true); + expect(path).toBe('目录'); + expect((await fs.stat(nodePath.join(tmp, '目录'))).isDirectory()).toBe(true); + }); + + it('rename 文档自动补 .md 并去重', async () => { + const s = new FileService(tmp); + await s.create('', 'a', false); + await s.create('', 'b', false); + const { path } = await s.rename('b.md', 'a'); + expect(path).toBe('a-1.md'); + }); + + it('move 到目标目录', async () => { + const s = new FileService(tmp); + await s.create('', 'dir', true); + await s.create('', 'x', false); + const { path } = await s.move('x.md', 'dir'); + expect(path).toBe('dir/x.md'); + }); + + it('move 禁止移入自身内部', async () => { + const s = new FileService(tmp); + await s.create('', 'dir', true); + await s.create('', 'dir/sub', true); + await expect(s.move('dir', 'dir/sub')).rejects.toThrow(); + }); + + it('delete 文档与递归文件夹', async () => { + const s = new FileService(tmp); + await s.create('', 'f', true); + await s.create('f', 'inner', false); + await s.delete('f', true); + await expect(fs.stat(nodePath.join(tmp, 'f'))).rejects.toThrow(); + }); + + it('listTree 排序:文件夹在前、忽略隐藏目录', async () => { + const s = new FileService(tmp); + await s.create('', 'zdoc', false); + await s.create('', 'afolder', true); + await s.create('', '.hidden', true); + await s.create('afolder', 'in', false); + const tree = await s.listTree(); + const folders = tree.filter(n => n.type === 'folder').map(n => n.name); + const docs = tree.filter(n => n.type === 'doc' && n.parent === '').map(n => n.name); + expect(folders[0]).toBe('afolder'); + expect(docs).toContain('zdoc'); + expect(folders).not.toContain('.hidden'); + }); + + it('importFiles 复制并去重,返回成功与失败', async () => { + const s = new FileService(tmp); + const ext = nodePath.join(tmp, '_external'); + await fs.mkdir(ext, { recursive: true }); + await fs.writeFile(nodePath.join(ext, 'p.md'), 'hi'); + const r = await s.importFiles([nodePath.join(ext, 'p.md'), nodePath.join(ext, 'nope.md')], ''); + expect(r.ok).toEqual(['p.md']); + expect(r.failed.length).toBe(1); + }); +}); + +describe('FileService 金标准:writeDoc → readDoc 往返字节一致 (FR-S5/FR-M1)', () => { + it('纯正文往返', async () => { + const s = new FileService(tmp); + await s.create('', 'doc', false); + const body = '# 标题\n\n段落含 **粗** 与 `code`。'; + await s.writeDoc({ path: 'doc.md', meta: {}, body }); + const r = await s.readDoc('doc.md'); + expect(r.body).toBe(body); + expect(r.meta).toEqual({}); + }); + + it('含 frontmatter 往返', async () => { + const s = new FileService(tmp); + await s.create('', 'doc', false); + const meta = { title: '标题', tags: ['a','b'], star: true, links: ['x/y.md'] }; + const body = '# 正文\n内容'; + await s.writeDoc({ path: 'doc.md', meta, body }); + const r = await s.readDoc('doc.md'); + expect(r.meta).toEqual(meta); + expect(r.body).toBe(body); + }); + + it('重开后内容仍一致(模拟重启)', async () => { + const body = '持久化内容\n- 项一\n- 项二'; + await (new FileService(tmp)).create('', 'persist', false); + await (new FileService(tmp)).writeDoc({ path: 'persist.md', meta: { star: true }, body }); + // 新实例(模拟重启)读取 + const r = await (new FileService(tmp)).readDoc('persist.md'); + expect(r.body).toBe(body); + expect(r.meta.star).toBe(true); + }); +}); + +describe('ConfigStore', () => { + it('默认空 meta', async () => { + const cs = new ConfigStore(tmp); + const m = await cs.getVaultMeta(); + expect(m.currentRoot).toBe(''); + expect(m.recents).toEqual([]); + }); + + it('setCurrentRoot 记录最近库并去重截断', async () => { + const cs = new ConfigStore(tmp); + await cs.setCurrentRoot('C:/a'); + await cs.setCurrentRoot('C:/b'); + await cs.setCurrentRoot('C:/a'); + const m1 = await cs.getVaultMeta(); + expect(m1.currentRoot).toBe('C:/a'); + expect(m1.recents).toEqual(['C:/a', 'C:/b']); + // 超出上限截断 + for (let i = 0; i < 8; i++) await cs.setCurrentRoot('C:/n' + i); + const m2 = await cs.getVaultMeta(); + expect(m2.recents.length).toBeLessThanOrEqual(5); + }); + + it('重启后持久化(新实例读取)', async () => { + await (new ConfigStore(tmp)).setDefaultRoot('C:/default'); + const m = await (new ConfigStore(tmp)).getVaultMeta(); + expect(m.defaultRoot).toBe('C:/default'); + }); +}); diff --git a/tests/unit/frontmatter-codec.test.ts b/tests/unit/frontmatter-codec.test.ts new file mode 100644 index 0000000..4e9f5d9 --- /dev/null +++ b/tests/unit/frontmatter-codec.test.ts @@ -0,0 +1,52 @@ +import { describe, it, expect } from 'vitest'; +import { splitFrontmatter, joinFrontmatter } from '../../apps/desktop/src/main/safe/frontmatter-codec'; +import type { DocMeta } from '@hunter/shared'; + +describe('splitFrontmatter', () => { + it('无 frontmatter 的纯 Markdown', () => { + const { meta, body } = splitFrontmatter('# 标题\n正文'); + expect(meta).toEqual({}); + expect(body).toBe('# 标题\n正文'); + }); + it('解析四字段', () => { + const raw = '---\ntitle: 我的标题\ntags: [a, b]\nstar: true\nlinks: [x/y.md]\n---\n# 正文'; + const { meta, body } = splitFrontmatter(raw); + expect(meta).toEqual({ title: '我的标题', tags: ['a','b'], star: true, links: ['x/y.md'] }); + expect(body).toBe('# 正文'); + }); + it('带引号的值', () => { + const { meta } = splitFrontmatter('---\ntitle: "含: 冒号"\n---\n'); + expect(meta.title).toBe('含: 冒号'); + }); + it('空数组与 false 不解析为有值', () => { + const { meta } = splitFrontmatter('---\ntags: []\nstar: false\n---\n'); + expect(meta.tags).toEqual([]); + expect(meta.star).toBe(false); + }); +}); + +describe('joinFrontmatter (FR-M2 精简)', () => { + it('空 meta 不写围栏', () => { + expect(joinFrontmatter({}, '# 正文')).toBe('# 正文'); + }); + it('默认值不落盘:star:false / 空数组', () => { + const out = joinFrontmatter({ star: false, tags: [], links: [] }, 'body'); + expect(out).toBe('body'); + }); + it('star:true 落盘', () => { + const out = joinFrontmatter({ star: true }, 'body'); + expect(out).toContain('star: true'); + }); + it('含特殊字符的标题加引号', () => { + const out = joinFrontmatter({ title: 'a:b#c' }, 'body'); + expect(out).toContain('title: "a:b#c"'); + }); + it('往返:解析后再序列化保持等价', () => { + const raw = '---\ntitle: T\ntags: [x, y]\nstar: true\n---\n# H\n正文'; + const { meta, body } = splitFrontmatter(raw); + const out = joinFrontmatter(meta, body); + const again = splitFrontmatter(out); + expect(again.meta).toEqual(meta); + expect(again.body).toBe(body); + }); +}); diff --git a/tests/unit/graph-builder.test.ts b/tests/unit/graph-builder.test.ts new file mode 100644 index 0000000..02cc65a --- /dev/null +++ b/tests/unit/graph-builder.test.ts @@ -0,0 +1,48 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import nodePath from 'node:path'; +import os from 'node:os'; +import { promises as fs } from 'node:fs'; +import { FileService } from '../../apps/desktop/src/main/services/file-service'; +import { buildGraphData } from '../../apps/desktop/src/main/services/graph-builder'; +import type { TreeNode } from '@hunter/shared'; + +const tmp = nodePath.join(os.tmpdir(), 'dsh-graph-test'); +beforeEach(async () => { await fs.rm(tmp, { recursive: true, force: true }); await fs.mkdir(tmp, { recursive: true }); }); +afterEach(async () => { await fs.rm(tmp, { recursive: true, force: true }); }); + +describe('M7 buildGraphData (FR-G1/G2/G3)', () => { + it('节点=文档,按顶层分组着色', async () => { + const s = new FileService(tmp); + await s.create('', 'grpA', true); + await s.create('', 'grpB', true); + await s.create('grpA', 'd1', false); + await s.create('grpB', 'd2', false); + await s.writeDoc({ path: 'grpA/d1.md', meta: { title: '文档一' }, body: 'x' }); + await s.writeDoc({ path: 'grpB/d2.md', meta: { star: true }, body: 'y' }); + const tree = await s.listTree(); + const g = await buildGraphData(tmp, tree); + expect(g.nodes.length).toBe(2); + const n1 = g.nodes.find(n => n.id === 'grpA/d1.md'); + expect(n1?.group).toBe('grpA'); + expect(n1?.title).toBe('文档一'); + const n2 = g.nodes.find(n => n.id === 'grpB/d2.md'); + expect(n2?.star).toBe(true); + expect(n1?.color).not.toBe(n2?.color); // 不同组不同色 + }); + + it('边=关联链接,仅目标存在且 .md', async () => { + const s = new FileService(tmp); + await s.create('', 'a', false); + await s.create('', 'b', false); + await s.create('', 'c', false); + await s.writeDoc({ path: 'a.md', meta: { links: ['b.md', 'c.md', 'nope.md'] }, body: '1' }); + await s.writeDoc({ path: 'b.md', meta: {}, body: '2' }); + await s.writeDoc({ path: 'c.md', meta: {}, body: '3' }); + const tree = await s.listTree(); + const g = await buildGraphData(tmp, tree); + expect(g.edges.length).toBe(2); // b.md, c.md 存在;nope.md 忽略 + expect(g.edges.some(e => e.target === 'b.md')).toBe(true); + expect(g.edges.some(e => e.target === 'c.md')).toBe(true); + expect(g.edges.some(e => e.target === 'nope.md')).toBe(false); + }); +}); diff --git a/tests/unit/keymap-service.test.ts b/tests/unit/keymap-service.test.ts new file mode 100644 index 0000000..69be4e8 --- /dev/null +++ b/tests/unit/keymap-service.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { KeymapService } from '../../apps/desktop/src/renderer/features/keymap/keymap-service'; + +describe('M9 KeymapService (FR-K4 冲突检测 / K5 恢复默认)', () => { + let km: KeymapService; + beforeEach(() => { km = new KeymapService(); }); + + it('注册与读取', () => { + km.register({ id: 'bold', scope: 'editor', combo: 'Ctrl+B', defaultCombo: 'Ctrl+B' }); + expect(km.getAll().length).toBe(1); + }); + + it('冲突检测:同 combo 不同 id 报冲突', () => { + km.register({ id: 'bold', scope: 'editor', combo: 'Ctrl+B', defaultCombo: 'Ctrl+B' }); + km.register({ id: 'other', scope: 'editor', combo: 'Ctrl+B', defaultCombo: 'Ctrl+X' }); + expect(km.conflictCount()).toBe(1); + expect(km.conflicts()['Ctrl+B']).toEqual(['bold', 'other']); + }); + + it('无冲突时 conflictCount=0', () => { + km.register({ id: 'bold', scope: 'editor', combo: 'Ctrl+B', defaultCombo: 'Ctrl+B' }); + km.register({ id: 'italic', scope: 'editor', combo: 'Ctrl+I', defaultCombo: 'Ctrl+I' }); + expect(km.conflictCount()).toBe(0); + }); + + it('setCombo 修改键位', () => { + km.register({ id: 'bold', scope: 'editor', combo: 'Ctrl+B', defaultCombo: 'Ctrl+B' }); + km.setCombo('bold', 'Ctrl+Shift+B'); + expect(km.getAll()[0].combo).toBe('Ctrl+Shift+B'); + }); + + it('resetAll 恢复默认', () => { + km.register({ id: 'bold', scope: 'editor', combo: 'Ctrl+Shift+B', defaultCombo: 'Ctrl+B' }); + km.resetAll(); + expect(km.getAll()[0].combo).toBe('Ctrl+B'); + }); + + it('作用域裁决:编辑器聚焦时 editor 优先', () => { + km.register({ id: 'find', scope: 'global', combo: 'Ctrl+F', defaultCombo: 'Ctrl+F' }); + km.register({ id: 'efind', scope: 'editor', combo: 'Ctrl+F', defaultCombo: 'Ctrl+F' }); + expect(km.resolve('Ctrl+F', true)?.id).toBe('efind'); + expect(km.resolve('Ctrl+F', false)?.id).toBe('find'); + }); +}); diff --git a/tests/unit/migration-service.test.ts b/tests/unit/migration-service.test.ts new file mode 100644 index 0000000..80d25ac --- /dev/null +++ b/tests/unit/migration-service.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import nodePath from 'node:path'; +import os from 'node:os'; +import { promises as fs } from 'node:fs'; +import { MigrationService } from '../../apps/desktop/src/main/services/migration-service'; + +const tmp = nodePath.join(os.tmpdir(), 'dsh-mig-test'); +beforeEach(async () => { await fs.rm(tmp, { recursive: true, force: true }); await fs.mkdir(tmp, { recursive: true }); }); +afterEach(async () => { await fs.rm(tmp, { recursive: true, force: true }); }); + +describe('M10 MigrationService 幂等保护 (FR-D2/D3)', () => { + it('未迁移时 isMigrated=false', async () => { + const m = new MigrationService(tmp); + expect(await m.isMigrated()).toBe(false); + }); + + it('shouldMigrate:旧数据存在 + 未迁移 + 新库空 → true', async () => { + const oldDir = nodePath.join(tmp, 'old'); const vault = nodePath.join(tmp, 'vault'); + await fs.mkdir(oldDir, { recursive: true }); await fs.mkdir(vault, { recursive: true }); + await fs.writeFile(nodePath.join(oldDir, 'data.json'), '{}'); + const m = new MigrationService(tmp); + expect(await m.shouldMigrate(oldDir, vault)).toBe(true); + }); + + it('shouldMigrate:新库非空 → false', async () => { + const oldDir = nodePath.join(tmp, 'old'); const vault = nodePath.join(tmp, 'vault'); + await fs.mkdir(oldDir, { recursive: true }); await fs.mkdir(vault, { recursive: true }); + await fs.writeFile(nodePath.join(vault, 'existing.md'), 'x'); + const m = new MigrationService(tmp); + expect(await m.shouldMigrate(oldDir, vault)).toBe(false); + }); + + it('迁移后标记,再次 shouldMigrate=false(幂等)', async () => { + const oldDir = nodePath.join(tmp, 'old'); const vault = nodePath.join(tmp, 'vault'); + await fs.mkdir(oldDir, { recursive: true }); await fs.mkdir(vault, { recursive: true }); + await fs.writeFile(nodePath.join(oldDir, 'd.json'), '{}'); + const m = new MigrationService(tmp); + await m.migrate(oldDir, vault); + expect(await m.isMigrated()).toBe(true); + expect(await m.shouldMigrate(oldDir, vault)).toBe(false); + }); + + it('旧数据不存在 → false', async () => { + const m = new MigrationService(tmp); + expect(await m.shouldMigrate(nodePath.join(tmp, 'nope'), tmp)).toBe(false); + }); +}); diff --git a/tests/unit/roundtrip.test.ts b/tests/unit/roundtrip.test.ts new file mode 100644 index 0000000..5536627 --- /dev/null +++ b/tests/unit/roundtrip.test.ts @@ -0,0 +1,42 @@ +import { describe, it, expect } from 'vitest'; +import { promises as fs } from 'node:fs'; +import nodePath from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { unified } from 'unified'; +import remarkParse from 'remark-parse'; +import remarkStringify from 'remark-stringify'; +import remarkGfm from 'remark-gfm'; +import remarkMath from 'remark-math'; + +const __dirname = nodePath.dirname(fileURLToPath(import.meta.url)); +const fxDir = nodePath.join(__dirname, '..', 'fixtures', 'roundtrip'); + +const parse = (md: string) => unified().use(remarkParse).use(remarkGfm).use(remarkMath).parse(md); +const roundtrip = (md: string) => + unified().use(remarkParse).use(remarkGfm).use(remarkMath) + .use(remarkStringify).use(remarkGfm).use(remarkMath).processSync(md).toString(); + +function normalize(node: any): any { + if (node === null || typeof node !== 'object') return node; + if (Array.isArray(node)) return node.map(normalize); + const { position, ...rest } = node; + for (const k of Object.keys(rest)) rest[k] = normalize(rest[k]); + return rest; +} + +const CASES = ['emphasis-nested','escaping','tables','codeblock','tasklist','headings','thematic-break','blockquote','lists-nested','whitespace','breaks','links','images','math']; + +describe('Markdown 往返 fixture 集(mdast 等价)', () => { + it('fixture 数量足够', async () => { + const names = (await fs.readdir(fxDir)).filter(f => f.endsWith('.md') && f !== 'README.md'); + expect(names.length).toBeGreaterThanOrEqual(15); + }); + + for (const name of CASES) { + it(name + ' 往返 mdast 等价', async () => { + const md = await fs.readFile(nodePath.join(fxDir, name + '.md'), 'utf8'); + const mdPrime = roundtrip(md); + expect(normalize(parse(mdPrime))).toEqual(normalize(parse(md))); + }); + } +}); diff --git a/tests/unit/safe-path.test.ts b/tests/unit/safe-path.test.ts new file mode 100644 index 0000000..fe4a84d --- /dev/null +++ b/tests/unit/safe-path.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect } from 'vitest'; +import nodePath from 'node:path'; +import os from 'node:os'; +import { promises as fs } from 'node:fs'; +import { assertInsideVault, sanitizeName, dedupeName, IllegalPathError } from '../../apps/desktop/src/main/safe/safe-path'; + +describe('sanitizeName (FR-F13)', () => { + it('替换非法字符为下划线', () => { + expect(sanitizeName('a:bd?')).toBe('a_b_c_d_'); + }); + it('空名回退为未命名', () => { + expect(sanitizeName(' ')).toBe('未命名'); + expect(sanitizeName('')).toBe('未命名'); + }); + it('保留正常名', () => { + expect(sanitizeName('我的笔记')).toBe('我的笔记'); + }); +}); + +describe('assertInsideVault (FR-F15)', () => { + const root = nodePath.join(os.tmpdir(), 'dsh-vault-test'); + it('接受库内相对路径', () => { + expect(assertInsideVault(root, 'a/b.md')).toBe(nodePath.resolve(root, 'a/b.md')); + }); + it('拒绝绝对路径', () => { + expect(() => assertInsideVault(root, nodePath.resolve(root, 'x'))).toThrow(IllegalPathError); + }); + it('拒绝盘符前缀', () => { + expect(() => assertInsideVault(root, 'C:/x')).toThrow(IllegalPathError); + }); + it('拒绝 .. 穿越', () => { + expect(() => assertInsideVault(root, '../escape.md')).toThrow(IllegalPathError); + expect(() => assertInsideVault(root, 'a/../../escape.md')).toThrow(IllegalPathError); + }); +}); + +describe('dedupeName (FR-F14)', () => { + const root = nodePath.join(os.tmpdir(), 'dsh-vault-dedupe'); + it('重名追加序号', async () => { + await fs.rm(root, { recursive: true, force: true }); + await fs.mkdir(nodePath.join(root, 'd'), { recursive: true }); + await fs.writeFile(nodePath.join(root, 'd', 'n.md'), ''); + expect(await dedupeName(root, 'd', 'n.md')).toBe('n-1.md'); + await fs.writeFile(nodePath.join(root, 'd', 'n-1.md'), ''); + expect(await dedupeName(root, 'd', 'n.md')).toBe('n-2.md'); + }); + it('不冲突时原样返回', async () => { + expect(await dedupeName(root, 'd', 'unique.md')).toBe('unique.md'); + }); +}); diff --git a/tests/unit/search-service.test.ts b/tests/unit/search-service.test.ts new file mode 100644 index 0000000..46f8548 --- /dev/null +++ b/tests/unit/search-service.test.ts @@ -0,0 +1,58 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import nodePath from 'node:path'; +import os from 'node:os'; +import { promises as fs } from 'node:fs'; +import { FileService } from '../../apps/desktop/src/main/services/file-service'; +import { SearchService } from '../../apps/desktop/src/main/services/search-service'; + +const tmp = nodePath.join(os.tmpdir(), 'dsh-search-test'); +beforeEach(async () => { await fs.rm(tmp, { recursive: true, force: true }); await fs.mkdir(tmp, { recursive: true }); }); +afterEach(async () => { await fs.rm(tmp, { recursive: true, force: true }); }); + +async function mk(root: string) { + const fs2 = new FileService(root); + const ss = new SearchService(root, fs2); + return { fs2, ss }; +} + +describe('M4 SearchService (FR-R)', () => { + it('全文搜索命中标题与正文,返回摘要', async () => { + const { fs2, ss } = await mk(tmp); + await fs2.writeDoc({ path: 'a.md', meta: { title: 'Markdown 指南' }, body: '介绍 Markdown 的用法\n关键字段落' }); + const hits = await ss.search('关键字'); + expect(hits.length).toBe(1); + expect(hits[0].title).toBe('Markdown 指南'); + expect(hits[0].snippet).toContain('关键字'); + }); + + it('大小写不敏感', async () => { + const { fs2, ss } = await mk(tmp); + await fs2.writeDoc({ path: 'b.md', meta: {}, body: 'Hello World' }); + expect((await ss.search('hello')).length).toBe(1); + expect((await ss.search('HELLO')).length).toBe(1); + }); + + it('空查询返回空', async () => { + const { fs2, ss } = await mk(tmp); + await fs2.writeDoc({ path: 'c.md', meta: {}, body: 'x' }); + expect(await ss.search('')).toEqual([]); + expect(await ss.search(' ')).toEqual([]); + }); + + it('标签汇总:按字母排序、计数', async () => { + const { fs2, ss } = await mk(tmp); + await fs2.writeDoc({ path: 'a.md', meta: { tags: ['z', 'a'] }, body: '1' }); + await fs2.writeDoc({ path: 'b.md', meta: { tags: ['a'] }, body: '2' }); + const stats = await ss.tagStats(); + expect(stats.map(s => s.tag)).toEqual(['a', 'z']); + expect(stats.find(s => s.tag === 'a')?.count).toBe(2); + expect(stats.find(s => s.tag === 'z')?.count).toBe(1); + }); + + it('搜索结果上限 100', async () => { + const { fs2, ss } = await mk(tmp); + for (let i = 0; i < 120; i++) await fs2.writeDoc({ path: `d${i}.md`, meta: {}, body: 'common' }); + const hits = await ss.search('common'); + expect(hits.length).toBeLessThanOrEqual(100); + }); +}); diff --git a/tests/unit/smart-color.test.ts b/tests/unit/smart-color.test.ts new file mode 100644 index 0000000..64d592a --- /dev/null +++ b/tests/unit/smart-color.test.ts @@ -0,0 +1,38 @@ +import { describe, it, expect } from 'vitest'; +import { analyzeImage } from '../../apps/desktop/src/main/safe/smart-color'; + +describe('M5 智能取色 analyzeImage (FR-A9)', () => { + it('空像素返回默认深色', () => { + const r = analyzeImage({ pixels: [] }); + expect(r.mode).toBe('dark'); + expect(r.accent).toMatch(/^#[0-9a-f]{6}$/); + }); + + it('暗图 → 深色模式', () => { + const dark: [number,number,number][] = Array(50).fill([10, 15, 25]); + expect(analyzeImage({ pixels: dark }).mode).toBe('dark'); + }); + + it('亮图 → 浅色模式', () => { + const light: [number,number,number][] = Array(50).fill([240, 235, 220]); + expect(analyzeImage({ pixels: light }).mode).toBe('light'); + }); + + it('强调色色相约束在冷色/紫红区间 [180,330]', () => { + const warm: [number,number,number][] = Array(50).fill([200, 60, 30]); // 暖橙 + const r = analyzeImage({ pixels: warm }); + expect(r.accentHue).toBeGreaterThanOrEqual(180); + expect(r.accentHue).toBeLessThanOrEqual(330); + }); + + it('输出 accent 为合法 hex', () => { + const r = analyzeImage({ pixels: Array(20).fill([30, 80, 200]) }); + expect(r.accent).toMatch(/^#[0-9a-f]{6}$/); + }); + + it('不透明度建议在合理范围', () => { + const r = analyzeImage({ pixels: Array(20).fill([100,100,100]) }); + expect(r.suggestedBgOpacity).toBeGreaterThan(0); + expect(r.suggestedBgOpacity).toBeLessThanOrEqual(0.8); + }); +}); diff --git a/tests/unit/tab-store.test.ts b/tests/unit/tab-store.test.ts new file mode 100644 index 0000000..0f88404 --- /dev/null +++ b/tests/unit/tab-store.test.ts @@ -0,0 +1,47 @@ +/** @vitest-environment jsdom */ +import { describe, it, expect, beforeEach } from 'vitest'; +import { useTabs } from '../../apps/desktop/src/renderer/features/tabs/tab-store'; + +beforeEach(() => { localStorage.clear(); useTabs.setState({ tabs: [], activeId: null }); }); + +describe('M8 多标签与会话恢复 (FR-T)', () => { + it('openTab 新建标签,重复路径激活已有', () => { + useTabs.getState().openTab('a.md'); + useTabs.getState().openTab('b.md'); + expect(useTabs.getState().tabs.length).toBe(2); + const aId = useTabs.getState().tabs[0].id; + useTabs.getState().openTab('a.md'); + expect(useTabs.getState().tabs.length).toBe(2); // 不重复 + expect(useTabs.getState().activeId).toBe(aId); + }); + + it('togglePin 固定标签排前', () => { + useTabs.getState().openTab('a.md'); + useTabs.getState().openTab('b.md'); + useTabs.getState().togglePin(useTabs.getState().tabs[1].id); // 固定 b + expect(useTabs.getState().tabs[0].path).toBe('b.md'); + expect(useTabs.getState().tabs[0].pinned).toBe(true); + }); + + it('closeTab 切到相邻', () => { + useTabs.getState().openTab('a.md'); + useTabs.getState().openTab('b.md'); + useTabs.getState().openTab('c.md'); + const bId = useTabs.getState().tabs[1].id; + useTabs.getState().setActive(bId); + useTabs.getState().closeTab(bId); + expect(useTabs.getState().tabs.length).toBe(2); + expect(useTabs.getState().activeId).not.toBe(bId); + }); + + it('会话恢复:persist 后 restore 恢复标签列表与激活页', () => { + useTabs.getState().openTab('x.md'); + useTabs.getState().openTab('y.md'); + useTabs.getState().persist(); + // 模拟重启:清空后 restore + useTabs.setState({ tabs: [], activeId: null }); + useTabs.getState().restore(); + expect(useTabs.getState().tabs.length).toBe(2); + expect(useTabs.getState().tabs.some(t => t.path === 'x.md')).toBe(true); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..828b84a --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'vitest/config'; +import { resolve } from 'node:path'; + +export default defineConfig({ + resolve: { + alias: { '@hunter/shared': resolve(__dirname, 'packages/shared/src/index.ts') }, + modulesDirectories: [ + 'node_modules', + 'apps/desktop/node_modules', + 'apps/desktop/node_modules/.pnpm', + ] + }, + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + globals: false + } +});