Scrap the "resource" tag, make figures nice
This commit is contained in:
parent
df194dd041
commit
8eee541ccc
|
@ -15,3 +15,4 @@ adminmailer/adminmailer
|
||||||
local/backups
|
local/backups
|
||||||
/tmp
|
/tmp
|
||||||
*.exe
|
*.exe
|
||||||
|
.DS_Store
|
||||||
|
|
Binary file not shown.
|
@ -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; }
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
|
||||||
match = ytLongMatch
|
|
||||||
html = makeYoutubeEmbed(string(ytLongMatch), s.Preview)
|
|
||||||
} 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
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.Advance(len(match))
|
reader.Advance(len(match))
|
||||||
return NewEmbed(html), parser.NoChildren
|
return NewEmbed(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 embedParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
|
||||||
|
@ -80,36 +82,28 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------
|
// ----------------------
|
||||||
|
|
|
@ -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>`)
|
||||||
|
|
||||||
|
src := n.Args["src"]
|
||||||
|
alt := n.Args["alt"]
|
||||||
|
if embedHTML, _, canEmbed := htmlForURLEmbed(src, c.Opts.Previews); canEmbed {
|
||||||
|
c.W.WriteString(embedHTML)
|
||||||
|
} else {
|
||||||
var srcAttr, altAttr string
|
var srcAttr, altAttr string
|
||||||
if src := n.Args["src"]; src != "" {
|
if src != "" {
|
||||||
srcAttr = fmt.Sprintf(` src="%s"`, src)
|
srcAttr = fmt.Sprintf(` src="%s"`, src)
|
||||||
}
|
}
|
||||||
if alt := n.Args["alt"]; alt != "" {
|
if alt != "" {
|
||||||
altAttr = fmt.Sprintf(` alt="%s"`, alt)
|
altAttr = fmt.Sprintf(` alt="%s"`, alt)
|
||||||
}
|
}
|
||||||
c.W.WriteString(fmt.Sprintf(`<img%s%s>`, srcAttr, altAttr))
|
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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue