Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.

Commit 5fab3a7

Browse files
authored
✨ Introduce AlertBlockSyntax (#570)
* ✨ Introduce `CalloutBlockSyntax` * ⚡️ More flex patterns * 🚀 Produce correct structure * ⚡️ Case-insensitive types * ⚡️ Improve `canParse` * ✅ Fill test cases * Update AUTHORS * 🚚 Rename * 📝 Add CHANGELOG * 🔥 Remove `zh` * Update CHANGELOG.md * Update pubspec.yaml * 🔖 Fix version * 🧪 Add escape brackets case * ⚡️ const type text map
1 parent d2e7903 commit 5fab3a7

10 files changed

+225
-6
lines changed

AUTHORS

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ Daniel Schubert <[email protected]>
1010
Jirka Daněk <[email protected]>
1111
Seth Westphal <[email protected]>
1212
Tim Maffett <[email protected]>
13-
13+

CHANGELOG.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
## 7.1.2-wip
1+
## 7.2.0-wip
22

33
* Require Dart `^3.1.0`.
44
* Update all CommonMark specification links to 0.30.
5-
* Fix beginning of line detection in `AutolinkExtensionSyntax`.
5+
* Fix beginning of line detection in `AutolinkExtensionSyntax`.
6+
* Add a new syntax `AlertBlockSyntax` to parse GitHub Alerts
67

78
## 7.1.1
89

lib/markdown.dart

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import 'src/version.dart';
4242

4343
export 'src/ast.dart';
4444
export 'src/block_parser.dart';
45+
export 'src/block_syntaxes/alert_block_syntax.dart';
4546
export 'src/block_syntaxes/block_syntax.dart';
4647
export 'src/block_syntaxes/blockquote_syntax.dart';
4748
export 'src/block_syntaxes/code_block_syntax.dart';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import '../ast.dart';
6+
import '../block_parser.dart';
7+
import '../line.dart';
8+
import '../patterns.dart';
9+
import 'block_syntax.dart';
10+
import 'code_block_syntax.dart';
11+
import 'paragraph_syntax.dart';
12+
13+
/// Parses GitHub Alerts blocks.
14+
///
15+
/// See also: https://docs.github.com/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts
16+
class AlertBlockSyntax extends BlockSyntax {
17+
const AlertBlockSyntax();
18+
19+
@override
20+
RegExp get pattern => alertPattern;
21+
22+
@override
23+
bool canParse(BlockParser parser) {
24+
return pattern.hasMatch(parser.current.content) &&
25+
parser.lines.any((line) => _contentLineRegExp.hasMatch(line.content));
26+
}
27+
28+
/// Whether this alert ends with a lazy continuation line.
29+
// The definition of lazy continuation lines:
30+
// https://spec.commonmark.org/0.30/#lazy-continuation-line
31+
static bool _lazyContinuation = false;
32+
static final _contentLineRegExp = RegExp(r'>?\s?(.*)*');
33+
34+
@override
35+
List<Line> parseChildLines(BlockParser parser) {
36+
// Grab all of the lines that form the alert, stripping off the ">".
37+
final childLines = <Line>[];
38+
_lazyContinuation = false;
39+
40+
while (!parser.isDone) {
41+
final strippedContent =
42+
parser.current.content.replaceFirst(RegExp(r'^\s*>?\s*'), '');
43+
final match = _contentLineRegExp.firstMatch(strippedContent);
44+
if (match != null) {
45+
childLines.add(Line(strippedContent));
46+
parser.advance();
47+
_lazyContinuation = false;
48+
continue;
49+
}
50+
51+
final lastLine = childLines.last;
52+
53+
// A paragraph continuation is OK. This is content that cannot be parsed
54+
// as any other syntax except Paragraph, and it doesn't match the bar in
55+
// a Setext header.
56+
// Because indented code blocks cannot interrupt paragraphs, a line
57+
// matched CodeBlockSyntax is also paragraph continuation text.
58+
final otherMatched =
59+
parser.blockSyntaxes.firstWhere((s) => s.canParse(parser));
60+
if ((otherMatched is ParagraphSyntax &&
61+
!lastLine.isBlankLine &&
62+
!codeFencePattern.hasMatch(lastLine.content)) ||
63+
(otherMatched is CodeBlockSyntax &&
64+
!indentPattern.hasMatch(lastLine.content))) {
65+
childLines.add(parser.current);
66+
_lazyContinuation = true;
67+
parser.advance();
68+
} else {
69+
break;
70+
}
71+
}
72+
73+
return childLines;
74+
}
75+
76+
@override
77+
Node parse(BlockParser parser) {
78+
// Parse the alert type from the first line.
79+
final type =
80+
pattern.firstMatch(parser.current.content)!.group(1)!.toLowerCase();
81+
parser.advance();
82+
final childLines = parseChildLines(parser);
83+
// Recursively parse the contents of the alert.
84+
final children = BlockParser(childLines, parser.document).parseLines(
85+
// The setext heading underline cannot be a lazy continuation line in a
86+
// block quote.
87+
// https://spec.commonmark.org/0.30/#example-93
88+
disabledSetextHeading: _lazyContinuation,
89+
parentSyntax: this,
90+
);
91+
92+
// Mapping the alert title text.
93+
const typeTextMap = {
94+
'note': 'Note',
95+
'tip': 'Tip',
96+
'important': 'Important',
97+
'caution': 'Caution',
98+
'warning': 'Warning',
99+
};
100+
final titleText = typeTextMap[type]!;
101+
final titleElement = Element('p', [Text(titleText)])
102+
..attributes['class'] = 'markdown-alert-title';
103+
final elementClass = 'markdown-alert markdown-alert-${type.toLowerCase()}';
104+
return Element('div', [titleElement, ...children])
105+
..attributes['class'] = elementClass;
106+
}
107+
}

lib/src/extension_set.dart

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'block_syntaxes/alert_block_syntax.dart';
12
import 'block_syntaxes/block_syntax.dart';
23
import 'block_syntaxes/fenced_code_block_syntax.dart';
34
import 'block_syntaxes/footnote_def_syntax.dart';
@@ -60,6 +61,7 @@ class ExtensionSet {
6061
const UnorderedListWithCheckboxSyntax(),
6162
const OrderedListWithCheckboxSyntax(),
6263
const FootnoteDefSyntax(),
64+
const AlertBlockSyntax(),
6365
],
6466
),
6567
List<InlineSyntax>.unmodifiable(

lib/src/patterns.dart

+9
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,12 @@ final htmlCharactersPattern = RegExp(
151151

152152
/// A line starts with `[`.
153153
final linkReferenceDefinitionPattern = RegExp(r'^[ ]{0,3}\[');
154+
155+
/// Alert type patterns.
156+
/// A alert block is similar to a blockquote,
157+
/// starts with `> [!TYPE]`, and only 5 types are supported
158+
/// with case-insensitive.
159+
final alertPattern = RegExp(
160+
r'^\s{0,3}>\s{0,3}\\?\[!(note|tip|important|caution|warning)\\?\]\s*$',
161+
caseSensitive: false,
162+
);

lib/src/version.dart

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: markdown
2-
version: 7.1.2-wip
2+
version: 7.2.0-wip
33

44
description: >-
55
A portable Markdown library written in Dart that can parse Markdown into HTML.

test/extensions/alert_extension.unit

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
>>> type note
2+
> [!NoTe]
3+
> Test note alert.
4+
<<<
5+
<div class="markdown-alert markdown-alert-note">
6+
<p class="markdown-alert-title">Note</p>
7+
<p>Test note alert.</p>
8+
</div>
9+
>>> type tip
10+
> [!TiP]
11+
> Test tip alert.
12+
<<<
13+
<div class="markdown-alert markdown-alert-tip">
14+
<p class="markdown-alert-title">Tip</p>
15+
<p>Test tip alert.</p>
16+
</div>
17+
>>> type important
18+
> [!ImpoRtanT]
19+
> Test important alert.
20+
<<<
21+
<div class="markdown-alert markdown-alert-important">
22+
<p class="markdown-alert-title">Important</p>
23+
<p>Test important alert.</p>
24+
</div>
25+
>>> type warning
26+
> [!WarNinG]
27+
> Test warning alert.
28+
<<<
29+
<div class="markdown-alert markdown-alert-warning">
30+
<p class="markdown-alert-title">Warning</p>
31+
<p>Test warning alert.</p>
32+
</div>
33+
>>> type caution
34+
> [!CauTioN]
35+
> Test caution alert.
36+
<<<
37+
<div class="markdown-alert markdown-alert-caution">
38+
<p class="markdown-alert-title">Caution</p>
39+
<p>Test caution alert.</p>
40+
</div>
41+
>>> invalid type
42+
> [!foo]
43+
> Test foo alert.
44+
<<<
45+
<blockquote>
46+
<p>[!foo]
47+
Test foo alert.</p>
48+
</blockquote>
49+
>>> contents can both contain/not contain starting quote
50+
> [!NOTE]
51+
Test note alert.
52+
>Test note alert x2.
53+
<<<
54+
<div class="markdown-alert markdown-alert-note">
55+
<p class="markdown-alert-title">Note</p>
56+
<p>Test note alert.
57+
Test note alert x2.</p>
58+
</div>
59+
>>> spaces everywhere
60+
> [!NOTE]
61+
> Test note alert.
62+
> Test note alert x2.
63+
<<<
64+
<div class="markdown-alert markdown-alert-note">
65+
<p class="markdown-alert-title">Note</p>
66+
<p>Test note alert.
67+
Test note alert x2.</p>
68+
</div>
69+
>>> title has 3 more spaces then fallback to blockquote
70+
> [!NOTE]
71+
> Test blockquote.
72+
<<<
73+
<blockquote>
74+
<p>[!NOTE]
75+
Test blockquote.</p>
76+
</blockquote>
77+
>>>nested blockquote
78+
> [!NOTE]
79+
>> Test nested blockquote.
80+
<<<
81+
<div class="markdown-alert markdown-alert-note">
82+
<p class="markdown-alert-title">Note</p>
83+
<blockquote>
84+
<p>Test nested blockquote.</p>
85+
</blockquote>
86+
</div>
87+
>>>escape brackets
88+
> \[!note\]
89+
> Test escape brackets.
90+
<<<
91+
<div class="markdown-alert markdown-alert-note">
92+
<p class="markdown-alert-title">Note</p>
93+
<p>Test escape brackets.</p>
94+
</div>

test/markdown_test.dart

+6-1
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,17 @@ void main() async {
4242
'extensions/unordered_list_with_checkboxes.unit',
4343
blockSyntaxes: [const UnorderedListWithCheckboxSyntax()],
4444
);
45+
testFile(
46+
'extensions/alert_extension.unit',
47+
blockSyntaxes: [const AlertBlockSyntax()],
48+
);
49+
50+
// Inline syntax extensions
4551
testFile(
4652
'extensions/autolink_extension.unit',
4753
inlineSyntaxes: [AutolinkExtensionSyntax()],
4854
);
4955

50-
// Inline syntax extensions
5156
testFile(
5257
'extensions/emojis.unit',
5358
inlineSyntaxes: [EmojiSyntax()],

0 commit comments

Comments
 (0)