Skip to content

Commit

Permalink
Upgrade markdown package reference (#2364)
Browse files Browse the repository at this point in the history
  • Loading branch information
jezell authored and matthew-carroll committed Nov 8, 2024
1 parent 55bc88c commit 3e859a0
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 47 deletions.
25 changes: 14 additions & 11 deletions super_editor_markdown/lib/src/image_syntax.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class SuperEditorImageSyntax extends md.LinkSyntax {
);

@override
md.Node? close(
Iterable<md.Node>? close(
md.InlineParser parser,
covariant md.SimpleDelimiter opener,
md.Delimiter? closer, {
Expand All @@ -51,7 +51,7 @@ class SuperEditorImageSyntax extends md.LinkSyntax {
var leftParenIndex = parser.pos;
var inlineLink = _parseInlineLink(parser);
if (inlineLink != null) {
return _tryCreateInlineLink(parser, inlineLink, getChildren: getChildren);
return [ _tryCreateInlineLink(parser, inlineLink, getChildren: getChildren) ];
}
// At this point, we've matched `[...](`, but that `(` did not pan out to
// be an inline link. We must now check if `[...]` is simply a shortcut
Expand Down Expand Up @@ -140,7 +140,7 @@ class SuperEditorImageSyntax extends md.LinkSyntax {
/// Tries to create a reference link node.
///
/// Returns the link if it was successfully created, `null` otherwise.
md.Node? _tryCreateReferenceLink(md.InlineParser parser, String label,
List<md.Node>? _tryCreateReferenceLink(md.InlineParser parser, String label,
{required List<md.Node> Function() getChildren}) {
return _resolveReferenceLink(label, parser.document.linkReferences, getChildren: getChildren);
}
Expand Down Expand Up @@ -236,19 +236,21 @@ class SuperEditorImageSyntax extends md.LinkSyntax {
/// Otherwise, returns `null`.
///
/// [label] does not need to be normalized.
md.Node? _resolveReferenceLink(
List<md.Node>? _resolveReferenceLink(
String label,
Map<String, md.LinkReference> linkReferences, {
required List<md.Node> Function() getChildren,
}) {
final linkReference = linkReferences[_normalizeLinkLabel(label)];
if (linkReference != null) {
return createNode(
linkReference.destination,
linkReference.title,
//size: linkReference.size,
getChildren: getChildren,
);
return [
createNode(
linkReference.destination,
linkReference.title,
//size: linkReference.size,
getChildren: getChildren,
)
];
} else {
// This link has no reference definition. But we allow users of the
// library to specify a custom resolver function ([linkResolver]) that
Expand All @@ -262,7 +264,7 @@ class SuperEditorImageSyntax extends md.LinkSyntax {
if (resolved != null) {
getChildren();
}
return resolved;
return resolved != null ? [resolved] : null;
}
}

Expand Down Expand Up @@ -480,6 +482,7 @@ class SuperEditorImageSyntax extends md.LinkSyntax {
}
}

@override
md.Element createNode(
String destination,
String? title, {
Expand Down
94 changes: 69 additions & 25 deletions super_editor_markdown/lib/src/markdown_to_document_parsing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ MutableDocument deserializeMarkdownToDocument(
List<ElementToNodeConverter> customElementToNodeConverters = const [],
bool encodeHtml = false,
}) {
final markdownLines = const LineSplitter().convert(markdown);
final markdownLines = const LineSplitter().convert(markdown).map<md.Line>((String l) {
return md.Line(l);
}).toList();

final markdownDoc = md.Document(
encodeHtml: encodeHtml,
blockSyntaxes: [
...customBlockSyntax,
if (syntax == MarkdownSyntax.superEditor) ...[
Expand Down Expand Up @@ -58,6 +61,14 @@ MutableDocument deserializeMarkdownToDocument(
);
}

// Add 1 hanging line for every 2 blank lines at the end, need this to preserve behavior pre markdown 7.2.1
final hangingEmptyLines = markdownLines.reversed.takeWhile((md.Line l) => l.isBlankLine);
if(hangingEmptyLines.isNotEmpty && documentNodes.lastOrNull is ListItemNode) {
for(var i = 0; i < hangingEmptyLines.length ~/ 2; i++) {
documentNodes.add(ParagraphNode(id: Editor.createNodeId(), text: AttributedText()));
}
}

return MutableDocument(nodes: documentNodes);
}

Expand Down Expand Up @@ -391,6 +402,7 @@ class _MarkdownToDocument implements md.NodeVisitor {
text,
md.Document(
inlineSyntaxes: [
SingleStrikethroughSyntax(), // this needs to be before md.StrikethroughSyntax to be recognized
md.StrikethroughSyntax(),
UnderlineSyntax(),
if (syntax == MarkdownSyntax.superEditor) //
Expand Down Expand Up @@ -519,24 +531,56 @@ abstract class ElementToNodeConverter {
DocumentNode? handleElement(md.Element element);
}

/// A Markdown [TagSyntax] that matches underline spans of text, which are represented in
/// A Markdown [DelimiterSyntax] that matches underline spans of text, which are represented in
/// Markdown with surrounding `¬` tags, e.g., "this is ¬underline¬ text".
///
/// This [TagSyntax] produces `Element`s with a `u` tag.
class UnderlineSyntax extends md.TagSyntax {
UnderlineSyntax() : super('¬', requiresDelimiterRun: true, allowIntraWord: true);
/// This [DelimiterSyntax] produces `Element`s with a `u` tag.
class UnderlineSyntax extends md.DelimiterSyntax {

/// According to the docs:
///
/// https://pub.dev/documentation/markdown/latest/markdown/DelimiterSyntax-class.html
///
/// The DelimiterSyntax constructor takes a nullable. However, the problem is there is a bug in the underlying dart
/// library if you don't pass it. Due to these two lines, one sets it to const [] if not passed, then the next tries
/// to sort. So we have to pass something at the moment or it blows up.
///
/// https://github.com/dart-lang/markdown/blob/d53feae0760a4f0aae5ffdfb12d8e6acccf14b40/lib/src/inline_syntaxes/delimiter_syntax.dart#L67
/// https://github.com/dart-lang/markdown/blob/d53feae0760a4f0aae5ffdfb12d8e6acccf14b40/lib/src/inline_syntaxes/delimiter_syntax.dart#L319
static final _tags = [ md.DelimiterTag("u", 1) ];

UnderlineSyntax() : super('¬', requiresDelimiterRun: true, allowIntraWord: true, tags: _tags);

@override
md.Node? close(
Iterable<md.Node>? close(
md.InlineParser parser,
md.Delimiter opener,
md.Delimiter closer, {
required List<md.Node> Function() getChildren,
required String tag,
}) {
return md.Element('u', getChildren());
final element = md.Element('u', getChildren());
return [ element ];
}
}

/// A Markdown [DelimiterSyntax] that matches strikethrough spans of text, which are represented in
/// Markdown with surrounding `~` tags, e.g., "this is ~strikethrough~ text".
///
/// Markdown in library in 7.2.1 seems to not be matching single strikethroughs
///
/// This [DelimiterSyntax] produces `Element`s with a `del` tag.
class SingleStrikethroughSyntax extends md.DelimiterSyntax {
SingleStrikethroughSyntax()
: super(
'~',
requiresDelimiterRun: true,
allowIntraWord: true,
tags: [md.DelimiterTag('del', 1)],
);
}


/// Parses a paragraph preceded by an alignment token.
class _ParagraphWithAlignmentSyntax extends _EmptyLinePreservingParagraphSyntax {
/// This pattern matches the text aligment notation.
Expand All @@ -548,7 +592,7 @@ class _ParagraphWithAlignmentSyntax extends _EmptyLinePreservingParagraphSyntax

@override
bool canParse(md.BlockParser parser) {
if (!_alignmentNotationPattern.hasMatch(parser.current)) {
if (!_alignmentNotationPattern.hasMatch(parser.current.content)) {
return false;
}

Expand All @@ -564,7 +608,7 @@ class _ParagraphWithAlignmentSyntax extends _EmptyLinePreservingParagraphSyntax
/// We found a paragraph alignment token, but the block after the alignment token isn't a paragraph.
/// Therefore, the paragraph alignment token is actually regular content. This parser doesn't need to
/// take any action.
if (_standardNonParagraphBlockSyntaxes.any((syntax) => syntax.pattern.hasMatch(nextLine))) {
if (_standardNonParagraphBlockSyntaxes.any((syntax) => syntax.pattern.hasMatch(nextLine.content))) {
return false;
}

Expand All @@ -575,7 +619,7 @@ class _ParagraphWithAlignmentSyntax extends _EmptyLinePreservingParagraphSyntax

@override
md.Node? parse(md.BlockParser parser) {
final match = _alignmentNotationPattern.firstMatch(parser.current);
final match = _alignmentNotationPattern.firstMatch(parser.current.content);

// We've parsed the alignment token on the current line. We know a paragraph starts on the
// next line. Move the parser to the next line so that we can parse the paragraph.
Expand Down Expand Up @@ -630,13 +674,13 @@ class _EmptyLinePreservingParagraphSyntax extends md.BlockSyntax {
return false;
}

if (parser.current.isEmpty) {
if (parser.current.content.isEmpty) {
// We consider this input to be a separator between blocks because
// it started with an empty line. We want to parse this input.
return true;
}

if (_isAtParagraphEnd(parser, ignoreEmptyBlocks: _endsWithHardLineBreak(parser.current))) {
if (_isAtParagraphEnd(parser, ignoreEmptyBlocks: _endsWithHardLineBreak(parser.current.content))) {
// Another parser wants to parse this input. Let the other parser run.
return false;
}
Expand All @@ -648,12 +692,12 @@ class _EmptyLinePreservingParagraphSyntax extends md.BlockSyntax {
@override
md.Node? parse(md.BlockParser parser) {
final childLines = <String>[];
final startsWithEmptyLine = parser.current.isEmpty;
final startsWithEmptyLine = parser.current.content.isEmpty;

// A hard line break causes the next line to be treated
// as part of the same paragraph, except if the next line is
// the beginning of another block element.
bool hasHardLineBreak = _endsWithHardLineBreak(parser.current);
bool hasHardLineBreak = _endsWithHardLineBreak(parser.current.content);

if (startsWithEmptyLine) {
// The parser started at an empty line.
Expand All @@ -669,7 +713,7 @@ class _EmptyLinePreservingParagraphSyntax extends md.BlockSyntax {
return null;
}

if (!_blankLinePattern.hasMatch(parser.current)) {
if (!_blankLinePattern.hasMatch(parser.current.content)) {
// We found an empty line, but the following line isn't blank.
// As there is no hard line break, the first line is consumed
// as a separator between blocks.
Expand All @@ -682,7 +726,7 @@ class _EmptyLinePreservingParagraphSyntax extends md.BlockSyntax {
childLines.add('');

// Check for a hard line break, so we consume the next line if we found one.
hasHardLineBreak = _endsWithHardLineBreak(parser.current);
hasHardLineBreak = _endsWithHardLineBreak(parser.current.content);
parser.advance();
}

Expand All @@ -691,9 +735,9 @@ class _EmptyLinePreservingParagraphSyntax extends md.BlockSyntax {
// ends with a hard line break.
while (!_isAtParagraphEnd(parser, ignoreEmptyBlocks: hasHardLineBreak)) {
final currentLine = parser.current;
childLines.add(currentLine);
childLines.add(currentLine.content);

hasHardLineBreak = _endsWithHardLineBreak(currentLine);
hasHardLineBreak = _endsWithHardLineBreak(currentLine.content);

parser.advance();
}
Expand Down Expand Up @@ -777,7 +821,7 @@ class _TaskSyntax extends md.BlockSyntax {

@override
md.Node? parse(md.BlockParser parser) {
final match = pattern.firstMatch(parser.current);
final match = pattern.firstMatch(parser.current.content);
if (match == null) {
return null;
}
Expand All @@ -795,10 +839,10 @@ class _TaskSyntax extends md.BlockSyntax {
// - find a blank line OR
// - find the start of another block element (including another task)
while (!parser.isDone &&
!_blankLinePattern.hasMatch(parser.current) &&
!_standardNonParagraphBlockSyntaxes.any((syntax) => syntax.pattern.hasMatch(parser.current))) {
!_blankLinePattern.hasMatch(parser.current.content) &&
!_standardNonParagraphBlockSyntaxes.any((syntax) => syntax.pattern.hasMatch(parser.current.content))) {
buffer.write('\n');
buffer.write(parser.current);
buffer.write(parser.current.content);

parser.advance();
}
Expand Down Expand Up @@ -832,7 +876,7 @@ class _HeaderWithAlignmentSyntax extends md.BlockSyntax {

@override
bool canParse(md.BlockParser parser) {
if (!_alignmentNotationPattern.hasMatch(parser.current)) {
if (!_alignmentNotationPattern.hasMatch(parser.current.content)) {
return false;
}

Expand All @@ -846,7 +890,7 @@ class _HeaderWithAlignmentSyntax extends md.BlockSyntax {
}

// Only parse if the next line is header.
if (!_headerSyntax.pattern.hasMatch(nextLine)) {
if (!_headerSyntax.pattern.hasMatch(nextLine.content)) {
return false;
}

Expand All @@ -855,7 +899,7 @@ class _HeaderWithAlignmentSyntax extends md.BlockSyntax {

@override
md.Node? parse(md.BlockParser parser) {
final match = _alignmentNotationPattern.firstMatch(parser.current);
final match = _alignmentNotationPattern.firstMatch(parser.current.content);

// We've parsed the alignment token on the current line. We know a header starts on the
// next line. Move the parser to the next line so that we can parse the header.
Expand Down
2 changes: 1 addition & 1 deletion super_editor_markdown/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies:

super_editor: ^0.3.0-dev
logging: ^1.0.1
markdown: ^5.0.0
markdown: ^7.2.1

dependency_overrides:
# Override to local mono-repo path so devs can test this repo
Expand Down
13 changes: 7 additions & 6 deletions super_editor_markdown/test/custom_parsers/callout_block.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,38 +22,38 @@ class CalloutBlockSyntax extends md.BlockSyntax {
// This method was adapted from the standard Blockquote parser, and
// the standard code fence block parser.
@override
List<String> parseChildLines(md.BlockParser parser) {
List<md.Line?> parseChildLines(md.BlockParser parser) {
// Grab all of the lines that form the custom block, stripping off the
// first line, e.g., "@@@ customBlock", and the last line, e.g., "@@@".
var childLines = <String>[];

while (!parser.isDone) {
final openingLine = pattern.firstMatch(parser.current);
final openingLine = pattern.firstMatch(parser.current.content);
if (openingLine != null) {
// This is the first line. Ignore it.
parser.advance();
continue;
}
final closingLine = _endLinePattern.firstMatch(parser.current);
final closingLine = _endLinePattern.firstMatch(parser.current.content);
if (closingLine != null) {
// This is the closing line. Ignore it.
parser.advance();

// If we're followed by a blank line, skip it, so that we don't end
// up with an extra paragraph for that blank line.
if (parser.current.trim().isEmpty) {
if (parser.current.content.trim().isEmpty) {
parser.advance();
}

// We're done.
break;
}

childLines.add(parser.current);
childLines.add(parser.current.content);
parser.advance();
}

return childLines;
return childLines.map((l) => md.Line(l)).toList();
}

// This method was adapted from the standard Blockquote parser, and
Expand Down Expand Up @@ -95,6 +95,7 @@ _InlineMarkdownToDocument _parseInline(md.Element element) {
element.textContent,
md.Document(
inlineSyntaxes: [
SingleStrikethroughSyntax(), // this needs to be before md.StrikethroughSyntax to be recognized
md.StrikethroughSyntax(),
UnderlineSyntax(),
],
Expand Down
Loading

0 comments on commit 3e859a0

Please sign in to comment.