Add persitent URLs for year/month/day in the diary
This commit is contained in:
@@ -69,3 +69,13 @@ FolderName/
|
|||||||
| Day (`DD/`) | Entry content and photo grid |
|
| Day (`DD/`) | Entry content and photo grid |
|
||||||
|
|
||||||
Days with photos but no `index.md` still appear in the month view and can be created by clicking their heading link.
|
Days with photos but no `index.md` still appear in the month view and can be created by clicking their heading link.
|
||||||
|
|
||||||
|
#### Persistent date links
|
||||||
|
|
||||||
|
Each diary root exposes three stable paths intended for browser bookmarks. They redirect to the current dated URL on every visit:
|
||||||
|
|
||||||
|
| Path | Redirects to |
|
||||||
|
|------|-------------|
|
||||||
|
| `<diary>/today/` | `<diary>/YYYY/MM/DD/` (or `…/?edit` if the day folder does not exist yet) |
|
||||||
|
| `<diary>/this-month/` | `<diary>/YYYY/MM/` |
|
||||||
|
| `<diary>/this-year/` | `<diary>/YYYY/` |
|
||||||
|
|||||||
@@ -21,6 +21,67 @@ func init() {
|
|||||||
|
|
||||||
type diaryHandler struct{}
|
type diaryHandler struct{}
|
||||||
|
|
||||||
|
// redirect resolves the persistent date links (today, this-month, this-year)
|
||||||
|
// inside any diary root to a dated URL. Returns ok=false otherwise.
|
||||||
|
func (d *diaryHandler) redirect(root, fsPath, urlPath string) (string, bool) {
|
||||||
|
base := path.Base(strings.TrimSuffix(urlPath, "/"))
|
||||||
|
switch base {
|
||||||
|
case "today", "this-month", "this-year":
|
||||||
|
default:
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
parentFS := filepath.Dir(fsPath)
|
||||||
|
parentURLPath := parentURL(urlPath)
|
||||||
|
_, diaryRootFS, diaryRootURL, ok := findDiaryContext(root, parentFS, parentURLPath)
|
||||||
|
if !ok {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
year := fmt.Sprintf("%d", now.Year())
|
||||||
|
month := fmt.Sprintf("%02d", int(now.Month()))
|
||||||
|
day := fmt.Sprintf("%02d", now.Day())
|
||||||
|
|
||||||
|
switch base {
|
||||||
|
case "today":
|
||||||
|
target := path.Join(diaryRootURL, year, month, day) + "/"
|
||||||
|
dayFS := filepath.Join(diaryRootFS, year, month, day)
|
||||||
|
if _, err := os.Stat(dayFS); err != nil {
|
||||||
|
target += "?edit"
|
||||||
|
}
|
||||||
|
return target, true
|
||||||
|
case "this-month":
|
||||||
|
return path.Join(diaryRootURL, year, month) + "/", true
|
||||||
|
case "this-year":
|
||||||
|
return path.Join(diaryRootURL, year) + "/", true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultHeading returns the German long-form date as the editor pre-fill
|
||||||
|
// heading for a diary day folder (depth 3 inside a diary root).
|
||||||
|
func (d *diaryHandler) defaultHeading(root, fsPath, urlPath string) (string, bool) {
|
||||||
|
depth, _, _, ok := findDiaryContext(root, fsPath, urlPath)
|
||||||
|
if !ok || depth != 3 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
day, err := strconv.Atoi(filepath.Base(fsPath))
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
monthFS := filepath.Dir(fsPath)
|
||||||
|
month, err := strconv.Atoi(filepath.Base(monthFS))
|
||||||
|
if err != nil || month < 1 || month > 12 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
year, err := strconv.Atoi(filepath.Base(filepath.Dir(monthFS)))
|
||||||
|
if err != nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return formatGermanDate(time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)), true
|
||||||
|
}
|
||||||
|
|
||||||
func (d *diaryHandler) handle(root, fsPath, urlPath string) *specialPage {
|
func (d *diaryHandler) handle(root, fsPath, urlPath string) *specialPage {
|
||||||
depth, diaryRootFS, diaryRootURL, ok := findDiaryContext(root, fsPath, urlPath)
|
depth, diaryRootFS, diaryRootURL, ok := findDiaryContext(root, fsPath, urlPath)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -37,8 +37,19 @@ type specialPage struct {
|
|||||||
|
|
||||||
// pageTypeHandler is implemented by each special folder type (diary, gallery, …).
|
// pageTypeHandler is implemented by each special folder type (diary, gallery, …).
|
||||||
// handle returns nil when the handler does not apply to the given path.
|
// 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.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
type pageTypeHandler interface {
|
type pageTypeHandler interface {
|
||||||
handle(root, fsPath, urlPath string) *specialPage
|
handle(root, fsPath, urlPath string) *specialPage
|
||||||
|
redirect(root, fsPath, urlPath string) (target string, ok bool)
|
||||||
|
defaultHeading(root, fsPath, urlPath string) (heading string, ok bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// pageTypeHandlers is the registry. Each type registers itself via init().
|
// pageTypeHandlers is the registry. Each type registers itself via init().
|
||||||
@@ -204,6 +215,13 @@ func (h *handler) serveDir(w http.ResponseWriter, r *http.Request, urlPath, fsPa
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, ph := range pageTypeHandlers {
|
||||||
|
if target, ok := ph.redirect(h.root, fsPath, urlPath); ok {
|
||||||
|
http.Redirect(w, r, target, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
indexPath := filepath.Join(fsPath, "index.md")
|
indexPath := filepath.Join(fsPath, "index.md")
|
||||||
rawMD, _ := os.ReadFile(indexPath)
|
rawMD, _ := os.ReadFile(indexPath)
|
||||||
|
|
||||||
@@ -255,7 +273,14 @@ func (h *handler) serveDir(w http.ResponseWriter, r *http.Request, urlPath, fsPa
|
|||||||
rawContent = string(sections[sectionIndex])
|
rawContent = string(sections[sectionIndex])
|
||||||
}
|
}
|
||||||
} else if editMode && rawContent == "" && urlPath != "/" {
|
} else if editMode && rawContent == "" && urlPath != "/" {
|
||||||
rawContent = "# " + pageTitle(urlPath) + "\n\n"
|
heading := pageTitle(urlPath)
|
||||||
|
for _, ph := range pageTypeHandlers {
|
||||||
|
if custom, ok := ph.defaultHeading(h.root, fsPath, urlPath); ok {
|
||||||
|
heading = custom
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rawContent = "# " + heading + "\n\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
data := pageData{
|
data := pageData{
|
||||||
|
|||||||
Reference in New Issue
Block a user