β¨ Introduce AlertBlockSyntax (dart-lang/markdown#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
diff --git a/pkgs/markdown/AUTHORS b/pkgs/markdown/AUTHORS
index 7f87fc4..ca5b46b 100644
--- a/pkgs/markdown/AUTHORS
+++ b/pkgs/markdown/AUTHORS
@@ -10,4 +10,4 @@
Jirka DanΔk <dnk@mail.muni.cz>
Seth Westphal <westy92@gmail.com>
Tim Maffett <timmaffett@gmail.com>
-
+Alex Li <alexv.525.li@gmail.com>
diff --git a/pkgs/markdown/CHANGELOG.md b/pkgs/markdown/CHANGELOG.md
index 5fcfd57..fe71414 100644
--- a/pkgs/markdown/CHANGELOG.md
+++ b/pkgs/markdown/CHANGELOG.md
@@ -1,8 +1,9 @@
-## 7.1.2-wip
+## 7.2.0-wip
* Require Dart `^3.1.0`.
* Update all CommonMark specification links to 0.30.
-* Fix beginning of line detection in `AutolinkExtensionSyntax`.
+* Fix beginning of line detection in `AutolinkExtensionSyntax`.
+* Add a new syntax `AlertBlockSyntax` to parse GitHub Alerts
## 7.1.1
diff --git a/pkgs/markdown/lib/markdown.dart b/pkgs/markdown/lib/markdown.dart
index 409baaf..a09e763 100644
--- a/pkgs/markdown/lib/markdown.dart
+++ b/pkgs/markdown/lib/markdown.dart
@@ -42,6 +42,7 @@
export 'src/ast.dart';
export 'src/block_parser.dart';
+export 'src/block_syntaxes/alert_block_syntax.dart';
export 'src/block_syntaxes/block_syntax.dart';
export 'src/block_syntaxes/blockquote_syntax.dart';
export 'src/block_syntaxes/code_block_syntax.dart';
diff --git a/pkgs/markdown/lib/src/block_syntaxes/alert_block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/alert_block_syntax.dart
new file mode 100644
index 0000000..be28ca9
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/alert_block_syntax.dart
@@ -0,0 +1,107 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../ast.dart';
+import '../block_parser.dart';
+import '../line.dart';
+import '../patterns.dart';
+import 'block_syntax.dart';
+import 'code_block_syntax.dart';
+import 'paragraph_syntax.dart';
+
+/// Parses GitHub Alerts blocks.
+///
+/// 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
+class AlertBlockSyntax extends BlockSyntax {
+ const AlertBlockSyntax();
+
+ @override
+ RegExp get pattern => alertPattern;
+
+ @override
+ bool canParse(BlockParser parser) {
+ return pattern.hasMatch(parser.current.content) &&
+ parser.lines.any((line) => _contentLineRegExp.hasMatch(line.content));
+ }
+
+ /// Whether this alert ends with a lazy continuation line.
+ // The definition of lazy continuation lines:
+ // https://spec.commonmark.org/0.30/#lazy-continuation-line
+ static bool _lazyContinuation = false;
+ static final _contentLineRegExp = RegExp(r'>?\s?(.*)*');
+
+ @override
+ List<Line> parseChildLines(BlockParser parser) {
+ // Grab all of the lines that form the alert, stripping off the ">".
+ final childLines = <Line>[];
+ _lazyContinuation = false;
+
+ while (!parser.isDone) {
+ final strippedContent =
+ parser.current.content.replaceFirst(RegExp(r'^\s*>?\s*'), '');
+ final match = _contentLineRegExp.firstMatch(strippedContent);
+ if (match != null) {
+ childLines.add(Line(strippedContent));
+ parser.advance();
+ _lazyContinuation = false;
+ continue;
+ }
+
+ final lastLine = childLines.last;
+
+ // A paragraph continuation is OK. This is content that cannot be parsed
+ // as any other syntax except Paragraph, and it doesn't match the bar in
+ // a Setext header.
+ // Because indented code blocks cannot interrupt paragraphs, a line
+ // matched CodeBlockSyntax is also paragraph continuation text.
+ final otherMatched =
+ parser.blockSyntaxes.firstWhere((s) => s.canParse(parser));
+ if ((otherMatched is ParagraphSyntax &&
+ !lastLine.isBlankLine &&
+ !codeFencePattern.hasMatch(lastLine.content)) ||
+ (otherMatched is CodeBlockSyntax &&
+ !indentPattern.hasMatch(lastLine.content))) {
+ childLines.add(parser.current);
+ _lazyContinuation = true;
+ parser.advance();
+ } else {
+ break;
+ }
+ }
+
+ return childLines;
+ }
+
+ @override
+ Node parse(BlockParser parser) {
+ // Parse the alert type from the first line.
+ final type =
+ pattern.firstMatch(parser.current.content)!.group(1)!.toLowerCase();
+ parser.advance();
+ final childLines = parseChildLines(parser);
+ // Recursively parse the contents of the alert.
+ final children = BlockParser(childLines, parser.document).parseLines(
+ // The setext heading underline cannot be a lazy continuation line in a
+ // block quote.
+ // https://spec.commonmark.org/0.30/#example-93
+ disabledSetextHeading: _lazyContinuation,
+ parentSyntax: this,
+ );
+
+ // Mapping the alert title text.
+ const typeTextMap = {
+ 'note': 'Note',
+ 'tip': 'Tip',
+ 'important': 'Important',
+ 'caution': 'Caution',
+ 'warning': 'Warning',
+ };
+ final titleText = typeTextMap[type]!;
+ final titleElement = Element('p', [Text(titleText)])
+ ..attributes['class'] = 'markdown-alert-title';
+ final elementClass = 'markdown-alert markdown-alert-${type.toLowerCase()}';
+ return Element('div', [titleElement, ...children])
+ ..attributes['class'] = elementClass;
+ }
+}
diff --git a/pkgs/markdown/lib/src/extension_set.dart b/pkgs/markdown/lib/src/extension_set.dart
index 660759e..58a25d8 100644
--- a/pkgs/markdown/lib/src/extension_set.dart
+++ b/pkgs/markdown/lib/src/extension_set.dart
@@ -1,3 +1,4 @@
+import 'block_syntaxes/alert_block_syntax.dart';
import 'block_syntaxes/block_syntax.dart';
import 'block_syntaxes/fenced_code_block_syntax.dart';
import 'block_syntaxes/footnote_def_syntax.dart';
@@ -60,6 +61,7 @@
const UnorderedListWithCheckboxSyntax(),
const OrderedListWithCheckboxSyntax(),
const FootnoteDefSyntax(),
+ const AlertBlockSyntax(),
],
),
List<InlineSyntax>.unmodifiable(
diff --git a/pkgs/markdown/lib/src/patterns.dart b/pkgs/markdown/lib/src/patterns.dart
index c75838f..4899ea1 100644
--- a/pkgs/markdown/lib/src/patterns.dart
+++ b/pkgs/markdown/lib/src/patterns.dart
@@ -151,3 +151,12 @@
/// A line starts with `[`.
final linkReferenceDefinitionPattern = RegExp(r'^[ ]{0,3}\[');
+
+/// Alert type patterns.
+/// A alert block is similar to a blockquote,
+/// starts with `> [!TYPE]`, and only 5 types are supported
+/// with case-insensitive.
+final alertPattern = RegExp(
+ r'^\s{0,3}>\s{0,3}\\?\[!(note|tip|important|caution|warning)\\?\]\s*$',
+ caseSensitive: false,
+);
diff --git a/pkgs/markdown/lib/src/version.dart b/pkgs/markdown/lib/src/version.dart
index c8a2b00..a70cac5 100644
--- a/pkgs/markdown/lib/src/version.dart
+++ b/pkgs/markdown/lib/src/version.dart
@@ -1,2 +1,2 @@
// Generated code. Do not modify.
-const packageVersion = '7.1.2-wip';
+const packageVersion = '7.2.0-wip';
diff --git a/pkgs/markdown/pubspec.yaml b/pkgs/markdown/pubspec.yaml
index 6ddea8f..2ef835f 100644
--- a/pkgs/markdown/pubspec.yaml
+++ b/pkgs/markdown/pubspec.yaml
@@ -1,5 +1,5 @@
name: markdown
-version: 7.1.2-wip
+version: 7.2.0-wip
description: >-
A portable Markdown library written in Dart that can parse Markdown into HTML.
diff --git a/pkgs/markdown/test/extensions/alert_extension.unit b/pkgs/markdown/test/extensions/alert_extension.unit
new file mode 100644
index 0000000..c2a3da1
--- /dev/null
+++ b/pkgs/markdown/test/extensions/alert_extension.unit
@@ -0,0 +1,94 @@
+>>> type note
+> [!NoTe]
+> Test note alert.
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<p>Test note alert.</p>
+</div>
+>>> type tip
+> [!TiP]
+> Test tip alert.
+<<<
+<div class="markdown-alert markdown-alert-tip">
+<p class="markdown-alert-title">Tip</p>
+<p>Test tip alert.</p>
+</div>
+>>> type important
+> [!ImpoRtanT]
+> Test important alert.
+<<<
+<div class="markdown-alert markdown-alert-important">
+<p class="markdown-alert-title">Important</p>
+<p>Test important alert.</p>
+</div>
+>>> type warning
+> [!WarNinG]
+> Test warning alert.
+<<<
+<div class="markdown-alert markdown-alert-warning">
+<p class="markdown-alert-title">Warning</p>
+<p>Test warning alert.</p>
+</div>
+>>> type caution
+> [!CauTioN]
+> Test caution alert.
+<<<
+<div class="markdown-alert markdown-alert-caution">
+<p class="markdown-alert-title">Caution</p>
+<p>Test caution alert.</p>
+</div>
+>>> invalid type
+> [!foo]
+> Test foo alert.
+<<<
+<blockquote>
+<p>[!foo]
+Test foo alert.</p>
+</blockquote>
+>>> contents can both contain/not contain starting quote
+> [!NOTE]
+Test note alert.
+>Test note alert x2.
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<p>Test note alert.
+Test note alert x2.</p>
+</div>
+>>> spaces everywhere
+ > [!NOTE]
+> Test note alert.
+ > Test note alert x2.
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<p>Test note alert.
+Test note alert x2.</p>
+</div>
+>>> title has 3 more spaces then fallback to blockquote
+> [!NOTE]
+> Test blockquote.
+<<<
+<blockquote>
+<p>[!NOTE]
+Test blockquote.</p>
+</blockquote>
+>>>nested blockquote
+> [!NOTE]
+>> Test nested blockquote.
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<blockquote>
+<p>Test nested blockquote.</p>
+</blockquote>
+</div>
+>>>escape brackets
+> \[!note\]
+> Test escape brackets.
+<<<
+<div class="markdown-alert markdown-alert-note">
+<p class="markdown-alert-title">Note</p>
+<p>Test escape brackets.</p>
+</div>
diff --git a/pkgs/markdown/test/markdown_test.dart b/pkgs/markdown/test/markdown_test.dart
index 4d21fe8..1fea683 100644
--- a/pkgs/markdown/test/markdown_test.dart
+++ b/pkgs/markdown/test/markdown_test.dart
@@ -43,11 +43,16 @@
blockSyntaxes: [const UnorderedListWithCheckboxSyntax()],
);
testFile(
+ 'extensions/alert_extension.unit',
+ blockSyntaxes: [const AlertBlockSyntax()],
+ );
+
+ // Inline syntax extensions
+ testFile(
'extensions/autolink_extension.unit',
inlineSyntaxes: [AutolinkExtensionSyntax()],
);
- // Inline syntax extensions
testFile(
'extensions/emojis.unit',
inlineSyntaxes: [EmojiSyntax()],