diff --git a/frontend/app_flowy/packages/flowy_editor/example/assets/example.json b/frontend/app_flowy/packages/flowy_editor/example/assets/example.json
index fe74b22dad..e6357d93f9 100644
--- a/frontend/app_flowy/packages/flowy_editor/example/assets/example.json
+++ b/frontend/app_flowy/packages/flowy_editor/example/assets/example.json
@@ -73,7 +73,7 @@
},
{
"insert": " / ",
- "attributes": { "highlightColor": "0xFFFFFF00" }
+ "attributes": { "backgroundColor": "0xFFFFFF00" }
},
{
"insert": "to see all the types of content you can add - entity, headers, videos, sub pages, etc."
diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart
index d05f768895..eb4eb0f653 100644
--- a/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart
+++ b/frontend/app_flowy/packages/flowy_editor/lib/src/infra/html_converter.dart
@@ -3,7 +3,7 @@ import 'dart:collection';
import 'package:flowy_editor/src/document/attributes.dart';
import 'package:flowy_editor/src/document/node.dart';
import 'package:flowy_editor/src/document/text_delta.dart';
-import 'package:flutter/foundation.dart';
+import 'package:flowy_editor/src/render/rich_text/rich_text_style.dart';
import 'package:flutter/material.dart';
import 'package:html/parser.dart' show parse;
import 'package:html/dom.dart' as html;
@@ -11,6 +11,7 @@ import 'package:html/dom.dart' as html;
const String tagH1 = "h1";
const String tagH2 = "h2";
const String tagH3 = "h3";
+const String tagOrderedList = "ol";
const String tagUnorderedList = "ul";
const String tagList = "li";
const String tagParagraph = "p";
@@ -21,23 +22,33 @@ const String tagStrong = "strong";
const String tagSpan = "span";
const String tagCode = "code";
-class HTMLConverter {
+extension on Color {
+ String toRgbaString() {
+ return 'rgba($red, $green, $blue, $alpha)';
+ }
+}
+
+/// Converting the HTML to nodes
+class HTMLToNodesConverter {
final html.Document _document;
+
+ /// This flag is used for parsing HTML pasting from Google Docs
+ /// Google docs wraps the the content inside the `` tag. It's strange.
+ ///
+ /// If a `` element is parsing in the
, we regard it as as text spans.
+ /// Otherwise, it's parsed as a container.
bool _inParagraph = false;
- HTMLConverter(String htmlString) : _document = parse(htmlString);
+ HTMLToNodesConverter(String htmlString) : _document = parse(htmlString);
List toNodes() {
- final result = [];
-
final childNodes = _document.body?.nodes.toList() ?? [];
- _handleContainer(result, childNodes);
-
- return result;
+ return _handleContainer(childNodes);
}
- _handleContainer(List nodes, List childNodes) {
+ List _handleContainer(List childNodes) {
final delta = Delta();
+ final result = [];
for (final child in childNodes) {
if (child is html.Element) {
if (child.localName == tagAnchor ||
@@ -46,83 +57,139 @@ class HTMLConverter {
child.localName == tagStrong) {
_handleRichTextElement(delta, child);
} else if (child.localName == tagBold) {
- // Google docs wraps the the content inside the tag.
+ // Google docs wraps the the content inside the `` tag.
// It's strange
if (!_inParagraph) {
- _handleBTag(nodes, child);
+ result.addAll(_handleBTag(child));
} else {
- _handleRichText(nodes, child);
+ result.add(_handleRichText(child));
}
} else {
- _handleElement(nodes, child);
+ result.addAll(_handleElement(child));
}
} else {
delta.insert(child.text ?? "");
}
}
if (delta.operations.isNotEmpty) {
- nodes.add(TextNode(type: "text", delta: delta));
+ result.add(TextNode(type: "text", delta: delta));
}
+ return result;
}
- _handleBTag(List nodes, html.Element element) {
+ List _handleBTag(html.Element element) {
final childNodes = element.nodes;
- _handleContainer(nodes, childNodes);
+ return _handleContainer(childNodes);
}
- _handleElement(List nodes, html.Element element,
+ List _handleElement(html.Element element,
[Map? attributes]) {
if (element.localName == tagH1) {
- _handleHeadingElement(nodes, element, tagH1);
+ return [_handleHeadingElement(element, tagH1)];
} else if (element.localName == tagH2) {
- _handleHeadingElement(nodes, element, tagH2);
+ return [_handleHeadingElement(element, tagH2)];
} else if (element.localName == tagH3) {
- _handleHeadingElement(nodes, element, tagH3);
+ return [_handleHeadingElement(element, tagH3)];
} else if (element.localName == tagUnorderedList) {
- _handleUnorderedList(nodes, element);
+ return _handleUnorderedList(element);
+ } else if (element.localName == tagOrderedList) {
+ return _handleOrderedList(element);
} else if (element.localName == tagList) {
- _handleListElement(nodes, element);
+ return _handleListElement(element);
} else if (element.localName == tagParagraph) {
- _handleParagraph(nodes, element, attributes);
+ return [_handleParagraph(element, attributes)];
} else {
final delta = Delta();
delta.insert(element.text);
if (delta.operations.isNotEmpty) {
- nodes.add(TextNode(type: "text", delta: delta));
+ return [TextNode(type: "text", delta: delta)];
}
}
+ return [];
}
- _handleParagraph(List nodes, html.Element element,
+ Node _handleParagraph(html.Element element,
[Map? attributes]) {
_inParagraph = true;
- _handleRichText(nodes, element, attributes);
+ final node = _handleRichText(element, attributes);
_inParagraph = false;
+ return node;
+ }
+
+ Map _cssStringToMap(String? cssString) {
+ final result = {};
+ if (cssString == null) {
+ return result;
+ }
+
+ final entries = cssString.split(";");
+ for (final entry in entries) {
+ final tuples = entry.split(":");
+ if (tuples.length < 2) {
+ continue;
+ }
+ result[tuples[0]] = tuples[1];
+ }
+
+ return result;
}
Attributes? _getDeltaAttributesFromHtmlAttributes(
LinkedHashMap