repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

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
M shared/mdparser.go
+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 }