feat: Theme loading and metadata (#13)
commitc92f17c167Author: Minecon724 <minecon724@noreply.git.m724.eu> Date: Sat Sep 13 10:46:57 2025 +0000 Display everything in the UI commitfa533b8ba5Author: Minecon724 <minecon724@noreply.git.m724.eu> Date: Sat Sep 13 10:46:40 2025 +0000 Add theme loader commitadbd5af8d1Author: Minecon724 <minecon724@noreply.git.m724.eu> Date: Sat Sep 13 10:17:00 2025 +0000 Fix webpack commit933ad0b23aAuthor: Minecon724 <minecon724@noreply.git.m724.eu> Date: Sat Sep 13 10:16:51 2025 +0000 Add metadata to builtin themes commit1b8aa2fc35Author: Minecon724 <minecon724@noreply.git.m724.eu> Date: Sat Sep 13 10:16:38 2025 +0000 Remove accessibility themes
This commit is contained in:
		
					parent
					
						
							
								4d10f6261d
							
						
					
				
			
			
				commit
				
					
						93446a85af
					
				
			
		
					 21 changed files with 373 additions and 82 deletions
				
			
		| 
						 | 
				
			
			@ -16,6 +16,7 @@ import (
 | 
			
		|||
 | 
			
		||||
	_ "net/http/pprof" // Used for debugging if enabled and a web server is running
 | 
			
		||||
 | 
			
		||||
	"forgejo.org/models/theme"
 | 
			
		||||
	"forgejo.org/modules/container"
 | 
			
		||||
	"forgejo.org/modules/graceful"
 | 
			
		||||
	"forgejo.org/modules/log"
 | 
			
		||||
| 
						 | 
				
			
			@ -217,6 +218,13 @@ func serveInstalled(_ context.Context, ctx *cli.Command) error {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Load themes
 | 
			
		||||
	themes, tlerr := theme.GetThemeIds()
 | 
			
		||||
	if (tlerr != nil) {
 | 
			
		||||
		log.Error("Failed to load themes: ", tlerr)
 | 
			
		||||
	}
 | 
			
		||||
	setting.UI.Themes = themes
 | 
			
		||||
 | 
			
		||||
	// Set up Chi routes
 | 
			
		||||
	webRoutes := routers.NormalRoutes()
 | 
			
		||||
	err := listen(webRoutes, true)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										107
									
								
								models/theme/theme_loader.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								models/theme/theme_loader.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,107 @@
 | 
			
		|||
package theme
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"maps"
 | 
			
		||||
	"slices"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"forgejo.org/modules/assetfs"
 | 
			
		||||
	"forgejo.org/modules/log"
 | 
			
		||||
	"forgejo.org/modules/public"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/sync/singleflight"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	themeFilenamePrefix = "theme-"
 | 
			
		||||
	themeFilenameSuffix = ".css"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	group        singleflight.Group
 | 
			
		||||
	assetFs      *assetfs.LayeredFS
 | 
			
		||||
	loaded       bool
 | 
			
		||||
	loadedThemes map[string]Theme // this static variable feels suspicious
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func GetThemeIds() ([]string, error) {
 | 
			
		||||
	themes, err := GetThemes()
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return slices.Collect(maps.Keys(themes)), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetThemes gets the installed Themes.
 | 
			
		||||
//
 | 
			
		||||
// It doesn't install them to settings.UI.Themes.
 | 
			
		||||
func GetThemes() (map[string]Theme, error) {
 | 
			
		||||
	if loaded {
 | 
			
		||||
		return loadedThemes, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if assetFs == nil {
 | 
			
		||||
		assetFs = public.AssetFS()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err, _ := group.Do("load-themes", func() (any, error) {
 | 
			
		||||
		_themes, err := loadThemes(assetFs)
 | 
			
		||||
 | 
			
		||||
		log.Info("Loaded %d themes", len(_themes))
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			// Retry on error TODO might want to improve that
 | 
			
		||||
			group.Forget("load-themes")
 | 
			
		||||
		} else {
 | 
			
		||||
			loadedThemes = _themes
 | 
			
		||||
			loaded = true
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil, err
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return loadedThemes, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// loadThemes scans for and loads themes from an asset filesystem.
 | 
			
		||||
//
 | 
			
		||||
// Returns: a map of theme ID: theme metadata.
 | 
			
		||||
func loadThemes(assetFs *assetfs.LayeredFS) (map[string]Theme, error) {
 | 
			
		||||
	entries, err := assetFs.ListFiles("assets/css")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	themes := make(map[string]Theme)
 | 
			
		||||
 | 
			
		||||
	for _, entry := range entries {
 | 
			
		||||
		if !(strings.HasPrefix(entry, themeFilenamePrefix) && strings.HasSuffix(entry, themeFilenameSuffix)) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		id := entry[len(themeFilenamePrefix) : len(entry)-len(themeFilenameSuffix)]
 | 
			
		||||
 | 
			
		||||
		theme := Theme{
 | 
			
		||||
			ID:   id,
 | 
			
		||||
			Name: getFriendlyThemeName(id),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := loadThemeMeta(assetFs, entry, &theme); err != nil {
 | 
			
		||||
			log.Warn("Failed to load meta of theme %s: %s", entry, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		themes[id] = theme
 | 
			
		||||
 | 
			
		||||
		log.Debug("Found theme: %s (%s)", theme.Name, id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(themes) == 0 {
 | 
			
		||||
		return nil, fmt.Errorf("no themes found")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return themes, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										108
									
								
								models/theme/theme_meta.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								models/theme/theme_meta.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,108 @@
 | 
			
		|||
package theme
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"forgejo.org/modules/assetfs"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// The Theme struct represents the metadata of a theme. Most fields are allowed to be null / blank.
 | 
			
		||||
type Theme struct {
 | 
			
		||||
	// ID is the theme id, e.g., forgejo-dark
 | 
			
		||||
	ID 			  	string
 | 
			
		||||
	// Name is the theme's readable name, e.g., Forgejo. Note that the scheme is excluded in favor of the Scheme property.
 | 
			
		||||
	Name		   	string
 | 
			
		||||
	// Family is the theme's family, e.g. Catppuccin.
 | 
			
		||||
	Family      string
 | 
			
		||||
	// Author is the theme's author. It may be an invididual, a group, or an entity.
 | 
			
		||||
	Author      string
 | 
			
		||||
	// Url is the URL to the theme's website, e.g. https://forgejo.org.
 | 
			
		||||
	Url         string
 | 
			
		||||
	// Description is the theme's short description.
 | 
			
		||||
	Description string
 | 
			
		||||
	// Scheme is the theme's color scheme. It must be either: dark, light, or auto.
 | 
			
		||||
	Scheme      string
 | 
			
		||||
	// Recommended means the theme is guaranteed to be bug-free, usually a first-party theme.
 | 
			
		||||
	Recommended bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// loadThemeMeta loads theme metadata from a file in a layered FS.
 | 
			
		||||
//
 | 
			
		||||
// Theme ID is expected to be prefilled. The metadata will be assigned to the passed `theme` object.
 | 
			
		||||
func loadThemeMeta(assetFs *assetfs.LayeredFS, filename string, theme *Theme) error {
 | 
			
		||||
	file, err := assetFs.Open("assets/css/" + filename)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to open file: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 | 
			
		||||
	kv, err := scanThemeForKeyValueMetaMap(file, theme.ID) // ID is expected to be prefilled
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error scanning theme for meta: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	theme.Name = kv["name"]
 | 
			
		||||
	theme.Family = kv["family"]
 | 
			
		||||
	theme.Author = kv["author"]
 | 
			
		||||
	theme.Url = kv["url"]
 | 
			
		||||
	theme.Description = kv["description"]
 | 
			
		||||
	theme.Scheme = strings.ToLower(kv["scheme"])
 | 
			
		||||
	theme.Recommended = kv["recommended"] == "yes"
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// scanThemeForKeyValueMetaMap finds raw key-value metadata in an open file.
 | 
			
		||||
func scanThemeForKeyValueMetaMap(file io.Reader, id string) (map[string]string, error) {
 | 
			
		||||
	var kv = make(map[string]string)
 | 
			
		||||
 | 
			
		||||
	scanner := bufio.NewScanner(file)
 | 
			
		||||
	ok := false
 | 
			
		||||
 | 
			
		||||
	// webpack adds headers
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		if strings.TrimSpace(scanner.Text()) == "/* theme "+id {
 | 
			
		||||
			ok = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, errors.New("theme has no meta")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
 | 
			
		||||
		parts := strings.Fields(line)
 | 
			
		||||
 | 
			
		||||
		if len(parts) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		key := strings.ToLower(parts[0])
 | 
			
		||||
 | 
			
		||||
		if key == "*/" {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(parts) == 1 {
 | 
			
		||||
			kv[key] = "yes"
 | 
			
		||||
		} else {
 | 
			
		||||
			kv[key] = strings.Join(parts[1:], " ")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := scanner.Err(); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("scanner error: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return kv, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								models/theme/theme_name.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								models/theme/theme_name.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
package theme
 | 
			
		||||
 | 
			
		||||
import "strings"
 | 
			
		||||
 | 
			
		||||
// getFriendlyThemeName converts an raw theme name to a friendly, readable name.
 | 
			
		||||
// Example: forgejo-dark -> Forgejo Dark, catppuccin-maroon-auto -> Catppuccin Maroon Auto
 | 
			
		||||
func getFriendlyThemeName(themeName string) string {
 | 
			
		||||
	themeName = strings.ReplaceAll(themeName, "-", " ")
 | 
			
		||||
 | 
			
		||||
	themeName = strings.ToLower(themeName)
 | 
			
		||||
	themeName = strings.Title(themeName)
 | 
			
		||||
 | 
			
		||||
	return themeName
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -812,6 +812,10 @@ manage_themes = Default theme
 | 
			
		|||
manage_openid = OpenID addresses
 | 
			
		||||
email_desc = Your primary email address will be used for notifications, password recovery and, provided that it is not hidden, web-based Git operations.
 | 
			
		||||
theme_desc = This theme will be used for the web interface when you are logged in.
 | 
			
		||||
theme_recommended = Recommended
 | 
			
		||||
theme_light = Light
 | 
			
		||||
theme_dark = Dark
 | 
			
		||||
theme_auto = Auto (per system)
 | 
			
		||||
primary = Primary
 | 
			
		||||
activated = Activated
 | 
			
		||||
requires_activation = Requires activation
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,7 @@ import (
 | 
			
		|||
	"forgejo.org/models/db"
 | 
			
		||||
	"forgejo.org/models/organization"
 | 
			
		||||
	repo_model "forgejo.org/models/repo"
 | 
			
		||||
	"forgejo.org/models/theme"
 | 
			
		||||
	user_model "forgejo.org/models/user"
 | 
			
		||||
	"forgejo.org/modules/base"
 | 
			
		||||
	"forgejo.org/modules/log"
 | 
			
		||||
| 
						 | 
				
			
			@ -335,15 +336,15 @@ func Repos(ctx *context.Context) {
 | 
			
		|||
func Appearance(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["Title"] = ctx.Tr("settings.appearance")
 | 
			
		||||
	ctx.Data["PageIsSettingsAppearance"] = true
 | 
			
		||||
	ctx.Data["AllThemes"] = setting.UI.Themes
 | 
			
		||||
	ctx.Data["ThemeName"] = func(themeName string) string {
 | 
			
		||||
		fullThemeName := "themes.names." + themeName
 | 
			
		||||
		if ctx.Locale.HasKey(fullThemeName) {
 | 
			
		||||
			return ctx.Locale.TrString(fullThemeName)
 | 
			
		||||
		}
 | 
			
		||||
		return themeName
 | 
			
		||||
 | 
			
		||||
	themes, err := theme.GetThemes()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("Failed to load themes", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Data["AllThemes"] = themes
 | 
			
		||||
 | 
			
		||||
	var hiddenCommentTypes *big.Int
 | 
			
		||||
	val, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,37 +7,64 @@
 | 
			
		|||
		</h4>
 | 
			
		||||
		<div class="ui attached segment">
 | 
			
		||||
			<div class="ui email list">
 | 
			
		||||
				<div class="item">
 | 
			
		||||
					{{ctx.Locale.Tr "settings.theme_desc"}}
 | 
			
		||||
				</div>
 | 
			
		||||
 | 
			
		||||
			<form class="ui form" action="{{.Link}}/theme" method="post">
 | 
			
		||||
				{{.CsrfTokenHtml}}
 | 
			
		||||
					<div class="field">
 | 
			
		||||
						<label for="ui">{{ctx.Locale.Tr "settings.ui"}}</label>
 | 
			
		||||
						<div class="ui selection dropdown" id="ui">
 | 
			
		||||
							<input name="theme" type="hidden" value="{{.SignedUser.Theme}}">
 | 
			
		||||
							{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 | 
			
		||||
							<div class="text">
 | 
			
		||||
								{{- range $i,$a := .AllThemes -}}
 | 
			
		||||
									{{if eq $.SignedUser.Theme $a}}{{call $.ThemeName $a}}{{end}}
 | 
			
		||||
								{{- end -}}
 | 
			
		||||
							</div>
 | 
			
		||||
 | 
			
		||||
							<div class="menu">
 | 
			
		||||
							{{range $i,$a := .AllThemes}}
 | 
			
		||||
								<div class="item{{if eq $.SignedUser.Theme $a}} active selected{{end}}" data-value="{{$a}}">
 | 
			
		||||
									{{call $.ThemeName $a}}
 | 
			
		||||
								</div>
 | 
			
		||||
							{{end}}
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
				{{$theme := index .AllThemes $.SignedUser.Theme}}
 | 
			
		||||
				{{if $theme.Author}}
 | 
			
		||||
					<div class="item">
 | 
			
		||||
						<p>
 | 
			
		||||
							<strong>{{$theme.Name}}</strong>
 | 
			
		||||
							by <em><a href="{{$theme.Url}}">{{$theme.Author}}</a></em> {{if $theme.Recommended}}<span data-tooltip-content="{{ctx.Locale.Tr "settings.theme_recommended"}}">{{svg "octicon-verified" 16 "dropdown icon"}}</span>{{end}}
 | 
			
		||||
						</p>
 | 
			
		||||
						{{if $theme.Description}}
 | 
			
		||||
							<p><em>"{{$theme.Description}}"</em></p>
 | 
			
		||||
						{{end}}
 | 
			
		||||
					</div>
 | 
			
		||||
 | 
			
		||||
				<div class="field">
 | 
			
		||||
					<button class="ui primary button">{{ctx.Locale.Tr "settings.update_theme"}}</button>
 | 
			
		||||
				</div>
 | 
			
		||||
			</form>
 | 
			
		||||
					<div class="item tw-mb-auto">
 | 
			
		||||
						{{ctx.Locale.Tr "settings.theme_desc"}}
 | 
			
		||||
					</div>
 | 
			
		||||
				{{else}}
 | 
			
		||||
					<div class="item tw-mb-4">
 | 
			
		||||
						{{ctx.Locale.Tr "settings.theme_desc"}}
 | 
			
		||||
					</div>
 | 
			
		||||
				{{end}}
 | 
			
		||||
 | 
			
		||||
				<form class="ui form" action="{{.Link}}/theme" method="post">
 | 
			
		||||
					{{.CsrfTokenHtml}}
 | 
			
		||||
						<div class="field">
 | 
			
		||||
							<label for="ui">{{ctx.Locale.Tr "settings.ui"}}</label>
 | 
			
		||||
							<div class="ui selection dropdown" id="ui">
 | 
			
		||||
								<input name="theme" type="hidden" value="{{.SignedUser.Theme}}">
 | 
			
		||||
								{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 | 
			
		||||
								<div class="text">
 | 
			
		||||
									{{- range $i,$a := .AllThemes -}}
 | 
			
		||||
										{{if eq $.SignedUser.Theme $i}}{{$a.Name}}{{end}}
 | 
			
		||||
									{{- end -}}
 | 
			
		||||
								</div>
 | 
			
		||||
 | 
			
		||||
								<div class="menu">
 | 
			
		||||
								{{range $i,$a := .AllThemes}}
 | 
			
		||||
									<div class="item{{if eq $.SignedUser.Theme $i}} active selected{{end}} tw-flex" data-value="{{$i}}">
 | 
			
		||||
										{{$a.Name}}
 | 
			
		||||
										{{if $a.Recommended}}
 | 
			
		||||
											<span data-tooltip-content="{{ctx.Locale.Tr "settings.theme_recommended"}}">{{svg "octicon-verified" 16 "dropdown icon"}}</span>
 | 
			
		||||
										{{end}}
 | 
			
		||||
										{{if eq $a.Scheme "auto"}}
 | 
			
		||||
											<span data-tooltip-content="{{ctx.Locale.Tr "settings.theme_auto"}}">{{svg "octicon-sync" 16 "dropdown icon"}}</span>
 | 
			
		||||
										{{else if eq $a.Scheme "dark"}}
 | 
			
		||||
											<span data-tooltip-content="{{ctx.Locale.Tr "settings.theme_dark"}}">{{svg "octicon-moon" 16 "dropdown icon"}}</span>
 | 
			
		||||
										{{else if eq $a.Scheme "light"}}
 | 
			
		||||
											<span data-tooltip-content="{{ctx.Locale.Tr "settings.theme_light"}}">{{svg "octicon-sun" 16 "dropdown icon"}}</span>
 | 
			
		||||
										{{end}}
 | 
			
		||||
									</div>
 | 
			
		||||
								{{end}}
 | 
			
		||||
								</div>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
 | 
			
		||||
					<div class="field">
 | 
			
		||||
						<button class="ui primary button">{{ctx.Locale.Tr "settings.update_theme"}}</button>
 | 
			
		||||
					</div>
 | 
			
		||||
				</form>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,13 @@
 | 
			
		|||
#!/bin/bash
 | 
			
		||||
set -euo pipefail
 | 
			
		||||
 | 
			
		||||
make --no-print-directory watch-frontend &
 | 
			
		||||
make --no-print-directory watch-backend &
 | 
			
		||||
make --no-print-directory watch-frontend | tee >(
 | 
			
		||||
  awk -v pattern="webpack" '
 | 
			
		||||
    $0 ~ pattern {
 | 
			
		||||
      system("make --no-print-directory watch-backend &")
 | 
			
		||||
    }
 | 
			
		||||
  '
 | 
			
		||||
) &
 | 
			
		||||
 | 
			
		||||
trap 'kill $(jobs -p)' EXIT
 | 
			
		||||
wait
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,2 +0,0 @@
 | 
			
		|||
@import "theme-forgejo-light-deuteranopia-protanopia.css";
 | 
			
		||||
@import "theme-forgejo-dark-deuteranopia-protanopia.css" (prefers-color-scheme: dark);
 | 
			
		||||
| 
						 | 
				
			
			@ -1,2 +0,0 @@
 | 
			
		|||
@import "theme-forgejo-light-tritanopia.css";
 | 
			
		||||
@import "theme-forgejo-dark-tritanopia.css" (prefers-color-scheme: dark);
 | 
			
		||||
| 
						 | 
				
			
			@ -1,2 +1,12 @@
 | 
			
		|||
/* theme forgejo-auto
 | 
			
		||||
    Name Forgejo
 | 
			
		||||
    Family Forgejo
 | 
			
		||||
    Author Forgejo authors
 | 
			
		||||
    Url https://forgejo.org/
 | 
			
		||||
    Description The fresh look of freedom.
 | 
			
		||||
    Scheme Auto
 | 
			
		||||
    Recommended
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
@import "theme-forgejo-light.css";
 | 
			
		||||
@import "theme-forgejo-dark.css" (prefers-color-scheme: dark);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +0,0 @@
 | 
			
		|||
@import "./theme-forgejo-dark.css";
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
  /* removed rows/words: use red colors from vanilla forgejo-dark */
 | 
			
		||||
  --color-diff-added-word-bg: #214d88;
 | 
			
		||||
  --color-diff-added-row-border: #214d88;
 | 
			
		||||
  --color-diff-added-row-bg: #18184f;
 | 
			
		||||
  --color-code-bg: #0d1117;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +0,0 @@
 | 
			
		|||
@import "./theme-forgejo-dark.css";
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
  /* removed rows/words: use red colors from vanilla forgejo-dark */
 | 
			
		||||
  --color-diff-added-word-bg: #214d88;
 | 
			
		||||
  --color-diff-added-row-border: #214d88;
 | 
			
		||||
  --color-diff-added-row-bg: #152846;
 | 
			
		||||
  --color-code-bg: #0d1117;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +1,13 @@
 | 
			
		|||
/* theme forgejo-dark
 | 
			
		||||
    Name Forgejo
 | 
			
		||||
    Family Forgejo
 | 
			
		||||
    Author Forgejo authors
 | 
			
		||||
    Url https://forgejo.org/
 | 
			
		||||
    Description The fresh look of freedom.
 | 
			
		||||
    Scheme Dark
 | 
			
		||||
    Recommended
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
@import "../chroma/dark.css";
 | 
			
		||||
@import "../codemirror/dark.css";
 | 
			
		||||
@import "../markup/dark.css";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
@import "./theme-forgejo-light.css";
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
  --color-diff-removed-word-bg: #c8c850;
 | 
			
		||||
  --color-diff-removed-row-border: #c8c850;
 | 
			
		||||
  --color-diff-removed-row-bg: #ffecc4;
 | 
			
		||||
  --color-diff-added-word-bg: #b8c0ff;
 | 
			
		||||
  --color-diff-added-row-border: #b8c0ff;
 | 
			
		||||
  --color-diff-added-row-bg: #e0e0ff;
 | 
			
		||||
  --color-code-bg: #ffffff;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
@import "./theme-forgejo-light.css";
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
  --color-diff-removed-word-bg: #ffb8c0;
 | 
			
		||||
  --color-diff-removed-row-border: #ffb8c0;
 | 
			
		||||
  --color-diff-removed-row-bg: #ffd8d8;
 | 
			
		||||
  --color-diff-added-word-bg: #b8c0ff;
 | 
			
		||||
  --color-diff-added-row-border: #b8c0ff;
 | 
			
		||||
  --color-diff-added-row-bg: #c0e8ff;
 | 
			
		||||
  --color-code-bg: #ffffff;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +1,13 @@
 | 
			
		|||
/* theme forgejo-light
 | 
			
		||||
    Name Forgejo
 | 
			
		||||
    Family Forgejo
 | 
			
		||||
    Author Forgejo authors
 | 
			
		||||
    Url https://forgejo.org/
 | 
			
		||||
    Description The fresh look of freedom.
 | 
			
		||||
    Scheme Light
 | 
			
		||||
    Recommended
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
@import "../chroma/light.css";
 | 
			
		||||
@import "../codemirror/light.css";
 | 
			
		||||
@import "../markup/light.css";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,2 +1,12 @@
 | 
			
		|||
/* theme gitea-auto
 | 
			
		||||
    Name Gitea
 | 
			
		||||
    Family Gitea
 | 
			
		||||
    Author The Gitea Authors
 | 
			
		||||
    Url https://github.com/go-gitea/gitea
 | 
			
		||||
    Description The OG style.
 | 
			
		||||
    Scheme Auto
 | 
			
		||||
    Recommended
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
@import "./theme-gitea-light.css" (prefers-color-scheme: light);
 | 
			
		||||
@import "./theme-gitea-dark.css" (prefers-color-scheme: dark);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,13 @@
 | 
			
		|||
/* theme gitea-dark
 | 
			
		||||
    Name Gitea
 | 
			
		||||
    Family Gitea
 | 
			
		||||
    Author The Gitea Authors
 | 
			
		||||
    Url https://github.com/go-gitea/gitea
 | 
			
		||||
    Description The OG style.
 | 
			
		||||
    Scheme Dark
 | 
			
		||||
    Recommended
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
@import "../chroma/dark.css";
 | 
			
		||||
@import "../codemirror/dark.css";
 | 
			
		||||
@import "../markup/dark.css";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,13 @@
 | 
			
		|||
/* theme gitea-light
 | 
			
		||||
    Name Gitea
 | 
			
		||||
    Family Gitea
 | 
			
		||||
    Author The Gitea Authors
 | 
			
		||||
    Url https://github.com/go-gitea/gitea
 | 
			
		||||
    Description The OG style.
 | 
			
		||||
    Scheme Light
 | 
			
		||||
    Recommended
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
@import "../chroma/light.css";
 | 
			
		||||
@import "../codemirror/light.css";
 | 
			
		||||
@import "../markup/light.css";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -157,6 +157,7 @@ export default {
 | 
			
		|||
        minify: true,
 | 
			
		||||
        css: true,
 | 
			
		||||
        legalComments: 'none',
 | 
			
		||||
        exclude: [new RegExp(`css\\/(${Object.keys(themes).join('|')})\\.css$`)],
 | 
			
		||||
      }),
 | 
			
		||||
    ],
 | 
			
		||||
    splitChunks: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue