Rework diary to focus on yearly pages
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user