Skip to content

Commit

Permalink
Support auto-linkifying URLs
Browse files Browse the repository at this point in the history
  • Loading branch information
belak committed Dec 9, 2024
1 parent 55c9bf6 commit 17f99ba
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 7 deletions.
32 changes: 28 additions & 4 deletions messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strings"

"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
Expand Down Expand Up @@ -60,11 +61,15 @@ func TextToBlocks(data string) (*pb.Block, error) {
util.Prioritized(parser.NewEmphasisParser(), 500),

// Custom additions

// NOTE: underline must be a higher priority than the emphasis
// parser to work correctly.
util.Prioritized(newMultiCharInlineParser('_', "Underline"), 450),

util.Prioritized(newMultiCharInlineParser('|', "Spoiler"), 1000),
util.Prioritized(newMultiCharInlineParser('~', "Strikethrough"), 1000),

// TODO: this doesn't work at the moment - it interacts in weird ways with the EmphasisParser.
util.Prioritized(newMultiCharInlineParser('_', "Underline"), 450),
util.Prioritized(extension.NewLinkifyParser(), 1000),
),
parser.WithParagraphTransformers(
util.Prioritized(parser.LinkReferenceParagraphTransformer, 100),
Expand Down Expand Up @@ -99,6 +104,16 @@ func nodeToBlocks(doc ast.Node, src []byte) ([]*pb.Block, error) {

for cur := doc; cur != nil; cur = cur.NextSibling() {
switch node := cur.(type) {
// case *ast.Blockquote: // TODO: actually supported
// case *ast.CodeBlock:
// case *ast.CodeSpan:
// case *ast.FencedCodeBlock:
// case *ast.HTMLBlock:
// case *ast.Heading: // TODO: actually supported
// case *ast.Image:
// case *ast.RawHTML:
// case *ast.String:
// case *ast.ThematicBreak:
case *ast.Document:
nodes, err := nodeToBlocks(cur.FirstChild(), src)
if err != nil {
Expand All @@ -115,6 +130,11 @@ func nodeToBlocks(doc ast.Node, src []byte) ([]*pb.Block, error) {
case *ast.Text:
fmt.Println("text:", string(node.Value(src)))
ret = append(ret, seabird.NewTextBlock(string(node.Value(src))))
case *ast.AutoLink:
ret = append(ret, seabird.NewLinkBlock(
string(node.URL(src)),
seabird.NewTextBlock(string(node.Label(src))),
))
case *ast.Link:
fmt.Printf("link: %+v\n", node.Destination)
nodes, err := nodeToBlocks(cur.FirstChild(), src)
Expand Down Expand Up @@ -188,7 +208,7 @@ func nodeToBlocks(doc ast.Node, src []byte) ([]*pb.Block, error) {
// This was originally based off parser.ScanDelimiter, but has been simplified
// and tweaked to work better with how spoiler and strikethrough blocks work in
// Discord to the point that it now no longer resembles the original.
func ScanMultiCharDelimiter(line []byte, targetLen int, processor parser.DelimiterProcessor) *parser.Delimiter {
func scanMultiCharDelimiter(line []byte, targetLen int, processor parser.DelimiterProcessor) *parser.Delimiter {
if len(line) < targetLen {
return nil
}
Expand All @@ -213,6 +233,10 @@ type multiCharInlineParser struct {
processor *multiCharDelimiterProcessor
}

// newMultiCharInlineParser is sort of a port of
// extension.NewStrikethroughParser, but generalized so it can work with
// multiple types of characters, allowing for support of underline,
// strikethrough, and spoiler tags with the same code.
func newMultiCharInlineParser(baseChar byte, kind string) parser.InlineParser {
return &multiCharInlineParser{
baseChar: baseChar,
Expand Down Expand Up @@ -240,7 +264,7 @@ func (p *multiCharInlineParser) Parse(parent ast.Node, block text.Reader, pc par
}
}

node := ScanMultiCharDelimiter(line, 2, p.processor)
node := scanMultiCharDelimiter(line, 2, p.processor)
if node == nil {
return nil
}
Expand Down
23 changes: 20 additions & 3 deletions messages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@ func TestTextToBlocks(t *testing.T) {
}{
// Simple Cases
{
name: "simple-text",
input: "hello world",
expected: seabird.NewTextBlock("hello world"),
name: "simple-text",
input: "hello world",
expected: seabird.NewContainerBlock(
// TODO: the container here is a side effect of the Linkify
// extension, but ideally it shouldn't exist. Maybe we can
// re-merge the text blocks.
seabird.NewTextBlock("hello"),
seabird.NewTextBlock(" world"),
),
},
{
name: "italics-simple",
Expand All @@ -36,6 +42,17 @@ func TestTextToBlocks(t *testing.T) {
seabird.NewTextBlock("hello"),
),
},
{
name: "linkify-simple",
input: "hello https://seabird.chat",
expected: seabird.NewContainerBlock(
seabird.NewTextBlock("hello "),
seabird.NewLinkBlock(
"https://seabird.chat",
seabird.NewTextBlock("https://seabird.chat"),
),
),
},
{
name: "strikethrough-simple",
input: "~~hello world~~",
Expand Down

0 comments on commit 17f99ba

Please sign in to comment.