Rework diary to focus on yearly pages

This commit is contained in:
2026-05-28 13:54:44 +02:00
parent 20a6bac3d6
commit 61e50c033f
14 changed files with 641 additions and 402 deletions
+63 -26
View File
@@ -27,29 +27,28 @@ var (
// specialPage is the result returned by a pageTypeHandler.
// Content is injected into the page after the standard markdown content.
// SuppressContent hides the markdown-rendered content (handler owns rendering).
// SuppressListing hides the default file/folder listing.
// Widget is a persistent sidebar widget rendered outside the main content area.
type specialPage struct {
Content template.HTML
SuppressContent bool
SuppressListing bool
SuppressTOC bool
Widget template.HTML
}
// pageTypeHandler is implemented by each special folder type (diary, gallery, …).
// handle returns nil when the handler does not apply to the given path.
// redirect returns ok=true with an absolute URL when the request should be
// short-circuited with a 302 redirect (e.g. persistent date links in a diary).
// defaultHeading returns ok=true with custom pre-fill heading text for the
// editor when no index.md exists yet (e.g. German long-form date for a diary
// day). Handlers that don't need a hook should return the zero value.
// short-circuited with a 302 redirect (e.g. persistent date links in a diary,
// or virtual diary URLs in edit mode that delegate to the year file's editor).
//
// When adding a new hook, prefer a sibling method here over folding logic
// into main.go or render.go. If this list grows much beyond three, consider
// collapsing into a single overrides struct returned per request.
// into main.go or render.go.
type pageTypeHandler interface {
handle(root, fsPath, urlPath string) *specialPage
redirect(root, fsPath, urlPath string) (target string, ok bool)
defaultHeading(root, fsPath, urlPath string) (heading string, ok bool)
redirect(root, fsPath, urlPath string, r *http.Request) (target string, ok bool)
}
// pageTypeHandlers is the registry. Each type registers itself via init().
@@ -218,7 +217,7 @@ func (h *handler) serveDir(w http.ResponseWriter, r *http.Request, urlPath, fsPa
}
for _, ph := range pageTypeHandlers {
if target, ok := ph.redirect(h.root, fsPath, urlPath); ok {
if target, ok := ph.redirect(h.root, fsPath, urlPath, r); ok {
http.Redirect(w, r, target, http.StatusFound)
return
}
@@ -229,17 +228,18 @@ func (h *handler) serveDir(w http.ResponseWriter, r *http.Request, urlPath, fsPa
// Determine section index (-1 = whole page).
sectionIndex := -1
insertBefore := -1
if editMode {
if s := r.URL.Query().Get("section"); s != "" {
if n, err := strconv.Atoi(s); err == nil && n >= 0 {
sectionIndex = n
}
}
}
var rendered template.HTML
if len(rawMD) > 0 && !editMode {
rendered = renderMarkdown(rawMD)
if s := r.URL.Query().Get("insert_before"); s != "" {
if n, err := strconv.Atoi(s); err == nil && n >= 0 {
insertBefore = n
}
}
}
var special *specialPage
@@ -251,6 +251,11 @@ func (h *handler) serveDir(w http.ResponseWriter, r *http.Request, urlPath, fsPa
}
}
var rendered template.HTML
if len(rawMD) > 0 && !editMode && (special == nil || !special.SuppressContent) {
rendered = renderMarkdown(rawMD)
}
var entries []entry
if !editMode && (special == nil || !special.SuppressListing) {
entries = listEntries(fsPath, urlPath)
@@ -263,26 +268,32 @@ func (h *handler) serveDir(w http.ResponseWriter, r *http.Request, urlPath, fsPa
var specialContent template.HTML
var sidebarWidget template.HTML
suppressTOC := false
if special != nil {
specialContent = special.Content
sidebarWidget = special.Widget
suppressTOC = special.SuppressTOC
}
rawContent := string(rawMD)
if editMode && sectionIndex >= 0 {
if editMode && insertBefore >= 0 {
heading := r.URL.Query().Get("heading")
level := r.URL.Query().Get("level")
if level == "" {
level = "###"
}
if heading != "" {
rawContent = level + " " + heading + "\n\n"
} else {
rawContent = ""
}
} else if editMode && sectionIndex >= 0 {
sections := splitSections(rawMD)
if sectionIndex < len(sections) {
rawContent = string(sections[sectionIndex])
}
} else if editMode && rawContent == "" && urlPath != "/" {
heading := pageTitle(urlPath)
for _, ph := range pageTypeHandlers {
if custom, ok := ph.defaultHeading(h.root, fsPath, urlPath); ok {
heading = custom
break
}
}
rawContent = "# " + heading + "\n\n"
rawContent = "# " + pageTitle(urlPath) + "\n\n"
}
parent := ""
@@ -296,12 +307,14 @@ func (h *handler) serveDir(w http.ResponseWriter, r *http.Request, urlPath, fsPa
EditMode: editMode,
IsRoot: urlPath == "/",
SectionIndex: sectionIndex,
InsertBefore: insertBefore,
PostURL: urlPath,
RawContent: rawContent,
Content: rendered,
Entries: entries,
SpecialContent: specialContent,
SidebarWidget: sidebarWidget,
SuppressTOC: suppressTOC,
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
@@ -350,9 +363,33 @@ func (h *handler) handlePost(w http.ResponseWriter, r *http.Request, urlPath, fs
indexPath := filepath.Join(fsPath, "index.md")
redirectTarget := urlPath
// If a section index was submitted, splice the edited section back into
// the full file rather than replacing the whole document.
if s := r.FormValue("section"); s != "" {
// insert_before splices a new section into the file *at* index N rather
// than replacing index N (used by the diary "create new day" flow).
// section replaces the section at index N (used by per-section edits).
// Exactly one of insert_before / section should be set; insert_before
// wins if both are present.
if s := r.FormValue("insert_before"); s != "" {
insertIndex, err := strconv.Atoi(s)
if err != nil || insertIndex < 0 {
http.Error(w, "bad insert_before", http.StatusBadRequest)
return
}
rawMD, _ := os.ReadFile(indexPath)
sections := splitSections(rawMD)
if insertIndex > len(sections) {
insertIndex = len(sections)
}
newSection := []byte(content)
inserted := make([][]byte, 0, len(sections)+1)
inserted = append(inserted, sections[:insertIndex]...)
inserted = append(inserted, newSection)
inserted = append(inserted, sections[insertIndex:]...)
content = string(joinSections(inserted))
ids := headingIDs([]byte(content))
if insertIndex-1 >= 0 && insertIndex-1 < len(ids) {
redirectTarget = urlPath + "#" + ids[insertIndex-1]
}
} else if s := r.FormValue("section"); s != "" {
sectionIndex, err := strconv.Atoi(s)
if err != nil || sectionIndex < 0 {
http.Error(w, "bad section", http.StatusBadRequest)