Merge pull request #790 from AppFlowy-IO/feat/copy-styles

Feat/copy styles
This commit is contained in:
Vincent Chan 2022-08-09 11:30:30 +08:00 committed by GitHub
commit 8e310d233f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 104 additions and 17 deletions

View File

@ -4,6 +4,7 @@ import 'package:flowy_editor/document/attributes.dart';
import 'package:flowy_editor/document/node.dart'; import 'package:flowy_editor/document/node.dart';
import 'package:flowy_editor/document/text_delta.dart'; import 'package:flowy_editor/document/text_delta.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:html/parser.dart' show parse; import 'package:html/parser.dart' show parse;
import 'package:html/dom.dart' as html; import 'package:html/dom.dart' as html;
@ -205,20 +206,58 @@ class HTMLConverter {
} }
} }
String deltaToHtml(Delta delta) { html.Element deltaToHtml(Delta delta, [String? subType]) {
var result = "<p>"; final childNodes = <html.Node>[];
String tagName = tagParagraph;
if (subType == "bulleted-list") {
tagName = tagList;
}
for (final op in delta.operations) { for (final op in delta.operations) {
if (op is TextInsert) { if (op is TextInsert) {
final attributes = op.attributes; final attributes = op.attributes;
if (attributes != null && attributes["bold"] == true) { if (attributes != null && attributes["bold"] == true) {
result += '<strong>${op.content}</strong>'; final strong = html.Element.tag("strong");
strong.append(html.Text(op.content));
childNodes.add(strong);
} else { } else {
result += op.content; childNodes.add(html.Text(op.content));
} }
} }
} }
result += "</p>"; if (tagName != tagParagraph) {
return result; 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 "";
} }

View File

@ -1,3 +1,4 @@
import 'package:html/dom.dart' as html;
import 'package:flowy_editor/flowy_editor.dart'; import 'package:flowy_editor/flowy_editor.dart';
import 'package:flowy_editor/service/keyboard_service.dart'; import 'package:flowy_editor/service/keyboard_service.dart';
import 'package:flowy_editor/infra/html_converter.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:flutter/services.dart';
import 'package:rich_clipboard/rich_clipboard.dart'; import 'package:rich_clipboard/rich_clipboard.dart';
class _HTMLNormalizer {
final List<html.Node> nodes;
html.Element? _pendingList;
_HTMLNormalizer(this.nodes);
List<html.Node> normalize() {
final result = <html.Node>[];
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<html.Node> result) {
if (_pendingList == null) {
return;
}
result.add(_pendingList!);
_pendingList = null;
}
}
_handleCopy(EditorState editorState) async { _handleCopy(EditorState editorState) async {
final selection = editorState.cursorSelection; final selection = editorState.cursorSelection;
if (selection == null || selection.isCollapsed) { if (selection == null || selection.isCollapsed) {
@ -18,7 +63,7 @@ _handleCopy(EditorState editorState) async {
final delta = final delta =
textNode.delta.slice(selection.start.offset, selection.end.offset); textNode.delta.slice(selection.start.offset, selection.end.offset);
final htmlString = deltaToHtml(delta); final htmlString = stringify(deltaToHtml(delta));
debugPrint('copy html: $htmlString'); debugPrint('copy html: $htmlString');
RichClipboard.setData(RichClipboardData(html: htmlString)); RichClipboard.setData(RichClipboardData(html: htmlString));
} else { } else {
@ -31,27 +76,30 @@ _handleCopy(EditorState editorState) async {
final endNode = editorState.document.nodeAtPath(selection.end.path)!; final endNode = editorState.document.nodeAtPath(selection.end.path)!;
final traverser = NodeIterator(editorState.document, beginNode, endNode); final traverser = NodeIterator(editorState.document, beginNode, endNode);
var copyString = ""; final nodes = <html.Node>[];
while (traverser.moveNext()) { while (traverser.moveNext()) {
final node = traverser.current; final node = traverser.current;
if (node.type == "text") { if (node.type == "text") {
final textNode = node as TextNode; final textNode = node as TextNode;
String? subType = textNode.attributes["subtype"];
if (node == beginNode) { if (node == beginNode) {
final htmlString = final htmlElement =
deltaToHtml(textNode.delta.slice(selection.start.offset)); deltaToHtml(textNode.delta.slice(selection.start.offset), subType);
copyString += htmlString; nodes.add(htmlElement);
} else if (node == endNode) { } else if (node == endNode) {
final htmlString = final htmlElement =
deltaToHtml(textNode.delta.slice(0, selection.end.offset)); deltaToHtml(textNode.delta.slice(0, selection.end.offset), subType);
copyString += htmlString; nodes.add(htmlElement);
} else { } else {
final htmlString = deltaToHtml(textNode.delta); final htmlElement = deltaToHtml(textNode.delta, subType);
copyString += htmlString; nodes.add(htmlElement);
} }
} }
// TODO: handle image and other blocks // TODO: handle image and other blocks
} }
final copyString = _HTMLNormalizer(nodes).normalize().fold<String>(
"", ((previousValue, element) => previousValue + stringify(element)));
debugPrint('copy html: $copyString'); debugPrint('copy html: $copyString');
RichClipboard.setData(RichClipboardData(html: copyString)); RichClipboard.setData(RichClipboardData(html: copyString));
} }