diff --git a/pkgs/markdown/CHANGELOG.md b/pkgs/markdown/CHANGELOG.md index cc4337702f..d6e4693d8f 100644 --- a/pkgs/markdown/CHANGELOG.md +++ b/pkgs/markdown/CHANGELOG.md @@ -1,5 +1,7 @@ ## 7.3.1-wip +* Preserve metadata passed to fenced code blocks as + `data-metadata` on the created `pre` element. * Update the README link to the markdown playground (https://dart-lang.github.io/tools). * Update `package:web` API references in the example. diff --git a/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart index de695c7a99..c6a4f52e12 100644 --- a/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart +++ b/pkgs/markdown/lib/src/block_syntaxes/fenced_code_block_syntax.dart @@ -22,9 +22,9 @@ class FencedCodeBlockSyntax extends BlockSyntax { @override Node parse(BlockParser parser) { - final openingFence = _FenceMatch.fromMatch(pattern.firstMatch( - escapePunctuation(parser.current.content), - )!); + final openingFence = _FenceMatch.fromMatch( + pattern.firstMatch(escapePunctuation(parser.current.content))!, + ); var text = parseChildLines( parser, @@ -39,16 +39,31 @@ class FencedCodeBlockSyntax extends BlockSyntax { text = '$text\n'; } + final (languageString, metadataString) = openingFence.languageAndMetadata; + final code = Element.text('code', text); - if (openingFence.hasLanguage) { - var language = decodeHtmlCharacters(openingFence.language); - if (parser.document.encodeHtml) { - language = escapeHtmlAttribute(language); - } - code.attributes['class'] = 'language-$language'; + if (languageString != null) { + final processedLanguage = _processAttribute(languageString, + encodeHtml: parser.document.encodeHtml); + code.attributes['class'] = 'language-$processedLanguage'; + } + + final pre = Element('pre', [code]); + if (metadataString != null) { + final processedMetadata = _processAttribute(metadataString, + encodeHtml: parser.document.encodeHtml); + pre.attributes['data-metadata'] = processedMetadata; } - return Element('pre', [code]); + return pre; + } + + static String _processAttribute(String value, {bool encodeHtml = false}) { + final decodedValue = decodeHtmlCharacters(value); + if (encodeHtml) { + return escapeHtmlAttribute(decodedValue); + } + return decodedValue; } @override @@ -144,12 +159,30 @@ class _FenceMatch { // https://spec.commonmark.org/0.30/#info-string. final String info; - // The first word of the info string is typically used to specify the language - // of the code sample, - // https://spec.commonmark.org/0.30/#example-143. - String get language => info.split(' ').first; + /// Returns the language and remaining metadata from the [info] string. + /// + /// The language is the first word of the info string, + /// to match the (unspecified, but typical) behavior of CommonMark parsers, + /// as suggested in https://spec.commonmark.org/0.30/#example-143. + /// + /// The metadata is any remaining part of the info string after the language. + (String? language, String? metadata) get languageAndMetadata { + if (info.isEmpty) { + return (null, null); + } - bool get hasInfo => info.isNotEmpty; + // We assume the info string is trimmed already. + final firstSpaceIndex = info.indexOf(' '); + if (firstSpaceIndex == -1) { + // If there is no space, the whole string is the language. + return (info, null); + } - bool get hasLanguage => language.isNotEmpty; + return ( + info.substring(0, firstSpaceIndex), + info.substring(firstSpaceIndex + 1), + ); + } + + bool get hasInfo => info.isNotEmpty; } diff --git a/pkgs/markdown/test/common_mark/fenced_code_blocks.unit b/pkgs/markdown/test/common_mark/fenced_code_blocks.unit index 06dec5865c..5854bc370f 100644 --- a/pkgs/markdown/test/common_mark/fenced_code_blocks.unit +++ b/pkgs/markdown/test/common_mark/fenced_code_blocks.unit @@ -214,7 +214,7 @@ def foo(x) end ~~~~~~~ <<< -
def foo(x)
+
def foo(x)
   return 3
 end
 
@@ -234,7 +234,7 @@ foo

foo ~~~ <<< -
foo
+
foo
 
>>> Fenced code blocks - 147 ``` diff --git a/pkgs/markdown/test/gfm/fenced_code_blocks.unit b/pkgs/markdown/test/gfm/fenced_code_blocks.unit index f6fa8432a7..52f217ae18 100644 --- a/pkgs/markdown/test/gfm/fenced_code_blocks.unit +++ b/pkgs/markdown/test/gfm/fenced_code_blocks.unit @@ -214,7 +214,7 @@ def foo(x) end ~~~~~~~ <<< -
def foo(x)
+
def foo(x)
   return 3
 end
 
@@ -234,7 +234,7 @@ foo

foo ~~~ <<< -
foo
+
foo
 
>>> Fenced code blocks - 117 ``` diff --git a/pkgs/markdown/test/original/fenced_code_block.unit b/pkgs/markdown/test/original/fenced_code_block.unit index 779e747c3c..3492dfca6a 100644 --- a/pkgs/markdown/test/original/fenced_code_block.unit +++ b/pkgs/markdown/test/original/fenced_code_block.unit @@ -5,3 +5,27 @@ <<<
'foo'
 
+>>> with basic metadata string +```dart meta +code +``` + +<<< +
code
+
+>>> with characters to escape +```dart title="main.dart" +code +``` + +<<< +
code
+
+>>> with HTML character reference +```dart | +code +``` + +<<< +
code
+
\ No newline at end of file