fix: support GitHub Pages project path
This commit is contained in:
parent
eef76d89f7
commit
2f552b453b
2
.github/workflows/pages.yml
vendored
2
.github/workflows/pages.yml
vendored
@ -44,6 +44,8 @@ jobs:
|
||||
run: npm ci
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
DOCS_SITE_BASE_PATH: /docs
|
||||
run: npm run docs:build
|
||||
|
||||
- name: Smoke generated site
|
||||
|
||||
@ -17,8 +17,9 @@ const root=document.documentElement;const saved=localStorage.getItem("theme");if
|
||||
const sidebar=document.querySelector(".sidebar");document.querySelector("[data-nav-toggle]")?.addEventListener("click",()=>sidebar?.classList.toggle("open"));document.querySelectorAll(".sidebar a").forEach(a=>a.addEventListener("click",()=>sidebar?.classList.remove("open")));
|
||||
document.querySelector("[data-locale]")?.addEventListener("change",e=>{const url=e.target.selectedOptions[0]?.dataset.url;if(url)location.href=url});
|
||||
const modal=document.querySelector(".search-modal");const input=document.querySelector("[data-search-input]");const results=document.querySelector("[data-search-results]");let pagefindReady;
|
||||
function openSearch(){modal?.classList.add("open");setTimeout(()=>input?.focus(),0);pagefindReady ||= import("/pagefind/pagefind.js").then(m=>m.init?.().then?.(()=>m)??m)}
|
||||
function withBase(url){const base=window.OPENCLAW_DOCS_BASE||"";return url.startsWith("/")?base+url:url}
|
||||
function openSearch(){modal?.classList.add("open");setTimeout(()=>input?.focus(),0);pagefindReady ||= import(withBase("/pagefind/pagefind.js")).then(m=>m.init?.().then?.(()=>m)??m)}
|
||||
document.querySelectorAll("[data-search-open]").forEach(b=>b.addEventListener("click",openSearch));document.querySelector("[data-search-close]")?.addEventListener("click",()=>modal?.classList.remove("open"));modal?.addEventListener("click",e=>{if(e.target===modal)modal.classList.remove("open")});addEventListener("keydown",e=>{if((e.metaKey||e.ctrlKey)&&e.key.toLowerCase()==="k"){e.preventDefault();openSearch()}if(e.key==="Escape")modal?.classList.remove("open")});
|
||||
let seq=0;input?.addEventListener("input",async e=>{const q=e.target.value.trim();const id=++seq;if(!q){results.innerHTML="";return}results.innerHTML="<p>Searching...</p>";const pf=await pagefindReady;if(id!==seq)return;const search=await pf.search(q);const items=await Promise.all(search.results.slice(0,12).map(r=>r.data()));if(id!==seq)return;results.innerHTML=items.length?items.map(item=>'<a class="search-result" href="'+item.url+'"><strong>'+item.meta.title+'</strong><p>'+item.excerpt+'</p></a>').join(""):"<p>No results.</p>"});
|
||||
let seq=0;input?.addEventListener("input",async e=>{const q=e.target.value.trim();const id=++seq;if(!q){results.innerHTML="";return}results.innerHTML="<p>Searching...</p>";const pf=await pagefindReady;if(id!==seq)return;const search=await pf.search(q);const items=await Promise.all(search.results.slice(0,12).map(r=>r.data()));if(id!==seq)return;results.innerHTML=items.length?items.map(item=>'<a class="search-result" href="'+withBase(item.url)+'"><strong>'+item.meta.title+'</strong><p>'+item.excerpt+'</p></a>').join(""):"<p>No results.</p>"});
|
||||
`;
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ const docsDir = path.join(root, "docs");
|
||||
const outDir = path.join(root, "dist", "docs-site");
|
||||
const config = JSON.parse(fs.readFileSync(path.join(docsDir, "docs.json"), "utf8"));
|
||||
const md = createMarkdownRenderer();
|
||||
const basePath = normalizeBasePath(process.env.DOCS_SITE_BASE_PATH ?? "");
|
||||
|
||||
fs.rmSync(outDir, { recursive: true, force: true });
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
@ -114,9 +115,9 @@ function writePage(page) {
|
||||
const activeTab = activeTabTitle(nav, page.slug);
|
||||
const prev = activeIndex > 0 ? flat[activeIndex - 1] : null;
|
||||
const next = activeIndex >= 0 && activeIndex < flat.length - 1 ? flat[activeIndex + 1] : null;
|
||||
const html = localizeLinks(renderMdxish(page.body, md), page.locale);
|
||||
const html = rewriteInternalUrls(renderMdxish(page.body, md), page.locale);
|
||||
const toc = tableOfContents(html);
|
||||
const outPath = path.join(outDir, pageUrl(page).replace(/^\//, ""), "index.html");
|
||||
const outPath = path.join(outDir, pageRoute(page).replace(/^\//, ""), "index.html");
|
||||
fs.mkdirSync(path.dirname(outPath), { recursive: true });
|
||||
fs.writeFileSync(outPath, layout({ page, nav, activeTab, html, toc, prev, next }), "utf8");
|
||||
}
|
||||
@ -132,9 +133,9 @@ function layout({ page, nav, activeTab, html, toc, prev, next }) {
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="description" content="${escapeAttr(page.summary || config.description || "")}">
|
||||
<title>${escapeHtml(title)}</title>
|
||||
<link rel="icon" href="/assets/pixel-lobster.svg">
|
||||
<link rel="stylesheet" href="/assets/docs-site.css">
|
||||
<script>document.documentElement.dataset.theme=localStorage.getItem("theme")||"light"</script>
|
||||
<link rel="icon" href="${publicPath("/assets/pixel-lobster.svg")}">
|
||||
<link rel="stylesheet" href="${publicPath("/assets/docs-site.css")}">
|
||||
<script>window.OPENCLAW_DOCS_BASE=${JSON.stringify(basePath)};document.documentElement.dataset.theme=localStorage.getItem("theme")||"light"</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="topbar"><button data-nav-toggle>Menu</button><strong>${escapeHtml(config.name)}</strong><button class="search-button" data-search-open>Search</button></div>
|
||||
@ -154,7 +155,7 @@ ${tocHtml(toc)}
|
||||
</main>
|
||||
</div>
|
||||
${searchModal()}
|
||||
<script type="module" src="/assets/docs-site.js"></script>
|
||||
<script type="module" src="${publicPath("/assets/docs-site.js")}"></script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
@ -172,7 +173,7 @@ function sidebar(page, nav, activeTab) {
|
||||
}).join("");
|
||||
const groups = (nav.find((tab) => tab.title === activeTab) ?? nav[0])?.groups ?? [];
|
||||
return `<aside class="sidebar">
|
||||
<a class="brand" href="${page.locale === "en" ? "/" : `/${page.locale}/`}"><img src="/assets/pixel-lobster.svg" alt=""><span><strong>${escapeHtml(config.name)}</strong><small>self-hosted agent gateway</small></span></a>
|
||||
<a class="brand" href="${pageUrl(pageByKey.get(pageKey(page.locale, "index")) ?? page)}"><img src="${publicPath("/assets/pixel-lobster.svg")}" alt=""><span><strong>${escapeHtml(config.name)}</strong><small>self-hosted agent gateway</small></span></a>
|
||||
<div class="tools"><select data-locale aria-label="Language">${options}</select><button type="button" data-theme-toggle>Theme</button></div>
|
||||
<button class="search-button" type="button" data-search-open>Search docs <span>⌘K</span></button>
|
||||
<nav class="tabs">${tabs}</nav>
|
||||
@ -219,7 +220,7 @@ function writeRedirects() {
|
||||
const target = path.join(outDir, source.replace(/^\//, ""), "index.html");
|
||||
if (fs.existsSync(target)) continue;
|
||||
fs.mkdirSync(path.dirname(target), { recursive: true });
|
||||
fs.writeFileSync(target, redirectHtml(dest), "utf8");
|
||||
fs.writeFileSync(target, redirectHtml(publicPath(dest)), "utf8");
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,7 +234,9 @@ function writeStaticAssets() {
|
||||
fs.writeFileSync(path.join(assetsDir, "docs-site.css"), siteCss(), "utf8");
|
||||
fs.writeFileSync(path.join(assetsDir, "docs-site.js"), siteJs(), "utf8");
|
||||
fs.writeFileSync(path.join(outDir, ".nojekyll"), "", "utf8");
|
||||
fs.writeFileSync(path.join(outDir, "CNAME"), process.env.DOCS_SITE_CNAME ?? "docs.openclaw.ai", "utf8");
|
||||
if (process.env.DOCS_SITE_CNAME) {
|
||||
fs.writeFileSync(path.join(outDir, "CNAME"), process.env.DOCS_SITE_CNAME, "utf8");
|
||||
}
|
||||
}
|
||||
|
||||
function copyPublicFiles() {
|
||||
@ -266,22 +269,32 @@ function firstPage(tab) {
|
||||
}
|
||||
|
||||
function localeUrlForSlug(locale, slug) {
|
||||
return pageByKey.has(pageKey(locale, slug)) ? pageUrl(pageByKey.get(pageKey(locale, slug))) : locale === "en" ? "/" : `/${locale}/`;
|
||||
return pageByKey.has(pageKey(locale, slug)) ? pageUrl(pageByKey.get(pageKey(locale, slug))) : publicPath(locale === "en" ? "/" : `/${locale}/`);
|
||||
}
|
||||
|
||||
function pageUrl(page) {
|
||||
return publicPath(pageRoute(page));
|
||||
}
|
||||
|
||||
function pageRoute(page) {
|
||||
const prefix = page.locale === "en" ? "" : `/${page.locale}`;
|
||||
return page.slug === "index" ? (prefix ? `${prefix}/` : "/") : `${prefix}/${page.slug}/`;
|
||||
}
|
||||
|
||||
function localizeLinks(html, locale) {
|
||||
if (locale === "en") return html;
|
||||
return html.replace(/href="\/([^"#?]*)([#?][^"]*)?"/g, (match, target, suffix = "") => {
|
||||
if (!target || target.startsWith("assets/") || target.startsWith("pagefind/")) return match;
|
||||
function rewriteInternalUrls(html, locale) {
|
||||
return html.replace(/\b(href|src)="\/([^"#?]*)([#?][^"]*)?"/g, (match, attr, target, suffix = "") => {
|
||||
if (attr === "src") return `${attr}="${publicPath(`/${target}`)}${suffix}"`;
|
||||
if (!target || target.startsWith("assets/") || target.startsWith("pagefind/")) {
|
||||
return `${attr}="${publicPath(`/${target}`)}${suffix}"`;
|
||||
}
|
||||
const segments = target.replace(/\/$/, "").split("/");
|
||||
const maybeLocale = segments[0];
|
||||
if (pageByKey.has(pageKey(maybeLocale, normalizeSlug(segments.slice(1).join("/") || "index")))) {
|
||||
return `${attr}="${pageUrl(pageByKey.get(pageKey(maybeLocale, normalizeSlug(segments.slice(1).join("/") || "index"))))}${suffix}"`;
|
||||
}
|
||||
const slug = normalizeSlug(target.replace(/\/$/, ""));
|
||||
if (!pageByKey.has(pageKey(locale, slug))) return match;
|
||||
const localized = slug === "index" ? `/${locale}/` : `/${locale}/${target.replace(/\/$/, "")}/`;
|
||||
return `href="${localized}${suffix}"`;
|
||||
const page = pageByKey.get(pageKey(locale, slug)) ?? pageByKey.get(pageKey("en", slug));
|
||||
return page ? `${attr}="${pageUrl(page)}${suffix}"` : `${attr}="${publicPath(`/${target}`)}${suffix}"`;
|
||||
});
|
||||
}
|
||||
|
||||
@ -303,6 +316,17 @@ function cleanPath(value) {
|
||||
return hash ? `${cleaned}#${hash}` : cleaned;
|
||||
}
|
||||
|
||||
function publicPath(value) {
|
||||
if (!basePath) return value;
|
||||
if (value === "/") return `${basePath}/`;
|
||||
return `${basePath}${value.startsWith("/") ? value : `/${value}`}`;
|
||||
}
|
||||
|
||||
function normalizeBasePath(value) {
|
||||
if (!value || value === "/") return "";
|
||||
return `/${value.replace(/^\/+|\/+$/g, "")}`;
|
||||
}
|
||||
|
||||
function htmlLang(locale) {
|
||||
return locale === "zh-CN" ? "zh-CN" : locale === "zh-TW" ? "zh-TW" : locale;
|
||||
}
|
||||
|
||||
@ -34,10 +34,14 @@ for (const rel of required) {
|
||||
}
|
||||
}
|
||||
const zhReactions = fs.readFileSync(path.join(site, "zh-CN/tools/reactions/index.html"), "utf8");
|
||||
if (!zhReactions.includes('data-url="/zh-CN/tools/reactions/"')) {
|
||||
if (!/data-url="(?:\/docs)?\/zh-CN\/tools\/reactions\/"/.test(zhReactions)) {
|
||||
throw new Error("zh-CN reactions: language picker does not preserve current page");
|
||||
}
|
||||
if (!zhReactions.includes('href="/zh-CN/tools/agent-send')) {
|
||||
if (!/href="(?:\/docs)?\/zh-CN\/tools\/agent-send/.test(zhReactions)) {
|
||||
throw new Error("zh-CN reactions: article links do not stay in locale");
|
||||
}
|
||||
const index = fs.readFileSync(path.join(site, "index.html"), "utf8");
|
||||
if (/src="\/assets\//.test(index) || /href="\/assets\//.test(index)) {
|
||||
throw new Error("index: absolute asset paths were not base-path rewritten");
|
||||
}
|
||||
console.log(`docs site smoke ok: ${required.length} checks`);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user