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
9 changes: 9 additions & 0 deletions apps/frontend/astro.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import path from "node:path";

import { unified } from "@astrojs/markdown-remark";
import react from "@astrojs/react";
import starlight from "@astrojs/starlight";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig, passthroughImageService } from "astro/config";
import starlightLinksValidator from "starlight-links-validator";

import { rehypeCardImages } from "./src/plugins/rehypeCardImages.js";

const base = "/frontend";

// `pnpm dev:backend` serves the card endpoints; override to point at another instance.
Expand All @@ -21,6 +24,12 @@ export default defineConfig({
outDir: "./build",
// One screenshot; not worth a native image dependency.
image: { service: passthroughImageService() },
markdown: {
// Starlight appends its own plugins to whatever processor is configured here.
processor: unified({
rehypePlugins: [rehypeCardImages],
}),
},
// Astro prefixes `base` onto the source but not the destination, hence it spelled out here.
redirects: {
// Sidebar group labels are not routes, so send them to the group's first page.
Expand Down
136 changes: 136 additions & 0 deletions apps/frontend/e2e/docs-card-previews.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { expect, test } from "@playwright/test";
import type { Page } from "@playwright/test";

/*
* `rehypeCardImages` assembles the previews at build time,
* so the markdown names neither the themes nor the classes asserted on here.
*/
const cardImage = (page: Page, alt: string, variant: "light" | "dark") =>
page.locator(`main img[alt="${alt}"].card-preview-${variant}`);

test("a preview that names no theme renders one image per site theme", async ({
page,
}) => {
await page.goto("docs/");

await expect(cardImage(page, "Top Langs", "light")).toHaveAttribute(
"src",
"/api/top-langs?username=anuraghazra&langs_count=4&theme=light_github",
);
await expect(cardImage(page, "Top Langs", "dark")).toHaveAttribute(
"src",
"/api/top-langs?username=anuraghazra&langs_count=4&theme=dark_github",
);
});

test("the pin and gist previews use the repocard themes", async ({ page }) => {
await page.goto("docs/");

for (const alt of ["Readme Card", "Gist Card"]) {
await expect(cardImage(page, alt, "light")).toHaveAttribute(
"src",
/theme=light_github_repocard$/,
);
await expect(cardImage(page, alt, "dark")).toHaveAttribute(
"src",
/theme=dark_github_repocard$/,
);
}
});

test("a preview links to itself, unless the markdown links it elsewhere", async ({
page,
}) => {
await page.goto("docs/");

// Opening a preview shows its query string, so each copy links to its own theme.
await expect(
page.locator('main a.card-preview-light:has(img[alt="Top Langs"])'),
).toHaveAttribute(
"href",
"/api/top-langs?username=anuraghazra&langs_count=4&theme=light_github",
);

// The repo card already points at the repo it describes, which is more useful.
await expect(
page.locator('main a:has(img[alt="Readme Card"])'),
).toHaveAttribute(
"href",
"https://github.com/anuraghazra/github-readme-stats",
);
});

test("a preview that names a theme stays a single image", async ({ page }) => {
await page.goto("docs/customization/theming/");

await expect(page.locator('main img[src*="theme=transparent"]')).toHaveCount(
1,
);
});

test("every sample on the themes page is deferred and links to itself", async ({
page,
}) => {
await page.goto("docs/customization/themes/");

const samples = page.locator('main img[src^="/api"]');
await expect(samples).not.toHaveCount(0);
await expect(
page.locator('main a[href^="/api"] > img[src^="/api"]'),
).toHaveCount(await samples.count());
await expect(
page.locator('main img[src^="/api"]:not([loading="lazy"])'),
).toHaveCount(0);
});

test("cards laid out as HTML are deferred, not split", async ({ page }) => {
await page.goto("docs/customization/aligning-cards/");

// The rows are hand-written HTML, so only the plugin's raw branch reaches them.
await expect(
page.locator('main .card-row img:not([loading="lazy"])'),
).toHaveCount(0);

// Each already names a theme, so the pairs stay as the markdown wrote them.
await expect(page.locator('main .card-row img[src^="/api"]')).toHaveCount(8);
await expect(
page.locator("main .card-row img.card-preview-light"),
).toHaveCount(4);
await expect(
page.locator("main .card-row img.card-preview-dark"),
).toHaveCount(4);
});

// Starlight server-renders its theme onto `<html>`, which `prefers-color-scheme` cannot see.
test.describe("in a browser set to dark", () => {
test.use({ colorScheme: "dark" });

test("the visible preview follows the site theme, not the browser", async ({
page,
}) => {
const requested: Array<string> = [];
page.on("request", (request) => requested.push(request.url()));

await page.goto("docs/");
await expect(cardImage(page, "Top Langs", "dark")).toBeVisible();
await expect(cardImage(page, "Top Langs", "light")).toBeHidden();

/*
* A hidden copy has no layout box, so lazy loading never requests it.
* The pin and gist pair is the fair comparison:
* both sit below the fold, where the dev server's late CSS cannot briefly reveal one.
*/
await expect
.poll(() => requested.some((url) => url.includes("dark_github_repocard")))
.toBe(true);
expect(
requested.filter((url) => url.includes("light_github_repocard")),
).toEqual([]);

const themeSelect = page.locator("header").getByRole("combobox");
await themeSelect.selectOption({ label: "Light" });

await expect(cardImage(page, "Top Langs", "light")).toBeVisible();
await expect(cardImage(page, "Top Langs", "dark")).toBeHidden();
});
});
1 change: 1 addition & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"uuid": "^14.0.1"
},
"devDependencies": {
"@astrojs/markdown-remark": "7.2.2",
"@types/react": "19.2.17",
"@types/react-dom": "19.2.3",
"clsx": "2.1.1",
Expand Down
6 changes: 2 additions & 4 deletions apps/frontend/src/content/docs/docs/cards/gist-pin.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,8 @@ You can customize the appearance and behavior of the gist card using the [common

## Demo

<img class="card-preview-light" src="/api/gist?id=bbfce31e0217a3689c8d961a356cb10d&theme=light_github_repocard" alt="Gist Card" />
<img class="card-preview-dark" src="/api/gist?id=bbfce31e0217a3689c8d961a356cb10d&theme=dark_github_repocard" alt="Gist Card" />
![Gist Card](/api/gist?id=bbfce31e0217a3689c8d961a356cb10d)

Use [show\_owner](#options) query option to include the gist's owner username

<img class="card-preview-light" src="/api/gist?id=bbfce31e0217a3689c8d961a356cb10d&show_owner=true&theme=light_github_repocard" alt="Gist Card" />
<img class="card-preview-dark" src="/api/gist?id=bbfce31e0217a3689c8d961a356cb10d&show_owner=true&theme=dark_github_repocard" alt="Gist Card" />
![Gist Card](/api/gist?id=bbfce31e0217a3689c8d961a356cb10d&show_owner=true)
12 changes: 4 additions & 8 deletions apps/frontend/src/content/docs/docs/cards/repo-pin.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,16 @@ You can customize the appearance and behavior of the pinned repository card usin

## Demo

<img class="card-preview-light" src="/api/pin?username=anuraghazra&repo=github-readme-stats&theme=light_github_repocard" alt="Readme Card" />
<img class="card-preview-dark" src="/api/pin?username=anuraghazra&repo=github-readme-stats&theme=dark_github_repocard" alt="Readme Card" />
![Readme Card](/api/pin?username=anuraghazra&repo=github-readme-stats)

Use [show\_owner](#options) query option to include the repo's owner username:

<img class="card-preview-light" src="/api/pin?username=anuraghazra&repo=github-readme-stats&show_owner=true&theme=light_github_repocard" alt="Readme Card" />
<img class="card-preview-dark" src="/api/pin?username=anuraghazra&repo=github-readme-stats&show_owner=true&theme=dark_github_repocard" alt="Readme Card" />
![Readme Card](/api/pin?username=anuraghazra&repo=github-readme-stats&show_owner=true)

Use [show](#options) query option to display the user's contributions to the repository:

<img class="card-preview-light" src="/api/pin?username=anuraghazra&repo=github-readme-stats&show=prs_authored,prs_commented,prs_reviewed,issues_authored,issues_commented&theme=light_github_repocard" alt="Readme Card" />
<img class="card-preview-dark" src="/api/pin?username=anuraghazra&repo=github-readme-stats&show=prs_authored,prs_commented,prs_reviewed,issues_authored,issues_commented&theme=dark_github_repocard" alt="Readme Card" />
![Readme Card](/api/pin?username=anuraghazra&repo=github-readme-stats&show=prs_authored,prs_commented,prs_reviewed,issues_authored,issues_commented)

You can also specify the `repo` parameter in the form `<user_or_organization>/<repository>` to pin a repository from any user or organization, not just your own. This allows you to showcase repositories you contributed to, regardless of ownership.

<img class="card-preview-light" src="/api/pin?username=anuraghazra&repo=statykjs/statyk&show_owner=true&show=prs_authored,prs_commented,prs_reviewed,issues_authored,issues_commented&theme=light_github_repocard" alt="Readme Card" />
<img class="card-preview-dark" src="/api/pin?username=anuraghazra&repo=statykjs/statyk&show_owner=true&show=prs_authored,prs_commented,prs_reviewed,issues_authored,issues_commented&theme=dark_github_repocard" alt="Readme Card" />
![Readme Card](/api/pin?username=anuraghazra&repo=statykjs/statyk&show_owner=true&show=prs_authored,prs_commented,prs_reviewed,issues_authored,issues_commented)
31 changes: 7 additions & 24 deletions apps/frontend/src/content/docs/docs/cards/top-languages.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,45 +153,28 @@ You can use the `&stats_format=bytes` option to display the stats in bytes inste

## Demo

<img class="card-preview-light" src="/api/top-langs?username=anuraghazra&theme=light_github" alt="Top Langs" />
<img class="card-preview-dark" src="/api/top-langs?username=anuraghazra&theme=dark_github" alt="Top Langs" />
![Top Langs](/api/top-langs?username=anuraghazra)

### Compact layout

<img class="card-preview-light" src="/api/top-langs?username=anuraghazra&layout=compact&theme=light_github" alt="Top Langs" />
<img class="card-preview-dark" src="/api/top-langs?username=anuraghazra&layout=compact&theme=dark_github" alt="Top Langs" />
![Top Langs](/api/top-langs?username=anuraghazra&layout=compact)

### Donut Chart layout

<a href="/api/top-langs?username=anuraghazra&layout=donut">
<img class="card-preview-light" src="/api/top-langs?username=anuraghazra&layout=donut&theme=light_github" alt="Top Langs" />
<img class="card-preview-dark" src="/api/top-langs?username=anuraghazra&layout=donut&theme=dark_github" alt="Top Langs" />
</a>
![Top Langs](/api/top-langs?username=anuraghazra&layout=donut)

### Donut Vertical Chart layout

<a href="/api/top-langs?username=anuraghazra&layout=donut-vertical">
<img class="card-preview-light" src="/api/top-langs?username=anuraghazra&layout=donut-vertical&theme=light_github" alt="Top Langs" />
<img class="card-preview-dark" src="/api/top-langs?username=anuraghazra&layout=donut-vertical&theme=dark_github" alt="Top Langs" />
</a>
![Top Langs](/api/top-langs?username=anuraghazra&layout=donut-vertical)

### Pie Chart layout

<a href="/api/top-langs?username=anuraghazra&layout=pie">
<img class="card-preview-light" src="/api/top-langs?username=anuraghazra&layout=pie&theme=light_github" alt="Top Langs" />
<img class="card-preview-dark" src="/api/top-langs?username=anuraghazra&layout=pie&theme=dark_github" alt="Top Langs" />
</a>
![Top Langs](/api/top-langs?username=anuraghazra&layout=pie)

### Hidden progress bars

<a href="/api/top-langs?username=anuraghazra&hide_progress=true">
<img class="card-preview-light" src="/api/top-langs?username=anuraghazra&hide_progress=true&theme=light_github" alt="Top Langs" />
<img class="card-preview-dark" src="/api/top-langs?username=anuraghazra&hide_progress=true&theme=dark_github" alt="Top Langs" />
</a>
![Top Langs](/api/top-langs?username=anuraghazra&hide_progress=true)

### Display bytes instead of percentage

<a href="/api/top-langs?username=anuraghazra&stats_format=bytes">
<img class="card-preview-light" src="/api/top-langs?username=anuraghazra&stats_format=bytes&theme=light_github" alt="Top Langs" />
<img class="card-preview-dark" src="/api/top-langs?username=anuraghazra&stats_format=bytes&theme=dark_github" alt="Top Langs" />
</a>
![Top Langs](/api/top-langs?username=anuraghazra&stats_format=bytes)
9 changes: 3 additions & 6 deletions apps/frontend/src/content/docs/docs/cards/wakatime.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,10 @@ Custom title should be URI-escaped, as specified in [Percent Encoding](https://e

## Demo

<img class="card-preview-light" src="/api/wakatime?username=alan&theme=light_github" alt="Alan's WakaTime stats" />
<img class="card-preview-dark" src="/api/wakatime?username=alan&theme=dark_github" alt="Alan's WakaTime stats" />
![Alan's WakaTime stats](/api/wakatime?username=alan)

<img class="card-preview-light" src="/api/wakatime?username=alan&card_width=315&hide_progress=true&theme=light_github" alt="Alan's WakaTime stats" />
<img class="card-preview-dark" src="/api/wakatime?username=alan&card_width=315&hide_progress=true&theme=dark_github" alt="Alan's WakaTime stats" />
![Alan's WakaTime stats](/api/wakatime?username=alan&card_width=315&hide_progress=true)

### Compact layout

<img class="card-preview-light" src="/api/wakatime?username=alan&layout=compact&theme=light_github" alt="Alan's WakaTime stats" />
<img class="card-preview-dark" src="/api/wakatime?username=alan&layout=compact&theme=dark_github" alt="Alan's WakaTime stats" />
![Alan's WakaTime stats](/api/wakatime?username=alan&layout=compact)
7 changes: 4 additions & 3 deletions apps/frontend/src/content/docs/docs/customization/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ Preview [all available themes](/frontend/docs/customization/themes/) or read the

## Light and Dark Mode

<img class="card-preview-light" src="/api?username=anuraghazra&show_icons=true&theme=light_github" alt="Anurag's GitHub stats" />
<img class="card-preview-dark" src="/api?username=anuraghazra&show_icons=true&theme=dark_github" alt="Anurag's GitHub stats" />
![Anurag's GitHub stats](/api?username=anuraghazra&show_icons=true)

There are several ways to switch a card between modes on the client side.

Expand Down Expand Up @@ -125,6 +124,8 @@ Any of [the available themes](/frontend/docs/customization/themes/) turns transp
<details>
<summary>👀 Show example</summary>

![Anurag's GitHub stats](/api?username=anuraghazra&show_icons=true&bg_color=00000000)
<!-- set theme=default explicitly so rehypeCardImages.ts doesn't create a light and a dark version -->

![Anurag's GitHub stats](/api?username=anuraghazra&show_icons=true&bg_color=00000000&theme=default)

</details>
Loading