GoBlog/markdown.go

194 lines
4.9 KiB
Go
Raw Normal View History

2020-07-28 19:38:12 +00:00
package main
import (
"bytes"
"io"
2020-10-18 10:10:00 +00:00
2021-05-22 16:21:25 +00:00
marktag "git.jlel.se/jlelse/goldmark-mark"
2020-07-28 19:38:12 +00:00
"github.com/yuin/goldmark"
2020-10-18 10:10:00 +00:00
emoji "github.com/yuin/goldmark-emoji"
2020-09-21 14:53:20 +00:00
"github.com/yuin/goldmark/ast"
2020-07-28 19:38:12 +00:00
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
2020-09-21 14:53:20 +00:00
"github.com/yuin/goldmark/renderer"
2020-07-28 19:38:12 +00:00
"github.com/yuin/goldmark/renderer/html"
2020-09-21 14:53:20 +00:00
"github.com/yuin/goldmark/util"
2022-03-10 10:56:17 +00:00
"go.goblog.app/app/pkgs/highlighting"
"go.goblog.app/app/pkgs/htmlbuilder"
2020-07-28 19:38:12 +00:00
)
func (a *goBlog) initMarkdown() {
if a.md != nil && a.absoluteMd != nil && a.titleMd != nil {
// Already initialized
return
}
defaultGoldmarkOptions := []goldmark.Option{
2020-07-28 19:38:12 +00:00
goldmark.WithRendererOptions(
html.WithUnsafe(),
),
goldmark.WithParserOptions(
parser.WithAutoHeadingID(),
),
goldmark.WithExtensions(
2020-10-18 10:10:00 +00:00
extension.Table,
extension.Strikethrough,
2020-07-28 19:38:12 +00:00
extension.Footnote,
extension.Typographer,
2020-11-28 08:02:12 +00:00
extension.Linkify,
2021-05-22 16:21:25 +00:00
marktag.Mark,
2021-05-24 17:34:36 +00:00
emoji.Emoji,
2022-03-10 10:56:17 +00:00
highlighting.Highlighting,
2020-07-28 19:38:12 +00:00
),
}
2021-08-05 06:09:34 +00:00
publicAddress := ""
if srv := a.cfg.Server; srv != nil {
publicAddress = srv.PublicAddress
}
a.md = goldmark.New(append(defaultGoldmarkOptions, goldmark.WithExtensions(&customExtension{
absoluteLinks: false,
2021-08-05 06:09:34 +00:00
publicAddress: publicAddress,
}))...)
a.absoluteMd = goldmark.New(append(defaultGoldmarkOptions, goldmark.WithExtensions(&customExtension{
absoluteLinks: true,
2021-08-05 06:09:34 +00:00
publicAddress: publicAddress,
}))...)
2021-08-04 21:26:38 +00:00
a.titleMd = goldmark.New(
goldmark.WithParser(
// Override, no need for special Markdown parsers
2021-08-04 21:26:38 +00:00
parser.NewParser(
parser.WithBlockParsers(
util.Prioritized(parser.NewParagraphParser(), 1000)),
2021-08-05 06:09:34 +00:00
parser.WithInlineParsers(),
parser.WithParagraphTransformers(),
2021-08-04 21:26:38 +00:00
),
),
goldmark.WithExtensions(
extension.Typographer,
emoji.Emoji,
),
)
2020-07-28 19:38:12 +00:00
}
func (a *goBlog) renderMarkdownToWriter(w io.Writer, source string, absoluteLinks bool) (err error) {
if absoluteLinks {
err = a.absoluteMd.Convert([]byte(source), w)
} else {
err = a.md.Convert([]byte(source), w)
}
return err
2020-07-28 19:38:12 +00:00
}
2020-09-18 11:11:25 +00:00
func (a *goBlog) renderText(s string) string {
2021-09-07 20:16:28 +00:00
if s == "" {
return ""
}
pr, pw := io.Pipe()
go func() {
_ = pw.CloseWithError(a.renderMarkdownToWriter(pw, s, false))
}()
text, err := htmlTextFromReader(pr)
_ = pr.CloseWithError(err)
if err != nil {
return ""
}
2022-02-11 23:48:59 +00:00
return text
}
2021-08-04 21:26:38 +00:00
func (a *goBlog) renderMdTitle(s string) string {
2021-09-07 20:16:28 +00:00
if s == "" {
return ""
}
pr, pw := io.Pipe()
go func() {
_ = pw.CloseWithError(a.titleMd.Convert([]byte(s), pw))
}()
text, err := htmlTextFromReader(pr)
_ = pr.CloseWithError(err)
if err != nil {
2021-08-04 21:26:38 +00:00
return ""
}
2022-02-11 23:48:59 +00:00
return text
2021-08-04 21:26:38 +00:00
}
2020-09-21 14:53:20 +00:00
// Extensions etc...
// Links
type customExtension struct {
publicAddress string
2022-02-18 15:35:53 +00:00
absoluteLinks bool
}
2020-09-21 14:53:20 +00:00
2020-09-21 15:05:50 +00:00
func (l *customExtension) Extend(m goldmark.Markdown) {
2020-09-21 14:53:20 +00:00
m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(&customRenderer{
absoluteLinks: l.absoluteLinks,
publicAddress: l.publicAddress,
}, 500),
2020-09-21 14:53:20 +00:00
))
}
type customRenderer struct {
publicAddress string
2022-02-18 15:35:53 +00:00
absoluteLinks bool
}
2020-09-21 14:53:20 +00:00
2020-09-21 15:05:50 +00:00
func (c *customRenderer) RegisterFuncs(r renderer.NodeRendererFuncRegisterer) {
r.Register(ast.KindLink, c.renderLink)
r.Register(ast.KindImage, c.renderImage)
2020-09-21 14:53:20 +00:00
}
2020-09-21 15:05:50 +00:00
func (c *customRenderer) renderLink(w util.BufWriter, _ []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
2020-09-21 14:53:20 +00:00
if entering {
2020-12-14 21:05:54 +00:00
n := node.(*ast.Link)
_, _ = w.WriteString("<a href=\"")
2020-09-21 14:53:20 +00:00
// Make URL absolute if it's relative
2020-12-14 21:05:54 +00:00
newDestination := util.URLEscape(n.Destination, true)
if c.absoluteLinks && c.publicAddress != "" && (bytes.HasPrefix(newDestination, []byte("/")) || bytes.HasPrefix(newDestination, []byte("#"))) {
_, _ = w.Write(util.EscapeHTML([]byte(c.publicAddress)))
2020-09-21 14:53:20 +00:00
}
2020-12-14 21:05:54 +00:00
_, _ = w.Write(util.EscapeHTML(newDestination))
_, _ = w.WriteRune('"')
2020-09-21 14:53:20 +00:00
// Open external links (links that start with "http") in new tab
2021-06-14 14:29:22 +00:00
if isAbsoluteURL(string(n.Destination)) {
2020-09-21 14:53:20 +00:00
_, _ = w.WriteString(` target="_blank" rel="noopener"`)
}
// Title
if n.Title != nil {
2020-12-14 21:05:54 +00:00
_, _ = w.WriteString(" title=\"")
2020-09-21 14:53:20 +00:00
_, _ = w.Write(n.Title)
2020-12-14 21:05:54 +00:00
_, _ = w.WriteRune('"')
2020-09-21 14:53:20 +00:00
}
2020-12-14 21:05:54 +00:00
_, _ = w.WriteRune('>')
2020-09-21 14:53:20 +00:00
} else {
_, _ = w.WriteString("</a>")
}
return ast.WalkContinue, nil
}
2020-09-21 15:05:50 +00:00
func (c *customRenderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
n := node.(*ast.Image)
2022-02-11 23:48:59 +00:00
dest := string(n.Destination)
// Make destination absolute if it's relative
if c.absoluteLinks && c.publicAddress != "" {
resolved, err := resolveURLReferences(c.publicAddress, dest)
if err != nil {
return ast.WalkStop, err
}
if len(resolved) > 0 {
dest = resolved[0]
}
2020-09-21 15:05:50 +00:00
}
hb := htmlbuilder.NewHtmlBuilder(w)
hb.WriteElementOpen("a", "href", dest)
2022-03-16 07:28:03 +00:00
imgEls := []any{"src", dest, "alt", string(n.Text(source)), "loading", "lazy"}
2022-02-11 23:48:59 +00:00
if len(n.Title) > 0 {
imgEls = append(imgEls, "title", string(n.Title))
2020-09-21 15:05:50 +00:00
}
hb.WriteElementOpen("img", imgEls...)
hb.WriteElementClose("a")
2020-09-21 15:05:50 +00:00
return ast.WalkSkipChildren, nil
}