105 lines
4.2 KiB
JavaScript
105 lines
4.2 KiB
JavaScript
/* ═══════════ 文档页逻辑(docs.html) ═══════════
|
||
hash 路由(#01-quick-start 可直链某篇)+ fetch content/*.md + marked 前端渲染。
|
||
文档为站点自有的可信 markdown,无需注入消毒。 */
|
||
(function () {
|
||
'use strict';
|
||
|
||
/* 文档页自己掌控滚动:禁用浏览器滚动恢复。否则刷新时浏览器恢复到上次滚动位置,
|
||
若上次停在 topbar 高度附近,fixed 顶栏会遮住 h1 上半截(实测实踩);
|
||
切篇/刷新统一回顶,行为可预期。 */
|
||
if ('scrollRestoration' in history) history.scrollRestoration = 'manual';
|
||
|
||
/* 目录标题与 md 一级标题语义一致(08/09 目录用简称,正文原标题保留) */
|
||
var DOCS = [
|
||
{ id: '01-quick-start', title: '快速上手', file: '01-quick-start.md' },
|
||
{ id: '02-core-concepts', title: '核心概念', file: '02-core-concepts.md' },
|
||
{ id: '03-conversations', title: '对话', file: '03-conversations.md' },
|
||
{ id: '04-input-context', title: '输入与上下文', file: '04-input-context.md' },
|
||
{ id: '05-execution-security', title: '执行与安全', file: '05-execution-security.md' },
|
||
{ id: '06-conversation-assets', title: '对话资产管理', file: '06-conversation-assets.md' },
|
||
{ id: '07-agent-capabilities', title: '智能体能力', file: '07-agent-capabilities.md' },
|
||
{ id: '08-quick-dock', title: '快捷窗口', file: '08-quick-dock.md' },
|
||
{ id: '09-settings', title: '个人空间设置', file: '09-settings.md' },
|
||
{ id: '10-cli', title: 'CLI(开发中)', file: '10-cli.md' }
|
||
];
|
||
|
||
var navEl = document.getElementById('docsNav');
|
||
var contentEl = document.getElementById('docsContent');
|
||
var currentId = null;
|
||
var loadSeq = 0; /* 防快速切换时旧响应覆盖新内容 */
|
||
|
||
/* 建左侧目录 */
|
||
DOCS.forEach(function (doc) {
|
||
var a = document.createElement('a');
|
||
a.href = '#' + doc.id;
|
||
a.textContent = doc.title;
|
||
a.dataset.docId = doc.id;
|
||
navEl.appendChild(a);
|
||
});
|
||
|
||
function docById(id) {
|
||
for (var i = 0; i < DOCS.length; i++) if (DOCS[i].id === id) return DOCS[i];
|
||
return null;
|
||
}
|
||
|
||
/* hash → doc id;不匹配时:尚未加载过 → 默认第一篇;已加载 → 返回 null 忽略
|
||
(可能是渲染内容里的锚点 hash,不应触发切篇) */
|
||
function docIdFromHash() {
|
||
var h = (location.hash || '').replace(/^#\/?/, '');
|
||
if (docById(h)) return h;
|
||
return currentId === null ? DOCS[0].id : null;
|
||
}
|
||
|
||
function renderNav() {
|
||
var links = navEl.querySelectorAll('a');
|
||
for (var i = 0; i < links.length; i++) {
|
||
links[i].classList.toggle('active', links[i].dataset.docId === currentId);
|
||
}
|
||
}
|
||
|
||
function postProcess() {
|
||
/* 外部链接新标签打开;内容区内的站外相对链接(如指向主仓库文件)在官网无意义,原样保留 */
|
||
var links = contentEl.querySelectorAll('a[href^="http"]');
|
||
for (var i = 0; i < links.length; i++) {
|
||
links[i].setAttribute('target', '_blank');
|
||
links[i].setAttribute('rel', 'noopener');
|
||
}
|
||
}
|
||
|
||
function loadDoc(id) {
|
||
var doc = docById(id);
|
||
if (!doc) return;
|
||
currentId = id;
|
||
renderNav();
|
||
var seq = ++loadSeq;
|
||
contentEl.innerHTML = '<p class="docs-loading">加载中…</p>';
|
||
fetch('content/' + doc.file)
|
||
.then(function (res) {
|
||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||
return res.text();
|
||
})
|
||
.then(function (md) {
|
||
if (seq !== loadSeq) return; /* 期间已切到别的篇目 */
|
||
contentEl.innerHTML = marked.parse(md);
|
||
postProcess();
|
||
window.scrollTo(0, 0);
|
||
})
|
||
.catch(function (err) {
|
||
if (seq !== loadSeq) return;
|
||
contentEl.innerHTML = '<p class="docs-error">文档加载失败(' + String(err) + ')。请刷新重试。</p>';
|
||
});
|
||
}
|
||
|
||
window.addEventListener('hashchange', function () {
|
||
var id = docIdFromHash();
|
||
if (id && id !== currentId) loadDoc(id);
|
||
});
|
||
|
||
var first = docIdFromHash();
|
||
if (location.hash !== '#' + first) {
|
||
/* 把默认篇目写进地址栏,刷新/分享行为一致 */
|
||
history.replaceState(null, '', '#' + first);
|
||
}
|
||
loadDoc(first);
|
||
})();
|