{
for (const page of pages) for (const lang of ["en", "ko"] as const) expect(PAGES[page].text(lang).opening.back?.to).toBe("hub");
});
+ it("sorts each guide's end links into the guide, the site and away", () => {
+ expect(linkWay("/guide")).toEqual({ way: "guide", page: "hub", lang: "en" });
+ expect(linkWay("/guide/ko")).toEqual({ way: "guide", page: "hub", lang: "ko" });
+ expect(linkWay("/guide/play/ko")).toEqual({ way: "guide", page: "play", lang: "ko" });
+ expect(linkWay("/guide/play#flash")).toEqual({ way: "site" });
+ expect(linkWay("/pattern-lab")).toEqual({ way: "site" });
+ expect(linkWay("https://community.patternflow.work/community")).toEqual({ way: "away" });
+ expect(linkWay("//example.com/")).toEqual({ way: "away" });
+ for (const page of pages) {
+ for (const lang of ["en", "ko"] as const) {
+ for (const { href } of PAGES[page].text(lang).next.links) {
+ const to = linkWay(href);
+ if (to.way === "away") expect(href).toMatch(/^https:\/\//);
+ // A link back into the guide stays in the reader's language.
+ if (to.way === "guide") expect(to.lang).toBe(lang);
+ }
+ }
+ }
+ expect(PAGES.audio.text("en").next.links.map((l) => linkWay(l.href).way)).toEqual(["away", "away", "away", "away", "away", "site", "guide"]);
+ });
+
it("never offers a kit or an assembled board", () => {
const words = JSON.stringify([HUB_COPY, ...pages.flatMap((p) => [PAGES[p].text("en"), PAGES[p].text("ko")])]);
expect(words).not.toMatch(/\bkits?\b|pre-?assembled|assembled board|키트|완제품/i);
diff --git a/web/src/components/guide/pages.ts b/web/src/components/guide/pages.ts
index 6a1f3297..b1c78246 100644
--- a/web/src/components/guide/pages.ts
+++ b/web/src/components/guide/pages.ts
@@ -161,6 +161,21 @@ export function screenAt(pathname: string): { page: GuideScreen; lang: GuideLang
return null;
}
+/**
+ * Where a link at a guide's end goes. Another page of the guide is followed
+ * in place (world/GuideLink): the words leave and the stage stays. Another
+ * page of the site is an ordinary link, →, in the same tab. Anything else is
+ * away from the site, ↗, in a new tab. The community is on its own host, so
+ * it is away.
+ */
+export type LinkWay = { way: "guide"; page: GuideScreen; lang: GuideLang } | { way: "site" } | { way: "away" };
+
+export function linkWay(href: string): LinkWay {
+ if (!href.startsWith("/") || href.startsWith("//")) return { way: "away" };
+ const screen = screenAt(href);
+ return screen ? { way: "guide", ...screen } : { way: "site" };
+}
+
/**
* Where the reader is on a guide, as an anchor: "#flash-3" for a step whose
* card is on screen, "#flash" for a chapter's title, "#next" for the end, and