From 8bbae46a7c31f2fe9666d230058d5ade16dc5cbf Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Fri, 9 Dec 2022 10:36:01 +0800 Subject: [PATCH] feat: add rules for editorstate.apply --- .../appflowy_editor/lib/src/editor_state.dart | 36 ++++++++++++++++--- .../lib/src/history/undo_manager.dart | 4 +-- .../code_block/code_block_shortcut_event.dart | 3 +- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart index 95bab3c231..361dfb742f 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:appflowy_editor/src/core/document/node.dart'; import 'package:appflowy_editor/src/infra/log.dart'; import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart'; import 'package:appflowy_editor/src/render/style/editor_style.dart'; @@ -119,8 +120,11 @@ class EditorState { /// /// The options can be used to determine whether the editor /// should record the transaction in undo/redo stack. - apply(Transaction transaction, - [ApplyOptions options = const ApplyOptions(recordUndo: true)]) { + void apply( + Transaction transaction, { + ApplyOptions options = const ApplyOptions(recordUndo: true), + ruleCount = 0, + }) { if (!editable) { return; } @@ -132,6 +136,7 @@ class EditorState { _observer.add(transaction); WidgetsBinding.instance.addPostFrameCallback((_) { + _applyRules(ruleCount); updateCursorSelection(transaction.afterSelection); }); @@ -153,7 +158,7 @@ class EditorState { } } - _debouncedSealHistoryItem() { + void _debouncedSealHistoryItem() { if (disableSealTimer) { return; } @@ -168,7 +173,7 @@ class EditorState { }); } - _applyOperation(Operation op) { + void _applyOperation(Operation op) { if (op is InsertOperation) { document.insert(op.path, op.nodes); } else if (op is UpdateOperation) { @@ -179,4 +184,27 @@ class EditorState { document.updateText(op.path, op.delta); } } + + void _applyRules(int ruleCount) { + // Set a maximum count to prevent a dead loop. + if (ruleCount >= 5) { + return; + } + + final tr = transaction; + + // Rules + _insureLastNodeEditable(tr); + + if (tr.operations.isNotEmpty) { + apply(tr, ruleCount: ruleCount + 1); + } + } + + void _insureLastNodeEditable(Transaction tr) { + if (document.root.children.isEmpty || + document.root.children.last.id != 'text') { + tr.insertNode([document.root.children.length], TextNode.empty()); + } + } } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/history/undo_manager.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/history/undo_manager.dart index 1ef8bf5c10..fa4ad0492d 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/history/undo_manager.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/history/undo_manager.dart @@ -123,7 +123,7 @@ class UndoManager { final transaction = historyItem.toTransaction(s); s.apply( transaction, - const ApplyOptions( + options: const ApplyOptions( recordUndo: false, recordRedo: true, ), @@ -143,7 +143,7 @@ class UndoManager { final transaction = historyItem.toTransaction(s); s.apply( transaction, - const ApplyOptions( + options: const ApplyOptions( recordUndo: true, recordRedo: false, ), diff --git a/frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/code_block/code_block_shortcut_event.dart b/frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/code_block/code_block_shortcut_event.dart index 62ae1e44c5..d23d22a78d 100644 --- a/frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/code_block/code_block_shortcut_event.dart +++ b/frontend/app_flowy/packages/appflowy_editor_plugins/lib/src/code_block/code_block_shortcut_event.dart @@ -97,7 +97,8 @@ SelectionMenuItem codeBlockMenuItem = SelectionMenuItem( return; } final transaction = editorState.transaction; - if (textNodes.first.toPlainText().isEmpty) { + final textNode = textNodes.first; + if (textNode.toPlainText().isEmpty && textNode.next is TextNode) { transaction.updateNode(textNodes.first, { BuiltInAttributeKey.subtype: kCodeBlockSubType, kCodeBlockAttrTheme: 'vs',