Skip to content

Commit a4bc9d1

Browse files
committed
Add Support for Custom Alert Titles
1 parent 22d6637 commit a4bc9d1

13 files changed

Lines changed: 736 additions & 138 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,20 @@ with the exception that 0.x versions can break between minor versions.
99
## [Unreleased]
1010
### Added
1111
- Allow customizing HTML attributes for alert title `<p>` tag via `AttributeProvider`
12+
- New configuration for `AlertsExtension` to allow authors to provide custom
13+
titles per alert. See the
14+
[custom titles section of the alerts README](./commonmark-ext-gfm-alerts/README.md#custom-alert-titles)
15+
for more information.
16+
- New configuration for `AlertsExtension` to allow alerts to be nested within
17+
other blocks (including other alerts). See
18+
[this section of the alerts README](./commonmark-ext-gfm-alerts/README.md#nesting-alerts)
19+
for more information.
1220

1321
## [0.28.0] - 2026-03-31
1422
### Added
1523
- New extension for alerts (aka callouts/admonitions)
1624
- Syntax:
17-
```
25+
```markdown
1826
> [!NOTE]
1927
> The text of the note.
2028
```
@@ -102,9 +110,9 @@ with the exception that 0.x versions can break between minor versions.
102110
### Added
103111
- New extension for footnotes!
104112
- Syntax:
105-
```
113+
```markdown
106114
Main text[^1]
107-
115+
108116
[^1]: Additional text in a footnote
109117
```
110118
- Inline footnotes like `^[inline footnote]` are also supported when enabled
@@ -269,7 +277,7 @@ with the exception that 0.x versions can break between minor versions.
269277
- Use class `ImageAttributesExtension` in artifact `commonmark-ext-image-attributes`
270278
- Extension for task lists (GitHub-style), thanks @dohertyfjatl
271279
- Syntax:
272-
```
280+
```markdown
273281
- [x] task #1
274282
- [ ] task #2
275283
```

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

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

3546
private final Map<String, String> customTypes;
47+
private final boolean customTitlesAllowed;
48+
private final boolean nestedAlertsAllowed;
3649

3750
private AlertsExtension(Builder builder) {
3851
this.customTypes = new HashMap<>(builder.customTypes);
52+
this.customTitlesAllowed = builder.customTitlesAllowed;
53+
this.nestedAlertsAllowed = builder.nestedAlertsAllowed;
3954
}
4055

4156
public static Extension create() {
@@ -50,7 +65,8 @@ public static Builder builder() {
5065
public void extend(Parser.Builder parserBuilder) {
5166
var allowedTypes = new HashSet<>(STANDARD_TYPES);
5267
allowedTypes.addAll(customTypes.keySet());
53-
parserBuilder.postProcessor(new AlertPostProcessor(allowedTypes));
68+
parserBuilder.customBlockParserFactory(
69+
new AlertBlockParser.Factory(allowedTypes, customTitlesAllowed, nestedAlertsAllowed));
5470
}
5571

5672
@Override
@@ -83,6 +99,8 @@ public Set<Character> getSpecialCharacters() {
8399
*/
84100
public static class Builder {
85101
private final Map<String, String> customTypes = new HashMap<>();
102+
private boolean customTitlesAllowed = false;
103+
private boolean nestedAlertsAllowed = false;
86104

87105
/**
88106
* Adds a custom alert type with a display title.
@@ -108,6 +126,47 @@ public Builder addCustomType(String type, String title) {
108126
return this;
109127
}
110128

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

0 commit comments

Comments
 (0)