Files
probably-rendering-one-more…/apps/waterfall-wiki/src/components/SkillDocs.tsx
T
codex-bot 8062dbc878
Endoleap brand checks / verify (pull_request) Successful in 2m25s
creative workspace review checks / verify (pull_request) Failing after 2m20s
feat(ui): apply Endoleap themes, brand marks and favicons
2026-11-01 01:44:51 +08:00

50 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>;
}