Ctrl K
ComunidadGuías…

blume.config.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { defineConfig } from "blume";

interface ThemeMetadata {
  value: string;
  color_scheme: "dark" | "light";
  preview: {
    darker: string;
    dark: string;
    primary: string;
    content: string;
  };
  docs: {
    muted: string;
    muted_foreground: string;
    border: string;
    accent_foreground: string;
    code_background: string;
  };
}

interface BunRuntime {
  file(path: URL): { text(): Promise<string> };
  write(path: URL, content: string): Promise<number>;
  YAML: { parse(source: string): unknown };
}

const bun = (globalThis as typeof globalThis & { Bun: BunRuntime }).Bun;
const themes = bun.YAML.parse(
  await bun.file(new URL("./config/themes.yml", import.meta.url)).text(),
) as ThemeMetadata[];
const defaultTheme = themes.find((theme) => theme.value === "neon");
if (!defaultTheme) throw new Error("The default Neon theme is missing");

const docsThemes = Object.fromEntries(
  themes.map((theme) => [
    theme.value,
    {
      colorScheme: theme.color_scheme,
      tokens: {
        "--blume-background": theme.preview.darker,
        "--blume-foreground": theme.preview.content,
        "--blume-muted": theme.docs.muted,
        "--blume-muted-foreground": theme.docs.muted_foreground,
        "--blume-border": theme.docs.border,
        "--blume-accent": theme.preview.primary,
        "--blume-accent-foreground": theme.docs.accent_foreground,
        "--blume-code-background": theme.docs.code_background,
      },
    },
  ]),
);

// The colours are plain CSS keyed off data-hackatime-theme so the cascade,
// not a script, paints them. :root[data-theme][data-hackatime-theme="x"] is
// one attribute more specific than Blume's :root[data-theme="dark"] token
// block, so these rules win in both colour schemes regardless of sheet order.
const themeCss = themes
  .map((theme) => {
    const declarations = Object.entries(docsThemes[theme.value].tokens)
      .map(([property, value]) => `  ${property}: ${value};`)
      .join("\n");
    return [
      `:root[data-hackatime-theme="${theme.value}"],`,
      `:root[data-theme][data-hackatime-theme="${theme.value}"] {`,
      declarations,
      "}",
    ].join("\n");
  })
  .join("\n\n");

// Pages can ship a screenshot per colour scheme by marking each <img> with
// data-docs-theme="light" or "dark"; only the one matching data-theme shows.
const themedImageCss = [
  `:root[data-theme="dark"] [data-docs-theme="light"],`,
  `:root:not([data-theme="dark"]) [data-docs-theme="dark"] {`,
  "  display: none;",
  "}",
].join("\n");

const themeCssUrl = new URL("./tmp/hackatime-docs-themes.css", import.meta.url);
await bun.write(
  themeCssUrl,
  `/* Generated by blume.config.ts from config/themes.yml. Do not edit. */\n\n${themeCss}\n\n${themedImageCss}\n`,
);

// The script only picks the theme from the cookie and stamps attributes; the
// same pattern as Blume's own dark-mode init, re-applied on astro:after-swap
// because the client router swaps in a fresh <html> before the new page paints.
const themeScript = `(() => {
  const schemes = ${JSON.stringify(
    Object.fromEntries(
      themes.map((theme) => [theme.value, theme.color_scheme]),
    ),
  )};
  const apply = () => {
    const cookie = document.cookie
      .split("; ")
      .find((entry) => entry.startsWith("hackatime_theme="));
    const selected = cookie
      ? cookie.slice("hackatime_theme=".length)
      : "neon";
    const theme = Object.hasOwn(schemes, selected) ? selected : "neon";

    document.documentElement.dataset.hackatimeTheme = theme;
    document.documentElement.dataset.theme = schemes[theme];
    try {
      localStorage.setItem("blume-theme", schemes[theme]);
    } catch {}
  };

  apply();
  document.addEventListener("astro:after-swap", apply);
})();`;

export default defineConfig({
  title: "Hackatime Docs",
  description:
    "Learn how to set up Hackatime, track your coding time, and build integrations.",
  basePath: "/docs",
  logo: {
    image: "/images/new-icon-rounded.png",
    text: "Hackatime",
    href: "/",
  },
  github: {
    owner: "hackclub",
    repo: "hackatime",
  },
  navigation: {
    sidebar: {
      display: "page",
    },
  },
  integrations: [
    {
      name: "hackatime-docs-theme",
      hooks: {
        "astro:config:setup": ({ injectScript }) => {
          injectScript("head-inline", themeScript);
          injectScript("page-ssr", `import ${JSON.stringify(themeCssUrl.pathname)};`);
        },
      },
    },
  ],
  theme: {
    accent: defaultTheme.preview.primary,
    background: defaultTheme.preview.dark,
    mode: defaultTheme.color_scheme,
    radius: "lg",
  },
  deployment: {
    output: "static",
    site: "https://hackatime.hackclub.com",
  },
  redirects: [
    {
      from: "/getting-started/configuration",
      to: "/configuration",
      status: 301,
    },
  ],
  analytics: {
    scripts: [
      {
        content: `window.addEventListener("blume:track", (event) => {
          if (event.detail?.event !== "feedback") return;

          let visitorToken = localStorage.getItem("hackatime-docs-visitor");
          if (!visitorToken) {
            visitorToken = crypto.randomUUID();
            localStorage.setItem("hackatime-docs-visitor", visitorToken);
          }

          fetch("/docs/feedback", {
            method: "POST",
            credentials: "same-origin",
            keepalive: true,
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              helpful: event.detail.props.helpful === "yes",
              path: event.detail.props.path,
              title: event.detail.props.title,
              visitor_token: visitorToken,
            }),
          }).catch(() => {});
        });`,
      },
    ],
  },
  seo: {
    og: {
      enabled: true,
      logo: "/images/hackatime-icon.svg",
      palette: {
        accent: defaultTheme.preview.primary,
        background: defaultTheme.preview.darker,
        foreground: defaultTheme.preview.content,
        muted: defaultTheme.docs.muted_foreground,
        border: defaultTheme.docs.border,
      },
    },
    rss: { enabled: false },
    sitemap: true,
    robots: false,
    structuredData: true,
    x: { handle: "@hackclub" },
  },
});