mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #790 from AppFlowy-IO/feat/copy-styles
Feat/copy styles
This commit is contained in:
commit
8e310d233f
@ -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 "";
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user