memos/web/src/components/Editor/Editor.tsx
2021-12-08 23:43:52 +08:00

169 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useRef } from "react";
import TinyUndo from "tiny-undo";
import appContext from "../../stores/appContext";
import { storage } from "../../helpers/storage";
import useRefresh from "../../hooks/useRefresh";
import Only from "../common/OnlyWhen";
import "../../less/editor.less";
export interface EditorRefActions {
focus: FunctionType;
insertText: (text: string) => void;
setContent: (text: string) => void;
getContent: () => string;
}
interface Props {
className: string;
initialContent: string;
placeholder: string;
showConfirmBtn: boolean;
showCancelBtn: boolean;
showTools: boolean;
onConfirmBtnClick: (content: string) => void;
onCancelBtnClick: () => void;
onContentChange: (content: string) => void;
}
const Editor = forwardRef((props: Props, ref: React.ForwardedRef<EditorRefActions>) => {
const {
globalState: { useTinyUndoHistoryCache },
} = useContext(appContext);
const {
className,
initialContent,
placeholder,
showConfirmBtn,
showCancelBtn,
showTools,
onConfirmBtnClick: handleConfirmBtnClickCallback,
onCancelBtnClick: handleCancelBtnClickCallback,
onContentChange: handleContentChangeCallback,
} = props;
const editorRef = useRef<HTMLTextAreaElement>(null);
const tinyUndoRef = useRef<TinyUndo | null>(null);
const refresh = useRefresh();
useEffect(() => {
if (initialContent) {
editorRef.current!.value = initialContent;
refresh();
}
}, []);
useEffect(() => {
if (useTinyUndoHistoryCache) {
const { tinyUndoActionsCache, tinyUndoIndexCache } = storage.get(["tinyUndoActionsCache", "tinyUndoIndexCache"]);
tinyUndoRef.current = new TinyUndo(editorRef.current!, {
interval: 5000,
initialActions: tinyUndoActionsCache,
initialIndex: tinyUndoIndexCache,
});
tinyUndoRef.current.subscribe((actions, index) => {
storage.set({
tinyUndoActionsCache: actions,
tinyUndoIndexCache: index,
});
});
return () => {
tinyUndoRef.current?.destroy();
};
} else {
tinyUndoRef.current?.destroy();
tinyUndoRef.current = null;
storage.remove(["tinyUndoActionsCache", "tinyUndoIndexCache"]);
}
}, [useTinyUndoHistoryCache]);
useEffect(() => {
if (editorRef.current) {
editorRef.current.style.height = "auto";
editorRef.current.style.height = (editorRef.current.scrollHeight ?? 0) + "px";
}
}, [editorRef.current?.value]);
useImperativeHandle(
ref,
() => ({
focus: () => {
editorRef.current!.focus();
},
insertText: (rawText: string) => {
const prevValue = editorRef.current!.value;
editorRef.current!.value = prevValue + rawText;
handleContentChangeCallback(editorRef.current!.value);
refresh();
},
setContent: (text: string) => {
editorRef.current!.value = text;
refresh();
},
getContent: (): string => {
return editorRef.current?.value ?? "";
},
}),
[]
);
const handleEditorInput = useCallback(() => {
handleContentChangeCallback(editorRef.current!.value);
refresh();
}, []);
const handleEditorKeyDown = useCallback((event: React.KeyboardEvent<HTMLTextAreaElement>) => {
event.stopPropagation();
if (event.code === "Enter") {
if (event.metaKey || event.ctrlKey) {
handleCommonConfirmBtnClick();
}
}
refresh();
}, []);
const handleCommonConfirmBtnClick = useCallback(() => {
handleConfirmBtnClickCallback(editorRef.current!.value);
editorRef.current!.value = "";
refresh();
// After confirm btn clicked, tiny-undo should reset state(clear actions and index)
tinyUndoRef.current?.resetState();
}, []);
const handleCommonCancelBtnClick = useCallback(() => {
handleCancelBtnClickCallback();
}, []);
return (
<div className={"common-editor-wrapper " + className}>
<textarea
className="common-editor-inputer"
rows={1}
placeholder={placeholder}
ref={editorRef}
onInput={handleEditorInput}
onKeyDown={handleEditorKeyDown}
></textarea>
<div className="common-tools-wrapper">
<Only when={showTools}>
<div className={"common-tools-container"}>{/* nth */}</div>
</Only>
<div className="btns-container">
<Only when={showCancelBtn}>
<button className="action-btn cancel-btn" onClick={handleCommonCancelBtnClick}>
撤销修改
</button>
</Only>
<Only when={showConfirmBtn}>
<button className="action-btn confirm-btn" disabled={!editorRef.current?.value} onClick={handleCommonConfirmBtnClick}>
记下<span className="icon-text">✍️</span>
</button>
</Only>
</div>
</div>
</div>
);
});
export default Editor;