import { useState } from 'react'; import { useStore } from '../../store'; import { Star, Tag, Link2, FileText, Clock, Plus, X, Check, ListChecks, ArrowLeftRight, Folder, ChevronDown } from 'lucide-react'; // 信息面板:路径/标签/关联文档(双向+批量)/时间。样式统一走设计 token export function DocInfoPanel() { const currentPath = useStore(s => s.currentPath); const title = useStore(s => s.title); const meta = useStore(s => s.meta); const tree = useStore(s => s.tree); const toggleStar = useStore(s => s.toggleStar); const addTag = useStore(s => s.addTag); const removeTag = useStore(s => s.removeTag); const linkBatch = useStore(s => s.linkBatch); const openDoc = useStore(s => s.openDoc); const [tagInput, setTagInput] = useState(''); const [quickSel, setQuickSel] = useState(''); const [batchOpen, setBatchOpen] = useState(false); const [checked, setChecked] = useState>(new Set()); if (!currentPath) { return
未打开文档
; } // 库内 .md 文件供关联选择(排除当前文档与已关联项) const docs = tree.filter(n => n.type === 'doc'); const mtime = tree.find(n => n.path === currentPath)?.mtime; const links = meta.links ?? []; const linkNames = new Map(); for (const d of docs) linkNames.set(d.path, d.name); const quickCandidates = docs.filter(d => d.path !== currentPath && !links.includes(d.path)); const toggleCheck = (p: string) => { const n = new Set(checked); if (n.has(p)) n.delete(p); else n.add(p); setChecked(n); }; return (
{/* 标题 + 星标 */}
{title}
{/* 路径 */}
} title="路径">
{currentPath}
{/* 标签 */}
} title="标签">
{(meta.tags ?? []).map(t => ( {t} ))} {(meta.tags ?? []).length === 0 && 暂无标签}
setTagInput(e.target.value)} onKeyDown={e => { if (e.key === 'Enter' && tagInput.trim()) { addTag(tagInput.trim()); setTagInput(''); } }} placeholder="添加标签,回车确认" style={inp} />
{/* 关联文档:双向 + 批量 */}
} title={"关联文档" + (links.length ? '(' + links.length + ')' : '')}> {links.length === 0 ? (
暂无关联,可在下方快速关联或批量关联
) : (
{links.map(l => (
{ e.currentTarget.style.borderColor = 'var(--line-strong)'; e.currentTarget.style.boxShadow = 'var(--shadow-lift)'; }} onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--control-border)'; e.currentTarget.style.boxShadow = 'var(--shadow-soft)'; }}> {linkNames.get(l) ?? l} 双向
))}
)} {/* 快速双向关联 */}
{/* 批量关联 */}
{batchOpen && (
{quickCandidates.length === 0 ? (
没有可关联的文档
) : quickCandidates.map(d => ( ))}
)} {batchOpen && (
)}
{/* 时间 */}
} title="时间">
修改:{mtime ? new Date(mtime).toLocaleString('zh-CN') : '—'}
创建:{meta.createdAt ? new Date(meta.createdAt).toLocaleString('zh-CN') : '—'}
); } function Section({ icon, title, children }: { icon: React.ReactNode; title: string; children: React.ReactNode }) { return (
{icon}{title}
{children}
); } const chip: React.CSSProperties = { display: 'inline-flex', alignItems: 'center', gap: 4, background: 'var(--control-border)', color: 'var(--text-secondary)', borderRadius: 999, padding: '2px 8px', fontSize: 12 }; const chipX: React.CSSProperties = { border: 'none', background: 'transparent', color: 'var(--text-tertiary)', cursor: 'pointer', display: 'inline-flex', padding: 0, borderRadius: 4 }; const inp: React.CSSProperties = { background: 'var(--bg-secondary)', border: '1px solid var(--control-border)', color: 'var(--text-primary)', borderRadius: 6, padding: '5px 8px', fontSize: 13, flex: 1, outline: 'none' }; const btn = (disabled?: boolean): React.CSSProperties => ({ minWidth: 30, height: 28, border: '1px solid var(--control-border)', background: 'transparent', color: 'var(--text-secondary)', cursor: disabled ? 'not-allowed' : 'pointer', borderRadius: 6, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 4, padding: '0 8px', fontSize: 12, opacity: disabled ? 0.4 : 1 }); const btnPrimary = (disabled?: boolean): React.CSSProperties => ({ minWidth: 52, height: 28, border: 'none', background: 'var(--accent)', color: '#fff', cursor: disabled ? 'not-allowed' : 'pointer', borderRadius: 6, padding: '0 10px', fontSize: 12, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', opacity: disabled ? 0.4 : 1 }); const iconBtn: React.CSSProperties = { border: 'none', background: 'transparent', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', padding: 4, borderRadius: 6 }; const iconSmall: React.CSSProperties = { border: 'none', background: 'transparent', color: 'var(--text-tertiary)', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', padding: 2, borderRadius: 4 };