mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
feat: editor enhancement for order list (#763)
This commit is contained in:
@ -1,5 +1,4 @@
|
|||||||
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef } from "react";
|
import { forwardRef, ReactNode, useCallback, useEffect, useImperativeHandle, useRef } from "react";
|
||||||
import useRefresh from "../../hooks/useRefresh";
|
|
||||||
import "../../less/editor.less";
|
import "../../less/editor.less";
|
||||||
|
|
||||||
export interface EditorRefActions {
|
export interface EditorRefActions {
|
||||||
@ -10,6 +9,7 @@ export interface EditorRefActions {
|
|||||||
getContent: () => string;
|
getContent: () => string;
|
||||||
getSelectedContent: () => string;
|
getSelectedContent: () => string;
|
||||||
getCursorPosition: () => number;
|
getCursorPosition: () => number;
|
||||||
|
setCursorPosition: (pos: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -25,7 +25,6 @@ interface Props {
|
|||||||
const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef<EditorRefActions>) {
|
const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef<EditorRefActions>) {
|
||||||
const { className, initialContent, placeholder, fullscreen, onPaste, onContentChange: handleContentChangeCallback } = props;
|
const { className, initialContent, placeholder, fullscreen, onPaste, onContentChange: handleContentChangeCallback } = props;
|
||||||
const editorRef = useRef<HTMLTextAreaElement>(null);
|
const editorRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const refresh = useRefresh();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editorRef.current && initialContent) {
|
if (editorRef.current && initialContent) {
|
||||||
@ -36,10 +35,16 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef<
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editorRef.current && !fullscreen) {
|
if (editorRef.current && !fullscreen) {
|
||||||
|
updateEditorHeight();
|
||||||
|
}
|
||||||
|
}, [editorRef.current?.value, fullscreen]);
|
||||||
|
|
||||||
|
const updateEditorHeight = () => {
|
||||||
|
if (editorRef.current) {
|
||||||
editorRef.current.style.height = "auto";
|
editorRef.current.style.height = "auto";
|
||||||
editorRef.current.style.height = (editorRef.current.scrollHeight ?? 0) + "px";
|
editorRef.current.style.height = (editorRef.current.scrollHeight ?? 0) + "px";
|
||||||
}
|
}
|
||||||
}, [editorRef.current?.value, fullscreen]);
|
};
|
||||||
|
|
||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
ref,
|
ref,
|
||||||
@ -66,7 +71,7 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef<
|
|||||||
editorRef.current.focus();
|
editorRef.current.focus();
|
||||||
editorRef.current.selectionEnd = endPosition + prefix.length + content.length;
|
editorRef.current.selectionEnd = endPosition + prefix.length + content.length;
|
||||||
handleContentChangeCallback(editorRef.current.value);
|
handleContentChangeCallback(editorRef.current.value);
|
||||||
refresh();
|
updateEditorHeight();
|
||||||
},
|
},
|
||||||
removeText: (start: number, length: number) => {
|
removeText: (start: number, length: number) => {
|
||||||
if (!editorRef.current) {
|
if (!editorRef.current) {
|
||||||
@ -79,14 +84,14 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef<
|
|||||||
editorRef.current.focus();
|
editorRef.current.focus();
|
||||||
editorRef.current.selectionEnd = start;
|
editorRef.current.selectionEnd = start;
|
||||||
handleContentChangeCallback(editorRef.current.value);
|
handleContentChangeCallback(editorRef.current.value);
|
||||||
refresh();
|
updateEditorHeight();
|
||||||
},
|
},
|
||||||
setContent: (text: string) => {
|
setContent: (text: string) => {
|
||||||
if (editorRef.current) {
|
if (editorRef.current) {
|
||||||
editorRef.current.value = text;
|
editorRef.current.value = text;
|
||||||
editorRef.current.focus();
|
editorRef.current.focus();
|
||||||
handleContentChangeCallback(editorRef.current.value);
|
handleContentChangeCallback(editorRef.current.value);
|
||||||
refresh();
|
updateEditorHeight();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getContent: (): string => {
|
getContent: (): string => {
|
||||||
@ -100,13 +105,16 @@ const Editor = forwardRef(function Editor(props: Props, ref: React.ForwardedRef<
|
|||||||
const end = editorRef.current?.selectionEnd;
|
const end = editorRef.current?.selectionEnd;
|
||||||
return editorRef.current?.value.slice(start, end) ?? "";
|
return editorRef.current?.value.slice(start, end) ?? "";
|
||||||
},
|
},
|
||||||
|
setCursorPosition: (pos: number) => {
|
||||||
|
editorRef.current?.setSelectionRange(pos, pos);
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleEditorInput = useCallback(() => {
|
const handleEditorInput = useCallback(() => {
|
||||||
handleContentChangeCallback(editorRef.current?.value ?? "");
|
handleContentChangeCallback(editorRef.current?.value ?? "");
|
||||||
refresh();
|
updateEditorHeight();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -15,6 +15,7 @@ import showResourcesSelectorDialog from "./ResourcesSelectorDialog";
|
|||||||
import "../less/memo-editor.less";
|
import "../less/memo-editor.less";
|
||||||
|
|
||||||
const listItemSymbolList = ["- [ ] ", "- [x] ", "- [X] ", "* ", "- "];
|
const listItemSymbolList = ["- [ ] ", "- [x] ", "- [X] ", "* ", "- "];
|
||||||
|
const emptyOlReg = /^([1-9][0-9]*)\. $/;
|
||||||
|
|
||||||
const getEditorContentCache = (): string => {
|
const getEditorContentCache = (): string => {
|
||||||
return storage.get(["editorContentCache"]).editorContentCache ?? "";
|
return storage.get(["editorContentCache"]).editorContentCache ?? "";
|
||||||
@ -124,17 +125,52 @@ const MemoEditor = () => {
|
|||||||
const contentBeforeCursor = editorRef.current.getContent().slice(0, cursorPosition);
|
const contentBeforeCursor = editorRef.current.getContent().slice(0, cursorPosition);
|
||||||
const rowValue = last(contentBeforeCursor.split("\n"));
|
const rowValue = last(contentBeforeCursor.split("\n"));
|
||||||
if (rowValue) {
|
if (rowValue) {
|
||||||
if (listItemSymbolList.includes(rowValue)) {
|
if (listItemSymbolList.includes(rowValue) || emptyOlReg.test(rowValue)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
editorRef.current.removeText(cursorPosition - rowValue.length, rowValue.length);
|
editorRef.current.removeText(cursorPosition - rowValue.length, rowValue.length);
|
||||||
} else {
|
} else {
|
||||||
|
// unordered list / checked list
|
||||||
|
let matched = false;
|
||||||
for (const listItemSymbol of listItemSymbolList) {
|
for (const listItemSymbol of listItemSymbolList) {
|
||||||
if (rowValue.startsWith(listItemSymbol)) {
|
if (rowValue.startsWith(listItemSymbol)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
editorRef.current.insertText("", `\n${listItemSymbol}`);
|
editorRef.current.insertText("", `\n${listItemSymbol}`);
|
||||||
|
matched = true;
|
||||||
break;
|
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;
|
return;
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
import { useCallback, useState } from "react";
|
|
||||||
|
|
||||||
const useRefresh = () => {
|
|
||||||
const [, setBoolean] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const refresh = useCallback(() => {
|
|
||||||
setBoolean((ps) => {
|
|
||||||
return !ps;
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return refresh;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useRefresh;
|
|
Reference in New Issue
Block a user