From 542687dc9d6d6a09a46ed0dcbd8af22a4e24d318 Mon Sep 17 00:00:00 2001 From: SebaSOFT Date: Mon, 28 Sep 2026 03:47:59 +0000 Subject: [PATCH] feat(docs): Google 'Add to preferred sources' button in the site footer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Standard JavaScript implementation from the official Google docs: publisher.js in the VitePress head + the auto-scanned [google-add-preferred-source-btn] container. - PreferredSourceFooter.vue mounts in the layout-bottom slot: renders the Google button (theme-aware: light/dark follows the site theme) plus an honest note — the eligible source is the domain sebasoft.github.io (Google's source preferences tool accepts domains and subdomains, not subdirectories). - Browser E2E verified on the HTTP-served build: publisher.js scans and initializes the container (data-initialized=true), the container survives SPA route changes (layout-bottom slot stays mounted), and clicking opens the Google service iframe (news.google.com/swg/ui). - tests/contracts/preferred-source-footer.test.ts: 4 contract tests over the footer surface. --- docs/.vitepress/config.ts | 1 + .../theme/PreferredSourceFooter.vue | 40 +++++++++++++++++++ docs/.vitepress/theme/index.ts | 10 +++++ .../contracts/preferred-source-footer.test.ts | 33 +++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 docs/.vitepress/theme/PreferredSourceFooter.vue create mode 100644 tests/contracts/preferred-source-footer.test.ts diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 8b09afa..88fbf77 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -27,6 +27,7 @@ export default defineConfig({ ['meta', { httpEquiv: 'Origin-Trial', content: ORIGIN_TRIAL_WEBMCP }], ['script', { src: '/neuron-js/webmcp/webmcp.js' }], ['script', { src: '/neuron-js/webmcp/neuron-webmcp.js', defer: 'true' }], + ['script', { async: 'true', src: 'https://news.google.com/swg/js/v1/publisher.js' }], ], ignoreDeadLinks: true, markdown: { diff --git a/docs/.vitepress/theme/PreferredSourceFooter.vue b/docs/.vitepress/theme/PreferredSourceFooter.vue new file mode 100644 index 0000000..a74c80f --- /dev/null +++ b/docs/.vitepress/theme/PreferredSourceFooter.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 1142adf..c292208 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -1,7 +1,9 @@ import DefaultTheme from "vitepress/theme"; import { inBrowser } from "vitepress"; import type { EnhanceAppContext } from "vitepress"; +import { h } from "vue"; import mermaid from "mermaid"; +import PreferredSourceFooter from "./PreferredSourceFooter.vue"; import "./custom.css"; const isDarkMode = () => document.documentElement.classList.contains("dark"); @@ -56,6 +58,11 @@ const scheduleRender = () => { export default { extends: DefaultTheme, + Layout() { + return h(DefaultTheme.Layout, null, { + "layout-bottom": () => h(PreferredSourceFooter), + }); + }, enhanceApp({ router }: EnhanceAppContext) { if (!inBrowser) { return; @@ -68,6 +75,9 @@ export default { router.onAfterRouteChanged = async (to) => { await afterRouteChange?.(to); scheduleRender(); + // The Google preferred-source button lives in the layout-bottom slot, + // which VitePress keeps mounted across SPA route changes; publisher.js + // initializes it once on load — verified in the browser E2E. }; }, setup() { diff --git a/tests/contracts/preferred-source-footer.test.ts b/tests/contracts/preferred-source-footer.test.ts new file mode 100644 index 0000000..653cc3c --- /dev/null +++ b/tests/contracts/preferred-source-footer.test.ts @@ -0,0 +1,33 @@ +// NJS-SEO-6 follow-up: contract test for the Google "Add to preferred sources" +// footer button on the documentation site. +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +const read = (path: string) => readFileSync(path, "utf8"); + +describe("Google preferred-source footer button", () => { + it("loads the official publisher.js in the VitePress head", () => { + expect(read("docs/.vitepress/config.ts")).toContain( + "https://news.google.com/swg/js/v1/publisher.js", + ); + }); + + it("renders the standard button container in the layout-bottom footer", () => { + const component = read("docs/.vitepress/theme/PreferredSourceFooter.vue"); + expect(component).toContain("google-add-preferred-source-btn"); + expect(component).toContain("preferred-source-footer"); + // theme aware: light/dark follows the site theme + expect(component).toContain("isDark"); + }); + + it("mounts the footer via the layout-bottom slot", () => { + const theme = read("docs/.vitepress/theme/index.ts"); + expect(theme).toContain("PreferredSourceFooter"); + expect(theme).toContain("layout-bottom"); + }); + + it("states the eligible source honestly (domain, not subdirectory)", () => { + const component = read("docs/.vitepress/theme/PreferredSourceFooter.vue"); + expect(component).toContain("sebasoft.github.io"); + }); +});