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) {
|
||||
final localName = domNode.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)) {
|
||||
nodes.addAll(_parseSpecialElements(domNode));
|
||||
nodes.addAll(
|
||||
_parseSpecialElements(
|
||||
domNode,
|
||||
type: ParagraphBlockKeys.type,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else if (domNode is dom.Text) {
|
||||
delta.insert(domNode.text);
|
||||
|
|
@ -46,7 +52,10 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
|||
return nodes;
|
||||
}
|
||||
|
||||
Iterable<Node> _parseSpecialElements(dom.Element element) {
|
||||
Iterable<Node> _parseSpecialElements(
|
||||
dom.Element element, {
|
||||
required String type,
|
||||
}) {
|
||||
final localName = element.localName;
|
||||
switch (localName) {
|
||||
case HTMLTags.h1:
|
||||
|
|
@ -60,22 +69,21 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
|||
case HTMLTags.orderedList:
|
||||
return _parseOrderListElement(element);
|
||||
case HTMLTags.list:
|
||||
return _parseListElement(element);
|
||||
return _parseListElement(element, type: type);
|
||||
case HTMLTags.paragraph:
|
||||
return [_parseParagraphElement(element)];
|
||||
case HTMLTags.blockQuote:
|
||||
return [_parseBlockQuoteElement(element)];
|
||||
case HTMLTags.image:
|
||||
break;
|
||||
return [_parseImageElement(element)];
|
||||
default:
|
||||
return [paragraphNode(text: element.text)];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
void _parseFormattingElement(Delta delta, dom.Element element) {
|
||||
Attributes _parserFormattingElementAttributes(dom.Element element) {
|
||||
final localName = element.localName;
|
||||
Attributes? attributes;
|
||||
Attributes attributes = {};
|
||||
switch (localName) {
|
||||
case HTMLTags.bold || HTMLTags.strong:
|
||||
attributes = {FlowyRichTextKeys.bold: true};
|
||||
|
|
@ -92,81 +100,93 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
|||
case HTMLTags.code:
|
||||
attributes = {FlowyRichTextKeys.code: true};
|
||||
case HTMLTags.span:
|
||||
attributes = _getDeltaAttributesFromHTMLAttributes(
|
||||
final deltaAttributes = _getDeltaAttributesFromHTMLAttributes(
|
||||
element.attributes,
|
||||
);
|
||||
) ??
|
||||
{};
|
||||
attributes.addAll(deltaAttributes);
|
||||
break;
|
||||
case HTMLTags.anchor:
|
||||
final href = element.attributes['href'];
|
||||
if (href != null) {
|
||||
attributes = {
|
||||
FlowyRichTextKeys.href: href,
|
||||
};
|
||||
attributes = {FlowyRichTextKeys.href: href};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(false, 'Unknown formatting element: $element');
|
||||
break;
|
||||
}
|
||||
delta.insert(element.text, attributes: attributes);
|
||||
for (final child in element.children) {
|
||||
attributes.addAll(_parserFormattingElementAttributes(child));
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
|
||||
Node _parseHeadingElement(
|
||||
dom.Element element, {
|
||||
required int level,
|
||||
}) =>
|
||||
headingNode(
|
||||
}) {
|
||||
final delta = _parseDeltaElement(element);
|
||||
return headingNode(
|
||||
level: level,
|
||||
delta: Delta()..insert(element.text),
|
||||
delta: delta,
|
||||
);
|
||||
}
|
||||
|
||||
Node _parseBlockQuoteElement(dom.Element element) => quoteNode(
|
||||
delta: Delta()..insert(element.text),
|
||||
);
|
||||
|
||||
Iterable<Node> _parseUnOrderListElement(dom.Element element) {
|
||||
final children = element.nodes.toList().whereType<dom.Element>();
|
||||
return children.map(
|
||||
(e) => bulletedListNode(
|
||||
delta: Delta()
|
||||
..insert(
|
||||
e.text,
|
||||
),
|
||||
),
|
||||
);
|
||||
final result = <Node>[];
|
||||
for (final child in element.children) {
|
||||
result.addAll(_parseListElement(child, type: NumberedListBlockKeys.type));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Iterable<Node> _parseOrderListElement(dom.Element element) {
|
||||
final children = element.nodes.toList().whereType<dom.Element>();
|
||||
return children.map(
|
||||
(e) => numberedListNode(
|
||||
delta: Delta()
|
||||
..insert(
|
||||
e.text,
|
||||
),
|
||||
),
|
||||
);
|
||||
final result = <Node>[];
|
||||
for (final child in element.children) {
|
||||
result.addAll(_parseListElement(child, type: NumberedListBlockKeys.type));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Iterable<Node> _parseListElement(dom.Element element) {
|
||||
final children = element.nodes.toList().whereType<dom.Element>();
|
||||
return children
|
||||
.map((e) => _parseSpecialElements(e))
|
||||
.expand((element) => element);
|
||||
Iterable<Node> _parseListElement(
|
||||
dom.Element element, {
|
||||
required String type,
|
||||
}) {
|
||||
final delta = _parseDeltaElement(element);
|
||||
return [
|
||||
Node(type: type, attributes: {ParagraphBlockKeys.delta: delta.toJson()})
|
||||
];
|
||||
}
|
||||
|
||||
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 children = element.nodes.toList();
|
||||
for (final child in children) {
|
||||
if (child is dom.Element) {
|
||||
_parseFormattingElement(delta, child);
|
||||
final attributes = _parserFormattingElementAttributes(child);
|
||||
delta.insert(child.text, attributes: attributes);
|
||||
} else {
|
||||
delta.insert(child.text ?? '');
|
||||
}
|
||||
}
|
||||
return paragraphNode(delta: delta);
|
||||
return delta;
|
||||
}
|
||||
|
||||
Attributes? _getDeltaAttributesFromHTMLAttributes(
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@ version: 1.0.0-dev.0
|
|||
homepage: https://github.com/AppFlowy-IO/appflowy-editor
|
||||
|
||||
platforms:
|
||||
android:
|
||||
ios:
|
||||
linux:
|
||||
macos:
|
||||
windows:
|
||||
web:
|
||||
windows:
|
||||
|
||||
environment:
|
||||
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