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
46 changes: 37 additions & 9 deletions scripts/docs-site/assets.mjs

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added scripts/docs-site/assets/molty-avatar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 34 additions & 4 deletions scripts/docs-site/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { renderPageOgSvg } from "./og-card-template.mjs";
const root = process.cwd();
const docsDir = path.join(root, "docs");
const siteAssetsDir = path.join(root, "scripts", "docs-site");
const shellPublicAssetsDir = path.join(siteAssetsDir, "assets");
const outDir = path.join(root, "dist", "docs-site");
const config = JSON.parse(fs.readFileSync(path.join(docsDir, "docs.json"), "utf8"));
const sourceMetadata = readSourceMetadata(root);
Expand Down Expand Up @@ -325,6 +326,7 @@ ${page.hidden ? "" : pageMarkdownScript(page)}
<h1>${escapeHtml(page.title)}</h1>
${pageStatus(page)}
</header>
${pageSearchMetadata(page, nav)}
<div class="doc"${page.hidden ? ' data-pagefind-ignore' : ' data-pagefind-body'}>${html}</div>
${page.hidden ? "" : pageFeedback(page)}
${pager(prev, next)}
Expand Down Expand Up @@ -352,7 +354,7 @@ function siteHeader(page, nav, activeTab) {
return `<header class="site-header">
<div class="header-row">
<div class="header-left"><a class="brand" href="${pageUrl(pageByKey.get(pageKey(page.locale, "index")) ?? page)}"><img src="${publicPath("/assets/pixel-lobster.svg")}" alt=""></a>${languagePicker(page)}</div>
<button class="search-button" type="button" data-search-open>${icon("search")}<span class="search-label">Search...</span><span class="search-shortcut">⌘K</span></button>
<button class="search-button" type="button" data-search-open>${icon("search")}<span class="search-label">Search...</span><span class="search-shortcut" aria-hidden="true">${icon("command")}<span>K</span></span></button>
<nav class="header-links">${topLink("GitHub", "https://github.com/openclaw/openclaw", "github")}${topLink("Releases", "https://github.com/openclaw/openclaw/releases", "package")}${topLink("Discord", "https://discord.com/invite/clawd", "discord")}<button class="theme-toggle" type="button" data-theme-toggle aria-label="Toggle theme"><span class="theme-toggle-icon theme-toggle-icon-dark">${icon("moon")}</span><span class="theme-toggle-icon theme-toggle-icon-light">${icon("sun")}</span></button></nav>
<button class="nav-toggle" type="button" data-nav-toggle>Menu</button>
</div>
Expand Down Expand Up @@ -405,6 +407,16 @@ function articleMeta(page, nav) {
return crumbTrail || tools ? `<div class="article-meta-row">${crumbTrail}${tools}</div>` : "";
}

function pageSearchMetadata(page, nav) {
if (page.hidden) return "";
const category = activeTabTitle(nav, page.slug);
const section = groupForPage(nav, page.slug);
return [
category ? `<span hidden data-pagefind-meta="category">${escapeHtml(category)}</span>` : "",
section ? `<span hidden data-pagefind-meta="section">${escapeHtml(section)}</span>` : "",
].filter(Boolean).join("");
}

function breadcrumbs(page, nav) {
if (page.hidden) return "";
const activeTab = activeTabTitle(nav, page.slug);
Expand Down Expand Up @@ -479,6 +491,9 @@ function icon(name) {
if (name === "perplexity") return `<svg class="icon icon-perplexity" aria-hidden="true" focusable="false" width="18" height="18" viewBox="0 0 34 38" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M5.12114 0.0400391L15.919 9.98864V9.98636V0.062995H18.0209V10.0332L28.8671 0.0400391V11.3829H33.3202V27.744H28.8808V37.8442L18.0209 28.303V37.9538H15.919V28.4604L5.13338 37.96V27.744H0.680176V11.3829H5.12114V0.0400391ZM14.3344 13.4592H2.78208V25.6677H5.13074V21.8167L14.3344 13.4592ZM7.23518 22.7379V33.3271L15.919 25.6786V14.8506L7.23518 22.7379ZM18.0814 25.5775V14.8404L26.7677 22.7282V27.744H26.7789V33.219L18.0814 25.5775ZM28.8808 25.6677H31.2183V13.4592H19.752L28.8808 21.7302V25.6677ZM26.7652 11.3829V4.81584L19.6374 11.3829H26.7652ZM14.3507 11.3829H7.22306V4.81584L14.3507 11.3829Z"/></svg>`;
const paths = {
"search": '<path d="m21 21-4.35-4.35"/><circle cx="11" cy="11" r="7"/>',
"command": '<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3"/>',
"corner-down-left": '<path d="M20 4v7a4 4 0 0 1-4 4H4"/><path d="m9 10-5 5 5 5"/>',
"x": '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
"package": '<path d="m21 8-9-5-9 5 9 5 9-5Z"/><path d="m3 8 9 5 9-5"/><path d="M12 22V13"/><path d="m3 8v8l9 6 9-6V8"/>',
"moon": '<path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/>',
"sun": '<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>',
Expand Down Expand Up @@ -542,7 +557,17 @@ function raiseIssueUrl(page) {
}

function searchModal() {
return `<div class="search-modal"><div class="search-panel"><div class="search-head"><input data-search-input placeholder="Search commands, channels, config..."><button data-search-close>Close</button></div><div class="search-hints" aria-label="Search shortcuts"><button type="button" data-search-suggestion="install">install</button><button type="button" data-search-suggestion="telegram">telegram</button><button type="button" data-search-suggestion="gateway">gateway</button><button type="button" data-search-suggestion="plugins">plugins</button></div><div class="search-results" data-search-results></div></div></div>`;
const avatar = chatAvatarAssets();
const suggestions = [
"Install OpenClaw",
"Set up Telegram",
"Fix Gateway",
"Build a plugin",
];
const molty = chatApiUrl
? `<button class="search-molty" type="button" data-search-molty><img src="${avatar.staticPath}" alt=""><span><span data-search-molty-prefix>Ask Molty</span> <strong data-search-molty-term hidden></strong></span><span class="search-molty-shortcut" aria-hidden="true">${icon("command")}${icon("corner-down-left")}</span></button>`
: "";
return `<div class="search-modal"><div class="search-panel" role="dialog" aria-modal="true" aria-label="Search documentation"><div class="search-head"><input data-search-input placeholder="Search commands, channels, config..." aria-label="Search documentation"><button class="search-close" type="button" data-search-clear aria-label="Clear search">${icon("x")}</button></div><div class="search-hints" aria-label="Search suggestions">${suggestions.map(label => `<button type="button" data-search-suggestion="${escapeAttr(label)}">${escapeHtml(label)}</button>`).join("")}</div><div class="search-results" data-search-results role="listbox" aria-label="Search results"></div>${molty}</div></div>`;
}

function writeLlmsIndex() {
Expand Down Expand Up @@ -642,10 +667,14 @@ function docsOrigin() {
}

function chatAvatarAssets() {
const staticPath = fs.existsSync(path.join(docsDir, "assets", "molty-avatar.png"))
const staticPath = fs.existsSync(path.join(shellPublicAssetsDir, "molty-avatar.png"))
? "/assets/molty-avatar.png"
: fs.existsSync(path.join(docsDir, "assets", "molty-avatar.png"))
? "/assets/molty-avatar.png"
: "/assets/pixel-lobster.svg";
const hoverPath = fs.existsSync(path.join(docsDir, "assets", "molty-avatar-hover.gif"))
const hoverPath = fs.existsSync(path.join(shellPublicAssetsDir, "molty-avatar-hover.gif"))
? "/assets/molty-avatar-hover.gif"
: fs.existsSync(path.join(docsDir, "assets", "molty-avatar-hover.gif"))
? "/assets/molty-avatar-hover.gif"
: staticPath;
return { staticPath: publicPath(staticPath), hoverPath: publicPath(hoverPath) };
Expand Down Expand Up @@ -799,6 +828,7 @@ function collectNavSlugs(nav) {
function writeStaticAssets() {
const assetsDir = path.join(outDir, "assets");
fs.mkdirSync(assetsDir, { recursive: true });
copyDir(shellPublicAssetsDir, assetsDir);
fs.writeFileSync(path.join(assetsDir, "docs-site.css"), shellCss, "utf8");
fs.writeFileSync(path.join(assetsDir, "docs-site.js"), shellJs, "utf8");
const mermaidDist = path.join(root, "node_modules", "mermaid", "dist");
Expand Down
13 changes: 10 additions & 3 deletions scripts/docs-site/smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,16 @@ if (!/Português \(BR\)/.test(index)) {
if (
!/data-docs-chat/.test(index) ||
!/OPENCLAW_DOCS_CHAT_API/.test(index) ||
!/data-static-src="\/assets\/(?:molty-avatar\.png|pixel-lobster\.svg)"/.test(index) ||
!/data-hover-src="\/assets\/(?:molty-avatar-hover\.gif|pixel-lobster\.svg)"/.test(index) ||
!/data-static-src="\/assets\/molty-avatar\.png"/.test(index) ||
!/data-hover-src="\/assets\/molty-avatar-hover\.gif"/.test(index) ||
!/<h2 id="docs-chat-title">Molty<\/h2>/.test(index)
) {
throw new Error("index: docs chat widget was not rendered");
}
if (!fs.existsSync(path.join(site, "assets/molty-avatar.png"))
|| !fs.existsSync(path.join(site, "assets/molty-avatar-hover.gif"))) {
throw new Error("assets: Molty avatar assets were not copied");
}
const chatCss = fs.readFileSync(path.join(site, "assets/docs-site.css"), "utf8");
if (!/data-chat-copy/.test(index)
|| !/data-chat-retry/.test(index)
Expand Down Expand Up @@ -327,7 +331,10 @@ if (!/function initChat/.test(siteJs)
if (/matchMedia\("\(min-width:1121px\)"\)\.matches\)setOpen\(true\)/.test(siteJs)) {
throw new Error("assets: docs chat must stay closed until Ask Molty is pressed");
}
if (!/function runSearch/.test(siteJs) || !/setTimeout\(\(\)=>runSearch\(expandSearchQuery\(q\),id\),140\)/.test(siteJs)) {
if (!/function runSearch/.test(siteJs)
|| !/function scheduleSearch\(immediate=false\)/.test(siteJs)
|| !/input\?\.addEventListener\("input",\(\)=>scheduleSearch\(false\)\)/.test(siteJs)
|| !/setTimeout\(run,240\)/.test(siteJs)) {
throw new Error("assets: search input is not debounced");
}
if (!/const searchAliases=/.test(siteJs) || !/data-search-suggestion/.test(index)) {
Expand Down
41 changes: 34 additions & 7 deletions scripts/docs-site/visual-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -204,20 +204,47 @@ async function checkDesktop() {
const rect = key?.getBoundingClientRect();
return {
display: style?.display,
text: key?.textContent?.trim(),
hasCommandIcon: Boolean(key?.querySelector(".icon-command")),
width: rect?.width,
height: rect?.height,
borderRadius: style?.borderRadius,
borderColor: style?.borderColor,
backgroundColor: style?.backgroundColor,
fontSize: style?.fontSize,
};
});
if (searchShortcut.display !== "grid"
|| searchShortcut.width < 42
|| searchShortcut.height < 26
|| parseFloat(searchShortcut.borderRadius ?? "0") < 8
|| parseFloat(searchShortcut.fontSize ?? "0") < 12) {
throw new Error(`search shortcut keycap failed: ${JSON.stringify(searchShortcut)}`);
if (searchShortcut.text !== "K"
|| !searchShortcut.hasCommandIcon
|| searchShortcut.width > 32
|| searchShortcut.height > 18
|| parseFloat(searchShortcut.borderRadius ?? "0") !== 0
|| searchShortcut.backgroundColor !== "rgba(0, 0, 0, 0)") {
throw new Error(`search shortcut inline hint failed: ${JSON.stringify(searchShortcut)}`);
}
await page.keyboard.press(process.platform === "darwin" ? "Meta+K" : "Control+K");
const searchOpen = await page.evaluate(() => ({
open: document.querySelector(".search-modal")?.classList.contains("open"),
focused: document.activeElement?.matches("[data-search-input]"),
}));
if (!searchOpen.open || !searchOpen.focused) {
throw new Error(`search shortcut did not open and focus input: ${JSON.stringify(searchOpen)}`);
}
await page.keyboard.press("Escape");
await page.evaluate(() => {
const textarea = document.createElement("textarea");
textarea.dataset.shortcutSmoke = "true";
document.body.append(textarea);
textarea.focus();
});
await page.keyboard.press(process.platform === "darwin" ? "Meta+K" : "Control+K");
const editableShortcut = await page.evaluate(() => ({
open: document.querySelector(".search-modal")?.classList.contains("open"),
focused: document.activeElement?.matches("[data-shortcut-smoke]"),
}));
if (editableShortcut.open || !editableShortcut.focused) {
throw new Error(`search shortcut hijacked editable target: ${JSON.stringify(editableShortcut)}`);
}
await page.evaluate(() => document.querySelector("[data-shortcut-smoke]")?.remove());
await page.waitForTimeout(350);
const initialFloatingChat = await page.evaluate(() => {
const chat = document.querySelector(".docs-chat");
Expand Down