-
-
Notifications
You must be signed in to change notification settings - Fork 380
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* #701 Improve note support : WYSIWYG markdown First implementation with a wysiwyg markdown editor. Update: - Add Lexical markdown editor - consistent rendering between card and preview - removed edit modal, replaced by preview with save action - simple markdown shortcut: underline, bold, italic etc... * #701 Improve note support : WYSIWYG markdown improved performance to not rerender all note card when one is updated * Use markdown shortcuts * Remove the alignment actions * Drop history buttons * Fix code and highlighting buttons * Remove the unneeded update markdown plugin * Remove underline support as it's not markdown native * - added ListPlugin because if absent, there's a bug where you can't escape a list with enter + enter - added codeblock plugin - added prose dark:prose-invert prose-p:m-0 like you said (there's room for improvement I think, don't took the time too deep dive in) and removed theme - Added a switch to show raw markdown - Added back the react markdown for card (SSR) * delete theme.ts * add theme back for code element to be more like prism theme from markdown-readonly * move the new editor back to the edit menu * move the bookmark markdown component into dashboard/bookmark * move the tooltip into its own component * move save button to toolbar * Better raw markdown --------- Co-authored-by: Giuseppe Lapenta <giuseppe.lapenta@enovacom.com> Co-authored-by: Mohamed Bassem <me@mbassem.com>
- Loading branch information
1 parent
16f2ce3
commit 40c5262
Showing
14 changed files
with
1,177 additions
and
129 deletions.
There are no files selected for viewing
43 changes: 43 additions & 0 deletions
43
apps/web/components/dashboard/bookmarks/BookmarkMarkdownComponent.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<div className="h-full overflow-hidden"> | ||
{readOnly ? ( | ||
<MarkdownReadonly>{bookmark.content.text}</MarkdownReadonly> | ||
) : ( | ||
<MarkdownEditor onSave={onSave} isSaving={isPending}> | ||
{bookmark.content.text} | ||
</MarkdownEditor> | ||
)} | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<LexicalComposer initialConfig={initialConfig}> | ||
<div className="flex h-full flex-col justify-stretch"> | ||
<ToolbarPlugin | ||
isRawMarkdownMode={isRawMarkdownMode} | ||
setIsRawMarkdownMode={setIsRawMarkdownMode} | ||
onSave={onSave && (() => onSave(rawMarkdown))} | ||
isSaving={!!isSaving} | ||
/> | ||
{isRawMarkdownMode ? ( | ||
<PlainTextPlugin | ||
contentEditable={ | ||
<ContentEditable className="h-full w-full min-w-full overflow-auto p-2" /> | ||
} | ||
ErrorBoundary={LexicalErrorBoundary} | ||
/> | ||
) : ( | ||
<RichTextPlugin | ||
contentEditable={ | ||
<ContentEditable className="prose h-full w-full min-w-full overflow-auto p-2 dark:prose-invert prose-p:m-0" /> | ||
} | ||
ErrorBoundary={LexicalErrorBoundary} | ||
/> | ||
)} | ||
</div> | ||
<HistoryPlugin /> | ||
<AutoFocusPlugin /> | ||
<TabIndentationPlugin /> | ||
<MarkdownShortcutPlugin transformers={TRANSFORMERS} /> | ||
<OnChangePlugin onChange={handleOnChange} /> | ||
<ListPlugin /> | ||
</LexicalComposer> | ||
); | ||
}, | ||
); | ||
// needed for linter because of memo | ||
MarkdownEditor.displayName = "MarkdownEditor"; | ||
|
||
export default MarkdownEditor; |
Oops, something went wrong.