From 849e19e1ef8b14c4af911c04e7c9640597d8483e Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 8 Aug 2022 13:37:56 +0800 Subject: [PATCH 1/3] fix: popuplist will not show when the selection is not collapsed --- .../slash_handler.dart | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/slash_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/slash_handler.dart index 68bcdca7a6..f824b87234 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/slash_handler.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/slash_handler.dart @@ -72,16 +72,22 @@ FlowyKeyEventHandler slashShortcutHandler = (editorState, event) { final rect = selectable.getCursorRectInPosition(selection.start); final offset = selectable.localToGlobal(rect.topLeft); - if (!selection.isCollapsed) { - TransactionBuilder(editorState) - ..deleteText( - textNode, - selection.start.offset, - selection.end.offset - selection.start.offset, - ) - ..commit(); - } + TransactionBuilder(editorState) + ..replaceText(textNode, selection.start.offset, + selection.end.offset - selection.start.offset, '/') + ..commit(); + + _editorState = editorState; + WidgetsBinding.instance.addPostFrameCallback((_) { + showPopupList(context, editorState, offset); + }); + + return KeyEventResult.handled; +}; + +void showPopupList( + BuildContext context, EditorState editorState, Offset offset) { _popupListOverlay?.remove(); _popupListOverlay = OverlayEntry( builder: (context) => Positioned( @@ -97,16 +103,12 @@ FlowyKeyEventHandler slashShortcutHandler = (editorState, event) { Overlay.of(context)?.insert(_popupListOverlay!); editorState.service.selectionService.currentSelection - .removeListener(clearPopupListOverlay); + .removeListener(clearPopupList); editorState.service.selectionService.currentSelection - .addListener(clearPopupListOverlay); - // editorState.service.keyboardService?.disable(); - _editorState = editorState; + .addListener(clearPopupList); +} - return KeyEventResult.handled; -}; - -void clearPopupListOverlay() { +void clearPopupList() { _popupListOverlay?.remove(); _popupListOverlay = null; @@ -215,7 +217,7 @@ class _PopupListWidgetState extends State { } if (event.logicalKey == LogicalKeyboardKey.escape) { - clearPopupListOverlay(); + clearPopupList(); return KeyEventResult.handled; } From 7855e5403ceeee9a434c4ff11079fbc38dceb092 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 8 Aug 2022 13:59:38 +0800 Subject: [PATCH 2/3] feat: convert text to bulleted list style when inputing '* ' or '- ' --- .../lib/service/editor_service.dart | 2 + .../delete_text_handler.dart | 18 +++++-- .../whitespace_handler.dart | 51 +++++++++++++++++++ 3 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/whitespace_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 5c088b46d6..ba3d883c77 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 @@ -16,6 +16,7 @@ import 'package:flowy_editor/service/internal_key_event_handlers/delete_text_han 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/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'; import 'package:flowy_editor/service/keyboard_service.dart'; import 'package:flowy_editor/service/render_plugin_service.dart'; import 'package:flowy_editor/service/scroll_service.dart'; @@ -40,6 +41,7 @@ List defaultKeyEventHandler = [ copyPasteKeysHandler, enterInEdgeOfTextNodeHandler, updateTextStyleByCommandXHandler, + whiteSpaceHandler, ]; class FlowyEditor extends StatefulWidget { 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 5f59f4266d..861449c0ab 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 @@ -1,8 +1,9 @@ -import 'package:flowy_editor/flowy_editor.dart'; -import 'package:flowy_editor/service/keyboard_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flowy_editor/flowy_editor.dart'; +import 'package:flowy_editor/service/keyboard_service.dart'; + // Handle delete text. FlowyKeyEventHandler deleteTextHandler = (editorState, event) { if (event.logicalKey != LogicalKeyboardKey.backspace) { @@ -28,9 +29,16 @@ FlowyKeyEventHandler deleteTextHandler = (editorState, event) { if (index < 0) { // 1. style if (textNode.subtype != null) { - transactionBuilder.updateNode(textNode, { - 'subtype': null, - }); + transactionBuilder + ..updateNode(textNode, { + 'subtype': null, + }) + ..afterSelection = Selection.collapsed( + Position( + path: textNode.path, + offset: 0, + ), + ); } else { // 2. non-style // find previous text node. diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/whitespace_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/whitespace_handler.dart new file mode 100644 index 0000000000..59641bcf8a --- /dev/null +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/whitespace_handler.dart @@ -0,0 +1,51 @@ +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/operation/transaction_builder.dart'; +import 'package:flowy_editor/render/rich_text/rich_text_style.dart'; +import 'package:flowy_editor/service/keyboard_service.dart'; + +FlowyKeyEventHandler whiteSpaceHandler = (editorState, event) { + if (event.logicalKey != LogicalKeyboardKey.space) { + return KeyEventResult.ignored; + } + + /// Process markdown input style. + /// + /// like, #, *, -, 1., -[], + + final selection = editorState.service.selectionService.currentSelection.value; + if (selection == null || !selection.isCollapsed) { + return KeyEventResult.ignored; + } + + final textNodes = editorState.service.selectionService.currentSelectedNodes + .whereType(); + if (textNodes.length != 1) { + return KeyEventResult.ignored; + } + + final builder = TransactionBuilder(editorState); + final textNode = textNodes.first; + final text = textNode.toRawString(); + if (text == '*' || text == '-') { + builder + ..deleteText(textNode, 0, 1) + ..updateNode(textNode, { + StyleKey.subtype: StyleKey.bulletedList, + }) + ..afterSelection = Selection.collapsed( + Position( + path: textNode.path, + offset: 0, + ), + ) + ..commit(); + return KeyEventResult.handled; + } + + return KeyEventResult.ignored; +}; From 19fc1546810b0d22593914229284a6915b27e46a Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 8 Aug 2022 15:33:11 +0800 Subject: [PATCH 3/3] feat: implement markdown input, like, #, *, -, -[] --- .../whitespace_handler.dart | 110 +++++++++++++++--- 1 file changed, 95 insertions(+), 15 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/whitespace_handler.dart b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/whitespace_handler.dart index 59641bcf8a..b3642cc1a1 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/whitespace_handler.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/service/internal_key_event_handlers/whitespace_handler.dart @@ -4,10 +4,15 @@ 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/editor_state.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'; +const _bulletedListSymbols = ['*', '-']; +const _checkboxListSymbols = ['[x]', '-[x]']; +const _unCheckboxListSymbols = ['[]', '-[]']; + FlowyKeyEventHandler whiteSpaceHandler = (editorState, event) { if (event.logicalKey != LogicalKeyboardKey.space) { return KeyEventResult.ignored; @@ -28,24 +33,99 @@ FlowyKeyEventHandler whiteSpaceHandler = (editorState, event) { return KeyEventResult.ignored; } - final builder = TransactionBuilder(editorState); final textNode = textNodes.first; final text = textNode.toRawString(); - if (text == '*' || text == '-') { - builder - ..deleteText(textNode, 0, 1) - ..updateNode(textNode, { - StyleKey.subtype: StyleKey.bulletedList, - }) - ..afterSelection = Selection.collapsed( - Position( - path: textNode.path, - offset: 0, - ), - ) - ..commit(); - return KeyEventResult.handled; + if ((_checkboxListSymbols + _unCheckboxListSymbols).any(text.startsWith)) { + return _toCheckboxList(editorState, textNode); + } else if (_bulletedListSymbols.any(text.startsWith)) { + return _toBulletedList(editorState, textNode); + } else if (_countOfSign(text) != 0) { + return _toHeadingStyle(editorState, textNode); } return KeyEventResult.ignored; }; + +KeyEventResult _toBulletedList(EditorState editorState, TextNode textNode) { + if (textNode.subtype == StyleKey.bulletedList) { + return KeyEventResult.ignored; + } + TransactionBuilder(editorState) + ..deleteText(textNode, 0, 1) + ..updateNode(textNode, { + StyleKey.subtype: StyleKey.bulletedList, + }) + ..afterSelection = Selection.collapsed( + Position( + path: textNode.path, + offset: 0, + ), + ) + ..commit(); + return KeyEventResult.handled; +} + +KeyEventResult _toCheckboxList(EditorState editorState, TextNode textNode) { + if (textNode.subtype == StyleKey.checkbox) { + return KeyEventResult.ignored; + } + final String symbol; + bool check = false; + final symbols = List.from(_checkboxListSymbols) + ..retainWhere(textNode.toRawString().startsWith); + if (symbols.isNotEmpty) { + symbol = symbols.first; + check = true; + } else { + symbol = (List.from(_unCheckboxListSymbols) + ..retainWhere(textNode.toRawString().startsWith)) + .first; + check = false; + } + + TransactionBuilder(editorState) + ..deleteText(textNode, 0, symbol.length) + ..updateNode(textNode, { + StyleKey.subtype: StyleKey.checkbox, + StyleKey.checkbox: check, + }) + ..afterSelection = Selection.collapsed( + Position( + path: textNode.path, + offset: 0, + ), + ) + ..commit(); + return KeyEventResult.handled; +} + +KeyEventResult _toHeadingStyle(EditorState editorState, TextNode textNode) { + final x = _countOfSign(textNode.toRawString()); + final hX = 'h$x'; + if (textNode.attributes.heading == hX) { + return KeyEventResult.ignored; + } + TransactionBuilder(editorState) + ..deleteText(textNode, 0, x) + ..updateNode(textNode, { + StyleKey.subtype: StyleKey.heading, + StyleKey.heading: hX, + }) + ..afterSelection = Selection.collapsed( + Position( + path: textNode.path, + offset: 0, + ), + ) + ..commit(); + return KeyEventResult.handled; +} + +int _countOfSign(String text) { + for (var i = 6; i >= 0; i--) { + if (text.startsWith('#' * i)) { + return i; + } + } + return 0; +}