diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a05436f..a344192 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3845,7 +3845,7 @@ packages: rc-tabs: 15.0.0(react-dom@18.3.1)(react@18.3.1) rc-textarea: 1.6.3(react-dom@18.3.1)(react@18.3.1) rc-tooltip: 6.2.0(react-dom@18.3.1)(react@18.3.1) - rc-tree: 5.8.5(react-dom@18.3.1)(react@18.3.1) + rc-tree: 5.8.6(react-dom@18.3.1)(react@18.3.1) rc-tree-select: 5.20.0(react-dom@18.3.1)(react@18.3.1) rc-upload: 4.5.2(react-dom@18.3.1)(react@18.3.1) rc-util: 5.39.3(react-dom@18.3.1)(react@18.3.1) @@ -9984,7 +9984,7 @@ packages: array-tree-filter: 2.1.0 classnames: 2.5.1 rc-select: 14.13.1(react-dom@18.3.1)(react@18.3.1) - rc-tree: 5.8.5(react-dom@18.3.1)(react@18.3.1) + rc-tree: 5.8.6(react-dom@18.3.1)(react@18.3.1) rc-util: 5.39.3(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -10424,14 +10424,14 @@ packages: '@babel/runtime': 7.24.5 classnames: 2.5.1 rc-select: 14.13.1(react-dom@18.3.1)(react@18.3.1) - rc-tree: 5.8.5(react-dom@18.3.1)(react@18.3.1) + rc-tree: 5.8.6(react-dom@18.3.1)(react@18.3.1) rc-util: 5.39.3(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) dev: false - /rc-tree@5.8.5(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-PRfcZtVDNkR7oh26RuNe1hpw11c1wfgzwmPFL0lnxGnYefe9lDAO6cg5wJKIAwyXFVt5zHgpjYmaz0CPy1ZtKg==} + /rc-tree@5.8.6(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-6dtpMaWBZ/I8su1Ub2CeUYwmkKNg9YpGJqXYUDd0KYnOnrW0wCjncxpdYrf28NLPJtUwBR5dFxeGOiFox5YaHg==} engines: {node: '>=10.x'} peerDependencies: react: '*' diff --git a/src/filehandle/md_editor/component/MDContent.tsx b/src/filehandle/md_editor/component/MDContent.tsx index 0dcb6b7..3d8998c 100644 --- a/src/filehandle/md_editor/component/MDContent.tsx +++ b/src/filehandle/md_editor/component/MDContent.tsx @@ -1,9 +1,11 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { App } from 'antd'; -import { type DH, type FH, writeFile } from '@/filehandle/utils/fileManager'; +import type { DH, FH } from '@/filehandle/utils/fileManager'; +import { writeFile } from '@/filehandle/utils/fileManager'; +import type { IMDEditorExpose } from './MDEditor'; import MDEditor from './MDEditor'; import { Sidebar } from './MDSidebar'; import styles from './styles/MDContent.module.less'; @@ -19,11 +21,14 @@ interface IMDContentProps { export const MDContent: React.FC> = (props) => { const { handle } = props; - const { message } = App.useApp(); + const { message, modal } = App.useApp(); const [markdown, setMarkdown] = useState(''); + const [changed, setChanged] = useState(false); const [currentHandle, setCurrentHandle] = useState(null); + const editorRef = useRef(null); + useMemo(() => { currentHandle && getMarkdown(currentHandle).then((text) => { @@ -38,25 +43,60 @@ export const MDContent: React.FC> = (props) => { }, []); const handleSelect = (handle: FH) => { - setCurrentHandle(handle); + if (!changed) return setCurrentHandle(handle); + + modal.confirm({ + title: '温馨提示', + content: '是否保存更改?如果不保存,您的更改会丢失。', + okText: '保存', + cancelText: '放弃更改', + onOk() { + if (currentHandle && editorRef.current) { + writeFile(currentHandle, editorRef.current.getMarkdown()) + .then(() => { + setCurrentHandle(handle); + }) + .catch((err) => { + message.error((err as Error).message); + }); + } + }, + onCancel() { + setCurrentHandle(handle); + } + }); + }; + + const handleSetChanged = (changed: boolean) => { + setChanged(changed); }; const handleSave = (md: string) => { if (currentHandle) { - writeFile(currentHandle, md).catch((err) => { - message.error((err as Error).message); - }); + writeFile(currentHandle, md) + .then(() => { + setChanged(false); + }) + .catch((err) => { + message.error((err as Error).message); + }); } }; return (
{handle instanceof FileSystemDirectoryHandle ? ( - + ) : null}
- +
); diff --git a/src/filehandle/md_editor/component/MDEditor.tsx b/src/filehandle/md_editor/component/MDEditor.tsx index 9f4632c..553a478 100644 --- a/src/filehandle/md_editor/component/MDEditor.tsx +++ b/src/filehandle/md_editor/component/MDEditor.tsx @@ -1,4 +1,4 @@ -import { useMemo, useRef, useState } from 'react'; +import { forwardRef, useImperativeHandle, useMemo, useRef } from 'react'; import styles from './styles/MDEditor.module.less'; import { reduce } from './theme-reduce'; @@ -36,9 +36,15 @@ import { /* callCommand, */ getMarkdown /* insert */ } from '@milkdown/utils'; interface IMDEditorProps { markdown?: string; + changed: boolean; + onChanged(changed: boolean): any; onSave(markdown: string): any; } +export interface IMDEditorExpose { + getMarkdown(): string; +} + function createMDEditor( el: HTMLElement, value = '', @@ -85,52 +91,58 @@ function createMDEditor( const getMDString = getMarkdown(); -const MDEditor: React.FC = (props) => { - const { markdown = '', onSave } = props; +const MDEditor = forwardRef( + function MDEditor(props, ref) { + const { markdown = '', changed, onChanged, onSave } = props; - const editorContainerRef = useRef(null); - const editorRef = useRef(); - const mdStringRef = useRef(''); + const editorContainerRef = useRef(null); + const editorRef = useRef(); + const mdStringRef = useRef(''); - const [changed, setChanged] = useState(false); - - useMemo(() => { - (async () => { - if (editorRef.current) { - await editorRef.current.destroy(true); + useImperativeHandle(ref, () => ({ + getMarkdown() { + return editorRef.current ? getMDString(editorRef.current.ctx) : ''; } + })); - setChanged(false); - createMDEditor(editorContainerRef.current!, markdown, onUpdate).then( - (value) => { - editorRef.current = value; - mdStringRef.current = markdown; + useMemo(() => { + (async () => { + if (editorRef.current) { + await editorRef.current.destroy(true); } - ); - })(); - }, [markdown]); - function onUpdate(md: string) { - setChanged(true); - mdStringRef.current = md; - } + createMDEditor(editorContainerRef.current!, markdown, onUpdate).then( + (value) => { + editorRef.current = value; + mdStringRef.current = markdown; + onChanged(false); + } + ); + })(); + }, [markdown]); + + function onUpdate(md: string) { + onChanged(true); + mdStringRef.current = md; + } - const handleSave = (e: React.KeyboardEvent) => { - if (e.ctrlKey && e.key.toLocaleLowerCase() === 's') { - e.preventDefault(); + const handleSave = (e: React.KeyboardEvent) => { + if (e.ctrlKey && e.key.toLocaleLowerCase() === 's') { + e.preventDefault(); - changed && onSave(getMDString(editorRef.current!.ctx)); - } - }; - - return ( -
- ); -}; + changed && onSave(getMDString(editorRef.current!.ctx)); + } + }; + + return ( +
+ ); + } +); export default MDEditor; diff --git a/src/filehandle/md_editor/component/MDSidebar.tsx b/src/filehandle/md_editor/component/MDSidebar.tsx index b3bb159..bd88918 100644 --- a/src/filehandle/md_editor/component/MDSidebar.tsx +++ b/src/filehandle/md_editor/component/MDSidebar.tsx @@ -20,6 +20,7 @@ import styles from './styles/MDSidebar.module.less'; interface ISidebarProps { handle: DH; + changed: boolean; onSelect(handle: FH): void; } @@ -57,10 +58,12 @@ async function getDeepChildren(handle: DH): Promise { function Menu({ activeId, + changed, items, onItemClick }: { activeId: string; + changed: boolean; items: ExtendFileInfo[]; onItemClick: (file: ExtendFileInfo) => void; }) { @@ -94,7 +97,8 @@ function Menu({ className={classNames(styles.row, { [styles.active]: activeId === item.id && !expands.includes(activeId), - [styles.directory]: item.children + [styles.directory]: item.children, + [styles.changed]: changed })} onClick={ item.children @@ -118,6 +122,7 @@ function Menu({ ) : null} @@ -128,7 +133,7 @@ function Menu({ } export const Sidebar: React.FC> = (props) => { - const { handle, onSelect } = props; + const { handle, changed, onSelect } = props; const { message } = App.useApp(); @@ -154,7 +159,12 @@ export const Sidebar: React.FC> = (props) => { return ( - + ); }; diff --git a/src/filehandle/md_editor/component/styles/MDSidebar.module.less b/src/filehandle/md_editor/component/styles/MDSidebar.module.less index d22684a..3130c52 100644 --- a/src/filehandle/md_editor/component/styles/MDSidebar.module.less +++ b/src/filehandle/md_editor/component/styles/MDSidebar.module.less @@ -59,6 +59,11 @@ &:not(.directory):hover { background-color: rgb(0 0 0 / 6%); } + + &.active.changed > span::after { + content: '*'; + color: var(--color-primary); + } } }