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 ece4f6b9f4..6895643744 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 @@ -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 htmlAttributes) { + final attrs = {}; + 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? 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 = "

"; + + for (final op in delta.operations) { + if (op is TextInsert) { + final attributes = op.attributes; + if (attributes != null && attributes["bold"] == true) { + result += '${op.content}'; + } else { + result += op.content; + } + } + } + + result += "

"; + return result; +} 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 6e6e30dbf4..f413801771 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 @@ -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,12 +85,16 @@ _pasteMultipleLinesInText( .delete(remain.length) .concat(firstTextNode.delta)); - path[path.length - 1]++; final tailNodes = nodes.sublist(1); - if (tailNodes.last.type == "text") { - final tailTextNode = tailNodes.last as TextNode; - tailTextNode.delta = tailTextNode.delta.concat(remain); - } else if (remain.length > 0) { + 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)); } @@ -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) { diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart index 59632773e5..a304272846 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/selection_service.dart @@ -437,7 +437,7 @@ class _FlowySelectionState extends State final selection = Selection( start: isDownward ? start : end, end: isDownward ? end : start); debugPrint('[_onPanUpdate] isDownward = $isDownward, $selection'); - editorState.service.selectionService.updateSelection(selection); + editorState.updateCursorSelection(selection); } }