Skip to content

Commit fc35f74

Browse files
committed
feat: markdown to delta
1 parent f6e1f21 commit fc35f74

File tree

4 files changed

+171
-3
lines changed

4 files changed

+171
-3
lines changed

frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@ export 'src/plugins/markdown/encoder/document_markdown_encoder.dart';
3838
export 'src/plugins/markdown/encoder/parser/node_parser.dart';
3939
export 'src/plugins/markdown/encoder/parser/text_node_parser.dart';
4040
export 'src/plugins/markdown/encoder/parser/image_node_parser.dart';
41+
export 'src/plugins/markdown/decoder/delta_markdown_decoder.dart';

frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/text_delta.dart

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class TextInsert extends TextOperation {
6262

6363
return other is TextInsert &&
6464
other.text == text &&
65-
mapEquals(_attributes, other._attributes);
65+
_mapEquals(_attributes, other._attributes);
6666
}
6767

6868
@override
@@ -99,7 +99,7 @@ class TextRetain extends TextOperation {
9999

100100
return other is TextRetain &&
101101
other.length == length &&
102-
mapEquals(_attributes, other._attributes);
102+
_mapEquals(_attributes, other._attributes);
103103
}
104104

105105
@override
@@ -181,7 +181,7 @@ class Delta extends Iterable<TextOperation> {
181181
lastOp.length += textOperation.length;
182182
return;
183183
}
184-
if (mapEquals(lastOp.attributes, textOperation.attributes)) {
184+
if (_mapEquals(lastOp.attributes, textOperation.attributes)) {
185185
if (lastOp is TextInsert && textOperation is TextInsert) {
186186
lastOp.text += textOperation.text;
187187
return;
@@ -539,3 +539,10 @@ class _OpIterator {
539539
}
540540
}
541541
}
542+
543+
bool _mapEquals<T, U>(Map<T, U>? a, Map<T, U>? b) {
544+
if ((a == null || a.isEmpty) && (b == null || b.isEmpty)) {
545+
return true;
546+
}
547+
return mapEquals(a, b);
548+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import 'dart:convert';
2+
3+
import 'package:appflowy_editor/appflowy_editor.dart';
4+
import 'package:markdown/markdown.dart' as md;
5+
6+
class DeltaMarkdownDecoder extends Converter<String, Delta>
7+
with md.NodeVisitor {
8+
final _delta = Delta();
9+
final Attributes _attributes = {};
10+
11+
@override
12+
Delta convert(String input) {
13+
final document =
14+
md.Document(extensionSet: md.ExtensionSet.gitHubWeb).parseInline(input);
15+
for (final node in document) {
16+
node.accept(this);
17+
}
18+
return _delta;
19+
}
20+
21+
@override
22+
void visitElementAfter(md.Element element) {
23+
_removeAttributeKey(element);
24+
}
25+
26+
@override
27+
bool visitElementBefore(md.Element element) {
28+
_addAttributeKey(element);
29+
return true;
30+
}
31+
32+
@override
33+
void visitText(md.Text text) {
34+
_delta.add(TextInsert(text.text, attributes: {..._attributes}));
35+
}
36+
37+
void _addAttributeKey(md.Element element) {
38+
if (element.tag == 'strong') {
39+
_attributes[BuiltInAttributeKey.bold] = true;
40+
} else if (element.tag == 'em') {
41+
_attributes[BuiltInAttributeKey.italic] = true;
42+
} else if (element.tag == 'code') {
43+
_attributes[BuiltInAttributeKey.code] = true;
44+
} else if (element.tag == 'del') {
45+
_attributes[BuiltInAttributeKey.strikethrough] = true;
46+
} else if (element.tag == 'a') {
47+
_attributes[BuiltInAttributeKey.href] = element.attributes['href'];
48+
}
49+
}
50+
51+
void _removeAttributeKey(md.Element element) {
52+
if (element.tag == 'strong') {
53+
_attributes.remove(BuiltInAttributeKey.bold);
54+
} else if (element.tag == 'em') {
55+
_attributes.remove(BuiltInAttributeKey.italic);
56+
} else if (element.tag == 'code') {
57+
_attributes.remove(BuiltInAttributeKey.code);
58+
} else if (element.tag == 'del') {
59+
_attributes.remove(BuiltInAttributeKey.strikethrough);
60+
} else if (element.tag == 'a') {
61+
_attributes.remove(BuiltInAttributeKey.href);
62+
}
63+
}
64+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import 'package:appflowy_editor/appflowy_editor.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
4+
void main() async {
5+
group('delta_markdown_decoder.dart', () {
6+
test('bold', () {
7+
final delta = Delta(operations: [
8+
TextInsert('Welcome to '),
9+
TextInsert('AppFlowy', attributes: {
10+
BuiltInAttributeKey.bold: true,
11+
}),
12+
]);
13+
final result = DeltaMarkdownDecoder().convert('Welcome to **AppFlowy**');
14+
expect(result, delta);
15+
});
16+
17+
test('italic', () {
18+
final delta = Delta(operations: [
19+
TextInsert('Welcome to '),
20+
TextInsert('AppFlowy', attributes: {
21+
BuiltInAttributeKey.italic: true,
22+
}),
23+
]);
24+
final result = DeltaMarkdownDecoder().convert('Welcome to _AppFlowy_');
25+
expect(result, delta);
26+
});
27+
28+
test('strikethrough', () {
29+
final delta = Delta(operations: [
30+
TextInsert('Welcome to '),
31+
TextInsert('AppFlowy', attributes: {
32+
BuiltInAttributeKey.strikethrough: true,
33+
}),
34+
]);
35+
final result = DeltaMarkdownDecoder().convert('Welcome to ~~AppFlowy~~');
36+
expect(result, delta);
37+
});
38+
39+
test('href', () {
40+
final delta = Delta(operations: [
41+
TextInsert('Welcome to '),
42+
TextInsert('AppFlowy', attributes: {
43+
BuiltInAttributeKey.href: 'https://appflowy.io',
44+
}),
45+
]);
46+
final result = DeltaMarkdownDecoder()
47+
.convert('Welcome to [AppFlowy](https://appflowy.io)');
48+
expect(result, delta);
49+
});
50+
51+
test('code', () {
52+
final delta = Delta(operations: [
53+
TextInsert('Welcome to '),
54+
TextInsert('AppFlowy', attributes: {
55+
BuiltInAttributeKey.code: true,
56+
}),
57+
]);
58+
final result = DeltaMarkdownDecoder().convert('Welcome to `AppFlowy`');
59+
expect(result, delta);
60+
});
61+
62+
test('bold', () {
63+
const markdown =
64+
'***<u>`Welcome`</u>*** ***~~to~~*** ***[AppFlowy](https://appflowy.io)***';
65+
final delta = Delta(operations: [
66+
TextInsert('<u>', attributes: {
67+
BuiltInAttributeKey.italic: true,
68+
BuiltInAttributeKey.bold: true,
69+
}),
70+
TextInsert('Welcome', attributes: {
71+
BuiltInAttributeKey.code: true,
72+
BuiltInAttributeKey.italic: true,
73+
BuiltInAttributeKey.bold: true,
74+
}),
75+
TextInsert('</u>', attributes: {
76+
BuiltInAttributeKey.italic: true,
77+
BuiltInAttributeKey.bold: true,
78+
}),
79+
TextInsert(' '),
80+
TextInsert('to', attributes: {
81+
BuiltInAttributeKey.italic: true,
82+
BuiltInAttributeKey.bold: true,
83+
BuiltInAttributeKey.strikethrough: true,
84+
}),
85+
TextInsert(' '),
86+
TextInsert('AppFlowy', attributes: {
87+
BuiltInAttributeKey.href: 'https://appflowy.io',
88+
BuiltInAttributeKey.bold: true,
89+
BuiltInAttributeKey.italic: true,
90+
}),
91+
]);
92+
final result = DeltaMarkdownDecoder().convert(markdown);
93+
expect(result, delta);
94+
});
95+
});
96+
}

0 commit comments

Comments
 (0)