v0.9.7
parent
ccb61b00fa
commit
5256d20ac9
|
|
@ -203,8 +203,8 @@
|
|||
min-height: 57px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: var(--header-bg);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
|
@ -454,14 +454,8 @@
|
|||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
/* 文件名过长时显示省略号,不折行 */
|
||||
.content-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 600px;
|
||||
color: var(--text-color);
|
||||
.content-header .preview-header-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,28 @@ function DocumentEditor() {
|
|||
const editorCtxRef = useRef(null)
|
||||
const modeSwitchingRef = useRef(false)
|
||||
|
||||
const isHeaderPdf = selectedFile?.toLowerCase().endsWith('.pdf')
|
||||
const HeaderIcon = isHeaderPdf ? FilePdfOutlined : FileTextOutlined
|
||||
const headerLabel = selectedFile ? selectedFile.split('/').filter(Boolean).pop() : '请选择文件'
|
||||
|
||||
const linkTreeData = useMemo(() => {
|
||||
const markSelectable = (nodes) => nodes.map((node) => {
|
||||
if (node.children?.length) {
|
||||
return {
|
||||
...node,
|
||||
selectable: false,
|
||||
children: markSelectable(node.children),
|
||||
}
|
||||
}
|
||||
return {
|
||||
...node,
|
||||
selectable: true,
|
||||
}
|
||||
})
|
||||
|
||||
return markSelectable(treeData)
|
||||
}, [treeData])
|
||||
|
||||
const navigateWithTransition = (to) => {
|
||||
if (document.startViewTransition) {
|
||||
document.startViewTransition(() => navigate(to))
|
||||
|
|
@ -97,6 +119,20 @@ function DocumentEditor() {
|
|||
setSearchParams(nextParams, { replace: true })
|
||||
}
|
||||
|
||||
const encodeMarkdownLinkTarget = (targetPath) => {
|
||||
if (!targetPath) return targetPath
|
||||
|
||||
return targetPath
|
||||
.split('/')
|
||||
.map((part) => {
|
||||
if (!part || part === '.' || part === '..') {
|
||||
return part
|
||||
}
|
||||
return encodeURIComponent(part)
|
||||
})
|
||||
.join('/')
|
||||
}
|
||||
|
||||
// 插入内链接
|
||||
const handleInsertLink = () => {
|
||||
if (!linkTarget) {
|
||||
|
|
@ -113,7 +149,7 @@ function DocumentEditor() {
|
|||
const fileName = linkTarget.split('/').pop()
|
||||
// 如果没有选中文字,则使用文件名作为链接文字;否则保留原文字
|
||||
const linkTitle = selection || fileName
|
||||
const linkText = `[${linkTitle}](${linkTarget})`
|
||||
const linkText = `[${linkTitle}](${encodeMarkdownLinkTarget(linkTarget)})`
|
||||
|
||||
editor.replaceSelection(linkText)
|
||||
editor.focus()
|
||||
|
|
@ -1068,7 +1104,10 @@ function DocumentEditor() {
|
|||
|
||||
<Content className="document-content">
|
||||
<div className="content-header">
|
||||
<h3>{selectedFile || '请选择文件'}</h3>
|
||||
<h3 className="preview-header-title">
|
||||
<HeaderIcon className="preview-header-icon" style={isHeaderPdf ? { color: '#f5222d' } : undefined} />
|
||||
<span className="preview-header-text">{headerLabel}</span>
|
||||
</h3>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
|
|
@ -1187,15 +1226,16 @@ function DocumentEditor() {
|
|||
style={{ width: '100%' }}
|
||||
value={linkTarget}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
treeData={treeData}
|
||||
treeData={linkTreeData}
|
||||
placeholder="请选择文件"
|
||||
treeDefaultExpandAll
|
||||
onChange={setLinkTarget}
|
||||
fieldNames={{ label: 'title', value: 'key', children: 'children' }}
|
||||
showSearch
|
||||
filterTreeNode={(inputValue, treeNode) => {
|
||||
return treeNode.title.toLowerCase().indexOf(inputValue.toLowerCase()) >= 0
|
||||
return Boolean(treeNode.isLeaf) && treeNode.title.toLowerCase().indexOf(inputValue.toLowerCase()) >= 0
|
||||
}}
|
||||
allowClear
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -267,15 +267,38 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.docs-content-header h3 {
|
||||
margin: 0;
|
||||
.docs-header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
gap: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.docs-header-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
flex: 0 1 auto;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.docs-header-icon {
|
||||
font-size: 15px;
|
||||
color: var(--text-color-secondary);
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.docs-header-text {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.docs-content-wrapper {
|
||||
|
|
|
|||
|
|
@ -69,6 +69,19 @@ function DocumentPage() {
|
|||
const contentRef = useRef(null)
|
||||
const modeSwitchingRef = useRef(false)
|
||||
|
||||
const getHeaderDisplay = (filePath) => {
|
||||
const resolvedPath = filePath || 'README.md'
|
||||
const fileName = resolvedPath.split('/').filter(Boolean).pop() || 'README.md'
|
||||
const isPdf = fileName.toLowerCase().endsWith('.pdf')
|
||||
const FileIcon = isPdf ? FilePdfOutlined : FileTextOutlined
|
||||
|
||||
return {
|
||||
fileName,
|
||||
FileIcon,
|
||||
isPdf,
|
||||
}
|
||||
}
|
||||
|
||||
const navigateWithTransition = (to) => {
|
||||
if (document.startViewTransition) {
|
||||
document.startViewTransition(() => navigate(to))
|
||||
|
|
@ -422,21 +435,49 @@ function DocumentPage() {
|
|||
return dirParts.join('/')
|
||||
}
|
||||
|
||||
const normalizeMarkdownHref = (href) => {
|
||||
if (!href) return href
|
||||
|
||||
const [pathPart, hashPart = ''] = href.split('#')
|
||||
const [rawPath, searchPart = ''] = pathPart.split('?')
|
||||
|
||||
const decodedPath = rawPath
|
||||
.split('/')
|
||||
.map((part) => {
|
||||
try {
|
||||
return decodeURIComponent(part)
|
||||
} catch (e) {
|
||||
return part
|
||||
}
|
||||
})
|
||||
.join('/')
|
||||
|
||||
const rebuilt = searchPart ? `${decodedPath}?${searchPart}` : decodedPath
|
||||
return hashPart ? `${rebuilt}#${hashPart}` : rebuilt
|
||||
}
|
||||
|
||||
const isExternalHref = (href) => {
|
||||
return Boolean(href && (/^[a-z][a-z\d+.-]*:/i.test(href) || href.startsWith('//')))
|
||||
}
|
||||
|
||||
// 处理markdown内部链接点击
|
||||
const handleMarkdownLink = (e, href) => {
|
||||
const normalizedHref = normalizeMarkdownHref(href)
|
||||
|
||||
// 检查是否是外部链接
|
||||
if (!href || href.startsWith('http://') || href.startsWith('https://') || href.startsWith('//')) {
|
||||
if (!normalizedHref || isExternalHref(normalizedHref)) {
|
||||
return // 外部链接,允许默认行为
|
||||
}
|
||||
|
||||
// 检查是否是锚点链接
|
||||
if (href.startsWith('#')) {
|
||||
if (normalizedHref.startsWith('#')) {
|
||||
return // 锚点链接,允许默认行为
|
||||
}
|
||||
|
||||
// 检查是否是文档文件(.md 或 .pdf)
|
||||
const isMd = href.endsWith('.md')
|
||||
const isPdf = href.toLowerCase().endsWith('.pdf')
|
||||
const pathOnly = normalizedHref.split(/[?#]/)[0]
|
||||
const isMd = pathOnly.endsWith('.md')
|
||||
const isPdf = pathOnly.toLowerCase().endsWith('.pdf')
|
||||
|
||||
if (!isMd && !isPdf) {
|
||||
return // 不是文档文件,允许默认行为
|
||||
|
|
@ -445,22 +486,14 @@ function DocumentPage() {
|
|||
// 阻止默认跳转
|
||||
e.preventDefault()
|
||||
|
||||
// 先解码 href(因为 Markdown 中的链接可能已经是 URL 编码的)
|
||||
let decodedHref = href
|
||||
try {
|
||||
decodedHref = decodeURIComponent(href)
|
||||
} catch (e) {
|
||||
// 解码失败,使用原始值
|
||||
}
|
||||
|
||||
// 解析路径
|
||||
let targetPath
|
||||
if (decodedHref.startsWith('.') || decodedHref.startsWith('..')) {
|
||||
if (pathOnly.startsWith('.') || pathOnly.startsWith('..')) {
|
||||
// 真正的相对路径,相对于当前文件
|
||||
targetPath = resolveRelativePath(selectedFile, decodedHref)
|
||||
targetPath = resolveRelativePath(selectedFile, pathOnly)
|
||||
} else {
|
||||
// 项目内绝对路径(由编辑器生成),相对于项目根目录
|
||||
targetPath = decodedHref.startsWith('/') ? decodedHref.substring(1) : decodedHref
|
||||
targetPath = pathOnly.startsWith('/') ? pathOnly.substring(1) : pathOnly
|
||||
}
|
||||
|
||||
// 自动展开父目录
|
||||
|
|
@ -836,7 +869,7 @@ function DocumentPage() {
|
|||
if (!searchKeyword) {
|
||||
return {
|
||||
a: ({ node, href, children, ...props }) => {
|
||||
const isExternal = href && (href.startsWith('http') || href.startsWith('//'));
|
||||
const isExternal = isExternalHref(href);
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
|
|
@ -874,7 +907,7 @@ function DocumentPage() {
|
|||
|
||||
return {
|
||||
a: ({ node, href, children, ...props }) => {
|
||||
const isExternal = href && (href.startsWith('http') || href.startsWith('//'));
|
||||
const isExternal = isExternalHref(href);
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
|
|
@ -994,8 +1027,18 @@ function DocumentPage() {
|
|||
{/* 右侧内容区 */}
|
||||
<Layout className="docs-content-layout">
|
||||
<Content className="docs-content" ref={contentRef}>
|
||||
<div className="docs-content-header">
|
||||
<h3>{selectedFile || 'README.md'}</h3>
|
||||
<div className="docs-content-header" title={selectedFile || 'README.md'}>
|
||||
{(() => {
|
||||
const { fileName, FileIcon, isPdf } = getHeaderDisplay(selectedFile)
|
||||
return (
|
||||
<div className="docs-header-title">
|
||||
<span className="docs-header-item">
|
||||
<FileIcon className="docs-header-icon" style={isPdf ? { color: '#f5222d' } : undefined} />
|
||||
<span className="docs-header-text">{fileName}</span>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
<div className={`docs-content-wrapper ${viewMode === 'pdf' ? 'pdf-mode' : ''}`}>
|
||||
{loading ? (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState, useEffect, useRef } from 'react'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { Layout, Button, Modal, Input, Spin, Anchor } from 'antd'
|
||||
import { CloseOutlined, LockOutlined, MenuFoldOutlined, MenuUnfoldOutlined, FileTextOutlined } from '@ant-design/icons'
|
||||
import { CloseOutlined, LockOutlined, MenuFoldOutlined, MenuUnfoldOutlined, FileTextOutlined, FilePdfOutlined } from '@ant-design/icons'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import rehypeHighlight from 'rehype-highlight'
|
||||
|
|
@ -117,6 +117,45 @@ function FileSharePage() {
|
|||
window.open(exportFileSharePDF(shareCode), '_blank')
|
||||
}
|
||||
|
||||
const isExternalHref = (href) => {
|
||||
return Boolean(href && (/^[a-z][a-z\d+.-]*:/i.test(href) || href.startsWith('//')))
|
||||
}
|
||||
|
||||
const isInternalFileHref = (href) => {
|
||||
if (!href) return false
|
||||
if (href.startsWith('#')) return false
|
||||
if (isExternalHref(href)) return false
|
||||
const pathOnly = href.split(/[?#]/)[0]
|
||||
return pathOnly.endsWith('.md') || pathOnly.toLowerCase().endsWith('.pdf')
|
||||
}
|
||||
|
||||
const handleMarkdownLink = (e, href) => {
|
||||
if (!isInternalFileHref(href)) return
|
||||
e.preventDefault()
|
||||
Toast.error('无法打开内部文件链接', '单文件分享模式不支持跳转到其他内部文件')
|
||||
}
|
||||
|
||||
const markdownComponents = {
|
||||
a: ({ node, href, children, ...props }) => {
|
||||
const isExternal = isExternalHref(href)
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
onClick={(e) => handleMarkdownLink(e, href)}
|
||||
target={isExternal ? '_blank' : undefined}
|
||||
rel={isExternal ? 'noopener noreferrer' : undefined}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const isHeaderPdf = contentInfo?.type === 'pdf'
|
||||
const HeaderIcon = isHeaderPdf ? FilePdfOutlined : FileTextOutlined
|
||||
const headerLabel = contentInfo?.filename || '文件分享'
|
||||
|
||||
return (
|
||||
<div className="preview-page file-share-page">
|
||||
<div className="file-share-shell">
|
||||
|
|
@ -131,7 +170,10 @@ function FileSharePage() {
|
|||
>
|
||||
<CloseOutlined />
|
||||
</button>
|
||||
<h3>{contentInfo?.filename || shareInfo?.name || '文件分享'}</h3>
|
||||
<h3 className="preview-header-title">
|
||||
<HeaderIcon className="preview-header-icon" style={isHeaderPdf ? { color: '#f5222d' } : undefined} />
|
||||
<span className="preview-header-text">{headerLabel}</span>
|
||||
</h3>
|
||||
</div>
|
||||
{loading ? (
|
||||
<div className="preview-loading">
|
||||
|
|
@ -145,7 +187,11 @@ function FileSharePage() {
|
|||
<VirtualPDFViewer url={contentInfo.document_url} filename={contentInfo.filename} />
|
||||
) : (
|
||||
<div className="markdown-body">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeSlug, rehypeHighlight]}>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeSlug, rehypeHighlight]}
|
||||
components={markdownComponents}
|
||||
>
|
||||
{contentInfo?.content || ''}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -247,7 +247,34 @@
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.preview-content-header h3 {
|
||||
.preview-header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.preview-header-icon {
|
||||
font-size: 15px;
|
||||
color: var(--text-color-secondary);
|
||||
flex: none;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.preview-header-text {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.preview-content-header h3:not(.preview-header-title) {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
|
|
|
|||
|
|
@ -181,6 +181,31 @@ function ProjectSharePage() {
|
|||
}
|
||||
}
|
||||
|
||||
const normalizeMarkdownHref = (href) => {
|
||||
if (!href) return href
|
||||
|
||||
const [pathPart, hashPart = ''] = href.split('#')
|
||||
const [rawPath, searchPart = ''] = pathPart.split('?')
|
||||
|
||||
const decodedPath = rawPath
|
||||
.split('/')
|
||||
.map((part) => {
|
||||
try {
|
||||
return decodeURIComponent(part)
|
||||
} catch (e) {
|
||||
return part
|
||||
}
|
||||
})
|
||||
.join('/')
|
||||
|
||||
const rebuilt = searchPart ? `${decodedPath}?${searchPart}` : decodedPath
|
||||
return hashPart ? `${rebuilt}#${hashPart}` : rebuilt
|
||||
}
|
||||
|
||||
const isExternalHref = (href) => {
|
||||
return Boolean(href && (/^[a-z][a-z\d+.-]*:/i.test(href) || href.startsWith('//')))
|
||||
}
|
||||
|
||||
const handleSearch = async (value) => {
|
||||
const keyword = value || ''
|
||||
setSearchKeyword(keyword)
|
||||
|
|
@ -297,6 +322,14 @@ function ProjectSharePage() {
|
|||
return dirParts.join('/')
|
||||
}
|
||||
|
||||
const resolveMarkdownTarget = (relativePath) => {
|
||||
if (!relativePath) return relativePath
|
||||
if (relativePath.startsWith('.')) {
|
||||
return resolveRelativePath(selectedFile, relativePath)
|
||||
}
|
||||
return relativePath.startsWith('/') ? relativePath.substring(1) : relativePath
|
||||
}
|
||||
|
||||
const openSharedFile = (key) => {
|
||||
setSelectedFile(key)
|
||||
setSelectedNodeKey(key)
|
||||
|
|
@ -324,16 +357,14 @@ function ProjectSharePage() {
|
|||
}
|
||||
|
||||
const handleMarkdownLink = (e, href) => {
|
||||
if (!href || href.startsWith('http') || href.startsWith('//') || href.startsWith('#')) return
|
||||
const isMd = href.endsWith('.md')
|
||||
const isPdf = href.toLowerCase().endsWith('.pdf')
|
||||
const normalizedHref = normalizeMarkdownHref(href)
|
||||
if (!normalizedHref || isExternalHref(normalizedHref) || normalizedHref.startsWith('#')) return
|
||||
const pathOnly = normalizedHref.split(/[?#]/)[0]
|
||||
const isMd = pathOnly.endsWith('.md')
|
||||
const isPdf = pathOnly.toLowerCase().endsWith('.pdf')
|
||||
if (!isMd && !isPdf) return
|
||||
e.preventDefault()
|
||||
let decodedHref = href
|
||||
try {
|
||||
decodedHref = decodeURIComponent(href)
|
||||
} catch {}
|
||||
openSharedFile(resolveRelativePath(selectedFile, decodedHref))
|
||||
openSharedFile(resolveMarkdownTarget(pathOnly))
|
||||
}
|
||||
|
||||
const handleExportPDF = () => {
|
||||
|
|
@ -355,6 +386,27 @@ function ProjectSharePage() {
|
|||
[filteredTreeData, openKeys]
|
||||
)
|
||||
|
||||
const markdownComponents = {
|
||||
a: ({ node, href, children, ...props }) => {
|
||||
const isExternal = isExternalHref(href)
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
onClick={(e) => handleMarkdownLink(e, href)}
|
||||
target={isExternal ? '_blank' : undefined}
|
||||
rel={isExternal ? 'noopener noreferrer' : undefined}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const isHeaderPdf = selectedFile.toLowerCase().endsWith('.pdf')
|
||||
const HeaderIcon = isHeaderPdf ? FilePdfOutlined : FileTextOutlined
|
||||
const headerLabel = selectedFile ? selectedFile.split('/').filter(Boolean).pop() : 'README.md'
|
||||
|
||||
return (
|
||||
<div className="preview-page">
|
||||
<Layout className="preview-layout">
|
||||
|
|
@ -454,7 +506,10 @@ function ProjectSharePage() {
|
|||
<Layout className="preview-content-layout">
|
||||
<Content className="preview-content" ref={contentRef}>
|
||||
<div className="preview-content-header">
|
||||
<h3>{selectedFile || 'README.md'}</h3>
|
||||
<h3 className="preview-header-title">
|
||||
<HeaderIcon className="preview-header-icon" style={isHeaderPdf ? { color: '#f5222d' } : undefined} />
|
||||
<span className="preview-header-text">{headerLabel}</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div className={`preview-content-wrapper ${viewMode === 'pdf' ? 'pdf-mode' : ''}`}>
|
||||
{loading ? (
|
||||
|
|
@ -467,13 +522,18 @@ function ProjectSharePage() {
|
|||
<VirtualPDFViewer url={pdfUrl} filename={pdfFilename} />
|
||||
) : (
|
||||
<div className="markdown-body" onClick={(e) => {
|
||||
if (e.defaultPrevented) return
|
||||
const target = e.target.closest('a')
|
||||
if (target) {
|
||||
const href = target.getAttribute('href')
|
||||
if (href) handleMarkdownLink(e, href)
|
||||
}
|
||||
}} ref={viewerRef}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeSlug, rehypeHighlight]}>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeSlug, rehypeHighlight]}
|
||||
components={markdownComponents}
|
||||
>
|
||||
{markdownContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -157,14 +157,33 @@ function ProjectList({ type = 'my' }) {
|
|||
// 更新项目
|
||||
const handleUpdateProject = async (values) => {
|
||||
try {
|
||||
await updateProject(currentProject.id, {
|
||||
const shouldKeepOpenForShare = currentProject?.is_public !== 1 && values.is_public
|
||||
const res = await updateProject(currentProject.id, {
|
||||
...values,
|
||||
is_public: values.is_public ? 1 : 0,
|
||||
})
|
||||
const updatedProject = res.data || {
|
||||
...currentProject,
|
||||
...values,
|
||||
is_public: values.is_public ? 1 : 0,
|
||||
}
|
||||
setCurrentProject(updatedProject)
|
||||
message.success('项目更新成功')
|
||||
await fetchProjects()
|
||||
|
||||
if (shouldKeepOpenForShare) {
|
||||
const shareRes = await getProjectShareInfo(currentProject.id)
|
||||
setShareInfo(shareRes.data)
|
||||
setHasPassword(shareRes.data.has_password)
|
||||
setPassword(shareRes.data.access_pass || '')
|
||||
return
|
||||
}
|
||||
|
||||
setShareInfo({ enabled: false, share_url: null, has_password: false, access_pass: null })
|
||||
setHasPassword(false)
|
||||
setPassword('')
|
||||
setEditModalVisible(false)
|
||||
editForm.resetFields()
|
||||
fetchProjects()
|
||||
} catch (error) {
|
||||
console.error('Update project error:', error)
|
||||
message.error('项目更新失败')
|
||||
|
|
@ -762,7 +781,7 @@ function ProjectList({ type = 'my' }) {
|
|||
</div>
|
||||
) : isPublicEnablePending ? (
|
||||
<div style={{ color: '#8c8c8c', lineHeight: 1.7 }}>
|
||||
保存项目后将自动生成新的项目分享链接。
|
||||
保存项目后将自动生成项目分享链接。
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
|
|
|||
Loading…
Reference in New Issue