Skip to content

Add alert blocks in markdown #29121

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 16 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
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 {
Comment on lines +206 to +207
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The firstParagraph might be nil, need to fix a panic: Fix markdown render #33870

return ast.WalkContinue, nil
}
firstTextNode, ok := firstParagraph.FirstChild().(*ast.Text)
if !ok {
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