;
+ format: ElementFormatType;
+ icon: LucideIcon;
+ label: string;
+ }[] = [
+ {
+ command: FORMAT_ELEMENT_COMMAND,
+ format: "left",
+ icon: AlignLeft,
+ label: "Left Align",
+ },
+ {
+ command: FORMAT_ELEMENT_COMMAND,
+ format: "center",
+ icon: AlignCenter,
+ label: "Center Align",
+ },
+ {
+ command: FORMAT_ELEMENT_COMMAND,
+ format: "right",
+ icon: AlignRight,
+ label: "Right Align",
+ },
+ ];
+
+ return (
+
+
+
+
+
+ {formatButtons.map(({ command, format, icon: Icon, isActive, label }) => (
+
+ ))}
+
+ {alignButtons.map(({ command, format, icon: Icon, label }) => (
+
+ ))}
+
+ );
+}
diff --git a/apps/web/components/ui/markdown/plugins/update-markdown-editor-plugin.tsx b/apps/web/components/ui/markdown/plugins/update-markdown-editor-plugin.tsx
new file mode 100644
index 00000000..14212fc8
--- /dev/null
+++ b/apps/web/components/ui/markdown/plugins/update-markdown-editor-plugin.tsx
@@ -0,0 +1,15 @@
+import { useEffect } from "react";
+import { $convertFromMarkdownString, TRANSFORMERS } from "@lexical/markdown";
+import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
+
+export const UpdateMarkdownPlugin = ({ markdown }: { markdown: string }) => {
+ const [editor] = useLexicalComposerContext();
+
+ useEffect(() => {
+ editor.update(() => {
+ $convertFromMarkdownString(markdown, TRANSFORMERS);
+ });
+ }, [markdown, editor]);
+
+ return null;
+};
diff --git a/apps/web/components/ui/markdown/theme/theme.ts b/apps/web/components/ui/markdown/theme/theme.ts
new file mode 100644
index 00000000..22b6507b
--- /dev/null
+++ b/apps/web/components/ui/markdown/theme/theme.ts
@@ -0,0 +1,89 @@
+// there is more than implemented, this come from lexical.dev git
+export const MarkdownEditorTheme = {
+ ltr: "text-left",
+ rtl: "text-right",
+ paragraph: "m-0 relative",
+ quote:
+ "ml-5 my-0 text-sm text-gray-600 border-l-4 border-gray-300 pl-4 dark:text-gray-400 dark:border-gray-600",
+ heading: {
+ h1: "text-4xl font-bold m-0",
+ h2: "text-3xl font-semibold m-0",
+ h3: "text-2xl font-medium m-0",
+ h4: "text-xl font-normal m-0",
+ h5: "text-lg font-light m-0",
+ h6: "text-base font-thin m-0",
+ },
+ list: {
+ nested: {
+ listitem: "list-none",
+ },
+ ol: "list-decimal pl-4",
+ ul: "list-disc pl-4",
+ listitem: "ml-8",
+ listitemChecked: "line-through text-gray-600 dark:text-gray-400",
+ listitemUnchecked:
+ "ml-2 mr-2 pl-6 pr-6 relative outline-none list-none dark:text-gray-300",
+ },
+ hashtag:
+ "bg-blue-100 border-b border-blue-300 dark:bg-blue-900 dark:border-blue-700",
+ image: "",
+ link: "text-blue-600 no-underline hover:underline cursor-pointer dark:text-blue-400",
+ text: {
+ bold: "font-bold dark:text-white",
+ code: "bg-gray-200 px-1 py-0.5 font-mono text-sm dark:bg-gray-700 dark:text-gray-200",
+ italic: "italic",
+ strikethrough: "line-through",
+ subscript: "text-xs align-sub",
+ superscript: "text-xs align-super",
+ underline: "underline",
+ underlineStrikethrough: "underline line-through",
+ },
+ code: "bg-gray-200 font-mono block px-4 py-2 text-sm overflow-x-auto relative dark:bg-gray-800 dark:text-gray-200",
+ codeHighlight: {
+ atrule: "text-blue-600 dark:text-blue-400",
+ attr: "text-blue-600 dark:text-blue-400",
+ boolean: "text-purple-500 dark:text-purple-400",
+ builtin: "text-green-600 dark:text-green-400",
+ cdata: "text-gray-500 dark:text-gray-400",
+ char: "text-green-600 dark:text-green-400",
+ class: "text-pink-600 dark:text-pink-400",
+ "class-name": "text-pink-600 dark:text-pink-400",
+ comment: "text-gray-500 dark:text-gray-400",
+ constant: "text-purple-500 dark:text-purple-400",
+ deleted: "text-purple-500 dark:text-purple-400",
+ doctype: "text-gray-500 dark:text-gray-400",
+ entity: "text-orange-500 dark:text-orange-400",
+ function: "text-pink-600 dark:text-pink-400",
+ important: "text-yellow-500 dark:text-yellow-400",
+ inserted: "text-green-600 dark:text-green-400",
+ keyword: "text-blue-600 dark:text-blue-400",
+ namespace: "text-yellow-500 dark:text-yellow-400",
+ number: "text-purple-500 dark:text-purple-400",
+ operator: "text-orange-500 dark:text-orange-400",
+ prolog: "text-gray-500 dark:text-gray-400",
+ property: "text-purple-500 dark:text-purple-400",
+ punctuation: "text-gray-400 dark:text-gray-500",
+ regex: "text-yellow-500 dark:text-yellow-400",
+ selector: "text-green-600 dark:text-green-400",
+ string: "text-green-600 dark:text-green-400",
+ symbol: "text-purple-500 dark:text-purple-400",
+ tag: "text-purple-500 dark:text-purple-400",
+ url: "text-orange-500 dark:text-orange-400",
+ variable: "text-yellow-500 dark:text-yellow-400",
+ },
+ characterLimit: "inline bg-red-200 dark:bg-red-700",
+ mark: "bg-yellow-100 border-b-2 border-yellow-300 dark:bg-yellow-800 dark:border-yellow-600",
+ markOverlap:
+ "bg-yellow-300 border-b-2 border-yellow-500 dark:bg-yellow-600 dark:border-yellow-400",
+ embedBlock: {
+ base: "select-none",
+ focus: "outline outline-2 outline-blue-500",
+ },
+ layoutContainer: "grid gap-2 my-2",
+ layoutItem:
+ "border border-dashed border-gray-300 px-4 py-2 dark:border-gray-600",
+ hr: "border-none my-4 cursor-pointer",
+ hrSelected:
+ "outline outline-2 outline-blue-500 select-none dark:outline-blue-400",
+ specialText: "bg-yellow-300 font-bold dark:bg-yellow-600 dark:text-black",
+};
diff --git a/apps/web/components/ui/markdown/utils/usePointerInteraction.ts b/apps/web/components/ui/markdown/utils/usePointerInteraction.ts
new file mode 100644
index 00000000..165ecb2d
--- /dev/null
+++ b/apps/web/components/ui/markdown/utils/usePointerInteraction.ts
@@ -0,0 +1,31 @@
+import { useEffect, useState } from "react";
+
+/**
+ * Detect if the user currently presses or releases a mouse button.
+ */
+
+export function usePointerInteractions() {
+ const [isPointerDown, setIsPointerDown] = useState(false);
+ const [isPointerReleased, setIsPointerReleased] = useState(true);
+
+ useEffect(() => {
+ const handlePointerUp = () => {
+ setIsPointerDown(false);
+ setIsPointerReleased(true);
+ document.removeEventListener("pointerup", handlePointerUp);
+ };
+
+ const handlePointerDown = () => {
+ setIsPointerDown(true);
+ setIsPointerReleased(false);
+ document.addEventListener("pointerup", handlePointerUp);
+ };
+
+ document.addEventListener("pointerdown", handlePointerDown);
+ return () => {
+ document.removeEventListener("pointerdown", handlePointerDown);
+ };
+ }, []);
+
+ return { isPointerDown, isPointerReleased };
+}
diff --git a/apps/web/package.json b/apps/web/package.json
index 3affe516..fb6f9a58 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -23,6 +23,10 @@
"@hoarder/shared-react": "workspace:^0.1.0",
"@hoarder/trpc": "workspace:^0.1.0",
"@hookform/resolvers": "^3.3.4",
+ "@lexical/list": "^0.20.2",
+ "@lexical/markdown": "^0.20.2",
+ "@lexical/react": "^0.20.2",
+ "@lexical/rich-text": "^0.20.2",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
@@ -53,6 +57,7 @@
"fastest-levenshtein": "^1.0.16",
"i18next": "^23.16.5",
"i18next-resources-to-backend": "^1.2.1",
+ "lexical": "^0.20.2",
"lucide-react": "^0.330.0",
"next": "14.2.13",
"next-auth": "^4.24.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c440d82b..f27b2330 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -478,6 +478,18 @@ importers:
'@hookform/resolvers':
specifier: ^3.3.4
version: 3.3.4(react-hook-form@7.50.1(react@18.3.1))
+ '@lexical/list':
+ specifier: ^0.20.2
+ version: 0.20.2
+ '@lexical/markdown':
+ specifier: ^0.20.2
+ version: 0.20.2
+ '@lexical/react':
+ specifier: ^0.20.2
+ version: 0.20.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.20)
+ '@lexical/rich-text':
+ specifier: ^0.20.2
+ version: 0.20.2
'@radix-ui/react-collapsible':
specifier: ^1.0.3
version: 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -568,6 +580,9 @@ importers:
i18next-resources-to-backend:
specifier: ^1.2.1
version: 1.2.1
+ lexical:
+ specifier: ^0.20.2
+ version: 0.20.2
lucide-react:
specifier: ^0.330.0
version: 0.330.0(react@18.3.1)
@@ -3178,6 +3193,77 @@ packages:
'@leichtgewicht/ip-codec@2.0.4':
resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==}
+ '@lexical/clipboard@0.20.2':
+ resolution: {integrity: sha512-pdgSmrUhOKo23kYBzzkybKipRebaEglHBlJr0E5B8cDr2f3pWVwr0/eRYxgmm5zzg+ZSNpGIdGvpFxFhKhkc4w==}
+
+ '@lexical/code@0.20.2':
+ resolution: {integrity: sha512-+sU6+5MXbwGqdHxKCncFSGpFFgR6iIx86SUEmw+sBOjnFfCXeHJo7FUQKKPI04TvoT3+7eCAwdV2rTKlj2lfsg==}
+
+ '@lexical/devtools-core@0.20.2':
+ resolution: {integrity: sha512-jz7+ohju3gcs24dXIYryLD2Ekr/2U8qOhiAIGuWuC1OZgRI2X/x/jGhCxh3cCUBl0JfEHnbOiaCQpWK2wPLa0A==}
+ peerDependencies:
+ react: '>=17.x'
+ react-dom: '>=17.x'
+
+ '@lexical/dragon@0.20.2':
+ resolution: {integrity: sha512-Ql+VXmNdh9fAqxYfPpsOsVHcs81EtUqFabgBQC8/xJdbA9Y6mfrj7g7JfYt0M05qCdsK2noI1VgPSWH+gRRDNg==}
+
+ '@lexical/hashtag@0.20.2':
+ resolution: {integrity: sha512-iHCHtCBmlGOqyRhfzrF5wi9E2QMv5bJcMfgjR4GQKD+TPbajyM4z/SXPoS7R8ii1d7pp1YTyufdCcRcw4bIx5g==}
+
+ '@lexical/history@0.20.2':
+ resolution: {integrity: sha512-cinRrW0+hlfnr6+Lv4bIDe0SmNyjqiKj6vbLHYymNcdgq3RjtoJ/fERgZHsDY+rxSC8fJ8q/Eq0ZKDgp1QLTcQ==}
+
+ '@lexical/html@0.20.2':
+ resolution: {integrity: sha512-6+fdqu973wdjsWr64UP/o9X/ON3vOSBupikP3D706kpvbpTjpmUGtucSB9gEXrBas1IfJCCZ6f5g1OSmKFn4wQ==}
+
+ '@lexical/link@0.20.2':
+ resolution: {integrity: sha512-tcaVIIbkJujx3SEBGFu/ibRxnRc2kAk9C7oCwrRLrOycASngXWS3jCpIRICsW6jQ8ZlT475GZmcXD0Atf9BqXQ==}
+
+ '@lexical/list@0.20.2':
+ resolution: {integrity: sha512-laQaGIsWIMwmwv35OB8D87AtwrguGDFiEh6Ist3N9yI8KA+R2WlJlmSZvu4MYjG3VM5jlfl0U8ADurjZ1NpNzw==}
+
+ '@lexical/mark@0.20.2':
+ resolution: {integrity: sha512-T2IQwbbt3f2BgYR3F5kO5sN0dWgXQCBdn3iv1+EXphog1U3tV1EU1N697A74nZB5f6cIEdvnYjNEx8pYaNthpw==}
+
+ '@lexical/markdown@0.20.2':
+ resolution: {integrity: sha512-jk6tAvjLsXL1nv+ZBMhYkBuhT6QlxX46PyzTG4lIUOdkvLZNQUzQD+aMKmw0IGynz0bvY/aTH3RBniWZc6fzLQ==}
+
+ '@lexical/offset@0.20.2':
+ resolution: {integrity: sha512-z89cr8jJHTH7UsI71ojBs66ef+RFHX4RXCN5bXs2caKg4P4rnVx8gxRI9vg8JVFVIsXQViJeJTx3KzkZAzTf0A==}
+
+ '@lexical/overflow@0.20.2':
+ resolution: {integrity: sha512-mwVSHQyIm4tpqs8/ZWrb7YSdjFALNWhp6O5Zi4Zh3QPKguQ8I9A+zKPqp6YfpR5eluiWNqCndrXXspbfAVj/0Q==}
+
+ '@lexical/plain-text@0.20.2':
+ resolution: {integrity: sha512-mDIj1J3ZDrV8b88aW7ttsGnJkM3yfvIsJpRZD1aQI5sjngoJHNxKjiN9MOTg7hlgTubZlQ/98AfUbyb/EPOhoA==}
+
+ '@lexical/react@0.20.2':
+ resolution: {integrity: sha512-j01mrUVbqzPs+/FaL7ny8tdT9iQ/P0iaEZ0d5qZbC7Z2Z/bGWmjkXuQOeirLkFJsPRsYqnQwoIoUN3cMzc3EGw==}
+ peerDependencies:
+ react: '>=17.x'
+ react-dom: '>=17.x'
+
+ '@lexical/rich-text@0.20.2':
+ resolution: {integrity: sha512-YtfPFbG3j9ySPMGDHpVYfHaHlmmNx/hmhEupq1PPkKPcwwGWKDJUsdx3TLrSUSHau1mm06hR7Aacw1FMwnGHGg==}
+
+ '@lexical/selection@0.20.2':
+ resolution: {integrity: sha512-Ta3fiu9NyY5p2eVwHZboyTTPqm8D9GassNm3inc0xBn4qC+XD3MQLFAoWSzgGaw06bLlHsDGDV/9BbWve8uSZg==}
+
+ '@lexical/table@0.20.2':
+ resolution: {integrity: sha512-m1DxAg4WUJ5FqnlLQtrZeRtigldaFQlutLtIU2+KthHo/4Ah3fBcmM3ZwPEz7W0ALQmLsaUKjwGNkvUy+5qCdw==}
+
+ '@lexical/text@0.20.2':
+ resolution: {integrity: sha512-Zw6WagBUp/OmANloFstSC9qdIBY22CB68iKngmbrGeoq8UDJRqyPKNdGL4cpj3xLncw+YC5mbuT40HtfgM9yiQ==}
+
+ '@lexical/utils@0.20.2':
+ resolution: {integrity: sha512-xH6eVNQ9ugKp2gPHwXIXoBzorZiXrxq55ORZCvkVJi8BeUoGI0si/4Rq7MyvPoIbunuPdWiVfMYia0RrL+8HRQ==}
+
+ '@lexical/yjs@0.20.2':
+ resolution: {integrity: sha512-k5jaOyDa0c/B3FwYDYLW7s/11hZYXjqlLy1C/a+ftr7b25MQhAMxDIe/I7aLi4ky2ul+TguozTrlqxz/JXIf4w==}
+ peerDependencies:
+ yjs: '>=13.5.22'
+
'@mapbox/node-pre-gyp@1.0.11':
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
hasBin: true
@@ -4902,6 +4988,7 @@ packages:
acorn-import-assertions@1.9.0:
resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
+ deprecated: package has been renamed to acorn-import-attributes
peerDependencies:
acorn: ^8
@@ -5716,6 +5803,10 @@ packages:
resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==}
engines: {node: '>=6'}
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
cmdk@1.0.0:
resolution: {integrity: sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==}
peerDependencies:
@@ -8357,6 +8448,9 @@ packages:
resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
engines: {node: '>=0.10.0'}
+ isomorphic.js@0.2.5:
+ resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
+
isostring@0.0.1:
resolution: {integrity: sha512-wRcdJtXCe2LGtXnD14fXMkduWVdbeGkzBIKg8WcKeEOi6SIc+hRjYYw76WNx3v5FebhUWZrBTWB0NOl3/sagdQ==}
@@ -8623,6 +8717,14 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
+ lexical@0.20.2:
+ resolution: {integrity: sha512-hWWuRcLt99s9B5uuf6RnGWV+zFEJ4mKTlloK4SxSvgESLhyOW9KW1FDnFFCDSmztm5jcPG8Aj6fmY3bo4lTy7w==}
+
+ lib0@0.2.98:
+ resolution: {integrity: sha512-XteTiNO0qEXqqweWx+b21p/fBnNHUA1NwAtJNJek1oPrewEZs2uiT4gWivHKr9GqCjDPAhchz0UQO8NwU3bBNA==}
+ engines: {node: '>=16'}
+ hasBin: true
+
lighthouse-logger@1.4.2:
resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==}
@@ -10778,6 +10880,12 @@ packages:
peerDependencies:
react: '>= 16.8 || 18.0.0'
+ react-error-boundary@3.1.4:
+ resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==}
+ engines: {node: '>=10', npm: '>=6'}
+ peerDependencies:
+ react: '>=16.13.1'
+
react-error-overlay@6.0.11:
resolution: {integrity: sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==}
@@ -13045,6 +13153,10 @@ packages:
yauzl@2.10.0:
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
+ yjs@13.6.20:
+ resolution: {integrity: sha512-Z2YZI+SYqK7XdWlloI3lhMiKnCdFCVC4PchpdO+mCYwtiTwncjUbnRK9R1JmkNfdmHyDXuWN3ibJAt0wsqTbLQ==}
+ engines: {node: '>=16.0.0', npm: '>=8.0.0'}
+
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -15201,7 +15313,7 @@ snapshots:
'@docusaurus/utils-common': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
'@docusaurus/utils-validation': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.3.3)
'@mdx-js/react': 3.0.1(@types/react@18.3.12)(react@18.3.1)
- clsx: 2.1.0
+ clsx: 2.1.1
copy-text-to-clipboard: 3.2.0
infima: 0.2.0-alpha.44
lodash: 4.17.21
@@ -15245,7 +15357,7 @@ snapshots:
'@types/history': 4.7.11
'@types/react': 18.3.12
'@types/react-router-config': 5.0.11
- clsx: 2.1.0
+ clsx: 2.1.1
parse-numeric-range: 1.3.0
prism-react-renderer: 2.3.1(react@18.3.1)
react: 18.3.1
@@ -15274,7 +15386,7 @@ snapshots:
'@docusaurus/utils-validation': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.3.3)
algoliasearch: 4.22.1
algoliasearch-helper: 3.16.3(algoliasearch@4.22.1)
- clsx: 2.1.0
+ clsx: 2.1.1
eta: 2.2.0
fs-extra: 11.2.0
lodash: 4.17.21
@@ -16317,6 +16429,174 @@ snapshots:
'@leichtgewicht/ip-codec@2.0.4':
dev: false
+ '@lexical/clipboard@0.20.2':
+ dependencies:
+ '@lexical/html': 0.20.2
+ '@lexical/list': 0.20.2
+ '@lexical/selection': 0.20.2
+ '@lexical/utils': 0.20.2
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/code@0.20.2':
+ dependencies:
+ '@lexical/utils': 0.20.2
+ lexical: 0.20.2
+ prismjs: 1.29.0
+ dev: false
+
+ '@lexical/devtools-core@0.20.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@lexical/html': 0.20.2
+ '@lexical/link': 0.20.2
+ '@lexical/mark': 0.20.2
+ '@lexical/table': 0.20.2
+ '@lexical/utils': 0.20.2
+ lexical: 0.20.2
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ dev: false
+
+ '@lexical/dragon@0.20.2':
+ dependencies:
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/hashtag@0.20.2':
+ dependencies:
+ '@lexical/utils': 0.20.2
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/history@0.20.2':
+ dependencies:
+ '@lexical/utils': 0.20.2
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/html@0.20.2':
+ dependencies:
+ '@lexical/selection': 0.20.2
+ '@lexical/utils': 0.20.2
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/link@0.20.2':
+ dependencies:
+ '@lexical/utils': 0.20.2
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/list@0.20.2':
+ dependencies:
+ '@lexical/utils': 0.20.2
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/mark@0.20.2':
+ dependencies:
+ '@lexical/utils': 0.20.2
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/markdown@0.20.2':
+ dependencies:
+ '@lexical/code': 0.20.2
+ '@lexical/link': 0.20.2
+ '@lexical/list': 0.20.2
+ '@lexical/rich-text': 0.20.2
+ '@lexical/text': 0.20.2
+ '@lexical/utils': 0.20.2
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/offset@0.20.2':
+ dependencies:
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/overflow@0.20.2':
+ dependencies:
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/plain-text@0.20.2':
+ dependencies:
+ '@lexical/clipboard': 0.20.2
+ '@lexical/selection': 0.20.2
+ '@lexical/utils': 0.20.2
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/react@0.20.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(yjs@13.6.20)':
+ dependencies:
+ '@lexical/clipboard': 0.20.2
+ '@lexical/code': 0.20.2
+ '@lexical/devtools-core': 0.20.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@lexical/dragon': 0.20.2
+ '@lexical/hashtag': 0.20.2
+ '@lexical/history': 0.20.2
+ '@lexical/link': 0.20.2
+ '@lexical/list': 0.20.2
+ '@lexical/mark': 0.20.2
+ '@lexical/markdown': 0.20.2
+ '@lexical/overflow': 0.20.2
+ '@lexical/plain-text': 0.20.2
+ '@lexical/rich-text': 0.20.2
+ '@lexical/selection': 0.20.2
+ '@lexical/table': 0.20.2
+ '@lexical/text': 0.20.2
+ '@lexical/utils': 0.20.2
+ '@lexical/yjs': 0.20.2(yjs@13.6.20)
+ lexical: 0.20.2
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-error-boundary: 3.1.4(react@18.3.1)
+ transitivePeerDependencies:
+ - yjs
+ dev: false
+
+ '@lexical/rich-text@0.20.2':
+ dependencies:
+ '@lexical/clipboard': 0.20.2
+ '@lexical/selection': 0.20.2
+ '@lexical/utils': 0.20.2
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/selection@0.20.2':
+ dependencies:
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/table@0.20.2':
+ dependencies:
+ '@lexical/clipboard': 0.20.2
+ '@lexical/utils': 0.20.2
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/text@0.20.2':
+ dependencies:
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/utils@0.20.2':
+ dependencies:
+ '@lexical/list': 0.20.2
+ '@lexical/selection': 0.20.2
+ '@lexical/table': 0.20.2
+ lexical: 0.20.2
+ dev: false
+
+ '@lexical/yjs@0.20.2(yjs@13.6.20)':
+ dependencies:
+ '@lexical/offset': 0.20.2
+ '@lexical/selection': 0.20.2
+ lexical: 0.20.2
+ yjs: 13.6.20
+ dev: false
+
'@mapbox/node-pre-gyp@1.0.11':
dependencies:
detect-libc: 2.0.3
@@ -19713,6 +19993,9 @@ snapshots:
clsx@2.1.0:
dev: false
+ clsx@2.1.1:
+ dev: false
+
cmdk@1.0.0(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -23217,6 +23500,9 @@ snapshots:
isobject@3.0.1: {}
+ isomorphic.js@0.2.5:
+ dev: false
+
isostring@0.0.1:
dev: false
@@ -23627,6 +23913,14 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
+ lexical@0.20.2:
+ dev: false
+
+ lib0@0.2.98:
+ dependencies:
+ isomorphic.js: 0.2.5
+ dev: false
+
lighthouse-logger@1.4.2:
dependencies:
debug: 2.6.9
@@ -26691,6 +26985,12 @@ snapshots:
react: 18.3.1
dev: false
+ react-error-boundary@3.1.4(react@18.3.1):
+ dependencies:
+ '@babel/runtime': 7.26.0
+ react: 18.3.1
+ dev: false
+
react-error-overlay@6.0.11:
dev: false
@@ -29853,6 +30153,11 @@ snapshots:
fd-slicer: 1.1.0
dev: false
+ yjs@13.6.20:
+ dependencies:
+ lib0: 0.2.98
+ dev: false
+
yocto-queue@0.1.0: {}
yocto-queue@1.0.0: {}