feat(release): v0.3.0

commit 533ac4e58256e6520a86af964fcf4c2f9a98d4ba
Author: adminoo <git@kadath.corp>
Date:   Mon Feb 23 18:52:59 2026 +0100

    feat: freebsd release tarball generator

commit 874fb63fd0
Author: adminoo <git@kadath.corp>
Date:   Mon Feb 23 14:05:24 2026 +0100

    feat: bump changelog

commit 46ab7e2911
Author: adminoo <git@kadath.corp>
Date:   Mon Feb 23 13:58:14 2026 +0100

    feat: margin and page breaks

commit 44751a808a
Author: adminoo <git@kadath.corp>
Date:   Mon Feb 23 13:57:56 2026 +0100

    feat: picture are worth thousand words

commit a5683428e0
Author: adminoo <git@kadath.corp>
Date:   Mon Feb 23 13:39:00 2026 +0100

    feat: navigate individual sections

commit 0d9b7c4e7b
Author: adminoo <git@kadath.corp>
Date:   Mon Feb 23 13:38:19 2026 +0100

    feat: make use of vendoring
This commit is contained in:
2026-02-23 19:17:17 +01:00
parent b571588b15
commit 5fdcede6f8
11 changed files with 507 additions and 11 deletions

View File

@ -43,6 +43,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.handleTags(w, r)
return
}
if strings.Contains(path, "/sections/") {
h.handleSections(w, r)
return
}
h.handleNotes(w, r)
return
}
@ -185,8 +189,9 @@ func (h *Handler) handleNotes(w http.ResponseWriter, r *http.Request) {
return
}
// Convert markdown to HTML
htmlContent, err := render.RenderMarkdown([]byte(note.Content))
// Convert markdown to HTML, linking section headings
basePath := basePathFromRequest(r)
htmlContent, err := renderNoteMarkdown(note.Content, note.ID, basePath)
if err != nil {
http.Error(w, "Failed to render markdown", http.StatusInternalServerError)
return
@ -197,7 +202,7 @@ func (h *Handler) handleNotes(w http.ResponseWriter, r *http.Request) {
state.RenderedNote = htmlContent
state.LastActive = hash
// Ensure note view carries proxy prefix for links/forms.
state.BasePath = basePathFromRequest(r)
state.BasePath = basePath
if err := h.templates.Render(w, "index", state); err != nil {
log.Printf("render error: %v", err)
@ -211,7 +216,7 @@ func (h *Handler) setActiveNote(state *ViewState, noteID string) error {
return err
}
htmlContent, err := render.RenderMarkdown([]byte(note.Content))
htmlContent, err := renderNoteMarkdown(note.Content, note.ID, state.BasePath)
if err != nil {
return err
}
@ -357,3 +362,92 @@ func parseTagRoute(path string) (noteID string, tag string, isRemove bool) {
return noteID, "", false
}
func parseSectionRoute(path string) (noteID string, sectionID string) {
parts := strings.Split(strings.Trim(path, "/"), "/")
if len(parts) < 4 || parts[0] != "notes" || parts[2] != "sections" {
return "", ""
}
return parts[1], parts[3]
}
func (h *Handler) handleSections(w http.ResponseWriter, r *http.Request) {
// Build base state
state, err := h.buildViewState(r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
noteID, sectionID := parseSectionRoute(r.URL.Path)
if noteID == "" || sectionID == "" {
http.NotFound(w, r)
return
}
n, err := h.notesService.GetNoteByHash(noteID)
if err != nil {
http.Error(w, "Note not found", http.StatusNotFound)
return
}
var sectionContent string
for _, section := range note.ParseSections(n.Content) {
if section.ID == sectionID {
sectionContent = section.Content
break
}
}
if sectionContent == "" {
http.Error(w, "Section not found", http.StatusNotFound)
return
}
basePath := basePathFromRequest(r)
htmlContent, err := render.RenderMarkdown([]byte(sectionContent))
if err != nil {
http.Error(w, "Failed to render markdown", http.StatusInternalServerError)
return
}
state.Note = n
state.RenderedNote = htmlContent
state.LastActive = noteID
state.BasePath = basePath
if err := h.templates.Render(w, "index", state); err != nil {
log.Printf("render error: %v", err)
http.Error(w, "Render error", http.StatusInternalServerError)
}
}
func renderNoteMarkdown(content, noteID, basePath string) (template.HTML, error) {
linked := linkifySectionsMarkdown(content, noteID, basePath)
return render.RenderMarkdown([]byte(linked))
}
func linkifySectionsMarkdown(content, noteID, basePath string) string {
sections := note.ParseSections(content)
if len(sections) == 0 {
return content
}
lines := strings.Split(content, "\n")
sectionIdx := 0
for i, line := range lines {
heading, ok := note.ParseH2Heading(line)
if !ok {
continue
}
if sectionIdx >= len(sections) {
break
}
link := basePath + "/notes/" + noteID + "/sections/" + sections[sectionIdx].ID
lines[i] = "## [" + heading + "](" + link + ")"
sectionIdx++
}
return strings.Join(lines, "\n")
}