diff --git a/apps/web/components/dashboard/bookmarks/BookmarkMarkdownComponent.tsx b/apps/web/components/dashboard/bookmarks/BookmarkMarkdownComponent.tsx
new file mode 100644
index 00000000..74eb0868
--- /dev/null
+++ b/apps/web/components/dashboard/bookmarks/BookmarkMarkdownComponent.tsx
@@ -0,0 +1,43 @@
+import MarkdownEditor from "@/components/ui/markdown/markdown-editor";
+import { MarkdownReadonly } from "@/components/ui/markdown/markdown-readonly";
+import { toast } from "@/components/ui/use-toast";
+
+import type { ZBookmarkTypeText } from "@hoarder/shared/types/bookmarks";
+import { useUpdateBookmarkText } from "@hoarder/shared-react/hooks/bookmarks";
+
+export function BookmarkMarkdownComponent({
+ children: bookmark,
+ readOnly = true,
+}: {
+ children: ZBookmarkTypeText;
+ readOnly?: boolean;
+}) {
+ const { mutate: updateBookmarkMutator, isPending } = useUpdateBookmarkText({
+ onSuccess: () => {
+ toast({
+ description: "Note updated!",
+ });
+ },
+ onError: () => {
+ toast({ description: "Something went wrong", variant: "destructive" });
+ },
+ });
+
+ const onSave = (text: string) => {
+ updateBookmarkMutator({
+ bookmarkId: bookmark.id,
+ text,
+ });
+ };
+ return (
+
+ {readOnly ? (
+ {bookmark.content.text}
+ ) : (
+
+ {bookmark.content.text}
+
+ )}
+
+ );
+}
diff --git a/apps/web/components/dashboard/bookmarks/BookmarkedTextEditor.tsx b/apps/web/components/dashboard/bookmarks/BookmarkedTextEditor.tsx
index e0434943..b2c27c7e 100644
--- a/apps/web/components/dashboard/bookmarks/BookmarkedTextEditor.tsx
+++ b/apps/web/components/dashboard/bookmarks/BookmarkedTextEditor.tsx
@@ -1,20 +1,12 @@
-import { useState } from "react";
-import { ActionButton } from "@/components/ui/action-button";
-import { Button } from "@/components/ui/button";
+import { BookmarkMarkdownComponent } from "@/components/dashboard/bookmarks/BookmarkMarkdownComponent";
import {
Dialog,
- DialogClose,
DialogContent,
- DialogDescription,
- DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
-import { Textarea } from "@/components/ui/textarea";
-import { toast } from "@/components/ui/use-toast";
-import { useUpdateBookmarkText } from "@hoarder/shared-react/hooks/bookmarks";
-import { BookmarkTypes, ZBookmark } from "@hoarder/shared/types/bookmarks";
+import { ZBookmark, ZBookmarkTypeText } from "@hoarder/shared/types/bookmarks";
export function BookmarkedTextEditor({
bookmark,
@@ -26,55 +18,20 @@ export function BookmarkedTextEditor({
setOpen: (open: boolean) => void;
}) {
const isNewBookmark = bookmark === undefined;
- const [noteText, setNoteText] = useState(
- bookmark && bookmark.content.type == BookmarkTypes.TEXT
- ? bookmark.content.text
- : "",
- );
-
- const { mutate: updateBookmarkMutator, isPending } = useUpdateBookmarkText({
- onSuccess: () => {
- toast({
- description: "Note updated!",
- });
- setOpen(false);
- },
- onError: () => {
- toast({ description: "Something went wrong", variant: "destructive" });
- },
- });
-
- const onSave = () => {
- updateBookmarkMutator({
- bookmarkId: bookmark.id,
- text: noteText,
- });
- };
return (
);
diff --git a/apps/web/components/dashboard/bookmarks/TextCard.tsx b/apps/web/components/dashboard/bookmarks/TextCard.tsx
index 14a4f905..9d168910 100644
--- a/apps/web/components/dashboard/bookmarks/TextCard.tsx
+++ b/apps/web/components/dashboard/bookmarks/TextCard.tsx
@@ -2,7 +2,7 @@
import Image from "next/image";
import Link from "next/link";
-import { MarkdownComponent } from "@/components/ui/markdown-component";
+import { BookmarkMarkdownComponent } from "@/components/dashboard/bookmarks/BookmarkMarkdownComponent";
import { bookmarkLayoutSwitch } from "@/lib/userLocalSettings/bookmarksLayout";
import { cn } from "@/lib/utils";
@@ -20,15 +20,16 @@ export default function TextCard({
bookmark: ZBookmarkTypeText;
className?: string;
}) {
- const bookmarkedText = bookmark.content;
-
const banner = bookmark.assets.find((a) => a.assetType == "bannerImage");
-
return (
<>
{bookmarkedText.text}}
+ content={
+
+ {bookmark}
+
+ }
footer={
getSourceUrl(bookmark) && (
diff --git a/apps/web/components/dashboard/preview/TextContentSection.tsx b/apps/web/components/dashboard/preview/TextContentSection.tsx
index 327436c6..a58bc717 100644
--- a/apps/web/components/dashboard/preview/TextContentSection.tsx
+++ b/apps/web/components/dashboard/preview/TextContentSection.tsx
@@ -1,7 +1,8 @@
import Image from "next/image";
-import { MarkdownComponent } from "@/components/ui/markdown-component";
+import { BookmarkMarkdownComponent } from "@/components/dashboard/bookmarks/BookmarkMarkdownComponent";
import { ScrollArea } from "@radix-ui/react-scroll-area";
+import type { ZBookmarkTypeText } from "@hoarder/shared/types/bookmarks";
import { getAssetUrl } from "@hoarder/shared-react/utils/assetUtils";
import { BookmarkTypes, ZBookmark } from "@hoarder/shared/types/bookmarks";
@@ -27,7 +28,9 @@ export function TextContentSection({ bookmark }: { bookmark: ZBookmark }) {
/>
)}
- {bookmark.content.text}
+
+ {bookmark as ZBookmarkTypeText}
+
);
}
diff --git a/apps/web/components/ui/markdown/markdown-editor.tsx b/apps/web/components/ui/markdown/markdown-editor.tsx
new file mode 100644
index 00000000..85f0c878
--- /dev/null
+++ b/apps/web/components/ui/markdown/markdown-editor.tsx
@@ -0,0 +1,124 @@
+import { memo, useMemo, useState } from "react";
+import ToolbarPlugin from "@/components/ui/markdown/plugins/toolbar-plugin";
+import { MarkdownEditorTheme } from "@/components/ui/markdown/theme";
+import {
+ CodeHighlightNode,
+ CodeNode,
+ registerCodeHighlighting,
+} from "@lexical/code";
+import { LinkNode } from "@lexical/link";
+import { ListItemNode, ListNode } from "@lexical/list";
+import {
+ $convertFromMarkdownString,
+ $convertToMarkdownString,
+ TRANSFORMERS,
+} from "@lexical/markdown";
+import { AutoFocusPlugin } from "@lexical/react/LexicalAutoFocusPlugin";
+import {
+ InitialConfigType,
+ LexicalComposer,
+} from "@lexical/react/LexicalComposer";
+import { ContentEditable } from "@lexical/react/LexicalContentEditable";
+import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
+import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
+import { HorizontalRuleNode } from "@lexical/react/LexicalHorizontalRuleNode";
+import { ListPlugin } from "@lexical/react/LexicalListPlugin";
+import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
+import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
+import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
+import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
+import { TabIndentationPlugin } from "@lexical/react/LexicalTabIndentationPlugin";
+import { HeadingNode, QuoteNode } from "@lexical/rich-text";
+import { $getRoot, EditorState, LexicalEditor } from "lexical";
+
+function onError(error: Error) {
+ console.error(error);
+}
+
+const EDITOR_NODES = [
+ HeadingNode,
+ ListNode,
+ ListItemNode,
+ QuoteNode,
+ LinkNode,
+ CodeNode,
+ HorizontalRuleNode,
+ CodeHighlightNode,
+];
+
+interface MarkdownEditorProps {
+ children: string;
+ onSave?: (markdown: string) => void;
+ isSaving?: boolean;
+}
+
+const MarkdownEditor = memo(
+ ({ children: initialMarkdown, onSave, isSaving }: MarkdownEditorProps) => {
+ const [isRawMarkdownMode, setIsRawMarkdownMode] = useState(false);
+ const [rawMarkdown, setRawMarkdown] = useState(initialMarkdown);
+
+ const initialConfig: InitialConfigType = useMemo(
+ () => ({
+ namespace: "editor",
+ onError,
+ theme: MarkdownEditorTheme,
+ nodes: EDITOR_NODES,
+ editorState: (editor: LexicalEditor) => {
+ registerCodeHighlighting(editor);
+ $convertFromMarkdownString(initialMarkdown, TRANSFORMERS);
+ },
+ }),
+ [initialMarkdown],
+ );
+
+ const handleOnChange = (editorState: EditorState) => {
+ editorState.read(() => {
+ let markdownString;
+ if (isRawMarkdownMode) {
+ markdownString = $getRoot()?.getFirstChild()?.getTextContent() ?? "";
+ } else {
+ markdownString = $convertToMarkdownString(TRANSFORMERS);
+ }
+ setRawMarkdown(markdownString);
+ });
+ };
+
+ return (
+
+
+
onSave(rawMarkdown))}
+ isSaving={!!isSaving}
+ />
+ {isRawMarkdownMode ? (
+
+ }
+ ErrorBoundary={LexicalErrorBoundary}
+ />
+ ) : (
+
+ }
+ ErrorBoundary={LexicalErrorBoundary}
+ />
+ )}
+
+
+
+
+
+
+
+
+ );
+ },
+);
+// needed for linter because of memo
+MarkdownEditor.displayName = "MarkdownEditor";
+
+export default MarkdownEditor;
diff --git a/apps/web/components/ui/markdown-component.tsx b/apps/web/components/ui/markdown/markdown-readonly.tsx
similarity index 92%
rename from apps/web/components/ui/markdown-component.tsx
rename to apps/web/components/ui/markdown/markdown-readonly.tsx
index d3c832ac..29077480 100644
--- a/apps/web/components/ui/markdown-component.tsx
+++ b/apps/web/components/ui/markdown/markdown-readonly.tsx
@@ -1,61 +1,57 @@
-import React from "react";
-import CopyBtn from "@/components/ui/copy-button";
-import { cn } from "@/lib/utils";
-import Markdown from "react-markdown";
-import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
-import { dracula } from "react-syntax-highlighter/dist/cjs/styles/prism";
-import remarkBreaks from "remark-breaks";
-import remarkGfm from "remark-gfm";
-
-function PreWithCopyBtn({ className, ...props }: React.ComponentProps<"pre">) {
- const ref = React.useRef(null);
- return (
-
- {
- return ref.current?.textContent ?? "";
- }}
- />
-
-
- );
-}
-
-export function MarkdownComponent({
- children: markdown,
-}: {
- children: string;
-}) {
- return (
- ;
- },
- code({ className, children, ...props }) {
- const match = /language-(\w+)/.exec(className ?? "");
- return match ? (
- // @ts-expect-error -- Refs are not compatible for some reason
-
- {String(children).replace(/\n$/, "")}
-
- ) : (
-
- {children}
-
- );
- },
- }}
- >
- {markdown}
-
- );
-}
+import React from "react";
+import CopyBtn from "@/components/ui/copy-button";
+import { cn } from "@/lib/utils";
+import Markdown from "react-markdown";
+import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
+import { dracula } from "react-syntax-highlighter/dist/cjs/styles/prism";
+import remarkBreaks from "remark-breaks";
+import remarkGfm from "remark-gfm";
+
+function PreWithCopyBtn({ className, ...props }: React.ComponentProps<"pre">) {
+ const ref = React.useRef(null);
+ return (
+
+ {
+ return ref.current?.textContent ?? "";
+ }}
+ />
+
+
+ );
+}
+
+export function MarkdownReadonly({ children: markdown }: { children: string }) {
+ return (
+ ;
+ },
+ code({ className, children, ...props }) {
+ const match = /language-(\w+)/.exec(className ?? "");
+ return match ? (
+ // @ts-expect-error -- Refs are not compatible for some reason
+
+ {String(children).replace(/\n$/, "")}
+
+ ) : (
+
+ {children}
+
+ );
+ },
+ }}
+ >
+ {markdown}
+
+ );
+}
diff --git a/apps/web/components/ui/markdown/plugins/toolbar-plugin.tsx b/apps/web/components/ui/markdown/plugins/toolbar-plugin.tsx
new file mode 100644
index 00000000..28e265e2
--- /dev/null
+++ b/apps/web/components/ui/markdown/plugins/toolbar-plugin.tsx
@@ -0,0 +1,290 @@
+import { useCallback, useEffect, useState } from "react";
+import { Button } from "@/components/ui/button";
+import { useTranslation } from "@/lib/i18n/client";
+import {
+ $convertFromMarkdownString,
+ $convertToMarkdownString,
+ TRANSFORMERS,
+} from "@lexical/markdown";
+import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
+import { mergeRegister } from "@lexical/utils";
+import {
+ $createParagraphNode,
+ $createTextNode,
+ $getRoot,
+ $getSelection,
+ $isRangeSelection,
+ FORMAT_TEXT_COMMAND,
+ LexicalCommand,
+ SELECTION_CHANGE_COMMAND,
+ TextFormatType,
+} from "lexical";
+import {
+ Bold,
+ Code,
+ Highlighter,
+ Italic,
+ LucideIcon,
+ Save,
+ Strikethrough,
+} from "lucide-react";
+
+import { ActionButton } from "../../action-button";
+import InfoTooltip from "../../info-tooltip";
+import { Label } from "../../label";
+import { Switch } from "../../switch";
+
+const LowPriority = 1;
+
+function MarkdownToolTip() {
+ const { t } = useTranslation();
+ return (
+
+
+
+
+ {t("editor.text_toolbar.markdown_shortcuts.label")} |
+
+
+
+
+
+ {t("editor.text_toolbar.markdown_shortcuts.heading.label")}
+ |
+
+ {t("editor.text_toolbar.markdown_shortcuts.heading.example")}
+ |
+
+
+
+ {t("editor.text_toolbar.markdown_shortcuts.bold.label")}
+ |
+
+ {t("editor.text_toolbar.markdown_shortcuts.bold.example")}
+ |
+
+
+
+ {t("editor.text_toolbar.markdown_shortcuts.italic.label")}
+ |
+
+ {t("editor.text_toolbar.markdown_shortcuts.italic.example")}
+ |
+
+
+
+ {t("editor.text_toolbar.markdown_shortcuts.blockquote.label")}
+ |
+
+ {t("editor.text_toolbar.markdown_shortcuts.blockquote.example")}
+ |
+
+
+
+ {t("editor.text_toolbar.markdown_shortcuts.ordered_list.label")}
+ |
+
+ {t("editor.text_toolbar.markdown_shortcuts.ordered_list.example")}
+ |
+
+
+
+ {t("editor.text_toolbar.markdown_shortcuts.unordered_list.label")}
+ |
+
+ {t(
+ "editor.text_toolbar.markdown_shortcuts.unordered_list.example",
+ )}
+ |
+
+
+
+ {t("editor.text_toolbar.markdown_shortcuts.inline_code.label")}
+ |
+
+ {t("editor.text_toolbar.markdown_shortcuts.inline_code.example")}
+ |
+
+
+
+ {t("editor.text_toolbar.markdown_shortcuts.block_code.label")}
+ |
+
+ {t("editor.text_toolbar.markdown_shortcuts.block_code.example")}
+ |
+
+
+
+
+ );
+}
+
+export default function ToolbarPlugin({
+ isRawMarkdownMode = false,
+ setIsRawMarkdownMode,
+ onSave,
+ isSaving,
+}: {
+ isRawMarkdownMode: boolean;
+ setIsRawMarkdownMode: (value: boolean) => void;
+ onSave?: () => void;
+ isSaving: boolean;
+}) {
+ const { t } = useTranslation();
+ const [editor] = useLexicalComposerContext();
+ const [editorToolbarState, setEditorToolbarState] = useState<{
+ isBold: boolean;
+ isItalic: boolean;
+ isStrikethrough: boolean;
+ isHighlight: boolean;
+ isCode: boolean;
+ }>({
+ isBold: false,
+ isItalic: false,
+ isStrikethrough: false,
+ isHighlight: false,
+ isCode: false,
+ });
+
+ const $updateToolbar = useCallback(() => {
+ const selection = $getSelection();
+ if ($isRangeSelection(selection)) {
+ setEditorToolbarState({
+ isBold: selection.hasFormat("bold"),
+ isItalic: selection.hasFormat("italic"),
+ isStrikethrough: selection.hasFormat("strikethrough"),
+ isHighlight: selection.hasFormat("highlight"),
+ isCode: selection.hasFormat("code"),
+ });
+ }
+ }, []);
+
+ useEffect(() => {
+ return mergeRegister(
+ editor.registerUpdateListener(({ editorState }) => {
+ editorState.read(() => {
+ $updateToolbar();
+ });
+ }),
+ editor.registerCommand(
+ SELECTION_CHANGE_COMMAND,
+ (_payload, _newEditor) => {
+ $updateToolbar();
+ return false;
+ },
+ LowPriority,
+ ),
+ );
+ }, [editor, $updateToolbar]);
+
+ const formatButtons: {
+ command: LexicalCommand;
+ format: TextFormatType;
+ isActive?: boolean;
+ icon: LucideIcon;
+ label: string;
+ }[] = [
+ {
+ command: FORMAT_TEXT_COMMAND,
+ format: "bold",
+ icon: Bold,
+ isActive: editorToolbarState.isBold,
+ label: t("editor.text_toolbar.bold"),
+ },
+ {
+ command: FORMAT_TEXT_COMMAND,
+ format: "italic",
+ icon: Italic,
+ isActive: editorToolbarState.isItalic,
+ label: t("editor.text_toolbar.italic"),
+ },
+ {
+ command: FORMAT_TEXT_COMMAND,
+ format: "strikethrough",
+ icon: Strikethrough,
+ isActive: editorToolbarState.isStrikethrough,
+ label: t("editor.text_toolbar.strikethrough"),
+ },
+ {
+ command: FORMAT_TEXT_COMMAND,
+ format: "code",
+ icon: Code,
+ isActive: editorToolbarState.isCode,
+ label: t("editor.text_toolbar.code"),
+ },
+ {
+ command: FORMAT_TEXT_COMMAND,
+ format: "highlight",
+ icon: Highlighter,
+ isActive: editorToolbarState.isHighlight,
+ label: t("editor.text_toolbar.highlight"),
+ },
+ ];
+
+ const handleRawMarkdownToggle = useCallback(() => {
+ editor.update(() => {
+ console.log(isRawMarkdownMode);
+ const root = $getRoot();
+ const firstChild = root.getFirstChild();
+ if (isRawMarkdownMode) {
+ if (firstChild) {
+ $convertFromMarkdownString(firstChild.getTextContent(), TRANSFORMERS);
+ }
+ setIsRawMarkdownMode(false);
+ } else {
+ const markdown = $convertToMarkdownString(TRANSFORMERS);
+ const pNode = $createParagraphNode();
+ pNode.append($createTextNode(markdown));
+ root.clear().append(pNode);
+ setIsRawMarkdownMode(true);
+ }
+ });
+ }, [editor, isRawMarkdownMode]);
+
+ return (
+
+
+ {formatButtons.map(
+ ({ command, format, icon: Icon, isActive, label }) => (
+
+ ),
+ )}
+
+
+
+
+
+
+ {onSave && (
+
{
+ onSave?.();
+ }}
+ >
+
+ Save
+
+ )}
+
+
+
+ );
+}
diff --git a/apps/web/components/ui/markdown/theme.ts b/apps/web/components/ui/markdown/theme.ts
new file mode 100644
index 00000000..ff088e32
--- /dev/null
+++ b/apps/web/components/ui/markdown/theme.ts
@@ -0,0 +1,35 @@
+export const MarkdownEditorTheme = {
+ code: "bg-[#282A36] text-[#F8F8F2] font-mono block px-4 py-2 my-2 text-sm overflow-x-auto relative rounded-md shadow-sm",
+ codeHighlight: {
+ atrule: "text-[#8BE9FD]",
+ attr: "text-[#8BE9FD]",
+ boolean: "text-[#FF79C6]",
+ builtin: "text-[#50FA7B]",
+ cdata: "text-[#6272A4]",
+ char: "text-[#50FA7B]",
+ class: "text-[#FF79C6]",
+ "class-name": "text-[#FF79C6]",
+ comment: "text-[#6272A4]",
+ constant: "text-[#FF79C6]",
+ deleted: "text-[#FF5555]",
+ doctype: "text-[#6272A4]",
+ entity: "text-[#FFB86C]",
+ function: "text-[#50FA7B]",
+ important: "text-[#F1FA8C]",
+ inserted: "text-[#50FA7B]",
+ keyword: "text-[#FF79C6]",
+ namespace: "text-[#F1FA8C]",
+ number: "text-[#BD93F9]",
+ operator: "text-[#FFB86C]",
+ prolog: "text-[#6272A4]",
+ property: "text-[#FFB86C]",
+ punctuation: "text-[#F8F8F2]",
+ regex: "text-[#FF5555]",
+ selector: "text-[#50FA7B]",
+ string: "text-[#F1FA8C]",
+ symbol: "text-[#FF79C6]",
+ tag: "text-[#FF79C6]",
+ url: "text-[#8BE9FD]",
+ variable: "text-[#F1FA8C]",
+ },
+};
diff --git a/apps/web/components/ui/switch.tsx b/apps/web/components/ui/switch.tsx
new file mode 100644
index 00000000..2438dc86
--- /dev/null
+++ b/apps/web/components/ui/switch.tsx
@@ -0,0 +1,28 @@
+"use client";
+
+import * as React from "react";
+import { cn } from "@/lib/utils";
+import * as SwitchPrimitives from "@radix-ui/react-switch";
+
+const Switch = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+));
+Switch.displayName = SwitchPrimitives.Root.displayName;
+
+export { Switch };
diff --git a/apps/web/lib/i18n/locales/de/translation.json b/apps/web/lib/i18n/locales/de/translation.json
index 40a167ad..d744dc97 100644
--- a/apps/web/lib/i18n/locales/de/translation.json
+++ b/apps/web/lib/i18n/locales/de/translation.json
@@ -180,7 +180,20 @@
"import_as_separate_bookmarks": "Als separate Lesezeichen importieren",
"placeholder": "Fügen Sie einen Link oder ein Bild ein, schreiben Sie eine Notiz oder ziehen Sie ein Bild hierher ...",
"new_item": "NEUER EINTRAG",
- "disabled_submissions": "Einsendungen sind deaktiviert"
+ "disabled_submissions": "Einsendungen sind deaktiviert",
+ "text_toolbar": {
+ "undo": "Rückgängig",
+ "redo": "Wiederholen",
+ "bold": "Fett",
+ "italic": "Kursiv",
+ "underline": "Unterstrichen",
+ "strikethrough": "Durchgestrichen",
+ "code": "Code",
+ "highlight": "Hervorheben",
+ "align_left": "Linksbündig",
+ "align_center": "Zentriert",
+ "align_right": "Rechtsbündig"
+ }
},
"toasts": {
"bookmarks": {
diff --git a/apps/web/lib/i18n/locales/en/translation.json b/apps/web/lib/i18n/locales/en/translation.json
index 530d489a..9fbf0fd3 100644
--- a/apps/web/lib/i18n/locales/en/translation.json
+++ b/apps/web/lib/i18n/locales/en/translation.json
@@ -180,7 +180,55 @@
"import_as_separate_bookmarks": "Import as separate Bookmarks",
"placeholder": "Paste a link or an image, write a note or drag and drop an image in here ...",
"new_item": "NEW ITEM",
- "disabled_submissions": "Submissions are disabled"
+ "disabled_submissions": "Submissions are disabled",
+ "text_toolbar": {
+ "undo": "Undo",
+ "redo": "Redo",
+ "bold": "Bold",
+ "italic": "Italic",
+ "underline": "Underline",
+ "strikethrough": "Strikethrough",
+ "code": "Code",
+ "highlight": "Highlight",
+ "align_left": "Left Align",
+ "align_center": "Center Align",
+ "align_right": "Right Align",
+ "markdown_shortcuts": {
+ "label": "Markdown shortcuts",
+ "heading": {
+ "label": "Heading",
+ "example": "# H1, ## H2, ### H3"
+ },
+ "bold": {
+ "label": "Bold",
+ "example": "**text** or CTRL+b"
+ },
+ "italic": {
+ "label": "Italic",
+ "example": "*Italic* or _Italic_ or CTRL+i"
+ },
+ "blockquote": {
+ "label": "Blockquote",
+ "example": "> Blockquote"
+ },
+ "ordered_list": {
+ "label": "Ordered List",
+ "example": "1. List item"
+ },
+ "unordered_list": {
+ "label": "Unordered List",
+ "example": "- List item"
+ },
+ "inline_code": {
+ "label": "Inline Code",
+ "example": "`Code`"
+ },
+ "block_code": {
+ "label": "Block Code",
+ "example": "``` + space"
+ }
+ }
+ }
},
"toasts": {
"bookmarks": {
diff --git a/apps/web/lib/i18n/locales/fr/translation.json b/apps/web/lib/i18n/locales/fr/translation.json
index d369ad44..142e9148 100644
--- a/apps/web/lib/i18n/locales/fr/translation.json
+++ b/apps/web/lib/i18n/locales/fr/translation.json
@@ -180,7 +180,20 @@
"import_as_separate_bookmarks": "Importer comme favoris séparés",
"placeholder": "Collez un lien ou une image, écrivez une note ou glissez-déposez une image ici ...",
"new_item": "NOUVEL ÉLÉMENT",
- "disabled_submissions": "Les soumissions sont désactivées"
+ "disabled_submissions": "Les soumissions sont désactivées",
+ "text_toolbar": {
+ "undo": "Annuler",
+ "redo": "Rétablir",
+ "bold": "Gras",
+ "italic": "Italique",
+ "underline": "Souligné",
+ "strikethrough": "Barré",
+ "code": "Code",
+ "highlight": "Surligner",
+ "align_left": "Aligner à gauche",
+ "align_center": "Aligner au centre",
+ "align_right": "Aligner à droite"
+ }
},
"toasts": {
"bookmarks": {
diff --git a/apps/web/package.json b/apps/web/package.json
index 3affe516..7edae7a8 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -23,6 +23,11 @@
"@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/plain-text": "^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",
@@ -33,6 +38,7 @@
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
+ "@radix-ui/react-switch": "^1.1.2",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-toggle": "^1.0.3",
@@ -53,6 +59,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..959c9871 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -478,6 +478,21 @@ 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/plain-text':
+ 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)
@@ -508,6 +523,9 @@ importers:
'@radix-ui/react-slot':
specifier: ^1.0.2
version: 1.0.2(@types/react@18.2.58)(react@18.3.1)
+ '@radix-ui/react-switch':
+ specifier: ^1.1.2
+ version: 1.1.2(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-tabs':
specifier: ^1.0.4
version: 1.0.4(@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 +586,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 +3199,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
@@ -3335,6 +3427,9 @@ packages:
'@radix-ui/primitive@1.0.1':
resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==}
+ '@radix-ui/primitive@1.1.1':
+ resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==}
+
'@radix-ui/react-arrow@1.0.3':
resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==}
peerDependencies:
@@ -3397,6 +3492,15 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-compose-refs@1.1.1':
+ resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@radix-ui/react-context@1.0.1':
resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==}
peerDependencies:
@@ -3415,6 +3519,15 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-context@1.1.1':
+ resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@radix-ui/react-dialog@1.0.5':
resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==}
peerDependencies:
@@ -3598,6 +3711,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-primitive@2.0.1':
+ resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==}
+ 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-progress@1.1.0':
resolution: {integrity: sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==}
peerDependencies:
@@ -3686,6 +3812,28 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-slot@1.1.1':
+ resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-switch@1.1.2':
+ resolution: {integrity: sha512-zGukiWHjEdBCRyXvKR6iXAQG6qXm2esuAD6kDOi9Cn+1X6ev3ASo4+CsYaD6Fov9r/AQFekqnD/7+V0Cs6/98g==}
+ 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-tabs@1.0.4':
resolution: {integrity: sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==}
peerDependencies:
@@ -3747,6 +3895,15 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-use-callback-ref@1.1.0':
+ resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@radix-ui/react-use-controllable-state@1.0.1':
resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==}
peerDependencies:
@@ -3756,6 +3913,15 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-use-controllable-state@1.1.0':
+ resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@radix-ui/react-use-escape-keydown@1.0.3':
resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==}
peerDependencies:
@@ -3774,6 +3940,15 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-use-layout-effect@1.1.0':
+ resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@radix-ui/react-use-previous@1.0.1':
resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==}
peerDependencies:
@@ -3783,6 +3958,15 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-use-previous@1.1.0':
+ resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@radix-ui/react-use-rect@1.0.1':
resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==}
peerDependencies:
@@ -3801,6 +3985,15 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-use-size@1.1.0':
+ resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@radix-ui/react-visually-hidden@1.0.3':
resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==}
peerDependencies:
@@ -4902,6 +5095,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 +5910,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 +8555,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 +8824,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 +10987,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 +13260,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 +15420,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 +15464,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 +15493,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 +16536,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
@@ -16549,6 +16936,9 @@ snapshots:
'@babel/runtime': 7.26.0
dev: false
+ '@radix-ui/primitive@1.1.1':
+ dev: false
+
'@radix-ui/react-arrow@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)':
dependencies:
'@babel/runtime': 7.26.0
@@ -16608,6 +16998,12 @@ snapshots:
react: 18.3.1
dev: false
+ '@radix-ui/react-compose-refs@1.1.1(@types/react@18.2.58)(react@18.3.1)':
+ dependencies:
+ '@types/react': 18.2.58
+ react: 18.3.1
+ dev: false
+
'@radix-ui/react-context@1.0.1(@types/react@18.2.58)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.26.0
@@ -16621,6 +17017,12 @@ snapshots:
react: 18.3.1
dev: false
+ '@radix-ui/react-context@1.1.1(@types/react@18.2.58)(react@18.3.1)':
+ dependencies:
+ '@types/react': 18.2.58
+ react: 18.3.1
+ dev: false
+
'@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)':
dependencies:
'@babel/runtime': 7.23.9
@@ -16828,6 +17230,15 @@ snapshots:
react-dom: 18.3.1(react@18.3.1)
dev: false
+ '@radix-ui/react-primitive@2.0.1(@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-slot': 1.1.1(@types/react@18.2.58)(react@18.3.1)
+ '@types/react': 18.2.58
+ '@types/react-dom': 18.2.19
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ dev: false
+
'@radix-ui/react-progress@1.1.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-context': 1.1.0(@types/react@18.2.58)(react@18.3.1)
@@ -16936,6 +17347,28 @@ snapshots:
react: 18.3.1
dev: false
+ '@radix-ui/react-slot@1.1.1(@types/react@18.2.58)(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.58)(react@18.3.1)
+ '@types/react': 18.2.58
+ react: 18.3.1
+ dev: false
+
+ '@radix-ui/react-switch@1.1.2(@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/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.58)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.2.58)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.19)(@types/react@18.2.58)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.58)(react@18.3.1)
+ '@radix-ui/react-use-previous': 1.1.0(@types/react@18.2.58)(react@18.3.1)
+ '@radix-ui/react-use-size': 1.1.0(@types/react@18.2.58)(react@18.3.1)
+ '@types/react': 18.2.58
+ '@types/react-dom': 18.2.19
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ dev: false
+
'@radix-ui/react-tabs@1.0.4(@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:
'@babel/runtime': 7.23.9
@@ -17014,6 +17447,12 @@ snapshots:
react: 18.3.1
dev: false
+ '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.2.58)(react@18.3.1)':
+ dependencies:
+ '@types/react': 18.2.58
+ react: 18.3.1
+ dev: false
+
'@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.58)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.26.0
@@ -17022,6 +17461,13 @@ snapshots:
react: 18.3.1
dev: false
+ '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.2.58)(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.58)(react@18.3.1)
+ '@types/react': 18.2.58
+ react: 18.3.1
+ dev: false
+
'@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.2.58)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.26.0
@@ -17037,6 +17483,12 @@ snapshots:
react: 18.3.1
dev: false
+ '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.2.58)(react@18.3.1)':
+ dependencies:
+ '@types/react': 18.2.58
+ react: 18.3.1
+ dev: false
+
'@radix-ui/react-use-previous@1.0.1(@types/react@18.2.58)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.26.0
@@ -17044,6 +17496,12 @@ snapshots:
react: 18.3.1
dev: false
+ '@radix-ui/react-use-previous@1.1.0(@types/react@18.2.58)(react@18.3.1)':
+ dependencies:
+ '@types/react': 18.2.58
+ react: 18.3.1
+ dev: false
+
'@radix-ui/react-use-rect@1.0.1(@types/react@18.2.58)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.26.0
@@ -17060,6 +17518,13 @@ snapshots:
react: 18.3.1
dev: false
+ '@radix-ui/react-use-size@1.1.0(@types/react@18.2.58)(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.58)(react@18.3.1)
+ '@types/react': 18.2.58
+ react: 18.3.1
+ dev: false
+
'@radix-ui/react-visually-hidden@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)':
dependencies:
'@babel/runtime': 7.26.0
@@ -19713,6 +20178,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 +23685,9 @@ snapshots:
isobject@3.0.1: {}
+ isomorphic.js@0.2.5:
+ dev: false
+
isostring@0.0.1:
dev: false
@@ -23627,6 +24098,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 +27170,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 +30338,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: {}