Skip to content

Commit d2df7f6

Browse files
committed
Add Support for Custom Alert Titles
1 parent 75c6e4c commit d2df7f6

13 files changed

Lines changed: 767 additions & 146 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().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().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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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()
22+
* @see AlertsExtension.Builder#disallowCustomTitles()
23+
*/
24+
public class AlertTitle extends CustomNode {
25+
}

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

Lines changed: 61 additions & 2 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;
@@ -24,16 +24,31 @@
2424
* ({@link org.commonmark.parser.Parser.Builder#extensions(Iterable)},
2525
* {@link HtmlRenderer.Builder#extensions(Iterable)}).
2626
* Parsed alerts become {@link Alert} blocks.
27+
*
28+
* The {@link #create() default configuration} of this extension will match GFM
29+
* exactly, with the following exceptions:
30+
*
31+
* - Alert markers take precedence over link reference definitions.
32+
* - Lazy continuation is not allowed between the marker and the body text. Example:
33+
*
34+
* <pre>{@code
35+
* > [!NOTE]
36+
* Lazy body text will be parsed as a new paragraph
37+
* }</pre>
2738
*/
2839
public class AlertsExtension implements Parser.ParserExtension, HtmlRenderer.HtmlRendererExtension,
2940
MarkdownRenderer.MarkdownRendererExtension {
3041

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

3344
private final Map<String, String> customTypes;
45+
private final boolean customTitlesAllowed;
46+
private final boolean nestedAlertsAllowed;
3447

3548
private AlertsExtension(Builder builder) {
3649
this.customTypes = new HashMap<>(builder.customTypes);
50+
this.customTitlesAllowed = builder.customTitlesAllowed;
51+
this.nestedAlertsAllowed = builder.nestedAlertsAllowed;
3752
}
3853

3954
public static Extension create() {
@@ -48,7 +63,8 @@ public static Builder builder() {
4863
public void extend(Parser.Builder parserBuilder) {
4964
var allowedTypes = new HashSet<>(STANDARD_TYPES);
5065
allowedTypes.addAll(customTypes.keySet());
51-
parserBuilder.postProcessor(new AlertPostProcessor(allowedTypes));
66+
parserBuilder.customBlockParserFactory(
67+
new AlertBlockParser.Factory(allowedTypes, customTitlesAllowed, nestedAlertsAllowed));
5268
}
5369

5470
@Override
@@ -76,6 +92,8 @@ public Set<Character> getSpecialCharacters() {
7692
*/
7793
public static class Builder {
7894
private final Map<String, String> customTypes = new HashMap<>();
95+
private boolean customTitlesAllowed = false;
96+
private boolean nestedAlertsAllowed = false;
7997

8098
/**
8199
* Adds a custom alert type with a display title.
@@ -101,6 +119,47 @@ public Builder addCustomType(String type, String title) {
101119
return this;
102120
}
103121

122+
/**
123+
* Allows custom titles on alerts. See {@link AlertTitle} for more information.
124+
* @return {@code this}
125+
*/
126+
public Builder allowCustomTitles() {
127+
customTitlesAllowed = true;
128+
return this;
129+
}
130+
131+
/**
132+
* Disallows custom titles on alerts. See {@link AlertTitle} for more information.
133+
* @return {@code this}
134+
*/
135+
public Builder disallowCustomTitles() {
136+
customTitlesAllowed = false;
137+
return this;
138+
}
139+
140+
/**
141+
* Allows alerts to be parsed within blocks other than {@code Document} (the root).
142+
* <p>
143+
* Note that even with this enabled, {@link Parser.Builder#maxOpenBlockParsers(int)}
144+
* will be respected.
145+
* @return {@code this}
146+
*/
147+
public Builder allowNestedAlerts() {
148+
nestedAlertsAllowed = true;
149+
return this;
150+
}
151+
152+
/**
153+
* Prevents alerts from being parsed within blocks other than {@code Document}
154+
* (the root). If an alert appears within another block, it will be parsed as
155+
* a regular {@code BlockQuote}.
156+
* @return {@code this}
157+
*/
158+
public Builder disallowNestedAlerts() {
159+
nestedAlertsAllowed = false;
160+
return this;
161+
}
162+
104163
/**
105164
* @return a configured {@link Extension}
106165
*/

0 commit comments

Comments
 (0)