diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index 34b86eaa..ff76064e 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next"; import useLocalStorage from "react-use/lib/useLocalStorage"; import { memoServiceClient } from "@/grpcweb"; import { TAB_SPACE_WIDTH, UNKNOWN_ID } from "@/helpers/consts"; +import { isValidUrl } from "@/helpers/utils"; import { useGlobalStore, useResourceStore } from "@/store/module"; import { useMemoStore, useUserStore } from "@/store/v1"; import { MemoRelation, MemoRelation_Type } from "@/types/proto/api/v2/memo_relation_service"; @@ -115,6 +116,20 @@ const MemoEditor = (props: Props) => { handleSaveBtnClick(); return; } + if (event.key === "b") { + const boldDelimiter = "**"; + event.preventDefault(); + styleHighlightedText(editorRef.current, boldDelimiter); + } + if (event.key === "i") { + const italicsDelimiter = "*"; + event.preventDefault(); + styleHighlightedText(editorRef.current, italicsDelimiter); + } + if (event.key === "k") { + event.preventDefault(); + hyperlinkHighlightedTest(editorRef.current); + } } if (event.key === "Tab") { event.preventDefault(); @@ -239,6 +254,9 @@ const MemoEditor = (props: Props) => { if (event.clipboardData && event.clipboardData.files.length > 0) { event.preventDefault(); await uploadMultiFiles(event.clipboardData.files); + } else if (editorRef.current != null && isValidUrl(event.clipboardData.getData("Text"))) { + event.preventDefault(); + hyperlinkHighlightedTest(editorRef.current, event.clipboardData.getData("Text")); } }; @@ -339,6 +357,33 @@ const MemoEditor = (props: Props) => { editorRef.current?.focus(); }; + const styleHighlightedText = (editor: EditorRefActions, delimiter: string) => { + const cursorPosition = editor.getCursorPosition(); + const selectedContent = editor.getSelectedContent(); + if (selectedContent.startsWith(delimiter) && selectedContent.endsWith(delimiter)) { + editor.insertText(selectedContent.slice(delimiter.length, -delimiter.length)); + const newContentLength = selectedContent.length - delimiter.length * 2; + editor.setCursorPosition(cursorPosition, cursorPosition + newContentLength); + } else { + editor.insertText(`${delimiter}${selectedContent}${delimiter}`); + editor.setCursorPosition(cursorPosition + delimiter.length, cursorPosition + delimiter.length + selectedContent.length); + } + }; + + const hyperlinkHighlightedTest = (editor: EditorRefActions, url?: string) => { + const cursorPosition = editor.getCursorPosition(); + const selectedContent = editor.getSelectedContent(); + const blankURL = "url"; + url = url ?? blankURL; + + editor.insertText(`[${selectedContent}](${url})`); + + if (url === blankURL) { + const newCursorStart = cursorPosition + selectedContent.length + 3; + editor.setCursorPosition(newCursorStart, newCursorStart + url.length); + } + }; + const editorConfig = useMemo( () => ({ className: editorClassName ?? "", diff --git a/web/src/helpers/utils.ts b/web/src/helpers/utils.ts index b03b20c8..8ed11e6d 100644 --- a/web/src/helpers/utils.ts +++ b/web/src/helpers/utils.ts @@ -83,3 +83,12 @@ export const formatBytes = (bytes: number) => { i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; }; + +export const isValidUrl = (url: string): boolean => { + try { + new URL(url); + return true; + } catch (err) { + return false; + } +};