Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified public/gowasm.wasm
100644 → 100755
Binary file not shown.
4 changes: 3 additions & 1 deletion public/js/editor.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion public/js/project_edit.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

160 changes: 160 additions & 0 deletions src/parsing/embed_asset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package parsing

import (
"fmt"
"regexp"
"strings"

"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"
)

var (
// NOTE(ben): If you update this regex, make sure to also update the file
// extensions recognized in markdown_upload.ts to ensure that uploaded files
// get the ![foo](url) syntax.
PlainVideoLinkOnly = `https?://(handmade\.network|beta-handmadenet\.work|([^/.:]+\.)?handmade\.local(:\d+)?|assets\.media\.handmade\.network|hmn-assets-2\.ams3\.cdn\.digitaloceanspaces\.com|localhost(:\d+)?)/[^\s"'<>()]+?\.(mp4|webm|mov|m4v)`

REPlainVideoBareLink = regexp.MustCompile(`(?i)^` + PlainVideoLinkOnly + `$`)
REPlainVideoMarkdownLink = regexp.MustCompile(`(?i)^!\[[^\]]*?\]\((?P<url>` + PlainVideoLinkOnly + `)\)$`)
)

func videoEmbed(url string, preview bool) string {
if preview {
return `
<div class="aspect-ratio-real--16x9 ba bg1 i flex items-center justify-center">
Embedded video
</div>
`
} else {
return fmt.Sprintf(`<video src="%s" preload="metadata" controls></video>`, url)
}
}

func htmlForAssetEmbed(url string, preview bool) (string, []byte, bool) {
url = strings.TrimSpace(url)

if match := REPlainVideoMarkdownLink.FindSubmatch([]byte(url)); match != nil {
url := match[REPlainVideoMarkdownLink.SubexpIndex("url")]
return videoEmbed(string(url), preview), match[0], true
} else if match := REPlainVideoBareLink.FindString(url); match != "" {
return videoEmbed(match, preview), []byte(match), true
}

return "", nil, false
}

// ----------------------
// Parser and delimiters
// ----------------------

type assetEmbedParser struct {
Preview bool
}

var _ parser.BlockParser = assetEmbedParser{}

func (s assetEmbedParser) Trigger() []byte {
return nil
}

func (s assetEmbedParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
restOfLine, _ := reader.PeekLine()

if html, match, ok := htmlForAssetEmbed(string(restOfLine), s.Preview); ok {
reader.Advance(len(match))
return NewAssetEmbed(html), parser.NoChildren
} else {
return nil, parser.NoChildren
}
}

func (s assetEmbedParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
return parser.Close
}

func (s assetEmbedParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {}

func (s assetEmbedParser) CanInterruptParagraph() bool {
return true
}

func (s assetEmbedParser) CanAcceptIndentedLine() bool {
return false
}

// ----------------------
// AST node
// ----------------------

type AssetEmbedNode struct {
ast.BaseBlock
HTML string
}

func (n *AssetEmbedNode) Dump(source []byte, level int) {
ast.DumpHelper(n, source, level, nil, nil)
}

var KindAssetEmbed = ast.NewNodeKind("AssetEmbed")

func (n *AssetEmbedNode) Kind() ast.NodeKind {
return KindAssetEmbed
}

func NewAssetEmbed(HTML string) ast.Node {
return &AssetEmbedNode{
HTML: HTML,
}
}

// ----------------------
// Renderer
// ----------------------

type AssetEmbedHTMLRenderer struct {
html.Config
}

func NewAssetEmbedHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
r := &AssetEmbedHTMLRenderer{
Config: html.NewConfig(),
}
for _, opt := range opts {
opt.SetHTMLOption(&r.Config)
}
return r
}

func (r *AssetEmbedHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(KindAssetEmbed, r.renderAssetEmbed)
}

func (r *AssetEmbedHTMLRenderer) renderAssetEmbed(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if entering {
w.WriteString(n.(*AssetEmbedNode).HTML)
}
return ast.WalkSkipChildren, nil
}

// ----------------------
// Extension
// ----------------------

type AssetEmbedExtension struct {
Preview bool
}

func (e AssetEmbedExtension) Extend(m goldmark.Markdown) {
m.Parser().AddOptions(parser.WithBlockParsers(
util.Prioritized(assetEmbedParser{Preview: e.Preview}, 500),
))
m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(NewAssetEmbedHTMLRenderer(), 500),
))
}
58 changes: 29 additions & 29 deletions src/parsing/embed.go → src/parsing/embed_external.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func htmlForURLEmbed(url string, preview bool) (string, []byte, bool) {
} else if match := extract(REYoutubeShort, []byte(url), "vid"); match != nil {
return makeYoutubeEmbed(string(match), preview), match, true
} else if match := extract(REVimeo, []byte(url), "vid"); match != nil {
return previewOrLegitEmbed("Vimeo", `
return previewOrLegitExternalEmbed("Vimeo", `
<div class="aspect-ratio aspect-ratio--16x9">
<iframe class="aspect-ratio--object" src="https://player.vimeo.com/video/`+string(match)+`" frameborder="0" allow="fullscreen; picture-in-picture" allowfullscreen></iframe>
</div>
Expand All @@ -46,43 +46,43 @@ func htmlForURLEmbed(url string, preview bool) (string, []byte, bool) {
// Parser and delimiters
// ----------------------

type embedParser struct {
type externalEmbedParser struct {
Preview bool
}

var _ parser.BlockParser = embedParser{}
var _ parser.BlockParser = externalEmbedParser{}

func (s embedParser) Trigger() []byte {
func (s externalEmbedParser) Trigger() []byte {
return nil
}

func (s embedParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
func (s externalEmbedParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
restOfLine, _ := reader.PeekLine()

if html, match, ok := htmlForURLEmbed(string(restOfLine), s.Preview); ok {
html = `<div class="mw6">` + html + `</div>`
reader.Advance(len(match))
return NewEmbed(html), parser.NoChildren
return NewExternalEmbed(html), parser.NoChildren
} else {
return nil, parser.NoChildren
}
}

func (s embedParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
func (s externalEmbedParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
return parser.Close
}

func (s embedParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {}
func (s externalEmbedParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {}

func (s embedParser) CanInterruptParagraph() bool {
func (s externalEmbedParser) CanInterruptParagraph() bool {
return true
}

func (s embedParser) CanAcceptIndentedLine() bool {
func (s externalEmbedParser) CanAcceptIndentedLine() bool {
return false
}

func previewOrLegitEmbed(name, legitHTML string, preview bool) string {
func previewOrLegitExternalEmbed(name, legitHTML string, preview bool) string {
if preview {
return `
<div class="aspect-ratio aspect-ratio--16x9">
Expand Down Expand Up @@ -112,23 +112,23 @@ func makeYoutubeEmbed(vid string, preview bool) string {
// AST node
// ----------------------

type EmbedNode struct {
type ExternalEmbedNode struct {
ast.BaseBlock
HTML string
}

func (n *EmbedNode) Dump(source []byte, level int) {
func (n *ExternalEmbedNode) Dump(source []byte, level int) {
ast.DumpHelper(n, source, level, nil, nil)
}

var KindEmbed = ast.NewNodeKind("Embed")
var KindExternalEmbed = ast.NewNodeKind("ExternalEmbed")

func (n *EmbedNode) Kind() ast.NodeKind {
return KindEmbed
func (n *ExternalEmbedNode) Kind() ast.NodeKind {
return KindExternalEmbed
}

func NewEmbed(HTML string) ast.Node {
return &EmbedNode{
func NewExternalEmbed(HTML string) ast.Node {
return &ExternalEmbedNode{
HTML: HTML,
}
}
Expand All @@ -137,12 +137,12 @@ func NewEmbed(HTML string) ast.Node {
// Renderer
// ----------------------

type EmbedHTMLRenderer struct {
type ExternalEmbedHTMLRenderer struct {
html.Config
}

func NewEmbedHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
r := &EmbedHTMLRenderer{
func NewExternalEmbedHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
r := &ExternalEmbedHTMLRenderer{
Config: html.NewConfig(),
}
for _, opt := range opts {
Expand All @@ -151,13 +151,13 @@ func NewEmbedHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
return r
}

func (r *EmbedHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(KindEmbed, r.renderEmbed)
func (r *ExternalEmbedHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(KindExternalEmbed, r.renderExternalEmbed)
}

func (r *EmbedHTMLRenderer) renderEmbed(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
func (r *ExternalEmbedHTMLRenderer) renderExternalEmbed(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if entering {
w.WriteString(n.(*EmbedNode).HTML)
w.WriteString(n.(*ExternalEmbedNode).HTML)
}
return ast.WalkSkipChildren, nil
}
Expand All @@ -166,15 +166,15 @@ func (r *EmbedHTMLRenderer) renderEmbed(w util.BufWriter, source []byte, n ast.N
// Extension
// ----------------------

type EmbedExtension struct {
type ExternalEmbedExtension struct {
Preview bool
}

func (e EmbedExtension) Extend(m goldmark.Markdown) {
func (e ExternalEmbedExtension) Extend(m goldmark.Markdown) {
m.Parser().AddOptions(parser.WithBlockParsers(
util.Prioritized(embedParser{Preview: e.Preview}, 500),
util.Prioritized(externalEmbedParser{Preview: e.Preview}, 500),
))
m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(NewEmbedHTMLRenderer(), 500),
util.Prioritized(NewExternalEmbedHTMLRenderer(), 500),
))
}
Loading
Loading