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
21 changes: 12 additions & 9 deletions scripts/docs-site/analytics-behavior.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ function wireEvents(collected) {
test("selection placement and sanitized destination share one event without changing native clicks", async t => {
const destination = "https://discord.com/invite/clawd";
const { page, context, events, collected } = await openSite(t, {
articleLinks: `\n[Community](${destination})\n\n[External reference](https://example.org/guide?ref=docs#intro)\n\n[Product](https://openclaw.ai/install?ref=docs#intro)\n`,
articleLinks: `\n[Community](${destination})\n\n[External reference](https://example.org/guide?ref=docs#intro)\n\n[Product](https://openclaw.ai/install?ref=docs#intro)\n\n[Redirect slash](https://openclaw.ai/discord/?from=docs-home-community&unknown=ignored#join)\n\n[Not Discord](https://openclaw.ai/discord-extra?from=docs-header)\n`,
});
page.setDefaultTimeout(5000);
// Abort only after observing native navigation so the fixture stays loaded.
Expand Down Expand Up @@ -152,11 +152,14 @@ test("selection placement and sanitized destination share one event without chan
}
}
}
for (const [selector, placement] of [[".community-invite", "community_invite"], [".site-footer", "footer"], [".doc", "article"]]) {
await clickAndObserve(`${selector} a[href="${destination}"]`, placement, "social_link", "discord", destination, true);
for (const [selector, placement, tag] of [[".site-header", "header", "docs-header"], [".community-invite", "community_invite", "docs-community-invite"], [".site-footer", "footer", "docs-footer"]]) {
await clickAndObserve(`${selector} a[href="https://openclaw.ai/discord?from=${tag}"]`, placement, "social_link", "discord", "https://openclaw.ai/discord");
}
await clickAndObserve(`.doc a[href="${destination}"]`, "article", "social_link", "discord", destination, true);
await clickAndObserve('.doc a[href^="https://openclaw.ai/discord/?"]', "article", "social_link", "discord", "https://openclaw.ai/discord/");
await clickAndObserve('.doc a[href^="https://openclaw.ai/discord-extra"]', "article", "site_link", "openclaw.ai/discord-extra", "https://openclaw.ai/discord-extra");
await clickAndObserve('.doc a[href^="https://example.org/"]', "article", "public_reference", "https://example.org/guide", "https://example.org/guide");
await clickAndObserve('.doc a[href^="https://openclaw.ai/"]', "article", "site_link", "openclaw.ai/install", "https://openclaw.ai/install");
await clickAndObserve('.doc a[href^="https://openclaw.ai/install"]', "article", "site_link", "openclaw.ai/install", "https://openclaw.ai/install");
for (const label of ["Edit source", "Raise issue"]) {
const link = page.getByRole("link", { name: label, exact: true });
const url = new URL(await link.getAttribute("href"));
Expand All @@ -165,15 +168,15 @@ test("selection placement and sanitized destination share one event without chan
}

const beforePrivate = (await events("select_content")).length;
const nativeBeforePrivate = count("click", destination).length;
const nativeBeforePrivate = count("click", "https://openclaw.ai/discord").length;
await page.locator("[data-chat-toggle]").click();
await page.locator(`.site-footer a[href="${destination}"]`).click();
await page.locator('.site-footer a[href="https://openclaw.ai/discord?from=docs-footer"]').click();
await page.waitForTimeout(1200);
await page.locator("[data-chat-minimize]").click();
await page.waitForTimeout(1200);
assert.equal((await events("select_content")).length, beforePrivate, "private selection is not replayed on close");
assert.equal(count("click", destination).length, nativeBeforePrivate, "private native click is not replayed on close");
await clickAndObserve(`.site-footer a[href="${destination}"]`, "footer", "social_link", "discord", destination, true);
assert.equal(count("click", "https://openclaw.ai/discord").length, nativeBeforePrivate, "private native click is not replayed on close");
await clickAndObserve('.site-footer a[href="https://openclaw.ai/discord?from=docs-footer"]', "footer", "social_link", "discord", "https://openclaw.ai/discord");
});

test("selection destination stays absent for same-origin, non-anchor, unknown and blocked contexts", async t => {
Expand Down Expand Up @@ -205,7 +208,7 @@ test("selection destination stays absent for same-origin, non-anchor, unknown an
assert.equal((await events("select_content")).length, before);
await setBrowserPrivacy(page, true);
const requestsBefore = googleRequests.length;
await page.locator('.site-footer a[href="https://discord.com/invite/clawd"]').click();
await page.locator('.site-footer a[href="https://openclaw.ai/discord?from=docs-footer"]').click();
await page.waitForTimeout(1200);
await setBrowserPrivacy(page, false);
assert.equal((await events("select_content")).length, before);
Expand Down
3 changes: 2 additions & 1 deletion scripts/docs-site/analytics-events.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ export function createDocsAnalyticsEvents(analytics) {
const destination = url.origin === window.location.origin ? {} : { link_url: url.origin + url.pathname, link_domain: url.hostname };
const artifact = artifactChoice(url);
if (link.matches(".search-result")) flushSearch();
if (socialHosts[url.hostname]) select("social_link", socialHosts[url.hostname], location, destination);
if (url.origin === "https://openclaw.ai" && /^\/discord\/?$/.test(url.pathname)) select("social_link", "discord", location, destination);
else if (socialHosts[url.hostname]) select("social_link", socialHosts[url.hostname], location, destination);
else if (canonicalHosts.has(url.hostname) && !/\/ask-molty\/|\/__elements/i.test(url.pathname)) {
select(link.matches(".search-result") ? "search_result" : artifact ? "download" : url.hostname === "docs.openclaw.ai" ? "documentation" : "site_link", url.hostname === "docs.openclaw.ai" ? url.pathname : url.hostname + url.pathname, location, { ...artifact, ...destination });
} else if (link.closest(".page-actions")) select("page_action", /chatgpt/.test(url.hostname) ? "ask_chatgpt" : /claude/.test(url.hostname) ? "ask_claude" : "view_markdown", location, destination);
Expand Down
12 changes: 6 additions & 6 deletions scripts/docs-site/build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ function siteHeader(page) {
<button class="search-button" type="button" data-search-open aria-label="${escapeAttr(controls[0])}">${icon("search")}<span class="search-label">${escapeHtml(controls[0])}</span><span class="search-shortcut" aria-hidden="true">${icon("command")}<span>K</span></span></button>
<div class="header-actions">
<nav class="network-nav" aria-label="OpenClaw sites">${network.map(([label, href]) => `<a href="${href}">${label}</a>`).join("")}</nav>
<nav class="header-links" aria-label="Community and preferences"><div class="header-social-group" role="group" aria-label="Social links">${socialIconLinks()}</div><div class="header-preferences-group" role="group" aria-label="Preferences">${languagePicker(page)}<button class="theme-toggle" type="button" data-theme-toggle aria-label="${escapeAttr(controls[1])}"><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></div><button class="nav-toggle" type="button" data-nav-toggle aria-label="${escapeAttr(controls[2])}" aria-expanded="false"><span></span><span></span><span></span></button></nav>
<nav class="header-links" aria-label="Community and preferences"><div class="header-social-group" role="group" aria-label="Social links">${socialIconLinks("docs-header")}</div><div class="header-preferences-group" role="group" aria-label="Preferences">${languagePicker(page)}<button class="theme-toggle" type="button" data-theme-toggle aria-label="${escapeAttr(controls[1])}"><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></div><button class="nav-toggle" type="button" data-nav-toggle aria-label="${escapeAttr(controls[2])}" aria-expanded="false"><span></span><span></span><span></span></button></nav>
</div>
</div>
</header>`;
Expand All @@ -416,7 +416,7 @@ function siteFooter(analyticsEligible = false) {
const socials = [
["X", "https://x.com/openclaw", "x-social"],
["GitHub", "https://github.com/openclaw/openclaw", "github"],
["Discord", "https://discord.com/invite/clawd", "discord"],
["Discord", "https://openclaw.ai/discord?from=docs-footer", "discord"],
["Reddit", "https://www.reddit.com/r/openclaw/", "reddit"],
["TikTok", "https://www.tiktok.com/@open_claw", "tiktok"],
["LinkedIn", "https://www.linkedin.com/company/the-openclaw-foundation", "linkedin"],
Expand Down Expand Up @@ -452,7 +452,7 @@ ${rewriteInternalUrls(docsQuickNav(page.slug, icon, page.locale), page.locale)}
return `<details class="docs-section${current ? " current" : ""}" name="docs-sections" data-docs-section="${escapeAttr(tab.title)}"><summary${current ? ' aria-current="true"' : ""}><span>${escapeHtml(tab.title)}</span>${releaseVersionHtml(release, page.locale)}${icon("chevron-down")}</summary><div class="docs-section-pages">${tab.groups.map((group) => navGroupHtml(page, group)).join("")}</div></details>`;
}).join("")}</nav>
<div class="sidebar-tools">
<nav class="sidebar-socials" aria-label="Community links">${socialIconLinks()}</nav>
<nav class="sidebar-socials" aria-label="Community links">${socialIconLinks("docs-sidebar")}</nav>
<button class="theme-toggle" type="button" data-theme-toggle aria-label="${escapeAttr(copy.controls[1])}"><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>
</div>
</aside>`;
Expand All @@ -476,7 +476,7 @@ function communityInvite(locale) {
<p class="community-invite__text">${escapeHtml(strings.communityBody)}</p>
<div class="community-invite__links" dir="ltr">
<a class="community-invite__cta" href="https://www.reddit.com/r/openclaw/" target="_blank" rel="noopener noreferrer" aria-label="${escapeAttr(joinLabel("Reddit"))}" title="${escapeAttr(joinLabel("Reddit"))}">${icon("reddit")}<span>${escapeHtml(strings.communityJoin)}</span></a>
<a class="community-invite__cta" href="https://discord.com/invite/clawd" target="_blank" rel="noopener noreferrer" aria-label="${escapeAttr(joinLabel("Discord"))}" title="${escapeAttr(joinLabel("Discord"))}">${icon("discord")}<span>${escapeHtml(strings.communityJoin)}</span></a>
<a class="community-invite__cta" href="https://openclaw.ai/discord?from=docs-community-invite" target="_blank" rel="noopener noreferrer" aria-label="${escapeAttr(joinLabel("Discord"))}" title="${escapeAttr(joinLabel("Discord"))}">${icon("discord")}<span>${escapeHtml(strings.communityJoin)}</span></a>
<a class="community-invite__cta" href="https://x.com/openclaw" target="_blank" rel="noopener noreferrer" aria-label="${escapeAttr(followLabel)}" title="${escapeAttr(followLabel)}">${icon("x-social")}<span>${escapeHtml(strings.communityFollow)}</span></a>
</div>
</div>
Expand Down Expand Up @@ -505,11 +505,11 @@ function localeDisplayName(code) {
return localePickerLabels[code] ?? localeLabels[code] ?? code;
}

function socialIconLinks() {
function socialIconLinks(placement) {
return [
["GitHub", "https://github.com/openclaw/openclaw", "github"],
["Reddit", "https://www.reddit.com/r/openclaw/", "reddit"],
["Discord", "https://discord.com/invite/clawd", "discord"],
["Discord", `https://openclaw.ai/discord?from=${placement}`, "discord"],
["X", "https://x.com/openclaw", "x-social"],
].map(([label, href, iconName]) => `<a class="top-icon-link" href="${escapeAttr(href)}" aria-label="${escapeAttr(label)}" title="${escapeAttr(label)}">${icon(iconName)}</a>`).join("");
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/docs-site/community-invite-visual.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
const base = `http://127.0.0.1:${server.address().port}`;
const expectedLinks = [
["Join", "https://www.reddit.com/r/openclaw/"],
["Join", "https://discord.com/invite/clawd"],
["Join", "https://openclaw.ai/discord?from=docs-community-invite"],
["Follow", "https://x.com/openclaw"],
];
let checked = 0;
Expand Down
2 changes: 1 addition & 1 deletion scripts/docs-site/community-invite.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ test("community artwork opts out of Pinterest without changing the invitation or
const links = DomUtils.findAll(node => node.name === "a", invite.children);
assert.deepEqual(links.map(node => [DomUtils.textContent(node).trim(), node.attribs.href]), [
[strings.communityJoin, "https://www.reddit.com/r/openclaw/"],
[strings.communityJoin, "https://discord.com/invite/clawd"],
[strings.communityJoin, "https://openclaw.ai/discord?from=docs-community-invite"],
[strings.communityFollow, "https://x.com/openclaw"],
]);
const optedOut = all(node => node.attribs && ("nopin" in node.attribs || "data-pin-nopin" in node.attribs));
Expand Down
16 changes: 16 additions & 0 deletions scripts/docs-site/discord-source-tags.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { parseDocument, DomUtils } from "htmlparser2";
import { communitySection } from "./home-sections.mjs";
import { localeLabels } from "./config.mjs";

test("every locale uses the registered home Discord placement without a new acquisition campaign", () => {
for (const locale of Object.keys(localeLabels)) {
const document = parseDocument(communitySection(() => "", locale));
const link = DomUtils.findOne(node => node.attribs?.class === "home-discord-button", document.children);
const url = new URL(link.attribs.href);
assert.equal(url.origin + url.pathname, "https://openclaw.ai/discord", locale);
assert.deepEqual([...url.searchParams], [["from", "docs-home-community"]], locale);
assert.ok(DomUtils.findOne(node => node.name === "a" && node.attribs.href === "https://community.openclaw.ai/", document.children), locale);
}
});
2 changes: 1 addition & 1 deletion scripts/docs-site/home-sections.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ export function communitySection(icon, locale = "en") {
['X', xDescription, 'https://x.com/openclaw', 'x-social'],
];
const xLogo = '<svg class="icon" viewBox="0 0 24 24" aria-hidden="true" fill="currentColor"><path d="M18.9 2H22l-6.8 7.8L23.2 22H17l-4.9-7.4L5.6 22H2.4l7.2-8.4L1.5 2h6.4l4.5 6.8L18.9 2ZM17.5 20h2L6.8 3.9H4.7L17.5 20Z"/></svg>';
return `<section class="home-community" aria-labelledby="community"><h2 id="community">${escapeUiText(heading)}</h2><div class="home-discord"><div class="home-discord-copy"><h3>${escapeUiText(title)}</h3><p>${escapeUiText(description)}</p><a class="home-discord-button" href="https://discord.com/invite/clawd">${escapeUiText(join)} ${icon('arrow-right')}</a></div><div class="home-discord-art" aria-hidden="true"><img src="/assets/discord-invite-mesh.webp" alt="" width="1236" height="800" loading="lazy" decoding="async"></div></div><div class="home-community-grid">${links.map(([title, text, href, glyph]) => `<a class="home-community-link" href="${href}">${glyph === 'x-social' ? xLogo : icon(glyph)}<h3>${escapeUiText(title)}${icon('arrow-right')}</h3><p>${escapeUiText(text)}</p></a>`).join('')}</div></section>`;
return `<section class="home-community" aria-labelledby="community"><h2 id="community">${escapeUiText(heading)}</h2><div class="home-discord"><div class="home-discord-copy"><h3>${escapeUiText(title)}</h3><p>${escapeUiText(description)}</p><a class="home-discord-button" href="https://openclaw.ai/discord?from=docs-home-community">${escapeUiText(join)} ${icon('arrow-right')}</a></div><div class="home-discord-art" aria-hidden="true"><img src="/assets/discord-invite-mesh.webp" alt="" width="1236" height="800" loading="lazy" decoding="async"></div></div><div class="home-community-grid">${links.map(([title, text, href, glyph]) => `<a class="home-community-link" href="${href}">${glyph === 'x-social' ? xLogo : icon(glyph)}<h3>${escapeUiText(title)}${icon('arrow-right')}</h3><p>${escapeUiText(text)}</p></a>`).join('')}</div></section>`;
}
4 changes: 3 additions & 1 deletion scripts/docs-site/preview-build.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ test("requested pages outside navigation fit inside the 30-page preview, includi
}
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/);
for (const tag of ["docs-header", "docs-sidebar", "docs-footer", "docs-community-invite"]) {
assert.ok(html.includes(`href="https://openclaw.ai/discord?from=${tag}"`), tag);
}
const sidebar = html.match(/<aside class="sidebar">([\s\S]*?)<\/aside>/)[1];
assert.doesNotMatch(sidebar, /sidebar-support|>Support<\/a>/, 'no duplicate sidebar support button');
assert.equal([...sidebar.slice(sidebar.indexOf('<nav class="docs-sections"')).matchAll(/class="nav-link/g)].length, 35);
Expand Down
Loading
Loading