2026-08-08 07:09:54 +08:00
|
|
|
import { useState, useEffect, useMemo } from 'react';
|
|
|
|
|
import { DocContent } from '../api';
|
2026-08-09 03:37:41 +08:00
|
|
|
import { cleanDisplayText } from '../presentation';
|
2026-08-09 10:43:56 +08:00
|
|
|
import { renderKnowledgeMarkdown } from '../markdown';
|
2026-08-08 07:09:54 +08:00
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
doc: DocContent;
|
|
|
|
|
onSave: (title: string, body: string) => void;
|
2026-08-09 10:43:56 +08:00
|
|
|
onWikiSelect?: (target: string) => void;
|
2026-08-08 07:09:54 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-09 10:43:56 +08:00
|
|
|
export function Editor({ doc, onSave, onWikiSelect }: Props) {
|
2026-08-08 07:09:54 +08:00
|
|
|
const [editing, setEditing] = useState(false);
|
|
|
|
|
const [title, setTitle] = useState(doc.meta.title);
|
|
|
|
|
const [body, setBody] = useState(doc.body);
|
|
|
|
|
|
|
|
|
|
// 切换文档时重置
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setTitle(doc.meta.title);
|
|
|
|
|
setBody(doc.body);
|
|
|
|
|
setEditing(false);
|
|
|
|
|
}, [doc.meta.id]);
|
|
|
|
|
|
|
|
|
|
const rendered = useMemo(() => {
|
|
|
|
|
try {
|
2026-08-09 10:43:56 +08:00
|
|
|
return renderKnowledgeMarkdown(body);
|
2026-08-08 07:09:54 +08:00
|
|
|
} catch {
|
|
|
|
|
return '<p>渲染失败</p>';
|
|
|
|
|
}
|
|
|
|
|
}, [body]);
|
|
|
|
|
|
|
|
|
|
const handleSave = () => {
|
|
|
|
|
onSave(title, body);
|
|
|
|
|
setEditing(false);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleCancel = () => {
|
|
|
|
|
setTitle(doc.meta.title);
|
|
|
|
|
setBody(doc.body);
|
|
|
|
|
setEditing(false);
|
|
|
|
|
};
|
2026-08-09 03:37:41 +08:00
|
|
|
const displayPath = doc.meta.id.split('/').map(cleanDisplayText).join(' / ');
|
2026-08-08 07:09:54 +08:00
|
|
|
|
2026-08-09 10:43:56 +08:00
|
|
|
const handleRenderedClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
|
|
|
const anchor = (event.target as HTMLElement).closest<HTMLAnchorElement>('a[data-hololake-wiki="true"]');
|
|
|
|
|
if (!anchor) return;
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
const prefix = 'hololake://knowledge/';
|
|
|
|
|
const rawTarget = anchor.href.startsWith(prefix) ? anchor.href.slice(prefix.length) : anchor.title;
|
|
|
|
|
if (rawTarget) onWikiSelect?.(decodeURIComponent(rawTarget));
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-08 07:09:54 +08:00
|
|
|
return (
|
|
|
|
|
<div className="kb-editor">
|
2026-08-08 11:15:34 +08:00
|
|
|
<div className="kb-page-symbol" aria-hidden="true">◇</div>
|
2026-08-08 07:09:54 +08:00
|
|
|
{/* 元信息栏 */}
|
|
|
|
|
<div className="kb-editor-meta">
|
2026-08-09 03:37:41 +08:00
|
|
|
<span className="kb-editor-path">HoloLake / {displayPath}</span>
|
2026-08-08 07:09:54 +08:00
|
|
|
<span className="kb-editor-date">
|
|
|
|
|
更新于 {new Date(doc.meta.updatedAt).toLocaleString('zh-CN')}
|
|
|
|
|
</span>
|
|
|
|
|
<span className="kb-editor-author">by {doc.meta.author}</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 标题 */}
|
|
|
|
|
{editing ? (
|
|
|
|
|
<input
|
|
|
|
|
className="kb-editor-title-input"
|
|
|
|
|
value={title}
|
|
|
|
|
onChange={e => setTitle(e.target.value)}
|
|
|
|
|
placeholder="文档标题"
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<h1 className="kb-editor-title" onClick={() => setEditing(true)}>
|
2026-08-09 03:37:41 +08:00
|
|
|
{cleanDisplayText(doc.meta.title)}
|
2026-08-08 07:09:54 +08:00
|
|
|
</h1>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* 操作栏 */}
|
|
|
|
|
<div className="kb-editor-toolbar">
|
|
|
|
|
{editing ? (
|
|
|
|
|
<>
|
|
|
|
|
<button className="kb-btn-primary" onClick={handleSave}>保存</button>
|
|
|
|
|
<button className="kb-btn-secondary" onClick={handleCancel}>取消</button>
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
2026-08-08 11:15:34 +08:00
|
|
|
<button className="kb-btn-primary" onClick={() => setEditing(true)}>编辑页面</button>
|
2026-08-08 07:09:54 +08:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* 内容区 */}
|
|
|
|
|
{editing ? (
|
|
|
|
|
<div className="kb-editor-edit-pane">
|
|
|
|
|
<textarea
|
|
|
|
|
className="kb-editor-textarea"
|
|
|
|
|
value={body}
|
|
|
|
|
onChange={e => setBody(e.target.value)}
|
|
|
|
|
placeholder="用 Markdown 写作..."
|
|
|
|
|
spellCheck={false}
|
|
|
|
|
/>
|
|
|
|
|
<div className="kb-editor-preview-pane">
|
|
|
|
|
<div
|
|
|
|
|
className="kb-markdown-render"
|
2026-08-09 10:43:56 +08:00
|
|
|
onClick={handleRenderedClick}
|
2026-08-08 07:09:54 +08:00
|
|
|
dangerouslySetInnerHTML={{ __html: rendered }}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="kb-editor-read-pane">
|
|
|
|
|
<div
|
|
|
|
|
className="kb-markdown-render"
|
2026-08-09 10:43:56 +08:00
|
|
|
onClick={handleRenderedClick}
|
2026-08-08 07:09:54 +08:00
|
|
|
dangerouslySetInnerHTML={{ __html: rendered }}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|