105 lines
2.8 KiB
TypeScript
105 lines
2.8 KiB
TypeScript
|
|
import { useState, useEffect, useMemo } from 'react';
|
||
|
|
import { DocContent } from '../api';
|
||
|
|
import { marked } from 'marked';
|
||
|
|
|
||
|
|
interface Props {
|
||
|
|
doc: DocContent;
|
||
|
|
onSave: (title: string, body: string) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function Editor({ doc, onSave }: Props) {
|
||
|
|
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 {
|
||
|
|
return marked(body, { async: false }) as string;
|
||
|
|
} catch {
|
||
|
|
return '<p>渲染失败</p>';
|
||
|
|
}
|
||
|
|
}, [body]);
|
||
|
|
|
||
|
|
const handleSave = () => {
|
||
|
|
onSave(title, body);
|
||
|
|
setEditing(false);
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleCancel = () => {
|
||
|
|
setTitle(doc.meta.title);
|
||
|
|
setBody(doc.body);
|
||
|
|
setEditing(false);
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="kb-editor">
|
||
|
|
{/* 元信息栏 */}
|
||
|
|
<div className="kb-editor-meta">
|
||
|
|
<span className="kb-editor-path">{doc.meta.id}</span>
|
||
|
|
<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)}>
|
||
|
|
{doc.meta.title}
|
||
|
|
</h1>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{/* 操作栏 */}
|
||
|
|
<div className="kb-editor-toolbar">
|
||
|
|
{editing ? (
|
||
|
|
<>
|
||
|
|
<button className="kb-btn-primary" onClick={handleSave}>保存</button>
|
||
|
|
<button className="kb-btn-secondary" onClick={handleCancel}>取消</button>
|
||
|
|
</>
|
||
|
|
) : (
|
||
|
|
<button className="kb-btn-primary" onClick={() => setEditing(true)}>编辑</button>
|
||
|
|
)}
|
||
|
|
</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"
|
||
|
|
dangerouslySetInnerHTML={{ __html: rendered }}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
) : (
|
||
|
|
<div className="kb-editor-read-pane">
|
||
|
|
<div
|
||
|
|
className="kb-markdown-render"
|
||
|
|
dangerouslySetInnerHTML={{ __html: rendered }}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|