···
+
// heavily inspired by: https://github.com/kaleocheng/goldmark-extensions
+
"github.com/yuin/goldmark"
+
"github.com/yuin/goldmark/ast"
+
"github.com/yuin/goldmark/parser"
+
"github.com/yuin/goldmark/renderer"
+
"github.com/yuin/goldmark/renderer/html"
+
"github.com/yuin/goldmark/text"
+
"github.com/yuin/goldmark/util"
+
// An AtNode struct represents an AtNode
+
var _ ast.Node = &AtNode{}
+
// Dump implements Node.Dump.
+
func (n *AtNode) Dump(source []byte, level int) {
+
ast.DumpHelper(n, source, level, nil, nil)
+
// KindAt is a NodeKind of the At node.
+
var KindAt = ast.NewNodeKind("At")
+
// Kind implements Node.Kind.
+
func (n *AtNode) Kind() ast.NodeKind {
+
var atRegexp = regexp.MustCompile(`(^|\s|\()(@)([a-zA-Z0-9.-]+)(\b)`)
+
// NewAtParser return a new InlineParser that parses
+
func NewAtParser() parser.InlineParser {
+
func (s *atParser) Trigger() []byte {
+
func (s *atParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
+
line, segment := block.PeekLine()
+
m := atRegexp.FindSubmatchIndex(line)
+
node.AppendChild(node, ast.NewTextSegment(text.NewSegment(segment.Start, segment.Start+m[1])))
+
node.handle = string(node.Text(block.Source())[1:])
+
// atHtmlRenderer is a renderer.NodeRenderer implementation that
+
type atHtmlRenderer struct {
+
// NewAtHTMLRenderer returns a new AtHTMLRenderer.
+
func NewAtHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
+
Config: html.NewConfig(),
+
for _, opt := range opts {
+
opt.SetHTMLOption(&r.Config)
+
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
+
func (r *atHtmlRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+
reg.Register(KindAt, r.renderAt)
+
func (r *atHtmlRenderer) renderAt(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
+
w.WriteString(`<a href="/@`)
+
w.WriteString(n.(*AtNode).handle)
+
w.WriteString(`" class="text-red-500">`)
+
return ast.WalkContinue, nil
+
// At is an extension that allow you to use at expression like '@user.bsky.social' .
+
func (e *atExt) Extend(m goldmark.Markdown) {
+
m.Parser().AddOptions(parser.WithInlineParsers(
+
util.Prioritized(NewAtParser(), 500),
+
m.Renderer().AddOptions(renderer.WithNodeRenderers(
+
util.Prioritized(NewAtHTMLRenderer(), 500),
+
// FindUserMentions returns Set of user handles from given markup soruce.
+
// It doesn't guarntee unique DIDs
+
func FindUserMentions(source string) []string {
+
mentionsSet = make(map[string]struct{})
+
sourceBytes = []byte(source)
+
root = md.Parser().Parse(text.NewReader(sourceBytes))
+
ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
+
if entering && n.Kind() == KindAt {
+
handle := n.(*AtNode).handle
+
mentionsSet[handle] = struct{}{}
+
return ast.WalkSkipChildren, nil
+
return ast.WalkContinue, nil
+
for handle := range mentionsSet {
+
mentions = append(mentions, handle)