Skip to content

Add attention blocks within quote blocks for Note and Warning #21711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Nov 9, 2022
34 changes: 34 additions & 0 deletions modules/markup/markdown/ast.go
Original file line number Diff line number Diff line change
@@ -180,3 +180,37 @@ func IsColorPreview(node ast.Node) bool {
_, ok := node.(*ColorPreview)
return ok
}

const (
AttentionNote string = "Note"
AttentionWarning string = "Warning"
)

// Attention is an inline for a color preview
type Attention struct {
ast.BaseInline
AttentionType string
}

// Dump implements Node.Dump.
func (n *Attention) Dump(source []byte, level int) {
m := map[string]string{}
m["AttentionType"] = n.AttentionType
ast.DumpHelper(n, source, level, m, nil)
}

// KindAttention is the NodeKind for Attention
var KindAttention = ast.NewNodeKind("Attention")

// Kind implements Node.Kind.
func (n *Attention) Kind() ast.NodeKind {
return KindAttention
}

// NewAttention returns a new Attention node.
func NewAttention(attentionType string) *Attention {
return &Attention{
BaseInline: ast.BaseInline{},
AttentionType: attentionType,
}
}
37 changes: 37 additions & 0 deletions modules/markup/markdown/goldmark.go
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/common"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/svg"
giteautil "code.gitea.io/gitea/modules/util"

"github.com/microcosm-cc/bluemonday/css"
@@ -46,6 +47,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
ctx.TableOfContents = make([]markup.Header, 0, 100)
}

attentionMarkedBlockquotes := make(container.Set[*ast.Blockquote])
_ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
@@ -184,6 +186,18 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
if css.ColorHandler(strings.ToLower(string(colorContent))) {
v.AppendChild(v, NewColorPreview(colorContent))
}
case *ast.Emphasis:
// check if inside blockquote for attention, expected hierarchy is
// Emphasis < Paragraph < Blockquote
blockquote, isInBlockquote := n.Parent().Parent().(*ast.Blockquote)
if isInBlockquote && !attentionMarkedBlockquotes.Contains(blockquote) {
fullText := string(n.Text(reader.Source()))
if fullText == AttentionNote || fullText == AttentionWarning {
v.SetAttributeString("class", []byte("attention-"+strings.ToLower(fullText)))
v.Parent().InsertBefore(v.Parent(), v, NewAttention(fullText))
attentionMarkedBlockquotes.Add(blockquote)
}
}
}
return ast.WalkContinue, nil
})
@@ -273,6 +287,7 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(KindSummary, r.renderSummary)
reg.Register(KindIcon, r.renderIcon)
reg.Register(ast.KindCodeSpan, r.renderCodeSpan)
reg.Register(KindAttention, r.renderAttention)
reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
}
@@ -309,6 +324,28 @@ func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Nod
return ast.WalkContinue, nil
}

// renderAttention renders a quote marked with i.e. "> **Note**" or "> **Warning**" with a corresponding svg
func (r *HTMLRenderer) renderAttention(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if entering {
_, _ = w.WriteString(`<span class="attention-icon attention-`)
n := node.(*Attention)
_, _ = w.WriteString(strings.ToLower(n.AttentionType))
_, _ = w.WriteString(`">`)

var octiconType string
switch n.AttentionType {
case AttentionNote:
octiconType = "info"
case AttentionWarning:
octiconType = "alert"
}
_, _ = w.WriteString(string(svg.RenderHTML("octicon-" + octiconType)))
} else {
_, _ = w.WriteString("</span>\n")
}
return ast.WalkContinue, nil
}

func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*ast.Document)

7 changes: 7 additions & 0 deletions modules/markup/sanitizer.go
Original file line number Diff line number Diff line change
@@ -58,6 +58,13 @@ func createDefaultPolicy() *bluemonday.Policy {
// For color preview
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^color-preview$`)).OnElements("span")

// For attention
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^attention-\w+$`)).OnElements("strong")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^attention-icon attention-\w+$`)).OnElements("span", "strong")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^svg octicon-\w+$`)).OnElements("svg")
policy.AllowAttrs("viewBox", "width", "height", "aria-hidden").OnElements("svg")
policy.AllowAttrs("fill-rule", "d").OnElements("path")

// For Chroma markdown plugin
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(chroma )?language-[\w-]+( display)?( is-loading)?$`)).OnElements("code")

14 changes: 14 additions & 0 deletions web_src/less/_base.less
Original file line number Diff line number Diff line change
@@ -1732,6 +1732,20 @@ a.ui.card:hover,
border-radius: .15em;
}

.attention-icon {
vertical-align: text-top;
}

.attention-note {
font-weight: unset;
color: var(--color-info-text);
}

.attention-warning {
font-weight: unset;
color: var(--color-warning-text);
}

footer {
background-color: var(--color-footer);
border-top: 1px solid var(--color-secondary);