diff --git a/public/parsing.wasm b/public/parsing.wasm index e2ef7d2..7b124ed 100644 Binary files a/public/parsing.wasm and b/public/parsing.wasm differ diff --git a/src/parsing/bbcode.go b/src/parsing/bbcode.go index 203f1c5..5d4d6ea 100644 --- a/src/parsing/bbcode.go +++ b/src/parsing/bbcode.go @@ -1,7 +1,6 @@ package parsing import ( - "fmt" "strings" "github.com/yuin/goldmark" @@ -34,9 +33,7 @@ func (s bParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) g // link.Protocol = protocol // return link - lineBytes, segment := block.PeekLine() - fmt.Printf("line: %s\n", string(lineBytes)) - fmt.Printf("segment: %#v\n", segment) + lineBytes, _ := block.PeekLine() line := string(lineBytes) diff --git a/src/parsing/embed.go b/src/parsing/embed.go new file mode 100644 index 0000000..fe6bc6f --- /dev/null +++ b/src/parsing/embed.go @@ -0,0 +1,152 @@ +package parsing + +import ( + "regexp" + + "github.com/yuin/goldmark" + gast "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" +) + +var ( + REEmbedTag = regexp.MustCompile(`^!embed\((?P.+?)\)`) + + REYoutubeLong = regexp.MustCompile(`^https://www\.youtube\.com/watch?.*v=(?P[a-zA-Z0-9_-]{11})`) + REYoutubeShort = regexp.MustCompile(`^https://youtu\.be/(?P[a-zA-Z0-9_-]{11})`) + REVimeo = regexp.MustCompile(`^https://vimeo\.com/(?P\d+)`) +) + +// ---------------------- +// Parser and delimiters +// ---------------------- + +type embedParser struct{} + +// TODO: Make this a block parser +func NewEmbedParser() parser.InlineParser { + return embedParser{} +} + +func (s embedParser) Trigger() []byte { + return []byte{'!'} +} + +func (s embedParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node { + restOfLine, _ := block.PeekLine() + urlMatch := REEmbedTag.FindSubmatch(restOfLine) + if urlMatch == nil { + return nil + } + url := urlMatch[REEmbedTag.SubexpIndex("url")] + + html := "" + if ytLongMatch := extract(REYoutubeLong, url, "vid"); ytLongMatch != nil { + html = makeYoutubeEmbed(string(ytLongMatch)) + } else if ytShortMatch := extract(REYoutubeShort, url, "vid"); ytShortMatch != nil { + html = makeYoutubeEmbed(string(ytShortMatch)) + } else if vimeoMatch := extract(REVimeo, url, "vid"); vimeoMatch != nil { + html = ` +
+
+ +
+
` + } + + if html == "" { + return nil + } + + block.Advance(len(urlMatch[0])) + return NewEmbed(html) +} + +func extract(re *regexp.Regexp, src []byte, subexpName string) []byte { + m := re.FindSubmatch(src) + if m == nil { + return nil + } + return m[re.SubexpIndex(subexpName)] +} + +func makeYoutubeEmbed(vid string) string { + return ` +
+
+ +
+
` +} + +// ---------------------- +// AST node +// ---------------------- + +type EmbedNode struct { + gast.BaseBlock + HTML string +} + +func (n *EmbedNode) Dump(source []byte, level int) { + gast.DumpHelper(n, source, level, nil, nil) +} + +var KindEmbed = gast.NewNodeKind("Embed") + +func (n *EmbedNode) Kind() gast.NodeKind { + return KindEmbed +} + +func NewEmbed(HTML string) gast.Node { + return &EmbedNode{ + HTML: HTML, + } +} + +// ---------------------- +// Renderer +// ---------------------- + +type EmbedHTMLRenderer struct { + html.Config +} + +func NewEmbedHTMLRenderer(opts ...html.Option) renderer.NodeRenderer { + r := &EmbedHTMLRenderer{ + Config: html.NewConfig(), + } + for _, opt := range opts { + opt.SetHTMLOption(&r.Config) + } + return r +} + +func (r *EmbedHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { + reg.Register(KindEmbed, r.renderEmbed) +} + +func (r *EmbedHTMLRenderer) renderEmbed(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) { + if entering { + w.WriteString(n.(*EmbedNode).HTML) + } + return gast.WalkSkipChildren, nil +} + +// ---------------------- +// Extension +// ---------------------- + +type EmbedExtension struct{} + +func (e EmbedExtension) Extend(m goldmark.Markdown) { + m.Parser().AddOptions(parser.WithInlineParsers( + util.Prioritized(NewEmbedParser(), 500), + )) + m.Renderer().AddOptions(renderer.WithNodeRenderers( + util.Prioritized(NewEmbedHTMLRenderer(), 500), + )) +} diff --git a/src/parsing/parsing.go b/src/parsing/parsing.go index ee8e1ce..dcf8cd8 100644 --- a/src/parsing/parsing.go +++ b/src/parsing/parsing.go @@ -91,7 +91,7 @@ func tokenizeBBCode(input string) []token { } var md = goldmark.New( - goldmark.WithExtensions(extension.GFM, SpoilerExtension{}, bTag{}), + goldmark.WithExtensions(extension.GFM, SpoilerExtension{}, EmbedExtension{}, bTag{}), ) func ParsePostInput(source string) string { diff --git a/src/parsing/spoilers.go b/src/parsing/spoilers.go index 2d44d33..9bda15c 100644 --- a/src/parsing/spoilers.go +++ b/src/parsing/spoilers.go @@ -72,7 +72,7 @@ func (n *SpoilerNode) Kind() gast.NodeKind { return KindSpoiler } -func NewSpoiler() *SpoilerNode { +func NewSpoiler() gast.Node { return &SpoilerNode{} }