aboutsummaryrefslogtreecommitdiffstats
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/i18n/el.go3
-rw-r--r--internal/web/static/blog.css7
-rw-r--r--internal/web/templates/dashboard/design.html10
-rw-r--r--internal/web/templates/layouts/blog.html2
-rw-r--r--internal/web/theme.go47
-rw-r--r--internal/web/web_test.go41
6 files changed, 86 insertions, 24 deletions
diff --git a/internal/i18n/el.go b/internal/i18n/el.go
index e2523cb..bdec8a2 100644
--- a/internal/i18n/el.go
+++ b/internal/i18n/el.go
@@ -388,9 +388,8 @@ var el = map[string]string{
"Medium": "Μεσαίο",
"Tall": "Ψηλό",
"menu in the header": "μενού στην κεφαλίδα",
- "Link style": "Στυλ συνδέσμων",
+ "Underline": "Υπογράμμιση",
"Uppercase, spaced": "Κεφαλαία, αραιά",
- "Plain, underlined": "Απλοί, υπογραμμισμένοι",
"Show the date on posts": "Εμφάνιση ημερομηνίας στις δημοσιεύσεις",
"Show tags with a # in front": "Εμφάνιση των ετικετών με # μπροστά",
"Rule under titles": "Γραμμή κάτω από τους τίτλους",
diff --git a/internal/web/static/blog.css b/internal/web/static/blog.css
index 8cd969e..054532d 100644
--- a/internal/web/static/blog.css
+++ b/internal/web/static/blog.css
@@ -28,8 +28,11 @@ img { max-width: 100%; height: auto; }
.tagline { margin: 0.3em 0 0; opacity: 0.85; }
.site-nav { border-bottom: 1px solid rgba(0,0,0,0.1); }
.nav-inner { padding: 0.3em 1em; }
-.site-nav a { display: inline-block; padding: 0.5em 0.8em; text-decoration: none; font-weight: bold; }
-.site-nav a.active, .site-nav a:hover { text-decoration: underline; }
+.site-nav a { display: inline-block; padding: 0.5em 0.8em; text-decoration: none; }
+/* menu link styles (body.nav-*, from the theme's switches; colours are the theme CSS's) */
+.nav-bold .site-nav a { font-weight: bold; }
+.nav-upper .site-nav a { text-transform: uppercase; letter-spacing: 0.05em; }
+.nav-ul-always .site-nav a, .nav-ul-hover .site-nav a:hover, .site-nav a.active { text-decoration: underline; }
.body-wrap { padding: 1.5em 1em; }
.content { padding: 1.5em 2em; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.15); }
.page-title { margin-top: 0; }
diff --git a/internal/web/templates/dashboard/design.html b/internal/web/templates/dashboard/design.html
index 476e311..7b11e44 100644
--- a/internal/web/templates/dashboard/design.html
+++ b/internal/web/templates/dashboard/design.html
@@ -155,14 +155,16 @@
<option value="center"{{if eq $t.NavAlign "center"}} selected{{end}}>{{t "Centred"}}</option>
<option value="right"{{if eq $t.NavAlign "right"}} selected{{end}}>{{t "Right"}}</option>
</select></label>
- <label>{{t "Link style"}}<br><select name="nav_style">
- <option value="normal"{{if eq $t.NavStyle "normal"}} selected{{end}}>{{t "Normal"}}</option>
- <option value="uppercase"{{if eq $t.NavStyle "uppercase"}} selected{{end}}>{{t "Uppercase, spaced"}}</option>
- <option value="plain"{{if eq $t.NavStyle "plain"}} selected{{end}}>{{t "Plain, underlined"}}</option>
+ <label>{{t "Underline"}}<br><select name="nav_underline">
+ <option value="never"{{if eq $t.NavUnderline "never"}} selected{{end}}>{{t "Never"}}</option>
+ <option value="hover"{{if eq $t.NavUnderline "hover"}} selected{{end}}>{{t "Only on hover"}}</option>
+ <option value="always"{{if eq $t.NavUnderline "always"}} selected{{end}}>{{t "Always"}}</option>
</select></label>
<label>{{t "Background"}}<br><input type="color" name="nav_bg" value="{{$t.NavBg}}"></label>
<label>{{t "Text"}}<br><input type="color" name="nav_text" value="{{$t.NavText}}"></label>
</div>
+ <label class="check"><input type="checkbox" name="nav_bold"{{if $t.NavBold}} checked{{end}}> {{t "Bold"}}</label>
+ <label class="check"><input type="checkbox" name="nav_upper"{{if $t.NavUpper}} checked{{end}}> {{t "Uppercase, spaced"}}</label>
<div class="hoverpick">
<input type="checkbox" name="nav_hover_custom" id="nav-hover-custom"{{if $t.NavHover}} checked{{end}}>
<label for="nav-hover-custom" class="check">{{t "Change colour when the mouse is over a link"}}</label>
diff --git a/internal/web/templates/layouts/blog.html b/internal/web/templates/layouts/blog.html
index 29147e7..7ee0b54 100644
--- a/internal/web/templates/layouts/blog.html
+++ b/internal/web/templates/layouts/blog.html
@@ -12,7 +12,7 @@
<style>{{css .Data.css}}</style>
</head>
{{$l := .Data.layout}}
-<body class="{{if $l.HasLeft}}has-left {{end}}{{if $l.HasRight}}has-right {{end}}{{.Data.theme.TitleRuleClass}}">
+<body class="{{if $l.HasLeft}}has-left {{end}}{{if $l.HasRight}}has-right {{end}}{{.Data.theme.NavClass}}{{with .Data.theme.TitleRuleClass}} {{.}}{{end}}">
{{if or $l.Header .Data.theme.HeaderImage}}{{if .Data.theme.HeaderOn}}<div class="site-header">
{{if .Data.theme.HeaderImage}}<div class="header-image"><a href="/"><img src="/media/{{.Data.theme.HeaderImage}}" alt=""></a></div>{{end}}
{{range $l.Header}}{{template "module" (dict "m" . "root" $)}}{{end}}
diff --git a/internal/web/theme.go b/internal/web/theme.go
index 0437f51..9eae856 100644
--- a/internal/web/theme.go
+++ b/internal/web/theme.go
@@ -43,10 +43,17 @@ type Theme struct {
HeaderRule string `json:"header_rule"` // none | line | dots | stripes, drawn beside the title/logo modules in the header
NavAlign string `json:"nav_align"` // left | center | right (header menu)
- NavStyle string `json:"nav_style"` // normal | uppercase | plain (regular weight, underlined)
- NavBg string `json:"nav_bg"`
- NavText string `json:"nav_text"`
- NavHover string `json:"nav_hover"` // "" = same as NavText
+ // The link styles are switches that combine; blog.css draws them from
+ // the body classes NavClass makes. NavStyle is the select they replaced
+ // (normal | uppercase | plain): still decoded from themes saved before,
+ // turned into the switches by ParseTheme and never written again.
+ NavStyle string `json:"nav_style,omitempty"`
+ NavBold bool `json:"nav_bold"`
+ NavUpper bool `json:"nav_upper"` // uppercase, letter-spaced
+ NavUnderline string `json:"nav_underline"` // hover | never | always
+ NavBg string `json:"nav_bg"`
+ NavText string `json:"nav_text"`
+ NavHover string `json:"nav_hover"` // "" = same as NavText
ShowDates bool `json:"show_dates"`
DateFormat string `json:"date_format"` // long | short | iso (rendered by i18n.FormatDate)
@@ -108,7 +115,7 @@ func DefaultTheme() Theme {
ContentStyle: "card", ContentPadding: "normal",
Font: "sans", HeadingFont: "same", FontSize: "normal",
HeaderImageHeight: "medium", HeaderAlign: "left", HeaderBg: "#2f3a4a", HeaderText: "#ffffff", TitleSize: "normal", HeaderRule: "none",
- NavAlign: "left", NavStyle: "normal", NavBg: "#ffffff", NavText: "#222222",
+ NavAlign: "left", NavBold: true, NavUnderline: "hover", NavBg: "#ffffff", NavText: "#222222",
ShowDates: true, DateFormat: "long", PostsPerPage: postsPerPageChoices[0], ListStyle: "full", PostImage: "top", TitleRule: "none", PostNotices: true,
FooterBg: "#2f3a4a", FooterColor: "#d0d5dc", FooterAlign: "center",
LogoSize: "medium",
@@ -122,6 +129,15 @@ func ParseTheme(raw json.RawMessage) Theme {
if len(raw) > 0 {
_ = json.Unmarshal(raw, &t)
}
+ // Themes saved with the old link-style select: it becomes the switches
+ // it stood for, and is dropped from the JSON on the next save.
+ switch t.NavStyle {
+ case "uppercase":
+ t.NavUpper = true
+ case "plain":
+ t.NavBold, t.NavUnderline = false, "always"
+ }
+ t.NavStyle = ""
t.normalize()
return t
}
@@ -190,7 +206,7 @@ func (t *Theme) normalize() {
t.TitleSize = pick(t.TitleSize, "normal", "small", "large")
t.HeaderRule = pick(t.HeaderRule, "none", "line", "dots", "stripes")
t.NavAlign = pick(t.NavAlign, "left", "center", "right")
- t.NavStyle = pick(t.NavStyle, "normal", "uppercase", "plain")
+ t.NavUnderline = pick(t.NavUnderline, "hover", "never", "always")
t.NavBg = color(t.NavBg, d.NavBg)
t.NavText = color(t.NavText, d.NavText)
t.NavHover = optColor(t.NavHover)
@@ -259,7 +275,9 @@ func ThemeFromForm(cur Theme, f url.Values) Theme {
t.TitleSize = get("title_size")
t.HeaderRule = get("header_rule")
t.NavAlign = get("nav_align")
- t.NavStyle = get("nav_style")
+ t.NavBold = on("nav_bold")
+ t.NavUpper = on("nav_upper")
+ t.NavUnderline = get("nav_underline")
t.NavBg = get("nav_bg")
t.NavText = get("nav_text")
t.NavHover = hover("nav_hover")
@@ -504,6 +522,19 @@ func (t Theme) RuleClass() string {
return "ruled rule-" + t.HeaderRule
}
+// NavClass is the body classes blog.css draws the menu links from (weight,
+// case, underline); the theme CSS only colours them.
+func (t Theme) NavClass() string {
+ c := []string{"nav-ul-" + t.NavUnderline}
+ if t.NavBold {
+ c = append(c, "nav-bold")
+ }
+ if t.NavUpper {
+ c = append(c, "nav-upper")
+ }
+ return strings.Join(c, " ")
+}
+
// TitleRuleClass is the body class blog.css draws the rule under page and
// post titles from; the theme CSS only colours it.
func (t Theme) TitleRuleClass() string {
@@ -551,7 +582,7 @@ a:hover {{"{"}}{{if .LinkHover}} color:{{.LinkHover}};{{end}}{{if eq .LinkUnderl
.header-image img { max-height:{{.HeaderImageMaxHeight}}; }
.site-nav { background:{{.NavBg}}; }
.nav-inner { text-align:{{.NavAlign}}; }
-.site-nav a { color:{{.NavText}};{{if eq .NavStyle "uppercase"}} text-transform:uppercase; letter-spacing:0.05em; font-size:0.9em;{{else if eq .NavStyle "plain"}} font-weight:normal; text-decoration:underline;{{end}} }
+.site-nav a { color:{{.NavText}}; }
{{- if .NavHover}}
.site-nav a:hover { color:{{.NavHover}}; }
{{- end}}
diff --git a/internal/web/web_test.go b/internal/web/web_test.go
index 2f3bc4f..e12f9e0 100644
--- a/internal/web/web_test.go
+++ b/internal/web/web_test.go
@@ -101,17 +101,17 @@ func TestThemeNormalizeAndCSS(t *testing.T) {
t.Errorf("old theme content defaults: %+v", th)
}
f := url.Values{"bg_color": {"#ABCDEF"}, "bg_image": {"not-a-uuid"}, "logo_size": {"large"},
- "link_hover": {"#ff0000"}, "nav_hover": {"#00ff00"}, "nav_hover_custom": {"on"}, "nav_style": {"uppercase"}, "nav_align": {"center"},
+ "link_hover": {"#ff0000"}, "nav_hover": {"#00ff00"}, "nav_hover_custom": {"on"}, "nav_upper": {"on"}, "nav_align": {"center"},
"link_underline": {"hover"}, "heading_font": {"serif"}, "content_style": {"flat"}, "content_padding": {"roomy"}, "date_format": {"iso"}, "header_rule": {"stripes"}}
th = ThemeFromForm(DefaultTheme(), f)
if th.BgColor != "#abcdef" || th.BgImage != "" || th.LogoSize != "large" || th.RuleClass() != "ruled rule-stripes" {
t.Errorf("from form: %+v", th)
}
- if th.LinkHover != "" || th.NavHover != "#00ff00" || th.ShowDates || th.DateFormat != "iso" {
+ if th.LinkHover != "" || th.NavHover != "#00ff00" || th.ShowDates || th.DateFormat != "iso" || th.NavBold || !th.NavUpper {
t.Errorf("hover checkbox / unticked checkboxes: %+v", th)
}
css := th.CSS()
- for _, want := range []string{"background-color:#abcdef", "text-transform:uppercase", ".site-nav a:hover { color:#00ff00", "a:hover { text-decoration:underline",
+ for _, want := range []string{"background-color:#abcdef", ".site-nav a:hover { color:#00ff00", "a:hover { text-decoration:underline",
"h1, h2, h3, h4, h5, h6 { font-family:Georgia", "text-align:center", "border-radius:0", "padding:2.2em 3em", "@media (max-width: 700px)", ".logo { max-height:140px"} {
if !strings.Contains(css, want) {
t.Errorf("css missing %q:\n%s", want, css)
@@ -124,10 +124,10 @@ func TestThemeNormalizeAndCSS(t *testing.T) {
if css := DefaultTheme().CSS(); strings.Contains(css, "padding:") || strings.Contains(css, "a:hover") {
t.Errorf("default css should not set padding or hover: %s", css)
}
- // Plain menu links: regular weight and underlined, instead of blog.css's bold.
- css = ThemeFromForm(DefaultTheme(), url.Values{"nav_style": {"plain"}}).CSS()
- if !strings.Contains(css, ".site-nav a { color:#222222; font-weight:normal; text-decoration:underline; }") {
- t.Errorf("plain nav css: %s", css)
+ // The menu links' weight, case and underline are body classes blog.css draws; the theme CSS only colours them.
+ css = DefaultTheme().CSS()
+ if !strings.Contains(css, ".site-nav a { color:#222222; }") || strings.Contains(css, "font-weight") || strings.Contains(css, "text-transform") {
+ t.Errorf("nav css: %s", css)
}
if css := DefaultTheme().CSS(); strings.Contains(css, "@font-face") || strings.Contains(css, "h1, h2") {
t.Errorf("default css should declare no font faces or heading rule: %s", css)
@@ -139,6 +139,33 @@ func TestThemeNormalizeAndCSS(t *testing.T) {
}
}
+// The menu link styles are switches; the select they replaced is migrated
+// from stored themes (bold + hover underline is what blog.css always drew).
+func TestNavStyles(t *testing.T) {
+ th := ParseTheme([]byte(`{"nav_style":"uppercase"}`))
+ if !th.NavUpper || !th.NavBold || th.NavUnderline != "hover" || th.NavStyle != "" || strings.Contains(string(th.JSON()), "nav_style") {
+ t.Errorf("uppercase migration: %+v %s", th, th.JSON())
+ }
+ th = ParseTheme([]byte(`{"nav_style":"plain"}`))
+ if th.NavBold || th.NavUpper || th.NavUnderline != "always" {
+ t.Errorf("plain migration: %+v", th)
+ }
+ for _, raw := range []string{`{}`, `{"nav_style":"normal"}`, `{"nav_underline":"wavy"}`} {
+ th = ParseTheme([]byte(raw))
+ if !th.NavBold || th.NavUpper || th.NavUnderline != "hover" || th.NavClass() != "nav-ul-hover nav-bold" {
+ t.Errorf("%s: %+v %q", raw, th, th.NavClass())
+ }
+ }
+ // New saves carry the switches: a stored false stays false, and unticked boxes are off.
+ if th = ParseTheme([]byte(`{"nav_bold":false,"nav_upper":true,"nav_underline":"never"}`)); th.NavBold || !th.NavUpper || th.NavUnderline != "never" {
+ t.Errorf("stored switches: %+v", th)
+ }
+ th = ThemeFromForm(DefaultTheme(), url.Values{"nav_underline": {"always"}})
+ if th.NavBold || th.NavUpper || th.NavClass() != "nav-ul-always" {
+ t.Errorf("from form: %+v %q", th, th.NavClass())
+ }
+}
+
// Announcements on post pages are on by default (the look blogs had before
// the switch) and, like every checkbox, off when the box is unticked; stored
// themes from before the option get the default too.