mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
Add support for Obsidian type blockquote alerts
* Make the alert type parsing more flexible to support more types * Add `AlertTitle` and `AlertSign` (for folding) Note that GitHub will not render callouts with alert title/sign. See https://help.obsidian.md/Editing+and+formatting/Callouts Closes #12805 Closes #12801
This commit is contained in:
parent
0c453420e6
commit
e651d29801
5 changed files with 129 additions and 39 deletions
|
@ -24,6 +24,20 @@ Blockquote render hook templates receive the following [context]:
|
||||||
|
|
||||||
(`string`) Applicable when [`Type`](#type) is `alert`, this is the alert type converted to lowercase. See the [alerts](#alerts) section below.
|
(`string`) Applicable when [`Type`](#type) is `alert`, this is the alert type converted to lowercase. See the [alerts](#alerts) section below.
|
||||||
|
|
||||||
|
###### AlertTitle
|
||||||
|
|
||||||
|
{{< new-in 0.134.0 >}}
|
||||||
|
|
||||||
|
(`hstring.HTML`) Applicable when [`Type`](#type) is `alert` when using [Obsidian callouts] syntax, this is the alert title converted to HTML.
|
||||||
|
|
||||||
|
###### AlertSign
|
||||||
|
|
||||||
|
{{< new-in 0.134.0 >}}
|
||||||
|
|
||||||
|
(`string`) Applicable when [`Type`](#type) is `alert` when using [Obsidian callouts] syntax, this is one of "+", "-" or "" (empty string) to indicate the presence of a foldable sign.
|
||||||
|
|
||||||
|
[Obsidian callouts]: https://help.obsidian.md/Editing+and+formatting/Callouts
|
||||||
|
|
||||||
###### Attributes
|
###### Attributes
|
||||||
|
|
||||||
(`map`) The [Markdown attributes], available if you configure your site as follows:
|
(`map`) The [Markdown attributes], available if you configure your site as follows:
|
||||||
|
@ -117,13 +131,13 @@ Also known as _callouts_ or _admonitions_, alerts are blockquotes used to emphas
|
||||||
|
|
||||||
|
|
||||||
{{% note %}}
|
{{% note %}}
|
||||||
This syntax is compatible with the GitHub Alert Markdown extension.
|
This syntax is compatible with both the GitHub Alert Markdown extension and Obsidian's callout syntax.
|
||||||
|
But note that GitHub will not recognize callouts with one of Obsidian's extensions (e.g. callout title or the foldable sign).
|
||||||
{{% /note %}}
|
{{% /note %}}
|
||||||
|
|
||||||
|
|
||||||
The first line of each alert is an alert designator consisting of an exclamation point followed by the alert type, wrapped within brackets.
|
The first line of each alert is an alert designator consisting of an exclamation point followed by the alert type, wrapped within brackets.
|
||||||
|
|
||||||
The blockquote render hook below renders a multilingual alert if an alert desginator is present, otherwise it renders a blockquote according to the CommonMark specification.
|
The blockquote render hook below renders a multilingual alert if an alert designator is present, otherwise it renders a blockquote according to the CommonMark specification.
|
||||||
|
|
||||||
{{< code file=layouts/_default/_markup/render-blockquote.html copy=true >}}
|
{{< code file=layouts/_default/_markup/render-blockquote.html copy=true >}}
|
||||||
{{ $emojis := dict
|
{{ $emojis := dict
|
||||||
|
|
|
@ -109,6 +109,16 @@ type BlockquoteContext interface {
|
||||||
// The GitHub alert type converted to lowercase, e.g. "note".
|
// The GitHub alert type converted to lowercase, e.g. "note".
|
||||||
// Only set if Type is "alert".
|
// Only set if Type is "alert".
|
||||||
AlertType() string
|
AlertType() string
|
||||||
|
|
||||||
|
// The alert title.
|
||||||
|
// Currently only relevant for Obsidian alerts.
|
||||||
|
// GitHub does not suport alert titles and will not render alerts with titles.
|
||||||
|
AlertTitle() hstring.HTML
|
||||||
|
|
||||||
|
// The alert sign, "+" or "-" or "" used to indicate folding.
|
||||||
|
// Currently only relevant for Obsidian alerts.
|
||||||
|
// GitHub does not suport alert signs and will not render alerts with signs.
|
||||||
|
AlertSign() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type PositionerSourceTargetProvider interface {
|
type PositionerSourceTargetProvider interface {
|
||||||
|
|
|
@ -74,8 +74,8 @@ func (r *htmlRenderer) renderBlockquote(w util.BufWriter, src []byte, node ast.N
|
||||||
ordinal := ctx.GetAndIncrementOrdinal(ast.KindBlockquote)
|
ordinal := ctx.GetAndIncrementOrdinal(ast.KindBlockquote)
|
||||||
|
|
||||||
typ := typeRegular
|
typ := typeRegular
|
||||||
alertType := resolveGitHubAlert(string(text))
|
alert := resolveBlockQuoteAlert(string(text))
|
||||||
if alertType != "" {
|
if alert.typ != "" {
|
||||||
typ = typeAlert
|
typ = typeAlert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ func (r *htmlRenderer) renderBlockquote(w util.BufWriter, src []byte, node ast.N
|
||||||
bqctx := &blockquoteContext{
|
bqctx := &blockquoteContext{
|
||||||
BaseContext: render.NewBaseContext(ctx, renderer, n, src, nil, ordinal),
|
BaseContext: render.NewBaseContext(ctx, renderer, n, src, nil, ordinal),
|
||||||
typ: typ,
|
typ: typ,
|
||||||
alertType: alertType,
|
alert: alert,
|
||||||
text: hstring.HTML(text),
|
text: hstring.HTML(text),
|
||||||
AttributesHolder: attributes.New(n.Attributes(), attributes.AttributesOwnerGeneral),
|
AttributesHolder: attributes.New(n.Attributes(), attributes.AttributesOwnerGeneral),
|
||||||
}
|
}
|
||||||
|
@ -133,11 +133,9 @@ func (r *htmlRenderer) renderBlockquoteDefault(
|
||||||
|
|
||||||
type blockquoteContext struct {
|
type blockquoteContext struct {
|
||||||
hooks.BaseContext
|
hooks.BaseContext
|
||||||
|
|
||||||
text hstring.HTML
|
text hstring.HTML
|
||||||
alertType string
|
|
||||||
typ string
|
typ string
|
||||||
|
alert blockQuoteAlert
|
||||||
*attributes.AttributesHolder
|
*attributes.AttributesHolder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,25 +144,40 @@ func (c *blockquoteContext) Type() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *blockquoteContext) AlertType() string {
|
func (c *blockquoteContext) AlertType() string {
|
||||||
return c.alertType
|
return c.alert.typ
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *blockquoteContext) AlertTitle() hstring.HTML {
|
||||||
|
return hstring.HTML(c.alert.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *blockquoteContext) AlertSign() string {
|
||||||
|
return c.alert.sign
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *blockquoteContext) Text() hstring.HTML {
|
func (c *blockquoteContext) Text() hstring.HTML {
|
||||||
return c.text
|
return c.text
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts
|
var blockQuoteAlertRe = regexp.MustCompile(`^<p>\[!([a-zA-Z]+)\](-|\+)?[^\S\r\n]?([^\n]*)\n?`)
|
||||||
// Five types:
|
|
||||||
// [!NOTE], [!TIP], [!WARNING], [!IMPORTANT], [!CAUTION]
|
|
||||||
// Note that GitHub's implementation is case-insensitive.
|
|
||||||
var gitHubAlertRe = regexp.MustCompile(`(?i)^<p>\[!(NOTE|TIP|WARNING|IMPORTANT|CAUTION)\]`)
|
|
||||||
|
|
||||||
// resolveGitHubAlert returns one of note, tip, warning, important or caution.
|
func resolveBlockQuoteAlert(s string) blockQuoteAlert {
|
||||||
// An empty string if no match.
|
m := blockQuoteAlertRe.FindStringSubmatch(s)
|
||||||
func resolveGitHubAlert(s string) string {
|
if len(m) == 4 {
|
||||||
m := gitHubAlertRe.FindStringSubmatch(s)
|
return blockQuoteAlert{
|
||||||
if len(m) == 2 {
|
typ: strings.ToLower(m[1]),
|
||||||
return strings.ToLower(m[1])
|
sign: m[2],
|
||||||
|
title: m[3],
|
||||||
}
|
}
|
||||||
return ""
|
}
|
||||||
|
|
||||||
|
return blockQuoteAlert{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blockquote alert syntax was introduced by GitHub, but is also used
|
||||||
|
// by Obsidian which also support some extended attributes: More types, alert titles and a +/- sign for folding.
|
||||||
|
type blockQuoteAlert struct {
|
||||||
|
typ string
|
||||||
|
sign string
|
||||||
|
title string
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,3 +109,48 @@ Content: {{ .Content }}
|
||||||
b := hugolib.Test(t, files)
|
b := hugolib.Test(t, files)
|
||||||
b.AssertFileContent("public/p1/index.html", "Content: <blockquote>\n</blockquote>\n")
|
b.AssertFileContent("public/p1/index.html", "Content: <blockquote>\n</blockquote>\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBlockquObsidianWithTitleAndSign(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
files := `
|
||||||
|
-- hugo.toml --
|
||||||
|
-- content/_index.md --
|
||||||
|
---
|
||||||
|
title: "Home"
|
||||||
|
---
|
||||||
|
|
||||||
|
> [!danger]
|
||||||
|
> Do not approach or handle without protective gear.
|
||||||
|
|
||||||
|
|
||||||
|
> [!tip] Callouts can have custom titles
|
||||||
|
> Like this one.
|
||||||
|
|
||||||
|
> [!tip] Title-only callout
|
||||||
|
|
||||||
|
> [!faq]- Foldable negated callout
|
||||||
|
> Yes! In a foldable callout, the contents are hidden when the callout is collapsed
|
||||||
|
|
||||||
|
> [!faq]+ Foldable callout
|
||||||
|
> Yes! In a foldable callout, the contents are hidden when the callout is collapsed
|
||||||
|
|
||||||
|
-- layouts/index.html --
|
||||||
|
{{ .Content }}
|
||||||
|
-- layouts/_default/_markup/render-blockquote.html --
|
||||||
|
AlertType: {{ .AlertType }}|
|
||||||
|
AlertTitle: {{ .AlertTitle }}|
|
||||||
|
AlertSign: {{ .AlertSign | safeHTML }}|
|
||||||
|
Text: {{ .Text }}|
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
b := hugolib.Test(t, files)
|
||||||
|
b.AssertFileContent("public/index.html",
|
||||||
|
"AlertType: tip|\nAlertTitle: Callouts can have custom titles|\nAlertSign: |",
|
||||||
|
"AlertType: tip|\nAlertTitle: Title-only callout</p>|\nAlertSign: |",
|
||||||
|
"AlertType: faq|\nAlertTitle: Foldable negated callout|\nAlertSign: -|\nText: <p>Yes!",
|
||||||
|
"AlertType: faq|\nAlertTitle: Foldable callout|\nAlertSign: +|",
|
||||||
|
"AlertType: danger|\nAlertTitle: |\nAlertSign: |\nText: <p>Do not approach or handle without protective gear.</p>\n|",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -19,42 +19,50 @@ import (
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResolveGitHubAlert(t *testing.T) {
|
func TestResolveBlockQuoteAlert(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
expected string
|
expected blockQuoteAlert
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
input: "[!NOTE]",
|
input: "[!NOTE]",
|
||||||
expected: "note",
|
expected: blockQuoteAlert{typ: "note"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "[!WARNING]",
|
input: "[!FaQ]",
|
||||||
expected: "warning",
|
expected: blockQuoteAlert{typ: "faq"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "[!TIP]",
|
input: "[!NOTE]+",
|
||||||
expected: "tip",
|
expected: blockQuoteAlert{typ: "note", sign: "+"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "[!IMPORTANT]",
|
input: "[!NOTE]-",
|
||||||
expected: "important",
|
expected: blockQuoteAlert{typ: "note", sign: "-"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "[!CAUTION]",
|
input: "[!NOTE] This is a note",
|
||||||
expected: "caution",
|
expected: blockQuoteAlert{typ: "note", title: "This is a note"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
input: "[!FOO]",
|
input: "[!NOTE]+ This is a note",
|
||||||
expected: "",
|
expected: blockQuoteAlert{typ: "note", sign: "+", title: "This is a note"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[!NOTE]+ This is a title\nThis is not.",
|
||||||
|
expected: blockQuoteAlert{typ: "note", sign: "+", title: "This is a title"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[!NOTE]\nThis is not.",
|
||||||
|
expected: blockQuoteAlert{typ: "note"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for i, test := range tests {
|
||||||
c.Assert(resolveGitHubAlert("<p>"+test.input), qt.Equals, test.expected)
|
c.Assert(resolveBlockQuoteAlert("<p>"+test.input), qt.Equals, test.expected, qt.Commentf("Test %d", i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue