diff --git a/frontend/src/pages/Document/DocumentEditor.jsx b/frontend/src/pages/Document/DocumentEditor.jsx index e5bae51..c6a8549 100644 --- a/frontend/src/pages/Document/DocumentEditor.jsx +++ b/frontend/src/pages/Document/DocumentEditor.jsx @@ -17,6 +17,7 @@ import { FileImageOutlined, FilePdfOutlined, FileTextOutlined, + UndoOutlined, } from '@ant-design/icons' import { Editor } from '@bytemd/react' import gfm from '@bytemd/plugin-gfm' @@ -78,13 +79,19 @@ function DocumentEditor() { return } - // 简单的从路径获取文件名 - const fileName = linkTarget.split('/').pop() - const linkText = `[${fileName}](${linkTarget})` - if (editorCtxRef.current && editorCtxRef.current.editor) { - editorCtxRef.current.editor.replaceSelection(linkText) - editorCtxRef.current.editor.focus() + const editor = editorCtxRef.current.editor + // 获取当前选中的文字 + const selection = editor.getSelection() + + // 简单的从路径获取文件名作为备选 + const fileName = linkTarget.split('/').pop() + // 如果没有选中文字,则使用文件名作为链接文字;否则保留原文字 + const linkTitle = selection || fileName + const linkText = `[${linkTitle}](${linkTarget})` + + editor.replaceSelection(linkText) + editor.focus() } setLinkModalVisible(false) @@ -191,11 +198,33 @@ function DocumentEditor() { setFileContent(res.data.content) } catch (error) { Toast.error('加载失败', '加载文件失败') - } finally { - setLoading(false) - } + } finally { + setLoading(false) } } + } + + // 重置当前编辑内容(重新从服务器加载) + const handleReset = () => { + if (!selectedFile) return + + Modal.confirm({ + title: '确认重置', + content: '确定要重置当前修改吗?所有未保存的更改都将丢失。', + onOk: async () => { + setLoading(true) + try { + const res = await getFileContent(projectId, selectedFile) + setFileContent(res.data.content) + Toast.success('重置成功', '已恢复至最后保存的版本') + } catch (error) { + Toast.error('重置失败', '无法重新加载文件内容') + } finally { + setLoading(false) + } + }, + }) + } const handleSaveFile = async () => { if (!selectedFile) { @@ -915,12 +944,11 @@ function DocumentEditor() { 保存 diff --git a/frontend/src/pages/Document/DocumentPage.jsx b/frontend/src/pages/Document/DocumentPage.jsx index 3322d72..301993c 100644 --- a/frontend/src/pages/Document/DocumentPage.jsx +++ b/frontend/src/pages/Document/DocumentPage.jsx @@ -249,21 +249,31 @@ function DocumentPage() { // 解码失败,使用原始值 } - // 解析相对路径 - const targetPath = resolveRelativePath(selectedFile, decodedHref) + // 解析路径 + let targetPath + if (decodedHref.startsWith('.') || decodedHref.startsWith('..')) { + // 真正的相对路径,相对于当前文件 + targetPath = resolveRelativePath(selectedFile, decodedHref) + } else { + // 项目内绝对路径(由编辑器生成),相对于项目根目录 + targetPath = decodedHref.startsWith('/') ? decodedHref.substring(1) : decodedHref + } // 自动展开父目录 - const parentPath = targetPath.substring(0, targetPath.lastIndexOf('/')) - if (parentPath && !openKeys.includes(parentPath)) { - // 收集所有父路径 - const pathParts = parentPath.split('/') - const allParentPaths = [] - let currentPath = '' - for (const part of pathParts) { - currentPath = currentPath ? `${currentPath}/${part}` : part - allParentPaths.push(currentPath) + const lastSlashIndex = targetPath.lastIndexOf('/') + if (lastSlashIndex !== -1) { + const parentPath = targetPath.substring(0, lastSlashIndex) + if (parentPath && !openKeys.includes(parentPath)) { + // 收集所有父路径 + const pathParts = parentPath.split('/') + const allParentPaths = [] + let currentPath = '' + for (const part of pathParts) { + currentPath = currentPath ? `${currentPath}/${part}` : part + allParentPaths.push(currentPath) + } + setOpenKeys([...new Set([...openKeys, ...allParentPaths])]) } - setOpenKeys([...new Set([...openKeys, ...allParentPaths])]) } // 选中文件并加载 @@ -590,15 +600,20 @@ function DocumentPage() { remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw, rehypeSlug, rehypeHighlight]} components={{ - a: ({ node, href, children, ...props }) => ( - handleMarkdownLink(e, href)} - {...props} - > - {children} - - ), + a: ({ node, href, children, ...props }) => { + const isExternal = href && (href.startsWith('http') || href.startsWith('//')); + return ( + handleMarkdownLink(e, href)} + target={isExternal ? '_blank' : undefined} + rel={isExternal ? 'noopener noreferrer' : undefined} + {...props} + > + {children} + + ); + }, }} > {markdownContent}