From c3a5e669c1a85d3b0e1b0d598b19e7abbdebac06 Mon Sep 17 00:00:00 2001
From: Jonas Finnemann Jensen
Date: Thu, 22 Feb 2024 17:31:01 +0100
Subject: [PATCH] Implement `AlertBlockSyntax` using `BlockquoteSyntax`
As I understand the alert-block syntax, it is an extension on the
orignal blockquote support in markdown. Basically, it's a blockquote
that is rendered differently.
That means that if alert-block syntax isn't supported a markdown parser
will render it as a blockquote. This provides a nice graceful fallback.
It also means that if you're implementing it, you really should
implement it the same way you implement blockquote parsing.
---
lib/markdown.dart | 1 -
.../block_syntaxes/alert_block_syntax.dart | 110 ------------------
lib/src/block_syntaxes/blockquote_syntax.dart | 62 ++++++++++
lib/src/extension_set.dart | 2 +-
test/extensions/alert_extension.unit | 9 ++
5 files changed, 72 insertions(+), 112 deletions(-)
delete mode 100644 lib/src/block_syntaxes/alert_block_syntax.dart
diff --git a/lib/markdown.dart b/lib/markdown.dart
index a09e763a..409baaf6 100644
--- a/lib/markdown.dart
+++ b/lib/markdown.dart
@@ -42,7 +42,6 @@ import 'src/version.dart';
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/lib/src/block_syntaxes/alert_block_syntax.dart b/lib/src/block_syntaxes/alert_block_syntax.dart
deleted file mode 100644
index 54286040..00000000
--- a/lib/src/block_syntaxes/alert_block_syntax.dart
+++ /dev/null
@@ -1,110 +0,0 @@
-// 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 parseChildLines(BlockParser parser) {
- // Grab all of the lines that form the alert, stripping off the ">".
- final childLines = [];
- _lazyContinuation = false;
-
- while (!parser.isDone) {
- final strippedContent =
- parser.current.content.replaceFirst(RegExp(r'^\s*>?\s*'), '');
- final match = strippedContent.isEmpty
- ? null
- : _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';
- return Element('div', [titleElement, ...children])
- ..attributes['class'] = elementClass;
- }
-}
diff --git a/lib/src/block_syntaxes/blockquote_syntax.dart b/lib/src/block_syntaxes/blockquote_syntax.dart
index afbe23a0..a4f55377 100644
--- a/lib/src/block_syntaxes/blockquote_syntax.dart
+++ b/lib/src/block_syntaxes/blockquote_syntax.dart
@@ -97,3 +97,65 @@ class BlockquoteSyntax extends BlockSyntax {
return Element('blockquote', children);
}
}
+
+/// 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 {
+ @override
+ RegExp get pattern => alertPattern;
+
+ const AlertBlockSyntax();
+
+ @override
+ List parseChildLines(BlockParser parser) {
+ return const BlockquoteSyntax().parseChildLines(parser);
+ }
+
+ @override
+ Node parse(BlockParser parser) {
+ // Parse the alert type from the first line.
+ final type =
+ pattern.firstMatch(parser.current.content)!.group(1)!.toLowerCase();
+
+ final childLines = parseChildLines(parser);
+ // Until we've parse all the child lines, we can't actually know if this is
+ // a blockquote containing `[!note]` or if this is an alert-block.
+ //
+ // This is because `> [!note]` is not a valid alert-block!
+ final isBlockquote = childLines.length <= 1;
+
+ if (!isBlockquote) {
+ // Always remove the first line, this is the line that contained the type.
+ childLines.removeAt(0);
+ }
+
+ // Recursively parse the contents of the blockquote.
+ 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: BlockquoteSyntax._lazyContinuation,
+ parentSyntax: this,
+ );
+
+ if (isBlockquote) {
+ return Element('blockquote', children);
+ }
+
+ // 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';
+ return Element('div', [titleElement, ...children])
+ ..attributes['class'] = elementClass;
+ }
+}
diff --git a/lib/src/extension_set.dart b/lib/src/extension_set.dart
index 58a25d86..2463b462 100644
--- a/lib/src/extension_set.dart
+++ b/lib/src/extension_set.dart
@@ -1,5 +1,5 @@
-import 'block_syntaxes/alert_block_syntax.dart';
import 'block_syntaxes/block_syntax.dart';
+import 'block_syntaxes/blockquote_syntax.dart';
import 'block_syntaxes/fenced_code_block_syntax.dart';
import 'block_syntaxes/footnote_def_syntax.dart';
import 'block_syntaxes/header_with_id_syntax.dart';
diff --git a/test/extensions/alert_extension.unit b/test/extensions/alert_extension.unit
index 0f422b5c..0d7f3385 100644
--- a/test/extensions/alert_extension.unit
+++ b/test/extensions/alert_extension.unit
@@ -129,3 +129,12 @@ Additional markdown text.
with two lines.
Additional markdown text.
+>>> #584
+> [!CAUTION]
+>
+> some warning
+<<<
+