Cleanup
This commit is contained in:
@@ -52,36 +52,45 @@ type = diary
|
|||||||
|
|
||||||
### 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/
|
FolderName/
|
||||||
.page-settings ← type = diary
|
.page-settings ← type = diary
|
||||||
YYYY/
|
YYYY/
|
||||||
YYYY-MM-DD Desc.jpg ← photos named with date prefix
|
index.md ← `# YYYY` + `## YYYY-MM` + `### YYYY-MM-DD` sections
|
||||||
MM/
|
YYYY-MM-DD Desc.jpg ← photos named with the date they belong to
|
||||||
DD/
|
|
||||||
index.md ← diary entry for that day
|
|
||||||
```
|
```
|
||||||
|
|
||||||
| View | What renders |
|
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
|
||||||
| Year (`YYYY/`) | Section per month with link and photo count |
|
yet contain are rendered as **virtual** headings with an `[edit]` button that
|
||||||
| Month (`MM/`) | Section per day with entry content and photo grid |
|
splices a new section into the year file at the right chronological position;
|
||||||
| Day (`DD/`) | Entry content and photo grid |
|
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
|
#### 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 |
|
| Path | Redirects to |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `<diary>/today/` | `<diary>/YYYY/MM/DD/` (or `…/?edit` if the day folder does not exist yet) |
|
| `<diary>/today/` | `<diary>/YYYY/#YYYY-MM-DD` (or the year file's insert-section editor when today's section doesn't exist yet) |
|
||||||
| `<diary>/this-month/` | `<diary>/YYYY/MM/` |
|
| `<diary>/this-month/` | `<diary>/YYYY/#YYYY-MM` |
|
||||||
| `<diary>/this-year/` | `<diary>/YYYY/` |
|
| `<diary>/this-year/` | `<diary>/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
|
## Quick-Add Bookmarklet
|
||||||
|
|
||||||
Replace `wiki.host` with your wiki host and `/Topics/Bookmarks/` with the destination page (one bookmarklet per target):
|
Replace `wiki.host` with your wiki host and `/Topics/Bookmarks/` with the destination page (one bookmarklet per target):
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
|
|
||||||
// Section 0 is pre-heading content, editable via full-page edit.
|
// 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.
|
// 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
|
// Skip headings that already carry a server-rendered edit link.
|
||||||
// slice templates bake their own edit URLs pointing at the year file).
|
|
||||||
headings.forEach(function (h, i) {
|
headings.forEach(function (h, i) {
|
||||||
if (h.querySelector('a.btn')) return;
|
if (h.querySelector('a.btn')) return;
|
||||||
var a = document.createElement('a');
|
var a = document.createElement('a');
|
||||||
@@ -18,4 +17,3 @@
|
|||||||
h.appendChild(a);
|
h.appendChild(a);
|
||||||
});
|
});
|
||||||
}());
|
}());
|
||||||
``
|
|
||||||
|
|||||||
@@ -71,14 +71,13 @@ func (d *diaryHandler) dateShortcutRedirect(root, fsPath, urlPath string) (strin
|
|||||||
switch base {
|
switch base {
|
||||||
case "today":
|
case "today":
|
||||||
dayHeading := fmt.Sprintf("%s-%s-%s", year, month, day)
|
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
|
return yearURL + "#" + dayHeading, true
|
||||||
}
|
}
|
||||||
// Missing day: route through the insert flow so today's section
|
// Missing day: route through the insert flow so today's section
|
||||||
// is spliced in at the right chronological position.
|
// 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)
|
insertIdx := computeInsertIndex(sections, dayHeading)
|
||||||
return fmt.Sprintf("%s?edit&insert_before=%d&heading=%s",
|
return fmt.Sprintf("%s?edit&insert_before=%d&heading=%s",
|
||||||
yearURL, insertIdx, url.QueryEscape(dayHeading)), true
|
yearURL, insertIdx, url.QueryEscape(dayHeading)), true
|
||||||
@@ -245,14 +244,14 @@ func findSectionIndex(sections [][]byte, target string) (int, bool) {
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// computeInsertIndex returns the index at which a new day section with the
|
// computeInsertIndex returns the section index at which a new date heading
|
||||||
// given target date heading (YYYY-MM-DD) should be spliced in to keep date
|
// (target = `YYYY-MM` or `YYYY-MM-DD`) should be spliced in to keep date
|
||||||
// sections chronologically ordered. Only date-format headings — `YYYY`,
|
// sections chronologically ordered. Only date-format headings participate in
|
||||||
// `YYYY-MM`, or `YYYY-MM-DD` — participate in the comparison; non-date
|
// the comparison; non-date headings (e.g. `## Events` in a year intro) are
|
||||||
// headings (e.g. `### Movies` in a year intro) are skipped so the new day
|
// skipped so the new section is placed relative to the surrounding date
|
||||||
// is placed relative to the surrounding date sections, not the intro.
|
// sections, not the intro. Falls back to len(sections) when target is
|
||||||
// Falls back to len(sections) when target is greater than every date
|
// greater than every date heading. ISO formatting means string comparison
|
||||||
// heading. String comparison works for ISO dates.
|
// is equivalent to chronological order.
|
||||||
func computeInsertIndex(sections [][]byte, target string) int {
|
func computeInsertIndex(sections [][]byte, target string) int {
|
||||||
for i := 1; i < len(sections); i++ {
|
for i := 1; i < len(sections); i++ {
|
||||||
_, text := sectionHeading(sections[i])
|
_, text := sectionHeading(sections[i])
|
||||||
@@ -297,18 +296,6 @@ func isDateHeading(text string) bool {
|
|||||||
return true
|
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`
|
// 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
|
// sections in the year's index.md. Used by the calendar widget to populate
|
||||||
// all 12 month grids in a single file read.
|
// all 12 month grids in a single file read.
|
||||||
@@ -652,8 +639,9 @@ func sectionBody(section []byte) template.HTML {
|
|||||||
return renderMarkdown(body)
|
return renderMarkdown(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderDiaryYear renders the full year file with photos attached to each
|
// renderDiaryYear renders the year page: every section from the year file
|
||||||
// `### YYYY-MM-DD` section.
|
// (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 {
|
func renderDiaryYear(yearFS, yearURL string) template.HTML {
|
||||||
year, err := strconv.Atoi(filepath.Base(yearFS))
|
year, err := strconv.Atoi(filepath.Base(yearFS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -663,16 +651,17 @@ func renderDiaryYear(yearFS, yearURL string) template.HTML {
|
|||||||
sections := splitSections(raw)
|
sections := splitSections(raw)
|
||||||
photos := yearPhotos(yearFS, yearURL)
|
photos := yearPhotos(yearFS, yearURL)
|
||||||
|
|
||||||
out := buildSectionsForRange(sections, photos, 1, len(sections), yearURL)
|
out := buildFileSections(sections, photos, yearURL)
|
||||||
out = appendVirtualEntries(out, sections, photos, year, yearURL)
|
out = appendVirtualEntries(out, sections, photos, year, yearURL)
|
||||||
return renderDiaryContent(out)
|
return renderDiaryContent(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildSectionsForRange converts raw splitSections entries in [start, end)
|
// buildFileSections converts the year file's sections (skipping the
|
||||||
// into rendered diarySection entries, attaching photos to day sections.
|
// pre-heading section 0) into rendered diarySection entries. Photos are
|
||||||
func buildSectionsForRange(sections [][]byte, photos []diaryPhoto, start, end int, yearURL string) []diarySection {
|
// attached to level-3 headings whose text parses as `YYYY-MM-DD`.
|
||||||
|
func buildFileSections(sections [][]byte, photos []diaryPhoto, yearURL string) []diarySection {
|
||||||
var out []diarySection
|
var out []diarySection
|
||||||
for i := start; i < end; i++ {
|
for i := 1; i < len(sections); i++ {
|
||||||
level, text := sectionHeading(sections[i])
|
level, text := sectionHeading(sections[i])
|
||||||
if level == 0 {
|
if level == 0 {
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in New Issue
Block a user