From 6dcf3b3fa747116c890ca708c0837669641a6462 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 8 Aug 2022 20:34:26 +0800 Subject: [PATCH 1/4] feat: set mouse cursor style to text when hovering on the text node --- .../lib/render/rich_text/flowy_rich_text.dart | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart index 366942ee6c..6250c62db2 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/flowy_rich_text.dart @@ -35,7 +35,7 @@ class FlowyRichText extends StatefulWidget { this.cursorHeight, this.cursorWidth = 2.0, this.textSpanDecorator, - this.placeholderText = 'Type \'/\' for commands', + this.placeholderText = ' ', this.placeholderTextSpanDecorator, required this.textNode, required this.editorState, @@ -138,11 +138,16 @@ class _FlowyRichTextState extends State with Selectable { } Widget _buildRichText(BuildContext context) { - return Stack( - children: [ - _buildPlaceholderText(context), - _buildSingleRichText(context), - ], + return MouseRegion( + cursor: SystemMouseCursors.text, + child: widget.textNode.toRawString().isEmpty + ? Stack( + children: [ + _buildPlaceholderText(context), + _buildSingleRichText(context), + ], + ) + : _buildSingleRichText(context), ); } From 364a8e6ad2daba8784aa463a2d595ab15cb64019 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 8 Aug 2022 22:24:52 +0800 Subject: [PATCH 2/4] chore: replace 'checkbox' with StyleKey.checkbox --- .../flowy_editor/lib/render/rich_text/checkbox_text.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart index 5c02955d7b..317d1a6bdf 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/render/rich_text/checkbox_text.dart @@ -77,7 +77,7 @@ class _CheckboxNodeWidgetState extends State debugPrint('[Checkbox] onTap...'); TransactionBuilder(widget.editorState) ..updateNode(widget.textNode, { - 'checkbox': !check, + StyleKey.checkbox: !check, }) ..commit(); }, From af2f6a03d561b2ea87c1c2cf0cf299403ed5b16c Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 8 Aug 2022 23:18:07 +0800 Subject: [PATCH 3/4] feat: implement logic that pressing enter key without shift key in text nodes --- .../lib/service/editor_service.dart | 4 +- .../enter_in_edge_of_text_node_handler.dart | 94 ---------------- ...er_without_shift_in_text_node_handler.dart | 105 ++++++++++++++++++ 3 files changed, 107 insertions(+), 96 deletions(-) delete mode 100644 frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_in_edge_of_text_node_handler.dart create mode 100644 frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/editor_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/editor_service.dart index ba3d883c77..78f7bb76fa 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/editor_service.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/editor_service.dart @@ -13,7 +13,7 @@ import 'package:flowy_editor/service/internal_key_event_handlers/arrow_keys_hand import 'package:flowy_editor/service/internal_key_event_handlers/copy_paste_handler.dart'; import 'package:flowy_editor/service/internal_key_event_handlers/delete_nodes_handler.dart'; import 'package:flowy_editor/service/internal_key_event_handlers/delete_text_handler.dart'; -import 'package:flowy_editor/service/internal_key_event_handlers/enter_in_edge_of_text_node_handler.dart'; +import 'package:flowy_editor/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart'; import 'package:flowy_editor/service/internal_key_event_handlers/slash_handler.dart'; import 'package:flowy_editor/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart'; import 'package:flowy_editor/service/internal_key_event_handlers/whitespace_handler.dart'; @@ -39,7 +39,7 @@ List defaultKeyEventHandler = [ flowyDeleteNodesHandler, arrowKeysHandler, copyPasteKeysHandler, - enterInEdgeOfTextNodeHandler, + enterWithoutShiftInTextNodesHandler, updateTextStyleByCommandXHandler, whiteSpaceHandler, ]; diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_in_edge_of_text_node_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_in_edge_of_text_node_handler.dart deleted file mode 100644 index 9ea754e162..0000000000 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_in_edge_of_text_node_handler.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'dart:collection'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -import 'package:flowy_editor/document/node.dart'; -import 'package:flowy_editor/document/position.dart'; -import 'package:flowy_editor/document/selection.dart'; -import 'package:flowy_editor/document/text_delta.dart'; -import 'package:flowy_editor/extensions/node_extensions.dart'; -import 'package:flowy_editor/extensions/path_extensions.dart'; -import 'package:flowy_editor/operation/transaction_builder.dart'; -import 'package:flowy_editor/render/rich_text/rich_text_style.dart'; -import 'package:flowy_editor/service/keyboard_service.dart'; - -FlowyKeyEventHandler enterInEdgeOfTextNodeHandler = (editorState, event) { - if (event.logicalKey != LogicalKeyboardKey.enter) { - return KeyEventResult.ignored; - } - - final nodes = editorState.service.selectionService.currentSelectedNodes; - final selection = editorState.service.selectionService.currentSelection.value; - if (selection == null || - nodes.length != 1 || - nodes.first is! TextNode || - !selection.isCollapsed) { - return KeyEventResult.ignored; - } - - final textNode = nodes.first as TextNode; - if (textNode.selectable!.end() == selection.end) { - if (textNode.subtype != null && textNode.delta.length == 0) { - TransactionBuilder(editorState) - ..deleteNode(textNode) - ..insertNode( - textNode.path, - textNode.copyWith( - children: LinkedList(), - delta: Delta([TextInsert('')]), - attributes: {}, - ), - ) - ..afterSelection = Selection.collapsed( - Position( - path: textNode.path, - offset: 0, - ), - ) - ..commit(); - } else { - final needCopyAttributes = StyleKey.globalStyleKeys - .where((key) => key != StyleKey.heading) - .contains(textNode.subtype); - TransactionBuilder(editorState) - ..insertNode( - textNode.path.next, - textNode.copyWith( - children: LinkedList(), - delta: Delta([TextInsert('')]), - attributes: needCopyAttributes ? textNode.attributes : {}, - ), - ) - ..afterSelection = Selection.collapsed( - Position( - path: textNode.path.next, - offset: 0, - ), - ) - ..commit(); - } - - return KeyEventResult.handled; - } else if (textNode.selectable!.start() == selection.start) { - TransactionBuilder(editorState) - ..insertNode( - textNode.path, - textNode.copyWith( - children: LinkedList(), - delta: Delta([TextInsert('')]), - attributes: {}, - ), - ) - ..afterSelection = Selection.collapsed( - Position( - path: textNode.path.next, - offset: 0, - ), - ) - ..commit(); - return KeyEventResult.handled; - } - - return KeyEventResult.ignored; -}; diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart new file mode 100644 index 0000000000..8a53c1e9f4 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:flowy_editor/document/node.dart'; +import 'package:flowy_editor/document/position.dart'; +import 'package:flowy_editor/document/selection.dart'; +import 'package:flowy_editor/extensions/path_extensions.dart'; +import 'package:flowy_editor/operation/transaction_builder.dart'; +import 'package:flowy_editor/render/rich_text/rich_text_style.dart'; +import 'package:flowy_editor/service/keyboard_service.dart'; + +/// Handle some cases where enter is pressed and shift is not pressed. +/// +/// 1. Multiple selection and the selected nodes are [TextNode] +/// 1.1 delete the nodes expect for the first and the last, +/// and delete the text in the first and the last node by case. +/// 2. Single selection and the selected node is [TextNode] +/// 2.1 split the node into two nodes with style +/// 2.2 or insert a empty text node before. +FlowyKeyEventHandler enterWithoutShiftHandler = (editorState, event) { + if (event.logicalKey != LogicalKeyboardKey.enter || event.isShiftPressed) { + return KeyEventResult.ignored; + } + + final nodes = editorState.service.selectionService.currentSelectedNodes; + final textNodes = nodes.whereType().toList(growable: false); + final selection = editorState.service.selectionService.currentSelection.value; + + if (selection == null || nodes.length != textNodes.length) { + return KeyEventResult.ignored; + } + + // Multiple selection + if (!selection.isSingle) { + final length = textNodes.length; + final List subTextNodes = + length >= 3 ? textNodes.sublist(1, textNodes.length - 2) : []; + final afterSelection = Selection.collapsed( + Position(path: textNodes.first.path.next, offset: 0), + ); + TransactionBuilder(editorState) + ..deleteText( + textNodes.first, + selection.start.offset, + textNodes.first.toRawString().length, + ) + ..deleteNodes(subTextNodes) + ..deleteText( + textNodes.last, + 0, + selection.end.offset, + ) + ..afterSelection = afterSelection + ..commit(); + return KeyEventResult.handled; + } + + // Single selection and the selected node is [TextNode] + if (textNodes.length != 1) { + return KeyEventResult.ignored; + } + + final textNode = textNodes.first; + + // If selection is collapsed and position.start.offset == 0, + // insert a empty text node before. + if (selection.isCollapsed && selection.start.offset == 0) { + final afterSelection = Selection.collapsed( + Position(path: textNode.path.next, offset: 0), + ); + TransactionBuilder(editorState) + ..insertNode( + textNode.path, + TextNode.empty(), + ) + ..afterSelection = afterSelection + ..commit(); + return KeyEventResult.handled; + } + + // Otherwise, + // split the node into two nodes with style + final needCopyAttributes = StyleKey.globalStyleKeys + .where((key) => key != StyleKey.heading) + .contains(textNode.subtype); + final afterSelection = Selection.collapsed( + Position(path: textNode.path.next, offset: 0), + ); + TransactionBuilder(editorState) + ..deleteText( + textNode, + selection.start.offset, + textNode.toRawString().length, + ) + ..insertNode( + textNode.path.next, + textNode.copyWith( + attributes: needCopyAttributes ? textNode.attributes : {}, + delta: textNode.delta.slice(selection.end.offset), + ), + ) + ..afterSelection = afterSelection + ..commit(); + return KeyEventResult.handled; +}; From 585c9f87535ec5a97865df901827dcecd7b8e8ef Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 8 Aug 2022 23:28:40 +0800 Subject: [PATCH 4/4] fix: fix something wrong when deleting the text nodes --- .../packages/flowy_editor/lib/document/node.dart | 2 +- .../delete_text_handler.dart | 8 +++++++- .../enter_without_shift_in_text_node_handler.dart | 13 +++++++------ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/document/node.dart b/frontend/app_flowy/packages/flowy_editor/lib/document/node.dart index c3f75c5c9c..21883362b1 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/document/node.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/document/node.dart @@ -183,7 +183,7 @@ class TextNode extends Node { super(children: children ?? LinkedList(), attributes: attributes ?? {}); TextNode.empty() - : _delta = Delta([TextInsert(' ')]), + : _delta = Delta([TextInsert('')]), super( type: 'text', children: LinkedList(), diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/delete_text_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/delete_text_handler.dart index 861449c0ab..e44f0002a0 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/delete_text_handler.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/delete_text_handler.dart @@ -46,8 +46,14 @@ FlowyKeyEventHandler deleteTextHandler = (editorState, event) { if (textNode.previous is TextNode) { final previous = textNode.previous as TextNode; transactionBuilder + ..mergeText(previous, textNode) ..deleteNode(textNode) - ..mergeText(previous, textNode); + ..afterSelection = Selection.collapsed( + Position( + path: previous.path, + offset: previous.toRawString().length, + ), + ); break; } } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart index 8a53c1e9f4..cf71830386 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart @@ -17,7 +17,8 @@ import 'package:flowy_editor/service/keyboard_service.dart'; /// 2. Single selection and the selected node is [TextNode] /// 2.1 split the node into two nodes with style /// 2.2 or insert a empty text node before. -FlowyKeyEventHandler enterWithoutShiftHandler = (editorState, event) { +FlowyKeyEventHandler enterWithoutShiftInTextNodesHandler = + (editorState, event) { if (event.logicalKey != LogicalKeyboardKey.enter || event.isShiftPressed) { return KeyEventResult.ignored; } @@ -87,11 +88,6 @@ FlowyKeyEventHandler enterWithoutShiftHandler = (editorState, event) { Position(path: textNode.path.next, offset: 0), ); TransactionBuilder(editorState) - ..deleteText( - textNode, - selection.start.offset, - textNode.toRawString().length, - ) ..insertNode( textNode.path.next, textNode.copyWith( @@ -99,6 +95,11 @@ FlowyKeyEventHandler enterWithoutShiftHandler = (editorState, event) { delta: textNode.delta.slice(selection.end.offset), ), ) + ..deleteText( + textNode, + selection.start.offset, + textNode.toRawString().length - selection.start.offset, + ) ..afterSelection = afterSelection ..commit(); return KeyEventResult.handled;