M0-M11 狩夜人笔记初始实现:11里程碑+86测试

This commit is contained in:
hunter-dev
2026-08-24 20:35:22 +08:00
commit 3ebd33536b
106 changed files with 10490 additions and 0 deletions
@@ -0,0 +1,79 @@
import { useState } from 'react';
import { useStore } from '../../store';
import { invoke } from '../../ipc-client';
import { IPC } from '@hunter/shared';
import type { TreeNode } from '@hunter/shared';
export function DocInfoPanel() {
const { currentPath, title, meta, tree, toggleStar, addTag, removeTag, addLink, removeLink } = useStore();
const [tagInput, setTagInput] = useState('');
const [linkInput, setLinkInput] = useState('');
if (!currentPath) {
return <div style={{ padding: 16, opacity: 0.5, fontSize: 13 }}>未打开文档</div>;
}
// 库内 .md 文件供关联链接选择
const docs = tree.filter(n => n.type === 'doc');
const mtime = tree.find(n => n.path === currentPath)?.mtime;
return (
<div style={{ padding: 16, overflow: 'auto', fontSize: 13 }}>
<div style={{ marginBottom: 12, display: 'flex', alignItems: 'center', gap: 8 }}>
<strong>{title}</strong>
<button onClick={() => toggleStar()} title="星标" style={{ cursor: 'pointer', background: 'none', border: 'none', fontSize: 16, color: meta.star ? '#ffd54a' : '#888' }}>
{meta.star ? '★' : '☆'}
</button>
</div>
<Section title="路径">
<div style={{ opacity: 0.6, wordBreak: 'break-all' }}>{currentPath}</div>
</Section>
<Section title="标签">
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginBottom: 6 }}>
{(meta.tags ?? []).map(t => (
<span key={t} style={{ background: '#2a2a3a', borderRadius: 4, padding: '2px 8px', display: 'flex', alignItems: 'center', gap: 4 }}>
{t}<span style={{ cursor: 'pointer', opacity: 0.6 }} onClick={() => removeTag(t)}>×</span>
</span>
))}
{(meta.tags ?? []).length === 0 && <span style={{ opacity: 0.4 }}>无标签</span>}
</div>
<div style={{ display: 'flex', gap: 4 }}>
<input value={tagInput} onChange={e => setTagInput(e.target.value)} onKeyDown={e => { if (e.key === 'Enter' && tagInput.trim()) { addTag(tagInput.trim()); setTagInput(''); } }} placeholder="添加标签" style={inp} />
<button style={btn} onClick={() => { if (tagInput.trim()) { addTag(tagInput.trim()); setTagInput(''); } }}>+</button>
</div>
</Section>
<Section title="关联文档">
<div style={{ marginBottom: 6 }}>
{(meta.links ?? []).map(l => (
<div key={l} style={{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }}>
<span style={{ opacity: 0.8 }}>{l}</span>
<span style={{ cursor: 'pointer', opacity: 0.6 }} onClick={() => removeLink(l)}>×</span>
</div>
))}
{(meta.links ?? []).length === 0 && <span style={{ opacity: 0.4 }}>无关联</span>}
</div>
<select value={linkInput} onChange={e => setLinkInput(e.target.value)} style={inp}>
<option value="">选择库内文档…</option>
{docs.filter(d => d.path !== currentPath && !(meta.links ?? []).includes(d.path + '')).map(d => (
<option key={d.path} value={d.path}>{d.name}({d.path})</option>
))}
</select>
<button style={{ ...btn, marginLeft: 4 }} onClick={() => { if (linkInput) { addLink(linkInput); setLinkInput(''); } }}>关联</button>
</Section>
<Section title="修改时间">
<div style={{ opacity: 0.6 }}>{mtime ? new Date(mtime).toLocaleString('zh-CN') : '—'}</div>
<div style={{ opacity: 0.4, fontSize: 12, marginTop: 4 }}>创建时间:—(按设计不落盘)</div>
</Section>
</div>
);
}
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (<div style={{ marginBottom: 16 }}><div style={{ opacity: 0.5, marginBottom: 6 }}>{title}</div>{children}</div>);
}
const inp: React.CSSProperties = { background: '#222', border: '1px solid #444', color: '#ddd', borderRadius: 4, padding: '3px 8px', fontSize: 13, flex: 1 };
const btn: React.CSSProperties = { minWidth: 28, height: 26, border: '1px solid #444', background: 'transparent', color: '#ccc', cursor: 'pointer', borderRadius: 4 };