diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 59cb333e379f5..ad0b1fafaca0b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -10,7 +10,7 @@ pnpm run build:packages pnpm run dev:frontend ``` -The easiest way to run and test the project is to deploy it to Vercel as described in the [deployment guide](../docs/deploy.md). +The easiest way to run and test the project is to deploy it to Vercel as described in the [deployment guide](https://github-stats-extended.vercel.app/frontend/docs/deploy/). ## Tests @@ -46,13 +46,13 @@ Never edit the generated files by hand — change the `.graphql` file and regene ## Themes Contribution -We have stopped the addition of new themes to decrease maintenance efforts. If you are considering contributing your theme just because you are using it personally, then instead of adding it to our theme collection, you can use card [customization options](../docs/advanced_documentation.md#customization). +We have stopped the addition of new themes to decrease maintenance efforts. If you are considering contributing your theme just because you are using it personally, then instead of adding it to our theme collection, you can use card [customization options](https://github-stats-extended.vercel.app/frontend/docs/customization/common-options/). ## Translations Contribution -GitHub-Stats-Extended supports multiple languages. If we are missing your language, you can contribute it! You can check the currently supported languages [here](../docs/advanced_documentation.md#available-locales). +GitHub-Stats-Extended supports multiple languages. If we are missing your language, you can contribute it! You can check the currently supported languages [here](https://github-stats-extended.vercel.app/frontend/docs/customization/locales/). -To contribute your language you need to edit the [backend/src/translations.js](../backend/src/translations.js) file and add a new property to each object where the key is the language code in [ISO 639-1 standard](https://www.andiamo.co.uk/resources/iso-language-codes/) and the value is the translated string. +To contribute your language you need to edit the [packages/core/src/translations.ts](../packages/core/src/translations.ts) file and add a new property to each object where the key is the language code in [ISO 639-1 standard](https://www.andiamo.co.uk/resources/iso-language-codes/) and the value is the translated string. ## Any contributions you make will be under the MIT Software License diff --git a/.github/workflows/generate-theme-readme.yml b/.github/workflows/generate-theme-readme.yml index 204e6b65e81c2..dd6b873cb600c 100644 --- a/.github/workflows/generate-theme-readme.yml +++ b/.github/workflows/generate-theme-readme.yml @@ -37,7 +37,7 @@ jobs: uses: ./.github/actions/install-dependencies - name: Generate readme - run: pnpm --filter ./packages/core/ run generate-theme-readme + run: pnpm run generate-theme-readme - name: Create Pull Request if themes README has changed uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 diff --git a/.gitignore b/.gitignore index d314c97112b67..e2a871679be21 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,8 @@ apps/backend/vercel_token apps/backend-copy apps/frontend/.env +# Astro's generated types and content store +apps/frontend/.astro .turbo diff --git a/README.md b/README.md index 46f255235ced4..cdb5412bd598f 100644 --- a/README.md +++ b/README.md @@ -5,82 +5,35 @@ -This project is the [extended, actively maintained successor](docs/fork.md) of [github-readme-stats](https://github.com/anuraghazra/github-readme-stats). It generates [various stats cards](#card-types), e.g. about your GitHub contributions, your top languages, etc. You can [customize](#advanced-customization) the cards via multiple parameters. - -## Table of Contents - -- [Quick Start](#quick-start) -- [Migration from github-readme-stats](#migration-from-github-readme-stats) -- [Card Types](#card-types) -- [Advanced Customization](#advanced-customization) -- [Run It Yourself](#run-it-yourself) -- [Acknowledgements](#acknowledgements) -- [Contributing](#contributing) +This project is the [extended, actively maintained successor](https://github-stats-extended.vercel.app/frontend/docs/fork/) of [github-readme-stats](https://github.com/anuraghazra/github-readme-stats). It generates stats cards about your GitHub contributions, your top languages and more, which you can [customize](https://github-stats-extended.vercel.app/frontend/docs/customization/common-options/) through a large set of parameters. ## Quick Start -- Copy and paste this into your markdown: - ```markdown - [![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra)](https://github.com/stats-organization/github-stats-extended) - ``` -- Change the `?username=` value to your GitHub username. -- Done! - ---- - -As more comfortable alternative, use the [GitHub-Stats-Extended Wizard](https://github-stats-extended.vercel.app/frontend) to create your custom stats card. Copy the generated markdown code and paste it into your [GitHub profile README](https://docs.github.com/en/account-and-profile/how-tos/profile-customization/managing-your-profile-readme#adding-a-profile-readme). Done! - -## Migration from github-readme-stats +Copy and paste this into your markdown, then change the `?username=` value to your GitHub username: -To migrate from [github-readme-stats](https://github.com/anuraghazra/github-readme-stats) you only need to change the domain from `github-readme-stats.vercel.app` to `github-stats-extended.vercel.app`: - -```diff -- https://github-readme-stats.vercel.app/api?username=octocat&theme=radical -+ https://github-stats-extended.vercel.app/api?username=octocat&theme=radical +```markdown +[![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra)](https://github.com/stats-organization/github-stats-extended) ``` -GitHub-Stats-Extended aims to be fully compatible with github-readme-stats. For more details see [Compatibility Notes](docs/fork.md#compatibility-notes). - -## Card Types - -- Show your GitHub statistics: - - ![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra) - -- ...your top languages...: - - ![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&langs_count=4) - -- ...and development time: - - [![Alan's WakaTime stats](https://github-stats-extended.vercel.app/api/wakatime?username=alan&langs_count=6)](https://wakatime.com/@alan) - -- Pin more than 6 repos in your GitHub profile: - - [![Readme Card](https://github-stats-extended.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats)](https://github.com/anuraghazra/github-readme-stats) - -- Pin Gists in your GitHub profile: - - [![Gist Card](https://github-stats-extended.vercel.app/api/gist?id=bbfce31e0217a3689c8d961a356cb10d)](https://gist.github.com/Yizack/bbfce31e0217a3689c8d961a356cb10d) - -- Customize all the cards: - - [![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api/?username=anuraghazra&show_icons=true&theme=calm&rank_icon=github&include_all_commits=true&custom_title=Anurag's+Stats&disable_animations=true&number_format=long&show=prs_merged_percentage,prs_reviewed)](https://github-stats-extended.vercel.app/api/?username=anuraghazra&show_icons=true&theme=calm&rank_icon=github&include_all_commits=true&custom_title=Anurag's+Stats&disable_animations=true&number_format=long&show=prs_merged_percentage,prs_reviewed) - -## Advanced Customization +As a more comfortable alternative, use the [card wizard](https://github-stats-extended.vercel.app/frontend) to configure your card visually, then copy the generated markdown into your [GitHub profile README](https://docs.github.com/en/account-and-profile/how-tos/profile-customization/managing-your-profile-readme#adding-a-profile-readme). -The [GitHub-Stats-Extended Wizard](https://github-stats-extended.vercel.app/frontend) offers some essential customization options. For more advanced customization check out the [advanced documentation](docs/advanced_documentation.md). +## Documentation -## Run It Yourself +The full documentation is served by the deployment it describes, so every sample it shows is rendered by the version that is actually running: -If you want to run GitHub-Stats-Extended on your own, there are two main deployment options: you can use [github-readme-stats-action](https://github.com/stats-organization/github-readme-stats-action) to generate cards in your own GitHub Actions workflow. Or you can self-host GitHub-Stats-Extended on Vercel. +**[github-stats-extended.vercel.app/frontend/docs](https://github-stats-extended.vercel.app/frontend/docs)** -See [Run It Yourself](docs/deploy.md) for detailed instructions. +- [Overview](https://github-stats-extended.vercel.app/frontend/docs/) — quick start and migration from github-readme-stats. +- [Cards](https://github-stats-extended.vercel.app/frontend/docs/#card-types) — a preview of every card, each linking through to its own options. +- [Customization](https://github-stats-extended.vercel.app/frontend/docs/customization/common-options/) — options every card shares, plus theming and locales. +- [Available Themes](https://github-stats-extended.vercel.app/frontend/docs/customization/themes/) — the built-in themes, rendered as live samples. +- [Run It Yourself](https://github-stats-extended.vercel.app/frontend/docs/deploy/) — GitHub Actions or a self-hosted Vercel deployment. +- [Fork Information](https://github-stats-extended.vercel.app/frontend/docs/fork/) — what this project adds on top of github-readme-stats. ## Acknowledgements -This project is based on [github-readme-stats](https://github.com/anuraghazra/github-readme-stats). On top of that project's functionality GitHub-Stats-Extended adds several new features and improvements. See [Fork Information](docs/fork.md) for a list of changes. The frontend added to GitHub-Stats-Extended is based on [GitHub Trends](https://github.com/avgupta456/github-trends). Big thanks to [@anuraghazra](https://github.com/anuraghazra), [@avgupta456](https://github.com/avgupta456), [@rickstaa](https://github.com/rickstaa), [@qwerty541](https://github.com/qwerty541) and everyone else who worked on these projects! ❤️ +This project is based on [github-readme-stats](https://github.com/anuraghazra/github-readme-stats). The card wizard is based on [GitHub Trends](https://github.com/avgupta456/github-trends). Big thanks to [@anuraghazra](https://github.com/anuraghazra), [@avgupta456](https://github.com/avgupta456), [@rickstaa](https://github.com/rickstaa), [@qwerty541](https://github.com/qwerty541) and everyone else who worked on these projects! ❤️ ## Contributing -Contributions are welcome! +Contributions are welcome! See [CONTRIBUTING.md](.github/CONTRIBUTING.md) to get started. diff --git a/apps/backend/.vercelignore b/apps/backend/.vercelignore index cdf6243da63e9..5fc5132e14d9b 100644 --- a/apps/backend/.vercelignore +++ b/apps/backend/.vercelignore @@ -5,7 +5,5 @@ coverage scripts tests .env -**/*.md **/*.svg -.eslintrc.json codecov.yml diff --git a/apps/frontend/astro.config.ts b/apps/frontend/astro.config.ts new file mode 100644 index 0000000000000..361b3f9fa31b9 --- /dev/null +++ b/apps/frontend/astro.config.ts @@ -0,0 +1,113 @@ +import path from "node:path"; + +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"; + +const base = "/frontend"; + +/* + * One app: Starlight serves the docs under `/frontend/docs`, and the card + * wizard is a page of the same site at `/frontend`. + */ +export default defineConfig({ + site: "https://github-stats-extended.vercel.app", + base, + outDir: "./build", + // One screenshot; not worth a native image dependency. + image: { service: passthroughImageService() }, + // 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. + "/docs/cards": `${base}/docs/cards/stats/`, + "/docs/customization": `${base}/docs/customization/common-options/`, + }, + integrations: [ + react(), + starlight({ + title: "GitHub Stats Extended", + description: "Dynamically generate GitHub stats for your READMEs.", + logo: { src: "./src/assets/appLogo64.png", alt: "" }, + // Starlight defaults to a favicon.svg this project does not ship. + favicon: "/favicon.ico", + head: [ + { + tag: "link", + attrs: { rel: "apple-touch-icon", href: `${base}/logo192.png` }, + }, + { + tag: "link", + attrs: { rel: "manifest", href: `${base}/manifest.json` }, + }, + { tag: "meta", attrs: { name: "theme-color", content: "#000000" } }, + ], + social: [ + { + icon: "github", + label: "GitHub", + href: "https://github.com/stats-organization/github-stats-extended", + }, + ], + // The docs link to each other by site path, so a rename must fail the build. + plugins: [starlightLinksValidator()], + customCss: ["./src/styles/starlight-theme.css"], + components: { SiteTitle: "./src/components/SiteTitle.astro" }, + sidebar: [ + { label: "Overview", slug: "docs" }, + { + label: "Cards", + items: [ + { label: "Stats Card", slug: "docs/cards/stats" }, + { label: "Top Languages Card", slug: "docs/cards/top-languages" }, + { label: "Repo Pin Card", slug: "docs/cards/repo-pin" }, + { label: "Gist Pin Card", slug: "docs/cards/gist-pin" }, + { label: "WakaTime Card", slug: "docs/cards/wakatime" }, + ], + }, + { + label: "Customization", + items: [ + { + label: "Common Options", + slug: "docs/customization/common-options", + }, + { label: "Theming", slug: "docs/customization/theming" }, + // Generated by packages/core scripts/generate-theme-readme.js. + { label: "Available Themes", slug: "docs/customization/themes" }, + { label: "Locales", slug: "docs/customization/locales" }, + { + label: "Aligning Cards", + slug: "docs/customization/aligning-cards", + }, + ], + }, + { label: "Demo Gallery", slug: "docs/demos" }, + { label: "Run It Yourself", slug: "docs/deploy" }, + { label: "Fork Information", slug: "docs/fork" }, + ], + }), + ], + vite: { + plugins: [tailwindcss()], + resolve: { + conditions: ["@stats/source"], + alias: [ + { + find: "../fetchers/wakatime.js", + replacement: path.resolve( + import.meta.dirname, + "src/wakatime-override.ts", + ), + }, + ], + }, + // The backend code the wizard reuses imports `pg`, which never runs in the browser. + build: { + rolldownOptions: { + external: ["pg"], + }, + }, + }, +}); diff --git a/apps/frontend/e2e/app.spec.ts b/apps/frontend/e2e/app.spec.ts index 04f41e5e8c1b8..63b2b5e674be0 100644 --- a/apps/frontend/e2e/app.spec.ts +++ b/apps/frontend/e2e/app.spec.ts @@ -1,25 +1,29 @@ import { expect, test } from "@playwright/test"; +const REPO_URL = "https://github.com/stats-organization/github-stats-extended"; + test("load initial page correctly", async ({ page }) => { await page.goto(""); // Expect a title "to contain" a substring. await expect(page).toHaveTitle(/GitHub Stats Extended/); - // Page title / header branding - await expect(page.getByText("GitHub Stats Extended")).toBeVisible(); - - // Logo exists - await expect(page.locator('img[alt="logo"]')).toBeVisible(); + // Header branding, with the logo beside the wordmark. + const siteTitle = page.getByRole("link", { name: "GitHub Stats Extended" }); + await expect(siteTitle).toBeVisible(); + await expect(siteTitle.locator("img")).toBeVisible(); - // Star on GitHub link - const starButton = page.getByRole("link", { name: /star on/i }); - await expect(starButton).toBeVisible(); + // The two halves of the site, with this one marked as current. + await expect( + page.getByRole("link", { name: "Wizard", exact: true }), + ).toHaveAttribute("aria-current", "page"); + await expect( + page.getByRole("link", { name: "Docs", exact: true }), + ).toHaveAttribute("href", "/frontend/docs/"); - const githubLink = page.locator( - 'a[href*="github.com/stats-organization/github-stats-extended"]', - ); - await expect(githubLink).toHaveAttribute("target", "_blank"); + await expect( + page.getByRole("link", { name: "GitHub", exact: true }), + ).toHaveAttribute("href", REPO_URL); // Login buttons const publicAccessBtn = page.getByRole("button", { @@ -39,72 +43,53 @@ test("load initial page correctly", async ({ page }) => { await expect(guestBtn).toBeEnabled(); }); -test("theme picker switches and persists the theme", async ({ page }) => { +test("theme selection applies, persists and reaches the wizard", async ({ + page, +}) => { await page.goto(""); const html = page.locator("html"); - const trigger = page.getByRole("button", { name: /choose theme/i }); - const options = page.getByRole("menuitemradio"); - - // The menu is closed initially. - await expect(trigger).toBeVisible(); - await expect(trigger).toHaveAttribute("aria-expanded", "false"); - await expect(options).toHaveCount(0); - - // Opening it reveals the options (portaled to body, above the stepper). - await trigger.click(); - await expect(trigger).toHaveAttribute("aria-expanded", "true"); - - // Read the themes off the options instead of hardcoding their names. - const firstTheme = await options.first().getAttribute("data-theme-value"); - const lastTheme = await options.last().getAttribute("data-theme-value"); - - // Selecting an option applies the theme, persists it, and closes the menu. - await options.last().click(); - await expect(html).toHaveAttribute("data-theme", lastTheme ?? ""); - await expect(options).toHaveCount(0); + const themeSelect = page.getByRole("combobox", { name: "Select theme" }); + + // The site's own control owns `data-theme`, which is also what daisyUI reads. + await themeSelect.selectOption("dark"); + await expect(html).toHaveAttribute("data-theme", "dark"); await expect( - page.evaluate(() => localStorage.getItem("theme")), - ).resolves.toBe(lastTheme); + page.evaluate(() => localStorage.getItem("starlight-theme")), + ).resolves.toBe("dark"); - // The choice survives a reload and is marked as selected on reopen. + // The choice survives a reload. await page.reload(); - await expect(html).toHaveAttribute("data-theme", lastTheme ?? ""); - await trigger.click(); - await expect(options.last()).toHaveAttribute("aria-checked", "true"); + await expect(html).toHaveAttribute("data-theme", "dark"); + await expect(themeSelect).toHaveValue("dark"); - // Switching to another option works too. - await options.first().click(); - await expect(html).toHaveAttribute("data-theme", firstTheme ?? ""); + // Switching back works too. + await themeSelect.selectOption("light"); + await expect(html).toHaveAttribute("data-theme", "light"); }); test("navigates between steps", async ({ page }) => { await page.goto(""); + // Scoped to the page content: Astro's dev toolbar has headings of its own. + const heading = page.locator("main").getByRole("heading", { level: 1 }); + // We are at stage 1 - await expect(page.getByRole("heading", { level: 1 })).toContainText("Login"); + await expect(heading).toContainText("Login"); // Go to stage 2 await page.getByRole("button", { name: "Select card" }).click(); - await expect(page.getByRole("heading", { level: 1 })).toContainText( - "Select a Card", - ); + await expect(heading).toContainText("Select a Card"); // Go to stage 3 await page.getByRole("button", { name: "Modify parameters" }).click(); - await expect(page.getByRole("heading", { level: 1 })).toContainText( - "Modify Card Parameters", - ); + await expect(heading).toContainText("Modify Card Parameters"); // Go to stage 4 await page.getByRole("button", { name: "Select theme" }).click(); - await expect(page.getByRole("heading", { level: 1 })).toContainText( - "Choose a Theme", - ); + await expect(heading).toContainText("Choose a Theme"); // Go to stage 5 await page.getByRole("button", { name: "Display card" }).click(); - await expect(page.getByRole("heading", { level: 1 })).toContainText( - "Display your Card", - ); + await expect(heading).toContainText("Display your Card"); }); diff --git a/apps/frontend/e2e/docs-redirects.spec.ts b/apps/frontend/e2e/docs-redirects.spec.ts new file mode 100644 index 0000000000000..c1c51e693b7b9 --- /dev/null +++ b/apps/frontend/e2e/docs-redirects.spec.ts @@ -0,0 +1,29 @@ +import { expect, test } from "@playwright/test"; + +// Nothing links to a redirect, so the build's link validator cannot catch a broken one. +const redirects = [ + { + from: "docs/cards", + to: "/frontend/docs/cards/stats/", + heading: "Stats Card", + }, + { + from: "docs/customization", + to: "/frontend/docs/customization/common-options/", + heading: "Common Options", + }, +]; + +for (const { from, to, heading } of redirects) { + test(`/${from} redirects to ${to}`, async ({ page }) => { + for (const url of [from, `${from}/`]) { + await page.goto(url); + + await expect(page).toHaveURL(to); + // Scoped to the page content: Astro's dev toolbar has headings of its own. + await expect( + page.locator("main").getByRole("heading", { level: 1 }), + ).toHaveText(heading); + } + }); +} diff --git a/apps/frontend/e2e/stats-rank.spec.ts b/apps/frontend/e2e/stats-rank.spec.ts index 4bbfe8b4375fe..b1180e2f74040 100644 --- a/apps/frontend/e2e/stats-rank.spec.ts +++ b/apps/frontend/e2e/stats-rank.spec.ts @@ -7,9 +7,10 @@ test("selecting 'None' progress style hides the rank circle", async ({ // Stage 0 -> 2: go to customization await page.getByRole("button", { name: "Modify Parameters" }).click(); - await expect(page.getByRole("heading", { level: 1 })).toContainText( - "Modify Card Parameters", - ); + // Scoped to the page content: Astro's dev toolbar has headings of its own. + await expect( + page.locator("main").getByRole("heading", { level: 1 }), + ).toContainText("Modify Card Parameters"); const preview = page.locator("#svgWrapper"); const rankCircle = preview.locator('[data-testid="rank-circle"]'); @@ -18,7 +19,11 @@ test("selecting 'None' progress style hides the rank circle", async ({ await expect(preview).toBeAttached(); await expect(rankCircle).toHaveCount(1); - await page.getByRole("combobox").selectOption({ label: "None" }); + // Scoped: the site header has a theme combobox of its own. + await page + .locator("main") + .getByRole("combobox") + .selectOption({ label: "None" }); await expect(rankCircle).toHaveCount(0); }); diff --git a/apps/frontend/e2e/app-trends-auth.spec.ts b/apps/frontend/e2e/wizard-auth.spec.ts similarity index 79% rename from apps/frontend/e2e/app-trends-auth.spec.ts rename to apps/frontend/e2e/wizard-auth.spec.ts index 6976e1eb0fc33..5e7e054a72f18 100644 --- a/apps/frontend/e2e/app-trends-auth.spec.ts +++ b/apps/frontend/e2e/wizard-auth.spec.ts @@ -5,7 +5,7 @@ import type { Page } from "@playwright/test"; * Puts the SPA into an authenticated state without contacting GitHub by * stubbing the OAuth code exchange and the follow-up user-access lookup: * `authenticate` returns a userId (which flips `isAuthenticated` to true), and - * `user-access` returns metadata so AppTrends does not immediately log back out. + * `user-access` returns metadata so the wizard does not immediately log back out. * @param page - The Playwright page to install the route handlers on. */ async function mockAuthEndpoints(page: Page): Promise { @@ -25,7 +25,7 @@ async function mockAuthEndpoints(page: Page): Promise { ); } -test.describe("AppTrends auth-driven stage transition", () => { +test.describe("the wizard auth-driven stage transition", () => { test("auto-advances from Login to Select a Card once authenticated", async ({ page, }) => { @@ -34,12 +34,13 @@ test.describe("AppTrends auth-driven stage transition", () => { // Land on the app as GitHub does after the OAuth redirect (URL carries `code`). await page.goto("?code=test-oauth-code"); - // AppTrends starts unauthenticated on stage 0 ("Login"). When the code + // the wizard starts unauthenticated on stage 0 ("Login"). When the code // exchange flips `isAuthenticated` false -> true, the render-phase // transition must advance the stepper to stage 1 ("Select a Card"). - await expect(page.getByRole("heading", { level: 1 })).toContainText( - "Select a Card", - ); + // Scoped to the page content: Astro's dev toolbar has headings of its own. + await expect( + page.locator("main").getByRole("heading", { level: 1 }), + ).toContainText("Select a Card"); }); test("keeps a manually selected earlier step instead of re-forcing it", async ({ @@ -48,7 +49,7 @@ test.describe("AppTrends auth-driven stage transition", () => { await mockAuthEndpoints(page); await page.goto("?code=test-oauth-code"); - const heading = page.getByRole("heading", { level: 1 }); + const heading = page.locator("main").getByRole("heading", { level: 1 }); await expect(heading).toContainText("Select a Card"); // Going back to "Login" while still authenticated must stick: the transition diff --git a/apps/frontend/index.html b/apps/frontend/index.html deleted file mode 100644 index ff41b6247fa2d..0000000000000 --- a/apps/frontend/index.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - GitHub Stats Extended - - - - -
- - - diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 13bce35494c22..3c1bf9f115620 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -4,19 +4,22 @@ "license": "MIT", "private": true, "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview", + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", "test": "vitest", "test:e2e": "playwright test", - "lint": "eslint", - "typecheck": "tsc -p tsconfig.typecheck.json" + "lint": "astro sync && eslint", + "typecheck": "astro sync && tsc -p tsconfig.typecheck.json" }, "dependencies": { + "@astrojs/react": "^6.0.2", + "@astrojs/starlight": "^0.41.7", "@reduxjs/toolkit": "^2.12.0", "@stats-organization/github-readme-stats-backend": "workspace:^", "@stats-organization/github-readme-stats-core": "workspace:^", "@tailwindcss/vite": "^4.3.3", + "astro": "^7.2.0", "axios": "catalog:default", "axios-cache-interceptor": "^1.12.0", "daisyui": "^5.6.18", @@ -34,10 +37,10 @@ "devDependencies": { "@types/react": "19.2.17", "@types/react-dom": "19.2.3", - "@vitejs/plugin-react": "6.0.3", "clsx": "2.1.1", + "jsdom": "catalog:default", + "starlight-links-validator": "0.25.2", "tailwindcss": "4.3.3", - "vite": "catalog:default", "vitest": "catalog:default" }, "browserslist": { diff --git a/apps/frontend/playwright.config.ts b/apps/frontend/playwright.config.ts index 0e38be08fcd6a..81684b84182e9 100644 --- a/apps/frontend/playwright.config.ts +++ b/apps/frontend/playwright.config.ts @@ -8,7 +8,7 @@ import { defineConfig, devices } from "@playwright/test"; // import path from 'path'; // dotenv.config({ path: path.resolve(__dirname, '.env') }); -const baseURL = "http://localhost:5173/frontend/"; +const baseURL = "http://localhost:4321/frontend/"; /** * See https://playwright.dev/docs/test-configuration. @@ -25,6 +25,8 @@ export default defineConfig({ workers: 1, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: "html", + // The wizard is a client-only island; on a cold dev server its bundle takes a while. + expect: { timeout: 15_000 }, /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('')`. */ @@ -77,5 +79,7 @@ export default defineConfig({ command: "pnpm run dev", url: baseURL, reuseExistingServer: !process.env["CI"], + // `astro dev` detaches itself in an AI-agent shell, which reads as exiting early. + env: { ASTRO_DEV_BACKGROUND: "1" }, }, }); diff --git a/apps/frontend/src/axios-override.ts b/apps/frontend/src/axios-override.ts index 52308e4998dc2..bdfe56bc8bd17 100644 --- a/apps/frontend/src/axios-override.ts +++ b/apps/frontend/src/axios-override.ts @@ -8,18 +8,18 @@ import { DEMO_WAKATIME_USER, HOST, } from "./constants"; -import additionalUserStars from "./mockData/additional_user_stars.json" with { type: "json" }; -import commentedIssues from "./mockData/commented_issues.json" with { type: "json" }; -import commentedPrs from "./mockData/commented_prs.json" with { type: "json" }; -import commits from "./mockData/commits.json" with { type: "json" }; -import contributions from "./mockData/contributions.json" with { type: "json" }; -import gist_graphql from "./mockData/gist-graphql.json" with { type: "json" }; -import gist_rest from "./mockData/gist-rest.json" with { type: "json" }; -import repository from "./mockData/repository.json" with { type: "json" }; -import reviewedPrs from "./mockData/reviewed_prs.json" with { type: "json" }; -import topLanguages from "./mockData/top_languages.json" with { type: "json" }; -import userStats from "./mockData/user_stats.json" with { type: "json" }; -import wakatimeProxy from "./mockData/wakatime_proxy.json" with { type: "json" }; +import additionalUserStars from "./wizard/mockData/additional_user_stars.json" with { type: "json" }; +import commentedIssues from "./wizard/mockData/commented_issues.json" with { type: "json" }; +import commentedPrs from "./wizard/mockData/commented_prs.json" with { type: "json" }; +import commits from "./wizard/mockData/commits.json" with { type: "json" }; +import contributions from "./wizard/mockData/contributions.json" with { type: "json" }; +import gist_graphql from "./wizard/mockData/gist-graphql.json" with { type: "json" }; +import gist_rest from "./wizard/mockData/gist-rest.json" with { type: "json" }; +import repository from "./wizard/mockData/repository.json" with { type: "json" }; +import reviewedPrs from "./wizard/mockData/reviewed_prs.json" with { type: "json" }; +import topLanguages from "./wizard/mockData/top_languages.json" with { type: "json" }; +import userStats from "./wizard/mockData/user_stats.json" with { type: "json" }; +import wakatimeProxy from "./wizard/mockData/wakatime_proxy.json" with { type: "json" }; const cachedAxios = setupCache(axios, { // Cache for 30 minutes diff --git a/apps/frontend/src/components/Generic/ThemePicker.tsx b/apps/frontend/src/components/Generic/ThemePicker.tsx deleted file mode 100644 index a4397497f62d0..0000000000000 --- a/apps/frontend/src/components/Generic/ThemePicker.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { useCallback, useEffect, useRef, useState } from "react"; -import type { JSX } from "react"; -import { createPortal } from "react-dom"; -import { - FaCheck as CheckIcon, - FaMoon as MoonIcon, - FaSun as SunIcon, -} from "react-icons/fa"; - -import { useTheme } from "../../redux/selectors/themeSelectors"; -import { THEMES } from "../../redux/slices/theme"; - -interface ThemeIconProps { - isDark: boolean; - className: string; -} - -/** Sun for light themes, moon for dark ones. */ -function ThemeIcon({ isDark, className }: ThemeIconProps): JSX.Element { - return isDark ? ( - - ) : ( - - ); -} - -export function ThemePicker(): JSX.Element { - const { theme, isDark, setTheme } = useTheme(); - const [open, setOpen] = useState(false); - const [position, setPosition] = useState({ top: 0, right: 0 }); - const buttonRef = useRef(null); - const menuRef = useRef(null); - - const handleToggle = useCallback(() => { - if (!open && buttonRef.current) { - const rect = buttonRef.current.getBoundingClientRect(); - setPosition({ - top: rect.bottom + 8, - right: window.innerWidth - rect.right, - }); - } - setOpen((value) => !value); - }, [open]); - - useEffect(() => { - if (!open) { - return undefined; - } - - const close = () => { - setOpen(false); - }; - const handlePointerDown = (event: MouseEvent) => { - const target = event.target as Node; - if ( - !menuRef.current?.contains(target) && - !buttonRef.current?.contains(target) - ) { - setOpen(false); - } - }; - const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === "Escape") { - setOpen(false); - } - }; - - document.addEventListener("mousedown", handlePointerDown); - document.addEventListener("keydown", handleKeyDown); - window.addEventListener("resize", close); - // Close on scroll so the fixed menu can't detach from its button. - window.addEventListener("scroll", close, true); - - return () => { - document.removeEventListener("mousedown", handlePointerDown); - document.removeEventListener("keydown", handleKeyDown); - window.removeEventListener("resize", close); - window.removeEventListener("scroll", close, true); - }; - }, [open]); - - return ( - <> - - - {open && - createPortal( -
    - {THEMES.map((option) => ( -
  • - -
  • - ))} -
, - document.body, - )} - - ); -} diff --git a/apps/frontend/src/components/SiteTitle.astro b/apps/frontend/src/components/SiteTitle.astro new file mode 100644 index 0000000000000..2607fd72b98eb --- /dev/null +++ b/apps/frontend/src/components/SiteTitle.astro @@ -0,0 +1,56 @@ +--- +// Starlight has no nav slot, so the two top-level destinations sit beside the title. +import Default from "@astrojs/starlight/components/SiteTitle.astro"; + +const base = import.meta.env.BASE_URL.replace(/\/$/, ""); +const path = Astro.url.pathname; +const links = [ + { href: `${base}/`, label: "Wizard", current: !path.startsWith(`${base}/docs`) }, + { href: `${base}/docs/`, label: "Docs", current: path.startsWith(`${base}/docs`) }, +]; +--- + + + + + + diff --git a/apps/frontend/src/content.config.ts b/apps/frontend/src/content.config.ts new file mode 100644 index 0000000000000..6eb205a7fcc2f --- /dev/null +++ b/apps/frontend/src/content.config.ts @@ -0,0 +1,13 @@ +import { defineCollection } from "astro:content"; + +import { docsLoader } from "@astrojs/starlight/loaders"; +import { docsSchema } from "@astrojs/starlight/schema"; + +/* + * Starlight fixes this collection at `src/content/docs/` and maps it to the site root, + * which the wizard owns — hence the pages sitting one level down, in `docs/`. + * https://starlight.astro.build/guides/pages/#pages-from-markdown + */ +export const collections = { + docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), +}; diff --git a/apps/frontend/src/content/docs/_README.md b/apps/frontend/src/content/docs/_README.md new file mode 100644 index 0000000000000..1f56e1cc9ae52 --- /dev/null +++ b/apps/frontend/src/content/docs/_README.md @@ -0,0 +1,14 @@ +# Why `docs/docs` + +Starlight fixes its content collection at `src/content/docs/`, and maps that folder to the **site root** (`/frontend`), which the card wizard occupies (`src/pages/index.astro`). + +The pages therefore sit one level down, in `docs/`, so they publish at `/frontend/docs/`. +Nothing else can be renamed: the outer name is Starlight's, the inner one is the URL segment. + +Flattening this needs a custom `generateId` on the loader, which is the one thing `starlight-links-validator` cannot follow. +It derives valid routes from file paths, so every internal link would be reported broken. +The nesting buys build-time dead-link checking. + +Starlight ignores files starting with `_`, which is why this note isn't published. + +See . diff --git a/apps/frontend/src/content/docs/docs/cards/gist-pin.md b/apps/frontend/src/content/docs/docs/cards/gist-pin.md new file mode 100644 index 0000000000000..dd4648bd36edb --- /dev/null +++ b/apps/frontend/src/content/docs/docs/cards/gist-pin.md @@ -0,0 +1,32 @@ +--- +title: Gist Pin Card +--- + +GitHub gist pins allow you to pin gists in your GitHub profile using a GitHub profile readme. + +## Usage + +Copy-paste this code into your readme and change the links. + +Endpoint: `api/gist?id=bbfce31e0217a3689c8d961a356cb10d` + +```md +[![Gist Card](https://github-stats-extended.vercel.app/api/gist?id=bbfce31e0217a3689c8d961a356cb10d)](https://gist.github.com/Yizack/bbfce31e0217a3689c8d961a356cb10d/) +``` + +## Options + +You can customize the appearance and behavior of the gist card using the [common options](/frontend/docs/customization/common-options/) and exclusive options listed in the table below. + +| Name | Description | Type | Default value | +| ------------------- | ------------------------------------------------------------------------------------------------------- | ------- | ------------- | +| `show_owner` | Shows the gist's owner name. | boolean | `false` | +| `browser_rendering` | Compute text wrapping of gist description natively in the browser, instead of computing it server-side. | boolean | `false` | + +## Demo + +![Gist Card](https://github-stats-extended.vercel.app/api/gist?id=bbfce31e0217a3689c8d961a356cb10d) + +Use [show\_owner](#options) query option to include the gist's owner username + +![Gist Card](https://github-stats-extended.vercel.app/api/gist?id=bbfce31e0217a3689c8d961a356cb10d&show_owner=true) diff --git a/apps/frontend/src/content/docs/docs/cards/repo-pin.md b/apps/frontend/src/content/docs/docs/cards/repo-pin.md new file mode 100644 index 0000000000000..ff26c94032ff6 --- /dev/null +++ b/apps/frontend/src/content/docs/docs/cards/repo-pin.md @@ -0,0 +1,49 @@ +--- +title: Repo Pin Card +--- + +GitHub extra pins allow you to pin more than 6 repositories in your profile using a GitHub profile readme. + +Yay! You are no longer limited to 6 pinned repositories. + +## Usage + +Copy-paste this code into your readme and change the links. + +Endpoint: `api/pin?username=anuraghazra&repo=type-trident` + +```md +[![Readme Card](https://github-stats-extended.vercel.app/api/pin/?username=anuraghazra&repo=type-trident)](https://github.com/anuraghazra/type-trident) +``` + +## Options + +You can customize the appearance and behavior of the pinned repository card using the [common options](/frontend/docs/customization/common-options/) and exclusive options listed in the table below. + +| Name | Description | Type | Default value | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------- | ------------------ | +| `show_owner` | Shows the repo's owner name. | boolean | `false` | +| `browser_rendering` | Compute text wrapping of repository description natively in the browser, instead of computing it server-side. | boolean | `false` | +| `description_lines_count` | Manually set the number of lines for the description. Specified value will be clamped between 1 and 3. If this parameter is not specified, the number of lines will be automatically adjusted according to the actual length of the description. | number | `null` | +| `card_width` | Sets the card's width manually. | number | `400px (approx.)` | +| `show_icons` | Shows icons near all stats enabled via `show`. | boolean | `true` | +| `line_height` | Sets the line height between stats enabled via `show`. | integer | `22` | +| `text_bold` | Uses bold text for all stats enabled via `show`. | boolean | `false` | +| `number_format` | Switches between two available formats for displaying the numbers for all stats enabled via `show`: `short` (i.e. `6.6k`) and `long` (i.e. `6626`). | enum | `short` | +| `show` | Shows [additional items](/frontend/docs/cards/stats/#showing-additional-individual-stats) on stats card (i.e. `prs_authored`, `prs_commented`, `prs_reviewed`, `issues_authored` or `issues_commented`). | string (comma-separated values) | `null` | + +## Demo + +![Readme Card](https://github-stats-extended.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats) + +Use [show\_owner](#options) query option to include the repo's owner username: + +![Readme Card](https://github-stats-extended.vercel.app/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: + +![Readme Card](https://github-stats-extended.vercel.app/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 `/` 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. + +![Readme Card](https://github-stats-extended.vercel.app/api/pin/?username=anuraghazra&repo=statykjs/statyk&show_owner=true&show=prs_authored,prs_commented,prs_reviewed,issues_authored,issues_commented) diff --git a/apps/frontend/src/content/docs/docs/cards/stats.md b/apps/frontend/src/content/docs/docs/cards/stats.md new file mode 100644 index 0000000000000..0522463f8871c --- /dev/null +++ b/apps/frontend/src/content/docs/docs/cards/stats.md @@ -0,0 +1,92 @@ +--- +title: Stats Card +--- + +The stats card shows a summary of your GitHub activity: stars earned, commits, pull requests, issues, contributions and an overall rank. + +:::caution[Warning] +By default, the stats card only shows statistics like stars, commits, and pull requests from public repositories. To show private statistics on the stats card, [allow GitHub-Stats-Extended to access your private contributions](/frontend/docs/fork/#private-contributions-support) or [deploy your own instance](/frontend/docs/deploy/). +::: + +:::note +Available ranks are S (top 1%), A+ (12.5%), A (25%), A- (37.5%), B+ (50%), B (62.5%), B- (75%), C+ (87.5%) and C (everyone). This ranking scheme is based on the [Japanese academic grading](https://wikipedia.org/wiki/Academic_grading_in_Japan) system. The global percentile is calculated as a weighted sum of percentiles for each statistic (number of commits, pull requests, reviews, issues, stars, and followers), based on the cumulative distribution function of the [exponential](https://wikipedia.org/wiki/exponential_distribution) and the [log-normal](https://wikipedia.org/wiki/Log-normal_distribution) distributions. The implementation can be investigated at [calculateRank.ts](https://github.com/stats-organization/github-stats-extended/blob/master/packages/core/src/calculateRank.ts). The circle around the rank shows 100 minus the global percentile. +::: + +## Hiding individual stats + +You can pass a query parameter `&hide=` to hide any specific stats with comma-separated values. + +> Options: `&hide=stars,commits,prs,issues,contribs` + +```md +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&hide=contribs,prs) +``` + +## Showing additional individual stats + +You can pass a query parameter `&show=` to show any specific additional stats with comma-separated values. + +> Options: `&show=reviews,discussions_started,discussions_answered,prs_merged,prs_merged_percentage,prs_authored,prs_commented,prs_reviewed,issues_authored,issues_commented` + +```md +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&show=reviews,discussions_started,discussions_answered,prs_merged,prs_merged_percentage,prs_authored,prs_commented,prs_reviewed,issues_authored,issues_commented) +``` + +## Showing icons + +To enable icons, you can pass `&show_icons=true` in the query param, like so: + +```md +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true) +``` + +## Filtering by repository and owner + +To compute your stats for only a specific repository, you can pass a query parameter `&repo=/`. You can also specify a comma-separated list of multiple repositories, e.g. `&repo=userA/repositoryA,organizationB/repositoryB`. And you can select all repositories owned by specific organizations or users by providing a comma-separated list of owners via the `owner` query parameter, e.g. `&owner=userA,organizationB,organizationC`. The `repo` and `owner` filters are supported by the following items: `commits` (when used with `&include_all_commits=true`), `prs_authored`, `prs_commented`, `prs_reviewed`, `issues_authored` and `issues_commented`. Note that most of these items are not displayed by default, but [you can enable them individually](#showing-additional-individual-stats). + +(Some of these mentioned items are similar to other items which are included by default, e.g. `issues_authored` is similar to `issues`. The difference is how these values are fetched - [via GraphQL or via REST API](https://github.com/anuraghazra/github-readme-stats/discussions/1770#number-of-commits-is-incorrect). The default items use GraphQL, but filtering by repository works better via REST API.) + +Alternatively, you can use the `role` parameter to specify a comma-separated list of [roles](https://docs.github.com/en/graphql/reference/enums#repositoryaffiliation). The stats will include all repositories in which the user has the specified role. By default, only repositories where the user is OWNER will be included, but you could e.g. set `&role=OWNER,ORGANIZATION_MEMBER,COLLABORATOR`. The `role` parameter is supported by all items except the following: `commits` (when used with `&include_all_commits=true`), `prs_authored`, `prs_commented`, `prs_reviewed`, `issues_authored` and `issues_commented`. + +## Showing commits count for specified year + +You can specify a year and fetch only the commits that were made in that year by passing `&commits_year=YYYY` to the parameter. + +```md +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&commits_year=2020) +``` + +## Options + +You can customize the appearance and behavior of the stats card using the [common options](/frontend/docs/customization/common-options/) and the exclusive options listed in the table below. + +| Name | Description | Type | Default value | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ----------------------------------- | +| `hide` | Hides the [specified items](#hiding-individual-stats) from stats. | string (comma-separated values) | `null` | +| `hide_title` | Hides the title of your stats card. | boolean | `false` | +| `card_width` | Sets the card's width manually. | number | `500px (approx.)` | +| `hide_rank` | Hides the rank and automatically resizes the card width. | boolean | `false` | +| `rank_icon` | Shows alternative rank icon (i.e. `github`, `percentile` or `default`). | enum | `default` | +| `show_icons` | Shows icons near all stats. | boolean | `false` | +| `include_all_commits` | Count total commits instead of just the current year commits. | boolean | `false` | +| `line_height` | Sets the line height between text. | integer | `25` | +| `exclude_repo` | Excludes specified repositories. Affects only the count for "Total Stars Earned". | string (comma-separated values) | `null` | +| `repo` | Count only stats from the specified repositories. Affects only [certain items](#filtering-by-repository-and-owner). | string (comma-separated values) | `null` | +| `owner` | Count only stats from the specified organizations or users. Affects only [certain items](#filtering-by-repository-and-owner). | string (comma-separated values) | `null` | +| `role` | Include repositories where the user has one of the specified [roles](https://docs.github.com/en/graphql/reference/enums#repositoryaffiliation) (OWNER, ORGANIZATION_MEMBER, COLLABORATOR). | string (comma-separated values) | `OWNER` | +| `custom_title` | Sets a custom title for the card. | string | ` GitHub Stats` | +| `text_bold` | Uses bold text. | boolean | `true` | +| `disable_animations` | Disables all animations in the card. | boolean | `false` | +| `ring_color` | Color of the rank circle. | string (hex color) | `2f80ed` | +| `number_format` | Switches between two available formats for displaying the card values: `short` (i.e. `6.6k`) and `long` (i.e. `6626`). | enum | `short` | +| `number_precision` | Enforce the number of digits after the decimal point for `short` number format. Must be an integer between 0 and 2. Will be ignored for `long` number format. | integer (0, 1 or 2) | `null` | +| `show` | Shows [additional items](#showing-additional-individual-stats) on stats card (i.e. `reviews`, `discussions_started`, `discussions_answered`, `prs_merged` or `prs_merged_percentage`. And the following, which support the `repo` and `owner` filters: `prs_authored`, `prs_commented`, `prs_reviewed`, `issues_authored` or `issues_commented`). | string (comma-separated values) | `null` | +| `commits_year` | Filters and counts only commits made in the specified year. | integer _(YYYY)_ | ` (one year to date)` | + +:::caution[Warning] +Custom title should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding) (i.e: `Anurag's GitHub Stats` should become `Anurag%27s%20GitHub%20Stats`). You can use [urlencoder.org](https://www.urlencoder.org/) to help you do this automatically. +::: + +:::note +When hide\_rank=`true`, the minimum card width is 270 px + the title length and padding. +::: diff --git a/apps/frontend/src/content/docs/docs/cards/top-languages.md b/apps/frontend/src/content/docs/docs/cards/top-languages.md new file mode 100644 index 0000000000000..528593eb1fb15 --- /dev/null +++ b/apps/frontend/src/content/docs/docs/cards/top-languages.md @@ -0,0 +1,178 @@ +--- +title: Top Languages Card +--- + +The top languages card shows your most frequently used languages. + +:::caution[Warning] +By default, the language card shows language results only from public repositories. To include languages used in private repositories, [allow GitHub-Stats-Extended to access your private contributions](/frontend/docs/fork/#private-contributions-support) or [deploy your own instance](/frontend/docs/deploy/). +::: + +:::caution[Warning] +This card shows language usage only inside your own non-forked repositories, not depending on who the author of the commits is. It does not include your contributions into another users/organizations repositories. Currently there are no way to get this data from GitHub API. If you want this behavior to be improved you can support [this feature request](https://github.com/orgs/community/discussions/18230) created by [@rickstaa](https://github.com/rickstaa) inside GitHub Community. +::: + +:::caution[Warning] +Currently this card shows data only about first 1000 repositories. This is because GitHub API limitations which cause downtimes of public instances (see [#1471](https://github.com/anuraghazra/github-readme-stats/issues/1471)). In future this behavior will be improved by releasing GitHub action or providing environment variables for user's own instances. +::: + +## Usage + +Copy-paste this code into your readme and change the links. + +Endpoint: `api/top-langs?username=anuraghazra` + +```md +[![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/stats-organization/github-stats-extended) +``` + +## Options + +You can customize the appearance and behavior of the top languages card using the [common options](/frontend/docs/customization/common-options/) and exclusive options listed in the table below. + +| Name | Description | Type | Default value | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------- | --------------------------------------------------- | +| `hide` | Hides the [specified languages](#hide-individual-languages) from card. | string (comma-separated values) | `null` | +| `hide_title` | Hides the title of your card. | boolean | `false` | +| `layout` | Switches between five available layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`. | enum | `normal` | +| `card_width` | Sets the card's width manually. | number | `300` | +| `langs_count` | Shows more languages on the card, between 1-20. | integer | `5` for `normal` and `donut`, `6` for other layouts | +| `exclude_repo` | Excludes specified repositories. | string (comma-separated values) | `null` | +| `role` | Include repositories where the user has one of the specified [roles](https://docs.github.com/en/graphql/reference/enums#repositoryaffiliation) (OWNER, ORGANIZATION_MEMBER, COLLABORATOR). | string (comma-separated values) | `OWNER` | +| `custom_title` | Sets a custom title for the card. | string | `Most Used Languages` | +| `disable_animations` | Disables all animations in the card. | boolean | `false` | +| `prog_bar_bg_color` | Background color of the bars. (Applies only to `normal` layout.) | string (hex color) | `#ddd` | +| `hide_progress` | Uses the compact layout option, hides percentages, and removes the bars. | boolean | `false` | +| `hide_values` | Hides language percentages or bytes while keeping the progress bars or chart. | boolean | `false` | +| `size_weight` | Configures language stats algorithm (see [Language stats algorithm](#language-stats-algorithm)). | integer | `1` | +| `count_weight` | Configures language stats algorithm (see [Language stats algorithm](#language-stats-algorithm)). | integer | `0` | +| `stats_format` | Switches between two available formats for language's stats `percentages` and `bytes`. | enum | `percentages` | + +:::caution[Warning] +Language names and custom title should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding) (i.e: `c++` should become `c%2B%2B`, `jupyter notebook` should become `jupyter%20notebook`, `Most Used Languages` should become `Most%20Used%20Languages`, etc.) You can use [urlencoder.org](https://www.urlencoder.org/) to help you do this automatically. +::: + +## Language stats algorithm + +We use the following algorithm to calculate the languages percentages on the language card: + +```js +ranking_index = (byte_count ^ size_weight) * (repo_count ^ count_weight); +``` + +By default, only the byte count is used for determining the languages percentages shown on the language card (i.e. `size_weight=1` and `count_weight=0`). You can, however, use the `&size_weight=` and `&count_weight=` options to weight the language usage calculation. The values must be positive real numbers. [More details about the algorithm can be found here](https://github.com/anuraghazra/github-readme-stats/issues/1600#issuecomment-1046056305). + +- `&size_weight=1&count_weight=0` - _(default)_ Orders by byte count. +- `&size_weight=0.5&count_weight=0.5` - _(recommended)_ Uses both byte and repo count for ranking + - `&size_weight=0&count_weight=1` - Orders by repo count + +```md +![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&size_weight=0.5&count_weight=0.5) +``` + +If the percentages still look wrong to you, these two comments cover most of the reasons why, and are worth reading before opening an issue: + +- +- + +## Exclude individual repositories + +You can use the `&exclude_repo=repo1,repo2` parameter to exclude individual repositories. + +```md +![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&exclude_repo=github-readme-stats,anuraghazra.github.io) +``` + +## Hide individual languages + +You can use `&hide=language1,language2` parameter to hide individual languages. + +```md +![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&hide=javascript,html) +``` + +Language names with spaces or symbols need to be [percent-encoded](#options), so Jupyter Notebook becomes `&hide=jupyter%20notebook` and C++ becomes `&hide=c%2B%2B`. + +## Show more languages + +You can use the `&langs_count=` option to increase or decrease the number of languages shown on the card. Valid values are integers between 1 and 20 (inclusive). By default it was set to `5` for `normal` & `donut` and `6` for other layouts. + +```md +![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&langs_count=8) +``` + +## Compact Language Card Layout + +You can use the `&layout=compact` option to change the card design. + +```md +![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&layout=compact) +``` + +## Donut Chart Language Card Layout + +You can use the `&layout=donut` option to change the card design. + +```md +[![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&layout=donut)](https://github.com/stats-organization/github-stats-extended) +``` + +## Donut Vertical Chart Language Card Layout + +You can use the `&layout=donut-vertical` option to change the card design. + +```md +[![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&layout=donut-vertical)](https://github.com/stats-organization/github-stats-extended) +``` + +## Pie Chart Language Card Layout + +You can use the `&layout=pie` option to change the card design. + +```md +[![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&layout=pie)](https://github.com/stats-organization/github-stats-extended) +``` + +## Hide Progress Bars + +You can use the `&hide_progress=true` option to hide the percentages and the progress bars (layout will be automatically set to `compact`). + +```md +![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&hide_progress=true) +``` + +## Change format of language's stats + +You can use the `&stats_format=bytes` option to display the stats in bytes instead of percentage. + +```md +![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&stats_format=bytes) +``` + +## Demo + +![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra) + +### Compact layout + +![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&layout=compact) + +### Donut Chart layout + +[![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&layout=donut)](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&layout=donut) + +### Donut Vertical Chart layout + +[![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&layout=donut-vertical)](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&layout=donut-vertical) + +### Pie Chart layout + +[![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&layout=pie)](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&layout=pie) + +### Hidden progress bars + +[![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&hide_progress=true)](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&hide_progress=true) + +### Display bytes instead of percentage + +[![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&stats_format=bytes)](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra&stats_format=bytes) diff --git a/apps/frontend/src/content/docs/docs/cards/wakatime.md b/apps/frontend/src/content/docs/docs/cards/wakatime.md new file mode 100644 index 0000000000000..6801aa6534c74 --- /dev/null +++ b/apps/frontend/src/content/docs/docs/cards/wakatime.md @@ -0,0 +1,51 @@ +--- +title: WakaTime Card +--- + +The WakaTime card shows how long you have spent coding in each language, taken from your WakaTime profile. + +:::caution[Warning] +Please be aware that we currently only show data from WakaTime profiles that are public. You therefore have to make sure that **BOTH** `Display code time publicly` and `Display languages, editors, os, categories publicly` are enabled. +::: + +:::caution[Warning] +In case you just created a new WakaTime account, then it might take up to 24 hours until your stats will become visible on the WakaTime stats card. +::: + +Change the `?username=` value to your [WakaTime](https://wakatime.com) username. + +```md +[![Alan's WakaTime stats](https://github-stats-extended.vercel.app/api/wakatime?username=alan)](https://wakatime.com/@alan) +``` + +## Options + +You can customize the appearance and behavior of the WakaTime stats card using the [common options](/frontend/docs/customization/common-options/) and exclusive options listed in the table below. + +| Name | Description | Type | Default value | +| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ---------------- | +| `hide` | Hides the languages specified from the card. | string (comma-separated values) | `null` | +| `hide_title` | Hides the title of your card. | boolean | `false` | +| `card_width` | Sets the card's width manually. | number | `495` | +| `line_height` | Sets the line height between text. | integer | `25` | +| `hide_progress` | Hides the progress bar and percentage. | boolean | `false` | +| `custom_title` | Sets a custom title for the card. | string | `WakaTime Stats` | +| `layout` | Switches between two available layouts `default` & `compact`. | enum | `default` | +| `langs_count` | Limits the number of languages on the card, defaults to all reported languages. | integer | `null` | +| `api_domain` | Sets a custom API domain for the card, e.g. to use services like [Hakatime](https://github.com/mujx/hakatime) or [Wakapi](https://github.com/muety/wakapi) | string | `wakatime.com` | +| `display_format` | Sets the WakaTime stats display format. Choose `time` to display time-based stats or `percent` to show percentages. | enum | `time` | +| `disable_animations` | Disables all animations in the card. | boolean | `false` | + +:::caution[Warning] +Custom title should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding) (i.e: `WakaTime Stats` should become `WakaTime%20Stats`). You can use [urlencoder.org](https://www.urlencoder.org/) to help you do this automatically. +::: + +## Demo + +![Alan's WakaTime stats](https://github-stats-extended.vercel.app/api/wakatime?username=alan) + +![Alan's WakaTime stats](https://github-stats-extended.vercel.app/api/wakatime?username=alan&card_width=315&hide_progress=true) + +### Compact layout + +![Alan's WakaTime stats](https://github-stats-extended.vercel.app/api/wakatime?username=alan&layout=compact) diff --git a/apps/frontend/src/content/docs/docs/customization/aligning-cards.md b/apps/frontend/src/content/docs/docs/customization/aligning-cards.md new file mode 100644 index 0000000000000..e8bbb1e8c11a7 --- /dev/null +++ b/apps/frontend/src/content/docs/docs/customization/aligning-cards.md @@ -0,0 +1,70 @@ +--- +title: Aligning Cards +--- + +By default, GitHub does not lay out the cards side by side. To do that, you can use such approaches: + +## Stats and top languages cards + + +```html + + + + + + +``` + +
+👀 Show example + + + +
+ +## Pinning repositories + +```html + + + + + + +``` + +
+👀 Show example + + + +
diff --git a/apps/frontend/src/content/docs/docs/customization/common-options.md b/apps/frontend/src/content/docs/docs/customization/common-options.md new file mode 100644 index 0000000000000..ed1014df484ef --- /dev/null +++ b/apps/frontend/src/content/docs/docs/customization/common-options.md @@ -0,0 +1,37 @@ +--- +title: Common Options +--- + +Every card accepts the options below, on top of the exclusive options listed on its own page. + +| Name | Description | Type | Default value | +| --------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ------------- | +| `title_color` | Card's title color. | string (hex color) | `2f80ed` | +| `text_color` | Body text color. | string (hex color) | `434d58` | +| `icon_color` | Icons color if available. | string (hex color) | `4c71f2` | +| `border_color` | Card's border color. Does not apply when `hide_border` is enabled. | string (hex color) | `e4e2e2` | +| `bg_color` | Card's background color. | string (hex color or a gradient in the form of _angle,start,end_) | `fffefe` | +| `hide_border` | Hides the card's border. | boolean | `false` | +| `theme` | Name of the theme, choose from [all available themes](/frontend/docs/customization/themes/). | enum | `default` | +| `cache_seconds` | Sets the cache header manually (min: 21600, max: 86400). | integer | `21600` | +| `locale` | Sets the language in the card, you can check full list of available locales [here](/frontend/docs/customization/locales/). | enum | `en` | +| `border_radius` | Corner rounding on the card. | number | `4.5` | + +:::caution[Warning] +This section applies only to our public deployment at [github-stats-extended.vercel.app](https://github-stats-extended.vercel.app/frontend). Self-hosted instances, the [GitHub Actions workflow](/frontend/docs/deploy/#github-action) and the npm package may behave differently. + +We use caching there to reduce the load on our servers (see [this discussion](https://github.com/anuraghazra/github-readme-stats/issues/1471#issuecomment-1271551425)). + +Cards are cached for a few hours to several days. +The maintainers set that duration by hand and adjust it as load changes. + +If you need your card data to update more frequently, you can [deploy your own instance](/frontend/docs/deploy/) and set the [`CACHE_SECONDS`](/frontend/docs/deploy/#available-environment-variables) environment variable to your preferred value. + +Alternatively, you can use the [GitHub Actions workflow](/frontend/docs/deploy/#github-action) to update your cards on a schedule. +::: + +## Gradient in bg\_color + +You can provide multiple comma-separated values in the bg\_color option to render a gradient with the following format: + + &bg_color=DEG,COLOR1,COLOR2,COLOR3...COLOR10 diff --git a/apps/frontend/src/content/docs/docs/customization/locales.md b/apps/frontend/src/content/docs/docs/customization/locales.md new file mode 100644 index 0000000000000..e05919febe326 --- /dev/null +++ b/apps/frontend/src/content/docs/docs/customization/locales.md @@ -0,0 +1,73 @@ +--- +title: Locales +--- + +Here is a list of all available locales: + + + +
+ +| Code | Locale | +| ------- | ---------------- | +| `ar` | Arabic | +| `az` | Azerbaijani | +| `be` | Belarusian | +| `bn` | Bengali | +| `bg` | Bulgarian | +| `my` | Burmese | +| `ca` | Catalan | +| `cn` | Chinese | +| `zh-tw` | Chinese (Taiwan) | +| `cs` | Czech | +| `nl` | Dutch | +| `en` | English | +| `fil` | Filipino | +| `fi` | Finnish | +| `fr` | French | +| `de` | German | + + + +| Code | Locale | +| ------- | --------------------- | +| `el` | Greek | +| `he` | Hebrew | +| `hi` | Hindi | +| `hu` | Hungarian | +| `id` | Indonesian | +| `it` | Italian | +| `ja` | Japanese | +| `kr` | Korean | +| `ml` | Malayalam | +| `np` | Nepali | +| `no` | Norwegian | +| `fa` | Persian (Farsi) | +| `pl` | Polish | +| `pt-br` | Portuguese (Brazil) | +| `pt-pt` | Portuguese (Portugal) | +| `ro` | Romanian | + + + +| Code | Locale | +| --------- | ------------------ | +| `ru` | Russian | +| `sa` | Sanskrit | +| `sr` | Serbian (Cyrillic) | +| `sr-latn` | Serbian (Latin) | +| `sk` | Slovak | +| `es` | Spanish | +| `sw` | Swahili | +| `se` | Swedish | +| `ta` | Tamil | +| `th` | Thai | +| `tr` | Turkish | +| `uk-ua` | Ukrainian | +| `ur` | Urdu | +| `uz` | Uzbek | +| `vi` | Vietnamese | + +
+ +If we don't support your language, please consider contributing! You can find more information about how to do it in our [contributing guidelines](https://github.com/stats-organization/github-stats-extended/blob/master/.github/CONTRIBUTING.md#translations-contribution). diff --git a/packages/core/src/themes/README.md b/apps/frontend/src/content/docs/docs/customization/themes.md similarity index 65% rename from packages/core/src/themes/README.md rename to apps/frontend/src/content/docs/docs/customization/themes.md index b58b7071e5baa..d589add623971 100644 --- a/packages/core/src/themes/README.md +++ b/apps/frontend/src/content/docs/docs/customization/themes.md @@ -1,4 +1,6 @@ -## Available Themes +--- +title: Available Themes +--- @@ -14,68 +16,74 @@ Use `?theme=THEME_NAME` parameter like so: > These themes work with all five of our cards: Stats Card, Repo Card, Gist Card, Top Languages Card, and WakaTime Card. -| | | | -| :------------------------------------------------------------: | :------------------------------------------------------------: | :------------------------------------------------------------------: | -| `default` ![default][default] | `transparent` ![transparent][transparent] | `shadow_red` ![shadow_red][shadow_red] | -| `shadow_green` ![shadow_green][shadow_green] | `shadow_blue` ![shadow_blue][shadow_blue] | `dark` ![dark][dark] | -| `radical` ![radical][radical] | `merko` ![merko][merko] | `gruvbox` ![gruvbox][gruvbox] | -| `gruvbox_light` ![gruvbox_light][gruvbox_light] | `tokyonight` ![tokyonight][tokyonight] | `onedark` ![onedark][onedark] | -| `cobalt` ![cobalt][cobalt] | `synthwave` ![synthwave][synthwave] | `highcontrast` ![highcontrast][highcontrast] | -| `dracula` ![dracula][dracula] | `prussian` ![prussian][prussian] | `monokai` ![monokai][monokai] | -| `vue` ![vue][vue] | `vue-dark` ![vue-dark][vue-dark] | `shades-of-purple` ![shades-of-purple][shades-of-purple] | -| `nightowl` ![nightowl][nightowl] | `buefy` ![buefy][buefy] | `blue-green` ![blue-green][blue-green] | -| `algolia` ![algolia][algolia] | `great-gatsby` ![great-gatsby][great-gatsby] | `darcula` ![darcula][darcula] | -| `bear` ![bear][bear] | `solarized-dark` ![solarized-dark][solarized-dark] | `solarized-light` ![solarized-light][solarized-light] | -| `chartreuse-dark` ![chartreuse-dark][chartreuse-dark] | `nord` ![nord][nord] | `gotham` ![gotham][gotham] | -| `material-palenight` ![material-palenight][material-palenight] | `graywhite` ![graywhite][graywhite] | `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark] | -| `ayu-mirage` ![ayu-mirage][ayu-mirage] | `midnight-purple` ![midnight-purple][midnight-purple] | `calm` ![calm][calm] | -| `flag-india` ![flag-india][flag-india] | `omni` ![omni][omni] | `react` ![react][react] | -| `jolly` ![jolly][jolly] | `maroongold` ![maroongold][maroongold] | `yeblu` ![yeblu][yeblu] | -| `blueberry` ![blueberry][blueberry] | `slateorange` ![slateorange][slateorange] | `kacho_ga` ![kacho_ga][kacho_ga] | -| `outrun` ![outrun][outrun] | `ocean_dark` ![ocean_dark][ocean_dark] | `city_lights` ![city_lights][city_lights] | -| `github_dark` ![github_dark][github_dark] | `github_dark_dimmed` ![github_dark_dimmed][github_dark_dimmed] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple] | -| `aura_dark` ![aura_dark][aura_dark] | `panda` ![panda][panda] | `noctis_minimus` ![noctis_minimus][noctis_minimus] | -| `cobalt2` ![cobalt2][cobalt2] | `swift` ![swift][swift] | `aura` ![aura][aura] | -| `apprentice` ![apprentice][apprentice] | `moltack` ![moltack][moltack] | `codeSTACKr` ![codeSTACKr][codeSTACKr] | -| `rose_pine` ![rose_pine][rose_pine] | `catppuccin_latte` ![catppuccin_latte][catppuccin_latte] | `catppuccin_mocha` ![catppuccin_mocha][catppuccin_mocha] | -| `date_night` ![date_night][date_night] | `one_dark_pro` ![one_dark_pro][one_dark_pro] | `rose` ![rose][rose] | -| `holi` ![holi][holi] | `neon` ![neon][neon] | `blue_navy` ![blue_navy][blue_navy] | -| `calm_pink` ![calm_pink][calm_pink] | `ambient_gradient` ![ambient_gradient][ambient_gradient] | | +| | | | +| :------------------------------------------------------------: | :------------------------------------------------------------------: | :------------------------------------------------------------: | +| `default` ![default][default] | `light_github` ![light_github][light_github] | `dark_github` ![dark_github][dark_github] | +| `transparent` ![transparent][transparent] | `shadow_red` ![shadow_red][shadow_red] | `shadow_green` ![shadow_green][shadow_green] | +| `shadow_blue` ![shadow_blue][shadow_blue] | `dark` ![dark][dark] | `radical` ![radical][radical] | +| `merko` ![merko][merko] | `gruvbox` ![gruvbox][gruvbox] | `gruvbox_light` ![gruvbox_light][gruvbox_light] | +| `tokyonight` ![tokyonight][tokyonight] | `onedark` ![onedark][onedark] | `cobalt` ![cobalt][cobalt] | +| `synthwave` ![synthwave][synthwave] | `highcontrast` ![highcontrast][highcontrast] | `dracula` ![dracula][dracula] | +| `prussian` ![prussian][prussian] | `monokai` ![monokai][monokai] | `vue` ![vue][vue] | +| `vue-dark` ![vue-dark][vue-dark] | `shades-of-purple` ![shades-of-purple][shades-of-purple] | `nightowl` ![nightowl][nightowl] | +| `buefy` ![buefy][buefy] | `blue-green` ![blue-green][blue-green] | `algolia` ![algolia][algolia] | +| `great-gatsby` ![great-gatsby][great-gatsby] | `darcula` ![darcula][darcula] | `bear` ![bear][bear] | +| `solarized-dark` ![solarized-dark][solarized-dark] | `solarized-light` ![solarized-light][solarized-light] | `chartreuse-dark` ![chartreuse-dark][chartreuse-dark] | +| `nord` ![nord][nord] | `gotham` ![gotham][gotham] | `material-palenight` ![material-palenight][material-palenight] | +| `graywhite` ![graywhite][graywhite] | `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark] | `ayu-mirage` ![ayu-mirage][ayu-mirage] | +| `midnight-purple` ![midnight-purple][midnight-purple] | `calm` ![calm][calm] | `flag-india` ![flag-india][flag-india] | +| `omni` ![omni][omni] | `react` ![react][react] | `jolly` ![jolly][jolly] | +| `maroongold` ![maroongold][maroongold] | `yeblu` ![yeblu][yeblu] | `blueberry` ![blueberry][blueberry] | +| `slateorange` ![slateorange][slateorange] | `kacho_ga` ![kacho_ga][kacho_ga] | `outrun` ![outrun][outrun] | +| `ocean_dark` ![ocean_dark][ocean_dark] | `city_lights` ![city_lights][city_lights] | `github_dark` ![github_dark][github_dark] | +| `github_dark_dimmed` ![github_dark_dimmed][github_dark_dimmed] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple] | `aura_dark` ![aura_dark][aura_dark] | +| `panda` ![panda][panda] | `noctis_minimus` ![noctis_minimus][noctis_minimus] | `cobalt2` ![cobalt2][cobalt2] | +| `swift` ![swift][swift] | `aura` ![aura][aura] | `apprentice` ![apprentice][apprentice] | +| `moltack` ![moltack][moltack] | `codeSTACKr` ![codeSTACKr][codeSTACKr] | `rose_pine` ![rose_pine][rose_pine] | +| `catppuccin_latte` ![catppuccin_latte][catppuccin_latte] | `catppuccin_mocha` ![catppuccin_mocha][catppuccin_mocha] | `date_night` ![date_night][date_night] | +| `one_dark_pro` ![one_dark_pro][one_dark_pro] | `rose` ![rose][rose] | `holi` ![holi][holi] | +| `neon` ![neon][neon] | `blue_navy` ![blue_navy][blue_navy] | `calm_pink` ![calm_pink][calm_pink] | +| `ambient_gradient` ![ambient_gradient][ambient_gradient] | | | ## Repo Card > These themes work with all five of our cards: Stats Card, Repo Card, Gist Card, Top Languages Card, and WakaTime Card. -| | | | -| :-----------------------------------------------------------------: | :-----------------------------------------------------------------: | :-----------------------------------------------------------------------: | -| `default_repocard` ![default_repocard][default_repocard_repo] | `transparent` ![transparent][transparent_repo] | `shadow_red` ![shadow_red][shadow_red_repo] | -| `shadow_green` ![shadow_green][shadow_green_repo] | `shadow_blue` ![shadow_blue][shadow_blue_repo] | `dark` ![dark][dark_repo] | -| `radical` ![radical][radical_repo] | `merko` ![merko][merko_repo] | `gruvbox` ![gruvbox][gruvbox_repo] | -| `gruvbox_light` ![gruvbox_light][gruvbox_light_repo] | `tokyonight` ![tokyonight][tokyonight_repo] | `onedark` ![onedark][onedark_repo] | -| `cobalt` ![cobalt][cobalt_repo] | `synthwave` ![synthwave][synthwave_repo] | `highcontrast` ![highcontrast][highcontrast_repo] | -| `dracula` ![dracula][dracula_repo] | `prussian` ![prussian][prussian_repo] | `monokai` ![monokai][monokai_repo] | -| `vue` ![vue][vue_repo] | `vue-dark` ![vue-dark][vue-dark_repo] | `shades-of-purple` ![shades-of-purple][shades-of-purple_repo] | -| `nightowl` ![nightowl][nightowl_repo] | `buefy` ![buefy][buefy_repo] | `blue-green` ![blue-green][blue-green_repo] | -| `algolia` ![algolia][algolia_repo] | `great-gatsby` ![great-gatsby][great-gatsby_repo] | `darcula` ![darcula][darcula_repo] | -| `bear` ![bear][bear_repo] | `solarized-dark` ![solarized-dark][solarized-dark_repo] | `solarized-light` ![solarized-light][solarized-light_repo] | -| `chartreuse-dark` ![chartreuse-dark][chartreuse-dark_repo] | `nord` ![nord][nord_repo] | `gotham` ![gotham][gotham_repo] | -| `material-palenight` ![material-palenight][material-palenight_repo] | `graywhite` ![graywhite][graywhite_repo] | `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark_repo] | -| `ayu-mirage` ![ayu-mirage][ayu-mirage_repo] | `midnight-purple` ![midnight-purple][midnight-purple_repo] | `calm` ![calm][calm_repo] | -| `flag-india` ![flag-india][flag-india_repo] | `omni` ![omni][omni_repo] | `react` ![react][react_repo] | -| `jolly` ![jolly][jolly_repo] | `maroongold` ![maroongold][maroongold_repo] | `yeblu` ![yeblu][yeblu_repo] | -| `blueberry` ![blueberry][blueberry_repo] | `slateorange` ![slateorange][slateorange_repo] | `kacho_ga` ![kacho_ga][kacho_ga_repo] | -| `outrun` ![outrun][outrun_repo] | `ocean_dark` ![ocean_dark][ocean_dark_repo] | `city_lights` ![city_lights][city_lights_repo] | -| `github_dark` ![github_dark][github_dark_repo] | `github_dark_dimmed` ![github_dark_dimmed][github_dark_dimmed_repo] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple_repo] | -| `aura_dark` ![aura_dark][aura_dark_repo] | `panda` ![panda][panda_repo] | `noctis_minimus` ![noctis_minimus][noctis_minimus_repo] | -| `cobalt2` ![cobalt2][cobalt2_repo] | `swift` ![swift][swift_repo] | `aura` ![aura][aura_repo] | -| `apprentice` ![apprentice][apprentice_repo] | `moltack` ![moltack][moltack_repo] | `codeSTACKr` ![codeSTACKr][codeSTACKr_repo] | -| `rose_pine` ![rose_pine][rose_pine_repo] | `catppuccin_latte` ![catppuccin_latte][catppuccin_latte_repo] | `catppuccin_mocha` ![catppuccin_mocha][catppuccin_mocha_repo] | -| `date_night` ![date_night][date_night_repo] | `one_dark_pro` ![one_dark_pro][one_dark_pro_repo] | `rose` ![rose][rose_repo] | -| `holi` ![holi][holi_repo] | `neon` ![neon][neon_repo] | `blue_navy` ![blue_navy][blue_navy_repo] | -| `calm_pink` ![calm_pink][calm_pink_repo] | `ambient_gradient` ![ambient_gradient][ambient_gradient_repo] | | +| | | | +| :-----------------------------------------------------------------: | :--------------------------------------------------------------------------: | :-----------------------------------------------------------------------: | +| `default_repocard` ![default_repocard][default_repocard_repo] | `light_github_repocard` ![light_github_repocard][light_github_repocard_repo] | `dark_github_repocard` ![dark_github_repocard][dark_github_repocard_repo] | +| `transparent` ![transparent][transparent_repo] | `shadow_red` ![shadow_red][shadow_red_repo] | `shadow_green` ![shadow_green][shadow_green_repo] | +| `shadow_blue` ![shadow_blue][shadow_blue_repo] | `dark` ![dark][dark_repo] | `radical` ![radical][radical_repo] | +| `merko` ![merko][merko_repo] | `gruvbox` ![gruvbox][gruvbox_repo] | `gruvbox_light` ![gruvbox_light][gruvbox_light_repo] | +| `tokyonight` ![tokyonight][tokyonight_repo] | `onedark` ![onedark][onedark_repo] | `cobalt` ![cobalt][cobalt_repo] | +| `synthwave` ![synthwave][synthwave_repo] | `highcontrast` ![highcontrast][highcontrast_repo] | `dracula` ![dracula][dracula_repo] | +| `prussian` ![prussian][prussian_repo] | `monokai` ![monokai][monokai_repo] | `vue` ![vue][vue_repo] | +| `vue-dark` ![vue-dark][vue-dark_repo] | `shades-of-purple` ![shades-of-purple][shades-of-purple_repo] | `nightowl` ![nightowl][nightowl_repo] | +| `buefy` ![buefy][buefy_repo] | `blue-green` ![blue-green][blue-green_repo] | `algolia` ![algolia][algolia_repo] | +| `great-gatsby` ![great-gatsby][great-gatsby_repo] | `darcula` ![darcula][darcula_repo] | `bear` ![bear][bear_repo] | +| `solarized-dark` ![solarized-dark][solarized-dark_repo] | `solarized-light` ![solarized-light][solarized-light_repo] | `chartreuse-dark` ![chartreuse-dark][chartreuse-dark_repo] | +| `nord` ![nord][nord_repo] | `gotham` ![gotham][gotham_repo] | `material-palenight` ![material-palenight][material-palenight_repo] | +| `graywhite` ![graywhite][graywhite_repo] | `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark_repo] | `ayu-mirage` ![ayu-mirage][ayu-mirage_repo] | +| `midnight-purple` ![midnight-purple][midnight-purple_repo] | `calm` ![calm][calm_repo] | `flag-india` ![flag-india][flag-india_repo] | +| `omni` ![omni][omni_repo] | `react` ![react][react_repo] | `jolly` ![jolly][jolly_repo] | +| `maroongold` ![maroongold][maroongold_repo] | `yeblu` ![yeblu][yeblu_repo] | `blueberry` ![blueberry][blueberry_repo] | +| `slateorange` ![slateorange][slateorange_repo] | `kacho_ga` ![kacho_ga][kacho_ga_repo] | `outrun` ![outrun][outrun_repo] | +| `ocean_dark` ![ocean_dark][ocean_dark_repo] | `city_lights` ![city_lights][city_lights_repo] | `github_dark` ![github_dark][github_dark_repo] | +| `github_dark_dimmed` ![github_dark_dimmed][github_dark_dimmed_repo] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple_repo] | `aura_dark` ![aura_dark][aura_dark_repo] | +| `panda` ![panda][panda_repo] | `noctis_minimus` ![noctis_minimus][noctis_minimus_repo] | `cobalt2` ![cobalt2][cobalt2_repo] | +| `swift` ![swift][swift_repo] | `aura` ![aura][aura_repo] | `apprentice` ![apprentice][apprentice_repo] | +| `moltack` ![moltack][moltack_repo] | `codeSTACKr` ![codeSTACKr][codeSTACKr_repo] | `rose_pine` ![rose_pine][rose_pine_repo] | +| `catppuccin_latte` ![catppuccin_latte][catppuccin_latte_repo] | `catppuccin_mocha` ![catppuccin_mocha][catppuccin_mocha_repo] | `date_night` ![date_night][date_night_repo] | +| `one_dark_pro` ![one_dark_pro][one_dark_pro_repo] | `rose` ![rose][rose_repo] | `holi` ![holi][holi_repo] | +| `neon` ![neon][neon_repo] | `blue_navy` ![blue_navy][blue_navy_repo] | `calm_pink` ![calm_pink][calm_pink_repo] | +| `ambient_gradient` ![ambient_gradient][ambient_gradient_repo] | | | [default]: https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=default [default_repocard]: https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=default_repocard +[light_github]: https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=light_github +[dark_github]: https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=dark_github +[light_github_repocard]: https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=light_github_repocard +[dark_github_repocard]: https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=dark_github_repocard [transparent]: https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=transparent [shadow_red]: https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=shadow_red [shadow_green]: https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=shadow_green @@ -151,6 +159,10 @@ Use `?theme=THEME_NAME` parameter like so: [ambient_gradient]: https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=ambient_gradient [default_repo]: https://github-stats-extended.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default [default_repocard_repo]: https://github-stats-extended.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default_repocard +[light_github_repo]: https://github-stats-extended.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=light_github +[dark_github_repo]: https://github-stats-extended.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=dark_github +[light_github_repocard_repo]: https://github-stats-extended.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=light_github_repocard +[dark_github_repocard_repo]: https://github-stats-extended.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=dark_github_repocard [transparent_repo]: https://github-stats-extended.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=transparent [shadow_red_repo]: https://github-stats-extended.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=shadow_red [shadow_green_repo]: https://github-stats-extended.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=shadow_green diff --git a/apps/frontend/src/content/docs/docs/customization/theming.md b/apps/frontend/src/content/docs/docs/customization/theming.md new file mode 100644 index 0000000000000..5b5bf519664a0 --- /dev/null +++ b/apps/frontend/src/content/docs/docs/customization/theming.md @@ -0,0 +1,105 @@ +--- +title: Theming +--- + +With inbuilt themes, you can customize the look of the card without doing any [manual customization](/frontend/docs/customization/common-options/). + +Use `&theme=THEME_NAME` parameter like so : + +```md +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&theme=radical) +``` + +## All inbuilt themes + +GitHub Stats Extended comes with several built-in themes (e.g. `dark`, `radical`, `merko`, `gruvbox`, `tokyonight`, `onedark`, `cobalt`, `synthwave`, `highcontrast`, `dracula`). + +GitHub Stats Extended Themes + +You can look at a preview for [all available themes](/frontend/docs/customization/themes/) or checkout the [theme config file](https://github.com/stats-organization/github-stats-extended/blob/master/packages/core/src/themes/index.ts). Please note that we paused the addition of new themes to decrease maintenance efforts; all pull requests related to new themes will be closed. + +## Responsive Card Theme + + + + Anurag's GitHub stats + + +Since GitHub will re-upload the cards and serve them from their [CDN](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-anonymized-urls), we can not infer the browser/GitHub theme on the server side. There are, however, four methods you can use to create dynamics themes on the client side. + +### Use GitHub's new media feature (recommended) + +You can use [GitHub's new media feature](https://github.blog/changelog/2022-05-19-specify-theme-context-for-images-in-markdown-beta/) in HTML to specify whether to display images for light or dark themes. This is done using the HTML `` element in combination with the `prefers-color-scheme` media feature. + + +```html + + + + + +``` + +
+👀 Show example + + + + + + + +
+ +### Use GitHub's theme context tag + +You can use [GitHub's theme context](https://github.blog/changelog/2021-11-24-specify-theme-context-for-images-in-markdown/) tags to switch the theme based on the user GitHub theme automatically. This is done by appending `#gh-dark-mode-only` or `#gh-light-mode-only` to the end of an image URL. This tag will define whether the image specified in the markdown is only shown to viewers using a light or a dark GitHub theme: + +```md +[![Anurag's GitHub stats-Dark](https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&theme=dark#gh-dark-mode-only)](https://github.com/stats-organization/github-stats-extended#gh-dark-mode-only) +[![Anurag's GitHub stats-Light](https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&theme=default#gh-light-mode-only)](https://github.com/stats-organization/github-stats-extended#gh-light-mode-only) +``` + +:::note +`#gh-dark-mode-only` and `#gh-light-mode-only` are honoured by GitHub when it renders +markdown, so there is no live example here: outside GitHub both images are shown. +::: + +### Use the transparent theme + +We have included a `transparent` theme that has a transparent background. This theme is optimized to look good on GitHub's dark and light default themes. You can enable this theme using the `&theme=transparent` parameter like so: + +```md +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&theme=transparent) +``` + +
+👀 Show example + +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&theme=transparent) + +
+ +### Add transparent alpha channel to a themes bg\_color + +You can use the `bg_color` parameter to make any of [the available themes](/frontend/docs/customization/themes/) transparent. This is done by setting the `bg_color` to a color with a transparent alpha channel (i.e. `bg_color=00000000`): + +```md +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&bg_color=00000000) +``` + +
+👀 Show example + +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&bg_color=00000000) + +
diff --git a/apps/frontend/src/content/docs/docs/demos.md b/apps/frontend/src/content/docs/docs/demos.md new file mode 100644 index 0000000000000..db91a86d299f5 --- /dev/null +++ b/apps/frontend/src/content/docs/docs/demos.md @@ -0,0 +1,81 @@ +--- +title: Demo Gallery +--- + +## Default + +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra) + +## Hiding specific stats + +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&hide=contribs,issues) + +## Showing additional stats + +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&show=reviews,discussions_started,discussions_answered,prs_merged,prs_merged_percentage,prs_commented,prs_reviewed,issues_commented) + +## Showing stats for a specific repository + +![Anurag's GitHub stats for anuraghazra/github-readme-stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&repo=anuraghazra/github-readme-stats&hide=prs,issues,stars,commits,contribs&show=prs_authored,prs_commented,prs_reviewed,issues_authored,issues_commented&hide_rank=true&custom_title=Anurag%27s%20Stats%20for%20github-readme-stats&card_width=370) + +## Showing stats for a specific organization + +![Anurag's GitHub stats for razorpay](https://github-stats-extended.vercel.app/api?username=anuraghazra&owner=razorpay&hide=prs,issues,stars,commits,contribs&show=prs_authored,prs_commented,prs_reviewed,issues_authored,issues_commented&hide_rank=true&custom_title=Anurag%27s%20Stats%20for%20razorpay&card_width=370) + +## Showing icons + +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&hide=issues&show_icons=true) + +## Shows GitHub logo instead rank level + +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&rank_icon=github) + +## Shows user rank percentile instead of rank level + +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&rank_icon=percentile) + +## Customize Border Color + +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&border_color=2e4058) + +## Include All Commits + +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&include_all_commits=true) + +## Themes + +Choose from any of the [default themes](/frontend/docs/customization/themes/) + +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&theme=radical) + +## Gradient + +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&bg_color=30,e96443,904e95&title_color=fff&text_color=fff) + +## Customizing stats card + +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api/?username=anuraghazra&show_icons=true&title_color=fff&icon_color=79ff97&text_color=9f9f9f&bg_color=151515) + +## Setting card locale + +![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api/?username=anuraghazra&locale=es) + +## Customizing repo card + +![Customized Card](https://github-stats-extended.vercel.app/api/pin?username=anuraghazra&repo=github-readme-stats&title_color=fff&icon_color=f9f9f9&text_color=9f9f9f&bg_color=151515) + +## Gist card + +![Gist Card](https://github-stats-extended.vercel.app/api/gist?id=bbfce31e0217a3689c8d961a356cb10d) + +## Customizing gist card + +![Gist Card](https://github-stats-extended.vercel.app/api/gist?id=bbfce31e0217a3689c8d961a356cb10d&theme=calm) + +## Top languages + +![Top Langs](https://github-stats-extended.vercel.app/api/top-langs/?username=anuraghazra) + +## WakaTime card + +![Alan's WakaTime stats](https://github-stats-extended.vercel.app/api/wakatime?username=alan) diff --git a/docs/deploy.md b/apps/frontend/src/content/docs/docs/deploy.md similarity index 93% rename from docs/deploy.md rename to apps/frontend/src/content/docs/docs/deploy.md index b3de00cbfa62c..e20de4a0f6aa2 100644 --- a/docs/deploy.md +++ b/apps/frontend/src/content/docs/docs/deploy.md @@ -1,4 +1,6 @@ -# Run It Yourself +--- +title: Run It Yourself +--- We cache generated cards for a few hours or days to avoid potential rate-limiting in the GitHub API or on Vercel. If you want to set your own cache duration or you want to include private contributions in your stats without granting our hosted version of GitHub-Stats-Extended access to your private contributions, you can run GitHub-Stats-Extended on your own. @@ -74,8 +76,9 @@ Selecting the right scopes for your token is important in case you want to displ #### Fine-grained token -> [!WARNING]\ -> This limits the scope of commits to public repositories only. +:::caution[Warning] +This limits the scope of commits to public repositories only. +::: - Go to [Account → Settings → Developer Settings → Personal access tokens → Fine-grained tokens](https://github.com/settings/personal-access-tokens). - Click on `Generate new token → Generate new token`. @@ -161,12 +164,12 @@ GitHub Stats Extended provides several environment variables that can be used to UPDATE_AFTER_HOURS - Sets the duration in hours after which the server proactively regenerates a previously requested card. Defaults to 11 hours. + Sets the duration in hours after which the server proactively regenerates a previously requested card. Defaults to 11 hours. Any int or float DELETE_AFTER_HOURS - Sets the duration in hours after which the server stops proactively regenerating a previously requested card if it hasn't been requested again in the meantime. Defaults to 8 days, i.e. 192 hours. + Sets the duration in hours after which the server stops proactively regenerating a previously requested card if it hasn't been requested again in the meantime. Defaults to 8 days, i.e. 192 hours. Any int or float @@ -194,8 +197,9 @@ GitHub Stats Extended provides several environment variables that can be used to See [the Vercel documentation](https://vercel.com/docs/concepts/projects/environment-variables) on adding these environment variables to your Vercel instance. -> [!WARNING] -> Please remember to redeploy your instance after making any changes to the environment variables so that the updates take effect. The changes will not be applied to the previous deployments. +:::caution[Warning] +Please remember to redeploy your instance after making any changes to the environment variables so that the updates take effect. The changes will not be applied to the previous deployments. +::: ### Keep your fork up to date diff --git a/docs/fork.md b/apps/frontend/src/content/docs/docs/fork.md similarity index 96% rename from docs/fork.md rename to apps/frontend/src/content/docs/docs/fork.md index 145a3f2e929c3..fac00b26154fa 100644 --- a/docs/fork.md +++ b/apps/frontend/src/content/docs/docs/fork.md @@ -1,4 +1,6 @@ -# Fork Information +--- +title: Fork Information +--- This project is an actively maintained fork and extension of [github-readme-stats](https://github.com/anuraghazra/github-readme-stats). @@ -18,7 +20,7 @@ The frontend is based on [GitHub Trends](https://github.com/avgupta456/github-tr To include stars from repos which are not owned by you, but where you are a collaborator or organization member, add `&role=OWNER,ORGANIZATION_MEMBER,COLLABORATOR` to your stats card url. To include such repos in your language stats, you can also add the same parameter to your top languages card url. -See [here](advanced_documentation.md#filtering-by-repository-and-owner) for full feature documentation. +See [here](/frontend/docs/cards/stats/#filtering-by-repository-and-owner) for full feature documentation. The resolution of this most requested feature in github-readme-stats was [originally implemented](https://github.com/anuraghazra/github-readme-stats/issues/1#issuecomment-855681098) by [@developStorm](https://github.com/developStorm). @@ -46,7 +48,7 @@ GitHub-Stats-Extended adds the ability to show contribution stats for specific r Especially for regular contributors in open source projects it might make sense to display an overview of their own contributions to these projects on their GitHub profile. -See [here](advanced_documentation.md#filtering-by-repository-and-owner) for full feature documentation. +See [here](/frontend/docs/cards/stats/#filtering-by-repository-and-owner) for full feature documentation. --- diff --git a/apps/frontend/src/content/docs/docs/frontend-screenshot.png b/apps/frontend/src/content/docs/docs/frontend-screenshot.png new file mode 100644 index 0000000000000..4a0b2dbae6efe Binary files /dev/null and b/apps/frontend/src/content/docs/docs/frontend-screenshot.png differ diff --git a/apps/frontend/src/content/docs/docs/index.md b/apps/frontend/src/content/docs/docs/index.md new file mode 100644 index 0000000000000..6ccd292c9d36a --- /dev/null +++ b/apps/frontend/src/content/docs/docs/index.md @@ -0,0 +1,100 @@ +--- +title: GitHub Stats Extended +--- + +Dynamically generate GitHub stats for your READMEs. + +GitHub-Stats-Extended is the [extended, actively maintained successor](/frontend/docs/fork/) of [github-readme-stats](https://github.com/anuraghazra/github-readme-stats). It generates stats cards about your GitHub contributions, your top languages and more, which you can [customize](/frontend/docs/customization/common-options/) through a large set of parameters. + + + + Anurag's GitHub stats + + +## Quick Start + +Copy and paste this into your markdown, then change the `?username=` value to your GitHub username: + +```md +[![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra)](https://github.com/stats-organization/github-stats-extended) +``` + +As a more comfortable alternative, use the [card wizard](https://github-stats-extended.vercel.app/frontend) to configure your card visually, then copy the generated markdown into your [GitHub profile README](https://docs.github.com/en/account-and-profile/how-tos/profile-customization/managing-your-profile-readme#adding-a-profile-readme). + +## Card Types + +### [Stats Card](/frontend/docs/cards/stats/) + + + + Anurag's GitHub stats + + +### [Top Languages Card](/frontend/docs/cards/top-languages/) + + + + Top Langs + + +### [WakaTime Card](/frontend/docs/cards/wakatime/) + + + + Alan's WakaTime stats + + +### [Repo Card](/frontend/docs/cards/repo-pin/) + + + + Readme Card + + +### [Gist Card](/frontend/docs/cards/gist-pin/) + + + + Gist Card + + +## Migration from github-readme-stats + +To migrate from [github-readme-stats](https://github.com/anuraghazra/github-readme-stats) you only need to change the domain from `github-readme-stats.vercel.app` to `github-stats-extended.vercel.app`: + +```diff +- https://github-readme-stats.vercel.app/api?username=octocat&theme=radical ++ https://github-stats-extended.vercel.app/api?username=octocat&theme=radical +``` + +GitHub-Stats-Extended aims to be fully compatible with github-readme-stats. For details see [Compatibility Notes](/frontend/docs/fork/#compatibility-notes). + +## Where to next + +- [Cards](/frontend/docs/cards/stats/) — the options each card accepts. +- [Customization](/frontend/docs/customization/common-options/) — options every card shares, plus theming and locales. +- [Available Themes](/frontend/docs/customization/themes/) — the built-in themes, rendered as live samples. +- [Run It Yourself](/frontend/docs/deploy/) — GitHub Actions or a self-hosted Vercel deployment. +- [Fork Information](/frontend/docs/fork/) — what this project adds on top of github-readme-stats. + +## Acknowledgements + +This project is based on [github-readme-stats](https://github.com/anuraghazra/github-readme-stats). The card wizard is based on [GitHub Trends](https://github.com/avgupta456/github-trends). Big thanks to [@anuraghazra](https://github.com/anuraghazra), [@avgupta456](https://github.com/avgupta456), [@rickstaa](https://github.com/rickstaa), [@qwerty541](https://github.com/qwerty541) and everyone else who worked on these projects! ❤️ diff --git a/apps/frontend/src/index.css b/apps/frontend/src/index.css index 54b223dd31bd0..14ae121a249c8 100644 --- a/apps/frontend/src/index.css +++ b/apps/frontend/src/index.css @@ -1,3 +1,4 @@ +@import "./styles/layer-order.css"; @import "tailwindcss"; @plugin "daisyui" { themes: @@ -13,11 +14,13 @@ Override background color in dark mode with GitHub's dark mode background color. name: "light"; --color-primary: #2f80ed; --color-error: var(--color-red-700); + --color-error-content: #ffffff; } @plugin "daisyui/theme" { name: "dark"; --color-primary: #2f80ed; - --color-error: var(--color-red-900); + --color-error: var(--color-red-400); + --color-error-content: #0d1117; --color-base-100: #0d1117; } @@ -28,82 +31,85 @@ Override background color in dark mode with GitHub's dark mode background color. --transition-surface: 300ms; } -body { - margin: 0; - font-family: "Segoe UI", Ubuntu, Sans-Serif; +/* + * Everything here is nested so it reaches only the wizard's own nodes: + * the page background, font and reset belong to the site, which shares this page. + */ +/* Scoping only: no background, so overlays mounted on `body` stay see-through. */ +.wizard { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - background-color: var(--color-base-100); color: var(--color-base-content); -} -button { - cursor: pointer; -} + button { + cursor: pointer; + } -/* Smoothly animate color changes when switching themes / on hover. */ -*, -*::before, -*::after { - transition-property: background-color, border-color, color, fill, stroke; - /* - * An ease-out curve: animations start at full speed so quick interactions - * like button hovers feel responsive instead of laggy. - */ - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - transition-duration: var(--transition-fast); -} + /* Smoothly animate color changes when switching themes / on hover. */ + *, + *::before, + *::after { + transition-property: background-color, border-color, color, fill, stroke; + /* + * An ease-out curve: animations start at full speed so quick interactions + * like button hovers feel responsive instead of laggy. + */ + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: var(--transition-fast); + } -/* - * Large surface backgrounds fade more slowly so a theme switch reads as a - * deliberate fade rather than an instant flip. Durations reference the custom - * properties above so the reduced-motion override below zeroes them out - * regardless of selector specificity. - */ -body, -.bg-base-100, -.bg-base-200, -.bg-base-300 { - transition-duration: var(--transition-surface); -} + /* + * Large surface backgrounds fade more slowly so a theme switch reads as a + * deliberate fade rather than an instant flip. Durations reference the custom + * properties above so the reduced-motion override below zeroes them out + * regardless of selector specificity. + */ + &, + & .bg-base-100, + & .bg-base-200, + & .bg-base-300 { + transition-duration: var(--transition-surface); + } -/* - * DaisyUI's `.btn` ships its own 200ms transition which felt slow. The global - * rule above is unlayered, so it would otherwise clobber the button's property - * list and timing function. Restore both here (so fill, text and border animate - * together on outline buttons) and only speed up the duration. - */ -.btn { - transition-property: color, background-color, border-color, box-shadow; - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - transition-duration: var(--transition-fast); -} + /* + * DaisyUI's `.btn` ships its own 200ms transition which felt slow. The rule + * above is unlayered, so it would otherwise clobber the button's property list + * and timing function. Restore both here (so fill, text and border animate + * together on outline buttons) and only speed up the duration. + */ + .btn { + transition-property: color, background-color, border-color, box-shadow; + transition-timing-function: cubic-bezier(0, 0, 0.2, 1); + transition-duration: var(--transition-fast); + } -/* - * DaisyUI's default hover is very subtle; make it noticeable. Primary always darkens, - * while soft lightens in dark mode and darkens in light mode, so the change shows. - */ -.btn-primary:hover { - background-color: color-mix(in oklab, var(--color-primary), #000 30%); -} -.btn-soft { - background-color: color-mix(in oklab, var(--color-base-100), #888 35%); -} -.btn-soft:hover { - background-color: color-mix(in oklab, var(--color-base-100), #888 60%); -} -.btn-error { - color: var(--color-neutral-content); -} -.btn-error:hover { - color: var(--color-neutral-content); - background-color: color-mix(in oklab, var(--color-error), #f00 40%); -} + /* + * DaisyUI's default hover is very subtle; make it noticeable. Primary always darkens, + * while soft lightens in dark mode and darkens in light mode, so the change shows. + */ + .btn-primary:hover { + background-color: color-mix(in oklab, var(--color-primary), #000 30%); + } + .btn-soft { + background-color: color-mix(in oklab, var(--color-base-100), #888 35%); + } + .btn-soft:hover { + background-color: color-mix(in oklab, var(--color-base-100), #888 60%); + } + .btn-error { + color: var(--color-error-content); + } + .btn-error:hover { + color: var(--color-error-content); + background-color: color-mix(in oklab, var(--color-error), #f00 40%); + } -input:focus, -select:focus { - outline: 0; - border: var(--color-primary) solid 1px; + /* Unscoped, this strips the focus ring from the site's search box. */ + input:focus, + select:focus { + outline: 0; + border: var(--color-primary) solid 1px; + } } /* Respect users who prefer reduced motion. */ diff --git a/apps/frontend/src/index.tsx b/apps/frontend/src/index.tsx deleted file mode 100644 index f2a0f657e5cd4..0000000000000 --- a/apps/frontend/src/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import "./axios-override"; - -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; -import { Provider } from "react-redux"; - -import { AppTrends } from "./pages/App/AppTrends"; -import { store } from "./redux/store"; - -import "./index.css"; - -const root = createRoot(document.getElementById("root") as HTMLElement); - -root.render( - - - - - , -); diff --git a/apps/frontend/src/modules.d.ts b/apps/frontend/src/modules.d.ts index 8e5a1b4b5d2fe..bb96604b64938 100644 --- a/apps/frontend/src/modules.d.ts +++ b/apps/frontend/src/modules.d.ts @@ -1,10 +1,3 @@ -declare module "*.css"; - -declare module "*.png" { - const img: string; - export default img; -} - // This package doesn't have a @types counter part declare module "save-svg-as-png" { const saveSvgAsPng: ( diff --git a/apps/frontend/src/pages/App/Header.tsx b/apps/frontend/src/pages/App/Header.tsx deleted file mode 100644 index 7701ef1c1109d..0000000000000 --- a/apps/frontend/src/pages/App/Header.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import type { JSX } from "react"; -import { FaGithub as GithubIcon } from "react-icons/fa"; - -import appIcon from "../../assets/appLogo64.png"; -import { ThemePicker } from "../../components/Generic/ThemePicker"; -import { ProgressBar } from "../../components/Home/Progress"; -import { STAGE_LABELS } from "../../models/Stage"; -import type { StageIndex } from "../../models/Stage"; - -interface HeaderProps { - currStageIndex: StageIndex; - onStageIndexChange: (stageIndex: StageIndex) => void; -} - -const items = STAGE_LABELS.map((it) => it.shortTitle); - -export function Header({ - currStageIndex, - onStageIndexChange, -}: HeaderProps): JSX.Element { - return ( - <> -
-
- {/* Logo */} - - logo - GitHub Stats Extended - - {/* Star on GitHub + theme toggle */} - -
-
- { - onStageIndexChange(itemIndex as StageIndex); - }} - /> - - ); -} diff --git a/apps/frontend/src/pages/index.astro b/apps/frontend/src/pages/index.astro new file mode 100644 index 0000000000000..ef925b121e311 --- /dev/null +++ b/apps/frontend/src/pages/index.astro @@ -0,0 +1,39 @@ +--- +import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro"; + +import { Wizard } from "../wizard/Wizard"; + +// Tailwind and daisyUI, loaded only here: the docs pages are styled by Starlight. +import "../index.css"; +--- + +{/* `splash` drops the sidebar and table of contents, leaving the wizard the full width. */} + + {/* `not-content` opts out of the prose spacing Starlight applies to markdown. */} +
+
+ +{/* Only this page loads these rules, so the docs keep Starlight's reading column. */} + diff --git a/apps/frontend/src/redux/selectors/themeSelectors.ts b/apps/frontend/src/redux/selectors/themeSelectors.ts deleted file mode 100644 index ec18dd59e36e1..0000000000000 --- a/apps/frontend/src/redux/selectors/themeSelectors.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useCallback } from "react"; -import { useDispatch, useSelector } from "react-redux"; - -import { isDarkTheme, setTheme as setThemeAction } from "../slices/theme"; -import type { StoreState } from "../store"; - -/** - * Reads the active DaisyUI theme from the Redux store and exposes a setter - * that persists the choice and updates the `data-theme` attribute. - */ -export function useTheme(): { - theme: string; - isDark: boolean; - setTheme: (theme: string) => void; -} { - const dispatch = useDispatch(); - const theme = useSelector((state: StoreState) => state.theme.theme); - - const setTheme = useCallback( - (next: string) => { - dispatch(setThemeAction(next)); - }, - [dispatch], - ); - - return { theme, isDark: isDarkTheme(theme), setTheme }; -} diff --git a/apps/frontend/src/redux/slices/theme.ts b/apps/frontend/src/redux/slices/theme.ts deleted file mode 100644 index 4f78d7a93ee1e..0000000000000 --- a/apps/frontend/src/redux/slices/theme.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; -import type { PayloadAction } from "@reduxjs/toolkit"; - -interface ThemeOption { - /** DaisyUI theme name, used as the `data-theme` value. */ - name: string; - label: string; - /** Whether the theme has a dark background (drives toast styling, etc.). */ - isDark: boolean; -} - -/** - * Themes exposed in the theme picker. - * Keep in sync with the `themes:` list in `index.css`. - */ -export const THEMES: ReadonlyArray = [ - { name: "light", label: "Light", isDark: false }, - { name: "dark", label: "Dark", isDark: true }, -]; - -const DEFAULT_LIGHT_THEME = "light"; -const DEFAULT_DARK_THEME = "dark"; - -function isValidTheme(name: string | null): name is string { - return !!name && THEMES.some((theme) => theme.name === name); -} - -export function isDarkTheme(name: string): boolean { - return THEMES.find((theme) => theme.name === name)?.isDark ?? false; -} - -/** - * @public - * Exported so the inferred store type can name it (see the note in `user.ts`). - */ -export interface ThemeState { - theme: string; -} - -function getInitialTheme(): string { - const stored = localStorage.getItem("theme"); - if (isValidTheme(stored)) { - return stored; - } - return window.matchMedia("(prefers-color-scheme: dark)").matches - ? DEFAULT_DARK_THEME - : DEFAULT_LIGHT_THEME; -} - -const initialState: ThemeState = { - theme: getInitialTheme(), -}; - -const themeSlice = createSlice({ - name: "theme", - initialState, - reducers: { - setTheme: (state, action: PayloadAction) => { - const theme = action.payload; - localStorage.setItem("theme", theme); - document.documentElement.setAttribute("data-theme", theme); - state.theme = theme; - }, - }, -}); - -export const { setTheme } = themeSlice.actions; - -export default themeSlice.reducer; diff --git a/apps/frontend/src/redux/store.ts b/apps/frontend/src/redux/store.ts index 97fc03a06cd7b..73870b8e2c9d3 100644 --- a/apps/frontend/src/redux/store.ts +++ b/apps/frontend/src/redux/store.ts @@ -3,13 +3,11 @@ import { configureStore } from "@reduxjs/toolkit"; import { USE_LOGGER } from "../constants"; import { loggerMiddleware } from "./logger"; -import theme from "./slices/theme"; import user from "./slices/user"; const store = configureStore({ reducer: { user, - theme, }, middleware: (getDefaultMiddleware) => { const middleware = getDefaultMiddleware(); diff --git a/apps/frontend/src/shared/LinkExternal.tsx b/apps/frontend/src/shared/LinkExternal.tsx new file mode 100644 index 0000000000000..75d250091a41c --- /dev/null +++ b/apps/frontend/src/shared/LinkExternal.tsx @@ -0,0 +1,37 @@ +import { clsx } from "clsx"; +import type { JSX, ReactNode } from "react"; +import { FaExternalLinkAlt as ExternalIcon } from "react-icons/fa"; + +interface LinkExternalProps { + href: string; + children: ReactNode; + className?: string; + /** + * Set to `false` for links that already carry their own mark, such as the + * branded GitHub button, where a second icon would only add noise. + */ + showIcon?: boolean; +} + +/** + * A link that opens in a new tab, with the `rel` that `target="_blank"` + * requires, and a trailing icon so the new tab is announced before the click. + */ +export function LinkExternal({ + href, + children, + className, + showIcon = true, +}: LinkExternalProps): JSX.Element { + return ( + + {children} + {showIcon && } + + ); +} diff --git a/apps/frontend/src/styles/layer-order.css b/apps/frontend/src/styles/layer-order.css new file mode 100644 index 0000000000000..8b1fbc9d67e91 --- /dev/null +++ b/apps/frontend/src/styles/layer-order.css @@ -0,0 +1,8 @@ +/* + * Tailwind's utilities must outrank Starlight's reset, whose `margin: 0` otherwise beats every margin utility in the wizard. + * Preflight stays ahead so the chrome keeps its styling. + * Imported by both entry sheets: + * only the first `@layer` wins, and dev loads them in the opposite order to a build. + */ +@layer theme, base, components, starlight.base, starlight.reset, starlight.core, + starlight.content, starlight.components, starlight.utils, utilities; diff --git a/apps/frontend/src/styles/starlight-theme.css b/apps/frontend/src/styles/starlight-theme.css new file mode 100644 index 0000000000000..ea0852ae98102 --- /dev/null +++ b/apps/frontend/src/styles/starlight-theme.css @@ -0,0 +1,41 @@ +@import "./layer-order.css"; + +/* + * Gives the docs the wizard's accent and GitHub's dark background, so the two + * halves read as one site. + */ +:root { + --app-primary: #2f80ed; + + --sl-font: "Segoe UI", Ubuntu, Sans-Serif; + --sl-color-accent: var(--app-primary); + --sl-color-accent-low: color-mix(in oklab, var(--app-primary), #000 45%); + --sl-color-accent-high: color-mix(in oklab, var(--app-primary), #fff 45%); + --sl-color-text-accent: var(--app-primary); +} + +:root[data-theme="dark"] { + --sl-color-bg: #0d1117; + --sl-color-bg-sidebar: #0d1117; +} + +/* Prose styles render images as blocks, so the row is explicit. */ +.card-row { + display: flex; + flex-wrap: nowrap; + gap: 0.5rem; + align-items: stretch; +} + +.card-row a { + flex: 1 1 0; + min-width: 0; + display: flex; +} + +.card-row img { + width: 100%; + height: 100%; + object-fit: contain; + object-position: center; +} diff --git a/apps/frontend/src/pages/Home/Home.tsx b/apps/frontend/src/wizard/Home/Home.tsx similarity index 93% rename from apps/frontend/src/pages/Home/Home.tsx rename to apps/frontend/src/wizard/Home/Home.tsx index 52788559be50a..14a84c7d009bd 100644 --- a/apps/frontend/src/pages/Home/Home.tsx +++ b/apps/frontend/src/wizard/Home/Home.tsx @@ -5,24 +5,25 @@ import { useDispatch } from "react-redux"; import { BounceLoader } from "react-spinners"; import { v4 as uuidv4 } from "uuid"; -import { authenticate } from "../../api/user"; -import { DEFAULT_OPTION as LANGUAGES_DEFAULT_LAYOUT } from "../../components/Home/LanguagesLayoutSection"; -import { DEFAULT_OPTION as WAKATIME_DEFAULT_LAYOUT } from "../../components/Home/WakatimeLayoutSection"; import { DEMO_USER } from "../../constants"; -import { - CATEGORY_BY_CARD_TYPE, - CardCategory, - CardType, -} from "../../models/CardType"; -import { STAGE_LABELS } from "../../models/Stage"; -import type { StageIndex } from "../../models/Stage"; -import { useTheme } from "../../redux/selectors/themeSelectors"; import { useIsAuthenticated, usePrivateAccess, useUserId, } from "../../redux/selectors/userSelectors"; import { login } from "../../redux/slices/user"; +import { LinkExternal } from "../../shared/LinkExternal"; +import { authenticate } from "../api/user"; +import { DEFAULT_OPTION as LANGUAGES_DEFAULT_LAYOUT } from "../components/Home/LanguagesLayoutSection"; +import { DEFAULT_OPTION as WAKATIME_DEFAULT_LAYOUT } from "../components/Home/WakatimeLayoutSection"; +import { + CATEGORY_BY_CARD_TYPE, + CardCategory, + CardType, +} from "../models/CardType"; +import { STAGE_LABELS } from "../models/Stage"; +import type { StageIndex } from "../models/Stage"; +import { useIsDarkTheme } from "../useIsDarkTheme"; import { buildCardUrl } from "./buildCardUrl"; import { getDefaultCardOptions } from "./cardOptions"; @@ -77,7 +78,7 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element { setCardOptions((prev) => ({ ...prev, selectedUserId: userId })); } - const { isDark } = useTheme(); + const isDark = useIsDarkTheme(); const cardCategory = CATEGORY_BY_CARD_TYPE[selectedCard]; const [theme, setTheme] = useState(() => getDefaultTheme(isDark, cardCategory), @@ -204,13 +205,12 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {

You are logged in as{" "} - {userId} - + .

diff --git a/apps/frontend/src/pages/Home/buildCardUrl.test.ts b/apps/frontend/src/wizard/Home/buildCardUrl.test.ts similarity index 90% rename from apps/frontend/src/pages/Home/buildCardUrl.test.ts rename to apps/frontend/src/wizard/Home/buildCardUrl.test.ts index 9e3b3820868b6..bb3b1c4b5d82c 100644 --- a/apps/frontend/src/pages/Home/buildCardUrl.test.ts +++ b/apps/frontend/src/wizard/Home/buildCardUrl.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from "vitest"; -import { DEFAULT_OPTION as LANGUAGES_DEFAULT_LAYOUT } from "../../components/Home/LanguagesLayoutSection"; -import { DEFAULT_OPTION as STATS_DEFAULT_RANK } from "../../components/Home/StatsRankSection"; -import { DEFAULT_OPTION as WAKATIME_DEFAULT_LAYOUT } from "../../components/Home/WakatimeLayoutSection"; -import { CardType } from "../../models/CardType"; +import { DEFAULT_OPTION as LANGUAGES_DEFAULT_LAYOUT } from "../components/Home/LanguagesLayoutSection"; +import { DEFAULT_OPTION as STATS_DEFAULT_RANK } from "../components/Home/StatsRankSection"; +import { DEFAULT_OPTION as WAKATIME_DEFAULT_LAYOUT } from "../components/Home/WakatimeLayoutSection"; +import { CardType } from "../models/CardType"; import { buildCardUrl } from "./buildCardUrl"; import type { CardOptions } from "./cardOptions"; diff --git a/apps/frontend/src/pages/Home/buildCardUrl.ts b/apps/frontend/src/wizard/Home/buildCardUrl.ts similarity index 90% rename from apps/frontend/src/pages/Home/buildCardUrl.ts rename to apps/frontend/src/wizard/Home/buildCardUrl.ts index 28081a0c2564a..ec4c8f5e3220f 100644 --- a/apps/frontend/src/pages/Home/buildCardUrl.ts +++ b/apps/frontend/src/wizard/Home/buildCardUrl.ts @@ -1,9 +1,9 @@ -import { DEFAULT_OPTION as LANGUAGES_DEFAULT_LAYOUT } from "../../components/Home/LanguagesLayoutSection"; -import { DEFAULT_OPTION as STATS_DEFAULT_RANK } from "../../components/Home/StatsRankSection"; -import { DEFAULT_OPTION as WAKATIME_DEFAULT_LAYOUT } from "../../components/Home/WakatimeLayoutSection"; -import { CardType } from "../../models/CardType"; -import { cardUrl } from "../../models/CardUrl"; -import type { CardUrlBuilder } from "../../models/CardUrl"; +import { DEFAULT_OPTION as LANGUAGES_DEFAULT_LAYOUT } from "../components/Home/LanguagesLayoutSection"; +import { DEFAULT_OPTION as STATS_DEFAULT_RANK } from "../components/Home/StatsRankSection"; +import { DEFAULT_OPTION as WAKATIME_DEFAULT_LAYOUT } from "../components/Home/WakatimeLayoutSection"; +import { CardType } from "../models/CardType"; +import { cardUrl } from "../models/CardUrl"; +import type { CardUrlBuilder } from "../models/CardUrl"; import type { CardOptions } from "./cardOptions"; diff --git a/apps/frontend/src/pages/Home/cardOptions.ts b/apps/frontend/src/wizard/Home/cardOptions.ts similarity index 78% rename from apps/frontend/src/pages/Home/cardOptions.ts rename to apps/frontend/src/wizard/Home/cardOptions.ts index 7e9a3650a60e1..461e997502169 100644 --- a/apps/frontend/src/pages/Home/cardOptions.ts +++ b/apps/frontend/src/wizard/Home/cardOptions.ts @@ -1,8 +1,8 @@ -import type { SelectOption } from "../../components/Generic/Select"; -import { DEFAULT_OPTION as LANGUAGES_DEFAULT_LAYOUT } from "../../components/Home/LanguagesLayoutSection"; -import { DEFAULT_OPTION as STATS_DEFAULT_RANK } from "../../components/Home/StatsRankSection"; -import { DEFAULT_OPTION as WAKATIME_DEFAULT_LAYOUT } from "../../components/Home/WakatimeLayoutSection"; import { DEMO_GIST, DEMO_REPO, DEMO_WAKATIME_USER } from "../../constants"; +import type { SelectOption } from "../components/Generic/Select"; +import { DEFAULT_OPTION as LANGUAGES_DEFAULT_LAYOUT } from "../components/Home/LanguagesLayoutSection"; +import { DEFAULT_OPTION as STATS_DEFAULT_RANK } from "../components/Home/StatsRankSection"; +import { DEFAULT_OPTION as WAKATIME_DEFAULT_LAYOUT } from "../components/Home/WakatimeLayoutSection"; /** * All user-tunable card parameters collected during the customize stage. diff --git a/apps/frontend/src/pages/Home/stages/Customize.tsx b/apps/frontend/src/wizard/Home/stages/Customize.tsx similarity index 92% rename from apps/frontend/src/pages/Home/stages/Customize.tsx rename to apps/frontend/src/wizard/Home/stages/Customize.tsx index 3336aaeaf7ef3..9ab3c858cb8f6 100644 --- a/apps/frontend/src/pages/Home/stages/Customize.tsx +++ b/apps/frontend/src/wizard/Home/stages/Customize.tsx @@ -1,26 +1,28 @@ import type { JSX } from "react"; -import { CardImage } from "../../../components/Card/CardImage"; -import { CheckboxSection } from "../../../components/Home/CheckboxSection"; -import { LanguagesLayoutSection } from "../../../components/Home/LanguagesLayoutSection"; -import { NumericSection } from "../../../components/Home/NumericSection"; -import { StatsRankSection } from "../../../components/Home/StatsRankSection"; -import { TextSection } from "../../../components/Home/TextSection"; -import { WakatimeLayoutSection } from "../../../components/Home/WakatimeLayoutSection"; import { DEMO_GIST, DEMO_REPO, DEMO_USER, DEMO_WAKATIME_USER, } from "../../../constants"; +import { useIsAuthenticated } from "../../../redux/selectors/userSelectors"; +import { LinkExternal } from "../../../shared/LinkExternal"; +import { CardImage } from "../../components/Card/CardImage"; +import { CheckboxSection } from "../../components/Home/CheckboxSection"; +import { LanguagesLayoutSection } from "../../components/Home/LanguagesLayoutSection"; +import { NumericSection } from "../../components/Home/NumericSection"; +import { StatsRankSection } from "../../components/Home/StatsRankSection"; +import { TextSection } from "../../components/Home/TextSection"; +import { WakatimeLayoutSection } from "../../components/Home/WakatimeLayoutSection"; +import { docsUrl } from "../../docsUrl"; import { CATEGORY_BY_CARD_TYPE, CardCategory, CardType, -} from "../../../models/CardType"; -import type { CardUrlBuilder } from "../../../models/CardUrl"; -import type { StageIndex } from "../../../models/Stage"; -import { useIsAuthenticated } from "../../../redux/selectors/userSelectors"; +} from "../../models/CardType"; +import type { CardUrlBuilder } from "../../models/CardUrl"; +import type { StageIndex } from "../../models/Stage"; import type { CardOptions } from "../cardOptions"; /** @@ -213,13 +215,12 @@ export function CustomizeStage({ description={ <> Set your{" "} - WakaTime - {" "} + {" "} username to fetch your stats. } @@ -403,13 +404,12 @@ export function CustomizeStage({ )}

For more customization options check the{" "} - customization documentation - {" "} + {" "} after you copied your card URL in step 5.
diff --git a/apps/frontend/src/pages/Home/stages/Display.tsx b/apps/frontend/src/wizard/Home/stages/Display.tsx similarity index 88% rename from apps/frontend/src/pages/Home/stages/Display.tsx rename to apps/frontend/src/wizard/Home/stages/Display.tsx index a04e1f7ad8ad7..89ea760175991 100644 --- a/apps/frontend/src/pages/Home/stages/Display.tsx +++ b/apps/frontend/src/wizard/Home/stages/Display.tsx @@ -2,12 +2,12 @@ import type { JSX } from "react"; import { toast } from "react-toastify"; import { saveSvgAsPng } from "save-svg-as-png"; -import { CardImage } from "../../../components/Card/CardImage"; -import { getCardThemeBackdrop } from "../../../components/Card/themeBackdrop"; -import { Button } from "../../../components/Generic/Button"; import { HOST } from "../../../constants"; -import type { CardUrlBuilder } from "../../../models/CardUrl"; -import { useTheme } from "../../../redux/selectors/themeSelectors"; +import { CardImage } from "../../components/Card/CardImage"; +import { getCardThemeBackdrop } from "../../components/Card/themeBackdrop"; +import { Button } from "../../components/Generic/Button"; +import type { CardUrlBuilder } from "../../models/CardUrl"; +import { useIsDarkTheme } from "../../useIsDarkTheme"; interface DisplayStageProps { filename: string; @@ -24,7 +24,7 @@ export function DisplayStage({ card, guestHint, }: DisplayStageProps): JSX.Element { - const { isDark } = useTheme(); + const isDark = useIsDarkTheme(); const downloadPNG = () => { saveSvgAsPng( diff --git a/apps/frontend/src/pages/Home/stages/Login/Login.tsx b/apps/frontend/src/wizard/Home/stages/Login/Login.tsx similarity index 100% rename from apps/frontend/src/pages/Home/stages/Login/Login.tsx rename to apps/frontend/src/wizard/Home/stages/Login/Login.tsx diff --git a/apps/frontend/src/pages/Home/stages/Login/LoginAccountDeleteModal.tsx b/apps/frontend/src/wizard/Home/stages/Login/LoginAccountDeleteModal.tsx similarity index 88% rename from apps/frontend/src/pages/Home/stages/Login/LoginAccountDeleteModal.tsx rename to apps/frontend/src/wizard/Home/stages/Login/LoginAccountDeleteModal.tsx index 905205d210ca3..e94e374522c61 100644 --- a/apps/frontend/src/pages/Home/stages/Login/LoginAccountDeleteModal.tsx +++ b/apps/frontend/src/wizard/Home/stages/Login/LoginAccountDeleteModal.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef } from "react"; import type { JSX, RefObject } from "react"; import { createPortal } from "react-dom"; -import { Button } from "../../../../components/Generic/Button"; +import { Button } from "../../../components/Generic/Button"; function useOutsideAlerter( ref: RefObject, @@ -42,7 +42,8 @@ export function LoginAccountDeleteModal( useOutsideAlerter(wrapperRef, onClose); return createPortal( -
+ // `wizard` because this mounts on `body`, outside the element the app's styles are scoped to. +
diff --git a/apps/frontend/src/pages/Home/stages/Login/LoginOptions.tsx b/apps/frontend/src/wizard/Home/stages/Login/LoginOptions.tsx similarity index 96% rename from apps/frontend/src/pages/Home/stages/Login/LoginOptions.tsx rename to apps/frontend/src/wizard/Home/stages/Login/LoginOptions.tsx index 6ed1a7a639492..813edc7fcabb7 100644 --- a/apps/frontend/src/pages/Home/stages/Login/LoginOptions.tsx +++ b/apps/frontend/src/wizard/Home/stages/Login/LoginOptions.tsx @@ -1,11 +1,11 @@ import type { JSX } from "react"; import { FaGithub as GithubIcon } from "react-icons/fa"; -import { Button } from "../../../../components/Generic/Button"; import { GITHUB_PRIVATE_AUTH_URL, GITHUB_PUBLIC_AUTH_URL, } from "../../../../constants"; +import { Button } from "../../../components/Generic/Button"; import { LoginBox } from "./LoginBox"; diff --git a/apps/frontend/src/pages/Home/stages/SelectCard.tsx b/apps/frontend/src/wizard/Home/stages/SelectCard.tsx similarity index 89% rename from apps/frontend/src/pages/Home/stages/SelectCard.tsx rename to apps/frontend/src/wizard/Home/stages/SelectCard.tsx index b961253916e05..9c4f129cd3fa1 100644 --- a/apps/frontend/src/pages/Home/stages/SelectCard.tsx +++ b/apps/frontend/src/wizard/Home/stages/SelectCard.tsx @@ -1,18 +1,18 @@ import { useMemo } from "react"; import type { JSX } from "react"; -import { Card } from "../../../components/Card/Card"; import { DEMO_GIST, DEMO_REPO, DEMO_USER, DEMO_WAKATIME_USER, } from "../../../constants"; -import { CardType } from "../../../models/CardType"; -import { cardUrl } from "../../../models/CardUrl"; -import type { CardUrlBuilder } from "../../../models/CardUrl"; -import { useTheme } from "../../../redux/selectors/themeSelectors"; import { useUserId } from "../../../redux/selectors/userSelectors"; +import { Card } from "../../components/Card/Card"; +import { CardType } from "../../models/CardType"; +import { cardUrl } from "../../models/CardUrl"; +import type { CardUrlBuilder } from "../../models/CardUrl"; +import { useIsDarkTheme } from "../../useIsDarkTheme"; interface SelectCardStageProps { selectedCardType: CardType; @@ -24,7 +24,7 @@ export function SelectCardStage({ onCardTypeChange, }: SelectCardStageProps): JSX.Element { const userId = useUserId(DEMO_USER); - const { isDark } = useTheme(); + const isDark = useIsDarkTheme(); const options = useMemo< Array<{ diff --git a/apps/frontend/src/pages/Home/stages/Theme.tsx b/apps/frontend/src/wizard/Home/stages/Theme.tsx similarity index 85% rename from apps/frontend/src/pages/Home/stages/Theme.tsx rename to apps/frontend/src/wizard/Home/stages/Theme.tsx index 54d7688268423..fc3ab86d570c4 100644 --- a/apps/frontend/src/pages/Home/stages/Theme.tsx +++ b/apps/frontend/src/wizard/Home/stages/Theme.tsx @@ -2,14 +2,16 @@ import type { ThemeName } from "@stats-organization/github-readme-stats-core"; import { themes } from "@stats-organization/github-readme-stats-core"; import type { JSX } from "react"; -import { Card } from "../../../components/Card/Card"; +import { LinkExternal } from "../../../shared/LinkExternal"; +import { Card } from "../../components/Card/Card"; import { getCardThemeBackdrop, getThemeSortRank, -} from "../../../components/Card/themeBackdrop"; -import { CardCategory } from "../../../models/CardType"; -import type { CardUrlBuilder } from "../../../models/CardUrl"; -import { useTheme } from "../../../redux/selectors/themeSelectors"; +} from "../../components/Card/themeBackdrop"; +import { docsUrl } from "../../docsUrl"; +import { CardCategory } from "../../models/CardType"; +import type { CardUrlBuilder } from "../../models/CardUrl"; +import { useIsDarkTheme } from "../../useIsDarkTheme"; const excludedThemes: Array = [ "default", @@ -60,7 +62,7 @@ export function ThemeStage({ category, onThemeChange, }: ThemeStageProps): JSX.Element { - const { isDark } = useTheme(); + const isDark = useIsDarkTheme(); const themeList = category === CardCategory.REPO ? repoCardThemes : nonRepoCardThemes; @@ -97,13 +99,12 @@ export function ThemeStage({
{"For more theme options check the "} - customization documentation - + {" after you copied your card URL in step 5."}
diff --git a/apps/frontend/src/pages/Home/useCardDescriptor.ts b/apps/frontend/src/wizard/Home/useCardDescriptor.ts similarity index 95% rename from apps/frontend/src/pages/Home/useCardDescriptor.ts rename to apps/frontend/src/wizard/Home/useCardDescriptor.ts index 56d5fb67763d5..1723ffba4986a 100644 --- a/apps/frontend/src/pages/Home/useCardDescriptor.ts +++ b/apps/frontend/src/wizard/Home/useCardDescriptor.ts @@ -8,8 +8,8 @@ import { DEMO_WAKATIME_USER, HOST, } from "../../constants"; -import { CardType } from "../../models/CardType"; -import type { CardUrlBuilder } from "../../models/CardUrl"; +import { CardType } from "../models/CardType"; +import type { CardUrlBuilder } from "../models/CardUrl"; /** Per-card metadata for the final display stage. */ export interface CardDescriptor { diff --git a/apps/frontend/src/pages/App/AppTrends.tsx b/apps/frontend/src/wizard/Wizard.tsx similarity index 70% rename from apps/frontend/src/pages/App/AppTrends.tsx rename to apps/frontend/src/wizard/Wizard.tsx index 6b557e2a48e8c..10cb1eec4a4e0 100644 --- a/apps/frontend/src/pages/App/AppTrends.tsx +++ b/apps/frontend/src/wizard/Wizard.tsx @@ -1,23 +1,26 @@ -import { useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; +import { StrictMode, useEffect, useState } from "react"; +import type { JSX } from "react"; +import { Provider, useDispatch } from "react-redux"; import { ToastContainer, toast } from "react-toastify"; -import { getUserMetadata } from "../../api/user"; -import { clearAxiosCache } from "../../axios-override"; -import type { StageIndex } from "../../models/Stage"; -import { useTheme } from "../../redux/selectors/themeSelectors"; +import { clearAxiosCache } from "../axios-override"; import { useIsAuthenticated, useUserKey, useUserToken, -} from "../../redux/selectors/userSelectors"; +} from "../redux/selectors/userSelectors"; import { logout as _logout, setUserAccess as _setUserAccess, -} from "../../redux/slices/user"; -import { HomeScreen } from "../Home/Home"; +} from "../redux/slices/user"; +import { store } from "../redux/store"; -import { Header } from "./Header"; +import { HomeScreen } from "./Home/Home"; +import { getUserMetadata } from "./api/user"; +import { ProgressBar } from "./components/Home/Progress"; +import { STAGE_LABELS } from "./models/Stage"; +import type { StageIndex } from "./models/Stage"; +import { useIsDarkTheme } from "./useIsDarkTheme"; const toMessage = ( input: string | ErrorEvent | PromiseRejectionEvent, @@ -53,11 +56,13 @@ const showError = (event: ErrorEvent | PromiseRejectionEvent): void => { }); }; -export function AppTrends() { +const STAGE_TITLES = STAGE_LABELS.map((it) => it.shortTitle); + +function WizardApp(): JSX.Element { const userKey = useUserKey(); const userToken = useUserToken(); const isAuthenticated = useIsAuthenticated(); - const { isDark } = useTheme(); + const isDark = useIsDarkTheme(); const [stage, setStage] = useState(isAuthenticated ? 1 : 0); @@ -112,8 +117,14 @@ export function AppTrends() { }, [dispatch, userKey]); return ( -
-
+
+ { + setStage(itemIndex as StageIndex); + }} + />
@@ -121,3 +132,14 @@ export function AppTrends() {
); } + +/** Island root: the wizard owns the store, since it is the only React on the site. */ +export function Wizard(): JSX.Element { + return ( + + + + + + ); +} diff --git a/apps/frontend/src/api/user.ts b/apps/frontend/src/wizard/api/user.ts similarity index 97% rename from apps/frontend/src/api/user.ts rename to apps/frontend/src/wizard/api/user.ts index 4fe73bb2f63fa..445f013660e0e 100644 --- a/apps/frontend/src/api/user.ts +++ b/apps/frontend/src/wizard/api/user.ts @@ -1,6 +1,6 @@ import axios from "axios"; -import { HOST } from "../constants"; +import { HOST } from "../../constants"; const authenticate = async ( code: string, diff --git a/apps/frontend/src/components/Card/Card.tsx b/apps/frontend/src/wizard/components/Card/Card.tsx similarity index 100% rename from apps/frontend/src/components/Card/Card.tsx rename to apps/frontend/src/wizard/components/Card/Card.tsx diff --git a/apps/frontend/src/components/Card/CardImage.tsx b/apps/frontend/src/wizard/components/Card/CardImage.tsx similarity index 94% rename from apps/frontend/src/components/Card/CardImage.tsx rename to apps/frontend/src/wizard/components/Card/CardImage.tsx index 9d0e73a620f49..09519100c6324 100644 --- a/apps/frontend/src/components/Card/CardImage.tsx +++ b/apps/frontend/src/wizard/components/Card/CardImage.tsx @@ -1,6 +1,6 @@ import { clsx } from "clsx"; -import { HOST } from "../../constants"; +import { HOST } from "../../../constants"; import type { CardUrlBuilder } from "../../models/CardUrl"; import { SvgInline } from "./SvgInline"; diff --git a/apps/frontend/src/components/Card/SvgInline.tsx b/apps/frontend/src/wizard/components/Card/SvgInline.tsx similarity index 97% rename from apps/frontend/src/components/Card/SvgInline.tsx rename to apps/frontend/src/wizard/components/Card/SvgInline.tsx index 68d0bd758347c..c5cd9b47307a0 100644 --- a/apps/frontend/src/components/Card/SvgInline.tsx +++ b/apps/frontend/src/wizard/components/Card/SvgInline.tsx @@ -7,12 +7,12 @@ import type { JSX } from "react"; import Skeleton from "react-loading-skeleton"; import "react-loading-skeleton/dist/skeleton.css"; -import { setShouldMock } from "../../axios-override.js"; -import { createMockRequest, createMockResponse } from "../../mock-http.js"; +import { setShouldMock } from "../../../axios-override.js"; import { useIsAuthenticated, useUserToken, -} from "../../redux/selectors/userSelectors.js"; +} from "../../../redux/selectors/userSelectors.js"; +import { createMockRequest, createMockResponse } from "../../mock-http.js"; interface SvgInlineProps { url: string; diff --git a/apps/frontend/src/components/Card/themeBackdrop.ts b/apps/frontend/src/wizard/components/Card/themeBackdrop.ts similarity index 100% rename from apps/frontend/src/components/Card/themeBackdrop.ts rename to apps/frontend/src/wizard/components/Card/themeBackdrop.ts diff --git a/apps/frontend/src/components/Generic/Button.tsx b/apps/frontend/src/wizard/components/Generic/Button.tsx similarity index 100% rename from apps/frontend/src/components/Generic/Button.tsx rename to apps/frontend/src/wizard/components/Generic/Button.tsx diff --git a/apps/frontend/src/components/Generic/Checkbox.tsx b/apps/frontend/src/wizard/components/Generic/Checkbox.tsx similarity index 100% rename from apps/frontend/src/components/Generic/Checkbox.tsx rename to apps/frontend/src/wizard/components/Generic/Checkbox.tsx diff --git a/apps/frontend/src/components/Generic/Select.tsx b/apps/frontend/src/wizard/components/Generic/Select.tsx similarity index 100% rename from apps/frontend/src/components/Generic/Select.tsx rename to apps/frontend/src/wizard/components/Generic/Select.tsx diff --git a/apps/frontend/src/components/Home/CheckboxSection.tsx b/apps/frontend/src/wizard/components/Home/CheckboxSection.tsx similarity index 100% rename from apps/frontend/src/components/Home/CheckboxSection.tsx rename to apps/frontend/src/wizard/components/Home/CheckboxSection.tsx diff --git a/apps/frontend/src/components/Home/LanguagesLayoutSection.tsx b/apps/frontend/src/wizard/components/Home/LanguagesLayoutSection.tsx similarity index 100% rename from apps/frontend/src/components/Home/LanguagesLayoutSection.tsx rename to apps/frontend/src/wizard/components/Home/LanguagesLayoutSection.tsx diff --git a/apps/frontend/src/components/Home/NumericSection.tsx b/apps/frontend/src/wizard/components/Home/NumericSection.tsx similarity index 100% rename from apps/frontend/src/components/Home/NumericSection.tsx rename to apps/frontend/src/wizard/components/Home/NumericSection.tsx diff --git a/apps/frontend/src/components/Home/Progress.tsx b/apps/frontend/src/wizard/components/Home/Progress.tsx similarity index 92% rename from apps/frontend/src/components/Home/Progress.tsx rename to apps/frontend/src/wizard/components/Home/Progress.tsx index 161df49ba3d9d..6e9b9def16cbe 100644 --- a/apps/frontend/src/components/Home/Progress.tsx +++ b/apps/frontend/src/wizard/components/Home/Progress.tsx @@ -61,7 +61,8 @@ export function ProgressBar({ const rightDisabled = currItemIndex === items.length - 1; return ( -
+ // The site header is fixed, so the bar parks directly under it rather than at the viewport top. +