50 lines
4.9 KiB
TypeScript
50 lines
4.9 KiB
TypeScript
import {previewUrl} from '@/content/page-links';
|
||
import Head from 'next/head';
|
||
import Link from 'next/link';
|
||
import {useState,useEffect,useRef} from 'react';
|
||
import {X,PanelRight,Copy,ExternalLink} from 'lucide-react';
|
||
import {Shell} from './Shell';
|
||
import {getGuidance,guidanceMarkdown,guidanceBacklinks,humanDocumentUrl,markdownDocumentUrl,legacyDocumentPath,type GuidanceResource} from '@/content/skills/guidance';
|
||
|
||
export function SkillDocs({documentPath,documentId='index',skillId='street-people'}:{documentPath?:string;documentId?:string;skillId?:string}){
|
||
const path=documentPath??legacyDocumentPath(skillId,documentId);
|
||
const doc=getGuidance(path)!;
|
||
const [open,setOpen]=useState(false);
|
||
const [selected,setSelected]=useState<GuidanceResource|null>(null);
|
||
const [copied,setCopied]=useState(false);
|
||
const panel=useRef<HTMLElement>(null);
|
||
useEffect(()=>{setOpen(window.innerWidth>760)},[]);
|
||
useEffect(()=>{setSelected(null);setCopied(false)},[path]);
|
||
useEffect(()=>{if(!open)return;const handler=(e:KeyboardEvent)=>{if(e.key==='Escape')setOpen(false)};window.addEventListener('keydown',handler);return()=>window.removeEventListener('keydown',handler)},[open]);
|
||
const resource=(id:string)=>doc.resources.find(r=>r.id===id)!;
|
||
const incoming=guidanceBacklinks(path);
|
||
async function copy(){
|
||
try{
|
||
const value=new URL(markdownDocumentUrl(path),location.origin).href;
|
||
if(navigator.clipboard)await navigator.clipboard.writeText(value);
|
||
else{const input=document.createElement('textarea');input.value=value;document.body.appendChild(input);input.select();const ok=document.execCommand('copy');input.remove();if(!ok)throw Error('Copy unavailable');}
|
||
setCopied(true);
|
||
}catch{setCopied(false)}
|
||
}
|
||
return <Shell><Head><title>{doc.title} · 指导文档 · 啡跃 Endoleap</title><link rel="canonical" href={humanDocumentUrl(path)}/><link rel="alternate" type="text/markdown" href={markdownDocumentUrl(path)} title={doc.title}/></Head>
|
||
<div className="docs-top"><Link href="/skills">← 全部指导文档</Link><button onClick={()=>setOpen(v=>!v)} aria-expanded={open}><PanelRight size={16}/>文档与来源 · {doc.resources.length}</button></div>
|
||
<div className={`docs-layout ${open?'with-sources':''}`}><article className="docs-body">
|
||
<div className="docs-eyebrow">{doc.collection} / 指导文档</div><h1>{doc.title}</h1>
|
||
<div className="docs-actions"><button onClick={copy}><Copy size={14}/>{copied?'已复制本文 Markdown 地址':'复制本文给 Agent'}</button><a href={markdownDocumentUrl(path)} target="_blank" rel="noreferrer">本文 Markdown ↗</a></div>
|
||
<p className="docs-hint">网页与 Markdown 来自同一份文档。每篇都有独立地址,可以直接进入,也可以沿引用继续阅读。</p>
|
||
{doc.blocks.map((b,i)=>b.type==='heading'?<h2 key={i}>{b.text}</h2>:b.type==='code'?<pre key={i}>{b.text}</pre>:b.type==='links'?<div className="docs-inline-links" key={i}>{b.refs!.map(id=>{
|
||
const r=resource(id);
|
||
return r.kind==='document'?<Link href={r.human!} key={id}>文档 · {r.title}<ExternalLink size={12}/></Link>:<a href={r.href} key={id} onClick={e=>{e.preventDefault();setSelected(r);setOpen(true);panel.current?.focus()}}>{r.kind==='image'?'图片':r.method} · {r.title}<ExternalLink size={12}/></a>;
|
||
})}</div>:<p key={i}>{b.text}</p>)}
|
||
{incoming.length>0&&<><h2>引用本文的文档</h2><div className="docs-inline-links">{incoming.map(d=><Link key={d.path} href={humanDocumentUrl(d.path)}>{d.title}<ExternalLink size={12}/></Link>)}</div></>}
|
||
</article>
|
||
{open&&<aside className="docs-sources" ref={panel} tabIndex={-1} aria-label="文档与资源"><header><div><b>文档与来源</b><small>本文的文档、参考图与接口</small></div><button aria-label="关闭文档与来源" onClick={()=>setOpen(false)}><X size={18}/></button></header>
|
||
{(['document','image','api'] as const).map(kind=><section key={kind}><h3>{kind==='document'?'指导文档':kind==='image'?'参考与示意图':'API 接口'}</h3>{doc.resources.filter(r=>r.kind===kind).map(r=><button key={r.id} className={selected?.id===r.id?'selected':''} onClick={()=>setSelected(r)}><span>{r.method||(r.kind==='image'?'IMG':'MD')}</span><div>{r.title}<small>{r.human||r.href}</small></div></button>)}</section>)}
|
||
{selected&&<div className="docs-resource-preview"><h3>{selected.title}</h3>{selected.kind==='image'?<img src={previewUrl(selected.href,1280)} alt={selected.title}/>:selected.kind==='document'?<pre>{guidanceMarkdown(selected.documentPath!)}</pre>:<><code>{selected.method} {selected.href}</code><pre>{JSON.stringify(selected.detail||{method:selected.method,path:selected.href},null,2)}</pre><p>{selected.description}</p></>}
|
||
<div className="docs-resource-actions">{selected.human&&<Link href={selected.human}>打开文档网页 ↗</Link>}{selected.method!=='POST'&&!selected.template&&<a href={selected.href} target="_blank" rel="noreferrer">{selected.kind==='document'?'打开 Markdown':'打开资源'} ↗</a>}</div>
|
||
</div>}
|
||
</aside>}
|
||
</div>
|
||
</Shell>;
|
||
}
|