fix: convert html to document (#152)
* fix:html to node converter fixes * html tests added * html decoder optimization * feat: refactor code * mobile platforms added --------- Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
parent
c573b9a80f
commit
a77b25aa50
4 changed files with 751 additions and 47 deletions
|
|
@ -30,9 +30,15 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
||||||
if (domNode is dom.Element) {
|
if (domNode is dom.Element) {
|
||||||
final localName = domNode.localName;
|
final localName = domNode.localName;
|
||||||
if (HTMLTags.formattingElements.contains(localName)) {
|
if (HTMLTags.formattingElements.contains(localName)) {
|
||||||
_parseFormattingElement(delta, domNode);
|
final attributes = _parserFormattingElementAttributes(domNode);
|
||||||
|
delta.insert(domNode.text, attributes: attributes);
|
||||||
} else if (HTMLTags.specialElements.contains(localName)) {
|
} else if (HTMLTags.specialElements.contains(localName)) {
|
||||||
nodes.addAll(_parseSpecialElements(domNode));
|
nodes.addAll(
|
||||||
|
_parseSpecialElements(
|
||||||
|
domNode,
|
||||||
|
type: ParagraphBlockKeys.type,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if (domNode is dom.Text) {
|
} else if (domNode is dom.Text) {
|
||||||
delta.insert(domNode.text);
|
delta.insert(domNode.text);
|
||||||
|
|
@ -46,7 +52,10 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<Node> _parseSpecialElements(dom.Element element) {
|
Iterable<Node> _parseSpecialElements(
|
||||||
|
dom.Element element, {
|
||||||
|
required String type,
|
||||||
|
}) {
|
||||||
final localName = element.localName;
|
final localName = element.localName;
|
||||||
switch (localName) {
|
switch (localName) {
|
||||||
case HTMLTags.h1:
|
case HTMLTags.h1:
|
||||||
|
|
@ -60,22 +69,21 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
||||||
case HTMLTags.orderedList:
|
case HTMLTags.orderedList:
|
||||||
return _parseOrderListElement(element);
|
return _parseOrderListElement(element);
|
||||||
case HTMLTags.list:
|
case HTMLTags.list:
|
||||||
return _parseListElement(element);
|
return _parseListElement(element, type: type);
|
||||||
case HTMLTags.paragraph:
|
case HTMLTags.paragraph:
|
||||||
return [_parseParagraphElement(element)];
|
return [_parseParagraphElement(element)];
|
||||||
case HTMLTags.blockQuote:
|
case HTMLTags.blockQuote:
|
||||||
return [_parseBlockQuoteElement(element)];
|
return [_parseBlockQuoteElement(element)];
|
||||||
case HTMLTags.image:
|
case HTMLTags.image:
|
||||||
break;
|
return [_parseImageElement(element)];
|
||||||
default:
|
default:
|
||||||
return [paragraphNode(text: element.text)];
|
return [paragraphNode(text: element.text)];
|
||||||
}
|
}
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _parseFormattingElement(Delta delta, dom.Element element) {
|
Attributes _parserFormattingElementAttributes(dom.Element element) {
|
||||||
final localName = element.localName;
|
final localName = element.localName;
|
||||||
Attributes? attributes;
|
Attributes attributes = {};
|
||||||
switch (localName) {
|
switch (localName) {
|
||||||
case HTMLTags.bold || HTMLTags.strong:
|
case HTMLTags.bold || HTMLTags.strong:
|
||||||
attributes = {FlowyRichTextKeys.bold: true};
|
attributes = {FlowyRichTextKeys.bold: true};
|
||||||
|
|
@ -92,81 +100,93 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
||||||
case HTMLTags.code:
|
case HTMLTags.code:
|
||||||
attributes = {FlowyRichTextKeys.code: true};
|
attributes = {FlowyRichTextKeys.code: true};
|
||||||
case HTMLTags.span:
|
case HTMLTags.span:
|
||||||
attributes = _getDeltaAttributesFromHTMLAttributes(
|
final deltaAttributes = _getDeltaAttributesFromHTMLAttributes(
|
||||||
element.attributes,
|
element.attributes,
|
||||||
);
|
) ??
|
||||||
|
{};
|
||||||
|
attributes.addAll(deltaAttributes);
|
||||||
break;
|
break;
|
||||||
case HTMLTags.anchor:
|
case HTMLTags.anchor:
|
||||||
final href = element.attributes['href'];
|
final href = element.attributes['href'];
|
||||||
if (href != null) {
|
if (href != null) {
|
||||||
attributes = {
|
attributes = {FlowyRichTextKeys.href: href};
|
||||||
FlowyRichTextKeys.href: href,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
assert(false, 'Unknown formatting element: $element');
|
assert(false, 'Unknown formatting element: $element');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
delta.insert(element.text, attributes: attributes);
|
for (final child in element.children) {
|
||||||
|
attributes.addAll(_parserFormattingElementAttributes(child));
|
||||||
|
}
|
||||||
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
Node _parseHeadingElement(
|
Node _parseHeadingElement(
|
||||||
dom.Element element, {
|
dom.Element element, {
|
||||||
required int level,
|
required int level,
|
||||||
}) =>
|
}) {
|
||||||
headingNode(
|
final delta = _parseDeltaElement(element);
|
||||||
level: level,
|
return headingNode(
|
||||||
delta: Delta()..insert(element.text),
|
level: level,
|
||||||
);
|
delta: delta,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Node _parseBlockQuoteElement(dom.Element element) => quoteNode(
|
Node _parseBlockQuoteElement(dom.Element element) => quoteNode(
|
||||||
delta: Delta()..insert(element.text),
|
delta: Delta()..insert(element.text),
|
||||||
);
|
);
|
||||||
|
|
||||||
Iterable<Node> _parseUnOrderListElement(dom.Element element) {
|
Iterable<Node> _parseUnOrderListElement(dom.Element element) {
|
||||||
final children = element.nodes.toList().whereType<dom.Element>();
|
final result = <Node>[];
|
||||||
return children.map(
|
for (final child in element.children) {
|
||||||
(e) => bulletedListNode(
|
result.addAll(_parseListElement(child, type: NumberedListBlockKeys.type));
|
||||||
delta: Delta()
|
}
|
||||||
..insert(
|
return result;
|
||||||
e.text,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<Node> _parseOrderListElement(dom.Element element) {
|
Iterable<Node> _parseOrderListElement(dom.Element element) {
|
||||||
final children = element.nodes.toList().whereType<dom.Element>();
|
final result = <Node>[];
|
||||||
return children.map(
|
for (final child in element.children) {
|
||||||
(e) => numberedListNode(
|
result.addAll(_parseListElement(child, type: NumberedListBlockKeys.type));
|
||||||
delta: Delta()
|
}
|
||||||
..insert(
|
return result;
|
||||||
e.text,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<Node> _parseListElement(dom.Element element) {
|
Iterable<Node> _parseListElement(
|
||||||
final children = element.nodes.toList().whereType<dom.Element>();
|
dom.Element element, {
|
||||||
return children
|
required String type,
|
||||||
.map((e) => _parseSpecialElements(e))
|
}) {
|
||||||
.expand((element) => element);
|
final delta = _parseDeltaElement(element);
|
||||||
|
return [
|
||||||
|
Node(type: type, attributes: {ParagraphBlockKeys.delta: delta.toJson()})
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
Node _parseParagraphElement(dom.Element element) {
|
Node _parseParagraphElement(dom.Element element) {
|
||||||
// TODO: parse image and checkbox.
|
final delta = _parseDeltaElement(element);
|
||||||
|
return paragraphNode(delta: delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node _parseImageElement(dom.Element element) {
|
||||||
|
final src = element.attributes['src'] ?? '';
|
||||||
|
return imageNode(
|
||||||
|
url: src,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Delta _parseDeltaElement(dom.Element element) {
|
||||||
final delta = Delta();
|
final delta = Delta();
|
||||||
final children = element.nodes.toList();
|
final children = element.nodes.toList();
|
||||||
for (final child in children) {
|
for (final child in children) {
|
||||||
if (child is dom.Element) {
|
if (child is dom.Element) {
|
||||||
_parseFormattingElement(delta, child);
|
final attributes = _parserFormattingElementAttributes(child);
|
||||||
|
delta.insert(child.text, attributes: attributes);
|
||||||
} else {
|
} else {
|
||||||
delta.insert(child.text ?? '');
|
delta.insert(child.text ?? '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return paragraphNode(delta: delta);
|
return delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
Attributes? _getDeltaAttributesFromHTMLAttributes(
|
Attributes? _getDeltaAttributesFromHTMLAttributes(
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,12 @@ version: 1.0.0-dev.0
|
||||||
homepage: https://github.com/AppFlowy-IO/appflowy-editor
|
homepage: https://github.com/AppFlowy-IO/appflowy-editor
|
||||||
|
|
||||||
platforms:
|
platforms:
|
||||||
|
android:
|
||||||
|
ios:
|
||||||
linux:
|
linux:
|
||||||
macos:
|
macos:
|
||||||
windows:
|
|
||||||
web:
|
web:
|
||||||
|
windows:
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|
|
||||||
344
test/plugins/html/decoder/document_html_decoder_test.dart
Normal file
344
test/plugins/html/decoder/document_html_decoder_test.dart
Normal file
|
|
@ -0,0 +1,344 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
group('document_html_decoder_test.dart', () {
|
||||||
|
const example = {
|
||||||
|
'document': {
|
||||||
|
'type': 'page',
|
||||||
|
'children': [
|
||||||
|
{
|
||||||
|
'type': 'heading',
|
||||||
|
'data': {
|
||||||
|
'level': 1,
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'AppFlowyEditor'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'heading',
|
||||||
|
'data': {
|
||||||
|
'level': 2,
|
||||||
|
'delta': [
|
||||||
|
{'insert': '👋 '},
|
||||||
|
{
|
||||||
|
'insert': 'Welcome to',
|
||||||
|
'attributes': {'bold': true}
|
||||||
|
},
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'AppFlowy Editor',
|
||||||
|
'attributes': {
|
||||||
|
'bold': true,
|
||||||
|
'italic': true,
|
||||||
|
'href': 'appflowy.io'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'AppFlowy Editor is a '},
|
||||||
|
{
|
||||||
|
'insert': 'highly customizable',
|
||||||
|
'attributes': {'bold': true}
|
||||||
|
},
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'rich-text editor',
|
||||||
|
'attributes': {'italic': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{
|
||||||
|
'insert': 'Here',
|
||||||
|
'attributes': {'underline': true}
|
||||||
|
},
|
||||||
|
{'insert': ' is an example '},
|
||||||
|
{
|
||||||
|
'insert': 'your',
|
||||||
|
'attributes': {'strikethrough': true}
|
||||||
|
},
|
||||||
|
{'insert': ' you can give a try'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'heading',
|
||||||
|
'data': {
|
||||||
|
'level': 3,
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'Features!'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'numbered_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': '[x] Customizable'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'numbered_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': '[x] Test-covered'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'numbered_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': '[ ] more to come!'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'numbered_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'First item'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'numbered_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'Second item'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'List element'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'quote',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
' This is a quote!\n'
|
||||||
|
''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'You can also use '},
|
||||||
|
{
|
||||||
|
'insert': 'AppFlowy Editor',
|
||||||
|
'attributes': {'bold': true, 'italic': true}
|
||||||
|
},
|
||||||
|
{'insert': ' as a component to build your own app.'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'heading',
|
||||||
|
'data': {
|
||||||
|
'level': 3,
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'Awesome features'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{
|
||||||
|
'insert':
|
||||||
|
'If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
' \n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': 'Span element',
|
||||||
|
'attributes': {'bold': true, 'italic': true}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': 'Span element two',
|
||||||
|
'attributes': {'underline': true}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': 'Span element three',
|
||||||
|
'attributes': {'bold': true, 'strikethrough': true}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': 'This is an anchor tag!',
|
||||||
|
'attributes': {'href': 'https://appflowy.io'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
' Code block\n'
|
||||||
|
'',
|
||||||
|
'attributes': {'code': true}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': 'Italic one',
|
||||||
|
'attributes': {'italic': true}
|
||||||
|
},
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Italic two',
|
||||||
|
'attributes': {'italic': true}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': 'Bold tag',
|
||||||
|
'attributes': {'bold': true}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parser document', () async {
|
||||||
|
const rawHTML = """<h1>AppFlowyEditor</h1>
|
||||||
|
<h2>👋 <strong>Welcome to</strong> <strong><em><a href="appflowy.io">AppFlowy Editor</a></em></strong></h2>
|
||||||
|
<p>AppFlowy Editor is a <strong>highly customizable</strong> <em>rich-text editor</em></p>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<p><u>Here</u> is an example <del>your</del> you can give a try</p>
|
||||||
|
|
||||||
|
<span style="font-weight: bold;background-color: #cccccc;font-style: italic;">Span element</span>
|
||||||
|
|
||||||
|
<span style="font-weight: medium;text-decoration: underline;">Span element two</span>
|
||||||
|
|
||||||
|
<span style="font-weight: 900;text-decoration: line-through;">Span element three</span>
|
||||||
|
|
||||||
|
<a href="https://appflowy.io">This is an anchor tag!</a>
|
||||||
|
|
||||||
|
<img src="https://images.squarespace-cdn.com/content/v1/617f6f16b877c06711e87373/c3f23723-37f4-44d7-9c5d-6e2a53064ae7/Asset+10.png?format=1500w" />
|
||||||
|
|
||||||
|
<h3>Features!</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>[x] Customizable</li>
|
||||||
|
<li>[x] Test-covered</li>
|
||||||
|
<li>[ ] more to come!</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>First item</li>
|
||||||
|
<li>Second item</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<li>List element</li>
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<p>This is a quote!</p>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<code>
|
||||||
|
Code block
|
||||||
|
</code>
|
||||||
|
|
||||||
|
<em>Italic one</em> <i>Italic two</i>
|
||||||
|
|
||||||
|
<b>Bold tag</b>
|
||||||
|
<img src="http://appflowy.io" alt="AppFlowy">
|
||||||
|
<p>You can also use <strong><em>AppFlowy Editor</em></strong> as a component to build your own app.</p>
|
||||||
|
<h3>Awesome features</h3>
|
||||||
|
<p>If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!</p>
|
||||||
|
<hr>""";
|
||||||
|
final result = DocumentHTMLDecoder().convert(rawHTML);
|
||||||
|
|
||||||
|
expect(result.toJson(), example);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
338
test/plugins/html/html_document_test.dart
Normal file
338
test/plugins/html/html_document_test.dart
Normal file
|
|
@ -0,0 +1,338 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('html_document_test.dart tests', () {
|
||||||
|
test('htmlToDocument()', () {
|
||||||
|
final document = htmlToDocument(rawHTML);
|
||||||
|
expect(document.toJson(), data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
'document': {
|
||||||
|
'type': 'page',
|
||||||
|
'children': [
|
||||||
|
{
|
||||||
|
'type': 'heading',
|
||||||
|
'data': {
|
||||||
|
'level': 1,
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'AppFlowyEditor'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'heading',
|
||||||
|
'data': {
|
||||||
|
'level': 2,
|
||||||
|
'delta': [
|
||||||
|
{'insert': '👋 '},
|
||||||
|
{
|
||||||
|
'insert': 'Welcome to',
|
||||||
|
'attributes': {'bold': true}
|
||||||
|
},
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'AppFlowy Editor',
|
||||||
|
'attributes': {
|
||||||
|
'bold': true,
|
||||||
|
'italic': true,
|
||||||
|
'href': 'appflowy.io'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'AppFlowy Editor is a '},
|
||||||
|
{
|
||||||
|
'insert': 'highly customizable',
|
||||||
|
'attributes': {'bold': true}
|
||||||
|
},
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'rich-text editor',
|
||||||
|
'attributes': {'italic': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{
|
||||||
|
'insert': 'Here',
|
||||||
|
'attributes': {'underline': true}
|
||||||
|
},
|
||||||
|
{'insert': ' is an example '},
|
||||||
|
{
|
||||||
|
'insert': 'your',
|
||||||
|
'attributes': {'strikethrough': true}
|
||||||
|
},
|
||||||
|
{'insert': ' you can give a try'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'heading',
|
||||||
|
'data': {
|
||||||
|
'level': 3,
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'Features!'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'numbered_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': '[x] Customizable'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'numbered_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': '[x] Test-covered'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'numbered_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': '[ ] more to come!'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'numbered_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'First item'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'numbered_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'Second item'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'List element'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'quote',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
' This is a quote!\n'
|
||||||
|
''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'You can also use '},
|
||||||
|
{
|
||||||
|
'insert': 'AppFlowy Editor',
|
||||||
|
'attributes': {'bold': true, 'italic': true}
|
||||||
|
},
|
||||||
|
{'insert': ' as a component to build your own app.'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'heading',
|
||||||
|
'data': {
|
||||||
|
'level': 3,
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'Awesome features'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{
|
||||||
|
'insert':
|
||||||
|
'If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
' \n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': 'Span element',
|
||||||
|
'attributes': {'bold': true, 'italic': true}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': 'Span element two',
|
||||||
|
'attributes': {'underline': true}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': 'Span element three',
|
||||||
|
'attributes': {'bold': true, 'strikethrough': true}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': 'This is an anchor tag!',
|
||||||
|
'attributes': {'href': 'https://appflowy.io'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
' Code block\n'
|
||||||
|
'',
|
||||||
|
'attributes': {'code': true}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': 'Italic one',
|
||||||
|
'attributes': {'italic': true}
|
||||||
|
},
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Italic two',
|
||||||
|
'attributes': {'italic': true}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': 'Bold tag',
|
||||||
|
'attributes': {'bold': true}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'insert': '\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
'\n'
|
||||||
|
''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const rawHTML = """<h1>AppFlowyEditor</h1>
|
||||||
|
<h2>👋 <strong>Welcome to</strong> <strong><em><a href="appflowy.io">AppFlowy Editor</a></em></strong></h2>
|
||||||
|
<p>AppFlowy Editor is a <strong>highly customizable</strong> <em>rich-text editor</em></p>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<p><u>Here</u> is an example <del>your</del> you can give a try</p>
|
||||||
|
|
||||||
|
<span style="font-weight: bold;background-color: #cccccc;font-style: italic;">Span element</span>
|
||||||
|
|
||||||
|
<span style="font-weight: medium;text-decoration: underline;">Span element two</span>
|
||||||
|
|
||||||
|
<span style="font-weight: 900;text-decoration: line-through;">Span element three</span>
|
||||||
|
|
||||||
|
<a href="https://appflowy.io">This is an anchor tag!</a>
|
||||||
|
|
||||||
|
<img src="https://images.squarespace-cdn.com/content/v1/617f6f16b877c06711e87373/c3f23723-37f4-44d7-9c5d-6e2a53064ae7/Asset+10.png?format=1500w" />
|
||||||
|
|
||||||
|
<h3>Features!</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>[x] Customizable</li>
|
||||||
|
<li>[x] Test-covered</li>
|
||||||
|
<li>[ ] more to come!</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>First item</li>
|
||||||
|
<li>Second item</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<li>List element</li>
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<p>This is a quote!</p>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<code>
|
||||||
|
Code block
|
||||||
|
</code>
|
||||||
|
|
||||||
|
<em>Italic one</em> <i>Italic two</i>
|
||||||
|
|
||||||
|
<b>Bold tag</b>
|
||||||
|
<img src="http://appflowy.io" alt="AppFlowy">
|
||||||
|
<p>You can also use <strong><em>AppFlowy Editor</em></strong> as a component to build your own app.</p>
|
||||||
|
<h3>Awesome features</h3>
|
||||||
|
<p>If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!</p>
|
||||||
|
<hr>""";
|
||||||
Loading…
Reference in a new issue