add handling for parsing multiple headings as separate pages

This commit is contained in:
2026-01-04 05:58:02 +00:00
parent a8f6d5bfbf
commit dcf3b72215
+88 -31
View File
@@ -15,8 +15,17 @@ import (
"github.com/charmbracelet/bubbles/viewport" "github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour" "github.com/charmbracelet/glamour"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/text"
) )
type Section struct {
Title string
Content string
Hints []string
}
type ViewState int type ViewState int
const ( const (
@@ -28,10 +37,8 @@ const (
type NoteView struct { type NoteView struct {
// the actual content // the actual content
path string path string
title string // the first line is always # title
content string // strip the first line from file rest is content
rawContent string // full content for editing rawContent string // full content for editing
hints []string //a hint is anything that matches **text** or __text__ in content sections []Section
// display layer // display layer
err error err error
loading bool loading bool
@@ -49,10 +56,8 @@ type loadNote struct {
} }
type loadedNote struct { type loadedNote struct {
title string
content string
rawContent string rawContent string
hints []string sections []Section
err error err error
} }
@@ -143,10 +148,8 @@ func (m *NoteView) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case loadedNote: case loadedNote:
m.loading = false m.loading = false
m.title = msg.title
m.content = msg.content
m.rawContent = msg.rawContent m.rawContent = msg.rawContent
m.hints = msg.hints m.sections = msg.sections
m.err = msg.err m.err = msg.err
m.viewState = StateTitleOnly m.viewState = StateTitleOnly
m.vp.SetContent(m.renderNote()) m.vp.SetContent(m.renderNote())
@@ -188,30 +191,71 @@ func fetchContent(path string) tea.Cmd {
} }
rawContent := string(data) rawContent := string(data)
content := rawContent sections := parseSections(data)
lines := strings.Split(content, "\n")
var title string
// title
if len(lines) > 0 && strings.HasPrefix(lines[0], "# ") {
title = lines[0]
content = strings.Join(lines[1:], "\n")
}
// hints
hints := extractHints(content)
content = strings.TrimSpace(content)
return loadedNote{ return loadedNote{
title: title,
content: content,
rawContent: rawContent, rawContent: rawContent,
hints: hints, sections: sections,
} }
} }
} }
func parseSections(source []byte) []Section {
md := goldmark.New()
reader := text.NewReader(source)
doc := md.Parser().Parse(reader)
var sections []Section
var currentTitle string
var currentStart int = -1
for child := doc.FirstChild(); child != nil; child = child.NextSibling() {
if child.Kind() == ast.KindHeading {
heading := child.(*ast.Heading)
if currentStart != -1 {
headingStart := heading.Lines().At(0).Start
if headingStart > currentStart {
sectionContentRaw := string(source[currentStart:headingStart])
sectionContent := strings.TrimSpace(sectionContentRaw)
sections = append(sections, Section{
Title: currentTitle,
Content: sectionContent,
Hints: extractHints(sectionContent),
})
}
}
if heading.Lines().Len() > 0 {
start := heading.Lines().At(0).Start
stop := heading.Lines().At(heading.Lines().Len() - 1).Stop
currentTitle = string(source[start:stop])
currentStart = stop
} else {
currentTitle = string(heading.Text(source))
currentStart = -1
}
} else {
if currentStart == -1 {
currentStart = 0
}
}
}
if currentStart != -1 && currentStart < len(source) {
sectionContentRaw := string(source[currentStart:])
sectionContent := strings.TrimSpace(sectionContentRaw)
sections = append(sections, Section{
Title: currentTitle,
Content: sectionContent,
Hints: extractHints(sectionContent),
})
}
return sections
}
func saveContent(path, content string) tea.Cmd { func saveContent(path, content string) tea.Cmd {
return func() tea.Msg { return func() tea.Msg {
err := os.WriteFile(path, []byte(content), 0644) err := os.WriteFile(path, []byte(content), 0644)
@@ -237,9 +281,17 @@ func extractHints(content string) []string {
} }
func (m NoteView) renderNote() string { func (m NoteView) renderNote() string {
title, err1 := m.mdRenderer.Render(m.title) var titleText, contentText string
if len(m.sections) > 0 {
titleText = m.sections[0].Title
contentText = m.sections[0].Content
} else {
contentText = m.rawContent
}
title, err1 := m.mdRenderer.Render(titleText)
if err1 != nil { if err1 != nil {
title = m.title + "\n\n" title = titleText + "\n\n"
} }
var body string var body string
@@ -247,14 +299,19 @@ func (m NoteView) renderNote() string {
switch m.viewState { switch m.viewState {
case StateContent: case StateContent:
body, err2 = m.mdRenderer.Render(m.content) body, err2 = m.mdRenderer.Render(contentText)
case StateHints: case StateHints:
if len(m.hints) == 0 { var currentHints []string
if len(m.sections) > 0 {
currentHints = m.sections[0].Hints
}
if len(currentHints) == 0 {
body = "No hints available." body = "No hints available."
} else { } else {
// Format hints as a list // Format hints as a list
hintsList := "" hintsList := ""
for _, h := range m.hints { for _, h := range currentHints {
hintsList += "- " + h + "\n" hintsList += "- " + h + "\n"
} }
body, err2 = m.mdRenderer.Render(hintsList) body, err2 = m.mdRenderer.Render(hintsList)