fix: editor document to html encoder added (#175)
* fix:added html document encoder * code optimization * code refactored * correction of the html attributes variable
This commit is contained in:
parent
7fe5bb85d4
commit
6677e2ae62
6 changed files with 949 additions and 518 deletions
|
|
@ -64,4 +64,5 @@ export 'src/core/document/deprecated/node.dart';
|
||||||
export 'src/core/document/deprecated/document.dart';
|
export 'src/core/document/deprecated/document.dart';
|
||||||
|
|
||||||
export 'src/plugins/html/html_document_decoder.dart';
|
export 'src/plugins/html/html_document_decoder.dart';
|
||||||
|
export 'src/plugins/html/html_document_encoder.dart';
|
||||||
export 'src/plugins/html/html_document.dart';
|
export 'src/plugins/html/html_document.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:html/parser.dart' show parse;
|
import 'package:html/parser.dart' show parse;
|
||||||
import 'package:html/dom.dart' as dom;
|
import 'package:html/dom.dart' as dom;
|
||||||
|
|
@ -31,7 +30,11 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
||||||
final localName = domNode.localName;
|
final localName = domNode.localName;
|
||||||
if (HTMLTags.formattingElements.contains(localName)) {
|
if (HTMLTags.formattingElements.contains(localName)) {
|
||||||
final attributes = _parserFormattingElementAttributes(domNode);
|
final attributes = _parserFormattingElementAttributes(domNode);
|
||||||
delta.insert(domNode.text, attributes: attributes);
|
nodes.add(
|
||||||
|
paragraphNode(
|
||||||
|
delta: Delta()..insert(domNode.text, attributes: attributes),
|
||||||
|
),
|
||||||
|
);
|
||||||
} else if (HTMLTags.specialElements.contains(localName)) {
|
} else if (HTMLTags.specialElements.contains(localName)) {
|
||||||
nodes.addAll(
|
nodes.addAll(
|
||||||
_parseSpecialElements(
|
_parseSpecialElements(
|
||||||
|
|
@ -69,11 +72,16 @@ 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, type: type);
|
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:
|
||||||
return [_parseImageElement(element)];
|
return [_parseImageElement(element)];
|
||||||
default:
|
default:
|
||||||
|
|
@ -113,7 +121,6 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
assert(false, 'Unknown formatting element: $element');
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
for (final child in element.children) {
|
for (final child in element.children) {
|
||||||
|
|
@ -133,34 +140,37 @@ class DocumentHTMLDecoder extends Converter<String, Document> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Node _parseBlockQuoteElement(dom.Element element) => quoteNode(
|
Iterable<Node> _parseBlockQuoteElement(dom.Element element) {
|
||||||
delta: Delta()..insert(element.text),
|
return element.children
|
||||||
);
|
.map((child) => _parseListElement(child, type: QuoteBlockKeys.type))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
Iterable<Node> _parseUnOrderListElement(dom.Element element) {
|
Iterable<Node> _parseUnOrderListElement(dom.Element element) {
|
||||||
final result = <Node>[];
|
return element.children
|
||||||
for (final child in element.children) {
|
.map(
|
||||||
result.addAll(_parseListElement(child, type: NumberedListBlockKeys.type));
|
(child) => _parseListElement(child, type: BulletedListBlockKeys.type),
|
||||||
}
|
)
|
||||||
return result;
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<Node> _parseOrderListElement(dom.Element element) {
|
Iterable<Node> _parseOrderListElement(dom.Element element) {
|
||||||
final result = <Node>[];
|
return element.children
|
||||||
for (final child in element.children) {
|
.map(
|
||||||
result.addAll(_parseListElement(child, type: NumberedListBlockKeys.type));
|
(child) => _parseListElement(child, type: NumberedListBlockKeys.type),
|
||||||
}
|
)
|
||||||
return result;
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<Node> _parseListElement(
|
Node _parseListElement(
|
||||||
dom.Element element, {
|
dom.Element element, {
|
||||||
required String type,
|
required String type,
|
||||||
}) {
|
}) {
|
||||||
final delta = _parseDeltaElement(element);
|
final delta = _parseDeltaElement(element);
|
||||||
return [
|
return Node(
|
||||||
Node(type: type, attributes: {ParagraphBlockKeys.delta: delta.toJson()})
|
type: type != ParagraphBlockKeys.type ? type : BulletedListBlockKeys.type,
|
||||||
];
|
attributes: {ParagraphBlockKeys.delta: delta.toJson()},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Node _parseParagraphElement(dom.Element element) {
|
Node _parseParagraphElement(dom.Element element) {
|
||||||
|
|
@ -278,6 +288,7 @@ class HTMLTags {
|
||||||
static const underline = 'u';
|
static const underline = 'u';
|
||||||
static const del = 'del';
|
static const del = 'del';
|
||||||
static const strong = 'strong';
|
static const strong = 'strong';
|
||||||
|
static const checkbox = 'input';
|
||||||
static const span = 'span';
|
static const span = 'span';
|
||||||
static const code = 'code';
|
static const code = 'code';
|
||||||
static const blockQuote = 'blockquote';
|
static const blockQuote = 'blockquote';
|
||||||
|
|
@ -302,15 +313,19 @@ class HTMLTags {
|
||||||
HTMLTags.h3,
|
HTMLTags.h3,
|
||||||
HTMLTags.unorderedList,
|
HTMLTags.unorderedList,
|
||||||
HTMLTags.orderedList,
|
HTMLTags.orderedList,
|
||||||
|
HTMLTag.div,
|
||||||
HTMLTags.list,
|
HTMLTags.list,
|
||||||
HTMLTags.paragraph,
|
HTMLTags.paragraph,
|
||||||
HTMLTags.blockQuote,
|
HTMLTags.blockQuote,
|
||||||
|
HTMLTags.checkbox,
|
||||||
|
HTMLTag.image
|
||||||
];
|
];
|
||||||
|
|
||||||
static bool isTopLevel(String tag) {
|
static bool isTopLevel(String tag) {
|
||||||
return tag == h1 ||
|
return tag == h1 ||
|
||||||
tag == h2 ||
|
tag == h2 ||
|
||||||
tag == h3 ||
|
tag == h3 ||
|
||||||
|
tag == checkbox ||
|
||||||
tag == paragraph ||
|
tag == paragraph ||
|
||||||
tag == div ||
|
tag == div ||
|
||||||
tag == blockQuote;
|
tag == blockQuote;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,239 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_editor/src/core/document/document.dart';
|
import 'package:html/dom.dart' as dom;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class DocumentHTMLEncoder extends Converter<Document, String> {
|
class DocumentHTMLEncoder extends Converter<Document, String> {
|
||||||
DocumentHTMLEncoder();
|
DocumentHTMLEncoder();
|
||||||
|
|
||||||
|
dom.Element? _stashListContainer;
|
||||||
|
final List<dom.Node> _result = [];
|
||||||
|
final List<Node> nodes = [];
|
||||||
@override
|
@override
|
||||||
String convert(Document input) {
|
String convert(Document input) {
|
||||||
final buffer = StringBuffer();
|
List<Node> documentNodes = input.root.children.toList();
|
||||||
return buffer.toString();
|
nodes.addAll(documentNodes);
|
||||||
|
return toHTMLString();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<dom.Node> toHTMLNodes() {
|
||||||
|
for (final documentNode in nodes) {
|
||||||
|
if (documentNode.type != ImageBlockKeys.type) {
|
||||||
|
_addTextNode(documentNode);
|
||||||
|
} else {
|
||||||
|
final anchor = dom.Element.tag(HTMLTags.image);
|
||||||
|
anchor.attributes["src"] = documentNode.attributes[ImageBlockKeys.url];
|
||||||
|
_result.add(anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_stashListContainer != null) {
|
||||||
|
_result.add(_stashListContainer!);
|
||||||
|
_stashListContainer = null;
|
||||||
|
}
|
||||||
|
return _result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addTextNode(
|
||||||
|
Node documentNode,
|
||||||
|
) {
|
||||||
|
_addElement(
|
||||||
|
documentNode,
|
||||||
|
_textNodeToHtml(
|
||||||
|
documentNode,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addElement(Node documentNode, dom.Element element) {
|
||||||
|
if (element.localName == HTMLTags.list) {
|
||||||
|
final isNumbered = documentNode.type == NumberedListBlockKeys.type;
|
||||||
|
_stashListContainer ??= dom.Element.tag(
|
||||||
|
isNumbered ? HTMLTags.orderedList : HTMLTags.unorderedList,
|
||||||
|
);
|
||||||
|
_stashListContainer?.append(element);
|
||||||
|
} else {
|
||||||
|
if (_stashListContainer != null) {
|
||||||
|
_result.add(_stashListContainer!);
|
||||||
|
_stashListContainer = null;
|
||||||
|
}
|
||||||
|
_result.add(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String toHTMLString() {
|
||||||
|
final elements = toHTMLNodes();
|
||||||
|
final copyString = elements.fold<String>(
|
||||||
|
'',
|
||||||
|
(previousValue, element) => previousValue + stringify(element),
|
||||||
|
);
|
||||||
|
return copyString.replaceAll("\n", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
dom.Element _textNodeToHtml(
|
||||||
|
Node documentNode,
|
||||||
|
) {
|
||||||
|
return _deltaToHtml(
|
||||||
|
Delta.fromJson(documentNode.attributes[ParagraphBlockKeys.delta]),
|
||||||
|
type: documentNode.type,
|
||||||
|
attributes: documentNode.attributes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _textDecorationsFromAttributes(Attributes attributes) {
|
||||||
|
final List<String> textDecoration = [];
|
||||||
|
if (attributes[BuiltInAttributeKey.strikethrough] == true) {
|
||||||
|
textDecoration.add('line-through');
|
||||||
|
}
|
||||||
|
if (attributes[BuiltInAttributeKey.underline] == true) {
|
||||||
|
textDecoration.add('underline');
|
||||||
|
}
|
||||||
|
|
||||||
|
return textDecoration.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
String _attributesToCssStyle(Map<String, dynamic> attributes) {
|
||||||
|
final cssMap = <String, String>{};
|
||||||
|
if (attributes[BuiltInAttributeKey.highlightColor] != null) {
|
||||||
|
final color = Color(
|
||||||
|
int.tryParse(attributes[BuiltInAttributeKey.highlightColor]) ??
|
||||||
|
0xFFFFFFFF,
|
||||||
|
);
|
||||||
|
|
||||||
|
cssMap['background-color'] = color.toRgbaString();
|
||||||
|
}
|
||||||
|
if (attributes[BuiltInAttributeKey.textColor] != null) {
|
||||||
|
final color = Color(
|
||||||
|
int.parse(attributes[BuiltInAttributeKey.textColor]),
|
||||||
|
);
|
||||||
|
cssMap['color'] = color.toRgbaString();
|
||||||
|
}
|
||||||
|
if (attributes[BuiltInAttributeKey.bold] == true) {
|
||||||
|
cssMap['font-weight'] = 'bold';
|
||||||
|
}
|
||||||
|
final textDecoration = _textDecorationsFromAttributes(attributes);
|
||||||
|
if (textDecoration.isNotEmpty) {
|
||||||
|
cssMap['text-decoration'] = textDecoration;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributes[BuiltInAttributeKey.italic] == true) {
|
||||||
|
cssMap['font-style'] = 'italic';
|
||||||
|
}
|
||||||
|
return _cssMapToCssStyle(cssMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _cssMapToCssStyle(Map<String, String> cssMap) {
|
||||||
|
return cssMap.entries.map((e) => '${e.key}: ${e.value}').join('; ');
|
||||||
|
}
|
||||||
|
|
||||||
|
dom.Element _deltaToHtml(
|
||||||
|
Delta delta, {
|
||||||
|
required String type,
|
||||||
|
required Attributes attributes,
|
||||||
|
}) {
|
||||||
|
final childNodes = <dom.Node>[];
|
||||||
|
String tagName = HTMLTags.paragraph;
|
||||||
|
|
||||||
|
if (type == BulletedListBlockKeys.type ||
|
||||||
|
type == NumberedListBlockKeys.type) {
|
||||||
|
tagName = HTMLTags.list;
|
||||||
|
} else if (type == TodoListBlockKeys.type) {
|
||||||
|
final node = dom.Element.html('<input type="checkbox" />');
|
||||||
|
|
||||||
|
node.attributes['checked'] =
|
||||||
|
attributes[TodoListBlockKeys.checked].toString();
|
||||||
|
tagName = HTMLTags.checkbox;
|
||||||
|
childNodes.add(node);
|
||||||
|
} else if (type == HeadingBlockKeys.type) {
|
||||||
|
if (attributes[HeadingBlockKeys.level] == 1) {
|
||||||
|
tagName = HTMLTags.h1;
|
||||||
|
} else if (attributes[HeadingBlockKeys.level] == 2) {
|
||||||
|
tagName = HTMLTags.h2;
|
||||||
|
} else if (attributes[HeadingBlockKeys.level] == 3) {
|
||||||
|
tagName = HTMLTags.h3;
|
||||||
|
}
|
||||||
|
} else if (type == QuoteBlockKeys.type) {
|
||||||
|
tagName = HTMLTags.blockQuote;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final op in delta) {
|
||||||
|
if (op is TextInsert) {
|
||||||
|
final attributes = op.attributes;
|
||||||
|
if (attributes != null) {
|
||||||
|
if (attributes.length == 1) {
|
||||||
|
final element = _applyAttributes(attributes, text: op.text);
|
||||||
|
childNodes.add(element);
|
||||||
|
} else {
|
||||||
|
final span = dom.Element.tag(HTMLTags.span);
|
||||||
|
final cssString = _attributesToCssStyle(attributes);
|
||||||
|
if (cssString.isNotEmpty) {
|
||||||
|
span.attributes['style'] = cssString;
|
||||||
|
}
|
||||||
|
span.append(dom.Text(op.text));
|
||||||
|
childNodes.add(span);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
childNodes.add(dom.Text(op.text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagName == HTMLTags.blockQuote) {
|
||||||
|
final blockQuote = dom.Element.tag(tagName);
|
||||||
|
blockQuote.append(_insertText(HTMLTag.paragraph, childNodes: childNodes));
|
||||||
|
return blockQuote;
|
||||||
|
} else if (tagName == HTMLTags.checkbox) {
|
||||||
|
return _insertText(HTMLTag.div, childNodes: childNodes);
|
||||||
|
} else if (!HTMLTags.isTopLevel(tagName)) {
|
||||||
|
final result = dom.Element.tag(HTMLTags.list);
|
||||||
|
result.append(_insertText(HTMLTag.paragraph, childNodes: childNodes));
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return _insertText(tagName, childNodes: childNodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dom.Element _applyAttributes(Attributes attributes, {required String text}) {
|
||||||
|
if (attributes[FlowyRichTextKeys.bold] == true) {
|
||||||
|
final strong = dom.Element.tag(HTMLTags.strong);
|
||||||
|
strong.append(dom.Text(text));
|
||||||
|
return strong;
|
||||||
|
} else if (attributes[FlowyRichTextKeys.underline] == true) {
|
||||||
|
final underline = dom.Element.tag(HTMLTags.underline);
|
||||||
|
underline.append(dom.Text(text));
|
||||||
|
return underline;
|
||||||
|
} else if (attributes[FlowyRichTextKeys.italic] == true) {
|
||||||
|
final italic = dom.Element.tag(HTMLTags.italic);
|
||||||
|
italic.append(dom.Text(text));
|
||||||
|
return italic;
|
||||||
|
} else if (attributes[FlowyRichTextKeys.strikethrough] == true) {
|
||||||
|
final del = dom.Element.tag(HTMLTags.del);
|
||||||
|
del.append(dom.Text(text));
|
||||||
|
return del;
|
||||||
|
} else if (attributes[FlowyRichTextKeys.code] == true) {
|
||||||
|
final code = dom.Element.tag(HTMLTags.code);
|
||||||
|
code.append(dom.Text(text));
|
||||||
|
return code;
|
||||||
|
} else if (attributes[FlowyRichTextKeys.href] != null) {
|
||||||
|
final anchor = dom.Element.tag(HTMLTags.anchor);
|
||||||
|
anchor.attributes['href'] = attributes[FlowyRichTextKeys.href];
|
||||||
|
anchor.append(dom.Text(text));
|
||||||
|
return anchor;
|
||||||
|
} else {
|
||||||
|
final paragraph = dom.Element.tag(HTMLTags.paragraph);
|
||||||
|
|
||||||
|
paragraph.append(dom.Text(text));
|
||||||
|
return paragraph;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dom.Element _insertText(
|
||||||
|
String tagName, {
|
||||||
|
required List<dom.Node> childNodes,
|
||||||
|
}) {
|
||||||
|
final p = dom.Element.tag(tagName);
|
||||||
|
for (final node in childNodes) {
|
||||||
|
p.append(node);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,341 +4,288 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
group('document_html_decoder_test.dart', () {
|
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(() {
|
setUpAll(() {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('parser document', () async {
|
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);
|
final result = DocumentHTMLDecoder().convert(rawHTML);
|
||||||
|
|
||||||
expect(result.toJson(), example);
|
expect(result.toJson(), example);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rawHTML =
|
||||||
|
'''<h1>AppFlowyEditor</h1><h2>👋 <strong>Welcome to</strong> <span style="font-weight: bold; font-style: italic">AppFlowy Editor</span></h2><p>AppFlowy Editor is a <strong>highly customizable</strong> <i>rich-text editor</i></p><p> <u>Here</u> is an example <del>your</del> you can give a try</p><p> <span style="font-weight: bold; font-style: italic">Span element</span></p><p> <u>Span element two</u></p><p> <span style="font-weight: bold; text-decoration: line-through">Span element three</span></p><p> <a href="https://appflowy.io">This is an anchor tag!</a></p><h3>Features!</h3><ul><li><p>[x] Customizable</p></li><li><p>[x] Test-covered</p></li><li><p>[ ] more to come!</p></li><li><p>First item</p></li><li><p>Second item</p></li><li><p>List element</p></li></ul><blockquote><p>This is a quote!</p></blockquote><p><code> Code block</code></p><p> <i>Italic one</i></p><p> <i>Italic two</i></p><p> <strong>Bold tag</strong></p><p>You can also use <span style="font-weight: bold; font-style: italic">AppFlowy Editor</span> 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><p></p><p></p>''';
|
||||||
|
|
||||||
|
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}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'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': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Here',
|
||||||
|
'attributes': {'underline': true}
|
||||||
|
},
|
||||||
|
{'insert': ' is an example '},
|
||||||
|
{
|
||||||
|
'insert': 'your',
|
||||||
|
'attributes': {'strikethrough': true}
|
||||||
|
},
|
||||||
|
{'insert': ' you can give a try'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Span element',
|
||||||
|
'attributes': {'bold': true, 'italic': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Span element two',
|
||||||
|
'attributes': {'underline': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Span element three',
|
||||||
|
'attributes': {'bold': true, 'strikethrough': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'This is an anchor tag!',
|
||||||
|
'attributes': {'href': 'https://appflowy.io'}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'heading',
|
||||||
|
'data': {
|
||||||
|
'level': 3,
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'Features!'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'bulleted_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': '[x] Customizable'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'bulleted_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': '[x] Test-covered'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'bulleted_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': '[ ] more to come!'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'bulleted_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'First item'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'bulleted_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'Second item'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'bulleted_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'List element'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'quote',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'This is a quote!'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{
|
||||||
|
'insert': ' Code block',
|
||||||
|
'attributes': {'code': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Italic one',
|
||||||
|
'attributes': {'italic': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Italic two',
|
||||||
|
'attributes': {'italic': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Bold tag',
|
||||||
|
'attributes': {'bold': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'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': []}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {'delta': []}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
290
test/plugins/html/encoder/document_html_encoder_test.dart
Normal file
290
test/plugins/html/encoder/document_html_encoder_test.dart
Normal file
|
|
@ -0,0 +1,290 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
group('document_html_encoder_test.dart', () {
|
||||||
|
setUpAll(() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
});
|
||||||
|
test('parser document', () async {
|
||||||
|
final result = DocumentHTMLEncoder().convert(Document.fromJson(delta));
|
||||||
|
|
||||||
|
expect(result, example);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const example =
|
||||||
|
'''<h1>AppFlowyEditor</h1><h2>👋 <strong>Welcome to</strong> <span style="font-weight: bold; font-style: italic">AppFlowy Editor</span></h2><p>AppFlowy Editor is a <strong>highly customizable</strong> <i>rich-text editor</i></p><p> <u>Here</u> is an example <del>your</del> you can give a try</p><p> <span style="font-weight: bold; font-style: italic">Span element</span></p><p> <u>Span element two</u></p><p> <span style="font-weight: bold; text-decoration: line-through">Span element three</span></p><p> <a href="https://appflowy.io">This is an anchor tag!</a></p><h3>Features!</h3><ul><li><p>[x] Customizable</p></li><li><p>[x] Test-covered</p></li><li><p>[ ] more to come!</p></li><li><p>First item</p></li><li><p>Second item</p></li><li><p>List element</p></li></ul><blockquote><p>This is a quote!</p></blockquote><p><code> Code block</code></p><p> <i>Italic one</i></p><p> <i>Italic two</i></p><p> <strong>Bold tag</strong></p><p>You can also use <span style="font-weight: bold; font-style: italic">AppFlowy Editor</span> 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><p></p><p></p>''';
|
||||||
|
|
||||||
|
const delta = {
|
||||||
|
'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}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'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': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Here',
|
||||||
|
'attributes': {'underline': true}
|
||||||
|
},
|
||||||
|
{'insert': ' is an example '},
|
||||||
|
{
|
||||||
|
'insert': 'your',
|
||||||
|
'attributes': {'strikethrough': true}
|
||||||
|
},
|
||||||
|
{'insert': ' you can give a try'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Span element',
|
||||||
|
'attributes': {'bold': true, 'italic': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Span element two',
|
||||||
|
'attributes': {'underline': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Span element three',
|
||||||
|
'attributes': {'bold': true, 'strikethrough': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'This is an anchor tag!',
|
||||||
|
'attributes': {'href': 'https://appflowy.io'}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'heading',
|
||||||
|
'data': {
|
||||||
|
'level': 3,
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'Features!'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'bulleted_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': '[x] Customizable'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'bulleted_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': '[x] Test-covered'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'bulleted_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': '[ ] more to come!'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'bulleted_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'First item'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'bulleted_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'Second item'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'bulleted_list',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'List element'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'quote',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'This is a quote!'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{
|
||||||
|
'insert': ' Code block',
|
||||||
|
'attributes': {'code': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Italic one',
|
||||||
|
'attributes': {'italic': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Italic two',
|
||||||
|
'attributes': {'italic': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Bold tag',
|
||||||
|
'attributes': {'bold': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'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': []}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {'delta': []}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -8,6 +8,12 @@ void main() {
|
||||||
expect(document.toJson(), data);
|
expect(document.toJson(), data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
group('document_html_test.dart tests', () {
|
||||||
|
test('documentToHtml()', () {
|
||||||
|
final document = documentToHTML(Document.fromJson(data));
|
||||||
|
expect(document, rawHTML);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
|
|
@ -33,14 +39,10 @@ const data = {
|
||||||
'insert': 'Welcome to',
|
'insert': 'Welcome to',
|
||||||
'attributes': {'bold': true}
|
'attributes': {'bold': true}
|
||||||
},
|
},
|
||||||
{'insert': ' '},
|
{'insert': ' '},
|
||||||
{
|
{
|
||||||
'insert': 'AppFlowy Editor',
|
'insert': 'AppFlowy Editor',
|
||||||
'attributes': {
|
'attributes': {'bold': true, 'italic': true}
|
||||||
'bold': true,
|
|
||||||
'italic': true,
|
|
||||||
'href': 'appflowy.io'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -54,7 +56,7 @@ const data = {
|
||||||
'insert': 'highly customizable',
|
'insert': 'highly customizable',
|
||||||
'attributes': {'bold': true}
|
'attributes': {'bold': true}
|
||||||
},
|
},
|
||||||
{'insert': ' '},
|
{'insert': ' '},
|
||||||
{
|
{
|
||||||
'insert': 'rich-text editor',
|
'insert': 'rich-text editor',
|
||||||
'attributes': {'italic': true}
|
'attributes': {'italic': true}
|
||||||
|
|
@ -66,6 +68,7 @@ const data = {
|
||||||
'type': 'paragraph',
|
'type': 'paragraph',
|
||||||
'data': {
|
'data': {
|
||||||
'delta': [
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
{
|
{
|
||||||
'insert': 'Here',
|
'insert': 'Here',
|
||||||
'attributes': {'underline': true}
|
'attributes': {'underline': true}
|
||||||
|
|
@ -79,6 +82,54 @@ const data = {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Span element',
|
||||||
|
'attributes': {'bold': true, 'italic': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Span element two',
|
||||||
|
'attributes': {'underline': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Span element three',
|
||||||
|
'attributes': {'bold': true, 'strikethrough': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'This is an anchor tag!',
|
||||||
|
'attributes': {'href': 'https://appflowy.io'}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'type': 'heading',
|
'type': 'heading',
|
||||||
'data': {
|
'data': {
|
||||||
|
|
@ -89,7 +140,7 @@ const data = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'type': 'numbered_list',
|
'type': 'bulleted_list',
|
||||||
'data': {
|
'data': {
|
||||||
'delta': [
|
'delta': [
|
||||||
{'insert': '[x] Customizable'}
|
{'insert': '[x] Customizable'}
|
||||||
|
|
@ -97,7 +148,7 @@ const data = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'type': 'numbered_list',
|
'type': 'bulleted_list',
|
||||||
'data': {
|
'data': {
|
||||||
'delta': [
|
'delta': [
|
||||||
{'insert': '[x] Test-covered'}
|
{'insert': '[x] Test-covered'}
|
||||||
|
|
@ -105,7 +156,7 @@ const data = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'type': 'numbered_list',
|
'type': 'bulleted_list',
|
||||||
'data': {
|
'data': {
|
||||||
'delta': [
|
'delta': [
|
||||||
{'insert': '[ ] more to come!'}
|
{'insert': '[ ] more to come!'}
|
||||||
|
|
@ -113,7 +164,7 @@ const data = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'type': 'numbered_list',
|
'type': 'bulleted_list',
|
||||||
'data': {
|
'data': {
|
||||||
'delta': [
|
'delta': [
|
||||||
{'insert': 'First item'}
|
{'insert': 'First item'}
|
||||||
|
|
@ -121,7 +172,7 @@ const data = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'type': 'numbered_list',
|
'type': 'bulleted_list',
|
||||||
'data': {
|
'data': {
|
||||||
'delta': [
|
'delta': [
|
||||||
{'insert': 'Second item'}
|
{'insert': 'Second item'}
|
||||||
|
|
@ -129,7 +180,7 @@ const data = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'type': 'paragraph',
|
'type': 'bulleted_list',
|
||||||
'data': {
|
'data': {
|
||||||
'delta': [
|
'delta': [
|
||||||
{'insert': 'List element'}
|
{'insert': 'List element'}
|
||||||
|
|
@ -138,12 +189,55 @@ const data = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'type': 'quote',
|
'type': 'quote',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': 'This is a quote!'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
'data': {
|
'data': {
|
||||||
'delta': [
|
'delta': [
|
||||||
{
|
{
|
||||||
'insert': '\n'
|
'insert': ' Code block',
|
||||||
' This is a quote!\n'
|
'attributes': {'code': true}
|
||||||
''
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Italic one',
|
||||||
|
'attributes': {'italic': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Italic two',
|
||||||
|
'attributes': {'italic': true}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'paragraph',
|
||||||
|
'data': {
|
||||||
|
'delta': [
|
||||||
|
{'insert': ' '},
|
||||||
|
{
|
||||||
|
'insert': 'Bold tag',
|
||||||
|
'attributes': {'bold': true}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -157,7 +251,7 @@ const data = {
|
||||||
'insert': 'AppFlowy Editor',
|
'insert': 'AppFlowy Editor',
|
||||||
'attributes': {'bold': true, 'italic': true}
|
'attributes': {'bold': true, 'italic': true}
|
||||||
},
|
},
|
||||||
{'insert': ' as a component to build your own app.'}
|
{'insert': ' as a component to build your own app. '}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -183,156 +277,14 @@ const data = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'type': 'paragraph',
|
'type': 'paragraph',
|
||||||
'data': {
|
'data': {'delta': []}
|
||||||
'delta': [
|
},
|
||||||
{
|
{
|
||||||
'insert': '\n'
|
'type': 'paragraph',
|
||||||
'\n'
|
'data': {'delta': []}
|
||||||
' \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>
|
const rawHTML =
|
||||||
<h2>👋 <strong>Welcome to</strong> <strong><em><a href="appflowy.io">AppFlowy Editor</a></em></strong></h2>
|
'''<h1>AppFlowyEditor</h1><h2>👋 <strong>Welcome to</strong> <span style="font-weight: bold; font-style: italic">AppFlowy Editor</span></h2><p>AppFlowy Editor is a <strong>highly customizable</strong> <i>rich-text editor</i></p><p> <u>Here</u> is an example <del>your</del> you can give a try</p><p> <span style="font-weight: bold; font-style: italic">Span element</span></p><p> <u>Span element two</u></p><p> <span style="font-weight: bold; text-decoration: line-through">Span element three</span></p><p> <a href="https://appflowy.io">This is an anchor tag!</a></p><h3>Features!</h3><ul><li><p>[x] Customizable</p></li><li><p>[x] Test-covered</p></li><li><p>[ ] more to come!</p></li><li><p>First item</p></li><li><p>Second item</p></li><li><p>List element</p></li></ul><blockquote><p>This is a quote!</p></blockquote><p><code> Code block</code></p><p> <i>Italic one</i></p><p> <i>Italic two</i></p><p> <strong>Bold tag</strong></p><p>You can also use <span style="font-weight: bold; font-style: italic">AppFlowy Editor</span> 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><p></p><p></p>''';
|
||||||
<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