diff --git a/assets/diary/diary-day.html b/assets/diary/diary-day.html index 834e260..05c4b84 100644 --- a/assets/diary/diary-day.html +++ b/assets/diary/diary-day.html @@ -1,5 +1,5 @@ {{if .Photos}} -
+
{{range .Photos}} {{.Name}} {{end}} diff --git a/assets/diary/diary-month.html b/assets/diary/diary-month.html index 526d325..7ef6988 100644 --- a/assets/diary/diary-month.html +++ b/assets/diary/diary-month.html @@ -1,16 +1,14 @@ {{range .Days}} -
-

- {{if .URL}}{{.Heading}}{{else}}{{.Heading}}{{end}} - {{if .EditURL}}edit{{end}} -

- {{if .Content}}
{{.Content}}
{{end}} - {{if .Photos}} -
- {{range .Photos}} - {{.Name}} - {{end}} -
+

+ {{if .URL}}{{.Heading}}{{else}}{{.Heading}}{{end}} + {{if .EditURL}}edit{{end}} +

+{{if .Content}}{{.Content}}{{end}} +{{if .Photos}} +
+ {{range .Photos}} + {{.Name}} {{end}}
{{end}} +{{end}} diff --git a/assets/diary/diary-year.html b/assets/diary/diary-year.html index 8e0cc17..263677f 100644 --- a/assets/diary/diary-year.html +++ b/assets/diary/diary-year.html @@ -1,8 +1,7 @@ +

Months

{{range .Months}} -
-

- {{.Name}} - {{if .PhotoCount}}({{.PhotoCount}} photos){{end}} -

-
+

+ {{.Name}} + {{if .PhotoCount}}({{.PhotoCount}} photos){{end}} +

{{end}} diff --git a/assets/page.html b/assets/page.html index 6176607..5f28b1f 100644 --- a/assets/page.html +++ b/assets/page.html @@ -61,10 +61,11 @@
{{.Content}}
{{end}} {{if .SpecialContent}} -
{{.SpecialContent}}
+
{{.SpecialContent}}
{{end}} {{if or .Content .SpecialContent}} + {{end}} {{if .Content}} diff --git a/assets/sections.js b/assets/sections.js index 10382e2..09b05c3 100644 --- a/assets/sections.js +++ b/assets/sections.js @@ -9,7 +9,7 @@ headings.forEach(function (h, i) { var a = document.createElement('a'); a.href = '?edit§ion=' + (i + 1); - a.className = 'btn btn-small section-edit'; + a.className = 'btn btn-small'; a.textContent = 'edit'; h.appendChild(a); }); diff --git a/assets/style.css b/assets/style.css index a7fae4b..7e78eec 100644 --- a/assets/style.css +++ b/assets/style.css @@ -325,59 +325,32 @@ textarea { box-sizing: border-box; } -/* === Diary views === */ -.diary-section { - margin: 2rem 0; - padding-top: 1.5rem; - border-top: 1px dashed var(--secondary); -} - -.diary-section:first-child { - border-top: none; - padding-top: 0; - margin-top: 0; -} - -.diary-section h2 { - font-size: 1.2rem; - color: var(--text); - margin-bottom: 0.75rem; - font-weight: normal; -} - -.diary-photo-count { +/* === Muted text === */ +.muted { color: var(--text-muted); font-size: 0.85rem; } -.diary-photo-grid { +/* === Photo grid === */ +.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 0.4rem; margin-top: 0.75rem; } -.diary-photo-grid a { +.photo-grid a { display: block; line-height: 0; } -.diary-photo-grid img { +.photo-grid img { width: 100%; height: 140px; object-fit: cover; display: block; } -.diary-section .content { - margin-bottom: 0.75rem; -} - -/* === Section edit links === */ -.section-edit { - margin-left: 0.75rem; -} - /* === Empty state === */ .empty { padding: 1rem; @@ -405,7 +378,99 @@ hr { background: var(--primary-hover); } +/* === Table of contents === */ +.toc { + position: fixed; + top: 1rem; + right: 1rem; + width: 14rem; + max-height: calc(100vh - 2rem); + overflow-y: auto; + border: 1px solid var(--secondary); + background: var(--bg); + padding: 0.5rem 0.75rem; + font-size: 0.85rem; +} + +.toc-header { + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-muted); + border-bottom: 1px dashed var(--secondary); + padding-bottom: 0.25rem; + margin-bottom: 0.4rem; +} + +.toc ul { + list-style: none; + margin: 0; + padding: 0; +} + +.toc li { + margin: 0.15rem 0; +} + +.toc a { + color: var(--link); + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.toc a:hover { + color: var(--link-hover); +} + +.toc-h2 { + padding-left: 0.8rem; +} +.toc-h3 { + padding-left: 1.6rem; +} + +.toc-toggle { + display: none; +} + /* === Responsive === */ +@media (max-width: 1100px) { + .toc-toggle { + display: block; + background: none; + border: 1px solid var(--secondary); + color: var(--text); + font: inherit; + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.05em; + cursor: pointer; + padding: 0.4rem 0.75rem; + margin: 1rem auto; + width: calc(100% - 2rem); + max-width: 860px; + } + .toc-toggle::before { + content: "▸ "; + color: var(--secondary); + } + .toc-toggle[aria-expanded="true"]::before { + content: "▾ "; + } + .toc { + position: static; + display: none; + width: calc(100% - 2rem); + max-width: 860px; + margin: 0 auto 1rem; + max-height: none; + } + .toc.is-open { + display: block; + } +} + @media (max-width: 600px) { header { padding: 0.5rem 0.75rem; @@ -416,4 +481,8 @@ hr { textarea { min-height: 50vh; } + .toc-toggle, + .toc.is-open { + width: calc(100% - 1.5rem); + } } diff --git a/assets/toc.js b/assets/toc.js new file mode 100644 index 0000000..c797477 --- /dev/null +++ b/assets/toc.js @@ -0,0 +1,57 @@ +(function () { + var content = document.querySelector("main"); + if (!content) return; + + var headings = content.querySelectorAll("h1, h2, h3"); + if (headings.length < 2) return; + + var nav = document.createElement("nav"); + nav.className = "toc"; + + var header = document.createElement("div"); + header.className = "toc-header"; + header.textContent = "Contents"; + nav.appendChild(header); + + var list = document.createElement("ul"); + headings.forEach(function (h) { + if (!h.id) return; + var li = document.createElement("li"); + li.className = "toc-" + h.tagName.toLowerCase(); + var a = document.createElement("a"); + a.href = "#" + h.id; + a.textContent = h.textContent; + li.appendChild(a); + list.appendChild(li); + }); + nav.appendChild(list); + + var toggle = document.createElement("button"); + toggle.type = "button"; + toggle.className = "toc-toggle"; + toggle.textContent = "Contents"; + toggle.setAttribute("aria-expanded", "false"); + toggle.addEventListener("click", function () { + var open = nav.classList.toggle("is-open"); + toggle.setAttribute("aria-expanded", open ? "true" : "false"); + }); + + var main = document.querySelector("main"); + if (main) { + main.parentNode.insertBefore(toggle, main); + main.parentNode.insertBefore(nav, main); + } else { + document.body.appendChild(toggle); + document.body.appendChild(nav); + } + + var pageHeader = document.querySelector("header"); + function updateTop() { + if (!pageHeader || getComputedStyle(nav).position !== "fixed") return; + var rect = pageHeader.getBoundingClientRect(); + nav.style.top = Math.max(8, rect.bottom + 8) + "px"; + } + window.addEventListener("scroll", updateTop, { passive: true }); + window.addEventListener("resize", updateTop); + updateTop(); +})(); diff --git a/diary.go b/diary.go index 9b22306..5c85c29 100644 --- a/diary.go +++ b/diary.go @@ -69,12 +69,14 @@ type diaryPhoto struct { } type diaryMonthSummary struct { + ID string Name string URL string PhotoCount int } type diaryDaySection struct { + ID string Heading string URL string EditURL string @@ -82,7 +84,10 @@ type diaryDaySection struct { Photos []diaryPhoto } -type diaryYearData struct{ Months []diaryMonthSummary } +type diaryYearData struct { + Months []diaryMonthSummary + Year int +} type diaryMonthData struct{ Days []diaryDaySection } type diaryDayData struct{ Photos []diaryPhoto } @@ -191,6 +196,7 @@ func renderDiaryYear(fsPath, urlPath string) template.HTML { } monthDate := time.Date(year, time.Month(monthNum), 1, 0, 0, 0, 0, time.UTC) months = append(months, diaryMonthSummary{ + ID: monthDate.Format("2006-01"), Name: monthDate.Format("January 2006"), URL: path.Join(urlPath, e.Name()) + "/", PhotoCount: count, @@ -198,7 +204,7 @@ func renderDiaryYear(fsPath, urlPath string) template.HTML { } var buf bytes.Buffer - if err := diaryYearTmpl.Execute(&buf, diaryYearData{Months: months}); err != nil { + if err := diaryYearTmpl.Execute(&buf, diaryYearData{Months: months, Year: year}); err != nil { log.Printf("diary year template: %v", err) return "" } @@ -280,6 +286,7 @@ func renderDiaryMonth(fsPath, urlPath string) template.HTML { } sections = append(sections, diaryDaySection{ + ID: date.Format("2006-01-02"), Heading: heading, URL: dayURL, EditURL: dayURL + "?edit",