feat: markdown to delta

This commit is contained in:
Lucas.Xu 2022-11-08 17:19:14 +08:00
parent f6e1f2185e
commit fc35f74751
4 changed files with 171 additions and 3 deletions

View File

@ -38,3 +38,4 @@ export 'src/plugins/markdown/encoder/document_markdown_encoder.dart';
export 'src/plugins/markdown/encoder/parser/node_parser.dart';
export 'src/plugins/markdown/encoder/parser/text_node_parser.dart';
export 'src/plugins/markdown/encoder/parser/image_node_parser.dart';
export 'src/plugins/markdown/decoder/delta_markdown_decoder.dart';

View File

@ -62,7 +62,7 @@ class TextInsert extends TextOperation {
return other is TextInsert &&
other.text == text &&
mapEquals(_attributes, other._attributes);
_mapEquals(_attributes, other._attributes);
}
@override
@ -99,7 +99,7 @@ class TextRetain extends TextOperation {
return other is TextRetain &&
other.length == length &&
mapEquals(_attributes, other._attributes);
_mapEquals(_attributes, other._attributes);
}
@override
@ -181,7 +181,7 @@ class Delta extends Iterable<TextOperation> {
lastOp.length += textOperation.length;
return;
}
if (mapEquals(lastOp.attributes, textOperation.attributes)) {
if (_mapEquals(lastOp.attributes, textOperation.attributes)) {
if (lastOp is TextInsert && textOperation is TextInsert) {
lastOp.text += textOperation.text;
return;
@ -539,3 +539,10 @@ class _OpIterator {
}
}
}
bool _mapEquals<T, U>(Map<T, U>? a, Map<T, U>? b) {
if ((a == null || a.isEmpty) && (b == null || b.isEmpty)) {
return true;
}
return mapEquals(a, b);
}

View File

@ -0,0 +1,64 @@
import 'dart:convert';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:markdown/markdown.dart' as md;
class DeltaMarkdownDecoder extends Converter<String, Delta>
with md.NodeVisitor {
final _delta = Delta();
final Attributes _attributes = {};
@override
Delta convert(String input) {
final document =
md.Document(extensionSet: md.ExtensionSet.gitHubWeb).parseInline(input);
for (final node in document) {
node.accept(this);
}
return _delta;
}
@override
void visitElementAfter(md.Element element) {
_removeAttributeKey(element);
}
@override
bool visitElementBefore(md.Element element) {
_addAttributeKey(element);
return true;
}
@override
void visitText(md.Text text) {
_delta.add(TextInsert(text.text, attributes: {..._attributes}));
}
void _addAttributeKey(md.Element element) {
if (element.tag == 'strong') {
_attributes[BuiltInAttributeKey.bold] = true;
} else if (element.tag == 'em') {
_attributes[BuiltInAttributeKey.italic] = true;
} else if (element.tag == 'code') {
_attributes[BuiltInAttributeKey.code] = true;
} else if (element.tag == 'del') {
_attributes[BuiltInAttributeKey.strikethrough] = true;
} else if (element.tag == 'a') {
_attributes[BuiltInAttributeKey.href] = element.attributes['href'];
}
}
void _removeAttributeKey(md.Element element) {
if (element.tag == 'strong') {
_attributes.remove(BuiltInAttributeKey.bold);
} else if (element.tag == 'em') {
_attributes.remove(BuiltInAttributeKey.italic);
} else if (element.tag == 'code') {
_attributes.remove(BuiltInAttributeKey.code);
} else if (element.tag == 'del') {
_attributes.remove(BuiltInAttributeKey.strikethrough);
} else if (element.tag == 'a') {
_attributes.remove(BuiltInAttributeKey.href);
}
}
}

View File

@ -0,0 +1,96 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter_test/flutter_test.dart';
void main() async {
group('delta_markdown_decoder.dart', () {
test('bold', () {
final delta = Delta(operations: [
TextInsert('Welcome to '),
TextInsert('AppFlowy', attributes: {
BuiltInAttributeKey.bold: true,
}),
]);
final result = DeltaMarkdownDecoder().convert('Welcome to **AppFlowy**');
expect(result, delta);
});
test('italic', () {
final delta = Delta(operations: [
TextInsert('Welcome to '),
TextInsert('AppFlowy', attributes: {
BuiltInAttributeKey.italic: true,
}),
]);
final result = DeltaMarkdownDecoder().convert('Welcome to _AppFlowy_');
expect(result, delta);
});
test('strikethrough', () {
final delta = Delta(operations: [
TextInsert('Welcome to '),
TextInsert('AppFlowy', attributes: {
BuiltInAttributeKey.strikethrough: true,
}),
]);
final result = DeltaMarkdownDecoder().convert('Welcome to ~~AppFlowy~~');
expect(result, delta);
});
test('href', () {
final delta = Delta(operations: [
TextInsert('Welcome to '),
TextInsert('AppFlowy', attributes: {
BuiltInAttributeKey.href: 'https://appflowy.io',
}),
]);
final result = DeltaMarkdownDecoder()
.convert('Welcome to [AppFlowy](https://appflowy.io)');
expect(result, delta);
});
test('code', () {
final delta = Delta(operations: [
TextInsert('Welcome to '),
TextInsert('AppFlowy', attributes: {
BuiltInAttributeKey.code: true,
}),
]);
final result = DeltaMarkdownDecoder().convert('Welcome to `AppFlowy`');
expect(result, delta);
});
test('bold', () {
const markdown =
'***<u>`Welcome`</u>*** ***~~to~~*** ***[AppFlowy](https://appflowy.io)***';
final delta = Delta(operations: [
TextInsert('<u>', attributes: {
BuiltInAttributeKey.italic: true,
BuiltInAttributeKey.bold: true,
}),
TextInsert('Welcome', attributes: {
BuiltInAttributeKey.code: true,
BuiltInAttributeKey.italic: true,
BuiltInAttributeKey.bold: true,
}),
TextInsert('</u>', attributes: {
BuiltInAttributeKey.italic: true,
BuiltInAttributeKey.bold: true,
}),
TextInsert(' '),
TextInsert('to', attributes: {
BuiltInAttributeKey.italic: true,
BuiltInAttributeKey.bold: true,
BuiltInAttributeKey.strikethrough: true,
}),
TextInsert(' '),
TextInsert('AppFlowy', attributes: {
BuiltInAttributeKey.href: 'https://appflowy.io',
BuiltInAttributeKey.bold: true,
BuiltInAttributeKey.italic: true,
}),
]);
final result = DeltaMarkdownDecoder().convert(markdown);
expect(result, delta);
});
});
}