diff --git a/frontend/app_flowy/packages/flowy_editor/lib/infra/html_converter.dart b/frontend/app_flowy/packages/flowy_editor/lib/infra/html_converter.dart
index 5a1a7912da..708e47cb85 100644
--- a/frontend/app_flowy/packages/flowy_editor/lib/infra/html_converter.dart
+++ b/frontend/app_flowy/packages/flowy_editor/lib/infra/html_converter.dart
@@ -4,6 +4,7 @@ import 'package:flowy_editor/document/attributes.dart';
import 'package:flowy_editor/document/node.dart';
import 'package:flowy_editor/document/text_delta.dart';
import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
import 'package:html/parser.dart' show parse;
import 'package:html/dom.dart' as html;
@@ -205,20 +206,58 @@ class HTMLConverter {
}
}
-String deltaToHtml(Delta delta) {
- var result = "
";
+html.Element deltaToHtml(Delta delta, [String? subType]) {
+ final childNodes = [];
+ String tagName = tagParagraph;
+
+ if (subType == "bulleted-list") {
+ tagName = tagList;
+ }
for (final op in delta.operations) {
if (op is TextInsert) {
final attributes = op.attributes;
if (attributes != null && attributes["bold"] == true) {
- result += '${op.content}';
+ final strong = html.Element.tag("strong");
+ strong.append(html.Text(op.content));
+ childNodes.add(strong);
} else {
- result += op.content;
+ childNodes.add(html.Text(op.content));
}
}
}
- result += "
";
- return result;
+ if (tagName != tagParagraph) {
+ final p = html.Element.tag(tagParagraph);
+ for (final node in childNodes) {
+ p.append(node);
+ }
+ final result = html.Element.tag("li");
+ result.append(p);
+ return result;
+ } else {
+ final p = html.Element.tag(tagName);
+ for (final node in childNodes) {
+ p.append(node);
+ }
+ return p;
+ }
+}
+
+String stringify(html.Node node) {
+ if (node is html.Element) {
+ String result = '<${node.localName}>';
+
+ for (final node in node.nodes) {
+ result += stringify(node);
+ }
+
+ return result += '${node.localName}>';
+ }
+
+ if (node is html.Text) {
+ return node.text;
+ }
+
+ return "";
}
diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/copy_paste_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/copy_paste_handler.dart
index 8cab09b293..d3eeb073b9 100644
--- a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/copy_paste_handler.dart
+++ b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/copy_paste_handler.dart
@@ -1,3 +1,4 @@
+import 'package:html/dom.dart' as html;
import 'package:flowy_editor/flowy_editor.dart';
import 'package:flowy_editor/service/keyboard_service.dart';
import 'package:flowy_editor/infra/html_converter.dart';
@@ -6,6 +7,50 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rich_clipboard/rich_clipboard.dart';
+class _HTMLNormalizer {
+ final List nodes;
+ html.Element? _pendingList;
+
+ _HTMLNormalizer(this.nodes);
+
+ List normalize() {
+ final result = [];
+
+ for (final item in nodes) {
+ if (item is Text) {
+ result.add(item);
+ continue;
+ }
+
+ if (item is html.Element) {
+ if (item.localName == "li") {
+ if (_pendingList != null) {
+ _pendingList!.append(item);
+ } else {
+ final ulItem = html.Element.tag("ul");
+ ulItem.append(item);
+
+ _pendingList = ulItem;
+ }
+ } else {
+ _pushList(result);
+ result.add(item);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ _pushList(List result) {
+ if (_pendingList == null) {
+ return;
+ }
+ result.add(_pendingList!);
+ _pendingList = null;
+ }
+}
+
_handleCopy(EditorState editorState) async {
final selection = editorState.cursorSelection;
if (selection == null || selection.isCollapsed) {
@@ -18,7 +63,7 @@ _handleCopy(EditorState editorState) async {
final delta =
textNode.delta.slice(selection.start.offset, selection.end.offset);
- final htmlString = deltaToHtml(delta);
+ final htmlString = stringify(deltaToHtml(delta));
debugPrint('copy html: $htmlString');
RichClipboard.setData(RichClipboardData(html: htmlString));
} else {
@@ -31,27 +76,30 @@ _handleCopy(EditorState editorState) async {
final endNode = editorState.document.nodeAtPath(selection.end.path)!;
final traverser = NodeIterator(editorState.document, beginNode, endNode);
- var copyString = "";
+ final nodes = [];
while (traverser.moveNext()) {
final node = traverser.current;
if (node.type == "text") {
final textNode = node as TextNode;
+ String? subType = textNode.attributes["subtype"];
if (node == beginNode) {
- final htmlString =
- deltaToHtml(textNode.delta.slice(selection.start.offset));
- copyString += htmlString;
+ final htmlElement =
+ deltaToHtml(textNode.delta.slice(selection.start.offset), subType);
+ nodes.add(htmlElement);
} else if (node == endNode) {
- final htmlString =
- deltaToHtml(textNode.delta.slice(0, selection.end.offset));
- copyString += htmlString;
+ final htmlElement =
+ deltaToHtml(textNode.delta.slice(0, selection.end.offset), subType);
+ nodes.add(htmlElement);
} else {
- final htmlString = deltaToHtml(textNode.delta);
- copyString += htmlString;
+ final htmlElement = deltaToHtml(textNode.delta, subType);
+ nodes.add(htmlElement);
}
}
// TODO: handle image and other blocks
-
}
+
+ final copyString = _HTMLNormalizer(nodes).normalize().fold(
+ "", ((previousValue, element) => previousValue + stringify(element)));
debugPrint('copy html: $copyString');
RichClipboard.setData(RichClipboardData(html: copyString));
}