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
20 changes: 15 additions & 5 deletions src/components/SiteHeader.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
import { getLang, t, localePath, type Lang } from "~/i18n";
import { getStarCount, formatStars } from "~/lib/github";
import { productRepos } from "~/lib/releases.mjs";
import { findDoc, getProduct } from "~/content/docs/_meta";
import SearchModal from "./SearchModal.astro";
import TwMark from "./TwMark.astro";
Expand All @@ -20,8 +21,6 @@ const sEn = t("en");
const sZh = t("zh-CN");
/** data-localize for a link on a bilingual page: the 404 page's script prefixes /zh-CN for Chinese visitors */
const localize = (path: string) => (bilingual ? path : undefined);
const stars = await getStarCount();
const starLabel = formatStars(stars);

// On the home page anchor links work as in-page jumps; on /docs/* and
// /changelog they need to navigate back to the home first.
Expand All @@ -46,6 +45,15 @@ const currentDocMeta = currentDocSlug
? findDoc(currentDocSlug, (docMatch?.[1] as "lite" | "core" | undefined) ?? "thinkwatch")
: undefined;

// The GitHub pill shows ThinkWatch Lite's repository, except on the enterprise
// edition's page and its guides (/docs/<slug>), which show the enterprise one.
const enterprise =
/^\/thinkwatch(\/|$)/.test(cleanPath) ||
(docMatch !== null && !docMatch[1] && docMatch[2] !== "lite" && docMatch[2] !== "core");
const starRepo = enterprise ? productRepos.enterprise : productRepos.lite;
const stars = await getStarCount(starRepo);
const starLabel = formatStars(stars);

function targetPath(target: Lang): string {
// The 404 page stands in for every missing path, and /404 itself does not
// exist: its language choice leads to the home page.
Expand Down Expand Up @@ -137,9 +145,10 @@ const zhAttrs = (attrs: Record<string, string>) => (bilingual ? JSON.stringify(a

<!-- GitHub icon + star pill (star count hidden on narrow phones) -->
<a
href="https://github.com/ThinkWatchProject/ThinkWatch"
href={`https://github.com/${starRepo}`}
target="_blank"
rel="noopener"
data-star-repo={starRepo}
aria-label={`GitHub repository — ${stars ?? 0} stars`}
class="group flex items-center gap-1.5 text-xs font-medium text-[var(--color-muted)] hover:text-white border border-white/10 hover:border-white/25 rounded-md px-2 sm:pl-2 sm:pr-2.5 h-9 transition-colors"
>
Expand Down Expand Up @@ -311,8 +320,9 @@ const zhAttrs = (attrs: Record<string, string>) => (bilingual ? JSON.stringify(a
// Fetch live GitHub star count and update the pill
{
const el = document.querySelector<HTMLSpanElement>("[data-star-count]");
if (el) {
fetch("https://api.github.com/repos/ThinkWatchProject/ThinkWatch", {
const repo = el?.closest<HTMLAnchorElement>("[data-star-repo]")?.dataset.starRepo;
if (el && repo) {
fetch(`https://api.github.com/repos/${repo}`, {
headers: { Accept: "application/vnd.github+json" },
})
.then((r) => r.ok ? r.json() : null)
Expand Down
32 changes: 17 additions & 15 deletions src/lib/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,25 @@ export type { Product, Release };

const REPO = productRepos.enterprise;

let cached: number | null | undefined;
const starCache = new Map<string, Promise<number | null>>();

export async function getStarCount(): Promise<number | null> {
if (cached !== undefined) return cached;
try {
const res = await fetch(`https://api.github.com/repos/${REPO}`, { headers: githubHeaders() });
if (!res.ok) {
cached = null;
return null;
}
const data: { stargazers_count?: number } = await res.json();
cached = typeof data.stargazers_count === "number" ? data.stargazers_count : null;
return cached;
} catch {
cached = null;
return null;
/** A repository's star count, fetched once per build and repository. */
export function getStarCount(repo: string): Promise<number | null> {
let cached = starCache.get(repo);
if (!cached) {
cached = (async () => {
try {
const res = await fetch(`https://api.github.com/repos/${repo}`, { headers: githubHeaders() });
if (!res.ok) return null;
const data: { stargazers_count?: number } = await res.json();
return typeof data.stargazers_count === "number" ? data.stargazers_count : null;
} catch {
return null;
}
})();
starCache.set(repo, cached);
}
return cached;
}

export function formatStars(n: number | null): string {
Expand Down
Loading