From 5844a870ceac73f8fb12fe0fd5dd8d7f0bf0eb85 Mon Sep 17 00:00:00 2001 From: luxick Date: Thu, 28 May 2026 21:34:46 +0200 Subject: [PATCH] Cleanup --- README.md | 37 +++++++++++++++++++----------- assets/page/sections.js | 4 +--- diary.go | 51 ++++++++++++++++------------------------- 3 files changed, 44 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index a5be82b..0cd1c36 100644 --- a/README.md +++ b/README.md @@ -52,36 +52,45 @@ type = diary ### Diary -Designed for a chronological photo diary. Expected structure: +Designed for a chronological photo diary. The whole year lives in a single +file as ISO-headed sections; photos are loose JPEGs named with a date prefix. ``` FolderName/ .page-settings ← type = diary YYYY/ - YYYY-MM-DD Desc.jpg ← photos named with date prefix - MM/ - DD/ - index.md ← diary entry for that day + index.md ← `# YYYY` + `## YYYY-MM` + `### YYYY-MM-DD` sections + YYYY-MM-DD Desc.jpg ← photos named with the date they belong to ``` -| View | What renders | -|------|-------------| -| Year (`YYYY/`) | Section per month with link and photo count | -| Month (`MM/`) | Section per day with entry content and photo grid | -| Day (`DD/`) | Entry content and photo grid | +The year page (`YYYY/`) renders every section in the file with photos +attached to each `### YYYY-MM-DD` heading. Months and days the file doesn't +yet contain are rendered as **virtual** headings with an `[edit]` button that +splices a new section into the year file at the right chronological position; +virtual day headings still carry photos for that date. Past years render +every month/day slot; the current year stops at today; future years skip +virtual entries entirely. The file may contain non-date headings (e.g. +`## Events` → `### Festival` between `# YYYY` and `## YYYY-01`); these keep +their document position. -Days with photos but no `index.md` still appear in the month view and can be created by clicking their heading link. +A sidebar calendar widget shows one month grid at a time; the month-name +button opens a dropdown of all twelve months, and a separate year dropdown +jumps between years. Day cells link to the matching anchor on the year page +regardless of whether the date has a real section yet. #### Persistent date links -Each diary root exposes three stable paths intended for browser bookmarks. They redirect to the current dated URL on every visit: +Each diary root exposes three stable paths intended for browser bookmarks. +They resolve against the year page rather than separate per-day URLs: | Path | Redirects to | |------|-------------| -| `/today/` | `/YYYY/MM/DD/` (or `…/?edit` if the day folder does not exist yet) | -| `/this-month/` | `/YYYY/MM/` | +| `/today/` | `/YYYY/#YYYY-MM-DD` (or the year file's insert-section editor when today's section doesn't exist yet) | +| `/this-month/` | `/YYYY/#YYYY-MM` | | `/this-year/` | `/YYYY/` | +Legacy `YYYY/MM/` and `YYYY/MM/DD/` URLs (no longer the canonical form) redirect to the matching anchor on the year page. + ## Quick-Add Bookmarklet Replace `wiki.host` with your wiki host and `/Topics/Bookmarks/` with the destination page (one bookmarklet per target): diff --git a/assets/page/sections.js b/assets/page/sections.js index 00342a8..9dae812 100644 --- a/assets/page/sections.js +++ b/assets/page/sections.js @@ -6,8 +6,7 @@ // Section 0 is pre-heading content, editable via full-page edit. // Sections 1..N each start at a heading; that is the index sent to the server. - // Skip headings that already carry a server-rendered edit link (the diary - // slice templates bake their own edit URLs pointing at the year file). + // Skip headings that already carry a server-rendered edit link. headings.forEach(function (h, i) { if (h.querySelector('a.btn')) return; var a = document.createElement('a'); @@ -18,4 +17,3 @@ h.appendChild(a); }); }()); -`` diff --git a/diary.go b/diary.go index ed4a5ef..24723a2 100644 --- a/diary.go +++ b/diary.go @@ -71,14 +71,13 @@ func (d *diaryHandler) dateShortcutRedirect(root, fsPath, urlPath string) (strin switch base { case "today": dayHeading := fmt.Sprintf("%s-%s-%s", year, month, day) - if dayHeadingExists(diaryRootFS, year, dayHeading) { + raw, _ := os.ReadFile(filepath.Join(diaryRootFS, year, "index.md")) + sections := splitSections(raw) + if _, found := findSectionIndex(sections, dayHeading); found { return yearURL + "#" + dayHeading, true } // Missing day: route through the insert flow so today's section // is spliced in at the right chronological position. - yearFS := filepath.Join(diaryRootFS, year) - raw, _ := os.ReadFile(filepath.Join(yearFS, "index.md")) - sections := splitSections(raw) insertIdx := computeInsertIndex(sections, dayHeading) return fmt.Sprintf("%s?edit&insert_before=%d&heading=%s", yearURL, insertIdx, url.QueryEscape(dayHeading)), true @@ -245,14 +244,14 @@ func findSectionIndex(sections [][]byte, target string) (int, bool) { return 0, false } -// computeInsertIndex returns the index at which a new day section with the -// given target date heading (YYYY-MM-DD) should be spliced in to keep date -// sections chronologically ordered. Only date-format headings — `YYYY`, -// `YYYY-MM`, or `YYYY-MM-DD` — participate in the comparison; non-date -// headings (e.g. `### Movies` in a year intro) are skipped so the new day -// is placed relative to the surrounding date sections, not the intro. -// Falls back to len(sections) when target is greater than every date -// heading. String comparison works for ISO dates. +// computeInsertIndex returns the section index at which a new date heading +// (target = `YYYY-MM` or `YYYY-MM-DD`) should be spliced in to keep date +// sections chronologically ordered. Only date-format headings participate in +// the comparison; non-date headings (e.g. `## Events` in a year intro) are +// skipped so the new section is placed relative to the surrounding date +// sections, not the intro. Falls back to len(sections) when target is +// greater than every date heading. ISO formatting means string comparison +// is equivalent to chronological order. func computeInsertIndex(sections [][]byte, target string) int { for i := 1; i < len(sections); i++ { _, text := sectionHeading(sections[i]) @@ -297,18 +296,6 @@ func isDateHeading(text string) bool { return true } -// dayHeadingExists reads the year file and reports whether a `### date` -// section exists with the given heading text (e.g. "2026-05-28"). -func dayHeadingExists(diaryRootFS, year, dateText string) bool { - raw, err := os.ReadFile(filepath.Join(diaryRootFS, year, "index.md")) - if err != nil { - return false - } - sections := splitSections(raw) - _, ok := findSectionIndex(sections, dateText) - return ok -} - // daysWithEntriesByMonth returns a `month → set[day]` map of `### YYYY-MM-DD` // sections in the year's index.md. Used by the calendar widget to populate // all 12 month grids in a single file read. @@ -652,8 +639,9 @@ func sectionBody(section []byte) template.HTML { return renderMarkdown(body) } -// renderDiaryYear renders the full year file with photos attached to each -// `### YYYY-MM-DD` section. +// renderDiaryYear renders the year page: every section from the year file +// (with photos attached to `### YYYY-MM-DD` headings) plus virtual entries +// for every month/day slot the file doesn't yet contain. func renderDiaryYear(yearFS, yearURL string) template.HTML { year, err := strconv.Atoi(filepath.Base(yearFS)) if err != nil { @@ -663,16 +651,17 @@ func renderDiaryYear(yearFS, yearURL string) template.HTML { sections := splitSections(raw) photos := yearPhotos(yearFS, yearURL) - out := buildSectionsForRange(sections, photos, 1, len(sections), yearURL) + out := buildFileSections(sections, photos, yearURL) out = appendVirtualEntries(out, sections, photos, year, yearURL) return renderDiaryContent(out) } -// buildSectionsForRange converts raw splitSections entries in [start, end) -// into rendered diarySection entries, attaching photos to day sections. -func buildSectionsForRange(sections [][]byte, photos []diaryPhoto, start, end int, yearURL string) []diarySection { +// buildFileSections converts the year file's sections (skipping the +// pre-heading section 0) into rendered diarySection entries. Photos are +// attached to level-3 headings whose text parses as `YYYY-MM-DD`. +func buildFileSections(sections [][]byte, photos []diaryPhoto, yearURL string) []diarySection { var out []diarySection - for i := start; i < end; i++ { + for i := 1; i < len(sections); i++ { level, text := sectionHeading(sections[i]) if level == 0 { continue