diff --git a/web/src/components/Editor/Editor.tsx b/web/src/components/Editor/Editor.tsx index 8c27dd27..110accd6 100644 --- a/web/src/components/Editor/Editor.tsx +++ b/web/src/components/Editor/Editor.tsx @@ -1,5 +1,4 @@ import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef } from "react"; -import useRefresh from "../../hooks/useRefresh"; import "../../less/editor.less"; export interface EditorRefActions { @@ -10,6 +9,7 @@ export interface EditorRefActions { getContent: () => string; getSelectedContent: () => string; getCursorPosition: () => number; + setCursorPosition: (pos: number) => void; } interface Props { @@ -25,7 +25,6 @@ interface Props { const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef) { const { className, initialContent, placeholder, fullscreen, onPaste, onContentChange: handleContentChangeCallback } = props; const editorRef = useRef(null); - const refresh = useRefresh(); useEffect(() => { if (editorRef.current && initialContent) { @@ -36,10 +35,16 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef< useEffect(() => { if (editorRef.current && !fullscreen) { + updateEditorHeight(); + } + }, [editorRef.current?.value, fullscreen]); + + const updateEditorHeight = () => { + if (editorRef.current) { editorRef.current.style.height = "auto"; editorRef.current.style.height = (editorRef.current.scrollHeight ?? 0) + "px"; } - }, [editorRef.current?.value, fullscreen]); + }; useImperativeHandle( ref, @@ -66,7 +71,7 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef< editorRef.current.focus(); editorRef.current.selectionEnd = endPosition + prefix.length + content.length; handleContentChangeCallback(editorRef.current.value); - refresh(); + updateEditorHeight(); }, removeText: (start: number, length: number) => { if (!editorRef.current) { @@ -79,14 +84,14 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef< editorRef.current.focus(); editorRef.current.selectionEnd = start; handleContentChangeCallback(editorRef.current.value); - refresh(); + updateEditorHeight(); }, setContent: (text: string) => { if (editorRef.current) { editorRef.current.value = text; editorRef.current.focus(); handleContentChangeCallback(editorRef.current.value); - refresh(); + updateEditorHeight(); } }, getContent: (): string => { @@ -100,13 +105,16 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef< const end = editorRef.current?.selectionEnd; return editorRef.current?.value.slice(start, end) ?? ""; }, + setCursorPosition: (pos: number) => { + editorRef.current?.setSelectionRange(pos, pos); + }, }), [] ); const handleEditorInput = useCallback(() => { handleContentChangeCallback(editorRef.current?.value ?? ""); - refresh(); + updateEditorHeight(); }, []); return ( diff --git a/web/src/components/MemoEditor.tsx b/web/src/components/MemoEditor.tsx index 120a6508..04f94606 100644 --- a/web/src/components/MemoEditor.tsx +++ b/web/src/components/MemoEditor.tsx @@ -15,6 +15,7 @@ import showResourcesSelectorDialog from "./ResourcesSelectorDialog"; import "../less/memo-editor.less"; const listItemSymbolList = ["- [ ] ", "- [x] ", "- [X] ", "* ", "- "]; +const emptyOlReg = /^([1-9][0-9]*)\. $/; const getEditorContentCache = (): string => { return storage.get(["editorContentCache"]).editorContentCache ?? ""; @@ -124,17 +125,52 @@ const MemoEditor = () => { const contentBeforeCursor = editorRef.current.getContent().slice(0, cursorPosition); const rowValue = last(contentBeforeCursor.split("\n")); if (rowValue) { - if (listItemSymbolList.includes(rowValue)) { + if (listItemSymbolList.includes(rowValue) || emptyOlReg.test(rowValue)) { event.preventDefault(); editorRef.current.removeText(cursorPosition - rowValue.length, rowValue.length); } else { + // unordered list / checked list + let matched = false; for (const listItemSymbol of listItemSymbolList) { if (rowValue.startsWith(listItemSymbol)) { event.preventDefault(); editorRef.current.insertText("", `\n${listItemSymbol}`); + matched = true; break; } } + if (!matched) { + // ordered list + const olReg = /^([1-9][0-9]*)\. /; + const olRes = olReg.exec(rowValue); + if (olRes) { + let order = parseInt(olRes[1]) + 1; + event.preventDefault(); + const contentAfterCursor = editorRef.current.getContent().slice(cursorPosition); + editorRef.current.insertText("", `\n${order}. `); + if (contentAfterCursor) { + // correct the order + order++; + const nextRows = contentAfterCursor.split("\n").slice(1); + const rowStart = contentBeforeCursor.split("\n").length + 1; + const content = editorRef.current.getContent().split("\n"); + let updated = false; + for (let i = 0; i < nextRows.length; i++) { + const rowRes = olReg.exec(nextRows[i]); + if (rowRes) { + content[rowStart + i] = nextRows[i].replace(rowRes[1], (order + i).toString()); + updated = true; + } else { + break; + } + } + if (updated) { + editorRef.current.setContent(content.join("\n")); + editorRef.current.setCursorPosition(cursorPosition + 4); + } + } + } + } } } return; diff --git a/web/src/hooks/useRefresh.ts b/web/src/hooks/useRefresh.ts deleted file mode 100644 index e892d9b6..00000000 --- a/web/src/hooks/useRefresh.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useCallback, useState } from "react"; - -const useRefresh = () => { - const [, setBoolean] = useState(false); - - const refresh = useCallback(() => { - setBoolean((ps) => { - return !ps; - }); - }, []); - - return refresh; -}; - -export default useRefresh;