diff --git a/frontend/src/pages/pages-tree.md b/frontend/src/pages/pages-tree.md index c0297c1317..85d46c7740 100644 --- a/frontend/src/pages/pages-tree.md +++ b/frontend/src/pages/pages-tree.md @@ -78,19 +78,17 @@ https://github.com/haiwen/seahub/pull/7273 │ ├── editor-api.js 处理文件下载和上传 │ └── index.js 画板编辑器的套壳,处理快捷键,保存内容等 - -—————————————————————————————————————————————————没有查看————————————————————————————————————————————————— - -## markdown 普通字符编辑器(刘宏博 2024) +## 02 markdown 普通字符编辑器(刘宏博 2024 重构) https://github.com/haiwen/seahub/pull/5998 ├── plain-markdown-editor -│ ├── code-mirror.css -│ ├── code-mirror.js -│ ├── helper.js -│ ├── index.js -│ └── style.css +│ ├── code-mirror.js codemirror 代码阅读器定制后效果 +│ ├── helper.js 获取文件信息的 API 封装后的函数 +│ ├── index.js 普通文本编辑器入口,左侧是格式化编辑代码,右侧显示预览 + + +—————————————————————————————————————————————————没有查看————————————————————————————————————————————————— ## markdown 富文本编辑器 diff --git a/frontend/src/pages/plain-markdown-editor/code-mirror.js b/frontend/src/pages/plain-markdown-editor/code-mirror.js index 3fe4d323bf..0aa0a26812 100644 --- a/frontend/src/pages/plain-markdown-editor/code-mirror.js +++ b/frontend/src/pages/plain-markdown-editor/code-mirror.js @@ -20,14 +20,20 @@ class SeafileCodeMirror extends React.Component { componentDidMount() { const { initialValue, autoFocus } = this.props; + // 初始化编辑器 this.view = new EditorView({ doc: initialValue, extensions: [ + // basicSetup: 一个基本的编辑器配置,包括了光标、选择、滚动条等基本功能 basicSetup, + // markdown: markdown 语言的解析器 + // languages: 一个对象,key 是语言的名称,value 是语言对应的解析器 markdown({ codeLanguages: languages }), + // EditorView.updateListener: 一个监听器,每当编辑器的状态更新时,会被调用 EditorView.updateListener.of((viewUpdate) => { this.onValueChanged(viewUpdate); }), + // EditorView.lineWrapping: 使得编辑器支持自动换行 EditorView.lineWrapping ], parent: this.codeMirrorRef, @@ -42,7 +48,9 @@ class SeafileCodeMirror extends React.Component { } componentDidUpdate(prevProps) { + // 父节点重新渲染时,初始化变化,重新渲染编辑器 if (!prevProps.initialValue && prevProps.initialValue !== this.props.initialValue) { + // 用新的值替换全部旧的值 this.view.dispatch({ changes: { from: 0, @@ -53,6 +61,7 @@ class SeafileCodeMirror extends React.Component { } } + // 双向的值的变化 onValueChanged = (viewUpdate) => { const { onChange } = this.props; if (onChange && viewUpdate.docChanged) { diff --git a/frontend/src/pages/plain-markdown-editor/helper.js b/frontend/src/pages/plain-markdown-editor/helper.js index 48d958e2c0..6d8473a6e7 100644 --- a/frontend/src/pages/plain-markdown-editor/helper.js +++ b/frontend/src/pages/plain-markdown-editor/helper.js @@ -1,18 +1,21 @@ import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; +// API 获取文档信息 const getFileInfo = async (repoID, filePath) => { const fileInfoRes = await seafileAPI.getFileInfo(repoID, filePath); const { mtime, size, starred, permission, last_modifier_name: lastModifier, id } = fileInfoRes.data; return { mtime, size, starred, permission, lastModifier, id }; }; +// API 获取下载链接 const getFileDownloadUrl = async (repoID, filePath) => { const fileDownloadUrlRes = await seafileAPI.getFileDownloadLink(repoID, filePath); const downloadUrl = fileDownloadUrlRes.data; return downloadUrl; }; +// API 获取权限 const setPermission = async (permission, repoID) => { let hasPermission = permission === 'rw' || permission === 'cloud-edit'; // get custom permission @@ -26,12 +29,14 @@ const setPermission = async (permission, repoID) => { return hasPermission; }; +// API 获取文档内容 const setFileContent = async (downloadUrl) => { const fileContentRes = await seafileAPI.getFileContent(downloadUrl); const markdownContent = fileContentRes.data; return markdownContent; }; +// 获取全部信息入口 export const getPlainOptions = async ({ fileName, filePath, repoID }) => { const fileIcon = Utils.getFileIconUrl(fileName); document.getElementById('favicon').href = fileIcon; @@ -40,5 +45,15 @@ export const getPlainOptions = async ({ fileName, filePath, repoID }) => { const markdownContent = await setFileContent(downloadUrl); const hasPermission = await setPermission(fileInfo.permission, repoID); + // 早期是多个 await 串行执行,可能消耗较多时间;可以改成多个 API 同时执行,减少网络请求的时间 + // const [fileInfo, downloadUrl] = await Promise.all([ + // getFileInfo(repoID, filePath), + // getFileDownloadUrl(repoID, filePath), + // ]); + // const [markdownContent, hasPermission] = await Promise.all([ + // setFileContent(downloadUrl), + // setPermission(fileInfo.permission, repoID), + // ]); + return { markdownContent, hasPermission, fileInfo }; }; diff --git a/frontend/src/pages/plain-markdown-editor/index.js b/frontend/src/pages/plain-markdown-editor/index.js index 0de1ab275a..af2594ce5e 100644 --- a/frontend/src/pages/plain-markdown-editor/index.js +++ b/frontend/src/pages/plain-markdown-editor/index.js @@ -56,16 +56,29 @@ const initOptions = { }; const PlainMarkdownEditor = (props) => { + // 编辑器的值 const [editorValue, setEditorValue] = useState(''); + // 预览组件的值 const [previewValue, setPreviewValue] = useState(''); + + // 判断鼠标在左侧还是在右侧面板内(决定其他快捷键交互等) const [isMouseInLeftSide, setIsMouseInLeftSide] = useState(false); const [isMouseInRightSide, setIsMouseInRightSide] = useState(false); + + // 滚动位置(左右两个面板同步滚动) const [scrollPercentage, setScrollPercentage] = useState(0); + + // 获取左右两个滚动面板的 DOM,进一步获取位置信息 const leftPanelRef = useRef(null); const rightPanelRef = useRef(null); + + // 编辑器属性(包括文档信息,编辑器模式等信息) const [options, setOptions] = useState(initOptions); + + // 设置保存状态 const [saving, setSaving] = useState(false); + // 保存时,设置编辑器信息(markdown格式),以及转换成 HTML 信息 const setContent = useCallback((markdownContent) => { setEditorValue(markdownContent); processor.process(markdownContent, (error, vfile) => { @@ -74,6 +87,7 @@ const PlainMarkdownEditor = (props) => { }); }, []); + // 更新编辑器属性(界面初始化后,用全局变量更新具体的文档信息-获取文档信息,用户权限,文档下载链接等 API 操作,然后更新到当前状态) const updateOptions = useCallback(async ({ fileName, filePath, repoID }) => { const { markdownContent, hasPermission, fileInfo } = await getPlainOptions({ fileName, filePath, repoID }); setContent(markdownContent); @@ -88,11 +102,13 @@ const PlainMarkdownEditor = (props) => { }, [options, setContent]); // 注意:useLayoutEffect 在浏览器绘制 DOM 之后执行,而 useEffect 在浏览器绘制 DOM 之前执行。 + // 这里确定首先加载编辑器基本骨架,然后网络请求获取内容,避免全部白屏Loading情况 useLayoutEffect(() => { updateOptions({ fileName, filePath, repoID }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // 离开页面前,询问是否保存 const onUnload = useCallback((event) => { const { contentChanged } = options; if (!contentChanged) return; @@ -102,6 +118,7 @@ const PlainMarkdownEditor = (props) => { return confirmationMessage; }, [options]); + // 离开页面前,询问是否保存 useEffect(() => { window.addEventListener('beforeunload', onUnload); return () => { @@ -109,11 +126,13 @@ const PlainMarkdownEditor = (props) => { }; }, [onUnload]); + // 在代码编辑器中,更新编辑器内容,这里是回调函数 const updateCode = useCallback((newCode) => { setContent(String(newCode)); !options.onContentChanged && setOptions({ ...options, contentChanged: true }); }, [options, setContent]); + // 鼠标事件,滚动事件处理 const onEnterLeftPanel = useCallback(() => { setIsMouseInLeftSide(true); setIsMouseInRightSide(false); @@ -149,17 +168,21 @@ const PlainMarkdownEditor = (props) => { leftPanelElm.scrollTop = scrollPercentage * leftPanelElm.scrollHeight; }, [isMouseInRightSide, scrollPercentage]); + // 更新文件信息 const updateFileInfoMtime = useCallback((fileInfo) => { const { fileInfo: oldFileInfo } = options; const newFileInfo = Object.assign({}, oldFileInfo, { mtime: fileInfo.mtime, id: fileInfo.id, lastModifier: fileInfo.last_modifier_name }); return newFileInfo; }, [options]); + // 保存编辑器内容 const onSaveEditorContent = useCallback(() => { setSaving(true); let fileInfo = options.fileInfo; + // 先保存内容 editorApi.saveContent(editorValue).then(() => { + // 重新获取文件信息(例如上次保存事件) editorApi.getFileInfo().then((res) => { fileInfo = updateFileInfoMtime(res.data); setOptions({ @@ -178,6 +201,7 @@ const PlainMarkdownEditor = (props) => { }); }, [editorValue, options, setOptions, updateFileInfoMtime]); + // 快捷键 const onHotKey = useCallback((event) => { if (isHotkey('mod+s', event)) { event.preventDefault(); @@ -186,6 +210,7 @@ const PlainMarkdownEditor = (props) => { } }, [editorValue, onSaveEditorContent]); + // 切换收藏 const toggleStar = useCallback(() => { const starred = options.fileInfo.starred; const newFileInfo = Object.assign({}, options.fileInfo, { starred: !starred }); @@ -201,17 +226,20 @@ const PlainMarkdownEditor = (props) => { }); }, [options]); + // 切换到纯文本编辑器 const setEditorMode = useCallback(() => { const { origin, pathname } = window.location; window.location.href = origin + pathname; }, []); + // 空回调函数 const ignoreCallBack = useCallback(() => void 0, []); if (options.loading) return ; return ( <> + {/* 普通文本和富文本,公共的工具栏,包括保存,星标,历史入口,分享,锁定等功能 */} { /> + {/* 编辑器内部,左侧是编辑器,右侧是预览 */} { onScroll={onRightScroll} > + {/* 预览 previewValue */}