astrion-website/demo/site-assets/docs.js

172 lines
7.2 KiB
JavaScript
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.

/* ═══════════ 文档页逻辑docs.html ═══════════
hash 路由(#01-quick-start 可直链某篇)+ fetch content/*.md + marked 前端渲染。
文档为站点自有的可信 markdown无需注入消毒。
多语言:与主页同一 localStorageastrion_site_locale英文版在 content/en/ 同文件名,
缺失时回退中文;目录末尾附语言切换按钮(桌面端目录底部 / 移动端 chips 条尾部)。 */
(function () {
'use strict';
/* 文档页自己掌控滚动:禁用浏览器滚动恢复。否则刷新时浏览器恢复到上次滚动位置,
若上次停在 topbar 高度附近fixed 顶栏会遮住 h1 上半截(实测实踩);
切篇/刷新统一回顶,行为可预期。 */
if ('scrollRestoration' in history) history.scrollRestoration = 'manual';
/* 目录标题与 md 一级标题语义一致08/09 目录用简称,正文原标题保留);
titleEn 为英文目录标题 */
var DOCS = [
{ id: '01-quick-start', title: '快速上手', titleEn: 'Quick Start', file: '01-quick-start.md' },
{ id: '02-core-concepts', title: '核心概念', titleEn: 'Core Concepts', file: '02-core-concepts.md' },
{ id: '03-conversations', title: '对话', titleEn: 'Conversations', file: '03-conversations.md' },
{ id: '04-input-context', title: '输入与上下文', titleEn: 'Input & Context', file: '04-input-context.md' },
{ id: '05-execution-security', title: '执行与安全', titleEn: 'Execution & Security', file: '05-execution-security.md' },
{ id: '06-conversation-assets', title: '对话资产管理', titleEn: 'Conversation Assets', file: '06-conversation-assets.md' },
{ id: '07-agent-capabilities', title: '智能体能力', titleEn: 'Agent Capabilities', file: '07-agent-capabilities.md' },
{ id: '08-quick-dock', title: '快捷窗口', titleEn: 'Quick Dock', file: '08-quick-dock.md' },
{ id: '09-settings', title: '个人空间设置', titleEn: 'Personal Space Settings', file: '09-settings.md' },
{ id: '10-cli', title: 'CLI开发中', titleEn: 'CLI (In Development)', file: '10-cli.md' }
];
var DICT = window.SITE_I18N || {};
var navEl = document.getElementById('docsNav');
var contentEl = document.getElementById('docsContent');
var currentId = null;
var loadSeq = 0; /* 防快速切换时旧响应覆盖新内容 */
/* 与主页 site.js 同款检测localStorage 优先 → 浏览器语言zh 开头中文,否则英文) */
function detectLocale() {
try {
var stored = localStorage.getItem('astrion_site_locale');
if (stored === 'zh-CN' || stored === 'en-US') return stored;
} catch (e) {}
var nav = (navigator.language || '').toLowerCase();
return nav.indexOf('zh') === 0 ? 'zh-CN' : 'en-US';
}
var locale = detectLocale();
function tr(key) {
var d = DICT[locale] || {};
return d[key] != null ? d[key] : key;
}
/* 顶栏/侧栏等静态 [data-i18n] / [data-i18n-aria] 文案替换 + 页面 title */
function applyStaticI18n() {
var d = DICT[locale];
if (!d) return;
document.documentElement.setAttribute('lang', locale);
var i, k, nodes;
nodes = document.querySelectorAll('[data-i18n]');
for (i = 0; i < nodes.length; i++) {
k = nodes[i].getAttribute('data-i18n');
if (d[k] != null) nodes[i].textContent = d[k];
}
nodes = document.querySelectorAll('[data-i18n-aria]');
for (i = 0; i < nodes.length; i++) {
k = nodes[i].getAttribute('data-i18n-aria');
if (d[k] != null) nodes[i].setAttribute('aria-label', d[k]);
}
document.title = tr('docs.pageTitle');
}
/* 建左侧目录(末尾附语言切换按钮;切语言时整体重建) */
function buildNav() {
navEl.innerHTML = '';
DOCS.forEach(function (doc) {
var a = document.createElement('a');
a.href = '#' + doc.id;
a.textContent = locale === 'en-US' ? doc.titleEn : doc.title;
a.dataset.docId = doc.id;
navEl.appendChild(a);
});
var langBtn = document.createElement('button');
langBtn.type = 'button';
langBtn.className = 'docs-lang-toggle';
langBtn.textContent = locale === 'zh-CN' ? 'English' : '中文';
langBtn.setAttribute('aria-label', locale === 'zh-CN' ? 'Switch to English' : '切换为中文');
langBtn.addEventListener('click', function () {
locale = locale === 'zh-CN' ? 'en-US' : 'zh-CN';
try { localStorage.setItem('astrion_site_locale', locale); } catch (e) {}
applyStaticI18n();
buildNav();
renderNav();
if (currentId) loadDoc(currentId);
});
navEl.appendChild(langBtn);
}
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">' + tr('docs.loading') + '</p>';
var path = (locale === 'en-US' ? 'content/en/' : 'content/') + doc.file;
fetch(path)
.then(function (res) {
if (!res.ok && locale === 'en-US') {
/* 英文版缺失时回退中文(保底:单篇未翻译不至于空白) */
return fetch('content/' + doc.file).then(function (r2) {
if (!r2.ok) throw new Error('HTTP ' + r2.status);
return r2.text();
});
}
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">' +
tr('docs.loadFailedTpl').replace('{err}', String(err)) + '</p>';
});
}
window.addEventListener('hashchange', function () {
var id = docIdFromHash();
if (id && id !== currentId) loadDoc(id);
});
applyStaticI18n();
buildNav();
var first = docIdFromHash();
if (location.hash !== '#' + first) {
/* 把默认篇目写进地址栏,刷新/分享行为一致 */
history.replaceState(null, '', '#' + first);
}
loadDoc(first);
})();