From 7556843ec13773eb3c9330fcaf6de05c60f17102 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Sat, 10 May 2025 16:45:25 +0000 Subject: [PATCH] Add theme info Display is rough right now but better than nothing. Icons especially to be improved. --- ABOUT-FORK.md | 6 +- cmd/web.go | 3 + modules/theme/theme.go | 143 +++++++++++++++--- options/locale/locale_en-US.ini | 5 +- routers/web/user/setting/profile.go | 17 +-- templates/user/settings/appearance.tmpl | 40 ++++- tools/watch.sh | 9 +- ...e-forgejo-auto-deuteranopia-protanopia.css | 2 - .../themes/theme-forgejo-auto-tritanopia.css | 2 - web_src/css/themes/theme-forgejo-auto.css | 10 ++ ...e-forgejo-dark-deuteranopia-protanopia.css | 9 -- .../themes/theme-forgejo-dark-tritanopia.css | 9 -- web_src/css/themes/theme-forgejo-dark.css | 10 ++ ...-forgejo-light-deuteranopia-protanopia.css | 11 -- .../themes/theme-forgejo-light-tritanopia.css | 11 -- web_src/css/themes/theme-forgejo-light.css | 10 ++ web_src/css/themes/theme-gitea-auto.css | 10 ++ web_src/css/themes/theme-gitea-dark.css | 10 ++ web_src/css/themes/theme-gitea-light.css | 10 ++ 19 files changed, 231 insertions(+), 96 deletions(-) delete mode 100644 web_src/css/themes/theme-forgejo-auto-deuteranopia-protanopia.css delete mode 100644 web_src/css/themes/theme-forgejo-auto-tritanopia.css delete mode 100644 web_src/css/themes/theme-forgejo-dark-deuteranopia-protanopia.css delete mode 100644 web_src/css/themes/theme-forgejo-dark-tritanopia.css delete mode 100644 web_src/css/themes/theme-forgejo-light-deuteranopia-protanopia.css delete mode 100644 web_src/css/themes/theme-forgejo-light-tritanopia.css diff --git a/ABOUT-FORK.md b/ABOUT-FORK.md index 7b3b94c4d9..204f9299e3 100644 --- a/ABOUT-FORK.md +++ b/ABOUT-FORK.md @@ -24,7 +24,6 @@ Don't be overwhelmed, commits are very granular... except when they aren't. - [*Fix forgejo/forgejo#7250*](/git724/forgejo/commit/6eeb0009ef574900e5f11936bc783acf561d8825): Fixes [forgejo/forgejo#7250](https://codeberg.org/forgejo/forgejo/issues/7250) - [*Fix issue popup for non-JSON responses*](/git724/forgejo/commit/22a74730d36ec13cd098a2a0e3e4f294160580ca): Basically, if you hover on a issue #, previously it displayed a popup with a "Network error" because it expects messages to be JSON. Now it can parse both. - [*Issue popup message that the issue doesn't exist*](/git724/forgejo/commit/6f2a441ed52722337137eb16f157ca076e3983be): Adds an "Issue not found" message that replaces generic 404. -- [*Theme picker warning with hardcoded link*](/git724/forgejo/commit/a94e53c01722fa03a16e23e56de5e72c983ff228): Adds a warning above theme picker that third-party themes are not as reliable and credits. [**Points to here, this is hardcoded.**](https://git.m724.eu/git724/git724/src/branch/master/THEMES.md) - [*Move user RSS icon (WIP)*](/git724/forgejo/commit/c17f3738377c0939d9f95535990ce05482b2e9d3): Moves RSS link to *Public activity*, because the RSS feed is public activity. I don't remember why WIP. - [*Center padlock icon on profile page*](/git724/forgejo/commit/8ce85c11fd0122a7ff5274d982d141637802aa39): Centers vertically *Email privacy* link icon on user profile page. - [*Fix footer link margin*](/git724/forgejo/commit/cbdce79d8b2f878e0635ba513a8a27ead59d9888) @@ -38,9 +37,8 @@ Don't be overwhelmed, commits are very granular... except when they aren't. - [*Remove hover transition from buttons*](/git724/forgejo/commit/d965f0080289eda053461bc0a61c643abf11756e) - [*Remove "API" from footer*](/git724/forgejo/commit/5979129aa6acde0422654fd6ec0f4efd862eb975) - [*Dynamic theme loading*](/git724/forgejo/commit/d71c372080e4008551db79f4d92a2fbdfcde19cc): Loads themes from a directory on startup. Normally you'd list every theme variant in your config, which can be inconvenient, especially with a lot of themes. -- [*Improve theme picker*](/git724/forgejo/commit/9542895e03e487cfd0a7c673257cc383295b5d76): Makes the theme picker searchable and changes `names-like-this` to `Names Like This`. - -You can see [all my changes here](/git724/forgejo/commits/branch/v11.0/forgejo/search?q=Minecon724&all=). \ +- [*Improve theme picker*](/git724/forgejo/commit/9542895e03e487cfd0a7c673257cc383295b5d76): Makes the theme picker searchable and changes `names-like-this` to `Names Like This`. (precedes the below) +- [*Add theme info*](): Adds theme info (url, author, desc, etc.), see [the stock themes](/git724/forgejo/src/branch/v11.0/forgejo/web_src/css/themes). Also tweaks `make watch` so webpack completes first. Display is to be improved. --- diff --git a/cmd/web.go b/cmd/web.go index 3e7fdee4bf..21128e408a 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -22,6 +22,7 @@ import ( "forgejo.org/modules/process" "forgejo.org/modules/public" "forgejo.org/modules/setting" + "forgejo.org/modules/theme" "forgejo.org/routers" "forgejo.org/routers/install" @@ -218,6 +219,8 @@ func serveInstalled(ctx *cli.Context) error { } } + theme.GetThemes() + // Set up Chi routes webRoutes := routers.NormalRoutes() err := listen(webRoutes, true) diff --git a/modules/theme/theme.go b/modules/theme/theme.go index 140d23b335..f8c1fb92ca 100644 --- a/modules/theme/theme.go +++ b/modules/theme/theme.go @@ -1,7 +1,12 @@ package theme import ( + "bufio" + "errors" "fmt" + "io" + "maps" + "slices" "strings" "forgejo.org/modules/assetfs" @@ -12,77 +17,96 @@ import ( "golang.org/x/sync/singleflight" ) -const ( - key string = "load-themes" -) - var ( group singleflight.Group assetFs *assetfs.LayeredFS loaded bool + themes map[string]Theme ) -// LoadThemes loads installed themes +type Theme struct { + ID string + Name string + Family string + Author string + Url string + Description string + Scheme string + Verified bool +} + +// GetThemes gets installed Themes // // Note that you can't just run this during init, because webpack mightn't have completed yet. // Hence, we're loading on first demand. -func LoadThemes() error { +func GetThemes() (map[string]Theme, error) { if loaded { - return nil + return themes, nil } if assetFs == nil { assetFs = public.AssetFS() } - _, err, _ := group.Do(key, func() (interface{}, error) { - themes, err := loadThemesInner(assetFs) + _, err, _ := group.Do("load-themes", func() (any, error) { + _themes, err := loadThemesInner(assetFs) + + log.Info("Loaded %d themes", len(_themes)) if err != nil { - group.Forget(key) + group.Forget("load-themes") } else { - setting.UI.Themes = themes + setting.UI.Themes = slices.Collect(maps.Keys(_themes)) + themes = _themes loaded = true } return nil, err }) - return err + return themes, err } -func loadThemesInner(assetFs *assetfs.LayeredFS) ([]string, error) { +func loadThemesInner(assetFs *assetfs.LayeredFS) (map[string]Theme, error) { entries, err := assetFs.ListFiles("assets/css") if err != nil { return nil, err } - var themes []string + themes := make(map[string]Theme) for _, entry := range entries { if !(strings.HasPrefix(entry, "theme-") && strings.HasSuffix(entry, ".css")) { continue } - theme := entry[6 : len(entry)-4] - themes = append(themes, theme) + id := entry[6 : len(entry)-4] + theme := Theme{ + ID: id, + Name: getFriendlyThemeName(id), + } - log.Debug("Found theme: %s", theme) + if err := loadThemeMeta(assetFs, entry, &theme); err != nil { + log.Warn("Failed to load meta of theme %s: %s", id, err) + } + + themes[id] = theme + + log.Debug("Found theme: %s (%s)", theme.Name, theme.ID) } - if len(themes) > 0 { - log.Info("Loaded %d themes", len(themes)) - return themes, nil - } else { + if len(themes) == 0 { return nil, fmt.Errorf("no themes found") } + + return themes, nil } -// GetFriendlyThemeName converts an raw theme name (forgejo-dark) to a friendly name (Forgejo Dark) +// getFriendlyThemeName converts an raw theme name (forgejo-dark) to a friendly name (Forgejo Dark) // // Example: forgejo-dark -> Forgejo Dark, catppuccin-maroon-auto -> Catppuccin Maroon Auto -func GetFriendlyThemeName(themeName string) string { +func getFriendlyThemeName(themeName string) string { themeName = strings.ReplaceAll(themeName, "-", " ") themeName = strings.ToLower(themeName) @@ -90,3 +114,76 @@ func GetFriendlyThemeName(themeName string) string { return themeName } + +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 := scanTheme(file, filename[6:len(filename)-4]) + + 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.Verified = kv["verified"] == "yes" + + return nil +} + +func scanTheme(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 +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index db856b2bb9..e57e5fbbc3 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -835,7 +835,6 @@ 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_warning = WARNING: Themes are made by third parties. Click to read more. primary = Primary activated = Activated requires_activation = Requires activation @@ -849,6 +848,10 @@ email_deletion_desc = The email address and related information will be removed email_deletion_success = The email address has been removed. theme_update_success = Your theme was updated. theme_update_error = The selected theme does not exist. +theme_auto = Dynamic / Synchronizes with your system +theme_light = Light +theme_dark = Dark +theme_verified = Verified openid_deletion = Remove OpenID Address openid_deletion_desc = Removing this OpenID address from your account will prevent you from signing in with it. Continue? openid_deletion_success = The OpenID address has been removed. diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 2185906de9..cc3190da79 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -328,29 +328,16 @@ func Repos(ctx *context.Context) { // Appearance render user's appearance settings func Appearance(ctx *context.Context) { - err := theme.LoadThemes() + themes, err := theme.GetThemes() if err != nil { ctx.ServerError("Failed to load themes", err) return } - ctx.Data["AllThemes"] = setting.UI.Themes - 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) - } - - // We should probably cache, because this does some operations on the string - themeName = theme.GetFriendlyThemeName(themeName) - - return themeName - } + ctx.Data["AllThemes"] = themes var hiddenCommentTypes *big.Int val, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes) diff --git a/templates/user/settings/appearance.tmpl b/templates/user/settings/appearance.tmpl index be2b8d9962..a556b4b5b0 100644 --- a/templates/user/settings/appearance.tmpl +++ b/templates/user/settings/appearance.tmpl @@ -7,10 +7,26 @@