diff --git a/.vscode/settings.json b/.vscode/settings.json index 17448e0d7..ade2c5a09 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,6 @@ { // The linter in the CI is quite strict, so running `cargo fmt` on save is probably a good idea! "editor.formatOnSave": true, - "files.autoSave": "onFocusChange", "rust-analyzer.checkOnSave.command": "clippy", "search.exclude": { "**/.git": true, @@ -14,23 +13,18 @@ "javascript", "javascriptreact", "typescript", - "typescriptreact" + "typescriptreact", + "svelte", + "svlete.ts" ], "typescript.preferences.importModuleSpecifierEnding": "minimal", "eslint.alwaysShowStatus": true, "eslint.format.enable": true, "eslint.lintTask.enable": true, - "eslint.quiet": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, - "eslint.workingDirectories": [ - "./data-browser", - "./react", - "./lib", - "./cli", - "./svelte" - ], + "eslint.workingDirectories": [{ "directory": "browser" }], "typescript.preferences.preferTypeOnlyAutoImports": true, "rustTestExplorer.rootCargoManifestFilePath": "./Cargo.toml", // This won't work in multi-root workspaces, could be fixed by using a rust-analyzer.toml once there is some more documentation on that. diff --git a/Cargo.lock b/Cargo.lock index bd2b93578..3d80476b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -539,6 +539,17 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -578,6 +589,7 @@ version = "0.40.0" dependencies = [ "assert_cmd", "atomic_lib", + "base64 0.21.7", "clap", "colored", "dirs", @@ -641,6 +653,7 @@ dependencies = [ "urlencoding", "walkdir", "webp", + "yrs", ] [[package]] @@ -679,6 +692,7 @@ dependencies = [ "ureq", "url", "urlencoding", + "yrs", ] [[package]] @@ -1092,6 +1106,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console" version = "0.15.11" @@ -1326,6 +1349,20 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core 0.9.11", +] + [[package]] name = "deranged" version = "0.4.0" @@ -1593,6 +1630,27 @@ dependencies = [ "str-buf", ] +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "exr" version = "1.73.0" @@ -1619,6 +1677,9 @@ name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +dependencies = [ + "getrandom 0.2.16", +] [[package]] name = "fd-lock" @@ -1827,8 +1888,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1928,6 +1991,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -3262,6 +3331,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4ed3a7192fa19f5f48f99871f2755047fabefd7f222f12a1df1773796a102" +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.11.2" @@ -4674,6 +4749,15 @@ dependencies = [ "parking_lot 0.11.2", ] +[[package]] +name = "smallstr" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862077b1e764f04c251fe82a2ef562fd78d7cadaeb072ca7c2bcaf7217b1ff3b" +dependencies = [ + "smallvec", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -6219,6 +6303,24 @@ dependencies = [ "synstructure", ] +[[package]] +name = "yrs" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f904a99678a852d7cbc6958c94087f739c10cfb19642635951219c525a5fdb89" +dependencies = [ + "arc-swap", + "async-lock", + "async-trait", + "dashmap", + "fastrand", + "serde", + "serde_json", + "smallstr", + "smallvec", + "thiserror 2.0.16", +] + [[package]] name = "zerocopy" version = "0.8.26" diff --git a/browser/.eslintrc.cjs b/browser/.eslintrc.cjs deleted file mode 100644 index 9709c8ae5..000000000 --- a/browser/.eslintrc.cjs +++ /dev/null @@ -1,119 +0,0 @@ -module.exports = { - root: true, - ignorePatterns: ['./.eslint.cjs', '**/vite.config.ts'], - extends: [ - 'eslint:recommended', - 'plugin:prettier/recommended', - "plugin:import/recommended", - "plugin:import/typescript", - 'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react - 'plugin:react/jsx-runtime', - 'plugin:@typescript-eslint/eslint-recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin - 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin - 'prettier', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier - 'plugin:jsx-a11y/recommended', - ], - parser: '@typescript-eslint/parser', // Specifies the ESLint parser - env: { - browser: true, - es6: true, - node: true, - }, - parserOptions: { - ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features - sourceType: 'module', // Allows for the use of imports - ecmaFeatures: { - jsx: true, // Allows for the parsing of JSX - arrowFunctions: true, - }, - // Next two lines enable deeper TS type checking - // https://typescript-eslint.io/docs/linting/typed-linting/ - tsconfigRootDir: __dirname, - project: [ - 'lib/tsconfig.json', - 'cli/tsconfig.json', - 'react/tsconfig.json', - 'data-browser/tsconfig.json', - 'e2e/tsconfig.json', - 'create-template/tsconfig.json', - ], - }, - plugins: ['react', '@typescript-eslint', 'prettier', 'react-hooks', 'jsx-a11y'], - settings: { - react: { - version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use - }, - 'import/resolver': { - node: { - extensions: ['.js', '.jsx', '.ts', '.tsx'], - paths: ['./src'], - }, - }, - }, - rules: { - // Existing rules - 'comma-dangle': 'off', // https://eslint.org/docs/rules/comma-dangle - 'function-paren-newline': 'off', // https://eslint.org/docs/rules/function-paren-newline - 'global-require': 'off', // https://eslint.org/docs/rules/global-require - // Turn this on when we have migrated all import paths to use `.js` - // "import/extensions": ["error", "ignorePackages"], - "import/no-unresolved": "off", - 'import/no-dynamic-require': 'off', // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-dynamic-require.md - 'import/no-named-as-default': 'off', - 'no-inner-declarations': 'off', // https://eslint.org/docs/rules/no-inner-declarations// New rules - 'class-methods-use-this': 'off', - //Allow underscores https://stackoverflow.com/questions/57802057/eslint-configuring-no-unused-vars-for-typescript - '@typescript-eslint/no-unused-vars': ['error', { 'varsIgnorePattern': '^_', 'argsIgnorePattern': '^_' }], - 'react-hooks/exhaustive-deps': 'warn', - // 'no-unused-vars': ["error", { "ie": "^_" }], - 'import/prefer-default-export': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/no-var-requires': 'off', - '@typescript-eslint/ban-ts-comment': 'off', - '@typescript-eslint/no-explicit-any': 'error', - "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks - 'no-console': ['error', { allow: ['error', 'warn'] }], - "react/prop-types": "off", - "padding-line-between-statements": [ - "error", - { - "blankLine": "always", - "next": "return", - "prev": "*" - }, - { - "blankLine": "always", - "next": "export", - "prev": "*" - }, - { - "blankLine": "always", - "next": "multiline-block-like", - "prev": "*" - }, - { - "blankLine": "always", - "next": "*", - "prev": "multiline-block-like" - }, - { - "blankLine": "any", - "next": "export", - "prev": "export" - } - ], - "@typescript-eslint/explicit-member-accessibility": "error", - "eqeqeq": "error", - "no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTaggedTemplates": true }], - "jsx-a11y/no-autofocus": "off", - // This has a bug, so we use typescripts version - "no-shadow": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "no-eval": "error", - "no-implied-eval": "error", - "@typescript-eslint/no-shadow": ["error"], - "@typescript-eslint/member-ordering": "error", - "react/no-unknown-property": ["error", { "ignore": ["about"] }], - 'react-hooks/react-compiler': 'error', - }, -}; diff --git a/browser/.gitignore b/browser/.gitignore index c89ec246b..94d171d9c 100644 --- a/browser/.gitignore +++ b/browser/.gitignore @@ -34,3 +34,7 @@ data-browser/coverage **/tomic-svelte-**.tgz **/tomic-cli-**.tgz **/tomic-create-template-**.tgz + +data-browser/src/locales/.wuchale +data-browser/src/locales/data.js + diff --git a/browser/.prettierignore b/browser/.prettierignore index 264e8a7d8..c0fdc9ece 100644 --- a/browser/.prettierignore +++ b/browser/.prettierignore @@ -1,8 +1,9 @@ build -**/node_modules -**/dist +**/node_modules/** +**/dist/** **/package.json **/yarn.lock **/package-lock.json **/.eslintrc.js **/tsconfig.json +**/.svelte-kit/** diff --git a/browser/.prettierrc.json b/browser/.prettierrc.json index 3177c3a8e..80ab8b54c 100644 --- a/browser/.prettierrc.json +++ b/browser/.prettierrc.json @@ -1,4 +1,7 @@ { + "plugins": [ + "prettier-plugin-svelte" + ], "semi": true, "printWidth": 80, "tabWidth": 2, @@ -7,6 +10,5 @@ "useTabs": false, "arrowParens": "avoid", "jsxSingleQuote": true, - "trailingComma": "all", - "jsdocParser": true + "trailingComma": "all" } diff --git a/browser/CHANGELOG.md b/browser/CHANGELOG.md index aefeafd62..7923d76f6 100644 --- a/browser/CHANGELOG.md +++ b/browser/CHANGELOG.md @@ -29,6 +29,10 @@ This changelog covers all five packages, as they are (for now) updated as a whol - Fix types masquerading as esm module in cjs build. - `store.search()` now handles multiple values for the same property correctly. - [#1077](https://github.com/atomicdata-dev/atomic-server/issues/1077) Fix bug where resource.new would not be set back to true when saving fails. +- Added `ResourceEvents.LoadingChange` event on `Resource` to listen for changes to the loading state of the resource. +- Added `resource.stable` property to `Resource` to get a stable reference to the resource, even when it is proxied. +- Added `resource.merge()` method to merge a resource into another resource while preserving local changes on the current resource. +- `store.addResources()` now merges incoming resources with resources already present in the store. - SEMI BREAKING CHANGE: When using generated types by cli, @tomic/lib now requires them to be generated by @tomic/cli v0.41.0 or above. - BREAKING CHANGE: The `StoreEvents.ResourceRemoved` event callback now only receives the subject of the resource instead of the resource itself. @@ -37,6 +41,7 @@ This changelog covers all five packages, as they are (for now) updated as a whol - BREAKING CHANGE: `useCanWrite` now only returns a boolean. There is no longer a message returned. - BREAKING CHANGE: `useCanWrite` does not take an agent as argument any more and only checks the agent set in the store. If you need to explicitly check a different agent, use `await resource.canWrite(agent)`. - BREAKING CHANGE: `useDebounce` and `useDebouncedCallback` are no longer exported. +- BREAKING CHANGE: @tomic/react now requires React 19.2.0 or above. - Added `useDebouncedSave` hook. - Add a cjs build. diff --git a/browser/cli/package.json b/browser/cli/package.json index e1e33a1c8..288530490 100644 --- a/browser/cli/package.json +++ b/browser/cli/package.json @@ -13,7 +13,7 @@ "@tomic/lib": "workspace:*", "chalk": "^5.3.0", "prettier": "3.0.3", - "typescript": "^5.6.3" + "typescript": "^5.9.3" }, "description": "Generate types from Atomic Data ontologies", "license": "MIT", @@ -23,10 +23,11 @@ }, "scripts": { "build": "tsc", - "lint": "eslint ./src --ext .js,.ts", + "lint": "eslint ./src --ext .js,.ts && pnpm prettier-check", "lint-fix": "eslint ./src --ext .js,.ts --fix", "prepublishOnly": "pnpm run build && pnpm run lint && pnpm run lint-package", "lint-package": "pnpm dlx publint", + "prettier-check": "prettier --check ./src", "watch": "tsc --build --watch", "start": "pnpm watch", "tsc": "pnpm exec tsc --build", diff --git a/browser/cli/src/DatatypeToTSTypeMap.ts b/browser/cli/src/DatatypeToTSTypeMap.ts index 83bfd65ae..ea69df25b 100644 --- a/browser/cli/src/DatatypeToTSTypeMap.ts +++ b/browser/cli/src/DatatypeToTSTypeMap.ts @@ -13,5 +13,6 @@ export const DatatypeToTSTypeMap = { [Datatype.MARKDOWN]: 'string', [Datatype.URI]: 'string', [Datatype.JSON]: 'JSONValue', + [Datatype.YDOC]: 'never', [Datatype.UNKNOWN]: 'JSONValue', }; diff --git a/browser/create-template/package.json b/browser/create-template/package.json index ee821b97b..10eb7543e 100644 --- a/browser/create-template/package.json +++ b/browser/create-template/package.json @@ -16,7 +16,7 @@ }, "devDependencies": { "@types/node": "^20.17.0", - "typescript": "^5.6.3" + "typescript": "^5.9.3" }, "description": "Generate templates using Atomic Data", "license": "MIT", @@ -26,14 +26,15 @@ }, "scripts": { "build": "tsc", - "lint": "eslint ./src --ext .js,.ts", + "lint": "eslint ./src --ext .js,.ts && pnpm prettier-check", "lint-fix": "eslint ./src --ext .js,.ts --fix", "prepublishOnly": "pnpm run build && pnpm run lint && pnpm run lint-package", "lint-package": "pnpm dlx publint", "watch": "tsc --build --watch", "start": "pnpm exec tsc --build --watch", "tsc": "pnpm exec tsc --build", - "typecheck": "pnpm exec tsc --noEmit" + "typecheck": "pnpm exec tsc --noEmit", + "prettier-check": "prettier --check ./src" }, "bin": { "create-template": "./bin/src/index.js" diff --git a/browser/create-template/src/createOutputFolder.ts b/browser/create-template/src/createOutputFolder.ts index 8dc9ee42f..de25f35f2 100644 --- a/browser/create-template/src/createOutputFolder.ts +++ b/browser/create-template/src/createOutputFolder.ts @@ -17,7 +17,7 @@ export async function createOutputFolder(outputDir: string): Promise { try { fs.mkdirSync(outputDir); - } catch (error) { + } catch (e) { console.error(`Failed to create directory: ${outputDir}`); process.exit(1); } diff --git a/browser/create-template/src/index.ts b/browser/create-template/src/index.ts index 2c36d34de..530895208 100644 --- a/browser/create-template/src/index.ts +++ b/browser/create-template/src/index.ts @@ -1,5 +1,4 @@ #!/usr/bin/env node -/* eslint-disable no-console */ import path from 'node:path'; import { parseArgs } from 'node:util'; import { copyTemplate } from './copyTemplate.js'; diff --git a/browser/create-template/templates/nextjs-site/package.json b/browser/create-template/templates/nextjs-site/package.json index f24ba39f4..8d886dd58 100644 --- a/browser/create-template/templates/nextjs-site/package.json +++ b/browser/create-template/templates/nextjs-site/package.json @@ -17,8 +17,8 @@ "gray-matter": "^4.0.3", "modern-css-reset": "^1.4.0", "next": "15.0.4", - "react": "19.0.0", - "react-dom": "19.0.0", + "react": "19.2.0", + "react-dom": "19.2.0", "remark": "^15.0.1", "remark-html": "^16.0.1", "zod": "^3.23.8" diff --git a/browser/data-browser/.npmrc b/browser/data-browser/.npmrc new file mode 100644 index 000000000..150cdf725 --- /dev/null +++ b/browser/data-browser/.npmrc @@ -0,0 +1 @@ +public-hoist-pattern[]=pdfjs-dist diff --git a/browser/data-browser/.prettierignore b/browser/data-browser/.prettierignore new file mode 100644 index 000000000..70372308a --- /dev/null +++ b/browser/data-browser/.prettierignore @@ -0,0 +1 @@ +/src/locales/** diff --git a/browser/data-browser/package.json b/browser/data-browser/package.json index ba251d8d2..45dc16515 100644 --- a/browser/data-browser/package.json +++ b/browser/data-browser/package.json @@ -16,27 +16,40 @@ "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", "@emoji-mart/react": "^1.1.1", - "@emotion/is-prop-valid": "^1.3.1", + "@emotion/is-prop-valid": "^1.4.0", + "@floating-ui/dom": "^1.7.4", "@modelcontextprotocol/sdk": "^1.13.3", "@oddbird/css-anchor-positioning": "^0.6.1", "@openrouter/ai-sdk-provider": "^1.2.0", - "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-popover": "^1.1.15", "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-tabs": "^1.1.1", "@tanstack/react-router": "^1.95.1", - "@tiptap/extension-file-handler": "^2.25.0", - "@tiptap/extension-image": "^2.11.7", - "@tiptap/extension-link": "^2.11.7", - "@tiptap/extension-mention": "^2.11.7", - "@tiptap/extension-placeholder": "^2.11.7", - "@tiptap/extension-typography": "^2.11.7", - "@tiptap/pm": "^2.11.7", - "@tiptap/react": "^2.11.7", - "@tiptap/starter-kit": "^2.11.7", - "@tiptap/suggestion": "^2.11.7", + "@tiptap/core": "^3.7.2", + "@tiptap/extension-collaboration": "^3.7.2", + "@tiptap/extension-collaboration-caret": "^3.7.2", + "@tiptap/extension-drag-handle-react": "^3.7.2", + "@tiptap/extension-file-handler": "^3.7.2", + "@tiptap/extension-image": "^3.7.2", + "@tiptap/extension-link": "^3.7.2", + "@tiptap/extension-list": "^3.7.2", + "@tiptap/extension-mention": "^3.7.2", + "@tiptap/extension-placeholder": "^3.7.2", + "@tiptap/extension-table": "^3.10.5", + "@tiptap/extension-text-align": "^3.7.2", + "@tiptap/extension-text-style": "^3.7.2", + "@tiptap/extension-typography": "^3.7.2", + "@tiptap/markdown": "^3.7.2", + "@tiptap/pm": "^3.7.2", + "@tiptap/react": "^3.7.2", + "@tiptap/starter-kit": "^3.7.2", + "@tiptap/suggestion": "^3.7.2", + "@tiptap/y-tiptap": "^3.0.0", "@tomic/react": "workspace:*", "@uiw/codemirror-theme-github": "^4.24.1", "@uiw/react-codemirror": "^4.24.1", + "@wuchale/jsx": "^0.9.4", + "@wuchale/vite-plugin": "^0.15.3", "ai": "^5.0.29", "clsx": "^2.1.1", "downshift": "^9.0.9", @@ -45,48 +58,47 @@ "polished": "^4.3.1", "prismjs": "^1.29.0", "quick-score": "^0.2.0", - "react": "^19.0.0", + "react": "^19.2.0", "react-colorful": "^5.6.1", - "react-dom": "^19.0.0", + "react-dom": "^19.2.0", "react-dropzone": "^11.7.1", "react-hot-toast": "^2.4.1", "react-hotkeys-hook": "^3.4.7", "react-icons": "^4.12.0", "react-intersection-observer": "^9.13.1", - "react-is": "^19.0.0", + "react-is": "^19.2.0", "react-markdown": "^9.0.3", - "react-pdf": "^9.1.1", + "react-pdf": "^10.2.0", "react-virtualized-auto-sizer": "^1.0.24", "react-window": "^1.8.10", "reactflow": "^11.11.4", "remark-gfm": "^4.0.0", "styled-components": "^6.1.19", "stylis": "4.3.0", - "tippy.js": "^6.3.7", "tiptap-markdown": "^0.8.10", - "wuchale": "^0.16.5", - "@wuchale/jsx": "^0.7.4", - "@wuchale/vite-plugin": "^0.14.6", + "wuchale": "^0.18.3", + "y-protocols": "^1.0.6", + "yjs": "^13.6.27", "zod": "^4.1.5" }, "devDependencies": { "@tanstack/router-devtools": "^1.95.1", "@types/prismjs": "^1.26.5", - "@types/react": "^19.0.0", - "@types/react-dom": "^19.0.0", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", "@types/react-window": "^1.8.8", - "@vitejs/plugin-react": "^4.3.4", - "babel-plugin-react-compiler": "19.1.0-rc.2", + "@vitejs/plugin-react": "^5.0.4", + "babel-plugin-react-compiler": "1.0.0", "babel-plugin-styled-components": "^2.1.4", "csstype": "^3.1.3", "gh-pages": "^5.0.0", "lint-staged": "^10.5.4", "types-wm": "^1.1.0", - "typescript": "^5.6.3", - "vite": "^5.4.10", + "typescript": "^5.9.3", + "vite": "^7.1.12", "vite-plugin-prismjs": "^0.0.11", - "vite-plugin-pwa": "^0.20.5", - "vite-plugin-webfont-dl": "^3.9.5" + "vite-plugin-pwa": "^1.1.0", + "vite-plugin-webfont-dl": "^3.11.1" }, "type": "module", "homepage": "https://atomicdata.dev/", @@ -99,12 +111,13 @@ "name": "@tomic/data-browser", "private": true, "repository": { - "url": "https://github.com/atomicdata-dev/atomic-data-browser/" + "url": "https://github.com/atomicdata-dev/atomic-server" }, "scripts": { "build": "vite build", - "lint": "eslint ./src --ext .js,.jsx,.ts,.tsx", + "lint": "eslint --quiet ./src --ext .js,.jsx,.ts,.tsx && pnpm prettier-check ./src", "lint-fix": "eslint ./src --ext .js,.jsx,.ts,.tsx --fix", + "prettier-check": "prettier --check ./src", "preview": "vite preview", "start": "vite", "test": "vitest run", diff --git a/browser/data-browser/src/App.tsx b/browser/data-browser/src/App.tsx index 27843dfba..0913939e6 100644 --- a/browser/data-browser/src/App.tsx +++ b/browser/data-browser/src/App.tsx @@ -1,4 +1,4 @@ -import { StoreContext, Store } from '@tomic/react'; +import { StoreContext, Store, enableYjs } from '@tomic/react'; import { isDev } from './config'; import { registerHandlers } from './handlers'; @@ -33,6 +33,8 @@ const store = new Store({ serverUrl, }); +await enableYjs(); + store.parseMetaTags(); declare global { diff --git a/browser/data-browser/src/Providers.tsx b/browser/data-browser/src/Providers.tsx index 276e1052f..45a6fedf9 100644 --- a/browser/data-browser/src/Providers.tsx +++ b/browser/data-browser/src/Providers.tsx @@ -21,6 +21,7 @@ import { Toaster } from './components/Toaster'; import { McpServersProvider } from './components/AI/MCP/useMcpServers'; import { AISettingsContextProvider } from '@components/AI/AISettingsContext'; import { LocaleProvider } from '@components/LocaleContext'; +import { CustomContextItemsProvider } from './components/ResourceContextMenu'; // Setup bugsnag for error handling, but only if there's an API key const ErrBoundary = window.bugsnagApiKey @@ -66,10 +67,12 @@ export const Providers: React.FC = ({ children }) => { - - - {children} - + + + + {children} + + diff --git a/browser/data-browser/src/chunks/AI/RealAIChat.tsx b/browser/data-browser/src/chunks/AI/RealAIChat.tsx index c7aaffa40..10bdee9d0 100644 --- a/browser/data-browser/src/chunks/AI/RealAIChat.tsx +++ b/browser/data-browser/src/chunks/AI/RealAIChat.tsx @@ -35,7 +35,7 @@ import { MessageContextItem } from './MessageContextItem'; import { useProcessMessages } from './useProcessMessages'; import { NoKeyOverlay } from './NoKeyOverlay'; import { useOpenRouterModels } from './useOpenRouterModels'; -import type { MentionItem } from '@chunks/MarkdownEditor/AIChatInput/types'; +import type { MentionItem } from '@chunks/RTE/AIChatInput/types'; import { useChat } from '@ai-sdk/react'; import { useClientOnlyTransport } from './ClientOnlyTransport'; import { useGenerativeData } from './useGenerativeData'; @@ -43,7 +43,7 @@ import { FollowUpPrompt } from './FollowUpPrompt'; import { useAISettings } from '@components/AI/AISettingsContext'; const AIChatInput = React.lazy( - () => import('@chunks/MarkdownEditor/AIChatInput/AsyncAIChatInput'), + () => import('@chunks/RTE/AIChatInput/AsyncAIChatInput'), ); interface RealAIChatProps { diff --git a/browser/data-browser/src/chunks/CodeEditor/AsyncJSONEditor.tsx b/browser/data-browser/src/chunks/CodeEditor/AsyncJSONEditor.tsx index 089e0ee05..b24a86200 100644 --- a/browser/data-browser/src/chunks/CodeEditor/AsyncJSONEditor.tsx +++ b/browser/data-browser/src/chunks/CodeEditor/AsyncJSONEditor.tsx @@ -56,7 +56,7 @@ const AsyncJSONEditor: React.FC = ({ ); // Wrap jsonParseLinter so we can tap into diagnostics - const validationLinter = useCallback(() => { + const validationLinter = useMemo(() => { const delegate = jsonParseLinter(); return (view: EditorView) => { @@ -85,8 +85,7 @@ const AsyncJSONEditor: React.FC = ({ }, [onValidationChange, required]); const extensions = useMemo( - // eslint-disable-next-line react-hooks/react-compiler - () => [json(), linter(validationLinter())], + () => [json(), linter(validationLinter)], [validationLinter], ); diff --git a/browser/data-browser/src/chunks/CurrencyPicker/CurrencyPicker.tsx b/browser/data-browser/src/chunks/CurrencyPicker/CurrencyPicker.tsx index 199cf1127..f181ae65e 100644 --- a/browser/data-browser/src/chunks/CurrencyPicker/CurrencyPicker.tsx +++ b/browser/data-browser/src/chunks/CurrencyPicker/CurrencyPicker.tsx @@ -35,7 +35,7 @@ const CurrencyPicker: FC = ({ resource }) => { } // We only want to run this effect once. Maybe we should find a better way to do this. - // eslint-disable-next-line react-hooks/react-compiler, react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( diff --git a/browser/data-browser/src/chunks/MarkdownEditor/BubbleMenu.tsx b/browser/data-browser/src/chunks/MarkdownEditor/BubbleMenu.tsx deleted file mode 100644 index ac3a8c076..000000000 --- a/browser/data-browser/src/chunks/MarkdownEditor/BubbleMenu.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { BubbleMenu as TipTapBubbleMenu } from '@tiptap/react'; -import { - FaBold, - FaCode, - FaItalic, - FaLink, - FaQuoteLeft, - FaStrikethrough, -} from 'react-icons/fa6'; -import { styled } from 'styled-components'; -import * as RadixPopover from '@radix-ui/react-popover'; -import { Row } from '../../components/Row'; - -import { Popover } from '../../components/Popover'; -import { useState } from 'react'; -import { transparentize } from 'polished'; -import { EditLinkForm } from './EditLinkForm'; -import { useTipTapEditor } from './TiptapContext'; -import { ToggleButton } from './ToggleButton'; -import { NodeSelectMenu } from './NodeSelectMenu'; - -export function BubbleMenu(): React.JSX.Element { - const editor = useTipTapEditor(); - const [linkMenuOpen, setLinkMenuOpen] = useState(false); - - if (!editor) { - return <>; - } - - return ( - - - - editor.chain().focus().toggleBold().run()} - disabled={!editor.can().chain().focus().toggleBold().run()} - type='button' - > - - - editor.chain().focus().toggleItalic().run()} - disabled={!editor.can().chain().focus().toggleItalic().run()} - type='button' - > - - - editor.chain().focus().toggleStrike().run()} - disabled={!editor.can().chain().focus().toggleStrike().run()} - type='button' - > - - - editor.chain().focus().toggleBlockquote().run()} - disabled={!editor.can().chain().focus().toggleBlockquote().run()} - type='button' - > - - - editor.chain().focus().toggleCode().run()} - disabled={!editor.can().chain().focus().toggleCode().run()} - type='button' - > - - - - - - } - > - setLinkMenuOpen(false)} /> - - - - ); -} - -const BubbleMenuInner = styled(Row)` - background-color: ${p => p.theme.colors.bg}; - border-radius: ${p => p.theme.radius}; - padding: ${p => p.theme.size(2)}; - box-shadow: ${p => p.theme.boxShadowSoft}; - - @supports (backdrop-filter: blur(5px)) { - background-color: ${p => transparentize(0.15, p.theme.colors.bg)}; - backdrop-filter: blur(5px); - } -`; - -const StyledPopover = styled(Popover)` - background-color: ${p => p.theme.colors.bg}; - backdrop-filter: blur(5px); - padding: ${p => p.theme.size()}; - border-radius: ${p => p.theme.radius}; - - @supports (backdrop-filter: blur(5px)) { - background-color: ${p => transparentize(0.15, p.theme.colors.bg)}; - backdrop-filter: blur(5px); - } -`; diff --git a/browser/data-browser/src/chunks/MarkdownEditor/EditorWrapperBase.tsx b/browser/data-browser/src/chunks/MarkdownEditor/EditorWrapperBase.tsx deleted file mode 100644 index cee32aacb..000000000 --- a/browser/data-browser/src/chunks/MarkdownEditor/EditorWrapperBase.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { styled } from 'styled-components'; - -export const EditorWrapperBase = styled.div<{ hideEditor: boolean }>` - position: relative; - background-color: ${p => p.theme.colors.bg}; - - &:not(:focus-within) { - & .tiptap p.is-editor-empty:first-child::before { - color: ${p => p.theme.colors.textLight}; - content: attr(data-placeholder); - float: left; - height: 0; - pointer-events: none; - } - } - - & .tiptap { - display: ${p => (p.hideEditor ? 'none' : 'block')}; - outline: none; - width: min(100%, 75ch); - - .tiptap-image { - max-width: 100%; - height: auto; - } - - pre { - padding: 0.75rem 1rem; - background-color: ${p => p.theme.colors.bg1}; - border-radius: ${p => p.theme.radius}; - font-family: monospace; - - code { - white-space: pre; - color: inherit; - padding: 0; - background: none; - font-size: 0.8rem; - } - } - - blockquote { - margin-inline-start: 0; - border-inline-start: 3px solid ${p => p.theme.colors.textLight2}; - color: ${p => p.theme.colors.textLight}; - padding-inline-start: 1rem; - } - } -`; diff --git a/browser/data-browser/src/chunks/PDFViewer/index.tsx b/browser/data-browser/src/chunks/PDFViewer/index.tsx index 771e03316..83459d6e1 100644 --- a/browser/data-browser/src/chunks/PDFViewer/index.tsx +++ b/browser/data-browser/src/chunks/PDFViewer/index.tsx @@ -1,10 +1,14 @@ import { useCallback, useMemo, useState, type JSX } from 'react'; import { pdfjs, Document, Page } from 'react-pdf'; -import 'react-pdf/dist/esm/Page/TextLayer.css'; -import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; +import 'react-pdf/dist/Page/TextLayer.css'; +import 'react-pdf/dist/Page/AnnotationLayer.css'; import { styled } from 'styled-components'; -pdfjs.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js`; +pdfjs.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.min.mjs', + import.meta.url, +).toString(); + interface PDFViewerProps { url: string; className?: string; diff --git a/browser/data-browser/src/chunks/MarkdownEditor/AIChatInput/AsyncAIChatInput.tsx b/browser/data-browser/src/chunks/RTE/AIChatInput/AsyncAIChatInput.tsx similarity index 97% rename from browser/data-browser/src/chunks/MarkdownEditor/AIChatInput/AsyncAIChatInput.tsx rename to browser/data-browser/src/chunks/RTE/AIChatInput/AsyncAIChatInput.tsx index 351c9dae9..c93777887 100644 --- a/browser/data-browser/src/chunks/MarkdownEditor/AIChatInput/AsyncAIChatInput.tsx +++ b/browser/data-browser/src/chunks/RTE/AIChatInput/AsyncAIChatInput.tsx @@ -5,7 +5,7 @@ import Mention from '@tiptap/extension-mention'; import FileHandler from '@tiptap/extension-file-handler'; import { TiptapContextProvider } from '../TiptapContext'; import { EditorWrapperBase } from '../EditorWrapperBase'; -import { searchSuggestionBuilder } from './resourceSuggestions'; +import { searchSuggestionBuilder } from './mcpSuggestions'; import { useRef, useState } from 'react'; import { EditorEvents } from '../EditorEvents'; import { Markdown } from 'tiptap-markdown'; @@ -179,7 +179,9 @@ const AsyncAIChatInput: React.FC< [serversWithResources, searchResourcesOfServer, disabled], ); - const handleChange = (value: string) => { + const handleChange = () => { + // @ts-expect-error - markdown is a valid storage + const value = editor.storage.markdown.getMarkdown(); setMarkdown(value); markdownRef.current = value; onChange(value); diff --git a/browser/data-browser/src/chunks/MarkdownEditor/AIChatInput/MentionList.tsx b/browser/data-browser/src/chunks/RTE/AIChatInput/MentionList.tsx similarity index 90% rename from browser/data-browser/src/chunks/MarkdownEditor/AIChatInput/MentionList.tsx rename to browser/data-browser/src/chunks/RTE/AIChatInput/MentionList.tsx index f99f93401..860df1d21 100644 --- a/browser/data-browser/src/chunks/MarkdownEditor/AIChatInput/MentionList.tsx +++ b/browser/data-browser/src/chunks/RTE/AIChatInput/MentionList.tsx @@ -1,4 +1,4 @@ -import { forwardRef, useEffect, useImperativeHandle } from 'react'; +import { forwardRef, useImperativeHandle } from 'react'; import styled from 'styled-components'; import { getIconForClass } from '../../../helpers/iconMap'; import { useSelectedIndex } from '../../../hooks/useSelectedIndex'; @@ -21,25 +21,22 @@ export interface MentionListRef { } export const MentionList = forwardRef( - ({ items, onSelect }, ref) => { - const { selectedIndex, onKeyDown, onMouseOver, onClick, resetIndex } = - useSelectedIndex( - items, - index => { - if (index === undefined) { - return; - } - - const item = items[index]; - - if (item) { - onSelect(item); - } - }, - 0, - ); - - useEffect(() => resetIndex(), [items]); + ({ items, onSelect, query }, ref) => { + const { selectedIndex, onKeyDown, onMouseOver, onClick } = useSelectedIndex( + items, + index => { + if (index === undefined) { + return; + } + + const item = items[index]; + + if (item) { + onSelect(item); + } + }, + { initialIndex: 0, key: query }, + ); useImperativeHandle(ref, () => ({ onKeyDown: ({ event }) => { diff --git a/browser/data-browser/src/chunks/MarkdownEditor/AIChatInput/resourceSuggestions.ts b/browser/data-browser/src/chunks/RTE/AIChatInput/mcpSuggestions.ts similarity index 84% rename from browser/data-browser/src/chunks/MarkdownEditor/AIChatInput/resourceSuggestions.ts rename to browser/data-browser/src/chunks/RTE/AIChatInput/mcpSuggestions.ts index 972ece2aa..f3ee37ae0 100644 --- a/browser/data-browser/src/chunks/MarkdownEditor/AIChatInput/resourceSuggestions.ts +++ b/browser/data-browser/src/chunks/RTE/AIChatInput/mcpSuggestions.ts @@ -1,5 +1,4 @@ import { ReactRenderer } from '@tiptap/react'; -import tippy, { type Instance } from 'tippy.js'; import { MentionList, type MentionListProps, @@ -10,6 +9,8 @@ import type { SuggestionOptions, SuggestionProps } from '@tiptap/suggestion'; import type { SearchResourcesOfServer } from '@components/AI/MCP/useMcpServers'; import type { MCPServer } from '@chunks/AI/types'; import type { CategorySuggestion, SearchSuggestion } from './types'; +import styles from '../floatingMenu.module.css'; +import { computePosition, flip, inline, offset, shift } from '@floating-ui/dom'; enum SuggestionState { PickingCategory, @@ -95,7 +96,25 @@ export function searchSuggestionBuilder( items, render() { let component: ReactRenderer; - let popup: Instance[]; + + const setPosition = ( + props: SuggestionProps, + ) => { + if (!props.decorationNode) { + console.error('No decoration node'); + + return; + } + + computePosition(props.decorationNode, component.element, { + placement: 'top', + middleware: [flip(), shift(), inline(), offset(10)], + }).then(({ x, y }) => { + component.element.style.setProperty('--left', `${x}px`); + component.element.style.setProperty('--top', `${y}px`); + document.body.appendChild(component.element); + }); + }; const update = ( newP: SuggestionProps, @@ -106,12 +125,9 @@ export function searchSuggestionBuilder( return; } - popup[0].setProps({ - getReferenceClientRect: newP.clientRect as () => DOMRect, - }); + setPosition(newP); }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any const editPropsForMenus = ( props: SuggestionProps, ): SuggestionProps => { @@ -154,21 +170,10 @@ export function searchSuggestionBuilder( component = new ReactRenderer(MentionList, { props: newProps, editor: props.editor, + className: styles.renderer, }); - if (!props.clientRect) { - return; - } - - popup = tippy('body', { - getReferenceClientRect: props.clientRect as () => DOMRect, - appendTo: () => document.body, - content: component.element, - showOnCreate: true, - interactive: true, - trigger: 'manual', - placement: 'top-start', - }); + setPosition(props); }, onUpdate(oldProps) { @@ -178,7 +183,7 @@ export function searchSuggestionBuilder( onKeyDown(props) { if (props.event.key === 'Escape') { - popup[0].hide(); + component.destroy(); return true; } @@ -193,7 +198,6 @@ export function searchSuggestionBuilder( onExit() { state = SuggestionState.PickingCategory; - popup[0].destroy(); component.destroy(); }, }; diff --git a/browser/data-browser/src/chunks/MarkdownEditor/AIChatInput/types.ts b/browser/data-browser/src/chunks/RTE/AIChatInput/types.ts similarity index 100% rename from browser/data-browser/src/chunks/MarkdownEditor/AIChatInput/types.ts rename to browser/data-browser/src/chunks/RTE/AIChatInput/types.ts diff --git a/browser/data-browser/src/chunks/MarkdownEditor/AsyncMarkdownEditor.tsx b/browser/data-browser/src/chunks/RTE/AsyncMarkdownEditor.tsx similarity index 62% rename from browser/data-browser/src/chunks/MarkdownEditor/AsyncMarkdownEditor.tsx rename to browser/data-browser/src/chunks/RTE/AsyncMarkdownEditor.tsx index 05b734286..0de7e3295 100644 --- a/browser/data-browser/src/chunks/MarkdownEditor/AsyncMarkdownEditor.tsx +++ b/browser/data-browser/src/chunks/RTE/AsyncMarkdownEditor.tsx @@ -1,21 +1,26 @@ -import { EditorContent, FloatingMenu, useEditor } from '@tiptap/react'; +import { EditorContent, useEditor } from '@tiptap/react'; +import { FloatingMenu } from '@tiptap/react/menus'; import { StarterKit } from '@tiptap/starter-kit'; import { Link } from '@tiptap/extension-link'; import { Placeholder } from '@tiptap/extension-placeholder'; import { Typography } from '@tiptap/extension-typography'; -import { styled } from 'styled-components'; -import { Markdown } from 'tiptap-markdown'; +import { Markdown } from '@tiptap/markdown'; import { EditorEvents } from './EditorEvents'; import { FaCode } from 'react-icons/fa6'; import { useCallback, useState } from 'react'; import { BubbleMenu } from './BubbleMenu'; import { TiptapContextProvider } from './TiptapContext'; -import { ToggleButton } from './ToggleButton'; import { SlashCommands, buildSuggestion } from './SlashMenu/CommandsExtension'; +import { TableKit } from '@tiptap/extension-table'; import { ExtendedImage } from './ImagePicker'; -import { transition } from '../../helpers/transition'; import { usePopoverContainer } from '../../components/Popover'; -import { EditorWrapperBase } from './EditorWrapperBase'; +import { + StyledEditorWrapper, + RawEditor, + FloatingMenuText, + FloatingCodeButton, +} from './sharedEditorStyles'; +import { TaskItem, TaskList } from '@tiptap/extension-list'; export type AsyncMarkdownEditorProps = { placeholder?: string; @@ -27,10 +32,6 @@ export type AsyncMarkdownEditorProps = { onBlur?: () => void; }; -const MIN_EDITOR_HEIGHT = '10rem'; -// The lineheight of a textarea. -const LINE_HEIGHT = 1.15; - export default function AsyncMarkdownEditor({ placeholder, initialContent, @@ -42,12 +43,21 @@ export default function AsyncMarkdownEditor({ }: AsyncMarkdownEditorProps): React.JSX.Element { const containerRef = usePopoverContainer(); + /* eslint-disable-next-line react-hooks/refs */ const container = containerRef.current ?? document.body; + /* eslint-disable-next-line react-hooks/refs */ const [extensions] = useState(() => [ - StarterKit, - Markdown, + StarterKit.configure({ + link: false, + }), + Markdown.configure({ + markedOptions: { + gfm: true, + }, + }), Typography, + TableKit, Link.configure({ protocols: [ 'http', @@ -64,6 +74,10 @@ export default function AsyncMarkdownEditor({ target: '_blank', }, }), + TaskList, + TaskItem.configure({ + nested: true, + }), ExtendedImage.configure({ HTMLAttributes: { class: 'tiptap-image', @@ -83,6 +97,7 @@ export default function AsyncMarkdownEditor({ const editor = useEditor({ extensions, content: markdown, + contentType: 'markdown', onBlur, autofocus: !!autoFocus, editorProps: { @@ -94,10 +109,17 @@ export default function AsyncMarkdownEditor({ }, }); - const handleChange = useCallback( - (value: string) => { - setMarkdown(value); - onChange?.(value); + const handleChange = useCallback(() => { + const value = editor.getMarkdown(); + + setMarkdown(value); + onChange?.(value); + }, [onChange, editor]); + + const handleRawChange = useCallback( + (val: string) => { + setMarkdown(val); + onChange?.(val); }, [onChange], ); @@ -106,7 +128,7 @@ export default function AsyncMarkdownEditor({ setCodeMode(enable); if (!enable) { - editor?.commands.setContent(markdown); + editor?.commands.setContent(markdown, { contentType: 'markdown' }); } }; @@ -116,7 +138,7 @@ export default function AsyncMarkdownEditor({ {codeMode && ( handleChange(e.target.value)} + onChange={e => handleRawChange(e.target.value)} value={markdown} /> )} @@ -139,53 +161,3 @@ export default function AsyncMarkdownEditor({ ); } - -// Textareas do not automatically grow when the content exceeds the height of the textarea. -// This function calculates the height of the textarea based on the number of lines in the content. -const calcHeight = (value: string) => { - const lines = value.split('\n').length; - - return `calc(${lines * LINE_HEIGHT}em + 5px)`; -}; - -const StyledEditorWrapper = styled(EditorWrapperBase)` - min-height: ${MIN_EDITOR_HEIGHT}; - border-radius: ${p => p.theme.radius}; - box-shadow: 0 0 0 1px ${p => p.theme.colors.bg2}; - min-height: ${MIN_EDITOR_HEIGHT}; - padding: ${p => p.theme.size()}; - ${transition('box-shadow')} - - &:focus-within { - box-shadow: 0 0 0 2px ${p => p.theme.colors.main}; - } - - & .tiptap { - width: min(100%, 75ch); - min-height: ${MIN_EDITOR_HEIGHT}; - } -`; - -const RawEditor = styled.textarea.attrs(p => ({ - style: { height: calcHeight((p.value as string) ?? '') }, -}))` - border: none; - width: 100%; - min-height: ${MIN_EDITOR_HEIGHT}; - outline: none; - overflow: visible; - height: fit-content; - background-color: transparent; - color: ${p => p.theme.colors.text}; - resize: none; -`; - -const FloatingMenuText = styled.span` - color: ${p => p.theme.colors.textLight}; -`; - -const FloatingCodeButton = styled(ToggleButton)` - position: absolute; - top: 0.5rem; - right: 0.5rem; -`; diff --git a/browser/data-browser/src/chunks/RTE/BubbleMenu.tsx b/browser/data-browser/src/chunks/RTE/BubbleMenu.tsx new file mode 100644 index 000000000..f7ea0912a --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/BubbleMenu.tsx @@ -0,0 +1,155 @@ +import { BubbleMenu as TipTapBubbleMenu } from '@tiptap/react/menus'; +import { + FaBold, + FaCode, + FaItalic, + FaLink, + FaQuoteLeft, + FaStrikethrough, +} from 'react-icons/fa6'; +import { styled } from 'styled-components'; +import * as RadixPopover from '@radix-ui/react-popover'; +import { Column, Row } from '../../components/Row'; + +import { Popover } from '../../components/Popover'; +import { useState } from 'react'; +import { transparentize } from 'polished'; +import { EditLinkForm } from './EditLinkForm'; +import { useTipTapEditor } from './TiptapContext'; +import { ToggleButton } from './ToggleButton'; +import { NodeSelectMenu } from './NodeSelectMenu'; +import { useEditorState } from '@tiptap/react'; + +interface BubbleMenuProps { + children?: React.ReactNode; + extraItems?: React.ReactNode; + onShow?: () => void; +} + +export function BubbleMenu({ + children, + extraItems, + onShow, +}: BubbleMenuProps): React.JSX.Element { + const editor = useTipTapEditor(); + const [linkMenuOpen, setLinkMenuOpen] = useState(false); + + const { isBold, isItalic, isStrikethrough, isBlockquote, isCode, isLink } = + useEditorState({ + editor, + selector: snapshot => ({ + isBold: snapshot.editor.isActive('bold'), + isItalic: snapshot.editor.isActive('italic'), + isStrikethrough: snapshot.editor.isActive('strike'), + isBlockquote: snapshot.editor.isActive('blockquote'), + isCode: snapshot.editor.isActive('code'), + isLink: snapshot.editor.isActive('link'), + }), + }); + + if (!editor.isInitialized) { + return <>; + } + + return ( + + + + + editor.chain().focus().toggleBold().run()} + disabled={!editor.can().chain().focus().toggleBold().run()} + type='button' + > + + + editor.chain().focus().toggleItalic().run()} + disabled={!editor.can().chain().focus().toggleItalic().run()} + type='button' + > + + + editor.chain().focus().toggleStrike().run()} + disabled={!editor.can().chain().focus().toggleStrike().run()} + type='button' + > + + + editor.chain().focus().toggleBlockquote().run()} + disabled={!editor.can().chain().focus().toggleBlockquote().run()} + type='button' + > + + + editor.chain().focus().toggleCode().run()} + disabled={!editor.can().chain().focus().toggleCode().run()} + type='button' + > + + + + + + } + > + setLinkMenuOpen(false)} /> + + {children} + + {extraItems} + + + ); +} + +const BubbleMenuInner = styled(Column)` + background-color: ${p => p.theme.colors.bg}; + border-radius: ${p => p.theme.radius}; + padding: ${p => p.theme.size(2)}; + box-shadow: ${p => p.theme.boxShadowSoft}; + border: ${p => + p.theme.darkMode ? `1px solid ${p.theme.colors.bg2}` : 'none'}; + @supports (backdrop-filter: blur(5px)) { + background-color: ${p => transparentize(0.15, p.theme.colors.bg)}; + backdrop-filter: blur(5px); + } +`; + +const StyledPopover = styled(Popover)` + background-color: ${p => p.theme.colors.bg}; + backdrop-filter: blur(5px); + padding: ${p => p.theme.size()}; + border-radius: ${p => p.theme.radius}; + border: ${p => + p.theme.darkMode ? `1px solid ${p.theme.colors.bg2}` : 'none'}; + + @supports (backdrop-filter: blur(5px)) { + background-color: ${p => transparentize(0.15, p.theme.colors.bg)}; + backdrop-filter: blur(5px); + } +`; diff --git a/browser/data-browser/src/chunks/RTE/CollaborativeEditor.tsx b/browser/data-browser/src/chunks/RTE/CollaborativeEditor.tsx new file mode 100644 index 000000000..bc2d9d1e2 --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/CollaborativeEditor.tsx @@ -0,0 +1,325 @@ +import { EditorContent, useEditor, type Editor } from '@tiptap/react'; +import { FloatingMenu } from '@tiptap/react/menus'; +import { StarterKit } from '@tiptap/starter-kit'; +import { Link } from '@tiptap/extension-link'; +import { Placeholder } from '@tiptap/extension-placeholder'; +import { Typography } from '@tiptap/extension-typography'; +import Collaboration from '@tiptap/extension-collaboration'; +import CollaborationCaret from '@tiptap/extension-collaboration-caret'; +import TextAlign from '@tiptap/extension-text-align'; +import { TaskList, TaskItem } from '@tiptap/extension-list'; +import DragHandle from '@tiptap/extension-drag-handle-react'; +import { + Color, + BackgroundColor, + TextStyle, +} from '@tiptap/extension-text-style'; +import { useEffect, useState } from 'react'; +import { TiptapContextProvider } from './TiptapContext'; +import { SlashCommands, buildSuggestion } from './SlashMenu/CommandsExtension'; +import { + ResourceCommands, + buildResourceSuggestion, +} from './ResourceExtension/ResourceExtention'; +import { ExtendedImage } from './ImagePicker'; +import { FloatingMenuText } from './sharedEditorStyles'; +import * as Y from 'yjs'; +import { + dataBrowser, + useCanWrite, + useDebouncedSave, + useResource, + useStore, + type Core, + type Resource, + type Server, +} from '@tomic/react'; +import { EditorEvents } from './EditorEvents'; +import { useYSync } from './useYSync'; +import { randomItem } from '@helpers/randomItem'; +import { EditorWrapperBase } from './EditorWrapperBase'; +import styled from 'styled-components'; +import { useSettings } from '@helpers/AppSettings'; +import { FullBubbleMenu } from './FullBubbleMenu'; +import { + ResourceNode, + ResourceNodeInline, +} from './ResourceExtension/ResourceNode'; +import { IsInRTEContex } from '@hooks/useIsInRTE'; +import { FaGripVertical, FaLink, FaTable } from 'react-icons/fa6'; +import { useUpload } from '@hooks/useUpload'; +import FileHandler from '@tiptap/extension-file-handler'; +import { supportedImageTypes } from '@views/File/fileTypeUtils'; +import type { SuggestionItem } from './types'; +import { useNewResourceUI } from '@components/forms/NewForm/useNewResourceUI'; +import { addIf } from '@helpers/addIf'; + +export type CollaborativeEditorProps = { + placeholder?: string; + doc: Y.Doc; + resource: Resource; + property: string; + id?: string; + onBlur?: () => void; +}; + +const COLORS = ['#70d6ff', '#ff70a6', '#ff9770', '#ffd670', '#e9ff70']; + +export default function CollaborativeEditor({ + placeholder, + doc, + property, + id, + resource, + onBlur, +}: CollaborativeEditorProps): React.JSX.Element { + const store = useStore(); + const [color] = useState(randomItem(COLORS)); + const showNewResourceUI = useNewResourceUI(); + const [save] = useDebouncedSave(resource, 2000); + const { agent, drive } = useSettings(); + const agentResource = useResource(agent?.subject); + const { upload } = useUpload(resource); + const awareness = useYSync(resource, property, doc); + const canWrite = useCanWrite(resource); + + const uploadAndInsertImage = async ( + currentEditor: Editor, + files: File[], + pos: number, + ) => { + const subjects = await upload(files); + + for (const imageSubject of subjects) { + const image = await store.getResource(imageSubject); + + currentEditor.commands.insertContentAt(pos, { + type: 'image', + attrs: { src: image.props.downloadUrl }, + }); + } + }; + + const editor = useEditor( + { + extensions: [ + StarterKit.configure({ + undoRedo: false, + link: false, + }), + Typography, + Link.extend({ + parseHTML: () => [ + { + tag: 'a[href]', + getAttrs: node => { + // Links with a data-type are custom nodes that should be ignored by the link extension + if (node.getAttribute('data-type')) { + return false; + } + + // Default link parsing + return { + href: node.getAttribute('href'), + target: node.getAttribute('target'), + }; + }, + }, + ], + }).configure({ + autolink: true, + openOnClick: true, + protocols: [ + 'http', + 'https', + 'mailto', + { + scheme: 'tel', + optionalSlashes: true, + }, + ], + HTMLAttributes: { + class: 'tiptap-link', + rel: 'noopener noreferrer', + target: '_blank', + }, + }), + ExtendedImage.configure({ + uploadImage: upload, + HTMLAttributes: { + class: 'tiptap-image', + }, + }), + Placeholder.configure({ + placeholder: placeholder ?? 'Start typing...', + }), + SlashCommands.configure({ + suggestion: buildSuggestion(document.body, [ + { + title: 'Resource', + id: 'resource', + icon: FaLink, + command: ({ range, editor: internalEditor }) => + internalEditor + .chain() + .focus() + .deleteRange(range) + .insertContent('@') + .run(), + } as SuggestionItem, + { + title: 'Data Table', + id: 'data-table', + icon: FaTable, + command: ({ range, editor: internalEditor }) => { + showNewResourceUI(dataBrowser.classes.table, resource.subject, { + skipNavigation: true, + onCreated: table => { + internalEditor + .chain() + .focus() + .deleteRange(range) + .setResource({ subject: table.subject }) + .run(); + }, + }); + }, + }, + ]), + }), + ResourceCommands.configure({ + suggestion: buildResourceSuggestion(document.body, store, drive), + }), + ResourceNode.configure({ + store, + }), + ResourceNodeInline.configure({ + store, + }), + Collaboration.configure({ + document: doc, + field: 'content', + }), + ...addIf( + canWrite, + CollaborationCaret.configure({ + provider: { + awareness, + }, + user: { + name: agentResource.title, + color, + }, + }), + ), + TextAlign.configure({ + types: ['heading', 'paragraph'], + }), + TaskList, + TaskItem.configure({ + nested: true, + }), + TextStyle, + Color, + BackgroundColor, + FileHandler.configure({ + allowedMimeTypes: Array.from(supportedImageTypes), + onDrop: (currentEditor, files, pos) => { + uploadAndInsertImage(currentEditor, files, pos); + }, + onPaste: (currentEditor, files, htmlContent) => { + if (htmlContent) { + // if there is htmlContent, stop manual insertion & let other extensions handle insertion via inputRule + // you could extract the pasted file from this url string and upload it to a server for example + + return false; + } + + uploadAndInsertImage( + currentEditor, + files, + currentEditor.state.selection.anchor, + ); + }, + }), + ], + editable: canWrite, + onBlur, + editorProps: { + attributes: { + ...(id && { id }), + 'aria-label': 'Rich Text Editor', + 'aria-multiline': 'true', + 'aria-readonly': canWrite ? 'true' : 'false', + spellcheck: 'true', + }, + }, + }, + [canWrite, drive], + ); + + useEffect(() => { + if (agentResource) { + editor.commands.updateUser?.({ + name: agentResource.props.name ?? 'Untitled Agent', + color, + }); + } + }, [agentResource, editor.commands, color, canWrite]); + + return ( + + + + + + + + + + Type '/' for options or '@' for resources + + + + + + editor?.commands.focus('end')} /> + + + + ); +} + +const ClickUnderHandler = styled.div` + flex: 1; + width: 100%; + min-height: 10rem; +`; + +export const StyledEditorWrapper = styled(EditorWrapperBase)` + box-shadow: none; + min-height: 100%; + border-radius: ${p => p.theme.radius}; + min-height: 10rem; + width: 100%; + flex: 1; + display: flex; + flex-direction: column; + + & .tiptap { + width: 100%; + ::spelling-error { + text-decoration: wavy red underline; + } + } + .drag-handle { + align-items: center; + border-radius: 0.25rem; + cursor: grab; + display: flex; + height: 1.5rem; + justify-content: center; + width: 1.5rem; + color: ${p => p.theme.colors.textLight2}; + } +`; diff --git a/browser/data-browser/src/chunks/RTE/ColorMenu.tsx b/browser/data-browser/src/chunks/RTE/ColorMenu.tsx new file mode 100644 index 000000000..b8ce0a95f --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/ColorMenu.tsx @@ -0,0 +1,269 @@ +import { Column, Row } from '@components/Row'; +import { useTipTapEditor } from './TiptapContext'; +import { MdFormatColorFill, MdFormatColorText } from 'react-icons/md'; +import { useLocalStorage } from '@hooks/useLocalStorage'; +import styled from 'styled-components'; +import { transition } from '@helpers/transition'; +import { useState, useRef, useEffect } from 'react'; +import { useEditorState } from '@tiptap/react'; +import { FaPencil } from 'react-icons/fa6'; +import { desaturate, readableColor, setLightness } from 'polished'; + +const MAX_LAST_USED_COLORS = 9; +const defaultColors = [ + '#7c8c04', + '#333333', + '#000080', + '#800000', + '#014421', + '#008080', + '#4B0082', + '#eb3535', + '#148a12', +]; +const defaultBackgroundColors = defaultColors.map(color => + desaturate(0.5, setLightness(0.7, color)), +); + +// Add a good highlight color to the first position. +defaultBackgroundColors[0] = '#e9ff70'; + +export const ColorMenu: React.FC = () => { + const editor = useTipTapEditor(); + const { selectedTextColor, selectedBackgroundColor } = useEditorState({ + editor, + selector: snapshot => { + return { + selectedTextColor: snapshot.editor.getAttributes('textStyle').color, + selectedBackgroundColor: + snapshot.editor.getAttributes('textStyle').backgroundColor, + }; + }, + }); + + const [lastUsedTextColors = [], setLastUsedTextColors] = useLocalStorage< + string[] + >('atomic.rte.lastUsedTextColors', defaultColors); + + const [lastUsedBackgroundColor = [], setLastUsedBackgroundColor] = + useLocalStorage( + 'atomic.rte.lastUsedBackgroundColor', + defaultBackgroundColors, + ); + + const setTextColor = (color: string) => { + editor.chain().setColor(color).run(); + setLastUsedTextColors(prev => [ + color, + ...(prev.includes(color) + ? prev.filter(c => c !== color) + : prev.slice(0, MAX_LAST_USED_COLORS - 1)), + ]); + }; + + const setBackgroundColor = (color: string) => { + editor.chain().setBackgroundColor(color).run(); + setLastUsedBackgroundColor(prev => [ + color, + ...(prev.includes(color) + ? prev.filter(c => c !== color) + : prev.slice(0, MAX_LAST_USED_COLORS - 1)), + ]); + }; + + const [handleTextColorInputChange, handleTextColorInputBlur] = useColor( + selectedTextColor, + setTextColor, + ); + + const [handleBackgroundColorInputChange, handleBackgroundColorInputBlur] = + useColor(selectedBackgroundColor, setBackgroundColor); + + const preventDefault = (event: React.MouseEvent) => { + event.preventDefault(); + }; + + useEffect(() => { + // The bubble menu might need to be repositioned if this component is shown. + editor.commands.setMeta('bubbleMenu', 'updatePosition'); + }, [editor]); + + return ( + + + + + {lastUsedTextColors.map(color => ( + setTextColor(color)} + onMouseDown={preventDefault} + /> + ))} + editor.chain().focus().unsetColor().run()} + onMouseDown={preventDefault} + /> + + + + + {lastUsedBackgroundColor.map(color => ( + setBackgroundColor(color)} + onMouseDown={preventDefault} + /> + ))} + editor.chain().focus().unsetBackgroundColor().run()} + onMouseDown={preventDefault} + /> + + + ); +}; + +const useColor = (initialColor: string, onSelect: (color: string) => void) => { + const [isChanging, setIsChanging] = useState(false); + const colorRef = useRef(initialColor); + + const onInputChange = (event: React.ChangeEvent) => { + const color = event.target.value; + colorRef.current = color; + setIsChanging(true); + }; + + const onInputBlur = () => { + if (!isChanging) { + return; + } + + setIsChanging(false); + onSelect(colorRef.current); + }; + + return [onInputChange, onInputBlur]; +}; + +const ColorButton = styled.button<{ color: string }>` + background-color: ${p => p.color}; + border: none; + height: 1.5rem; + aspect-ratio: 1/1; + border-radius: 50%; + cursor: pointer; + ${transition('transform')}; + &:hover, + &:focus-visible { + outline: none; + transform: scale(1.3); + } + + &:active { + transform: scale(1.1); + } + + &.unset { + position: relative; + border: 1px solid ${p => p.theme.colors.textLight}; + display: grid; + place-items: center; + &::before { + content: ''; + position: absolute; + height: 100%; + width: 2px; + background-color: ${p => p.theme.colors.alert}; + transform: rotate(45deg); + transform-origin: center; + } + } +`; + +interface ColorInputProps { + label: string; + value: string; + onChange: (event: React.ChangeEvent) => void; + onBlur: (event: React.FocusEvent) => void; +} + +const ColorInput: React.FC = ({ + label, + value, + onChange, + onBlur, +}) => { + return ( + +
+ +
+ +
+ ); +}; + +const HiddenColorInput = styled.input` + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; +`; + +const ColorInputLabel = styled.label<{ color: string }>` + --CIL_foreground: ${p => readableColor(p.color ?? p.theme.colors.bg)}; + cursor: pointer; + position: relative; + gap: 0.5rem; + background-color: ${p => p.color}; + height: 1.5rem; + width: 1.5rem; + border-radius: 50%; + border: 1px solid var(--CIL_foreground); + &:focus-within { + outline: solid ${p => p.theme.colors.main}; + } + div { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + display: grid; + place-items: center; + + svg { + fill: var(--CIL_foreground); + width: 0.75rem; + height: 0.75rem; + } + } +`; diff --git a/browser/data-browser/src/chunks/MarkdownEditor/EditLinkForm.tsx b/browser/data-browser/src/chunks/RTE/EditLinkForm.tsx similarity index 100% rename from browser/data-browser/src/chunks/MarkdownEditor/EditLinkForm.tsx rename to browser/data-browser/src/chunks/RTE/EditLinkForm.tsx diff --git a/browser/data-browser/src/chunks/MarkdownEditor/EditorEvents.tsx b/browser/data-browser/src/chunks/RTE/EditorEvents.tsx similarity index 73% rename from browser/data-browser/src/chunks/MarkdownEditor/EditorEvents.tsx rename to browser/data-browser/src/chunks/RTE/EditorEvents.tsx index 747c9bc22..eb49588f6 100644 --- a/browser/data-browser/src/chunks/MarkdownEditor/EditorEvents.tsx +++ b/browser/data-browser/src/chunks/RTE/EditorEvents.tsx @@ -2,7 +2,7 @@ import { useEffect } from 'react'; import { useTipTapEditor } from './TiptapContext'; interface EditorEventsProps { - onChange?: (content: string) => void; + onChange?: () => void; } export function EditorEvents({ onChange }: EditorEventsProps): null { @@ -12,12 +12,10 @@ export function EditorEvents({ onChange }: EditorEventsProps): null { if (!editor) return; const callback = () => { - onChange?.(editor.storage.markdown.getMarkdown()); + onChange?.(); }; - if (editor) { - editor.on('update', callback); - } + editor.on('update', callback); return () => { if (editor) { diff --git a/browser/data-browser/src/chunks/RTE/EditorWrapperBase.tsx b/browser/data-browser/src/chunks/RTE/EditorWrapperBase.tsx new file mode 100644 index 000000000..412101051 --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/EditorWrapperBase.tsx @@ -0,0 +1,121 @@ +import { styled } from 'styled-components'; + +export const EditorWrapperBase = styled.div<{ hideEditor: boolean }>` + position: relative; + background-color: ${p => p.theme.colors.bg}; + + &:not(:focus-within) { + & .tiptap p.is-editor-empty:first-child::before { + color: ${p => p.theme.colors.textLight}; + content: attr(data-placeholder); + float: left; + height: 0; + pointer-events: none; + } + } + + & .tiptap { + :first-child { + margin-top: 0; + } + display: ${p => (p.hideEditor ? 'none' : 'block')}; + outline: none; + width: min(100%, 75ch); + + .tiptap-image { + max-width: 100%; + height: auto; + } + + /* Give a remote user a caret */ + .collaboration-carets__caret { + border-left: 1px solid #0d0d0d; + border-right: 1px solid #0d0d0d; + margin-left: -1px; + margin-right: -1px; + pointer-events: none; + position: relative; + word-break: normal; + } + + /* Render the username above the caret */ + .collaboration-carets__label { + border-radius: 3px 3px 3px 0; + color: #0d0d0d; + font-size: 12px; + font-style: normal; + font-weight: 600; + left: -1px; + line-height: normal; + padding: 0.1rem 0.3rem; + position: absolute; + top: -1.4em; + user-select: none; + white-space: nowrap; + } + + pre { + padding: 0.75rem 1rem; + background-color: ${p => p.theme.colors.bg1}; + border-radius: ${p => p.theme.radius}; + font-family: monospace; + + code { + white-space: pre; + color: inherit; + padding: 0; + background: none; + font-size: 0.8rem; + } + } + + blockquote { + margin-inline-start: 0; + border-inline-start: 3px solid ${p => p.theme.colors.textLight2}; + color: ${p => p.theme.colors.textLight}; + padding-inline-start: 1rem; + } + + /* List styles */ + ul, + ol { + padding: 0 1rem; + li { + margin-bottom: 0; + } + li p { + margin-top: 0.25em; + margin-bottom: 0.25em; + } + } + /* Task list specific styles */ + ul[data-type='taskList'] { + list-style: none; + margin-left: 0; + padding: 0; + + li { + align-items: flex-start; + display: flex; + + > label { + flex: 0 0 auto; + margin-right: 0.5rem; + user-select: none; + } + + > div { + flex: 1 1 auto; + } + } + + input[type='checkbox'] { + cursor: pointer; + } + + ul[data-type='taskList'] { + margin: 0; + } + } + } +`; diff --git a/browser/data-browser/src/chunks/RTE/FullBubbleMenu.tsx b/browser/data-browser/src/chunks/RTE/FullBubbleMenu.tsx new file mode 100644 index 000000000..90135c778 --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/FullBubbleMenu.tsx @@ -0,0 +1,99 @@ +import { ButtonGroup } from '@components/ButtonGroup'; +import { + FaAlignLeft, + FaAlignCenter, + FaAlignRight, + FaPalette, +} from 'react-icons/fa6'; +import { BubbleMenu } from './BubbleMenu'; +import { styled } from 'styled-components'; +import { useTipTapEditor } from './TiptapContext'; +import { useEditorState } from '@tiptap/react'; +import { ToggleButton } from './ToggleButton'; +import { useState } from 'react'; +import { ColorMenu } from './ColorMenu'; + +export const FullBubbleMenu: React.FC = () => { + const editor = useTipTapEditor(); + const [colorMenuOpen, setColorMenuOpen] = useState(false); + const { alignedLeft, alignedCenter, alignedRight } = useEditorState({ + editor, + selector: snapshot => ({ + alignedLeft: snapshot.editor.isActive({ textAlign: 'left' }), + alignedCenter: snapshot.editor.isActive({ textAlign: 'center' }), + alignedRight: snapshot.editor.isActive({ textAlign: 'right' }), + }), + }); + + const alignTextOptions = [ + { + icon: , + label: 'Left', + value: 'left', + checked: alignedLeft, + }, + { + icon: , + label: 'Center', + value: 'center', + checked: alignedCenter, + }, + { + icon: , + label: 'Right', + value: 'right', + checked: alignedRight, + }, + ]; + + if (!editor.view) { + return null; + } + + return ( + {colorMenuOpen && }} + onShow={() => { + const style = editor.getAttributes('textStyle'); + setColorMenuOpen(!!style.color || !!style.backgroundColor); + }} + > + + { + editor.chain().focus().setTextAlign(value).run(); + }} + value={ + alignedLeft + ? 'left' + : alignedCenter + ? 'center' + : alignedRight + ? 'right' + : 'left' + } + /> + + { + setColorMenuOpen(!colorMenuOpen); + requestAnimationFrame(() => { + editor.commands.setMeta('bubbleMenu', 'updatePosition'); + }); + }} + $active={colorMenuOpen} + type='button' + > + + + + ); +}; + +const Separator = styled.div` + width: 1px; + height: 2rem; + background-color: ${p => p.theme.colors.bg2}; +`; diff --git a/browser/data-browser/src/chunks/MarkdownEditor/ImagePicker.tsx b/browser/data-browser/src/chunks/RTE/ImagePicker.tsx similarity index 85% rename from browser/data-browser/src/chunks/MarkdownEditor/ImagePicker.tsx rename to browser/data-browser/src/chunks/RTE/ImagePicker.tsx index 420178f95..263833faa 100644 --- a/browser/data-browser/src/chunks/MarkdownEditor/ImagePicker.tsx +++ b/browser/data-browser/src/chunks/RTE/ImagePicker.tsx @@ -1,11 +1,11 @@ import { NodeViewWrapper, ReactNodeViewRenderer, - type Editor, + type ReactNodeViewProps, } from '@tiptap/react'; -import { Image } from '@tiptap/extension-image'; +import { Image, type ImageOptions } from '@tiptap/extension-image'; import { styled } from 'styled-components'; -import { forwardRef, useState } from 'react'; +import { useState } from 'react'; import { Button } from '../../components/Button'; import { InputStyled, InputWrapper } from '../../components/forms/InputStyles'; import { Column, Row } from '../../components/Row'; @@ -19,29 +19,31 @@ import { imageMimeTypes } from '../../helpers/filetypes'; import { useHTMLFormFieldValidation } from '../../helpers/useHTMLFormFieldValidation'; import { transition } from '../../helpers/transition'; -type PartialImageNodeProps = { - node: { - attrs: { - src?: string; - alt?: string; - }; - }; - updateAttributes: (attrs: { src: string; alt?: string }) => void; - selected: boolean; - editor: Editor; -}; +interface ExtendedImageProps extends ImageOptions { + uploadImage?: (file: File[]) => Promise; +} + +export const ExtendedImage = Image.extend({ + addOptions() { + return { + ...this.parent?.(), + onNewFilePicked: undefined, + } as ExtendedImageProps; + }, -export const ExtendedImage = Image.extend({ addNodeView() { - // @ts-ignore. Weird type issue probably due to incorrect tiptap types. return ReactNodeViewRenderer(MarkdownEditorImage); }, }); -const MarkdownEditorImage = forwardRef< - HTMLImageElement | HTMLDivElement, - PartialImageNodeProps ->(({ node, updateAttributes, selected, editor }, ref) => { +const MarkdownEditorImage = ({ + node, + updateAttributes, + selected, + editor, + extension, + ref, +}: ReactNodeViewProps) => { const store = useStore(); const [showPicker, setShowPicker] = useState(false); @@ -71,6 +73,13 @@ const MarkdownEditorImage = forwardRef< editor.chain().focus().run(); }; + const uploadAndSet = extension.options.uploadImage + ? async (file: File) => { + const subjects = await extension.options.uploadImage([file]); + setSelectedSubject(subjects[0]); + } + : undefined; + if (node.attrs.src) { return ( @@ -130,16 +139,15 @@ const MarkdownEditorImage = forwardRef< undefined} + onNewFilePicked={uploadAndSet} allowedMimes={imageMimeTypes} /> ); -}); +}; MarkdownEditorImage.displayName = 'MarkdownEditorImage'; diff --git a/browser/data-browser/src/chunks/MarkdownEditor/NodeSelectMenu.tsx b/browser/data-browser/src/chunks/RTE/NodeSelectMenu.tsx similarity index 64% rename from browser/data-browser/src/chunks/MarkdownEditor/NodeSelectMenu.tsx rename to browser/data-browser/src/chunks/RTE/NodeSelectMenu.tsx index 5c2b60c75..61cb01320 100644 --- a/browser/data-browser/src/chunks/MarkdownEditor/NodeSelectMenu.tsx +++ b/browser/data-browser/src/chunks/RTE/NodeSelectMenu.tsx @@ -1,9 +1,12 @@ import { BasicSelect } from '../../components/forms/BasicSelect'; import { useTipTapEditor } from './TiptapContext'; -import type { Editor } from '@tiptap/react'; +import { useEditorState, type Editor } from '@tiptap/react'; const getSelectedNode = (editor: Editor): string => { if (editor.isActive('codeBlock')) return 'codeBlock'; + if (editor.isActive('orderedList')) return 'orderedList'; + if (editor.isActive('bulletList')) return 'bulletList'; + if (editor.isActive('taskList')) return 'taskList'; if (editor.isActive('heading', { level: 1 })) return 'heading-1'; if (editor.isActive('heading', { level: 2 })) return 'heading-2'; if (editor.isActive('heading', { level: 3 })) return 'heading-3'; @@ -24,24 +27,40 @@ const nodeData = (name: string): [title: string, level?: number] => { export function NodeSelectMenu(): React.JSX.Element { const editor = useTipTapEditor(); + const { activeNode } = useEditorState({ + editor, + selector: snapshot => ({ + activeNode: getSelectedNode(snapshot.editor), + }), + }); if (!editor) return <>; - const selectedNode = getSelectedNode(editor); - const changeNodeType = (nodeType: string) => { const [targetNodeTitle, level] = nodeData(nodeType); - editor.commands.setNode(targetNodeTitle, level ? { level } : undefined); + + if (nodeType === 'orderedList') { + editor.commands.toggleOrderedList(); + } else if (nodeType === 'bulletList') { + editor.commands.toggleBulletList(); + } else if (nodeType === 'taskList') { + editor.commands.toggleTaskList(); + } else { + editor.commands.setNode(targetNodeTitle, level ? { level } : undefined); + } }; return ( changeNodeType(e.target.value)} > + + + diff --git a/browser/data-browser/src/chunks/RTE/ResourceExtension/RTENodeViewWrapper.tsx b/browser/data-browser/src/chunks/RTE/ResourceExtension/RTENodeViewWrapper.tsx new file mode 100644 index 000000000..385af66a1 --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/ResourceExtension/RTENodeViewWrapper.tsx @@ -0,0 +1,29 @@ +import { NodeViewWrapper } from '@tiptap/react'; +import { styled } from 'styled-components'; +import styles from './ResourceNode.module.css'; + +const stopPropagation = (e: React.MouseEvent) => + e.stopPropagation(); + +interface RTENodeViewWrapperProps { + wide?: boolean; +} + +export const RTENodeViewWrapper: React.FC< + React.PropsWithChildren +> = ({ children, wide = false }) => { + return ( + + {children} + + ); +}; + +const StyledNodeViewWrapper = styled(NodeViewWrapper)` + margin-bottom: 1rem; +`; diff --git a/browser/data-browser/src/chunks/RTE/ResourceExtension/ResourceComponent.tsx b/browser/data-browser/src/chunks/RTE/ResourceExtension/ResourceComponent.tsx new file mode 100644 index 000000000..5f39d0234 --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/ResourceExtension/ResourceComponent.tsx @@ -0,0 +1,59 @@ +import { AtomicLink } from '@components/AtomicLink'; +import { getIconForClass } from '@helpers/iconMap'; +import type { ReactNodeViewProps } from '@tiptap/react'; +import { NodeViewWrapper } from '@tiptap/react'; +import { dataBrowser, useResource } from '@tomic/react'; +import ResourceCard from '@views/Card/ResourceCard'; +import { styled } from 'styled-components'; +import { TableRTE } from '../TableRTE'; +import { RTENodeViewWrapper } from './RTENodeViewWrapper'; +import { ErrorBoundary } from '@views/ErrorPage'; + +export const ResourceComponent = ( + props: ReactNodeViewProps, +) => { + const resource = useResource(props.node.attrs.subject); + + const [Component, wide] = resource.matchClass( + { + [dataBrowser.classes.table]: [TableRTE, true], + }, + [ResourceCard, false], + ); + + return ( + + + + + + ); +}; + +export const ResourceInlineComponent = ( + props: ReactNodeViewProps, +) => { + const resource = useResource(props.node.attrs.subject); + const Icon = getIconForClass(resource.getClasses()[0]); + + return ( + + + + {resource.title} + + + ); +}; + +const StyledAtomicLink = styled(AtomicLink)` + display: inline-flex; + align-items: center; + gap: 0.5ch; + color: ${props => props.theme.colors.mainSelectedFg}; + background-color: ${props => props.theme.colors.mainSelectedBg}; + padding: 0rem 0.4rem; + border-radius: ${props => props.theme.radius}; + border: 1px solid ${props => props.theme.colors.mainSelectedFg}; + user-select: none; +`; diff --git a/browser/data-browser/src/chunks/RTE/ResourceExtension/ResourceExtention.ts b/browser/data-browser/src/chunks/RTE/ResourceExtension/ResourceExtention.ts new file mode 100644 index 000000000..bf0045bdc --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/ResourceExtension/ResourceExtention.ts @@ -0,0 +1,79 @@ +import { Extension, type Editor, type Range } from '@tiptap/react'; +import { Suggestion, type SuggestionOptions } from '@tiptap/suggestion'; +import type { Store } from '@tomic/react'; +import type { SuggestionItem } from '../types'; +import { getIconForClass } from '@helpers/iconMap'; +import { PluginKey } from '@tiptap/pm/state'; +import { createRenderFunction } from '../SlashMenu/CommandsExtension'; + +const resourceSuggestionPluginKey = new PluginKey('resourceSuggestion'); + +export const ResourceCommands = Extension.create({ + name: 'resourceCommands', + addOptions() { + return { + suggestion: { + char: '@', + // @ts-expect-error I'm not really sure how to type this. + command: ({ editor, range, props }) => { + props.command({ editor, range }); + }, + }, + }; + }, + addProseMirrorPlugins() { + return [ + Suggestion({ + editor: this.editor, + pluginKey: resourceSuggestionPluginKey, + ...this.options.suggestion, + }), + ]; + }, +}); + +export const buildResourceSuggestion = ( + container: HTMLElement, + store: Store, + drive: string, +): Partial => ({ + items: async ({ query }: { query: string }): Promise => { + const results = await store.search(query.toLowerCase(), { + limit: 10, + // Including the results could lead to weird behavior when the document itself is returned from the server. + include: false, + parents: [drive], + }); + + const resources = await Promise.all(results.map(x => store.getResource(x))); + + return resources.map(r => ({ + title: r.title, + id: r.subject, + icon: getIconForClass(r.getClasses()[0]), + command: ({ editor, range }) => { + const subject = r.subject; + const isBlockContext = getIsBlockContext(editor, range); + const command = editor.chain().focus().deleteRange(range); + + if (isBlockContext) { + command.setResource({ subject }).run(); + } else { + command.setResourceInline({ subject }).insertContent(' ').run(); + } + }, + })); + }, + + render: createRenderFunction(container), +}); + +const getIsBlockContext = (editor: Editor, range: Range) => { + const { from } = range; + + // Resolve the position and the parent node + const $pos = editor.state.doc.resolve(from); + + // Text offset tells us the distance to a previous node. This is 0 if there is no previous node meaning we are in a block context. + return $pos.textOffset === 0; +}; diff --git a/browser/data-browser/src/chunks/RTE/ResourceExtension/ResourceNode.module.css b/browser/data-browser/src/chunks/RTE/ResourceExtension/ResourceNode.module.css new file mode 100644 index 000000000..b46118bcd --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/ResourceExtension/ResourceNode.module.css @@ -0,0 +1,18 @@ +/** Can be added to a node view to make it wider than the container.**/ +.wideNode { + /* Add the wide-wrapper class to the node renderer */ +} + +.nodeRenderer { + width: 100%; + + &:has(.wideNode) { + width: 1100px; + margin-left: -150px; + + @container (max-width: 1100px) { + width: 100%; + margin-left: 0; + } + } +} diff --git a/browser/data-browser/src/chunks/RTE/ResourceExtension/ResourceNode.ts b/browser/data-browser/src/chunks/RTE/ResourceExtension/ResourceNode.ts new file mode 100644 index 000000000..d678a4b14 --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/ResourceExtension/ResourceNode.ts @@ -0,0 +1,173 @@ +import { Node } from '@tiptap/core'; +import { ReactNodeViewRenderer } from '@tiptap/react'; +import { unknownSubject, type Store } from '@tomic/react'; +import { + ResourceComponent, + ResourceInlineComponent, +} from './ResourceComponent'; +import styles from './ResourceNode.module.css'; + +interface ResourceNodeOptions { + store?: Store; +} + +export interface SetResourceNodeOptions { + subject: string; +} + +const TYPES = { + BLOCK: 'resource-block', + INLINE: 'resource-inline', +} as const; + +declare module '@tiptap/core' { + interface Commands { + resource: { + /** + * Add a resource view to the document. + * @param options Object containing the subject. + */ + setResource: (options: SetResourceNodeOptions) => ReturnType; + }; + resourceInline: { + setResourceInline: (options: SetResourceNodeOptions) => ReturnType; + }; + } +} + +export const ResourceNode = Node.create({ + name: 'atomic-data-resource', + group: 'block', + atom: true, + + addOptions() { + return { + store: undefined, + }; + }, + + parseHTML() { + return [ + { + tag: `a[data-type="${TYPES.BLOCK}"]`, + getAttrs: node => { + const dataType = node.getAttribute('data-type'); + + if (dataType !== TYPES.BLOCK) { + return false; // Not a resource-block, ignore + } + + return { + subject: node.getAttribute('href'), // Extract the attribute + }; + }, + }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + const title = + this.options.store?.getResourceLoading(HTMLAttributes['subject']).title ?? + ''; + + return [ + 'a', + { + 'data-type': TYPES.BLOCK, + href: HTMLAttributes['subject'], + }, + title, + ]; + }, + + addCommands() { + return { + setResource: + options => + ({ commands }) => { + return commands.insertContent({ + type: this.name, + attrs: options, + }); + }, + }; + }, + + addAttributes() { + return { + subject: { + default: unknownSubject, + }, + }; + }, + + addNodeView() { + return ReactNodeViewRenderer(ResourceComponent, { + className: styles.nodeRenderer, + contentDOMElementTag: 'div', + ignoreMutation: ({ mutation }) => { + return ( + mutation.type === 'attributes' && + mutation.attributeName === 'aria-hidden' + ); + }, + }); + }, +}); + +export const ResourceNodeInline = ResourceNode.extend({ + name: 'atomic-data-resource-inline', + group: 'inline', + inline: true, + parseHTML() { + return [ + { + tag: `a[data-type="${TYPES.INLINE}"]`, + getAttrs: node => { + const dataType = node.getAttribute('data-type'); + + if (dataType !== TYPES.INLINE) { + return false; // Not a resource-block, ignore + } + + return { + 'data-type': TYPES.INLINE, + subject: node.getAttribute('href'), + }; + }, + }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + const title = + this.options.store?.getResourceLoading(HTMLAttributes['subject']).title ?? + ''; + + return [ + 'a', + { + 'data-type': TYPES.INLINE, + href: HTMLAttributes['subject'], + }, + title, + ]; + }, + + addCommands() { + return { + setResourceInline: + options => + ({ commands }) => { + return commands.insertContent({ + type: this.name, + attrs: options, + }); + }, + }; + }, + + addNodeView() { + return ReactNodeViewRenderer(ResourceInlineComponent); + }, +}); diff --git a/browser/data-browser/src/chunks/MarkdownEditor/SlashMenu/CommandList.tsx b/browser/data-browser/src/chunks/RTE/SlashMenu/CommandList.tsx similarity index 69% rename from browser/data-browser/src/chunks/MarkdownEditor/SlashMenu/CommandList.tsx rename to browser/data-browser/src/chunks/RTE/SlashMenu/CommandList.tsx index 3f9a38141..6a71e3b30 100644 --- a/browser/data-browser/src/chunks/MarkdownEditor/SlashMenu/CommandList.tsx +++ b/browser/data-browser/src/chunks/RTE/SlashMenu/CommandList.tsx @@ -1,30 +1,24 @@ -import type { Editor, Range } from '@tiptap/react'; import { transparentize } from 'polished'; import { forwardRef, useState, - useEffect, useImperativeHandle, useId, useCallback, } from 'react'; -import type { IconType } from 'react-icons'; import { styled } from 'styled-components'; import { ScrollArea } from '../../../components/ScrollArea'; +import type { SuggestionItem } from '../types'; +import { useOnValueChange } from '@helpers/useOnValueChange'; +import { Column } from '@components/Row'; export type CommandListRefType = { onKeyDown: (event: KeyboardEvent) => boolean; }; -export type CommandItem = { - title: string; - icon: IconType; - command: (props: { editor: Editor; range: Range }) => void; -}; - export interface CommandListProps { - items: CommandItem[]; - command: (item: CommandItem) => void; + items: SuggestionItem[]; + command: (item: SuggestionItem) => void; } const buildItemId = (compId: string, index: number) => @@ -52,7 +46,7 @@ export const CommandList = forwardRef( [command, items], ); - useEffect(() => setSelectedIndex(0), [items]); + useOnValueChange(() => setSelectedIndex(0), [items]); useImperativeHandle( ref, @@ -89,23 +83,26 @@ export const CommandList = forwardRef( ); return ( - - {items.map((item, index) => { - const Icon = item.icon; - - return ( - selectItem(index)} - onMouseEnter={() => setSelectedIndex(index)} - active={selectedIndex === index} - > - - {item.title} - - ); - })} + + + {items.length === 0 &&
No results found
} + {items.map((item, index) => { + const Icon = item.icon; + + return ( + selectItem(index)} + onMouseEnter={() => setSelectedIndex(index)} + active={selectedIndex === index} + > + + {item.title} + + ); + })} +
); }, @@ -140,4 +137,19 @@ const ListItemButton = styled.button<{ active: boolean }>` gap: 1ch; padding: 0.5rem; border-radius: ${p => p.theme.radius}; + max-width: 60ch; + overflow: hidden; + + & > svg { + min-width: 1rem; + flex-basis: 1rem; + } + + & > span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } `; + +const ContainedColumn = styled(Column)``; diff --git a/browser/data-browser/src/chunks/MarkdownEditor/SlashMenu/CommandsExtension.ts b/browser/data-browser/src/chunks/RTE/SlashMenu/CommandsExtension.ts similarity index 60% rename from browser/data-browser/src/chunks/MarkdownEditor/SlashMenu/CommandsExtension.ts rename to browser/data-browser/src/chunks/RTE/SlashMenu/CommandsExtension.ts index 28071de50..faba5a155 100644 --- a/browser/data-browser/src/chunks/MarkdownEditor/SlashMenu/CommandsExtension.ts +++ b/browser/data-browser/src/chunks/RTE/SlashMenu/CommandsExtension.ts @@ -1,20 +1,28 @@ import { Extension, ReactRenderer } from '@tiptap/react'; -import { Suggestion, type SuggestionOptions } from '@tiptap/suggestion'; -import tippy, { type Instance } from 'tippy.js'; +import { + Suggestion, + type SuggestionOptions, + type SuggestionProps, +} from '@tiptap/suggestion'; +import { computePosition, flip, inline, shift } from '@floating-ui/dom'; +import styles from '../floatingMenu.module.css'; + import { CommandList, - type CommandItem, type CommandListProps, type CommandListRefType, } from './CommandList'; import { + FaCheck, FaCode, FaHeading, FaImage, + FaListOl, FaListUl, FaParagraph, FaQuoteLeft, } from 'react-icons/fa6'; +import type { SuggestionItem } from '../types'; export const SlashCommands = Extension.create({ name: 'slashCommands', @@ -39,37 +47,119 @@ export const SlashCommands = Extension.create({ }, }); +export const createRenderFunction = + (container: HTMLElement): SuggestionOptions['render'] => + () => { + let component: ReactRenderer; + + const updatePosition = (props: SuggestionProps) => { + if (!props.decorationNode) { + return; + } + + computePosition(props.decorationNode, component.element, { + placement: 'bottom-start', + middleware: [flip(), shift(), inline()], + }).then(({ x, y }) => { + component.element.style.setProperty('--left', `${x}px`); + component.element.style.setProperty('--top', `${y}px`); + container.appendChild(component.element); + }); + }; + + return { + onStart(props) { + component = new ReactRenderer(CommandList, { + props, + editor: props.editor, + className: styles.renderer, + }); + + // Set the initial position, this position might be obstructed so we update the position again after we render the elements. + updatePosition(props); + + requestAnimationFrame(() => { + updatePosition(props); + }); + }, + + onUpdate(props) { + component.updateProps(props); + updatePosition(props); + }, + + onKeyDown(props) { + if (props.event.key === 'Escape') { + component.destroy(); + + return true; + } + + if (!component.ref) { + return false; + } + + return component.ref.onKeyDown(props.event); + }, + + onExit() { + component.destroy(); + }, + }; + }; + export const buildSuggestion = ( container: HTMLElement, -): Partial => ({ - items: ({ query }: { query: string }): CommandItem[] => + extraItems: SuggestionItem[] = [], +): Partial> => ({ + items: async ({ query }: { query: string }): Promise => [ { title: 'Bullet List', + id: 'bullet-list', icon: FaListUl, command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleBulletList().run(), - } as CommandItem, + } as SuggestionItem, + { + title: 'Ordered List', + id: 'ordered-list', + icon: FaListOl, + command: ({ editor, range }) => + editor.chain().focus().deleteRange(range).toggleOrderedList().run(), + } as SuggestionItem, + { + title: 'Task List', + id: 'task-list', + icon: FaCheck, + command: ({ editor, range }) => + editor.chain().focus().deleteRange(range).toggleTaskList().run(), + } as SuggestionItem, { title: 'Codeblock', + id: 'codeblock', icon: FaCode, command: ({ editor, range }) => editor.chain().focus().deleteRange(range).setNode('codeBlock').run(), - } as CommandItem, + } as SuggestionItem, { title: 'Quote', + id: 'quote', icon: FaQuoteLeft, command: ({ editor, range }) => editor.chain().focus().deleteRange(range).setBlockquote().run(), - } as CommandItem, + } as SuggestionItem, { title: 'Image', + id: 'image', icon: FaImage, command: ({ editor, range }) => editor.chain().focus().deleteRange(range).setImage({ src: '' }).run(), - } as CommandItem, + } as SuggestionItem, + ...extraItems, { title: 'Heading 1', + id: 'heading-1', icon: FaHeading, command: ({ editor, range }) => editor @@ -78,9 +168,10 @@ export const buildSuggestion = ( .deleteRange(range) .setNode('heading', { level: 1 }) .run(), - } as CommandItem, + } as SuggestionItem, { title: 'Heading 2', + id: 'heading-2', icon: FaHeading, command: ({ editor, range }) => editor @@ -89,9 +180,10 @@ export const buildSuggestion = ( .deleteRange(range) .setNode('heading', { level: 2 }) .run(), - } as CommandItem, + } as SuggestionItem, { title: 'Heading 3', + id: 'heading-3', icon: FaHeading, command: ({ editor, range }) => editor @@ -100,9 +192,10 @@ export const buildSuggestion = ( .deleteRange(range) .setNode('heading', { level: 3 }) .run(), - } as CommandItem, + } as SuggestionItem, { title: 'Heading 4', + id: 'heading-4', icon: FaHeading, command: ({ editor, range }) => editor @@ -111,9 +204,10 @@ export const buildSuggestion = ( .deleteRange(range) .setNode('heading', { level: 4 }) .run(), - } as CommandItem, + } as SuggestionItem, { title: 'Heading 5', + id: 'heading-5', icon: FaHeading, command: ({ editor, range }) => editor @@ -122,9 +216,10 @@ export const buildSuggestion = ( .deleteRange(range) .setNode('heading', { level: 5 }) .run(), - } as CommandItem, + } as SuggestionItem, { title: 'Heading 6', + id: 'heading-6', icon: FaHeading, command: ({ editor, range }) => editor @@ -133,71 +228,15 @@ export const buildSuggestion = ( .deleteRange(range) .setNode('heading', { level: 6 }) .run(), - } as CommandItem, + } as SuggestionItem, { title: 'Paragraph', + id: 'paragraph', icon: FaParagraph, command: ({ editor, range }) => editor.chain().focus().deleteRange(range).setNode('paragraph').run(), - } as CommandItem, + } as SuggestionItem, ].filter(item => item.title.toLowerCase().includes(query.toLowerCase())), - render: () => { - let component: ReactRenderer; - let popup: Instance[]; - - return { - onStart: props => { - component = new ReactRenderer(CommandList, { - props, - editor: props.editor, - }); - - if (!props.clientRect) { - return; - } - - popup = tippy('body', { - getReferenceClientRect: props.clientRect as () => DOMRect, - appendTo: () => container, - content: component.element, - showOnCreate: true, - interactive: true, - trigger: 'manual', - placement: 'bottom-start', - }); - }, - - onUpdate(props) { - component.updateProps(props); - - if (!props.clientRect) { - return; - } - - popup[0].setProps({ - getReferenceClientRect: props.clientRect as () => DOMRect, - }); - }, - - onKeyDown(props) { - if (props.event.key === 'Escape') { - popup[0].hide(); - - return true; - } - - if (!component.ref) { - return false; - } - - return component.ref.onKeyDown(props.event); - }, - - onExit() { - popup[0].destroy(); - component.destroy(); - }, - }; - }, + render: createRenderFunction(container), }); diff --git a/browser/data-browser/src/chunks/RTE/TableRTE.tsx b/browser/data-browser/src/chunks/RTE/TableRTE.tsx new file mode 100644 index 000000000..2d1c146bb --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/TableRTE.tsx @@ -0,0 +1,33 @@ +import { AtomicLink } from '@components/AtomicLink'; +import { HideInPrint } from '@components/HideInPrint'; +import { useResource, type DataBrowser } from '@tomic/react'; +import { TableResource } from '@views/TablePage/TableResource'; +import { FaArrowUpRightFromSquare } from 'react-icons/fa6'; +import { styled } from 'styled-components'; + +interface TableRTEProps { + subject: string; +} + +export const TableRTE: React.FC = ({ subject }) => { + const resource = useResource(subject); + + return ( + +
+ + + {resource.title} + +
+
+ ); +}; + +const TableTitle = styled(AtomicLink)` + display: flex; + align-items: center; + gap: 1ch; + color: ${p => p.theme.colors.textLight}; + padding-inline-start: 0.5rem; +`; diff --git a/browser/data-browser/src/chunks/MarkdownEditor/TiptapContext.tsx b/browser/data-browser/src/chunks/RTE/TiptapContext.tsx similarity index 50% rename from browser/data-browser/src/chunks/MarkdownEditor/TiptapContext.tsx rename to browser/data-browser/src/chunks/RTE/TiptapContext.tsx index 1ac48b7e3..aa48a9ed9 100644 --- a/browser/data-browser/src/chunks/MarkdownEditor/TiptapContext.tsx +++ b/browser/data-browser/src/chunks/RTE/TiptapContext.tsx @@ -1,20 +1,22 @@ import type { Editor } from '@tiptap/react'; import { createContext, useContext } from 'react'; -type TiptapContextType = Editor | null; +type TiptapContextType = Editor; -export const TiptapContext = createContext(null); +export const TiptapContext = createContext({} as Editor); export const useTipTapEditor = (): TiptapContextType => useContext(TiptapContext); interface TipTapContextProviderProps { - editor: Editor | null; + editor: Editor; } export const TiptapContextProvider = ({ editor, children, -}: React.PropsWithChildren) => ( - {children} -); +}: React.PropsWithChildren) => { + return ( + {children} + ); +}; diff --git a/browser/data-browser/src/chunks/MarkdownEditor/ToggleButton.tsx b/browser/data-browser/src/chunks/RTE/ToggleButton.tsx similarity index 100% rename from browser/data-browser/src/chunks/MarkdownEditor/ToggleButton.tsx rename to browser/data-browser/src/chunks/RTE/ToggleButton.tsx diff --git a/browser/data-browser/src/chunks/RTE/floatingMenu.module.css b/browser/data-browser/src/chunks/RTE/floatingMenu.module.css new file mode 100644 index 000000000..924389b47 --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/floatingMenu.module.css @@ -0,0 +1,6 @@ +.renderer { + position: absolute; + top: var(--top, 0); + left: var(--left, 0); + width: max-content; +} diff --git a/browser/data-browser/src/chunks/RTE/getCollaborativeEditorSchema.ts b/browser/data-browser/src/chunks/RTE/getCollaborativeEditorSchema.ts new file mode 100644 index 000000000..8b5c0e52c --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/getCollaborativeEditorSchema.ts @@ -0,0 +1,90 @@ +import { getSchema, type Extensions } from '@tiptap/core'; +import { Link } from '@tiptap/extension-link'; +import { TaskList, TaskItem } from '@tiptap/extension-list'; +import TextAlign from '@tiptap/extension-text-align'; +import { + TextStyle, + Color, + BackgroundColor, +} from '@tiptap/extension-text-style'; +import Typography from '@tiptap/extension-typography'; +import StarterKit from '@tiptap/starter-kit'; +import { type Store } from '@tomic/react'; +import { + ResourceNode, + ResourceNodeInline, +} from './ResourceExtension/ResourceNode'; +import Image from '@tiptap/extension-image'; +import type { Schema } from '@tiptap/pm/model'; + +export function getCollaborativeEditorSchema(store: Store): { + schema: Schema; + extensions: Extensions; +} { + const extensions = [ + StarterKit.configure({ + undoRedo: false, + link: false, + }), + Typography, + Link.extend({ + parseHTML: () => [ + { + tag: 'a[href]', + getAttrs: node => { + // Links with a data-type are custom nodes that should be ignored by the link extension + if (node.getAttribute('data-type')) { + return false; + } + + // Default link parsing + return { + href: node.getAttribute('href'), + target: node.getAttribute('target'), + }; + }, + }, + ], + }).configure({ + autolink: true, + openOnClick: true, + protocols: [ + 'http', + 'https', + 'mailto', + { + scheme: 'tel', + optionalSlashes: true, + }, + ], + HTMLAttributes: { + class: 'tiptap-link', + rel: 'noopener noreferrer', + target: '_blank', + }, + }), + Image.configure({ + HTMLAttributes: { + class: 'tiptap-image', + }, + }), + ResourceNode.configure({ + store, + }), + ResourceNodeInline.configure({ + store, + }), + TextAlign.configure({ + types: ['heading', 'paragraph'], + }), + TaskList, + TaskItem.configure({ + nested: true, + }), + TextStyle, + Color, + BackgroundColor, + ]; + + return { schema: getSchema(extensions), extensions }; +} diff --git a/browser/data-browser/src/chunks/RTE/sharedEditorStyles.ts b/browser/data-browser/src/chunks/RTE/sharedEditorStyles.ts new file mode 100644 index 000000000..f344ef5e4 --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/sharedEditorStyles.ts @@ -0,0 +1,68 @@ +// Textareas do not automatically grow when the content exceeds the height of the textarea. + +import { styled } from 'styled-components'; +import { EditorWrapperBase } from './EditorWrapperBase'; +import { ToggleButton } from './ToggleButton'; +import { transition } from '../../helpers/transition'; + +const MIN_EDITOR_HEIGHT = '10rem'; +// The lineheight of a textarea. +const LINE_HEIGHT = 1.15; + +// This function calculates the height of the textarea based on the number of lines in the content. +const calcHeight = (value: string) => { + const lines = value.split('\n').length; + + return `calc(${lines * LINE_HEIGHT}em + 5px)`; +}; + +export const StyledEditorWrapper = styled(EditorWrapperBase)` + min-height: ${MIN_EDITOR_HEIGHT}; + border-radius: ${p => p.theme.radius}; + box-shadow: 0 0 0 1px ${p => p.theme.colors.bg2}; + min-height: ${MIN_EDITOR_HEIGHT}; + padding: ${p => p.theme.size()}; + ${transition('box-shadow')} + + &:focus-within { + box-shadow: 0 0 0 2px ${p => p.theme.colors.main}; + } + + & .tiptap { + width: min(100%, 75ch); + min-height: ${MIN_EDITOR_HEIGHT}; + + table { + border-collapse: collapse; + td, + th { + border: 1px solid ${p => p.theme.colors.bg2}; + padding: ${p => p.theme.size(2)}; + } + } + } +`; + +export const RawEditor = styled.textarea.attrs(p => ({ + style: { height: calcHeight((p.value as string) ?? '') }, +}))` + border: none; + width: 100%; + min-height: ${MIN_EDITOR_HEIGHT}; + outline: none; + overflow: visible; + height: fit-content; + background-color: transparent; + color: ${p => p.theme.colors.text}; + resize: none; +`; + +export const FloatingMenuText = styled.span` + color: ${p => p.theme.colors.textLight}; +`; + +export const FloatingCodeButton = styled(ToggleButton)` + position: absolute; + top: 0.5rem; + right: 0.5rem; +`; diff --git a/browser/data-browser/src/chunks/RTE/types.ts b/browser/data-browser/src/chunks/RTE/types.ts new file mode 100644 index 000000000..54238eff1 --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/types.ts @@ -0,0 +1,9 @@ +import type { Editor, Range } from '@tiptap/react'; +import type { IconType } from 'react-icons'; + +export type SuggestionItem = { + id: string; + title: string; + icon: IconType; + command: (props: { editor: Editor; range: Range }) => void; +}; diff --git a/browser/data-browser/src/chunks/RTE/useYSync.ts b/browser/data-browser/src/chunks/RTE/useYSync.ts new file mode 100644 index 000000000..ab71731a4 --- /dev/null +++ b/browser/data-browser/src/chunks/RTE/useYSync.ts @@ -0,0 +1,83 @@ +import { useStore, type Resource } from '@tomic/react'; +import { useEffect } from 'react'; +import * as awarenessProtocol from 'y-protocols/awareness'; +import * as Y from 'yjs'; + +type AwarenessUpdate = { + added: number[]; + removed: number[]; + updated: number[]; +}; + +export function useYSync( + resource: Resource, + property: string, + doc: Y.Doc, +): awarenessProtocol.Awareness { + const store = useStore(); + const awareness = new awarenessProtocol.Awareness(doc); + + useEffect(() => { + const handleAwarenessUpdate = ( + { added, updated, removed }: AwarenessUpdate, + origin: string, + ) => { + if (origin !== 'local') { + // Only send local updates to the server. + return; + } + + const changedClients = [...updated, ...added, ...removed]; + + const encodedUpdate = awarenessProtocol.encodeAwarenessUpdate( + awareness, + changedClients, + ); + + store.broadcastYSyncUpdate(resource.subject, property, { + awarenessUpdate: encodedUpdate, + }); + }; + + awareness.on('update', handleAwarenessUpdate); + + const unsubYSync = store.subscribeYSync( + resource.subject, + property, + ({ awarenessUpdate, docUpdate }) => { + if (awarenessUpdate) { + awarenessProtocol.applyAwarenessUpdate( + awareness, + awarenessUpdate, + 'server', + ); + } + + if (docUpdate) { + Y.applyUpdateV2(doc, docUpdate); + } + }, + ); + + return () => { + awareness.off('update', handleAwarenessUpdate); + unsubYSync(); + }; + }, [awareness, resource.subject, property, store, doc]); + + useEffect(() => { + const cb = doc.on('updateV2', (udpate, _origin, _doc, transaction) => { + if (transaction.local) { + store.broadcastYSyncUpdate(resource.subject, property, { + docUpdate: udpate, + }); + } + }); + + return () => { + doc.off('updateV2', cb); + }; + }, [resource.subject, property, store, doc]); + + return awareness; +} diff --git a/browser/data-browser/src/components/AllPropsSimple.tsx b/browser/data-browser/src/components/AllPropsSimple.tsx index 1b8b8b514..c7907248a 100644 --- a/browser/data-browser/src/components/AllPropsSimple.tsx +++ b/browser/data-browser/src/components/AllPropsSimple.tsx @@ -1,11 +1,12 @@ import { datatypes, - JSONValue, + AtomicValue, properties, Resource, useResource, useSubject, useTitle, + isYDoc, } from '@tomic/react'; import { useMemo, type JSX } from 'react'; import { styled } from 'styled-components'; @@ -19,16 +20,18 @@ export interface AllPropsSimpleProps { export function AllPropsSimple({ resource }: AllPropsSimpleProps): JSX.Element { return (
    - {[...resource.getPropVals()].map(([prop, val]) => ( - - ))} + {[...resource.getPropVals()] + .filter(([_, val]) => !isYDoc(val)) + .map(([prop, val]) => ( + + ))}
); } interface RowProps { prop: string; - val: JSONValue; + val: AtomicValue; } function Row({ prop, val }: RowProps): JSX.Element { diff --git a/browser/data-browser/src/components/AtomicLink.tsx b/browser/data-browser/src/components/AtomicLink.tsx index bc65d6f39..d4d4e3089 100644 --- a/browser/data-browser/src/components/AtomicLink.tsx +++ b/browser/data-browser/src/components/AtomicLink.tsx @@ -1,4 +1,4 @@ -import { ReactNode, forwardRef, type JSX } from 'react'; +import { ReactNode, useCallback, useEffect, useRef } from 'react'; import { styled } from 'styled-components'; import { constructOpenURL, pathToURL } from '../helpers/navigation'; import { FaExternalLinkAlt } from 'react-icons/fa'; @@ -6,6 +6,8 @@ import { ErrorLook } from '../components/ErrorLook'; import { isRunningInTauri } from '../helpers/tauri'; import { useNavigateWithTransition } from '../hooks/useNavigateWithTransition'; import clsx from 'clsx'; +import { useIsInRTE } from '@hooks/useIsInRTE'; +import { useCombineRefs } from '@hooks/useCombineRefs'; export interface AtomicLinkProps extends React.AnchorHTMLAttributes { @@ -21,83 +23,130 @@ export interface AtomicLinkProps clean?: boolean; /** Used to extend with styled */ className?: string; + ref?: React.Ref; } /** * Renders a link. Either a subject or a href is required. You can wrap this * around other components and pass the `clean` prop to skip styling. */ -export const AtomicLink = forwardRef( - ( - { children, clean, subject, path, href, untabbable, className, ...props }, - ref, - ): JSX.Element => { - const navigate = useNavigateWithTransition(); - - if (subject === undefined && href === undefined && path === undefined) { - return ( - - No `subject`, `path` or `href` passed to this AtomicLink. - - ); +export const AtomicLink: React.FC> = ({ + children, + clean, + subject, + path, + href, + untabbable, + className, + ref, + ...props +}) => { + const innerRef = useRef(null); + const combinedRef = useCombineRefs([ref, innerRef]); + const navigate = useNavigateWithTransition(); + const isInRTE = useIsInRTE(); + + let isOnCurrentPage: boolean; + + const handleClick = (e: React.MouseEvent) => { + if (href) { + // When there is a regular URL, let the browser handle it + return; } - let isOnCurrentPage: boolean; + e.preventDefault(); - try { - isOnCurrentPage = subject - ? window.location.toString() === constructOpenURL(subject) - : false; - } catch (e) { - return {subject}; + if (path) { + navigate(path); + + return; } - const handleClick = (e: React.MouseEvent) => { - if (href) { - // When there is a regular URL, let the browser handle it + if (subject) { + if (isOnCurrentPage) { return; } - e.preventDefault(); + navigate(constructOpenURL(subject)); + } + }; - if (path) { - navigate(path); + const constructHref = useCallback( + () => href || subject || pathToURL(path!), + [href, subject, path], + ); - return; - } + let hrefConstructed: string | undefined = constructHref(); - if (subject) { - if (isOnCurrentPage) { - return; - } + if (isInRTE) { + // HACK: The Tiptap editor has an event handler that always opens links in new tabs. We can't disable it so we have to remove the href from links when inside the editor. + hrefConstructed = undefined; + } - navigate(constructOpenURL(subject)); - } + useEffect(() => { + if (!innerRef.current) return; + + if (!isInRTE) return; + + // HACK: Because we remove the href from the links in the RTE we need to restore them when printing. + const handleBeforePrint = () => { + innerRef.current?.setAttribute('href', constructHref()); }; - const hrefConstructed = href || subject || pathToURL(path!); + const handleAfterPrint = () => { + innerRef.current?.removeAttribute('href'); + }; + window.addEventListener('beforeprint', handleBeforePrint); + window.addEventListener('afterprint', handleAfterPrint); + + return () => { + window.removeEventListener('beforeprint', handleBeforePrint); + window.removeEventListener('afterprint', handleAfterPrint); + }; + }, [constructHref, isInRTE]); + + if (subject === undefined && href === undefined && path === undefined) { return ( - - {children} - {href && !clean && } - + + No `subject`, `path` or `href` passed to this AtomicLink. + ); - }, -); + } + + try { + isOnCurrentPage = subject + ? window.location.toString() === constructOpenURL(subject) + : false; + } catch (e) { + return {subject}; + } + + return ( + + {children} + {href && !clean && ( + <> + {' '} + + + )} + + ); +}; AtomicLink.displayName = 'AtomicLink'; @@ -124,7 +173,6 @@ export const LinkView = styled.a` } &.atomic-link_external { - display: inline-flex; align-items: center; gap: 0.6ch; } diff --git a/browser/data-browser/src/components/Button.tsx b/browser/data-browser/src/components/Button.tsx index 3ca411d73..f0381cf7b 100644 --- a/browser/data-browser/src/components/Button.tsx +++ b/browser/data-browser/src/components/Button.tsx @@ -43,7 +43,6 @@ const getButtonComp = ({ clean, icon, subtle, alert }: ButtonProps) => { } if (clean) { - // @ts-ignore Comp = ButtonClean; } @@ -131,7 +130,6 @@ interface ButtonBarProps { } /** Button inside the navigation bar */ -// eslint-disable-next-line prettier/prettier export const ButtonBar = styled(ButtonClean)` padding-right: 0.5rem; padding-left: 0.5rem; @@ -157,7 +155,6 @@ export const ButtonBar = styled(ButtonClean)` `; /** Button with some optional margins around it */ -// eslint-disable-next-line prettier/prettier export const ButtonDefault = styled(ButtonBase)` --button-bg-color: ${p => p.theme.colors.main}; --button-bg-color-hover: ${p => p.theme.colors.mainLight}; diff --git a/browser/data-browser/src/components/ButtonGroup.tsx b/browser/data-browser/src/components/ButtonGroup.tsx index 6266a15b9..2176f7368 100644 --- a/browser/data-browser/src/components/ButtonGroup.tsx +++ b/browser/data-browser/src/components/ButtonGroup.tsx @@ -12,22 +12,25 @@ export interface ButtonGroupProps { options: ButtonGroupOption[]; name: string; onChange: (value: string) => void; + /** Setting value will make the button group controlled */ + value?: string; } export function ButtonGroup({ options, name, onChange, + value, }: ButtonGroupProps): JSX.Element { const [selected, setSelected] = useState( () => options.find(o => o.checked)?.value, ); const handleChange = useCallback( - (checked: boolean, value: string) => { + (checked: boolean, newVal: string) => { if (checked) { - onChange(value); - setSelected(value); + onChange(newVal); + setSelected(newVal); } }, [onChange], @@ -40,7 +43,7 @@ export function ButtonGroup({ {...option} key={option.value} onChange={handleChange} - checked={selected === option.value} + checked={(value ?? selected) === option.value} name={name} /> ))} @@ -115,6 +118,7 @@ const Label = styled.label` input:checked + & { background-color: ${p => p.theme.colors.bg1}; color: ${p => p.theme.colors.text}; + border: 1px solid ${p => p.theme.colors.bg2}; } :hover { diff --git a/browser/data-browser/src/components/CodeBlock.tsx b/browser/data-browser/src/components/CodeBlock.tsx index aafe0fa56..3137acb0d 100644 --- a/browser/data-browser/src/components/CodeBlock.tsx +++ b/browser/data-browser/src/components/CodeBlock.tsx @@ -7,9 +7,14 @@ import { Button } from './Button'; interface CodeBlockProps { content?: string; loading?: boolean; + wordWrap?: boolean; } -export function CodeBlock({ content, loading }: CodeBlockProps) { +export function CodeBlock({ + content, + loading, + wordWrap = false, +}: CodeBlockProps) { const [isCopied, setIsCopied] = useState(undefined); function copyToClipboard() { @@ -19,7 +24,10 @@ export function CodeBlock({ content, loading }: CodeBlockProps) { } return ( - + {loading ? ( 'loading...' ) : ( @@ -55,4 +63,8 @@ export const CodeBlockStyled = styled.pre` font-family: monospace; width: 100%; overflow-x: auto; + + &.word-wrap { + white-space: pre-wrap; + } `; diff --git a/browser/data-browser/src/components/CustomPopover.tsx b/browser/data-browser/src/components/CustomPopover.tsx new file mode 100644 index 000000000..29143b890 --- /dev/null +++ b/browser/data-browser/src/components/CustomPopover.tsx @@ -0,0 +1,188 @@ +import { + useEffect, + useId, + useRef, + useState, + type ReactNode, + type RefObject, +} from 'react'; +import { styled } from 'styled-components'; +import { transparentize } from 'polished'; +import { fadeIn } from '@helpers/commonAnimations'; +import { useControlLock } from '@hooks/useControlLock'; +import { useDialogTreeInfo } from './Dialog/dialogContext'; +import { useOnValueChange } from '@helpers/useOnValueChange'; + +export interface TriggerProps { + onClick: () => void; + 'data-popover-target': string; +} + +export interface PopoverPropsFromHook { + isOpen: boolean; + setIsOpen: React.Dispatch>; + anchorName: string; +} +export interface PopoverProps extends PopoverPropsFromHook { + Trigger: ReactNode; + className?: string; + noLock?: boolean; +} + +export interface UsePopoverProps { + defaultOpen?: boolean; + autoFocusElement?: RefObject; +} + +export interface UsePopoverReturn { + triggerProps: { + onClick: () => void; + 'data-popover-target': string; + }; + popoverProps: PopoverPropsFromHook; + openPopover: () => void; + closePopover: () => void; + isOpen: boolean; +} + +export const usePopover = ({ + defaultOpen = false, + autoFocusElement, +}: UsePopoverProps): UsePopoverReturn => { + const id = useId(); + const [isOpen, setIsOpen] = useState(defaultOpen); + const { setHasOpenInnerPopup } = useDialogTreeInfo(); + + const openPopover = () => { + setIsOpen(true); + }; + + const closePopover = () => { + setIsOpen(false); + }; + + const triggerProps = { + onClick: () => setIsOpen(prev => !prev), + 'data-popover-target': id, + }; + + const popoverProps = { + anchorName: id, + isOpen, + setIsOpen, + }; + + useOnValueChange(() => { + setHasOpenInnerPopup(isOpen); + }, [isOpen]); + + useEffect(() => { + if (isOpen && autoFocusElement && autoFocusElement.current) { + autoFocusElement.current.focus(); + } + }, [isOpen, autoFocusElement]); + + return { triggerProps, popoverProps, openPopover, closePopover, isOpen }; +}; + +/** + * Popover component, consists of an outer dialog element and an inner content div. + * To style the content div use `${CustomPopover.Content}: { ... }` + */ +export function CustomPopover({ + Trigger, + anchorName, + isOpen, + setIsOpen, + className, + noLock, + children, +}: React.PropsWithChildren) { + const popoverRef = useRef(null); + const contentRef = useRef(null); + + useEffect(() => { + if (isOpen && !popoverRef.current?.matches(':popover-open')) { + popoverRef.current?.showPopover(); + } else if (!isOpen && popoverRef.current?.matches(':popover-open')) { + popoverRef.current?.hidePopover(); + } + }, [isOpen]); + + useEffect(() => { + const handleToggle = (e: ToggleEvent) => { + if (e.newState === 'closed') { + setIsOpen(false); + } + }; + + if (!popoverRef.current) return; + + const popover = popoverRef.current; + popover.addEventListener('toggle', handleToggle); + + return () => { + popover.removeEventListener('toggle', handleToggle); + }; + }, [setIsOpen]); + + useControlLock(!noLock && !!isOpen); + + return ( + + {Trigger} + + {isOpen && children} + + + ); +} + +const PopoverContent = styled.div``; + +CustomPopover.Content = PopoverContent; + +const Wrapper = styled.div<{ anchorName: string }>` + display: contents; + + & *[data-popover-target='${p => p.anchorName}'] { + anchor-name: --${p => p.anchorName}; + } +`; + +const Popover = styled.div<{ anchorName: string }>` + @position-try --top-right { + position-area: top span-right; + } + @position-try --top-left { + position-area: top span-left; + } + @position-try --bottom-right { + position-area: bottom span-right; + } + @position-try --bottom-left { + position-area: bottom span-left; + } + + border: none; + background-color: ${p => transparentize(0.2, p.theme.colors.bgBody)}; + backdrop-filter: blur(10px); + box-shadow: ${p => p.theme.boxShadowSoft}; + border-radius: ${p => p.theme.radius}; + animation: ${fadeIn} 0.1s ease-in-out; + margin: 0; + padding: 0; + inset: auto; + position: fixed; + position-anchor: --${p => p.anchorName}; + position-area: top center; + position-try: --top-right, --top-left, --bottom-right, --bottom-left; + max-height: unset; + min-width: max-content; +`; diff --git a/browser/data-browser/src/components/Dialog/index.tsx b/browser/data-browser/src/components/Dialog/index.tsx index 816275c51..5db470a39 100644 --- a/browser/data-browser/src/components/Dialog/index.tsx +++ b/browser/data-browser/src/components/Dialog/index.tsx @@ -113,7 +113,7 @@ const InnerDialog: React.FC> = ({ cancelDialog(); } }, - [innerDialogRef.current, cancelDialog, isTopLevel], + [cancelDialog, isTopLevel], ); // Close the dialog when the escape key is pressed @@ -139,15 +139,13 @@ const InnerDialog: React.FC> = ({ if (show) { if (!dialogRef.current.hasAttribute('open')) - // @ts-ignore dialogRef.current.showModal(); } if (dialogRef.current.hasAttribute('data-closing')) { // TODO: Use getAnimations() api to wait for the animations to complete instead of a timeout. return timeoutEffect(() => { - // @ts-ignore - dialogRef.current.close(); + dialogRef.current?.close(); dialogRef.current?.removeAttribute('data-closing'); onClosed(); }, ANIM_MS); diff --git a/browser/data-browser/src/components/HideInPrint.tsx b/browser/data-browser/src/components/HideInPrint.tsx new file mode 100644 index 000000000..2e8e9ba49 --- /dev/null +++ b/browser/data-browser/src/components/HideInPrint.tsx @@ -0,0 +1,8 @@ +import { styled } from 'styled-components'; + +export const HideInPrint = styled.div` + display: contents; + @media print { + display: none; + } +`; diff --git a/browser/data-browser/src/components/HotKeyWrapper.tsx b/browser/data-browser/src/components/HotKeyWrapper.tsx index c6d0dd5d0..28a59475f 100644 --- a/browser/data-browser/src/components/HotKeyWrapper.tsx +++ b/browser/data-browser/src/components/HotKeyWrapper.tsx @@ -74,7 +74,10 @@ function HotKeysWrapper({ children }: Props): JSX.Element { shortcuts.edit, e => { e.preventDefault(); - Client.isValidSubject(subject) && navigate(editURL(subject!)); + + if (Client.isValidSubject(subject)) { + navigate(editURL(subject!)); + } }, {}, [subject], @@ -83,7 +86,10 @@ function HotKeysWrapper({ children }: Props): JSX.Element { shortcuts.data, e => { e.preventDefault(); - Client.isValidSubject(subject) && navigate(dataURL(subject!)); + + if (Client.isValidSubject(subject)) { + navigate(dataURL(subject!)); + } }, {}, [subject], diff --git a/browser/data-browser/src/components/IconButton/IconButton.tsx b/browser/data-browser/src/components/IconButton/IconButton.tsx index 55234857c..bda31d8e8 100644 --- a/browser/data-browser/src/components/IconButton/IconButton.tsx +++ b/browser/data-browser/src/components/IconButton/IconButton.tsx @@ -227,7 +227,8 @@ const MagicIconButton = styled(IconButtonBase)` opacity: 0; z-index: -2; will-change: filter; - background: radial-gradient(ellipse at top right, #365ccd, transparent), + background: + radial-gradient(ellipse at top right, #365ccd, transparent), radial-gradient( ellipse at bottom left, ${adjustHue(-45, '#365ccd')}, diff --git a/browser/data-browser/src/components/LocaleContext.tsx b/browser/data-browser/src/components/LocaleContext.tsx index 531affd8f..7d6ca41c8 100644 --- a/browser/data-browser/src/components/LocaleContext.tsx +++ b/browser/data-browser/src/components/LocaleContext.tsx @@ -1,13 +1,14 @@ import { useLocalStorage } from '@hooks/useLocalStorage'; import React, { createContext, useContext, useEffect, useState } from 'react'; import { loadLocale } from 'wuchale/load-utils'; +import { useOnValueChange } from '@helpers/useOnValueChange'; -interface LocaleContext { +interface LocaleContextType { locale: string; setLocale: (locale: string) => void; } -const LocaleContext = createContext({ +const LocaleContext = createContext({ locale: 'en', setLocale: () => {}, }); @@ -21,8 +22,11 @@ export const LocaleProvider = ({ children }: React.PropsWithChildren) => { ); const [localeLoaded, setLocaleLoaded] = useState(false); - useEffect(() => { + useOnValueChange(() => { setLocaleLoaded(false); + }, [locale]); + + useEffect(() => { loadLocale(locale).then(() => setLocaleLoaded(true)); }, [locale]); diff --git a/browser/data-browser/src/components/Main.tsx b/browser/data-browser/src/components/Main.tsx index bce635d09..466d6d7d3 100644 --- a/browser/data-browser/src/components/Main.tsx +++ b/browser/data-browser/src/components/Main.tsx @@ -6,7 +6,6 @@ import { transitionName, } from '../helpers/transitionName'; import { ViewTransitionProps } from '../helpers/ViewTransitionProps'; -import { MAIN_CONTAINER } from '../helpers/containers'; import Parent from './Parent'; import { useResource } from '@tomic/react'; import { CalculatedPageHeight } from '../globalCssVars'; @@ -35,7 +34,6 @@ export function Main({ } const StyledMain = memo(styled.main` - container: ${MAIN_CONTAINER} / inline-size; ${p => transitionName(RESOURCE_PAGE_TRANSITION_TAG, p.subject)}; height: calc( ${CalculatedPageHeight.var()} - ${p => p.theme.heights.breadCrumbBar} @@ -45,9 +43,20 @@ const StyledMain = memo(styled.main` ${p => p.theme.heights.breadCrumbBar} + ${p => p.theme.size(2)} ); - width: 100%; + width: 100cqw; @media (prefers-reduced-motion: no-preference) { scroll-behavior: smooth; } + + @media print { + display: block; + position: static; + height: auto; + overflow-y: visible; + overflow: visible; + scroll-padding: 0; + page-break-after: auto; + page-break-inside: auto; + } `); diff --git a/browser/data-browser/src/components/Navigation.tsx b/browser/data-browser/src/components/Navigation.tsx index 19997fc06..a20b06172 100644 --- a/browser/data-browser/src/components/Navigation.tsx +++ b/browser/data-browser/src/components/Navigation.tsx @@ -16,6 +16,8 @@ import { SearchbarFakeInput } from './Searchbar/SearchbarInput'; import { CalculatedPageHeight } from '../globalCssVars'; import { AISidebarContextProvider } from './AI/AISidebarContext'; import { AISidebarContainer } from './AI/AISidebarContainer'; +import { HideInPrint } from './HideInPrint'; +import { MAIN_CONTAINER } from '@helpers/containers'; export const NAVBAR_HEIGHT = '2.5rem'; @@ -43,8 +45,6 @@ const AISidebarMemo = React.memo(AISidebarContainer); /** Wraps the entire app and adds a navbar at the bottom or the top */ export function NavWrapper({ children }: NavWrapperProps): JSX.Element { const { navbarTop, navbarFloating } = useSettings(); - const contentRef = React.useRef(null); - const navbarPosition = getPosition(navbarTop, navbarFloating); return ( @@ -52,14 +52,12 @@ export function NavWrapper({ children }: NavWrapperProps): JSX.Element { {navbarTop && } - + {children} - + + + {!navbarTop && } @@ -74,7 +72,7 @@ interface ContentProps { const Content = styled.div` display: block; flex: 1; - overflow-y: auto; + container: ${MAIN_CONTAINER} / inline-size; `; /** Persistently shown navigation bar */ @@ -91,7 +89,7 @@ function NavBar(): JSX.Element { const isInStandaloneMode = React.useMemo( () => machesStandalone || - //@ts-ignore + // @ts-expect-error standalone is available on the navigator object. window.navigator.standalone || document.referrer.includes('android-app://') || isRunningInTauri(), @@ -158,6 +156,10 @@ const NavBarBase = styled.div` display: none; } } + + @media print { + display: none; + } `; /** Width of the floating navbar in rem */ @@ -231,4 +233,11 @@ const SideBarWrapper = styled.div<{ navbarPosition: NavBarPosition }>` @starting-style { opacity: 0; } + + @media print { + height: auto; + ${CalculatedPageHeight.define('auto')} + position: static; + display: block; + } `; diff --git a/browser/data-browser/src/components/NewInstanceButton/Base.tsx b/browser/data-browser/src/components/NewInstanceButton/Base.tsx index 918236295..19af55cc8 100644 --- a/browser/data-browser/src/components/NewInstanceButton/Base.tsx +++ b/browser/data-browser/src/components/NewInstanceButton/Base.tsx @@ -59,7 +59,7 @@ export function Base({ {label} ) : ( - label ?? title + (label ?? title) )} {children} diff --git a/browser/data-browser/src/components/Parent.tsx b/browser/data-browser/src/components/Parent.tsx index a44b5bc27..b9d6626d1 100644 --- a/browser/data-browser/src/components/Parent.tsx +++ b/browser/data-browser/src/components/Parent.tsx @@ -34,31 +34,28 @@ function Parent({ resource }: ParentProps): JSX.Element { return ( - - {parent ? ( - - ) : ( - - )} + {!parent && } + + {parent && } {resource.title} - - - {enableAI && ( - setIsOpen(prev => !prev)} - > - - - )} - - - + + + + {enableAI && ( + setIsOpen(prev => !prev)} + > + + + )} + + ); } @@ -74,6 +71,10 @@ const ParentWrapper = styled.nav` justify-content: flex-start; view-transition-name: ${BREADCRUMB_BAR_TRANSITION_TAG}; + + @media print { + display: none; + } `; type NestedParentProps = { @@ -155,6 +156,7 @@ const BreadCrumbBase = css` white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + min-width: 0; `; const BreadCrumbCurrent = styled.span` @@ -164,7 +166,7 @@ const BreadCrumbCurrent = styled.span` const Breadcrumb = styled.a` ${BreadCrumbBase} align-self: center; - cursor: 'pointer'; + cursor: pointer; text-decoration: none; border-radius: ${p => p.theme.radius}; @@ -178,6 +180,16 @@ const Breadcrumb = styled.a` } `; +const BreadcrumbRow = styled(Row)` + flex-shrink: 1; + min-width: 0; + overflow: hidden; + max-width: 80vw; + & > * { + min-width: 0; + } +`; + const Spacer = styled.span` flex: 1; `; diff --git a/browser/data-browser/src/components/Popover.tsx b/browser/data-browser/src/components/Popover.tsx index 29f2b25d3..0ac006222 100644 --- a/browser/data-browser/src/components/Popover.tsx +++ b/browser/data-browser/src/components/Popover.tsx @@ -26,6 +26,7 @@ export interface PopoverProps { noArrow?: boolean; noLock?: boolean; modal?: boolean; + side?: 'top' | 'bottom' | 'left' | 'right'; } export function Popover({ @@ -38,6 +39,7 @@ export function Popover({ modal, onOpenChange, Trigger, + side = 'bottom', }: PropsWithChildren): JSX.Element { const { setHasOpenInnerPopup } = useDialogTreeInfo(); const containerRef = useContext(PopoverContainerContext); @@ -67,7 +69,12 @@ export function Popover({ > {Trigger} - + {children} {!noArrow && } diff --git a/browser/data-browser/src/components/PropVal.tsx b/browser/data-browser/src/components/PropVal.tsx index 970a23f2c..8fbcce5f4 100644 --- a/browser/data-browser/src/components/PropVal.tsx +++ b/browser/data-browser/src/components/PropVal.tsx @@ -34,7 +34,7 @@ function PropVal({ const property = useProperty(propertyURL); const truncated = truncateUrl(propertyURL, 10, true); - if (property.loading) { + if (property.loading || resource.loading) { return ( diff --git a/browser/data-browser/src/components/ResourceContextMenu/CustomContextItemsContext.tsx b/browser/data-browser/src/components/ResourceContextMenu/CustomContextItemsContext.tsx new file mode 100644 index 000000000..4d2c7498b --- /dev/null +++ b/browser/data-browser/src/components/ResourceContextMenu/CustomContextItemsContext.tsx @@ -0,0 +1,95 @@ +import { + createContext, + useContext, + useState, + useCallback, + type PropsWithChildren, + useEffect, +} from 'react'; +import type { DropdownItem } from '../Dropdown'; + +export interface CustomContextItemsContextValue { + items: DropdownItem[]; + registerItems: (items: DropdownItem[]) => () => void; +} + +const CustomContextItemsContext = createContext< + CustomContextItemsContextValue | undefined +>(undefined); + +export function CustomContextItemsProvider({ children }: PropsWithChildren) { + const [itemsMap, setItemsMap] = useState>( + new Map(), + ); + + const registerItems = useCallback((items: DropdownItem[]) => { + const id = Math.random().toString(36).substring(7); + + setItemsMap(prev => { + const next = new Map(prev); + next.set(id, items); + + return next; + }); + + // Return cleanup function + return () => { + setItemsMap(prev => { + const next = new Map(prev); + next.delete(id); + + return next; + }); + }; + }, []); + + const items = Array.from(itemsMap.values()).flat(); + + return ( + + {children} + + ); +} + +export function useCustomContextItemsContext() { + const context = useContext(CustomContextItemsContext); + + if (!context) { + throw new Error( + 'useCustomContextItemsContext must be used within CustomContextItemsProvider', + ); + } + + return context; +} + +/** + * Hook to register custom context menu items for the ResourceContextMenu. + * The items will be automatically cleaned up when the component unmounts. + * + * @param items - Array of DropdownItem to add to the context menu + * + * @example + * ```tsx + * useCustomContextItems([ + * { + * id: 'export-pdf', + * label: 'Export as PDF', + * helper: 'Export this document as a PDF file', + * icon: , + * onClick: () => handleExportPDF(), + * }, + * DIVIDER, + * ]); + * ``` + */ +export function useCustomContextItems(items: DropdownItem[]) { + const { registerItems } = useCustomContextItemsContext(); + + useEffect(() => { + const cleanup = registerItems(items); + + return cleanup; + }, [registerItems, items]); +} diff --git a/browser/data-browser/src/components/ResourceContextMenu/index.tsx b/browser/data-browser/src/components/ResourceContextMenu/index.tsx index 0254b216b..a79d46667 100644 --- a/browser/data-browser/src/components/ResourceContextMenu/index.tsx +++ b/browser/data-browser/src/components/ResourceContextMenu/index.tsx @@ -41,6 +41,14 @@ import { addIf } from '../../helpers/addIf'; import { useNavigateWithTransition } from '../../hooks/useNavigateWithTransition'; import { newContextItem, useAISidebar } from '../AI/AISidebarContext'; import { type AIAtomicResourceMessageContext } from '@chunks/AI/types'; +import { useCustomContextItemsContext } from './CustomContextItemsContext'; + +export { + CustomContextItemsProvider, + useCustomContextItems, +} from './CustomContextItemsContext'; + +export { DIVIDER, type DropdownItem } from '../Dropdown'; export const ContextMenuOptions = { View: 'view', @@ -98,6 +106,7 @@ export function ResourceContextMenu({ const canWrite = useCanWrite(resource); const { enableScope } = useQueryScopeHandler(subject); const { setContextItems, isOpen, setIsOpen } = useAISidebar(); + const { items: customItems } = useCustomContextItemsContext(); // Try to not have a useResource hook in here, as that will lead to many costly fetches when the user enters a new subject const addToChat = () => { @@ -130,7 +139,7 @@ export function ResourceContextMenu({ } catch (error) { toast.error(error.message); } - }, [resource, navigate, currentSubject, onAfterDelete]); + }, [resource, navigate, currentSubject, subject, onAfterDelete]); if (subject === undefined) { return null; @@ -242,13 +251,16 @@ export function ResourceContextMenu({ ), ]; + // Add custom items from context (if any) before filtering + const allItems = [...items, ...customItems]; + const filteredItems = showOnly - ? items.filter( + ? allItems.filter( item => !isItem(item) || showOnly.includes(item.id as ContextMenuOptionsUnion), ) - : items; + : allItems; const triggerComp = trigger ?? diff --git a/browser/data-browser/src/components/Row.tsx b/browser/data-browser/src/components/Row.tsx index c36473c48..10c00de17 100644 --- a/browser/data-browser/src/components/Row.tsx +++ b/browser/data-browser/src/components/Row.tsx @@ -60,7 +60,7 @@ Column.displayName = 'Column'; * This component is only exported so it can be used in css selectors. */ export const Flex = styled.div` - align-items: ${p => (p.center ? 'center' : p.align ?? 'initial')}; + align-items: ${p => (p.center ? 'center' : (p.align ?? 'initial'))}; display: flex; gap: ${p => p.gap ?? `${p.theme.margin}rem`}; justify-content: ${p => p.justify ?? 'start'}; diff --git a/browser/data-browser/src/components/ScrollArea.tsx b/browser/data-browser/src/components/ScrollArea.tsx index 45f41d0d4..7498e3687 100644 --- a/browser/data-browser/src/components/ScrollArea.tsx +++ b/browser/data-browser/src/components/ScrollArea.tsx @@ -5,7 +5,7 @@ import { forwardRef, type JSX } from 'react'; const SIZE = '0.8rem'; -export interface ScrollAreaProps { +export interface ScrollAreaProps extends React.HTMLAttributes { className?: string; type?: 'hover' | 'scroll'; } @@ -13,9 +13,9 @@ export interface ScrollAreaProps { export const ScrollArea = forwardRef< HTMLDivElement, React.PropsWithChildren ->(({ children, className, type = 'scroll' }, ref): JSX.Element => { +>(({ children, className, type = 'scroll', ...rest }, ref): JSX.Element => { return ( - + {children} diff --git a/browser/data-browser/src/components/Searchbar/Searchbar.tsx b/browser/data-browser/src/components/Searchbar/Searchbar.tsx index 3df6f2e2c..700cdd09a 100644 --- a/browser/data-browser/src/components/Searchbar/Searchbar.tsx +++ b/browser/data-browser/src/components/Searchbar/Searchbar.tsx @@ -17,6 +17,7 @@ import { base64StringToFilter, filterToBase64String, } from '../../routes/Search/searchUtils'; +import { addFieldsIf } from '@helpers/addIf'; function addTagsToFilter( base64Filter: string | undefined, @@ -52,10 +53,10 @@ export function Searchbar(): JSX.Element { to: paths.search, search: prev => ({ query: q, - ...(scope ? { queryscope: scope } : {}), - ...(tags.length > 0 - ? { filters: addTagsToFilter(prev.filters, tags) } - : {}), + ...addFieldsIf(!!scope, { queryscope: scope }), + ...addFieldsIf(tags.length > 0, { + filters: addTagsToFilter(prev.filters, tags), + }), }), replace: true, }); diff --git a/browser/data-browser/src/components/Searchbar/SearchbarInput.tsx b/browser/data-browser/src/components/Searchbar/SearchbarInput.tsx index 8e303027d..b2ab543bc 100644 --- a/browser/data-browser/src/components/Searchbar/SearchbarInput.tsx +++ b/browser/data-browser/src/components/Searchbar/SearchbarInput.tsx @@ -239,7 +239,7 @@ export const SearchbarInput: React.FC = ({ onClick: onTagClick, resetIndex, usingKeyboard, - } = useSelectedIndex(filteredTagList, onSelect); + } = useSelectedIndex(filteredTagList, onSelect, { key: tagQueryValue }); const handleKeyDown = (e: React.KeyboardEvent) => { if (tagRect) { diff --git a/browser/data-browser/src/components/Shortcut.tsx b/browser/data-browser/src/components/Shortcut.tsx index 71f5fdf1e..738207868 100644 --- a/browser/data-browser/src/components/Shortcut.tsx +++ b/browser/data-browser/src/components/Shortcut.tsx @@ -31,7 +31,8 @@ const KBD = styled.kbd` background-color: ${p => p.theme.colors.bg1}; text-transform: capitalize; border-radius: 5px; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI Adjusted', - 'Segoe UI', 'Liberation Sans', sans-serif; + font-family: + -apple-system, BlinkMacSystemFont, 'Segoe UI Adjusted', 'Segoe UI', + 'Liberation Sans', sans-serif; padding: 0.3em; `; diff --git a/browser/data-browser/src/components/SideBar/AppMenu.tsx b/browser/data-browser/src/components/SideBar/AppMenu.tsx index 5c704e7f2..bbbac2f6a 100644 --- a/browser/data-browser/src/components/SideBar/AppMenu.tsx +++ b/browser/data-browser/src/components/SideBar/AppMenu.tsx @@ -44,19 +44,17 @@ export function AppMenu({ onItemClick }: AppMenuProps): JSX.Element { setShowInstallButton(false); } }); - }, [event.current]); + }, []); useEffect(() => { - const listener = (e: BeforeInstallPromptEvent) => { + const listener = (e: Event) => { e.preventDefault(); setShowInstallButton(true); - event.current = e; + event.current = e as unknown as BeforeInstallPromptEvent; }; - //@ts-ignore window.addEventListener('beforeinstallprompt', listener); - //@ts-ignore return () => window.removeEventListener('beforeinstallprompt', listener); }, []); @@ -66,7 +64,7 @@ export function AppMenu({ onItemClick }: AppMenuProps): JSX.Element { icon={} label={ agent - ? agentResource.get(core.properties.name) ?? 'User Settings' + ? (agentResource.get(core.properties.name) ?? 'User Settings') : 'Login' } helper='See and edit the current Agent / User (u)' diff --git a/browser/data-browser/src/components/SideBar/ResourceSideBar/SidebarItemTitle.tsx b/browser/data-browser/src/components/SideBar/ResourceSideBar/SidebarItemTitle.tsx index 4feae1b4f..9dc63b919 100644 --- a/browser/data-browser/src/components/SideBar/ResourceSideBar/SidebarItemTitle.tsx +++ b/browser/data-browser/src/components/SideBar/ResourceSideBar/SidebarItemTitle.tsx @@ -123,8 +123,8 @@ const StyledIconButton = styled(IconButton)` const ActionWrapper = styled.div<{ isDragging?: boolean }>` --aw-box-shadow-start: 0 0 0 0px rgba(0, 0, 0, 0.1); - --aw-box-shadow-end: 0 0 0 1px ${p => p.theme.colors.main}, - ${p => p.theme.boxShadowSoft}; + --aw-box-shadow-end: + 0 0 0 1px ${p => p.theme.colors.main}, ${p => p.theme.boxShadowSoft}; display: flex; width: 100%; diff --git a/browser/data-browser/src/components/SideBar/SideBarDrive.tsx b/browser/data-browser/src/components/SideBar/SideBarDrive.tsx index 8b3fc42ee..46bf71a02 100644 --- a/browser/data-browser/src/components/SideBar/SideBarDrive.tsx +++ b/browser/data-browser/src/components/SideBar/SideBarDrive.tsx @@ -1,4 +1,5 @@ import { + core, dataBrowser, useArray, useCanWrite, @@ -58,7 +59,9 @@ export function SideBarDrive({ const navigate = useNavigateWithTransition(); const agentCanWrite = useCanWrite(driveResource); const [currentSubject] = useCurrentSubject(); - const currentResource = useResource(currentSubject); + const currentResource = useResource(currentSubject, { + track: [core.properties.parent], + }); const [ancestry, setAncestry] = useState([]); useEffect(() => { diff --git a/browser/data-browser/src/components/SideBar/index.tsx b/browser/data-browser/src/components/SideBar/index.tsx index 3ac8575a3..2b088d46c 100644 --- a/browser/data-browser/src/components/SideBar/index.tsx +++ b/browser/data-browser/src/components/SideBar/index.tsx @@ -54,7 +54,7 @@ export function SideBar(): JSX.Element { if (!isWideScreen) { setSideBarLocked(false); } - }, [isWideScreen]); + }, [isWideScreen, setSideBarLocked]); const sidebarVisible = sideBarLocked || (hoveringOverSideBar && isWideScreen); @@ -141,6 +141,10 @@ const StyledNav = styled.nav.attrs(p => ({ overflow-y: auto; overflow-x: hidden; padding-bottom: ${p => p.theme.size()}; + + @media print { + display: none; + } `; const MenuWrapper = styled.div` diff --git a/browser/data-browser/src/components/TableEditor/Cell.tsx b/browser/data-browser/src/components/TableEditor/Cell.tsx index 4052d390b..b8d293a52 100644 --- a/browser/data-browser/src/components/TableEditor/Cell.tsx +++ b/browser/data-browser/src/components/TableEditor/Cell.tsx @@ -15,6 +15,9 @@ import { import { FaExpandAlt } from 'react-icons/fa'; import { IconButton } from '../IconButton/IconButton'; import { KeyboardInteraction } from './helpers/keyboardHandlers'; +import { CSSVar } from '@helpers/CSSVar'; + +export const CELL_WIDTH = new CSSVar('table-cell-width'); export enum CellAlign { Start = 'flex-start', @@ -89,8 +92,9 @@ export function Cell({ const shouldEnterEditMode = useCallback( (e: React.MouseEvent) => { - // @ts-ignore - if (e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') { + const target = e.target as HTMLElement; + + if (target.tagName === 'INPUT' || target.tagName === 'BUTTON') { // If the user clicked on an input don't enter edit mode. (Necessary for normal checkbox behavior) return false; } @@ -103,6 +107,10 @@ export function Cell({ const handleMouseDown = useCallback( (e: React.MouseEvent) => { + if ((e.target as HTMLElement).tagName === 'BUTTON') { + return; + } + if (disabledKeyboardInteractions.has(KeyboardInteraction.ExitEditMode)) { return; } @@ -226,6 +234,7 @@ export function Cell({ return ( ` +export const CellWrapper = styled.div.attrs(p => ({ + style: { + [CELL_WIDTH.raw]: `var(--cell-width-${p.index})`, + } as Record, +}))` background-color: ${p => p.disabled ? p.theme.colors.bg1 : p.theme.colors.bg}; cursor: ${p => (p.disabled ? 'not-allowed' : 'pointer')}; diff --git a/browser/data-browser/src/components/TableEditor/TableEditor.tsx b/browser/data-browser/src/components/TableEditor/TableEditor.tsx index 9d7e4692f..20f076616 100644 --- a/browser/data-browser/src/components/TableEditor/TableEditor.tsx +++ b/browser/data-browser/src/components/TableEditor/TableEditor.tsx @@ -58,6 +58,7 @@ interface FancyTableProps { onRowExpand?: (index: number) => void; HeadingComponent: TableHeadingComponent; NewColumnButtonComponent: React.ComponentType; + ref?: React.RefObject; } interface RowProps { @@ -101,7 +102,6 @@ function FancyTableInner({ const ariaUsageId = useId(); const scrollerRef = useRef(null); const headerRef = useRef(null); - const { listRef, tableRef, @@ -109,6 +109,7 @@ function FancyTableInner({ disabledKeyboardInteractions, readOnly, } = useTableEditorContext(); + const [onScroll, setOnScroll] = useState(() => undefined); const { templateColumns, contentRowWidth, resizeCell } = useCellSizes( @@ -210,6 +211,7 @@ function FancyTableInner({ tabIndex={0} onKeyDown={handleKeyDown} totalContentHeight={itemCount * rowHeight!} + columnSizes={columnSizes ?? []} ref={tableRef} > @@ -240,6 +242,7 @@ function FancyTableInner({ interface TableProps { gridTemplateColumns: string; + columnSizes: number[]; contentRowWidth: string; rowHeight: number; totalContentHeight: number; @@ -249,6 +252,13 @@ const Table = styled.div.attrs(p => ({ style: { '--table-template-columns': p.gridTemplateColumns, '--table-content-width': p.contentRowWidth, + ...p.columnSizes.reduce( + (acc, size, i) => ({ + ...acc, + [`--cell-width-${i + 1}`]: `${size}px`, + }), + {}, + ), } as Record, }))` --table-height: 80vh; diff --git a/browser/data-browser/src/components/TableEditor/TableHeader.tsx b/browser/data-browser/src/components/TableEditor/TableHeader.tsx index aecac5774..c26239aef 100644 --- a/browser/data-browser/src/components/TableEditor/TableHeader.tsx +++ b/browser/data-browser/src/components/TableEditor/TableHeader.tsx @@ -54,9 +54,6 @@ export function TableHeader({ (event: DragStartEvent) => { const key = columns.map(columnToKey).indexOf(event.active.id as string); setActiveIndex(key); - - // Bug in react-compiler linter - // eslint-disable-next-line react-hooks/react-compiler document.body.style.cursor = 'grabbing'; }, [columns, columnToKey], diff --git a/browser/data-browser/src/components/TableEditor/TableHeading.tsx b/browser/data-browser/src/components/TableEditor/TableHeading.tsx index c625cdf85..b56e9ba60 100644 --- a/browser/data-browser/src/components/TableEditor/TableHeading.tsx +++ b/browser/data-browser/src/components/TableEditor/TableHeading.tsx @@ -51,11 +51,13 @@ export function TableHeading({ setIsDragging(isDragging); }, [isDragging]); - const setRef = useCallback((node: HTMLDivElement) => { - setNodeRef(node); - // @ts-ignore - targetRef.current = node; - }, []); + const setRef = useCallback( + (node: HTMLDivElement) => { + setNodeRef(node); + targetRef.current = node; + }, + [setNodeRef], + ); return ( theme.darkMode ? 'var(--dark-color)' : 'var(--light-color)'}; filter: brightness(1.05); + transform: scale(1.1); box-shadow: 0 1px 20px 0px var(--shadow-color); } `; diff --git a/browser/data-browser/src/components/Tag/TagSelectPopover.tsx b/browser/data-browser/src/components/Tag/TagSelectPopover.tsx index 83b57e4cf..da84af237 100644 --- a/browser/data-browser/src/components/Tag/TagSelectPopover.tsx +++ b/browser/data-browser/src/components/Tag/TagSelectPopover.tsx @@ -41,13 +41,25 @@ export const TagSelectPopover: React.FC = ({ .filter(tag => tag.title.includes(filterValue)) .map(t => t.subject); + const modifyTags = (add: boolean, tag: string) => { + if (add) { + setSelectedTags([...selectedTags, tag]); + } else if (selectedTags.includes(tag)) { + setSelectedTags(selectedTags.filter(t => t !== tag)); + } + }; + const { selectedIndex, onKeyDown, onMouseOver, resetIndex, usingKeyboard } = - useSelectedIndex(filteredTags, index => { - if (index !== undefined) { - const tag = filteredTags[index]; - modifyTags(!selectedTags.includes(tag), tag); - } - }); + useSelectedIndex( + filteredTags, + index => { + if (index !== undefined) { + const tag = filteredTags[index]; + modifyTags(!selectedTags.includes(tag), tag); + } + }, + { key: filterValue }, + ); const handleNewTag = async (tag: Resource) => { try { @@ -64,14 +76,6 @@ export const TagSelectPopover: React.FC = ({ setFilterValue(''); }; - const modifyTags = (add: boolean, tag: string) => { - if (add) { - setSelectedTags([...selectedTags, tag]); - } else if (selectedTags.includes(tag)) { - setSelectedTags(selectedTags.filter(t => t !== tag)); - } - }; - return ( ; case Datatype.JSON: - return ; + return ; + case Datatype.YDOC: + return ; case Datatype.URI: return ( {value as string} diff --git a/browser/data-browser/src/components/YDocValue.tsx b/browser/data-browser/src/components/YDocValue.tsx new file mode 100644 index 000000000..d2a9f88d7 --- /dev/null +++ b/browser/data-browser/src/components/YDocValue.tsx @@ -0,0 +1,42 @@ +import { styled } from 'styled-components'; +import * as Y from 'yjs'; +import { FaEye } from 'react-icons/fa6'; +import { Button } from './Button'; +import { useState } from 'react'; +import { CodeBlock } from './CodeBlock'; +import { Column } from './Row'; + +interface YDocValueProps { + value: Y.Doc | undefined; +} + +export const YDocValue: React.FC = ({ value }) => { + const [showState, setShowState] = useState(false); + + if (!value) { + return Empty; + } + + return ( + + setShowState(!showState)}> + + {showState ? 'Hide encoded state' : 'Show encoded state'} + + {showState && ( + + )} + + ); +}; + +const SubtleButton = styled(Button)` + color: ${p => p.theme.colors.textLight}; + display: flex; + align-items: center; + gap: 0.5rem; + &:hover, + &:focus-visible { + color: ${p => p.theme.colors.main}; + } +`; diff --git a/browser/data-browser/src/components/forms/FileDropzone/FileDropzoneInput.tsx b/browser/data-browser/src/components/forms/FileDropzone/FileDropzoneInput.tsx index 7b4241cd2..12f201d31 100644 --- a/browser/data-browser/src/components/forms/FileDropzone/FileDropzoneInput.tsx +++ b/browser/data-browser/src/components/forms/FileDropzone/FileDropzoneInput.tsx @@ -37,7 +37,7 @@ export function FileDropzoneInput({ onFilesUploaded?.(uploaded); } }, - [upload], + [upload, onFilesUploaded], ); const { getRootProps, getInputProps } = useDropzone({ @@ -57,7 +57,7 @@ export function FileDropzoneInput({ {error && {error.message}} - {isUploading ? 'Uploading...' : text ?? defaultText} + {isUploading ? 'Uploading...' : (text ?? defaultText)} diff --git a/browser/data-browser/src/components/forms/FilePicker/FilePickerDialog.tsx b/browser/data-browser/src/components/forms/FilePicker/FilePickerDialog.tsx index b73162173..aa119cb07 100644 --- a/browser/data-browser/src/components/forms/FilePicker/FilePickerDialog.tsx +++ b/browser/data-browser/src/components/forms/FilePicker/FilePickerDialog.tsx @@ -15,13 +15,13 @@ import { Button } from '../../Button'; import { Row } from '../../Row'; import { useSettings } from '../../../helpers/AppSettings'; import { useMediaQuery } from '../../../hooks/useMediaQuery'; +import { useOnValueChange } from '@helpers/useOnValueChange'; interface FilePickerProps { show: boolean; onShowChange?: (show: boolean) => void; onResourcePicked: (subject: string) => void; - onNewFilePicked: (file: File) => void; - noUpload?: boolean; + onNewFilePicked?: (file: File) => void; allowedMimes?: Set; } @@ -31,7 +31,6 @@ export function FilePickerDialog({ onNewFilePicked, onResourcePicked, allowedMimes, - noUpload = false, }: FilePickerProps): React.JSX.Element { const { drive } = useSettings(); const [dialogProps, showDialog, closeDialog] = useDialog({ @@ -64,7 +63,7 @@ export function FilePickerDialog({ const handleFileInputChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; - if (file) { + if (file && onNewFilePicked) { onNewFilePicked(file); closeDialog(true); } @@ -80,10 +79,11 @@ export function FilePickerDialog({ } }; + useOnValueChange(() => updateQuery(''), [show]); + useEffect(() => { if (show) { showDialog(); - updateQuery(''); } }, [show, showDialog]); @@ -102,7 +102,7 @@ export function FilePickerDialog({ onChange={e => updateQuery(e.target.value)} /> - {!noUpload && ( + {!!onNewFilePicked && ( + + )} +
+ {elements.map(subject => ( + + ))} +
+ ); } -type DocumentSubPageProps = { - resource: Resource; - setEditMode: (arg: boolean) => void; -}; - -function DocumentPageEdit({ - resource, - setEditMode, -}: DocumentSubPageProps): JSX.Element { - const [elements, setElements] = useArray( - resource, - dataBrowser.properties.elements, - { commit: false, validate: false, commitDebounce: 0 }, - ); - - const titleRef = useRef(null); - const store = useStore(); - const ref = useRef(null); - const [err, setErr] = useState(undefined); - const [current, setCurrent] = useState(0); - - const sensors = useSensors( - useSensor(PointerSensor), - useSensor(KeyboardSensor, { - coordinateGetter: sortableKeyboardCoordinates, - }), - ); - - // On init, focus on the last element - useEffect(() => { - setCurrent(elements.length - 1); - - if (elements === undefined) { - setElements([]); - } - }, []); - - // Always have one element - useEffect(() => { - if (elements.length === 0) { - addElement(0); - } - }, [JSON.stringify(elements)]); - - useHotkeys( - 'enter', - e => { - e.preventDefault(); - addElement(current + 1); - }, - { enableOnTags: ['TEXTAREA'] }, - [current], - ); - - /** Move from title to first element */ - useHotkeys( - 'enter', - e => { - e.preventDefault(); - addElement(0); - focusElement(0); - }, - { enableOnTags: ['INPUT'] }, - [addElement, focusElement], - ); - - useHotkeys( - 'up', - e => { - e.preventDefault(); - - if (!current || current === 0) { - titleRef.current?.focus(); - } else { - focusElement(current - 1); - } - }, - { enableOnTags: ['TEXTAREA'] }, - [current], - ); - - useHotkeys( - 'down', - e => { - e.preventDefault(); - - if (document.activeElement === titleRef.current) { - focusElement(0); - } else { - focusElement(current + 1); - } - }, - { enableOnTags: ['TEXTAREA', 'INPUT'] }, - [current], - ); - - // Move current element up - useHotkeys( - shortcuts.moveLineUp, - e => { - e.preventDefault(); - moveElement(current, current - 1); - }, - { enableOnTags: ['TEXTAREA'] }, - [current], - ); - - // Move element down - useHotkeys( - shortcuts.moveLineDown, - e => { - e.preventDefault(); - moveElement(current, current + 1); - }, - { enableOnTags: ['TEXTAREA'] }, - [current], - ); - - // Lose focus - useHotkeys( - 'esc', - e => { - e.preventDefault(); - setCurrent(-1); - }, - { enableOnTags: ['TEXTAREA'] }, - ); - - /** Creates a new Element at the given position, with the Document as its parent */ - async function addElement(position: number) { - // When an element is created, it should be a Resource that has this document as its parent. - // or maybe a nested resource? - const elementSubject = store.createSubject(resource.subject); - const newElements = [...elements]; - newElements.splice(position, 0, elementSubject); - - try { - const newElement = await store.newResource({ - subject: elementSubject, - isA: dataBrowser.classes.paragraph, - parent: resource.subject, - propVals: { - [core.properties.description]: '', - }, - }); - - await setElements(newElements); - focusElement(position); - await newElement.save(); - await resource.save(); - } catch (e) { - setErr(e); - } - } - - function focusElement(goto: number) { - if (goto > elements.length - 1) { - goto = elements.length - 1; - } else if (goto < 0) { - goto = 0; - } - - setCurrent(goto); - let found: HTMLInputElement | undefined = ref?.current?.children[ - goto - ]?.getElementsByClassName('element')[0] as HTMLInputElement; - - if (!found) { - found = ref?.current?.children[goto] as HTMLInputElement; - } - - if (found) { - found.focus(); - } else { - ref.current?.focus(); - } - } - - async function deleteElement(number: number) { - if (elements.length === 1) { - setElements([]); - focusElement(0); - resource.save(); - - return; - } - - setElements(elements.toSpliced(number, 1)); - focusElement(number - 1); - resource.save(); - } - - /** Sets the subject for a specific element and moves to the next element */ - async function setElement(index: number, subject: string) { - setElements(elements.with(index, subject)); - - if (index === elements.length - 1) { - addElement(index + 1); - } else { - focusElement(index + 1); - resource.save(); - } - } - - function moveElement(from: number, to: number) { - const element = elements[from]; - setElements(elements.toSpliced(from, 1).toSpliced(to, 0, element)); - focusElement(to); - resource.save(); - } - - function handleSortEnd(event: DragEndEvent): void { - const { active, over } = event; - - if (active.id !== over?.id) { - const oldIndex = elements.indexOf(active.id.toString()); - - if (!over?.id) { - return; - } - - const newIndex = elements.indexOf(over.id.toString()); - moveElement(oldIndex, newIndex); - } - } - - /** Create elements for every new File resource */ - function handleUploadedFiles(fileSubjects: string[]) { - toast.success('Upload succeeded!'); - fileSubjects.map(subject => elements.push(subject)); - setElements([...elements]); - resource.save(); - } - - /** Add a new line, or move to the last line if it is empty */ - async function handleNewLineMaybe() { - const lastSubject = elements[elements.length - 1]; - - if (!lastSubject) { - addElement(elements.length); - - return; - } - - const lastElem = await store.getResource(lastSubject); - const description = lastElem.get(core.properties.description); - - if (description === undefined || description.length === 0) { - focusElement(elements.length - 1); - } else { - addElement(elements.length); - } - } - - return ( - - - - - - - {err?.message && {err.message}} - -
- - - {elements.map((elementSubject, index) => ( - - ))} - - - -
-
-
- ); -} - -function DocumentPageShow({ - resource, - setEditMode, -}: DocumentSubPageProps): JSX.Element { - const [elements] = useArray(resource, dataBrowser.properties.elements); - const canWrite = useCanWrite(resource); - - return ( - - -

{resource.title}

- {canWrite && ( - - )} -
- -
- {elements.map(subject => ( - - ))} -
-
- ); -} - -interface SortableElementProps extends ElementEditPropsBase { - subject: string; - index?: number; - active: boolean; -} - -function SortableElement(props: SortableElementProps) { - const { subject, active } = props; - - const { attributes, listeners, setNodeRef, transform, transition } = - useSortable({ id: subject }); - - const style = { - transform: CSS.Transform.toString(transform), - transition, - }; - - return ( - - - - - ); -} - const DocumentContainer = styled.div` width: min(100%, ${p => p.theme.containerWidth}rem); margin: auto; @@ -432,35 +67,10 @@ const DocumentContainer = styled.div` flex-direction: column; padding: 2rem; @media (max-width: ${props => props.theme.containerWidth}rem) { - padding: ${p => p.theme.margin}rem; + padding: ${p => p.theme.size()}; } `; -const NewLine = styled.div` - height: 20rem; - flex: 1; - cursor: text; -`; - -const SortableItemWrapper = styled.div` - display: flex; - flex-direction: row; - position: relative; -`; - -const GripItem = (props: GripItemProps) => { - return ( - - - - ); -}; - -interface GripItemProps { - /** The element is currently selected */ - active: boolean; -} - const FullPageWrapper = styled.div` background-color: ${p => p.theme.colors.bg}; display: flex; @@ -470,30 +80,12 @@ const FullPageWrapper = styled.div` box-sizing: border-box; `; -const SortHandleStyled = styled.div` - width: 1rem; - flex: 1; - display: flex; - align-items: center; - opacity: ${p => (p.active ? 0.3 : 0)}; - position: absolute; - left: -1rem; - bottom: 0; - height: 100%; - /* TODO fix cursor while dragging */ - cursor: grab; - border: solid 1px transparent; +const UpgradeMessage = styled(Column)` + background-color: ${p => p.theme.colors.mainSelectedBg}; + border: 1px solid ${p => p.theme.colors.mainSelectedFg}; + color: ${p => p.theme.colors.mainSelectedFg}; + padding: ${p => p.theme.size()}; border-radius: ${p => p.theme.radius}; - - &:drop(active), - &:focus, - &:active { - opacity: 0.5; - } - - &:hover { - opacity: 0.5; - } `; export default DocumentPage; diff --git a/browser/data-browser/src/views/Element.tsx b/browser/data-browser/src/views/Element.tsx index 5918dd8c6..f580eada7 100644 --- a/browser/data-browser/src/views/Element.tsx +++ b/browser/data-browser/src/views/Element.tsx @@ -1,237 +1,28 @@ -import * as React from 'react'; -import { useState, type JSX } from 'react'; -import { - properties, - classes, - useArray, - useCanWrite, - useResource, - useServerSearch, - useString, -} from '@tomic/react'; +import { type JSX } from 'react'; +import { useResource, dataBrowser, core } from '@tomic/react'; import { styled, css } from 'styled-components'; -import { useHotkeys } from 'react-hotkeys-hook'; -import { ResourceInline } from './ResourceInline'; import Markdown from '../components/datatypes/Markdown'; import ResourceCard from './Card/ResourceCard'; -import { shortcuts } from '../components/HotKeyWrapper'; -import { ErrorLook } from '../components/ErrorLook'; interface ElementShowProps { subject: string; } -/** Shared between all elements */ -export interface ElementEditPropsBase { - /** Removes element from the Array */ - deleteElement: (i: number) => void; - /** Position of the active Element */ - current?: number; - /** Sets the position of the active Element */ - setCurrent: (i: number) => void; - /** Changes the subject of a specific item in the array */ - setElementSubject: (i: number, subject: string) => void; - /** Show a drag icon */ - canDrag: boolean; -} - -interface ElementEditProps extends ElementEditPropsBase { - subject: string; - /** Position in the array of Elements */ - index?: number; - active: boolean; -} - -const searchChar = '/'; -const helpChar = '?'; -const linkChar = '['; -const headerChar = '#'; - -/** An element is a section inside document, such as a Paragraph, Header or Image */ -export function ElementEdit({ - subject, - deleteElement, - index, - setCurrent, - setElementSubject: setElement, - active, - canDrag, -}: ElementEditProps): JSX.Element { - const resource = useResource(subject, { - // Prevents a race condition, see https://github.com/atomicdata-dev/atomic-data-browser/issues/189 - newResource: true, - }); - const [err, setErr] = useState(undefined); - const [text, setText] = useString(resource, properties.description, { - commit: true, - handleValidationError: setErr, - validate: false, - }); - const [klass] = useArray(resource, properties.isA); - const ref = React.useRef(null); - const canWrite = useCanWrite(resource); - - /** If it is not a text element */ - const isAResource = - klass.length > 0 && !klass.includes(classes.elements.paragraph); - - function handleOnChange(e: React.ChangeEvent) { - handleResize(); - setErr(undefined); - setText(e.target.value); - } - - /** Let the textarea grow */ - function handleResize() { - if (ref.current?.style) { - ref.current.style.height = '0'; - ref.current.style.height = ref.current.scrollHeight + 'px'; - } - } - - /** Resize the text area when the text changes, or it is set to active */ - React.useEffect((): void => { - handleResize(); - }, [ref, text, active]); - - /** Auto focus on select, move cursor to end */ - React.useEffect(() => { - ref?.current?.focus(); - text && ref?.current?.setSelectionRange(text?.length, text?.length); - }, [active]); - - /** Delete this element */ - useHotkeys( - 'backspace', - e => { - const isEmpty = text === '' || text === undefined; - - if ((active && isEmpty) || (active && isAResource)) { - e.preventDefault(); - deleteElement(index!); - } - }, - // no keybaord events captured by ContentEditable - { - enableOnTags: ['TEXTAREA'], - enabled: active, - }, - [index, text, active], - ); - - useHotkeys( - shortcuts.deleteLine, - e => { - if (active) { - e.preventDefault(); - deleteElement(index!); - } - }, - { - enableOnTags: ['TEXTAREA'], - enabled: active, - }, - [index, active], - ); - - function Err() { - if (err?.message) { - return {err.message}; - } else if (active && !canWrite) { - return Agent does not have edit rights; - } else { - return null; - } - } - - if (isAResource) { - return ( - setCurrent(index!)} - onBlur={() => setCurrent(-1)} - > - - - - ); - } +export function ElementShow({ subject }: ElementShowProps): JSX.Element { + const resource = useResource(subject); - if (!active) { + if (resource.hasClasses(dataBrowser.classes.paragraph)) { return ( - setCurrent(index!)} - onBlur={() => setCurrent(-1)} - > - - + + ); } - return ( - index && setCurrent(index)} - > - setCurrent(index!)} - onBlur={() => setCurrent(-1)} - placeholder={`type something (try ${helpChar} or ${searchChar})`} - // Not working, I think - autoFocus={active} - value={text ? text : ''} - /> - {text?.startsWith(searchChar) && ( - index && setElement(index, s)} - /> - )} - {text?.startsWith(helpChar) && ( - index && setElement(index, s)} - /> - )} - {text?.startsWith(linkChar) && ( - -

[link text](https://example.com)

-
- )} - {text?.startsWith(headerChar) && ( - -

# Big Header

-

## Header

-

### Smaller Header

-
- )} - -
- ); -} - -export function ElementShow({ subject }: ElementShowProps): JSX.Element { - const resource = useResource(subject); - const [text] = useString(resource, properties.description); - return ( - + ); } @@ -276,134 +67,3 @@ interface ElementViewProps { active?: boolean; canDrag?: boolean; } - -const ElementView = styled.textarea` - ${ElementTextStyle} - border: none; - width: 100%; - resize: none; - background-color: ${p => p.theme.colors.bg}; - color: ${p => p.theme.colors.text}; - padding: 0; - margin-bottom: 0.5rem; - &:focus { - outline: none; - ${ElementFocusStyle} - } -`; - -interface WidgetProps { - // Input without the matched string / character - query: string; - setElement: (subject: string) => void; -} - -/** Allows the user to search for Resources and include these as an Element. */ -function SearchWidget({ query, setElement }: WidgetProps) { - const { results } = useServerSearch(query); - // The currently selected result - const [index, setIndex] = useState(0); - - useHotkeys( - 'tab,enter', - e => { - e.preventDefault(); - results[index] && setElement(results[index]); - }, - { enableOnTags: ['TEXTAREA'] }, - [], - ); - - useHotkeys( - 'left', - e => { - e.preventDefault(); - let next = index - 1; - - if (next < 0) { - next = results.length - 1; - } - - setIndex(index - 1); - }, - { enableOnTags: ['TEXTAREA'] }, - [index], - ); - - useHotkeys( - 'right', - e => { - e.preventDefault(); - let next = index + 1; - - if (next > results.length - 1) { - next = 0; - } - - setIndex(index + 1); - }, - { enableOnTags: ['TEXTAREA'] }, - [index], - ); - - if (query === '') { - return ( - -

Search something...

-
- ); - } - - if (results.length === 0) { - return ( - -

No results

-
- ); - } - - return ( - -

(press tab to select, left / right to browse)

-

- -

-
- ); -} - -const WidgetWrapper = styled.div` - position: absolute; - top: 100%; - right: 0; - left: -1rem; - border-radius: ${p => p.theme.radius}; - border: solid 1px ${p => p.theme.colors.bg2}; - padding: ${p => p.theme.margin}rem; - padding-bottom: 0; - background-color: ${p => p.theme.colors.bg1}; - backdrop-filter: blur(6px); - opacity: 0.9; - z-index: 1; -`; - -function HelperWidget({ query }: WidgetProps) { - return ( - - {query && } -

Try typing these:

-

- {'links: '} - [clickable link](https://example.com) -

-

- {'styling:'} - **bold** and _cursive_ -

-

- {'headings:'} - ## Header -

-
- ); -} diff --git a/browser/data-browser/src/views/EndpointPage.tsx b/browser/data-browser/src/views/EndpointPage.tsx index 5ab5c5f62..4d14efe9a 100644 --- a/browser/data-browser/src/views/EndpointPage.tsx +++ b/browser/data-browser/src/views/EndpointPage.tsx @@ -34,7 +34,7 @@ function EndpointPage({ resource }: EndpointProps): JSX.Element { /** Create the URL using the variables */ async function constructSubject(e?: React.SyntheticEvent) { - e && e.preventDefault(); + e?.preventDefault(); const url = new URL(resource.subject); await Promise.all( diff --git a/browser/data-browser/src/views/File/fileTypeUtils.ts b/browser/data-browser/src/views/File/fileTypeUtils.ts index 9b5088ef2..1696fbdd6 100644 --- a/browser/data-browser/src/views/File/fileTypeUtils.ts +++ b/browser/data-browser/src/views/File/fileTypeUtils.ts @@ -1,4 +1,4 @@ -const supportedApplicationFormats = new Set([ +export const supportedApplicationFormats = new Set([ 'application/json', 'application/ld+json', 'application/ad+json', @@ -8,7 +8,7 @@ const supportedApplicationFormats = new Set([ 'application/x-sh', ]); -const supportedImageTypes = new Set([ +export const supportedImageTypes = new Set([ 'image/png', 'image/jpeg', 'image/gif', diff --git a/browser/data-browser/src/views/FolderPage/FolderDisplayStyle.ts b/browser/data-browser/src/views/FolderPage/FolderDisplayStyle.ts index a6feeac3c..c786ce031 100644 --- a/browser/data-browser/src/views/FolderPage/FolderDisplayStyle.ts +++ b/browser/data-browser/src/views/FolderPage/FolderDisplayStyle.ts @@ -4,4 +4,5 @@ export interface ViewProps { subResources: Map; onNewClick: () => void; showNewButton: boolean; + basic?: boolean; } diff --git a/browser/data-browser/src/views/FolderPage/GridItem/DocumentV2GridItem.tsx b/browser/data-browser/src/views/FolderPage/GridItem/DocumentV2GridItem.tsx new file mode 100644 index 000000000..1bf0d3cf2 --- /dev/null +++ b/browser/data-browser/src/views/FolderPage/GridItem/DocumentV2GridItem.tsx @@ -0,0 +1,19 @@ +import { GridItemDescription, InnerWrapper } from './components'; +import { GridItemViewProps } from './GridItemViewProps'; + +import type { JSX } from 'react'; +import { useDocumentText } from '@hooks/useDocumentText'; + +export function DocumentV2GridItem({ + resource, +}: GridItemViewProps): JSX.Element { + const text = useDocumentText(resource, 100); + + return ( + + +
{text}
+
+
+ ); +} diff --git a/browser/data-browser/src/views/FolderPage/GridItem/ResourceGridItem.tsx b/browser/data-browser/src/views/FolderPage/GridItem/ResourceGridItem.tsx index 74475897c..63bcdcaca 100644 --- a/browser/data-browser/src/views/FolderPage/GridItem/ResourceGridItem.tsx +++ b/browser/data-browser/src/views/FolderPage/GridItem/ResourceGridItem.tsx @@ -18,6 +18,7 @@ import { GridItemViewProps } from './GridItemViewProps'; import { FaFolder } from 'react-icons/fa'; import { ChatRoomGridItem } from './ChatRoomGridItem'; import { DocumentGridItem } from './DocumentGridItem'; +import { DocumentV2GridItem } from './DocumentV2GridItem'; import { ErrorBoundary } from '../../ErrorPage'; import { useNavigateWithTransition } from '../../../hooks/useNavigateWithTransition'; import { LoaderBlock } from '../../../components/Loader'; @@ -34,6 +35,7 @@ const gridItemMap = new Map>([ [core.classes.property, BasicGridItem], [dataBrowser.classes.chatroom, ChatRoomGridItem], [dataBrowser.classes.document, DocumentGridItem], + [dataBrowser.classes.documentV2, DocumentV2GridItem], [server.classes.file, FilePreviewThumbnail], [dataBrowser.classes.article, ArticleGridItem], ]); diff --git a/browser/data-browser/src/views/FolderPage/GridItem/components.tsx b/browser/data-browser/src/views/FolderPage/GridItem/components.tsx index a5115702e..9c1e8eceb 100644 --- a/browser/data-browser/src/views/FolderPage/GridItem/components.tsx +++ b/browser/data-browser/src/views/FolderPage/GridItem/components.tsx @@ -21,10 +21,10 @@ export const GridCard = styled.div.attrs(p => ({ `; export const GridItemWrapper = styled.a` - --shadow: 0px 0.7px 1.3px rgba(0, 0, 0, 0.06), - 0px 1.8px 3.2px rgba(0, 0, 0, 0.043), 0px 3.4px 6px rgba(0, 0, 0, 0.036), - 0px 6px 10.7px rgba(0, 0, 0, 0.03), 0px 11.3px 20.1px rgba(0, 0, 0, 0.024), - 0px 27px 48px rgba(0, 0, 0, 0.017); + --shadow: + 0px 0.7px 1.3px rgba(0, 0, 0, 0.06), 0px 1.8px 3.2px rgba(0, 0, 0, 0.043), + 0px 3.4px 6px rgba(0, 0, 0, 0.036), 0px 6px 10.7px rgba(0, 0, 0, 0.03), + 0px 11.3px 20.1px rgba(0, 0, 0, 0.024), 0px 27px 48px rgba(0, 0, 0, 0.017); --interaction-shadow: 0px 0px 0px 0px ${p => p.theme.colors.main}; --card-banner-padding: 1rem; --card-banner-height: calc(var(--card-banner-padding) * 2 + 1.5em); diff --git a/browser/data-browser/src/views/FolderPage/ListView.tsx b/browser/data-browser/src/views/FolderPage/ListView.tsx index 65569b7d9..980bf894a 100644 --- a/browser/data-browser/src/views/FolderPage/ListView.tsx +++ b/browser/data-browser/src/views/FolderPage/ListView.tsx @@ -1,5 +1,6 @@ import { - properties, + commits, + core, Resource, useResource, useString, @@ -20,6 +21,7 @@ export function ListView({ subResources, onNewClick, showNewButton, + basic, }: ViewProps): JSX.Element { return ( @@ -31,7 +33,7 @@ export function ListView({ Title Class - Last Modified + {!basic && Last Modified} @@ -43,9 +45,11 @@ export function ListView({ - - - + {!basic && ( + + + + )} ))} @@ -68,7 +72,7 @@ interface CellProps { function Title({ resource }: CellProps): JSX.Element { const [title] = useTitle(resource); - const [classType] = useString(resource, properties.isA); + const [classType] = useString(resource, core.properties.isA); const Icon = getIconForClass(classType ?? ''); return ( @@ -82,7 +86,7 @@ function Title({ resource }: CellProps): JSX.Element { } function LastCommit({ resource }: CellProps): JSX.Element { - const [commit] = useString(resource, properties.commit.lastCommit); + const [commit] = useString(resource, commits.properties.lastCommit); return ( @@ -92,7 +96,7 @@ function LastCommit({ resource }: CellProps): JSX.Element { } function ClassType({ resource }: CellProps): JSX.Element { - const [classType] = useString(resource, properties.isA); + const [classType] = useString(resource, core.properties.isA); const classTypeResource = useResource(classType); const [title] = useTitle(classTypeResource); @@ -111,8 +115,6 @@ const Wrapper = styled.div` --icon-width: 1rem; --icon-title-spacing: 1rem; --cell-padding: 0.4rem; - width: var(--container-width); - margin-inline: auto; `; const StyledTable = styled.table` diff --git a/browser/data-browser/src/views/ImporterPage.tsx b/browser/data-browser/src/views/ImporterPage.tsx index 614f42ca7..042164fca 100644 --- a/browser/data-browser/src/views/ImporterPage.tsx +++ b/browser/data-browser/src/views/ImporterPage.tsx @@ -1,5 +1,5 @@ import { Resource, useResource, useStore } from '@tomic/react'; -import { useCallback, useId, useState } from 'react'; +import { useCallback, useId, useMemo, useState } from 'react'; import { Button } from '../components/Button.jsx'; import { ContainerNarrow } from '../components/Containers'; import Field from '../components/forms/Field.jsx'; @@ -13,6 +13,7 @@ import { Column } from '../components/Row'; import { useCurrentSubject } from '../helpers/useCurrentSubject'; import { Title } from '../components/Title'; import toast from 'react-hot-toast'; +import { Main } from '@components/Main'; type ImporterPageProps = { resource?: Resource; @@ -47,68 +48,72 @@ export function ImporterPage({ resource }: ImporterPageProps) { } }, [parent, jsonAd, overwriteOutside, store]); + const disableImportButton = useMemo( + () => !parent || !jsonAd, + [parent, jsonAd], + ); + return ( - - - <p> - Read more about how importing Atomic Data works{' '} - <a href='https://docs.atomicdata.dev/create-json-ad.html'> - in the docs - </a> - . - </p> - <Column> - <Field label='JSON-AD'> - <InputWrapper> - <TextAreaStyled - // disabled={!!url} - rows={15} - placeholder='Paste your JSON-AD...' - value={jsonAd} - onChange={e => setJsonAd(e.target.value)} - > - {jsonAd} - </TextAreaStyled> - </InputWrapper> - </Field> - <Header>Options</Header> - <Group> - <Label> - <input - type='checkbox' - checked={overwriteOutside} - onChange={e => setOverwriteOutside(e.target.checked)} - /> - {`Overwrite resources that are outside the scope of the parent. Do this only if you trust the imported data.`} - </Label> - <Field - label='Target parent' - helper='This URL will be used as the default Parent for imported resources.' - required - fieldId={parentFieldId} - > + <Main> + <ContainerNarrow> + <Title resource={resource} prefix='Import to' link /> + <p> + Read more about how importing Atomic Data works{' '} + <a href='https://docs.atomicdata.dev/create-json-ad.html'> + in the docs + </a> + . + </p> + <Column> + <Field label='JSON-AD'> <InputWrapper> - <InputStyled - id={parentFieldId} - required - placeholder='Enter subject' - value={parent} - onChange={e => setParent(e.target.value)} - /> + <TextAreaStyled + rows={15} + placeholder='Paste your JSON-AD...' + value={jsonAd} + onChange={e => setJsonAd(e.target.value)} + > + {jsonAd} + </TextAreaStyled> </InputWrapper> </Field> - </Group> - {jsonAd !== '' && ( + <Header>Options</Header> + <Group> + <Label> + <input + type='checkbox' + checked={overwriteOutside} + onChange={e => setOverwriteOutside(e.target.checked)} + /> + {`Overwrite resources that are outside the scope of the parent. Do this only if you trust the imported data.`} + </Label> + <Field + label='Target parent' + helper='This URL will be used as the default Parent for imported resources.' + required + fieldId={parentFieldId} + > + <InputWrapper> + <InputStyled + id={parentFieldId} + required + placeholder='Enter subject' + value={parent} + onChange={e => setParent(e.target.value)} + /> + </InputWrapper> + </Field> + </Group> <Button data-test='import-post' - disabled={!parent} + disabled={disableImportButton} onClick={handleImport} > - {isImporting ? 'Importing...' : 'Send JSON'} + {isImporting ? 'Importing...' : 'Import'} </Button> - )} - </Column> - </ContainerNarrow> + </Column> + </ContainerNarrow> + </Main> ); } diff --git a/browser/data-browser/src/views/OntologyPage/OntologyPage.tsx b/browser/data-browser/src/views/OntologyPage/OntologyPage.tsx index 411a9a3fa..35e9fc40f 100644 --- a/browser/data-browser/src/views/OntologyPage/OntologyPage.tsx +++ b/browser/data-browser/src/views/OntologyPage/OntologyPage.tsx @@ -177,7 +177,7 @@ const FullPageWrapper = styled.div<{ edit: boolean }>` @container (max-width: 600px) { grid-template-areas: ${p => p.edit ? `'title' 'list' 'list'` : `'title' 'graph' 'list'`}; - grid-template-columns: 100vw; + grid-template-columns: 100cqw; ${SidebarSlot} { display: none; diff --git a/browser/data-browser/src/views/ResourceInline/ResourceInline.tsx b/browser/data-browser/src/views/ResourceInline/ResourceInline.tsx index a1e6774c7..c6c04218b 100644 --- a/browser/data-browser/src/views/ResourceInline/ResourceInline.tsx +++ b/browser/data-browser/src/views/ResourceInline/ResourceInline.tsx @@ -36,7 +36,7 @@ export function ResourceInline({ const resource = useResource(subject, { allowIncomplete: true }); const [isA] = useArray(resource, core.properties.isA); - const Comp = basic ? DefaultInline : classMap.get(isA[0]) ?? DefaultInline; + const Comp = basic ? DefaultInline : (classMap.get(isA[0]) ?? DefaultInline); if (!subject) { return <ErrorLook>No subject passed</ErrorLook>; diff --git a/browser/data-browser/src/views/ResourcePage.tsx b/browser/data-browser/src/views/ResourcePage.tsx index 02eb05573..16d6205a4 100644 --- a/browser/data-browser/src/views/ResourcePage.tsx +++ b/browser/data-browser/src/views/ResourcePage.tsx @@ -33,6 +33,7 @@ import { Main } from '../components/Main'; import { OntologyPage } from './OntologyPage'; import { TagPage } from './TagPage/TagPage'; import { AIChatPage } from '@views/AIChat/AIChatPage'; +import { DocumentV2FullPage } from './Document/DocumentV2FullPage'; /** These properties are passed to every View at Page level */ export type ResourcePageProps<Subject extends OptionalClass = never> = { @@ -123,6 +124,8 @@ function selectComponent(klass: string) { return TagPage; case ai.classes.aiChat: return AIChatPage; + case dataBrowser.classes.documentV2: + return DocumentV2FullPage; default: return ResourcePageDefault; } diff --git a/browser/data-browser/src/views/TablePage/EditorCells/AtomicURLCell.tsx b/browser/data-browser/src/views/TablePage/EditorCells/AtomicURLCell.tsx index 60673568b..a3d191f3d 100644 --- a/browser/data-browser/src/views/TablePage/EditorCells/AtomicURLCell.tsx +++ b/browser/data-browser/src/views/TablePage/EditorCells/AtomicURLCell.tsx @@ -24,11 +24,7 @@ import { FileDropzoneInput } from '../../../components/forms/FileDropzone/FileDr import { InputStyled, InputWrapper, -} from '../../../components/forms/InputStyles'; -import { - CursorMode, - useTableEditorContext, -} from '../../../components/TableEditor/TableEditorContext'; +} from '../../../components/forms/InputStyles'; //// import { getIconForClass } from '../../../helpers/iconMap'; import { CellContainer, DisplayCellProps, EditCellProps } from './Type'; import { useResourceSearch } from './useResourceSearch'; @@ -45,6 +41,7 @@ import { SearchResultWrapper, } from './CellComponents'; import { FaXmark } from 'react-icons/fa6'; +import { usePopover } from '@components/CustomPopover'; const useClassType = (subject: string) => { const property = useResource<Core.Property>(subject); @@ -64,17 +61,20 @@ function AtomicURLCellEdit({ property, resource: row, }: EditCellProps<JSONValue>): JSX.Element { + const inputRef = useRef<HTMLInputElement>(null); const cell = useResource(value as string); const { classType, hasClassType } = useClassType(property); const [title] = useTitle(cell); - const [open, setOpen] = useState(true); - const { setCursorMode } = useTableEditorContext(); + const { triggerProps, popoverProps, isOpen, closePopover } = usePopover({ + defaultOpen: true, + autoFocusElement: inputRef, + }); const selectedElement = useRef<HTMLLIElement>(null); const [searchValue, setSearchValue] = useState(''); const cellOptions = useMemo(() => { - if (open) { + if (isOpen) { return { disabledKeyboardInteractions: new Set([ KeyboardInteraction.ExitEditMode, @@ -83,7 +83,7 @@ function AtomicURLCellEdit({ } else { return {}; } - }, [open]); + }, [isOpen]); useCellOptions(cellOptions); @@ -96,26 +96,21 @@ function AtomicURLCellEdit({ const handleResultClick = useCallback( (result: string) => { onChange(result); - setOpen(false); - }, - [onChange], - ); - - const handleOpenChange = useCallback( - (state: boolean) => { - setOpen(state); - - if (!state) { - setCursorMode(CursorMode.Visual); - } + closePopover(); }, - [setCursorMode], + [onChange, closePopover], ); - const { results, selectedIndex, handleKeyDown } = useResourceSearch( + const { + results, + selectedIndex, + handleKeyDown, + onMouseOver, + onClick, + usingKeyboard, + } = useResourceSearch( searchValue, hasClassType ? classType.subject : undefined, - setOpen, handleResultClick, ); @@ -125,28 +120,28 @@ function AtomicURLCellEdit({ if (file) { onChange(file); - setOpen(false); + closePopover(); } }, - [onChange, setOpen], + [onChange, closePopover], ); const Trigger = useMemo(() => { return ( - <PopoverTrigger> + <PopoverTrigger {...triggerProps}> <FaEdit />{' '} {cell.subject === unknownSubject ? `select ${hasClassType ? classType.title : 'resource'}` : title} </PopoverTrigger> ); - }, [title, cell, classType, hasClassType]); + }, [title, cell, classType, hasClassType, triggerProps]); useEffect(() => { - if (selectedElement.current) { + if (selectedElement.current && usingKeyboard) { selectedElement.current.scrollIntoView({ block: 'nearest' }); } - }, [selectedIndex]); + }, [selectedIndex, usingKeyboard]); const placehoder = hasClassType ? `Search ${classType.title}` : 'Search...'; @@ -156,13 +151,7 @@ function AtomicURLCellEdit({ results.length === 0 && classType.subject !== server.classes.file; return ( - <SearchPopover - modal - Trigger={Trigger} - open={open} - onOpenChange={handleOpenChange} - noLock - > + <SearchPopover Trigger={Trigger} noLock {...popoverProps}> <InputWrapper> <InputStyled type='search' @@ -170,6 +159,7 @@ function AtomicURLCellEdit({ placeholder={placehoder} onChange={handleChange} onKeyDown={handleKeyDown} + ref={inputRef} /> </InputWrapper> <SearchResultWrapper> @@ -181,7 +171,11 @@ function AtomicURLCellEdit({ data-selected={index === selectedIndex} ref={index === selectedIndex ? selectedElement : null} > - <Result subject={result} onClick={handleResultClick} /> + <Result + subject={result} + onClick={() => onClick(index)} + onMouseOver={() => onMouseOver(index)} + /> </li> ))} </ol> @@ -212,22 +206,19 @@ function AtomicURLCellDisplay({ interface ResultProps { subject: string; - onClick: (subject: string) => void; + onClick: () => void; + onMouseOver: () => void; } -function Result({ subject, onClick }: ResultProps) { +function Result({ subject, onClick, onMouseOver }: ResultProps) { const resource = useResource(subject); const [title] = useTitle(resource); const [[classType]] = useArray(resource, core.properties.isA); const Icon = getIconForClass(classType); - const handleClick = useCallback(() => { - onClick(subject); - }, [subject]); - return ( - <ResultButton onClick={handleClick} tabIndex={-1}> + <ResultButton onClick={onClick} onMouseOver={onMouseOver} tabIndex={-1}> <Icon /> {title} </ResultButton> @@ -292,16 +283,9 @@ const ResultButton = styled.button` border: none; color: currentColor; cursor: pointer; + color: ${p => p.theme.colors.text}; padding: 0.3rem; border-radius: ${p => p.theme.radius}; - &:hover { - background: ${p => p.theme.colors.main}; - color: white; - - svg { - color: white; - } - } svg { color: ${p => p.theme.colors.textLight}; diff --git a/browser/data-browser/src/views/TablePage/EditorCells/CellComponents.tsx b/browser/data-browser/src/views/TablePage/EditorCells/CellComponents.tsx index f5a0a70c0..61b9d8989 100644 --- a/browser/data-browser/src/views/TablePage/EditorCells/CellComponents.tsx +++ b/browser/data-browser/src/views/TablePage/EditorCells/CellComponents.tsx @@ -1,6 +1,5 @@ import { styled } from 'styled-components'; -import * as RadixPopover from '@radix-ui/react-popover'; -import { Popover } from '../../../components/Popover'; +import { CustomPopover } from '@components/CustomPopover'; export const AbsoluteCell = styled.div` position: absolute; @@ -17,14 +16,17 @@ export const AbsoluteCell = styled.div` padding-inline: var(--table-inner-padding); padding-block: 3px; min-height: 40px; + overflow: hidden; `; -export const SearchPopover = styled(Popover)` - padding: 1rem; +export const SearchPopover = styled(CustomPopover)` border: 1px solid ${p => p.theme.colors.bg2}; - display: flex; - flex-direction: column; - gap: 1rem; + ${CustomPopover.Content} { + padding: 1rem; + display: flex; + flex-direction: column; + gap: 1rem; + } `; export const SearchResultWrapper = styled.div` @@ -34,24 +36,24 @@ export const SearchResultWrapper = styled.div` overflow-y: auto; ol { - padding: 0; + padding: 0 !important; margin: 0; } li { list-style: none; &[data-selected='true'] button { - background: ${p => p.theme.colors.main}; - color: white; - + background: ${p => p.theme.colors.mainSelectedBg}; + color: ${p => p.theme.colors.mainSelectedFg}; + box-shadow: 0 0 0 1px inset ${p => p.theme.colors.mainSelectedFg}; svg { - color: white; + color: ${p => p.theme.colors.mainSelectedFg}; } } } `; -export const PopoverTrigger = styled(RadixPopover.Trigger)` +export const PopoverTrigger = styled.button` border: none; background: none; color: ${p => p.theme.colors.main}; diff --git a/browser/data-browser/src/views/TablePage/EditorCells/DateCell.tsx b/browser/data-browser/src/views/TablePage/EditorCells/DateCell.tsx index ed9998156..d7aa05022 100644 --- a/browser/data-browser/src/views/TablePage/EditorCells/DateCell.tsx +++ b/browser/data-browser/src/views/TablePage/EditorCells/DateCell.tsx @@ -7,10 +7,11 @@ import { useString, validateDatatype, } from '@tomic/react'; -import { useCallback, useEffect, useState, type JSX } from 'react'; +import { useCallback, useState, type JSX } from 'react'; import { formatDate } from '../../../helpers/dates/formatDate'; import { InputBase } from './InputBase'; import { CellContainer, DisplayCellProps, EditCellProps } from './Type'; +import { useOnValueChange } from '@helpers/useOnValueChange'; function DateCellEdit({ value, @@ -29,14 +30,14 @@ function DateCellEdit({ try { validateDatatype(e.target.value, Datatype.DATE); onChange(e.target.value); - } catch (err) { + } catch (_) { // Do nothing. } }, [onChange], ); - useEffect(() => { + useOnValueChange(() => { setInnerValue(value as string); }, [value]); diff --git a/browser/data-browser/src/views/TablePage/EditorCells/DateTimeCell.tsx b/browser/data-browser/src/views/TablePage/EditorCells/DateTimeCell.tsx index 71a1545d3..6c3c7fb62 100644 --- a/browser/data-browser/src/views/TablePage/EditorCells/DateTimeCell.tsx +++ b/browser/data-browser/src/views/TablePage/EditorCells/DateTimeCell.tsx @@ -10,6 +10,7 @@ import { formatDate } from '../../../helpers/dates/formatDate'; import { InputBase } from './InputBase'; import { CellContainer, DisplayCellProps, EditCellProps } from './Type'; import { useDateTimeInput } from '../../../components/forms/hooks/useDateTimeInput'; +import { useOnValueChange } from '@helpers/useOnValueChange'; function DateTimeCellEdit({ value, @@ -53,7 +54,10 @@ function DateTimeCellDisplay({ ), ); - useEffect(() => { + useOnValueChange(() => { + // Relative date formatting is handled by the interval in useEffect + if (format === urls.instances.dateFormats.localRelative) return; + setDisplayData( toDisplayData( value, @@ -61,16 +65,18 @@ function DateTimeCellDisplay({ true, ), ); + }, [value, format]); + + useEffect(() => { + if (format !== urls.instances.dateFormats.localRelative) return; - if (format === urls.instances.dateFormats.localRelative) { - const interval = setInterval(() => { - setDisplayData( - toDisplayData(value, urls.instances.dateFormats.localRelative, true), - ); - }, 1000 * 60); + const interval = setInterval(() => { + setDisplayData( + toDisplayData(value, urls.instances.dateFormats.localRelative, true), + ); + }, 1000 * 60); - return () => clearInterval(interval); - } + return () => clearInterval(interval); }, [value, format]); return <>{displayData}</>; diff --git a/browser/data-browser/src/views/TablePage/EditorCells/InputBase.ts b/browser/data-browser/src/views/TablePage/EditorCells/InputBase.ts index bee08ce55..5c26c6ba1 100644 --- a/browser/data-browser/src/views/TablePage/EditorCells/InputBase.ts +++ b/browser/data-browser/src/views/TablePage/EditorCells/InputBase.ts @@ -2,6 +2,7 @@ import { styled } from 'styled-components'; export const InputBase = styled.input` position: absolute; + border: none; inset: 0; padding-inline: var(--table-inner-padding); background-color: ${p => p.theme.colors.bg}; diff --git a/browser/data-browser/src/views/TablePage/EditorCells/MultiRelationCell.tsx b/browser/data-browser/src/views/TablePage/EditorCells/MultiRelationCell.tsx index 894089021..0cd7349b6 100644 --- a/browser/data-browser/src/views/TablePage/EditorCells/MultiRelationCell.tsx +++ b/browser/data-browser/src/views/TablePage/EditorCells/MultiRelationCell.tsx @@ -1,4 +1,5 @@ import { + core, Core, JSONValue, unknownSubject, @@ -7,14 +8,7 @@ import { useResource, useTitle, } from '@tomic/react'; -import { - useCallback, - useEffect, - useMemo, - useRef, - useState, - type JSX, -} from 'react'; +import { useEffect, useRef, useState, type JSX } from 'react'; import { styled } from 'styled-components'; import { InputStyled, @@ -33,15 +27,15 @@ import { InlineFormattedResourceList } from '../../../components/InlineFormatted import { FaPlus, FaXmark } from 'react-icons/fa6'; import { AbsoluteCell, - PopoverTrigger, SearchPopover, SearchResultWrapper, } from './CellComponents'; import { Row } from '../../../components/Row'; -import { CellOptions } from '../../../components/TableEditor/hooks/useCellOptions'; import { Checkbox } from '../../../components/forms/Checkbox'; import { ResourceCell } from './ResourceCells/ResourceCell'; import { AtomicLink } from '../../../components/AtomicLink'; +import { usePopover } from '@components/CustomPopover'; +import { CELL_WIDTH } from '@components/TableEditor/Cell'; const useClassType = (subject: string) => { const property = useResource<Core.Property>(subject); @@ -60,90 +54,76 @@ function MultiRelationCellEdit({ onChange, property, }: EditCellProps<JSONValue>): JSX.Element { + const inputRef = useRef<HTMLInputElement>(null); const val = Array.isArray(value) ? value : []; - const { classType, hasClassType } = useClassType(property); - const [open, setOpen] = useState(true); - const { setCursorMode, activeCellRef } = useTableEditorContext(); + const { isOpen, triggerProps, popoverProps } = usePopover({ + defaultOpen: true, + autoFocusElement: inputRef, + }); + const { activeCellRef } = useTableEditorContext(); const selectedElement = useRef<HTMLLIElement>(null); const [searchValue, setSearchValue] = useState(''); - const cellOptions = useMemo((): CellOptions => { - const disabledKeyboardInteractions = new Set<KeyboardInteraction>([ - KeyboardInteraction.EditNextRow, - ]); - - if (open) { - disabledKeyboardInteractions.add(KeyboardInteraction.ExitEditMode); - } + const disabledKeyboardInteractions = new Set<KeyboardInteraction>([ + KeyboardInteraction.EditNextRow, + ]); - return { - disabledKeyboardInteractions, - hideActiveIndicator: true, - }; - }, [val, open]); + if (isOpen) { + disabledKeyboardInteractions.add(KeyboardInteraction.ExitEditMode); + } - useCellOptions(cellOptions); + useCellOptions({ + disabledKeyboardInteractions, + hideActiveIndicator: true, + }); - const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { + const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { e.preventDefault(); e.stopPropagation(); setSearchValue(e.target.value); - }, []); - - const handleResultClick = useCallback( - (result: string) => { - if (!result) return; - - if (val.includes(result)) { - onChange(val.filter(v => v !== result)); - } else { - onChange([...val, result]); - } - }, - [onChange, val], - ); + }; + + const handleResultClick = (result: string) => { + if (!result) return; + + if (val.includes(result)) { + onChange(val.filter(v => v !== result)); + } else { + onChange([...val, result]); + } + }; const handleRemoveItem = (subject: string) => { onChange(val.filter(v => v !== subject)); }; - const handleOpenChange = useCallback( - (state: boolean) => { - setOpen(state); - }, - [setCursorMode], - ); - - const { results, selectedIndex, handleKeyDown } = useResourceSearch( + const { + results, + selectedIndex, + handleKeyDown, + onMouseOver, + onClick, + usingKeyboard, + } = useResourceSearch( searchValue, hasClassType ? classType.subject : undefined, - setOpen, handleResultClick, + val as string[], ); - const Trigger = useMemo(() => { - return ( - <PopoverTrigger> - <IconButton title='Add resource'> - <FaPlus /> - </IconButton> - </PopoverTrigger> - ); - }, []); - useEffect(() => { - if (!open) { + if (!isOpen) { activeCellRef.current?.focus(); } - }, [open]); + }, [isOpen, activeCellRef]); useEffect(() => { - if (selectedElement.current) { + if (selectedElement.current && usingKeyboard) { selectedElement.current.scrollIntoView({ block: 'nearest' }); } - }, [selectedIndex]); + }, [selectedIndex, usingKeyboard]); const placehoder = hasClassType ? `Search ${classType.title}` : 'Search...'; @@ -153,19 +133,14 @@ function MultiRelationCellEdit({ return ( <AbsoluteCell> <Row wrapItems gap='1ch'> - {(value as string[])?.map(subject => ( - <ResourceItemButton - subject={subject} - key={subject} - onRemove={handleRemoveItem} - /> - ))} <SearchPopover - modal - Trigger={Trigger} - open={open} - onOpenChange={handleOpenChange} noLock + Trigger={ + <IconButton title='Add resource' {...triggerProps}> + <FaPlus /> + </IconButton> + } + {...popoverProps} > <InputWrapper> <InputStyled @@ -174,6 +149,7 @@ function MultiRelationCellEdit({ placeholder={placehoder} onChange={handleChange} onKeyDown={handleKeyDown} + ref={inputRef} /> </InputWrapper> <SearchResultWrapper> @@ -187,7 +163,8 @@ function MultiRelationCellEdit({ > <Result subject={result} - onClick={handleResultClick} + onClick={() => onClick(index)} + onMouseOver={() => onMouseOver(index)} selected={val.includes(result)} /> </li> @@ -197,6 +174,13 @@ function MultiRelationCellEdit({ {showNoResults && 'No results'} </SearchResultWrapper> </SearchPopover> + {(val as string[])?.map(subject => ( + <ResourceItemButton + subject={subject} + key={subject} + onRemove={handleRemoveItem} + /> + ))} </Row> </AbsoluteCell> ); @@ -215,9 +199,9 @@ function ResourceItemButton({ return ( <ResourceItemButtonWrapper> - <AtomicLink clean subject={resource.subject}> + <TruncatedAtomicLink clean subject={resource.subject}> {resource.title} - </AtomicLink> + </TruncatedAtomicLink> <IconButton title={`remove ${resource.title}`} onClick={() => onRemove(subject)} @@ -247,19 +231,20 @@ function MultiRelationCellDisplay({ interface ResultProps { subject: string; - onClick: (subject: string) => void; + onClick: () => void; + onMouseOver: () => void; selected: boolean; } -function Result({ subject, onClick, selected }: ResultProps) { +function Result({ subject, onClick, onMouseOver, selected }: ResultProps) { const resource = useResource(subject); const [title] = useTitle(resource); - const [[classType]] = useArray(resource, urls.properties.isA); + const [[classType]] = useArray(resource, core.properties.isA); const Icon = getIconForClass(classType); return ( - <ResultButton onClick={() => onClick(subject)} tabIndex={-1}> + <ResultButton onClick={onClick} onMouseOver={onMouseOver} tabIndex={-1}> <Checkbox checked={selected} onChange={() => undefined}></Checkbox> <Icon /> {title} @@ -272,13 +257,19 @@ export const MultiRelationCell: CellContainer<JSONValue> = { Display: MultiRelationCellDisplay, }; +const TruncatedAtomicLink = styled(AtomicLink)` + max-width: calc(${CELL_WIDTH.var()} - 4.5rem); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + const ResourceItemButtonWrapper = styled.span` display: inline-flex; padding-inline: 1ch; align-items: center; border: 1px solid ${p => p.theme.colors.main}; color: ${p => p.theme.colors.mainDark}; - border-radius: ${p => p.theme.radius}; `; @@ -293,14 +284,6 @@ const ResultButton = styled.button` cursor: pointer; padding: 0.3rem; border-radius: ${p => p.theme.radius}; - &:hover { - background: ${p => p.theme.colors.main}; - color: white; - - svg { - color: white; - } - } svg { color: ${p => p.theme.colors.textLight}; diff --git a/browser/data-browser/src/views/TablePage/EditorCells/ResourceCells/SimpleResourceLink.tsx b/browser/data-browser/src/views/TablePage/EditorCells/ResourceCells/SimpleResourceLink.tsx index 49d575dbf..028916d24 100644 --- a/browser/data-browser/src/views/TablePage/EditorCells/ResourceCells/SimpleResourceLink.tsx +++ b/browser/data-browser/src/views/TablePage/EditorCells/ResourceCells/SimpleResourceLink.tsx @@ -17,7 +17,7 @@ export function SimpleResourceLink({ const url = useMemo(() => { try { - return constructOpenURL(resource.getSubject()); + return constructOpenURL(resource.subject); } catch (e) { return '#'; } @@ -25,19 +25,14 @@ export function SimpleResourceLink({ const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => { e.preventDefault(); - // @ts-ignore navigate(url); }; - try { - return ( - <StyledAnchor href={url} onClick={handleClick} {...props}> - {children} - </StyledAnchor> - ); - } catch (e) { - return <>{resource.getSubject()}</>; - } + return ( + <StyledAnchor href={url} onClick={handleClick} {...props}> + {children} + </StyledAnchor> + ); } const StyledAnchor = styled.a` diff --git a/browser/data-browser/src/views/TablePage/EditorCells/SelectCell.tsx b/browser/data-browser/src/views/TablePage/EditorCells/SelectCell.tsx index 65a441038..16e5c6415 100644 --- a/browser/data-browser/src/views/TablePage/EditorCells/SelectCell.tsx +++ b/browser/data-browser/src/views/TablePage/EditorCells/SelectCell.tsx @@ -6,12 +6,10 @@ import { useResource, useStore, } from '@tomic/react'; -import { useCallback, useEffect, useMemo, useState, type JSX } from 'react'; +import { useEffect, useRef, useState, type JSX } from 'react'; import { FaPlus } from 'react-icons/fa'; -import * as RadixPopover from '@radix-ui/react-popover'; import { styled } from 'styled-components'; import { IconButton } from '../../../components/IconButton/IconButton'; -import { Popover } from '../../../components/Popover'; import { TagButton, Tag } from '../../../components/Tag'; import { CellContainer, DisplayCellProps, EditCellProps } from './Type'; import { @@ -27,9 +25,9 @@ import { useCellOptions, } from '../../../components/TableEditor'; import { useTableEditorContext } from '../../../components/TableEditor/TableEditorContext'; -import { CellOptions } from '../../../components/TableEditor/hooks/useCellOptions'; import { AbsoluteCell } from './CellComponents'; import { FaXmark } from 'react-icons/fa6'; +import { CustomPopover, usePopover } from '@components/CustomPopover'; const TAG_SPACING = '0.5rem'; @@ -55,97 +53,84 @@ function SelectCellEdit({ property, onChange, }: EditCellProps<JSONValue>): JSX.Element { + const inputRef = useRef<HTMLInputElement>(null); const val = (value as string[]) ?? emptyArray; - const store = useStore(); const propertyResource = useResource(property); const [allowsOnly] = useArray(propertyResource, core.properties.allowsOnly); const [query, setQuery] = useState(''); - const filteredTags = useMemo(() => { - const listWithTitles = buildListWithTitles(store, allowsOnly, val); - - return listWithTitles - .filter(v => v.title.includes(query)) - .map(ft => ft.subject); - }, [store, allowsOnly, val, query]); - const [open, setOpen] = useState(true); + + const filteredTags = buildListWithTitles(store, allowsOnly, val) + .filter(v => v.title.includes(query)) + .map(ft => ft.subject); + + const { isOpen, closePopover, triggerProps, popoverProps } = usePopover({ + defaultOpen: true, + autoFocusElement: inputRef, + }); const [selectedIndex, setSelectedIndex] = useState(0); const { activeCellRef } = useTableEditorContext(); - const cellOptions = useMemo((): CellOptions => { - const disabledKeyboardInteractions = new Set<KeyboardInteraction>([ - KeyboardInteraction.EditNextRow, - ]); + const disabledKeyboardInteractions = new Set<KeyboardInteraction>([ + KeyboardInteraction.EditNextRow, + ]); - if (open) { - disabledKeyboardInteractions.add(KeyboardInteraction.ExitEditMode); - } + if (isOpen) { + disabledKeyboardInteractions.add(KeyboardInteraction.ExitEditMode); + } - return { - disabledKeyboardInteractions, - hideActiveIndicator: true, - }; - }, [val, open]); + const cellOptions = { + disabledKeyboardInteractions, + hideActiveIndicator: true, + }; useCellOptions(cellOptions); - const handleSearch = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { + const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { setQuery(stringToSlug(e.target.value)); setSelectedIndex(0); - }, []); + }; - const handleAddTag = useCallback( - (subject: string) => { - onChange(Array.from(new Set([...val, subject]))); - }, - [val, onChange], - ); + const handleAddTag = (subject: string) => { + onChange(Array.from(new Set([...val, subject]))); + }; - const handleRemoveTag = useCallback( - (subject: string) => { - onChange(val.filter(tagSubject => tagSubject !== subject)); - }, - [val, onChange], - ); + const handleRemoveTag = (subject: string) => { + onChange(val.filter(tagSubject => tagSubject !== subject)); + }; - const changeSelection = useCallback( - (mod: number) => { - setSelectedIndex(prev => loopingIndex(prev + mod, filteredTags.length)); - }, - [filteredTags], - ); + const changeSelection = (mod: number) => { + setSelectedIndex(prev => loopingIndex(prev + mod, filteredTags.length)); + }; useEffect(() => { - if (!open) { + if (!isOpen) { activeCellRef.current?.focus(); } - }, [open]); - - const handleKeyDown = useCallback( - (e: React.KeyboardEvent<HTMLDivElement>) => { - switch (e.key) { - case 'ArrowUp': - e.preventDefault(); - changeSelection(-1); - break; - case 'ArrowDown': - e.preventDefault(); - changeSelection(1); - break; - case 'Enter': - e.preventDefault(); - handleAddTag(filteredTags[selectedIndex]); - break; - case 'Escape': - e.preventDefault(); - - setOpen(false); - break; - } - }, - [changeSelection, filteredTags, selectedIndex, open], - ); + }, [activeCellRef, isOpen]); + + const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => { + switch (e.key) { + case 'ArrowUp': + e.preventDefault(); + changeSelection(-1); + break; + case 'ArrowDown': + e.preventDefault(); + changeSelection(1); + break; + case 'Enter': + e.preventDefault(); + handleAddTag(filteredTags[selectedIndex]); + break; + case 'Escape': + e.preventDefault(); + + closePopover(); + break; + } + }; return ( <AbsoluteCell> @@ -160,23 +145,21 @@ function SelectCellEdit({ </TagIconButton> </Tag> ))} - <Popover - modal - defaultOpen + <CustomPopover noLock - open={open} - onOpenChange={setOpen} Trigger={ - <IconButton title='Add tag' as={RadixPopover.Trigger}> + <IconButton title='Add tag' type='button' {...triggerProps}> <StyledIcon /> </IconButton> } + {...popoverProps} > <Content onKeyDown={handleKeyDown}> <SearchInputWrapper> <InputStyled placeholder='Filter tags...' onChange={handleSearch} + ref={inputRef} /> </SearchInputWrapper> <ResultWrapper> @@ -189,10 +172,11 @@ function SelectCellEdit({ selected={i === selectedIndex} /> ))} + {filteredTags.length === 0 && 'No results'} </Row> </ResultWrapper> </Content> - </Popover> + </CustomPopover> </Row> </AbsoluteCell> ); @@ -241,6 +225,11 @@ const Content = styled.div` const ResultWrapper = styled.div` padding: ${p => p.theme.margin}rem; + border: ${p => + p.theme.darkMode ? '1px solid ' + p.theme.colors.bg2 : 'none'}; + border-top: none; + border-bottom-left-radius: ${p => p.theme.radius}; + border-bottom-right-radius: ${p => p.theme.radius}; `; const SearchInputWrapper = styled(InputWrapper)` diff --git a/browser/data-browser/src/views/TablePage/EditorCells/useResourceSearch.ts b/browser/data-browser/src/views/TablePage/EditorCells/useResourceSearch.ts index e628eec18..1750463d2 100644 --- a/browser/data-browser/src/views/TablePage/EditorCells/useResourceSearch.ts +++ b/browser/data-browser/src/views/TablePage/EditorCells/useResourceSearch.ts @@ -1,70 +1,60 @@ -import { SearchOpts, urls, useServerSearch } from '@tomic/react'; -import { useCallback, useMemo, useState } from 'react'; +import { core, SearchOpts, useServerSearch } from '@tomic/react'; +import { useCallback, useMemo } from 'react'; import { useSettings } from '../../../helpers/AppSettings'; +import { useSelectedIndex } from '@hooks/useSelectedIndex'; export function useResourceSearch( searchValue: string, classType: string | undefined, - setOpen: (state: boolean) => void, onResultPick: (result: string) => void, + valuesWhenEmpty: string[] = [], ) { - const [selectedIndex, setSelectedIndex] = useState(0); const { drive } = useSettings(); const searchOpts = useMemo( (): SearchOpts => ({ parents: drive, - filters: classType ? { [urls.properties.isA]: classType } : undefined, + filters: classType ? { [core.properties.isA]: classType } : undefined, + include: false, }), [drive, classType], ); const { results } = useServerSearch(searchValue, searchOpts); + const list = + !searchValue && valuesWhenEmpty !== undefined ? valuesWhenEmpty : results; + const { selectedIndex, onKeyDown, onMouseOver, onClick, usingKeyboard } = + useSelectedIndex( + list, + i => { + if (i === undefined) return; + + onResultPick(list[i]); + }, + { initialIndex: 0, key: searchValue }, + ); const handleKeyDown = useCallback( (e: React.KeyboardEvent<HTMLInputElement>) => { - if (e.key === 'Escape') { - e.preventDefault(); - setOpen(false); - - return; - } - if (e.key === 'Tab') { return; } - e.stopPropagation(); - - if (e.key === 'ArrowUp') { - e.preventDefault(); - setSelectedIndex(i => Math.max(0, i - 1)); - - return; - } - - if (e.key === 'ArrowDown') { - e.preventDefault(); - setSelectedIndex(i => Math.min(results.length - 1, i + 1)); - - return; - } - if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); - onResultPick(results[selectedIndex]); - - return; } - setSelectedIndex(0); + onKeyDown(e); }, - [results, onResultPick, selectedIndex], + [onKeyDown], ); return { - results, + results: list, selectedIndex, handleKeyDown, + onMouseOver, + onClick, + usingKeyboard, }; } diff --git a/browser/data-browser/src/views/TablePage/PropertyForm/ExternalPropertyDialog.tsx b/browser/data-browser/src/views/TablePage/PropertyForm/ExternalPropertyDialog.tsx index 03001e36a..9d50bd37e 100644 --- a/browser/data-browser/src/views/TablePage/PropertyForm/ExternalPropertyDialog.tsx +++ b/browser/data-browser/src/views/TablePage/PropertyForm/ExternalPropertyDialog.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, type JSX } from 'react'; +import { useState, type JSX } from 'react'; import { Dialog, DialogActions, @@ -10,6 +10,7 @@ import { ResourceSelector } from '../../../components/forms/ResourceSelector'; import { Resource, urls, useArray } from '@tomic/react'; import { Button } from '../../../components/Button'; import { FormValidationContextProvider } from '../../../components/forms/formValidation/FormValidationContextProvider'; +import { useOnValueChange } from '@helpers/useOnValueChange'; interface ExternalPropertyDialogProps { open: boolean; @@ -39,12 +40,12 @@ export function ExternalPropertyDialog({ } }; - useEffect(() => { + useOnValueChange(() => { if (open) { show(); setSubject(undefined); } - }, [open, show]); + }, [open]); return ( <Dialog {...dialogProps}> diff --git a/browser/data-browser/src/views/TablePage/PropertyForm/PropertyForm.tsx b/browser/data-browser/src/views/TablePage/PropertyForm/PropertyForm.tsx index 54319c1bb..179281ebd 100644 --- a/browser/data-browser/src/views/TablePage/PropertyForm/PropertyForm.tsx +++ b/browser/data-browser/src/views/TablePage/PropertyForm/PropertyForm.tsx @@ -1,4 +1,4 @@ -import { core, Resource, useString } from '@tomic/react'; +import { core, Resource, unknownSubject, useString } from '@tomic/react'; import { useCallback, useEffect, useMemo, type JSX } from 'react'; import { styled } from 'styled-components'; import { ErrorChip } from '../../../components/forms/ErrorChip'; @@ -28,6 +28,7 @@ export function PropertyForm({ setError: setNameError, setTouched: setNameTouched, } = useValidation('Required'); + const valueOptions = useMemo( () => ({ handleValidationError(e: Error | undefined) { @@ -38,7 +39,7 @@ export function PropertyForm({ } }, }), - [], + [setNameError], ); const [name, setName] = useString( @@ -73,7 +74,12 @@ export function PropertyForm({ // If name was already set remove the error. useEffect(() => { - if (existingProperty && !name && shortname) { + if ( + resource.subject !== unknownSubject && + existingProperty && + !name && + shortname + ) { setName(shortname); setNameError(undefined); } diff --git a/browser/data-browser/src/views/TablePage/TableCell.tsx b/browser/data-browser/src/views/TablePage/TableCell.tsx index 8c6dcc83f..f3537b805 100644 --- a/browser/data-browser/src/views/TablePage/TableCell.tsx +++ b/browser/data-browser/src/views/TablePage/TableCell.tsx @@ -2,8 +2,8 @@ import { commits, JSONValue, Property, - Resource, useDebouncedSave, + useResource, useValue, } from '@tomic/react'; import { useCallback, useContext, useMemo, type JSX } from 'react'; @@ -22,10 +22,10 @@ import { StringCell } from './EditorCells/StringCell'; import { TablePageContext } from './tablePageContext'; import { createValueChangedHistoryItem } from './helpers/useTableHistory'; -interface TableCell { +interface TableCellProps { columnIndex: number; rowIndex: number; - resource: Resource; + subject: string; property: Property; onEditNextRow?: () => void; } @@ -54,10 +54,13 @@ const emptyFunc = () => undefined; export function TableCell({ columnIndex, rowIndex, - resource, + subject, property, onEditNextRow, -}: TableCell): JSX.Element { +}: TableCellProps): JSX.Element { + const resource = useResource(subject, { + track: [property.subject], + }); const { setActiveCell } = useTableEditorContext(); const { addItemsToHistoryStack } = useContext(TablePageContext); // We give an empty error handler to debouncedSave so it doesn't spam the user with error popups when the value is invalid. @@ -103,7 +106,7 @@ export function TableCell({ setCreatedAt, createdAt, resource, - property, + property.subject, save, addItemsToHistoryStack, ], @@ -116,13 +119,15 @@ export function TableCell({ [onChange, dataType], ); + const propValCount = resource.getPropVals().size; + const handleEditNextRow = useCallback(() => { if (!savePending) { onEditNextRow?.(); // Only go to the next row if the resource has any properties set (It has two by default, isA and parent) // This prevents triggering a rerender and losing focus on the input. - if (resource.getPropVals().size > 2) { + if (propValCount > 2) { setActiveCell(rowIndex + 1, columnIndex); } } @@ -132,7 +137,7 @@ export function TableCell({ rowIndex, columnIndex, onEditNextRow, - resource, + propValCount, ]); return ( @@ -151,13 +156,11 @@ export function TableCell({ resource={resource} /> ) : ( - <> - <Editor.Display - value={value} - onChange={onChange} - property={property.subject} - /> - </> + <Editor.Display + value={value} + onChange={onChange} + property={property.subject} + /> )} </Cell> ); diff --git a/browser/data-browser/src/views/TablePage/TableHeadingMenu.tsx b/browser/data-browser/src/views/TablePage/TableHeadingMenu.tsx index 6f248ff40..3820f8ec5 100644 --- a/browser/data-browser/src/views/TablePage/TableHeadingMenu.tsx +++ b/browser/data-browser/src/views/TablePage/TableHeadingMenu.tsx @@ -6,14 +6,7 @@ import { useString, core, } from '@tomic/react'; -import { - useCallback, - useContext, - useEffect, - useMemo, - useState, - type JSX, -} from 'react'; +import { useCallback, useContext, useMemo, useState, type JSX } from 'react'; import { DropdownMenu, DropdownItem } from '../../components/Dropdown'; import { buildDefaultTrigger } from '../../components/Dropdown/DefaultTrigger'; import { FaEdit, FaEllipsisV, FaEye, FaTimes } from 'react-icons/fa'; @@ -48,9 +41,17 @@ export function TableHeadingMenu({ resource, }: TableHeadingMenuProps): JSX.Element { const [showEditDialog, setShowEditDialog] = useState(false); - const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [showDeleteDialog, setShowDeleteDialog_internal] = useState(false); const [fullDelete, setFullDelete] = useState(false); + const setShowDeleteDialog = useCallback((show: boolean) => { + setShowDeleteDialog_internal(show); + + if (!show) { + setFullDelete(false); + } + }, []); + const { tableClassSubject } = useContext(TablePageContext); const tableClassResource = useResource(tableClassSubject); const canWriteClass = useCanWrite(tableClassResource); @@ -84,7 +85,7 @@ export function TableHeadingMenu({ await removeProperty(); resource.destroy(); - }, [removeProperty]); + }, [removeProperty, resource]); const onConfirm = useCallback(() => { if (isExternalProperty) { @@ -119,16 +120,15 @@ export function TableHeadingMenu({ disabled: !canWriteClass, }, ], - [canWriteClass, canWriteProperty, navigate, resource], + [ + canWriteClass, + canWriteProperty, + navigate, + resource.subject, + setShowDeleteDialog, + ], ); - // Reset fullDelete when dialog is closed - useEffect(() => { - if (!showDeleteDialog) { - setFullDelete(false); - } - }, [showDeleteDialog]); - return ( <Wrapper> <DropdownMenu Trigger={Trigger} items={items} /> diff --git a/browser/data-browser/src/views/TablePage/TablePage.tsx b/browser/data-browser/src/views/TablePage/TablePage.tsx index cc3e73d5b..d384b0c4e 100644 --- a/browser/data-browser/src/views/TablePage/TablePage.tsx +++ b/browser/data-browser/src/views/TablePage/TablePage.tsx @@ -1,183 +1,39 @@ -import { Property, unknownSubject, useCanWrite, useStore } from '@tomic/react'; -import { useCallback, useId, useMemo, useState, type JSX } from 'react'; +import { useId, useState, type JSX } from 'react'; import { ContainerFull } from '../../components/Containers'; import { EditableTitle } from '../../components/EditableTitle'; -import { FancyTable } from '../../components/TableEditor'; import type { ResourcePageProps } from '../ResourcePage'; -import { TableHeading } from './TableHeading'; -import { useTableColumns } from './useTableColumns'; -import { TableNewRow, TableRow } from './TableRow'; -import { useTableData } from './useTableData'; -import { NewColumnButton } from './NewColumnButton'; -import { TablePageContext, TablePageContextType } from './tablePageContext'; -import { useHandlePaste } from './helpers/useHandlePaste'; -import { useHandleColumnResize } from './helpers/useHandleColumnResize'; -import { - createResourceDeletedHistoryItem, - useTableHistory, -} from './helpers/useTableHistory'; import { Row as FlexRow, Column } from '../../components/Row'; -import { useHandleClearCells } from './helpers/useHandleClearCells'; -import { useHandleCopyCommand } from './helpers/useHandleCopyCommand'; -import { ExpandedRowDialog } from './ExpandedRowDialog'; -import { IconButton } from '../../components/IconButton/IconButton'; -import { FaCode, FaFileCsv } from 'react-icons/fa6'; -import { ResourceCodeUsageDialog } from '../CodeUsage/ResourceCodeUsageDialog'; +import { FaFileCsv } from 'react-icons/fa6'; import { TableExportDialog } from './TableExportDialog'; import { TagBar } from '../../components/Tag/TagBar'; - -const columnToKey = (column: Property) => column.subject; +import { TableResource } from './TableResource'; +import { useCustomContextItems } from '@components/ResourceContextMenu/CustomContextItemsContext'; +import { DIVIDER } from '@components/Dropdown'; export function TablePage({ resource }: ResourcePageProps): JSX.Element { - const store = useStore(); const titleId = useId(); - const canWrite = useCanWrite(resource); - - const [showCodeUsageDialog, setShowCodeUsageDialog] = useState(false); const [showExportDialog, setShowExportDialog] = useState(false); - const { tableClass, sorting, setSortBy, collection, invalidateCollection } = - useTableData(resource); - - const { columns, reorderColumns } = useTableColumns(tableClass); - - const { undoLastItem, addItemsToHistoryStack } = - useTableHistory(invalidateCollection); - - const handlePaste = useHandlePaste( - resource, - collection, - tableClass, - invalidateCollection, - addItemsToHistoryStack, - ); - - const [showExpandedRowDialog, setShowExpandedRowDialog] = useState(false); - const [expandedRowSubject, setExpandedRowSubject] = useState<string>(); - - const handleRowExpand = useCallback( - async (index: number) => { - const row = await collection.getMemberWithIndex(index); - setExpandedRowSubject(row); - setShowExpandedRowDialog(true); - }, - [collection], - ); - - const tablePageContext: TablePageContextType = useMemo( - () => ({ - tableClassSubject: tableClass.subject, - sorting, - setSortBy, - addItemsToHistoryStack, - }), - [tableClass, setSortBy, sorting, addItemsToHistoryStack], - ); - - const handleDeleteRow = useCallback( - async (index: number) => { - const row = await collection.getMemberWithIndex(index); - - if (!row) { - return; - } - const rowResource = store.getResourceLoading(row); - addItemsToHistoryStack(createResourceDeletedHistoryItem(rowResource)); - - await rowResource.destroy(); - - invalidateCollection(); - }, - [collection, store, invalidateCollection, addItemsToHistoryStack], - ); - - const handleClearCells = useHandleClearCells( - collection, - addItemsToHistoryStack, - ); - - const handleCopyCommand = useHandleCopyCommand(collection); - - const [columnSizes, handleColumnResize] = useHandleColumnResize(resource); - - const Row = useCallback( - ({ index }: { index: number }) => { - if (index < collection.totalMembers) { - return ( - <TableRow collection={collection} index={index} columns={columns} /> - ); - } - - return ( - <TableNewRow - parent={resource} - columns={columns} - index={index} - invalidateTable={invalidateCollection} - /> - ); + useCustomContextItems([ + DIVIDER, + { + id: 'export-csv', + label: 'Export to CSV', + onClick: () => setShowExportDialog(true), + icon: <FaFileCsv />, }, - - // Resource can update a lot but its internals are stable so removing it from the array saves a lot of rerenders and shouldn't cause issues. - // eslint-disable-next-line react-hooks/react-compiler, react-hooks/exhaustive-deps - [collection, columns, invalidateCollection, resource.subject], - ); + ]); return ( <ContainerFull> - <TablePageContext.Provider value={tablePageContext}> - <Column> - <FlexRow justify='space-between'> - <EditableTitle resource={resource} id={titleId} /> - <FlexRow style={{ marginRight: '1rem' }}> - <IconButton - title='Use in code' - onClick={() => setShowCodeUsageDialog(true)} - > - <FaCode /> - </IconButton> - <IconButton - title='Export to CSV' - onClick={() => setShowExportDialog(true)} - > - <FaFileCsv /> - </IconButton> - </FlexRow> - </FlexRow> - <TagBar resource={resource} /> - <FancyTable - readOnly={!canWrite} - columns={columns} - columnSizes={columnSizes} - itemCount={collection.totalMembers + 1} - columnToKey={columnToKey} - labelledBy={titleId} - onClearRow={handleDeleteRow} - onCellResize={handleColumnResize} - onClearCells={handleClearCells} - onCopyCommand={handleCopyCommand} - onPasteCommand={handlePaste} - onUndoCommand={undoLastItem} - onColumnReorder={reorderColumns} - onRowExpand={handleRowExpand} - HeadingComponent={TableHeading} - NewColumnButtonComponent={NewColumnButton} - > - {Row} - </FancyTable> - </Column> - <ExpandedRowDialog - subject={expandedRowSubject ?? unknownSubject} - open={showExpandedRowDialog} - bindOpen={setShowExpandedRowDialog} - /> - </TablePageContext.Provider> - <ResourceCodeUsageDialog - subject={resource.subject} - show={showCodeUsageDialog} - bindShow={setShowCodeUsageDialog} - /> + <Column> + <FlexRow justify='space-between'> + <EditableTitle resource={resource} id={titleId} /> + </FlexRow> + <TagBar resource={resource} /> + <TableResource resource={resource} /> + </Column> <TableExportDialog subject={resource.subject} show={showExportDialog} diff --git a/browser/data-browser/src/views/TablePage/TableResource.tsx b/browser/data-browser/src/views/TablePage/TableResource.tsx new file mode 100644 index 000000000..c1f3e74dd --- /dev/null +++ b/browser/data-browser/src/views/TablePage/TableResource.tsx @@ -0,0 +1,158 @@ +import { + unknownSubject, + useCanWrite, + useStore, + type DataBrowser, + type Property, + type Resource, +} from '@tomic/react'; +import { useHandleClearCells } from '@views/TablePage/helpers/useHandleClearCells'; +import { useHandleColumnResize } from '@views/TablePage/helpers/useHandleColumnResize'; +import { useHandleCopyCommand } from '@views/TablePage/helpers/useHandleCopyCommand'; +import { useHandlePaste } from '@views/TablePage/helpers/useHandlePaste'; +import { + useTableHistory, + createResourceDeletedHistoryItem, +} from '@views/TablePage/helpers/useTableHistory'; +import { + TablePageContext, + type TablePageContextType, +} from '@views/TablePage/tablePageContext'; +import { TableNewRow, TableRow } from '@views/TablePage/TableRow'; +import { useTableColumns } from '@views/TablePage/useTableColumns'; +import { useTableData } from '@views/TablePage/useTableData'; +import { useId, useState, useCallback, useMemo } from 'react'; +import { FancyTable } from '../../components/TableEditor/TableEditor'; +import { NewColumnButton } from './NewColumnButton'; +import { TableHeading } from './TableHeading'; +import { ExpandedRowDialog } from './ExpandedRowDialog'; + +interface TableResourceProps { + resource: Resource<DataBrowser.Table>; +} + +const columnToKey = (column: Property) => column.subject; + +export const TableResource: React.FC<TableResourceProps> = ({ resource }) => { + const store = useStore(); + const titleId = useId(); + const canWrite = useCanWrite(resource); + + const { tableClass, sorting, setSortBy, collection, invalidateCollection } = + useTableData(resource); + + const { columns, reorderColumns } = useTableColumns(tableClass); + + const { undoLastItem, addItemsToHistoryStack } = + useTableHistory(invalidateCollection); + + const handlePaste = useHandlePaste( + resource, + collection, + tableClass, + invalidateCollection, + addItemsToHistoryStack, + ); + + const [showExpandedRowDialog, setShowExpandedRowDialog] = useState(false); + const [expandedRowSubject, setExpandedRowSubject] = useState<string>(); + + const handleRowExpand = useCallback( + async (index: number) => { + const row = await collection.getMemberWithIndex(index); + setExpandedRowSubject(row); + setShowExpandedRowDialog(true); + }, + [collection], + ); + + const tablePageContext: TablePageContextType = useMemo( + () => ({ + tableClassSubject: tableClass.subject, + sorting, + setSortBy, + addItemsToHistoryStack, + }), + [tableClass, setSortBy, sorting, addItemsToHistoryStack], + ); + + const handleDeleteRow = useCallback( + async (index: number) => { + const row = await collection.getMemberWithIndex(index); + + if (!row) { + return; + } + + const rowResource = store.getResourceLoading(row); + addItemsToHistoryStack(createResourceDeletedHistoryItem(rowResource)); + + await rowResource.destroy(); + + invalidateCollection(); + }, + [collection, store, invalidateCollection, addItemsToHistoryStack], + ); + + const handleClearCells = useHandleClearCells( + collection, + addItemsToHistoryStack, + ); + + const handleCopyCommand = useHandleCopyCommand(collection); + + const [columnSizes, handleColumnResize] = useHandleColumnResize(resource); + + const Row = useCallback( + ({ index }: { index: number }) => { + if (index < collection.totalMembers) { + return ( + <TableRow collection={collection} index={index} columns={columns} /> + ); + } + + return ( + <TableNewRow + parent={resource} + columns={columns} + index={index} + invalidateTable={invalidateCollection} + /> + ); + }, + + // Resource can update a lot but its internals are stable so removing it from the array saves a lot of rerenders and shouldn't cause issues. + // eslint-disable-next-line react-hooks/exhaustive-deps + [collection, columns, invalidateCollection, resource.subject], + ); + + return ( + <TablePageContext value={tablePageContext}> + <FancyTable + readOnly={!canWrite} + columns={columns} + columnSizes={columnSizes} + itemCount={collection.totalMembers + 1} + columnToKey={columnToKey} + labelledBy={titleId} + onClearRow={handleDeleteRow} + onCellResize={handleColumnResize} + onClearCells={handleClearCells} + onCopyCommand={handleCopyCommand} + onPasteCommand={handlePaste} + onUndoCommand={undoLastItem} + onColumnReorder={reorderColumns} + onRowExpand={handleRowExpand} + HeadingComponent={TableHeading} + NewColumnButtonComponent={NewColumnButton} + > + {Row} + </FancyTable> + <ExpandedRowDialog + subject={expandedRowSubject ?? unknownSubject} + open={showExpandedRowDialog} + bindOpen={setShowExpandedRowDialog} + /> + </TablePageContext> + ); +}; diff --git a/browser/data-browser/src/views/TablePage/TableRow.tsx b/browser/data-browser/src/views/TablePage/TableRow.tsx index adc0b34a5..d466bac66 100644 --- a/browser/data-browser/src/views/TablePage/TableRow.tsx +++ b/browser/data-browser/src/views/TablePage/TableRow.tsx @@ -1,4 +1,11 @@ -import { memo, useEffect, useState, type JSX } from 'react'; +import { + memo, + useEffect, + useEffectEvent, + useRef, + useState, + type JSX, +} from 'react'; import { Collection, DataBrowser, @@ -31,31 +38,38 @@ const TableCellMemo = memo(TableCell); function useMarkings(row: Resource, index: number) { const { setMarkings } = useTableEditorContext(); + const addMarkings = useEffectEvent(() => { + setMarkings(markings => { + const newMap = new Map(markings); + newMap.set( + index, + <WarningIcon title='Row is incomplete or has invalid data' />, + ); + + return newMap; + }); + }); + + const removeMarkings = useEffectEvent(() => { + setMarkings(markings => { + const newMap = new Map(markings); + newMap.delete(index); + + return newMap; + }); + }); + useEffect(() => { if (row.commitError) { - setMarkings(markings => { - const newMap = new Map(markings); - newMap.set( - index, - <WarningIcon title='Row is incomplete or has invalid data' />, - ); - - return newMap; - }); + addMarkings(); } return () => { - setMarkings(markings => { - const newMap = new Map(markings); - newMap.delete(index); - - return newMap; - }); + removeMarkings(); }; // Markings don't need to be updated when the function address changes... - // eslint-disable-next-line react-hooks/react-compiler, react-hooks/exhaustive-deps - }, [row, index]); + }, [row.commitError, index]); } export function TableRow({ @@ -84,7 +98,7 @@ export function TableRow({ key={column.subject} rowIndex={index} columnIndex={cIndex + 1} - resource={resource} + subject={resource.subject} property={column} /> ))} @@ -114,7 +128,7 @@ export function TableNewRow({ const [loading, setLoading] = useState(true); const resource = useResource(subject, resourceOpts); - + const resourceRef = useRef(resource); const onEditNextRow = useTableInvalidation(resource, invalidateTable); useMarkings(resource, index); @@ -124,16 +138,23 @@ export function TableNewRow({ return; } - resource + resourceRef.current .set(core.properties.parent, parent.subject) - .then(() => resource.set(core.properties.isA, [parent.props.classtype])) + .then(() => + resourceRef.current.set(core.properties.isA, [parent.props.classtype]), + ) .then(() => { setLoading(false); }); // We can't add resource to the list because we modify the resource in the effect so it would cause a loop. - // eslint-disable-next-line react-hooks/react-compiler, react-hooks/exhaustive-deps - }, [resource.subject, parent.subject, parent.props.classtype]); + // We put resource in a ref so we don't need to add it to the list. + }, [ + resource.subject, + parent.subject, + parent.props.classtype, + resource.commitError, + ]); if (loading) { return ( @@ -152,7 +173,7 @@ export function TableNewRow({ key={column.subject} rowIndex={index} columnIndex={cIndex + 1} - resource={resource} + subject={resource.subject} property={column} onEditNextRow={onEditNextRow} /> diff --git a/browser/data-browser/src/views/TablePage/helpers/useTableHistory.ts b/browser/data-browser/src/views/TablePage/helpers/useTableHistory.ts index 05bee9cb9..f61dab4da 100644 --- a/browser/data-browser/src/views/TablePage/helpers/useTableHistory.ts +++ b/browser/data-browser/src/views/TablePage/helpers/useTableHistory.ts @@ -1,4 +1,10 @@ -import { JSONValue, Resource, Store, useStore } from '@tomic/react'; +import { + JSONValue, + Resource, + Store, + useStore, + type PropVals, +} from '@tomic/react'; import { useCallback, useState } from 'react'; enum HistoryItemType { @@ -22,7 +28,7 @@ interface ResourceCreatedItem { interface ResourceDeletedItem { type: HistoryItemType.ResourceDeleted; subject: string; - propVals: Map<string, JSONValue>; + propVals: PropVals; } type HistoryItem = ValueChangeItem | ResourceCreatedItem | ResourceDeletedItem; diff --git a/browser/data-browser/vite.config.ts b/browser/data-browser/vite.config.ts index 495a57f2d..84137064c 100644 --- a/browser/data-browser/vite.config.ts +++ b/browser/data-browser/vite.config.ts @@ -1,4 +1,4 @@ -import { defineConfig, type PluginOption } from 'vite'; +import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { VitePWA } from 'vite-plugin-pwa'; import webfontDownload from 'vite-plugin-webfont-dl'; @@ -167,6 +167,7 @@ export default defineConfig({ // exclude: ['@tomic/lib', '@tomic/react'], }, build: { + target: 'baseline-widely-available', sourcemap: true, rollupOptions: { output: { diff --git a/browser/data-browser/wuchale.config.js b/browser/data-browser/wuchale.config.js index 64d9ea751..a4e4c7556 100644 --- a/browser/data-browser/wuchale.config.js +++ b/browser/data-browser/wuchale.config.js @@ -32,8 +32,13 @@ export default defineConfig({ otherLocales: ['es', 'fr', 'de'], adapters: { main: jsx({ - loaderPath: './src/locales/loader.ts', - heuristic: (msg, details) => { + runtime: { + useReactive: () => ({ init: false, use: false, }), + }, + loader: 'react', + heuristic: ({ msgStr, details }) => { + const [msg] = msgStr; + if (details.scope === 'script') { // Ignore certain functions if (details.call && IGNORED_FUNCTIONS.includes(details.call)) { diff --git a/browser/e2e/tests/documents.spec.ts b/browser/e2e/tests/documents.spec.ts index 5168f28d6..1b7041000 100644 --- a/browser/e2e/tests/documents.spec.ts +++ b/browser/e2e/tests/documents.spec.ts @@ -9,6 +9,8 @@ import { openNewSubjectWindow, timestamp, before, + setTitle, + waitForSearchIndex, } from './test-utils'; test.describe('documents', async () => { test.beforeEach(before); @@ -17,41 +19,69 @@ test.describe('documents', async () => { page, browser, }) => { + const folderTitle = 'SomeFolder'; + await signIn(page); await newDrive(page); await makeDrivePublic(page); // Create a document + await newResource('folder', page); + await setTitle(page, folderTitle); await newResource('document', page); const title = `Document ${timestamp()}`; await editTitle(title, page); const teststring = `My test: ${timestamp()}`; - await page.locator('textarea').fill(teststring); + await expect(page.getByText('loading...')).not.toBeVisible(); + + const editor = page.getByLabel('Rich Text Editor'); - await expect(page.locator(`text=${teststring}`)).toBeVisible(); + await editor.fill('/heading'); + await expect(page.getByText('Heading 1')).toBeVisible(); + await page.keyboard.press('Enter'); + await page.keyboard.type(teststring); + + await expect(page.getByRole('heading', { name: teststring })).toBeVisible(); // multi-user const currentSubject = await getCurrentSubject(page); - const page2 = await openNewSubjectWindow(browser, currentSubject!); + const page2 = await openNewSubjectWindow(browser, currentSubject!, true); + + await page2.getByRole('button', { name: 'Set Drive' }).click(); + await expect(page2.getByText('loading...')).not.toBeVisible(); await expect( - page2.locator(`text=${teststring}`), + page2.getByRole('heading', { name: teststring }), 'First paragraph title not visible in second tab. Not a websocket issue', ).toBeVisible(); expect(await page2.title()).toEqual(title); + await page2.getByLabel('Rich Text Editor').focus(); + await page2.keyboard.press('ArrowDown'); + await page2.keyboard.press('Enter'); // Add a new line on first page, check if it appears on the second - await page.keyboard.press('Enter'); const syncText = 'New paragraph'; - await page.keyboard.type(syncText); + await page2.keyboard.type(syncText); + await expect( - page2.locator(`text=${syncText}`), - 'New paragraph not found in second window. Websockets may not be working.', + page.locator(`text=${syncText}`), + 'New paragraph not found in first window. Sync might not be working.', ).toBeVisible(); // Delete a row, cmd + backspace - await page.keyboard.down('Alt'); - await page.keyboard.press('Backspace'); + await page2.getByText(syncText).selectText(); + + // Test if page1 can see the cursor of page2 + await expect( + page.getByLabel('Rich Text Editor').getByText('Test user edited'), + ).toBeVisible(); + + // Delete the word paragraph. + await page2.keyboard.press('ArrowRight'); + await page2.keyboard.down('Alt'); + await page2.keyboard.press('Backspace'); + await page2.keyboard.up('Alt'); + await expect( page.locator(`text=${syncText}`), 'Paragraph not deleted in first window.', @@ -60,5 +90,22 @@ test.describe('documents', async () => { page2.locator(`text=${syncText}`), 'Paragraph not deleted in second window', ).not.toBeVisible(); + + // Wait for AtomicServer to index the folder + await waitForSearchIndex(page2); + // Add a link to a folder to the document + await page2.keyboard.press('Space'); + await page2.keyboard.type('@'); + await page2.waitForTimeout(500); + await page2.keyboard.type(folderTitle, { delay: 50 }); + await expect( + page2.getByTestId('rte-command-list').getByText(folderTitle), + ).toBeVisible(); + await page2.keyboard.press('Enter'); + + // Check if the link is visible in the document + await expect( + page.getByLabel('Rich Text Editor').locator('a:has-text("SomeFolder")'), + ).toBeVisible(); }); }); diff --git a/browser/e2e/tests/e2e.spec.ts b/browser/e2e/tests/e2e.spec.ts index f16245a1f..e79b49fb0 100644 --- a/browser/e2e/tests/e2e.spec.ts +++ b/browser/e2e/tests/e2e.spec.ts @@ -31,8 +31,6 @@ import { waitForCommitOnCurrentResource, clickSidebarItem, inDialog, - PROPERTIES, - anyValue, } from './test-utils'; test.describe('data-browser', async () => { @@ -72,6 +70,11 @@ test.describe('data-browser', async () => { }); test('sign up and edit document atomicdata.dev', async ({ page }) => { + test.fixme( + true, + 'This test needs to be updated when atomicdata.dev has the new document editor.', + ); + await openAtomic(page); // Use invite await clickSidebarItem(DEMO_INVITE_NAME, page); @@ -469,11 +472,11 @@ test.describe('data-browser', async () => { 'https://atomicdata.dev/properties/localId': localID, 'https://atomicdata.dev/properties/name': name, }; - await page.fill( - '[placeholder="Paste your JSON-AD..."]', - JSON.stringify(importStr), - ); - await page.click('[data-test="import-post"]'); + await expect(page.getByRole('button', { name: 'Import' })).toBeDisabled(); + await page + .getByPlaceholder('Paste your JSON-AD...') + .pressSequentially(JSON.stringify(importStr)); + await page.getByRole('button', { name: 'Import' }).click(); await expect(page.locator('text=Imported!')).toBeVisible(); // get current url, append the localID @@ -542,17 +545,9 @@ test.describe('data-browser', async () => { // }, // }); - // commit for initializing the first element (paragraph) - const addParagraphCommit = waitForCommit(page, { - set: { - ['https://atomicdata.dev/properties/documents/elements']: anyValue, - }, - }); // Create new class from new resource menu await newResource('document', page); - await addParagraphCommit; - const firstTitleCommit = waitForCommit(page, { set: { ['https://atomicdata.dev/properties/name']: 'First Title', diff --git a/browser/e2e/tests/filePicker.spec.ts b/browser/e2e/tests/filePicker.spec.ts index 882fc99a1..73c7e61a7 100644 --- a/browser/e2e/tests/filePicker.spec.ts +++ b/browser/e2e/tests/filePicker.spec.ts @@ -3,7 +3,6 @@ import { test, expect, Page } from '@playwright/test'; import { DIALOG_CLOSE_BUTTON, FRONTEND_URL, - REBUILD_INDEX_TIME, before, fillSearchBox, inDialog, @@ -13,6 +12,7 @@ import { signIn, testFilePath, waitForCommit, + waitForSearchIndex, } from './test-utils'; const ONTOLOGY_NAME = 'filepicker-test'; @@ -102,7 +102,7 @@ test.describe('File Picker', () => { await createModel(page); // The new resource page relies on the search API to show ontology class buttons. If the prossess of creating the ontology took less than 5 seconds it will not appear on the new resource page. - await page.waitForTimeout(REBUILD_INDEX_TIME); + await waitForSearchIndex(page); { // Test selecting an existing file. diff --git a/browser/e2e/tests/search.spec.ts b/browser/e2e/tests/search.spec.ts index ddae1bfed..5ad9b131c 100644 --- a/browser/e2e/tests/search.spec.ts +++ b/browser/e2e/tests/search.spec.ts @@ -2,9 +2,7 @@ import { test, expect } from '@playwright/test'; import { signIn, newDrive, - waitForCommit, before, - REBUILD_INDEX_TIME, addressBar, clickSidebarItem, editTitle, @@ -13,7 +11,7 @@ import { contextMenuClick, timestamp, newResource, - anyValue, + waitForSearchIndex, } from './test-utils'; test.describe('search', async () => { test.beforeEach(before); @@ -45,14 +43,8 @@ test.describe('search', async () => { await setTitle(page, 'Salad folder'); // Create document called 'Avocado Salad' - const addParagraphCommit = waitForCommit(page, { - set: { - ['https://atomicdata.dev/properties/documents/elements']: anyValue, - }, - }); await page.locator('button:has-text("New Resource")').click(); await page.locator('button:has-text("document")').click(); - await addParagraphCommit; await editTitle('Avocado Salad', page); @@ -63,22 +55,15 @@ test.describe('search', async () => { await setTitle(page, 'Cake Folder'); // Create document called 'Avocado Cake' - - const addParagraphCommit2 = waitForCommit(page, { - set: { - ['https://atomicdata.dev/properties/documents/elements']: anyValue, - }, - }); await page.locator('button:has-text("New Resource")').click(); await page.locator('button:has-text("document")').click(); - await addParagraphCommit2; await editTitle('Avocado Cake', page); await clickSidebarItem('Cake Folder', page); // Set search scope to 'Cake folder' - await page.waitForTimeout(REBUILD_INDEX_TIME); + await waitForSearchIndex(page); await page.reload(); await contextMenuClick('scope', page); // Search for 'Avocado' @@ -130,7 +115,7 @@ test.describe('search', async () => { await expect(page.getByRole('link', { name: secondTagName })).toBeVisible(); // Wait for the index to be rebuilt - await page.waitForTimeout(REBUILD_INDEX_TIME); + await waitForSearchIndex(page); // Search for the folder by the first tag await addressBar(page).fill('tag:first'); diff --git a/browser/e2e/tests/template.spec.ts b/browser/e2e/tests/template.spec.ts index 54fb0a1dd..c4308f263 100644 --- a/browser/e2e/tests/template.spec.ts +++ b/browser/e2e/tests/template.spec.ts @@ -1,5 +1,4 @@ import { expect, test } from '@playwright/test'; -import AxeBuilder from '@axe-core/playwright'; import { exec } from 'child_process'; import { before, @@ -137,6 +136,10 @@ test.describe('Test create-template package', () => { test.beforeEach(before); test('apply next-js template', async ({ page }) => { + test.fixme( + true, + 'Template needs to be updated to Next.js 16 because we require React 19.2.0 or above.', + ); test.slow(); await signIn(page); const drive = await newDrive(page); @@ -171,21 +174,12 @@ test.describe('Test create-template package', () => { const response = await page.goto(url); expect(response?.status()).toBe(200); - // Check if home is following wcag AA standards - const homeScanResults = await new AxeBuilder({ page }).analyze(); - - expect(homeScanResults.violations).toEqual([]); - await expect(page.locator('body')).toContainText( 'This is a template site generated with @tomic/template.', ); await page.goto(`${url}/blog`); - // Check if blog is following wcag AA standards - const blogScanResults = await new AxeBuilder({ page }).analyze(); - expect(blogScanResults.violations).toEqual([]); - // Search for a blogpost const searchInput = page.getByRole('searchbox'); @@ -232,21 +226,12 @@ test.describe('Test create-template package', () => { const response = await page.goto(url); expect(response?.status()).toBe(200); - // Check if home is following wcag AA standards - const homeScanResults = await new AxeBuilder({ page }).analyze(); - - expect(homeScanResults.violations).toEqual([]); - await expect(page.locator('body')).toContainText( 'This is a template site generated with @tomic/template.', ); await page.goto(`${url}/blog`); - // Check if blog is following wcag AA standards - const blogScanResults = await new AxeBuilder({ page }).analyze(); - expect(blogScanResults.violations).toEqual([]); - // Search for a blogpost const searchInput = page.getByRole('searchbox'); await searchInput.fill('balloon'); diff --git a/browser/e2e/tests/test-utils.ts b/browser/e2e/tests/test-utils.ts index 0371fb6f9..dac4657ba 100644 --- a/browser/e2e/tests/test-utils.ts +++ b/browser/e2e/tests/test-utils.ts @@ -196,6 +196,10 @@ export async function waitForCommitOnCurrentResource( }); } +export async function waitForSearchIndex(page: Page) { + return page.waitForTimeout(REBUILD_INDEX_TIME); +} + export async function openAgentPage(page: Page) { page.goto(`${FRONTEND_URL}/app/agent`); } @@ -278,11 +282,19 @@ export async function newResource(klass: string, page: Page) { } /** Opens a new browser page (for) */ -export async function openNewSubjectWindow(browser: Browser, url: string) { +export async function openNewSubjectWindow( + browser: Browser, + url: string, + doSignIn: boolean = false, +) { const context2 = await browser.newContext(); const page = await context2.newPage(); await page.goto(FRONTEND_URL); + if (doSignIn) { + await signIn(page); + } + // Only when we run on `localhost` we don't need to change drive during tests if (SERVER_URL !== FRONTEND_URL) { try { diff --git a/browser/eslint.config.js b/browser/eslint.config.js new file mode 100644 index 000000000..8dc11c8ed --- /dev/null +++ b/browser/eslint.config.js @@ -0,0 +1,114 @@ +import { + defineConfig, + globalIgnores, +} from 'eslint/config'; +import tseslint from '@typescript-eslint/eslint-plugin'; +import tsparser from '@typescript-eslint/parser'; +import prettierConfig from 'eslint-config-prettier'; +import reactHooks from 'eslint-plugin-react-hooks'; +import jsxA11Y from 'eslint-plugin-jsx-a11y'; +import react from 'eslint-plugin-react'; +import js from "@eslint/js"; +import globals from 'globals'; + +export default defineConfig([ + globalIgnores([ + // These files are generated so we can't fix the linting errors in them. + 'data-browser/src/locales/**', + ]), + { + ...jsxA11Y.flatConfigs.recommended, + rules: { + ...jsxA11Y.flatConfigs.recommended.rules, + 'jsx-a11y/no-autofocus': 'off', + }, + }, + { + files: ['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'], + + languageOptions: { + parser: tsparser, + sourceType: 'module', + globals: { + ...globals.browser, + ...globals.node, + React: true, + }, + }, + + plugins: { + '@typescript-eslint': tseslint, + js, + }, + + rules: { + ...js.configs.recommended.rules, + ...tseslint.configs.recommended.rules, + 'no-undef': 'off', + 'no-unused-vars': 'off', + 'no-redeclare': 'off', + '@typescript-eslint/no-unused-vars': ['error', { 'varsIgnorePattern': '^_', 'argsIgnorePattern': '^_', "caughtErrorsIgnorePattern": "^_|^e$" }], + '@typescript-eslint/no-explicit-any': 'error', + 'no-shadow': 'off', + "@typescript-eslint/no-shadow": ["error"], + "@typescript-eslint/member-ordering": "error", + 'no-console': ['error', { allow: ['error', 'warn'] }], + 'class-methods-use-this': 'off', + 'semi': ['error', 'always'], + 'quotes': ['error', 'single'], + 'eqeqeq': ['error', 'always'], + 'padding-line-between-statements': [ + 'error', + { + 'blankLine': 'always', + 'next': 'return', + 'prev': '*' + }, + { + 'blankLine': 'always', + 'next': 'export', + 'prev': '*' + }, + { + 'blankLine': 'always', + 'next': 'multiline-block-like', + 'prev': '*' + }, + { + 'blankLine': 'always', + 'next': '*', + 'prev': 'multiline-block-like' + }, + { + 'blankLine': 'any', + 'next': 'export', + 'prev': 'export' + } + ], + ...prettierConfig.rules, + }, + }, + { + ...react.configs.flat.recommended, + rules: { + ...react.configs.flat.recommended.rules, + 'react/prop-types': 'off', + "react/no-unknown-property": ["error", { "ignore": ["about"] }], + }, + }, + react.configs.flat['jsx-runtime'], + { + ...reactHooks.configs.flat['recommended-latest'], + files: ['**/*.ts', '**/*.tsx'], + rules: { + ...reactHooks.configs.flat['recommended-latest'].rules, + 'react-hooks/preserve-manual-memoization': 'warn', + 'react-hooks/exhaustive-deps': 'warn', + 'react-hooks/set-state-in-effect': 'warn', + 'react-hooks/static-components': 'off', + // This rule is way to aggressive and seems to be designed for people that don't understand refs. + // But it looks like sometimes it matters for react compiler so we'll set it to warn instead. + 'react-hooks/refs': 'warn', + } + } +]); diff --git a/browser/lib/package.json b/browser/lib/package.json index cf9683ded..341e1d00b 100644 --- a/browser/lib/package.json +++ b/browser/lib/package.json @@ -13,9 +13,9 @@ "dependencies": { "@noble/ed25519": "1.6.0", "@noble/hashes": "^0.5.9", - "base64-arraybuffer": "^1.0.2", "fast-json-stable-stringify": "^2.1.0", - "ulidx": "^2.4.1" + "ulidx": "^2.4.1", + "yjs": "^13.6.27" }, "description": "The Atomic Data library for typescript/javascript", "devDependencies": { @@ -25,9 +25,17 @@ "@types/fast-json-stable-stringify": "^2.1.2", "tslib": "^2.8.0", "tsup": "^8.3.5", - "typescript": "^5.6.3", + "typescript": "^5.9.3", "vitest": "^2.1.3" }, + "peerDependencies": { + "yjs": "^13.6.27" + }, + "peerDependenciesMeta": { + "yjs": { + "optional": true + } + }, "files": [ "dist", "!dist/**/*.d.ts.map" @@ -55,9 +63,10 @@ "build": "tsup", "generate-ontologies": "ad-generate ontologies", "lint-package": "pnpm publint && pnpm attw", - "lint": "eslint ./src --ext .js,.jsx,.ts,.tsx", + "lint": "eslint ./src --ext .js,.jsx,.ts,.tsx && pnpm prettier-check", "lint-fix": "eslint ./src --ext .js,.jsx,.ts,.tsx --fix", "prepublishOnly": "pnpm run build && pnpm run lint && pnpm run lint-package", + "prettier-check": "prettier --check ./src", "start": "pnpm watch", "coverage": "vitest run --coverage", "test": "vitest run", diff --git a/browser/lib/src/base64.ts b/browser/lib/src/base64.ts new file mode 100644 index 000000000..309b46a16 --- /dev/null +++ b/browser/lib/src/base64.ts @@ -0,0 +1,42 @@ +export function decodeB64(base64: string): Uint8Array { + // 1. Node.js (via Buffer) + if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') { + // Buffer.from returns a Buffer, which extends Uint8Array. + return Buffer.from(base64, 'base64'); + } + + // 2. Browser (via atob) + if (typeof atob === 'function') { + const binaryString = atob(base64); + const bytes = new Uint8Array(binaryString.length); + + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + return bytes; + } + + throw new Error('Base64 decoding not supported in this environment.'); +} + +export function encodeB64(bytes: Uint8Array): string { + // 1. Node.js (via Buffer) + if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') { + return Buffer.from(bytes).toString('base64'); + } + + // 2. Browser (via btoa) + if (typeof btoa === 'function') { + // Convert Uint8Array to binary string + let binaryString = ''; + + for (let i = 0; i < bytes.length; i++) { + binaryString += String.fromCharCode(bytes[i]); + } + + return btoa(binaryString); + } + + throw new Error('Base64 encoding not supported in this environment.'); +} diff --git a/browser/lib/src/client.ts b/browser/lib/src/client.ts index 9e1177348..621cde271 100644 --- a/browser/lib/src/client.ts +++ b/browser/lib/src/client.ts @@ -197,6 +197,7 @@ export class Client { } resource.loading = false; + createdResources.forEach(r => (r.loading = false)); return { resource, createdResources }; } @@ -208,7 +209,7 @@ export class Client { endpoint: string, ): Promise<Commit> { const serialized = serializeDeterministically({ ...commit }); - const requestHeaders: HeadersInit = new Headers(); + const requestHeaders = new Headers(); requestHeaders.set('Content-Type', 'application/ad+json'); let response: Response; diff --git a/browser/lib/src/commit.ts b/browser/lib/src/commit.ts index 9ffeadfca..de822dec5 100644 --- a/browser/lib/src/commit.ts +++ b/browser/lib/src/commit.ts @@ -1,13 +1,19 @@ import { sign, getPublicKey, utils } from '@noble/ed25519'; import stringify from 'fast-json-stable-stringify'; -import { decode as decodeB64, encode as encodeB64 } from 'base64-arraybuffer'; // https://github.com/paulmillr/noble-ed25519/issues/38 import { sha512 } from '@noble/hashes/sha512'; +import { YLoader } from './yjs.js'; import { Client } from './client.js'; import { Resource } from './resource.js'; import type { Store } from './store.js'; -import type { JSONValue, JSONArray } from './value.js'; +import { + type JSONValue, + type JSONArray, + isSerializedYUpdate, + isJSONObject, +} from './value.js'; +import { decodeB64, encodeB64 } from './base64.js'; import { commits } from './ontologies/commits.js'; import { core } from './ontologies/core.js'; @@ -24,6 +30,7 @@ export interface CommitBuilderI { * be appended. https://atomicdata.dev/properties/push */ push?: Record<string, JSONArray>; + yUpdate?: Record<string, Uint8Array>; /** The properties that need to be removed. https://atomicdata.dev/properties/remove */ remove?: string[]; /** If true, the resource must be deleted. https://atomicdata.dev/properties/destroy */ @@ -38,6 +45,7 @@ export interface CommitBuilderI { interface CommitBuilderBase { set?: Map<string, JSONValue>; push?: Map<string, Set<JSONValue>>; + yUpdate?: Map<string, Uint8Array>; remove?: Set<string>; destroy?: boolean; previousCommit?: string; @@ -57,6 +65,7 @@ export class CommitBuilder { private _subject: string; private _set: Map<string, JSONValue>; private _push: Map<string, Set<JSONValue>>; + private _yUpdate: Map<string, Uint8Array>; private _remove: Set<string>; private _destroy?: boolean; private _previousCommit?: string; @@ -66,6 +75,7 @@ export class CommitBuilder { this._subject = Client.removeQueryParamsFromURL(subject); this._set = base.set ?? new Map(); this._push = base.push ?? new Map(); + this._yUpdate = base.yUpdate ?? new Map(); this._remove = base.remove ?? new Set(); this._destroy = base.destroy; this._previousCommit = base.previousCommit; @@ -83,6 +93,10 @@ export class CommitBuilder { return this._push; } + public get yUpdate() { + return this._yUpdate; + } + public get remove() { return this._remove; } @@ -117,12 +131,28 @@ export class CommitBuilder { public addRemoveAction(property: string): CommitBuilder { this._set.delete(property); this._push.delete(property); - + this._yUpdate.delete(property); this._remove.add(property); return this; } + public addYUpdateAction(property: string, update: Uint8Array): CommitBuilder { + YLoader.loadCheck(); + const Y = YLoader.Y; + + this.removeRemoveAction(property); + const existingUpdate = this._yUpdate.get(property); + + if (existingUpdate) { + this._yUpdate.set(property, Y.mergeUpdatesV2([existingUpdate, update])); + } else { + this._yUpdate.set(property, update); + } + + return this; + } + public removeRemoveAction(property: string): CommitBuilder { this._remove.delete(property); @@ -171,7 +201,8 @@ export class CommitBuilder { this.set.size > 0 || this.push.size > 0 || this.destroy || - this.remove.size > 0 + this.remove.size > 0 || + this.yUpdate.size > 0 ); } @@ -185,6 +216,7 @@ export class CommitBuilder { const base = { set: this.set, push: this.push, + yUpdate: this.yUpdate, remove: this.remove, destroy: this.destroy, previousCommit: this.previousCommit, @@ -203,6 +235,7 @@ export class CommitBuilder { remove: Array.from(this.remove), destroy: this.destroy, previousCommit: this.previousCommit, + yUpdate: Object.fromEntries(this.yUpdate.entries()), }; } @@ -272,24 +305,48 @@ const serializeMap = { createdAt: commits.properties.createdAt, signer: commits.properties.signer, signature: commits.properties.signature, + yUpdate: commits.properties.yUpdate, id: 'id', }; /** Replaces the keys of a Commit object with their respective json-ad key */ -const commitToJsonADObject = (commit: UnsignedCommit | Commit): JSONADObject => - Object.entries(commit).reduce<JSONADObject>( - (acc, [key, value]) => { - const serializedKey = - serializeMap[key as keyof Commit | keyof UnsignedCommit]; - - acc[serializedKey] = value as JSONValue; - - return acc; - }, - { - [core.properties.isA]: [commits.classes.commit], - }, - ); +function commitToJsonADObject(commit: UnsignedCommit | Commit): JSONADObject { + const jsonAdObj: JSONADObject = { + [core.properties.isA]: [commits.classes.commit], + }; + + for (const kv of Object.entries(commit)) { + const [key, value] = kv as [keyof Commit, Commit[keyof Commit]]; + const serializedKey = serializeMap[key]; + jsonAdObj[serializedKey] = serializeCommitValue(key, value); + } + + return jsonAdObj; +} + +function serializeCommitValue<K extends keyof Commit>( + key: K, + value: Commit[K], +): JSONValue { + // The value for yUpdate needs to be encoded to base64 before it is valid JSON-AD + if (key === 'yUpdate') { + const castValue = value as Commit['yUpdate']; + + if (castValue !== undefined) { + return Object.fromEntries( + Object.entries(castValue).map(([k, v]) => [ + k, + { type: 'ydoc', data: encodeB64(v) }, + ]), + ); + } + + return undefined; + } + + // The rest of the values can just be returned as is + return value as JSONValue; +} /** * Takes a commit and serializes it deterministically (canonicilaization). Is @@ -316,6 +373,10 @@ export function serializeDeterministically( delete commit.destroy; } + if (commit.yUpdate && Object.keys(commit.yUpdate).length === 0) { + delete commit.yUpdate; + } + const jsonadCommit = commitToJsonADObject(commit); return stringify(jsonadCommit); @@ -381,6 +442,7 @@ export function parseCommitResource(resource: Resource): Commit { subject: resource.get(commits.properties.subject), set: resource.get(commits.properties.set), push: resource.get(commits.properties.push), + yUpdate: parseYUpdateValue(resource.get(commits.properties.yUpdate)), signer: resource.get(commits.properties.signer), createdAt: resource.get(commits.properties.createdAt), remove: resource.get(commits.properties.remove), @@ -403,6 +465,7 @@ export function parseCommitJSON(str: string): Commit { const subject = jsonAdObj[commits.properties.subject]; const set = jsonAdObj[commits.properties.set]; const push = jsonAdObj[commits.properties.push]; + const yUpdate = parseYUpdateValue(jsonAdObj[commits.properties.yUpdate]); const signer = jsonAdObj[commits.properties.signer]; const createdAt = jsonAdObj[commits.properties.createdAt]; const remove: string[] | undefined = jsonAdObj[commits.properties.remove]; @@ -420,6 +483,7 @@ export function parseCommitJSON(str: string): Commit { subject, set, push, + yUpdate, signer, createdAt, remove, @@ -438,7 +502,7 @@ export function applyCommitToResource( resource: Resource, commit: Commit, ): Resource { - const { set, remove, push, destroy } = commit; + const { set, remove, push, destroy, yUpdate } = commit; if (set) { execSetCommit(set, resource); @@ -452,6 +516,10 @@ export function applyCommitToResource( execPushCommit(push, resource); } + if (yUpdate) { + execYUpdateCommit(yUpdate, resource); + } + if (destroy) { for (const [key] of resource.getPropVals()) { resource.setUnsafe(key, undefined); @@ -496,18 +564,32 @@ export function parseAndApplyCommit(jsonAdObjStr: string, store: Store) { } } -function execSetCommit( - set: Record<string, JSONValue>, - resource: Resource, - store?: Store, -) { - const parsedResources: Resource[] = []; +function parseYUpdateValue( + value: JSONValue, +): Record<string, Uint8Array> | undefined { + if (value === undefined) { + return undefined; + } + if (!isJSONObject(value)) { + throw new Error(`YUpdate value is not an object: ${value}`); + } + + return Object.fromEntries( + Object.entries(value).map(([k, v]) => { + if (isSerializedYUpdate(v)) { + return [k, decodeB64(v.data)]; + } else { + throw new Error(`YUpdate contains invalid update: ${k}`); + } + }), + ); +} + +function execSetCommit(set: Record<string, JSONValue>, resource: Resource) { for (const [key, value] of Object.entries(set)) { resource.setUnsafe(key, value); } - - store && store.addResources(parsedResources); } function execRemoveCommit(remove: string[], resource: Resource) { @@ -526,3 +608,46 @@ function execPushCommit(push: Record<string, JSONArray>, resource: Resource) { resource.setUnsafe(key, new_arr); } } + +function execYUpdateCommit( + yUpdate: Record<string, Uint8Array>, + resource: Resource, +) { + if (!YLoader.isLoaded()) { + console.warn( + 'Commit contains yUpdate but Yjs is not loaded. Skipping applying yjs updates', + ); + + return; + } + + const Y = YLoader.Y; + + for (const [key, value] of Object.entries(yUpdate)) { + const doc = resource.get(key); + + if (!doc) { + try { + const newDoc = new Y.Doc(); + Y.applyUpdateV2(newDoc, value); + resource.setUnsafe(key, newDoc); + } catch (e) { + console.error(e); + throw new Error(`Error applying yUpdate to new document: ${key}: ${e}`); + } + } else { + if (!(doc instanceof Y.Doc)) { + throw new Error(`Property ${key} is not a YDoc`); + } + + try { + Y.applyUpdateV2(doc, value); + } catch (e) { + console.error(e); + throw new Error( + `Error applying yUpdate to existing document: ${key}: ${e}`, + ); + } + } + } +} diff --git a/browser/lib/src/datatypes.ts b/browser/lib/src/datatypes.ts index 85d91f6ee..208496a7f 100644 --- a/browser/lib/src/datatypes.ts +++ b/browser/lib/src/datatypes.ts @@ -1,7 +1,7 @@ /** Each possible Atomic Datatype. See https://atomicdata.dev/collections/datatype */ -import { Client } from './index.js'; -import type { JSONValue } from './value.js'; +import { Client, YLoader } from './index.js'; +import type { AtomicValue } from './value.js'; // TODO: use strings from `./urls`, requires TS fix: https://github.com/microsoft/TypeScript/issues/40793 export enum Datatype { @@ -27,6 +27,7 @@ export enum Datatype { JSON = 'https://atomicdata.dev/datatypes/json', /** URI */ URI = 'https://atomicdata.dev/datatypes/uri', + YDOC = 'https://atomicdata.dev/datatypes/ydoc', UNKNOWN = 'unknown-datatype', } @@ -51,7 +52,7 @@ export interface ArrayError extends Error { /** Validates a JSON Value using a Datatype. Throws an error if things are wrong. */ export const validateDatatype = ( - value: JSONValue, + value: AtomicValue, datatype: Datatype, ): void => { let err: null | string = null; @@ -104,14 +105,14 @@ export const validateDatatype = ( } case Datatype.RESOURCEARRAY: { - if (!isArray(value)) { + if (!Array.isArray(value)) { err = 'Not an array'; break; } value.map((item, index) => { try { - Client.tryValidSubject(item); + Client.tryValidSubject(item as string); } catch (e) { const arrError: ArrayError = new Error(`Invalid URL`); arrError.index = index; @@ -134,6 +135,24 @@ export const validateDatatype = ( break; } + case Datatype.FLOAT: { + if (!isNumber(value)) { + err = 'Not a number'; + break; + } + + break; + } + + case Datatype.BOOLEAN: { + if (typeof value !== 'boolean') { + err = 'Not a boolean'; + break; + } + + break; + } + case Datatype.DATE: { if (!isString(value)) { err = 'Not a string'; @@ -147,6 +166,15 @@ export const validateDatatype = ( break; } + case Datatype.TIMESTAMP: { + if (!isNumber(value)) { + err = 'Not a number'; + break; + } + + break; + } + case Datatype.JSON: { try { JSON.stringify(value); @@ -166,6 +194,28 @@ export const validateDatatype = ( break; } + + case Datatype.YDOC: { + if (!YLoader.isLoaded()) { + console.warn( + 'Cannot validate YDoc because Yjs is not loaded. passing as valid', + ); + break; + } + + const Y = YLoader.Y; + + if (!(value instanceof Y.Doc)) { + err = 'Not a Yjs Doc'; + break; + } + + break; + } + + default: { + throw new Error(`Unsupported datatype: ${datatype}`); + } } if (err !== null) { @@ -173,15 +223,11 @@ export const validateDatatype = ( } }; -export function isArray(val: JSONValue): val is [] { - return Object.prototype.toString.call(val) === '[object Array]'; -} - -export function isString(val: JSONValue): val is string { +export function isString(val: AtomicValue): val is string { return typeof val === 'string'; } -export function isNumber(val: JSONValue): val is number { +export function isNumber(val: AtomicValue): val is number { return typeof val === 'number'; } @@ -198,5 +244,6 @@ export const reverseDatatypeMapping = { [Datatype.TIMESTAMP]: 'Timestamp', [Datatype.ATOMIC_URL]: 'Resource', [Datatype.RESOURCEARRAY]: 'ResourceArray', + [Datatype.YDOC]: 'YDoc', [Datatype.UNKNOWN]: 'Unknown', }; diff --git a/browser/lib/src/index.ts b/browser/lib/src/index.ts index 30eaf90e9..e22b789ae 100644 --- a/browser/lib/src/index.ts +++ b/browser/lib/src/index.ts @@ -51,3 +51,4 @@ export * from './truncate.js'; export * from './collection.js'; export * from './collectionBuilder.js'; export * from './ontology.js'; +export * from './yjs.js'; diff --git a/browser/lib/src/ontologies/commits.ts b/browser/lib/src/ontologies/commits.ts index 0e9a3313d..af12060bc 100644 --- a/browser/lib/src/ontologies/commits.ts +++ b/browser/lib/src/ontologies/commits.ts @@ -20,6 +20,7 @@ export const commits = { remove: 'https://atomicdata.dev/properties/remove', destroy: 'https://atomicdata.dev/properties/destroy', signature: 'https://atomicdata.dev/properties/signature', + yUpdate: 'https://atomicdata.dev/properties/yUpdate', }, __classDefs: { ['https://atomicdata.dev/classes/Commit']: [ @@ -30,6 +31,8 @@ export const commits = { 'https://atomicdata.dev/properties/destroy', 'https://atomicdata.dev/properties/remove', 'https://atomicdata.dev/properties/set', + 'https://atomicdata.dev/properties/push', + 'https://atomicdata.dev/properties/yUpdate', ], }, } as const satisfies OntologyBaseObject; @@ -51,7 +54,9 @@ declare module '../index.js' { recommends: | typeof commits.properties.destroy | typeof commits.properties.remove - | typeof commits.properties.set; + | typeof commits.properties.set + | typeof commits.properties.push + | typeof commits.properties.yUpdate; }; } @@ -66,6 +71,7 @@ declare module '../index.js' { [commits.properties.remove]: string[]; [commits.properties.destroy]: boolean; [commits.properties.signature]: string; + [commits.properties.yUpdate]: string; } interface PropSubjectToNameMapping { @@ -79,5 +85,6 @@ declare module '../index.js' { [commits.properties.remove]: 'remove'; [commits.properties.destroy]: 'destroy'; [commits.properties.signature]: 'signature'; + [commits.properties.yUpdate]: 'yUpdate'; } } diff --git a/browser/lib/src/ontologies/dataBrowser.ts b/browser/lib/src/ontologies/dataBrowser.ts index 557ec5645..e14d6186a 100644 --- a/browser/lib/src/ontologies/dataBrowser.ts +++ b/browser/lib/src/ontologies/dataBrowser.ts @@ -28,6 +28,7 @@ export const dataBrowser = { table: 'https://atomicdata.dev/classes/Table', tag: 'https://atomicdata.dev/classes/Tag', template: 'https://atomicdata.dev/ontology/data-browser/class/template', + documentV2: 'https://atomicdata.dev/classes/DocumentV2', }, properties: { color: 'https://atomicdata.dev/properties/color', @@ -58,6 +59,7 @@ export const dataBrowser = { tags: 'https://atomicdata.dev/properties/tags', tagList: 'https://atomicdata.dev/ontology/data-browser/property/tag-list', url: 'https://atomicdata.dev/property/url', + documentContent: 'https://atomicdata.dev/properties/documentContent', }, __classDefs: { ['https://atomicdata.dev/classes/Article']: [ @@ -141,6 +143,10 @@ export const dataBrowser = { 'https://atomicdata.dev/ontology/data-browser/property/image', 'https://atomicdata.dev/ontology/data-browser/property/resources', ], + ['https://atomicdata.dev/classes/DocumentV2']: [ + 'https://atomicdata.dev/properties/name', + 'https://atomicdata.dev/properties/documentContent', + ], }, } as const satisfies OntologyBaseObject; @@ -167,6 +173,7 @@ export namespace DataBrowser { export type Table = typeof dataBrowser.classes.table; export type Tag = typeof dataBrowser.classes.tag; export type Template = typeof dataBrowser.classes.template; + export type DocumentV2 = typeof dataBrowser.classes.documentV2; } declare module '../index.js' { @@ -287,6 +294,10 @@ declare module '../index.js' { | typeof dataBrowser.properties.resources; recommends: never; }; + [dataBrowser.classes.documentV2]: { + requires: BaseProps | 'https://atomicdata.dev/properties/name'; + recommends: typeof dataBrowser.properties.documentContent; + }; } interface PropTypeMapping { @@ -316,6 +327,7 @@ declare module '../index.js' { [dataBrowser.properties.tags]: string[]; [dataBrowser.properties.tagList]: string[]; [dataBrowser.properties.url]: string; + [dataBrowser.properties.documentContent]: never; } interface PropSubjectToNameMapping { @@ -345,5 +357,6 @@ declare module '../index.js' { [dataBrowser.properties.tags]: 'tags'; [dataBrowser.properties.tagList]: 'tagList'; [dataBrowser.properties.url]: 'url'; + [dataBrowser.properties.documentContent]: 'documentContent'; } } diff --git a/browser/lib/src/ontology.ts b/browser/lib/src/ontology.ts index f3271337b..7d0882573 100644 --- a/browser/lib/src/ontology.ts +++ b/browser/lib/src/ontology.ts @@ -1,4 +1,4 @@ -import { JSONValue } from './value.js'; +import { type AtomicValue } from './value.js'; export type OntologyBaseObject = { readonly classes: Record<string, string>; @@ -7,7 +7,7 @@ export type OntologyBaseObject = { }; // Extended via module augmentation -// eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface Classes { 'unknown-subject': { requires: BaseProps; @@ -22,11 +22,11 @@ export type BaseProps = | 'https://atomicdata.dev/properties/parent'; // Extended via module augmentation -// eslint-disable-next-line @typescript-eslint/no-empty-interface +// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface PropTypeMapping {} // Extended via module augmentation -// eslint-disable-next-line @typescript-eslint/no-empty-interface +// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface PropSubjectToNameMapping {} export type Requires<C extends keyof Classes> = Classes[C]['requires']; @@ -49,7 +49,7 @@ export type InferTypeOfValueInTriple< ? Prop extends Requires<Class> ? PropTypeMapping[Prop] : PropTypeMapping[Prop] | undefined - : JSONValue, + : AtomicValue, > = Returns; type QuickAccessKnownPropType<Class extends OptionalClass> = { @@ -61,9 +61,9 @@ type QuickAccessKnownPropType<Class extends OptionalClass> = { declare global { // We need to use var for declaring types on globalThis - // eslint-disable-next-line no-var + var ATOMIC_SUBJECT_TO_NAME_MAPPING: Map<string, string>; - // eslint-disable-next-line no-var + var ATOMIC_CLASS_DEFS: Map<string, Record<string, string>>; } diff --git a/browser/lib/src/parse.ts b/browser/lib/src/parse.ts index e70601063..67888feee 100644 --- a/browser/lib/src/parse.ts +++ b/browser/lib/src/parse.ts @@ -1,8 +1,17 @@ import { AtomicError } from './error.js'; -import { Client, isArray } from './index.js'; +import { Client } from './index.js'; import { server } from './ontologies/server.js'; import { Resource, unknownSubject } from './resource.js'; -import type { JSONObject, JSONValue } from './value.js'; +import { + type JSONObject, + type JSONValue, + isJSONObject, + isSerializedYUpdate, + type SerializedYUpdate, +} from './value.js'; +import { decodeB64 } from './base64.js'; +import { YLoader } from './yjs.js'; +import type * as Y from 'yjs'; /** * Parses a JSON-AD object or array into resources. Create a new instance each time you need to parse a json-ad string. @@ -61,6 +70,7 @@ export class JSONADParser { resourceSubject: string = unknownSubject, ): Resource { const resource = new Resource(resourceSubject); + resource.loading = false; try { for (const [key, value] of Object.entries(object)) { @@ -83,11 +93,15 @@ export class JSONADParser { continue; } + if (isSerializedYUpdate(value)) { + const doc = this.parseYDoc(value); + resource.setUnsafe(key, doc); + continue; + } + resource.setUnsafe(key, value); } - resource.loading = false; - if (resource.hasClasses(server.classes.error)) { resource.error = AtomicError.fromResource(resource); } @@ -101,7 +115,17 @@ export class JSONADParser { return resource; } -} -const isJSONObject = (value: JSONValue): value is JSONObject => - typeof value === 'object' && value !== null && !isArray(value); + private parseYDoc(value: SerializedYUpdate): Y.Doc | SerializedYUpdate { + if (!YLoader.isLoaded()) { + return value; + } + + const Y = YLoader.Y; + + const doc = new Y.Doc(); + Y.applyUpdateV2(doc, decodeB64(value.data)); + + return doc; + } +} diff --git a/browser/lib/src/resource.ts b/browser/lib/src/resource.ts index fb876987a..49d7b65b1 100644 --- a/browser/lib/src/resource.ts +++ b/browser/lib/src/resource.ts @@ -1,3 +1,5 @@ +import type * as Y from 'yjs'; +import { YLoader } from './yjs.js'; import { EventManager } from './EventManager.js'; import type { Agent } from './agent.js'; import { Client } from './client.js'; @@ -30,10 +32,12 @@ import { type JSONValue, type JSONArray, type JSONObject, + type AtomicValue, + isYDoc, } from './value.js'; /** Contains the PropertyURL / Value combinations */ -export type PropVals = Map<string, JSONValue>; +export type PropVals = Map<string, AtomicValue>; /** * If a resource has no subject, it will have this subject. This means that the @@ -43,10 +47,12 @@ export const unknownSubject = 'unknown-subject'; export enum ResourceEvents { LocalChange = 'local-change', + LoadingChange = 'loading-change', } type ResourceEventHandlers = { [ResourceEvents.LocalChange]: (prop: string, value: JSONValue) => void; + [ResourceEvents.LoadingChange]: (loading: boolean) => void; }; /** @@ -65,17 +71,14 @@ export class Resource<C extends OptionalClass = any> { public commitError?: Error; /** Is true for locally created, unsaved resources */ public new: boolean; - /** - * Is true when the Resource is currently being fetched, awaiting a response - * from the Server - */ - public loading = false; /** * Every commit that has been applied should be stored here, which prevents * applying the same commit twice */ public appliedCommitSignatures: Set<string> = new Set(); + private _loading = false; + private commitBuilder: CommitBuilder; private _subject: string; private propvals: PropVals = new Map(); @@ -89,6 +92,8 @@ export class Resource<C extends OptionalClass = any> { ResourceEventHandlers >(); + private errorRetries = 0; + public constructor(subject: string, newResource?: boolean) { if (typeof subject !== 'string') { // Check if the subject is an object with an @id property @@ -113,11 +118,25 @@ export class Resource<C extends OptionalClass = any> { return this; } + /** + * Is true when the Resource is currently being fetched, awaiting a response + * from the Server. + * Use `resource.on(ResourceEvents.LoadingChange, (loading) => {})` to listen for changes. + */ + public get loading(): boolean { + return this._loading; + } + /** The subject URL of the resource */ public get subject(): string { return this._subject; } + /** Stable reference to the resource, even when the resource is proxied, for example when using @tomic/react or @tomic/svelte. */ + public get stable(): Resource<C> { + return this.__internalObject; + } + /** A human readable title for the resource, returns first of either: name, shortname, filename or subject */ public get title(): string { return (this.get(core.properties.name) ?? @@ -202,6 +221,12 @@ export class Resource<C extends OptionalClass = any> { return this._store; } + public set loading(loading: boolean) { + if (this._loading === loading) return; + + this._loading = loading; + this.eventManager.emit(ResourceEvents.LoadingChange, loading); + } public on<T extends ResourceEvents>( event: T, callback: ResourceEventHandlers[T], @@ -302,7 +327,38 @@ export class Resource<C extends OptionalClass = any> { */ public clone(): Resource<C> { const res = new Resource(this.subject); - res.propvals = structuredClone(this.propvals); + + // Filter out YDoc instances before cloning + if (YLoader.isLoaded()) { + const Y = YLoader.Y; + + const nonYdocPropvals = new Map<string, AtomicValue>(); + const ydocPropvals = new Map<string, Y.Doc>(); + + for (const [key, value] of this.propvals.entries()) { + if (!isYDoc(value)) { + // Property is not a YDoc so we can just clone it. + nonYdocPropvals.set(key, value); + continue; + } + + // Property is a YDoc so we need to make a new Y.Doc instance and apply the state of the existing YDoc. + const newDoc = new Y.Doc(); + Y.applyUpdateV2(newDoc, Y.encodeStateAsUpdateV2(value)); + ydocPropvals.set(key, newDoc); + } + + res.propvals = structuredClone(nonYdocPropvals); + + // Set the YDoc instances using setUnsafe to setup any event listeners. + for (const [key, value] of ydocPropvals.entries()) { + res.setUnsafe(key, value); + } + } else { + // Yjs is not loaded, so the propvals can't contain YDoc instances. + res.propvals = structuredClone(this.propvals); + } + res.loading = this.loading; res.new = this.new; res.error = structuredClone(this.error); @@ -313,6 +369,63 @@ export class Resource<C extends OptionalClass = any> { return res as Resource<C>; } + /** Merges a resource into this resource. If this resource has uncommited changes those changes will be applied on top of the new propvals. + * Any unsaved changes on the incoming resource will not be merged. + */ + public merge(resourceB: Resource): void { + if (this.subject !== resourceB.subject) { + throw new Error('Cannot merge resources with different subjects'); + } + + const remoteProps = resourceB.getPropVals(); + + // Remove any propvals that are not present in the remote resource. + for (const [key] of this.propvals.entries()) { + if (!remoteProps.has(key)) { + this.propvals.delete(key); + } + } + + // Merge the remote propvals into this resource. + for (const [key, value] of remoteProps.entries()) { + // We handle YDoc instances separately because they need to be stable references. + if (YLoader.isLoaded() && isYDoc(value)) { + const Y = YLoader.Y; + const localDoc = this.propvals.get(key) as Y.Doc | undefined; + + if (!localDoc) { + this.setUnsafe(key, value); + } else { + const remoteState = Y.encodeStateAsUpdateV2(value); + Y.applyUpdateV2(localDoc, remoteState); + } + + continue; + } + + this.propvals.set(key, value); + } + + this.new = resourceB.new; + this.error = resourceB.error; + this.commitError = resourceB.commitError; + + if (this.commitBuilder.hasUnsavedChanges()) { + // We have changes so we want to apply those on top of the propvals we just got. + const changes: Commit = { + ...this.commitBuilder.toPlainObject(), + signature: '', + signer: '', + createdAt: 0, + }; + + applyCommitToResource(this, changes); + } + + // We set this last because it will trigger a loading change event. + this.loading = resourceB.loading; + } + /** Checks if the resource is both loaded and free from errors */ public isReady(): boolean { return !this.loading && this.error === undefined; @@ -434,6 +547,27 @@ export class Resource<C extends OptionalClass = any> { .buildAndFetch(); } + /** Gets a YDoc from the resource, or creates a new one if it doesn't exist */ + public getYDoc(property: string): Y.Doc { + YLoader.loadCheck(); + const Y = YLoader.Y; + + const value = this.get(property); + + if (value instanceof Y.Doc) { + return value; + } + + if (value !== undefined) { + throw new Error(`Value of property ${property} is not a YDoc`); + } + + const doc = new Y.Doc(); + this.setUnsafe(property, doc); + + return doc; + } + /** builds all versions using the Commits */ public async getHistory( progressCallback?: (percentage: number) => void, @@ -491,6 +625,19 @@ export class Resource<C extends OptionalClass = any> { } for (const [key, value] of versionPropvals.entries()) { + if (YLoader.isLoaded() && isYDoc(value)) { + // YDocs can't just be set so we need to handle them separately. + const Y = YLoader.Y; + + const undoUpdate = this.createUndoUpdateFromVersion(key, value); + const currentDoc = this.getYDoc(key); + + Y.applyUpdateV2(currentDoc, undoUpdate); + this.commitBuilder.addYUpdateAction(key, undoUpdate); + + continue; + } + await this.set(key, value); } @@ -732,12 +879,23 @@ export class Resource<C extends OptionalClass = any> { // Logic for handling error if the previousCommit is wrong. // Is not stable enough, and maybe not required at the time. if (e.message.includes('previousCommit')) { + if (this.errorRetries > 3) { + this.errorRetries = 0; + throw e; + } + + this.errorRetries++; + console.warn('previousCommit missing or mismatch, retrying...'); // We try again, but first we fetch the latest version of the resource to get its `lastCommit` const resourceFetched = await this.store.fetchResourceFromServer( this.subject, ); + if (resourceFetched.error) { + throw resourceFetched.error; + } + const fixedLastCommit = resourceFetched! .get(properties.commit.lastCommit) ?.toString(); @@ -786,6 +944,13 @@ export class Resource<C extends OptionalClass = any> { validate = false; } + // YDocs can not be set, sadly we can't really remove them from the value type so we have to throw an error. + if (isYDoc(value)) { + throw new Error( + 'YDoc values can not be set, you should edit the YDoc value directly.', + ); + } + if (validate) { const fullProp = await this.store.getProperty(prop); @@ -817,8 +982,12 @@ export class Resource<C extends OptionalClass = any> { * Set a Property, Value combination without performing validations or adding * it to the CommitBuilder. */ - public setUnsafe(prop: string, val: JSONValue): void { + public setUnsafe(prop: string, val: AtomicValue): void { this.propvals.set(prop, val); + + if (isYDoc(val)) { + val.on('updateV2', this.buildYDocCallback(prop)); + } } /** Sets the error on the Resource. Does not Throw. */ @@ -851,6 +1020,46 @@ export class Resource<C extends OptionalClass = any> { return parent.new; } + + private createUndoUpdateFromVersion(key: string, oldDoc: Y.Doc): Uint8Array { + const Y = YLoader.Y; + YLoader.loadCheck(); + + const currentDoc = this.propvals.get(key) as Y.Doc | undefined; + + // If the current value does not exist anymore we just return the old state as there is nothing to undo. + if (currentDoc === undefined) { + return Y.encodeStateAsUpdateV2(oldDoc); + } + + const oldStateVector = Y.encodeStateVector(oldDoc); + + // Get an update of all changes after the old document. + const diffUpdate = Y.encodeStateAsUpdateV2(currentDoc, oldStateVector); + const undoManager = new Y.UndoManager(oldDoc); + + Y.applyUpdateV2(oldDoc, diffUpdate); + // The two docs are now in sync but the undo manager tracked the change to the old doc. + undoManager.undo(); + + // The undo manager created a new update that removes all the changes we just made effectively reverting all changes made since the old document. + return Y.encodeStateAsUpdateV2(oldDoc, Y.encodeStateVector(currentDoc)); + } + + private buildYDocCallback( + property: string, + ): ( + update: Uint8Array, + _origin: unknown, + _doc: unknown, + transaction: Y.Transaction, + ) => void { + return (update, _origin, _doc, transaction) => { + if (transaction.local) { + this.commitBuilder.addYUpdateAction(property, update); + } + }; + } } /** Type of Rights (e.g. read or write) */ diff --git a/browser/lib/src/search.ts b/browser/lib/src/search.ts index 3e9ac922f..25618ebb9 100644 --- a/browser/lib/src/search.ts +++ b/browser/lib/src/search.ts @@ -90,10 +90,10 @@ export function buildSearchSubject( return v !== undefined; }); - query && url.searchParams.set('q', query); - include && url.searchParams.set('include', include.toString()); - limit && url.searchParams.set('limit', limit.toString()); - hasFilters && url.searchParams.set('filters', buildFilterString(filters)); + if (query) url.searchParams.set('q', query); + if (include) url.searchParams.set('include', include.toString()); + if (limit) url.searchParams.set('limit', limit.toString()); + if (hasFilters) url.searchParams.set('filters', buildFilterString(filters)); if (parents) { if (Array.isArray(parents)) { diff --git a/browser/lib/src/store.ts b/browser/lib/src/store.ts index 9db5252cb..0a9412e1a 100644 --- a/browser/lib/src/store.ts +++ b/browser/lib/src/store.ts @@ -22,11 +22,16 @@ import type { JSONValue } from './value.js'; import { authenticate, fetchWebSocket, startWebsocket } from './websockets.js'; import { endpoints } from './urls.js'; import { initOntologies } from './ontologies/index.js'; +import { decodeB64, encodeB64 } from './base64.js'; /** Function called when a resource is updated or removed */ type ResourceCallback<C extends OptionalClass = UnknownClass> = ( resource: Resource<C>, ) => void; +type YSyncCallback = (update: { + docUpdate?: Uint8Array; + awarenessUpdate?: Uint8Array; +}) => void; type SubjectCallback = (subject: string) => void; /** Callback called when the stores agent changes */ type AgentCallback = (agent: Agent | undefined) => void; @@ -52,6 +57,13 @@ type CreateResourceOptions = { propVals?: Record<string, JSONValue>; }; +type SerializedYSyncUpdate = { + subject: string; + property: string; + awareness_update?: string; + doc_update?: string; +}; + export interface StoreOpts { /** The default store URL, where to send commits and where to create new instances */ serverUrl?: string; @@ -115,7 +127,9 @@ const supportsWebSockets = () => typeof WebSocket !== 'undefined'; */ export class Store { /** A list of all functions that need to be called when a certain resource is updated */ - public subscribers: Map<string, Array<ResourceCallback>>; + public subscribers: Map<string, ResourceCallback[]>; + private ySyncSubscribers: Map<`${string}+${string}`, YSyncCallback[]> = + new Map(); private injectedFetch: Fetch; /** * The base URL of an Atomic Server. This is where to send commits, create new @@ -142,8 +156,10 @@ export class Store { this._resources = new Map(); this.webSockets = new Map(); this.subscribers = new Map(); - opts.serverUrl && this.setServerUrl(opts.serverUrl); - opts.agent && this.setAgent(opts.agent); + + if (opts.serverUrl) this.setServerUrl(opts.serverUrl); + if (opts.agent) this.setAgent(opts.agent); + this.client = new Client(this.injectedFetch); // We need to bind this method because it is passed down by other functions @@ -194,10 +210,10 @@ export class Store { } } + const storeResource = this.resources.get(resource.subject); + // Check if the resource has the same last commit as the one already in the store, if so, we don't want to notify so we don't trigger rerenders. if (!skipCommitCompare) { - const storeResource = this.resources.get(resource.subject); - if ( storeResource && !storeResource.hasClasses(collections.classes.collection) && @@ -210,9 +226,14 @@ export class Store { } } - this.resources.set(resource.subject, resource.__internalObject); - - this.notify(resource.__internalObject); + // If the resource is already in the store, we merge it so code that depends on the resource will get the new values. + if (storeResource) { + storeResource.merge(resource.__internalObject); + this.notify(storeResource); + } else { + this.resources.set(resource.subject, resource.__internalObject); + this.notify(resource.__internalObject); + } } /** @@ -286,7 +307,7 @@ export class Store { if (createdResources.find(res => res.subject === subject)?.isReady()) { return true; } - } catch (e) { + } catch (_) { // If the resource doesn't exist, we can use it } @@ -372,7 +393,7 @@ export class Store { }, ); - this.addResources(createdResources, { skipCommitCompare: true }); + this.addResources(createdResources); } return this.resources.get(subject)!; @@ -751,8 +772,11 @@ export class Store { this.serverUrl = url; this.eventManager.emit(StoreEvents.ServerURLChanged, url); + // TODO This is not the right place - supportsWebSockets() && this.openWebSocket(url); + if (supportsWebSockets()) { + this.openWebSocket(url); + } } /** Opens a WebSocket for this Atomic Server URL */ @@ -810,11 +834,118 @@ export class Store { ws?.send(`SUBSCRIBE ${subject}`); } } catch (e) { - // eslint-disable-next-line no-console console.error(e); } } + /** + * Subscribe to Yjs Sync messages send over the websocket connection. + * These sync messages can be used for realtime collaboration and are not persisted on the server. + * For regular updates to normal values an ydocs use `store.subscribe()` instead. + * @param subject The subject of the resource that you want to subscribe to. + * @param property The property that contains the ydoc. + * @param callback The callback that will be called when the doc or awareness state changes. + * @returns A function that can be called to unsubscribe. + */ + public subscribeYSync( + subject: string, + property: string, + callback: YSyncCallback, + ): () => void { + const ws = this.getWebSocketForSubject(subject); + const key = `${subject}+${property}` as const; + + const messageBody = JSON.stringify({ + subject, + property, + }); + + const unsub = () => { + const subscribers = this.ySyncSubscribers.get(key); + + if (subscribers) { + const afterUnsub = subscribers.filter(item => item !== callback); + + if (afterUnsub.length === 0) { + this.ySyncSubscribers.delete(key); + + if (ws?.readyState === 1) { + ws?.send(`Y_SYNC_UNSUBSCRIBE ${messageBody}`); + } + } else { + this.ySyncSubscribers.set(key, afterUnsub); + } + } + }; + + const subscribers = this.ySyncSubscribers.get(key); + + if (subscribers) { + subscribers.push(callback); + + return unsub; + } + + this.ySyncSubscribers.set(key, [callback]); + + if (ws?.readyState === 1) { + ws?.send(`Y_SYNC_SUBSCRIBE ${messageBody}`); + } + + return unsub; + } + + /** + * Broadcast a change to a ydoc or awareness state to all other listeners via the open websocket. + * These messages are not persisted and are meant for fast realtime collaboration. + * To persist changes call `resource.save()` instead. + * @param subject The subject of the resource. + * @param property The property that contains the ydoc. + * @param update The binary encoded update to send to the server. + */ + public broadcastYSyncUpdate( + subject: string, + property: string, + update: { docUpdate?: Uint8Array; awarenessUpdate?: Uint8Array }, + ): void { + const ws = this.getWebSocketForSubject(subject); + + const { docUpdate, awarenessUpdate } = update; + + const messageBody = { + subject: subject, + property: property, + ...(docUpdate && { doc_update: encodeB64(docUpdate) }), + ...(awarenessUpdate && { awareness_update: encodeB64(awarenessUpdate) }), + }; + + if (ws?.readyState === 1) { + ws?.send(`Y_SYNC_UPDATE ${JSON.stringify(messageBody)}`); + } + } + + /** + * @Internal + */ + public __handleAwarenessUpdateMessage(message: string): void { + const messageBody: SerializedYSyncUpdate = JSON.parse(message); + + const subscribers = this.ySyncSubscribers.get( + `${messageBody.subject}+${messageBody.property}`, + ); + + const awarenessUpdate = messageBody.awareness_update + ? decodeB64(messageBody.awareness_update) + : undefined; + const docUpdate = messageBody.doc_update + ? decodeB64(messageBody.doc_update) + : undefined; + + if (subscribers) { + subscribers.forEach(callback => callback({ docUpdate, awarenessUpdate })); + } + } + public unSubscribeWebSocket(subject: string): void { if (subject === unknownSubject) { return; @@ -823,7 +954,6 @@ export class Store { try { this.getDefaultWebSocket()?.send(`UNSUBSCRIBE ${subject}`); } catch (e) { - // eslint-disable-next-line no-console console.error(e); } } @@ -884,7 +1014,10 @@ export class Store { const ancestry: string[] = [resource.subject]; let lastAncestor: string = resource.get(core.properties.parent) as string; - lastAncestor && ancestry.push(lastAncestor); + + if (lastAncestor) { + ancestry.push(lastAncestor); + } while (lastAncestor) { const lastResource = await this.getResource(lastAncestor); diff --git a/browser/lib/src/value.ts b/browser/lib/src/value.ts index c37a69a9b..70d0b7520 100644 --- a/browser/lib/src/value.ts +++ b/browser/lib/src/value.ts @@ -1,16 +1,25 @@ import { JSONADParser } from './parse.js'; import type { Resource } from './resource.js'; +import type * as Y from 'yjs'; +import { YLoader } from './yjs.js'; export type JSONPrimitive = string | number | boolean; export type JSONValue = JSONPrimitive | JSONObject | JSONArray | undefined; export type JSONObject = { [key: string]: JSONValue }; export type JSONArray = Array<JSONValue>; +export type AtomicValue = JSONValue | Y.Doc; + +export type SerializedYUpdate = { + type: 'ydoc'; + data: string; +}; + /** * Tries to convert the value as an array of resources, which can be both URLs * or Nested Resources. Throws an error when fails */ -export function valToArray(val?: JSONValue): JSONArray { +export function valToArray(val?: AtomicValue): JSONArray { if (val === undefined) { throw new Error(`Not an array: ${val}, is ${typeof val}`); } @@ -23,7 +32,7 @@ export function valToArray(val?: JSONValue): JSONArray { } /** Tries to make a boolean from this value. Throws if it is not a boolean. */ -export function valToBoolean(val?: JSONValue): boolean { +export function valToBoolean(val?: AtomicValue): boolean { if (typeof val !== 'boolean') { throw new Error(`Not a boolean: ${val}, is a ${typeof val}`); } @@ -35,7 +44,7 @@ export function valToBoolean(val?: JSONValue): boolean { * Tries to convert the value (timestamp or date) to a JS Date. Throws an error * when fails. */ -export function valToDate(val?: JSONValue): Date { +export function valToDate(val?: AtomicValue): Date { // If it's a unix epoch timestamp... if (typeof val === 'number') { const date = new Date(0); // The 0 there is the key, which sets the date to the epoch @@ -52,7 +61,7 @@ export function valToDate(val?: JSONValue): Date { } /** Returns a number of the value, or throws an error */ -export function valToNumber(val?: JSONValue): number { +export function valToNumber(val?: AtomicValue): number { if (typeof val !== 'number') { throw new Error(`Not a number: ${val}, is a ${typeof val}`); } @@ -61,13 +70,13 @@ export function valToNumber(val?: JSONValue): number { } /** Returns a default string representation of the value. */ -export function valToString(val: JSONValue): string { +export function valToString(val: AtomicValue): string { // val && val.toString(); return val?.toString() ?? 'undefined'; } /** Returns either the URL of the resource, or the NestedResource itself. */ -export function valToResource(val: JSONValue): string | Resource { +export function valToResource(val: AtomicValue): string | Resource { if (typeof val === 'string') { return val; } @@ -93,3 +102,23 @@ export function valToResource(val: JSONValue): string | Resource { throw new Error(`Not a resource: ${val}, is a ${typeof val}`); } + +export function isYDoc(val: AtomicValue): val is Y.Doc { + if (!YLoader.isLoaded()) { + return false; + } + + const Y = YLoader.Y; + + return val instanceof Y.Doc; +} + +export const isJSONObject = (value: JSONValue): value is JSONObject => + typeof value === 'object' && value !== null && !Array.isArray(value); + +export const isSerializedYUpdate = ( + value: JSONValue, +): value is SerializedYUpdate => + isJSONObject(value) && + value.type === 'ydoc' && + typeof value.data === 'string'; diff --git a/browser/lib/src/websockets.ts b/browser/lib/src/websockets.ts index 14f3ef77a..460039b7b 100644 --- a/browser/lib/src/websockets.ts +++ b/browser/lib/src/websockets.ts @@ -45,6 +45,9 @@ function handleMessage(ev: MessageEvent, store: Store) { } else if (ev.data.startsWith('RESOURCE ')) { const resources = parseResourceMessage(ev); store.addResources(resources); + } else if (ev.data.startsWith('Y_SYNC_UPDATE ')) { + const update = ev.data.slice(14); + store.__handleAwarenessUpdateMessage(update); } else { console.warn('Unknown websocket message:', ev); } @@ -93,12 +96,13 @@ export async function authenticate( client.send('AUTHENTICATE ' + JSON.stringify(json)); // Maybe this should happen after the authentication is confirmed? - fetchAll && + if (fetchAll) { store.resources.forEach(r => { if (r.isUnauthorized() || r.loading) { store.fetchResourceFromServer(r.subject); } }); + } } const defaultTimeout = 5000; @@ -109,16 +113,7 @@ export async function fetchWebSocket( subject: string, ): Promise<Resource> { return new Promise((resolve, reject) => { - client.addEventListener('message', function listener(ev) { - const timeoutId = setTimeout(() => { - client.removeEventListener('message', listener); - reject( - new Error( - `Request for subject "${subject}" timed out after ${defaultTimeout}ms.`, - ), - ); - }, defaultTimeout); - + const listener = (ev: MessageEvent) => { if (ev.data.startsWith('RESOURCE ')) { parseResourceMessage(ev).forEach(resource => { // if it is the requested subject, return the resource @@ -129,7 +124,18 @@ export async function fetchWebSocket( } }); } - }); + }; + + const timeoutId = setTimeout(() => { + client.removeEventListener('message', listener); + reject( + new Error( + `Request for subject "${subject}" timed out after ${defaultTimeout}ms.`, + ), + ); + }, defaultTimeout); + + client.addEventListener('message', listener); client.send('GET ' + subject); }); } diff --git a/browser/lib/src/yjs.ts b/browser/lib/src/yjs.ts new file mode 100644 index 000000000..795fde0fc --- /dev/null +++ b/browser/lib/src/yjs.ts @@ -0,0 +1,43 @@ +import type * as Y from 'yjs'; + +/** + * To prevent bloat we don't always want to include Yjs in the bundle. + * Since Yjs is an optional dependency, we need to load it lazily and it might not even be installed. + */ +export class YLoader { + private static _Y: typeof Y | undefined; + + public static get Y(): typeof Y { + if (!this._Y) { + throw new Error('Y not initialized'); + } + + return this._Y; + } + + public static async initializeY(): Promise<void> { + if (this._Y) { + return; + } + + this._Y = await import('yjs'); + } + + public static isLoaded(): boolean { + return this._Y !== undefined; + } + + public static loadCheck(): void { + if (!this.isLoaded()) { + throw new Error('Yjs not initialized'); + } + } +} + +/** + * Enables the use of Yjs features in the library. + * Call this somewhere early on in your application and make sure the yjs package is installed. + */ +export const enableYjs = async () => { + await YLoader.initializeY(); +}; diff --git a/browser/package.json b/browser/package.json index b85d16682..1421207eb 100644 --- a/browser/package.json +++ b/browser/package.json @@ -1,22 +1,25 @@ { "devDependencies": { - "@types/node": "^20.17.0", - "@typescript-eslint/eslint-plugin": "^7.18.0", - "@typescript-eslint/parser": "^7.18.0", - "eslint": "^8.57.1", + "@eslint/js": "^9.39.0", + "@types/node": "^24.7.0", + "@typescript-eslint/eslint-plugin": "^8.46.2", + "@typescript-eslint/parser": "^8.46.2", + "eslint": "^9.39.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.31.0", - "eslint-plugin-jsx-a11y": "^6.10.1", + "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "6.0.0-rc.1", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "7.0.1", + "globals": "^15.11.0", "husky": "^8.0.3", "netlify-cli": "17.37.1", - "prettier": "3.2.5", + "prettier": "3.6.2", "prettier-plugin-jsdoc": "^1.3.0", + "prettier-plugin-svelte": "^3.2.7", "typedoc": "^0.25.13", "typedoc-plugin-missing-exports": "^2.3.0", - "typescript": "^5.6.3", + "typescript": "^5.9.3", "vite": "^5.4.10", "vitest": "^2.1.3" }, @@ -29,6 +32,7 @@ "lint": "pnpm run -r lint", "lint-fix": "pnpm run -r lint-fix", "lint-package": "pnpm run -r lint-package", + "prettier-check": "pnpm run -r prettier-check", "build": "pnpm --filter \"@tomic/lib\" run build && pnpm --filter=!./lib run -r build ", "test": "pnpm run -r test", "test-e2e": "pnpm run --filter @tomic/e2e test-e2e", diff --git a/browser/pnpm-lock.yaml b/browser/pnpm-lock.yaml index 6dcabba96..90b770d0a 100644 --- a/browser/pnpm-lock.yaml +++ b/browser/pnpm-lock.yaml @@ -8,63 +8,72 @@ importers: .: devDependencies: + '@eslint/js': + specifier: ^9.39.0 + version: 9.39.0 '@types/node': - specifier: ^20.17.0 - version: 20.17.0 + specifier: ^24.7.0 + version: 24.7.0 '@typescript-eslint/eslint-plugin': - specifier: ^7.18.0 - version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) + specifier: ^8.46.2 + version: 8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3))(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^7.18.0 - version: 7.18.0(eslint@8.57.1)(typescript@5.6.3) + specifier: ^8.46.2 + version: 8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3) eslint: - specifier: ^8.57.1 - version: 8.57.1 + specifier: ^9.39.0 + version: 9.39.0(jiti@2.3.3) eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.0(eslint@8.57.1) + version: 9.1.0(eslint@9.39.0(jiti@2.3.3)) eslint-plugin-import: specifier: ^2.31.0 - version: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1) + version: 2.31.0(@typescript-eslint/parser@8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3))(eslint@9.39.0(jiti@2.3.3)) eslint-plugin-jsx-a11y: - specifier: ^6.10.1 - version: 6.10.1(eslint@8.57.1) + specifier: ^6.10.2 + version: 6.10.2(eslint@9.39.0(jiti@2.3.3)) eslint-plugin-prettier: specifier: ^5.2.1 - version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.2.5) + version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.39.0(jiti@2.3.3)))(eslint@9.39.0(jiti@2.3.3))(prettier@3.6.2) eslint-plugin-react: - specifier: ^7.37.2 - version: 7.37.2(eslint@8.57.1) + specifier: ^7.37.5 + version: 7.37.5(eslint@9.39.0(jiti@2.3.3)) eslint-plugin-react-hooks: - specifier: 6.0.0-rc.1 - version: 6.0.0-rc.1(eslint@8.57.1) + specifier: 7.0.1 + version: 7.0.1(eslint@9.39.0(jiti@2.3.3)) + globals: + specifier: ^15.11.0 + version: 15.11.0 husky: specifier: ^8.0.3 version: 8.0.3 netlify-cli: specifier: 17.37.1 - version: 17.37.1(@swc/core@1.7.39)(@types/node@20.17.0)(picomatch@4.0.3) + version: 17.37.1(@swc/core@1.7.39)(@types/node@24.7.0)(picomatch@4.0.3) prettier: - specifier: 3.2.5 - version: 3.2.5 + specifier: 3.6.2 + version: 3.6.2 prettier-plugin-jsdoc: specifier: ^1.3.0 - version: 1.3.0(prettier@3.2.5) + version: 1.3.0(prettier@3.6.2) + prettier-plugin-svelte: + specifier: ^3.2.7 + version: 3.2.7(prettier@3.6.2)(svelte@5.1.4) typedoc: specifier: ^0.25.13 - version: 0.25.13(typescript@5.6.3) + version: 0.25.13(typescript@5.9.3) typedoc-plugin-missing-exports: specifier: ^2.3.0 - version: 2.3.0(typedoc@0.25.13(typescript@5.6.3)) + version: 2.3.0(typedoc@0.25.13(typescript@5.9.3)) typescript: - specifier: ^5.6.3 - version: 5.6.3 + specifier: ^5.9.3 + version: 5.9.3 vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@20.17.0)(terser@5.43.1) + version: 5.4.10(@types/node@24.7.0)(terser@5.43.1) vitest: specifier: ^2.1.3 - version: 2.1.3(@types/node@20.17.0)(terser@5.43.1) + version: 2.1.3(@types/node@24.7.0)(terser@5.43.1) cli: dependencies: @@ -78,8 +87,8 @@ importers: specifier: 3.0.3 version: 3.0.3 typescript: - specifier: ^5.6.3 - version: 5.6.3 + specifier: ^5.9.3 + version: 5.9.3 create-template: dependencies: @@ -97,14 +106,14 @@ importers: specifier: ^20.17.0 version: 20.17.0 typescript: - specifier: ^5.6.3 - version: 5.6.3 + specifier: ^5.9.3 + version: 5.9.3 data-browser: dependencies: '@ai-sdk/react': specifier: ^2.0.29 - version: 2.0.29(react@19.0.0)(zod@4.1.5) + version: 2.0.29(react@19.2.0)(zod@4.1.5) '@bugsnag/core': specifier: ^7.25.0 version: 7.25.0 @@ -125,19 +134,22 @@ importers: version: 1.1.4 '@dnd-kit/core': specifier: ^6.1.0 - version: 6.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 6.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@dnd-kit/sortable': specifier: ^8.0.0 - version: 8.0.0(@dnd-kit/core@6.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) + version: 8.0.0(@dnd-kit/core@6.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0) '@dnd-kit/utilities': specifier: ^3.2.2 - version: 3.2.2(react@19.0.0) + version: 3.2.2(react@19.2.0) '@emoji-mart/react': specifier: ^1.1.1 - version: 1.1.1(emoji-mart@5.6.0)(react@19.0.0) + version: 1.1.1(emoji-mart@5.6.0)(react@19.2.0) '@emotion/is-prop-valid': - specifier: ^1.3.1 - version: 1.3.1 + specifier: ^1.4.0 + version: 1.4.0 + '@floating-ui/dom': + specifier: ^1.7.4 + version: 1.7.4 '@modelcontextprotocol/sdk': specifier: ^1.13.3 version: 1.17.0 @@ -148,47 +160,77 @@ importers: specifier: ^1.2.0 version: 1.2.0(ai@5.0.29(zod@4.1.5))(zod@4.1.5) '@radix-ui/react-popover': - specifier: ^1.1.2 - version: 1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-scroll-area': specifier: ^1.2.0 - version: 1.2.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 1.2.0(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@radix-ui/react-tabs': specifier: ^1.1.1 - version: 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 1.1.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tanstack/react-router': specifier: ^1.95.1 - version: 1.95.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 1.95.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@tiptap/core': + specifier: ^3.7.2 + version: 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/extension-collaboration': + specifier: ^3.7.2 + version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/y-tiptap@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(yjs@13.6.27) + '@tiptap/extension-collaboration-caret': + specifier: ^3.7.2 + version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/y-tiptap@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)) + '@tiptap/extension-drag-handle-react': + specifier: ^3.7.2 + version: 3.7.2(@tiptap/extension-drag-handle@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/extension-collaboration@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/y-tiptap@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(yjs@13.6.27))(@tiptap/extension-node-range@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/y-tiptap@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)))(@tiptap/pm@3.7.2)(@tiptap/react@3.7.2(@floating-ui/dom@1.7.4)(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tiptap/extension-file-handler': - specifier: ^2.25.0 - version: 2.25.0(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/extension-text-style@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))) + specifier: ^3.7.2 + version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/extension-text-style@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)))(@tiptap/pm@3.7.2) '@tiptap/extension-image': - specifier: ^2.11.7 - version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + specifier: ^3.7.2 + version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) '@tiptap/extension-link': - specifier: ^2.11.7 - version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + specifier: ^3.7.2 + version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) + '@tiptap/extension-list': + specifier: ^3.7.2 + version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) '@tiptap/extension-mention': - specifier: ^2.11.7 - version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(@tiptap/suggestion@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)) + specifier: ^3.7.2 + version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/suggestion@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)) '@tiptap/extension-placeholder': - specifier: ^2.11.7 - version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + specifier: ^3.7.2 + version: 3.7.2(@tiptap/extensions@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)) + '@tiptap/extension-table': + specifier: ^3.10.5 + version: 3.10.5(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) + '@tiptap/extension-text-align': + specifier: ^3.7.2 + version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) + '@tiptap/extension-text-style': + specifier: ^3.7.2 + version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) '@tiptap/extension-typography': - specifier: ^2.11.7 - version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + specifier: ^3.7.2 + version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) + '@tiptap/markdown': + specifier: ^3.7.2 + version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) '@tiptap/pm': - specifier: ^2.11.7 - version: 2.11.7 + specifier: ^3.7.2 + version: 3.7.2 '@tiptap/react': - specifier: ^2.11.7 - version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: ^3.7.2 + version: 3.7.2(@floating-ui/dom@1.7.4)(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tiptap/starter-kit': - specifier: ^2.11.7 - version: 2.11.7 + specifier: ^3.7.2 + version: 3.7.2 '@tiptap/suggestion': - specifier: ^2.11.7 - version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + specifier: ^3.7.2 + version: 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) + '@tiptap/y-tiptap': + specifier: ^3.0.0 + version: 3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) '@tomic/react': specifier: workspace:* version: link:../react @@ -197,13 +239,13 @@ importers: version: 4.24.1(@codemirror/language@6.11.2)(@codemirror/state@6.5.2)(@codemirror/view@6.38.1) '@uiw/react-codemirror': specifier: ^4.24.1 - version: 4.24.1(@babel/runtime@7.27.6)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.2)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.38.1)(codemirror@6.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 4.24.1(@babel/runtime@7.27.6)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.2)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.38.1)(codemirror@6.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@wuchale/jsx': - specifier: ^0.7.4 - version: 0.7.4(react@19.0.0) + specifier: ^0.9.4 + version: 0.9.4(react@19.2.0) '@wuchale/vite-plugin': - specifier: ^0.14.6 - version: 0.14.6 + specifier: ^0.15.3 + version: 0.15.3 ai: specifier: ^5.0.29 version: 5.0.29(zod@4.1.5) @@ -212,7 +254,7 @@ importers: version: 2.1.1 downshift: specifier: ^9.0.9 - version: 9.0.10(react@19.0.0) + version: 9.0.10(react@19.2.0) emoji-mart: specifier: ^5.6.0 version: 5.6.0 @@ -229,93 +271,96 @@ importers: specifier: ^0.2.0 version: 0.2.0 react: - specifier: ^19.0.0 - version: 19.0.0 + specifier: ^19.2.0 + version: 19.2.0 react-colorful: specifier: ^5.6.1 - version: 5.6.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 5.6.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-dom: - specifier: ^19.0.0 - version: 19.0.0(react@19.0.0) + specifier: ^19.2.0 + version: 19.2.0(react@19.2.0) react-dropzone: specifier: ^11.7.1 - version: 11.7.1(react@19.0.0) + version: 11.7.1(react@19.2.0) react-hot-toast: specifier: ^2.4.1 - version: 2.4.1(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 2.4.1(csstype@3.1.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-hotkeys-hook: specifier: ^3.4.7 - version: 3.4.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 3.4.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-icons: specifier: ^4.12.0 - version: 4.12.0(react@19.0.0) + version: 4.12.0(react@19.2.0) react-intersection-observer: specifier: ^9.13.1 - version: 9.13.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 9.13.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-is: - specifier: ^19.0.0 - version: 19.0.0 + specifier: ^19.2.0 + version: 19.2.0 react-markdown: specifier: ^9.0.3 - version: 9.0.3(@types/react@19.0.1)(react@19.0.0) + version: 9.0.3(@types/react@19.2.2)(react@19.2.0) react-pdf: - specifier: ^9.1.1 - version: 9.1.1(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + specifier: ^10.2.0 + version: 10.2.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-virtualized-auto-sizer: specifier: ^1.0.24 - version: 1.0.24(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 1.0.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react-window: specifier: ^1.8.10 - version: 1.8.10(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 1.8.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0) reactflow: specifier: ^11.11.4 - version: 11.11.4(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 11.11.4(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) remark-gfm: specifier: ^4.0.0 version: 4.0.0 styled-components: specifier: ^6.1.19 - version: 6.1.19(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0) stylis: specifier: 4.3.0 version: 4.3.0 - tippy.js: - specifier: ^6.3.7 - version: 6.3.7 tiptap-markdown: specifier: ^0.8.10 - version: 0.8.10(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + version: 0.8.10(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) wuchale: - specifier: ^0.16.5 - version: 0.16.5 + specifier: ^0.18.3 + version: 0.18.3 + y-protocols: + specifier: ^1.0.6 + version: 1.0.6(yjs@13.6.27) + yjs: + specifier: ^13.6.27 + version: 13.6.27 zod: specifier: ^4.1.5 version: 4.1.5 devDependencies: '@tanstack/router-devtools': specifier: ^1.95.1 - version: 1.95.1(@tanstack/react-router@1.95.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + version: 1.95.1(@tanstack/react-router@1.95.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(csstype@3.1.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@types/prismjs': specifier: ^1.26.5 version: 1.26.5 '@types/react': - specifier: ^19.0.0 - version: 19.0.1 + specifier: ^19.2.2 + version: 19.2.2 '@types/react-dom': - specifier: ^19.0.0 - version: 19.0.1 + specifier: ^19.2.2 + version: 19.2.2(@types/react@19.2.2) '@types/react-window': specifier: ^1.8.8 version: 1.8.8 '@vitejs/plugin-react': - specifier: ^4.3.4 - version: 4.3.4(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)) + specifier: ^5.0.4 + version: 5.0.4(vite@7.1.12(@types/node@24.7.0)(jiti@2.3.3)(terser@5.43.1)(yaml@2.6.0)) babel-plugin-react-compiler: - specifier: 19.1.0-rc.2 - version: 19.1.0-rc.2 + specifier: 1.0.0 + version: 1.0.0 babel-plugin-styled-components: specifier: ^2.1.4 - version: 2.1.4(@babel/core@7.26.0)(styled-components@6.1.19(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) + version: 2.1.4(@babel/core@7.28.4)(styled-components@6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0)) csstype: specifier: ^3.1.3 version: 3.1.3 @@ -329,20 +374,20 @@ importers: specifier: ^1.1.0 version: 1.1.0 typescript: - specifier: ^5.6.3 - version: 5.6.3 + specifier: ^5.9.3 + version: 5.9.3 vite: - specifier: ^5.4.10 - version: 5.4.10(@types/node@20.17.0)(terser@5.43.1) + specifier: ^7.1.12 + version: 7.1.12(@types/node@24.7.0)(jiti@2.3.3)(terser@5.43.1)(yaml@2.6.0) vite-plugin-prismjs: specifier: ^0.0.11 version: 0.0.11(prismjs@1.29.0) vite-plugin-pwa: - specifier: ^0.20.5 - version: 0.20.5(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0) + specifier: ^1.1.0 + version: 1.1.0(vite@7.1.12(@types/node@24.7.0)(jiti@2.3.3)(terser@5.43.1)(yaml@2.6.0))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0) vite-plugin-webfont-dl: - specifier: ^3.9.5 - version: 3.9.5(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)) + specifier: ^3.11.1 + version: 3.11.1(vite@7.1.12(@types/node@24.7.0)(jiti@2.3.3)(terser@5.43.1)(yaml@2.6.0)) e2e: devDependencies: @@ -367,22 +412,22 @@ importers: '@noble/hashes': specifier: ^0.5.9 version: 0.5.9 - base64-arraybuffer: - specifier: ^1.0.2 - version: 1.0.2 fast-json-stable-stringify: specifier: ^2.1.0 version: 2.1.0 ulidx: specifier: ^2.4.1 version: 2.4.1 + yjs: + specifier: ^13.6.27 + version: 13.6.27 devDependencies: '@arethetypeswrong/cli': specifier: ^0.17.0 version: 0.17.0 '@microsoft/api-extractor': specifier: ^7.48.0 - version: 7.48.0(@types/node@20.17.0) + version: 7.48.0(@types/node@24.7.0) '@tomic/cli': specifier: workspace:* version: link:../cli @@ -394,13 +439,13 @@ importers: version: 2.8.0 tsup: specifier: ^8.3.5 - version: 8.3.5(@microsoft/api-extractor@7.48.0(@types/node@20.17.0))(@swc/core@1.7.39)(jiti@2.3.3)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.6.0) + version: 8.3.5(@microsoft/api-extractor@7.48.0(@types/node@24.7.0))(@swc/core@1.7.39)(jiti@2.3.3)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.6.0) typescript: - specifier: ^5.6.3 - version: 5.6.3 + specifier: ^5.9.3 + version: 5.9.3 vitest: specifier: ^2.1.3 - version: 2.1.3(@types/node@20.17.0)(terser@5.43.1) + version: 2.1.3(@types/node@24.7.0)(terser@5.43.1) react: dependencies: @@ -408,30 +453,33 @@ importers: specifier: workspace:* version: link:../lib react: - specifier: ^19.0.0 - version: 19.0.0 + specifier: ^19.2.0 + version: 19.2.0 react-dom: - specifier: ^19.0.0 - version: 19.0.0(react@19.0.0) + specifier: ^19.2.0 + version: 19.2.0(react@19.2.0) devDependencies: '@arethetypeswrong/cli': specifier: ^0.17.0 version: 0.17.0 '@types/react': - specifier: ^19.0.0 - version: 19.0.1 + specifier: ^19.2.2 + version: 19.2.2 '@types/react-dom': - specifier: ^19.0.0 - version: 19.0.1 + specifier: ^19.2.2 + version: 19.2.2(@types/react@19.2.2) '@types/react-router-dom': specifier: ^5.3.3 version: 5.3.3 tsup: specifier: ^8.3.5 - version: 8.3.5(@microsoft/api-extractor@7.48.0(@types/node@20.17.0))(@swc/core@1.7.39)(jiti@2.3.3)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.6.0) + version: 8.3.5(@microsoft/api-extractor@7.48.0(@types/node@24.7.0))(@swc/core@1.7.39)(jiti@2.3.3)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.6.0) typescript: - specifier: ^5.6.3 - version: 5.6.3 + specifier: ^5.9.3 + version: 5.9.3 + yjs: + specifier: ^13.6.27 + version: 13.6.27 svelte: dependencies: @@ -441,16 +489,16 @@ importers: devDependencies: '@sveltejs/adapter-auto': specifier: ^3.3.0 - version: 3.3.0(@sveltejs/kit@2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)))(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1))) + version: 3.3.0(@sveltejs/kit@2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)))(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1))) '@sveltejs/kit': specifier: ^2.7.2 - version: 2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)))(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)) + version: 2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)))(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)) '@sveltejs/package': specifier: ^2.3.6 version: 2.3.6(svelte@5.1.4)(typescript@5.6.3) '@sveltejs/vite-plugin-svelte': specifier: ^4.0.0 - version: 4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)) + version: 4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)) '@types/eslint': specifier: ^9.6.1 version: 9.6.1 @@ -462,7 +510,7 @@ importers: version: 9.1.0(eslint@9.13.0(jiti@2.3.3)) eslint-plugin-svelte: specifier: ^2.46.0 - version: 2.46.0(eslint@9.13.0(jiti@2.3.3))(svelte@5.1.4)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@20.17.0)(typescript@5.6.3)) + version: 2.46.0(eslint@9.13.0(jiti@2.3.3))(svelte@5.1.4)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@24.7.0)(typescript@5.6.3)) globals: specifier: ^15.11.0 version: 15.11.0 @@ -480,7 +528,7 @@ importers: version: 5.1.4 svelte-check: specifier: ^3.8.6 - version: 3.8.6(@babel/core@7.26.0)(postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@20.17.0)(typescript@5.6.3)))(postcss@8.4.47)(svelte@5.1.4) + version: 3.8.6(@babel/core@7.28.4)(postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@24.7.0)(typescript@5.6.3)))(postcss@8.5.6)(svelte@5.1.4) typescript: specifier: ^5.6.3 version: 5.6.3 @@ -489,10 +537,10 @@ importers: version: 8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3) vite: specifier: ^5.4.10 - version: 5.4.10(@types/node@20.17.0)(terser@5.43.1) + version: 5.4.10(@types/node@24.7.0)(terser@5.43.1) vitest: specifier: ^2.1.3 - version: 2.1.3(@types/node@20.17.0)(terser@5.43.1) + version: 2.1.3(@types/node@24.7.0)(terser@5.43.1) packages: @@ -561,10 +609,18 @@ packages: resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} engines: {node: '>=6.9.0'} + '@babel/core@7.28.4': + resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.27.5': resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.25.9': resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} @@ -594,6 +650,10 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.27.1': resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} engines: {node: '>=6.9.0'} @@ -612,6 +672,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.27.1': resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} @@ -660,8 +726,12 @@ packages: resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.27.7': - resolution: {integrity: sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==} + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} engines: {node: '>=6.0.0'} hasBin: true @@ -953,14 +1023,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-self@7.25.9': - resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-source@7.25.9': - resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1068,22 +1138,22 @@ packages: resolution: {integrity: sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==} engines: {node: '>=6.9.0'} - '@babel/types@7.25.6': - resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.25.9': - resolution: {integrity: sha512-OwS2CM5KocvQ/k7dFJa8i5bNGJP0hXWfVCfDkqRFP1IreH1JDC7wG6eCYCi0+McbfT8OR/kNqsI0UU0xP9H6PQ==} + '@babel/traverse@7.28.4': + resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.26.3': - resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} + '@babel/types@7.25.6': + resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} engines: {node: '>=6.9.0'} '@babel/types@7.27.7': resolution: {integrity: sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + engines: {node: '>=6.9.0'} + '@bugsnag/browser@7.25.0': resolution: {integrity: sha512-PzzWy5d9Ly1CU1KkxTB6ZaOw/dO+CYSfVtqxVJccy832e6+7rW/dvSw5Jy7rsNhgcKSKjZq86LtNkPSvritOLA==} @@ -1110,6 +1180,15 @@ packages: '@bugsnag/safe-json-stringify@6.0.0': resolution: {integrity: sha512-htzFO1Zc57S8kgdRK9mLcPVTW1BY2ijfH7Dk2CeZmspTWKdKqSo1iwmqrq2WtRjFlo8aRZYgLX0wFrDXF/9DLA==} + '@cacheable/memoize@2.0.3': + resolution: {integrity: sha512-hl9wfQgpiydhQEIv7fkjEzTGE+tcosCXLKFDO707wYJ/78FVOlowb36djex5GdbSyeHnG62pomYLMuV/OT8Pbw==} + + '@cacheable/memory@2.0.3': + resolution: {integrity: sha512-R3UKy/CKOyb1LZG/VRCTMcpiMDyLH7SH3JrraRdK6kf3GweWCOU3sgvE13W3TiDRbxnDKylzKJvhUAvWl9LQOA==} + + '@cacheable/utils@2.1.0': + resolution: {integrity: sha512-ZdxfOiaarMqMj+H7qwlt5EBKWaeGihSYVHdQv5lUsbn8MJJOTW82OIwirQ39U5tMZkNvy3bQE+ryzC+xTAb9/g==} + '@codemirror/autocomplete@6.18.6': resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==} @@ -1194,8 +1273,8 @@ packages: '@emotion/is-prop-valid@1.2.2': resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==} - '@emotion/is-prop-valid@1.3.1': - resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==} + '@emotion/is-prop-valid@1.4.0': + resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} '@emotion/memoize@0.8.1': resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} @@ -1230,6 +1309,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.25.11': + resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.19.11': resolution: {integrity: sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==} engines: {node: '>=12'} @@ -1254,6 +1339,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.25.11': + resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.19.11': resolution: {integrity: sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==} engines: {node: '>=12'} @@ -1278,6 +1369,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.25.11': + resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.19.11': resolution: {integrity: sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==} engines: {node: '>=12'} @@ -1302,6 +1399,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.25.11': + resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.19.11': resolution: {integrity: sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==} engines: {node: '>=12'} @@ -1326,6 +1429,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.25.11': + resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.19.11': resolution: {integrity: sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==} engines: {node: '>=12'} @@ -1350,6 +1459,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.25.11': + resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.19.11': resolution: {integrity: sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==} engines: {node: '>=12'} @@ -1374,6 +1489,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.25.11': + resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.19.11': resolution: {integrity: sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==} engines: {node: '>=12'} @@ -1398,6 +1519,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.11': + resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.19.11': resolution: {integrity: sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==} engines: {node: '>=12'} @@ -1422,6 +1549,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.25.11': + resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.19.11': resolution: {integrity: sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==} engines: {node: '>=12'} @@ -1446,6 +1579,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.25.11': + resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.19.11': resolution: {integrity: sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==} engines: {node: '>=12'} @@ -1470,6 +1609,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.25.11': + resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.19.11': resolution: {integrity: sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==} engines: {node: '>=12'} @@ -1494,6 +1639,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.25.11': + resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.19.11': resolution: {integrity: sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==} engines: {node: '>=12'} @@ -1518,6 +1669,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.25.11': + resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.19.11': resolution: {integrity: sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==} engines: {node: '>=12'} @@ -1542,6 +1699,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.25.11': + resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.19.11': resolution: {integrity: sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==} engines: {node: '>=12'} @@ -1566,6 +1729,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.25.11': + resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.19.11': resolution: {integrity: sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==} engines: {node: '>=12'} @@ -1590,6 +1759,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.25.11': + resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.19.11': resolution: {integrity: sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==} engines: {node: '>=12'} @@ -1614,6 +1789,18 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.25.11': + resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.11': + resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.19.11': resolution: {integrity: sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==} engines: {node: '>=12'} @@ -1638,12 +1825,24 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.11': + resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.24.0': resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.25.11': + resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.19.11': resolution: {integrity: sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==} engines: {node: '>=12'} @@ -1668,6 +1867,18 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.11': + resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.11': + resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.19.11': resolution: {integrity: sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==} engines: {node: '>=12'} @@ -1692,6 +1903,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.25.11': + resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.19.11': resolution: {integrity: sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==} engines: {node: '>=12'} @@ -1716,6 +1933,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.25.11': + resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.19.11': resolution: {integrity: sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==} engines: {node: '>=12'} @@ -1740,6 +1963,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.25.11': + resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.19.11': resolution: {integrity: sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==} engines: {node: '>=12'} @@ -1764,48 +1993,84 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.11': + resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.11.1': resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/config-array@0.18.0': resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.7.0': resolution: {integrity: sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@eslint/eslintrc@3.1.0': resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@8.57.1': - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@9.13.0': resolution: {integrity: sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.39.0': + resolution: {integrity: sha512-BIhe0sW91JGPiaF1mOuPy5v8NflqfjIcDNpC+LbW9f609WVRX1rArrhi6Z2ymvrAry9jw+5POTj4t2t62o8Bmw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@2.1.4': resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.2.1': resolution: {integrity: sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fastify/accept-negotiator@1.1.0': resolution: {integrity: sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==} engines: {node: '>=14'} @@ -1828,17 +2093,11 @@ packages: '@fastify/static@7.0.4': resolution: {integrity: sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==} - '@floating-ui/core@1.6.8': - resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} - - '@floating-ui/core@1.7.2': - resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==} - - '@floating-ui/dom@1.6.11': - resolution: {integrity: sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==} + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} - '@floating-ui/dom@1.7.2': - resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==} + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} '@floating-ui/react-dom@2.1.2': resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} @@ -1849,21 +2108,21 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - '@floating-ui/utils@0.2.8': - resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} - '@humanfs/core@0.19.0': resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==} engines: {node: '>=18.18.0'} + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + '@humanfs/node@0.16.5': resolution: {integrity: sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==} engines: {node: '>=18.18.0'} - '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -1873,14 +2132,14 @@ packages: resolution: {integrity: sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==} engines: {node: '>=10.10.0'} - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead - '@humanwhocodes/retry@0.3.1': resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@iarna/toml@2.2.5': resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} @@ -1895,13 +2154,16 @@ packages: resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - '@jridgewell/gen-mapping@0.3.10': - resolution: {integrity: sha512-HM2F4B9N4cA0RH2KQiIZOHAZqtP4xGS4IZ+SFe1SIbO4dyjf9MTY2Bo3vHYnm0hglWfXqBrzUBSa+cJfl3Xvrg==} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} + '@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'} @@ -1916,9 +2178,6 @@ packages: '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - '@jridgewell/sourcemap-codec@1.5.2': - resolution: {integrity: sha512-gKYheCylLIedI+CSZoDtGkFV9YEBxRRVcfCH7OfAqh4TyUyRjEE6WVE/aXDXX0p8BIe/QgLcaAoI0220KRRFgg==} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} @@ -1928,9 +2187,21 @@ packages: '@jridgewell/trace-mapping@0.3.27': resolution: {integrity: sha512-VO95AxtSFMelbg3ouljAYnfvTEwSWVt/2YLf+U5Ejd8iT5mXE2Sa/1LGyvySMne2CGsepGLI7KpF3EzE3Aq9Mg==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@keyv/bigmap@1.1.0': + resolution: {integrity: sha512-MX7XIUNwVRK+hjZcAbNJ0Z8DREo+Weu9vinBOjGU1thEi9F6vPhICzBbk4CCf3eEefKRz7n6TfZXwUFZTSgj8Q==} + engines: {node: '>= 18'} + peerDependencies: + keyv: ^5.5.3 + + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + '@lezer/common@1.2.3': resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} @@ -1971,6 +2242,70 @@ packages: resolution: {integrity: sha512-qFfbWFA7r1Sd8D697L7GkTd36yqDuTkvz0KfOGkgXR8EUhQn3/EDNIR/qUdQNMT8IjmasBvHWuXeisxtXTQT2g==} engines: {node: '>=18'} + '@napi-rs/canvas-android-arm64@0.1.80': + resolution: {integrity: sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/canvas-darwin-arm64@0.1.80': + resolution: {integrity: sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/canvas-darwin-x64@0.1.80': + resolution: {integrity: sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.80': + resolution: {integrity: sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/canvas-linux-arm64-gnu@0.1.80': + resolution: {integrity: sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/canvas-linux-arm64-musl@0.1.80': + resolution: {integrity: sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/canvas-linux-riscv64-gnu@0.1.80': + resolution: {integrity: sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + + '@napi-rs/canvas-linux-x64-gnu@0.1.80': + resolution: {integrity: sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/canvas-linux-x64-musl@0.1.80': + resolution: {integrity: sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/canvas-win32-x64-msvc@0.1.80': + resolution: {integrity: sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/canvas@0.1.80': + resolution: {integrity: sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==} + engines: {node: '>= 10'} + '@netlify/binary-info@1.0.0': resolution: {integrity: sha512-4wMPu9iN3/HL97QblBsBay3E1etIciR84izI3U+4iALY+JHCrI+a2jO0qbAZ/nxKoegypYEaiiqWXylm+/zfrw==} @@ -2336,17 +2671,17 @@ packages: '@polka/url@1.0.0-next.28': resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} - '@popperjs/core@2.11.8': - resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - '@radix-ui/number@1.1.0': resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} '@radix-ui/primitive@1.1.0': resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==} - '@radix-ui/react-arrow@1.1.0': - resolution: {integrity: sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==} + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2380,6 +2715,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-context@1.1.0': resolution: {integrity: sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==} peerDependencies: @@ -2398,6 +2742,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-direction@1.1.0': resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} peerDependencies: @@ -2407,8 +2760,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.1.1': - resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==} + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2420,8 +2773,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-focus-guards@1.1.1': - resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -2429,8 +2782,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-focus-scope@1.1.0': - resolution: {integrity: sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==} + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2451,8 +2804,17 @@ packages: '@types/react': optional: true - '@radix-ui/react-popover@1.1.2': - resolution: {integrity: sha512-u2HRUyWW+lOiA2g0Le0tMmT55FGOEWHwPFt1EPfbLly7uXQExFo5duNKqG2DzmFXIdqOeNd+TpE8baHWJCyP9w==} + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2464,8 +2826,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popper@1.2.0': - resolution: {integrity: sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==} + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2477,8 +2839,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-portal@1.1.2': - resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==} + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2503,8 +2865,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.0.0': - resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2516,8 +2878,34 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-roving-focus@1.1.0': - resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==} + '@radix-ui/react-primitive@2.0.0': + resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.0': + resolution: {integrity: sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2551,6 +2939,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-tabs@1.1.1': resolution: {integrity: sha512-3GBUDmP2DvzmtYLMsHmpA1GtR46ZDZ+OreXM/N+kkQJOPIgytFWWTfDQmBQKBvaFS0Vno0FktdbVzN28KGrMdw==} peerDependencies: @@ -2573,6 +2970,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-controllable-state@1.1.0': resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} peerDependencies: @@ -2582,8 +2988,26 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-escape-keydown@1.1.0': - resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -2600,8 +3024,17 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-rect@1.1.0': - resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -2609,8 +3042,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-size@1.1.0': - resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -2618,8 +3051,8 @@ packages: '@types/react': optional: true - '@radix-ui/rect@1.1.0': - resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} '@reactflow/background@11.3.14': resolution: {integrity: sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==} @@ -2660,6 +3093,9 @@ packages: '@remirror/core-constants@3.0.0': resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} + '@rolldown/pluginutils@1.0.0-beta.38': + resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==} + '@rollup/plugin-babel@5.3.1': resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} engines: {node: '>= 10.0.0'} @@ -2718,81 +3154,191 @@ packages: cpu: [arm] os: [android] + '@rollup/rollup-android-arm-eabi@4.52.5': + resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} + cpu: [arm] + os: [android] + '@rollup/rollup-android-arm64@4.24.0': resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} cpu: [arm64] os: [android] + '@rollup/rollup-android-arm64@4.52.5': + resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} + cpu: [arm64] + os: [android] + '@rollup/rollup-darwin-arm64@4.24.0': resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} cpu: [arm64] os: [darwin] + '@rollup/rollup-darwin-arm64@4.52.5': + resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} + cpu: [arm64] + os: [darwin] + '@rollup/rollup-darwin-x64@4.24.0': resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} cpu: [x64] os: [darwin] + '@rollup/rollup-darwin-x64@4.52.5': + resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.52.5': + resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.52.5': + resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} + cpu: [x64] + os: [freebsd] + '@rollup/rollup-linux-arm-gnueabihf@4.24.0': resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.24.0': resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.52.5': + resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.24.0': resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.52.5': + resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} + cpu: [arm64] + os: [linux] + '@rollup/rollup-linux-arm64-musl@4.24.0': resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-musl@4.52.5': + resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.52.5': + resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} + cpu: [loong64] + os: [linux] + '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} cpu: [ppc64] os: [linux] + '@rollup/rollup-linux-ppc64-gnu@4.52.5': + resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} + cpu: [ppc64] + os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.24.0': resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} cpu: [riscv64] os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.52.5': + resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.52.5': + resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} + cpu: [riscv64] + os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.24.0': resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} cpu: [s390x] os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.52.5': + resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} + cpu: [s390x] + os: [linux] + '@rollup/rollup-linux-x64-gnu@4.24.0': resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-gnu@4.52.5': + resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} + cpu: [x64] + os: [linux] + '@rollup/rollup-linux-x64-musl@4.24.0': resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-musl@4.52.5': + resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.52.5': + resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} + cpu: [arm64] + os: [openharmony] + '@rollup/rollup-win32-arm64-msvc@4.24.0': resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} cpu: [arm64] os: [win32] + '@rollup/rollup-win32-arm64-msvc@4.52.5': + resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} + cpu: [arm64] + os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.24.0': resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} cpu: [ia32] os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.52.5': + resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.52.5': + resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} + cpu: [x64] + os: [win32] + '@rollup/rollup-win32-x64-msvc@4.24.0': resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-msvc@4.52.5': + resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} + cpu: [x64] + os: [win32] + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -2840,8 +3386,8 @@ packages: '@surma/rollup-plugin-off-main-thread@2.2.3': resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} - '@sveltejs/acorn-typescript@1.0.5': - resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} + '@sveltejs/acorn-typescript@1.0.6': + resolution: {integrity: sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==} peerDependencies: acorn: ^8.9.0 @@ -2988,177 +3534,260 @@ packages: '@tanstack/store@0.7.0': resolution: {integrity: sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg==} - '@tiptap/core@2.11.7': - resolution: {integrity: sha512-zN+NFFxLsxNEL8Qioc+DL6b8+Tt2bmRbXH22Gk6F6nD30x83eaUSFlSv3wqvgyCq3I1i1NO394So+Agmayx6rQ==} + '@tiptap/core@3.7.2': + resolution: {integrity: sha512-fJwNpTx0aq4UU0HNkxPvPYfNBcTHQ/q5xBUdOB5Mgu6clwGES38jVsNNSudB8g53APUmJIS+2fJbkxl3V+0jww==} + peerDependencies: + '@tiptap/pm': ^3.7.2 + + '@tiptap/extension-blockquote@3.7.2': + resolution: {integrity: sha512-8rNDh1E1ratex9KicvNNnjJGtF313Kpf5hXHOUcIm8FQwvA/0Tu6jq7r6VgESMyo95R3EmzRpnCYQef+zDm6OQ==} + peerDependencies: + '@tiptap/core': ^3.7.2 + + '@tiptap/extension-bold@3.7.2': + resolution: {integrity: sha512-bwCn9lQEXnEi7LfIx3G/oaH4I0ZapAgrHzLCNJH/tNgRKVWym1H1Oa8PlkiFDbalWOdUkbgeAUqUaIB13k408Q==} + peerDependencies: + '@tiptap/core': ^3.7.2 + + '@tiptap/extension-bubble-menu@3.7.2': + resolution: {integrity: sha512-rCJu/X7sZEYWkOwLO342JP06f4giVBECPzr/SzG/fQdAidPW96eilPk3L82w5j24kS9odTlxSLlFlIf6UZ2b9w==} + peerDependencies: + '@tiptap/core': ^3.7.2 + '@tiptap/pm': ^3.7.2 + + '@tiptap/extension-bullet-list@3.7.2': + resolution: {integrity: sha512-OHYYXKjmxisLQws0tW8Dz14PcyIJmaed7eypZvIm/R3hxa/7lJY/2EM/Ti5g/w1U8WPBEH1hX3icRtiulserKw==} + peerDependencies: + '@tiptap/extension-list': ^3.7.2 + + '@tiptap/extension-code-block@3.7.2': + resolution: {integrity: sha512-TfixutvvbGCrSSCsfDK/PBm6A5FIzcPTSVDrmmsiAfqldj/Woy1T42dads+wv9SjKG06GlWDwYtDGAk2Uun8NA==} + peerDependencies: + '@tiptap/core': ^3.7.2 + '@tiptap/pm': ^3.7.2 + + '@tiptap/extension-code@3.7.2': + resolution: {integrity: sha512-J8FaCiKJJnHvQiPcbfbUtc5RNmGx/Gui/K5CDMPc17jhCiQ9JhR9idRPREV24Z2t7GujWX7LG6ZDDR82pSns+g==} + peerDependencies: + '@tiptap/core': ^3.7.2 + + '@tiptap/extension-collaboration-caret@3.7.2': + resolution: {integrity: sha512-guOhgA2gYS4wRRbpOkkcaSpruqZVlJ3Xqb379n0lwXrZONorFTOHl7/kan4Da4RM2IoaTg73OSjQkChyEAcvuw==} + peerDependencies: + '@tiptap/core': ^3.7.2 + '@tiptap/pm': ^3.7.2 + '@tiptap/y-tiptap': ^3.0.0-beta.3 + + '@tiptap/extension-collaboration@3.7.2': + resolution: {integrity: sha512-eIwFBQca7hz8p+UXtn9/B9p45qCFuKLTdCgog9bJjLY6K1ObY0/9fmraxou59Qym57XLs5cm0tc2Db1O5rOxkw==} + peerDependencies: + '@tiptap/core': ^3.7.2 + '@tiptap/pm': ^3.7.2 + '@tiptap/y-tiptap': ^3.0.0-beta.3 + yjs: ^13 + + '@tiptap/extension-document@3.7.2': + resolution: {integrity: sha512-OrHl402v2FWCUKR1Xi5MTNBAkKYQh7mtpw/WlJDFnk5z1qHLqz4UIcbGilDYzVPrNUZPhA1p3c+V5UUVUFzUfg==} + peerDependencies: + '@tiptap/core': ^3.7.2 + + '@tiptap/extension-drag-handle-react@3.7.2': + resolution: {integrity: sha512-WCgbdHNGjtcWIo1CYQhrKE3vEW/tKSAar/0ezxp48UJiSN79mOXq7R/hoC+DXfBUkEO3dkJEuLgoT0XV4uymWQ==} + peerDependencies: + '@tiptap/extension-drag-handle': ^3.7.2 + '@tiptap/pm': ^3.7.2 + '@tiptap/react': ^3.7.2 + react: ^16.8 || ^17 || ^18 || ^19 + react-dom: ^16.8 || ^17 || ^18 || ^19 + + '@tiptap/extension-drag-handle@3.7.2': + resolution: {integrity: sha512-YFnknAu+yuaDwvNdRm/hdgxnIfqYw/dM3o0C32zztvrhd8CE7gINcloF+O+HLxyZ5ut+gjm33QTqQqP/l550pA==} peerDependencies: - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.7.2 + '@tiptap/extension-collaboration': ^3.7.2 + '@tiptap/extension-node-range': ^3.7.2 + '@tiptap/pm': ^3.7.2 + '@tiptap/y-tiptap': ^3.0.0-beta.3 - '@tiptap/extension-blockquote@2.11.7': - resolution: {integrity: sha512-liD8kWowl3CcYCG9JQlVx1eSNc/aHlt6JpVsuWvzq6J8APWX693i3+zFqyK2eCDn0k+vW62muhSBe3u09hA3Zw==} + '@tiptap/extension-dropcursor@3.7.2': + resolution: {integrity: sha512-79y6M9pJYwqcqBHIWoomfptJp0QB/TP3Y+2NOL09sMNeSdUgmz5pCVObA4H48YMkoB0EcUtux2IUOM66e4nsJA==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extensions': ^3.7.2 - '@tiptap/extension-bold@2.11.7': - resolution: {integrity: sha512-VTR3JlldBixXbjpLTFme/Bxf1xeUgZZY3LTlt5JDlCW3CxO7k05CIa+kEZ8LXpog5annytZDUVtWqxrNjmsuHQ==} + '@tiptap/extension-file-handler@3.7.2': + resolution: {integrity: sha512-tdWsrZO+InXcP3jpSJd8qlCa6uNcZ/q1yARPLGsY6RKcGAq3ZmuOVkquRTOE5181kL34WtptUbQb+qQorMTXdw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.7.2 + '@tiptap/extension-text-style': ^3.7.2 + '@tiptap/pm': ^3.7.2 - '@tiptap/extension-bubble-menu@2.11.7': - resolution: {integrity: sha512-0vYqSUSSap3kk3/VT4tFE1/6StX70I3/NKQ4J68ZSFgkgyB3ZVlYv7/dY3AkEukjsEp3yN7m8Gw8ei2eEwyzwg==} + '@tiptap/extension-floating-menu@3.7.2': + resolution: {integrity: sha512-g19ratrXlplYDS29VLQa1y/IM/ro0UFhSS4fQokiQKkazwnA1ZVnebjw8ERYg5lkMm/hiImqstpgdO0LtoivvQ==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@floating-ui/dom': ^1.0.0 + '@tiptap/core': ^3.7.2 + '@tiptap/pm': ^3.7.2 - '@tiptap/extension-bullet-list@2.11.7': - resolution: {integrity: sha512-WbPogE2/Q3e3/QYgbT1Sj4KQUfGAJNc5pvb7GrUbvRQsAh7HhtuO8hqdDwH8dEdD/cNUehgt17TO7u8qV6qeBw==} + '@tiptap/extension-gapcursor@3.7.2': + resolution: {integrity: sha512-vCLo2dL2SfeWjh/gJKDiu0/fz6OF7obGTJvHg/yStkoUqlAEiwKoyHP/NXeTGYJMzZzUi0kY9DtTEJdGFvphuQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extensions': ^3.7.2 - '@tiptap/extension-code-block@2.11.7': - resolution: {integrity: sha512-To/y/2H04VWqiANy53aXjV7S6fA86c2759RsH1hTIe57jA1KyE7I5tlAofljOLZK/covkGmPeBddSPHGJbz++Q==} + '@tiptap/extension-hard-break@3.7.2': + resolution: {integrity: sha512-nNDo+5S1yRQ3JkBM+gwpEEVZ/Kw9qWoG/cpShyGYDHo1/y8MgO+VI0kSb/LuBTw7g+jmNXdf+ZaRRI/pXsUihg==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.7.2 - '@tiptap/extension-code@2.11.7': - resolution: {integrity: sha512-VpPO1Uy/eF4hYOpohS/yMOcE1C07xmMj0/D989D9aS1x95jWwUVrSkwC+PlWMUBx9PbY2NRsg1ZDwVvlNKZ6yQ==} + '@tiptap/extension-heading@3.7.2': + resolution: {integrity: sha512-eH/G66FIRlTQz4MhEmlNNNQgVTxhoqlkyFzgeG5aipIplYOdYa5Y6Wl0NF4xqr1jAHGLAK6LaYS4FXp3TE7LyA==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.7.2 - '@tiptap/extension-document@2.11.7': - resolution: {integrity: sha512-95ouJXPjdAm9+VBRgFo4lhDoMcHovyl/awORDI8gyEn0Rdglt+ZRZYoySFzbVzer9h0cre+QdIwr9AIzFFbfdA==} + '@tiptap/extension-horizontal-rule@3.7.2': + resolution: {integrity: sha512-pN+1hJAVVP3uqtpZ5Rm7z5XUB/NGprK6wExJ04xG117E4rTVcaEb1FnMILY3J3A5XbdC3vHX+cblR8mOl1PAMw==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.7.2 + '@tiptap/pm': ^3.7.2 - '@tiptap/extension-dropcursor@2.11.7': - resolution: {integrity: sha512-63mL+nxQILizsr5NbmgDeOjFEWi34BLt7evwL6UUZEVM15K8V1G8pD9Y0kCXrZYpHWz0tqFRXdrhDz0Ppu8oVw==} + '@tiptap/extension-image@3.7.2': + resolution: {integrity: sha512-GlFdoZULF9mEG3tMRqB1DDlyA75NIRHS5NKuoicQTAX9OegiZfTPYRmVOpLNaTunTt8mFL6Wx2Z9x5ZN2WdNBg==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.7.2 - '@tiptap/extension-file-handler@2.25.0': - resolution: {integrity: sha512-8qALTIz8rRumHP1vXYwCJ8IflfWJ8b9PMc/pcTOmyMfpUzwSn9tO6iVjuvWr5VgrcSgWErJB3YUq/1JiyxiCDA==} + '@tiptap/extension-italic@3.7.2': + resolution: {integrity: sha512-1tfF37LvKgA5hg09UBgOjdMLNRb1C6keIOBF0r5oHKeWPYOf4z3j5IU9PsFUoOn53XRMb1aiD/TNbGPyoT3Fyw==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/extension-text-style': ^2.7.0 + '@tiptap/core': ^3.7.2 - '@tiptap/extension-floating-menu@2.14.0': - resolution: {integrity: sha512-Khx7M7RfZlD1/T/PUlpJmao6FtEBa2L6td2hhaW1USflwGJGk0U/ud4UEqh+aZoJZrkot/EMhEvzmORF3nq+xw==} + '@tiptap/extension-link@3.7.2': + resolution: {integrity: sha512-9K54PxBiDSWAMfICqkb8jcQ6cL7vDAtjTk0zqBw4d+XuaUy0FC9QUdbx7r1Pkbf36K1/ApbvM9a7qpOirWk8Xw==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.7.2 + '@tiptap/pm': ^3.7.2 - '@tiptap/extension-gapcursor@2.11.7': - resolution: {integrity: sha512-EceesmPG7FyjXZ8EgeJPUov9G1mAf2AwdypxBNH275g6xd5dmU/KvjoFZjmQ0X1ve7mS+wNupVlGxAEUYoveew==} + '@tiptap/extension-list-item@3.7.2': + resolution: {integrity: sha512-962TFsx4eF5NMyLVhGFGF/btt5j3MipPhDiUsxzBgnlW8o5OonVepb9cDrqpEDQ2/wLvheWnCKuvmG7umasldQ==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/extension-list': ^3.7.2 - '@tiptap/extension-hard-break@2.11.7': - resolution: {integrity: sha512-zTkZSA6q+F5sLOdCkiC2+RqJQN0zdsJqvFIOVFL/IDVOnq6PZO5THzwRRLvOSnJJl3edRQCl/hUgS0L5sTInGQ==} + '@tiptap/extension-list-keymap@3.7.2': + resolution: {integrity: sha512-1du9eo+NPIkuRT258yUn9bovhip556aJo/yDtRbswEVNScP1E8y/kFRWvw0HD7/YWcNqok1ZteoSwShWnKAXRQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extension-list': ^3.7.2 - '@tiptap/extension-heading@2.11.7': - resolution: {integrity: sha512-8kWh7y4Rd2fwxfWOhFFWncHdkDkMC1Z60yzIZWjIu72+6yQxvo8w3yeb7LI7jER4kffbMmadgcfhCHC/fkObBA==} + '@tiptap/extension-list@3.7.2': + resolution: {integrity: sha512-/tYHmEkOGcVweAc9ZgnAXkzua5aJfu7TjZcKTq5fmDt6x9MY1eY1+egS7D9hVR2sUSAC10VgXmYdYPDsKF3p2g==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.7.2 + '@tiptap/pm': ^3.7.2 - '@tiptap/extension-history@2.11.7': - resolution: {integrity: sha512-Cu5x3aS13I040QSRoLdd+w09G4OCVfU+azpUqxufZxeNs9BIJC+0jowPLeOxKDh6D5GGT2A8sQtxc6a/ssbs8g==} + '@tiptap/extension-mention@3.7.2': + resolution: {integrity: sha512-y8ldoGItWii6DY+db37BqdmHIbwrIV7b7Lz0uI3lhb3tNNkjaa84XRTUK7mySXrkzp/FMvw8MXCTUF44aQdFZQ==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.7.2 + '@tiptap/pm': ^3.7.2 + '@tiptap/suggestion': ^3.7.2 - '@tiptap/extension-horizontal-rule@2.11.7': - resolution: {integrity: sha512-uVmQwD2dzZ5xwmvUlciy0ItxOdOfQjH6VLmu80zyJf8Yu7mvwP8JyxoXUX0vd1xHpwAhgQ9/ozjIWYGIw79DPQ==} + '@tiptap/extension-node-range@3.7.2': + resolution: {integrity: sha512-j4ZkxEhf1QF97OO/SiHcCceTzGstcjl4Bt4XtZoK++9N3tTKly8gUIYis+IjxAa0TiNyQPbnJvT3fon2iwtTLg==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.7.2 + '@tiptap/pm': ^3.7.2 - '@tiptap/extension-image@2.11.7': - resolution: {integrity: sha512-YvCmTDB7Oo+A56tR4S/gcNaYpqU4DDlSQcRp5IQvmQV5EekSe0lnEazGDoqOCwsit9qQhj4MPQJhKrnaWrJUrg==} + '@tiptap/extension-ordered-list@3.7.2': + resolution: {integrity: sha512-Tu61/JXh1RRd3Kb+s7A7jmpnB+w1pqGSRfMXBtYHDHDIGyXu255ru7soX44lJfHGq/zYcTFSHGSsi8o23QONJg==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/extension-list': ^3.7.2 - '@tiptap/extension-italic@2.11.7': - resolution: {integrity: sha512-r985bkQfG0HMpmCU0X0p/Xe7U1qgRm2mxvcp6iPCuts2FqxaCoyfNZ8YnMsgVK1mRhM7+CQ5SEg2NOmQNtHvPw==} + '@tiptap/extension-paragraph@3.7.2': + resolution: {integrity: sha512-HmDuAixTcvP4A/v6OLkh/C6nB86i7/DRNswBf/Udak8TgWUIcSUK0iActxxm5+B3MZTSf3U87JzyI6IeuElLIQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.7.2 - '@tiptap/extension-link@2.11.7': - resolution: {integrity: sha512-qKIowE73aAUrnQCIifYP34xXOHOsZw46cT/LBDlb0T60knVfQoKVE4ku08fJzAV+s6zqgsaaZ4HVOXkQYLoW7g==} + '@tiptap/extension-placeholder@3.7.2': + resolution: {integrity: sha512-YUr1rlxkgEBQDsMLpU8ruA4Uet37kXvwwFwIbDgaFd4NpfAD0fvX2zmPhHIBzsdH3e4V6eNp6IkmoYCWvugAAA==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/extensions': ^3.7.2 - '@tiptap/extension-list-item@2.11.7': - resolution: {integrity: sha512-6ikh7Y+qAbkSuIHXPIINqfzmWs5uIGrylihdZ9adaIyvrN1KSnWIqrZIk/NcZTg5YFIJlXrnGSRSjb/QM3WUhw==} + '@tiptap/extension-strike@3.7.2': + resolution: {integrity: sha512-I1G+4vZbCBTpAMmyVwaO8cLBJgXEf1DyEzc0B+HhTJiBa9qA9OKgRQEGFgisxu1kggjbzB6+d0+taHfjsZC1SQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.7.2 - '@tiptap/extension-mention@2.11.7': - resolution: {integrity: sha512-Q/fkceDOug4VjiqrCRLzBnOL9Oj+XugWwDgwfucJJMBOJxZ3++3eZGZ54dri/xK39A4ZD+xuMBF7PrJIy+Z5dw==} + '@tiptap/extension-table@3.10.5': + resolution: {integrity: sha512-kuMgvrZBGsYtTcN8t8dN92xez99OY251Yig2B3/3aOIqapMkAFTJ2tZVPeTSiGSJpo1Tuw6ly2hs4neOOjpzvQ==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 - '@tiptap/suggestion': ^2.7.0 + '@tiptap/core': ^3.10.5 + '@tiptap/pm': ^3.10.5 - '@tiptap/extension-ordered-list@2.11.7': - resolution: {integrity: sha512-bLGCHDMB0vbJk7uu8bRg8vES3GsvxkX7Cgjgm/6xysHFbK98y0asDtNxkW1VvuRreNGz4tyB6vkcVCfrxl4jKw==} + '@tiptap/extension-text-align@3.7.2': + resolution: {integrity: sha512-tUdoatcxM8u16tFVfEURFZwmxvZQR33f9VLtkyR+1aXgy0Pi87cNoFC60pTjH7gNtktEuagNfPE00tGMvqIehg==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.7.2 - '@tiptap/extension-paragraph@2.11.7': - resolution: {integrity: sha512-Pl3B4q6DJqTvvAdraqZaNP9Hh0UWEHL5nNdxhaRNuhKaUo7lq8wbDSIxIW3lvV0lyCs0NfyunkUvSm1CXb6d4Q==} + '@tiptap/extension-text-style@3.7.2': + resolution: {integrity: sha512-afbEnk+Cf9tOfnM+dcKRtyAVb99JZRzUd2qTGqqoEJuySRk5KckVBZSkwGAt6TiIKpPlmwHHB5YTdMx9Fg+tbQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.7.2 - '@tiptap/extension-placeholder@2.11.7': - resolution: {integrity: sha512-/06zXV4HIjYoiaUq1fVJo/RcU8pHbzx21evOpeG/foCfNpMI4xLU/vnxdUi6/SQqpZMY0eFutDqod1InkSOqsg==} + '@tiptap/extension-text@3.7.2': + resolution: {integrity: sha512-sKaeGYNP1+bAe2rvmzWLW5qH9DsSFOJlOUEOFchR0OX0rC7bbGS6/KuyAq0w6UkL+cMJnDyAbv3KeD2WEA192w==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.7.2 - '@tiptap/extension-strike@2.11.7': - resolution: {integrity: sha512-D6GYiW9F24bvAY7XMOARNZbC8YGPzdzWdXd8VOOJABhf4ynMi/oW4NNiko+kZ67jn3EGaKoz32VMJzNQgYi1HA==} + '@tiptap/extension-typography@3.7.2': + resolution: {integrity: sha512-2yW3gRVm+9G7INEFj9jaL5otCw7I/271VJW25PNYNh3ERtV54rO0UjfSykMSqu70OkNzGQtYE7nixPGXpOrulQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.7.2 - '@tiptap/extension-text-style@2.11.7': - resolution: {integrity: sha512-LHO6DBg/9SkCQFdWlVfw9nolUmw+Cid94WkTY+7IwrpyG2+ZGQxnKpCJCKyeaFNbDoYAtvu0vuTsSXeCkgShcA==} + '@tiptap/extension-underline@3.7.2': + resolution: {integrity: sha512-GDpUZllTD7uIdHjTzYJ6i4jUgCeviW40SCpLVVv1xH0gj1t1xu0Rnxmk+bXkF2XNe8jPXkMCgYNr6DR6eO8roQ==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.7.2 - '@tiptap/extension-text@2.11.7': - resolution: {integrity: sha512-wObCn8qZkIFnXTLvBP+X8KgaEvTap/FJ/i4hBMfHBCKPGDx99KiJU6VIbDXG8d5ZcFZE0tOetK1pP5oI7qgMlQ==} + '@tiptap/extensions@3.7.2': + resolution: {integrity: sha512-FaToSdU9fhQk2swkaXrAQNgdaE0dwLbUHcvilW5F4xTpQfZ3J535u5U2TUYf+f9KKSV5fTmD4QGNY9qxY7ihTg==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.7.2 + '@tiptap/pm': ^3.7.2 - '@tiptap/extension-typography@2.11.7': - resolution: {integrity: sha512-qyYROxuXuMAMw30RXFYjr9mfZv+7avD3BW+fVEIa3lwnUMFNExHj6j2HMgYvrPVByGXlQU/4uHWcB0uiG0Bf1w==} + '@tiptap/markdown@3.7.2': + resolution: {integrity: sha512-0cdCYYHdBDXcwjZsTOSySbdHQuHZct6nxvcp4dSVpP25kbZL3ONSJvLY5Nsy3rkXlmhk9qbyFwsexGiSIdFy8Q==} peerDependencies: - '@tiptap/core': ^2.7.0 + '@tiptap/core': ^3.7.2 + '@tiptap/pm': ^3.7.2 - '@tiptap/pm@2.11.7': - resolution: {integrity: sha512-7gEEfz2Q6bYKXM07vzLUD0vqXFhC5geWRA6LCozTiLdVFDdHWiBrvb2rtkL5T7mfLq03zc1QhH7rI3F6VntOEA==} + '@tiptap/pm@3.7.2': + resolution: {integrity: sha512-i2fvXDapwo/TWfHM6STYEbkYyF3qyfN6KEBKPrleX/Z80G5bLxom0gB79TsjLNxTLi6mdf0vTHgAcXMG1avc2g==} - '@tiptap/react@2.11.7': - resolution: {integrity: sha512-gQZEUkAoPsBptnB4T2gAtiUxswjVGhfsM9vOElQco+b11DYmy110T2Zuhg+2YGvB/CG3RoWJx34808P0FX1ijA==} + '@tiptap/react@3.7.2': + resolution: {integrity: sha512-tka4ioSmsGI4TyGZ7jAUoIw8t8DVjr1It0B38vZVLqg8M/ZFgR1NkF50TJ6qAkhy8Uz12AO50so0v79tV2pmEA==} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + '@tiptap/core': ^3.7.2 + '@tiptap/pm': ^3.7.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@2.11.7': - resolution: {integrity: sha512-K+q51KwNU/l0kqRuV5e1824yOLVftj6kGplGQLvJG56P7Rb2dPbM/JeaDbxQhnHT/KDGamG0s0Po0M3pPY163A==} + '@tiptap/starter-kit@3.7.2': + resolution: {integrity: sha512-43GwI+2Mtc/ci7J4eJOE02wZxp5KIsDTMMb0peNksPcEGaURGdDhav9zbAW24NRjRxU7Auk/zaQu9O8+ZE0v0A==} + + '@tiptap/suggestion@3.7.2': + resolution: {integrity: sha512-CYmIMeLqeGBotl7+4TrnGux/ov9IJoWTUQN/JcHp0aOoN3z8c/dQ6cziXXknr51jGHSdVYMWEyamLDZfcaGC1w==} + peerDependencies: + '@tiptap/core': ^3.7.2 + '@tiptap/pm': ^3.7.2 - '@tiptap/suggestion@2.11.7': - resolution: {integrity: sha512-I1ckVAEErpErPn/H9ZdDmTb5zuPNPiKj3krxCtJDUU4+3we0cgJY9NQFXl9//mrug3UIngH0ZQO+arbZfIk75A==} + '@tiptap/y-tiptap@3.0.0': + resolution: {integrity: sha512-HIeJZCj+KYJde2x6fONzo4o6kd7gW7eonwhQsv2p2VQnUgwNXMVhN+D6Z3AH/2i541Sq33y1PO4U/1ThCPjqbA==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} peerDependencies: - '@tiptap/core': ^2.7.0 - '@tiptap/pm': ^2.7.0 + prosemirror-model: ^1.7.1 + prosemirror-state: ^1.2.3 + prosemirror-view: ^1.9.10 + y-protocols: ^1.0.1 + yjs: ^13.5.38 '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} @@ -3375,6 +4004,9 @@ packages: '@types/node@20.17.0': resolution: {integrity: sha512-a7zRo0f0eLo9K5X9Wp5cAqTUNGzuFLDG2R7C4HY2BhcMAsxgSPuRvAC1ZB6QkuUQXf0YZAgfOX2ZyrBa2n4nHQ==} + '@types/node@24.7.0': + resolution: {integrity: sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -3387,8 +4019,10 @@ packages: '@types/pug@2.0.10': resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} - '@types/react-dom@19.0.1': - resolution: {integrity: sha512-hljHij7MpWPKF6u5vojuyfV0YA4YURsQG7KT6SzV0Zs2BXAtgdTxG6A229Ub/xiWV4w/7JL8fi6aAyjshH4meA==} + '@types/react-dom@19.2.2': + resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==} + peerDependencies: + '@types/react': ^19.2.0 '@types/react-router-dom@5.3.3': resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} @@ -3399,8 +4033,8 @@ packages: '@types/react-window@1.8.8': resolution: {integrity: sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==} - '@types/react@19.0.1': - resolution: {integrity: sha512-YW6614BDhqbpR5KtUYzTA+zlA7nayzJRA9ljz9CQoxthR0sDisYZLuvSMsil36t4EH/uAt8T52Xb4sVw17G+SQ==} + '@types/react@19.2.2': + resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} @@ -3435,17 +4069,6 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@7.18.0': - resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@typescript-eslint/eslint-plugin@8.11.0': resolution: {integrity: sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3457,15 +4080,13 @@ packages: typescript: optional: true - '@typescript-eslint/parser@7.18.0': - resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/eslint-plugin@8.46.4': + resolution: {integrity: sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/parser': ^8.46.4 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/parser@8.11.0': resolution: {integrity: sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==} @@ -3477,23 +4098,32 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@7.18.0': - resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/parser@8.46.4': + resolution: {integrity: sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.46.4': + resolution: {integrity: sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/scope-manager@8.11.0': resolution: {integrity: sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@7.18.0': - resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/scope-manager@8.46.4': + resolution: {integrity: sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.46.4': + resolution: {integrity: sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/type-utils@8.11.0': resolution: {integrity: sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==} @@ -3504,18 +4134,25 @@ packages: typescript: optional: true + '@typescript-eslint/type-utils@8.46.4': + resolution: {integrity: sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/types@5.62.0': resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@typescript-eslint/types@7.18.0': - resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} - engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/types@8.11.0': resolution: {integrity: sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.46.4': + resolution: {integrity: sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@5.62.0': resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3525,15 +4162,6 @@ packages: typescript: optional: true - '@typescript-eslint/typescript-estree@7.18.0': - resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - '@typescript-eslint/typescript-estree@8.11.0': resolution: {integrity: sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3543,11 +4171,11 @@ packages: typescript: optional: true - '@typescript-eslint/utils@7.18.0': - resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/typescript-estree@8.46.4': + resolution: {integrity: sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 + typescript: '>=4.8.4 <6.0.0' '@typescript-eslint/utils@8.11.0': resolution: {integrity: sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==} @@ -3555,18 +4183,25 @@ packages: peerDependencies: eslint: ^8.57.0 || ^9.0.0 + '@typescript-eslint/utils@8.46.4': + resolution: {integrity: sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/visitor-keys@5.62.0': resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@typescript-eslint/visitor-keys@7.18.0': - resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} - engines: {node: ^18.18.0 || >=20.0.0} - '@typescript-eslint/visitor-keys@8.11.0': resolution: {integrity: sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.46.4': + resolution: {integrity: sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@uiw/codemirror-extensions-basic-setup@4.24.1': resolution: {integrity: sha512-o1m1a8eUS3fWERMbDFvN8t8sZUFPgDKNemmlQ5Ot2vKm+Ax84lKP1dhEFgkiOaZ1bDHk4T5h6SjHuTghrJHKww==} peerDependencies: @@ -3607,11 +4242,11 @@ packages: engines: {node: '>=16'} hasBin: true - '@vitejs/plugin-react@4.3.4': - resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} - engines: {node: ^14.18.0 || >=16.0.0} + '@vitejs/plugin-react@5.0.4': + resolution: {integrity: sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==} + engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 '@vitest/expect@2.1.3': resolution: {integrity: sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==} @@ -3643,8 +4278,8 @@ packages: '@vitest/utils@2.1.3': resolution: {integrity: sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==} - '@wuchale/jsx@0.7.4': - resolution: {integrity: sha512-wyXFYTd8IPs9aQNqPf34mmaMpWAc7i2SKe0CwAMDlKA9ags2SLKdZmvxzzjJjCz5ahiBf9jYnIHWa4npGCpJMA==} + '@wuchale/jsx@0.9.4': + resolution: {integrity: sha512-IWDXB05jWuMbL5qusb+LWFwrq9yQOAOMEE6lXPPENM4ShpQjZnwBhkMg1vqDod0dprtUfZIzuUUnpxU6zMf6Og==} peerDependencies: react: ^19.1.1 solid-js: ^1.9.9 @@ -3654,8 +4289,8 @@ packages: solid-js: optional: true - '@wuchale/vite-plugin@0.14.6': - resolution: {integrity: sha512-/OPj/tK56xco16YscktzKbdc8hy0MtoEw4Bl5ifACxbTIFZ8CwFudiM37PrIwWqPHPlpzlxTjFdeauIu4X5qow==} + '@wuchale/vite-plugin@0.15.3': + resolution: {integrity: sha512-GtPT1gwAGJAb1oc7R5MuGyl1R8m50QdoqFiW/76g2e81d9iPUYNi/CTg8BEeg4WmQJ+91JRR//rwgEo+jG0fZQ==} '@xhmikosr/archive-type@6.0.1': resolution: {integrity: sha512-PB3NeJL8xARZt52yDBupK0dNPn8uIVQDe15qNehUpoeeLWCZyAOam4vGXnoZGz2N9D1VXtjievJuCsXam2TmbQ==} @@ -3952,6 +4587,10 @@ packages: resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} engines: {node: '>= 0.4'} + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + array.prototype.tosorted@1.1.4: resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} engines: {node: '>= 0.4'} @@ -4058,8 +4697,8 @@ packages: peerDependencies: prismjs: ^1.18.0 - babel-plugin-react-compiler@19.1.0-rc.2: - resolution: {integrity: sha512-kSNA//p5fMO6ypG8EkEVPIqAjwIXm5tMjfD1XRPL/sRjYSbJ6UsvORfaeolNWnZ9n310aM0xJP7peW26BuCVzA==} + babel-plugin-react-compiler@1.0.0: + resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==} babel-plugin-styled-components@2.1.4: resolution: {integrity: sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==} @@ -4091,10 +4730,6 @@ packages: bare-stream@2.3.2: resolution: {integrity: sha512-EFZHSIBkDgSHIwj2l2QZfP4U5OcD4xFAOwhSb/vlr9PIqyGJGvB/nfClJbcnh3EY4jtPE4zsb5ztae96bVF79A==} - base64-arraybuffer@1.0.2: - resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} - engines: {node: '>= 0.6.0'} - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -4213,6 +4848,9 @@ packages: resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} engines: {node: '>=14.16'} + cacheable@2.1.1: + resolution: {integrity: sha512-LmF4AXiSNdiRbI2UjH8pAp9NIXxeQsTotpEaegPiDcnN0YPygDJDV3l/Urc0mL72JWdATEorKqIHEx55nDlONg==} + cachedir@2.4.0: resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} engines: {node: '>=6'} @@ -4258,10 +4896,6 @@ packages: caniuse-lite@1.0.30001726: resolution: {integrity: sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==} - canvas@2.11.2: - resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==} - engines: {node: '>=6'} - ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -4624,10 +5258,6 @@ packages: resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} engines: {node: '>=12.0.0'} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -4785,15 +5415,6 @@ packages: supports-color: optional: true - debug@4.4.0: - resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -4809,10 +5430,6 @@ packages: decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} - decompress-response@4.2.1: - resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==} - engines: {node: '>=8'} - decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -4953,10 +5570,6 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -5094,8 +5707,8 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-iterator-helpers@1.1.0: - resolution: {integrity: sha512-/SurEfycdyssORP/E+bj4sEu1CWw4EmLDsHynHwSXQ7utgbrMRWW195pTrCjFgFCddf/UkYm3oqKPRq5i8bJbw==} + es-iterator-helpers@1.2.1: + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} engines: {node: '>= 0.4'} es-module-lexer@1.5.4: @@ -5154,6 +5767,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.25.11: + resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -5228,8 +5846,8 @@ packages: '@typescript-eslint/parser': optional: true - eslint-plugin-jsx-a11y@6.10.1: - resolution: {integrity: sha512-zHByM9WTUMnfsDTafGXRiqxp6lFtNoSOWBY6FonVRn3A+BUwN1L/tdBXT40BcBJi0cZjOGTXZ0eD/rTG9fEJ0g==} + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} engines: {node: '>=4.0'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 @@ -5248,14 +5866,14 @@ packages: eslint-config-prettier: optional: true - eslint-plugin-react-hooks@6.0.0-rc.1: - resolution: {integrity: sha512-7C4c7bdtd/B7Q+HruZxYhGjwZVvJawvQpilEYlRG1Jncuk1ZNqrFy9bO8SJNieyj3iDh8WPQA7BzzPO7sNAyEA==} + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} engines: {node: '>=18'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - eslint-plugin-react@7.37.2: - resolution: {integrity: sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==} + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 @@ -5278,6 +5896,10 @@ packages: resolution: {integrity: sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5286,11 +5908,9 @@ packages: resolution: {integrity: sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint@9.13.0: resolution: {integrity: sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==} @@ -5302,6 +5922,16 @@ packages: jiti: optional: true + eslint@9.39.0: + resolution: {integrity: sha512-iy2GE3MHrYTL5lrCtMZ0X1KLEKKUjmK0kzwcnefhR66txcEmXZD2YWgR5GNdcEwkNx3a0siYkSvl0vIC+Svjmg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + esm-env@1.0.0: resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==} @@ -5309,6 +5939,10 @@ packages: resolution: {integrity: sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -5500,14 +6134,6 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - fdir@6.4.4: - resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -5547,10 +6173,6 @@ packages: resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} engines: {node: '>=14'} - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -5633,20 +6255,15 @@ packages: resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} engines: {node: '>=18'} - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flat-cache@5.0.0: - resolution: {integrity: sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==} - engines: {node: '>=18'} + flat-cache@6.1.18: + resolution: {integrity: sha512-JUPnFgHMuAVmLmoH9/zoZ6RHOt5n9NlUw/sDXsTbROJ2SFoS2DS4s+swAV6UTeTbGH/CAsZIE6M8TaG/3jVxgQ==} - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} flush-write-stream@2.0.0: resolution: {integrity: sha512-uXClqPxT4xW0lcdSBheb2ObVU+kuqUk3Jk64EwieirEXZx9XUrVwp/JuBfKAWaM4T5Td/VL7QLDWPXp/MvGm/g==} @@ -5887,10 +6504,6 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -6026,6 +6639,9 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + hookified@1.12.2: + resolution: {integrity: sha512-aokUX1VdTpI0DUsndvW+OiwmBpKCu/NgRsSSkuSY0zq8PY6Q6a+lmOfAFDXAAOtBqJELvcWY9L1EVtzjbQcMdg==} + hosted-git-info@4.1.0: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} @@ -6127,6 +6743,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + image-meta@0.2.1: resolution: {integrity: sha512-K6acvFaelNxx8wc2VjbIzXKDVB0Khs0QT35U6NkGfTdCmjLNcO2945m7RFNR9/RPVFm48hq7QPzK8uGH18HCGw==} @@ -6195,9 +6815,6 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} - invariant@2.2.4: - resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} - ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -6229,10 +6846,6 @@ packages: is-arrayish@0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - is-async-function@2.0.0: - resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} - engines: {node: '>= 0.4'} - is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} @@ -6305,9 +6918,6 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - is-finalizationregistry@1.0.2: - resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} - is-finalizationregistry@1.1.1: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} @@ -6328,10 +6938,6 @@ packages: resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} engines: {node: '>=18'} - is-generator-function@1.0.10: - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} - engines: {node: '>= 0.4'} - is-generator-function@1.1.0: resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} engines: {node: '>= 0.4'} @@ -6396,10 +7002,6 @@ packages: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - is-path-inside@4.0.0: resolution: {integrity: sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==} engines: {node: '>=12'} @@ -6551,8 +7153,11 @@ packages: resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} engines: {node: '>=16'} - iterator.prototype@1.1.3: - resolution: {integrity: sha512-FW5iMbeQ6rBGm/oKgzq2aW4KvAGpxPzYES8N4g4xNXUKpL1mclMvOe+76AcLDTvD+Ze+sOpVhgdAQEKF4L9iGQ==} + isomorphic.js@0.2.5: + resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} jackspeak@3.4.3: @@ -6675,6 +7280,9 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + keyv@5.5.3: + resolution: {integrity: sha512-h0Un1ieD+HUrzBH6dJXhod3ifSghk5Hw/2Y4/KHBziPlZecrFyE9YOTPU6eOs0V9pYl8gOs86fkr/KN8lUX39A==} + kill-port@2.0.1: resolution: {integrity: sha512-e0SVOV5jFo0mx8r7bS29maVWp17qGqLBZ5ricNSajON6//kmb7qqqNnml4twNE8Dtj97UQD+gNFOaipS/q1zzQ==} hasBin: true @@ -6728,6 +7336,11 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lib0@0.2.114: + resolution: {integrity: sha512-gcxmNFzA4hv8UYi8j43uPlQ7CGcyMJ2KQb5kZASw6SnAKAf10hK12i2fjrS3Cl/ugZa5Ui6WwIu1/6MIXiHttQ==} + engines: {node: '>=16'} + hasBin: true + light-my-request@5.14.0: resolution: {integrity: sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==} @@ -6745,8 +7358,8 @@ packages: linkify-it@5.0.0: resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} - linkifyjs@4.2.0: - resolution: {integrity: sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==} + linkifyjs@4.3.2: + resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==} lint-staged@10.5.4: resolution: {integrity: sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg==} @@ -6901,8 +7514,11 @@ packages: magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} - make-cancellable-promise@1.3.2: - resolution: {integrity: sha512-GCXh3bq/WuMbS+Ky4JBPW1hYTOU+znU+Q5m9Pu+pI8EoUqIHk9+tviOKC6/qhHh8C4/As3tzJ69IF32kdz85ww==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + make-cancellable-promise@2.0.0: + resolution: {integrity: sha512-3SEQqTpV9oqVsIWqAcmDuaNeo7yBO3tqPtqGRcKkEo0lrzD3wqbKG9mkxO65KoOgXqj+zH2phJ2LiAsdzlogSw==} make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} @@ -6915,8 +7531,8 @@ packages: make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - make-event-props@1.6.2: - resolution: {integrity: sha512-iDwf7mA03WPiR8QxvcVHmVWEPfMY1RZXerDVNCRYW7dUr2ppH3J58Rwb39/WG39yTZdRSxr3x+2v22tvI0VEvA==} + make-event-props@2.0.0: + resolution: {integrity: sha512-G/hncXrl4Qt7mauJEXSg3AcdYzmpkIITTNl5I+rH9sog5Yw0kK6vseJjCaPfOXqOqQuPUP89Rkhfz5kPS8ijtw==} map-obj@5.0.2: resolution: {integrity: sha512-K6K2NgKnTXimT3779/4KxSvobxOtMmx1LBZ3NwRxT/MDIR3Br/fQ4Q+WCX5QxjyUR8zg5+RV9Tbf2c5pAWTD2A==} @@ -6938,6 +7554,11 @@ packages: peerDependencies: marked: '>=1 <15' + marked@16.4.0: + resolution: {integrity: sha512-CTPAcRBq57cn3R8n3hwc2REddc28hjR7RzDXQ+lXLmMJYqn20BaI2cGw6QjgZGIgVfp2Wdfw4aMzgNteQ6qJgQ==} + engines: {node: '>= 20'} + hasBin: true + marked@4.3.0: resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} engines: {node: '>= 12'} @@ -7044,8 +7665,8 @@ packages: resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} engines: {node: '>=10'} - merge-refs@1.3.0: - resolution: {integrity: sha512-nqXPXbso+1dcKDpPCXvwZyJILz+vSLqGGOnDrYHQYE+B8n9JTCekVLC65AfCpR4ggVyA/45Y0iR9LDyS2iI+zA==} + merge-refs@2.0.0: + resolution: {integrity: sha512-3+B21mYK2IqUWnd2EivABLT7ueDhb0b8/dGK8LoFQPrU61YITeCMn14F7y7qZafWNZhUEKb24cJdiT5Wxs3prg==} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: @@ -7161,10 +7782,6 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - mime-db@1.53.0: - resolution: {integrity: sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==} - engines: {node: '>= 0.6'} - mime-db@1.54.0: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} @@ -7203,10 +7820,6 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - mimic-response@2.1.0: - resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==} - engines: {node: '>=8'} - mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -7495,8 +8108,8 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} - object.entries@1.1.8: - resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} engines: {node: '>= 0.4'} object.fromentries@2.0.8: @@ -7511,6 +8124,10 @@ packages: resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + ofetch@1.4.1: resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} @@ -7779,6 +8396,9 @@ packages: resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} engines: {node: '>=16'} + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -7787,10 +8407,6 @@ packages: resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} engines: {node: '>=12'} - path2d@0.2.1: - resolution: {integrity: sha512-Fl2z/BHvkTNvkuBzYTpTuirHZg6wW9z8+4SND/3mDTEcYbbNKWAy21dz9D3ePNNwrrK8pqZO5vLPZ1hLF6T7XA==} - engines: {node: '>=6'} - pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -7798,9 +8414,9 @@ packages: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} - pdfjs-dist@4.4.168: - resolution: {integrity: sha512-MbkAjpwka/dMHaCfQ75RY1FXX3IewBVu6NGZOcxerRFlaBiIkZmUoR0jotX5VUzYZEXAGzSFtknWs5xRKliXPA==} - engines: {node: '>=18'} + pdfjs-dist@5.4.296: + resolution: {integrity: sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==} + engines: {node: '>=20.16.0 || >=22.3.0'} peek-readable@5.3.1: resolution: {integrity: sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==} @@ -7816,10 +8432,6 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} - picomatch@4.0.3: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} @@ -7961,6 +8573,10 @@ packages: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + prebuild-install@7.1.2: resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} engines: {node: '>=10'} @@ -8000,13 +8616,13 @@ packages: engines: {node: '>=14'} hasBin: true - prettier@3.2.5: - resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} engines: {node: '>=14'} hasBin: true - prettier@3.3.3: - resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} engines: {node: '>=14'} hasBin: true @@ -8053,8 +8669,8 @@ packages: property-information@6.5.0: resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} - prosemirror-changeset@2.2.1: - resolution: {integrity: sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==} + prosemirror-changeset@2.3.1: + resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==} prosemirror-collab@1.3.1: resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} @@ -8083,17 +8699,14 @@ packages: prosemirror-menu@1.2.4: resolution: {integrity: sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==} - prosemirror-model@1.23.0: - resolution: {integrity: sha512-Q/fgsgl/dlOAW9ILu4OOhYWQbc7TQd4BwKH/RwmUjyVf8682Be4zj3rOYdLnYEcGzyg8LL9Q5IWYKD8tdToreQ==} - prosemirror-model@1.25.0: resolution: {integrity: sha512-/8XUmxWf0pkj2BmtqZHYJipTBMHIdVjuvFzMvEoxrtyGNmfvdhBiRwYt/eFwy2wA9DtBW3RLqvZnjurEkHaFCw==} prosemirror-schema-basic@1.2.3: resolution: {integrity: sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==} - prosemirror-schema-list@1.4.1: - resolution: {integrity: sha512-jbDyaP/6AFfDfu70VzySsD75Om2t3sXTOdl5+31Wlxlg62td1haUpty/ybajSfJ1pkGadlOfwQq9kgW5IMo1Rg==} + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} prosemirror-state@1.4.3: resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==} @@ -8108,9 +8721,6 @@ packages: prosemirror-state: ^1.4.2 prosemirror-view: ^1.33.8 - prosemirror-transform@1.10.2: - resolution: {integrity: sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==} - prosemirror-transform@1.10.3: resolution: {integrity: sha512-Nhh/+1kZGRINbEHmVu39oynhcap4hWTs/BlU7NnxWj3+l0qi8I1mu67v6mMdEe/ltD8hHvU4FV6PHiCw2VSpMw==} @@ -8154,6 +8764,10 @@ packages: resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==} engines: {node: '>=12.20'} + qified@0.5.1: + resolution: {integrity: sha512-+BtFN3dCP+IaFA6IYNOu/f/uK1B8xD2QWyOeCse0rjtAebBmkzgd2d1OAXi3ikAzJMIBSdzZDNZ3wZKEUDQs5w==} + engines: {node: '>=20'} + qs@6.13.0: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} @@ -8213,10 +8827,10 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' - react-dom@19.0.0: - resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} + react-dom@19.2.0: + resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} peerDependencies: - react: ^19.0.0 + react: ^19.2.0 react-dropzone@11.7.1: resolution: {integrity: sha512-zxCMwhfPy1olUEbw3FLNPLhAm/HnaYH5aELIEglRbqabizKAdHs0h+WuyOpmA+v1JXn0++fpQDdNfUagWt5hJQ==} @@ -8260,8 +8874,8 @@ packages: react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - react-is@19.0.0: - resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==} + react-is@19.2.0: + resolution: {integrity: sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==} react-markdown@9.0.3: resolution: {integrity: sha512-Yk7Z94dbgYTOrdk41Z74GoKA7rThnsbbqBTRYuxoe08qvfQ9tJVhmAKw6BJS/ZORG7kTy/s1QvYzSuaoBA1qfw==} @@ -8269,8 +8883,8 @@ packages: '@types/react': '>=18' react: '>=18' - react-pdf@9.1.1: - resolution: {integrity: sha512-Cn3RTJZMqVOOCgLMRXDamLk4LPGfyB2Np3OwQAUjmHIh47EpuGW1OpAA1Z1GVDLoHx4d5duEDo/YbUkDbr4QFQ==} + react-pdf@10.2.0: + resolution: {integrity: sha512-zk0DIL31oCh8cuQycM0SJKfwh4Onz0/Nwi6wTOjgtEjWGUY6eM+/vuzvOP3j70qtEULn7m1JtaeGzud1w5fY2Q==} peerDependencies: '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -8279,36 +8893,36 @@ packages: '@types/react': optional: true - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} - react-remove-scroll-bar@2.3.6: - resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: '@types/react': optional: true - react-remove-scroll@2.6.0: - resolution: {integrity: sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==} + react-remove-scroll@2.7.1: + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - react-style-singleton@2.2.1: - resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true @@ -8326,8 +8940,8 @@ packages: react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - react@19.0.0: - resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} + react@19.2.0: + resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} reactflow@11.11.4: @@ -8386,10 +9000,6 @@ packages: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} - reflect.getprototypeof@1.0.6: - resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} - engines: {node: '>= 0.4'} - regenerate-unicode-properties@10.2.0: resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==} engines: {node: '>=4'} @@ -8433,8 +9043,8 @@ packages: remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} - remark-rehype@11.1.1: - resolution: {integrity: sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==} + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} @@ -8535,6 +9145,11 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rollup@4.52.5: + resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} @@ -8581,10 +9196,6 @@ packages: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} - safe-regex-test@1.0.3: - resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} - engines: {node: '>= 0.4'} - safe-regex-test@1.1.0: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} @@ -8602,8 +9213,8 @@ packages: sander@0.5.1: resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} - scheduler@0.25.0: - resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} @@ -8732,9 +9343,6 @@ packages: simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - simple-get@3.1.1: - resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==} - simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} @@ -8898,10 +9506,6 @@ packages: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} - string.prototype.matchall@4.0.11: - resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} - engines: {node: '>= 0.4'} - string.prototype.matchall@4.0.12: resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} engines: {node: '>= 0.4'} @@ -9262,9 +9866,6 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} - tippy.js@6.3.7: - resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} - tiptap-markdown@0.8.10: resolution: {integrity: sha512-iDVkR2BjAqkTDtFX0h94yVvE2AihCXlF0Q7RIXSJPRSR5I0PA1TMuAg6FHFpmqTn4tPxJ0by0CK7PUMlnFLGEQ==} peerDependencies: @@ -9345,6 +9946,12 @@ packages: peerDependencies: typescript: '>=4.2.0' + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -9410,10 +10017,6 @@ packages: resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} engines: {node: '>=10'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - type-fest@0.21.3: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} @@ -9517,6 +10120,11 @@ packages: engines: {node: '>=14.17'} hasBin: true + 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==} @@ -9551,6 +10159,9 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici-types@7.14.0: + resolution: {integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==} + unenv@1.10.0: resolution: {integrity: sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==} @@ -9702,22 +10313,22 @@ packages: urlpattern-polyfill@8.0.2: resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==} - use-callback-ref@1.3.2: - resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true - use-sidecar@1.1.2: - resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} engines: {node: '>=10'} peerDependencies: - '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': optional: true @@ -9776,22 +10387,22 @@ packages: resolution: {integrity: sha512-20NBQxg/zH+3FTrlU6BQTob720xkuXNYtrx7psAQ4E6pMcRDeLEK77QU9kXURU587+f2To7ASH1JVTGbXVV/vQ==} engines: {node: '>=12.0.0'} - vite-plugin-pwa@0.20.5: - resolution: {integrity: sha512-aweuI/6G6n4C5Inn0vwHumElU/UEpNuO+9iZzwPZGTCH87TeZ6YFMrEY6ZUBQdIHHlhTsbMDryFARcSuOdsz9Q==} + vite-plugin-pwa@1.1.0: + resolution: {integrity: sha512-VsSpdubPzXhHWVINcSx6uHRMpOHVHQcHsef1QgkOlEoaIDAlssFEW88LBq1a59BuokAhsh2kUDJbaX1bZv4Bjw==} engines: {node: '>=16.0.0'} peerDependencies: - '@vite-pwa/assets-generator': ^0.2.6 - vite: ^3.1.0 || ^4.0.0 || ^5.0.0 - workbox-build: ^7.1.0 - workbox-window: ^7.1.0 + '@vite-pwa/assets-generator': ^1.0.0 + vite: ^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + workbox-build: ^7.3.0 + workbox-window: ^7.3.0 peerDependenciesMeta: '@vite-pwa/assets-generator': optional: true - vite-plugin-webfont-dl@3.9.5: - resolution: {integrity: sha512-PSVM7s1XurzMPYXgUHBpZOJYsuzRNq3DGIABdUyq/mNFan1n434+NbmGTK9f9+dIDVHpitXFiE3CmbAdDyDQMg==} + vite-plugin-webfont-dl@3.11.1: + resolution: {integrity: sha512-5eQaMn3mJ0qNnL64R6wZouh0I4zkvK8aS+GsoCDLY4WYfWCYvNpf6TyauxEsMlbwwfznkKjXTd1DNBGs4fKAhQ==} peerDependencies: - vite: ^2 || ^3 || ^4 || ^5 + vite: ^2 || ^3 || ^4 || ^5 || ^6 || ^7 vite@5.4.10: resolution: {integrity: sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==} @@ -9824,6 +10435,46 @@ packages: terser: optional: true + vite@7.1.12: + resolution: {integrity: sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitefu@1.0.3: resolution: {integrity: sha512-iKKfOMBHob2WxEJbqbJjHAkmYgvFDPhuqrO82om83S8RLk+17FtyMBfcyeH8GqD0ihShtkMW/zzJgiA51hCNCQ==} peerDependencies: @@ -9904,10 +10555,6 @@ packages: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} - which-builtin-type@1.1.4: - resolution: {integrity: sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==} - engines: {node: '>= 0.4'} - which-builtin-type@1.2.1: resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} engines: {node: '>= 0.4'} @@ -10052,8 +10699,8 @@ packages: utf-8-validate: optional: true - wuchale@0.16.5: - resolution: {integrity: sha512-g/6ZR+gBhzZq227y5MJvhrJkG/wYUfUvUa+zvAo1Q6K9dhiMWSz6YS2p3Aix2uM3sAx0KeCe9y8XAKprXnQiFg==} + wuchale@0.18.3: + resolution: {integrity: sha512-l4JxOjfGLiqY1u33I3TRlvrTGyMZfrXPei73Gw9wgdIGDicMI85eKN+fBNQxlunOTDavxY1M0qQNRBuv7gjmKQ==} hasBin: true xdg-basedir@5.1.0: @@ -10069,6 +10716,12 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + y-protocols@1.0.6: + resolution: {integrity: sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + yjs: ^13.0.0 + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -10107,6 +10760,10 @@ packages: yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yjs@13.6.27: + resolution: {integrity: sha512-OIDwaflOaq4wC6YlPBy2L6ceKeKuF7DeTxx+jPzv1FHn9tCZ0ZwSRnUBxD05E3yed46fv/FWJbvR+Ud7x0L7zw==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -10131,11 +10788,11 @@ packages: peerDependencies: zod: ^3.24.1 - zod-validation-error@3.4.0: - resolution: {integrity: sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==} + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} engines: {node: '>=18.0.0'} peerDependencies: - zod: ^3.18.0 + zod: ^3.25.0 || ^4.0.0 zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} @@ -10183,12 +10840,12 @@ snapshots: dependencies: json-schema: 0.4.0 - '@ai-sdk/react@2.0.29(react@19.0.0)(zod@4.1.5)': + '@ai-sdk/react@2.0.29(react@19.2.0)(zod@4.1.5)': dependencies: '@ai-sdk/provider-utils': 3.0.7(zod@4.1.5) ai: 5.0.29(zod@4.1.5) - react: 19.0.0 - swr: 2.3.6(react@19.0.0) + react: 19.2.0 + swr: 2.3.6(react@19.2.0) throttleit: 2.1.0 optionalDependencies: zod: 4.1.5 @@ -10248,7 +10905,7 @@ snapshots: '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-module-transforms': 7.27.3(@babel/core@7.26.0) '@babel/helpers': 7.26.0 - '@babel/parser': 7.27.7 + '@babel/parser': 7.28.4 '@babel/template': 7.27.2 '@babel/traverse': 7.27.7 '@babel/types': 7.27.7 @@ -10260,21 +10917,49 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.28.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.4 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.1(supports-color@9.4.0) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/generator@7.27.5': dependencies: - '@babel/parser': 7.27.7 - '@babel/types': 7.27.7 - '@jridgewell/gen-mapping': 0.3.10 - '@jridgewell/trace-mapping': 0.3.27 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.25.9': dependencies: - '@babel/types': 7.26.3 + '@babel/types': 7.28.4 '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 7.27.7 + '@babel/types': 7.28.4 '@babel/helper-compilation-targets@7.27.2': dependencies: @@ -10284,29 +10969,29 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.26.0)': + '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.27.1 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.26.0) + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.27.7 + '@babel/traverse': 7.28.4 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.27.1(@babel/core@7.26.0)': + '@babel/helper-create-regexp-features-plugin@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 regexpu-core: 6.2.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.26.0)': + '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 debug: 4.4.1(supports-color@9.4.0) @@ -10315,24 +11000,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-globals@7.28.0': {} + '@babel/helper-member-expression-to-functions@7.27.1': dependencies: - '@babel/traverse': 7.27.7 - '@babel/types': 7.27.7 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.25.9': dependencies: '@babel/traverse': 7.25.9 - '@babel/types': 7.26.3 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.27.7 - '@babel/types': 7.27.7 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color @@ -10341,40 +11028,49 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-module-imports': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.27.7 + '@babel/traverse': 7.28.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.27.7 + '@babel/types': 7.28.4 '@babel/helper-plugin-utils@7.25.9': {} '@babel/helper-plugin-utils@7.27.1': {} - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.26.0)': + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.27.1 - '@babel/traverse': 7.27.7 + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.27.1(@babel/core@7.26.0)': + '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-member-expression-to-functions': 7.27.1 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.27.7 + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.27.7 - '@babel/types': 7.27.7 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color @@ -10387,495 +11083,500 @@ snapshots: '@babel/helper-wrap-function@7.27.1': dependencies: '@babel/template': 7.27.2 - '@babel/traverse': 7.27.7 - '@babel/types': 7.27.7 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color '@babel/helpers@7.26.0': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.27.7 + '@babel/types': 7.28.4 - '@babel/parser@7.27.7': + '@babel/helpers@7.28.4': dependencies: - '@babel/types': 7.27.7 + '@babel/template': 7.27.2 + '@babel/types': 7.28.4 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.26.0)': + '@babel/parser@7.28.4': dependencies: - '@babel/core': 7.26.0 + '@babel/types': 7.28.4 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.27.7 + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.26.0) + '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.4) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.27.7 + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 - '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.0)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-async-generator-functions@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-async-generator-functions@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.26.0) - '@babel/traverse': 7.27.7 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.4) + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-module-imports': 7.27.1 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.26.0) + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.4) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-block-scoping@7.27.5(@babel/core@7.26.0)': + '@babel/plugin-transform-block-scoping@7.27.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-class-static-block@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.27.7(@babel/core@7.26.0)': + '@babel/plugin-transform-classes@7.27.7(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.26.0) - '@babel/traverse': 7.27.7 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) + '@babel/traverse': 7.28.4 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/template': 7.27.2 - '@babel/plugin-transform-destructuring@7.27.7(@babel/core@7.26.0)': + '@babel/plugin-transform-destructuring@7.27.7(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.27.7 + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-exponentiation-operator@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-exponentiation-operator@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.27.7 + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-literals@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-logical-assignment-operators@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-logical-assignment-operators@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-systemjs@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.27.7 + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-object-rest-spread@7.27.7(@babel/core@7.26.0)': + '@babel/plugin-transform-object-rest-spread@7.27.7(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.27.7(@babel/core@7.26.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.26.0) - '@babel/traverse': 7.27.7 + '@babel/plugin-transform-destructuring': 7.27.7(@babel/core@7.28.4) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.4) + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.26.0) + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-optional-chaining@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-optional-chaining@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.26.0)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0)': + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-regenerator@7.27.5(@babel/core@7.26.0)': + '@babel/plugin-transform-regenerator@7.27.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-spread@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-spread@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.26.0)': + '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 - '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.4) '@babel/helper-plugin-utils': 7.27.1 - '@babel/preset-env@7.27.2(@babel/core@7.26.0)': + '@babel/preset-env@7.27.2(@babel/core@7.28.4)': dependencies: '@babel/compat-data': 7.27.7 - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-plugin-utils': 7.27.1 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0) - '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.0) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-async-generator-functions': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-block-scoping': 7.27.5(@babel/core@7.26.0) - '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-class-static-block': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-classes': 7.27.7(@babel/core@7.26.0) - '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-destructuring': 7.27.7(@babel/core@7.26.0) - '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-exponentiation-operator': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-logical-assignment-operators': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-modules-systemjs': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-object-rest-spread': 7.27.7(@babel/core@7.26.0) - '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.26.0) - '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-regenerator': 7.27.5(@babel/core@7.26.0) - '@babel/plugin-transform-regexp-modifiers': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-property-regex': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.26.0) - '@babel/plugin-transform-unicode-sets-regex': 7.27.1(@babel/core@7.26.0) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.0) - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.26.0) - babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.26.0) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.26.0) + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.4) + '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.4) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-async-generator-functions': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-block-scoping': 7.27.5(@babel/core@7.28.4) + '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-class-static-block': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-classes': 7.27.7(@babel/core@7.28.4) + '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-destructuring': 7.27.7(@babel/core@7.28.4) + '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-exponentiation-operator': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-logical-assignment-operators': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-modules-systemjs': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-object-rest-spread': 7.27.7(@babel/core@7.28.4) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.4) + '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-regenerator': 7.27.5(@babel/core@7.28.4) + '@babel/plugin-transform-regexp-modifiers': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-unicode-property-regex': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-unicode-sets-regex': 7.27.1(@babel/core@7.28.4) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.4) + babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.4) + babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.28.4) + babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.4) core-js-compat: 3.43.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.0)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/types': 7.27.7 + '@babel/types': 7.28.4 esutils: 2.0.3 '@babel/runtime@7.25.9': @@ -10887,16 +11588,16 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.27.7 - '@babel/types': 7.27.7 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 '@babel/traverse@7.25.9': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.27.5 - '@babel/parser': 7.27.7 + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.4 '@babel/template': 7.27.2 - '@babel/types': 7.27.7 + '@babel/types': 7.28.4 debug: 4.4.1(supports-color@9.4.0) globals: 11.12.0 transitivePeerDependencies: @@ -10905,32 +11606,39 @@ snapshots: '@babel/traverse@7.27.7': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.27.5 - '@babel/parser': 7.27.7 + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.4 '@babel/template': 7.27.2 - '@babel/types': 7.27.7 + '@babel/types': 7.28.4 debug: 4.4.1(supports-color@9.4.0) globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/types@7.25.6': + '@babel/traverse@7.28.4': dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - to-fast-properties: 2.0.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.4 + '@babel/template': 7.27.2 + '@babel/types': 7.28.4 + debug: 4.4.1(supports-color@9.4.0) + transitivePeerDependencies: + - supports-color - '@babel/types@7.25.9': + '@babel/types@7.25.6': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + to-fast-properties: 2.0.0 - '@babel/types@7.26.3': + '@babel/types@7.27.7': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/types@7.27.7': + '@babel/types@7.28.4': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 @@ -10969,6 +11677,22 @@ snapshots: '@bugsnag/safe-json-stringify@6.0.0': {} + '@cacheable/memoize@2.0.3': + dependencies: + '@cacheable/utils': 2.1.0 + + '@cacheable/memory@2.0.3': + dependencies: + '@cacheable/memoize': 2.0.3 + '@cacheable/utils': 2.1.0 + '@keyv/bigmap': 1.1.0(keyv@5.5.3) + hookified: 1.12.2 + keyv: 5.5.3 + + '@cacheable/utils@2.1.0': + dependencies: + keyv: 5.5.3 + '@codemirror/autocomplete@6.18.6': dependencies: '@codemirror/language': 6.11.2 @@ -11053,41 +11777,41 @@ snapshots: gonzales-pe: 4.3.0 node-source-walk: 6.0.2 - '@dnd-kit/accessibility@3.1.0(react@19.0.0)': + '@dnd-kit/accessibility@3.1.0(react@19.2.0)': dependencies: - react: 19.0.0 + react: 19.2.0 tslib: 2.8.0 - '@dnd-kit/core@6.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@dnd-kit/core@6.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@dnd-kit/accessibility': 3.1.0(react@19.0.0) - '@dnd-kit/utilities': 3.2.2(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + '@dnd-kit/accessibility': 3.1.0(react@19.2.0) + '@dnd-kit/utilities': 3.2.2(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) tslib: 2.8.0 - '@dnd-kit/sortable@8.0.0(@dnd-kit/core@6.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)': + '@dnd-kit/sortable@8.0.0(@dnd-kit/core@6.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)': dependencies: - '@dnd-kit/core': 6.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@dnd-kit/utilities': 3.2.2(react@19.0.0) - react: 19.0.0 + '@dnd-kit/core': 6.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@dnd-kit/utilities': 3.2.2(react@19.2.0) + react: 19.2.0 tslib: 2.8.0 - '@dnd-kit/utilities@3.2.2(react@19.0.0)': + '@dnd-kit/utilities@3.2.2(react@19.2.0)': dependencies: - react: 19.0.0 + react: 19.2.0 tslib: 2.8.0 - '@emoji-mart/react@1.1.1(emoji-mart@5.6.0)(react@19.0.0)': + '@emoji-mart/react@1.1.1(emoji-mart@5.6.0)(react@19.2.0)': dependencies: emoji-mart: 5.6.0 - react: 19.0.0 + react: 19.2.0 '@emotion/is-prop-valid@1.2.2': dependencies: '@emotion/memoize': 0.8.1 - '@emotion/is-prop-valid@1.3.1': + '@emotion/is-prop-valid@1.4.0': dependencies: '@emotion/memoize': 0.9.0 @@ -11109,6 +11833,9 @@ snapshots: '@esbuild/aix-ppc64@0.24.0': optional: true + '@esbuild/aix-ppc64@0.25.11': + optional: true + '@esbuild/android-arm64@0.19.11': optional: true @@ -11121,6 +11848,9 @@ snapshots: '@esbuild/android-arm64@0.24.0': optional: true + '@esbuild/android-arm64@0.25.11': + optional: true + '@esbuild/android-arm@0.19.11': optional: true @@ -11133,7 +11863,10 @@ snapshots: '@esbuild/android-arm@0.24.0': optional: true - '@esbuild/android-x64@0.19.11': + '@esbuild/android-arm@0.25.11': + optional: true + + '@esbuild/android-x64@0.19.11': optional: true '@esbuild/android-x64@0.21.2': @@ -11145,6 +11878,9 @@ snapshots: '@esbuild/android-x64@0.24.0': optional: true + '@esbuild/android-x64@0.25.11': + optional: true + '@esbuild/darwin-arm64@0.19.11': optional: true @@ -11157,6 +11893,9 @@ snapshots: '@esbuild/darwin-arm64@0.24.0': optional: true + '@esbuild/darwin-arm64@0.25.11': + optional: true + '@esbuild/darwin-x64@0.19.11': optional: true @@ -11169,6 +11908,9 @@ snapshots: '@esbuild/darwin-x64@0.24.0': optional: true + '@esbuild/darwin-x64@0.25.11': + optional: true + '@esbuild/freebsd-arm64@0.19.11': optional: true @@ -11181,6 +11923,9 @@ snapshots: '@esbuild/freebsd-arm64@0.24.0': optional: true + '@esbuild/freebsd-arm64@0.25.11': + optional: true + '@esbuild/freebsd-x64@0.19.11': optional: true @@ -11193,6 +11938,9 @@ snapshots: '@esbuild/freebsd-x64@0.24.0': optional: true + '@esbuild/freebsd-x64@0.25.11': + optional: true + '@esbuild/linux-arm64@0.19.11': optional: true @@ -11205,6 +11953,9 @@ snapshots: '@esbuild/linux-arm64@0.24.0': optional: true + '@esbuild/linux-arm64@0.25.11': + optional: true + '@esbuild/linux-arm@0.19.11': optional: true @@ -11217,6 +11968,9 @@ snapshots: '@esbuild/linux-arm@0.24.0': optional: true + '@esbuild/linux-arm@0.25.11': + optional: true + '@esbuild/linux-ia32@0.19.11': optional: true @@ -11229,6 +11983,9 @@ snapshots: '@esbuild/linux-ia32@0.24.0': optional: true + '@esbuild/linux-ia32@0.25.11': + optional: true + '@esbuild/linux-loong64@0.19.11': optional: true @@ -11241,6 +11998,9 @@ snapshots: '@esbuild/linux-loong64@0.24.0': optional: true + '@esbuild/linux-loong64@0.25.11': + optional: true + '@esbuild/linux-mips64el@0.19.11': optional: true @@ -11253,6 +12013,9 @@ snapshots: '@esbuild/linux-mips64el@0.24.0': optional: true + '@esbuild/linux-mips64el@0.25.11': + optional: true + '@esbuild/linux-ppc64@0.19.11': optional: true @@ -11265,6 +12028,9 @@ snapshots: '@esbuild/linux-ppc64@0.24.0': optional: true + '@esbuild/linux-ppc64@0.25.11': + optional: true + '@esbuild/linux-riscv64@0.19.11': optional: true @@ -11277,6 +12043,9 @@ snapshots: '@esbuild/linux-riscv64@0.24.0': optional: true + '@esbuild/linux-riscv64@0.25.11': + optional: true + '@esbuild/linux-s390x@0.19.11': optional: true @@ -11289,6 +12058,9 @@ snapshots: '@esbuild/linux-s390x@0.24.0': optional: true + '@esbuild/linux-s390x@0.25.11': + optional: true + '@esbuild/linux-x64@0.19.11': optional: true @@ -11301,6 +12073,12 @@ snapshots: '@esbuild/linux-x64@0.24.0': optional: true + '@esbuild/linux-x64@0.25.11': + optional: true + + '@esbuild/netbsd-arm64@0.25.11': + optional: true + '@esbuild/netbsd-x64@0.19.11': optional: true @@ -11313,9 +12091,15 @@ snapshots: '@esbuild/netbsd-x64@0.24.0': optional: true + '@esbuild/netbsd-x64@0.25.11': + optional: true + '@esbuild/openbsd-arm64@0.24.0': optional: true + '@esbuild/openbsd-arm64@0.25.11': + optional: true + '@esbuild/openbsd-x64@0.19.11': optional: true @@ -11328,6 +12112,12 @@ snapshots: '@esbuild/openbsd-x64@0.24.0': optional: true + '@esbuild/openbsd-x64@0.25.11': + optional: true + + '@esbuild/openharmony-arm64@0.25.11': + optional: true + '@esbuild/sunos-x64@0.19.11': optional: true @@ -11340,6 +12130,9 @@ snapshots: '@esbuild/sunos-x64@0.24.0': optional: true + '@esbuild/sunos-x64@0.25.11': + optional: true + '@esbuild/win32-arm64@0.19.11': optional: true @@ -11352,6 +12145,9 @@ snapshots: '@esbuild/win32-arm64@0.24.0': optional: true + '@esbuild/win32-arm64@0.25.11': + optional: true + '@esbuild/win32-ia32@0.19.11': optional: true @@ -11364,6 +12160,9 @@ snapshots: '@esbuild/win32-ia32@0.24.0': optional: true + '@esbuild/win32-ia32@0.25.11': + optional: true + '@esbuild/win32-x64@0.19.11': optional: true @@ -11376,18 +12175,28 @@ snapshots: '@esbuild/win32-x64@0.24.0': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)': + '@esbuild/win32-x64@0.25.11': + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@9.13.0(jiti@2.3.3))': dependencies: - eslint: 8.57.1 + eslint: 9.13.0(jiti@2.3.3) eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.4.0(eslint@9.13.0(jiti@2.3.3))': + '@eslint-community/eslint-utils@4.9.0(eslint@9.13.0(jiti@2.3.3))': dependencies: eslint: 9.13.0(jiti@2.3.3) eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.0(jiti@2.3.3))': + dependencies: + eslint: 9.39.0(jiti@2.3.3) + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.11.1': {} + '@eslint-community/regexpp@4.12.2': {} + '@eslint/config-array@0.18.0': dependencies: '@eslint/object-schema': 2.1.4 @@ -11396,14 +12205,30 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.1(supports-color@9.4.0) + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + '@eslint/core@0.7.0': {} - '@eslint/eslintrc@2.1.4': + '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 - debug: 4.4.0 - espree: 9.6.1 - globals: 13.24.0 + debug: 4.4.1(supports-color@9.4.0) + espree: 10.2.0 + globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -11412,11 +12237,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/eslintrc@3.1.0': + '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 debug: 4.4.1(supports-color@9.4.0) - espree: 10.2.0 + espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 @@ -11426,16 +12251,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@8.57.1': {} - '@eslint/js@9.13.0': {} + '@eslint/js@9.39.0': {} + '@eslint/object-schema@2.1.4': {} + '@eslint/object-schema@2.1.7': {} + '@eslint/plugin-kit@0.2.1': dependencies: levn: 0.4.1 + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + '@fastify/accept-negotiator@1.1.0': {} '@fastify/ajv-compiler@3.6.0': @@ -11471,57 +12303,45 @@ snapshots: fastq: 1.17.1 glob: 10.4.5 - '@floating-ui/core@1.6.8': - dependencies: - '@floating-ui/utils': 0.2.8 - - '@floating-ui/core@1.7.2': + '@floating-ui/core@1.7.3': dependencies: '@floating-ui/utils': 0.2.10 - '@floating-ui/dom@1.6.11': + '@floating-ui/dom@1.7.4': dependencies: - '@floating-ui/core': 1.6.8 - '@floating-ui/utils': 0.2.8 - - '@floating-ui/dom@1.7.2': - dependencies: - '@floating-ui/core': 1.7.2 + '@floating-ui/core': 1.7.3 '@floating-ui/utils': 0.2.10 - '@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@floating-ui/react-dom@2.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@floating-ui/dom': 1.6.11 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + '@floating-ui/dom': 1.7.4 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) '@floating-ui/utils@0.2.10': {} - '@floating-ui/utils@0.2.8': {} - '@humanfs/core@0.19.0': {} + '@humanfs/core@0.19.1': {} + '@humanfs/node@0.16.5': dependencies: '@humanfs/core': 0.19.0 '@humanwhocodes/retry': 0.3.1 - '@humanwhocodes/config-array@0.13.0': + '@humanfs/node@0.16.7': dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.0 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/momoa@2.0.4': {} - '@humanwhocodes/object-schema@2.0.3': {} - '@humanwhocodes/retry@0.3.1': {} + '@humanwhocodes/retry@0.4.3': {} + '@iarna/toml@2.2.5': {} '@import-maps/resolve@1.0.1': {} @@ -11539,20 +12359,25 @@ snapshots: dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.17.0 + '@types/node': 24.7.0 '@types/yargs': 16.0.9 chalk: 4.1.2 - '@jridgewell/gen-mapping@0.3.10': + '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/sourcemap-codec': 1.5.2 - '@jridgewell/trace-mapping': 0.3.27 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.2 - '@jridgewell/trace-mapping': 0.3.27 + '@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': {} @@ -11560,29 +12385,39 @@ snapshots: '@jridgewell/source-map@0.3.8': dependencies: - '@jridgewell/gen-mapping': 0.3.10 - '@jridgewell/trace-mapping': 0.3.27 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/sourcemap-codec@1.5.0': {} - '@jridgewell/sourcemap-codec@1.5.2': {} - '@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.2 + '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping@0.3.27': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.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 '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@keyv/bigmap@1.1.0(keyv@5.5.3)': + dependencies: + hookified: 1.12.2 + keyv: 5.5.3 + + '@keyv/serialize@1.1.1': {} '@lezer/common@1.2.3': {} @@ -11619,23 +12454,23 @@ snapshots: '@marijn/find-cluster-break@1.0.2': {} - '@microsoft/api-extractor-model@7.30.0(@types/node@20.17.0)': + '@microsoft/api-extractor-model@7.30.0(@types/node@24.7.0)': dependencies: '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.10.0(@types/node@20.17.0) + '@rushstack/node-core-library': 5.10.0(@types/node@24.7.0) transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.48.0(@types/node@20.17.0)': + '@microsoft/api-extractor@7.48.0(@types/node@24.7.0)': dependencies: - '@microsoft/api-extractor-model': 7.30.0(@types/node@20.17.0) + '@microsoft/api-extractor-model': 7.30.0(@types/node@24.7.0) '@microsoft/tsdoc': 0.15.1 '@microsoft/tsdoc-config': 0.17.1 - '@rushstack/node-core-library': 5.10.0(@types/node@20.17.0) + '@rushstack/node-core-library': 5.10.0(@types/node@24.7.0) '@rushstack/rig-package': 0.5.3 - '@rushstack/terminal': 0.14.3(@types/node@20.17.0) - '@rushstack/ts-command-line': 4.23.1(@types/node@20.17.0) + '@rushstack/terminal': 0.14.3(@types/node@24.7.0) + '@rushstack/ts-command-line': 4.23.1(@types/node@24.7.0) lodash: 4.17.21 minimatch: 3.0.8 resolve: 1.22.8 @@ -11671,6 +12506,50 @@ snapshots: transitivePeerDependencies: - supports-color + '@napi-rs/canvas-android-arm64@0.1.80': + optional: true + + '@napi-rs/canvas-darwin-arm64@0.1.80': + optional: true + + '@napi-rs/canvas-darwin-x64@0.1.80': + optional: true + + '@napi-rs/canvas-linux-arm-gnueabihf@0.1.80': + optional: true + + '@napi-rs/canvas-linux-arm64-gnu@0.1.80': + optional: true + + '@napi-rs/canvas-linux-arm64-musl@0.1.80': + optional: true + + '@napi-rs/canvas-linux-riscv64-gnu@0.1.80': + optional: true + + '@napi-rs/canvas-linux-x64-gnu@0.1.80': + optional: true + + '@napi-rs/canvas-linux-x64-musl@0.1.80': + optional: true + + '@napi-rs/canvas-win32-x64-msvc@0.1.80': + optional: true + + '@napi-rs/canvas@0.1.80': + optionalDependencies: + '@napi-rs/canvas-android-arm64': 0.1.80 + '@napi-rs/canvas-darwin-arm64': 0.1.80 + '@napi-rs/canvas-darwin-x64': 0.1.80 + '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.80 + '@napi-rs/canvas-linux-arm64-gnu': 0.1.80 + '@napi-rs/canvas-linux-arm64-musl': 0.1.80 + '@napi-rs/canvas-linux-riscv64-gnu': 0.1.80 + '@napi-rs/canvas-linux-x64-gnu': 0.1.80 + '@napi-rs/canvas-linux-x64-musl': 0.1.80 + '@napi-rs/canvas-win32-x64-msvc': 0.1.80 + optional: true + '@netlify/binary-info@1.0.0': {} '@netlify/blobs@7.4.0': {} @@ -11689,7 +12568,7 @@ snapshots: yaml: 2.6.0 yargs: 17.7.2 - '@netlify/build@29.55.2(@opentelemetry/api@1.8.0)(@swc/core@1.7.39)(@types/node@20.17.0)(picomatch@4.0.3)': + '@netlify/build@29.55.2(@opentelemetry/api@1.8.0)(@swc/core@1.7.39)(@types/node@24.7.0)(picomatch@4.0.3)': dependencies: '@bugsnag/js': 7.25.0 '@netlify/blobs': 7.4.0 @@ -11709,7 +12588,7 @@ snapshots: chalk: 5.3.0 clean-stack: 4.2.0 execa: 6.1.0 - fdir: 6.4.4(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.3) figures: 5.0.0 filter-obj: 5.1.0 got: 12.6.1 @@ -11746,8 +12625,8 @@ snapshots: strip-ansi: 7.1.0 supports-color: 9.4.0 terminal-link: 3.0.0 - ts-node: 10.9.2(@swc/core@1.7.39)(@types/node@20.17.0)(typescript@5.6.3) - typescript: 5.6.3 + ts-node: 10.9.2(@swc/core@1.7.39)(@types/node@24.7.0)(typescript@5.9.3) + typescript: 5.9.3 uuid: 9.0.1 yargs: 17.7.2 transitivePeerDependencies: @@ -11928,7 +12807,7 @@ snapshots: '@netlify/zip-it-and-ship-it@9.40.2(supports-color@9.4.0)': dependencies: - '@babel/parser': 7.27.7 + '@babel/parser': 7.28.4 '@babel/types': 7.25.6 '@netlify/binary-info': 1.0.0 '@netlify/serverless-functions-api': 1.30.1 @@ -11968,7 +12847,7 @@ snapshots: '@netlify/zip-it-and-ship-it@9.41.0(supports-color@9.4.0)': dependencies: - '@babel/parser': 7.27.7 + '@babel/parser': 7.28.4 '@babel/types': 7.25.6 '@netlify/binary-info': 1.0.0 '@netlify/serverless-functions-api': 1.30.1 @@ -12087,7 +12966,7 @@ snapshots: '@oddbird/css-anchor-positioning@0.6.1': dependencies: - '@floating-ui/dom': 1.7.2 + '@floating-ui/dom': 1.7.4 '@types/css-tree': 2.3.10 css-tree: 3.1.0 nanoid: 5.1.5 @@ -12185,286 +13064,358 @@ snapshots: '@polka/url@1.0.0-next.28': {} - '@popperjs/core@2.11.8': {} - '@radix-ui/number@1.1.0': {} '@radix-ui/primitive@1.1.0': {} - '@radix-ui/react-arrow@1.1.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.0.1 - '@types/react-dom': 19.0.1 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-collection@1.1.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/react-collection@1.1.0(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-context': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-slot': 1.1.0(@types/react@19.0.1)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.1.0(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.0.1 - '@types/react-dom': 19.0.1 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-compose-refs@1.1.0(@types/react@19.0.1)(react@19.0.0)': + '@radix-ui/react-compose-refs@1.1.0(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.0.0 + react: 19.2.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - '@radix-ui/react-context@1.1.0(@types/react@19.0.1)(react@19.0.0)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.0.0 + react: 19.2.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - '@radix-ui/react-context@1.1.1(@types/react@19.0.1)(react@19.0.0)': + '@radix-ui/react-context@1.1.0(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.0.0 + react: 19.2.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - '@radix-ui/react-direction@1.1.0(@types/react@19.0.1)(react@19.0.0)': + '@radix-ui/react-context@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.0.0 + react: 19.2.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/react-context@1.1.2(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@19.0.1)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.0.1 - '@types/react-dom': 19.0.1 + '@types/react': 19.2.2 - '@radix-ui/react-focus-guards@1.1.1(@types/react@19.0.1)(react@19.0.0)': + '@radix-ui/react-direction@1.1.0(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.0.0 + react: 19.2.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.0.1 - '@types/react-dom': 19.0.1 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-id@1.1.0(@types/react@19.0.1)(react@19.0.0)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0) - react: 19.0.0 + react: 19.2.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - '@radix-ui/react-popover@1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-id': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-popper': 1.2.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-portal': 1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-presence': 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-slot': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.1)(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-id@1.1.0(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-id@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) aria-hidden: 1.2.4 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - react-remove-scroll: 2.6.0(@types/react@19.0.1)(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) optionalDependencies: - '@types/react': 19.0.1 - '@types/react-dom': 19.0.1 - - '@radix-ui/react-popper@1.2.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': - dependencies: - '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-arrow': 1.1.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-context': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-use-rect': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-use-size': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/rect': 1.1.0 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@floating-ui/react-dom': 2.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/rect': 1.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.0.1 - '@types/react-dom': 19.0.1 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-portal@1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.0.1 - '@types/react-dom': 19.0.1 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-presence@1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/react-presence@1.1.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.0.1 - '@types/react-dom': 19.0.1 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-primitive@2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@radix-ui/react-slot': 1.1.0(@types/react@19.0.1)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.0.1 - '@types/react-dom': 19.0.1 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-roving-focus@1.1.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/react-primitive@2.0.0(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-slot': 1.1.0(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-roving-focus@1.1.0(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-collection': 1.1.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-context': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-direction': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-id': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.1)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + '@radix-ui/react-collection': 1.1.0(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.0.1 - '@types/react-dom': 19.0.1 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-scroll-area@1.2.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/react-scroll-area@1.2.0(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/number': 1.1.0 '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-direction': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-presence': 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.0.1 - '@types/react-dom': 19.0.1 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-slot@1.1.0(@types/react@19.0.1)(react@19.0.0)': + '@radix-ui/react-slot@1.1.0(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0) - react: 19.0.0 + '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - '@radix-ui/react-tabs@1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-tabs@1.1.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@radix-ui/primitive': 1.1.0 - '@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-direction': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-id': 1.1.0(@types/react@19.0.1)(react@19.0.0) - '@radix-ui/react-presence': 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.1)(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.0(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) optionalDependencies: - '@types/react': 19.0.1 - '@types/react-dom': 19.0.1 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) - '@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.0.1)(react@19.0.0)': + '@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.0.0 + react: 19.2.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - '@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.0.1)(react@19.0.0)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0) - react: 19.0.0 + react: 19.2.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.0.1)(react@19.0.0)': + '@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0) - react: 19.0.0 + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - '@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.0.1)(react@19.0.0)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.2)(react@19.2.0)': dependencies: - react: 19.0.0 + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - '@radix-ui/react-use-rect@1.1.0(@types/react@19.0.1)(react@19.0.0)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/rect': 1.1.0 - react: 19.0.0 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - '@radix-ui/react-use-size@1.1.0(@types/react@19.0.1)(react@19.0.0)': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0) - react: 19.0.0 + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - '@radix-ui/rect@1.1.0': {} + '@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 - '@reactflow/background@11.3.14(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.2)(react@19.2.0)': dependencies: - '@reactflow/core': 11.11.4(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/rect@1.1.1': {} + + '@reactflow/background@11.3.14(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@reactflow/core': 11.11.4(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) classcat: 5.0.5 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - zustand: 4.5.5(@types/react@19.0.1)(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + zustand: 4.5.5(@types/react@19.2.2)(react@19.2.0) transitivePeerDependencies: - '@types/react' - immer - '@reactflow/controls@11.2.14(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@reactflow/controls@11.2.14(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@reactflow/core': 11.11.4(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@reactflow/core': 11.11.4(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) classcat: 5.0.5 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - zustand: 4.5.5(@types/react@19.0.1)(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + zustand: 4.5.5(@types/react@19.2.2)(react@19.2.0) transitivePeerDependencies: - '@types/react' - immer - '@reactflow/core@11.11.4(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@reactflow/core@11.11.4(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@types/d3': 7.4.3 '@types/d3-drag': 3.0.7 @@ -12474,57 +13425,59 @@ snapshots: d3-drag: 3.0.0 d3-selection: 3.0.0 d3-zoom: 3.0.0 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - zustand: 4.5.5(@types/react@19.0.1)(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + zustand: 4.5.5(@types/react@19.2.2)(react@19.2.0) transitivePeerDependencies: - '@types/react' - immer - '@reactflow/minimap@11.7.14(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@reactflow/minimap@11.7.14(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@reactflow/core': 11.11.4(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@reactflow/core': 11.11.4(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@types/d3-selection': 3.0.11 '@types/d3-zoom': 3.0.8 classcat: 5.0.5 d3-selection: 3.0.0 d3-zoom: 3.0.0 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - zustand: 4.5.5(@types/react@19.0.1)(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + zustand: 4.5.5(@types/react@19.2.2)(react@19.2.0) transitivePeerDependencies: - '@types/react' - immer - '@reactflow/node-resizer@2.2.14(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@reactflow/node-resizer@2.2.14(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@reactflow/core': 11.11.4(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@reactflow/core': 11.11.4(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) classcat: 5.0.5 d3-drag: 3.0.0 d3-selection: 3.0.0 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - zustand: 4.5.5(@types/react@19.0.1)(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + zustand: 4.5.5(@types/react@19.2.2)(react@19.2.0) transitivePeerDependencies: - '@types/react' - immer - '@reactflow/node-toolbar@1.3.14(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@reactflow/node-toolbar@1.3.14(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@reactflow/core': 11.11.4(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@reactflow/core': 11.11.4(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) classcat: 5.0.5 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - zustand: 4.5.5(@types/react@19.0.1)(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + zustand: 4.5.5(@types/react@19.2.2)(react@19.2.0) transitivePeerDependencies: - '@types/react' - immer '@remirror/core-constants@3.0.0': {} - '@rollup/plugin-babel@5.3.1(@babel/core@7.26.0)(@types/babel__core@7.20.5)(rollup@2.79.2)': + '@rolldown/pluginutils@1.0.0-beta.38': {} + + '@rollup/plugin-babel@5.3.1(@babel/core@7.28.4)(@types/babel__core@7.20.5)(rollup@2.79.2)': dependencies: - '@babel/core': 7.26.0 + '@babel/core': 7.28.4 '@babel/helper-module-imports': 7.27.1 '@rollup/pluginutils': 3.1.0(rollup@2.79.2) rollup: 2.79.2 @@ -12580,54 +13533,120 @@ snapshots: '@rollup/rollup-android-arm-eabi@4.24.0': optional: true + '@rollup/rollup-android-arm-eabi@4.52.5': + optional: true + '@rollup/rollup-android-arm64@4.24.0': optional: true + '@rollup/rollup-android-arm64@4.52.5': + optional: true + '@rollup/rollup-darwin-arm64@4.24.0': optional: true + '@rollup/rollup-darwin-arm64@4.52.5': + optional: true + '@rollup/rollup-darwin-x64@4.24.0': optional: true + '@rollup/rollup-darwin-x64@4.52.5': + optional: true + + '@rollup/rollup-freebsd-arm64@4.52.5': + optional: true + + '@rollup/rollup-freebsd-x64@4.52.5': + optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.24.0': optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.52.5': + optional: true + '@rollup/rollup-linux-arm-musleabihf@4.24.0': optional: true + '@rollup/rollup-linux-arm-musleabihf@4.52.5': + optional: true + '@rollup/rollup-linux-arm64-gnu@4.24.0': optional: true + '@rollup/rollup-linux-arm64-gnu@4.52.5': + optional: true + '@rollup/rollup-linux-arm64-musl@4.24.0': optional: true + '@rollup/rollup-linux-arm64-musl@4.52.5': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.52.5': + optional: true + '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': optional: true + '@rollup/rollup-linux-ppc64-gnu@4.52.5': + optional: true + '@rollup/rollup-linux-riscv64-gnu@4.24.0': optional: true + '@rollup/rollup-linux-riscv64-gnu@4.52.5': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.52.5': + optional: true + '@rollup/rollup-linux-s390x-gnu@4.24.0': optional: true + '@rollup/rollup-linux-s390x-gnu@4.52.5': + optional: true + '@rollup/rollup-linux-x64-gnu@4.24.0': optional: true + '@rollup/rollup-linux-x64-gnu@4.52.5': + optional: true + '@rollup/rollup-linux-x64-musl@4.24.0': optional: true + '@rollup/rollup-linux-x64-musl@4.52.5': + optional: true + + '@rollup/rollup-openharmony-arm64@4.52.5': + optional: true + '@rollup/rollup-win32-arm64-msvc@4.24.0': optional: true + '@rollup/rollup-win32-arm64-msvc@4.52.5': + optional: true + '@rollup/rollup-win32-ia32-msvc@4.24.0': optional: true + '@rollup/rollup-win32-ia32-msvc@4.52.5': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.52.5': + optional: true + '@rollup/rollup-win32-x64-msvc@4.24.0': optional: true + '@rollup/rollup-win32-x64-msvc@4.52.5': + optional: true + '@rtsao/scc@1.1.0': {} - '@rushstack/node-core-library@5.10.0(@types/node@20.17.0)': + '@rushstack/node-core-library@5.10.0(@types/node@24.7.0)': dependencies: ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) @@ -12638,23 +13657,23 @@ snapshots: resolve: 1.22.10 semver: 7.5.4 optionalDependencies: - '@types/node': 20.17.0 + '@types/node': 24.7.0 '@rushstack/rig-package@0.5.3': dependencies: resolve: 1.22.10 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.14.3(@types/node@20.17.0)': + '@rushstack/terminal@0.14.3(@types/node@24.7.0)': dependencies: - '@rushstack/node-core-library': 5.10.0(@types/node@20.17.0) + '@rushstack/node-core-library': 5.10.0(@types/node@24.7.0) supports-color: 8.1.1 optionalDependencies: - '@types/node': 20.17.0 + '@types/node': 24.7.0 - '@rushstack/ts-command-line@4.23.1(@types/node@20.17.0)': + '@rushstack/ts-command-line@4.23.1(@types/node@24.7.0)': dependencies: - '@rushstack/terminal': 0.14.3(@types/node@20.17.0) + '@rushstack/terminal': 0.14.3(@types/node@24.7.0) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.2 @@ -12683,18 +13702,18 @@ snapshots: magic-string: 0.25.9 string.prototype.matchall: 4.0.12 - '@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)': + '@sveltejs/acorn-typescript@1.0.6(acorn@8.15.0)': dependencies: acorn: 8.15.0 - '@sveltejs/adapter-auto@3.3.0(@sveltejs/kit@2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)))(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)))': + '@sveltejs/adapter-auto@3.3.0(@sveltejs/kit@2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)))(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)))': dependencies: - '@sveltejs/kit': 2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)))(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)) + '@sveltejs/kit': 2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)))(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)) import-meta-resolve: 4.1.0 - '@sveltejs/kit@2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)))(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1))': + '@sveltejs/kit@2.7.2(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)))(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)) + '@sveltejs/vite-plugin-svelte': 4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.1.1 @@ -12708,7 +13727,7 @@ snapshots: sirv: 3.0.0 svelte: 5.1.4 tiny-glob: 0.2.9 - vite: 5.4.10(@types/node@20.17.0)(terser@5.43.1) + vite: 5.4.10(@types/node@24.7.0)(terser@5.43.1) '@sveltejs/package@2.3.6(svelte@5.1.4)(typescript@5.6.3)': dependencies: @@ -12721,25 +13740,25 @@ snapshots: transitivePeerDependencies: - typescript - '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)))(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1))': + '@sveltejs/vite-plugin-svelte-inspector@3.0.1(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)))(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)) + '@sveltejs/vite-plugin-svelte': 4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)) debug: 4.4.1(supports-color@9.4.0) svelte: 5.1.4 - vite: 5.4.10(@types/node@20.17.0)(terser@5.43.1) + vite: 5.4.10(@types/node@24.7.0)(terser@5.43.1) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1))': + '@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)))(svelte@5.1.4)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)) + '@sveltejs/vite-plugin-svelte-inspector': 3.0.1(@sveltejs/vite-plugin-svelte@4.0.0(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)))(svelte@5.1.4)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)) debug: 4.4.1(supports-color@9.4.0) deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.12 svelte: 5.1.4 - vite: 5.4.10(@types/node@20.17.0)(terser@5.43.1) - vitefu: 1.0.3(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)) + vite: 5.4.10(@types/node@24.7.0)(terser@5.43.1) + vitefu: 1.0.3(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)) transitivePeerDependencies: - supports-color @@ -12804,165 +13823,228 @@ snapshots: '@tanstack/history@1.95.0': {} - '@tanstack/react-router@1.95.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@tanstack/react-router@1.95.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@tanstack/history': 1.95.0 - '@tanstack/react-store': 0.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@tanstack/react-store': 0.7.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) jsesc: 3.0.2 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/react-store@0.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@tanstack/react-store@0.7.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@tanstack/store': 0.7.0 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - use-sync-external-store: 1.4.0(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + use-sync-external-store: 1.4.0(react@19.2.0) - '@tanstack/router-devtools@1.95.1(@tanstack/react-router@1.95.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@tanstack/router-devtools@1.95.1(@tanstack/react-router@1.95.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(csstype@3.1.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@tanstack/react-router': 1.95.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@tanstack/react-router': 1.95.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) clsx: 2.1.1 goober: 2.1.16(csstype@3.1.3) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) transitivePeerDependencies: - csstype '@tanstack/store@0.7.0': {} - '@tiptap/core@2.11.7(@tiptap/pm@2.11.7)': + '@tiptap/core@3.7.2(@tiptap/pm@3.7.2)': dependencies: - '@tiptap/pm': 2.11.7 + '@tiptap/pm': 3.7.2 - '@tiptap/extension-blockquote@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-blockquote@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) - '@tiptap/extension-bold@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-bold@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) - '@tiptap/extension-bubble-menu@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + '@tiptap/extension-bubble-menu@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) - '@tiptap/pm': 2.11.7 - tippy.js: 6.3.7 + '@floating-ui/dom': 1.7.4 + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 + optional: true + + '@tiptap/extension-bullet-list@3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))': + dependencies: + '@tiptap/extension-list': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) - '@tiptap/extension-bullet-list@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-code-block@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 - '@tiptap/extension-code-block@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + '@tiptap/extension-code@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) - '@tiptap/pm': 2.11.7 + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) - '@tiptap/extension-code@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-collaboration-caret@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/y-tiptap@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 + '@tiptap/y-tiptap': 3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) - '@tiptap/extension-document@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-collaboration@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/y-tiptap@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(yjs@13.6.27)': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 + '@tiptap/y-tiptap': 3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) + yjs: 13.6.27 - '@tiptap/extension-dropcursor@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + '@tiptap/extension-document@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) - '@tiptap/pm': 2.11.7 + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) - '@tiptap/extension-file-handler@2.25.0(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/extension-text-style@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)))': + '@tiptap/extension-drag-handle-react@3.7.2(@tiptap/extension-drag-handle@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/extension-collaboration@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/y-tiptap@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(yjs@13.6.27))(@tiptap/extension-node-range@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/y-tiptap@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)))(@tiptap/pm@3.7.2)(@tiptap/react@3.7.2(@floating-ui/dom@1.7.4)(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) - '@tiptap/extension-text-style': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-drag-handle': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/extension-collaboration@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/y-tiptap@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(yjs@13.6.27))(@tiptap/extension-node-range@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/y-tiptap@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)) + '@tiptap/pm': 3.7.2 + '@tiptap/react': 3.7.2(@floating-ui/dom@1.7.4)(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) - '@tiptap/extension-floating-menu@2.14.0(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + '@tiptap/extension-drag-handle@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/extension-collaboration@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/y-tiptap@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(yjs@13.6.27))(@tiptap/extension-node-range@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/y-tiptap@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) - '@tiptap/pm': 2.11.7 - tippy.js: 6.3.7 + '@floating-ui/dom': 1.7.4 + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/extension-collaboration': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/y-tiptap@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27))(yjs@13.6.27) + '@tiptap/extension-node-range': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 + '@tiptap/y-tiptap': 3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27) - '@tiptap/extension-gapcursor@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + '@tiptap/extension-dropcursor@3.7.2(@tiptap/extensions@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) - '@tiptap/pm': 2.11.7 + '@tiptap/extensions': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) - '@tiptap/extension-hard-break@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-file-handler@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/extension-text-style@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)))(@tiptap/pm@3.7.2)': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/extension-text-style': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) + '@tiptap/pm': 3.7.2 - '@tiptap/extension-heading@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-floating-menu@3.7.2(@floating-ui/dom@1.7.4)(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@floating-ui/dom': 1.7.4 + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 + optional: true - '@tiptap/extension-history@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + '@tiptap/extension-gapcursor@3.7.2(@tiptap/extensions@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) - '@tiptap/pm': 2.11.7 + '@tiptap/extensions': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) - '@tiptap/extension-horizontal-rule@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + '@tiptap/extension-hard-break@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) - '@tiptap/pm': 2.11.7 + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) - '@tiptap/extension-image@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-heading@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) - '@tiptap/extension-italic@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-horizontal-rule@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 - '@tiptap/extension-link@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + '@tiptap/extension-image@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) - '@tiptap/pm': 2.11.7 - linkifyjs: 4.2.0 + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) - '@tiptap/extension-list-item@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-italic@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) - '@tiptap/extension-mention@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(@tiptap/suggestion@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7))': + '@tiptap/extension-link@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) - '@tiptap/pm': 2.11.7 - '@tiptap/suggestion': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 + linkifyjs: 4.3.2 - '@tiptap/extension-ordered-list@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-list-item@3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/extension-list': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) - '@tiptap/extension-paragraph@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-list-keymap@3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/extension-list': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) - '@tiptap/extension-placeholder@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + '@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) - '@tiptap/pm': 2.11.7 + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 - '@tiptap/extension-strike@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-mention@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@tiptap/suggestion@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 + '@tiptap/suggestion': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) - '@tiptap/extension-text-style@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-node-range@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 - '@tiptap/extension-text@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-ordered-list@3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/extension-list': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) - '@tiptap/extension-typography@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + '@tiptap/extension-paragraph@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) - '@tiptap/pm@2.11.7': + '@tiptap/extension-placeholder@3.7.2(@tiptap/extensions@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2))': dependencies: - prosemirror-changeset: 2.2.1 + '@tiptap/extensions': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) + + '@tiptap/extension-strike@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': + dependencies: + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + + '@tiptap/extension-table@3.10.5(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)': + dependencies: + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 + + '@tiptap/extension-text-align@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': + dependencies: + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + + '@tiptap/extension-text-style@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': + dependencies: + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + + '@tiptap/extension-text@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': + dependencies: + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + + '@tiptap/extension-typography@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': + dependencies: + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + + '@tiptap/extension-underline@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))': + dependencies: + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + + '@tiptap/extensions@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)': + dependencies: + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 + + '@tiptap/markdown@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)': + dependencies: + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 + marked: 16.4.0 + + '@tiptap/pm@3.7.2': + dependencies: + prosemirror-changeset: 2.3.1 prosemirror-collab: 1.3.1 prosemirror-commands: 1.6.2 prosemirror-dropcursor: 1.8.1 @@ -12972,55 +14054,72 @@ snapshots: prosemirror-keymap: 1.2.2 prosemirror-markdown: 1.13.1 prosemirror-menu: 1.2.4 - prosemirror-model: 1.23.0 + prosemirror-model: 1.25.0 prosemirror-schema-basic: 1.2.3 - prosemirror-schema-list: 1.4.1 + prosemirror-schema-list: 1.5.1 prosemirror-state: 1.4.3 prosemirror-tables: 1.7.0 - prosemirror-trailing-node: 3.0.0(prosemirror-model@1.23.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1) - prosemirror-transform: 1.10.2 + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1) + prosemirror-transform: 1.10.3 prosemirror-view: 1.39.1 - '@tiptap/react@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@tiptap/react@3.7.2(@floating-ui/dom@1.7.4)(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) - '@tiptap/extension-bubble-menu': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) - '@tiptap/extension-floating-menu': 2.14.0(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) - '@tiptap/pm': 2.11.7 + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) '@types/use-sync-external-store': 0.0.6 fast-deep-equal: 3.1.3 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) - use-sync-external-store: 1.4.0(react@19.0.0) - - '@tiptap/starter-kit@2.11.7': - dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) - '@tiptap/extension-blockquote': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) - '@tiptap/extension-bold': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) - '@tiptap/extension-bullet-list': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) - '@tiptap/extension-code': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) - '@tiptap/extension-code-block': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) - '@tiptap/extension-document': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) - '@tiptap/extension-dropcursor': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) - '@tiptap/extension-gapcursor': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) - '@tiptap/extension-hard-break': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) - '@tiptap/extension-heading': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) - '@tiptap/extension-history': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) - '@tiptap/extension-horizontal-rule': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) - '@tiptap/extension-italic': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) - '@tiptap/extension-list-item': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) - '@tiptap/extension-ordered-list': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) - '@tiptap/extension-paragraph': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) - '@tiptap/extension-strike': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) - '@tiptap/extension-text': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) - '@tiptap/extension-text-style': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) - '@tiptap/pm': 2.11.7 - - '@tiptap/suggestion@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': - dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) - '@tiptap/pm': 2.11.7 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + use-sync-external-store: 1.4.0(react@19.2.0) + optionalDependencies: + '@tiptap/extension-bubble-menu': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) + '@tiptap/extension-floating-menu': 3.7.2(@floating-ui/dom@1.7.4)(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) + transitivePeerDependencies: + - '@floating-ui/dom' + + '@tiptap/starter-kit@3.7.2': + dependencies: + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/extension-blockquote': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) + '@tiptap/extension-bold': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) + '@tiptap/extension-bullet-list': 3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)) + '@tiptap/extension-code': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) + '@tiptap/extension-code-block': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) + '@tiptap/extension-document': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) + '@tiptap/extension-dropcursor': 3.7.2(@tiptap/extensions@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)) + '@tiptap/extension-gapcursor': 3.7.2(@tiptap/extensions@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)) + '@tiptap/extension-hard-break': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) + '@tiptap/extension-heading': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) + '@tiptap/extension-horizontal-rule': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) + '@tiptap/extension-italic': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) + '@tiptap/extension-link': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) + '@tiptap/extension-list': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) + '@tiptap/extension-list-item': 3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)) + '@tiptap/extension-list-keymap': 3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)) + '@tiptap/extension-ordered-list': 3.7.2(@tiptap/extension-list@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)) + '@tiptap/extension-paragraph': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) + '@tiptap/extension-strike': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) + '@tiptap/extension-text': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) + '@tiptap/extension-underline': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)) + '@tiptap/extensions': 3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 + + '@tiptap/suggestion@3.7.2(@tiptap/core@3.7.2(@tiptap/pm@3.7.2))(@tiptap/pm@3.7.2)': + dependencies: + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) + '@tiptap/pm': 3.7.2 + + '@tiptap/y-tiptap@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)': + dependencies: + lib0: 0.2.114 + prosemirror-model: 1.25.0 + prosemirror-state: 1.4.3 + prosemirror-view: 1.39.1 + y-protocols: 1.0.6(yjs@13.6.27) + yjs: 13.6.27 '@tokenizer/token@0.3.0': {} @@ -13038,24 +14137,24 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.27.7 - '@babel/types': 7.25.9 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.27.7 + '@babel/types': 7.28.4 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.27.7 - '@babel/types': 7.27.7 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 '@types/babel__traverse@7.20.6': dependencies: - '@babel/types': 7.27.7 + '@babel/types': 7.28.4 '@types/cookie@0.6.0': {} @@ -13213,7 +14312,7 @@ snapshots: '@types/http-proxy@1.17.15': dependencies: - '@types/node': 20.17.0 + '@types/node': 24.7.0 '@types/istanbul-lib-coverage@2.0.6': {} @@ -13262,6 +14361,10 @@ snapshots: dependencies: undici-types: 6.19.8 + '@types/node@24.7.0': + dependencies: + undici-types: 7.14.0 + '@types/normalize-package-data@2.4.4': {} '@types/parse-json@4.0.2': {} @@ -13270,26 +14373,26 @@ snapshots: '@types/pug@2.0.10': {} - '@types/react-dom@19.0.1': + '@types/react-dom@19.2.2(@types/react@19.2.2)': dependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 '@types/react-router-dom@5.3.3': dependencies: '@types/history': 4.7.11 - '@types/react': 19.0.1 + '@types/react': 19.2.2 '@types/react-router': 5.1.20 '@types/react-router@5.1.20': dependencies: '@types/history': 4.7.11 - '@types/react': 19.0.1 + '@types/react': 19.2.2 '@types/react-window@1.8.8': dependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - '@types/react@19.0.1': + '@types/react@19.2.2': dependencies: csstype: 3.1.3 @@ -13317,30 +14420,12 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 20.17.0 + '@types/node': 24.7.0 optional: true - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': - dependencies: - '@eslint-community/regexpp': 4.11.1 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 7.18.0 - eslint: 8.57.1 - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.6.3) - optionalDependencies: - typescript: 5.6.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3))(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3)': dependencies: - '@eslint-community/regexpp': 4.11.1 + '@eslint-community/regexpp': 4.12.2 '@typescript-eslint/parser': 8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3) '@typescript-eslint/scope-manager': 8.11.0 '@typescript-eslint/type-utils': 8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3) @@ -13356,16 +14441,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/eslint-plugin@8.46.4(@typescript-eslint/parser@8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3))(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.3.7 - eslint: 8.57.1 - optionalDependencies: - typescript: 5.6.3 + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.4 + '@typescript-eslint/type-utils': 8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.4 + eslint: 9.39.0(jiti@2.3.3) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -13382,27 +14471,40 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@7.18.0': + '@typescript-eslint/parser@8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.46.4 + '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.4 + debug: 4.4.1(supports-color@9.4.0) + eslint: 9.39.0(jiti@2.3.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.46.4(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 + '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3) + '@typescript-eslint/types': 8.46.4 + debug: 4.4.1(supports-color@9.4.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color '@typescript-eslint/scope-manager@8.11.0': dependencies: '@typescript-eslint/types': 8.11.0 '@typescript-eslint/visitor-keys': 8.11.0 - '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/scope-manager@8.46.4': dependencies: - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3) - debug: 4.4.0 - eslint: 8.57.1 - ts-api-utils: 1.3.0(typescript@5.6.3) - optionalDependencies: - typescript: 5.6.3 - transitivePeerDependencies: - - supports-color + '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/visitor-keys': 8.46.4 + + '@typescript-eslint/tsconfig-utils@8.46.4(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 '@typescript-eslint/type-utils@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3)': dependencies: @@ -13413,16 +14515,28 @@ snapshots: optionalDependencies: typescript: 5.6.3 transitivePeerDependencies: - - eslint + - eslint + - supports-color + + '@typescript-eslint/type-utils@8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3) + debug: 4.4.1(supports-color@9.4.0) + eslint: 9.39.0(jiti@2.3.3) + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: - supports-color '@typescript-eslint/types@5.62.0': {} - '@typescript-eslint/types@7.18.0': {} - '@typescript-eslint/types@8.11.0': {} - '@typescript-eslint/typescript-estree@5.62.0(supports-color@9.4.0)(typescript@5.6.3)': + '@typescript-eslint/types@8.46.4': {} + + '@typescript-eslint/typescript-estree@5.62.0(supports-color@9.4.0)(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 @@ -13430,24 +14544,9 @@ snapshots: globby: 11.1.0 is-glob: 4.0.3 semver: 7.7.2 - tsutils: 3.21.0(typescript@5.6.3) - optionalDependencies: - typescript: 5.6.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.6.3)': - dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.4.0 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.2 - ts-api-utils: 1.3.0(typescript@5.6.3) + tsutils: 3.21.0(typescript@5.9.3) optionalDependencies: - typescript: 5.6.3 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -13466,20 +14565,25 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.6.3)': + '@typescript-eslint/typescript-estree@8.46.4(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3) - eslint: 8.57.1 + '@typescript-eslint/project-service': 8.46.4(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.46.4(typescript@5.9.3) + '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/visitor-keys': 8.46.4 + debug: 4.4.1(supports-color@9.4.0) + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - - typescript '@typescript-eslint/utils@8.11.0(eslint@9.13.0(jiti@2.3.3))(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.3.3)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.13.0(jiti@2.3.3)) '@typescript-eslint/scope-manager': 8.11.0 '@typescript-eslint/types': 8.11.0 '@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3) @@ -13488,14 +14592,20 @@ snapshots: - supports-color - typescript - '@typescript-eslint/visitor-keys@5.62.0': + '@typescript-eslint/utils@8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 5.62.0 - eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.0(jiti@2.3.3)) + '@typescript-eslint/scope-manager': 8.46.4 + '@typescript-eslint/types': 8.46.4 + '@typescript-eslint/typescript-estree': 8.46.4(typescript@5.9.3) + eslint: 9.39.0(jiti@2.3.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color - '@typescript-eslint/visitor-keys@7.18.0': + '@typescript-eslint/visitor-keys@5.62.0': dependencies: - '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/types': 5.62.0 eslint-visitor-keys: 3.4.3 '@typescript-eslint/visitor-keys@8.11.0': @@ -13503,6 +14613,11 @@ snapshots: '@typescript-eslint/types': 8.11.0 eslint-visitor-keys: 3.4.3 + '@typescript-eslint/visitor-keys@8.46.4': + dependencies: + '@typescript-eslint/types': 8.46.4 + eslint-visitor-keys: 4.2.1 + '@uiw/codemirror-extensions-basic-setup@4.24.1(@codemirror/autocomplete@6.18.6)(@codemirror/commands@6.8.1)(@codemirror/language@6.11.2)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.1)': dependencies: '@codemirror/autocomplete': 6.18.6 @@ -13527,7 +14642,7 @@ snapshots: '@codemirror/state': 6.5.2 '@codemirror/view': 6.38.1 - '@uiw/react-codemirror@4.24.1(@babel/runtime@7.27.6)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.2)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.38.1)(codemirror@6.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + '@uiw/react-codemirror@4.24.1(@babel/runtime@7.27.6)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.11.2)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.38.1)(codemirror@6.0.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: '@babel/runtime': 7.27.6 '@codemirror/commands': 6.8.1 @@ -13536,8 +14651,8 @@ snapshots: '@codemirror/view': 6.38.1 '@uiw/codemirror-extensions-basic-setup': 4.24.1(@codemirror/autocomplete@6.18.6)(@codemirror/commands@6.8.1)(@codemirror/language@6.11.2)(@codemirror/lint@6.8.5)(@codemirror/search@6.5.11)(@codemirror/state@6.5.2)(@codemirror/view@6.38.1) codemirror: 6.0.2 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) transitivePeerDependencies: - '@codemirror/autocomplete' - '@codemirror/language' @@ -13564,14 +14679,15 @@ snapshots: - encoding - supports-color - '@vitejs/plugin-react@4.3.4(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1))': + '@vitejs/plugin-react@5.0.4(vite@7.1.12(@types/node@24.7.0)(jiti@2.3.3)(terser@5.43.1)(yaml@2.6.0))': dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4) + '@rolldown/pluginutils': 1.0.0-beta.38 '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 5.4.10(@types/node@20.17.0)(terser@5.43.1) + react-refresh: 0.17.0 + vite: 7.1.12(@types/node@24.7.0)(jiti@2.3.3)(terser@5.43.1)(yaml@2.6.0) transitivePeerDependencies: - supports-color @@ -13582,13 +14698,13 @@ snapshots: chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.3(@vitest/spy@2.1.3)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1))': + '@vitest/mocker@2.1.3(@vitest/spy@2.1.3)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1))': dependencies: '@vitest/spy': 2.1.3 estree-walker: 3.0.3 - magic-string: 0.30.12 + magic-string: 0.30.19 optionalDependencies: - vite: 5.4.10(@types/node@20.17.0)(terser@5.43.1) + vite: 5.4.10(@types/node@24.7.0)(terser@5.43.1) '@vitest/pretty-format@2.1.3': dependencies: @@ -13602,7 +14718,7 @@ snapshots: '@vitest/snapshot@2.1.3': dependencies: '@vitest/pretty-format': 2.1.3 - magic-string: 0.30.12 + magic-string: 0.30.19 pathe: 1.1.2 '@vitest/spy@2.1.3': @@ -13615,17 +14731,17 @@ snapshots: loupe: 3.1.2 tinyrainbow: 1.2.0 - '@wuchale/jsx@0.7.4(react@19.0.0)': + '@wuchale/jsx@0.9.4(react@19.2.0)': dependencies: - '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) + '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) acorn: 8.15.0 - wuchale: 0.16.5 + wuchale: 0.18.3 optionalDependencies: - react: 19.0.0 + react: 19.2.0 - '@wuchale/vite-plugin@0.14.6': + '@wuchale/vite-plugin@0.15.3': dependencies: - wuchale: 0.16.5 + wuchale: 0.18.3 '@xhmikosr/archive-type@6.0.1': dependencies: @@ -13905,7 +15021,7 @@ snapshots: array-buffer-byte-length@1.0.1: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 is-array-buffer: 3.0.4 array-buffer-byte-length@1.0.2: @@ -13917,12 +15033,12 @@ snapshots: array-includes@3.1.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.4 - is-string: 1.0.7 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 array-timsort@1.0.3: {} @@ -13936,11 +15052,11 @@ snapshots: array.prototype.findlast@1.2.5: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.24.0 es-errors: 1.3.0 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 es-shim-unscopables: 1.0.2 array.prototype.findlastindex@1.2.5: @@ -13966,22 +15082,29 @@ snapshots: es-abstract: 1.23.3 es-shim-unscopables: 1.0.2 + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.0.2 + array.prototype.tosorted@1.1.4: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.24.0 es-errors: 1.3.0 es-shim-unscopables: 1.0.2 arraybuffer.prototype.slice@1.0.3: dependencies: array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.24.0 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 @@ -14051,27 +15174,27 @@ snapshots: b4a@1.6.7: {} - babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.26.0): + babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.4): dependencies: '@babel/compat-data': 7.27.7 - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.4) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.26.0): + babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.28.4): dependencies: - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.4) core-js-compat: 3.43.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.26.0): + babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.28.4): dependencies: - '@babel/core': 7.26.0 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.4) transitivePeerDependencies: - supports-color @@ -14079,18 +15202,18 @@ snapshots: dependencies: prismjs: 1.29.0 - babel-plugin-react-compiler@19.1.0-rc.2: + babel-plugin-react-compiler@1.0.0: dependencies: - '@babel/types': 7.27.7 + '@babel/types': 7.28.4 - babel-plugin-styled-components@2.1.4(@babel/core@7.26.0)(styled-components@6.1.19(react-dom@19.0.0(react@19.0.0))(react@19.0.0)): + babel-plugin-styled-components@2.1.4(@babel/core@7.28.4)(styled-components@6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0)): dependencies: '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-module-imports': 7.25.9 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.28.4) lodash: 4.17.21 picomatch: 2.3.1 - styled-components: 6.1.19(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + styled-components: 6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0) transitivePeerDependencies: - '@babel/core' - supports-color @@ -14126,8 +15249,6 @@ snapshots: streamx: 2.20.1 optional: true - base64-arraybuffer@1.0.2: {} - base64-js@1.5.1: {} before-after-hook@2.2.3: {} @@ -14283,6 +15404,15 @@ snapshots: normalize-url: 8.0.1 responselike: 3.0.0 + cacheable@2.1.1: + dependencies: + '@cacheable/memoize': 2.0.3 + '@cacheable/memory': 2.0.3 + '@cacheable/utils': 2.1.0 + hookified: 1.12.2 + keyv: 5.5.3 + qified: 0.5.1 + cachedir@2.4.0: {} call-bind-apply-helpers@1.0.2: @@ -14324,16 +15454,6 @@ snapshots: caniuse-lite@1.0.30001726: {} - canvas@2.11.2: - dependencies: - '@mapbox/node-pre-gyp': 1.0.11(supports-color@9.4.0) - nan: 2.22.0 - simple-get: 3.1.1 - transitivePeerDependencies: - - encoding - - supports-color - optional: true - ccount@2.0.1: {} chai@5.1.2: @@ -14713,12 +15833,6 @@ snapshots: dependencies: luxon: 3.5.0 - cross-spawn@7.0.3: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -14822,7 +15936,7 @@ snapshots: data-view-buffer@1.0.1: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 es-errors: 1.3.0 is-data-view: 1.0.1 @@ -14834,7 +15948,7 @@ snapshots: data-view-byte-length@1.0.1: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 es-errors: 1.3.0 is-data-view: 1.0.1 @@ -14846,7 +15960,7 @@ snapshots: data-view-byte-offset@1.0.0: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 es-errors: 1.3.0 is-data-view: 1.0.1 @@ -14872,10 +15986,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.0: - dependencies: - ms: 2.1.3 - debug@4.4.1(supports-color@9.4.0): dependencies: ms: 2.1.3 @@ -14890,11 +16000,6 @@ snapshots: dependencies: character-entities: 2.0.2 - decompress-response@4.2.1: - dependencies: - mimic-response: 2.1.0 - optional: true - decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -14972,8 +16077,8 @@ snapshots: detective-postcss@6.1.3: dependencies: is-url: 1.2.4 - postcss: 8.4.47 - postcss-values-parser: 6.0.2(postcss@8.4.47) + postcss: 8.5.6 + postcss-values-parser: 6.0.2(postcss@8.5.6) detective-sass@5.0.3: dependencies: @@ -14989,10 +16094,10 @@ snapshots: detective-typescript@11.2.0(supports-color@9.4.0): dependencies: - '@typescript-eslint/typescript-estree': 5.62.0(supports-color@9.4.0)(typescript@5.6.3) + '@typescript-eslint/typescript-estree': 5.62.0(supports-color@9.4.0)(typescript@5.9.3) ast-module-types: 5.0.0 node-source-walk: 6.0.2 - typescript: 5.6.3 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -15012,10 +16117,6 @@ snapshots: dependencies: esutils: 2.0.3 - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -15048,12 +16149,12 @@ snapshots: dotenv@16.4.5: {} - downshift@9.0.10(react@19.0.0): + downshift@9.0.10(react@19.2.0): dependencies: '@babel/runtime': 7.27.6 compute-scroll-into-view: 3.1.1 prop-types: 15.8.1 - react: 19.0.0 + react: 19.2.0 react-is: 18.2.0 tslib: 2.8.0 @@ -15160,7 +16261,7 @@ snapshots: object.assign: 4.1.5 regexp.prototype.flags: 1.5.3 safe-array-concat: 1.1.2 - safe-regex-test: 1.0.3 + safe-regex-test: 1.1.0 string.prototype.trim: 1.2.9 string.prototype.trimend: 1.0.8 string.prototype.trimstart: 1.0.8 @@ -15230,28 +16331,30 @@ snapshots: es-define-property@1.0.0: dependencies: - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 es-define-property@1.0.1: {} es-errors@1.3.0: {} - es-iterator-helpers@1.1.0: + es-iterator-helpers@1.2.1: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.24.0 es-errors: 1.3.0 - es-set-tostringtag: 2.0.3 + es-set-tostringtag: 2.1.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 globalthis: 1.0.4 + gopd: 1.2.0 has-property-descriptors: 1.0.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 - internal-slot: 1.0.7 - iterator.prototype: 1.1.3 - safe-array-concat: 1.1.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 es-module-lexer@1.5.4: {} @@ -15265,7 +16368,7 @@ snapshots: es-set-tostringtag@2.0.3: dependencies: - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 hasown: 2.0.2 @@ -15401,6 +16504,35 @@ snapshots: '@esbuild/win32-ia32': 0.24.0 '@esbuild/win32-x64': 0.24.0 + esbuild@0.25.11: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.11 + '@esbuild/android-arm': 0.25.11 + '@esbuild/android-arm64': 0.25.11 + '@esbuild/android-x64': 0.25.11 + '@esbuild/darwin-arm64': 0.25.11 + '@esbuild/darwin-x64': 0.25.11 + '@esbuild/freebsd-arm64': 0.25.11 + '@esbuild/freebsd-x64': 0.25.11 + '@esbuild/linux-arm': 0.25.11 + '@esbuild/linux-arm64': 0.25.11 + '@esbuild/linux-ia32': 0.25.11 + '@esbuild/linux-loong64': 0.25.11 + '@esbuild/linux-mips64el': 0.25.11 + '@esbuild/linux-ppc64': 0.25.11 + '@esbuild/linux-riscv64': 0.25.11 + '@esbuild/linux-s390x': 0.25.11 + '@esbuild/linux-x64': 0.25.11 + '@esbuild/netbsd-arm64': 0.25.11 + '@esbuild/netbsd-x64': 0.25.11 + '@esbuild/openbsd-arm64': 0.25.11 + '@esbuild/openbsd-x64': 0.25.11 + '@esbuild/openharmony-arm64': 0.25.11 + '@esbuild/sunos-x64': 0.25.11 + '@esbuild/win32-arm64': 0.25.11 + '@esbuild/win32-ia32': 0.25.11 + '@esbuild/win32-x64': 0.25.11 + escalade@3.2.0: {} escape-goat@4.0.0: {} @@ -15426,14 +16558,14 @@ snapshots: eslint: 9.13.0(jiti@2.3.3) semver: 7.7.2 - eslint-config-prettier@9.1.0(eslint@8.57.1): - dependencies: - eslint: 8.57.1 - eslint-config-prettier@9.1.0(eslint@9.13.0(jiti@2.3.3)): dependencies: eslint: 9.13.0(jiti@2.3.3) + eslint-config-prettier@9.1.0(eslint@9.39.0(jiti@2.3.3)): + dependencies: + eslint: 9.39.0(jiti@2.3.3) + eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7 @@ -15442,17 +16574,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.0(jiti@2.3.3)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) - eslint: 8.57.1 + '@typescript-eslint/parser': 8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3) + eslint: 9.39.0(jiti@2.3.3) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3))(eslint@9.39.0(jiti@2.3.3)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -15461,9 +16593,9 @@ snapshots: array.prototype.flatmap: 1.3.2 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.57.1 + eslint: 9.39.0(jiti@2.3.3) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.0(jiti@2.3.3)) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -15475,87 +16607,85 @@ snapshots: string.prototype.trimend: 1.0.8 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/parser': 8.46.4(eslint@9.39.0(jiti@2.3.3))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jsx-a11y@6.10.1(eslint@8.57.1): + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.0(jiti@2.3.3)): dependencies: aria-query: 5.3.2 array-includes: 3.1.8 - array.prototype.flatmap: 1.3.2 + array.prototype.flatmap: 1.3.3 ast-types-flow: 0.0.8 axe-core: 4.10.2 axobject-query: 4.1.0 damerau-levenshtein: 1.0.8 emoji-regex: 9.2.2 - es-iterator-helpers: 1.1.0 - eslint: 8.57.1 + eslint: 9.39.0(jiti@2.3.3) hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 minimatch: 3.1.2 object.fromentries: 2.0.8 - safe-regex-test: 1.0.3 + safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-prettier@5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.2.5): + eslint-plugin-prettier@5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.39.0(jiti@2.3.3)))(eslint@9.39.0(jiti@2.3.3))(prettier@3.6.2): dependencies: - eslint: 8.57.1 - prettier: 3.2.5 + eslint: 9.39.0(jiti@2.3.3) + prettier: 3.6.2 prettier-linter-helpers: 1.0.0 synckit: 0.9.2 optionalDependencies: '@types/eslint': 9.6.1 - eslint-config-prettier: 9.1.0(eslint@8.57.1) + eslint-config-prettier: 9.1.0(eslint@9.39.0(jiti@2.3.3)) - eslint-plugin-react-hooks@6.0.0-rc.1(eslint@8.57.1): + eslint-plugin-react-hooks@7.0.1(eslint@9.39.0(jiti@2.3.3)): dependencies: - '@babel/core': 7.26.0 - '@babel/parser': 7.27.7 - '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.26.0) - eslint: 8.57.1 + '@babel/core': 7.28.4 + '@babel/parser': 7.28.4 + eslint: 9.39.0(jiti@2.3.3) hermes-parser: 0.25.1 - zod: 3.23.8 - zod-validation-error: 3.4.0(zod@3.23.8) + zod: 4.1.5 + zod-validation-error: 4.0.2(zod@4.1.5) transitivePeerDependencies: - supports-color - eslint-plugin-react@7.37.2(eslint@8.57.1): + eslint-plugin-react@7.37.5(eslint@9.39.0(jiti@2.3.3)): dependencies: array-includes: 3.1.8 array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.2 + array.prototype.flatmap: 1.3.3 array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 - es-iterator-helpers: 1.1.0 - eslint: 8.57.1 + es-iterator-helpers: 1.2.1 + eslint: 9.39.0(jiti@2.3.3) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 minimatch: 3.1.2 - object.entries: 1.1.8 + object.entries: 1.1.9 object.fromentries: 2.0.8 - object.values: 1.2.0 + object.values: 1.2.1 prop-types: 15.8.1 resolve: 2.0.0-next.5 semver: 6.3.1 - string.prototype.matchall: 4.0.11 + string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 - eslint-plugin-svelte@2.46.0(eslint@9.13.0(jiti@2.3.3))(svelte@5.1.4)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@20.17.0)(typescript@5.6.3)): + eslint-plugin-svelte@2.46.0(eslint@9.13.0(jiti@2.3.3))(svelte@5.1.4)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@24.7.0)(typescript@5.6.3)): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.3.3)) - '@jridgewell/sourcemap-codec': 1.5.2 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.13.0(jiti@2.3.3)) + '@jridgewell/sourcemap-codec': 1.5.5 eslint: 9.13.0(jiti@2.3.3) eslint-compat-utils: 0.5.1(eslint@9.13.0(jiti@2.3.3)) esutils: 2.0.3 known-css-properties: 0.35.0 - postcss: 8.4.47 - postcss-load-config: 3.1.4(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@20.17.0)(typescript@5.6.3)) - postcss-safe-parser: 6.0.0(postcss@8.4.47) + postcss: 8.5.6 + postcss-load-config: 3.1.4(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@24.7.0)(typescript@5.6.3)) + postcss-safe-parser: 6.0.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 semver: 7.7.2 svelte-eslint-parser: 0.43.0(svelte@5.1.4) @@ -15574,75 +16704,81 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + eslint-visitor-keys@3.4.3: {} eslint-visitor-keys@4.1.0: {} - eslint@8.57.1: + eslint-visitor-keys@4.2.1: {} + + eslint@9.13.0(jiti@2.3.3): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.3.3)) '@eslint-community/regexpp': 4.11.1 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 + '@eslint/config-array': 0.18.0 + '@eslint/core': 0.7.0 + '@eslint/eslintrc': 3.1.0 + '@eslint/js': 9.13.0 + '@eslint/plugin-kit': 0.2.1 + '@humanfs/node': 0.16.5 '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 + '@humanwhocodes/retry': 0.3.1 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.7 - doctrine: 3.0.0 + cross-spawn: 7.0.6 + debug: 4.4.1(supports-color@9.4.0) escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 + eslint-scope: 8.1.0 + eslint-visitor-keys: 4.1.0 + espree: 10.2.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 + file-entry-cache: 8.0.0 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 - strip-ansi: 6.0.1 text-table: 0.2.0 + optionalDependencies: + jiti: 2.3.3 transitivePeerDependencies: - supports-color - eslint@9.13.0(jiti@2.3.3): - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.13.0(jiti@2.3.3)) - '@eslint-community/regexpp': 4.11.1 - '@eslint/config-array': 0.18.0 - '@eslint/core': 0.7.0 - '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.13.0 - '@eslint/plugin-kit': 0.2.1 - '@humanfs/node': 0.16.5 + eslint@9.39.0(jiti@2.3.3): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.0(jiti@2.3.3)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.39.0 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.3.1 + '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.1(supports-color@9.4.0) escape-string-regexp: 4.0.0 - eslint-scope: 8.1.0 - eslint-visitor-keys: 4.1.0 - espree: 10.2.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -15657,7 +16793,6 @@ snapshots: minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 - text-table: 0.2.0 optionalDependencies: jiti: 2.3.3 transitivePeerDependencies: @@ -15669,7 +16804,13 @@ snapshots: dependencies: acorn: 8.15.0 acorn-jsx: 5.3.2(acorn@8.15.0) - eslint-visitor-keys: 4.1.0 + eslint-visitor-keys: 4.2.1 + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 espree@9.6.1: dependencies: @@ -15685,7 +16826,7 @@ snapshots: esrap@1.2.2: dependencies: - '@jridgewell/sourcemap-codec': 1.5.2 + '@jridgewell/sourcemap-codec': 1.5.5 '@types/estree': 1.0.8 esrecurse@4.3.0: @@ -15852,7 +16993,7 @@ snapshots: ext-list@2.2.2: dependencies: - mime-db: 1.53.0 + mime-db: 1.54.0 ext-name@5.0.0: dependencies: @@ -15954,14 +17095,6 @@ snapshots: dependencies: pend: 1.2.0 - fdir@6.4.4(picomatch@4.0.2): - optionalDependencies: - picomatch: 4.0.2 - - fdir@6.4.4(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -16001,10 +17134,6 @@ snapshots: escape-string-regexp: 5.0.0 is-unicode-supported: 1.3.0 - file-entry-cache@6.0.1: - dependencies: - flat-cache: 3.2.0 - file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -16107,23 +17236,18 @@ snapshots: path-exists: 5.0.0 unicorn-magic: 0.1.0 - flat-cache@3.2.0: - dependencies: - flatted: 3.3.1 - keyv: 4.5.4 - rimraf: 3.0.2 - flat-cache@4.0.1: dependencies: - flatted: 3.3.1 + flatted: 3.3.3 keyv: 4.5.4 - flat-cache@5.0.0: + flat-cache@6.1.18: dependencies: - flatted: 3.3.1 - keyv: 4.5.4 + cacheable: 2.1.1 + flatted: 3.3.3 + hookified: 1.12.2 - flatted@3.3.1: {} + flatted@3.3.3: {} flush-write-stream@2.0.0: dependencies: @@ -16217,9 +17341,9 @@ snapshots: function.prototype.name@1.1.6: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.24.0 functions-have-names: 1.2.3 function.prototype.name@1.1.8: @@ -16306,9 +17430,9 @@ snapshots: get-symbol-description@1.0.2: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 get-symbol-description@1.1.0: dependencies: @@ -16387,10 +17511,6 @@ snapshots: globals@11.12.0: {} - globals@13.24.0: - dependencies: - type-fest: 0.20.2 - globals@14.0.0: {} globals@15.11.0: {} @@ -16439,7 +17559,7 @@ snapshots: gopd@1.0.1: dependencies: - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 gopd@1.2.0: {} @@ -16551,6 +17671,8 @@ snapshots: highlight.js@10.7.3: {} + hookified@1.12.2: {} + hosted-git-info@4.1.0: dependencies: lru-cache: 6.0.0 @@ -16652,6 +17774,8 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: {} + image-meta@0.2.1: {} import-fresh@3.3.0: @@ -16725,10 +17849,6 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 - invariant@2.2.4: - dependencies: - loose-envify: 1.4.0 - ipaddr.js@1.9.1: {} ipx@2.1.0(@netlify/blobs@8.1.0): @@ -16775,8 +17895,8 @@ snapshots: is-array-buffer@3.0.4: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 + call-bind: 1.0.8 + get-intrinsic: 1.3.0 is-array-buffer@3.0.5: dependencies: @@ -16788,10 +17908,6 @@ snapshots: is-arrayish@0.3.2: {} - is-async-function@2.0.0: - dependencies: - has-tostringtag: 1.0.2 - is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -16814,7 +17930,7 @@ snapshots: is-boolean-object@1.1.2: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 has-tostringtag: 1.0.2 is-boolean-object@1.2.2: @@ -16863,10 +17979,6 @@ snapshots: is-extglob@2.1.1: {} - is-finalizationregistry@1.0.2: - dependencies: - call-bind: 1.0.7 - is-finalizationregistry@1.1.1: dependencies: call-bound: 1.0.4 @@ -16881,10 +17993,6 @@ snapshots: dependencies: get-east-asian-width: 1.3.0 - is-generator-function@1.0.10: - dependencies: - has-tostringtag: 1.0.2 - is-generator-function@1.1.0: dependencies: call-bound: 1.0.4 @@ -16934,8 +18042,6 @@ snapshots: is-obj@2.0.0: {} - is-path-inside@3.0.3: {} - is-path-inside@4.0.0: {} is-plain-obj@1.1.0: {} @@ -16954,7 +18060,7 @@ snapshots: is-regex@1.1.4: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 has-tostringtag: 1.0.2 is-regex@1.2.1: @@ -16970,7 +18076,7 @@ snapshots: is-shared-array-buffer@1.0.3: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 is-shared-array-buffer@1.0.4: dependencies: @@ -16993,7 +18099,7 @@ snapshots: is-symbol@1.0.4: dependencies: - has-symbols: 1.0.3 + has-symbols: 1.1.0 is-symbol@1.1.1: dependencies: @@ -17025,7 +18131,7 @@ snapshots: is-weakref@1.0.2: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 is-weakref@1.1.1: dependencies: @@ -17058,12 +18164,15 @@ snapshots: isexe@3.1.1: {} - iterator.prototype@1.1.3: + isomorphic.js@0.2.5: {} + + iterator.prototype@1.1.5: dependencies: - define-properties: 1.2.1 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - reflect.getprototypeof: 1.0.6 + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 set-function-name: 2.0.2 jackspeak@3.4.3: @@ -17161,8 +18270,8 @@ snapshots: dependencies: array-includes: 3.1.8 array.prototype.flat: 1.3.2 - object.assign: 4.1.5 - object.values: 1.2.0 + object.assign: 4.1.7 + object.values: 1.2.1 junk@4.0.1: {} @@ -17187,6 +18296,10 @@ snapshots: dependencies: json-buffer: 3.0.1 + keyv@5.5.3: + dependencies: + '@keyv/serialize': 1.1.1 + kill-port@2.0.1: dependencies: get-them-args: 1.3.2 @@ -17231,6 +18344,10 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lib0@0.2.114: + dependencies: + isomorphic.js: 0.2.5 + light-my-request@5.14.0: dependencies: cookie: 0.7.2 @@ -17247,7 +18364,7 @@ snapshots: dependencies: uc.micro: 2.1.0 - linkifyjs@4.2.0: {} + linkifyjs@4.3.2: {} lint-staged@10.5.4: dependencies: @@ -17436,13 +18553,17 @@ snapshots: magic-string@0.30.12: dependencies: - '@jridgewell/sourcemap-codec': 1.5.2 + '@jridgewell/sourcemap-codec': 1.5.5 magic-string@0.30.19: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - make-cancellable-promise@1.3.2: {} + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-cancellable-promise@2.0.0: {} make-dir@3.1.0: dependencies: @@ -17454,7 +18575,7 @@ snapshots: make-error@1.3.6: {} - make-event-props@1.6.2: {} + make-event-props@2.0.0: {} map-obj@5.0.2: {} @@ -17482,6 +18603,8 @@ snapshots: node-emoji: 2.1.3 supports-hyperlinks: 3.1.0 + marked@16.4.0: {} + marked@4.3.0: {} marked@9.1.6: {} @@ -17678,9 +18801,9 @@ snapshots: dependencies: is-plain-obj: 2.1.0 - merge-refs@1.3.0(@types/react@19.0.1): + merge-refs@2.0.0(@types/react@19.2.2): optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 merge-stream@2.0.0: {} @@ -17890,8 +19013,6 @@ snapshots: mime-db@1.52.0: {} - mime-db@1.53.0: {} - mime-db@1.54.0: {} mime-types@2.1.35: @@ -17914,9 +19035,6 @@ snapshots: mimic-function@5.0.1: {} - mimic-response@2.1.0: - optional: true - mimic-response@3.1.0: {} mimic-response@4.0.0: {} @@ -18022,12 +19140,12 @@ snapshots: nested-error-stacks@2.1.1: {} - netlify-cli@17.37.1(@swc/core@1.7.39)(@types/node@20.17.0)(picomatch@4.0.3): + netlify-cli@17.37.1(@swc/core@1.7.39)(@types/node@24.7.0)(picomatch@4.0.3): dependencies: '@bugsnag/js': 7.25.0 '@fastify/static': 7.0.4 '@netlify/blobs': 8.1.0 - '@netlify/build': 29.55.2(@opentelemetry/api@1.8.0)(@swc/core@1.7.39)(@types/node@20.17.0)(picomatch@4.0.3) + '@netlify/build': 29.55.2(@opentelemetry/api@1.8.0)(@swc/core@1.7.39)(@types/node@24.7.0)(picomatch@4.0.3) '@netlify/build-info': 7.15.1 '@netlify/config': 20.19.0 '@netlify/edge-bundler': 12.2.3(supports-color@9.4.0) @@ -18232,7 +19350,7 @@ snapshots: node-source-walk@6.0.2: dependencies: - '@babel/parser': 7.27.7 + '@babel/parser': 7.28.4 node-stream-zip@1.15.0: {} @@ -18318,7 +19436,7 @@ snapshots: object.assign@4.1.5: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 has-symbols: 1.0.3 object-keys: 1.1.1 @@ -18332,18 +19450,19 @@ snapshots: has-symbols: 1.1.0 object-keys: 1.1.1 - object.entries@1.1.8: + object.entries@1.1.9: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.0.0 + es-object-atoms: 1.1.1 object.fromentries@2.0.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 object.groupby@1.0.3: dependencies: @@ -18357,6 +19476,13 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + ofetch@1.4.1: dependencies: destr: 2.0.3 @@ -18618,24 +19744,19 @@ snapshots: path-to-regexp@8.2.0: {} + path-to-regexp@8.3.0: {} + path-type@4.0.0: {} path-type@5.0.0: {} - path2d@0.2.1: - optional: true - pathe@1.1.2: {} pathval@2.0.0: {} - pdfjs-dist@4.4.168: + pdfjs-dist@5.4.296: optionalDependencies: - canvas: 2.11.2 - path2d: 0.2.1 - transitivePeerDependencies: - - encoding - - supports-color + '@napi-rs/canvas': 0.1.80 peek-readable@5.3.1: {} @@ -18645,8 +19766,6 @@ snapshots: picomatch@2.3.1: {} - picomatch@4.0.2: {} - picomatch@4.0.3: {} pify@2.3.0: {} @@ -18719,29 +19838,29 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@20.17.0)(typescript@5.6.3)): + postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@24.7.0)(typescript@5.6.3)): dependencies: lilconfig: 2.1.0 yaml: 1.10.2 optionalDependencies: - postcss: 8.4.47 - ts-node: 10.9.2(@swc/core@1.7.39)(@types/node@20.17.0)(typescript@5.6.3) + postcss: 8.5.6 + ts-node: 10.9.2(@swc/core@1.7.39)(@types/node@24.7.0)(typescript@5.6.3) - postcss-load-config@6.0.1(jiti@2.3.3)(postcss@8.4.49)(yaml@2.6.0): + postcss-load-config@6.0.1(jiti@2.3.3)(postcss@8.5.6)(yaml@2.6.0): dependencies: lilconfig: 3.1.2 optionalDependencies: jiti: 2.3.3 - postcss: 8.4.49 + postcss: 8.5.6 yaml: 2.6.0 - postcss-safe-parser@6.0.0(postcss@8.4.47): + postcss-safe-parser@6.0.0(postcss@8.5.6): dependencies: - postcss: 8.4.47 + postcss: 8.5.6 - postcss-scss@4.0.9(postcss@8.4.47): + postcss-scss@4.0.9(postcss@8.5.6): dependencies: - postcss: 8.4.47 + postcss: 8.5.6 postcss-selector-parser@6.1.2: dependencies: @@ -18750,11 +19869,11 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss-values-parser@6.0.2(postcss@8.4.47): + postcss-values-parser@6.0.2(postcss@8.5.6): dependencies: color-name: 1.1.4 is-url-superb: 4.0.0 - postcss: 8.4.47 + postcss: 8.5.6 quote-unquote: 1.0.0 postcss@8.4.47: @@ -18769,6 +19888,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prebuild-install@7.1.2: dependencies: detect-libc: 2.0.3 @@ -18809,12 +19934,12 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier-plugin-jsdoc@1.3.0(prettier@3.2.5): + prettier-plugin-jsdoc@1.3.0(prettier@3.6.2): dependencies: binary-searching: 2.0.5 comment-parser: 1.4.1 mdast-util-from-markdown: 2.0.1 - prettier: 3.2.5 + prettier: 3.6.2 transitivePeerDependencies: - supports-color @@ -18823,12 +19948,17 @@ snapshots: prettier: 3.3.3 svelte: 5.1.4 - prettier@3.0.3: {} + prettier-plugin-svelte@3.2.7(prettier@3.6.2)(svelte@5.1.4): + dependencies: + prettier: 3.6.2 + svelte: 5.1.4 - prettier@3.2.5: {} + prettier@3.0.3: {} prettier@3.3.3: {} + prettier@3.6.2: {} + pretty-bytes@5.6.0: {} pretty-bytes@6.1.1: {} @@ -18866,9 +19996,9 @@ snapshots: property-information@6.5.0: {} - prosemirror-changeset@2.2.1: + prosemirror-changeset@2.3.1: dependencies: - prosemirror-transform: 1.10.2 + prosemirror-transform: 1.10.3 prosemirror-collab@1.3.1: dependencies: @@ -18876,34 +20006,34 @@ snapshots: prosemirror-commands@1.6.2: dependencies: - prosemirror-model: 1.23.0 + prosemirror-model: 1.25.0 prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.2 + prosemirror-transform: 1.10.3 prosemirror-dropcursor@1.8.1: dependencies: prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.2 + prosemirror-transform: 1.10.3 prosemirror-view: 1.39.1 prosemirror-gapcursor@1.3.2: dependencies: prosemirror-keymap: 1.2.2 - prosemirror-model: 1.23.0 + prosemirror-model: 1.25.0 prosemirror-state: 1.4.3 prosemirror-view: 1.39.1 prosemirror-history@1.4.1: dependencies: prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.2 + prosemirror-transform: 1.10.3 prosemirror-view: 1.39.1 rope-sequence: 1.3.4 prosemirror-inputrules@1.4.0: dependencies: prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.2 + prosemirror-transform: 1.10.3 prosemirror-keymap@1.2.2: dependencies: @@ -18914,7 +20044,7 @@ snapshots: dependencies: '@types/markdown-it': 14.1.2 markdown-it: 14.1.0 - prosemirror-model: 1.23.0 + prosemirror-model: 1.25.0 prosemirror-menu@1.2.4: dependencies: @@ -18923,28 +20053,24 @@ snapshots: prosemirror-history: 1.4.1 prosemirror-state: 1.4.3 - prosemirror-model@1.23.0: - dependencies: - orderedmap: 2.1.1 - prosemirror-model@1.25.0: dependencies: orderedmap: 2.1.1 prosemirror-schema-basic@1.2.3: dependencies: - prosemirror-model: 1.23.0 + prosemirror-model: 1.25.0 - prosemirror-schema-list@1.4.1: + prosemirror-schema-list@1.5.1: dependencies: - prosemirror-model: 1.23.0 + prosemirror-model: 1.25.0 prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.2 + prosemirror-transform: 1.10.3 prosemirror-state@1.4.3: dependencies: - prosemirror-model: 1.23.0 - prosemirror-transform: 1.10.2 + prosemirror-model: 1.25.0 + prosemirror-transform: 1.10.3 prosemirror-view: 1.39.1 prosemirror-tables@1.7.0: @@ -18955,27 +20081,23 @@ snapshots: prosemirror-transform: 1.10.3 prosemirror-view: 1.39.1 - prosemirror-trailing-node@3.0.0(prosemirror-model@1.23.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1): + prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.0)(prosemirror-state@1.4.3)(prosemirror-view@1.39.1): dependencies: '@remirror/core-constants': 3.0.0 escape-string-regexp: 4.0.0 - prosemirror-model: 1.23.0 + prosemirror-model: 1.25.0 prosemirror-state: 1.4.3 prosemirror-view: 1.39.1 - prosemirror-transform@1.10.2: - dependencies: - prosemirror-model: 1.23.0 - prosemirror-transform@1.10.3: dependencies: prosemirror-model: 1.25.0 prosemirror-view@1.39.1: dependencies: - prosemirror-model: 1.23.0 + prosemirror-model: 1.25.0 prosemirror-state: 1.4.3 - prosemirror-transform: 1.10.2 + prosemirror-transform: 1.10.3 proto-list@1.2.4: {} @@ -19012,6 +20134,10 @@ snapshots: dependencies: escape-goat: 4.0.0 + qified@0.5.1: + dependencies: + hookified: 1.12.2 + qs@6.13.0: dependencies: side-channel: 1.0.6 @@ -19063,46 +20189,46 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-colorful@5.6.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + react-colorful@5.6.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) - react-dom@19.0.0(react@19.0.0): + react-dom@19.2.0(react@19.2.0): dependencies: - react: 19.0.0 - scheduler: 0.25.0 + react: 19.2.0 + scheduler: 0.27.0 - react-dropzone@11.7.1(react@19.0.0): + react-dropzone@11.7.1(react@19.2.0): dependencies: attr-accept: 2.2.4 file-selector: 0.4.0 prop-types: 15.8.1 - react: 19.0.0 + react: 19.2.0 - react-hot-toast@2.4.1(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + react-hot-toast@2.4.1(csstype@3.1.3)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: goober: 2.1.16(csstype@3.1.3) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) transitivePeerDependencies: - csstype - react-hotkeys-hook@3.4.7(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + react-hotkeys-hook@3.4.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: hotkeys-js: 3.9.4 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) - react-icons@4.12.0(react@19.0.0): + react-icons@4.12.0(react@19.2.0): dependencies: - react: 19.0.0 + react: 19.2.0 - react-intersection-observer@9.13.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + react-intersection-observer@9.13.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - react: 19.0.0 + react: 19.2.0 optionalDependencies: - react-dom: 19.0.0(react@19.0.0) + react-dom: 19.2.0(react@19.2.0) react-is@16.13.1: {} @@ -19110,97 +20236,93 @@ snapshots: react-is@18.2.0: {} - react-is@19.0.0: {} + react-is@19.2.0: {} - react-markdown@9.0.3(@types/react@19.0.1)(react@19.0.0): + react-markdown@9.0.3(@types/react@19.2.2)(react@19.2.0): dependencies: '@types/hast': 3.0.4 - '@types/react': 19.0.1 + '@types/react': 19.2.2 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.2 html-url-attributes: 3.0.1 mdast-util-to-hast: 13.2.0 - react: 19.0.0 + react: 19.2.0 remark-parse: 11.0.0 - remark-rehype: 11.1.1 + remark-rehype: 11.1.2 unified: 11.0.5 unist-util-visit: 5.0.0 vfile: 6.0.3 transitivePeerDependencies: - supports-color - react-pdf@9.1.1(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + react-pdf@10.2.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: clsx: 2.1.1 dequal: 2.0.3 - make-cancellable-promise: 1.3.2 - make-event-props: 1.6.2 - merge-refs: 1.3.0(@types/react@19.0.1) - pdfjs-dist: 4.4.168 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + make-cancellable-promise: 2.0.0 + make-event-props: 2.0.0 + merge-refs: 2.0.0(@types/react@19.2.2) + pdfjs-dist: 5.4.296 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) tiny-invariant: 1.3.3 warning: 4.0.3 optionalDependencies: - '@types/react': 19.0.1 - transitivePeerDependencies: - - encoding - - supports-color + '@types/react': 19.2.2 - react-refresh@0.14.2: {} + react-refresh@0.17.0: {} - react-remove-scroll-bar@2.3.6(@types/react@19.0.1)(react@19.0.0): + react-remove-scroll-bar@2.3.8(@types/react@19.2.2)(react@19.2.0): dependencies: - react: 19.0.0 - react-style-singleton: 2.2.1(@types/react@19.0.1)(react@19.0.0) + react: 19.2.0 + react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.0) tslib: 2.8.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - react-remove-scroll@2.6.0(@types/react@19.0.1)(react@19.0.0): + react-remove-scroll@2.7.1(@types/react@19.2.2)(react@19.2.0): dependencies: - react: 19.0.0 - react-remove-scroll-bar: 2.3.6(@types/react@19.0.1)(react@19.0.0) - react-style-singleton: 2.2.1(@types/react@19.0.1)(react@19.0.0) + react: 19.2.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.2)(react@19.2.0) + react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.0) tslib: 2.8.0 - use-callback-ref: 1.3.2(@types/react@19.0.1)(react@19.0.0) - use-sidecar: 1.1.2(@types/react@19.0.1)(react@19.0.0) + use-callback-ref: 1.3.3(@types/react@19.2.2)(react@19.2.0) + use-sidecar: 1.1.3(@types/react@19.2.2)(react@19.2.0) optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - react-style-singleton@2.2.1(@types/react@19.0.1)(react@19.0.0): + react-style-singleton@2.2.3(@types/react@19.2.2)(react@19.2.0): dependencies: get-nonce: 1.0.1 - invariant: 2.2.4 - react: 19.0.0 + react: 19.2.0 tslib: 2.8.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - react-virtualized-auto-sizer@1.0.24(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + react-virtualized-auto-sizer@1.0.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) - react-window@1.8.10(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + react-window@1.8.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@babel/runtime': 7.25.9 memoize-one: 5.2.1 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) - react@19.0.0: {} + react@19.2.0: {} - reactflow@11.11.4(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + reactflow@11.11.4(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - '@reactflow/background': 11.3.14(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@reactflow/controls': 11.2.14(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@reactflow/core': 11.11.4(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@reactflow/minimap': 11.7.14(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@reactflow/node-resizer': 2.2.14(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - '@reactflow/node-toolbar': 1.3.14(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + '@reactflow/background': 11.3.14(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@reactflow/controls': 11.2.14(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@reactflow/core': 11.11.4(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@reactflow/minimap': 11.7.14(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@reactflow/node-resizer': 2.2.14(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@reactflow/node-toolbar': 1.3.14(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) transitivePeerDependencies: - '@types/react' - immer @@ -19283,16 +20405,6 @@ snapshots: get-proto: 1.0.1 which-builtin-type: 1.2.1 - reflect.getprototypeof@1.0.6: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - globalthis: 1.0.4 - which-builtin-type: 1.1.4 - regenerate-unicode-properties@10.2.0: dependencies: regenerate: 1.4.2 @@ -19303,7 +20415,7 @@ snapshots: regexp.prototype.flags@1.5.3: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-errors: 1.3.0 set-function-name: 2.0.2 @@ -19360,7 +20472,7 @@ snapshots: transitivePeerDependencies: - supports-color - remark-rehype@11.1.1: + remark-rehype@11.1.2: dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 @@ -19471,6 +20583,34 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.24.0 fsevents: 2.3.3 + rollup@4.52.5: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.52.5 + '@rollup/rollup-android-arm64': 4.52.5 + '@rollup/rollup-darwin-arm64': 4.52.5 + '@rollup/rollup-darwin-x64': 4.52.5 + '@rollup/rollup-freebsd-arm64': 4.52.5 + '@rollup/rollup-freebsd-x64': 4.52.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.5 + '@rollup/rollup-linux-arm-musleabihf': 4.52.5 + '@rollup/rollup-linux-arm64-gnu': 4.52.5 + '@rollup/rollup-linux-arm64-musl': 4.52.5 + '@rollup/rollup-linux-loong64-gnu': 4.52.5 + '@rollup/rollup-linux-ppc64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-gnu': 4.52.5 + '@rollup/rollup-linux-riscv64-musl': 4.52.5 + '@rollup/rollup-linux-s390x-gnu': 4.52.5 + '@rollup/rollup-linux-x64-gnu': 4.52.5 + '@rollup/rollup-linux-x64-musl': 4.52.5 + '@rollup/rollup-openharmony-arm64': 4.52.5 + '@rollup/rollup-win32-arm64-msvc': 4.52.5 + '@rollup/rollup-win32-ia32-msvc': 4.52.5 + '@rollup/rollup-win32-x64-gnu': 4.52.5 + '@rollup/rollup-win32-x64-msvc': 4.52.5 + fsevents: 2.3.3 + rope-sequence@1.3.4: {} router@2.2.0: @@ -19503,8 +20643,8 @@ snapshots: safe-array-concat@1.1.2: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 + call-bind: 1.0.8 + get-intrinsic: 1.3.0 has-symbols: 1.0.3 isarray: 2.0.5 @@ -19527,12 +20667,6 @@ snapshots: es-errors: 1.3.0 isarray: 2.0.5 - safe-regex-test@1.0.3: - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-regex: 1.1.4 - safe-regex-test@1.1.0: dependencies: call-bound: 1.0.4 @@ -19554,7 +20688,7 @@ snapshots: mkdirp: 0.5.6 rimraf: 2.7.1 - scheduler@0.25.0: {} + scheduler@0.27.0: {} secure-json-parse@2.7.0: {} @@ -19710,9 +20844,9 @@ snapshots: side-channel@1.0.6: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 es-errors: 1.3.0 - get-intrinsic: 1.2.4 + get-intrinsic: 1.3.0 object-inspect: 1.13.2 side-channel@1.1.0: @@ -19731,13 +20865,6 @@ snapshots: simple-concat@1.0.1: {} - simple-get@3.1.1: - dependencies: - decompress-response: 4.2.1 - once: 1.4.0 - simple-concat: 1.0.1 - optional: true - simple-get@4.0.1: dependencies: decompress-response: 6.0.0 @@ -19792,7 +20919,7 @@ snapshots: sorcery@0.11.1: dependencies: - '@jridgewell/sourcemap-codec': 1.5.2 + '@jridgewell/sourcemap-codec': 1.5.5 buffer-crc32: 1.0.0 minimist: 1.2.8 sander: 0.5.1 @@ -19904,24 +21031,9 @@ snapshots: string.prototype.includes@2.0.1: dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - - string.prototype.matchall@4.0.11: - dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.4 - gopd: 1.0.1 - has-symbols: 1.0.3 - internal-slot: 1.0.7 - regexp.prototype.flags: 1.5.3 - set-function-name: 2.0.2 - side-channel: 1.0.6 + es-abstract: 1.24.0 string.prototype.matchall@4.0.12: dependencies: @@ -19942,7 +21054,7 @@ snapshots: string.prototype.repeat@1.0.0: dependencies: define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.24.0 string.prototype.trim@1.2.10: dependencies: @@ -19956,10 +21068,10 @@ snapshots: string.prototype.trim@1.2.9: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 string.prototype.trimend@1.0.8: dependencies: @@ -20057,7 +21169,7 @@ snapshots: dependencies: inline-style-parser: 0.2.4 - styled-components@6.1.19(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + styled-components@6.1.19(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@emotion/is-prop-valid': 1.2.2 '@emotion/unitless': 0.8.1 @@ -20065,8 +21177,8 @@ snapshots: css-to-react-native: 3.2.0 csstype: 3.1.3 postcss: 8.4.49 - react: 19.0.0 - react-dom: 19.0.0(react@19.0.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) shallowequal: 1.1.0 stylis: 4.3.2 tslib: 2.6.2 @@ -20111,15 +21223,15 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@3.8.6(@babel/core@7.26.0)(postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@20.17.0)(typescript@5.6.3)))(postcss@8.4.47)(svelte@5.1.4): + svelte-check@3.8.6(@babel/core@7.28.4)(postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@24.7.0)(typescript@5.6.3)))(postcss@8.5.6)(svelte@5.1.4): dependencies: '@jridgewell/trace-mapping': 0.3.25 chokidar: 3.6.0 picocolors: 1.1.1 sade: 1.8.1 svelte: 5.1.4 - svelte-preprocess: 5.1.4(@babel/core@7.26.0)(postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@20.17.0)(typescript@5.6.3)))(postcss@8.4.47)(svelte@5.1.4)(typescript@5.6.3) - typescript: 5.6.3 + svelte-preprocess: 5.1.4(@babel/core@7.28.4)(postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@24.7.0)(typescript@5.6.3)))(postcss@8.5.6)(svelte@5.1.4)(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - '@babel/core' - coffeescript @@ -20136,24 +21248,24 @@ snapshots: eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 - postcss: 8.4.47 - postcss-scss: 4.0.9(postcss@8.4.47) + postcss: 8.5.6 + postcss-scss: 4.0.9(postcss@8.5.6) optionalDependencies: svelte: 5.1.4 - svelte-preprocess@5.1.4(@babel/core@7.26.0)(postcss-load-config@3.1.4(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@20.17.0)(typescript@5.6.3)))(postcss@8.4.47)(svelte@5.1.4)(typescript@5.6.3): + svelte-preprocess@5.1.4(@babel/core@7.28.4)(postcss-load-config@3.1.4(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@24.7.0)(typescript@5.6.3)))(postcss@8.5.6)(svelte@5.1.4)(typescript@5.9.3): dependencies: '@types/pug': 2.0.10 detect-indent: 6.1.0 - magic-string: 0.30.12 + magic-string: 0.30.19 sorcery: 0.11.1 strip-indent: 3.0.0 svelte: 5.1.4 optionalDependencies: - '@babel/core': 7.26.0 - postcss: 8.4.47 - postcss-load-config: 3.1.4(postcss@8.4.47)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@20.17.0)(typescript@5.6.3)) - typescript: 5.6.3 + '@babel/core': 7.28.4 + postcss: 8.5.6 + postcss-load-config: 3.1.4(postcss@8.5.6)(ts-node@10.9.2(@swc/core@1.7.39)(@types/node@24.7.0)(typescript@5.6.3)) + typescript: 5.9.3 svelte2tsx@0.7.22(svelte@5.1.4)(typescript@5.6.3): dependencies: @@ -20188,11 +21300,11 @@ snapshots: csso: 5.0.5 picocolors: 1.1.1 - swr@2.3.6(react@19.0.0): + swr@2.3.6(react@19.2.0): dependencies: dequal: 2.0.3 - react: 19.0.0 - use-sync-external-store: 1.4.0(react@19.0.0) + react: 19.2.0 + use-sync-external-store: 1.4.0(react@19.2.0) synckit@0.9.2: dependencies: @@ -20341,8 +21453,8 @@ snapshots: tinyglobby@0.2.9: dependencies: - fdir: 6.4.4(picomatch@4.0.2) - picomatch: 4.0.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 tinypool@1.0.1: {} @@ -20350,13 +21462,9 @@ snapshots: tinyspy@3.0.2: {} - tippy.js@6.3.7: - dependencies: - '@popperjs/core': 2.11.8 - - tiptap-markdown@0.8.10(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)): + tiptap-markdown@0.8.10(@tiptap/core@3.7.2(@tiptap/pm@3.7.2)): dependencies: - '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/core': 3.7.2(@tiptap/pm@3.7.2) '@types/markdown-it': 13.0.9 markdown-it: 14.1.0 markdown-it-task-lists: 2.1.1 @@ -20419,16 +21527,20 @@ snapshots: dependencies: typescript: 5.6.3 + ts-api-utils@2.1.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@swc/core@1.7.39)(@types/node@20.17.0)(typescript@5.6.3): + ts-node@10.9.2(@swc/core@1.7.39)(@types/node@24.7.0)(typescript@5.6.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.17.0 + '@types/node': 24.7.0 acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3 @@ -20440,6 +21552,27 @@ snapshots: yn: 3.1.1 optionalDependencies: '@swc/core': 1.7.39 + optional: true + + ts-node@10.9.2(@swc/core@1.7.39)(@types/node@24.7.0)(typescript@5.9.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 24.7.0 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optionalDependencies: + '@swc/core': 1.7.39 tsconfig-paths@3.15.0: dependencies: @@ -20454,7 +21587,7 @@ snapshots: tslib@2.8.0: {} - tsup@8.3.5(@microsoft/api-extractor@7.48.0(@types/node@20.17.0))(@swc/core@1.7.39)(jiti@2.3.3)(postcss@8.4.49)(typescript@5.6.3)(yaml@2.6.0): + tsup@8.3.5(@microsoft/api-extractor@7.48.0(@types/node@24.7.0))(@swc/core@1.7.39)(jiti@2.3.3)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.6.0): dependencies: bundle-require: 5.0.0(esbuild@0.24.0) cac: 6.7.14 @@ -20464,7 +21597,7 @@ snapshots: esbuild: 0.24.0 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(jiti@2.3.3)(postcss@8.4.49)(yaml@2.6.0) + postcss-load-config: 6.0.1(jiti@2.3.3)(postcss@8.5.6)(yaml@2.6.0) resolve-from: 5.0.0 rollup: 4.24.0 source-map: 0.8.0-beta.0 @@ -20473,20 +21606,20 @@ snapshots: tinyglobby: 0.2.9 tree-kill: 1.2.2 optionalDependencies: - '@microsoft/api-extractor': 7.48.0(@types/node@20.17.0) + '@microsoft/api-extractor': 7.48.0(@types/node@24.7.0) '@swc/core': 1.7.39 - postcss: 8.4.49 - typescript: 5.6.3 + postcss: 8.5.6 + typescript: 5.9.3 transitivePeerDependencies: - jiti - supports-color - tsx - yaml - tsutils@3.21.0(typescript@5.6.3): + tsutils@3.21.0(typescript@5.9.3): dependencies: tslib: 1.14.1 - typescript: 5.6.3 + typescript: 5.9.3 tunnel-agent@0.6.0: dependencies: @@ -20498,8 +21631,6 @@ snapshots: type-fest@0.16.0: {} - type-fest@0.20.2: {} - type-fest@0.21.3: {} type-fest@0.8.1: {} @@ -20523,7 +21654,7 @@ snapshots: typed-array-buffer@1.0.2: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 es-errors: 1.3.0 is-typed-array: 1.1.13 @@ -20535,10 +21666,10 @@ snapshots: typed-array-byte-length@1.0.1: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 + gopd: 1.2.0 + has-proto: 1.2.0 is-typed-array: 1.1.13 typed-array-byte-length@1.0.3: @@ -20552,10 +21683,10 @@ snapshots: typed-array-byte-offset@1.0.2: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.7 + call-bind: 1.0.8 for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 + gopd: 1.2.0 + has-proto: 1.2.0 is-typed-array: 1.1.13 typed-array-byte-offset@1.0.4: @@ -20570,10 +21701,10 @@ snapshots: typed-array-length@1.0.6: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 + gopd: 1.2.0 + has-proto: 1.2.0 is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 @@ -20590,17 +21721,17 @@ snapshots: dependencies: is-typedarray: 1.0.0 - typedoc-plugin-missing-exports@2.3.0(typedoc@0.25.13(typescript@5.6.3)): + typedoc-plugin-missing-exports@2.3.0(typedoc@0.25.13(typescript@5.9.3)): dependencies: - typedoc: 0.25.13(typescript@5.6.3) + typedoc: 0.25.13(typescript@5.9.3) - typedoc@0.25.13(typescript@5.6.3): + typedoc@0.25.13(typescript@5.9.3): dependencies: lunr: 2.3.9 marked: 4.3.0 minimatch: 9.0.5 shiki: 0.14.7 - typescript: 5.6.3 + typescript: 5.9.3 types-wm@1.1.0: {} @@ -20621,6 +21752,8 @@ snapshots: typescript@5.6.3: {} + typescript@5.9.3: {} + uc.micro@2.1.0: {} ufo@1.5.4: {} @@ -20637,9 +21770,9 @@ snapshots: unbox-primitive@1.0.2: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 has-bigints: 1.0.2 - has-symbols: 1.0.3 + has-symbols: 1.1.0 which-boxed-primitive: 1.0.2 unbox-primitive@1.1.0: @@ -20658,6 +21791,8 @@ snapshots: undici-types@6.19.8: {} + undici-types@7.14.0: {} + unenv@1.10.0: dependencies: consola: 3.2.3 @@ -20792,28 +21927,28 @@ snapshots: urlpattern-polyfill@8.0.2: {} - use-callback-ref@1.3.2(@types/react@19.0.1)(react@19.0.0): + use-callback-ref@1.3.3(@types/react@19.2.2)(react@19.2.0): dependencies: - react: 19.0.0 + react: 19.2.0 tslib: 2.8.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - use-sidecar@1.1.2(@types/react@19.0.1)(react@19.0.0): + use-sidecar@1.1.3(@types/react@19.2.2)(react@19.2.0): dependencies: detect-node-es: 1.1.0 - react: 19.0.0 + react: 19.2.0 tslib: 2.8.0 optionalDependencies: - '@types/react': 19.0.1 + '@types/react': 19.2.2 - use-sync-external-store@1.2.2(react@19.0.0): + use-sync-external-store@1.2.2(react@19.2.0): dependencies: - react: 19.0.0 + react: 19.2.0 - use-sync-external-store@1.4.0(react@19.0.0): + use-sync-external-store@1.4.0(react@19.2.0): dependencies: - react: 19.0.0 + react: 19.2.0 util-deprecate@1.0.2: {} @@ -20846,12 +21981,12 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 - vite-node@2.1.3(@types/node@20.17.0)(terser@5.43.1): + vite-node@2.1.3(@types/node@24.7.0)(terser@5.43.1): dependencies: cac: 6.7.14 debug: 4.4.1(supports-color@9.4.0) pathe: 1.1.2 - vite: 5.4.10(@types/node@20.17.0)(terser@5.43.1) + vite: 5.4.10(@types/node@24.7.0)(terser@5.43.1) transitivePeerDependencies: - '@types/node' - less @@ -20871,45 +22006,60 @@ snapshots: - prismjs - supports-color - vite-plugin-pwa@0.20.5(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0): + vite-plugin-pwa@1.1.0(vite@7.1.12(@types/node@24.7.0)(jiti@2.3.3)(terser@5.43.1)(yaml@2.6.0))(workbox-build@7.1.1(@types/babel__core@7.20.5))(workbox-window@7.1.0): dependencies: - debug: 4.3.7 + debug: 4.4.1(supports-color@9.4.0) pretty-bytes: 6.1.1 - tinyglobby: 0.2.9 - vite: 5.4.10(@types/node@20.17.0)(terser@5.43.1) + tinyglobby: 0.2.15 + vite: 7.1.12(@types/node@24.7.0)(jiti@2.3.3)(terser@5.43.1)(yaml@2.6.0) workbox-build: 7.1.1(@types/babel__core@7.20.5) workbox-window: 7.1.0 transitivePeerDependencies: - supports-color - vite-plugin-webfont-dl@3.9.5(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)): + vite-plugin-webfont-dl@3.11.1(vite@7.1.12(@types/node@24.7.0)(jiti@2.3.3)(terser@5.43.1)(yaml@2.6.0)): dependencies: axios: 1.7.7 clean-css: 5.3.3 - flat-cache: 5.0.0 + flat-cache: 6.1.18 picocolors: 1.1.1 - vite: 5.4.10(@types/node@20.17.0)(terser@5.43.1) + vite: 7.1.12(@types/node@24.7.0)(jiti@2.3.3)(terser@5.43.1)(yaml@2.6.0) transitivePeerDependencies: - debug - vite@5.4.10(@types/node@20.17.0)(terser@5.43.1): + vite@5.4.10(@types/node@24.7.0)(terser@5.43.1): dependencies: esbuild: 0.21.5 postcss: 8.4.47 rollup: 4.24.0 optionalDependencies: - '@types/node': 20.17.0 + '@types/node': 24.7.0 + fsevents: 2.3.3 + terser: 5.43.1 + + vite@7.1.12(@types/node@24.7.0)(jiti@2.3.3)(terser@5.43.1)(yaml@2.6.0): + dependencies: + esbuild: 0.25.11 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.5 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.7.0 fsevents: 2.3.3 + jiti: 2.3.3 terser: 5.43.1 + yaml: 2.6.0 - vitefu@1.0.3(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)): + vitefu@1.0.3(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)): optionalDependencies: - vite: 5.4.10(@types/node@20.17.0)(terser@5.43.1) + vite: 5.4.10(@types/node@24.7.0)(terser@5.43.1) - vitest@2.1.3(@types/node@20.17.0)(terser@5.43.1): + vitest@2.1.3(@types/node@24.7.0)(terser@5.43.1): dependencies: '@vitest/expect': 2.1.3 - '@vitest/mocker': 2.1.3(@vitest/spy@2.1.3)(vite@5.4.10(@types/node@20.17.0)(terser@5.43.1)) + '@vitest/mocker': 2.1.3(@vitest/spy@2.1.3)(vite@5.4.10(@types/node@24.7.0)(terser@5.43.1)) '@vitest/pretty-format': 2.1.3 '@vitest/runner': 2.1.3 '@vitest/snapshot': 2.1.3 @@ -20924,11 +22074,11 @@ snapshots: tinyexec: 0.3.1 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.10(@types/node@20.17.0)(terser@5.43.1) - vite-node: 2.1.3(@types/node@20.17.0)(terser@5.43.1) + vite: 5.4.10(@types/node@24.7.0)(terser@5.43.1) + vite-node: 2.1.3(@types/node@24.7.0)(terser@5.43.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 20.17.0 + '@types/node': 24.7.0 transitivePeerDependencies: - less - lightningcss @@ -20984,7 +22134,7 @@ snapshots: is-bigint: 1.0.4 is-boolean-object: 1.1.2 is-number-object: 1.0.7 - is-string: 1.0.7 + is-string: 1.1.1 is-symbol: 1.0.4 which-boxed-primitive@1.1.1: @@ -20995,21 +22145,6 @@ snapshots: is-string: 1.1.1 is-symbol: 1.1.1 - which-builtin-type@1.1.4: - dependencies: - function.prototype.name: 1.1.6 - has-tostringtag: 1.0.2 - is-async-function: 2.0.0 - is-date-object: 1.0.5 - is-finalizationregistry: 1.0.2 - is-generator-function: 1.0.10 - is-regex: 1.1.4 - is-weakref: 1.0.2 - isarray: 2.0.5 - which-boxed-primitive: 1.0.2 - which-collection: 1.0.2 - which-typed-array: 1.1.15 - which-builtin-type@1.2.1: dependencies: call-bound: 1.0.4 @@ -21036,9 +22171,9 @@ snapshots: which-typed-array@1.1.15: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.7 + call-bind: 1.0.8 for-each: 0.3.3 - gopd: 1.0.1 + gopd: 1.2.0 has-tostringtag: 1.0.2 which-typed-array@1.1.19: @@ -21110,10 +22245,10 @@ snapshots: workbox-build@7.1.1(@types/babel__core@7.20.5): dependencies: '@apideck/better-ajv-errors': 0.3.6(ajv@8.17.1) - '@babel/core': 7.26.0 - '@babel/preset-env': 7.27.2(@babel/core@7.26.0) + '@babel/core': 7.28.4 + '@babel/preset-env': 7.27.2(@babel/core@7.28.4) '@babel/runtime': 7.27.6 - '@rollup/plugin-babel': 5.3.1(@babel/core@7.26.0)(@types/babel__core@7.20.5)(rollup@2.79.2) + '@rollup/plugin-babel': 5.3.1(@babel/core@7.28.4)(@types/babel__core@7.20.5)(rollup@2.79.2) '@rollup/plugin-node-resolve': 15.3.1(rollup@2.79.2) '@rollup/plugin-replace': 2.4.2(rollup@2.79.2) '@rollup/plugin-terser': 0.4.4(rollup@2.79.2) @@ -21256,12 +22391,13 @@ snapshots: ws@8.17.1: {} - wuchale@0.16.5: + wuchale@0.18.3: dependencies: - '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) + '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) acorn: 8.15.0 chokidar: 4.0.3 - magic-string: 0.30.19 + magic-string: 0.30.21 + path-to-regexp: 8.3.0 picomatch: 4.0.3 pofile: 1.1.4 tinyglobby: 0.2.15 @@ -21275,6 +22411,11 @@ snapshots: xtend@4.0.2: {} + y-protocols@1.0.6(yjs@13.6.27): + dependencies: + lib0: 0.2.114 + yjs: 13.6.27 + y18n@5.0.8: {} yallist@3.1.1: {} @@ -21314,6 +22455,10 @@ snapshots: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 + yjs@13.6.27: + dependencies: + lib0: 0.2.114 + yn@3.1.1: {} yocto-queue@0.1.0: {} @@ -21332,9 +22477,9 @@ snapshots: dependencies: zod: 3.25.76 - zod-validation-error@3.4.0(zod@3.23.8): + zod-validation-error@4.0.2(zod@4.1.5): dependencies: - zod: 3.23.8 + zod: 4.1.5 zod@3.23.8: {} @@ -21342,11 +22487,11 @@ snapshots: zod@4.1.5: {} - zustand@4.5.5(@types/react@19.0.1)(react@19.0.0): + zustand@4.5.5(@types/react@19.2.2)(react@19.2.0): dependencies: - use-sync-external-store: 1.2.2(react@19.0.0) + use-sync-external-store: 1.2.2(react@19.2.0) optionalDependencies: - '@types/react': 19.0.1 - react: 19.0.0 + '@types/react': 19.2.2 + react: 19.2.0 zwitch@2.0.4: {} diff --git a/browser/react/package.json b/browser/react/package.json index 38c12a5db..e9976ce8e 100644 --- a/browser/react/package.json +++ b/browser/react/package.json @@ -13,20 +13,21 @@ }, "dependencies": { "@tomic/lib": "workspace:*", - "react": "^19.0.0", - "react-dom": "^19.0.0" + "react": "^19.2.0", + "react-dom": "^19.2.0" }, "devDependencies": { "@arethetypeswrong/cli": "^0.17.0", - "@types/react": "^19.0.0", - "@types/react-dom": "^19.0.0", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", "@types/react-router-dom": "^5.3.3", "tsup": "^8.3.5", - "typescript": "^5.6.3" + "typescript": "^5.9.3", + "yjs": "^13.6.27" }, "peerDependencies": { - "react": ">18.3.0", - "react-dom": ">18.3.0" + "react": ">19.2.0", + "react-dom": ">19.2.0" }, "files": [ "dist", @@ -57,9 +58,10 @@ }, "scripts": { "build": "tsup", - "lint": "eslint ./src --ext .js,.jsx,.ts,.tsx", + "lint": "eslint ./src --ext .js,.jsx,.ts,.tsx && pnpm prettier-check ./src", "lint-fix": "eslint ./src --ext .js,.jsx,.ts,.tsx --fix", "lint-package": "pnpm attw && pnpm publint", + "prettier-check": "prettier --check ./src", "prepublishOnly": "pnpm run lint && pnpm run build && pnpm run lint-package", "start": "pnpm watch", "watch": "tsup --watch", diff --git a/browser/react/src/components/Image.tsx b/browser/react/src/components/Image.tsx index e77553038..f92c20e8c 100644 --- a/browser/react/src/components/Image.tsx +++ b/browser/react/src/components/Image.tsx @@ -208,9 +208,9 @@ const toUrl = ( ) => { const url = new URL(base); const queryParams = new URLSearchParams(); - format && queryParams.set('f', format); - width && queryParams.set('w', width.toString()); - quality && queryParams.set('q', quality.toString()); + if (format) queryParams.set('f', format); + if (width) queryParams.set('w', width.toString()); + if (quality) queryParams.set('q', quality.toString()); url.search = queryParams.toString(); return url.toString(); diff --git a/browser/react/src/helpers/isDev.ts b/browser/react/src/helpers/isDev.ts index d609ff0ee..ca6f0603f 100644 --- a/browser/react/src/helpers/isDev.ts +++ b/browser/react/src/helpers/isDev.ts @@ -1,5 +1,5 @@ /** Returns true if this is run in locally, in Development mode */ export function isDev(): boolean { - //@ts-ignore This key does exist + // @ts-expect-error This key does exist return import.meta.env['MODE'] === 'development'; } diff --git a/browser/react/src/helpers/useOnValueChange.ts b/browser/react/src/helpers/useOnValueChange.ts new file mode 100644 index 000000000..9d2094842 --- /dev/null +++ b/browser/react/src/helpers/useOnValueChange.ts @@ -0,0 +1,10 @@ +import { useState } from 'react'; + +export function useOnValueChange(callback: () => void, dependants: unknown[]) { + const [deps, setDeps] = useState(dependants); + + if (deps.some((d, i) => d !== dependants[i])) { + setDeps(dependants); + callback(); + } +} diff --git a/browser/react/src/hooks.ts b/browser/react/src/hooks.ts index cf670310d..033ecd9bb 100644 --- a/browser/react/src/hooks.ts +++ b/browser/react/src/hooks.ts @@ -30,6 +30,16 @@ import { core, server, } from '@tomic/lib'; +import type * as Y from 'yjs'; +import { useOnValueChange } from './helpers/useOnValueChange.js'; + +export type UseResourceOptions = FetchOpts & { + /** + * If provided, the hook will only update the resource when the properties in this array change value. + * Useful for avoiding large rerenders in performance critical components. + */ + track?: string[]; +}; /** * Hook for getting a Resource in a React component. Will try to fetch the @@ -37,19 +47,30 @@ import { */ export function useResource<C extends OptionalClass = never>( subject: string = unknownSubject, - opts?: FetchOpts, + opts: UseResourceOptions = {}, ): Resource<C> { + const { track, ...fetchOpts } = opts; const store = useStore(); + const [prevSubject, setPrevSubject] = useState(subject); const [resource, setResource] = useState<Resource<C>>(() => - store.getResourceLoading(subject, opts), + store.getResourceLoading(subject, fetchOpts), + ); + const unsubLoadingChangeRef = useRef( + resource.on(ResourceEvents.LoadingChange, () => { + setResource(proxyResource(resource.stable)); + }), ); - const memoizedOpts = useMemoizedOpts(opts); - // If the subject changes, make sure to change the resource! - // When a component mounts, it needs to let the store know that it will subscribe to changes to that resource. - useEffect(() => { + const memoizedOpts = useMemoizedOpts(fetchOpts); + + // Update the resource when the subject changes + if (subject !== prevSubject) { + setPrevSubject(subject); setResource(proxyResource(store.getResourceLoading(subject, memoizedOpts))); + } + // When a component mounts or the subject changes, it needs to let the store know that it will subscribe to changes to that resource. + useEffect(() => { return store.subscribe(subject, (updated: Resource<C>) => { setResource(proxyResource(updated)); }); @@ -58,10 +79,23 @@ export function useResource<C extends OptionalClass = never>( }, [store, subject, memoizedOpts]); useEffect(() => { - return resource.__internalObject.on(ResourceEvents.LocalChange, () => { - setResource(proxyResource(resource.__internalObject)); + return resource.stable.on(ResourceEvents.LocalChange, prop => { + if (track === undefined || track.includes(prop)) { + setResource(proxyResource(resource.stable)); + } }); - }, [store, resource.__internalObject]); + }, [resource.stable, track]); + + // Update the proxy when the resource is done loading. + useEffect(() => { + if (unsubLoadingChangeRef.current) { + unsubLoadingChangeRef.current(); + } + + return resource.stable.on(ResourceEvents.LoadingChange, () => { + setResource(proxyResource(resource.stable)); + }); + }, [resource.stable]); return resource; } @@ -77,10 +111,30 @@ export function useResources( opts: FetchOpts = {}, ): Map<string, Resource> { const [resources, setResources] = useState(new Map<string, Resource>()); + const [prevSubjects, setPrevSubjects] = useState<string[]>([]); const store = useStore(); const memoizedOpts = useMemoizedOpts(opts); + if (subjects !== prevSubjects) { + setPrevSubjects(subjects); + setResources(prev => { + const newResources = new Map<string, Resource>(); + + for (const subject of subjects) { + const resource = store.getResourceLoading(subject, memoizedOpts); + + if (!prev.has(subject)) { + newResources.set(subject, proxyResource(resource)); + } else { + newResources.set(subject, prev.get(subject)!); + } + } + + return newResources; + }); + } + useEffect(() => { // When a change happens, set the new Resource. function handleNotify(updated: Resource) { @@ -92,25 +146,33 @@ export function useResources( }); } - setResources(prev => { - for (const subject of subjects) { - const resource = store.getResourceLoading(subject, memoizedOpts); - prev.set(subject, proxyResource(resource)); + const unsubLoadingFuncs: (() => void)[] = []; - // Let the store know to call handleNotify when a resource is updated. - store.subscribe(subject, handleNotify); - } + for (const resource of resources.values()) { + store.subscribe(resource.subject, handleNotify); + unsubLoadingFuncs.push( + resource.on(ResourceEvents.LoadingChange, () => { + setResources(prev => { + prev.set(resource.subject, proxyResource(resource)); - return new Map(prev); - }); + // We need to create new Maps for react hooks to update - React only checks references, not content + return new Map(prev); + }); + }), + ); + } return () => { // When the component is unmounted, unsubscribe from the store. - for (const subject of subjects) { - store.unsubscribe(subject, handleNotify); + for (const resource of resources.values()) { + store.unsubscribe(resource.subject, handleNotify); + } + + for (const unsubLoadingFunc of unsubLoadingFuncs) { + unsubLoadingFunc(); } }; - }, [subjects, store, memoizedOpts]); + }, [resources, store, memoizedOpts]); return resources; } @@ -244,12 +306,12 @@ export function useValue( timeoutId.current = setTimeout(async () => { try { - await resource.save(); + await resource.__internalObject.save(); } catch (e) { store.notifyError(e); } }, commitDebounce); - }, [resource, store, commitDebounce, commit]); + }, [resource.__internalObject, store, commitDebounce, commit]); /** * Validates the value. If it fails, it calls the function in the second @@ -259,7 +321,7 @@ export function useValue( async (newVal: JSONValue): Promise<void> => { if (newVal === undefined) { // remove the value - resource.remove(propertyURL); + resource.__internalObject.remove(propertyURL); set(undefined); saveResource(); @@ -271,7 +333,7 @@ export function useValue( // Validates and sets a property / value combination. Will invoke the // callback if the value is not valid. try { - await resource.set(propertyURL, newVal, validate); + await resource.__internalObject.set(propertyURL, newVal, validate); saveResource(); handleValidationError?.(undefined); } catch (e) { @@ -284,7 +346,8 @@ export function useValue( }, [ - resource, + // Optimization: We don't need to track the whole resource here since the underlying reference is stable. + resource.__internalObject, handleValidationError, store, validate, @@ -293,14 +356,6 @@ export function useValue( ], ); - useEffect(() => { - return resource.on(ResourceEvents.LocalChange, (prop, value) => { - if (prop === propertyURL) { - set(value); - } - }); - }, [resource, propertyURL]); - // Update value when resource changes. if (resource !== prevResourceReference) { let localVal: JSONValue | undefined; @@ -534,6 +589,30 @@ export function useDate( } } +/** + * Gets or creates a Yjs document for the given property. returns undefined if the resource is still loading. + */ +export function useYDoc( + resource: Resource, + propertyURL: string, +): Y.Doc | undefined { + const [doc, setDoc] = useState<Y.Doc | undefined>(() => + resource.loading ? undefined : resource.getYDoc(propertyURL), + ); + + useEffect(() => { + return resource.stable.on(ResourceEvents.LoadingChange, () => { + setDoc( + resource.stable.loading + ? undefined + : resource.stable.getYDoc(propertyURL), + ); + }); + }, [resource.stable, propertyURL]); + + return doc; +} + /** Preferred way of using the store in a Component or Hook */ export function useStore(): Store { const store = useContext(StoreContext); @@ -555,8 +634,7 @@ export function useCanWrite(resource: Resource): boolean { const [canWrite, setCanWrite] = useState<boolean>(false); const agent = store.getAgent(); - // If the subject changes, make sure to change the resource! - useEffect(() => { + useOnValueChange(() => { if (agent?.subject === undefined) { setCanWrite(false); @@ -565,15 +643,18 @@ export function useCanWrite(resource: Resource): boolean { if (resource.new) { setCanWrite(true); - - return; } - - resource.canWrite(agent.subject).then(([result]) => { - setCanWrite(result); - }); }, [resource, agent?.subject]); + // If the subject changes, make sure to change the resource! + useEffect(() => { + if (agent && !resource.new) { + resource.canWrite(agent.subject).then(([result]) => { + setCanWrite(result); + }); + } + }, [resource, agent]); + return canWrite; } diff --git a/browser/react/src/useCollection.ts b/browser/react/src/useCollection.ts index f0db5658e..e50361ba3 100644 --- a/browser/react/src/useCollection.ts +++ b/browser/react/src/useCollection.ts @@ -55,11 +55,11 @@ const buildCollection = ( ) => { const builder = new CollectionBuilder(store, server); - property && builder.setProperty(property); - value && builder.setValue(value); - sort_by && builder.setSortBy(sort_by); - sort_desc !== undefined && builder.setSortDesc(sort_desc); - pageSize && builder.setPageSize(pageSize); + if (property) builder.setProperty(property); + if (value) builder.setValue(value); + if (sort_by) builder.setSortBy(sort_by); + if (sort_desc !== undefined) builder.setSortDesc(sort_desc); + if (pageSize) builder.setPageSize(pageSize); return builder.build(); }; @@ -102,12 +102,11 @@ export function useCollection( collection.waitForReady().then(() => { setCollection(proxyCollection(collection.__internalObject)); }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { if (firstRun) { - setFirstRun(false); - return; } @@ -120,13 +119,14 @@ export function useCollection( newCollection.waitForReady().then(() => { setCollection(proxyCollection(newCollection.__internalObject)); + setFirstRun(false); }); - }, [queryFilterMemo, pageSize, store, server]); + }, [queryFilterMemo, pageSize, store, server, firstRun]); const invalidateCollection = useCallback(async () => { - await collection.refresh(); + await collection.__internalObject.refresh(); setCollection(proxyCollection(collection.__internalObject)); - }, [collection, store, server, queryFilter, pageSize]); + }, [collection.__internalObject]); return { collection, invalidateCollection, mapAll }; } @@ -134,6 +134,7 @@ export function useCollection( function useQueryFilterMemo(queryFilter: QueryFilter) { return useMemo( () => queryFilter, + // eslint-disable-next-line react-hooks/exhaustive-deps [ queryFilter.property, queryFilter.value, diff --git a/browser/react/src/useDebounce.ts b/browser/react/src/useDebounce.ts index 1e6dda21d..1b98338a5 100644 --- a/browser/react/src/useDebounce.ts +++ b/browser/react/src/useDebounce.ts @@ -1,4 +1,4 @@ -import type { Resource } from '@tomic/lib'; +import { Resource } from '@tomic/lib'; import { useCallback, useEffect, useRef, useState } from 'react'; // T is a generic type for value parameter, our case this will be string @@ -41,7 +41,7 @@ export function useDebouncedSave( timeoutId.current = setTimeout(async () => { try { - await resource.save(); + await resource.__internalObject.save(); setSavePending(false); } catch (e) { if (onError) { @@ -53,7 +53,7 @@ export function useDebouncedSave( }, timeout); setSavePending(true); - }, [resource, timeout, onError]); + }, [resource.__internalObject, timeout, onError]); return [save, savePending]; } diff --git a/browser/react/src/useMarkdown.ts b/browser/react/src/useMarkdown.ts index a51e3c9e1..5463be0d8 100644 --- a/browser/react/src/useMarkdown.ts +++ b/browser/react/src/useMarkdown.ts @@ -1,12 +1,12 @@ import { Datatype, - JSONValue, properties, Resource, Store, urls, valToArray, valToDate, + type AtomicValue, } from '@tomic/lib'; import { useEffect, useState } from 'react'; import { useStore, useString, useTitle } from './index.js'; @@ -57,7 +57,7 @@ export function useMarkdown(resource: Resource): string { } getPropValTexts(); - }, [resource]); + }, [resource, description, store, title]); if (resource.error) { return resource.error.message; @@ -69,7 +69,7 @@ export function useMarkdown(resource: Resource): string { /** Renders a single Atomic Property + Value as a single Markdown line */ async function propertyLine( propertySubject: string, - value: JSONValue, + value: AtomicValue, store: Store, ): Promise<string> { const property = await store.getProperty(propertySubject); diff --git a/browser/react/src/useServerSearch.tsx b/browser/react/src/useServerSearch.tsx index 4f47927c4..38e9ff510 100644 --- a/browser/react/src/useServerSearch.tsx +++ b/browser/react/src/useServerSearch.tsx @@ -1,12 +1,8 @@ -import { - buildSearchSubject, - removeCachedSearchResults, - SearchOpts, - urls, -} from '@tomic/lib'; -import { useEffect, useMemo, useState } from 'react'; -import { useArray, useResource, useServerURL, useStore } from './index.js'; +import { removeCachedSearchResults, SearchOpts } from '@tomic/lib'; +import { useEffect, useEffectEvent, useState } from 'react'; +import { useStore } from './index.js'; import { useDebounce } from './useDebounce.js'; +import { useOnValueChange } from './helpers/useOnValueChange.js'; interface SearchResults { /** Subject URLs for resources that match the query */ @@ -24,12 +20,6 @@ interface SearchOptsHook extends SearchOpts { allowEmptyQuery?: boolean; } -const noResultsResult = { - results: [], - loading: false, - error: undefined, -}; - /** Escape values for use in filter string */ export const escapeFilterValue = (value: string) => value.replace(/[+^`:{}"[\]()!\\*\s]/gm, '\\$&'); @@ -39,55 +29,65 @@ export function useServerSearch( query: string | undefined, opts: SearchOptsHook = {}, ): SearchResults { - const { debounce = 50 } = opts; + const { debounce = 50, allowEmptyQuery = false, ...searchOpts } = opts; const store = useStore(); const [results, setResults] = useState<string[]>([]); - const [serverURL] = useServerURL(); - // Calculating the query takes a while, so we debounce it + const [error, setError] = useState<Error | undefined>(undefined); + const [loading, setLoading] = useState(false); const debouncedQuery = useDebounce(query, debounce) ?? ''; - const searchSubjectURL: string = useMemo( - () => buildSearchSubject(serverURL, debouncedQuery, opts), - [debouncedQuery, opts, serverURL], - ); + useOnValueChange(() => { + if (debouncedQuery) { + setLoading(true); + } + + if (!debouncedQuery && !allowEmptyQuery) { + setResults([]); + setLoading(false); + } + }, [debouncedQuery, allowEmptyQuery]); - const resource = useResource(searchSubjectURL, { - noWebSocket: true, - }); + const updateResults = useEffectEvent( + (r: string[], relevantQuery: string, relevantOpts: SearchOpts) => { + // If the query became empty since the last fetch, don't update the results + if (relevantQuery !== debouncedQuery || relevantOpts !== searchOpts) { + return; + } - const [resultsIn] = useArray(resource, urls.properties.endpoint.results); + setResults(r); + }, + ); - // Only set new results if the resource is no longer loading, which improves UX useEffect(() => { - if (!resource.loading && resultsIn) { - setResults(resultsIn as string[]); + if (!debouncedQuery && !allowEmptyQuery) { + return; } - }, [ - // Prevent re-rendering if the resultsIn is the same - resultsIn?.toString(), - resource.loading, - ]); + + store + .search(debouncedQuery, searchOpts) + .then(r => { + updateResults(r, debouncedQuery, searchOpts); + setError(undefined); + }) + .catch(e => { + setError(e); + setResults([]); + }) + .finally(() => { + setLoading(false); + }); + }, [store, allowEmptyQuery, debouncedQuery, searchOpts]); // Remove cached results when component unmounts. useEffect(() => { return () => { removeCachedSearchResults(store); }; - }, []); - - const result = useMemo( - () => ({ - results, - loading: resource.loading, - error: resource.error, - }), - [results, resource.loading, resource.error], - ); - - if (!query && !opts.allowEmptyQuery) { - return noResultsResult; - } + }, [store]); - // Return the width so we can use it in our components - return result; + return { + results, + loading, + error, + }; } diff --git a/browser/svelte/eslint.config.js b/browser/svelte/eslint.config.js index e0ff2f538..48540c777 100644 --- a/browser/svelte/eslint.config.js +++ b/browser/svelte/eslint.config.js @@ -20,7 +20,7 @@ export default [ }, }, { - files: ['**/*.svelte'], + files: ['**/*.svelte', '**/*.svlete.ts'], languageOptions: { parserOptions: { parser: ts.parser, diff --git a/browser/svelte/package.json b/browser/svelte/package.json index 07fe3a18e..fd412d701 100644 --- a/browser/svelte/package.json +++ b/browser/svelte/package.json @@ -27,8 +27,8 @@ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "test": "vitest run", - "lint": "prettier --check . && eslint .", - "format": "prettier --write ." + "lint": "prettier --check . && eslint . && pnpm prettier-check", + "prettier-check": "prettier --check ./src" }, "exports": { ".": { diff --git a/browser/svelte/src/lib/stores/getResource.svelte.ts b/browser/svelte/src/lib/stores/getResource.svelte.ts index 020102269..0c2b58acf 100644 --- a/browser/svelte/src/lib/stores/getResource.svelte.ts +++ b/browser/svelte/src/lib/stores/getResource.svelte.ts @@ -66,6 +66,10 @@ export function getResource<T extends OptionalClass = never>( resource = proxyResource(resource.__internalObject); }); + const unsubLoading = resource.on(ResourceEvents.LoadingChange, () => { + resource = proxyResource(resource.__internalObject); + }); + const unsubRemote = store.subscribe(subject, r => { resource = proxyResource(r); }); @@ -73,6 +77,7 @@ export function getResource<T extends OptionalClass = never>( return () => { unsubLocal(); unsubRemote(); + unsubLoading(); }; }); diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 6aa81ddbe..25832780b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -13,6 +13,7 @@ atomic_lib = { version = "0.40.0", path = "../lib", features = [ "config", "rdf", ] } +base64 = "0.21" clap = { version = "4", features = ["cargo", "derive"] } colored = "2" dirs = "4" diff --git a/cli/src/new.rs b/cli/src/new.rs index 22694fac6..fda14b6b8 100644 --- a/cli/src/new.rs +++ b/cli/src/new.rs @@ -8,6 +8,7 @@ use atomic_lib::{ schema::{Class, Property}, Resource, Storelike, Value, }; +use base64::engine::{general_purpose, Engine}; use colored::Colorize; use promptly::prompt_opt; use regex::Regex; @@ -168,6 +169,15 @@ fn prompt_field( check_valid_json(&json).unwrap(); return Ok(Some(json)); } + DataType::YDoc => { + let msg = format!("YDoc{}", msg_appendix); + let Some(ydoc) = prompt_opt::<String, String>(msg)? else { + return Ok(None); + }; + // Check if it is a valid Base64 string + general_purpose::STANDARD.decode(&ydoc).unwrap(); + return Ok(Some(ydoc)); + } DataType::Integer => { let msg = format!("integer{}", msg_appendix); let number: Option<u32> = prompt_opt(msg)?; diff --git a/docs/src/commits/concepts.md b/docs/src/commits/concepts.md index 8584814be..072ec66e1 100644 --- a/docs/src/commits/concepts.md +++ b/docs/src/commits/concepts.md @@ -22,6 +22,7 @@ The **optional method fields** describe how the data must be changed: - `remove` - an array of Properties that need to be removed (including their values). - `set` - a Nested Resource which contains all the new or edited fields. - `push` - a Nested Resource which contains all the fields that are _appended_ to. This means adding items to a new or existing ResourceArray. +- `yUpdate` - a Nested Resource which contains Yjs updates (v2) for the given properties. These commands are executed in the order above. This means that you can set `destroy` to `true` and include `set`, which empties the existing resource and sets new values. @@ -84,7 +85,7 @@ Congratulations, you've just created a valid Commit! Here are currently working implementations of this process, including serialization and signing (links are permalinks). - [in Rust (atomic-lib)](https://github.com/atomicdata-dev/atomic-server/blob/ceb88c1ae58811f2a9e6bacb7eaa39a2a7aa1513/lib/src/commit.rs#L81). -- [in Typescript / Javascript (atomic-data-browser)](https://github.com/atomicdata-dev/atomic-data-browser/blob/fc899bb2cf54bdff593ee6b4debf52e20a85619e/src/atomic-lib/commit.ts#L51). +- [in Typescript / Javascript (atomic-data-browser)](https://github.com/atomicdata-dev/atomic-server/blob/6947650263d56e6c70a7f726ed0a51c0f4d8f25c/browser/lib/src/commit.ts#L299). If you want validate your implementation, check out the tests for these two projects. diff --git a/docs/src/core/json-ad.md b/docs/src/core/json-ad.md index 11913ec20..c9858017a 100644 --- a/docs/src/core/json-ad.md +++ b/docs/src/core/json-ad.md @@ -24,6 +24,7 @@ The types of values allowed are determined by the [datatype](../schema/datatypes - **atomic-url** datatype fields must be either a `string` (url) or an `object` (nested resource). - **resource-array** datatype fields must be an `array` of strings (must be a url) or objects (must be an nested resource). - **json** datatype fields can be any valid JSON value. +- **ydoc** datatype fields must be an `object` with a `type` field set to `"ydoc"` and a `data` field set to a base64-encoded [Yjs update v2](https://github.com/yjs/yjs). Named Resources are only allowed in the following places: diff --git a/docs/src/js-lib/agent.md b/docs/src/js-lib/agent.md index 9b98388da..d084fa270 100644 --- a/docs/src/js-lib/agent.md +++ b/docs/src/js-lib/agent.md @@ -4,14 +4,47 @@ An agent is an authenticated identity that can interact with Atomic Data resourc All writes in AtomicServer are signed by an agent and can therefore be proven to be authentic. Read more about agents in the [Atomic Data specification](../agents.md). -## Creating an Agent instance +## Agent Secret -Creating an agent can be done in two ways, either by using the `Agent` constructor or by using the `Agent.fromSecret` method. +Agents can be encoded into a single string called a secret. +This secret contains the private key and the subject of the agent. + +Encoding and decoding secrets is easy: + +```ts +// Encode as secret +const secret = agent.buildSecret(); + +// Decode from secret +const agent = Agent.fromSecret(secret); +``` + +## Manual creation + +It is recommended to use the `Agent.fromSecret` method to create an agent instance but you can also manually create an agent instance by passing in the private key and the subject. ```typescript const agent = new Agent('my-private-key', 'my-agent-subject'); ``` +## Advanced + +### Getting the public key + +If you need the agents public key you can use the async `getPublicKey` method. + +```typescript +const publicKey = await agent.getPublicKey(); +``` + +This will generate a public key from the private key and cache it on the agent instance. + +### Verifying the public key + +If you need to verify the public key of the agent you can use the `verifyPublicKeyWithServer` method. + ```typescript -const agent = Agent.fromSecret('my-long-secret-string'); +await agent.verifyPublicKeyWithServer(); ``` + +This will fetch the agent from the server and check if the public key matches the one on the agent instance. diff --git a/docs/src/js-lib/resource.md b/docs/src/js-lib/resource.md index 0377b2a26..cedf19db5 100644 --- a/docs/src/js-lib/resource.md +++ b/docs/src/js-lib/resource.md @@ -309,6 +309,46 @@ const version = userPicksVersion(versions); await resource.setVersion(version); ``` +## Yjs Documents + +AtomicServer supports Yjs documents as a datatype. +Using these you can build powerful collaborative editors. +Yjs documents are synced via atomic commits when you call `resource.save()`, just like regular properties. +This means that you don't have to use any provider server to sync the documents. + +To use any Yjs related feature you first need to install the `yjs` package using your package manager of choice. +You also need to tell @tomic/lib that Yjs is available by calling the following function somewhere early on in your application. + +```typescript +import { enableYjs } from '@tomic/lib'; + +await enableYjs(); +``` + +This will load the Yjs module and make it available to @tomic/lib. + +### Using Yjs documents + +To get a Yjs document from a resource, use the `.getYDoc` method and pass the property of the value containing the document. +If the value is still empty, a new document will be created and returned. +You can then use the Yjs doc like you would normally with Yjs. +Any change made to the document will be merged into the current commit. +When you call `resource.save()`, the changes will be synced to the server and with other clients. + +```typescript +const doc = resource.getYDoc('https://my-atomicserver.com/properties/yjs-document'); + +const text = doc.getText('content'); +const cursors = doc.getMap('cursors'); + +doc.transact(() => { + text.insert(0, 'Hello, world!'); + cursors.set(someClientId, 13); +}); + +await resource.save(); +``` + ## Useful methods and properties ### Subject diff --git a/docs/src/schema/datatypes.md b/docs/src/schema/datatypes.md index c16147c6d..0ac587706 100644 --- a/docs/src/schema/datatypes.md +++ b/docs/src/schema/datatypes.md @@ -138,3 +138,19 @@ example: 9883 ] ``` + +## YDoc + +_URL: `https://atomicdata.dev/datatypes/ydoc`_ + +A [Yjs document](https://github.com/yjs/yjs). +Stores a Yjs document state. (uses the update v2 format). +They are updated using commits via the [yUpdate](https://atomicdata.dev/properties/yUpdate) property. +When encoded into a JSON-AD value it will look like this: + +```json +{ + "type": "ydoc", + "data": "base64-encoded-updates" +} +``` diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 9f4ad688c..58f179eae 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -39,6 +39,7 @@ ureq = "2" url = "2" urlencoding = "2" ulid = "1.1.3" +yrs = "0.24.0" [dev-dependencies] criterion = "0.5" diff --git a/lib/defaults/default_store.json b/lib/defaults/default_store.json index 28bc191b1..9e6c06028 100644 --- a/lib/defaults/default_store.json +++ b/lib/defaults/default_store.json @@ -760,6 +760,16 @@ ], "https://atomicdata.dev/properties/shortname": "set" }, + { + "@id": "https://atomicdata.dev/properties/yUpdate", + "https://atomicdata.dev/properties/datatype": "https://atomicdata.dev/datatypes/atomicURL", + "https://atomicdata.dev/properties/description": "A field in a commit.\\\nNested resource mapping properties to Yjs state updates.", + "https://atomicdata.dev/properties/isA": [ + "https://atomicdata.dev/classes/Property" + ], + "https://atomicdata.dev/properties/parent": "https://atomicdata.dev/properties", + "https://atomicdata.dev/properties/shortname": "y-update" + }, { "@id": "https://atomicdata.dev/properties/secret", "https://atomicdata.dev/properties/datatype": "https://atomicdata.dev/datatypes/string", @@ -899,7 +909,9 @@ "https://atomicdata.dev/properties/recommends": [ "https://atomicdata.dev/properties/destroy", "https://atomicdata.dev/properties/remove", - "https://atomicdata.dev/properties/set" + "https://atomicdata.dev/properties/set", + "https://atomicdata.dev/properties/push", + "https://atomicdata.dev/properties/yUpdate" ], "https://atomicdata.dev/properties/requires": [ "https://atomicdata.dev/properties/createdAt", @@ -1148,6 +1160,15 @@ "https://atomicdata.dev/properties/parent": "https://atomicdata.dev/datatypes", "https://atomicdata.dev/properties/shortname": "json" }, + { + "@id": "https://atomicdata.dev/datatypes/ydoc", + "https://atomicdata.dev/properties/description": "A Yjs update-v2 encoded as base64", + "https://atomicdata.dev/properties/isA": [ + "https://atomicdata.dev/classes/Datatype" + ], + "https://atomicdata.dev/properties/parent": "https://atomicdata.dev/datatypes", + "https://atomicdata.dev/properties/shortname": "ydoc" + }, { "@id": "https://atomicdata.dev/classes/Folder", "https://atomicdata.dev/properties/description": "Acts as a parent for resources, useful for ordering data.", diff --git a/lib/src/commit.rs b/lib/src/commit.rs index 1ef735cb4..ba43c75eb 100644 --- a/lib/src/commit.rs +++ b/lib/src/commit.rs @@ -1,9 +1,5 @@ //! Describe changes / mutations to data -use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet}; -use urls::{SET, SIGNER}; - use crate::{ agents::{decode_base64, encode_base64}, datatype::DataType, @@ -13,7 +9,9 @@ use crate::{ values::SubResource, Atom, Resource, Storelike, Value, }; - +use serde::{Deserialize, Serialize}; +use std::collections::{HashMap, HashSet}; +use urls::{SET, SIGNER}; /// The `resource_new`, `resource_old` and `commit_resource` fields are only created if the Commit is persisted. /// When the Db is only notifying other of changes (e.g. if a new Message was added to a ChatRoom), these fields are not created. /// When deleting a resource, the `resource_new` field is None. @@ -90,8 +88,11 @@ pub struct Commit { /// Overwrites existing values #[serde(rename = "https://atomicdata.dev/properties/set")] pub set: Option<std::collections::HashMap<String, Value>>, - /// The set of property URLs that need to be removed + /// A map of properties and the Yjs updates to be applied to them (must be Value::YDoc) + #[serde(rename = "https://atomicdata.dev/properties/yUpdate")] + pub y_update: Option<std::collections::HashMap<String, Value>>, #[serde(rename = "https://atomicdata.dev/properties/remove")] + /// The set of property URLs that need to be removed pub remove: Option<Vec<String>>, /// If set to true, deletes the entire resource #[serde(rename = "https://atomicdata.dev/properties/destroy")] @@ -352,6 +353,40 @@ impl Commit { } } } + if let Some(y_update) = self.y_update.clone() { + for (prop, update) in y_update.iter() { + let update_bin = match update { + Value::YDoc(bin) => bin, + _ => { + return Err( + format!("Value in y_update is not of type YDoc: {}", prop).into() + ) + } + }; + + match resource.get(prop) { + Ok(val) => match val { + Value::YDoc(bin) => { + // Resource already has state so we will merge the update into it. + // let decoded_state = yrs::Update::decode_v2(bin) + // .map_err(|e| format!("Error decoding Yjs state: {}", e))?; + + // We can merge the state (that is saved as an update) and the incoming update without having to create a Yjs doc. + let merged_update = yrs::merge_updates_v2(vec![bin, update_bin]) + .map_err(|e| format!("Error merging Yjs updates: {}", e))?; + + resource.set(prop.into(), Value::YDoc(merged_update), store)?; + } + _ => return Err(format!("Property is not of type YDoc: {}", prop).into()), + }, + _ => { + // The property was not set yet so we initialize it with the update. + resource.set(prop.into(), Value::YDoc(update_bin.clone()), store)?; + } + }; + // We don't create any atoms because indexing yjs updates doesn't make much sense. + } + } // Remove all atoms from index if destroy if let Some(destroy) = self.destroy { if destroy { @@ -383,6 +418,10 @@ impl Commit { Ok(found) => Some(found.to_nested()?.to_owned()), Err(_) => None, }; + let y_update = match resource.get(urls::Y_UPDATE) { + Ok(found) => Some(found.to_nested()?.to_owned()), + Err(_) => None, + }; let remove = match resource.get(urls::REMOVE) { Ok(found) => Some(found.to_subjects(None)?), Err(_) => None, @@ -404,6 +443,7 @@ impl Commit { signer, set, push, + y_update, remove, destroy, previous_commit, @@ -463,6 +503,13 @@ impl Commit { Value::AtomicUrl(previous_commit.into()), ); } + if let Some(y_update) = &self.y_update { + let mut newy_update = PropVals::new(); + for (prop, val) in y_update { + newy_update.insert(prop.into(), val.clone()); + } + resource.set_unsafe(urls::Y_UPDATE.into(), newy_update.into()); + } resource.set_unsafe( SIGNER.into(), Value::new(&self.signer, &DataType::AtomicUrl)?, @@ -513,6 +560,8 @@ pub struct CommitBuilder { set: std::collections::HashMap<String, Value>, /// The set of PropVals that need to be appended to resource arrays. push: std::collections::HashMap<String, Value>, + /// A map of Propvals containing Yjs updates to be applied to the YDocs + y_update: std::collections::HashMap<String, Value>, /// The set of property URLs that need to be removed /// https://atomicdata.dev/properties/remove remove: HashSet<String>, @@ -532,6 +581,7 @@ impl CommitBuilder { push: HashMap::new(), subject, set: HashMap::new(), + y_update: HashMap::new(), remove: HashSet::new(), destroy: false, previous_commit: None, @@ -584,6 +634,16 @@ impl CommitBuilder { self.subject = subject; } + pub fn add_y_update(&mut self, prop: String, update: Value) -> AtomicResult<()> { + match update { + Value::YDoc(_) => { + self.y_update.insert(prop, update); + Ok(()) + } + _ => Err(format!("Expected YDoc in add_y_update, got {}", update).into()), + } + } + /// Set Property URLs which values to be removed pub fn remove(&mut self, prop: String) { self.remove.insert(prop); @@ -607,6 +667,7 @@ fn sign_at( subject: commitbuilder.subject, signer: agent.subject.clone(), set: Some(commitbuilder.set), + y_update: Some(commitbuilder.y_update), remove: Some(commitbuilder.remove.into_iter().collect()), destroy: Some(commitbuilder.destroy), created_at: sign_date, @@ -717,6 +778,7 @@ mod test { signer: String::from("https://localhost/author"), set: Some(set), push: None, + y_update: None, remove: Some(remove), previous_commit: None, destroy: Some(destroy), diff --git a/lib/src/datatype.rs b/lib/src/datatype.rs index 9be33e24b..4626c43d3 100644 --- a/lib/src/datatype.rs +++ b/lib/src/datatype.rs @@ -19,6 +19,7 @@ pub enum DataType { Timestamp, Uri, JSON, + YDoc, Unsupported(String), } @@ -36,6 +37,7 @@ pub fn match_datatype(string: &str) -> DataType { urls::TIMESTAMP => DataType::Timestamp, urls::URI => DataType::Uri, urls::JSON => DataType::JSON, + urls::YDOC => DataType::YDoc, unsupported_datatype => DataType::Unsupported(unsupported_datatype.into()), } } @@ -57,6 +59,7 @@ impl std::str::FromStr for DataType { urls::TIMESTAMP => DataType::Timestamp, urls::URI => DataType::Uri, urls::JSON => DataType::JSON, + urls::YDOC => DataType::YDoc, unsupported_datatype => DataType::Unsupported(unsupported_datatype.into()), }) } @@ -77,6 +80,7 @@ impl fmt::Display for DataType { DataType::Timestamp => write!(f, "{}", urls::TIMESTAMP), DataType::Uri => write!(f, "{}", urls::URI), DataType::JSON => write!(f, "{}", urls::JSON), + DataType::YDoc => write!(f, "{}", urls::YDOC), DataType::Unsupported(url) => write!(f, "{}", url), } } diff --git a/lib/src/parse.rs b/lib/src/parse.rs index fa84bbe0e..0e60376ef 100644 --- a/lib/src/parse.rs +++ b/lib/src/parse.rs @@ -426,6 +426,33 @@ fn parse_propval( Some(&prop), )); } + DataType::YDoc => { + let serde_json::Value::Object(map) = val else { + return Err(AtomicError::parse_error( + "Invalid value for YDoc, must be of shape { type: \"ydoc\", data: <base64 string> }", + subject.as_deref(), + Some(&prop), + )); + }; + + let Some(data) = map.get("data") else { + return Err(AtomicError::parse_error( + "Invalid value for YDoc, no data field", + subject.as_deref(), + Some(&prop), + )); + }; + + let serde_json::Value::String(data) = data else { + return Err(AtomicError::parse_error( + "Invalid value for YDoc, data field must be a string", + subject.as_deref(), + Some(&prop), + )); + }; + + Value::new(data.as_str(), &DataType::YDoc)? + } }; Ok((prop, atomic_val)) @@ -544,8 +571,8 @@ fn parse_json_ad_map_to_resource( let importer = parse_opts.importer.as_deref().unwrap(); if !orig.has_parent(store, importer) { Err( - format!("Cannot overwrite {subj} outside of importer! Enable `overwrite_outside`"), - )? + format!("Cannot overwrite {subj} outside of importer! Enable `overwrite_outside`"), + )? } }; orig diff --git a/lib/src/serialize.rs b/lib/src/serialize.rs index 62f363d63..88092ad98 100644 --- a/lib/src/serialize.rs +++ b/lib/src/serialize.rs @@ -1,5 +1,6 @@ //! Serialization / formatting / encoding (JSON, RDF, N-Triples) +use base64::engine::{general_purpose, Engine}; use serde_json::Map; use serde_json::Value as SerdeValue; use tracing::instrument; @@ -60,6 +61,15 @@ fn val_to_serde(value: Value) -> AtomicResult<SerdeValue> { } crate::values::SubResource::Subject(s) => SerdeValue::String(s), }, + Value::YDoc(val) => { + let mut obj = Map::new(); + obj.insert("type".to_string(), "ydoc".into()); + obj.insert( + "data".to_string(), + general_purpose::STANDARD.encode(val).into(), + ); + obj.into() + } }; Ok(json_val) } diff --git a/lib/src/urls.rs b/lib/src/urls.rs index cc821d733..2aa52c800 100644 --- a/lib/src/urls.rs +++ b/lib/src/urls.rs @@ -19,6 +19,7 @@ pub const MESSAGE: &str = "https://atomicdata.dev/classes/Message"; pub const IMPORTER: &str = "https://atomicdata.dev/classes/Importer"; pub const ERROR: &str = "https://atomicdata.dev/classes/Error"; pub const BOOKMARK: &str = "https://atomicdata.dev/class/Bookmark"; +pub const DOCUMENT_V2: &str = "https://atomicdata.dev/classes/DocumentV2"; pub const ONTOLOGY: &str = "https://atomicdata.dev/class/ontology"; pub const ENDPOINT_RESPONSE: &str = "https://atomicdata.dev/ontology/server/class/endpoint-response"; @@ -47,6 +48,7 @@ pub const SET: &str = "https://atomicdata.dev/properties/set"; pub const PUSH: &str = "https://atomicdata.dev/properties/push"; pub const REMOVE: &str = "https://atomicdata.dev/properties/remove"; pub const DESTROY: &str = "https://atomicdata.dev/properties/destroy"; +pub const Y_UPDATE: &str = "https://atomicdata.dev/properties/yUpdate"; pub const SIGNER: &str = "https://atomicdata.dev/properties/signer"; pub const CREATED_AT: &str = "https://atomicdata.dev/properties/createdAt"; pub const SIGNATURE: &str = "https://atomicdata.dev/properties/signature"; @@ -117,6 +119,8 @@ pub const IMAGE_HEIGHT: &str = "https://atomicdata.dev/properties/imageHeight"; // ... for ChatRooms and Messages pub const MESSAGES: &str = "https://atomicdata.dev/properties/messages"; pub const NEXT_PAGE: &str = "https://atomicdata.dev/properties/nextPage"; +// ... for DocumentV2 +pub const DOCUMENT_CONTENT: &str = "https://atomicdata.dev/properties/documentContent"; // ... for Importers pub const IMPORTER_URL: &str = "https://atomicdata.dev/properties/importer/url"; pub const IMPORTER_JSON: &str = "https://atomicdata.dev/properties/importer/json"; @@ -144,6 +148,7 @@ pub const DATE: &str = "https://atomicdata.dev/datatypes/date"; pub const TIMESTAMP: &str = "https://atomicdata.dev/datatypes/timestamp"; pub const URI: &str = "https://atomicdata.dev/datatypes/uri"; pub const JSON: &str = "https://atomicdata.dev/datatypes/json"; +pub const YDOC: &str = "https://atomicdata.dev/datatypes/ydoc"; // Methods pub const INSERT: &str = "https://atomicdata.dev/methods/insert"; diff --git a/lib/src/values.rs b/lib/src/values.rs index 8ce897cc0..ce2355477 100644 --- a/lib/src/values.rs +++ b/lib/src/values.rs @@ -7,6 +7,7 @@ use crate::{ utils::{check_valid_uri, check_valid_url}, Resource, }; +use base64::{engine::general_purpose, Engine}; use regex::Regex; use serde::{Deserialize, Serialize}; @@ -29,6 +30,7 @@ pub enum Value { Boolean(bool), Uri(String), JSON(serde_json::Value), + YDoc(Vec<u8>), Unsupported(UnsupportedValue), } @@ -85,6 +87,7 @@ impl Value { Value::Boolean(_) => DataType::Boolean, Value::Uri(_) => DataType::Uri, Value::JSON(_) => DataType::JSON, + Value::YDoc(_) => DataType::YDoc, Value::Unsupported(s) => DataType::Unsupported(s.datatype.clone()), } } @@ -167,6 +170,12 @@ impl Value { }; Ok(Value::Boolean(bool)) } + DataType::YDoc => { + let bin = general_purpose::STANDARD + .decode(value) + .map_err(|e| format!("Not a valid Base64 string: {}. {}", value, e))?; + Ok(Value::YDoc(bin)) + } } } @@ -360,6 +369,7 @@ impl fmt::Display for Value { Value::Boolean(b) => write!(f, "{}", b), Value::Uri(s) => write!(f, "{}", s), Value::JSON(s) => write!(f, "{}", s), + Value::YDoc(s) => write!(f, "{}", general_purpose::STANDARD.encode(s)), Value::Unsupported(u) => write!(f, "{}", u.value), } } diff --git a/server/Cargo.toml b/server/Cargo.toml index 27ca93915..63262f277 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -53,6 +53,7 @@ tracing-log = "0.2" ureq = "2" urlencoding = "2" ring = "0.17.14" +yrs = "0.24.0" [dependencies.instant-acme] optional = true diff --git a/server/src/actor_messages.rs b/server/src/actor_messages.rs index 47ff7298f..32925bff9 100644 --- a/server/src/actor_messages.rs +++ b/server/src/actor_messages.rs @@ -2,6 +2,7 @@ //! In this case it's for communication between the CommitMonitor and the WebSocketConnection. use actix::{prelude::Message, Addr}; +use serde::{Deserialize, Serialize}; /// Subscribes a WebSocketConnection to a Subject. #[derive(Message)] @@ -12,6 +13,29 @@ pub struct Subscribe { pub agent: String, } +#[derive(Deserialize, Serialize)] +pub struct YSubscriptionJSON { + pub subject: String, + pub property: String, +} + +#[derive(Message)] +#[rtype(result = "()")] +pub struct SubscribeYSync { + pub addr: Addr<crate::handlers::web_sockets::WebSocketConnection>, + pub subject: String, + pub property: String, + pub agent: String, +} + +#[derive(Message)] +#[rtype(result = "()")] +pub struct UnsubscribeYSync { + pub addr: Addr<crate::handlers::web_sockets::WebSocketConnection>, + pub subject: String, + pub property: String, +} + /// A message containing a Resource, which should be sent to subscribers #[derive(Message, Clone, Debug)] #[rtype(result = "()")] @@ -19,3 +43,16 @@ pub struct CommitMessage { /// Full resource of the Commit itself, the new resource, and the old one pub commit_response: atomic_lib::commit::CommitResponse, } + +/// A message that can contain both a Yjs Doc update or a Yjs Awareness update. +/// It is used to enable live collaboration on Yjs Docs and does not store these updates on the server. +#[derive(Message, Clone, Debug, Serialize, Deserialize)] +#[rtype(result = "()")] +pub struct YSyncUpdate { + pub subject: String, + pub property: String, + pub awareness_update: Option<String>, + pub doc_update: Option<String>, + #[serde(skip)] + pub addr: Option<Addr<crate::handlers::web_sockets::WebSocketConnection>>, +} diff --git a/server/src/appstate.rs b/server/src/appstate.rs index 25795e7b7..1c6b1b0d3 100644 --- a/server/src/appstate.rs +++ b/server/src/appstate.rs @@ -1,6 +1,10 @@ //! App state, which is accessible from handlers use crate::{ - commit_monitor::CommitMonitor, config::Config, errors::AtomicServerResult, search::SearchState, + commit_monitor::CommitMonitor, + config::Config, + errors::AtomicServerResult, + search::SearchState, + y_sync_broadcaster::{self, YSyncBroadcaster}, }; use atomic_lib::{ agents::Agent, @@ -23,6 +27,7 @@ pub struct AppState { pub config: Config, /// The Actix Address of the CommitMonitor, which should receive updates when a commit is applied pub commit_monitor: actix::Addr<CommitMonitor>, + pub y_sync_broadcaster: actix::Addr<YSyncBroadcaster>, pub search_state: SearchState, } @@ -65,6 +70,8 @@ impl AppState { let commit_monitor_clone = commit_monitor.clone(); + let y_sync_broadcaster = y_sync_broadcaster::create_y_sync_broadcaster(store.clone()); + // This closure is called every time a Commit is created let send_commit = move |commit_response: &CommitResponse| { commit_monitor_clone.do_send(crate::actor_messages::CommitMessage { @@ -98,6 +105,7 @@ impl AppState { store, config, commit_monitor, + y_sync_broadcaster, search_state, }) } diff --git a/server/src/bin.rs b/server/src/bin.rs index e965c919b..211bd3016 100644 --- a/server/src/bin.rs +++ b/server/src/bin.rs @@ -15,6 +15,7 @@ mod https; mod jsonerrors; mod routes; pub mod serve; +mod y_sync_broadcaster; // #[cfg(feature = "search")] mod search; #[cfg(test)] diff --git a/server/src/handlers/web_sockets.rs b/server/src/handlers/web_sockets.rs index 8aab868e4..e6ea00859 100644 --- a/server/src/handlers/web_sockets.rs +++ b/server/src/handlers/web_sockets.rs @@ -18,8 +18,12 @@ use atomic_lib::{ use std::time::{Duration, Instant}; use crate::{ - actor_messages::CommitMessage, appstate::AppState, commit_monitor::CommitMonitor, - errors::AtomicServerResult, helpers::get_auth_headers, + actor_messages::{CommitMessage, YSubscriptionJSON, YSyncUpdate}, + appstate::AppState, + commit_monitor::CommitMonitor, + errors::AtomicServerResult, + helpers::get_auth_headers, + y_sync_broadcaster::YSyncBroadcaster, }; /// Get an HTTP request, upgrade it to a Websocket connection @@ -40,6 +44,7 @@ pub async fn web_socket_handler( let result = ws::start( WebSocketConnection::new( appstate.commit_monitor.clone(), + appstate.y_sync_broadcaster.clone(), for_agent, // We need to make sure this is easily clone-able appstate.store.clone(), @@ -61,6 +66,7 @@ pub struct WebSocketConnection { subscribed: std::collections::HashSet<String>, /// The CommitMonitor Actor that receives and sends messages for Commits commit_monitor_addr: Addr<CommitMonitor>, + y_sync_broadcaster_addr: Addr<YSyncBroadcaster>, /// The Agent who is connected. /// If it's not specified, it's the Public Agent. agent: ForAgent, @@ -129,6 +135,42 @@ fn handle_ws_message( Err("UNSUBSCRIBE needs a subject".into()) } } + s if s.starts_with("Y_SYNC_SUBSCRIBE ") => { + let mut parts = s.split("Y_SYNC_SUBSCRIBE "); + + let Some(json) = parts.nth(1) else { + return Err("Y_SYNC_SUBSCRIBE needs a JSON object".into()); + }; + + let message: YSubscriptionJSON = serde_json::from_str(json)?; + + conn.y_sync_broadcaster_addr + .do_send(crate::actor_messages::SubscribeYSync { + addr: ctx.address(), + subject: message.subject.to_string(), + property: message.property.to_string(), + agent: conn.agent.to_string(), + }); + Ok(()) + } + s if s.starts_with("Y_SYNC_UNSUBSCRIBE ") => { + let mut parts = s.split("Y_SYNC_UNSUBSCRIBE "); + + let Some(json) = parts.nth(1) else { + return Err("Y_SYNC_UNSUBSCRIBE needs a JSON object".into()); + }; + + let message: YSubscriptionJSON = serde_json::from_str(json)?; + + conn.y_sync_broadcaster_addr + .do_send(crate::actor_messages::UnsubscribeYSync { + addr: ctx.address(), + subject: message.subject.to_string(), + property: message.property.to_string(), + }); + + Ok(()) + } s if s.starts_with("GET ") => { let mut parts = s.split("GET "); if let Some(subject) = parts.nth(1) { @@ -179,6 +221,23 @@ fn handle_ws_message( Err("AUTHENTICATE needs a JSON object".into()) } } + s if s.starts_with("Y_SYNC_UPDATE ") => { + let mut parts = s.split("Y_SYNC_UPDATE "); + let Some(json) = parts.nth(1) else { + return Err("Y_SYNC_UPDATE needs a JSON object".into()); + }; + + let mut update: YSyncUpdate = match serde_json::from_str(json) { + Ok(update) => update, + Err(err) => { + return Err(format!("Invalid Y_SYNC_UPDATE JSON: {}", err).into()) + } + }; + + update.addr = Some(ctx.address()); + conn.y_sync_broadcaster_addr.do_send(update); + Ok(()) + } other => { tracing::warn!("Unknown websocket message: {}", other); Err(format!("Unknown message: {}", other).into()) @@ -199,7 +258,12 @@ fn handle_ws_message( } impl WebSocketConnection { - fn new(commit_monitor_addr: Addr<CommitMonitor>, agent: ForAgent, store: Db) -> Self { + fn new( + commit_monitor_addr: Addr<CommitMonitor>, + y_sync_broadcaster_addr: Addr<YSyncBroadcaster>, + agent: ForAgent, + store: Db, + ) -> Self { let size = std::mem::size_of::<Db>(); if size > 10000 { tracing::warn!( @@ -213,6 +277,7 @@ impl WebSocketConnection { // Maybe this should be stored only in the CommitMonitor, and not here. subscribed: std::collections::HashSet::new(), commit_monitor_addr, + y_sync_broadcaster_addr, agent, store, } @@ -250,3 +315,15 @@ impl Handler<CommitMessage> for WebSocketConnection { ctx.text(formatted_commit); } } + +impl Handler<YSyncUpdate> for WebSocketConnection { + type Result = (); + + #[tracing::instrument(name = "handle_y_awareness_update", skip_all)] + fn handle(&mut self, msg: YSyncUpdate, ctx: &mut ws::WebsocketContext<Self>) { + ctx.text(format!( + "Y_SYNC_UPDATE {}", + serde_json::to_string(&msg).unwrap() + )); + } +} diff --git a/server/src/lib.rs b/server/src/lib.rs index 9110712de..ee80bf544 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -16,6 +16,7 @@ mod https; mod jsonerrors; mod routes; pub mod serve; +mod y_sync_broadcaster; // #[cfg(feature = "search")] mod search; #[cfg(test)] diff --git a/server/src/search.rs b/server/src/search.rs index 4a75677f5..60bd08eb7 100644 --- a/server/src/search.rs +++ b/server/src/search.rs @@ -1,9 +1,12 @@ //! Full-text search, powered by Tantivy. //! A folder for the index is stored in the config. //! You can see the Endpoint on `http://localhost/search` +use crate::config::Config; +use crate::errors::AtomicServerResult; use atomic_lib::Db; use atomic_lib::Resource; use atomic_lib::Storelike; +use regex::Regex; use tantivy::schema::Facet; use tantivy::schema::Field; use tantivy::schema::STORED; @@ -12,9 +15,11 @@ use tantivy::Index; use tantivy::IndexWriter; use tantivy::ReloadPolicy; -use crate::config::Config; -use crate::errors::AtomicServerResult; - +use yrs::updates::decoder::Decode; +use yrs::GetString; +use yrs::WriteTxn; +use yrs::XmlFragment; +use yrs::{Transact, Update}; /// The actual Schema used for search. /// It mimics a single Atom (or Triple). #[derive(Debug)] @@ -128,6 +133,23 @@ impl SearchState { doc.add_text(fields.description, description); }; + // If the resource has a document-content property, we extract the plain text and use that as the description instead. + // This way, documents can be indexed by search. + if let Ok(atomic_lib::Value::YDoc(state)) = resource.get(atomic_lib::urls::DOCUMENT_CONTENT) + { + let ydoc = yrs::Doc::new(); + let mut txn = ydoc.transact_mut(); + txn.apply_update( + Update::decode_v2(state) + .map_err(|e| format!("Failed to decode YDoc update: {}", e))?, + ) + .map_err(|e| format!("Failed to apply YDoc update: {}", e))?; + + let xml_content = txn.get_or_insert_xml_fragment("content"); + let content = extract_plain_text(&xml_content, &txn); + doc.add_text(fields.description, content); + } + let hierarchy = resource_to_facet(resource, store)?; doc.add_facet(fields.hierarchy, hierarchy); @@ -261,6 +283,30 @@ fn get_resource_title(resource: &Resource) -> String { } } +/// Recursively traverses the Yjs XmlFragment structure using a TreeWalker +/// and extracts all nested plain text content. +/// +/// This function requires a Transaction to read the text data correctly. +fn extract_plain_text(fragment: &yrs::XmlFragmentRef, txn: &yrs::TransactionMut) -> String { + let mut text_content = String::new(); + + for node in fragment.successors(txn) { + match node { + yrs::types::xml::XmlOut::Text(text) => { + text_content.push_str(&text.get_string(txn)); + } + _ => {} + } + } + + // Remove XML tags using regex + let xml_tag_regex = Regex::new(r"<[^>]*>").unwrap(); + let clean_text = xml_tag_regex.replace_all(&text_content, " "); + + // Clean up leading/trailing whitespace and return + clean_text.trim().to_string() +} + #[cfg(test)] mod tests { use super::*; diff --git a/server/src/y_sync_broadcaster.rs b/server/src/y_sync_broadcaster.rs new file mode 100644 index 000000000..1c51afaa4 --- /dev/null +++ b/server/src/y_sync_broadcaster.rs @@ -0,0 +1,155 @@ +use crate::{ + actor_messages::{SubscribeYSync, UnsubscribeYSync, YSyncUpdate}, + handlers::web_sockets::WebSocketConnection, +}; + +use actix::{ + prelude::{Actor, Context, Handler}, + Addr, +}; +use atomic_lib::{agents::ForAgent, Db, Storelike}; +use std::collections::{HashMap, HashSet}; + +#[derive(Eq, Hash, PartialEq, Clone)] +struct Subscription { + addr: Addr<WebSocketConnection>, + can_write: bool, +} + +pub struct YSyncBroadcaster { + subscriptions: HashMap<(String, String), HashSet<Subscription>>, + store: Db, +} + +impl Actor for YSyncBroadcaster { + type Context = Context<Self>; + + fn started(&mut self, _ctx: &mut Context<Self>) { + tracing::debug!("YAwarenessBroadcaster started"); + } +} + +impl Handler<SubscribeYSync> for YSyncBroadcaster { + type Result = (); + + fn handle(&mut self, msg: SubscribeYSync, _ctx: &mut Context<Self>) { + if !msg.subject.starts_with(&self.store.get_self_url().unwrap()) { + tracing::warn!("can't subscribe to external resource"); + return; + } + let key = (msg.subject.clone(), msg.property.clone()); + + let resource = match self.store.get_resource(&msg.subject) { + Ok(resource) => resource, + Err(e) => { + tracing::debug!( + "Subscribe failed for {} by {}: {}", + &msg.subject, + msg.agent, + e + ); + return; + } + }; + + let mut can_write = false; + + // First check if the agent has write rights, if not, check for read rights, if not, don't subscribe. + match atomic_lib::hierarchy::check_write( + &self.store, + &resource, + &ForAgent::AgentSubject(msg.agent.clone()), + ) { + Ok(_) => { + can_write = true; + } + Err(_) => { + match atomic_lib::hierarchy::check_read( + &self.store, + &resource, + &ForAgent::AgentSubject(msg.agent.clone()), + ) { + Ok(_) => {} + Err(unauthorized_err) => { + tracing::debug!( + "Not allowed {} to subscribe to {}: {}", + &msg.agent, + &msg.subject, + unauthorized_err + ); + return; + } + } + } + } + + let mut set = self + .subscriptions + .get(&key) + .unwrap_or(&HashSet::new()) + .clone(); + + set.insert(Subscription { + addr: msg.addr, + can_write, + }); + tracing::debug!("handle subscribe {} ", msg.subject); + self.subscriptions.insert(key.clone(), set); + } +} + +impl Handler<UnsubscribeYSync> for YSyncBroadcaster { + type Result = (); + + fn handle(&mut self, msg: UnsubscribeYSync, _ctx: &mut Context<Self>) { + let key = (msg.subject.clone(), msg.property.clone()); + + let Some(subscriber) = self.subscriptions.get(&key) else { + tracing::warn!("no subscribers for {}", msg.subject); + return; + }; + + let mut new_subscriber = subscriber.clone(); + new_subscriber.retain(|s| s.addr != msg.addr); + self.subscriptions.insert(key.clone(), new_subscriber); + } +} + +impl Handler<YSyncUpdate> for YSyncBroadcaster { + type Result = (); + + fn handle(&mut self, msg: YSyncUpdate, _ctx: &mut Context<Self>) { + let key = (msg.subject.clone(), msg.property.clone()); + + let Some(subscribers) = self.subscriptions.get(&key) else { + tracing::warn!("no subscribers for {}", msg.subject); + return (); + }; + + // Check if msg.addr is in the subscibers and has write rights, if not, don't send the update. + let Some(addr) = &msg.addr else { + tracing::warn!("no addr in update for {}", msg.subject); + return (); + }; + + if subscribers + .iter() + .find(|s| s.addr == *addr && s.can_write) + .is_none() + { + tracing::warn!("not allowed to send update to {}", msg.subject); + return (); + } + + for subscriber in subscribers { + subscriber.addr.do_send(msg.clone()); + } + } +} + +pub fn create_y_sync_broadcaster(store: Db) -> Addr<YSyncBroadcaster> { + YSyncBroadcaster::create(|_ctx: &mut Context<YSyncBroadcaster>| YSyncBroadcaster { + subscriptions: HashMap::new(), + store, + }) +}