Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Website UI, translations, and publishing for the OpenClaw docs site.

English content and navigation are authored in [`openclaw/openclaw`](https://github.com/openclaw/openclaw), under `docs/`, and synced here. This repository owns the current website renderer, design, search, and hosting.

## Support

For official OpenClaw support, visit [community.openclaw.ai](https://community.openclaw.ai/). Use this repository's issues for reproducible problems with the docs website, renderer, translations, or publishing. Report security vulnerabilities through the project's private security reporting process, not the public support forum.

## How it works

1. English docs are authored in `openclaw/openclaw`.
Expand Down
7 changes: 5 additions & 2 deletions scripts/docs-site/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const canonicalOrigin = (process.env.DOCS_SITE_CANONICAL_ORIGIN
?? (process.env.DOCS_SITE_CNAME ? `https://${process.env.DOCS_SITE_CNAME}` : "https://docs.openclaw.ai"))
.replace(/\/$/, "");
const feedbackIssueRepository = normalizeRepository(process.env.DOCS_FEEDBACK_ISSUE_REPO ?? "openclaw/openclaw");
const supportUrl = "https://community.openclaw.ai/";
const llmsFullAvailable = process.env.DOCS_SITE_LLMS_FULL_AVAILABLE === "1";
const ogImagePath = "/og-card.png";
const chatApiUrl = process.env.DOCS_SITE_CHAT_API_URL ?? "/ask-molty/api/chat";
Expand Down Expand Up @@ -375,6 +376,7 @@ ${pageStatus(page)}
</header>
${pageSearchMetadata(page, nav)}
<div class="doc"${page.hidden ? ' data-pagefind-ignore' : ' data-pagefind-body'}>${html}</div>
${page.hidden ? "" : `<aside class="page-support" aria-label="Official OpenClaw support" data-pagefind-ignore><p>For official OpenClaw support, visit our support forum.</p><a href="${supportUrl}">Get support</a></aside>`}
${page.hidden ? "" : pageFeedback(page)}
${pager(prev, next)}
</main>
Expand All @@ -395,7 +397,7 @@ function assetUrl(file) {

function siteHeader(page) {
const controls = homeStringsForLocale(page.locale).controls;
const network = [["Product", "https://openclaw.ai/"], ["Install", "https://openclaw.ai/install"], ["Ecosystem", "https://openclaw.ai/ecosystem"], ["Integrations", "https://openclaw.ai/integrations"], ["Blog", "https://openclaw.ai/blog"], ["Community", "https://community.openclaw.ai/"]];
const network = [["Product", "https://openclaw.ai/"], ["Install", "https://openclaw.ai/install"], ["Ecosystem", "https://openclaw.ai/ecosystem"], ["Integrations", "https://openclaw.ai/integrations"], ["Blog", "https://openclaw.ai/blog"], ["Community", "https://community.openclaw.ai/"], ["Support", supportUrl]];
return `<header class="site-header">
<div class="header-row">
<div class="header-left"><a class="brand" href="${pageUrl(allPageByKey.get(pageKey(page.locale, "index")) ?? page)}"${previewLinkAttrs(allPageByKey.get(pageKey(page.locale, "index")) ?? page, "OpenClaw Docs")}><img src="${publicPath("/assets/openclaw.svg")}" alt=""><span class="brand-name">OpenClaw</span><span class="brand-tag">Docs</span></a></div>
Expand All @@ -422,7 +424,7 @@ function siteFooter(analyticsEligible = false) {
];
const groups = [
["Product", [["Install", "https://openclaw.ai/install"], ["Showcase", "https://openclaw.ai/showcase"], ["Integrations", "https://openclaw.ai/integrations"], ["Docs", pageUrl(home), home]]],
["Resources", [["Blog", "https://openclaw.ai/blog"], ["Podcast", "https://openclaw.ai/podcast"], ["Press", "https://openclaw.ai/press"], ["Shoutouts", "https://openclaw.ai/shoutouts"], ["Releases", "https://github.com/openclaw/openclaw/releases"], ["Privacy policy", "https://openclaw.ai/privacy"]]],
["Resources", [["Support", supportUrl], ["Blog", "https://openclaw.ai/blog"], ["Podcast", "https://openclaw.ai/podcast"], ["Press", "https://openclaw.ai/press"], ["Shoutouts", "https://openclaw.ai/shoutouts"], ["Releases", "https://github.com/openclaw/openclaw/releases"], ["Privacy policy", "https://openclaw.ai/privacy"]]],
["Project", [["Foundation", "https://openclaw.org"], ["Security", "https://openclaw.ai/security"], ["GitHub", "https://github.com/openclaw/openclaw"]]],
];
return `<footer class="site-footer">
Expand All @@ -442,6 +444,7 @@ function sidebar(page, nav, activeTab) {
const copy = homeStringsForLocale(page.locale);
return `<aside class="sidebar">
<div class="sidebar-head"><a class="brand sidebar-brand" href="${pageUrl(allPageByKey.get(pageKey(page.locale, "index")) ?? page)}" aria-label="OpenClaw Docs"><img src="${publicPath("/assets/openclaw.svg")}" alt=""><span class="brand-name">OpenClaw</span><span class="brand-tag">Docs</span></a><button class="sidebar-close" type="button" data-nav-close aria-label="${escapeAttr(copy.controls[3])}">${icon("x")}</button></div>
<nav class="sidebar-support" aria-label="Official OpenClaw support"><a href="${supportUrl}">Support</a></nav>
${rewriteInternalUrls(docsQuickNav(page.slug, icon, page.locale), page.locale)}
<nav class="docs-sections" aria-label="${escapeAttr(copy.navigation[6])}" data-back-label="${escapeAttr(copy.controls[4])}" data-sections-label="${escapeAttr(copy.navigation[6])}">${nav.map((tab) => {
const current = tab.title === activeTab;
Expand Down
7 changes: 7 additions & 0 deletions scripts/docs-site/docs-layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@
.docs-layout .search-button:hover { background: var(--paper); border-color: color-mix(in srgb,var(--ink) 22%,transparent); }}

.sidebar-tools { display: none; }
.sidebar-support { flex-shrink: 0; margin-block-end: 12px; }
.sidebar-support a { display: flex; align-items: center; min-height: 40px; padding: 8px 12px; border: 1px solid var(--docs-border); color: var(--ink); font: 500 13px/1.5 var(--oc-font-body); text-decoration: none; }
.sidebar-support a:hover { background: var(--paper); }
.sidebar-support a:focus-visible, .page-support a:focus-visible { outline: 2px solid var(--oc-focus-ring); outline-offset: 3px; }
.docs-layout .page-support { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin-block: 32px 0; padding: 16px; border: 1px solid var(--docs-border); color: var(--docs-muted); font: 400 14px/1.5 var(--oc-font-body); }
.page-support p { margin: 0; }
.page-support a { flex-shrink: 0; color: var(--ink); font-weight: 500; text-underline-offset: 4px; }

.docs-layout .page-feedback { margin-top: 48px; padding: 24px 0 0; border-top: 1px solid var(--docs-border); align-items: center; gap: 16px; color: var(--docs-muted); font: 400 13px/1.5 var(--oc-font-body); }
.docs-layout .page-feedback-prompt { gap: 6px; }
Expand Down
11 changes: 11 additions & 0 deletions scripts/docs-site/preview-build.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ test("requested pages outside navigation fit inside the 30-page preview, includi
}
const html = fs.readFileSync(path.join(f.site, "target/index.html"), "utf8");
assert.match(html, /Requested page/);
const supportUrl = 'https://community.openclaw.ai/';
for (const scope of [
html.match(/<nav class="network-nav"[\s\S]*?<\/nav>/)[0],
html.match(/<aside class="sidebar">[\s\S]*?<\/aside>/)[0],
html.match(/<footer class="site-footer">[\s\S]*?<\/footer>/)[0],
]) {
assert.ok(scope.includes(`href="${supportUrl}">Support</a>`), 'persistent official support link');
}
assert.match(html, /<aside class="page-support"[^>]*>.*official OpenClaw support.*href="https:\/\/community\.openclaw\.ai\/">Get support<\/a>/);
assert.match(html, /Page source and issue[\s\S]*?Raise issue/);
assert.match(html, /https:\/\/discord\.com\/invite\/clawd/);
const sidebar = html.match(/<aside class="sidebar">([\s\S]*?)<\/aside>/)[1];
assert.equal([...sidebar.slice(sidebar.indexOf('<nav class="docs-sections"')).matchAll(/class="nav-link/g)].length, 35);
assert.match(sidebar, /href="\/guide\/page-0"/);
Expand Down
2 changes: 1 addition & 1 deletion scripts/docs-site/site-layout.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ html { scroll-padding-top: calc(var(--sticky-header-h, 102px) + 28px); }
.nav-link { border-left: 0; border-inline-start: 2px solid transparent; padding: 7px 10px; border-radius: 0; font-size: 13px; line-height: 1.5; }
.nav-link.active { border-inline-start-color: var(--brand); box-shadow: none; background: color-mix(in srgb, var(--brand) 7%, transparent); }
.article { width: 100%; min-width: 0; padding: 40px var(--docs-inset) 64px; border-inline: 1px solid var(--line); }
.article > :is(.article-header, .doc, .page-feedback, .page-nav) { max-width: var(--docs-reading-width); margin-inline: auto; }
.article > :is(.article-header, .doc, .page-support, .page-feedback, .page-nav) { max-width: var(--docs-reading-width); margin-inline: auto; }
.article-header { margin-bottom: 36px; }
.article-meta-row { gap: 12px; margin-bottom: 28px; }
.article h1 { font-size: clamp(32px, 3vw, 42px); font-weight: 600; line-height: 1.12; letter-spacing: -.03em; }
Expand Down
7 changes: 7 additions & 0 deletions scripts/docs-site/smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,13 @@ if (/\.oc-card:first-child\{border-color:var\(--brand\)/.test(siteCss)) {
throw new Error("assets: first card is hard-highlighted");
}
const footer = DomUtils.findOne((node) => node.name === "footer", indexDocument.children);
for (const className of ["network-nav", "sidebar-support", "site-footer", "page-support"]) {
const scope = DomUtils.findOne((node) => node.attribs?.class?.split(" ").includes(className), indexDocument.children);
const supportLink = scope && DomUtils.findOne((node) => node.name === "a"
&& node.attribs.href === "https://community.openclaw.ai/"
&& ["Support", "Get support"].includes(DomUtils.textContent(node).trim()), scope.children);
if (!supportLink) throw new Error(`${className}: official support destination is missing`);
}
const foundationLink = footer && DomUtils.findOne(
(node) => node.name === "a" && node.attribs.href === "https://openclaw.org"
&& DomUtils.textContent(node).trim() === "OpenClaw Foundation",
Expand Down
12 changes: 9 additions & 3 deletions scripts/docs-site/visual-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -444,10 +444,16 @@ async function checkHeaderSurface() {
await page.waitForFunction(() => getComputedStyle(document.querySelector(".site-header")).backgroundColor === "rgba(0, 0, 0, 0)");
}
await page.getByRole("link", { name: "Community", exact: true }).first().focus();
for (const name of ["GitHub", "Reddit", "Discord", "X", "Language: English", "Toggle theme"]) {
for (const name of ["Support", "GitHub", "Reddit", "Discord", "X", "Language: English", "Toggle theme"]) {
await page.keyboard.press("Tab");
const activeName = await page.evaluate(() => document.activeElement.getAttribute("aria-label"));
if (activeName !== name) throw new Error(`Header keyboard order: expected ${name}, got ${activeName}`);
const active = await page.evaluate(() => ({
name: document.activeElement.getAttribute("aria-label") ?? document.activeElement.textContent.trim(),
href: document.activeElement.getAttribute("href"),
}));
if (active.name !== name) throw new Error(`Header keyboard order: expected ${name}, got ${active.name}`);
if (name === "Support" && active.href !== "https://community.openclaw.ai/") {
throw new Error(`Header Support destination: expected official support, got ${active.href}`);
}
}
for (const width of [320, 390, 820, 821, 1024, 1120, 1121, 1440]) {
await page.setViewportSize({ width, height: 900 });
Expand Down
Loading