-
Notifications
You must be signed in to change notification settings - Fork 204
✨ Introduce AlertBlockSyntax #570
Changes from all commits
2791529
085eabc
cd63f43
d0bef01
3fa039f
4e18f53
4e5f95d
abec2dd
bfe95c9
18d05f8
18582f7
f3e3298
401ab1c
6f0a264
c634fcf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
>>> type note | ||
> [!NoTe] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These tests look great! Can you see about this text: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note foobar Ah it shows an alert. I'll add this case. |
||
> 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\] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ha! Normally I would call that a bug. But our job is to match what GitHub does, and they likely will not change this behavior without making a big fuss, so 🤷 lgtm 😁 |
||
> Test escape brackets. | ||
<<< | ||
<div class="markdown-alert markdown-alert-note"> | ||
<p class="markdown-alert-title">Note</p> | ||
<p>Test escape brackets.</p> | ||
</div> |
Uh oh!
There was an error while loading. Please reload this page.