feat: copy from html

This commit is contained in:
Vincent Chan 2022-08-03 17:29:45 +08:00
parent 3065f6d236
commit 2a6412f81a
3 changed files with 83 additions and 18 deletions

View File

@ -1,5 +1,6 @@
import 'dart:collection'; import 'dart:collection';
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';
@ -20,7 +21,8 @@ class HTMLConverter {
if (child is html.Element) { if (child is html.Element) {
if (child.localName == "a" || if (child.localName == "a" ||
child.localName == "span" || child.localName == "span" ||
child.localName == "strong") { child.localName == "strong" ||
child.localName == "b") {
_handleRichTextElement(delta, child); _handleRichTextElement(delta, child);
} else { } else {
_handleElement(result, child); _handleElement(result, child);
@ -63,9 +65,33 @@ class HTMLConverter {
_handleRichText(nodes, element); _handleRichText(nodes, element);
} }
Attributes? _getDeltaAttributesFromHtmlAttributes(
LinkedHashMap<Object, String> htmlAttributes) {
final attrs = <String, dynamic>{};
final styleString = htmlAttributes["style"];
if (styleString != null) {
final entries = styleString.split(";");
for (final entry in entries) {
final tuples = entry.split(":");
if (tuples.length < 2) {
continue;
}
if (tuples[0] == "font-weight") {
int? weight = int.tryParse(tuples[1]);
if (weight != null && weight > 500) {
attrs["bold"] = true;
}
}
}
}
return attrs.isEmpty ? null : attrs;
}
_handleRichTextElement(Delta delta, html.Element element) { _handleRichTextElement(Delta delta, html.Element element) {
if (element.localName == "span") { if (element.localName == "span") {
delta.insert(element.text); delta.insert(element.text,
_getDeltaAttributesFromHtmlAttributes(element.attributes));
} else if (element.localName == "a") { } else if (element.localName == "a") {
final hyperLink = element.attributes["href"]; final hyperLink = element.attributes["href"];
Map<String, dynamic>? attributes; Map<String, dynamic>? attributes;
@ -73,8 +99,10 @@ class HTMLConverter {
attributes = {"href": hyperLink}; attributes = {"href": hyperLink};
} }
delta.insert(element.text, attributes); delta.insert(element.text, attributes);
} else if (element.localName == "strong") { } else if (element.localName == "strong" || element.localName == "b") {
delta.insert(element.text, {"bold": true}); delta.insert(element.text, {"bold": true});
} else {
delta.insert(element.text);
} }
} }
@ -89,13 +117,7 @@ class HTMLConverter {
for (final child in element.nodes.toList()) { for (final child in element.nodes.toList()) {
if (child is html.Element) { if (child is html.Element) {
if (child.localName == "a" || _handleRichTextElement(delta, child);
child.localName == "span" ||
child.localName == "strong") {
_handleRichTextElement(delta, element);
} else {
delta.insert(child.text);
}
} else { } else {
delta.insert(child.text ?? ""); delta.insert(child.text ?? "");
} }
@ -147,3 +169,21 @@ class HTMLConverter {
} }
} }
} }
String deltaToHtml(Delta delta) {
var result = "<p>";
for (final op in delta.operations) {
if (op is TextInsert) {
final attributes = op.attributes;
if (attributes != null && attributes["bold"] == true) {
result += '<strong>${op.content}</strong>';
} else {
result += op.content;
}
}
}
result += "</p>";
return result;
}

View File

@ -5,7 +5,26 @@ 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';
_handleCopy() async { _handleCopy(EditorState editorState) async {
final selection = editorState.cursorSelection;
if (selection == null || selection.isCollapsed) {
return;
}
if (pathEquals(selection.start.path, selection.end.path)) {
final nodeAtPath = editorState.document.nodeAtPath(selection.end.path)!;
if (nodeAtPath.type == "text") {
final textNode = nodeAtPath as TextNode;
final delta =
textNode.delta.slice(selection.start.offset, selection.end.offset);
final htmlString = deltaToHtml(delta);
debugPrint('copy html: $htmlString');
RichClipboard.setData(RichClipboardData(html: htmlString));
} else {
debugPrint("unimplemented: copy non-text");
}
return;
}
debugPrint('copy'); debugPrint('copy');
} }
@ -20,6 +39,7 @@ _pasteHTML(EditorState editorState, String html) {
return; return;
} }
debugPrint('paste html: $html');
final converter = HTMLConverter(html); final converter = HTMLConverter(html);
final nodes = converter.toNodes(); final nodes = converter.toNodes();
@ -38,6 +58,7 @@ _pasteHTML(EditorState editorState, String html) {
tb.setAfterSelection(Selection.collapsed(Position( tb.setAfterSelection(Selection.collapsed(Position(
path: path, offset: startOffset + firstTextNode.delta.length))); path: path, offset: startOffset + firstTextNode.delta.length)));
tb.commit(); tb.commit();
return;
} }
} }
@ -64,12 +85,16 @@ _pasteMultipleLinesInText(
.delete(remain.length) .delete(remain.length)
.concat(firstTextNode.delta)); .concat(firstTextNode.delta));
path[path.length - 1]++;
final tailNodes = nodes.sublist(1); final tailNodes = nodes.sublist(1);
if (tailNodes.last.type == "text") { path[path.length - 1]++;
final tailTextNode = tailNodes.last as TextNode; if (tailNodes.isNotEmpty) {
tailTextNode.delta = tailTextNode.delta.concat(remain); if (tailNodes.last.type == "text") {
} else if (remain.length > 0) { final tailTextNode = tailNodes.last as TextNode;
tailTextNode.delta = tailTextNode.delta.concat(remain);
} else if (remain.length > 0) {
tailNodes.add(TextNode(type: "text", delta: remain));
}
} else {
tailNodes.add(TextNode(type: "text", delta: remain)); tailNodes.add(TextNode(type: "text", delta: remain));
} }
@ -165,7 +190,7 @@ _handleCut() {
FlowyKeyEventHandler copyPasteKeysHandler = (editorState, event) { FlowyKeyEventHandler copyPasteKeysHandler = (editorState, event) {
if (event.isMetaPressed && event.logicalKey == LogicalKeyboardKey.keyC) { if (event.isMetaPressed && event.logicalKey == LogicalKeyboardKey.keyC) {
_handleCopy(); _handleCopy(editorState);
return KeyEventResult.handled; return KeyEventResult.handled;
} }
if (event.isMetaPressed && event.logicalKey == LogicalKeyboardKey.keyV) { if (event.isMetaPressed && event.logicalKey == LogicalKeyboardKey.keyV) {

View File

@ -437,7 +437,7 @@ class _FlowySelectionState extends State<FlowySelection>
final selection = Selection( final selection = Selection(
start: isDownward ? start : end, end: isDownward ? end : start); start: isDownward ? start : end, end: isDownward ? end : start);
debugPrint('[_onPanUpdate] isDownward = $isDownward, $selection'); debugPrint('[_onPanUpdate] isDownward = $isDownward, $selection');
editorState.service.selectionService.updateSelection(selection); editorState.updateCursorSelection(selection);
} }
} }