add handling for parsing multiple headings as separate pages
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user