mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: copy from html
This commit is contained in:
parent
3065f6d236
commit
2a6412f81a
@ -1,5 +1,6 @@
|
||||
import 'dart:collection';
|
||||
|
||||
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';
|
||||
@ -20,7 +21,8 @@ class HTMLConverter {
|
||||
if (child is html.Element) {
|
||||
if (child.localName == "a" ||
|
||||
child.localName == "span" ||
|
||||
child.localName == "strong") {
|
||||
child.localName == "strong" ||
|
||||
child.localName == "b") {
|
||||
_handleRichTextElement(delta, child);
|
||||
} else {
|
||||
_handleElement(result, child);
|
||||
@ -63,9 +65,33 @@ class HTMLConverter {
|
||||
_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) {
|
||||
if (element.localName == "span") {
|
||||
delta.insert(element.text);
|
||||
delta.insert(element.text,
|
||||
_getDeltaAttributesFromHtmlAttributes(element.attributes));
|
||||
} else if (element.localName == "a") {
|
||||
final hyperLink = element.attributes["href"];
|
||||
Map<String, dynamic>? attributes;
|
||||
@ -73,8 +99,10 @@ class HTMLConverter {
|
||||
attributes = {"href": hyperLink};
|
||||
}
|
||||
delta.insert(element.text, attributes);
|
||||
} else if (element.localName == "strong") {
|
||||
} else if (element.localName == "strong" || element.localName == "b") {
|
||||
delta.insert(element.text, {"bold": true});
|
||||
} else {
|
||||
delta.insert(element.text);
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,13 +117,7 @@ class HTMLConverter {
|
||||
|
||||
for (final child in element.nodes.toList()) {
|
||||
if (child is html.Element) {
|
||||
if (child.localName == "a" ||
|
||||
child.localName == "span" ||
|
||||
child.localName == "strong") {
|
||||
_handleRichTextElement(delta, element);
|
||||
} else {
|
||||
delta.insert(child.text);
|
||||
}
|
||||
_handleRichTextElement(delta, child);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
@ -5,7 +5,26 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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');
|
||||
}
|
||||
|
||||
@ -20,6 +39,7 @@ _pasteHTML(EditorState editorState, String html) {
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint('paste html: $html');
|
||||
final converter = HTMLConverter(html);
|
||||
final nodes = converter.toNodes();
|
||||
|
||||
@ -38,6 +58,7 @@ _pasteHTML(EditorState editorState, String html) {
|
||||
tb.setAfterSelection(Selection.collapsed(Position(
|
||||
path: path, offset: startOffset + firstTextNode.delta.length)));
|
||||
tb.commit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,14 +85,18 @@ _pasteMultipleLinesInText(
|
||||
.delete(remain.length)
|
||||
.concat(firstTextNode.delta));
|
||||
|
||||
path[path.length - 1]++;
|
||||
final tailNodes = nodes.sublist(1);
|
||||
path[path.length - 1]++;
|
||||
if (tailNodes.isNotEmpty) {
|
||||
if (tailNodes.last.type == "text") {
|
||||
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));
|
||||
}
|
||||
|
||||
tb.insertNodes(path, tailNodes);
|
||||
tb.commit();
|
||||
@ -165,7 +190,7 @@ _handleCut() {
|
||||
|
||||
FlowyKeyEventHandler copyPasteKeysHandler = (editorState, event) {
|
||||
if (event.isMetaPressed && event.logicalKey == LogicalKeyboardKey.keyC) {
|
||||
_handleCopy();
|
||||
_handleCopy(editorState);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
if (event.isMetaPressed && event.logicalKey == LogicalKeyboardKey.keyV) {
|
||||
|
@ -437,7 +437,7 @@ class _FlowySelectionState extends State<FlowySelection>
|
||||
final selection = Selection(
|
||||
start: isDownward ? start : end, end: isDownward ? end : start);
|
||||
debugPrint('[_onPanUpdate] isDownward = $isDownward, $selection');
|
||||
editorState.service.selectionService.updateSelection(selection);
|
||||
editorState.updateCursorSelection(selection);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user