2020-07-28 19:38:12 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2021-06-18 12:32:03 +00:00
|
|
|
"html/template"
|
2022-01-26 11:02:12 +00:00
|
|
|
"io"
|
2020-10-18 10:10:00 +00:00
|
|
|
|
2021-05-22 16:21:25 +00:00
|
|
|
marktag "git.jlel.se/jlelse/goldmark-mark"
|
2021-11-15 20:48:16 +00:00
|
|
|
chromahtml "github.com/alecthomas/chroma/formatters/html"
|
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"
|
2021-11-15 20:48:16 +00:00
|
|
|
highlighting "github.com/yuin/goldmark-highlighting"
|
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-01-26 11:02:12 +00:00
|
|
|
"go.goblog.app/app/pkgs/bufferpool"
|
2020-07-28 19:38:12 +00:00
|
|
|
)
|
|
|
|
|
2021-06-06 12:39:42 +00:00
|
|
|
func (a *goBlog) initMarkdown() {
|
2021-11-14 07:40:19 +00:00
|
|
|
if a.md != nil && a.absoluteMd != nil && a.titleMd != nil {
|
|
|
|
// Already initialized
|
|
|
|
return
|
|
|
|
}
|
2020-12-23 13:36:27 +00:00
|
|
|
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,
|
2021-11-15 20:48:16 +00:00
|
|
|
highlighting.NewHighlighting(
|
2021-11-16 09:37:30 +00:00
|
|
|
highlighting.WithCustomStyle(chromaGoBlogStyle),
|
2021-11-15 20:48:16 +00:00
|
|
|
highlighting.WithFormatOptions(
|
|
|
|
chromahtml.ClassPrefix("c-"),
|
|
|
|
chromahtml.WithClasses(true),
|
|
|
|
),
|
|
|
|
),
|
2020-07-28 19:38:12 +00:00
|
|
|
),
|
2020-12-23 13:36:27 +00:00
|
|
|
}
|
2021-08-05 06:09:34 +00:00
|
|
|
publicAddress := ""
|
|
|
|
if srv := a.cfg.Server; srv != nil {
|
|
|
|
publicAddress = srv.PublicAddress
|
|
|
|
}
|
2021-06-06 12:39:42 +00:00
|
|
|
a.md = goldmark.New(append(defaultGoldmarkOptions, goldmark.WithExtensions(&customExtension{
|
|
|
|
absoluteLinks: false,
|
2021-08-05 06:09:34 +00:00
|
|
|
publicAddress: publicAddress,
|
2021-06-06 12:39:42 +00:00
|
|
|
}))...)
|
|
|
|
a.absoluteMd = goldmark.New(append(defaultGoldmarkOptions, goldmark.WithExtensions(&customExtension{
|
|
|
|
absoluteLinks: true,
|
2021-08-05 06:09:34 +00:00
|
|
|
publicAddress: publicAddress,
|
2021-06-06 12:39:42 +00:00
|
|
|
}))...)
|
2021-08-04 21:26:38 +00:00
|
|
|
a.titleMd = goldmark.New(
|
|
|
|
goldmark.WithParser(
|
2021-08-05 05:05:39 +00:00
|
|
|
// 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(),
|
2021-08-05 05:05:39 +00:00
|
|
|
parser.WithParagraphTransformers(),
|
2021-08-04 21:26:38 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
goldmark.WithExtensions(
|
|
|
|
extension.Typographer,
|
|
|
|
emoji.Emoji,
|
|
|
|
),
|
|
|
|
)
|
2020-07-28 19:38:12 +00:00
|
|
|
}
|
|
|
|
|
2021-06-06 12:39:42 +00:00
|
|
|
func (a *goBlog) renderMarkdown(source string, absoluteLinks bool) (rendered []byte, err error) {
|
2022-01-26 11:02:12 +00:00
|
|
|
buffer := bufferpool.Get()
|
2022-01-26 18:59:09 +00:00
|
|
|
err = a.renderMarkdownToWriter(buffer, source, absoluteLinks)
|
2022-01-26 11:02:12 +00:00
|
|
|
rendered = buffer.Bytes()
|
|
|
|
bufferpool.Put(buffer)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *goBlog) renderMarkdownToWriter(w io.Writer, source string, absoluteLinks bool) (err error) {
|
2020-12-23 13:36:27 +00:00
|
|
|
if absoluteLinks {
|
2022-01-26 11:02:12 +00:00
|
|
|
err = a.absoluteMd.Convert([]byte(source), w)
|
2020-12-23 13:36:27 +00:00
|
|
|
} else {
|
2022-01-26 11:02:12 +00:00
|
|
|
err = a.md.Convert([]byte(source), w)
|
2020-12-23 13:36:27 +00:00
|
|
|
}
|
2022-01-26 11:02:12 +00:00
|
|
|
return err
|
2020-07-28 19:38:12 +00:00
|
|
|
}
|
2020-09-18 11:11:25 +00:00
|
|
|
|
2021-06-18 12:32:03 +00:00
|
|
|
func (a *goBlog) renderMarkdownAsHTML(source string, absoluteLinks bool) (rendered template.HTML, err error) {
|
|
|
|
b, err := a.renderMarkdown(source, absoluteLinks)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return template.HTML(b), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *goBlog) safeRenderMarkdownAsHTML(source string) template.HTML {
|
|
|
|
h, _ := a.renderMarkdownAsHTML(source, false)
|
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
2021-06-06 12:39:42 +00:00
|
|
|
func (a *goBlog) renderText(s string) string {
|
2021-09-07 20:16:28 +00:00
|
|
|
if s == "" {
|
|
|
|
return ""
|
|
|
|
}
|
2021-06-06 12:39:42 +00:00
|
|
|
h, err := a.renderMarkdown(s, false)
|
2021-05-09 07:08:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
2021-09-01 09:14:49 +00:00
|
|
|
return htmlText(string(h))
|
2021-05-09 07:08:31 +00:00
|
|
|
}
|
|
|
|
|
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 ""
|
|
|
|
}
|
2022-01-26 11:02:12 +00:00
|
|
|
buffer := bufferpool.Get()
|
|
|
|
defer bufferpool.Put(buffer)
|
|
|
|
err := a.titleMd.Convert([]byte(s), buffer)
|
2021-08-04 21:26:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
2021-09-01 09:14:49 +00:00
|
|
|
return htmlText(buffer.String())
|
2021-08-04 21:26:38 +00:00
|
|
|
}
|
|
|
|
|
2020-09-21 14:53:20 +00:00
|
|
|
// Extensions etc...
|
|
|
|
|
|
|
|
// Links
|
2020-12-23 13:36:27 +00:00
|
|
|
type customExtension struct {
|
|
|
|
absoluteLinks bool
|
2021-06-06 12:39:42 +00:00
|
|
|
publicAddress string
|
2020-12-23 13:36:27 +00:00
|
|
|
}
|
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(
|
2020-12-23 13:36:27 +00:00
|
|
|
util.Prioritized(&customRenderer{
|
|
|
|
absoluteLinks: l.absoluteLinks,
|
2021-06-06 12:39:42 +00:00
|
|
|
publicAddress: l.publicAddress,
|
2020-12-23 13:36:27 +00:00
|
|
|
}, 500),
|
2020-09-21 14:53:20 +00:00
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2020-12-23 13:36:27 +00:00
|
|
|
type customRenderer struct {
|
|
|
|
absoluteLinks bool
|
2021-06-06 12:39:42 +00:00
|
|
|
publicAddress string
|
2020-12-23 13:36:27 +00:00
|
|
|
}
|
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)
|
2021-06-06 12:39:42 +00:00
|
|
|
if c.absoluteLinks && c.publicAddress != "" && 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)
|
|
|
|
// Make URL absolute if it's relative
|
2020-12-14 21:05:54 +00:00
|
|
|
destination := util.URLEscape(n.Destination, true)
|
2021-09-01 06:29:21 +00:00
|
|
|
if c.absoluteLinks && c.publicAddress != "" && bytes.HasPrefix(destination, []byte("/")) {
|
2021-06-06 12:39:42 +00:00
|
|
|
destination = util.EscapeHTML(append([]byte(c.publicAddress), destination...))
|
2020-12-14 21:05:54 +00:00
|
|
|
} else {
|
|
|
|
destination = util.EscapeHTML(destination)
|
2020-09-21 15:05:50 +00:00
|
|
|
}
|
|
|
|
_, _ = w.WriteString("<a href=\"")
|
2020-12-14 21:05:54 +00:00
|
|
|
_, _ = w.Write(destination)
|
2020-09-21 15:05:50 +00:00
|
|
|
_, _ = w.WriteString("\">")
|
|
|
|
_, _ = w.WriteString("<img src=\"")
|
2020-12-14 21:05:54 +00:00
|
|
|
_, _ = w.Write(destination)
|
|
|
|
_, _ = w.WriteString("\" alt=\"")
|
2020-09-21 15:05:50 +00:00
|
|
|
_, _ = w.Write(util.EscapeHTML(n.Text(source)))
|
|
|
|
_ = w.WriteByte('"')
|
|
|
|
_, _ = w.WriteString(" loading=\"lazy\"")
|
|
|
|
if n.Title != nil {
|
2020-12-14 21:05:54 +00:00
|
|
|
_, _ = w.WriteString(" title=\"")
|
2020-09-21 15:05:50 +00:00
|
|
|
_, _ = w.Write(n.Title)
|
|
|
|
_ = w.WriteByte('"')
|
|
|
|
}
|
|
|
|
_, _ = w.WriteString("></a>")
|
|
|
|
return ast.WalkSkipChildren, nil
|
|
|
|
}
|