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() {
保存
}
- onClick={handleDelete}
- disabled={!selectedFile}
+ icon={}
+ onClick={handleReset}
+ disabled={!selectedFile || loading}
>
- 删除
+ 重置
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}