···
1
+
// heavily inspired by: https://github.com/kaleocheng/goldmark-extensions
8
+
"github.com/yuin/goldmark"
9
+
"github.com/yuin/goldmark/ast"
10
+
"github.com/yuin/goldmark/parser"
11
+
"github.com/yuin/goldmark/renderer"
12
+
"github.com/yuin/goldmark/renderer/html"
13
+
"github.com/yuin/goldmark/text"
14
+
"github.com/yuin/goldmark/util"
17
+
// An AtNode struct represents an AtNode
18
+
type AtNode struct {
23
+
var _ ast.Node = &AtNode{}
25
+
// Dump implements Node.Dump.
26
+
func (n *AtNode) Dump(source []byte, level int) {
27
+
ast.DumpHelper(n, source, level, nil, nil)
30
+
// KindAt is a NodeKind of the At node.
31
+
var KindAt = ast.NewNodeKind("At")
33
+
// Kind implements Node.Kind.
34
+
func (n *AtNode) Kind() ast.NodeKind {
38
+
var atRegexp = regexp.MustCompile(`(^|\s|\()(@)([a-zA-Z0-9.-]+)(\b)`)
40
+
type atParser struct{}
42
+
// NewAtParser return a new InlineParser that parses
44
+
func NewAtParser() parser.InlineParser {
48
+
func (s *atParser) Trigger() []byte {
52
+
func (s *atParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
53
+
line, segment := block.PeekLine()
54
+
m := atRegexp.FindSubmatchIndex(line)
60
+
node.AppendChild(node, ast.NewTextSegment(text.NewSegment(segment.Start, segment.Start+m[1])))
61
+
node.handle = string(node.Text(block.Source())[1:])
65
+
// atHtmlRenderer is a renderer.NodeRenderer implementation that
66
+
// renders At nodes.
67
+
type atHtmlRenderer struct {
71
+
// NewAtHTMLRenderer returns a new AtHTMLRenderer.
72
+
func NewAtHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
73
+
r := &atHtmlRenderer{
74
+
Config: html.NewConfig(),
76
+
for _, opt := range opts {
77
+
opt.SetHTMLOption(&r.Config)
82
+
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
83
+
func (r *atHtmlRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
84
+
reg.Register(KindAt, r.renderAt)
87
+
func (r *atHtmlRenderer) renderAt(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
89
+
w.WriteString(`<a href="/@`)
90
+
w.WriteString(n.(*AtNode).handle)
91
+
w.WriteString(`" class="text-red-500">`)
93
+
w.WriteString("</a>")
95
+
return ast.WalkContinue, nil
100
+
// At is an extension that allow you to use at expression like '@user.bsky.social' .
101
+
var AtExt = &atExt{}
103
+
func (e *atExt) Extend(m goldmark.Markdown) {
104
+
m.Parser().AddOptions(parser.WithInlineParsers(
105
+
util.Prioritized(NewAtParser(), 500),
107
+
m.Renderer().AddOptions(renderer.WithNodeRenderers(
108
+
util.Prioritized(NewAtHTMLRenderer(), 500),
112
+
// FindUserMentions returns Set of user handles from given markup soruce.
113
+
// It doesn't guarntee unique DIDs
114
+
func FindUserMentions(source string) []string {
117
+
mentionsSet = make(map[string]struct{})
119
+
sourceBytes = []byte(source)
120
+
root = md.Parser().Parse(text.NewReader(sourceBytes))
122
+
ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
123
+
if entering && n.Kind() == KindAt {
124
+
handle := n.(*AtNode).handle
125
+
mentionsSet[handle] = struct{}{}
126
+
return ast.WalkSkipChildren, nil
128
+
return ast.WalkContinue, nil
130
+
for handle := range mentionsSet {
131
+
mentions = append(mentions, handle)