Scrap the "resource" tag, make figures nice

This commit is contained in:
Ben Visness 2022-10-26 12:32:39 -05:00
parent df194dd041
commit 8eee541ccc
7 changed files with 96 additions and 99 deletions

1
.gitignore vendored
View File

@ -15,3 +15,4 @@ adminmailer/adminmailer
local/backups local/backups
/tmp /tmp
*.exe *.exe
.DS_Store

Binary file not shown.

View File

@ -1176,7 +1176,7 @@ img, video {
.br2, .post-content code, .post-content pre > code, .post-content pre.hmn-code { .br2, .post-content code, .post-content pre > code, .post-content pre.hmn-code {
border-radius: 0.25rem; } border-radius: 0.25rem; }
.br3, .edu-article .edu-resource { .br3, figure {
border-radius: 0.5rem; } border-radius: 0.5rem; }
.br4 { .br4 {
@ -4602,7 +4602,7 @@ code, .code {
.pa2, .tab, header .root-item > a, header .submenu > a { .pa2, .tab, header .root-item > a, header .submenu > a {
padding: 0.5rem; } padding: 0.5rem; }
.pa3, .edu-article .edu-resource, header #login-popup { .pa3, header #login-popup {
padding: 1rem; } padding: 1rem; }
.pa4 { .pa4 {
@ -6353,7 +6353,7 @@ input[type=submit]:not(.button-small), .notice {
.f3 { .f3 {
font-size: 1.5rem; } font-size: 1.5rem; }
.f4, .edu-article .resource-title { .f4 {
font-size: 1.25rem; } font-size: 1.25rem; }
.f5 { .f5 {
@ -7426,7 +7426,7 @@ article code {
border-color: #ccc; border-color: #ccc;
border-color: var(--theme-color-dimmest); } border-color: var(--theme-color-dimmest); }
.bg--dim, .post-content code, .post-content pre > code, .post-content pre.hmn-code, .edu-article .edu-resource { .bg--dim, .post-content code, .post-content pre > code, .post-content pre.hmn-code, figure {
background-color: #f0f0f0; background-color: #f0f0f0;
background-color: var(--dim-background); } background-color: var(--dim-background); }
@ -8026,6 +8026,14 @@ pre {
padding: 0.7em; padding: 0.7em;
overflow-x: auto; } overflow-x: auto; }
figure {
display: flex;
flex-direction: column;
margin: 1rem 0;
padding: 1rem 1rem 0; }
figure figcaption {
padding: 0.5rem 0; }
.toolbar { .toolbar {
background-color: #fff; background-color: #fff;
background-color: var(--editor-toolbar-background); background-color: var(--editor-toolbar-background);
@ -8301,9 +8309,6 @@ nav.timecodes {
text-align: center; text-align: center;
margin: 10px 0; } margin: 10px 0; }
.edu-article .resource-title {
font-weight: bold; }
.edu-article .note { .edu-article .note {
color: red; } color: red; }

View File

@ -5,7 +5,6 @@ import (
"github.com/yuin/goldmark" "github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/ast"
gast "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer" "github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/renderer/html"
@ -23,6 +22,26 @@ var (
// TODO: Tweets // TODO: Tweets
) )
// Returns the HTML used to embed the given url, or false if the media cannot be embedded.
//
// The provided string need only start with the URL; if there is trailing content after the URL, it
// will be ignored.
func htmlForURLEmbed(url string, preview bool) (string, []byte, bool) {
if match := extract(REYoutubeLong, []byte(url), "vid"); match != nil {
return makeYoutubeEmbed(string(match), preview), match, true
} 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", `
<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>
`, preview), match, true
}
return "", nil, false
}
// ---------------------- // ----------------------
// Parser and delimiters // Parser and delimiters
// ---------------------- // ----------------------
@ -40,30 +59,13 @@ func (s embedParser) Trigger() []byte {
func (s embedParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) { func (s embedParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
restOfLine, _ := reader.PeekLine() restOfLine, _ := reader.PeekLine()
html := "" if html, match, ok := htmlForURLEmbed(string(restOfLine), s.Preview); ok {
var match []byte html = `<div class="mw6">` + html + `</div>`
if ytLongMatch := extract(REYoutubeLong, restOfLine, "vid"); ytLongMatch != nil { reader.Advance(len(match))
match = ytLongMatch return NewEmbed(html), parser.NoChildren
html = makeYoutubeEmbed(string(ytLongMatch), s.Preview) } else {
} else if ytShortMatch := extract(REYoutubeShort, restOfLine, "vid"); ytShortMatch != nil {
match = ytShortMatch
html = makeYoutubeEmbed(string(ytShortMatch), s.Preview)
} else if vimeoMatch := extract(REVimeo, restOfLine, "vid"); vimeoMatch != nil {
match = vimeoMatch
html = s.previewOrLegitEmbed("Vimeo", `
<div class="mw6">
<div class="aspect-ratio aspect-ratio--16x9">
<iframe class="aspect-ratio--object" src="https://player.vimeo.com/video/`+string(vimeoMatch)+`" frameborder="0" allow="fullscreen; picture-in-picture" allowfullscreen></iframe>
</div>
</div>`)
}
if html == "" {
return nil, parser.NoChildren return nil, parser.NoChildren
} }
reader.Advance(len(match))
return NewEmbed(html), parser.NoChildren
} }
func (s embedParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State { func (s embedParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
@ -80,37 +82,29 @@ func (s embedParser) CanAcceptIndentedLine() bool {
return false return false
} }
func (s embedParser) previewOrLegitEmbed(name string, legitHtml string) string { func previewOrLegitEmbed(name, legitHTML string, preview bool) string {
if s.Preview { if preview {
return ` return `
<div class="mw6"> <div class="aspect-ratio aspect-ratio--16x9">
<div class="aspect-ratio aspect-ratio--16x9"> <div class="aspect-ratio--object ba b--dimmest bg-light-gray i black flex items-center justify-center">
<div class="aspect-ratio--object ba b--dimmest bg-light-gray i black flex items-center justify-center"> ` + name + ` embed
` + name + ` embed </div>
</div> </div>
</div> `
</div> } else {
` return legitHTML
} }
return legitHtml
} }
func makeYoutubeEmbed(vid string, preview bool) string { func makeYoutubeEmbed(vid string, preview bool) string {
if preview { if preview {
return ` return `<img src="https://img.youtube.com/vi/` + vid + `/hqdefault.jpg">`
<div class="mw6">
<img src="https://img.youtube.com/vi/` + vid + `/hqdefault.jpg">
</div>
`
} else { } else {
return ` return `
<div class="mw6"> <div class="aspect-ratio aspect-ratio--16x9">
<div class="aspect-ratio aspect-ratio--16x9"> <iframe class="aspect-ratio--object" src="https://www.youtube-nocookie.com/embed/` + vid + `" frameborder="0" allowfullscreen></iframe>
<iframe class="aspect-ratio--object" src="https://www.youtube-nocookie.com/embed/` + vid + `" frameborder="0" allowfullscreen></iframe> </div>
</div> `
</div>
`
} }
} }
@ -119,21 +113,21 @@ func makeYoutubeEmbed(vid string, preview bool) string {
// ---------------------- // ----------------------
type EmbedNode struct { type EmbedNode struct {
gast.BaseBlock ast.BaseBlock
HTML string HTML string
} }
func (n *EmbedNode) Dump(source []byte, level int) { func (n *EmbedNode) Dump(source []byte, level int) {
gast.DumpHelper(n, source, level, nil, nil) ast.DumpHelper(n, source, level, nil, nil)
} }
var KindEmbed = gast.NewNodeKind("Embed") var KindEmbed = ast.NewNodeKind("Embed")
func (n *EmbedNode) Kind() gast.NodeKind { func (n *EmbedNode) Kind() ast.NodeKind {
return KindEmbed return KindEmbed
} }
func NewEmbed(HTML string) gast.Node { func NewEmbed(HTML string) ast.Node {
return &EmbedNode{ return &EmbedNode{
HTML: HTML, HTML: HTML,
} }
@ -161,11 +155,11 @@ func (r *EmbedHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegistere
reg.Register(KindEmbed, r.renderEmbed) reg.Register(KindEmbed, r.renderEmbed)
} }
func (r *EmbedHTMLRenderer) renderEmbed(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) { func (r *EmbedHTMLRenderer) renderEmbed(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
if entering { if entering {
w.WriteString(n.(*EmbedNode).HTML) w.WriteString(n.(*EmbedNode).HTML)
} }
return gast.WalkSkipChildren, nil return ast.WalkSkipChildren, nil
} }
// ---------------------- // ----------------------

View File

@ -6,10 +6,8 @@ import (
"regexp" "regexp"
"git.handmade.network/hmn/hmn/src/hmnurl" "git.handmade.network/hmn/hmn/src/hmnurl"
"git.handmade.network/hmn/hmn/src/utils"
"github.com/yuin/goldmark" "github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/ast"
gast "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer" "github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/renderer/html"
@ -43,7 +41,7 @@ var ggcodeTags = map[string]ggcodeTag{
Filter: ggcodeFilterEdu, Filter: ggcodeFilterEdu,
Renderer: func(c ggcodeRendererContext, n *ggcodeNode, entering bool) error { Renderer: func(c ggcodeRendererContext, n *ggcodeNode, entering bool) error {
if entering { if entering {
term, _ := n.Args["term"] term := n.Args["term"]
c.W.WriteString(fmt.Sprintf( c.W.WriteString(fmt.Sprintf(
`<a href="%s" class="glossary-term" data-term="%s">`, `<a href="%s" class="glossary-term" data-term="%s">`,
hmnurl.BuildEducationGlossary(term), hmnurl.BuildEducationGlossary(term),
@ -55,18 +53,6 @@ var ggcodeTags = map[string]ggcodeTag{
return nil return nil
}, },
}, },
"resource": {
Filter: ggcodeFilterEdu,
Renderer: func(c ggcodeRendererContext, n *ggcodeNode, entering bool) error {
if entering {
c.W.WriteString(`<div class="edu-resource">`)
c.W.WriteString(fmt.Sprintf(` <a class="resource-title" href="%s" target="_blank">%s</a>`, n.Args["url"], utils.OrDefault(n.Args["name"], "[missing `name`]")))
} else {
c.W.WriteString("</div>")
}
return nil
},
},
"note": { "note": {
Filter: ggcodeFilterEdu, Filter: ggcodeFilterEdu,
Renderer: func(c ggcodeRendererContext, n *ggcodeNode, entering bool) error { Renderer: func(c ggcodeRendererContext, n *ggcodeNode, entering bool) error {
@ -83,14 +69,22 @@ var ggcodeTags = map[string]ggcodeTag{
Renderer: func(c ggcodeRendererContext, n *ggcodeNode, entering bool) error { Renderer: func(c ggcodeRendererContext, n *ggcodeNode, entering bool) error {
if entering { if entering {
c.W.WriteString(`<figure>`) c.W.WriteString(`<figure>`)
var srcAttr, altAttr string
if src := n.Args["src"]; src != "" { src := n.Args["src"]
srcAttr = fmt.Sprintf(` src="%s"`, src) alt := n.Args["alt"]
if embedHTML, _, canEmbed := htmlForURLEmbed(src, c.Opts.Previews); canEmbed {
c.W.WriteString(embedHTML)
} else {
var srcAttr, altAttr string
if src != "" {
srcAttr = fmt.Sprintf(` src="%s"`, src)
}
if alt != "" {
altAttr = fmt.Sprintf(` alt="%s"`, alt)
}
c.W.WriteString(fmt.Sprintf(`<img%s%s>`, srcAttr, altAttr))
} }
if alt := n.Args["alt"]; alt != "" {
altAttr = fmt.Sprintf(` alt="%s"`, alt)
}
c.W.WriteString(fmt.Sprintf(`<img%s%s>`, srcAttr, altAttr))
c.W.WriteString(`<figcaption>`) c.W.WriteString(`<figcaption>`)
} else { } else {
c.W.WriteString(`</figcaption>`) c.W.WriteString(`</figcaption>`)
@ -195,7 +189,7 @@ func (s ggcodeInlineParser) Trigger() []byte {
return []byte("!()") return []byte("!()")
} }
func (s ggcodeInlineParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node { func (s ggcodeInlineParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
restOfLine, segment := block.PeekLine() // Gets the rest of the line (starting at the current parser cursor index), and the segment representing the indices in the source text. restOfLine, segment := block.PeekLine() // Gets the rest of the line (starting at the current parser cursor index), and the segment representing the indices in the source text.
if match := extractMap(reGGCodeInline, restOfLine); match != nil { if match := extractMap(reGGCodeInline, restOfLine); match != nil {
name := string(match["name"]) name := string(match["name"])
@ -240,7 +234,7 @@ func (p ggcodeDelimiterParser) CanOpenCloser(opener, closer *parser.Delimiter) b
return opener.Char == '(' && closer.Char == ')' return opener.Char == '(' && closer.Char == ')'
} }
func (p ggcodeDelimiterParser) OnMatch(consumes int) gast.Node { func (p ggcodeDelimiterParser) OnMatch(consumes int) ast.Node {
return p.Node return p.Node
} }
@ -249,7 +243,7 @@ func (p ggcodeDelimiterParser) OnMatch(consumes int) gast.Node {
// ---------------------- // ----------------------
type ggcodeNode struct { type ggcodeNode struct {
gast.BaseBlock ast.BaseBlock
Name string Name string
Args map[string]string Args map[string]string
} }
@ -257,12 +251,12 @@ type ggcodeNode struct {
var _ ast.Node = &ggcodeNode{} var _ ast.Node = &ggcodeNode{}
func (n *ggcodeNode) Dump(source []byte, level int) { func (n *ggcodeNode) Dump(source []byte, level int) {
gast.DumpHelper(n, source, level, n.Args, nil) ast.DumpHelper(n, source, level, n.Args, nil)
} }
var kindGGCode = gast.NewNodeKind("ggcode") var kindGGCode = ast.NewNodeKind("ggcode")
func (n *ggcodeNode) Kind() gast.NodeKind { func (n *ggcodeNode) Kind() ast.NodeKind {
return kindGGCode return kindGGCode
} }
@ -290,7 +284,7 @@ func (r *ggcodeHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegister
reg.Register(kindGGCode, r.render) reg.Register(kindGGCode, r.render)
} }
func (r *ggcodeHTMLRenderer) render(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) { func (r *ggcodeHTMLRenderer) render(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
node := n.(*ggcodeNode) node := n.(*ggcodeNode)
var renderer ggcodeRenderer = defaultGGCodeRenderer var renderer ggcodeRenderer = defaultGGCodeRenderer
if tag, ok := ggcodeTags[node.Name]; ok { if tag, ok := ggcodeTags[node.Name]; ok {
@ -303,7 +297,7 @@ func (r *ggcodeHTMLRenderer) render(w util.BufWriter, source []byte, n gast.Node
Source: source, Source: source,
Opts: r.Opts, Opts: r.Opts,
}, node, entering) }, node, entering)
return gast.WalkContinue, err return ast.WalkContinue, err
} }
func defaultGGCodeRenderer(c ggcodeRendererContext, n *ggcodeNode, entering bool) error { func defaultGGCodeRenderer(c ggcodeRendererContext, n *ggcodeNode, entering bool) error {

View File

@ -173,3 +173,15 @@ pre {
overflow-x: auto; overflow-x: auto;
} }
} }
figure {
@extend .bg--dim, .br3;
display: flex;
flex-direction: column;
margin: $spacing-medium 0;
padding: $spacing-medium $spacing-medium 0;
figcaption {
padding: $spacing-small 0;
}
}

View File

@ -1,13 +1,4 @@
.edu-article { .edu-article {
.edu-resource {
@extend .pa3, .bg--dim, .br3;
}
.resource-title {
@extend .f4;
font-weight: bold;
}
.note { .note {
color: red; color: red;
} }