- commit
- add3382
- parent
- e16c902
- author
- ChloƩ Vulquin
- date
- 2024-03-05 22:42:02 +0000 UTC
prose: infer title from first <h1> before any <p> (#93) This eliminates the need to use explicit title metadata for the vast majority of posts. This still allows explicitly setting a title via the frontmatter. If neither is found, the current logic goes on.
1 files changed,
+37,
-5
1@@ -12,9 +12,11 @@ import (
2 "github.com/yuin/goldmark"
3 highlighting "github.com/yuin/goldmark-highlighting"
4 meta "github.com/yuin/goldmark-meta"
5+ "github.com/yuin/goldmark/ast"
6 "github.com/yuin/goldmark/extension"
7 "github.com/yuin/goldmark/parser"
8 ghtml "github.com/yuin/goldmark/renderer/html"
9+ gtext "github.com/yuin/goldmark/text"
10 "go.abhg.dev/goldmark/anchor"
11 yaml "gopkg.in/yaml.v2"
12 )
13@@ -174,7 +176,6 @@ func ParseText(text string) (*ParsedText, error) {
14 Aliases: []string{},
15 },
16 }
17- var buf bytes.Buffer
18 hili := highlighting.NewHighlighting(
19 highlighting.WithFormatOptions(
20 html.WithLineNumbers(true),
21@@ -200,18 +201,41 @@ func ParseText(text string) (*ParsedText, error) {
22 ),
23 )
24 context := parser.NewContext()
25- if err := md.Convert([]byte(text), &buf, parser.WithContext(context)); err != nil {
26- return &parsed, err
27- }
28
29- parsed.Html = policy.Sanitize(buf.String())
30+ // we do the Parse/Render steps manually to get a chance to examine the AST
31+ btext := []byte(text)
32+ doc := md.Parser().Parse(gtext.NewReader(btext), parser.WithContext(context))
33 metaData := meta.Get(context)
34
35+ // title:
36+ // 1. if specified in frontmatter, use that
37 title, err := toString(metaData["title"])
38 if err != nil {
39 return &parsed, fmt.Errorf("front-matter field (%s): %w", "title", err)
40 }
41 parsed.MetaData.Title = title
42+ // 2. If an <h1> is found before a <p> or other heading is found, use that
43+ if parsed.MetaData.Title == "" {
44+ err = ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
45+ if n.Kind() == ast.KindHeading {
46+ if h := n.(*ast.Heading); h.Level == 1 {
47+ parsed.MetaData.Title = string(h.Text(btext))
48+ p := h.Parent()
49+ p.RemoveChild(p, n)
50+ }
51+ return ast.WalkStop, nil
52+ }
53+ if ast.IsParagraph(n) {
54+ return ast.WalkStop, nil
55+ }
56+ return ast.WalkContinue, nil
57+ })
58+ if err != nil {
59+ panic(err) // unreachable
60+ }
61+ }
62+ // 3. else, set it to nothing (slug should get used later down the line)
63+ // this is implicit since it's already ""
64
65 description, err := toString(metaData["description"])
66 if err != nil {
67@@ -284,5 +308,13 @@ func ParseText(text string) (*ParsedText, error) {
68 }
69 parsed.MetaData.Tags = tags
70
71+ // Rendering happens last to allow any of the previous steps to manipulate
72+ // the AST.
73+ var buf bytes.Buffer
74+ if err := md.Renderer().Render(&buf, btext, doc); err != nil {
75+ return &parsed, err
76+ }
77+ parsed.Html = policy.Sanitize(buf.String())
78+
79 return &parsed, nil
80 }