vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Add Catppuccin, Nord, and Solarized as built-in themes
0584bb700c0b144752ddc29764d697551234f862 · Keenan Nemetz · 1d ago · parent c980fe53
Add Catppuccin, Nord, and Solarized as built-in themes Ship four more Appearance choices: Catppuccin Mocha, Catppuccin Latte, Nord, and Solarized Light. Each is a token block in static/palettes.css, scoped to its data-theme value and loaded after Everforest, before instance theme packs and profile/custom.css. The ids join theme.js's allowlist and the account picker, and the instance repository's reserved-name check now covers nord and the catppuccin-/solarized- prefixes so a theme pack cannot shadow a built-in. NOTICE and three MIT license files record the palettes.
server/ui_test.go +27−3
| @@ -135,9 +135,10 @@ func TestStaticAssetsAreServedFromTheBinary(t *testing.T) { | |||
| 135 | 135 | } | |
| 136 | 136 | } | |
| 137 | 137 | ||
| 138 | // Everforest is built in: the shell starts on dark/medium, the account page owns | ||
| 139 | // the picker, and every variant is scoped to the attribute theme.js sets. If the | ||
| 140 | // shell stops naming either file the picker does nothing. The instance | ||
| 138 | // Everforest and the other built-in palettes are scoped to the attribute | ||
| 139 | // theme.js sets. The shell starts on Everforest dark/medium, the account page | ||
| 140 | // owns the picker, and every variant is scoped to the attribute theme.js sets. | ||
| 141 | // If the shell stops naming a sheet the picker does nothing. The instance | ||
| 141 | 142 | // repository may add its own themes; instance_repo_test.go covers that path. | |
| 142 | 143 | func TestEverforestThemesAndAccountPicker(t *testing.T) { | |
| 143 | 144 | t.Parallel() | |
| @@ -150,6 +151,7 @@ func TestEverforestThemesAndAccountPicker(t *testing.T) { | |||
| 150 | 151 | for _, want := range []string{ | |
| 151 | 152 | `src="/static/theme.js"`, | |
| 152 | 153 | `href="/static/everforest.css"`, | |
| 154 | `href="/static/palettes.css"`, | ||
| 153 | 155 | `data-theme="everforest-dark-medium"`, | |
| 154 | 156 | `data-font-scale="medium"`, | |
| 155 | 157 | } { | |
| @@ -187,6 +189,10 @@ func TestEverforestThemesAndAccountPicker(t *testing.T) { | |||
| 187 | 189 | "everforest-light-hard", | |
| 188 | 190 | "everforest-light-medium", | |
| 189 | 191 | "everforest-light-soft", | |
| 192 | "catppuccin-mocha", | ||
| 193 | "catppuccin-latte", | ||
| 194 | "nord", | ||
| 195 | "solarized-light", | ||
| 190 | 196 | } { | |
| 191 | 197 | if !strings.Contains(account, `value="`+variant+`"`) { | |
| 192 | 198 | t.Errorf("the theme picker does not offer %s", variant) | |
| @@ -216,6 +222,24 @@ func TestEverforestThemesAndAccountPicker(t *testing.T) { | |||
| 216 | 222 | t.Fatal("the Everforest sheet has an unscoped body rule and would restyle the fallback") | |
| 217 | 223 | } | |
| 218 | 224 | ||
| 225 | code, palettes := f.get("/static/palettes.css") | ||
| 226 | if code != 200 { | ||
| 227 | t.Fatalf("/static/palettes.css = %d", code) | ||
| 228 | } | ||
| 229 | for _, variant := range []string{ | ||
| 230 | "catppuccin-mocha", | ||
| 231 | "catppuccin-latte", | ||
| 232 | "nord", | ||
| 233 | "solarized-light", | ||
| 234 | } { | ||
| 235 | if !strings.Contains(palettes, `data-theme="`+variant+`"`) { | ||
| 236 | t.Errorf("the palette sheet does not define %s", variant) | ||
| 237 | } | ||
| 238 | } | ||
| 239 | if strings.Contains(palettes, "\nbody {") { | ||
| 240 | t.Fatal("the palette sheet has an unscoped body rule and would restyle the fallback") | ||
| 241 | } | ||
| 242 | |||
| 219 | 243 | code, base := f.get("/static/vole.css") | |
| 220 | 244 | if code != 200 { | |
| 221 | 245 | t.Fatalf("/static/vole.css = %d", code) | |