Skip to content

Commit cd6633f

Browse files
authored
Merge pull request #430 from rdestefa/issue-429-custom-alert-titles
Add Support for Custom Alert Titles
2 parents 75c6e4c + d09c3c0 commit cd6633f

15 files changed

Lines changed: 869 additions & 227 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,24 @@ with the exception that 0.x versions can break between minor versions.
88

99
## [Unreleased]
1010
### Added
11-
- Allow customizing HTML attributes for alert title `<p>` tag via `AttributeProvider`
1211
- Support rendering GFM task list items to Markdown
1312
- Support rendering YAML front matter to Markdown
13+
- Alerts
14+
- Allow customizing HTML attributes for alert title `<p>` tag via `AttributeProvider`
15+
- New configuration for `AlertsExtension` to allow authors to provide custom
16+
titles per alert. See the
17+
[custom titles section of the alerts README](./commonmark-ext-gfm-alerts/README.md#custom-alert-titles)
18+
for more information.
19+
- New configuration for `AlertsExtension` to allow alerts to be nested within
20+
other blocks (including other alerts). See
21+
[this section of the alerts README](./commonmark-ext-gfm-alerts/README.md#nesting-alerts)
22+
for more information.
1423

1524
## [0.28.0] - 2026-03-31
1625
### Added
1726
- New extension for alerts (aka callouts/admonitions)
1827
- Syntax:
19-
```
28+
```markdown
2029
> [!NOTE]
2130
> The text of the note.
2231
```
@@ -104,9 +113,9 @@ with the exception that 0.x versions can break between minor versions.
104113
### Added
105114
- New extension for footnotes!
106115
- Syntax:
107-
```
116+
```markdown
108117
Main text[^1]
109-
118+
110119
[^1]: Additional text in a footnote
111120
```
112121
- Inline footnotes like `^[inline footnote]` are also supported when enabled
@@ -271,7 +280,7 @@ with the exception that 0.x versions can break between minor versions.
271280
- Use class `ImageAttributesExtension` in artifact `commonmark-ext-image-attributes`
272281
- Extension for task lists (GitHub-style), thanks @dohertyfjatl
273282
- Syntax:
274-
```
283+
```markdown
275284
- [x] task #1
276285
- [ ] task #2
277286
```

README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -339,21 +339,22 @@ Use class `TablesExtension` in artifact `commonmark-ext-gfm-tables`.
339339

340340
Adds support for GitHub-style alerts (also known as callouts or admonitions) as described [here](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts), e.g.:
341341

342-
```
342+
```markdown
343343
> [!NOTE]
344344
> The text of the note.
345345
```
346346

347347
As types you can use NOTE, TIP, IMPORTANT, WARNING, CAUTION; or configure the extension to add additional ones.
348348

349-
Use class `AlertsExtension` in artifact `commonmark-ext-gfm-alerts`.
349+
Use class `AlertsExtension` in artifact `commonmark-ext-gfm-alerts`. See the
350+
[`AlertsExtension` README](./commonmark-ext-gfm-alerts/README.md) for more information.
350351

351352
### Footnotes
352353

353354
Enables footnotes like in [GitHub](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#footnotes)
354355
or [Pandoc](https://pandoc.org/MANUAL.html#footnotes):
355356

356-
```
357+
```markdown
357358
Main text[^1]
358359

359360
[^1]: Additional text in a footnote
@@ -370,7 +371,7 @@ is based on the text of the heading.
370371

371372
`# Heading` will be rendered as:
372373

373-
```
374+
```html
374375
<h1 id="heading">Heading</h1>
375376
```
376377

@@ -391,7 +392,7 @@ Use class `InsExtension` in artifact `commonmark-ext-ins`.
391392

392393
Adds support for metadata through a YAML front matter block. This extension only supports a subset of YAML syntax. Here's an example of what's supported:
393394

394-
```
395+
```markdown
395396
---
396397
key: value
397398
list:
@@ -414,11 +415,11 @@ Adds support for specifying attributes (specifically height and width) for image
414415

415416
The attribute elements are given as `key=value` pairs inside curly braces `{ }` after the image node to which they apply,
416417
for example:
417-
```
418+
```markdown
418419
![text](/url.png){width=640 height=480}
419420
```
420421
will be rendered as:
421-
```
422+
```html
422423
<img src="/url.png" alt="text" width="640" height="480" />
423424
```
424425

@@ -436,12 +437,12 @@ whitespace character or the letter `x` in lowercase or uppercase, then a right b
436437
whitespace before any other content.
437438

438439
For example:
439-
```
440+
```markdown
440441
- [ ] task #1
441442
- [x] task #2
442443
```
443444
will be rendered as:
444-
```
445+
```html
445446
<ul>
446447
<li><input type="checkbox" disabled=""> task #1</li>
447448
<li><input type="checkbox" disabled="" checked=""> task #2</li>

commonmark-ext-gfm-alerts/README.md

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Enables highlighting important information using blockquote syntax with five sta
66

77
## Usage
88

9-
#### Markdown Syntax
9+
### Markdown Syntax
1010

1111
```markdown
1212
> [!NOTE]
@@ -16,15 +16,15 @@ Enables highlighting important information using blockquote syntax with five sta
1616
> Critical information
1717
```
1818

19-
#### Standard GFM Types
19+
### Standard GFM Types
2020

2121
```java
2222
var extension = AlertsExtension.create();
2323
var parser = Parser.builder().extensions(List.of(extension)).build();
2424
var renderer = HtmlRenderer.builder().extensions(List.of(extension)).build();
2525
```
2626

27-
#### Custom Alert Types
27+
### Custom Alert Types
2828

2929
Add custom types beyond the five standard GFM types:
3030

@@ -36,7 +36,45 @@ var extension = AlertsExtension.builder()
3636

3737
Custom types must be UPPERCASE. Standard type titles can also be overridden for localization.
3838

39-
#### Styling
39+
### Custom Alert Titles
40+
41+
Allow authors to provide custom titles per alert by adding text after the alert
42+
marker on the same line:
43+
44+
```java
45+
var extension = AlertsExtension.builder().allowCustomTitles(true).build();
46+
```
47+
48+
```markdown
49+
> [!NOTE] Keep in mind <!-- Overrides default title of "Note" -->
50+
> Useful information
51+
52+
> [!WARNING] Be **very** careful <!-- Inline formatting is supported -->
53+
> Critical information
54+
```
55+
56+
### Nesting Alerts
57+
58+
By default, alerts cannot be nested within other blocks. Alerts within other
59+
blocks are parsed as regular block quotes.
60+
61+
```markdown
62+
<!-- Allowed -->
63+
> [!NOTE]
64+
> Useful information
65+
66+
<!-- Not allowed -->
67+
- > [!NOTE]
68+
> Useful information
69+
```
70+
71+
This behavior can be changed to allow nested alerts:
72+
73+
```java
74+
var extension = AlertsExtension.builder().allowNestedAlerts(true).build();
75+
```
76+
77+
### Styling
4078

4179
Alerts render as `<div>` elements with CSS classes:
4280

@@ -51,9 +89,9 @@ Basic CSS example:
5189

5290
```css
5391
.markdown-alert {
54-
padding: 0.5rem 1rem;
55-
margin-bottom: 1rem;
56-
border-left: 4px solid;
92+
padding: 0.5rem 1rem;
93+
margin-bottom: 1rem;
94+
border-left: 4px solid;
5795
}
5896

5997
.markdown-alert-note { border-color: #0969da; background-color: #ddf4ff; }

commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/Alert.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
/**
66
* Alert block for highlighting important information using {@code [!TYPE]} syntax.
7+
*
8+
* @see AlertTitle
79
*/
810
public class Alert extends CustomBlock {
911

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.commonmark.ext.gfm.alerts;
2+
3+
import org.commonmark.node.CustomNode;
4+
5+
/**
6+
* Inline content container for the optional custom title of an {@link Alert}.
7+
*
8+
* <p>
9+
* When present, an {@code AlertTitle} is always the first child of an {@link Alert}.
10+
* Its own children are the parsed inline nodes of the title (i.e., the text after
11+
* the {@code [!TYPE]} marker on the same line). For example, in
12+
*
13+
* <pre>{@code
14+
* > [!NOTE] Custom _title_
15+
* > Body text
16+
* }</pre>
17+
*
18+
* the {@code AlertTitle} contains a {@code Text} node ({@code "Custom "}) followed
19+
* by an {@code Emphasis} node wrapping {@code "title"}.
20+
*
21+
* @see AlertsExtension.Builder#allowCustomTitles(boolean)
22+
*/
23+
public class AlertTitle extends CustomNode {
24+
}

commonmark-ext-gfm-alerts/src/main/java/org/commonmark/ext/gfm/alerts/AlertsExtension.java

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package org.commonmark.ext.gfm.alerts;
22

33
import org.commonmark.Extension;
4-
import org.commonmark.ext.gfm.alerts.internal.AlertPostProcessor;
4+
import org.commonmark.ext.gfm.alerts.internal.AlertBlockParser;
55
import org.commonmark.ext.gfm.alerts.internal.AlertHtmlNodeRenderer;
66
import org.commonmark.ext.gfm.alerts.internal.AlertMarkdownNodeRenderer;
77
import org.commonmark.parser.Parser;
@@ -21,19 +21,44 @@
2121
* Extension for GFM alerts using {@code [!TYPE]} syntax (GitHub Flavored Markdown).
2222
* <p>
2323
* Create with {@link #create()} or {@link #builder()} and configure on builders
24-
* ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)},
25-
* {@link HtmlRenderer.Builder#extensions(Iterable)}).
26-
* Parsed alerts become {@link Alert} blocks.
24+
* ({@link Parser.Builder#extensions(Iterable)}, {@link HtmlRenderer.Builder#extensions(Iterable)}).
25+
* Parsed alerts become {@link Alert} blocks. If custom alert titles are allowed
26+
* via {@link Builder#allowCustomTitles(boolean)}, the inline formatting of those
27+
* titles will be parsed into {@link AlertTitle} nodes.
28+
*
29+
* The {@link #create() default configuration} of this extension will match GFM
30+
* exactly, with the following exceptions:
31+
*
32+
* - Alert markers take precedence over <a href="https://spec.commonmark.org/current/#shortcut-reference-link">shortcut reference links</a>.
33+
* - Alerts with no content are allowed. Example:
34+
*
35+
* <pre>{@code
36+
* <!-- Valid -->
37+
* > [!NOTE]
38+
*
39+
* <!-- Also valid if custom titles are allowed -->
40+
* > [!NOTE] Custom title
41+
* }</pre>
42+
* - Lazy continuation is not allowed between the marker and the body text. Example:
43+
*
44+
* <pre>{@code
45+
* > [!NOTE]
46+
* Lazy body text will be parsed as a new paragraph
47+
* }</pre>
2748
*/
2849
public class AlertsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension,
2950
MarkdownRenderer.MarkdownRendererExtension {
3051

3152
static final Set<String> STANDARD_TYPES = Set.of("NOTE", "TIP", "IMPORTANT", "WARNING", "CAUTION");
3253

3354
private final Map<String, String> customTypes;
55+
private final boolean customTitlesAllowed;
56+
private final boolean nestedAlertsAllowed;
3457

3558
private AlertsExtension(Builder builder) {
3659
this.customTypes = new HashMap<>(builder.customTypes);
60+
this.customTitlesAllowed = builder.customTitlesAllowed;
61+
this.nestedAlertsAllowed = builder.nestedAlertsAllowed;
3762
}
3863

3964
public static Extension create() {
@@ -48,7 +73,8 @@ public static Builder builder() {
4873
public void extend(Parser.Builder parserBuilder) {
4974
var allowedTypes = new HashSet<>(STANDARD_TYPES);
5075
allowedTypes.addAll(customTypes.keySet());
51-
parserBuilder.postProcessor(new AlertPostProcessor(allowedTypes));
76+
parserBuilder.customBlockParserFactory(
77+
new AlertBlockParser.Factory(allowedTypes, customTitlesAllowed, nestedAlertsAllowed));
5278
}
5379

5480
@Override
@@ -76,6 +102,8 @@ public Set<Character> getSpecialCharacters() {
76102
*/
77103
public static class Builder {
78104
private final Map<String, String> customTypes = new HashMap<>();
105+
private boolean customTitlesAllowed = false;
106+
private boolean nestedAlertsAllowed = false;
79107

80108
/**
81109
* Adds a custom alert type with a display title.
@@ -101,6 +129,34 @@ public Builder addCustomType(String type, String title) {
101129
return this;
102130
}
103131

132+
/**
133+
* Allows or disallows custom titles on alerts. Inline formatting is supported
134+
* within these titles.
135+
* @param allow Whether to allow or disallow custom titles on alerts.
136+
* @return {@code this}
137+
* @see AlertTitle
138+
*/
139+
public Builder allowCustomTitles(boolean allow) {
140+
customTitlesAllowed = allow;
141+
return this;
142+
}
143+
144+
/**
145+
* Allows or disallows parsing alerts within non-root blocks ({@code Document}).
146+
* <p>
147+
* When disallowed, if an alert appears within another block, it will be parsed as
148+
* a regular {@code BlockQuote}.
149+
* <p>
150+
* Note that even when this is allowed, {@link Parser.Builder#maxOpenBlockParsers(int)}
151+
* will be respected.
152+
* @param allow Whether to allow or disallow parsing alerts within non-root blocks.
153+
* @return {@code this}
154+
*/
155+
public Builder allowNestedAlerts(boolean allow) {
156+
nestedAlertsAllowed = allow;
157+
return this;
158+
}
159+
104160
/**
105161
* @return a configured {@link Extension}
106162
*/

0 commit comments

Comments
 (0)