diff --git a/README.md b/README.md index 66269be..d402ecc 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,11 @@ Default storage: SQLite at `data/atlantis.db` (override with `PRISMA_PROVIDER` + - Restore: Settings → Restore accepts validated backup JSON. - Checkpoints: Editor keeps up to 15 recent checkpoints per diagram. +## Acknowledgments + +- [Mermaid.js](https://mermaid.js.org/) for the diagramming capabilities. +- [tldraw](https://tldraw.com/) for the infinite canvas drawing experience. + ## Support If you find this project useful, you can support its development: diff --git a/atlantis-dev.db b/atlantis-dev.db new file mode 100644 index 0000000..02fe247 Binary files /dev/null and b/atlantis-dev.db differ diff --git a/package-lock.json b/package-lock.json index dbe78cf..ec264c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,7 @@ "sonner": "^2.0.7", "swagger-ui-dist": "^5.17.14", "tailwind-merge": "^3.4.0", + "tldraw": "^4.4.0", "usehooks-ts": "^3.1.1", "zod": "^4.3.5", "zustand": "^5.0.10" @@ -2001,6 +2002,7 @@ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", "license": "MIT", + "peer": true, "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" @@ -3444,6 +3446,60 @@ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "license": "MIT" }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz", + "integrity": "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", + "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "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 + } + } + }, "node_modules/@radix-ui/react-alert-dialog": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", @@ -3513,6 +3569,56 @@ } } }, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz", + "integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz", + "integrity": "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "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 + } + } + }, "node_modules/@radix-ui/react-checkbox": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", @@ -3543,6 +3649,36 @@ } } }, + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "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 + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", @@ -3617,6 +3753,34 @@ } } }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", + "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "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 + } + } + }, "node_modules/@radix-ui/react-dialog": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", @@ -3782,6 +3946,65 @@ } } }, + "node_modules/@radix-ui/react-form": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.8.tgz", + "integrity": "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-label": "2.1.7", + "@radix-ui/react-primitive": "2.1.3" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", + "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "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 + } + } + }, "node_modules/@radix-ui/react-id": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", @@ -3800,6 +4023,29 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "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 + } + } + }, "node_modules/@radix-ui/react-menu": { "version": "2.1.16", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", @@ -3858,22 +4104,22 @@ } } }, - "node_modules/@radix-ui/react-popper": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", - "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", + "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", "license": "MIT", "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-rect": "1.1.1", - "@radix-ui/react-use-size": "1.1.1", - "@radix-ui/rect": "1.1.1" + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", @@ -3890,14 +4136,26 @@ } } }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", - "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", + "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", "license": "MIT", "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -3914,13 +4172,23 @@ } } }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", - "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "node_modules/@radix-ui/react-one-time-password-field": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz", + "integrity": "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==", "license": "MIT", "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { @@ -3938,13 +4206,20 @@ } } }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "node_modules/@radix-ui/react-password-toggle-field": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz", + "integrity": "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.3" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-is-hydrated": "0.1.0" }, "peerDependencies": { "@types/react": "*", @@ -3961,7 +4236,44 @@ } } }, - "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", @@ -3979,21 +4291,22 @@ } } }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", - "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -4010,20 +4323,13 @@ } } }, - "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", - "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", "license": "MIT", "dependencies": { - "@radix-ui/number": "1.1.1", - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { @@ -4041,13 +4347,14 @@ } } }, - "node_modules/@radix-ui/react-separator": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", - "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.4" + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -4064,13 +4371,13 @@ } } }, - "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.4" + "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -4087,10 +4394,10 @@ } } }, - "node_modules/@radix-ui/react-slot": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", - "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -4105,16 +4412,43 @@ } } }, - "node_modules/@radix-ui/react-switch": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", - "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" @@ -4134,19 +4468,20 @@ } } }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", - "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { @@ -4164,24 +4499,21 @@ } } }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", - "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", "license": "MIT", "dependencies": { + "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-visually-hidden": "1.2.3" + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -4198,29 +4530,57 @@ } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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 } } }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", - "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -4231,66 +4591,93 @@ } } }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "node_modules/@radix-ui/react-separator": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", + "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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 } } }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", - "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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 } } }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", - "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "node_modules/@radix-ui/react-slider": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.1" + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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 } } }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -4301,64 +4688,108 @@ } } }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", - "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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 } } }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", - "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", "license": "MIT", "dependencies": { - "@radix-ui/rect": "1.1.1" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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 } } }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", - "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "node_modules/@radix-ui/react-toast": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", + "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@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 } } }, - "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", - "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", @@ -4375,79 +4806,395 @@ } } }, - "node_modules/@radix-ui/rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", - "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", - "license": "MIT" - }, - "node_modules/@redis/bloom": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.10.0.tgz", - "integrity": "sha512-doIF37ob+l47n0rkpRNgU8n4iacBlKM9xLiP1LtTZTvz8TloJB8qx/MgvhMhKdYG+CvCY2aPBnN2706izFn/4A==", + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", "license": "MIT", - "engines": { - "node": ">= 18" + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { - "@redis/client": "^5.10.0" + "@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 + } } }, - "node_modules/@redis/client": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.10.0.tgz", - "integrity": "sha512-JXmM4XCoso6C75Mr3lhKA3eNxSzkYi3nCzxDIKY+YOszYsJjuKbFgVtguVPbLMOttN4iu2fXoc2BGhdnYhIOxA==", + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", + "integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==", "license": "MIT", - "peer": true, "dependencies": { - "cluster-key-slot": "1.1.2" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-toggle-group": "1.1.11" }, - "engines": { - "node": ">= 18" + "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 + } } }, - "node_modules/@redis/json": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.10.0.tgz", - "integrity": "sha512-B2G8XlOmTPUuZtD44EMGbtoepQG34RCDXLZbjrtON1Djet0t5Ri7/YPXvL9aomXqP8lLTreaprtyLKF4tmXEEA==", + "node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", "license": "MIT", - "engines": { - "node": ">= 18" + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { - "@redis/client": "^5.10.0" + "@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 + } } }, - "node_modules/@redis/search": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.10.0.tgz", - "integrity": "sha512-3SVcPswoSfp2HnmWbAGUzlbUPn7fOohVu2weUQ0S+EMiQi8jwjL+aN2p6V3TI65eNfVsJ8vyPvqWklm6H6esmg==", + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", "license": "MIT", - "engines": { - "node": ">= 18" + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { - "@redis/client": "^5.10.0" + "@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 + } } }, - "node_modules/@redis/time-series": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.10.0.tgz", - "integrity": "sha512-cPkpddXH5kc/SdRhF0YG0qtjL+noqFT0AcHbQ6axhsPsO7iqPi1cjxgdkE9TNeKiBUUdCaU1DbqkR/LzbzPBhg==", + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", - "engines": { - "node": ">= 18" + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { - "@redis/client": "^5.10.0" + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@replit/codemirror-indentation-markers": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/@replit/codemirror-indentation-markers/-/codemirror-indentation-markers-6.5.3.tgz", - "integrity": "sha512-hL5Sfvw3C1vgg7GolLe/uxX5T3tmgOA3ZzqlMv47zjU1ON51pzNWiVbS22oh6crYhtVhv8b3gdXwoYp++2ilHw==", - "license": "MIT", - "peerDependencies": { + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "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 + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@redis/bloom": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.10.0.tgz", + "integrity": "sha512-doIF37ob+l47n0rkpRNgU8n4iacBlKM9xLiP1LtTZTvz8TloJB8qx/MgvhMhKdYG+CvCY2aPBnN2706izFn/4A==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.10.0" + } + }, + "node_modules/@redis/client": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.10.0.tgz", + "integrity": "sha512-JXmM4XCoso6C75Mr3lhKA3eNxSzkYi3nCzxDIKY+YOszYsJjuKbFgVtguVPbLMOttN4iu2fXoc2BGhdnYhIOxA==", + "license": "MIT", + "peer": true, + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@redis/json": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.10.0.tgz", + "integrity": "sha512-B2G8XlOmTPUuZtD44EMGbtoepQG34RCDXLZbjrtON1Djet0t5Ri7/YPXvL9aomXqP8lLTreaprtyLKF4tmXEEA==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.10.0" + } + }, + "node_modules/@redis/search": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.10.0.tgz", + "integrity": "sha512-3SVcPswoSfp2HnmWbAGUzlbUPn7fOohVu2weUQ0S+EMiQi8jwjL+aN2p6V3TI65eNfVsJ8vyPvqWklm6H6esmg==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.10.0" + } + }, + "node_modules/@redis/time-series": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.10.0.tgz", + "integrity": "sha512-cPkpddXH5kc/SdRhF0YG0qtjL+noqFT0AcHbQ6axhsPsO7iqPi1cjxgdkE9TNeKiBUUdCaU1DbqkR/LzbzPBhg==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.10.0" + } + }, + "node_modules/@remirror/core-constants": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", + "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", + "license": "MIT" + }, + "node_modules/@replit/codemirror-indentation-markers": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/@replit/codemirror-indentation-markers/-/codemirror-indentation-markers-6.5.3.tgz", + "integrity": "sha512-hL5Sfvw3C1vgg7GolLe/uxX5T3tmgOA3ZzqlMv47zjU1ON51pzNWiVbS22oh6crYhtVhv8b3gdXwoYp++2ilHw==", + "license": "MIT", + "peerDependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" @@ -4708,218 +5455,770 @@ ], "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.7.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.7.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", + "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "postcss": "^8.4.41", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tiptap/core": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.0.tgz", + "integrity": "sha512-aC9aROgia/SpJqhsXFiX9TsligL8d+oeoI8W3u00WI45s0VfsqjgeKQLDLF7Tu7hC+7F02teC84SAHuup003VQ==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-blockquote": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.20.0.tgz", + "integrity": "sha512-LQzn6aGtL4WXz2+rYshl/7/VnP2qJTpD7fWL96GXAzhqviPEY1bJES7poqJb3MU/gzl8VJUVzVzU1VoVfUKlbA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.20.0.tgz", + "integrity": "sha512-sQklEWiyf58yDjiHtm5vmkVjfIc/cBuSusmCsQ0q9vGYnEF1iOHKhGpvnCeEXNeqF3fiJQRlquzt/6ymle3Iwg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.20.0.tgz", + "integrity": "sha512-MDosUfs8Tj+nwg8RC+wTMWGkLJORXmbR6YZgbiX4hrc7G90Gopdd6kj6ht5/T8t7dLLaX7N0+DEHdUEPGED7dw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0", + "@tiptap/pm": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-bullet-list": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.20.0.tgz", + "integrity": "sha512-OcKMeopBbqWzhSi6o8nNz0aayogg1sfOAhto3NxJu3Ya32dwBFqmHXSYM6uW4jOphNvVPyjiq9aNRh3qTdd1dw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.20.0.tgz", + "integrity": "sha512-TYDWFeSQ9umiyrqsT6VecbuhL8XIHkUhO+gEk0sVvH67ZLwjFDhAIIgWIr1/dbIGPcvMZM19E7xUUhAdIaXaOQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.20.0.tgz", + "integrity": "sha512-lBbmNek14aCjrHcBcq3PRqWfNLvC6bcRa2Osc6e/LtmXlcpype4f6n+Yx+WZ+f2uUh0UmDRCz7BEyUETEsDmlQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0", + "@tiptap/pm": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.20.0.tgz", + "integrity": "sha512-oJfLIG3vAtZo/wg29WiBcyWt22KUgddpP8wqtCE+kY5Dw8znLR9ehNmVWlSWJA5OJUMO0ntAHx4bBT+I2MBd5w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-dropcursor": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.20.0.tgz", + "integrity": "sha512-d+cxplRlktVgZPwatnc34IArlppM0IFKS1J5wLk+ba1jidizsbMVh45tP/BTK2flhyfRqcNoB5R0TArhUpbkNQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.20.0.tgz", + "integrity": "sha512-rYs4Bv5pVjqZ/2vvR6oe7ammZapkAwN51As/WDbemvYDjfOGRqK58qGauUjYZiDzPOEIzI2mxGwsZ4eJhPW4Ig==", + "license": "MIT", + "optional": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.0.0", + "@tiptap/core": "^3.20.0", + "@tiptap/pm": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-gapcursor": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.20.0.tgz", + "integrity": "sha512-P/LasfvG9/qFq43ZAlNbAnPnXC+/RJf49buTrhtFvI9Zg0+Lbpjx1oh6oMHB19T88Y28KtrckfFZ8aTSUWDq6w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.20.0.tgz", + "integrity": "sha512-rqvhMOw4f+XQmEthncbvDjgLH6fz8L9splnKZC7OeS0eX8b0qd7+xI1u5kyxF3KA2Z0BnigES++jjWuecqV6mA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.20.0.tgz", + "integrity": "sha512-JgJhurnCe3eN6a0lEsNQM/46R1bcwzwWWZEFDSb1P9dR8+t1/5v7cMZWsSInpD7R4/74iJn0+M5hcXLwCmBmYA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-highlight": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-3.20.0.tgz", + "integrity": "sha512-ANL1wFz0s1ScNR4uBfO0s6Sz+qqGp2u6ynrCVk6TCT3d10CQ+gD1gSDTrVRC3CtlMKtHHH4fYrFAq284+J0gKA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.20.0.tgz", + "integrity": "sha512-6uvcutFMv+9wPZgptDkbRDjAm3YVxlibmkhWD5GuaWwS9L/yUtobpI3GycujRSUZ8D3q6Q9J7LqpmQtQRTalWA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0", + "@tiptap/pm": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.20.0.tgz", + "integrity": "sha512-/DhnKQF8yN8RxtuL8abZ28wd5281EaGoE2Oha35zXSOF1vNYnbyt8Ymkv/7u1BcWEWTvRPgaju0YCGXisPRLYw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-link": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.20.0.tgz", + "integrity": "sha512-qI/5A+R0ZWBxo/8HxSn1uOyr7odr3xHBZ/gzOR1GUJaZqjlJxkWFX0RtXMbLKEGEvT25o345cF7b0wFznEh8qA==", + "license": "MIT", + "dependencies": { + "linkifyjs": "^4.3.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0", + "@tiptap/pm": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-list": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.20.0.tgz", + "integrity": "sha512-+V0/gsVWAv+7vcY0MAe6D52LYTIicMSHw00wz3ISZgprSb2yQhJ4+4gurOnUrQ4Du3AnRQvxPROaofwxIQ66WQ==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0", + "@tiptap/pm": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-list-item": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.20.0.tgz", + "integrity": "sha512-qEtjaaGPuqaFB4VpLrGDoIe9RHnckxPfu6d3rc22ap6TAHCDyRv05CEyJogqccnFceG/v5WN4znUBER8RWnWHA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-list-keymap": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.20.0.tgz", + "integrity": "sha512-Z4GvKy04Ms4cLFN+CY6wXswd36xYsT2p/YL0V89LYFMZTerOeTjFYlndzn6svqL8NV1PRT5Diw4WTTxJSmcJPA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.20.0" + } + }, + "node_modules/@tiptap/extension-ordered-list": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.20.0.tgz", + "integrity": "sha512-jVKnJvrizLk7etwBMfyoj6H2GE4M+PD4k7Bwp6Bh1ohBWtfIA1TlngdS842Mx5i1VB2e3UWIwr8ZH46gl6cwMA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "^3.20.0" } }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", - "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@tiptap/extension-paragraph": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.20.0.tgz", + "integrity": "sha512-mM99zK4+RnEXIMCv6akfNATAs0Iija6FgyFA9J9NZ6N4o8y9QiNLLa6HjLpAC+W+VoCgQIekyoF/Q9ftxmAYDQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0" } }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", - "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@tiptap/extension-strike": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.20.0.tgz", + "integrity": "sha512-0vcTZRRAiDfon3VM1mHBr9EFmTkkUXMhm0Xtdtn0bGe+sIqufyi+hUYTEw93EQOD9XNsPkrud6jzQNYpX2H3AQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0" } }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", - "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@tiptap/extension-text": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.20.0.tgz", + "integrity": "sha512-tf8bE8tSaOEWabCzPm71xwiUhyMFKqY9jkP5af3Kr1/F45jzZFIQAYZooHI/+zCHRrgJ99MQHKHe1ZNvODrKHQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0" } }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", - "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@tiptap/extension-underline": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.20.0.tgz", + "integrity": "sha512-LzNXuy2jwR/y+ymoUqC72TiGzbOCjioIjsDu0MNYpHuHqTWPK5aV9Mh0nbZcYFy/7fPlV1q0W139EbJeYBZEAQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", - "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, + "node_modules/@tiptap/extensions": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.20.0.tgz", + "integrity": "sha512-HIsXX942w3nbxEQBlMAAR/aa6qiMBEP7CsSMxaxmTIVAmW35p6yUASw6GdV1u0o3lCZjXq2OSRMTskzIqi5uLg==", "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.0", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" }, - "engines": { - "node": ">=14.0.0" + "peerDependencies": { + "@tiptap/core": "^3.20.0", + "@tiptap/pm": "^3.20.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.7.1", - "dev": true, - "inBundle": true, + "node_modules/@tiptap/pm": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.20.0.tgz", + "integrity": "sha512-jn+2KnQZn+b+VXr8EFOJKsnjVNaA4diAEr6FOazupMt8W8ro1hfpYtZ25JL87Kao/WbMze55sd8M8BDXLUKu1A==", "license": "MIT", - "optional": true, + "peer": true, "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" + "prosemirror-changeset": "^2.3.0", + "prosemirror-collab": "^1.3.1", + "prosemirror-commands": "^1.6.2", + "prosemirror-dropcursor": "^1.8.1", + "prosemirror-gapcursor": "^1.3.2", + "prosemirror-history": "^1.4.1", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-markdown": "^1.13.1", + "prosemirror-menu": "^1.2.4", + "prosemirror-model": "^1.24.1", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.5.0", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.6.4", + "prosemirror-trailing-node": "^3.0.0", + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.38.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.7.1", - "dev": true, - "inBundle": true, + "node_modules/@tiptap/react": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.20.0.tgz", + "integrity": "sha512-jFLNzkmn18zqefJwPje0PPd9VhZ7Oy28YHiSvSc7YpBnQIbuN/HIxZ2lrOsKyEHta0WjRZjfU5X1pGxlbcGwOA==", "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@types/use-sync-external-store": "^0.0.6", + "fast-equals": "^5.3.3", + "use-sync-external-store": "^1.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "optionalDependencies": { + "@tiptap/extension-bubble-menu": "^3.20.0", + "@tiptap/extension-floating-menu": "^3.20.0" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.0", + "@tiptap/pm": "^3.20.0", + "@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" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.20.0.tgz", + "integrity": "sha512-W4+1re35pDNY/7rpXVg+OKo/Fa4Gfrn08Bq3E3fzlJw6gjE3tYU8dY9x9vC2rK9pd9NOp7Af11qCFDaWpohXkw==", + "license": "MIT", + "dependencies": { + "@tiptap/core": "^3.20.0", + "@tiptap/extension-blockquote": "^3.20.0", + "@tiptap/extension-bold": "^3.20.0", + "@tiptap/extension-bullet-list": "^3.20.0", + "@tiptap/extension-code": "^3.20.0", + "@tiptap/extension-code-block": "^3.20.0", + "@tiptap/extension-document": "^3.20.0", + "@tiptap/extension-dropcursor": "^3.20.0", + "@tiptap/extension-gapcursor": "^3.20.0", + "@tiptap/extension-hard-break": "^3.20.0", + "@tiptap/extension-heading": "^3.20.0", + "@tiptap/extension-horizontal-rule": "^3.20.0", + "@tiptap/extension-italic": "^3.20.0", + "@tiptap/extension-link": "^3.20.0", + "@tiptap/extension-list": "^3.20.0", + "@tiptap/extension-list-item": "^3.20.0", + "@tiptap/extension-list-keymap": "^3.20.0", + "@tiptap/extension-ordered-list": "^3.20.0", + "@tiptap/extension-paragraph": "^3.20.0", + "@tiptap/extension-strike": "^3.20.0", + "@tiptap/extension-text": "^3.20.0", + "@tiptap/extension-underline": "^3.20.0", + "@tiptap/extensions": "^3.20.0", + "@tiptap/pm": "^3.20.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tldraw/editor": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@tldraw/editor/-/editor-4.4.0.tgz", + "integrity": "sha512-RY7r2mFr6cBnixcuWjHCJG7XSb1ckUVFQrgp0EsJY6EgiD0ICt3U/iGdrr7kaXSsEfOHW/n8rb80S32oQzeQpQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@tiptap/core": "^3.12.1", + "@tiptap/pm": "^3.12.1", + "@tiptap/react": "^3.12.1", + "@tldraw/state": "4.4.0", + "@tldraw/state-react": "4.4.0", + "@tldraw/store": "4.4.0", + "@tldraw/tlschema": "4.4.0", + "@tldraw/utils": "4.4.0", + "@tldraw/validate": "4.4.0", + "@use-gesture/react": "^10.3.1", + "classnames": "^2.5.1", + "eventemitter3": "^4.0.7", + "idb": "^7.1.1", + "is-plain-object": "^5.0.0", + "rbush": "^3.0.1" + }, + "peerDependencies": { + "react": "^18.2.0 || ^19.2.1", + "react-dom": "^18.2.0 || ^19.2.1" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "dev": true, - "inBundle": true, + "node_modules/@tldraw/state": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@tldraw/state/-/state-4.4.0.tgz", + "integrity": "sha512-qtpFul/U7PZhKaY4LsPcag82VMZ1R/djkBKT22UXXmFAwnESvDmuphMh38NE3Wuy0/YhE9/K17dH0o2pODC3LA==", "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@tldraw/utils": "4.4.0" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.0", - "dev": true, - "inBundle": true, + "node_modules/@tldraw/state-react": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@tldraw/state-react/-/state-react-4.4.0.tgz", + "integrity": "sha512-JwS6+ahKMEYx34FwsKxiZ8mn2GPLnuG5hG9bk6BuaowwJSf/+CLKFxeGGs4ka0LYWqLh3jEIyUAAABmZ7tRdkw==", "license": "MIT", - "optional": true, "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@tybys/wasm-util": "^0.10.1" + "@tldraw/state": "4.4.0", + "@tldraw/utils": "4.4.0" + }, + "peerDependencies": { + "react": "^18.2.0 || ^19.2.1", + "react-dom": "^18.2.0 || ^19.2.1" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "dev": true, - "inBundle": true, + "node_modules/@tldraw/store": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@tldraw/store/-/store-4.4.0.tgz", + "integrity": "sha512-Sypg2tIwRRkF5fNEu+t/fejte5qjM4ReCPk9CLkKYSV1rPGbv4bHIYAiQHhRtWznJ8Fil1SJitPSMoQ++8xWvw==", "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@tldraw/state": "4.4.0", + "@tldraw/utils": "4.4.0" + }, + "peerDependencies": { + "react": "^18.2.0 || ^19.2.1" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { - "version": "2.8.1", - "dev": true, - "inBundle": true, - "license": "0BSD", - "optional": true - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", - "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@tldraw/tlschema": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@tldraw/tlschema/-/tlschema-4.4.0.tgz", + "integrity": "sha512-RV8vDgWCBHq1FAG6qUIzxiOhK2RkzVGW9uPE6ykM8enVJtGNo/5SA0xCAsGtwVm84LOE42r7qFxU0owEQwnLEQ==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" + "dependencies": { + "@tldraw/state": "4.4.0", + "@tldraw/store": "4.4.0", + "@tldraw/utils": "4.4.0", + "@tldraw/validate": "4.4.0" + }, + "peerDependencies": { + "react": "^18.2.0 || ^19.2.1", + "react-dom": "^18.2.0 || ^19.2.1" } }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", - "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" + "node_modules/@tldraw/utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@tldraw/utils/-/utils-4.4.0.tgz", + "integrity": "sha512-/YLoWAN5hVFm4xlXQnb878tpGREuw6A5clVt12cro4rq2fiMC8fz1Ac53CCDxxjWsmAaiTldnjunRVBhvi2AOQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "jittered-fractional-indexing": "^1.0.0", + "lodash.isequal": "^4.5.0", + "lodash.isequalwith": "^4.4.0", + "lodash.throttle": "^4.1.1", + "lodash.uniq": "^4.5.0" } }, - "node_modules/@tailwindcss/postcss": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", - "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", - "dev": true, - "license": "MIT", + "node_modules/@tldraw/validate": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@tldraw/validate/-/validate-4.4.0.tgz", + "integrity": "sha512-yZgjmLSbcei+plPQmu198KcsWKyK9cl2Nl4jnJ7WruHlKYTOlzjYrEmw0UdOG1JR3Odgf9tPmrep47BzMkf3Yg==", + "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.18", - "@tailwindcss/oxide": "4.1.18", - "postcss": "^8.4.41", - "tailwindcss": "4.1.18" + "@tldraw/utils": "4.4.0" } }, "node_modules/@tybys/wasm-util": { @@ -5254,6 +6553,28 @@ "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", "license": "MIT" }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "license": "MIT" + }, "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", @@ -5296,7 +6617,6 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "devOptional": true, "license": "MIT", "peer": true, "peerDependencies": { @@ -5325,6 +6645,12 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.53.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.0.tgz", @@ -5948,6 +7274,24 @@ "win32" ] }, + "node_modules/@use-gesture/core": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", + "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==", + "license": "MIT" + }, + "node_modules/@use-gesture/react": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz", + "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==", + "license": "MIT", + "dependencies": { + "@use-gesture/core": "10.3.1" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -6226,7 +7570,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/aria-hidden": { @@ -6988,6 +8331,12 @@ "url": "https://polar.sh/cva" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clean-webpack-plugin": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz", @@ -8125,6 +9474,18 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-abstract": { "version": "1.24.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", @@ -8315,7 +9676,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -8753,6 +10113,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -8805,6 +10171,15 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -9035,6 +10410,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/fractional-indexing": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fractional-indexing/-/fractional-indexing-3.2.0.tgz", + "integrity": "sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ==", + "license": "CC0-1.0", + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, "node_modules/framer-motion": { "version": "12.26.2", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.26.2.tgz", @@ -9517,6 +10901,15 @@ "node": ">=16.9.0" } }, + "node_modules/hotkeys-js": { + "version": "3.13.15", + "resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.13.15.tgz", + "integrity": "sha512-gHh8a/cPTCpanraePpjRxyIlxDFrIhYqjuh01UHWEwDpglJKCnvLW8kqSx5gQtOuSsJogNZXLhOdbSExpgUiqg==", + "license": "MIT", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, "node_modules/html2canvas": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", @@ -9965,6 +11358,15 @@ "node": ">=6" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", @@ -10213,6 +11615,18 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jittered-fractional-indexing": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jittered-fractional-indexing/-/jittered-fractional-indexing-1.0.1.tgz", + "integrity": "sha512-OpKFkVr4hU5ivd1ZCjZfHvVpWekraJvcePcMusBmgBmCVQK5JiRCA+4TT1vAUTLqGD9MkhqFwO0l3QspvlZgzw==", + "license": "CC0-1.0", + "dependencies": { + "fractional-indexing": "^3.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -10717,6 +12131,21 @@ "node": ">=10" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/linkifyjs": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz", + "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", + "license": "MIT" + }, "node_modules/loader-runner": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", @@ -10778,6 +12207,19 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "license": "MIT" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isequalwith": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.isequalwith/-/lodash.isequalwith-4.4.0.tgz", + "integrity": "sha512-dcZON0IalGBpRmJBmMkaoV7d3I80R2O+FrzsZyHdNSFrANq/cgDqKQNmAHE8UEj4+QYWwwhkQOVdLHiAopzlsQ==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -10791,6 +12233,18 @@ "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", "license": "MIT" }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -10842,6 +12296,15 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -10885,6 +12348,23 @@ "url": "https://github.com/wojtekmaj/make-event-props?sponsor=1" } }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, "node_modules/marked": { "version": "16.4.2", "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", @@ -10906,6 +12386,12 @@ "node": ">= 0.4" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, "node_modules/merge-refs": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-2.0.0.tgz", @@ -11505,6 +12991,12 @@ "node": ">= 0.8.0" } }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", + "license": "MIT" + }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -12217,6 +13709,204 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/prosemirror-changeset": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz", + "integrity": "sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==", + "license": "MIT", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", + "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz", + "integrity": "sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz", + "integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==", + "license": "MIT", + "dependencies": { + "@types/markdown-it": "^14.0.0", + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz", + "integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==", + "license": "MIT", + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.4", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", + "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", + "license": "MIT", + "peer": true, + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", + "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", + "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.4", + "prosemirror-state": "^1.4.4", + "prosemirror-transform": "^1.10.5", + "prosemirror-view": "^1.41.4" + } + }, + "node_modules/prosemirror-trailing-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", + "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", + "license": "MIT", + "dependencies": { + "@remirror/core-constants": "3.0.0", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.22.1", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.33.8" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.11.0.tgz", + "integrity": "sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.41.6", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.6.tgz", + "integrity": "sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -12236,6 +13926,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -12272,6 +13971,130 @@ ], "license": "MIT" }, + "node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", + "license": "ISC" + }, + "node_modules/radix-ui": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.4.3.tgz", + "integrity": "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-accessible-icon": "1.1.7", + "@radix-ui/react-accordion": "1.2.12", + "@radix-ui/react-alert-dialog": "1.1.15", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-aspect-ratio": "1.1.7", + "@radix-ui/react-avatar": "1.1.10", + "@radix-ui/react-checkbox": "1.3.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-context-menu": "2.2.16", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-dropdown-menu": "2.1.16", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-form": "0.1.8", + "@radix-ui/react-hover-card": "1.1.15", + "@radix-ui/react-label": "2.1.7", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-menubar": "1.1.16", + "@radix-ui/react-navigation-menu": "1.2.14", + "@radix-ui/react-one-time-password-field": "0.1.8", + "@radix-ui/react-password-toggle-field": "0.1.3", + "@radix-ui/react-popover": "1.1.15", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-progress": "1.1.7", + "@radix-ui/react-radio-group": "1.3.8", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-scroll-area": "1.2.10", + "@radix-ui/react-select": "2.2.6", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-slider": "1.3.6", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-switch": "1.2.6", + "@radix-ui/react-tabs": "1.1.13", + "@radix-ui/react-toast": "1.2.15", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-toggle-group": "1.1.11", + "@radix-ui/react-toolbar": "1.1.11", + "@radix-ui/react-tooltip": "1.2.8", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-escape-keydown": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "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 + } + } + }, + "node_modules/radix-ui/node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "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 + } + } + }, + "node_modules/radix-ui/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -12291,6 +14114,15 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "license": "MIT", + "dependencies": { + "quickselect": "^2.0.0" + } + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -12801,6 +14633,12 @@ "randombytes": "^2.1.0" } }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", + "license": "MIT" + }, "node_modules/roughjs": { "version": "4.6.6", "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", @@ -13848,6 +15686,32 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tldraw": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/tldraw/-/tldraw-4.4.0.tgz", + "integrity": "sha512-Djxpz82MLGIRkNCX3+9L68QYXy8E+2cTTGTgBj3LkUg11xZADBg5oxksXg/88c3pbnSG4b6ZRjFAVgtQTUk3JQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@tiptap/core": "^3.12.1", + "@tiptap/extension-code": "^3.12.1", + "@tiptap/extension-highlight": "^3.12.1", + "@tiptap/extension-list": "^3.12.1", + "@tiptap/pm": "^3.12.1", + "@tiptap/react": "^3.12.1", + "@tiptap/starter-kit": "^3.12.1", + "@tldraw/editor": "4.4.0", + "@tldraw/store": "4.4.0", + "classnames": "^2.5.1", + "hotkeys-js": "^3.13.9", + "idb": "^7.1.1", + "lz-string": "^1.5.0", + "radix-ui": "^1.4.2" + }, + "peerDependencies": { + "react": "^18.2.0 || ^19.2.1", + "react-dom": "^18.2.0 || ^19.2.1" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -14083,6 +15947,12 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, "node_modules/ufo": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", @@ -14301,6 +16171,16 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/usehooks-ts": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz", diff --git a/package.json b/package.json index b6b6580..5f8e58b 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "next-pwa": "^5.6.0", "next-themes": "^0.4.6", "pdfjs-dist": "^5.4.530", + "prisma": "^7.3.0", "react": "19.2.3", "react-dom": "19.2.3", "react-pdf": "^10.3.0", @@ -60,10 +61,10 @@ "sonner": "^2.0.7", "swagger-ui-dist": "^5.17.14", "tailwind-merge": "^3.4.0", + "tldraw": "^4.4.0", "usehooks-ts": "^3.1.1", "zod": "^4.3.5", - "zustand": "^5.0.10", - "prisma": "^7.3.0" + "zustand": "^5.0.10" }, "devDependencies": { "@prisma/config": "^7.2.0", @@ -84,4 +85,4 @@ "overrides": { "hono": "^4.11.4" } -} \ No newline at end of file +} diff --git a/prisma.config.ts b/prisma.config.ts index b99de5d..67f5682 100644 --- a/prisma.config.ts +++ b/prisma.config.ts @@ -1,6 +1,6 @@ import { defineConfig } from '@prisma/config'; -const url = process.env.DATABASE_URL ?? process.env.DB_CONNECTION ?? 'file:./data/atlantis.db'; +const url = process.env.DATABASE_URL ?? process.env.DB_CONNECTION ?? 'file:./atlantis-dev.db'; export default defineConfig({ datasource: { diff --git a/prisma/migrations/20260219054714_add_canvas_model/migration.sql b/prisma/migrations/20260219054714_add_canvas_model/migration.sql new file mode 100644 index 0000000..ed82383 --- /dev/null +++ b/prisma/migrations/20260219054714_add_canvas_model/migration.sql @@ -0,0 +1,140 @@ +-- CreateTable +CREATE TABLE "Tag" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "slug" TEXT NOT NULL, + "color" TEXT NOT NULL DEFAULT '#000000', + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "usageCount" INTEGER NOT NULL DEFAULT 0 +); + +-- CreateTable +CREATE TABLE "Diagram" ( + "id" TEXT NOT NULL PRIMARY KEY, + "title" TEXT NOT NULL, + "emoji" TEXT NOT NULL, + "description" TEXT NOT NULL DEFAULT '', + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + "isFavorite" BOOLEAN NOT NULL DEFAULT false, + "totalVersions" INTEGER NOT NULL DEFAULT 1, + "searchVector" TEXT NOT NULL DEFAULT '' +); + +-- CreateTable +CREATE TABLE "Content" ( + "id" TEXT NOT NULL PRIMARY KEY, + "diagramId" TEXT NOT NULL, + "content" TEXT NOT NULL, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Content_diagramId_fkey" FOREIGN KEY ("diagramId") REFERENCES "Diagram" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "Setting" ( + "key" TEXT NOT NULL PRIMARY KEY, + "value" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "Note" ( + "id" TEXT NOT NULL PRIMARY KEY, + "title" TEXT NOT NULL, + "content" TEXT NOT NULL, + "language" TEXT NOT NULL DEFAULT 'txt', + "emoji" TEXT NOT NULL DEFAULT '', + "starred" BOOLEAN NOT NULL DEFAULT false, + "private" BOOLEAN NOT NULL DEFAULT false, + "searchVector" TEXT NOT NULL DEFAULT '', + "hasTodos" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "Canvas" ( + "id" TEXT NOT NULL PRIMARY KEY, + "title" TEXT NOT NULL, + "content" TEXT NOT NULL, + "emoji" TEXT NOT NULL, + "isFavorite" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +-- CreateTable +CREATE TABLE "_DiagramToTag" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + CONSTRAINT "_DiagramToTag_A_fkey" FOREIGN KEY ("A") REFERENCES "Diagram" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "_DiagramToTag_B_fkey" FOREIGN KEY ("B") REFERENCES "Tag" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "_NoteToTag" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + CONSTRAINT "_NoteToTag_A_fkey" FOREIGN KEY ("A") REFERENCES "Note" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "_NoteToTag_B_fkey" FOREIGN KEY ("B") REFERENCES "Tag" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "_CanvasToTag" ( + "A" TEXT NOT NULL, + "B" TEXT NOT NULL, + CONSTRAINT "_CanvasToTag_A_fkey" FOREIGN KEY ("A") REFERENCES "Canvas" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "_CanvasToTag_B_fkey" FOREIGN KEY ("B") REFERENCES "Tag" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "Tag_name_key" ON "Tag"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "Tag_slug_key" ON "Tag"("slug"); + +-- CreateIndex +CREATE INDEX "Tag_slug_idx" ON "Tag"("slug"); + +-- CreateIndex +CREATE INDEX "diagram_updatedAt_idx" ON "Diagram"("updatedAt"); + +-- CreateIndex +CREATE INDEX "diagram_searchVector_idx" ON "Diagram"("searchVector"); + +-- CreateIndex +CREATE INDEX "content_diagram_updated_idx" ON "Content"("diagramId", "updatedAt"); + +-- CreateIndex +CREATE INDEX "note_updatedAt_idx" ON "Note"("updatedAt"); + +-- CreateIndex +CREATE INDEX "note_hasTodos_idx" ON "Note"("hasTodos"); + +-- CreateIndex +CREATE INDEX "note_searchVector_idx" ON "Note"("searchVector"); + +-- CreateIndex +CREATE INDEX "note_starred_idx" ON "Note"("starred"); + +-- CreateIndex +CREATE INDEX "canvas_updatedAt_idx" ON "Canvas"("updatedAt"); + +-- CreateIndex +CREATE UNIQUE INDEX "_DiagramToTag_AB_unique" ON "_DiagramToTag"("A", "B"); + +-- CreateIndex +CREATE INDEX "_DiagramToTag_B_index" ON "_DiagramToTag"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_NoteToTag_AB_unique" ON "_NoteToTag"("A", "B"); + +-- CreateIndex +CREATE INDEX "_NoteToTag_B_index" ON "_NoteToTag"("B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_CanvasToTag_AB_unique" ON "_CanvasToTag"("A", "B"); + +-- CreateIndex +CREATE INDEX "_CanvasToTag_B_index" ON "_CanvasToTag"("B"); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..2a5a444 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "sqlite" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7527236..c9e1b32 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -21,6 +21,7 @@ model Tag { notes Note[] diagrams Diagram[] + canvases Canvas[] @@index([slug]) } @@ -79,3 +80,20 @@ model Note { @@index([starred], map: "note_starred_idx") } +model Canvas { + id String @id + content String // JSON string for tldraw state + preview String? // SVG/PNG data URI for preview + title String + emoji String + isFavorite Boolean @default(false) + searchVector String @default("") + + createdAt DateTime @default(now()) + updatedAt DateTime + tags Tag[] + + @@index([updatedAt], map: "canvas_updatedAt_idx") + @@index([searchVector], map: "canvas_searchVector_idx") +} + diff --git a/prisma/schema.template.prisma b/prisma/schema.template.prisma index 56c4026..641f305 100644 --- a/prisma/schema.template.prisma +++ b/prisma/schema.template.prisma @@ -21,6 +21,7 @@ model Tag { notes Note[] diagrams Diagram[] + canvases Canvas[] @@index([slug]) } @@ -79,3 +80,20 @@ model Note { @@index([starred], map: "note_starred_idx") } +model Canvas { + id String @id + content String // JSON string for tldraw state + preview String? // SVG/PNG data URI for preview + title String + emoji String + isFavorite Boolean @default(false) + searchVector String @default("") + + createdAt DateTime @default(now()) + updatedAt DateTime + tags Tag[] + + @@index([updatedAt], map: "canvas_updatedAt_idx") + @@index([searchVector], map: "canvas_searchVector_idx") +} + diff --git a/public/openapi.json b/public/openapi.json index 6be1a2c..d3e6d50 100644 --- a/public/openapi.json +++ b/public/openapi.json @@ -18,6 +18,10 @@ { "name": "Notes", "description": "See and manage your notes" + }, + { + "name": "Canvases", + "description": "See and manage your drawing canvases" } ], "paths": { @@ -254,6 +258,127 @@ } } } + }, + "/api/access/canvases": { + "get": { + "tags": [ + "Canvases" + ], + "summary": "List all canvases", + "description": "Retrieve a paginated list of all canvases. Content is omitted from the list for performance. Requires `ENABLE_API_ACCESS=true`.", + "parameters": [ + { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "default": 1 + }, + "description": "Page number to retrieve" + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 10 + }, + "description": "Number of items per page" + } + ], + "responses": { + "200": { + "description": "A list of canvases", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CanvasList" + } + } + } + }, + "403": { + "description": "API Access Disabled" + } + } + } + }, + "/api/access/canvases/{id}": { + "get": { + "tags": [ + "Canvases" + ], + "summary": "Get a single canvas", + "description": "Retrieve the full details of a specific canvas by ID. Requires `ENABLE_API_ACCESS=true`.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The unique ID of the canvas" + } + ], + "responses": { + "200": { + "description": "The canvas details", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Canvas" + } + } + } + }, + "404": { + "description": "Canvas not found" + }, + "403": { + "description": "API Access Disabled" + } + } + } + }, + "/api/access/canvases/{id}/image": { + "get": { + "tags": [ + "Canvases" + ], + "summary": "Get canvas preview image", + "description": "Retrieve the SVG preview image of a canvas. Returns raw SVG content. Requires `ENABLE_API_ACCESS=true`.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The unique ID of the canvas" + } + ], + "responses": { + "200": { + "description": "SVG Image", + "content": { + "image/svg+xml": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Canvas or preview not found" + }, + "403": { + "description": "API Access Disabled" + } + } + } } }, "components": { @@ -425,6 +550,79 @@ } } } + }, + "Canvas": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "abc123canvas" + }, + "title": { + "type": "string", + "example": "My Awesome Drawing" + }, + "emoji": { + "type": "string", + "example": "🎨" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "color": { + "type": "string" + } + } + } + } + } + }, + "CanvasList": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Canvas" + } + }, + "pagination": { + "type": "object", + "properties": { + "page": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "totalPages": { + "type": "integer" + } + } + } + } } } } diff --git a/src/app/api/access/canvases/[id]/image/route.ts b/src/app/api/access/canvases/[id]/image/route.ts new file mode 100644 index 0000000..7fe99fa --- /dev/null +++ b/src/app/api/access/canvases/[id]/image/route.ts @@ -0,0 +1,45 @@ +import { ensureCsrfCookie } from '@/lib/csrf'; +import { getCanvasById } from '@/lib/canvas-data'; +import { logApiError } from '@/lib/logger'; +import { NextResponse } from 'next/server'; + +const apiAccessEnabled = process.env.ENABLE_API_ACCESS?.trim().toLowerCase() === 'true'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ id: string }> } +) { + if (!apiAccessEnabled) { + return new NextResponse('API Access Disabled', { status: 403 }); + } + + try { + await ensureCsrfCookie(); + const { id } = await params; + const canvas = await getCanvasById(id); + + if (!canvas || !canvas.preview) { + return new NextResponse('Preview not found', { status: 404 }); + } + + // canvas.preview is likely a data URI: "data:image/svg+xml;base64,..." + const matches = canvas.preview.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/); + + if (!matches || matches.length !== 3) { + return new NextResponse('Invalid preview data', { status: 500 }); + } + + const contentType = matches[1]; + const buffer = Buffer.from(matches[2], 'base64'); + + return new NextResponse(buffer, { + headers: { + 'Content-Type': contentType, + 'Cache-Control': 'public, max-age=60', // Cache for 1 min + }, + }); + } catch (error) { + logApiError('GET /api/access/canvases/[id]/image', error); + return new NextResponse('Internal Server Error', { status: 500 }); + } +} diff --git a/src/app/api/access/canvases/[id]/route.ts b/src/app/api/access/canvases/[id]/route.ts new file mode 100644 index 0000000..946d996 --- /dev/null +++ b/src/app/api/access/canvases/[id]/route.ts @@ -0,0 +1,43 @@ +import { ensureCsrfCookie } from '@/lib/csrf'; +import { getCanvasById } from '@/lib/canvas-data'; +import { logApiError } from '@/lib/logger'; +import { NextResponse } from 'next/server'; + +const apiAccessEnabled = process.env.ENABLE_API_ACCESS?.trim().toLowerCase() === 'true'; + +export async function GET( + request: Request, + { params }: { params: Promise<{ id: string }> } +) { + if (!apiAccessEnabled) { + return new NextResponse('API Access Disabled', { status: 403 }); + } + + try { + await ensureCsrfCookie(); + const { id } = await params; + + const canvas = await getCanvasById(id); + + if (!canvas) { + return NextResponse.json({ error: 'Canvas not found' }, { status: 404 }); + } + + return NextResponse.json({ + id: canvas.id, + title: canvas.title, + // content: canvas.content, // Optionally exclude raw tldraw JSON to avoid bloat, or include if requested? + // Let's include it for now as "content" is standard for "get by id" + content: JSON.parse(canvas.content), + emoji: canvas.emoji, + createdAt: canvas.createdAt, + updatedAt: canvas.updatedAt, + tags: canvas.tags, + // We can also provide a link to the preview image if exists + previewUrl: canvas.preview ? `/api/access/canvases/${id}/image` : null + }); + } catch (error) { + logApiError('GET /api/access/canvases/[id]', error); + return NextResponse.json({ error: 'Failed to fetch canvas' }, { status: 500 }); + } +} diff --git a/src/app/api/access/canvases/route.ts b/src/app/api/access/canvases/route.ts new file mode 100644 index 0000000..d02d101 --- /dev/null +++ b/src/app/api/access/canvases/route.ts @@ -0,0 +1,46 @@ +import { ensureCsrfCookie } from '@/lib/csrf'; +import { getCanvasPage } from '@/lib/canvas-data'; +import { logApiError } from '@/lib/logger'; +import { NextResponse } from 'next/server'; + +const apiAccessEnabled = process.env.ENABLE_API_ACCESS?.trim().toLowerCase() === 'true'; + +export async function GET(request: Request) { + if (!apiAccessEnabled) { + return new NextResponse('API Access Disabled', { status: 403 }); + } + + try { + await ensureCsrfCookie(); + + const { searchParams } = new URL(request.url); + const page = Math.max(parseInt(searchParams.get('page') || '1', 10), 1); + const limit = Math.max(parseInt(searchParams.get('limit') || '10', 10), 1); + const offset = (page - 1) * limit; + + const canvasesPage = await getCanvasPage({ limit, offset }); + + // Transform for public API + const data = canvasesPage.items.map((c) => ({ + id: c.id, + title: c.title, + emoji: c.emoji, + createdAt: c.createdAt, + updatedAt: c.updatedAt, + tags: c.tags, + })); + + return NextResponse.json({ + data, + pagination: { + page, + limit, + total: canvasesPage.total, + totalPages: Math.ceil(canvasesPage.total / limit), + }, + }); + } catch (error) { + logApiError('GET /api/access/canvases', error); + return NextResponse.json({ error: 'Failed to fetch canvases' }, { status: 500 }); + } +} diff --git a/src/app/api/canvases/[id]/route.ts b/src/app/api/canvases/[id]/route.ts new file mode 100644 index 0000000..44bfbf9 --- /dev/null +++ b/src/app/api/canvases/[id]/route.ts @@ -0,0 +1,76 @@ + +import { deleteCanvasById, getCanvasById, updateCanvasById } from '@/lib/canvas-data'; +import { csrfFailureResponse, validateCsrfToken } from '@/lib/csrf'; +import { logApiError } from '@/lib/logger'; +import { canvasUpdateSchema } from '@/lib/schemas'; +import { NextRequest, NextResponse } from 'next/server'; + +interface RouteContext { + params: Promise<{ id: string }>; +} + +export async function GET(req: NextRequest, { params }: RouteContext) { + try { + const { id } = await params; + const canvas = await getCanvasById(id); + + if (!canvas) { + return NextResponse.json({ error: 'Canvas not found' }, { status: 404 }); + } + + return NextResponse.json(canvas); + } catch (error) { + logApiError('GET /api/canvases/[id]', error); + return NextResponse.json({ error: 'Failed to load canvas' }, { status: 500 }); + } +} + +export async function PUT(req: NextRequest, { params }: RouteContext) { + if (!(await validateCsrfToken(req))) { + return csrfFailureResponse(); + } + + try { + const { id } = await params; + const json = await req.json(); + const result = canvasUpdateSchema.safeParse(json); + + if (!result.success) { + return NextResponse.json( + { error: 'Invalid input', details: result.error.flatten() }, + { status: 400 } + ); + } + + const updated = await updateCanvasById(id, result.data); + + if (!updated) { + return NextResponse.json({ error: 'Canvas not found' }, { status: 404 }); + } + + return NextResponse.json(updated); + } catch (error) { + logApiError('PUT /api/canvases/[id]', error); + return NextResponse.json({ error: 'Failed to update canvas' }, { status: 500 }); + } +} + +export async function DELETE(req: NextRequest, { params }: RouteContext) { + if (!(await validateCsrfToken(req))) { + return csrfFailureResponse(); + } + + try { + const { id } = await params; + const success = await deleteCanvasById(id); + + if (!success) { + return NextResponse.json({ error: 'Canvas not found' }, { status: 404 }); + } + + return NextResponse.json({ success: true }); + } catch (error) { + logApiError('DELETE /api/canvases/[id]', error); + return NextResponse.json({ error: 'Failed to delete canvas' }, { status: 500 }); + } +} diff --git a/src/app/api/canvases/route.ts b/src/app/api/canvases/route.ts new file mode 100644 index 0000000..95343f0 --- /dev/null +++ b/src/app/api/canvases/route.ts @@ -0,0 +1,59 @@ +import { createCanvas, getCanvasPage } from '@/lib/canvas-data'; +import { csrfFailureResponse, validateCsrfToken } from '@/lib/csrf'; +import { logApiError } from '@/lib/logger'; +import { canvasCreateSchema } from '@/lib/schemas'; +import { NextRequest, NextResponse } from 'next/server'; +import { z } from 'zod'; + +const searchSchema = z.object({ + limit: z.coerce.number().min(1).max(100).optional(), + offset: z.coerce.number().min(0).optional(), + query: z.string().optional(), + sort: z.enum(['recent', 'old', 'alphabetical']).optional(), + tagSlug: z.string().optional(), +}); + +export async function GET(req: NextRequest) { + const { searchParams } = new URL(req.url); + const result = searchSchema.safeParse(Object.fromEntries(searchParams)); + + if (!result.success) { + return NextResponse.json({ error: 'Invalid parameters' }, { status: 400 }); + } + + const { limit, offset, query, sort, tagSlug } = result.data; + + const page = await getCanvasPage({ + limit, + offset, + query, + sort, + tagSlug, + }); + + return NextResponse.json(page); +} + +export async function POST(req: NextRequest) { + if (!(await validateCsrfToken(req))) { + return csrfFailureResponse(); + } + + try { + const json = await req.json(); + const result = canvasCreateSchema.safeParse(json); + + if (!result.success) { + return NextResponse.json( + { error: 'Invalid input', details: result.error.flatten() }, + { status: 400 } + ); + } + + const canvas = await createCanvas(result.data); + return NextResponse.json(canvas, { status: 201 }); + } catch (error) { + logApiError('POST /api/canvases', error); + return NextResponse.json({ error: 'Failed to create canvas' }, { status: 500 }); + } +} diff --git a/src/app/api/settings/stats/route.ts b/src/app/api/settings/stats/route.ts index b87ba4b..7388dd0 100644 --- a/src/app/api/settings/stats/route.ts +++ b/src/app/api/settings/stats/route.ts @@ -5,14 +5,16 @@ type ActivityDay = { date: string; notes: number; diagrams: number; + canvases: number; }; export async function GET() { try { // Fetch total counts - const [totalNotes, totalDiagrams, totalTags] = await Promise.all([ + const [totalNotes, totalDiagrams, totalCanvases, totalTags] = await Promise.all([ prisma.note.count(), prisma.diagram.count(), + prisma.canvas.count(), prisma.tag.count(), ]); @@ -22,8 +24,8 @@ export async function GET() { thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 29); thirtyDaysAgo.setHours(0, 0, 0, 0); - // Fetch notes and diagrams updated in the last 30 days - const [recentNotes, recentDiagrams] = await Promise.all([ + // Fetch notes, diagrams, and canvases updated in the last 30 days + const [recentNotes, recentDiagrams, recentCanvases] = await Promise.all([ prisma.note.findMany({ where: { updatedAt: { gte: thirtyDaysAgo } }, select: { updatedAt: true }, @@ -32,17 +34,21 @@ export async function GET() { where: { updatedAt: { gte: thirtyDaysAgo } }, select: { updatedAt: true }, }), + prisma.canvas.findMany({ + where: { updatedAt: { gte: thirtyDaysAgo } }, + select: { updatedAt: true }, + }), ]); // Build activity map for the last 30 days - const activityMap = new Map(); + const activityMap = new Map(); // Initialize all 30 days with zeros for (let i = 0; i < 30; i++) { const date = new Date(thirtyDaysAgo); date.setDate(date.getDate() + i); const dateStr = date.toISOString().split('T')[0]; - activityMap.set(dateStr, { notes: 0, diagrams: 0 }); + activityMap.set(dateStr, { notes: 0, diagrams: 0, canvases: 0 }); } // Count notes per day @@ -63,6 +69,15 @@ export async function GET() { } } + // Count canvases per day + for (const canvas of recentCanvases) { + const dateStr = canvas.updatedAt.toISOString().split('T')[0]; + const existing = activityMap.get(dateStr); + if (existing) { + existing.canvases++; + } + } + // Convert map to sorted array const activity: ActivityDay[] = Array.from(activityMap.entries()) .sort(([a], [b]) => a.localeCompare(b)) @@ -71,6 +86,7 @@ export async function GET() { return NextResponse.json({ totalNotes, totalDiagrams, + totalCanvases, totalTags, activity, }); diff --git a/src/app/canvas/[id]/page.tsx b/src/app/canvas/[id]/page.tsx new file mode 100644 index 0000000..da7822a --- /dev/null +++ b/src/app/canvas/[id]/page.tsx @@ -0,0 +1,52 @@ +import { notFound } from 'next/navigation'; +import { DrawingEditor } from '@/components/DrawingEditor'; +import { getCanvasById, updateCanvasById } from '@/lib/canvas-data'; +// DashboardHeader removed as it was unused +// Usually editor pages have a header or at least navigation. +// Let's assume we want the standard layout or full screen. +// For now, let's just render the editor. + +interface PageProps { + params: Promise<{ id: string }>; +} + +export async function generateMetadata({ params }: PageProps) { + const { id } = await params; + const canvas = await getCanvasById(id); + if (!canvas) { + return { + title: 'Canvas Not Found // Atlantis', + }; + } + return { + title: `atlantis // ${canvas.title}`, + description: 'Freeform drawing canvas', + }; +} + +export default async function Page({ params }: PageProps) { + const { id } = await params; + const canvas = await getCanvasById(id); + + if (!canvas) { + notFound(); + } + + async function saveCanvas(content: string, preview?: string) { + 'use server'; + // We only save content here if strictly needed by the editor's auto-save hook + // but the editor will primarily use client-side API calls for granular updates + await updateCanvasById(id, { content, preview }); + } + + return ( +
+
+ +
+
+ ); +} diff --git a/src/app/canvas/new/page.tsx b/src/app/canvas/new/page.tsx new file mode 100644 index 0000000..107fe3b --- /dev/null +++ b/src/app/canvas/new/page.tsx @@ -0,0 +1,9 @@ +import { redirect } from 'next/navigation'; +import { createCanvas } from '@/lib/canvas-data'; + +export default async function Page() { + const canvas = await createCanvas({ + title: 'Untitled Canvas', + }); + redirect(`/canvas/${canvas.id}`); +} diff --git a/src/app/canvas/page.tsx b/src/app/canvas/page.tsx new file mode 100644 index 0000000..4104653 --- /dev/null +++ b/src/app/canvas/page.tsx @@ -0,0 +1,20 @@ +import { CanvasGrid } from '@/components/CanvasGrid'; +import { getCanvasPage } from '@/lib/canvas-data'; + +export const dynamic = 'force-dynamic'; + +export const metadata = { + title: 'atlantis // Canvases', +}; + +export default async function Page() { + const page = await getCanvasPage({ limit: 24, offset: 0 }); + return ( + + ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 892287a..0396148 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,5 @@ import Link from 'next/link'; -import { Star, FileText, PenSquare } from 'lucide-react'; +import { Star, FileText, PenSquare, PenTool } from 'lucide-react'; import { getHomePageData } from '@/lib/dashboard-data'; import { Button } from '@/components/ui/button'; import { DashboardHeader } from '@/components/DashboardHeader'; @@ -25,8 +25,16 @@ export default async function Page() { items: data.recentNotesRaw.items.filter((n) => !n.private).slice(0, 8), }; - const hasStarred = data.starredDiagrams.items.length > 0 || starredNotes.items.length > 0; - const hasRecent = data.recentDiagrams.items.length > 0 || recentNotes.items.length > 0; + const hasStarred = + data.starredDiagrams.items.length > 0 || + starredNotes.items.length > 0 || + data.starredCanvases.items.length > 0; + + const hasRecent = + data.recentDiagrams.items.length > 0 || + recentNotes.items.length > 0 || + data.recentCanvases.items.length > 0; + const isCompletelyEmpty = !hasStarred && !hasRecent; return ( @@ -54,6 +62,11 @@ export default async function Page() { + + + + + + + handleShare(e, canvas.id)} + > + + Copy link + + handleFavorite(e, canvas.id)} + > + + {canvas.isFavorite ? 'Unstar' : 'Star'} + + + + { + e.preventDefault(); + e.stopPropagation(); + setDeleteId(canvas.id); + }} + > + + Delete + + + + + + + + {canvas.tags && canvas.tags.length > 0 && ( +
+ {canvas.tags.map(tag => ( + + ))} +
+ )} +
+ {canvas.preview ? ( + {canvas.title} + ) : ( +
+ +
+ )} +
+
+ + ))} + + ); + + return ( + <> +
+ {/* Header */} +
+
+
+ + + 🔱 + +

atlantis // Canvases

+ +
+ +
+ +
+ +
+ + + +
+
+
+ + {/* Main Content */} +
+ {isLoading ? ( +
+ {[1, 2, 3, 4, 5, 6, 7, 8].map((i) => ( + + + + + + + + + + ))} +
+ ) : canvases.length === 0 ? ( +
+ + +
+ + 🎨 + +
+ No canvases yet + + Create your first freeform canvas to get started. + +
+ + + +
+
+ ) : ( +
+ {hasStarred && ( +
+

+ + Starred // + {starredCanvases.length} +

+ {renderCanvasGrid(starredCanvases)} +
+ )} + + {(otherCanvases.length > 0 || !hasStarred) && ( +
+
+

+ All canvases // + + {otherCanvases.length} of {Math.max(total - starredCanvases.length, 0)} + +

+ + + + + + + handleSortChange(v as NoteSortOption)}> + Recent + Oldest + Alphabetical + + + +
+ {renderCanvasGrid(otherCanvases)} +
+ )} + + {isFetchingMore && ( +
+ {[1, 2, 3, 4].map((i) => ( + + + + + + + + + + ))} +
+ )} + +
+
+ )} +
+ + {/* Footer */} + +
+ + + + !open && setDeleteId(null)}> + + + Delete canvas? + + This will permanently delete “{canvasToDelete?.title}”. This action + cannot be undone. + + + + Cancel + + Delete + + + + + + ); +} diff --git a/src/components/DashboardCardMenu.tsx b/src/components/DashboardCardMenu.tsx index c852e91..363d533 100644 --- a/src/components/DashboardCardMenu.tsx +++ b/src/components/DashboardCardMenu.tsx @@ -28,7 +28,7 @@ import { toast } from 'sonner'; interface DashboardCardMenuProps { id: string; title: string; - type: 'diagram' | 'note'; + type: 'diagram' | 'note' | 'canvas'; isStarred: boolean; } @@ -46,8 +46,19 @@ export function DashboardCardMenu({ id, title, type, isStarred }: DashboardCardM try { const csrfToken = await ensureCsrfToken(); - const url = type === 'diagram' ? `/api/diagrams/${id}` : `/api/notes/${id}`; - const body = type === 'diagram' ? { isFavorite: newStarred } : { starred: newStarred }; + let url = ''; + let body = {}; + + if (type === 'diagram') { + url = `/api/diagrams/${id}`; + body = { isFavorite: newStarred }; + } else if (type === 'note') { + url = `/api/notes/${id}`; + body = { starred: newStarred }; + } else { + url = `/api/canvases/${id}`; + body = { isFavorite: newStarred }; + } const res = await fetch(url, { method: 'PUT', @@ -70,7 +81,14 @@ export function DashboardCardMenu({ id, title, type, isStarred }: DashboardCardM const handleDelete = async () => { try { const csrfToken = await ensureCsrfToken(); - const url = type === 'diagram' ? `/api/diagrams/${id}` : `/api/notes/${id}`; + let url = ''; + if (type === 'diagram') { + url = `/api/diagrams/${id}`; + } else if (type === 'note') { + url = `/api/notes/${id}`; + } else { + url = `/api/canvases/${id}`; + } const res = await fetch(url, { method: 'DELETE', @@ -80,7 +98,7 @@ export function DashboardCardMenu({ id, title, type, isStarred }: DashboardCardM }); if (!res.ok) throw new Error('Failed to delete'); - toast.success(`${type === 'diagram' ? 'Diagram' : 'Note'} deleted`); + toast.success(`${type.charAt(0).toUpperCase() + type.slice(1)} deleted`); router.refresh(); } catch (error) { console.error('DashboardCardMenu: Failed to delete item:', error); @@ -92,7 +110,14 @@ export function DashboardCardMenu({ id, title, type, isStarred }: DashboardCardM const handleShare = async (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - const path = type === 'diagram' ? `/diagram/${id}` : `/notes/${id}`; + let path = ''; + if (type === 'diagram') { + path = `/diagram/${id}`; + } else if (type === 'note') { + path = `/notes/${id}`; + } else { + path = `/canvas/${id}`; + } const url = `${window.location.origin}${path}`; const success = await copyToClipboard(url); if (success) { @@ -125,6 +150,9 @@ export function DashboardCardMenu({ id, title, type, isStarred }: DashboardCardM console.error('DashboardCardMenu: Failed to download:', error); toast.error('Failed to download'); } + } else if (type === 'canvas') { + // For canvas, navigate to editor for now + window.open(`/canvas/${id}`, '_blank'); } else { // For diagrams, navigate to the diagram page where full download options exist window.open(`/diagram/${id}`, '_blank'); diff --git a/src/components/DashboardCards.tsx b/src/components/DashboardCards.tsx index 402c350..f2ee08b 100644 --- a/src/components/DashboardCards.tsx +++ b/src/components/DashboardCards.tsx @@ -6,18 +6,29 @@ import { Card, CardHeader, CardContent, CardTitle, CardDescription } from '@/com import { DashboardCardMenu } from '@/components/DashboardCardMenu'; import { TagBadge } from '@/components/TagBadge'; import { formatDate, cn } from '@/lib/utils'; -import { Diagram, Note } from '@/lib/types'; +import { Diagram, Note, Canvas } from '@/lib/types'; import { ArrowRight } from 'lucide-react'; export function CompactCard({ type, item, }: { - type: 'diagram' | 'note'; - item: Diagram | Omit; + type: 'diagram' | 'note' | 'canvas'; + item: Diagram | Omit | Omit; }) { - const href = type === 'diagram' ? `/diagram/${item.id}` : `/notes/${item.id}`; - const icon = item.emoji || (type === 'diagram' ? '📊' : '📝'); + let href = ''; + let icon = item.emoji; + + if (type === 'diagram') { + href = `/diagram/${item.id}`; + icon = icon || '📊'; + } else if (type === 'note') { + href = `/notes/${item.id}`; + icon = icon || '📝'; + } else { + href = `/canvas/${item.id}`; + icon = icon || '🎨'; + } return ( ; + type: 'diagram' | 'note' | 'canvas'; + item: Diagram | Omit | Omit; }) { - const href = type === 'diagram' ? `/diagram/${item.id}` : `/notes/${item.id}`; - const icon = item.emoji || (type === 'diagram' ? '📊' : '📝'); - const description = type === 'diagram' ? (item as Diagram).description : (item as Note).language; - const badgeLabel = type === 'diagram' ? 'Diagram' : 'Note'; - const isStarred = type === 'diagram' ? (item as Diagram).isFavorite : (item as Omit).starred; + let href = ''; + let icon = item.emoji; + let description = ''; + let badgeLabel = ''; + let isStarred = false; + let badgeClass = ''; + + if (type === 'diagram') { + const d = item as Diagram; + href = `/diagram/${d.id}`; + icon = icon || '📊'; + description = d.description || 'No description'; + badgeLabel = 'Diagram'; + isStarred = d.isFavorite; + badgeClass = 'border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-800 dark:bg-blue-900/20 dark:text-blue-300'; + } else if (type === 'note') { + const n = item as Omit; + href = `/notes/${n.id}`; + icon = icon || '📝'; + description = `Language // ${n.language}`; + badgeLabel = 'Note'; + isStarred = n.starred; + badgeClass = 'border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-900/20 dark:text-emerald-300'; + } else { + const c = item as Omit; + href = `/canvas/${c.id}`; + icon = icon || '🎨'; + description = 'Freeform Canvas'; + badgeLabel = 'Canvas'; + isStarred = c.isFavorite; + badgeClass = 'border-purple-200 bg-purple-50 text-purple-700 dark:border-purple-800 dark:bg-purple-900/20 dark:text-purple-300'; + } return ( @@ -59,9 +97,7 @@ export function DashboardCard({ {badgeLabel} @@ -85,7 +121,7 @@ export function DashboardCard({

- {type === 'diagram' ? description || 'No description' : `Language // ${description}`} + {description}

{item.tags && item.tags.length > 0 && (
diff --git a/src/components/DashboardHeader.tsx b/src/components/DashboardHeader.tsx index d18fbaf..fe70669 100644 --- a/src/components/DashboardHeader.tsx +++ b/src/components/DashboardHeader.tsx @@ -5,7 +5,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip import { useDiagramStore } from '@/lib/store'; import Link from 'next/link'; import { useEffect } from 'react'; -import { Settings2, Keyboard, BookOpen, Github } from 'lucide-react'; +import { Settings2, Keyboard, BookOpen, Github, Menu } from 'lucide-react'; import { GlobalSearchDialog } from '@/components/GlobalSearchDialog'; import { useShortcutPlatform } from '@/lib/use-platform'; import { useKeyboardShortcuts } from '@/lib/use-keyboard-shortcuts'; @@ -16,7 +16,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { Menu } from 'lucide-react'; + interface DashboardHeaderProps { enableApiAccess?: boolean; @@ -47,16 +47,16 @@ export function DashboardHeader({ enableApiAccess }: DashboardHeaderProps) { }, [setHasAiApiKey, setAiProvider]); return ( -
-
- +
+
+ 🔱 -

atlantis

+

atlantis

-
+
@@ -70,7 +70,7 @@ export function DashboardHeader({ enableApiAccess }: DashboardHeaderProps) { onClick={() => setPaletteOpen(true)} aria-label="Keyboard shortcuts" > - + @@ -83,13 +83,27 @@ export function DashboardHeader({ enableApiAccess }: DashboardHeaderProps) { -
- - +
+ + + + + + + Diagrams (Mermaid) + + + Notes + + + Drawings (Canvas) + + + + @@ -124,6 +138,9 @@ export function DashboardHeader({ enableApiAccess }: DashboardHeaderProps) { Docs )} + + Canvases +
diff --git a/src/components/DashboardSection.tsx b/src/components/DashboardSection.tsx index 4e9012b..b0397cd 100644 --- a/src/components/DashboardSection.tsx +++ b/src/components/DashboardSection.tsx @@ -2,15 +2,15 @@ import Link from 'next/link'; import { ArrowRight } from 'lucide-react'; -import { Diagram, Note } from '@/lib/types'; +import { Diagram, Note, Canvas } from '@/lib/types'; import { DashboardCard, EmptySectionPlaceholder } from './DashboardCards'; interface DashboardSectionProps { - items: (Diagram | Omit)[]; + items: (Diagram | Omit | Omit)[]; title: string; icon: React.ReactNode; viewAllHref: string; - type: 'diagram' | 'note'; + type: 'diagram' | 'note' | 'canvas'; emptyState: { icon: React.ReactNode; title: string; diff --git a/src/components/DiagramGrid.tsx b/src/components/DiagramGrid.tsx index 2f1072d..5879775 100644 --- a/src/components/DiagramGrid.tsx +++ b/src/components/DiagramGrid.tsx @@ -730,6 +730,7 @@ export function DiagramGrid({ open={isSearchOpen} onOpenChange={setIsSearchOpen} initialDiagrams={diagrams} + hideTrigger /> diff --git a/src/components/DrawingEditor.tsx b/src/components/DrawingEditor.tsx new file mode 100644 index 0000000..51d624d --- /dev/null +++ b/src/components/DrawingEditor.tsx @@ -0,0 +1,234 @@ +'use client'; + +import { useCallback, useEffect, useState, useRef } from 'react'; +import { Tldraw, useEditor, Editor } from 'tldraw'; +import 'tldraw/tldraw.css'; +import { useDebounceCallback } from 'usehooks-ts'; +import { toast } from 'sonner'; +import Link from 'next/link'; +import { ResponsiveTagPicker } from '@/components/responsive-tag-picker'; +import { Canvas, Tag } from '@/lib/types'; +import { CSRF_HEADER_NAME, ensureCsrfToken } from '@/lib/csrf-client'; + +interface DrawingEditorProps { + initialCanvas: Canvas; + onSave?: (content: string, preview?: string) => Promise; +} + +function EditorController({ + initialContent, + onSave, + onEditorMount +}: { + initialContent?: string; + onSave?: (content: string, preview?: string) => Promise; + onEditorMount?: (editor: Editor) => void; +}) { + const editor = useEditor(); + const isReadyRef = useRef(false); + + useEffect(() => { + if (onEditorMount) { + onEditorMount(editor); + } + }, [editor, onEditorMount]); + + // Load initial content + useEffect(() => { + if (isReadyRef.current) return; + isReadyRef.current = true; + if (initialContent) { + try { + const snapshot = JSON.parse(initialContent); + editor.loadSnapshot(snapshot); + } catch (e) { + console.error('Failed to load snapshot', e); + } + } + }, [editor, initialContent]); + + // Handle auto-save + const handleSave = useDebounceCallback(async () => { + if (!onSave) return; + const { document, session } = editor.getSnapshot(); + const json = JSON.stringify({ document, session }); + + // Generate SVG preview + let preview: string | undefined; + try { + // 1. Get all shape IDs to render everything + const shapeIds = Array.from(editor.getCurrentPageShapeIds()); + if (shapeIds.length > 0) { + const result = await editor.getSvgElement(shapeIds, { + scale: 1, + background: true, + }); + if (result) { + const svg = result.svg; + // Convert SVG to string + const serializer = new XMLSerializer(); + const svgString = serializer.serializeToString(svg); + // Base64 encode for simple storage/transport (or store as raw string if DB permits) + // For correct data URI: data:image/svg+xml;base64,... + preview = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgString)))}`; + } + } + } catch (e) { + console.error('Failed to generate preview', e); + } + + // We need to pass the preview alongside content. + // However, onSave currently only accepts content string (which is usually just the snapshot JSON). + // Quick fix: The backend `updateCanvasById` expects us to call it. + // But `onSave` in Page calls `saveCanvas` which calls `updateCanvasById`. + // Let's modify `onSave` signature in props to accept options object or second arg. + // OR better: The Page component defines `saveCanvas`. We should update `saveCanvas` to accept an optional preview. + // But `onSave` prop type is `(content: string) => Promise`. + // Let's perform a direct API call here for the preview to ensure it saves even if `onSave` is just for content syncing? + // Actually, the `Page` component's `saveCanvas` is a Server Action. We can't easily change its signature without changing the prop type everywhere. + // Let's assume we can change the prop type in `DrawingEditor` and update `Page`. + + await onSave(json, preview); + }, 1000); + + useEffect(() => { + const cleanup = editor.store.listen(() => { + handleSave(); + }); + return () => cleanup(); + }, [editor, handleSave]); + + return null; +} + +export function DrawingEditor({ initialCanvas, onSave }: DrawingEditorProps) { + const [canvas, setCanvas] = useState(initialCanvas); + const [tags, setTags] = useState(initialCanvas.tags || []); + + // Derived state for local edits before save + const [title, setTitle] = useState(initialCanvas.title); + const [emoji, setEmoji] = useState(initialCanvas.emoji); + + const lastSavedTitle = useRef(initialCanvas.title); + const lastSavedEmoji = useRef(initialCanvas.emoji); + const lastSavedTags = useRef(initialCanvas.tags || []); + + // Update document title + useEffect(() => { + document.title = `atlantis // ${title}`; + }, [title]); + + const saveMetadata = useCallback(async (updates: Omit, 'tags'> & { tags?: string[] }, silent = false) => { + try { + const csrfToken = await ensureCsrfToken(); + const res = await fetch(`/api/canvases/${canvas.id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + [CSRF_HEADER_NAME]: csrfToken, + }, + body: JSON.stringify(updates), + }); + + if (!res.ok) throw new Error('Failed to save metadata'); + + const updated = await res.json(); + setCanvas(prev => ({ ...prev, ...updated })); + + // Update refs + if (updates.title !== undefined) lastSavedTitle.current = updated.title; + if (updates.emoji !== undefined) lastSavedEmoji.current = updated.emoji; + if (updates.tags !== undefined) lastSavedTags.current = updated.tags || []; + + if (!silent) toast.success('Saved'); + } catch (error) { + console.error('Failed to save canvas metadata:', error); + if (!silent) toast.error('Failed to save changes'); + } + }, [canvas.id]); + + // Debounced title save + const debouncedSaveTitle = useDebounceCallback(async (newTitle: string) => { + if (newTitle === lastSavedTitle.current) return; + await saveMetadata({ title: newTitle }, true); + }, 1000); + + const handleTitleChange = (e: React.ChangeEvent) => { + setTitle(e.target.value); + debouncedSaveTitle(e.target.value); + }; + + // Save tags immediately when changed + useEffect(() => { + // loose comparison for tags + const currentTagIds = tags.map(t => t.id).sort().join(','); + const savedTagIds = lastSavedTags.current.map(t => t.id).sort().join(','); + + if (currentTagIds !== savedTagIds) { + saveMetadata({ tags: tags.map(t => t.id) }, true); + } + }, [tags, saveMetadata]); + + return ( +
+ {/* Header */} +
+ {/* Left: Branding & Title */} +
+ + 🔱 + 🔱 atlantis // + + +
+ +
+ { + setEmoji(e.target.value); + saveMetadata({ emoji: e.target.value }, true); + }} + className="w-8 text-xl bg-transparent border-none focus:ring-0 text-center p-0 cursor-pointer" + title="Change Emoji" + /> + +
+ +
+
+
+ + {/* Right: Actions */} +
+ {/* Placeholder for Search or other actions */} +
+
+ +
+ + + +
+
+ ); +} diff --git a/src/components/GlobalSearchDialog.tsx b/src/components/GlobalSearchDialog.tsx index 0410a61..59f52d4 100644 --- a/src/components/GlobalSearchDialog.tsx +++ b/src/components/GlobalSearchDialog.tsx @@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react'; import type { KeyboardEvent as ReactKeyboardEvent } from 'react'; import { useRouter } from 'next/navigation'; -import { Search, Star, Loader2, FileText, PenSquare } from 'lucide-react'; +import { Search, Star, Loader2, FileText, PenSquare, PenTool } from 'lucide-react'; import { Dialog, DialogContent, @@ -16,11 +16,11 @@ import { ScrollArea } from '@/components/ui/scroll-area'; import { Button } from '@/components/ui/button'; import { useShortcutPlatform } from '@/lib/use-platform'; import { cn, formatDate } from '@/lib/utils'; -import type { Diagram, Note } from '@/lib/types'; +import type { Canvas, Diagram, Note } from '@/lib/types'; type SearchResult = { id: string; - type: 'diagram' | 'note'; + type: 'diagram' | 'note' | 'canvas'; title: string; emoji: string; updatedAt: string; @@ -118,19 +118,21 @@ export function GlobalSearchDialog({ params.set('query', normalized); } - // Fetch diagrams and notes in parallel - const [diagramsRes, notesRes] = await Promise.all([ + // Fetch diagrams, notes, and canvases in parallel + const [diagramsRes, notesRes, canvasesRes] = await Promise.all([ fetch(`/api/diagrams?${params.toString()}`, { signal: controller.signal }), fetch(`/api/notes?${params.toString()}`, { signal: controller.signal }), + fetch(`/api/canvases?${params.toString()}`, { signal: controller.signal }), ]); - if (!diagramsRes.ok || !notesRes.ok) { + if (!diagramsRes.ok || !notesRes.ok || !canvasesRes.ok) { throw new Error('Failed to fetch results'); } - const [diagramsData, notesData] = await Promise.all([ + const [diagramsData, notesData, canvasesData] = await Promise.all([ diagramsRes.json(), notesRes.json(), + canvasesRes.json(), ]); const diagramItems: SearchResult[] = (diagramsData.items || []).map((d: Diagram) => ({ @@ -153,8 +155,17 @@ export function GlobalSearchDialog({ language: n.language, })); + const canvasItems: SearchResult[] = (canvasesData.items || []).map((c: Omit) => ({ + id: c.id, + type: 'canvas' as const, + title: c.title, + emoji: c.emoji || '🎨', + updatedAt: c.updatedAt, + isFavorite: c.isFavorite, + })); + // Combine and sort by favorite first, then by date - const combined = [...diagramItems, ...noteItems].sort((a, b) => { + const combined = [...diagramItems, ...noteItems, ...canvasItems].sort((a, b) => { if (a.isFavorite !== b.isFavorite) { return a.isFavorite ? -1 : 1; } @@ -202,6 +213,8 @@ export function GlobalSearchDialog({ } else { if (item.type === 'diagram') { router.push(`/diagram/${item.id}`); + } else if (item.type === 'canvas') { + router.push(`/canvas/${item.id}`); } else { router.push(`/notes/${item.id}`); } @@ -320,22 +333,26 @@ export function GlobalSearchDialog({
- {highlightText(item.title || (item.type === 'diagram' ? 'Untitled Diagram' : 'Untitled Note'))} + {highlightText(item.title || (item.type === 'diagram' ? 'Untitled Diagram' : item.type === 'canvas' ? 'Untitled Canvas' : 'Untitled Note'))} {item.type === 'diagram' ? ( + ) : item.type === 'canvas' ? ( + ) : ( )} - {item.type === 'diagram' ? 'Diagram' : 'Note'} + {item.type === 'diagram' ? 'Diagram' : item.type === 'canvas' ? 'Canvas' : 'Note'} {item.isFavorite && ( diff --git a/src/lib/canvas-data.ts b/src/lib/canvas-data.ts new file mode 100644 index 0000000..0a029a9 --- /dev/null +++ b/src/lib/canvas-data.ts @@ -0,0 +1,301 @@ +import type { Prisma } from '@prisma/client'; + +import { prisma } from './prisma'; +import { buildSearchVector, stripStopWords } from './search'; +import type { Canvas, CanvasPage, NoteSortOption } from './types'; +import { generateShortId, getRandomEmoji } from './utils'; + +const DEFAULT_PAGE_SIZE = 24; +const MAX_PAGE_SIZE = 100; +const MAX_TAGS_PER_ITEM = 3; +const TITLE_MAX = 200; + +type TagRow = { id: string; name: string; slug: string; color: string }; +type CanvasRow = Prisma.CanvasGetPayload & { tags?: TagRow[] }; +type CanvasListRow = Omit; + +const normalizeLimit = (limit?: number | null) => { + if (!Number.isFinite(limit)) return DEFAULT_PAGE_SIZE; + return Math.min(Math.max(Math.trunc(limit as number), 1), MAX_PAGE_SIZE); +}; + +const normalizeOffset = (offset?: number | null) => { + if (!Number.isFinite(offset)) return 0; + return Math.max(Math.trunc(offset as number), 0); +}; + +const mapTagIds = (ids: string[]) => ids.map((tagId) => ({ id: tagId })); + +// Content is JSON, so we don't index it for full-text search yet + +const toCanvas = (row: CanvasRow): Canvas => ({ + id: row.id, + title: row.title, + content: row.content, + preview: row.preview, + emoji: row.emoji, + isFavorite: row.isFavorite, + createdAt: row.createdAt.toISOString(), + updatedAt: row.updatedAt.toISOString(), + tags: row.tags, +}); + +// Explicit select for list queries — excludes heavy content & searchVector columns +const canvasListSelect = { + id: true, + title: true, + preview: true, + emoji: true, + isFavorite: true, + createdAt: true, + updatedAt: true, + tags: true, +} satisfies Prisma.CanvasSelect; + +const toCanvasListItem = (row: CanvasListRow): Omit => ({ + id: row.id, + title: row.title, + preview: row.preview, + emoji: row.emoji, + isFavorite: row.isFavorite, + createdAt: row.createdAt.toISOString(), + updatedAt: row.updatedAt.toISOString(), + tags: row.tags, +}); + +const validateCanvasLengths = (title?: string) => { + if (typeof title === 'string' && title.length > TITLE_MAX) { + throw new Error(`Title exceeds ${TITLE_MAX} characters`); + } +}; + +const isNotFoundError = (error: unknown): boolean => + typeof error === 'object' && error !== null && (error as { code?: string }).code === 'P2025'; + +async function ensureUniqueCanvasId(): Promise { + let id = generateShortId(); + let existing = await prisma.canvas.findUnique({ where: { id }, select: { id: true } }); + while (existing) { + id = generateShortId(); + existing = await prisma.canvas.findUnique({ where: { id }, select: { id: true } }); + } + return id; +} + +export async function getCanvasPage({ + limit = DEFAULT_PAGE_SIZE, + offset = 0, + query, + sort = 'recent', + favoritesOnly = false, + tagSlug, +}: { + limit?: number; + offset?: number; + query?: string; + sort?: NoteSortOption; + favoritesOnly?: boolean; + tagSlug?: string; +}): Promise { + const normalizedLimit = normalizeLimit(limit); + const normalizedOffset = normalizeOffset(offset); + + const where: Prisma.CanvasWhereInput = {}; + if (query?.trim()) { + where.searchVector = { contains: stripStopWords(query.trim()) }; + } + if (favoritesOnly) { + where.isFavorite = true; + } + if (tagSlug) { + where.tags = { some: { slug: tagSlug } }; + } + + const orderBy: Prisma.CanvasOrderByWithRelationInput = (() => { + switch (sort) { + case 'old': + return { updatedAt: 'asc' }; + case 'alphabetical': + return { title: 'asc' }; + case 'recent': + default: + return { updatedAt: 'desc' }; + } + })(); + + const [canvases, total] = await Promise.all([ + prisma.canvas.findMany({ + where, + orderBy, + skip: normalizedOffset, + take: normalizedLimit, + select: canvasListSelect, + }), + prisma.canvas.count({ where }), + ]); + + const items = canvases.map(toCanvasListItem); + const nextOffset = normalizedOffset + items.length; + const hasMore = nextOffset < total; + + return { items, total, hasMore, nextOffset }; +} + +export async function getCanvasById(id: string): Promise { + const canvas = await prisma.canvas.findUnique({ + where: { id }, + include: { tags: true }, + }); + if (!canvas) return null; + return toCanvas(canvas); +} + +export async function createCanvas({ + title, + content, + preview, + emoji, + tags, +}: { + title?: string; + content?: string; + preview?: string; + emoji?: string; + tags?: string[]; +}): Promise { + const now = new Date(); + validateCanvasLengths(title); + + if (tags && tags.length > MAX_TAGS_PER_ITEM) { + throw new Error(`Cannot add more than ${MAX_TAGS_PER_ITEM} tags`); + } + + const canvasId = await ensureUniqueCanvasId(); + const nextTitle = title || 'Untitled Canvas'; + const nextContent = content || '{}'; + + const searchVector = buildSearchVector(nextTitle, '', ''); + + const canvas = await prisma.$transaction(async (tx) => { + const created = await tx.canvas.create({ + data: { + id: canvasId, + title: nextTitle, + content: nextContent, + preview: preview, + emoji: emoji || getRandomEmoji(), + isFavorite: false, + searchVector, + createdAt: now, + updatedAt: now, + tags: tags ? { connect: mapTagIds(tags) } : undefined, + }, + include: { tags: true }, + }); + + if (tags && tags.length > 0) { + await tx.tag.updateMany({ + where: { id: { in: tags } }, + data: { usageCount: { increment: 1 } }, + }); + } + + return created; + }); + + return toCanvas(canvas); +} + +export async function updateCanvasById( + id: string, + updates: Partial> & { + tags?: string[]; + } +): Promise { + const existing = await prisma.canvas.findUnique({ + where: { id }, + include: { tags: { select: { id: true } } }, + }); + if (!existing) return null; + + validateCanvasLengths(updates.title); + + if (updates.tags && updates.tags.length > MAX_TAGS_PER_ITEM) { + throw new Error(`Cannot add more than ${MAX_TAGS_PER_ITEM} tags`); + } + + const now = new Date(); + const nextTitle = updates.title ?? existing.title; + const nextContent = updates.content ?? existing.content; + + const canvas = await prisma.$transaction(async (tx) => { + if (updates.tags) { + const oldTagIds = existing.tags.map((t) => t.id); + const newTagIds = updates.tags!; + + const addedTags = newTagIds.filter((id) => !oldTagIds.includes(id)); + const removedTags = oldTagIds.filter((id) => !newTagIds.includes(id)); + + if (addedTags.length > 0) { + await tx.tag.updateMany({ + where: { id: { in: addedTags } }, + data: { usageCount: { increment: 1 } }, + }); + } + + if (removedTags.length > 0) { + await tx.tag.updateMany({ + where: { id: { in: removedTags } }, + data: { usageCount: { decrement: 1 } }, + }); + } + } + + const updated = await tx.canvas.update({ + where: { id }, + data: { + title: nextTitle, + content: nextContent, + preview: updates.preview ?? existing.preview, + emoji: updates.emoji ?? existing.emoji, + isFavorite: typeof updates.isFavorite === 'boolean' ? updates.isFavorite : existing.isFavorite, + searchVector: buildSearchVector(nextTitle, '', ''), + updatedAt: now, + tags: updates.tags ? { set: mapTagIds(updates.tags) } : undefined, + }, + include: { tags: true }, + }); + + return updated; + }); + + return toCanvas(canvas); +} + +export async function deleteCanvasById(id: string): Promise { + try { + return await prisma.$transaction(async (tx) => { + const canvas = await tx.canvas.findUnique({ + where: { id }, + include: { tags: { select: { id: true } } }, + }); + + if (!canvas) return false; + + if (canvas.tags.length > 0) { + await tx.tag.updateMany({ + where: { id: { in: canvas.tags.map((t) => t.id) } }, + data: { usageCount: { decrement: 1 } }, + }); + } + + await tx.canvas.delete({ where: { id } }); + return true; + }); + } catch (error: unknown) { + if (isNotFoundError(error)) { + return false; + } + throw error; + } +} diff --git a/src/lib/dashboard-data.ts b/src/lib/dashboard-data.ts index 57e02cc..9de0a6f 100644 --- a/src/lib/dashboard-data.ts +++ b/src/lib/dashboard-data.ts @@ -1,6 +1,7 @@ import { prisma } from './prisma'; import { getDiagramPage } from './data'; import { getNotePage } from './notes-data'; +import { getCanvasPage } from './canvas-data'; export async function getHomePageData() { const [ @@ -14,8 +15,10 @@ export async function getHomePageData() { knowledgeStats, starredDiagrams, starredNotesRaw, + starredCanvases, recentDiagrams, recentNotesRaw, + recentCanvases, ] = await Promise.all([ getDashboardStats(), getTopTags(6), @@ -27,8 +30,10 @@ export async function getHomePageData() { getKnowledgeStats(), getDiagramPage({ limit: 4, favoritesOnly: true }), getNotePage({ limit: 4, starredOnly: true }), + getCanvasPage({ limit: 4, favoritesOnly: true }), getDiagramPage({ limit: 8, sort: 'recent' }), getNotePage({ limit: 10, sort: 'recent' }), + getCanvasPage({ limit: 8, sort: 'recent' }), ]); return { @@ -42,8 +47,10 @@ export async function getHomePageData() { knowledgeStats, starredDiagrams, starredNotesRaw, + starredCanvases, recentDiagrams, recentNotesRaw, + recentCanvases, }; } @@ -51,6 +58,7 @@ export interface DashboardStats { totalDiagrams: number; totalNotes: number; totalTags: number; + totalCanvases: number; starredItems: number; } @@ -77,7 +85,7 @@ export interface ActivityItem { id: string; title: string; emoji: string; - type: 'diagram' | 'note'; + type: 'diagram' | 'note' | 'canvas'; updatedAt: string; } @@ -85,7 +93,7 @@ export interface StaleItem { id: string; title: string; emoji: string; - type: 'diagram' | 'note'; + type: 'diagram' | 'note' | 'canvas'; daysSinceUpdate: number; } @@ -101,7 +109,7 @@ export interface KnowledgeStats { type DbItem = { id: string; title: string; emoji: string; updatedAt: Date }; // Helper to convert DB items to ActivityItem format -function toActivityItem(item: DbItem, type: 'diagram' | 'note'): ActivityItem { +function toActivityItem(item: DbItem, type: 'diagram' | 'note' | 'canvas'): ActivityItem { return { id: item.id, title: item.title, @@ -112,7 +120,7 @@ function toActivityItem(item: DbItem, type: 'diagram' | 'note'): ActivityItem { } // Helper to convert DB items to StaleItem format -function toStaleItem(item: DbItem, type: 'diagram' | 'note'): StaleItem { +function toStaleItem(item: DbItem, type: 'diagram' | 'note' | 'canvas'): StaleItem { const now = new Date(); const days = Math.floor((now.getTime() - item.updatedAt.getTime()) / (1000 * 60 * 60 * 24)); return { @@ -128,19 +136,22 @@ function toStaleItem(item: DbItem, type: 'diagram' | 'note'): StaleItem { * Get aggregated dashboard stats (counts). */ export async function getDashboardStats(): Promise { - const [totalDiagrams, totalNotes, totalTags, starredDiagrams, starredNotes] = await Promise.all([ + const [totalDiagrams, totalNotes, totalTags, totalCanvases, starredDiagrams, starredNotes, starredCanvases] = await Promise.all([ prisma.diagram.count(), prisma.note.count(), prisma.tag.count(), + prisma.canvas.count(), prisma.diagram.count({ where: { isFavorite: true } }), prisma.note.count({ where: { starred: true } }), + prisma.canvas.count({ where: { isFavorite: true } }), ]); return { totalDiagrams, totalNotes, totalTags, - starredItems: starredDiagrams + starredNotes, + totalCanvases, + starredItems: starredDiagrams + starredNotes + starredCanvases, }; } @@ -221,7 +232,7 @@ export async function getRecentTodos(limit = 5): Promise { * Get recent activity (items edited today or recently). */ export async function getRecentActivity(limit = 5): Promise { - const [diagrams, notes] = await Promise.all([ + const [diagrams, notes, canvases] = await Promise.all([ prisma.diagram.findMany({ orderBy: { updatedAt: 'desc' }, take: limit, @@ -232,11 +243,17 @@ export async function getRecentActivity(limit = 5): Promise { take: limit, select: { id: true, title: true, emoji: true, updatedAt: true }, }), + prisma.canvas.findMany({ + orderBy: { updatedAt: 'desc' }, + take: limit, + select: { id: true, title: true, emoji: true, updatedAt: true }, + }), ]); const combined: ActivityItem[] = [ ...diagrams.map((d) => toActivityItem(d, 'diagram')), ...notes.map((n) => toActivityItem(n, 'note')), + ...canvases.map((c) => toActivityItem(c, 'canvas')), ]; return combined @@ -251,7 +268,7 @@ export async function getStaleContent(daysThreshold = 30, limit = 3): Promise toStaleItem(d, 'diagram')), ...notes.map((n) => toStaleItem(n, 'note')), + ...canvases.map((c) => toStaleItem(c, 'canvas')), ]; return combined @@ -281,67 +305,100 @@ export async function getStaleContent(daysThreshold = 30, limit = 3): Promise { - // Fetch all IDs with minimal data for random sampling - const [diagrams, notes] = await Promise.all([ - prisma.diagram.findMany({ - select: { id: true, title: true, emoji: true, updatedAt: true }, - }), - prisma.note.findMany({ - select: { id: true, title: true, emoji: true, updatedAt: true }, - }), + // Use count + random skip to avoid loading all records + const [diagramCount, noteCount, canvasCount] = await Promise.all([ + prisma.diagram.count(), + prisma.note.count(), + prisma.canvas.count(), ]); - - const items: ActivityItem[] = [ - ...diagrams.map((d) => toActivityItem(d, 'diagram')), - ...notes.map((n) => toActivityItem(n, 'note')), - ]; - - // Fisher-Yates shuffle for unbiased random ordering - for (let i = items.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [items[i], items[j]] = [items[j], items[i]]; + const total = diagramCount + noteCount + canvasCount; + if (total === 0) return []; + + const items: ActivityItem[] = []; + const pickedIndices = new Set(); + const attempts = Math.min(limit, total); + + for (let i = 0; i < attempts && items.length < limit; i++) { + let idx: number; + do { + idx = Math.floor(Math.random() * total); + } while (pickedIndices.has(idx) && pickedIndices.size < total); + pickedIndices.add(idx); + + if (idx < diagramCount) { + const [item] = await prisma.diagram.findMany({ + skip: idx, take: 1, + select: { id: true, title: true, emoji: true, updatedAt: true }, + }); + if (item) items.push(toActivityItem(item, 'diagram')); + } else if (idx < diagramCount + noteCount) { + const [item] = await prisma.note.findMany({ + skip: idx - diagramCount, take: 1, + select: { id: true, title: true, emoji: true, updatedAt: true }, + }); + if (item) items.push(toActivityItem(item, 'note')); + } else { + const [item] = await prisma.canvas.findMany({ + skip: idx - diagramCount - noteCount, take: 1, + select: { id: true, title: true, emoji: true, updatedAt: true }, + }); + if (item) items.push(toActivityItem(item, 'canvas')); + } } - return items.slice(0, limit); + return items; } /** * Get knowledge base stats (depth metrics). */ export async function getKnowledgeStats(): Promise { + // Consolidated: 6 queries instead of 11 by combining count + min/max per model const [ totalCheckpoints, - diagramStats, - oldestDiagram, - newestDiagram, - oldestNote, - newestNote, - diagramCount, - noteCount, + diagramAgg, + noteAgg, + canvasAgg, ] = await Promise.all([ prisma.content.count(), - prisma.diagram.aggregate({ _avg: { totalVersions: true } }), - prisma.diagram.findFirst({ orderBy: { createdAt: 'asc' }, select: { createdAt: true } }), - prisma.diagram.findFirst({ orderBy: { createdAt: 'desc' }, select: { createdAt: true } }), - prisma.note.findFirst({ orderBy: { createdAt: 'asc' }, select: { createdAt: true } }), - prisma.note.findFirst({ orderBy: { createdAt: 'desc' }, select: { createdAt: true } }), - prisma.diagram.count(), - prisma.note.count(), + prisma.diagram.aggregate({ + _count: true, + _avg: { totalVersions: true }, + _min: { createdAt: true }, + _max: { createdAt: true }, + }), + prisma.note.aggregate({ + _count: true, + _min: { createdAt: true }, + _max: { createdAt: true }, + }), + prisma.canvas.aggregate({ + _count: true, + _min: { createdAt: true }, + _max: { createdAt: true }, + }), ]); - // Find the true oldest and newest across both diagrams and notes - const oldestDates = [oldestDiagram?.createdAt, oldestNote?.createdAt].filter(Boolean) as Date[]; - const newestDates = [newestDiagram?.createdAt, newestNote?.createdAt].filter(Boolean) as Date[]; + const oldestDates = [ + diagramAgg._min.createdAt, + noteAgg._min.createdAt, + canvasAgg._min.createdAt, + ].filter(Boolean) as Date[]; + const newestDates = [ + diagramAgg._max.createdAt, + noteAgg._max.createdAt, + canvasAgg._max.createdAt, + ].filter(Boolean) as Date[]; const oldest = oldestDates.length > 0 ? new Date(Math.min(...oldestDates.map(d => d.getTime()))) : null; const newest = newestDates.length > 0 ? new Date(Math.max(...newestDates.map(d => d.getTime()))) : null; return { totalCheckpoints, - avgVersionsPerDiagram: Math.round((diagramStats._avg.totalVersions || 0) * 10) / 10, + avgVersionsPerDiagram: Math.round((diagramAgg._avg.totalVersions || 0) * 10) / 10, oldestItemDate: oldest?.toISOString() || null, newestItemDate: newest?.toISOString() || null, - totalContentItems: diagramCount + noteCount, + totalContentItems: diagramAgg._count + noteAgg._count + canvasAgg._count, }; } diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index 459e825..f57d2e4 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -50,6 +50,4 @@ if (process.env.NODE_ENV !== 'test' && typeof window === 'undefined') { .catch((err) => console.error('Failed to init search indexes:', err)); } -if (process.env.NODE_ENV !== 'production') { - globalForPrisma.prisma = prisma; -} +globalForPrisma.prisma = prisma; diff --git a/src/lib/schemas.ts b/src/lib/schemas.ts index 8978b23..f32d978 100644 --- a/src/lib/schemas.ts +++ b/src/lib/schemas.ts @@ -48,3 +48,21 @@ export const backupDiagramSchema = z.object({ }); export const backupSchema = z.array(backupDiagramSchema); + +// Canvas API validation +export const canvasCreateSchema = z.object({ + title: z.string().max(200).optional(), + content: z.string().optional(), + preview: z.string().optional(), + emoji: z.string().max(10).optional(), + tags: z.array(z.string()).max(3).optional(), +}); + +export const canvasUpdateSchema = z.object({ + title: z.string().max(200).optional(), + content: z.string().optional(), + preview: z.string().optional(), + emoji: z.string().max(10).optional(), + isFavorite: z.boolean().optional(), + tags: z.array(z.string()).max(3).optional(), +}); diff --git a/src/lib/types.ts b/src/lib/types.ts index 6ff5277..c7706db 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -46,6 +46,25 @@ export interface Note { tags?: Tag[]; } +export interface Canvas { + id: string; + title: string; + content: string; + preview?: string | null; + emoji: string; + isFavorite: boolean; + createdAt: string; + updatedAt: string; + tags?: Tag[]; +} + +export interface CanvasPage { + items: Omit[]; + total: number; + hasMore: boolean; + nextOffset: number; +} + export type NoteSortOption = 'recent' | 'old' | 'alphabetical'; export interface NotePage {