Skip to content
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

Add alert blocks in markdown #29121

Merged
merged 16 commits into from
Feb 10, 2024
7 changes: 1 addition & 6 deletions modules/markup/markdown/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,7 @@ func IsColorPreview(node ast.Node) bool {
return ok
}

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

// Attention is an inline for a color preview
// Attention is an inline for an attention
type Attention struct {
ast.BaseInline
AttentionType string
Expand Down
84 changes: 68 additions & 16 deletions modules/markup/markdown/goldmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
}
}

attentionMarkedBlockquotes := make(container.Set[*ast.Blockquote])
_ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
Expand Down Expand Up @@ -197,18 +196,62 @@ 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)
}
case *ast.Blockquote:
// We only want attention blockquotes when the AST looks like:
// Text: "["
// Text: "!TYPE"
// Text(SoftLineBreak): "]"

// grab these nodes
firstParagraph := v.FirstChild()
if firstParagraph.ChildCount() < 3 {
return ast.WalkContinue, nil
}
firstTextNode, ok := firstParagraph.FirstChild().(*ast.Text)
if !ok {
yardenshoham marked this conversation as resolved.
Show resolved Hide resolved
return ast.WalkContinue, nil
}
secondTextNode, ok := firstTextNode.NextSibling().(*ast.Text)
if !ok {
return ast.WalkContinue, nil
}
thirdTextNode, ok := secondTextNode.NextSibling().(*ast.Text)
if !ok {
return ast.WalkContinue, nil
}

// make sure we adhere to the attention blockquote structure
if string(firstTextNode.Segment.Value(reader.Source())) != "[" ||
!attentionTypeRE.MatchString(string(secondTextNode.Segment.Value(reader.Source()))) ||
string(thirdTextNode.Segment.Value(reader.Source())) != "]" {
return ast.WalkContinue, nil
}

// grab attention type from markdown source
attentionType := strings.ToLower(strings.TrimPrefix(string(secondTextNode.Segment.Value(reader.Source())), "!"))

// color the blockquote
v.SetAttributeString("class", []byte("gt-py-3 attention attention-"+attentionType))

// create an emphasis to make it bold
emphasis := ast.NewEmphasis(2)
emphasis.SetAttributeString("class", []byte("gt-font-bold attention-"+attentionType))
firstParagraph.InsertBefore(firstParagraph, firstTextNode, emphasis)

// capitalize first letter
attentionText := ast.NewString([]byte(strings.ToUpper(string(attentionType[0])) + attentionType[1:]))

// replace the ![TYPE] with icon+Type
emphasis.AppendChild(emphasis, attentionText)
for i := 0; i < 2; i++ {
lineBreak := ast.NewText()
lineBreak.SetSoftLineBreak(true)
firstParagraph.InsertAfter(firstParagraph, emphasis, lineBreak)
}
firstParagraph.InsertBefore(firstParagraph, emphasis, NewAttention(attentionType))
firstParagraph.RemoveChild(firstParagraph, firstTextNode)
firstParagraph.RemoveChild(firstParagraph, secondTextNode)
firstParagraph.RemoveChild(firstParagraph, thirdTextNode)
}
return ast.WalkContinue, nil
})
Expand Down Expand Up @@ -339,17 +382,23 @@ func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Nod
// 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-`)
_, _ = w.WriteString(`<span class="gt-vm attention-`)
n := node.(*Attention)
_, _ = w.WriteString(strings.ToLower(n.AttentionType))
_, _ = w.WriteString(`">`)

var octiconType string
switch n.AttentionType {
case AttentionNote:
case "note":
octiconType = "info"
case AttentionWarning:
case "tip":
octiconType = "light-bulb"
case "important":
octiconType = "report"
case "warning":
octiconType = "alert"
case "caution":
octiconType = "stop"
}
_, _ = w.WriteString(string(svg.RenderHTML("octicon-" + octiconType)))
} else {
Expand Down Expand Up @@ -417,7 +466,10 @@ func (r *HTMLRenderer) renderSummary(w util.BufWriter, source []byte, node ast.N
return ast.WalkContinue, nil
}

var validNameRE = regexp.MustCompile("^[a-z ]+$")
var (
validNameRE = regexp.MustCompile("^[a-z ]+$")
attentionTypeRE = regexp.MustCompile("^!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)$")
)

func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
Expand Down
5 changes: 3 additions & 2 deletions modules/markup/sanitizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ func createDefaultPolicy() *bluemonday.Policy {
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(`^gt-py-3 attention attention-\w+$`)).OnElements("blockquote")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^gt-font-bold attention-\w+$`)).OnElements("strong")
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^gt-vm 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")
Expand Down
45 changes: 38 additions & 7 deletions web_src/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -1268,20 +1268,51 @@ img.ui.avatar,
border-radius: var(--border-radius);
}

.attention-icon {
vertical-align: text-top;
.attention {
color: unset !important;
}

.attention-note {
font-weight: unset;
color: var(--color-info-text);
blockquote.attention-note {
border-left-color: var(--color-blue-dark-2);
}

blockquote.attention-tip {
border-left-color: var(--color-success-border);
}

blockquote.attention-important {
border-left-color: var(--color-violet-dark-2);
}

blockquote.attention-warning {
border-left-color: var(--color-warning-text);
}

blockquote.attention-caution {
border-left-color: var(--color-red-dark-2);
}

.attention-warning {
font-weight: unset;
strong.attention-note, span.attention-note {
color: var(--color-blue-dark-2);
}

strong.attention-tip, span.attention-tip, .attention-tip svg path {
color: var(--color-success-border);
fill: var(--color-success-border);
}

strong.attention-important, span.attention-important {
color: var(--color-violet-dark-2);
}

strong.attention-warning, span.attention-warning {
color: var(--color-warning-text);
}

strong.attention-caution, span.attention-caution {
color: var(--color-red-dark-2);
}

.center:not(.popup) {
text-align: center;
}
Expand Down
Loading