mirror of https://github.com/usememos/memos.git
88 lines
1.8 KiB
Go
88 lines
1.8 KiB
Go
package parser
|
|
|
|
import (
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
gast "github.com/yuin/goldmark/ast"
|
|
"github.com/yuin/goldmark/parser"
|
|
"github.com/yuin/goldmark/text"
|
|
|
|
mast "github.com/usememos/memos/internal/markdown/ast"
|
|
)
|
|
|
|
const (
|
|
// MaxMentionLength matches the username token length accepted by the API.
|
|
MaxMentionLength = 32
|
|
)
|
|
|
|
type mentionParser struct{}
|
|
|
|
// NewMentionParser creates a new inline parser for @mention syntax.
|
|
func NewMentionParser() parser.InlineParser {
|
|
return &mentionParser{}
|
|
}
|
|
|
|
// Trigger returns the characters that trigger this parser.
|
|
func (*mentionParser) Trigger() []byte {
|
|
return []byte{'@'}
|
|
}
|
|
|
|
func isValidMentionRune(r rune) bool {
|
|
return unicode.IsLetter(r) || unicode.IsNumber(r) || r == '-'
|
|
}
|
|
|
|
func isMentionBoundary(r rune) bool {
|
|
return unicode.IsSpace(r) || unicode.IsPunct(r) || unicode.IsSymbol(r)
|
|
}
|
|
|
|
// Parse parses @mention syntax while avoiding email-address matches.
|
|
func (*mentionParser) Parse(_ gast.Node, block text.Reader, _ parser.Context) gast.Node {
|
|
line, _ := block.PeekLine()
|
|
if len(line) == 0 || line[0] != '@' {
|
|
return nil
|
|
}
|
|
|
|
prev := block.PrecendingCharacter()
|
|
if prev != '\n' && !isMentionBoundary(prev) {
|
|
return nil
|
|
}
|
|
|
|
start := 1
|
|
pos := start
|
|
runeCount := 0
|
|
hasLetterOrNumber := false
|
|
|
|
for pos < len(line) {
|
|
r, size := utf8.DecodeRune(line[pos:])
|
|
if r == utf8.RuneError && size == 1 {
|
|
break
|
|
}
|
|
if !isValidMentionRune(r) {
|
|
break
|
|
}
|
|
if unicode.IsLetter(r) || unicode.IsNumber(r) {
|
|
hasLetterOrNumber = true
|
|
}
|
|
runeCount++
|
|
if runeCount > MaxMentionLength {
|
|
break
|
|
}
|
|
pos += size
|
|
}
|
|
|
|
if pos <= start || !hasLetterOrNumber {
|
|
return nil
|
|
}
|
|
|
|
username := line[start:pos]
|
|
usernameCopy := make([]byte, len(username))
|
|
copy(usernameCopy, username)
|
|
|
|
block.Advance(pos)
|
|
|
|
return &mast.MentionNode{
|
|
Username: usernameCopy,
|
|
}
|
|
}
|