Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/build-release-assets/package.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ bun release
(
cd dist
tar -zcf theme-github-extras.tar.gz \
theme-github-pink-*.css theme-github-gitea-*.css theme-github-catppuccin-*.css
theme-github-pink-*.css theme-github-gitea-*.css theme-github-gruvbox-*.css theme-github-catppuccin-*.css
)

# Template assets must extract beside templates under the documented custom root.
Expand Down
4 changes: 2 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

Gitea GitHub Theme is a build-time CSS generation framework that produces static `.css` theme files for the
[Gitea](https://github.com/go-gitea/gitea) self-hosted code platform. The project takes design tokens and color
definitions as input, transforms them through a layered pipeline, and outputs 30+ minified CSS files covering 7 theme
series (default, colorblind, tritanopia, pink, gitea, catppuccin, high-contrast).
definitions as input, transforms them through a layered pipeline, and outputs 30+ minified CSS files covering 8 theme
series (default, colorblind, tritanopia, pink, gitea, gruvbox, catppuccin, high-contrast).

**Core principle:** Full type safety and CSS-in-JS developer experience at build time, pure static CSS delivered to the
browser with zero runtime overhead.
Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ Install the resulting files using your Forgejo data directory (commonly `data/gi
1. Extract `theme-github-base.tar.gz`, then place the contained `*.css` files in `<forgejo-data>/public/assets/css`
(create the directory if necessary). This archive contains the standard light, dark, auto, and soft-dark themes.
2. Optionally extract `theme-github-accessibility.tar.gz` for colorblind and high-contrast themes, or
`theme-github-extras.tar.gz` for Pink, Gitea-compatible, and Catppuccin themes. Place their CSS files in the same
`<forgejo-data>/public/assets/css` directory.
`theme-github-extras.tar.gz` for Pink, Gitea-compatible, Gruvbox, and Catppuccin themes. Place their CSS files in the
same `<forgejo-data>/public/assets/css` directory.
3. Extract `theme-github-fonts.tar.gz` into `<forgejo-data>/public`. This supplies the Mona Sans variable font; its SIL
Open Font License is included beside the font.
4. Extract the optional `theme-github-templates.tar.gz` archive into `<forgejo-data>`. It includes the templates and
Expand Down Expand Up @@ -188,6 +188,14 @@ THEMES = github-gitea-auto, github-gitea-light, github-gitea-dark
<img src="https://raw.githubusercontent.com/lutinglt/gitea-github-theme/assets/screenshots/gitea/gitea-dark.png"/>
</details>

### Gruvbox Themes

```ini
THEMES = github-gruvbox-auto, github-gruvbox-light, github-gruvbox-dark
```

The auto theme follows the system color-scheme preference and selects the matching Gruvbox light or dark palette.

### Catppuccin Themes

```ini
Expand Down
2 changes: 1 addition & 1 deletion dev/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ services:
FORGEJO__server__LANDING_PAGE: login
FORGEJO__security__INSTALL_LOCK: "true"
FORGEJO__ui__DEFAULT_THEME: github-auto
FORGEJO__ui__THEMES: "${FORGEJO_PREVIEW_THEMES:-github-auto,github-catppuccin-auto,github-catppuccin-frappe,github-catppuccin-latte,github-catppuccin-macchiato,github-catppuccin-mocha,github-colorblind-auto,github-colorblind-dark,github-colorblind-light,github-dark,github-gitea-auto,github-gitea-dark,github-gitea-light,github-high-contrast-auto,github-high-contrast-colorblind-auto,github-high-contrast-colorblind-dark,github-high-contrast-colorblind-light,github-high-contrast-dark,github-high-contrast-light,github-high-contrast-soft-dark,github-high-contrast-tritanopia-auto,github-high-contrast-tritanopia-dark,github-high-contrast-tritanopia-light,github-light,github-pink-auto,github-pink-dark,github-pink-light,github-pink-soft-dark,github-soft-dark,github-tritanopia-auto,github-tritanopia-dark,github-tritanopia-light}"
FORGEJO__ui__THEMES: "${FORGEJO_PREVIEW_THEMES:-github-auto,github-catppuccin-auto,github-catppuccin-frappe,github-catppuccin-latte,github-catppuccin-macchiato,github-catppuccin-mocha,github-colorblind-auto,github-colorblind-dark,github-colorblind-light,github-dark,github-gitea-auto,github-gitea-dark,github-gitea-light,github-gruvbox-auto,github-gruvbox-dark,github-gruvbox-light,github-high-contrast-auto,github-high-contrast-colorblind-auto,github-high-contrast-colorblind-dark,github-high-contrast-colorblind-light,github-high-contrast-dark,github-high-contrast-light,github-high-contrast-soft-dark,github-high-contrast-tritanopia-auto,github-high-contrast-tritanopia-dark,github-high-contrast-tritanopia-light,github-light,github-pink-auto,github-pink-dark,github-pink-light,github-pink-soft-dark,github-soft-dark,github-tritanopia-auto,github-tritanopia-dark,github-tritanopia-light}"
ports:
- "3000:3000"
- "2222:22"
Expand Down
70 changes: 67 additions & 3 deletions src/build-output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ const EXPECTED_THEME_FILES = [
`${PREFIX}gitea-dark.css`,
`${PREFIX}gitea-light.css`,
`${PREFIX}gitea-auto.css`,
// gruvbox
`${PREFIX}gruvbox-dark.css`,
`${PREFIX}gruvbox-light.css`,
`${PREFIX}gruvbox-auto.css`,
// catppuccin
`${PREFIX}catppuccin-mocha.css`,
`${PREFIX}catppuccin-latte.css`,
Expand Down Expand Up @@ -116,6 +120,66 @@ describe("构建产物基本验证", () => {
});
});

describe("Gruvbox 语法高亮 CSS", () => {
for (const mode of ["light", "dark"]) {
it(`${mode} includes Chroma and CodeMirror rules`, () => {
const css = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-${mode}.css`), "utf-8");

expect(css).toContain(".chroma .k{");
expect(css).toContain(".codemirror-container .tok-keyword{");
});
}

it("dark code blocks use a black overlay", () => {
const css = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-dark.css`), "utf-8");

expect(css).toContain("--color-markup-code-block:#00000020");
});

it("light code blocks use the Gruvbox light surface", () => {
const css = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-light.css`), "utf-8");

expect(css).toContain("--color-markup-code-block:#ebdbb2");
});

it("uses each mode's base background for the menu", () => {
const lightCss = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-light.css`), "utf-8");
const darkCss = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-dark.css`), "utf-8");

expect(lightCss).toContain("--color-menu:#fbf1c7");
expect(darkCss).toContain("--color-menu:#282828");
});

for (const [mode, background] of [
["light", "#fbf1c7"],
["dark", "#282828"],
]) {
it(`${mode} color-light matches color-body`, () => {
const css = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-${mode}.css`), "utf-8");

expect(css).toContain(`--color-light:${background}`);
expect(css).toContain(`--color-body:${background}`);
});
}

it("uses bg2 for the RSS tooltip background in both modes", () => {
const lightCss = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-light.css`), "utf-8");
const darkCss = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-dark.css`), "utf-8");

expect(lightCss).toContain("--color-tooltip-bg:#d5c4a1");
expect(darkCss).toContain("--color-tooltip-bg:#504945");
});

it("uses the requested shadow color in light mode only", () => {
const lightCss = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-light.css`), "utf-8");
const darkCss = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}gruvbox-dark.css`), "utf-8");

expect(lightCss).toContain("--color-shadow:#25292e0a");
expect(darkCss).toContain("--color-shadow:#1d202158");
});
});

it("ships page stylesheets unchanged with valid CSS and a template reference", async () => {
it("ships component stylesheets unchanged with valid CSS and a template reference", async () => {
const { transform } = await import("lightningcss");
const templates = fs
Expand Down Expand Up @@ -710,8 +774,8 @@ const SIZE_LIMITS = {
AUTO_MAX: 1 * 1024, // 1 KB
/** 非 auto 主题包含完整主题变量 + 公共样式,当前约 207-216 KB */
SOLID_MAX: 220 * 1024, // 220 KB (给予主题页面样式增长空间)
/** 所有 CSS 文件总大小上限,当前约 2.6 MB */
TOTAL_MAX: 5 * 1024 * 1024, // 5 MB
/** 所有 CSS 文件总大小上限,当前约 5.2 MB */
TOTAL_MAX: 6 * 1024 * 1024, // 6 MB
} as const;

describe("CSS 文件大小限制", () => {
Expand Down Expand Up @@ -743,7 +807,7 @@ describe("CSS 文件大小限制", () => {
).toEqual([]);
});

it("所有 CSS 文件总大小 < 5MB", () => {
it("所有 CSS 文件总大小 < 6MB", () => {
let totalSize = 0;
for (const fileName of EXPECTED_THEME_FILES) {
totalSize += fs.statSync(path.join(DIST_DIR, fileName)).size;
Expand Down
12 changes: 10 additions & 2 deletions src/core/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import pkg from "../../package.json";
import type { GiteaColor, GitHubColor, ThemeColor } from "../palette";
import { gitea2ThemeVars, github2ThemeColor, primer2Chroma, primer2CodeMirror, theme2ThemeVars } from "../palette";
import selectors from "../selectors";
import { otherThemeVars, syntaxVars, themeVars, type Syntax } from "../types";
import { otherThemeVars, syntaxVars, themeVars, type Chroma, type CodeMirror, type Syntax } from "../types";
import { createChroma } from "./chroma";
import { createCodeMirror } from "./codemirror";
import type { MapLeafNodes } from "./types";
Expand All @@ -39,7 +39,13 @@ function getThemeVersion(): string {
export type ThemeVars = { isDarkTheme: boolean } & MapLeafNodes<typeof themeVars, string>;
export type ThemeColors =
| { colorType: "github"; themeColor: GitHubColor }
| { colorType: "gitea"; themeColor: GiteaColor; syntaxColor: Syntax }
| {
colorType: "gitea";
themeColor: GiteaColor;
syntaxColor: Syntax;
chromaColor?: Chroma;
codeMirrorColor?: CodeMirror;
}
| { colorType: "theme"; themeColor: ThemeColor; syntaxColor: Syntax };
export type Theme = {
isDarkTheme: boolean;
Expand Down Expand Up @@ -134,5 +140,7 @@ export function createTheme(theme: Theme): void {
createCodeMirror(primer2CodeMirror(theme.themeColor));
} else {
createGlobalTheme(selectors.root, syntaxVars, theme.syntaxColor);
if (theme.colorType === "gitea" && theme.chromaColor) createChroma(theme.chromaColor);
if (theme.colorType === "gitea" && theme.codeMirrorColor) createCodeMirror(theme.codeMirrorColor);
}
}
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ export {
display2GitHubColor,
github2ThemeColor,
primer2GitHubColor,
primer2Chroma,
primer2CodeMirror,
} from "./palette";
export type { DisplayColor, GitHubColor, GiteaColor, ThemeColor } from "./palette";
export { chromaVars, customThemeVars, otherThemeVars, syntaxVars, themeVars } from "./types";
export type {
Ansi,
Chroma,
CodeMirror,
Console,
Diff,
GitHub,
Expand Down
7 changes: 7 additions & 0 deletions theme.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ export default defineThemeConfig({
light: { theme: themes.gitea.light },
},
},
{
themeSeriesName: "gruvbox",
themes: {
dark: { theme: themes.gruvbox.dark },
light: { theme: themes.gruvbox.light },
},
},
{
themeSeriesName: "catppuccin",
themes: {
Expand Down
Loading
Loading