diff --git a/templates/header.templ b/templates/header.templ index 9f8347c..e0293d8 100644 --- a/templates/header.templ +++ b/templates/header.templ @@ -98,14 +98,15 @@ templ Header(user User, currentPath string) { }
diff --git a/templates/header_templ.go b/templates/header_templ.go index d627977..0a4b4b8 100644 --- a/templates/header_templ.go +++ b/templates/header_templ.go @@ -266,102 +266,135 @@ func Header(user User, currentPath string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\" class=\"w-full flex items-center gap-2.5 px-3 py-1.5 rounded-lg text-sm transition-colors hover:bg-surface-hover\" style=\"color: var(--text-secondary);\"> ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\" style=\"color: var(--text-secondary);\"> ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if user.Theme == opt.Name { - templ_7745c5c3_Err = Icon("check", "h-4 w-4 shrink-0").Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "

Bookshelf

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - for _, w := range WoodOptions { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = Icon("check", "h-4 w-4").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "

Bookshelf

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, w := range WoodOptions { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if user.Role == "admin" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "Administration
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var20 = []any{activeClass(currentPath, "/admin")} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var20...) + var templ_7745c5c3_Var23 = []any{activeClass(currentPath, "/admin")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var23...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = Icon("grid", "h-5 w-5 shrink-0").Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "Dashboard ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var22 = []any{activeClass(currentPath, "/admin/library")} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var22...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "Libraries ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "Dashboard ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var24 = []any{activeClass(currentPath, "/admin/users")} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var24...) + var templ_7745c5c3_Var25 = []any{activeClass(currentPath, "/admin/library")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var25...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "Users ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "Libraries ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var26 = []any{activeClass(currentPath, "/admin/settings")} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var26...) + var templ_7745c5c3_Var27 = []any{activeClass(currentPath, "/admin/users")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var27...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "Users ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var29 = []any{activeClass(currentPath, "/admin/settings")} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var29...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } templ_7745c5c3_Err = Icon("gear", "h-5 w-5 shrink-0").Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "Settings
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "Settings
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -510,7 +543,7 @@ func Header(user User, currentPath string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -535,12 +568,12 @@ func SidebarUserMenu(user User) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var28 := templ.GetChildren(ctx) - if templ_7745c5c3_Var28 == nil { - templ_7745c5c3_Var28 = templ.NopComponent + templ_7745c5c3_Var31 := templ.GetChildren(ctx) + if templ_7745c5c3_Var31 == nil { + templ_7745c5c3_Var31 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -577,7 +610,7 @@ func SidebarUserMenu(user User) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "Profile
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "Logout
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -611,12 +644,12 @@ func SidebarSignIn(currentPath string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var30 := templ.GetChildren(ctx) - if templ_7745c5c3_Var30 == nil { - templ_7745c5c3_Var30 = templ.NopComponent + templ_7745c5c3_Var33 := templ.GetChildren(ctx) + if templ_7745c5c3_Var33 == nil { + templ_7745c5c3_Var33 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "
Sign in
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "\">
Create an account
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/templates/utils.go b/templates/utils.go index 1da27a0..88c9add 100644 --- a/templates/utils.go +++ b/templates/utils.go @@ -31,6 +31,17 @@ func navItemClass(current, target string) string { return base + "text-content-muted hover:text-content hover:bg-surface-hover" } +// themeCheckClass returns the class suffix for a theme option's checkmark +// wrapper: empty for the active theme (visible), " hidden" otherwise. The +// check element is always rendered so updateThemeIndicators() can move it +// client-side when the user picks a different theme. +func themeCheckClass(name, current string) string { + if name == current { + return "" + } + return " hidden" +} + func ContainsString(slice []string, item string) bool { for _, s := range slice { if s == item { diff --git a/templates/utils_test.go b/templates/utils_test.go index 2b5f50f..474b3bb 100644 --- a/templates/utils_test.go +++ b/templates/utils_test.go @@ -45,3 +45,12 @@ func TestGroupTunableSettingsEmpty(t *testing.T) { t.Errorf("expected empty groups, got live=%d restart=%d", len(live), len(restart)) } } + +func TestThemeCheckClass(t *testing.T) { + if got := themeCheckClass("dracula", "dracula"); got != "" { + t.Errorf("active theme should be visible, got %q", got) + } + if got := themeCheckClass("nord", "dracula"); got != " hidden" { + t.Errorf("inactive theme should be hidden, got %q", got) + } +} diff --git a/web/src/theme.ts b/web/src/theme.ts index 968e637..dc20078 100644 --- a/web/src/theme.ts +++ b/web/src/theme.ts @@ -41,6 +41,7 @@ const loadTheme = (): void => { // Change: Remove DOM element lookup, accept theme as parameter const changeTheme = async (theme: string): Promise => { applyTheme(theme); + updateThemeIndicators(); // Save to server if logged in const token = localStorage.getItem(TOKEN_STORAGE_KEY); @@ -78,6 +79,7 @@ const loadUserTheme = async (): Promise => { const data = await response.json(); if (data.theme) { applyTheme(data.theme as string); + updateThemeIndicators(); } } } catch { @@ -85,10 +87,26 @@ const loadUserTheme = async (): Promise => { } }; +// Update the active-theme checkmark in the Appearance menu to follow the +// theme currently applied to . Each theme button carries its theme name +// in data-theme and a .theme-check element that is shown for the active theme +// and hidden for the rest. +const updateThemeIndicators = (): void => { + const match = document.body.className.match(/theme-([\w-]+)/); + const current = match ? match[1] : DEFAULT_THEME; + document.querySelectorAll(".theme-btn").forEach((btn) => { + const check = btn.querySelector(".theme-check"); + if (!check) return; + const theme = btn.getAttribute("data-theme"); + check.classList.toggle("hidden", theme !== current); + }); +}; + // Initialize theme system const initializeTheme = (): void => { loadTheme(); loadUserTheme(); + updateThemeIndicators(); // Set theme select value to current theme const themeSelect = document.getElementById( @@ -210,6 +228,7 @@ Alpine.data("theme", () => ({ loadTheme, loadUserTheme, loadWoodPaneling, + updateThemeIndicators, updateWoodPanelingIndicators, })); @@ -221,6 +240,7 @@ export { loadTheme, loadUserTheme, loadWoodPaneling, + updateThemeIndicators, updateWoodPanelingIndicators, }; export type { ThemeType };