From ba8c5fbcd39edca517eb4dd13c39a48ce9be72cb Mon Sep 17 00:00:00 2001 From: Jiang Feng Date: Sat, 26 Apr 2025 00:20:49 +0800 Subject: [PATCH] feat: add novel editor --- web/package.json | 13 +- web/pnpm-lock.yaml | 1322 ++++++++++++++++- web/src/app/editor/page.tsx | 11 + web/src/components/editor/content.ts | 356 +++++ web/src/components/editor/extensions.tsx | 180 +++ .../generative/ai-completion-command.tsx | 66 + .../generative/ai-selector-commands.tsx | 81 + .../editor/generative/ai-selector.tsx | 123 ++ .../generative/generative-menu-switch.tsx | 54 + web/src/components/editor/image-upload.ts | 60 + web/src/components/editor/index.tsx | 173 +++ .../editor/selectors/color-selector.tsx | 192 +++ .../editor/selectors/link-selector.tsx | 104 ++ .../editor/selectors/math-selector.tsx | 35 + .../editor/selectors/node-selector.tsx | 144 ++ .../editor/selectors/text-buttons.tsx | 73 + web/src/components/editor/slash-command.tsx | 219 +++ web/src/components/ui/command.tsx | 177 +++ web/src/components/ui/icons/magic.tsx | 32 + web/src/components/ui/popover.tsx | 48 + web/src/styles/globals.css | 21 + web/src/styles/prosemirror.css | 281 ++++ 22 files changed, 3760 insertions(+), 5 deletions(-) create mode 100644 web/src/app/editor/page.tsx create mode 100644 web/src/components/editor/content.ts create mode 100644 web/src/components/editor/extensions.tsx create mode 100644 web/src/components/editor/generative/ai-completion-command.tsx create mode 100644 web/src/components/editor/generative/ai-selector-commands.tsx create mode 100644 web/src/components/editor/generative/ai-selector.tsx create mode 100644 web/src/components/editor/generative/generative-menu-switch.tsx create mode 100644 web/src/components/editor/image-upload.ts create mode 100644 web/src/components/editor/index.tsx create mode 100644 web/src/components/editor/selectors/color-selector.tsx create mode 100644 web/src/components/editor/selectors/link-selector.tsx create mode 100644 web/src/components/editor/selectors/math-selector.tsx create mode 100644 web/src/components/editor/selectors/node-selector.tsx create mode 100644 web/src/components/editor/selectors/text-buttons.tsx create mode 100644 web/src/components/editor/slash-command.tsx create mode 100644 web/src/components/ui/command.tsx create mode 100644 web/src/components/ui/icons/magic.tsx create mode 100644 web/src/components/ui/popover.tsx create mode 100644 web/src/styles/prosemirror.css diff --git a/web/package.json b/web/package.json index 4a9fb99..c976280 100644 --- a/web/package.json +++ b/web/package.json @@ -17,8 +17,10 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@ai-sdk/react": "^1.2.9", "@ant-design/icons": "^6.0.0", "@hookform/resolvers": "^5.0.1", + "@nanostores/react": "github:ai/react", "@radix-ui/react-accordion": "^1.2.8", "@radix-ui/react-checkbox": "^1.2.3", "@radix-ui/react-collapsible": "^1.1.8", @@ -26,6 +28,7 @@ "@radix-ui/react-dropdown-menu": "^2.1.11", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.1.4", + "@radix-ui/react-popover": "^1.1.11", "@radix-ui/react-scroll-area": "^1.2.4", "@radix-ui/react-select": "^2.2.2", "@radix-ui/react-separator": "^1.1.4", @@ -34,18 +37,23 @@ "@radix-ui/react-tabs": "^1.1.4", "@radix-ui/react-tooltip": "^1.2.0", "@t3-oss/env-nextjs": "^0.11.0", + "@tailwindcss/typography": "^0.5.16", "best-effort-json-parser": "^1.1.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "framer-motion": "^12.6.5", "hast": "^1.0.0", + "highlight.js": "^11.11.1", "katex": "^0.16.21", + "lowlight": "^3.3.0", "lru-cache": "^11.1.0", "lucide-react": "^0.487.0", "motion": "^12.6.5", "nanoid": "^5.1.5", "next": "^15.2.3", "next-themes": "^0.4.6", + "novel": "^1.0.2", "react": "^19.0.0", "react-dom": "^19.0.0", "react-hook-form": "^7.56.1", @@ -54,9 +62,12 @@ "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", + "sonner": "^2.0.3", "tailwind-merge": "^3.2.0", + "tiptap-markdown": "^0.8.10", "tw-animate-css": "^1.2.5", "unist-util-visit": "^5.0.0", + "use-debounce": "^10.0.4", "use-stick-to-bottom": "^1.1.0", "zod": "^3.24.3", "zustand": "^5.0.3" @@ -88,4 +99,4 @@ "sharp" ] } -} +} \ No newline at end of file diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 8e530e9..019226f 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -8,12 +8,18 @@ importers: .: dependencies: + '@ai-sdk/react': + specifier: ^1.2.9 + version: 1.2.9(react@19.1.0)(zod@3.24.3) '@ant-design/icons': specifier: ^6.0.0 version: 6.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@hookform/resolvers': specifier: ^5.0.1 version: 5.0.1(react-hook-form@7.56.1(react@19.1.0)) + '@nanostores/react': + specifier: github:ai/react + version: git+https://git@github.com:ai/react.git#a6ad745cc307881916944ee4d6ba04bdda978dc6(nanostores@1.0.1)(react@19.1.0) '@radix-ui/react-accordion': specifier: ^1.2.8 version: 1.2.8(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -35,6 +41,9 @@ importers: '@radix-ui/react-label': specifier: ^2.1.4 version: 2.1.4(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-popover': + specifier: ^1.1.11 + version: 1.1.11(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-scroll-area': specifier: ^1.2.4 version: 1.2.4(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -59,6 +68,9 @@ importers: '@t3-oss/env-nextjs': specifier: ^0.11.0 version: 0.11.1(typescript@5.8.3)(zod@3.24.3) + '@tailwindcss/typography': + specifier: ^0.5.16 + version: 0.5.16(tailwindcss@4.1.4) best-effort-json-parser: specifier: ^1.1.3 version: 1.1.3 @@ -68,15 +80,24 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) framer-motion: specifier: ^12.6.5 version: 12.7.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) hast: specifier: ^1.0.0 version: 1.0.0 + highlight.js: + specifier: ^11.11.1 + version: 11.11.1 katex: specifier: ^0.16.21 version: 0.16.21 + lowlight: + specifier: ^3.3.0 + version: 3.3.0 lru-cache: specifier: ^11.1.0 version: 11.1.0 @@ -91,10 +112,13 @@ importers: version: 5.1.5 next: specifier: ^15.2.3 - version: 15.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + novel: + specifier: ^1.0.2 + version: 1.0.2(@tiptap/extension-code-block@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7))(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: specifier: ^19.0.0 version: 19.1.0 @@ -119,15 +143,24 @@ importers: remark-math: specifier: ^6.0.0 version: 6.0.0 + sonner: + specifier: ^2.0.3 + version: 2.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) tailwind-merge: specifier: ^3.2.0 version: 3.2.0 + tiptap-markdown: + specifier: ^0.8.10 + version: 0.8.10(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) tw-animate-css: specifier: ^1.2.5 version: 1.2.5 unist-util-visit: specifier: ^5.0.0 version: 5.0.0 + use-debounce: + specifier: ^10.0.4 + version: 10.0.4(react@19.1.0) use-stick-to-bottom: specifier: ^1.1.0 version: 1.1.0(react@19.1.0) @@ -136,7 +169,7 @@ importers: version: 3.24.3 zustand: specifier: ^5.0.3 - version: 5.0.3(@types/react@19.1.2)(react@19.1.0) + version: 5.0.3(@types/react@19.1.2)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)) devDependencies: '@eslint/eslintrc': specifier: ^3.3.1 @@ -189,6 +222,32 @@ importers: packages: + '@ai-sdk/provider-utils@2.2.7': + resolution: {integrity: sha512-kM0xS3GWg3aMChh9zfeM+80vEZfXzR3JEUBdycZLtbRZ2TRT8xOj3WodGHPb06sUK5yD7pAXC/P7ctsi2fvUGQ==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.23.8 + + '@ai-sdk/provider@1.1.3': + resolution: {integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==} + engines: {node: '>=18'} + + '@ai-sdk/react@1.2.9': + resolution: {integrity: sha512-/VYm8xifyngaqFDLXACk/1czDRCefNCdALUyp+kIX6DUIYUWTM93ISoZ+qJ8+3E+FiJAKBQz61o8lIIl+vYtzg==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.23.8 + peerDependenciesMeta: + zod: + optional: true + + '@ai-sdk/ui-utils@1.2.8': + resolution: {integrity: sha512-nls/IJCY+ks3Uj6G/agNhXqQeLVqhNfoJbuNgCny+nX2veY5ADB91EcZUqVeQ/ionul2SeUswPY6Q/DxteY29Q==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.23.8 + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -214,6 +273,24 @@ packages: resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} engines: {node: '>=6.9.0'} + '@cfcs/core@0.0.6': + resolution: {integrity: sha512-FxfJMwoLB8MEMConeXUCqtMGqxdtePQxRBOiGip9ULcYYam3WfCgoY6xdnMaSkYvRvmosp5iuG+TiPofm65+Pw==} + + '@daybrush/utils@1.13.0': + resolution: {integrity: sha512-ALK12C6SQNNHw1enXK+UO8bdyQ+jaWNQ1Af7Z3FNxeAwjYhQT7do+TRE4RASAJ3ObaS2+TJ7TXR3oz2Gzbw0PQ==} + + '@egjs/agent@2.4.4': + resolution: {integrity: sha512-cvAPSlUILhBBOakn2krdPnOGv5hAZq92f1YHxYcfu0p7uarix2C6Ia3AVizpS1SGRZGiEkIS5E+IVTLg1I2Iog==} + + '@egjs/children-differ@1.0.1': + resolution: {integrity: sha512-DRvyqMf+CPCOzAopQKHtW+X8iN6Hy6SFol+/7zCUiE5y4P/OB8JP8FtU4NxtZwtafvSL4faD5KoQYPj3JHzPFQ==} + + '@egjs/component@3.0.5': + resolution: {integrity: sha512-cLcGizTrrUNA2EYE3MBmEDt2tQv1joVP1Q3oDisZ5nw0MZDx2kcgEXM+/kZpfa/PAkFvYVhRUZwytIQWoN3V/w==} + + '@egjs/list-differ@1.0.1': + resolution: {integrity: sha512-OTFTDQcWS+1ZREOdCWuk5hCBgYO4OsD30lXcOCyVOAjXMhgL5rBRDnt/otb6Nz8CzU0L/igdcaQBDLWc4t9gvg==} + '@emnapi/core@1.4.1': resolution: {integrity: sha512-4JFstCTaToCFrPqrGzgkF8N2NHjtsaY4uRh6brZQ5L9e4wbMieX8oDT8N7qfVFTQecHFEtkj4ve49VIZ3mKVqw==} @@ -449,6 +526,14 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@nanostores/react@git+https://git@github.com:ai/react.git#a6ad745cc307881916944ee4d6ba04bdda978dc6': + resolution: {commit: a6ad745cc307881916944ee4d6ba04bdda978dc6, repo: git@github.com:ai/react.git, type: git} + version: 1.0.0 + engines: {node: ^20.0.0 || >=22.0.0} + peerDependencies: + nanostores: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^1.0.0 + react: '>=18.0.0' + '@napi-rs/wasm-runtime@0.2.9': resolution: {integrity: sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==} @@ -526,6 +611,13 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -764,6 +856,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-popover@1.1.11': + resolution: {integrity: sha512-yFMfZkVA5G3GJnBgb2PxrrcLKm1ZLWXrbYVgdyTl//0TYEIHS9LJbnyz7WWcZ0qCq7hIlJZpRtxeSeIG5T5oJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popper@1.2.3': resolution: {integrity: sha512-iNb9LYUMkne9zIahukgQmHlSBp9XWGeQQ7FvUGNk45ywzOb6kQa+Ca38OphXlWDiKvyneo9S+KSJsLfLt8812A==} peerDependencies: @@ -1097,12 +1202,24 @@ packages: react: '>=18.0.0' react-dom: '>=18.0.0' + '@remirror/core-constants@3.0.0': + resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==} + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} '@rushstack/eslint-patch@1.11.0': resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==} + '@scena/dragscroll@1.4.0': + resolution: {integrity: sha512-3O8daaZD9VXA9CP3dra6xcgt/qrm0mg0xJCwiX6druCteQ9FFsXffkF8PrqxY4Z4VJ58fFKEa0RlKqbsi/XnRA==} + + '@scena/event-emitter@1.0.5': + resolution: {integrity: sha512-AzY4OTb0+7ynefmWFQ6hxDdk0CySAq/D4efljfhtRHCOP7MBF9zUfhKG3TJiroVjASqVgkRJFdenS8ArZo6Olg==} + + '@scena/matrix@1.1.1': + resolution: {integrity: sha512-JVKBhN0tm2Srl+Yt+Ywqu0oLgLcdemDQlD1OxmN9jaCTwaFPZ7tY8n6dhVgMEaR9qcR7r+kAlMXnSfNyYdE+Vg==} + '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} @@ -1222,6 +1339,212 @@ packages: '@tailwindcss/postcss@4.1.4': resolution: {integrity: sha512-bjV6sqycCEa+AQSt2Kr7wpGF1bOZJ5wsqnLEkqSbM/JEHxx/yhMH8wHmdkPyApF9xhHeMSwnnkDUUMMM/hYnXw==} + '@tailwindcss/typography@0.5.16': + resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + + '@tiptap/core@2.11.7': + resolution: {integrity: sha512-zN+NFFxLsxNEL8Qioc+DL6b8+Tt2bmRbXH22Gk6F6nD30x83eaUSFlSv3wqvgyCq3I1i1NO394So+Agmayx6rQ==} + peerDependencies: + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-blockquote@2.11.7': + resolution: {integrity: sha512-liD8kWowl3CcYCG9JQlVx1eSNc/aHlt6JpVsuWvzq6J8APWX693i3+zFqyK2eCDn0k+vW62muhSBe3u09hA3Zw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-bold@2.11.7': + resolution: {integrity: sha512-VTR3JlldBixXbjpLTFme/Bxf1xeUgZZY3LTlt5JDlCW3CxO7k05CIa+kEZ8LXpog5annytZDUVtWqxrNjmsuHQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-bubble-menu@2.11.7': + resolution: {integrity: sha512-0vYqSUSSap3kk3/VT4tFE1/6StX70I3/NKQ4J68ZSFgkgyB3ZVlYv7/dY3AkEukjsEp3yN7m8Gw8ei2eEwyzwg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-bullet-list@2.11.7': + resolution: {integrity: sha512-WbPogE2/Q3e3/QYgbT1Sj4KQUfGAJNc5pvb7GrUbvRQsAh7HhtuO8hqdDwH8dEdD/cNUehgt17TO7u8qV6qeBw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-character-count@2.11.7': + resolution: {integrity: sha512-gcVbKou+uxzg8N0BBKceLwtpWvN8g2TIjTuCdyAcAPukX63DqVWOkofFHn1RqZbstJmtF4pTGZs9OH/GJrp27Q==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-code-block-lowlight@2.11.7': + resolution: {integrity: sha512-+eUMxvDgoYmAvkuJ2ljV2COyeH6HwH8LqCNWma+mFZCRDAoXNeqSHbBtI0Vzy4PqchfmxcmKERc99xEzoS9XUQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/extension-code-block': ^2.7.0 + '@tiptap/pm': ^2.7.0 + highlight.js: ^11 + lowlight: ^2 || ^3 + + '@tiptap/extension-code-block@2.11.7': + resolution: {integrity: sha512-To/y/2H04VWqiANy53aXjV7S6fA86c2759RsH1hTIe57jA1KyE7I5tlAofljOLZK/covkGmPeBddSPHGJbz++Q==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-code@2.11.7': + resolution: {integrity: sha512-VpPO1Uy/eF4hYOpohS/yMOcE1C07xmMj0/D989D9aS1x95jWwUVrSkwC+PlWMUBx9PbY2NRsg1ZDwVvlNKZ6yQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-color@2.11.7': + resolution: {integrity: sha512-2CWb0Qnh8Crf9OwnnWB+M1QJtWrbn6IMSwuOzk+tSzdWSazjN8h6XAZVemr0qMdAA/SyUigzorStiPxN6o3/vQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/extension-text-style': ^2.7.0 + + '@tiptap/extension-document@2.11.7': + resolution: {integrity: sha512-95ouJXPjdAm9+VBRgFo4lhDoMcHovyl/awORDI8gyEn0Rdglt+ZRZYoySFzbVzer9h0cre+QdIwr9AIzFFbfdA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-dropcursor@2.11.7': + resolution: {integrity: sha512-63mL+nxQILizsr5NbmgDeOjFEWi34BLt7evwL6UUZEVM15K8V1G8pD9Y0kCXrZYpHWz0tqFRXdrhDz0Ppu8oVw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-floating-menu@2.11.7': + resolution: {integrity: sha512-DG54WoUu2vxHRVzKZiR5I5RMOYj45IlxQMkBAx1wjS0ch41W8DUYEeipvMMjCeKtEI+emz03xYUcOAP9LRmg+w==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-gapcursor@2.11.7': + resolution: {integrity: sha512-EceesmPG7FyjXZ8EgeJPUov9G1mAf2AwdypxBNH275g6xd5dmU/KvjoFZjmQ0X1ve7mS+wNupVlGxAEUYoveew==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-hard-break@2.11.7': + resolution: {integrity: sha512-zTkZSA6q+F5sLOdCkiC2+RqJQN0zdsJqvFIOVFL/IDVOnq6PZO5THzwRRLvOSnJJl3edRQCl/hUgS0L5sTInGQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-heading@2.11.7': + resolution: {integrity: sha512-8kWh7y4Rd2fwxfWOhFFWncHdkDkMC1Z60yzIZWjIu72+6yQxvo8w3yeb7LI7jER4kffbMmadgcfhCHC/fkObBA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-highlight@2.11.7': + resolution: {integrity: sha512-c/NH4kIpNOWCUQv8RkFNDyOcgt+2pYFpDf0QBJmzhAuv4BIeS2bDmDtuNS7VgoWRZH+xxCNXfvm2BG+kjtipEg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-history@2.11.7': + resolution: {integrity: sha512-Cu5x3aS13I040QSRoLdd+w09G4OCVfU+azpUqxufZxeNs9BIJC+0jowPLeOxKDh6D5GGT2A8sQtxc6a/ssbs8g==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-horizontal-rule@2.11.7': + resolution: {integrity: sha512-uVmQwD2dzZ5xwmvUlciy0ItxOdOfQjH6VLmu80zyJf8Yu7mvwP8JyxoXUX0vd1xHpwAhgQ9/ozjIWYGIw79DPQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-image@2.11.7': + resolution: {integrity: sha512-YvCmTDB7Oo+A56tR4S/gcNaYpqU4DDlSQcRp5IQvmQV5EekSe0lnEazGDoqOCwsit9qQhj4MPQJhKrnaWrJUrg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-italic@2.11.7': + resolution: {integrity: sha512-r985bkQfG0HMpmCU0X0p/Xe7U1qgRm2mxvcp6iPCuts2FqxaCoyfNZ8YnMsgVK1mRhM7+CQ5SEg2NOmQNtHvPw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-link@2.11.7': + resolution: {integrity: sha512-qKIowE73aAUrnQCIifYP34xXOHOsZw46cT/LBDlb0T60knVfQoKVE4ku08fJzAV+s6zqgsaaZ4HVOXkQYLoW7g==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-list-item@2.11.7': + resolution: {integrity: sha512-6ikh7Y+qAbkSuIHXPIINqfzmWs5uIGrylihdZ9adaIyvrN1KSnWIqrZIk/NcZTg5YFIJlXrnGSRSjb/QM3WUhw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-ordered-list@2.11.7': + resolution: {integrity: sha512-bLGCHDMB0vbJk7uu8bRg8vES3GsvxkX7Cgjgm/6xysHFbK98y0asDtNxkW1VvuRreNGz4tyB6vkcVCfrxl4jKw==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-paragraph@2.11.7': + resolution: {integrity: sha512-Pl3B4q6DJqTvvAdraqZaNP9Hh0UWEHL5nNdxhaRNuhKaUo7lq8wbDSIxIW3lvV0lyCs0NfyunkUvSm1CXb6d4Q==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-placeholder@2.11.7': + resolution: {integrity: sha512-/06zXV4HIjYoiaUq1fVJo/RcU8pHbzx21evOpeG/foCfNpMI4xLU/vnxdUi6/SQqpZMY0eFutDqod1InkSOqsg==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-strike@2.11.7': + resolution: {integrity: sha512-D6GYiW9F24bvAY7XMOARNZbC8YGPzdzWdXd8VOOJABhf4ynMi/oW4NNiko+kZ67jn3EGaKoz32VMJzNQgYi1HA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-task-item@2.11.7': + resolution: {integrity: sha512-m+UyE85nnqhQ4epLMYqdwaQj6DoqGGUNE0gyJOtJB1qhBi7GM7yPEDoiX82ByaQetWjoZIduRuQSRfgkD0MEeA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + + '@tiptap/extension-task-list@2.11.7': + resolution: {integrity: sha512-rgpkLvKxeSWibMpZazR5PkISSwz90Wnpe/KqIWLu/s3UuRE0Sc5kA8ZOva4ZAvcpSWEJ1cNn1OqllwHsj0NxwQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text-style@2.11.7': + resolution: {integrity: sha512-LHO6DBg/9SkCQFdWlVfw9nolUmw+Cid94WkTY+7IwrpyG2+ZGQxnKpCJCKyeaFNbDoYAtvu0vuTsSXeCkgShcA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-text@2.11.7': + resolution: {integrity: sha512-wObCn8qZkIFnXTLvBP+X8KgaEvTap/FJ/i4hBMfHBCKPGDx99KiJU6VIbDXG8d5ZcFZE0tOetK1pP5oI7qgMlQ==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-underline@2.11.7': + resolution: {integrity: sha512-NtoQw6PGijOAtXC6G+0Aq0/Z5wwEjPhNHs8nsjXogfWIgaj/aI4/zfBnA06eI3WT+emMYQTl0fTc4CUPnLVU8g==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/extension-youtube@2.11.7': + resolution: {integrity: sha512-Cf8rziGNIVdsQXGO6nIYcpG5wP6RO+7VkpLZcVYo2I2KvEaXv+3Nb3mG4X+F7AClPfCEIE4G2EIVCsh3DCZ96A==} + peerDependencies: + '@tiptap/core': ^2.7.0 + + '@tiptap/pm@2.11.7': + resolution: {integrity: sha512-7gEEfz2Q6bYKXM07vzLUD0vqXFhC5geWRA6LCozTiLdVFDdHWiBrvb2rtkL5T7mfLq03zc1QhH7rI3F6VntOEA==} + + '@tiptap/react@2.11.7': + resolution: {integrity: sha512-gQZEUkAoPsBptnB4T2gAtiUxswjVGhfsM9vOElQco+b11DYmy110T2Zuhg+2YGvB/CG3RoWJx34808P0FX1ijA==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tiptap/starter-kit@2.11.7': + resolution: {integrity: sha512-K+q51KwNU/l0kqRuV5e1824yOLVftj6kGplGQLvJG56P7Rb2dPbM/JeaDbxQhnHT/KDGamG0s0Po0M3pPY163A==} + + '@tiptap/suggestion@2.11.7': + resolution: {integrity: sha512-I1ckVAEErpErPn/H9ZdDmTb5zuPNPiKj3krxCtJDUU4+3we0cgJY9NQFXl9//mrug3UIngH0ZQO+arbZfIk75A==} + peerDependencies: + '@tiptap/core': ^2.7.0 + '@tiptap/pm': ^2.7.0 + '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} @@ -1255,15 +1578,36 @@ packages: '@types/katex@0.16.7': resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} + '@types/linkify-it@3.0.5': + resolution: {integrity: sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@13.0.9': + resolution: {integrity: sha512-1XPwR0+MgXLWfTn9gCsZ55AHOKW1WN+P9vr0PaQh5aerR9LLQXUbjfEAFhjmEmyoYFWAyuN2Mqkn40MZ4ukjBw==} + + '@types/markdown-it@14.1.2': + resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdurl@1.0.5': + resolution: {integrity: sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} '@types/node@20.17.30': resolution: {integrity: sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==} + '@types/node@22.15.2': + resolution: {integrity: sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==} + '@types/react-dom@19.1.1': resolution: {integrity: sha512-jFf/woGTVTjUJsl2O7hcopJ1r0upqoq/vIOoCj0yLh3RIXxWcljlpuZ+vEBRXsymD1jhfeJrlyTy/S1UW+4y1w==} peerDependencies: @@ -1281,6 +1625,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@typescript-eslint/eslint-plugin@8.30.1': resolution: {integrity: sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1667,6 +2014,12 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1697,10 +2050,24 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-styled@1.0.8: + resolution: {integrity: sha512-tCpP7kLRI8dI95rCh3Syl7I+v7PP+2JYOzWkl0bUEoSbJM+u8ITbutjlQVf0NC2/g4ULROJPi16sfwDIO8/84g==} + + css-to-mat@1.1.1: + resolution: {integrity: sha512-kvpxFYZb27jRd2vium35G7q5XZ2WJ9rWjDUMNT36M3Hc41qCrLXFM5iEKMGXcrPsKfXEN+8l/riB4QzwwwiEyQ==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -2046,6 +2413,9 @@ packages: react-dom: optional: true + framework-utils@1.1.0: + resolution: {integrity: sha512-KAfqli5PwpFJ8o3psRNs8svpMGyCSAe8nmGcjQ0zZBWN2H6dZDnq+ABp3N3hdUmFeMrLtjOCTXD4yplUJIWceg==} + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -2056,6 +2426,9 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + gesto@1.19.4: + resolution: {integrity: sha512-hfr/0dWwh0Bnbb88s3QVJd1ZRJeOWcgHPPwmiH6NnafDYvhTsxg+SLYu+q/oPNh9JS3V+nlr6fNs8kvPAtcRDQ==} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2174,6 +2547,10 @@ packages: highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} + highlightjs-vue@1.0.0: resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} @@ -2347,6 +2724,18 @@ packages: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true + jotai@2.12.3: + resolution: {integrity: sha512-DpoddSkmPGXMFtdfnoIHfueFeGP643nqYUWC6REjUcME+PG2UkAtYnLbffRDw3OURI9ZUTcRWkRGLsOvxuWMCg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=17.0.0' + react: '>=17.0.0' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2366,6 +2755,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -2386,6 +2778,12 @@ packages: resolution: {integrity: sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==} hasBin: true + keycode@2.2.1: + resolution: {integrity: sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==} + + keycon@1.4.0: + resolution: {integrity: sha512-p1NAIxiRMH3jYfTeXRs2uWbVJ1WpEjpi8ktzUyBJsX7/wn2qu2VRXktneBLNtKNxJmlUYxRi9gOJt1DuthXR7A==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2468,6 +2866,12 @@ packages: resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==} engines: {node: '>= 12.0.0'} + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + linkifyjs@4.2.0: + resolution: {integrity: sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==} + loader-runner@4.3.0: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} engines: {node: '>=6.11.5'} @@ -2480,6 +2884,12 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.castarray@4.4.0: + resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -2493,6 +2903,9 @@ packages: lowlight@1.20.0: resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} + lowlight@3.3.0: + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} + lru-cache@11.1.0: resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} engines: {node: 20 || >=22} @@ -2502,6 +2915,13 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + markdown-it-task-lists@2.1.1: + resolution: {integrity: sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==} + + markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -2557,6 +2977,9 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -2706,6 +3129,10 @@ packages: engines: {node: ^18 || >=20} hasBin: true + nanostores@1.0.1: + resolution: {integrity: sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw==} + engines: {node: ^20.0.0 || >=22.0.0} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -2742,6 +3169,11 @@ packages: node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + novel@1.0.2: + resolution: {integrity: sha512-lyMtoBsRCqgrQaNhlc8Ngpp+npJEQjPoGBLcnYlEr8mEf+lXZV7/m6CbEpGRfma+HZQVlU3YJOs4gCmzbLG+ow==} + peerDependencies: + react: '>=18' + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2778,6 +3210,12 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + orderedmap@2.1.1: + resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + + overlap-area@1.1.0: + resolution: {integrity: sha512-3dlJgJCaVeXH0/eZjYVJvQiLVVrPO4U1ZGqlATtx6QGO3b5eNM6+JgUKa7oStBTdYuGTk7gVoABCW6Tp+dhRdw==} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -2829,6 +3267,10 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -2918,6 +3360,68 @@ packages: property-information@7.0.0: resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} + prosemirror-changeset@2.2.1: + resolution: {integrity: sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==} + + prosemirror-collab@1.3.1: + resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==} + + prosemirror-commands@1.7.1: + resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} + + prosemirror-dropcursor@1.8.2: + resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==} + + prosemirror-gapcursor@1.3.2: + resolution: {integrity: sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==} + + prosemirror-history@1.4.1: + resolution: {integrity: sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==} + + prosemirror-inputrules@1.5.0: + resolution: {integrity: sha512-K0xJRCmt+uSw7xesnHmcn72yBGTbY45vm8gXI4LZXbx2Z0jwh5aF9xrGQgrVPu0WbyFVFF3E/o9VhJYz6SQWnA==} + + prosemirror-keymap@1.2.2: + resolution: {integrity: sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==} + + prosemirror-markdown@1.13.2: + resolution: {integrity: sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==} + + prosemirror-menu@1.2.5: + resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==} + + prosemirror-model@1.25.1: + resolution: {integrity: sha512-AUvbm7qqmpZa5d9fPKMvH1Q5bqYQvAZWOGRvxsB6iFLyycvC9MwNemNVjHVrWgjaoxAfY8XVg7DbvQ/qxvI9Eg==} + + prosemirror-schema-basic@1.2.4: + resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==} + + prosemirror-schema-list@1.5.1: + resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} + + prosemirror-state@1.4.3: + resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==} + + prosemirror-tables@1.7.1: + resolution: {integrity: sha512-eRQ97Bf+i9Eby99QbyAiyov43iOKgWa7QCGly+lrDt7efZ1v8NWolhXiB43hSDGIXT1UXgbs4KJN3a06FGpr1Q==} + + prosemirror-trailing-node@3.0.0: + resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==} + peerDependencies: + prosemirror-model: ^1.22.1 + prosemirror-state: ^1.4.2 + prosemirror-view: ^1.33.8 + + prosemirror-transform@1.10.4: + resolution: {integrity: sha512-pwDy22nAnGqNR1feOQKHxoFkkUtepoFAd3r2hbEDsnf4wp57kKA36hXsB3njA9FtONBEwSDnDeCiJe+ItD+ykw==} + + prosemirror-view@1.39.2: + resolution: {integrity: sha512-BmOkml0QWNob165gyUxXi5K5CVUgVPpqMEAAml/qzgKn9boLUWVPzQ6LtzXw8Cn1GtRQX4ELumPxqtLTDaAKtg==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2934,6 +3438,9 @@ packages: peerDependencies: webpack: ^4.0.0 || ^5.0.0 + react-css-styled@1.1.9: + resolution: {integrity: sha512-M7fJZ3IWFaIHcZEkoFOnkjdiUFmwd8d+gTh2bpqMOcnxy/0Gsykw4dsL4QBiKsxcGow6tETUa4NAUcmJF+/nfw==} + react-dom@19.1.0: resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: @@ -2957,6 +3464,15 @@ packages: '@types/react': '>=18' react: '>=18' + react-markdown@9.1.0: + resolution: {integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + + react-moveable@0.56.0: + resolution: {integrity: sha512-FmJNmIOsOA36mdxbrc/huiE4wuXSRlmon/o+/OrfNhSiYYYL0AV5oObtPluEhb2Yr/7EfYWBHTxF5aWAvjg1SA==} + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -2977,6 +3493,9 @@ packages: '@types/react': optional: true + react-selecto@1.26.3: + resolution: {integrity: sha512-Ubik7kWSnZyQEBNro+1k38hZaI1tJarE+5aD/qsqCOA1uUBSjgKVBy3EWRzGIbdmVex7DcxznFZLec/6KZNvwQ==} + react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -2992,6 +3511,12 @@ packages: peerDependencies: react: '>= 0.14.0' + react-tweet@3.2.2: + resolution: {integrity: sha512-hIkxAVPpN2RqWoDEbo3TTnN/pDcp9/Jb6pTgiA4EbXa9S+m2vHIvvZKHR+eS0PDIsYqe+zTmANRa5k6+/iwGog==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + react@19.1.0: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} @@ -3052,6 +3577,9 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rope-sequence@1.3.4: + resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -3081,6 +3609,12 @@ packages: resolution: {integrity: sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==} engines: {node: '>= 10.13.0'} + secure-json-parse@2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + + selecto@1.26.3: + resolution: {integrity: sha512-gZHgqMy5uyB6/2YDjv3Qqaf7bd2hTDOpPdxXlrez4R3/L0GiEWDCFaUfrflomgqdb3SxHF2IXY0Jw0EamZi7cw==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -3136,6 +3670,12 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + sonner@2.0.3: + resolution: {integrity: sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -3225,6 +3765,11 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + swr@2.3.3: + resolution: {integrity: sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + tailwind-merge@3.2.0: resolution: {integrity: sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==} @@ -3256,10 +3801,25 @@ packages: engines: {node: '>=10'} hasBin: true + throttleit@2.1.0: + resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} + engines: {node: '>=18'} + tinyglobby@0.2.12: resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} engines: {node: '>=12.0.0'} + tippy.js@6.3.7: + resolution: {integrity: sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==} + + tiptap-extension-global-drag-handle@0.1.18: + resolution: {integrity: sha512-jwFuy1K8DP3a4bFy76Hpc63w1Sil0B7uZ3mvhQomVvUFCU787Lg2FowNhn7NFzeyok761qY2VG+PZ/FDthWUdg==} + + tiptap-markdown@0.8.10: + resolution: {integrity: sha512-iDVkR2BjAqkTDtFX0h94yVvE2AihCXlF0Q7RIXSJPRSR5I0PA1TMuAg6FHFpmqTn4tPxJ0by0CK7PUMlnFLGEQ==} + peerDependencies: + '@tiptap/core': ^2.0.3 + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3282,6 +3842,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tunnel-rat@0.1.2: + resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==} + tw-animate-css@1.2.5: resolution: {integrity: sha512-ABzjfgVo+fDbhRREGL4KQZUqqdPgvc5zVrLyeW9/6mVqvaDepXc7EvedA+pYmMnIOsUAQMwcWzNvom26J2qYvQ==} @@ -3317,6 +3880,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -3324,6 +3890,9 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -3370,6 +3939,12 @@ packages: '@types/react': optional: true + use-debounce@10.0.4: + resolution: {integrity: sha512-6Cf7Yr7Wk7Kdv77nnJMf6de4HuDE4dTxKij+RqE9rufDsI6zsbjyAxcH5y2ueJCQAnfgKbzXbZHYlkFwmBlWkw==} + engines: {node: '>= 16.0.0'} + peerDependencies: + react: '*' + use-sidecar@1.1.3: resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} engines: {node: '>=10'} @@ -3385,6 +3960,14 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + use-sync-external-store@1.5.0: + resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -3394,6 +3977,9 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + watchpack@2.4.2: resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} engines: {node: '>=10.13.0'} @@ -3448,9 +4034,29 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod-to-json-schema@3.24.5: + resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} + peerDependencies: + zod: ^3.24.1 + zod@3.24.3: resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==} + zustand@4.5.6: + resolution: {integrity: sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + zustand@5.0.3: resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} engines: {node: '>=12.20.0'} @@ -3474,6 +4080,34 @@ packages: snapshots: + '@ai-sdk/provider-utils@2.2.7(zod@3.24.3)': + dependencies: + '@ai-sdk/provider': 1.1.3 + nanoid: 3.3.11 + secure-json-parse: 2.7.0 + zod: 3.24.3 + + '@ai-sdk/provider@1.1.3': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@1.2.9(react@19.1.0)(zod@3.24.3)': + dependencies: + '@ai-sdk/provider-utils': 2.2.7(zod@3.24.3) + '@ai-sdk/ui-utils': 1.2.8(zod@3.24.3) + react: 19.1.0 + swr: 2.3.3(react@19.1.0) + throttleit: 2.1.0 + optionalDependencies: + zod: 3.24.3 + + '@ai-sdk/ui-utils@1.2.8(zod@3.24.3)': + dependencies: + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.7(zod@3.24.3) + zod: 3.24.3 + zod-to-json-schema: 3.24.5(zod@3.24.3) + '@alloc/quick-lru@5.2.0': {} '@ant-design/colors@8.0.0': @@ -3497,6 +4131,22 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@cfcs/core@0.0.6': + dependencies: + '@egjs/component': 3.0.5 + + '@daybrush/utils@1.13.0': {} + + '@egjs/agent@2.4.4': {} + + '@egjs/children-differ@1.0.1': + dependencies: + '@egjs/list-differ': 1.0.1 + + '@egjs/component@3.0.5': {} + + '@egjs/list-differ@1.0.1': {} + '@emnapi/core@1.4.1': dependencies: '@emnapi/wasi-threads': 1.0.1 @@ -3696,6 +4346,11 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@nanostores/react@git+https://git@github.com:ai/react.git#a6ad745cc307881916944ee4d6ba04bdda978dc6(nanostores@1.0.1)(react@19.1.0)': + dependencies: + nanostores: 1.0.1 + react: 19.1.0 + '@napi-rs/wasm-runtime@0.2.9': dependencies: '@emnapi/core': 1.4.1 @@ -3747,6 +4402,11 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@opentelemetry/api@1.9.0': + optional: true + + '@popperjs/core@2.11.8': {} + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.2': {} @@ -3986,6 +4646,29 @@ snapshots: '@types/react': 19.1.2 '@types/react-dom': 19.1.1(@types/react@19.1.2) + '@radix-ui/react-popover@1.1.11(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.7(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.4(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-popper': 1.2.4(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-portal': 1.1.6(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.0(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0) + aria-hidden: 1.2.4 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.6.3(@types/react@19.1.2)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.2 + '@types/react-dom': 19.1.1(@types/react@19.1.2) + '@radix-ui/react-popper@1.2.3(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@floating-ui/react-dom': 2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -4314,10 +4997,25 @@ snapshots: react-dom: 19.1.0(react@19.1.0) react-is: 18.3.1 + '@remirror/core-constants@3.0.0': {} + '@rtsao/scc@1.1.0': {} '@rushstack/eslint-patch@1.11.0': {} + '@scena/dragscroll@1.4.0': + dependencies: + '@daybrush/utils': 1.13.0 + '@scena/event-emitter': 1.0.5 + + '@scena/event-emitter@1.0.5': + dependencies: + '@daybrush/utils': 1.13.0 + + '@scena/matrix@1.1.1': + dependencies: + '@daybrush/utils': 1.13.0 + '@standard-schema/utils@0.3.0': {} '@swc/counter@0.1.3': {} @@ -4405,6 +5103,227 @@ snapshots: postcss: 8.5.3 tailwindcss: 4.1.4 + '@tailwindcss/typography@0.5.16(tailwindcss@4.1.4)': + dependencies: + lodash.castarray: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + postcss-selector-parser: 6.0.10 + tailwindcss: 4.1.4 + + '@tiptap/core@2.11.7(@tiptap/pm@2.11.7)': + dependencies: + '@tiptap/pm': 2.11.7 + + '@tiptap/extension-blockquote@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-bold@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-bubble-menu@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/pm': 2.11.7 + tippy.js: 6.3.7 + + '@tiptap/extension-bullet-list@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-character-count@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/pm': 2.11.7 + + '@tiptap/extension-code-block-lowlight@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/extension-code-block@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(highlight.js@11.11.1)(lowlight@3.3.0)': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/extension-code-block': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@tiptap/pm': 2.11.7 + highlight.js: 11.11.1 + lowlight: 3.3.0 + + '@tiptap/extension-code-block@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/pm': 2.11.7 + + '@tiptap/extension-code@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-color@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/extension-text-style@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/extension-text-style': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + + '@tiptap/extension-document@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-dropcursor@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/pm': 2.11.7 + + '@tiptap/extension-floating-menu@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/pm': 2.11.7 + tippy.js: 6.3.7 + + '@tiptap/extension-gapcursor@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/pm': 2.11.7 + + '@tiptap/extension-hard-break@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-heading@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-highlight@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-history@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/pm': 2.11.7 + + '@tiptap/extension-horizontal-rule@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/pm': 2.11.7 + + '@tiptap/extension-image@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-italic@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-link@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/pm': 2.11.7 + linkifyjs: 4.2.0 + + '@tiptap/extension-list-item@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-ordered-list@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-paragraph@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-placeholder@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/pm': 2.11.7 + + '@tiptap/extension-strike@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-task-item@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/pm': 2.11.7 + + '@tiptap/extension-task-list@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-text-style@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-text@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-underline@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/extension-youtube@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + + '@tiptap/pm@2.11.7': + dependencies: + prosemirror-changeset: 2.2.1 + prosemirror-collab: 1.3.1 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.2 + prosemirror-gapcursor: 1.3.2 + prosemirror-history: 1.4.1 + prosemirror-inputrules: 1.5.0 + prosemirror-keymap: 1.2.2 + prosemirror-markdown: 1.13.2 + prosemirror-menu: 1.2.5 + prosemirror-model: 1.25.1 + prosemirror-schema-basic: 1.2.4 + prosemirror-schema-list: 1.5.1 + prosemirror-state: 1.4.3 + prosemirror-tables: 1.7.1 + prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.2) + prosemirror-transform: 1.10.4 + prosemirror-view: 1.39.2 + + '@tiptap/react@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/extension-bubble-menu': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@tiptap/extension-floating-menu': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@tiptap/pm': 2.11.7 + '@types/use-sync-external-store': 0.0.6 + fast-deep-equal: 3.1.3 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + use-sync-external-store: 1.5.0(react@19.1.0) + + '@tiptap/starter-kit@2.11.7': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/extension-blockquote': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-bold': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-bullet-list': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-code': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-code-block': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@tiptap/extension-document': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-dropcursor': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@tiptap/extension-gapcursor': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@tiptap/extension-hard-break': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-heading': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-history': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@tiptap/extension-horizontal-rule': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@tiptap/extension-italic': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-list-item': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-ordered-list': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-paragraph': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-strike': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-text': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-text-style': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/pm': 2.11.7 + + '@tiptap/suggestion@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)': + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/pm': 2.11.7 + '@tybys/wasm-util@0.9.0': dependencies: tslib: 2.8.1 @@ -4444,16 +5363,38 @@ snapshots: '@types/katex@0.16.7': {} + '@types/linkify-it@3.0.5': {} + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@13.0.9': + dependencies: + '@types/linkify-it': 3.0.5 + '@types/mdurl': 1.0.5 + + '@types/markdown-it@14.1.2': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 + '@types/mdurl@1.0.5': {} + + '@types/mdurl@2.0.0': {} + '@types/ms@2.1.0': {} '@types/node@20.17.30': dependencies: undici-types: 6.19.8 + '@types/node@22.15.2': + dependencies: + undici-types: 6.21.0 + '@types/react-dom@19.1.1(@types/react@19.1.2)': dependencies: '@types/react': 19.1.2 @@ -4470,6 +5411,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/use-sync-external-store@0.0.6': {} + '@typescript-eslint/eslint-plugin@8.30.1(@typescript-eslint/parser@8.30.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -4889,6 +5832,18 @@ snapshots: clsx@2.1.1: {} + cmdk@1.1.1(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-dialog': 1.1.10(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -4917,12 +5872,25 @@ snapshots: concat-map@0.0.1: {} + crelt@1.0.6: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + css-styled@1.0.8: + dependencies: + '@daybrush/utils': 1.13.0 + + css-to-mat@1.1.1: + dependencies: + '@daybrush/utils': 1.13.0 + '@scena/matrix': 1.1.1 + + cssesc@3.0.0: {} + csstype@3.1.3: {} damerau-levenshtein@1.0.8: {} @@ -5391,6 +6359,8 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + framework-utils@1.1.0: {} + function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -5404,6 +6374,11 @@ snapshots: functions-have-names@1.2.3: {} + gesto@1.19.4: + dependencies: + '@daybrush/utils': 1.13.0 + '@scena/event-emitter': 1.0.5 + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5573,6 +6548,8 @@ snapshots: highlight.js@10.7.3: {} + highlight.js@11.11.1: {} + highlightjs-vue@1.0.0: {} html-url-attributes@3.0.1: {} @@ -5751,6 +6728,11 @@ snapshots: jiti@2.4.2: {} + jotai@2.12.3(@types/react@19.1.2)(react@19.1.0): + optionalDependencies: + '@types/react': 19.1.2 + react: 19.1.0 + js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -5765,6 +6747,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema@0.4.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} json5@1.0.2: @@ -5784,6 +6768,15 @@ snapshots: dependencies: commander: 8.3.0 + keycode@2.2.1: {} + + keycon@1.4.0: + dependencies: + '@cfcs/core': 0.0.6 + '@daybrush/utils': 1.13.0 + '@scena/event-emitter': 1.0.5 + keycode: 2.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -5844,6 +6837,12 @@ snapshots: lightningcss-win32-arm64-msvc: 1.29.2 lightningcss-win32-x64-msvc: 1.29.2 + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + + linkifyjs@4.2.0: {} + loader-runner@4.3.0: {} loader-utils@2.0.4: @@ -5856,6 +6855,10 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.castarray@4.4.0: {} + + lodash.isplainobject@4.0.6: {} + lodash.merge@4.6.2: {} longest-streak@3.1.0: {} @@ -5869,12 +6872,29 @@ snapshots: fault: 1.0.4 highlight.js: 10.7.3 + lowlight@3.3.0: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + highlight.js: 11.11.1 + lru-cache@11.1.0: {} lucide-react@0.487.0(react@19.1.0): dependencies: react: 19.1.0 + markdown-it-task-lists@2.1.1: {} + + markdown-it@14.1.0: + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + markdown-table@3.0.4: {} math-intrinsics@1.1.0: {} @@ -6044,6 +7064,8 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + mdurl@2.0.0: {} + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -6290,6 +7312,8 @@ snapshots: nanoid@5.1.5: {} + nanostores@1.0.1: {} + natural-compare@1.4.0: {} neo-async@2.6.2: {} @@ -6299,7 +7323,7 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - next@15.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + next@15.3.0(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@next/env': 15.3.0 '@swc/counter': 0.1.3 @@ -6319,6 +7343,7 @@ snapshots: '@next/swc-linux-x64-musl': 15.3.0 '@next/swc-win32-arm64-msvc': 15.3.0 '@next/swc-win32-x64-msvc': 15.3.0 + '@opentelemetry/api': 1.9.0 sharp: 0.34.1 transitivePeerDependencies: - '@babel/core' @@ -6326,6 +7351,48 @@ snapshots: node-releases@2.0.19: {} + novel@1.0.2(@tiptap/extension-code-block@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7))(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@radix-ui/react-slot': 1.2.0(@types/react@19.1.2)(react@19.1.0) + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@tiptap/extension-character-count': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@tiptap/extension-code-block-lowlight': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/extension-code-block@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(highlight.js@11.11.1)(lowlight@3.3.0) + '@tiptap/extension-color': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/extension-text-style@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))) + '@tiptap/extension-highlight': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-horizontal-rule': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@tiptap/extension-image': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-link': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@tiptap/extension-placeholder': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@tiptap/extension-task-item': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@tiptap/extension-task-list': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-text-style': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-underline': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/extension-youtube': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)) + '@tiptap/pm': 2.11.7 + '@tiptap/react': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tiptap/starter-kit': 2.11.7 + '@tiptap/suggestion': 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) + '@types/node': 22.15.2 + cmdk: 1.1.1(@types/react-dom@19.1.1(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + jotai: 2.12.3(@types/react@19.1.2)(react@19.1.0) + katex: 0.16.21 + react: 19.1.0 + react-markdown: 9.1.0(@types/react@19.1.2)(react@19.1.0) + react-moveable: 0.56.0 + react-tweet: 3.2.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + tippy.js: 6.3.7 + tiptap-extension-global-drag-handle: 0.1.18 + tunnel-rat: 0.1.2(@types/react@19.1.2)(react@19.1.0) + transitivePeerDependencies: + - '@tiptap/extension-code-block' + - '@types/react' + - '@types/react-dom' + - highlight.js + - immer + - lowlight + - react-dom + - supports-color + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -6377,6 +7444,12 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + orderedmap@2.1.1: {} + + overlap-area@1.1.0: + dependencies: + '@daybrush/utils': 1.13.0 + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 @@ -6432,6 +7505,11 @@ snapshots: possible-typed-array-names@1.1.0: {} + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss@8.4.31: dependencies: nanoid: 3.3.11 @@ -6468,6 +7546,111 @@ snapshots: property-information@7.0.0: {} + prosemirror-changeset@2.2.1: + dependencies: + prosemirror-transform: 1.10.4 + + prosemirror-collab@1.3.1: + dependencies: + prosemirror-state: 1.4.3 + + prosemirror-commands@1.7.1: + dependencies: + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-dropcursor@1.8.2: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.39.2 + + prosemirror-gapcursor@1.3.2: + dependencies: + prosemirror-keymap: 1.2.2 + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-view: 1.39.2 + + prosemirror-history@1.4.1: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.39.2 + rope-sequence: 1.3.4 + + prosemirror-inputrules@1.5.0: + dependencies: + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-keymap@1.2.2: + dependencies: + prosemirror-state: 1.4.3 + w3c-keyname: 2.2.8 + + prosemirror-markdown@1.13.2: + dependencies: + '@types/markdown-it': 14.1.2 + markdown-it: 14.1.0 + prosemirror-model: 1.25.1 + + prosemirror-menu@1.2.5: + dependencies: + crelt: 1.0.6 + prosemirror-commands: 1.7.1 + prosemirror-history: 1.4.1 + prosemirror-state: 1.4.3 + + prosemirror-model@1.25.1: + dependencies: + orderedmap: 2.1.1 + + prosemirror-schema-basic@1.2.4: + dependencies: + prosemirror-model: 1.25.1 + + prosemirror-schema-list@1.5.1: + dependencies: + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + prosemirror-state@1.4.3: + dependencies: + prosemirror-model: 1.25.1 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.39.2 + + prosemirror-tables@1.7.1: + dependencies: + prosemirror-keymap: 1.2.2 + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + prosemirror-view: 1.39.2 + + prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.39.2): + dependencies: + '@remirror/core-constants': 3.0.0 + escape-string-regexp: 4.0.0 + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-view: 1.39.2 + + prosemirror-transform@1.10.4: + dependencies: + prosemirror-model: 1.25.1 + + prosemirror-view@1.39.2: + dependencies: + prosemirror-model: 1.25.1 + prosemirror-state: 1.4.3 + prosemirror-transform: 1.10.4 + + punycode.js@2.3.1: {} + punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -6482,6 +7665,11 @@ snapshots: schema-utils: 3.3.0 webpack: 5.99.6 + react-css-styled@1.1.9: + dependencies: + css-styled: 1.0.8 + framework-utils: 1.1.0 + react-dom@19.1.0(react@19.1.0): dependencies: react: 19.1.0 @@ -6513,6 +7701,40 @@ snapshots: transitivePeerDependencies: - supports-color + react-markdown@9.1.0(@types/react@19.1.2)(react@19.1.0): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.1.2 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.0 + react: 19.1.0 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + react-moveable@0.56.0: + dependencies: + '@daybrush/utils': 1.13.0 + '@egjs/agent': 2.4.4 + '@egjs/children-differ': 1.0.1 + '@egjs/list-differ': 1.0.1 + '@scena/dragscroll': 1.4.0 + '@scena/event-emitter': 1.0.5 + '@scena/matrix': 1.1.1 + css-to-mat: 1.1.1 + framework-utils: 1.1.0 + gesto: 1.19.4 + overlap-area: 1.1.0 + react-css-styled: 1.1.9 + react-selecto: 1.26.3 + react-remove-scroll-bar@2.3.8(@types/react@19.1.2)(react@19.1.0): dependencies: react: 19.1.0 @@ -6532,6 +7754,10 @@ snapshots: optionalDependencies: '@types/react': 19.1.2 + react-selecto@1.26.3: + dependencies: + selecto: 1.26.3 + react-style-singleton@2.2.3(@types/react@19.1.2)(react@19.1.0): dependencies: get-nonce: 1.0.1 @@ -6550,6 +7776,14 @@ snapshots: react: 19.1.0 refractor: 3.6.0 + react-tweet@3.2.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@swc/helpers': 0.5.15 + clsx: 2.1.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + swr: 2.3.3(react@19.1.0) + react@19.1.0: {} reflect.getprototypeof@1.0.10: @@ -6653,6 +7887,8 @@ snapshots: reusify@1.1.0: {} + rope-sequence@1.3.4: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -6693,6 +7929,21 @@ snapshots: ajv-formats: 2.1.1(ajv@8.17.1) ajv-keywords: 5.1.0(ajv@8.17.1) + secure-json-parse@2.7.0: {} + + selecto@1.26.3: + dependencies: + '@daybrush/utils': 1.13.0 + '@egjs/children-differ': 1.0.1 + '@scena/dragscroll': 1.4.0 + '@scena/event-emitter': 1.0.5 + css-styled: 1.0.8 + css-to-mat: 1.1.1 + framework-utils: 1.1.0 + gesto: 1.19.4 + keycon: 1.4.0 + overlap-area: 1.1.0 + semver@6.3.1: {} semver@7.7.1: {} @@ -6790,6 +8041,11 @@ snapshots: is-arrayish: 0.3.2 optional: true + sonner@2.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -6889,6 +8145,12 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + swr@2.3.3(react@19.1.0): + dependencies: + dequal: 2.0.3 + react: 19.1.0 + use-sync-external-store: 1.5.0(react@19.1.0) + tailwind-merge@3.2.0: {} tailwindcss@4.1.4: {} @@ -6911,11 +8173,27 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + throttleit@2.1.0: {} + tinyglobby@0.2.12: dependencies: fdir: 6.4.3(picomatch@4.0.2) picomatch: 4.0.2 + tippy.js@6.3.7: + dependencies: + '@popperjs/core': 2.11.8 + + tiptap-extension-global-drag-handle@0.1.18: {} + + tiptap-markdown@0.8.10(@tiptap/core@2.11.7(@tiptap/pm@2.11.7)): + dependencies: + '@tiptap/core': 2.11.7(@tiptap/pm@2.11.7) + '@types/markdown-it': 13.0.9 + markdown-it: 14.1.0 + markdown-it-task-lists: 2.1.1 + prosemirror-markdown: 1.13.2 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -6937,6 +8215,14 @@ snapshots: tslib@2.8.1: {} + tunnel-rat@0.1.2(@types/react@19.1.2)(react@19.1.0): + dependencies: + zustand: 4.5.6(@types/react@19.1.2)(react@19.1.0) + transitivePeerDependencies: + - '@types/react' + - immer + - react + tw-animate-css@1.2.5: {} type-check@0.4.0: @@ -6988,6 +8274,8 @@ snapshots: typescript@5.8.3: {} + uc.micro@2.1.0: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -6997,6 +8285,8 @@ snapshots: undici-types@6.19.8: {} + undici-types@6.21.0: {} + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -7076,6 +8366,10 @@ snapshots: optionalDependencies: '@types/react': 19.1.2 + use-debounce@10.0.4(react@19.1.0): + dependencies: + react: 19.1.0 + use-sidecar@1.1.3(@types/react@19.1.2)(react@19.1.0): dependencies: detect-node-es: 1.1.0 @@ -7088,6 +8382,12 @@ snapshots: dependencies: react: 19.1.0 + use-sync-external-store@1.5.0(react@19.1.0): + dependencies: + react: 19.1.0 + + util-deprecate@1.0.2: {} + vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 @@ -7103,6 +8403,8 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 + w3c-keyname@2.2.8: {} + watchpack@2.4.2: dependencies: glob-to-regexp: 0.4.1 @@ -7193,11 +8495,23 @@ snapshots: yocto-queue@0.1.0: {} + zod-to-json-schema@3.24.5(zod@3.24.3): + dependencies: + zod: 3.24.3 + zod@3.24.3: {} - zustand@5.0.3(@types/react@19.1.2)(react@19.1.0): + zustand@4.5.6(@types/react@19.1.2)(react@19.1.0): + dependencies: + use-sync-external-store: 1.5.0(react@19.1.0) optionalDependencies: '@types/react': 19.1.2 react: 19.1.0 + zustand@5.0.3(@types/react@19.1.2)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)): + optionalDependencies: + '@types/react': 19.1.2 + react: 19.1.0 + use-sync-external-store: 1.5.0(react@19.1.0) + zwitch@2.0.4: {} diff --git a/web/src/app/editor/page.tsx b/web/src/app/editor/page.tsx new file mode 100644 index 0000000..33ec6b5 --- /dev/null +++ b/web/src/app/editor/page.tsx @@ -0,0 +1,11 @@ +import ReportEditor from "~/components/editor"; + +export default function Page() { + return ( +
+
+ +
+
+ ); +} diff --git a/web/src/components/editor/content.ts b/web/src/components/editor/content.ts new file mode 100644 index 0000000..3ed5da6 --- /dev/null +++ b/web/src/components/editor/content.ts @@ -0,0 +1,356 @@ +export const defaultEditorContent = { + type: "doc", + content: [ + { + type: "heading", + attrs: { level: 2 }, + content: [{ type: "text", text: "Introducing Novel" }], + }, + { + type: "paragraph", + content: [ + { + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://github.com/steven-tey/novel", + target: "_blank", + }, + }, + ], + text: "Novel", + }, + { + type: "text", + text: " is a Notion-style WYSIWYG editor with AI-powered autocompletion. Built with ", + }, + { + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://tiptap.dev/", + target: "_blank", + }, + }, + ], + text: "Tiptap", + }, + { type: "text", text: " + " }, + { + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://sdk.vercel.ai/docs", + target: "_blank", + }, + }, + ], + text: "Vercel AI SDK", + }, + { type: "text", text: "." }, + ], + }, + { + type: "heading", + attrs: { level: 3 }, + content: [{ type: "text", text: "Installation" }], + }, + { + type: "codeBlock", + attrs: { language: null }, + content: [{ type: "text", text: "npm i novel" }], + }, + { + type: "heading", + attrs: { level: 3 }, + content: [{ type: "text", text: "Usage" }], + }, + { + type: "codeBlock", + attrs: { language: null }, + content: [ + { + type: "text", + text: 'import { Editor } from "novel";\n\nexport default function App() {\n return (\n \n )\n}', + }, + ], + }, + { + type: "heading", + attrs: { level: 3 }, + content: [{ type: "text", text: "Features" }], + }, + { + type: "orderedList", + attrs: { tight: true, start: 1 }, + content: [ + { + type: "listItem", + content: [ + { + type: "paragraph", + content: [{ type: "text", text: "Slash menu & bubble menu" }], + }, + ], + }, + { + type: "listItem", + content: [ + { + type: "paragraph", + content: [ + { type: "text", text: "AI autocomplete (type " }, + { type: "text", marks: [{ type: "code" }], text: "++" }, + { + type: "text", + text: " to activate, or select from slash menu)", + }, + ], + }, + ], + }, + { + type: "listItem", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Image uploads (drag & drop / copy & paste, or select from slash menu) ", + }, + ], + }, + ], + }, + { + type: "listItem", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Add tweets from the command slash menu:", + }, + ], + }, + { + type: "twitter", + attrs: { + src: "https://x.com/elonmusk/status/1800759252224729577", + }, + }, + ], + }, + { + type: "listItem", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Mathematical symbols with LaTeX expression:", + }, + ], + }, + { + type: "orderedList", + attrs: { + tight: true, + start: 1, + }, + content: [ + { + type: "listItem", + content: [ + { + type: "paragraph", + content: [ + { + type: "math", + attrs: { + latex: "E = mc^2", + }, + }, + ], + }, + ], + }, + { + type: "listItem", + content: [ + { + type: "paragraph", + content: [ + { + type: "math", + attrs: { + latex: "a^2 = \\sqrt{b^2 + c^2}", + }, + }, + ], + }, + ], + }, + { + type: "listItem", + content: [ + { + type: "paragraph", + content: [ + { + type: "math", + attrs: { + latex: + "\\hat{f} (\\xi)=\\int_{-\\infty}^{\\infty}f(x)e^{-2\\pi ix\\xi}dx", + }, + }, + ], + }, + ], + }, + { + type: "listItem", + content: [ + { + type: "paragraph", + content: [ + { + type: "math", + attrs: { + latex: + "A=\\begin{bmatrix}a&b\\\\c&d \\end{bmatrix}", + }, + }, + ], + }, + ], + }, + { + type: "listItem", + content: [ + { + type: "paragraph", + content: [ + { + type: "math", + attrs: { + latex: "\\sum_{i=0}^n x_i", + }, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + { + type: "image", + attrs: { + src: "https://public.blob.vercel-storage.com/pJrjXbdONOnAeZAZ/banner-2wQk82qTwyVgvlhTW21GIkWgqPGD2C.png", + alt: "banner.png", + title: "banner.png", + width: null, + height: null, + }, + }, + { type: "horizontalRule" }, + { + type: "heading", + attrs: { level: 3 }, + content: [{ type: "text", text: "Learn more" }], + }, + { + type: "taskList", + content: [ + { + type: "taskItem", + attrs: { checked: false }, + content: [ + { + type: "paragraph", + content: [ + { type: "text", text: "Star us on " }, + { + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://github.com/steven-tey/novel", + target: "_blank", + }, + }, + ], + text: "GitHub", + }, + ], + }, + ], + }, + { + type: "taskItem", + attrs: { checked: false }, + content: [ + { + type: "paragraph", + content: [ + { type: "text", text: "Install the " }, + { + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://www.npmjs.com/package/novel", + target: "_blank", + }, + }, + ], + text: "NPM package", + }, + ], + }, + ], + }, + { + type: "taskItem", + attrs: { checked: false }, + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + marks: [ + { + type: "link", + attrs: { + href: "https://vercel.com/templates/next.js/novel", + target: "_blank", + }, + }, + ], + text: "Deploy your own", + }, + { type: "text", text: " to Vercel" }, + ], + }, + ], + }, + ], + }, + ], +}; diff --git a/web/src/components/editor/extensions.tsx b/web/src/components/editor/extensions.tsx new file mode 100644 index 0000000..6f419aa --- /dev/null +++ b/web/src/components/editor/extensions.tsx @@ -0,0 +1,180 @@ +import { + AIHighlight, + CharacterCount, + CodeBlockLowlight, + Color, + CustomKeymap, + GlobalDragHandle, + HighlightExtension, + HorizontalRule, + Mathematics, + Placeholder, + StarterKit, + TaskItem, + TaskList, + TextStyle, + TiptapImage, + TiptapLink, + TiptapUnderline, + Twitter, + UpdatedImage, + UploadImagesPlugin, + Youtube, +} from "novel"; +import { Markdown } from "tiptap-markdown"; +import { cx } from "class-variance-authority"; +import { common, createLowlight } from "lowlight"; + +//TODO I am using cx here to get tailwind autocomplete working, idk if someone else can write a regex to just capture the class key in objects +const aiHighlight = AIHighlight; +//You can overwrite the placeholder with your own configuration +const placeholder = Placeholder; +const tiptapLink = TiptapLink.configure({ + HTMLAttributes: { + class: cx( + "text-muted-foreground underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer", + ), + }, +}); + +const tiptapImage = TiptapImage.extend({ + addProseMirrorPlugins() { + return [ + UploadImagesPlugin({ + imageClass: cx("opacity-40 rounded-lg border border-stone-200"), + }), + ]; + }, +}).configure({ + allowBase64: true, + HTMLAttributes: { + class: cx("rounded-lg border border-muted"), + }, +}); + +const updatedImage = UpdatedImage.configure({ + HTMLAttributes: { + class: cx("rounded-lg border border-muted"), + }, +}); + +const taskList = TaskList.configure({ + HTMLAttributes: { + class: cx("not-prose pl-2 "), + }, +}); +const taskItem = TaskItem.configure({ + HTMLAttributes: { + class: cx("flex gap-2 items-start my-4"), + }, + nested: true, +}); + +const horizontalRule = HorizontalRule.configure({ + HTMLAttributes: { + class: cx("mt-4 mb-6 border-t border-muted-foreground"), + }, +}); + +const starterKit = StarterKit.configure({ + bulletList: { + HTMLAttributes: { + class: cx("list-disc list-outside leading-3 -mt-2"), + }, + }, + orderedList: { + HTMLAttributes: { + class: cx("list-decimal list-outside leading-3 -mt-2"), + }, + }, + listItem: { + HTMLAttributes: { + class: cx("leading-normal -mb-2"), + }, + }, + blockquote: { + HTMLAttributes: { + class: cx("border-l-4 border-primary"), + }, + }, + codeBlock: false, + code: { + HTMLAttributes: { + class: cx("rounded-md bg-muted px-1.5 py-1 font-mono font-medium"), + spellcheck: "false", + }, + }, + horizontalRule: false, + dropcursor: { + color: "#DBEAFE", + width: 4, + }, + gapcursor: false, +}); + +const codeBlockLowlight = CodeBlockLowlight.configure({ + // configure lowlight: common / all / use highlightJS in case there is a need to specify certain language grammars only + // common: covers 37 language grammars which should be good enough in most cases + lowlight: createLowlight(common), +}); + +const youtube = Youtube.configure({ + HTMLAttributes: { + class: cx("rounded-lg border border-muted"), + }, + inline: false, +}); + +const twitter = Twitter.configure({ + HTMLAttributes: { + class: cx("not-prose"), + }, + inline: false, +}); + +const mathematics = Mathematics.configure({ + HTMLAttributes: { + class: cx("text-foreground rounded p-1 hover:bg-accent cursor-pointer"), + }, + katexOptions: { + throwOnError: false, + }, +}); + +const characterCount = CharacterCount.configure(); + +const markdownExtension = Markdown.configure({ + html: true, + tightLists: true, + tightListClass: "tight", + bulletListMarker: "-", + linkify: false, + breaks: false, + transformPastedText: false, + transformCopiedText: false, +}); + +const globalDragHandle = GlobalDragHandle.configure({}); + +export const defaultExtensions = [ + starterKit, + placeholder, + tiptapLink, + updatedImage, + taskList, + taskItem, + horizontalRule, + aiHighlight, + codeBlockLowlight, + youtube, + twitter, + mathematics, + characterCount, + TiptapUnderline, + markdownExtension, + HighlightExtension, + TextStyle, + Color, + CustomKeymap, + globalDragHandle, +]; diff --git a/web/src/components/editor/generative/ai-completion-command.tsx b/web/src/components/editor/generative/ai-completion-command.tsx new file mode 100644 index 0000000..eec566c --- /dev/null +++ b/web/src/components/editor/generative/ai-completion-command.tsx @@ -0,0 +1,66 @@ +import { CommandGroup, CommandItem, CommandSeparator } from "../../ui/command"; +import { useEditor } from "novel"; +import { Check, TextQuote, TrashIcon } from "lucide-react"; + +const AICompletionCommands = ({ + completion, + onDiscard, +}: { + completion: string; + onDiscard: () => void; +}) => { + const { editor } = useEditor(); + if (!editor) return null; + return ( + <> + + { + const selection = editor.view.state.selection; + editor + .chain() + .focus() + .insertContentAt( + { + from: selection.from, + to: selection.to, + }, + completion, + ) + .run(); + }} + > + + Replace selection + + { + const selection = editor.view.state.selection; + editor + .chain() + .focus() + .insertContentAt(selection.to + 1, completion) + .run(); + }} + > + + Insert below + + + + + + + + Discard + + + + ); +}; + +export default AICompletionCommands; diff --git a/web/src/components/editor/generative/ai-selector-commands.tsx b/web/src/components/editor/generative/ai-selector-commands.tsx new file mode 100644 index 0000000..65e65e4 --- /dev/null +++ b/web/src/components/editor/generative/ai-selector-commands.tsx @@ -0,0 +1,81 @@ +import { + ArrowDownWideNarrow, + CheckCheck, + RefreshCcwDot, + StepForward, + WrapText, +} from "lucide-react"; +import { getPrevText, useEditor } from "novel"; +import { CommandGroup, CommandItem, CommandSeparator } from "../../ui/command"; + +const options = [ + { + value: "improve", + label: "Improve writing", + icon: RefreshCcwDot, + }, + { + value: "fix", + label: "Fix grammar", + icon: CheckCheck, + }, + { + value: "shorter", + label: "Make shorter", + icon: ArrowDownWideNarrow, + }, + { + value: "longer", + label: "Make longer", + icon: WrapText, + }, +]; + +interface AISelectorCommandsProps { + onSelect: (value: string, option: string) => void; +} + +const AISelectorCommands = ({ onSelect }: AISelectorCommandsProps) => { + const { editor } = useEditor(); + if (!editor) return null; + return ( + <> + + {options.map((option) => ( + { + const slice = editor.state.selection.content(); + const text = editor.storage.markdown.serializer.serialize( + slice.content, + ); + onSelect(text, value); + }} + className="flex gap-2 px-4" + key={option.value} + value={option.value} + > + + {option.label} + + ))} + + + + { + const pos = editor.state.selection.from; + const text = getPrevText(editor, pos); + onSelect(text, "continue"); + }} + value="continue" + className="gap-2 px-4" + > + + Continue writing + + + + ); +}; + +export default AISelectorCommands; diff --git a/web/src/components/editor/generative/ai-selector.tsx b/web/src/components/editor/generative/ai-selector.tsx new file mode 100644 index 0000000..b0f299d --- /dev/null +++ b/web/src/components/editor/generative/ai-selector.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { Command, CommandInput } from "../../ui/command"; + +import { useCompletion } from "@ai-sdk/react"; +import { ArrowUp } from "lucide-react"; +import { useEditor } from "novel"; +import { addAIHighlight } from "novel"; +import { useState } from "react"; +import Markdown from "react-markdown"; +import { toast } from "sonner"; +import { Button } from "../../ui/button"; +import Magic from "../../ui/icons/magic"; +import { ScrollArea } from "../../ui/scroll-area"; +import AICompletionCommands from "./ai-completion-command"; +import AISelectorCommands from "./ai-selector-commands"; +import { LoadingOutlined } from "@ant-design/icons"; +//TODO: I think it makes more sense to create a custom Tiptap extension for this functionality https://tiptap.dev/docs/editor/ai/introduction + +interface AISelectorProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export function AISelector({ onOpenChange }: AISelectorProps) { + const { editor } = useEditor(); + const [inputValue, setInputValue] = useState(""); + + const { completion, complete, isLoading } = useCompletion({ + // id: "novel", + api: "/api/generate", + onResponse: (response) => { + if (response.status === 429) { + toast.error("You have reached your request limit for the day."); + return; + } + }, + onError: (e) => { + toast.error(e.message); + }, + }); + + if (!editor) return null; + + const hasCompletion = completion.length > 0; + + return ( + + {hasCompletion && ( +
+ +
+ {completion} +
+
+
+ )} + + {isLoading && ( +
+ + AI is thinking +
+ +
+
+ )} + {!isLoading && ( + <> +
+ addAIHighlight(editor)} + /> + +
+ {hasCompletion ? ( + { + editor.chain().unsetHighlight().focus().run(); + onOpenChange(false); + }} + completion={completion} + /> + ) : ( + + complete(value, { body: { option } }) + } + /> + )} + + )} +
+ ); +} diff --git a/web/src/components/editor/generative/generative-menu-switch.tsx b/web/src/components/editor/generative/generative-menu-switch.tsx new file mode 100644 index 0000000..a9b0fac --- /dev/null +++ b/web/src/components/editor/generative/generative-menu-switch.tsx @@ -0,0 +1,54 @@ +import { EditorBubble, removeAIHighlight, useEditor } from "novel"; +import { Fragment, type ReactNode, useEffect } from "react"; +import { Button } from "../../ui/button"; +import Magic from "../../ui/icons/magic"; +import { AISelector } from "./ai-selector"; + +interface GenerativeMenuSwitchProps { + children: ReactNode; + open: boolean; + onOpenChange: (open: boolean) => void; +} +const GenerativeMenuSwitch = ({ + children, + open, + onOpenChange, +}: GenerativeMenuSwitchProps) => { + const { editor } = useEditor(); + + useEffect(() => { + if (!open && editor) removeAIHighlight(editor); + }, [open]); + + if (!editor) return null; + return ( + { + onOpenChange(false); + editor.chain().unsetHighlight().run(); + }, + }} + className="border-muted bg-background flex w-fit max-w-[90vw] overflow-hidden rounded-md border shadow-xl" + > + {open && } + {!open && ( + + + {children} + + )} + + ); +}; + +export default GenerativeMenuSwitch; diff --git a/web/src/components/editor/image-upload.ts b/web/src/components/editor/image-upload.ts new file mode 100644 index 0000000..fb5dbd9 --- /dev/null +++ b/web/src/components/editor/image-upload.ts @@ -0,0 +1,60 @@ +import { createImageUpload } from "novel"; +import { toast } from "sonner"; + +const onUpload = (file: File) => { + const promise = fetch("/api/upload", { + method: "POST", + headers: { + "content-type": file?.type || "application/octet-stream", + "x-vercel-filename": file?.name || "image.png", + }, + body: file, + }); + + return new Promise((resolve, reject) => { + toast.promise( + promise.then(async (res) => { + // Successfully uploaded image + if (res.status === 200) { + const { url } = (await res.json()) as { url: string }; + // preload the image + const image = new Image(); + image.src = url; + image.onload = () => { + resolve(url); + }; + // No blob store configured + } else if (res.status === 401) { + resolve(file); + throw new Error("`BLOB_READ_WRITE_TOKEN` environment variable not found, reading image locally instead."); + // Unknown error + } else { + throw new Error("Error uploading image. Please try again."); + } + }), + { + loading: "Uploading image...", + success: "Image uploaded successfully.", + error: (e) => { + reject(e); + return e.message; + }, + }, + ); + }); +}; + +export const uploadFn = createImageUpload({ + onUpload, + validateFn: (file) => { + if (!file.type.includes("image/")) { + toast.error("File type not supported."); + return false; + } + if (file.size / 1024 / 1024 > 20) { + toast.error("File size too big (max 20MB)."); + return false; + } + return true; + }, +}); diff --git a/web/src/components/editor/index.tsx b/web/src/components/editor/index.tsx new file mode 100644 index 0000000..50aee66 --- /dev/null +++ b/web/src/components/editor/index.tsx @@ -0,0 +1,173 @@ +"use client"; + +import { + EditorCommand, + EditorCommandEmpty, + EditorCommandItem, + EditorCommandList, + EditorContent, + type EditorInstance, + EditorRoot, + ImageResizer, + type JSONContent, + handleCommandNavigation, + handleImageDrop, + handleImagePaste, +} from "novel"; +import { useEffect, useState } from "react"; +import { useDebouncedCallback } from "use-debounce"; +import { defaultExtensions } from "./extensions"; +import { ColorSelector } from "./selectors/color-selector"; +import { LinkSelector } from "./selectors/link-selector"; +import { MathSelector } from "./selectors/math-selector"; +import { NodeSelector } from "./selectors/node-selector"; +import { Separator } from "../ui/separator"; + +import GenerativeMenuSwitch from "./generative/generative-menu-switch"; +import { uploadFn } from "./image-upload"; +import { TextButtons } from "./selectors/text-buttons"; +import { slashCommand, suggestionItems } from "./slash-command"; +import { defaultEditorContent } from "./content"; + +import "~/styles/prosemirror.css"; + +const hljs = require("highlight.js"); + +const extensions = [...defaultExtensions, slashCommand]; + +const ReportEditor = () => { + const [initialContent, setInitialContent] = useState( + null, + ); + const [saveStatus, setSaveStatus] = useState("Saved"); + const [charsCount, setCharsCount] = useState(); + + const [openNode, setOpenNode] = useState(false); + const [openColor, setOpenColor] = useState(false); + const [openLink, setOpenLink] = useState(false); + const [openAI, setOpenAI] = useState(false); + + //Apply Codeblock Highlighting on the HTML from editor.getHTML() + const highlightCodeblocks = (content: string) => { + const doc = new DOMParser().parseFromString(content, "text/html"); + doc.querySelectorAll("pre code").forEach((el) => { + // @ts-ignore + // https://highlightjs.readthedocs.io/en/latest/api.html?highlight=highlightElement#highlightelement + hljs.highlightElement(el); + }); + return new XMLSerializer().serializeToString(doc); + }; + + const debouncedUpdates = useDebouncedCallback( + async (editor: EditorInstance) => { + const json = editor.getJSON(); + setCharsCount(editor.storage.characterCount.words()); + window.localStorage.setItem( + "html-content", + highlightCodeblocks(editor.getHTML()), + ); + window.localStorage.setItem("novel-content", JSON.stringify(json)); + window.localStorage.setItem( + "markdown", + editor.storage.markdown.getMarkdown(), + ); + setSaveStatus("Saved"); + }, + 500, + ); + + useEffect(() => { + const content = window.localStorage.getItem("novel-content"); + if (content) setInitialContent(JSON.parse(content)); + else setInitialContent(defaultEditorContent); + }, []); + + if (!initialContent) return null; + + return ( +
+
+
+ {saveStatus} +
+
+ {charsCount} Words +
+
+ + handleCommandNavigation(event), + }, + handlePaste: (view, event) => + handleImagePaste(view, event, uploadFn), + handleDrop: (view, event, _slice, moved) => + handleImageDrop(view, event, moved, uploadFn), + attributes: { + class: + "prose prose-base prose-p:my-4 dark:prose-invert prose-headings:font-title font-default focus:outline-none max-w-full", + }, + }} + onUpdate={({ editor }) => { + debouncedUpdates(editor); + setSaveStatus("Unsaved"); + }} + slotAfter={} + > + + + No results + + + {suggestionItems.map((item) => ( + item.command?.(val)} + className="hover:bg-accent aria-selected:bg-accent flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm" + key={item.title} + > +
+ {item.icon} +
+
+

{item.title}

+

+ {item.description} +

+
+
+ ))} +
+
+ + + + + + + + + + + + + + +
+
+
+ ); +}; + +export default ReportEditor; diff --git a/web/src/components/editor/selectors/color-selector.tsx b/web/src/components/editor/selectors/color-selector.tsx new file mode 100644 index 0000000..b41aa42 --- /dev/null +++ b/web/src/components/editor/selectors/color-selector.tsx @@ -0,0 +1,192 @@ +import { Check, ChevronDown } from "lucide-react"; +import { EditorBubbleItem, useEditor } from "novel"; + +import { Button } from "../../ui/button"; +import { Popover, PopoverContent, PopoverTrigger } from "../../ui/popover"; +export interface BubbleColorMenuItem { + name: string; + color: string; +} + +const TEXT_COLORS: BubbleColorMenuItem[] = [ + { + name: "Default", + color: "var(--novel-black)", + }, + { + name: "Purple", + color: "#9333EA", + }, + { + name: "Red", + color: "#E00000", + }, + { + name: "Yellow", + color: "#EAB308", + }, + { + name: "Blue", + color: "#2563EB", + }, + { + name: "Green", + color: "#008A00", + }, + { + name: "Orange", + color: "#FFA500", + }, + { + name: "Pink", + color: "#BA4081", + }, + { + name: "Gray", + color: "#A8A29E", + }, +]; + +const HIGHLIGHT_COLORS: BubbleColorMenuItem[] = [ + { + name: "Default", + color: "var(--novel-highlight-default)", + }, + { + name: "Purple", + color: "var(--novel-highlight-purple)", + }, + { + name: "Red", + color: "var(--novel-highlight-red)", + }, + { + name: "Yellow", + color: "var(--novel-highlight-yellow)", + }, + { + name: "Blue", + color: "var(--novel-highlight-blue)", + }, + { + name: "Green", + color: "var(--novel-highlight-green)", + }, + { + name: "Orange", + color: "var(--novel-highlight-orange)", + }, + { + name: "Pink", + color: "var(--novel-highlight-pink)", + }, + { + name: "Gray", + color: "var(--novel-highlight-gray)", + }, +]; + +interface ColorSelectorProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export const ColorSelector = ({ open, onOpenChange }: ColorSelectorProps) => { + const { editor } = useEditor(); + + if (!editor) return null; + const activeColorItem = TEXT_COLORS.find(({ color }) => + editor.isActive("textStyle", { color }), + ); + + const activeHighlightItem = HIGHLIGHT_COLORS.find(({ color }) => + editor.isActive("highlight", { color }), + ); + + return ( + + + + + + +
+
+ Color +
+ {TEXT_COLORS.map(({ name, color }) => ( + { + editor.commands.unsetColor(); + name !== "Default" && + editor + .chain() + .focus() + .setColor(color || "") + .run(); + onOpenChange(false); + }} + className="hover:bg-accent flex cursor-pointer items-center justify-between px-2 py-1 text-sm" + > +
+
+ A +
+ {name} +
+
+ ))} +
+
+
+ Background +
+ {HIGHLIGHT_COLORS.map(({ name, color }) => ( + { + editor.commands.unsetHighlight(); + name !== "Default" && + editor.chain().focus().setHighlight({ color }).run(); + onOpenChange(false); + }} + className="hover:bg-accent flex cursor-pointer items-center justify-between px-2 py-1 text-sm" + > +
+
+ A +
+ {name} +
+ {editor.isActive("highlight", { color }) && ( + + )} +
+ ))} +
+
+
+ ); +}; diff --git a/web/src/components/editor/selectors/link-selector.tsx b/web/src/components/editor/selectors/link-selector.tsx new file mode 100644 index 0000000..0be053a --- /dev/null +++ b/web/src/components/editor/selectors/link-selector.tsx @@ -0,0 +1,104 @@ +import { Button } from "../../ui/button"; +import { PopoverContent } from "../../ui/popover"; +import { cn } from "../../../lib/utils"; +import { Popover, PopoverTrigger } from "@radix-ui/react-popover"; +import { Check, Trash } from "lucide-react"; +import { useEditor } from "novel"; +import { useEffect, useRef } from "react"; + +export function isValidUrl(url: string) { + try { + new URL(url); + return true; + } catch (_e) { + return false; + } +} +export function getUrlFromString(str: string) { + if (isValidUrl(str)) return str; + try { + if (str.includes(".") && !str.includes(" ")) { + return new URL(`https://${str}`).toString(); + } + } catch (_e) { + return null; + } +} +interface LinkSelectorProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export const LinkSelector = ({ open, onOpenChange }: LinkSelectorProps) => { + const inputRef = useRef(null); + const { editor } = useEditor(); + + // Autofocus on input by default + useEffect(() => { + inputRef.current?.focus(); + }); + if (!editor) return null; + + return ( + + + + + +
{ + const target = e.currentTarget as HTMLFormElement; + e.preventDefault(); + const input = target[0] as HTMLInputElement; + const url = getUrlFromString(input.value); + if (url) { + editor.chain().focus().setLink({ href: url }).run(); + onOpenChange(false); + } + }} + className="flex p-1" + > + + {editor.getAttributes("link").href ? ( + + ) : ( + + )} +
+
+
+ ); +}; diff --git a/web/src/components/editor/selectors/math-selector.tsx b/web/src/components/editor/selectors/math-selector.tsx new file mode 100644 index 0000000..06b83b5 --- /dev/null +++ b/web/src/components/editor/selectors/math-selector.tsx @@ -0,0 +1,35 @@ +import { Button } from "../../ui/button"; +import { cn } from "../../../lib/utils"; +import { SigmaIcon } from "lucide-react"; +import { useEditor } from "novel"; + +export const MathSelector = () => { + const { editor } = useEditor(); + + if (!editor) return null; + + return ( + + ); +}; diff --git a/web/src/components/editor/selectors/node-selector.tsx b/web/src/components/editor/selectors/node-selector.tsx new file mode 100644 index 0000000..6785801 --- /dev/null +++ b/web/src/components/editor/selectors/node-selector.tsx @@ -0,0 +1,144 @@ +import { + Check, + CheckSquare, + ChevronDown, + Code, + Heading1, + Heading2, + Heading3, + ListOrdered, + type LucideIcon, + TextIcon, + TextQuote, +} from "lucide-react"; +import { EditorBubbleItem, useEditor } from "novel"; + +import { Button } from "../../ui/button"; +import { PopoverContent, PopoverTrigger } from "../../ui/popover"; +import { Popover } from "@radix-ui/react-popover"; + +export type SelectorItem = { + name: string; + icon: LucideIcon; + command: ( + editor: NonNullable["editor"]>, + ) => void; + isActive: ( + editor: NonNullable["editor"]>, + ) => boolean; +}; + +const items: SelectorItem[] = [ + { + name: "Text", + icon: TextIcon, + command: (editor) => editor.chain().focus().clearNodes().run(), + // I feel like there has to be a more efficient way to do this – feel free to PR if you know how! + isActive: (editor) => + editor.isActive("paragraph") && + !editor.isActive("bulletList") && + !editor.isActive("orderedList"), + }, + { + name: "Heading 1", + icon: Heading1, + command: (editor) => + editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(), + isActive: (editor) => editor.isActive("heading", { level: 1 }), + }, + { + name: "Heading 2", + icon: Heading2, + command: (editor) => + editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(), + isActive: (editor) => editor.isActive("heading", { level: 2 }), + }, + { + name: "Heading 3", + icon: Heading3, + command: (editor) => + editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(), + isActive: (editor) => editor.isActive("heading", { level: 3 }), + }, + { + name: "To-do List", + icon: CheckSquare, + command: (editor) => + editor.chain().focus().clearNodes().toggleTaskList().run(), + isActive: (editor) => editor.isActive("taskItem"), + }, + { + name: "Bullet List", + icon: ListOrdered, + command: (editor) => + editor.chain().focus().clearNodes().toggleBulletList().run(), + isActive: (editor) => editor.isActive("bulletList"), + }, + { + name: "Numbered List", + icon: ListOrdered, + command: (editor) => + editor.chain().focus().clearNodes().toggleOrderedList().run(), + isActive: (editor) => editor.isActive("orderedList"), + }, + { + name: "Quote", + icon: TextQuote, + command: (editor) => + editor.chain().focus().clearNodes().toggleBlockquote().run(), + isActive: (editor) => editor.isActive("blockquote"), + }, + { + name: "Code", + icon: Code, + command: (editor) => + editor.chain().focus().clearNodes().toggleCodeBlock().run(), + isActive: (editor) => editor.isActive("codeBlock"), + }, +]; +interface NodeSelectorProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export const NodeSelector = ({ open, onOpenChange }: NodeSelectorProps) => { + const { editor } = useEditor(); + if (!editor) return null; + const activeItem = items.filter((item) => item.isActive(editor)).pop() ?? { + name: "Multiple", + }; + + return ( + + + + + + {items.map((item) => ( + { + item.command(editor); + onOpenChange(false); + }} + className="hover:bg-accent flex cursor-pointer items-center justify-between rounded-sm px-2 py-1 text-sm" + > +
+
+ +
+ {item.name} +
+ {activeItem.name === item.name && } +
+ ))} +
+
+ ); +}; diff --git a/web/src/components/editor/selectors/text-buttons.tsx b/web/src/components/editor/selectors/text-buttons.tsx new file mode 100644 index 0000000..34dc54a --- /dev/null +++ b/web/src/components/editor/selectors/text-buttons.tsx @@ -0,0 +1,73 @@ +import { Button } from "../../ui/button"; +import { cn } from "../../../lib/utils"; +import { + BoldIcon, + CodeIcon, + ItalicIcon, + StrikethroughIcon, + UnderlineIcon, +} from "lucide-react"; +import { EditorBubbleItem, useEditor } from "novel"; +import type { SelectorItem } from "./node-selector"; + +export const TextButtons = () => { + const { editor } = useEditor(); + if (!editor) return null; + const items: SelectorItem[] = [ + { + name: "bold", + isActive: (editor) => editor.isActive("bold"), + command: (editor) => editor.chain().focus().toggleBold().run(), + icon: BoldIcon, + }, + { + name: "italic", + isActive: (editor) => editor.isActive("italic"), + command: (editor) => editor.chain().focus().toggleItalic().run(), + icon: ItalicIcon, + }, + { + name: "underline", + isActive: (editor) => editor.isActive("underline"), + command: (editor) => editor.chain().focus().toggleUnderline().run(), + icon: UnderlineIcon, + }, + { + name: "strike", + isActive: (editor) => editor.isActive("strike"), + command: (editor) => editor.chain().focus().toggleStrike().run(), + icon: StrikethroughIcon, + }, + { + name: "code", + isActive: (editor) => editor.isActive("code"), + command: (editor) => editor.chain().focus().toggleCode().run(), + icon: CodeIcon, + }, + ]; + return ( +
+ {items.map((item) => ( + { + item.command(editor); + }} + > + + + ))} +
+ ); +}; diff --git a/web/src/components/editor/slash-command.tsx b/web/src/components/editor/slash-command.tsx new file mode 100644 index 0000000..4a69435 --- /dev/null +++ b/web/src/components/editor/slash-command.tsx @@ -0,0 +1,219 @@ +import { + Brain, + CheckSquare, + Code, + Heading1, + Heading2, + Heading3, + ImageIcon, + List, + ListOrdered, + MessageSquarePlus, + Text, + TextQuote, + Twitter, + Youtube, +} from "lucide-react"; +import { Command, createSuggestionItems, renderItems } from "novel"; +import { uploadFn } from "./image-upload"; + +export const suggestionItems = createSuggestionItems([ + { + title: "Send Feedback", + description: "Let us know how we can improve.", + icon: , + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).run(); + window.open("/feedback", "_blank"); + }, + }, + { + title: "Text", + description: "Just start typing with plain text.", + searchTerms: ["p", "paragraph"], + icon: , + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .toggleNode("paragraph", "paragraph") + .run(); + }, + }, + { + title: "To-do List", + description: "Track tasks with a to-do list.", + searchTerms: ["todo", "task", "list", "check", "checkbox"], + icon: , + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).toggleTaskList().run(); + }, + }, + { + title: "Heading 1", + description: "Big section heading.", + searchTerms: ["title", "big", "large"], + icon: , + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .setNode("heading", { level: 1 }) + .run(); + }, + }, + { + title: "Heading 2", + description: "Medium section heading.", + searchTerms: ["subtitle", "medium"], + icon: , + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .setNode("heading", { level: 2 }) + .run(); + }, + }, + { + title: "Heading 3", + description: "Small section heading.", + searchTerms: ["subtitle", "small"], + icon: , + command: ({ editor, range }) => { + editor + .chain() + .focus() + .deleteRange(range) + .setNode("heading", { level: 3 }) + .run(); + }, + }, + { + title: "Bullet List", + description: "Create a simple bullet list.", + searchTerms: ["unordered", "point"], + icon: , + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).toggleBulletList().run(); + }, + }, + { + title: "Numbered List", + description: "Create a list with numbering.", + searchTerms: ["ordered"], + icon: , + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).toggleOrderedList().run(); + }, + }, + { + title: "Quote", + description: "Capture a quote.", + searchTerms: ["blockquote"], + icon: , + command: ({ editor, range }) => + editor + .chain() + .focus() + .deleteRange(range) + .toggleNode("paragraph", "paragraph") + .toggleBlockquote() + .run(), + }, + { + title: "Code", + description: "Capture a code snippet.", + searchTerms: ["codeblock"], + icon: , + command: ({ editor, range }) => + editor.chain().focus().deleteRange(range).toggleCodeBlock().run(), + }, + { + title: "Image", + description: "Upload an image from your computer.", + searchTerms: ["photo", "picture", "media"], + icon: , + command: ({ editor, range }) => { + editor.chain().focus().deleteRange(range).run(); + // upload image + const input = document.createElement("input"); + input.type = "file"; + input.accept = "image/*"; + input.onchange = async () => { + if (input.files?.length) { + const file = input.files[0]; + if (!file) return; + const pos = editor.view.state.selection.from; + uploadFn(file, editor.view, pos); + } + }; + input.click(); + }, + }, + { + title: "Youtube", + description: "Embed a Youtube video.", + searchTerms: ["video", "youtube", "embed"], + icon: , + command: ({ editor, range }) => { + const videoLink = prompt("Please enter Youtube Video Link"); + //From https://regexr.com/3dj5t + const ytRegex = new RegExp( + /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/, + ); + + if (videoLink && ytRegex.test(videoLink)) { + editor + .chain() + .focus() + .deleteRange(range) + .setYoutubeVideo({ + src: videoLink, + }) + .run(); + } else { + if (videoLink !== null) { + alert("Please enter a correct Youtube Video Link"); + } + } + }, + }, + { + title: "Twitter", + description: "Embed a Tweet.", + searchTerms: ["twitter", "embed"], + icon: , + command: ({ editor, range }) => { + const tweetLink = prompt("Please enter Twitter Link"); + const tweetRegex = new RegExp( + /^https?:\/\/(www\.)?x\.com\/([a-zA-Z0-9_]{1,15})(\/status\/(\d+))?(\/\S*)?$/, + ); + + if (tweetLink && tweetRegex.test(tweetLink)) { + editor + .chain() + .focus() + .deleteRange(range) + .setTweet({ + src: tweetLink, + }) + .run(); + } else { + if (tweetLink !== null) { + alert("Please enter a correct Twitter Link"); + } + } + }, + }, +]); + +export const slashCommand = Command.configure({ + suggestion: { + items: () => suggestionItems, + render: renderItems, + }, +}); diff --git a/web/src/components/ui/command.tsx b/web/src/components/ui/command.tsx new file mode 100644 index 0000000..77020a0 --- /dev/null +++ b/web/src/components/ui/command.tsx @@ -0,0 +1,177 @@ +"use client" + +import * as React from "react" +import { Command as CommandPrimitive } from "cmdk" +import { SearchIcon } from "lucide-react" + +import { cn } from "~/lib/utils" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "~/components/ui/dialog" + +function Command({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandDialog({ + title = "Command Palette", + description = "Search for a command to run...", + children, + ...props +}: React.ComponentProps & { + title?: string + description?: string +}) { + return ( + + + {title} + {description} + + + + {children} + + + + ) +} + +function CommandInput({ + className, + ...props +}: React.ComponentProps) { + return ( +
+ + +
+ ) +} + +function CommandList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandEmpty({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/web/src/components/ui/icons/magic.tsx b/web/src/components/ui/icons/magic.tsx new file mode 100644 index 0000000..410346f --- /dev/null +++ b/web/src/components/ui/icons/magic.tsx @@ -0,0 +1,32 @@ +export default function Magic({ className }: { className: string }) { + return ( + + Magic AI icon + + + + + + ); +} diff --git a/web/src/components/ui/popover.tsx b/web/src/components/ui/popover.tsx new file mode 100644 index 0000000..9be5957 --- /dev/null +++ b/web/src/components/ui/popover.tsx @@ -0,0 +1,48 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "~/lib/utils" + +function Popover({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function PopoverContent({ + className, + align = "center", + sideOffset = 4, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function PopoverAnchor({ + ...props +}: React.ComponentProps) { + return +} + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index dfa2072..c4b0047 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -1,5 +1,6 @@ @import "tailwindcss"; @import "tw-animate-css"; +@plugin "@tailwindcss/typography"; @custom-variant dark (&:is(.dark *)); @@ -128,6 +129,16 @@ --sidebar-border: oklch(0.922 0 0); --sidebar-ring: oklch(0.708 0 0); --brand: #007aff; + + --novel-highlight-default: #ffffff; + --novel-highlight-purple: #f6f3f8; + --novel-highlight-red: #fdebeb; + --novel-highlight-yellow: #fbf4a2; + --novel-highlight-blue: #c1ecf9; + --novel-highlight-green: #acf79f; + --novel-highlight-orange: #faebdd; + --novel-highlight-pink: #faf1f5; + --novel-highlight-gray: #f1f1ef; } .dark { @@ -164,6 +175,16 @@ --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.556 0 0); --brand: #4087f4; + + --novel-highlight-default: #000000; + --novel-highlight-purple: #3f2c4b; + --novel-highlight-red: #5c1a1a; + --novel-highlight-yellow: #5c4b1a; + --novel-highlight-blue: #1a3d5c; + --novel-highlight-green: #1a5c20; + --novel-highlight-orange: #5c3a1a; + --novel-highlight-pink: #5c1a3a; + --novel-highlight-gray: #3a3a3a; } @layer base { diff --git a/web/src/styles/prosemirror.css b/web/src/styles/prosemirror.css new file mode 100644 index 0000000..1f3b40e --- /dev/null +++ b/web/src/styles/prosemirror.css @@ -0,0 +1,281 @@ +@import "./globals.css"; + +.ProseMirror { + @apply p-12 px-8 sm:px-12; +} + +.ProseMirror .is-editor-empty:first-child::before { + content: attr(data-placeholder); + float: left; + color: hsl(var(--muted-foreground)); + pointer-events: none; + height: 0; +} +.ProseMirror p.is-empty::before { + content: attr(data-placeholder); + float: left; + color: hsl(var(--muted-foreground)); + pointer-events: none; + height: 0; +} + +/* Custom image styles */ + +.ProseMirror img { + transition: filter 0.1s ease-in-out; + + &:hover { + cursor: pointer; + filter: brightness(90%); + } + + &.ProseMirror-selectednode { + outline: 3px solid #5abbf7; + filter: brightness(90%); + } +} + +.img-placeholder { + position: relative; + + &:before { + content: ""; + box-sizing: border-box; + position: absolute; + top: 50%; + left: 50%; + width: 36px; + height: 36px; + border-radius: 50%; + border: 3px solid var(--novel-stone-200); + border-top-color: var(--novel-stone-800); + animation: spinning 0.6s linear infinite; + } +} + +.ProseMirror pre { + background: #0d0d0d; + border-radius: 0.5rem; + color: #fff; + font-family: "JetBrainsMono", monospace; + padding: 0.75rem 1rem; + + code { + background: none; + color: inherit; + font-size: 0.8rem; + padding: 0; + } + + .hljs-comment, + .hljs-quote { + color: #616161; + } + + .hljs-variable, + .hljs-template-variable, + .hljs-attribute, + .hljs-tag, + .hljs-name, + .hljs-regexp, + .hljs-link, + .hljs-name, + .hljs-selector-id, + .hljs-selector-class { + color: #f98181; + } + + .hljs-number, + .hljs-meta, + .hljs-built_in, + .hljs-builtin-name, + .hljs-literal, + .hljs-type, + .hljs-params { + color: #fbbc88; + } + + .hljs-string, + .hljs-symbol, + .hljs-bullet { + color: #b9f18d; + } + + .hljs-title, + .hljs-section { + color: #faf594; + } + + .hljs-keyword, + .hljs-selector-tag { + color: #70cff8; + } + + .hljs-emphasis { + font-style: italic; + } + + .hljs-strong { + font-weight: 700; + } +} + +@keyframes spinning { + to { + transform: rotate(360deg); + } +} + +/* Custom TODO list checkboxes – shoutout to this awesome tutorial: https://moderncss.dev/pure-css-custom-checkbox-style/ */ + +ul[data-type="taskList"] li > label { + margin-right: 0.2rem; + user-select: none; +} + +@media screen and (max-width: 768px) { + ul[data-type="taskList"] li > label { + margin-right: 0.5rem; + } +} + +ul[data-type="taskList"] li > label input[type="checkbox"] { + -webkit-appearance: none; + appearance: none; + background-color: hsl(var(--background)); + margin: 0; + cursor: pointer; + width: 1.2em; + height: 1.2em; + position: relative; + top: 5px; + border: 2px solid hsl(var(--border)); + margin-right: 0.3rem; + display: grid; + place-content: center; + + &:hover { + background-color: hsl(var(--accent)); + } + + &:active { + background-color: hsl(var(--accent)); + } + + &::before { + content: ""; + width: 0.65em; + height: 0.65em; + transform: scale(0); + transition: 120ms transform ease-in-out; + box-shadow: inset 1em 1em; + transform-origin: center; + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); + } + + &:checked::before { + transform: scale(1); + } +} + +ul[data-type="taskList"] li[data-checked="true"] > div > p { + color: var(--muted-foreground); + text-decoration: line-through; + text-decoration-thickness: 2px; +} + +/* Overwrite tippy-box original max-width */ + +.tippy-box { + max-width: 400px !important; +} + +.ProseMirror:not(.dragging) .ProseMirror-selectednode { + outline: none !important; + background-color: var(--novel-highlight-blue); + transition: background-color 0.2s; + box-shadow: none; +} + +.drag-handle { + position: fixed; + opacity: 1; + transition: opacity ease-in 0.2s; + border-radius: 0.25rem; + + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(0, 0, 0, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E"); + background-size: calc(0.5em + 0.375rem) calc(0.5em + 0.375rem); + background-repeat: no-repeat; + background-position: center; + width: 1.2rem; + height: 1.5rem; + z-index: 50; + cursor: grab; + + &:hover { + background-color: var(--novel-stone-100); + transition: background-color 0.2s; + } + + &:active { + background-color: var(--novel-stone-200); + transition: background-color 0.2s; + cursor: grabbing; + } + + &.hide { + opacity: 0; + pointer-events: none; + } + + @media screen and (max-width: 600px) { + display: none; + pointer-events: none; + } +} + +.dark .drag-handle { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(255, 255, 255, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E"); +} + +/* Custom Youtube Video CSS */ +iframe { + border: 8px solid #ffd00027; + border-radius: 4px; + min-width: 200px; + min-height: 200px; + display: block; + outline: 0px solid transparent; +} + +div[data-youtube-video] > iframe { + cursor: move; + aspect-ratio: 16 / 9; + width: 100%; +} + +.ProseMirror-selectednode iframe { + transition: outline 0.15s; + outline: 6px solid #fbbf24; +} + +@media only screen and (max-width: 480px) { + div[data-youtube-video] > iframe { + max-height: 50px; + } +} + +@media only screen and (max-width: 720px) { + div[data-youtube-video] > iframe { + max-height: 100px; + } +} + +/* CSS for bold coloring and highlighting issue*/ +span[style] > strong { + color: inherit; +} + +mark[style] > strong { + color: inherit; +}