From 7ff7491dab468eb5c062971c023d7a39e295bf8e Mon Sep 17 00:00:00 2001 From: nasum Date: Sun, 7 Apr 2024 00:17:25 +0900 Subject: [PATCH] close: #11 fix link --- .../src/components/common/editor/Editor.tsx | 15 +++- .../common/editor/plugins/LinkPlugin.ts | 82 +++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/common/editor/plugins/LinkPlugin.ts diff --git a/frontend/src/components/common/editor/Editor.tsx b/frontend/src/components/common/editor/Editor.tsx index 9040303..9af1966 100644 --- a/frontend/src/components/common/editor/Editor.tsx +++ b/frontend/src/components/common/editor/Editor.tsx @@ -17,7 +17,6 @@ import { LexicalComposer } from "@lexical/react/LexicalComposer"; import { ContentEditable } from "@lexical/react/LexicalContentEditable"; import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary"; import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin"; -import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin"; import { ListPlugin } from "@lexical/react/LexicalListPlugin"; import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin"; import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin"; @@ -26,7 +25,9 @@ import { TabIndentationPlugin } from "@lexical/react/LexicalTabIndentationPlugin import { HeadingNode, QuoteNode } from "@lexical/rich-text"; import { TableCellNode, TableNode, TableRowNode } from "@lexical/table"; import { EditorState } from "lexical"; + import CodeHighlightPlugin from "./plugins/CodeHighlight"; +import LinkPlugin from "./plugins/LinkPlugin"; import { Theme } from "./theme/Theme"; import "github-markdown-css"; @@ -47,6 +48,18 @@ const EditorContainer = styled.div` :where(ol) { list-style: decimal; } + + li[role="checkbox"] { + cursor: pointer; + span { + cursor: auto; + } + } + + a { + text-decoration: none; + cursor: pointer; + } `; const EditorActions = styled.div` diff --git a/frontend/src/components/common/editor/plugins/LinkPlugin.ts b/frontend/src/components/common/editor/plugins/LinkPlugin.ts new file mode 100644 index 0000000..199ca2b --- /dev/null +++ b/frontend/src/components/common/editor/plugins/LinkPlugin.ts @@ -0,0 +1,82 @@ +import { useEffect } from "react"; +import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext"; +import { $isLinkNode } from "@lexical/link"; +import type { LexicalEditor } from "lexical"; +import { $getNearestNodeFromDOMNode } from "lexical"; +import { BrowserOpenURL } from "../../../../../wailsjs/runtime"; + +export default function LinkPlugin() { + const [editor] = useLexicalComposerContext(); + + useEffect(() => { + const onClick = (e: MouseEvent) => { + const linkDomNode = getLinkDomNode(e, editor); + + if (linkDomNode === null) { + return; + } + + const href = linkDomNode.getAttribute("href"); + + let linkNode = null; + editor.update(() => { + const maybeLinkNode = $getNearestNodeFromDOMNode(linkDomNode); + + if ($isLinkNode(maybeLinkNode)) { + linkNode = maybeLinkNode; + } + }); + + try { + if (href !== null) { + BrowserOpenURL(href); + e.preventDefault(); + } + } catch (e) { + console.error(e); + } + }; + + return editor.registerRootListener( + ( + rootElement: null | HTMLElement, + prevRootElement: null | HTMLElement, + ) => { + if (prevRootElement !== null) { + prevRootElement.removeEventListener("click", onClick); + prevRootElement.removeEventListener("auxclick", onClick); + } + + if (rootElement !== null) { + rootElement.addEventListener("click", onClick); + rootElement.addEventListener("auxclick", onClick); + } + }, + ); + }, [editor]); + + return null; +} + +function isLinkDomNode(domNode: Node): boolean { + return domNode.nodeName.toLowerCase() === "a"; +} + +function getLinkDomNode( + event: MouseEvent | PointerEvent, + editor: LexicalEditor, +): HTMLAnchorElement | null { + return editor.getEditorState().read(() => { + const domNode = event.target as Node; + + if (isLinkDomNode(domNode)) { + return domNode as HTMLAnchorElement; + } + + if (domNode.parentNode && isLinkDomNode(domNode.parentNode)) { + return domNode.parentNode as HTMLAnchorElement; + } + + return null; + }); +}