From 19bf8e3b7ab5b5a015cd009e2047c8c108d243be Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 10 Oct 2022 20:07:52 +0800 Subject: [PATCH] refactor: move transaction.dart to core/transform --- .../lib/plugin/code_block_node_widget.dart | 27 +- .../plugin/horizontal_rule_node_widget.dart | 18 +- .../lib/plugin/tex_block_node_widget.dart | 28 +- .../lib/plugin/underscore_to_italic.dart | 6 +- .../appflowy_editor/lib/appflowy_editor.dart | 3 +- .../lib/src/commands/edit_text.dart | 7 +- .../lib/src/commands/format_text.dart | 23 +- .../lib/src/core/transform/transaction.dart | 267 ++++++++++++++++++ .../appflowy_editor/lib/src/editor_state.dart | 20 +- .../lib/src/operation/transaction.dart | 39 --- .../src/operation/transaction_builder.dart | 229 --------------- .../src/render/image/image_node_builder.dart | 24 +- .../src/render/image/image_upload_widget.dart | 13 +- .../selection_menu/selection_menu_widget.dart | 39 ++- .../lib/src/render/toolbar/toolbar_item.dart | 7 +- .../format_rich_text_style.dart | 30 +- .../lib/src/service/input_service.dart | 28 +- .../backspace_handler.dart | 90 +++--- .../copy_paste_handler.dart | 67 +++-- ...er_without_shift_in_text_node_handler.dart | 36 +-- .../markdown_syntax_to_styled_text.dart | 25 +- ...arkdown_syntax_to_styled_text_handler.dart | 12 +- .../number_list_helper.dart | 5 +- .../slash_handler.dart | 9 +- .../tab_handler.dart | 11 +- .../whitespace_handler.dart | 27 +- .../appflowy_editor/lib/src/undo_manager.dart | 7 +- .../test/legacy/operation_test.dart | 54 ++-- 28 files changed, 566 insertions(+), 585 deletions(-) create mode 100644 frontend/app_flowy/packages/appflowy_editor/lib/src/core/transform/transaction.dart delete mode 100644 frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction.dart delete mode 100644 frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction_builder.dart diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart index daf2eb26e7..cdfae8043e 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart @@ -26,9 +26,9 @@ ShortcutEventHandler _enterInCodeBlockHandler = (editorState, event) { return KeyEventResult.ignored; } if (selection.isCollapsed) { - TransactionBuilder(editorState) - ..insertText(codeBlockNode.first, selection.end.offset, '\n') - ..commit(); + editorState.transaction + .insertText(codeBlockNode.first, selection.end.offset, '\n'); + editorState.commit(); return KeyEventResult.handled; } return KeyEventResult.ignored; @@ -61,16 +61,16 @@ SelectionMenuItem codeBlockMenuItem = SelectionMenuItem( return; } if (textNodes.first.toPlainText().isEmpty) { - TransactionBuilder(editorState) + editorState.transaction ..updateNode(textNodes.first, { 'subtype': 'code_block', 'theme': 'vs', 'language': null, }) - ..afterSelection = selection - ..commit(); + ..afterSelection = selection; + editorState.commit(); } else { - TransactionBuilder(editorState) + editorState.transaction ..insertNode( selection.end.path.next, TextNode( @@ -83,8 +83,8 @@ SelectionMenuItem codeBlockMenuItem = SelectionMenuItem( delta: Delta()..insert('\n'), ), ) - ..afterSelection = selection - ..commit(); + ..afterSelection = selection; + editorState.commit(); } }, ); @@ -181,11 +181,10 @@ class __CodeBlockNodeWidgeState extends State<_CodeBlockNodeWidge> child: DropdownButton( value: _detectLanguage, onChanged: (value) { - TransactionBuilder(widget.editorState) - ..updateNode(widget.textNode, { - 'language': value, - }) - ..commit(); + widget.editorState.transaction.updateNode(widget.textNode, { + 'language': value, + }); + widget.editorState.commit(); }, items: allLanguages.keys.map>((String value) { return DropdownMenuItem( diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/horizontal_rule_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/horizontal_rule_node_widget.dart index 22da5912e3..b9f9436160 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/horizontal_rule_node_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/horizontal_rule_node_widget.dart @@ -18,7 +18,7 @@ ShortcutEventHandler _insertHorzaontalRule = (editorState, event) { } final textNode = textNodes.first; if (textNode.toPlainText() == '--') { - TransactionBuilder(editorState) + editorState.transaction ..deleteText(textNode, 0, 2) ..insertNode( textNode.path, @@ -29,8 +29,8 @@ ShortcutEventHandler _insertHorzaontalRule = (editorState, event) { ), ) ..afterSelection = - Selection.single(path: textNode.path.next, startOffset: 0) - ..commit(); + Selection.single(path: textNode.path.next, startOffset: 0); + editorState.commit(); return KeyEventResult.handled; } return KeyEventResult.ignored; @@ -54,7 +54,7 @@ SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem( } final textNode = textNodes.first; if (textNode.toPlainText().isEmpty) { - TransactionBuilder(editorState) + editorState.transaction ..insertNode( textNode.path, Node( @@ -64,10 +64,10 @@ SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem( ), ) ..afterSelection = - Selection.single(path: textNode.path.next, startOffset: 0) - ..commit(); + Selection.single(path: textNode.path.next, startOffset: 0); + editorState.commit(); } else { - TransactionBuilder(editorState) + editorState.transaction ..insertNode( selection.end.path.next, TextNode( @@ -78,8 +78,8 @@ SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem( delta: Delta()..insert('---'), ), ) - ..afterSelection = selection - ..commit(); + ..afterSelection = selection; + editorState.commit(); } }, ); diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart index b1184064de..e4d0fac186 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart @@ -23,7 +23,7 @@ SelectionMenuItem teXBlockMenuItem = SelectionMenuItem( final Path texNodePath; if (textNodes.first.toPlainText().isEmpty) { texNodePath = selection.end.path; - TransactionBuilder(editorState) + editorState.transaction ..insertNode( selection.end.path, Node( @@ -33,11 +33,11 @@ SelectionMenuItem teXBlockMenuItem = SelectionMenuItem( ), ) ..deleteNode(textNodes.first) - ..afterSelection = selection - ..commit(); + ..afterSelection = selection; + editorState.commit(); } else { texNodePath = selection.end.path.next; - TransactionBuilder(editorState) + editorState.transaction ..insertNode( selection.end.path.next, Node( @@ -46,8 +46,8 @@ SelectionMenuItem teXBlockMenuItem = SelectionMenuItem( attributes: {'tex': ''}, ), ) - ..afterSelection = selection - ..commit(); + ..afterSelection = selection; + editorState.commit(); } WidgetsBinding.instance.addPostFrameCallback((timeStamp) { final texState = @@ -142,9 +142,8 @@ class __TeXBlockNodeWidgetState extends State<_TeXBlockNodeWidget> { size: 16, ), onPressed: () { - TransactionBuilder(widget.editorState) - ..deleteNode(widget.node) - ..commit(); + widget.editorState.transaction.deleteNode(widget.node); + widget.editorState.commit(); }, ), ); @@ -175,12 +174,11 @@ class __TeXBlockNodeWidgetState extends State<_TeXBlockNodeWidget> { onPressed: () { Navigator.of(context).pop(); if (controller.text != _tex) { - TransactionBuilder(widget.editorState) - ..updateNode( - widget.node, - {'tex': controller.text}, - ) - ..commit(); + widget.editorState.transaction.updateNode( + widget.node, + {'tex': controller.text}, + ); + widget.editorState.commit(); } }, child: const Text('OK'), diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart index 1ebe3fdd27..4b34adabf1 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart @@ -31,7 +31,7 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) { // Delete the previous 'underscore', // update the style of the text surrounded by the two underscores to 'italic', // and update the cursor position. - TransactionBuilder(editorState) + editorState.transaction ..deleteText(textNode, firstUnderscore, 1) ..formatText( textNode, @@ -46,8 +46,8 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) { path: textNode.path, offset: selection.end.offset - 1, ), - ) - ..commit(); + ); + editorState.commit(); return KeyEventResult.handled; }; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart b/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart index b1c5ba3ed1..04b2714879 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart @@ -13,8 +13,7 @@ export 'src/core/document/attributes.dart'; export 'src/core/legacy/built_in_attribute_keys.dart'; export 'src/editor_state.dart'; export 'src/core/transform/operation.dart'; -export 'src/operation/transaction.dart'; -export 'src/operation/transaction_builder.dart'; +export 'src/core/transform/transaction.dart'; export 'src/render/selection/selectable.dart'; export 'src/service/editor_service.dart'; export 'src/service/render_plugin_service.dart'; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/edit_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/edit_text.dart index ab7d8b1fb2..91b8a4159e 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/edit_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/edit_text.dart @@ -4,7 +4,7 @@ import 'package:appflowy_editor/src/commands/text_command_infra.dart'; import 'package:appflowy_editor/src/core/document/node.dart'; import 'package:appflowy_editor/src/core/document/path.dart'; import 'package:appflowy_editor/src/editor_state.dart'; -import 'package:appflowy_editor/src/operation/transaction_builder.dart'; +import 'package:appflowy_editor/src/core/transform/transaction.dart'; import 'package:flutter/widgets.dart'; Future insertContextInText( @@ -22,9 +22,8 @@ Future insertContextInText( final completer = Completer(); - TransactionBuilder(editorState) - ..insertText(result, index, content) - ..commit(); + editorState.transaction.insertText(result, index, content); + editorState.commit(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { completer.complete(); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_text.dart index 7d00754afc..b5f235914c 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/format_text.dart @@ -6,7 +6,7 @@ import 'package:appflowy_editor/src/core/document/node.dart'; import 'package:appflowy_editor/src/core/document/path.dart'; import 'package:appflowy_editor/src/core/location/selection.dart'; import 'package:appflowy_editor/src/editor_state.dart'; -import 'package:appflowy_editor/src/operation/transaction_builder.dart'; +import 'package:appflowy_editor/src/core/transform/transaction.dart'; import 'package:flutter/widgets.dart'; Future updateTextNodeAttributes( @@ -23,9 +23,8 @@ Future updateTextNodeAttributes( final completer = Completer(); - TransactionBuilder(editorState) - ..updateNode(result, attributes) - ..commit(); + editorState.transaction.updateNode(result, attributes); + editorState.commit(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { completer.complete(); @@ -49,15 +48,13 @@ Future updateTextNodeDeltaAttributes( final newSelection = getSelection(editorState, selection: selection); final completer = Completer(); - - TransactionBuilder(editorState) - ..formatText( - result, - newSelection.startIndex, - newSelection.length, - attributes, - ) - ..commit(); + editorState.transaction.formatText( + result, + newSelection.startIndex, + newSelection.length, + attributes, + ); + editorState.commit(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { completer.complete(); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/core/transform/transaction.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/core/transform/transaction.dart new file mode 100644 index 0000000000..fd602ecd45 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/core/transform/transaction.dart @@ -0,0 +1,267 @@ +import 'dart:math'; + +import 'package:appflowy_editor/src/core/document/attributes.dart'; +import 'package:appflowy_editor/src/core/document/document.dart'; +import 'package:appflowy_editor/src/core/document/node.dart'; +import 'package:appflowy_editor/src/core/document/path.dart'; +import 'package:appflowy_editor/src/core/document/text_delta.dart'; +import 'package:appflowy_editor/src/core/location/position.dart'; +import 'package:appflowy_editor/src/core/location/selection.dart'; +import 'package:appflowy_editor/src/core/transform/operation.dart'; + +/// A [Transaction] has a list of [Operation] objects that will be applied +/// to the editor. +/// +/// There will be several ways to consume the transaction: +/// 1. Apply to the state to update the UI. +/// 2. Send to the backend to store and do operation transforming. +class Transaction { + Transaction({ + required this.document, + }); + + final Document document; + + /// The operations to be applied. + final List operations = []; + + /// The selection to be applied. + Selection? afterSelection; + + /// The before selection is to be recovered if needed. + Selection? beforeSelection; + + /// Inserts the [Node] at the given [Path]. + void insertNode( + Path path, + Node node, { + bool deepCopy = true, + }) { + insertNodes(path, [node], deepCopy: deepCopy); + } + + /// Inserts a sequence of [Node]s at the given [Path]. + void insertNodes( + Path path, + Iterable nodes, { + bool deepCopy = true, + }) { + if (deepCopy) { + add(InsertOperation(path, nodes.map((e) => e.copyWith()))); + } else { + add(InsertOperation(path, nodes)); + } + } + + /// Updates the attributes of the [Node]. + /// + /// The [attributes] will be merged into the existing attributes. + void updateNode(Node node, Attributes attributes) { + final inverted = invertAttributes(node.attributes, attributes); + add(UpdateOperation( + node.path, + {...attributes}, + inverted, + )); + } + + /// Deletes the [Node] in the document. + void deleteNode(Node node) { + deleteNodesAtPath(node.path); + } + + /// Deletes the [Node]s in the document. + void deleteNodes(Iterable nodes) { + nodes.forEach(deleteNode); + } + + /// Deletes the [Node]s at the given [Path]. + /// + /// The [length] indicates the number of consecutive deletions, + /// including the node of the current path. + void deleteNodesAtPath(Path path, [int length = 1]) { + if (path.isEmpty) return; + final nodes = []; + final parent = path.parent; + for (var i = 0; i < length; i++) { + final node = document.nodeAtPath(parent + [path.last + i]); + if (node == null) { + break; + } + nodes.add(node); + } + add(DeleteOperation(path, nodes)); + } + + /// Update the [TextNode]s with the given [Delta]. + void updateText(TextNode textNode, Delta delta) { + final inverted = delta.invert(textNode.delta); + add(UpdateTextOperation(textNode.path, delta, inverted)); + } + + /// Returns the JSON representation of the transaction. + Map toJson() { + final json = {}; + if (operations.isNotEmpty) { + json['operations'] = operations.map((o) => o.toJson()); + } + if (afterSelection != null) { + json['after_selection'] = afterSelection!.toJson(); + } + if (beforeSelection != null) { + json['before_selection'] = beforeSelection!.toJson(); + } + return json; + } + + /// Adds an operation to the transaction. + /// This method will merge operations if they are both TextEdits. + /// + /// Also, this method will transform the path of the operations + /// to avoid conflicts. + add(Operation op, {bool transform = true}) { + final Operation? last = operations.isEmpty ? null : operations.last; + if (last != null) { + if (op is UpdateTextOperation && + last is UpdateTextOperation && + op.path.equals(last.path)) { + final newOp = UpdateTextOperation( + op.path, + last.delta.compose(op.delta), + op.inverted.compose(last.inverted), + ); + operations[operations.length - 1] = newOp; + return; + } + } + if (transform) { + for (var i = 0; i < operations.length; i++) { + op = transformOperation(operations[i], op); + } + } + if (op is UpdateTextOperation && op.delta.isEmpty) { + return; + } + operations.add(op); + } +} + +extension TextTransaction on Transaction { + void mergeText( + TextNode first, + TextNode second, { + int? firstOffset, + int secondOffset = 0, + }) { + final firstLength = first.delta.length; + final secondLength = second.delta.length; + firstOffset ??= firstLength; + updateText( + first, + Delta() + ..retain(firstOffset) + ..delete(firstLength - firstOffset) + ..addAll(second.delta.slice(secondOffset, secondLength)), + ); + afterSelection = Selection.collapsed(Position( + path: first.path, + offset: firstOffset, + )); + } + + /// Inserts the text content at a specified index. + /// + /// Optionally, you may specify formatting attributes that are applied to the inserted string. + /// By default, the formatting attributes before the insert position will be reused. + void insertText( + TextNode textNode, + int index, + String text, { + Attributes? attributes, + }) { + var newAttributes = attributes; + if (index != 0 && attributes == null) { + newAttributes = + textNode.delta.slice(max(index - 1, 0), index).first.attributes; + if (newAttributes != null) { + newAttributes = {...newAttributes}; // make a copy + } + } + updateText( + textNode, + Delta() + ..retain(index) + ..insert(text, attributes: newAttributes), + ); + afterSelection = Selection.collapsed( + Position(path: textNode.path, offset: index + text.length), + ); + } + + /// Assigns a formatting attributes to a range of text. + formatText( + TextNode textNode, + int index, + int length, + Attributes attributes, + ) { + afterSelection = beforeSelection; + updateText( + textNode, + Delta() + ..retain(index) + ..retain(length, attributes: attributes), + ); + } + + /// Deletes the text of specified length starting at index. + deleteText( + TextNode textNode, + int index, + int length, + ) { + updateText( + textNode, + Delta() + ..retain(index) + ..delete(length), + ); + afterSelection = Selection.collapsed( + Position(path: textNode.path, offset: index), + ); + } + + /// Replaces the text of specified length starting at index. + /// + /// Optionally, you may specify formatting attributes that are applied to the inserted string. + /// By default, the formatting attributes before the insert position will be reused. + replaceText( + TextNode textNode, + int index, + int length, + String text, { + Attributes? attributes, + }) { + var newAttributes = attributes; + if (index != 0 && attributes == null) { + newAttributes = + textNode.delta.slice(max(index - 1, 0), index).first.attributes; + if (newAttributes != null) { + newAttributes = {...newAttributes}; // make a copy + } + } + updateText( + textNode, + Delta() + ..retain(index) + ..delete(length) + ..insert(text, attributes: newAttributes), + ); + afterSelection = Selection.collapsed( + Position( + path: textNode.path, + offset: index + text.length, + ), + ); + } +} 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 aad862cf9a..6c0acb16b9 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 @@ -8,7 +8,7 @@ import 'package:flutter/material.dart'; import 'package:appflowy_editor/src/core/location/selection.dart'; import 'package:appflowy_editor/src/core/document/document.dart'; import 'package:appflowy_editor/src/core/transform/operation.dart'; -import 'package:appflowy_editor/src/operation/transaction.dart'; +import 'package:appflowy_editor/src/core/transform/transaction.dart'; import 'package:appflowy_editor/src/undo_manager.dart'; class ApplyOptions { @@ -74,6 +74,24 @@ class EditorState { bool editable = true; + Transaction get transaction { + if (_transaction != null) { + return _transaction!; + } + _transaction = Transaction(document: document); + _transaction!.beforeSelection = _cursorSelection; + return _transaction!; + } + + Transaction? _transaction; + + void commit() { + if (_transaction != null) { + apply(_transaction!, const ApplyOptions(recordUndo: true)); + _transaction = null; + } + } + Selection? get cursorSelection { return _cursorSelection; } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction.dart deleted file mode 100644 index 2b86160718..0000000000 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'dart:collection'; -import 'package:flutter/material.dart'; -import 'package:appflowy_editor/src/core/location/selection.dart'; -import '../core/transform/operation.dart'; - -/// A [Transaction] has a list of [Operation] objects that will be applied -/// to the editor. It is an immutable class and used to store and transmit. -/// -/// If you want to build a new [Transaction], use [TransactionBuilder] directly. -/// -/// There will be several ways to consume the transaction: -/// 1. Apply to the state to update the UI. -/// 2. Send to the backend to store and do operation transforming. -/// 3. Used by the UndoManager to implement redo/undo. -@immutable -class Transaction { - final UnmodifiableListView operations; - final Selection? beforeSelection; - final Selection? afterSelection; - - const Transaction({ - required this.operations, - this.beforeSelection, - this.afterSelection, - }); - - Map toJson() { - final Map result = { - "operations": operations.map((e) => e.toJson()), - }; - if (beforeSelection != null) { - result["beforeSelection"] = beforeSelection!.toJson(); - } - if (afterSelection != null) { - result["afterSelection"] = afterSelection!.toJson(); - } - return result; - } -} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction_builder.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction_builder.dart deleted file mode 100644 index 8ae43bd8d8..0000000000 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/operation/transaction_builder.dart +++ /dev/null @@ -1,229 +0,0 @@ -import 'dart:collection'; -import 'dart:math'; - -import 'package:appflowy_editor/src/core/document/attributes.dart'; -import 'package:appflowy_editor/src/core/document/node.dart'; -import 'package:appflowy_editor/src/core/document/path.dart'; -import 'package:appflowy_editor/src/core/location/position.dart'; -import 'package:appflowy_editor/src/core/location/selection.dart'; -import 'package:appflowy_editor/src/core/document/text_delta.dart'; -import 'package:appflowy_editor/src/editor_state.dart'; -import 'package:appflowy_editor/src/core/transform/operation.dart'; -import 'package:appflowy_editor/src/operation/transaction.dart'; - -/// A [TransactionBuilder] is used to build the transaction from the state. -/// It will save a snapshot of the cursor selection state automatically. -/// The cursor can be restored if the transaction is undo. -class TransactionBuilder { - final List operations = []; - EditorState state; - Selection? beforeSelection; - Selection? afterSelection; - - TransactionBuilder(this.state); - - /// Commits the operations to the state - Future commit() async { - final transaction = finish(); - state.apply(transaction); - } - - /// Inserts the nodes at the position of path. - insertNode(Path path, Node node) { - insertNodes(path, [node]); - } - - /// Inserts a sequence of nodes at the position of path. - insertNodes(Path path, List nodes) { - beforeSelection = state.cursorSelection; - add(InsertOperation(path, nodes.map((node) => node.copyWith()).toList())); - } - - /// Updates the attributes of nodes. - updateNode(Node node, Attributes attributes) { - beforeSelection = state.cursorSelection; - final inverted = invertAttributes(node.attributes, attributes); - add(UpdateOperation( - node.path, - {...attributes}, - inverted, - )); - } - - /// Deletes a node in the document. - deleteNode(Node node) { - deleteNodesAtPath(node.path); - } - - deleteNodes(List nodes) { - nodes.forEach(deleteNode); - } - - /// Deletes a sequence of nodes at the path of the document. - /// The length specifies the length of the following nodes to delete( - /// including the start one). - deleteNodesAtPath(Path path, [int length = 1]) { - if (path.isEmpty) { - return; - } - final nodes = []; - final prefix = path.sublist(0, path.length - 1); - final last = path.last; - for (var i = 0; i < length; i++) { - final node = state.document.nodeAtPath(prefix + [last + i])!; - nodes.add(node); - } - - add(DeleteOperation(path, nodes.map((node) => node.copyWith()).toList())); - } - - textEdit(TextNode node, Delta Function() f) { - beforeSelection = state.cursorSelection; - final path = node.path; - - final delta = f(); - - final inverted = delta.invert(node.delta); - - add(UpdateTextOperation(path, delta, inverted)); - } - - setAfterSelection(Selection sel) { - afterSelection = sel; - } - - mergeText(TextNode firstNode, TextNode secondNode, - {int? firstOffset, int secondOffset = 0}) { - final firstLength = firstNode.delta.length; - final secondLength = secondNode.delta.length; - textEdit( - firstNode, - () => Delta() - ..retain(firstOffset ?? firstLength) - ..delete(firstLength - (firstOffset ?? firstLength)) - ..addAll(secondNode.delta.slice(secondOffset, secondLength)), - ); - afterSelection = Selection.collapsed( - Position( - path: firstNode.path, - offset: firstOffset ?? firstLength, - ), - ); - } - - /// Inserts content at a specified index. - /// Optionally, you may specify formatting attributes that are applied to the inserted string. - /// By default, the formatting attributes before the insert position will be used. - insertText( - TextNode node, - int index, - String content, { - Attributes? attributes, - }) { - var newAttributes = attributes; - if (index != 0 && attributes == null) { - newAttributes = - node.delta.slice(max(index - 1, 0), index).first.attributes; - if (newAttributes != null) { - newAttributes = Attributes.from(newAttributes); - } - } - textEdit( - node, - () => Delta() - ..retain(index) - ..insert( - content, - attributes: newAttributes, - ), - ); - afterSelection = Selection.collapsed( - Position(path: node.path, offset: index + content.length), - ); - } - - /// Assigns formatting attributes to a range of text. - formatText(TextNode node, int index, int length, Attributes attributes) { - textEdit( - node, - () => Delta() - ..retain(index) - ..retain(length, attributes: attributes)); - afterSelection = beforeSelection; - } - - /// Deletes length characters starting from index. - deleteText(TextNode node, int index, int length) { - textEdit( - node, - () => Delta() - ..retain(index) - ..delete(length)); - afterSelection = - Selection.collapsed(Position(path: node.path, offset: index)); - } - - replaceText(TextNode node, int index, int length, String content, - [Attributes? attributes]) { - var newAttributes = attributes; - if (attributes == null) { - final ops = node.delta.slice(index, index + length); - if (ops.isNotEmpty) { - newAttributes = ops.first.attributes; - } - } - textEdit( - node, - () => Delta() - ..retain(index) - ..delete(length) - ..insert(content, attributes: newAttributes), - ); - afterSelection = Selection.collapsed( - Position( - path: node.path, - offset: index + content.length, - ), - ); - } - - /// Adds an operation to the transaction. - /// This method will merge operations if they are both TextEdits. - /// - /// Also, this method will transform the path of the operations - /// to avoid conflicts. - add(Operation op, {bool transform = true}) { - final Operation? last = operations.isEmpty ? null : operations.last; - if (last != null) { - if (op is UpdateTextOperation && - last is UpdateTextOperation && - op.path.equals(last.path)) { - final newOp = UpdateTextOperation( - op.path, - last.delta.compose(op.delta), - op.inverted.compose(last.inverted), - ); - operations[operations.length - 1] = newOp; - return; - } - } - if (transform) { - for (var i = 0; i < operations.length; i++) { - op = transformOperation(operations[i], op); - } - } - if (op is UpdateTextOperation && op.delta.isEmpty) { - return; - } - operations.add(op); - } - - /// Generates a immutable [Transaction] to apply or transmit. - Transaction finish() { - return Transaction( - operations: UnmodifiableListView(operations), - beforeSelection: beforeSelection, - afterSelection: afterSelection, - ); - } -} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_node_builder.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_node_builder.dart index 7c4bd3a9b9..d37a7e6c2a 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_node_builder.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_node_builder.dart @@ -1,5 +1,4 @@ import 'package:appflowy_editor/src/core/document/node.dart'; -import 'package:appflowy_editor/src/operation/transaction_builder.dart'; import 'package:appflowy_editor/src/service/render_plugin_service.dart'; import 'package:flutter/material.dart'; import 'package:rich_clipboard/rich_clipboard.dart'; @@ -25,23 +24,20 @@ class ImageNodeBuilder extends NodeWidgetBuilder { RichClipboard.setData(RichClipboardData(text: src)); }, onDelete: () { - TransactionBuilder(context.editorState) - ..deleteNode(context.node) - ..commit(); + context.editorState.transaction.deleteNode(context.node); + context.editorState.commit(); }, onAlign: (alignment) { - TransactionBuilder(context.editorState) - ..updateNode(context.node, { - 'align': _alignmentToText(alignment), - }) - ..commit(); + context.editorState.transaction.updateNode(context.node, { + 'align': _alignmentToText(alignment), + }); + context.editorState.commit(); }, onResize: (width) { - TransactionBuilder(context.editorState) - ..updateNode(context.node, { - 'width': width, - }) - ..commit(); + context.editorState.transaction.updateNode(context.node, { + 'width': width, + }); + context.editorState.commit(); }, ); } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_upload_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_upload_widget.dart index 388d6286aa..458798f62b 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_upload_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_upload_widget.dart @@ -3,7 +3,7 @@ import 'dart:collection'; import 'package:appflowy_editor/src/core/document/node.dart'; import 'package:appflowy_editor/src/editor_state.dart'; import 'package:appflowy_editor/src/infra/flowy_svg.dart'; -import 'package:appflowy_editor/src/operation/transaction_builder.dart'; +import 'package:appflowy_editor/src/core/transform/transaction.dart'; import 'package:appflowy_editor/src/render/selection_menu/selection_menu_service.dart'; import 'package:flutter/material.dart'; @@ -192,11 +192,10 @@ extension on EditorState { 'align': 'center', }, ); - TransactionBuilder(this) - ..insertNode( - selection.start.path, - imageNode, - ) - ..commit(); + transaction.insertNode( + selection.start.path, + imageNode, + ); + commit(); } } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart index 9201ec8d44..98a37acf08 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart @@ -45,13 +45,12 @@ class SelectionMenuItem { final node = nodes.first as TextNode; final end = selection.start.offset; final start = node.toPlainText().substring(0, end).lastIndexOf('/'); - TransactionBuilder(editorState) - ..deleteText( - node, - start, - selection.start.offset - start, - ) - ..commit(); + editorState.transaction.deleteText( + node, + start, + selection.start.offset - start, + ); + editorState.commit(); } } } @@ -278,13 +277,12 @@ class _SelectionMenuWidgetState extends State { final nodes = selectionService.currentSelectedNodes; if (selection != null && nodes.length == 1) { widget.onSelectionUpdate(); - TransactionBuilder(widget.editorState) - ..deleteText( - nodes.first as TextNode, - selection.start.offset - length, - length, - ) - ..commit(); + widget.editorState.transaction.deleteText( + nodes.first as TextNode, + selection.start.offset - length, + length, + ); + widget.editorState.commit(); } } @@ -295,13 +293,12 @@ class _SelectionMenuWidgetState extends State { widget.editorState.service.selectionService.currentSelectedNodes; if (selection != null && nodes.length == 1) { widget.onSelectionUpdate(); - TransactionBuilder(widget.editorState) - ..insertText( - nodes.first as TextNode, - selection.end.offset, - text, - ) - ..commit(); + widget.editorState.transaction.insertText( + nodes.first as TextNode, + selection.end.offset, + text, + ); + widget.editorState.commit(); } } } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart index b3ed2e2471..091102805b 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart @@ -357,10 +357,9 @@ void showLinkMenu( _dismissLinkMenu(); }, onRemoveLink: () { - TransactionBuilder(editorState) - ..formatText( - textNode, index, length, {BuiltInAttributeKey.href: null}) - ..commit(); + editorState.transaction.formatText( + textNode, index, length, {BuiltInAttributeKey.href: null}); + editorState.commit(); _dismissLinkMenu(); }, onFocusChange: (value) { diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart index 7ac67f0cfd..e6e813903c 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart @@ -1,12 +1,5 @@ -import 'package:appflowy_editor/src/core/document/attributes.dart'; -import 'package:appflowy_editor/src/core/document/node.dart'; -import 'package:appflowy_editor/src/core/document/path.dart'; -import 'package:appflowy_editor/src/core/location/position.dart'; -import 'package:appflowy_editor/src/core/location/selection.dart'; -import 'package:appflowy_editor/src/editor_state.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/extensions/text_node_extensions.dart'; -import 'package:appflowy_editor/src/operation/transaction_builder.dart'; -import 'package:appflowy_editor/src/core/legacy/built_in_attribute_keys.dart'; void insertHeadingAfterSelection(EditorState editorState, String heading) { insertTextNodeAfterSelection(editorState, { @@ -54,16 +47,15 @@ bool insertTextNodeAfterSelection( formatTextNodes(editorState, attributes); } else { final next = selection.end.path.next; - final builder = TransactionBuilder(editorState); - builder + editorState.transaction ..insertNode( next, TextNode.empty(attributes: attributes), ) ..afterSelection = Selection.collapsed( Position(path: next, offset: 0), - ) - ..commit(); + ); + editorState.commit(); } return true; @@ -107,7 +99,7 @@ bool formatTextNodes(EditorState editorState, Attributes attributes) { return false; } - final builder = TransactionBuilder(editorState); + final transaction = editorState.transaction; for (final textNode in textNodes) { var newAttributes = {...textNode.attributes}; @@ -117,7 +109,7 @@ bool formatTextNodes(EditorState editorState, Attributes attributes) { } } newAttributes.addAll(attributes); - builder + transaction ..updateNode( textNode, newAttributes, @@ -130,7 +122,7 @@ bool formatTextNodes(EditorState editorState, Attributes attributes) { ); } - builder.commit(); + editorState.commit(); return true; } @@ -216,13 +208,13 @@ bool formatRichTextStyle(EditorState editorState, Attributes attributes) { return false; } - final builder = TransactionBuilder(editorState); + final transaction = editorState.transaction; // 1. All nodes are text nodes. // 2. The first node is not TextNode. // 3. The last node is not TextNode. if (nodes.length == textNodes.length && textNodes.length == 1) { - builder.formatText( + transaction.formatText( textNodes.first, selection.start.offset, selection.end.offset - selection.start.offset, @@ -239,7 +231,7 @@ bool formatRichTextStyle(EditorState editorState, Attributes attributes) { } else if (i == textNodes.length - 1 && textNode == nodes.last) { length = selection.end.offset; } - builder.formatText( + transaction.formatText( textNode, index, length, @@ -248,7 +240,7 @@ bool formatRichTextStyle(EditorState editorState, Attributes attributes) { } } - builder.commit(); + editorState.commit(); return true; } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/input_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/input_service.dart index f0cc94c677..bb0cfb5bd9 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/input_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/input_service.dart @@ -1,4 +1,5 @@ import 'package:appflowy_editor/src/infra/log.dart'; +import 'package:appflowy_editor/src/core/transform/transaction.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -7,7 +8,6 @@ import 'package:appflowy_editor/src/core/document/node.dart'; import 'package:appflowy_editor/src/core/location/selection.dart'; import 'package:appflowy_editor/src/editor_state.dart'; import 'package:appflowy_editor/src/extensions/node_extensions.dart'; -import 'package:appflowy_editor/src/operation/transaction_builder.dart'; /// [AppFlowyInputService] is responsible for processing text input, /// including text insertion, deletion and replacement. @@ -160,13 +160,12 @@ class _AppFlowyInputState extends State } if (currentSelection.isSingle) { final textNode = selectionService.currentSelectedNodes.first as TextNode; - TransactionBuilder(_editorState) - ..insertText( - textNode, - delta.insertionOffset, - delta.textInserted, - ) - ..commit(); + _editorState.transaction.insertText( + textNode, + delta.insertionOffset, + delta.textInserted, + ); + _editorState.commit(); } else { // TODO: implement } @@ -181,9 +180,9 @@ class _AppFlowyInputState extends State if (currentSelection.isSingle) { final textNode = selectionService.currentSelectedNodes.first as TextNode; final length = delta.deletedRange.end - delta.deletedRange.start; - TransactionBuilder(_editorState) - ..deleteText(textNode, delta.deletedRange.start, length) - ..commit(); + _editorState.transaction + .deleteText(textNode, delta.deletedRange.start, length); + _editorState.commit(); } else { // TODO: implement } @@ -198,10 +197,9 @@ class _AppFlowyInputState extends State if (currentSelection.isSingle) { final textNode = selectionService.currentSelectedNodes.first as TextNode; final length = delta.replacedRange.end - delta.replacedRange.start; - TransactionBuilder(_editorState) - ..replaceText( - textNode, delta.replacedRange.start, length, delta.replacementText) - ..commit(); + _editorState.transaction.replaceText( + textNode, delta.replacedRange.start, length, delta.replacementText); + _editorState.commit(); } else { // TODO: implement } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart index b74e68ec2f..686a9bc970 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/backspace_handler.dart @@ -28,11 +28,11 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) { final List nonTextNodes = nodes.where((node) => node is! TextNode).toList(growable: false); - final transactionBuilder = TransactionBuilder(editorState); + final transaction = editorState.transaction; List? cancelNumberListPath; if (nonTextNodes.isNotEmpty) { - transactionBuilder.deleteNodes(nonTextNodes); + transaction.deleteNodes(nonTextNodes); } if (textNodes.length == 1) { @@ -44,7 +44,7 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) { if (textNode.subtype == BuiltInAttributeKey.numberList) { cancelNumberListPath = textNode.path; } - transactionBuilder + transaction ..updateNode(textNode, { BuiltInAttributeKey.subtype: null, textNode.subtype!: null, @@ -61,20 +61,20 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) { return _backDeleteToPreviousTextNode( editorState, textNode, - transactionBuilder, + transaction, nonTextNodes, selection, ); } } else { if (selection.isCollapsed) { - transactionBuilder.deleteText( + transaction.deleteText( textNode, index, selection.start.offset - index, ); } else { - transactionBuilder.deleteText( + transaction.deleteText( textNode, selection.start.offset, selection.end.offset - selection.start.offset, @@ -84,33 +84,32 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) { } else { if (textNodes.isEmpty) { if (nonTextNodes.isNotEmpty) { - transactionBuilder.afterSelection = - Selection.collapsed(selection.start); + transaction.afterSelection = Selection.collapsed(selection.start); } - transactionBuilder.commit(); + editorState.commit(); return KeyEventResult.handled; } final startPosition = selection.start; final nodeAtStart = editorState.document.nodeAtPath(startPosition.path)!; - _deleteTextNodes(transactionBuilder, textNodes, selection); - transactionBuilder.commit(); + _deleteTextNodes(transaction, textNodes, selection); + editorState.commit(); if (nodeAtStart is TextNode && nodeAtStart.subtype == BuiltInAttributeKey.numberList) { makeFollowingNodesIncremental( editorState, startPosition.path, - transactionBuilder.afterSelection!, + transaction.afterSelection!, ); } return KeyEventResult.handled; } - if (transactionBuilder.operations.isNotEmpty) { + if (transaction.operations.isNotEmpty) { if (nonTextNodes.isNotEmpty) { - transactionBuilder.afterSelection = Selection.collapsed(selection.start); + transaction.afterSelection = Selection.collapsed(selection.start); } - transactionBuilder.commit(); + editorState.commit(); } if (cancelNumberListPath != null) { @@ -128,20 +127,20 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) { KeyEventResult _backDeleteToPreviousTextNode( EditorState editorState, TextNode textNode, - TransactionBuilder transactionBuilder, + Transaction transaction, List nonTextNodes, Selection selection, ) { if (textNode.next == null && textNode.children.isEmpty && textNode.parent?.parent != null) { - transactionBuilder + transaction ..deleteNode(textNode) ..insertNode(textNode.parent!.path.next, textNode) ..afterSelection = Selection.collapsed( Position(path: textNode.parent!.path.next, offset: 0), - ) - ..commit(); + ); + editorState.commit(); return KeyEventResult.handled; } @@ -152,15 +151,15 @@ KeyEventResult _backDeleteToPreviousTextNode( prevIsNumberList = true; } - transactionBuilder.mergeText(previousTextNode, textNode); + transaction.mergeText(previousTextNode, textNode); if (textNode.children.isNotEmpty) { - transactionBuilder.insertNodes( + transaction.insertNodes( previousTextNode.path.next, textNode.children.toList(growable: false), ); } - transactionBuilder.deleteNode(textNode); - transactionBuilder.afterSelection = Selection.collapsed( + transaction.deleteNode(textNode); + transaction.afterSelection = Selection.collapsed( Position( path: previousTextNode.path, offset: previousTextNode.toPlainText().length, @@ -168,16 +167,16 @@ KeyEventResult _backDeleteToPreviousTextNode( ); } - if (transactionBuilder.operations.isNotEmpty) { + if (transaction.operations.isNotEmpty) { if (nonTextNodes.isNotEmpty) { - transactionBuilder.afterSelection = Selection.collapsed(selection.start); + transaction.afterSelection = Selection.collapsed(selection.start); } - transactionBuilder.commit(); + editorState.commit(); } if (prevIsNumberList) { - makeFollowingNodesIncremental(editorState, previousTextNode!.path, - transactionBuilder.afterSelection!); + makeFollowingNodesIncremental( + editorState, previousTextNode!.path, transaction.afterSelection!); } return KeyEventResult.handled; @@ -197,7 +196,7 @@ KeyEventResult _handleDelete(EditorState editorState, RawKeyEvent event) { return KeyEventResult.ignored; } - final transactionBuilder = TransactionBuilder(editorState); + final transaction = editorState.transaction; if (textNodes.length == 1) { final textNode = textNodes.first; // The cursor is at the end of the line, @@ -206,55 +205,52 @@ KeyEventResult _handleDelete(EditorState editorState, RawKeyEvent event) { return _mergeNextLineIntoThisLine( editorState, textNode, - transactionBuilder, + transaction, selection, ); } final index = textNode.delta.nextRunePosition(selection.start.offset); if (selection.isCollapsed) { - transactionBuilder.deleteText( + transaction.deleteText( textNode, selection.start.offset, index - selection.start.offset, ); } else { - transactionBuilder.deleteText( + transaction.deleteText( textNode, selection.start.offset, selection.end.offset - selection.start.offset, ); } - transactionBuilder.commit(); + editorState.commit(); } else { final startPosition = selection.start; final nodeAtStart = editorState.document.nodeAtPath(startPosition.path)!; - _deleteTextNodes(transactionBuilder, textNodes, selection); - transactionBuilder.commit(); + _deleteTextNodes(transaction, textNodes, selection); + editorState.commit(); if (nodeAtStart is TextNode && nodeAtStart.subtype == BuiltInAttributeKey.numberList) { makeFollowingNodesIncremental( - editorState, startPosition.path, transactionBuilder.afterSelection!); + editorState, startPosition.path, transaction.afterSelection!); } } return KeyEventResult.handled; } -KeyEventResult _mergeNextLineIntoThisLine( - EditorState editorState, - TextNode textNode, - TransactionBuilder transactionBuilder, - Selection selection) { +KeyEventResult _mergeNextLineIntoThisLine(EditorState editorState, + TextNode textNode, Transaction transaction, Selection selection) { final nextNode = textNode.next; if (nextNode == null) { return KeyEventResult.ignored; } if (nextNode is TextNode) { - transactionBuilder.mergeText(textNode, nextNode); + transaction.mergeText(textNode, nextNode); } - transactionBuilder.deleteNode(nextNode); - transactionBuilder.commit(); + transaction.deleteNode(nextNode); + editorState.commit(); if (textNode.subtype == BuiltInAttributeKey.numberList) { makeFollowingNodesIncremental(editorState, textNode.path, selection); @@ -263,15 +259,15 @@ KeyEventResult _mergeNextLineIntoThisLine( return KeyEventResult.handled; } -void _deleteTextNodes(TransactionBuilder transactionBuilder, - List textNodes, Selection selection) { +void _deleteTextNodes( + Transaction transaction, List textNodes, Selection selection) { final first = textNodes.first; final last = textNodes.last; var content = textNodes.last.toPlainText(); content = content.substring(selection.end.offset, content.length); // Merge the fist and the last text node content, // and delete the all nodes expect for the first. - transactionBuilder + transaction ..deleteNodes(textNodes.sublist(1)) ..mergeText( first, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart index 1dd500b5ab..3dd53a1fb3 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart @@ -85,16 +85,16 @@ void _pasteHTML(EditorState editorState, String html) { } else if (nodes.length == 1) { final firstNode = nodes[0]; final nodeAtPath = editorState.document.nodeAtPath(path)!; - final tb = TransactionBuilder(editorState); + final tb = editorState.transaction; final startOffset = selection.start.offset; if (nodeAtPath.type == "text" && firstNode.type == "text") { final textNodeAtPath = nodeAtPath as TextNode; final firstTextNode = firstNode as TextNode; - tb.textEdit(textNodeAtPath, - () => (Delta()..retain(startOffset)) + firstTextNode.delta); - tb.setAfterSelection(Selection.collapsed(Position( + tb.updateText( + textNodeAtPath, (Delta()..retain(startOffset)) + firstTextNode.delta); + tb.afterSelection = (Selection.collapsed(Position( path: path, offset: startOffset + firstTextNode.delta.length))); - tb.commit(); + editorState.commit(); return; } } @@ -104,7 +104,7 @@ void _pasteHTML(EditorState editorState, String html) { void _pasteMultipleLinesInText( EditorState editorState, List path, int offset, List nodes) { - final tb = TransactionBuilder(editorState); + final tb = editorState.transaction; final firstNode = nodes[0]; final nodeAtPath = editorState.document.nodeAtPath(path)!; @@ -120,10 +120,9 @@ void _pasteMultipleLinesInText( final firstTextNode = firstNode as TextNode; final remain = textNodeAtPath.delta.slice(offset); - tb.textEdit( + tb.updateText( textNodeAtPath, - () => - (Delta() + (Delta() ..retain(offset) ..delete(remain.length)) + firstTextNode.delta); @@ -146,9 +145,9 @@ void _pasteMultipleLinesInText( tailNodes.add(TextNode(delta: remain)); } - tb.setAfterSelection(afterSelection); + tb.afterSelection = afterSelection; tb.insertNodes(path, tailNodes); - tb.commit(); + editorState.commit(); if (startNumber != null) { makeFollowingNodesIncremental(editorState, originalPath, afterSelection, @@ -161,9 +160,9 @@ void _pasteMultipleLinesInText( _computeSelectionAfterPasteMultipleNodes(editorState, nodes); path[path.length - 1]++; - tb.setAfterSelection(afterSelection); + tb.afterSelection = afterSelection; tb.insertNodes(path, nodes); - tb.commit(); + editorState.commit(); } void _handlePaste(EditorState editorState) async { @@ -196,15 +195,15 @@ void _pasteSingleLine( EditorState editorState, Selection selection, String line) { final node = editorState.document.nodeAtPath(selection.end.path)! as TextNode; final beginOffset = selection.end.offset; - TransactionBuilder(editorState) - ..textEdit( + editorState.transaction + ..updateText( node, - () => Delta() + Delta() ..retain(beginOffset) ..addAll(_lineContentToDelta(line))) - ..setAfterSelection(Selection.collapsed( - Position(path: selection.end.path, offset: beginOffset + line.length))) - ..commit(); + ..afterSelection = (Selection.collapsed( + Position(path: selection.end.path, offset: beginOffset + line.length))); + editorState.commit(); } /// parse url from the line text @@ -264,7 +263,7 @@ void _handlePastePlainText(EditorState editorState, String plainText) { final insertedLineSuffix = node.delta.slice(beginOffset); path[path.length - 1]++; - final tb = TransactionBuilder(editorState); + final tb = editorState.transaction; final List nodes = remains.map((e) => TextNode(delta: _lineContentToDelta(e))).toList(); @@ -279,16 +278,16 @@ void _handlePastePlainText(EditorState editorState, String plainText) { } // insert first line - tb.textEdit( + tb.updateText( node, - () => Delta() + Delta() ..retain(beginOffset) ..insert(firstLine) ..delete(node.delta.length - beginOffset)); // insert remains tb.insertNodes(path, nodes); - tb.setAfterSelection(afterSelection); - tb.commit(); + tb.afterSelection = afterSelection; + editorState.commit(); } } @@ -309,15 +308,15 @@ void _deleteSelectedContent(EditorState editorState) { if (selection.start.path.equals(selection.end.path) && beginNode.type == "text") { final textItem = beginNode as TextNode; - final tb = TransactionBuilder(editorState); + final tb = editorState.transaction; final len = selection.end.offset - selection.start.offset; - tb.textEdit( + tb.updateText( textItem, - () => Delta() + Delta() ..retain(selection.start.offset) ..delete(len)); - tb.setAfterSelection(Selection.collapsed(selection.start)); - tb.commit(); + tb.afterSelection = Selection.collapsed(selection.start); + editorState.commit(); return; } final traverser = NodeIterator( @@ -325,13 +324,13 @@ void _deleteSelectedContent(EditorState editorState) { startNode: beginNode, endNode: endNode, ); - final tb = TransactionBuilder(editorState); + final tb = editorState.transaction; while (traverser.moveNext()) { final item = traverser.current; if (item.type == "text" && beginNode == item) { final textItem = item as TextNode; final deleteLen = textItem.delta.length - selection.start.offset; - tb.textEdit(textItem, () { + tb.updateText(textItem, () { final delta = Delta() ..retain(selection.start.offset) ..delete(deleteLen); @@ -342,13 +341,13 @@ void _deleteSelectedContent(EditorState editorState) { } return delta; - }); + }()); } else { tb.deleteNode(item); } } - tb.setAfterSelection(Selection.collapsed(selection.start)); - tb.commit(); + tb.afterSelection = Selection.collapsed(selection.start); + editorState.commit(); } ShortcutEventHandler copyEventHandler = (editorState, event) { diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart index d7a0e257be..cafe40c27c 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart @@ -39,7 +39,7 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler = final afterSelection = Selection.collapsed( Position(path: textNodes.first.path.next, offset: 0), ); - TransactionBuilder(editorState) + editorState.transaction ..deleteText( textNodes.first, selection.start.offset, @@ -51,8 +51,8 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler = 0, selection.end.offset, ) - ..afterSelection = afterSelection - ..commit(); + ..afterSelection = afterSelection; + editorState.commit(); if (startNode is TextNode && startNode.subtype == BuiltInAttributeKey.numberList) { @@ -77,12 +77,12 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler = final afterSelection = Selection.collapsed( Position(path: textNode.path, offset: 0), ); - TransactionBuilder(editorState) + editorState.transaction ..updateNode(textNode, { BuiltInAttributeKey.subtype: null, }) - ..afterSelection = afterSelection - ..commit(); + ..afterSelection = afterSelection; + editorState.commit(); final nextNode = textNode.next; if (nextNode is TextNode && @@ -105,13 +105,13 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler = BuiltInAttributeKey.numberList; newNode.attributes[BuiltInAttributeKey.number] = prevNumber; final insertPath = textNode.path; - TransactionBuilder(editorState) + editorState.transaction ..insertNode( insertPath, newNode, ) - ..afterSelection = afterSelection - ..commit(); + ..afterSelection = afterSelection; + editorState.commit(); makeFollowingNodesIncremental(editorState, insertPath, afterSelection, beginNum: prevNumber); @@ -120,7 +120,7 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler = BuiltInAttributeKey.heading, BuiltInAttributeKey.quote, ].contains(subtype); - TransactionBuilder(editorState) + editorState.transaction ..insertNode( textNode.path, textNode.copyWith( @@ -129,8 +129,8 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler = attributes: needCopyAttributes ? null : {}, ), ) - ..afterSelection = afterSelection - ..commit(); + ..afterSelection = afterSelection; + editorState.commit(); } } return KeyEventResult.handled; @@ -145,25 +145,25 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler = Position(path: nextPath, offset: 0), ); - final transactionBuilder = TransactionBuilder(editorState); - transactionBuilder.insertNode( + final transaction = editorState.transaction; + transaction.insertNode( textNode.path.next, textNode.copyWith( attributes: attributes, delta: textNode.delta.slice(selection.end.offset), ), ); - transactionBuilder.deleteText( + transaction.deleteText( textNode, selection.start.offset, textNode.toPlainText().length - selection.start.offset, ); if (textNode.children.isNotEmpty) { final children = textNode.children.toList(growable: false); - transactionBuilder.deleteNodes(children); + transaction.deleteNodes(children); } - transactionBuilder.afterSelection = afterSelection; - transactionBuilder.commit(); + transaction.afterSelection = afterSelection; + editorState.commit(); // If the new type of a text node is number list, // the numbers of the following nodes should be incremental. diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart index ccbcd224cd..e740907f01 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart @@ -1,6 +1,7 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/extensions/text_node_extensions.dart'; import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart'; + import 'package:flutter/material.dart'; bool _isCodeStyle(TextNode textNode, int index) { @@ -72,7 +73,7 @@ ShortcutEventHandler backquoteToCodeHandler = (editorState, event) { return KeyEventResult.ignored; } - TransactionBuilder(editorState) + editorState.transaction ..deleteText(textNode, lastBackquoteIndex, 1) ..deleteText(textNode, firstBackquoteIndex, 2) ..formatText( @@ -88,8 +89,8 @@ ShortcutEventHandler backquoteToCodeHandler = (editorState, event) { path: textNode.path, offset: endIndex - 3, ), - ) - ..commit(); + ); + editorState.commit(); return KeyEventResult.handled; } @@ -103,7 +104,7 @@ ShortcutEventHandler backquoteToCodeHandler = (editorState, event) { // delete the backquote. // update the style of the text surround by ` ` to code. // and update the cursor position. - TransactionBuilder(editorState) + editorState.transaction ..deleteText(textNode, startIndex, 1) ..formatText( textNode, @@ -118,8 +119,8 @@ ShortcutEventHandler backquoteToCodeHandler = (editorState, event) { path: textNode.path, offset: endIndex - 1, ), - ) - ..commit(); + ); + editorState.commit(); return KeyEventResult.handled; }; @@ -165,7 +166,7 @@ ShortcutEventHandler doubleTildeToStrikethrough = (editorState, event) { // delete the last three tildes. // update the style of the text surround by `~~ ~~` to strikethrough. // and update the cursor position. - TransactionBuilder(editorState) + editorState.transaction ..deleteText(textNode, lastTildeIndex, 1) ..deleteText(textNode, thirdToLastTildeIndex, 2) ..formatText( @@ -181,8 +182,8 @@ ShortcutEventHandler doubleTildeToStrikethrough = (editorState, event) { path: textNode.path, offset: selection.end.offset - 3, ), - ) - ..commit(); + ); + editorState.commit(); return KeyEventResult.handled; }; @@ -219,7 +220,7 @@ ShortcutEventHandler markdownLinkToLinkHandler = (editorState, event) { // update the href attribute of the text surrounded by [ ] to the url, // delete everything after the text, // and update the cursor position. - TransactionBuilder(editorState) + editorState.transaction ..deleteText(textNode, firstOpeningBracket, 1) ..formatText( textNode, @@ -236,8 +237,8 @@ ShortcutEventHandler markdownLinkToLinkHandler = (editorState, event) { path: textNode.path, offset: firstOpeningBracket + linkText!.length, ), - ) - ..commit(); + ); + editorState.commit(); return KeyEventResult.handled; }; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text_handler.dart index 25b2ac4031..8b1c322db3 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text_handler.dart @@ -42,7 +42,7 @@ ShortcutEventHandler doubleAsterisksToBold = (editorState, event) { // delete the last three asterisks. // update the style of the text surround by `** **` to bold. // and update the cursor position. - TransactionBuilder(editorState) + editorState.transaction ..deleteText(textNode, lastAsterisIndex, 1) ..deleteText(textNode, thirdToLastAsteriskIndex, 2) ..formatText( @@ -59,8 +59,8 @@ ShortcutEventHandler doubleAsterisksToBold = (editorState, event) { path: textNode.path, offset: selection.end.offset - 3, ), - ) - ..commit(); + ); + editorState.commit(); return KeyEventResult.handled; }; @@ -108,7 +108,7 @@ ShortcutEventHandler doubleUnderscoresToBold = (editorState, event) { // delete the last three underscores. // update the style of the text surround by `__ __` to bold. // and update the cursor position. - TransactionBuilder(editorState) + editorState.transaction ..deleteText(textNode, lastAsterisIndex, 1) ..deleteText(textNode, thirdToLastUnderscoreIndex, 2) ..formatText( @@ -125,8 +125,8 @@ ShortcutEventHandler doubleUnderscoresToBold = (editorState, event) { path: textNode.path, offset: selection.end.offset - 3, ), - ) - ..commit(); + ); + editorState.commit(); return KeyEventResult.handled; }; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/number_list_helper.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/number_list_helper.dart index b03505a62e..c41ac07ad8 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/number_list_helper.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/number_list_helper.dart @@ -1,7 +1,6 @@ import 'package:appflowy_editor/src/core/location/selection.dart'; import 'package:appflowy_editor/src/editor_state.dart'; import 'package:appflowy_editor/src/core/legacy/built_in_attribute_keys.dart'; -import 'package:appflowy_editor/src/operation/transaction_builder.dart'; import 'package:appflowy_editor/src/core/document/attributes.dart'; void makeFollowingNodesIncremental( @@ -16,7 +15,7 @@ void makeFollowingNodesIncremental( int numPtr = beginNum + 1; var ptr = insertNode.next; - final builder = TransactionBuilder(editorState); + final builder = editorState.transaction; while (ptr != null) { if (ptr.subtype != BuiltInAttributeKey.numberList) { @@ -34,5 +33,5 @@ void makeFollowingNodesIncremental( } builder.afterSelection = afterSelection; - builder.commit(); + editorState.commit(); } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart index 610a8cba2d..259f8bcdce 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/slash_handler.dart @@ -1,5 +1,5 @@ import 'package:appflowy_editor/src/core/document/node.dart'; -import 'package:appflowy_editor/src/operation/transaction_builder.dart'; +import 'package:appflowy_editor/src/core/transform/transaction.dart'; import 'package:appflowy_editor/src/render/selection_menu/selection_menu_service.dart'; import 'package:appflowy_editor/src/extensions/node_extensions.dart'; import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart'; @@ -25,10 +25,9 @@ ShortcutEventHandler slashShortcutHandler = (editorState, event) { if (selection == null || context == null || selectable == null) { return KeyEventResult.ignored; } - TransactionBuilder(editorState) - ..replaceText(textNode, selection.start.offset, - selection.end.offset - selection.start.offset, event.character ?? '') - ..commit(); + editorState.transaction.replaceText(textNode, selection.start.offset, + selection.end.offset - selection.start.offset, event.character ?? ''); + editorState.commit(); WidgetsBinding.instance.addPostFrameCallback((_) { _selectionMenuService = diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/tab_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/tab_handler.dart index 0291fc34a5..6b92a1bf19 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/tab_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/tab_handler.dart @@ -15,9 +15,8 @@ ShortcutEventHandler tabHandler = (editorState, event) { final previous = textNode.previous; if (textNode.subtype != BuiltInAttributeKey.bulletedList) { - TransactionBuilder(editorState) - ..insertText(textNode, selection.end.offset, ' ' * 4) - ..commit(); + editorState.transaction.insertText(textNode, selection.end.offset, ' ' * 4); + editorState.commit(); return KeyEventResult.handled; } @@ -31,11 +30,11 @@ ShortcutEventHandler tabHandler = (editorState, event) { start: selection.start.copyWith(path: path), end: selection.end.copyWith(path: path), ); - TransactionBuilder(editorState) + editorState.transaction ..deleteNode(textNode) ..insertNode(path, textNode) - ..setAfterSelection(afterSelection) - ..commit(); + ..afterSelection = afterSelection; + editorState.commit(); return KeyEventResult.handled; }; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart index 09cc802516..cc63578473 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart @@ -1,3 +1,4 @@ +import 'package:appflowy_editor/src/core/transform/transaction.dart'; import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event_handler.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -6,7 +7,6 @@ import 'package:appflowy_editor/src/core/document/node.dart'; import 'package:appflowy_editor/src/core/location/position.dart'; import 'package:appflowy_editor/src/core/location/selection.dart'; import 'package:appflowy_editor/src/editor_state.dart'; -import 'package:appflowy_editor/src/operation/transaction_builder.dart'; import './number_list_helper.dart'; import 'package:appflowy_editor/src/extensions/attributes_extension.dart'; @@ -99,15 +99,14 @@ KeyEventResult _toNumberList(EditorState editorState, TextNode textNode, )); final insertPath = textNode.path; - - TransactionBuilder(editorState) + editorState.transaction ..deleteText(textNode, 0, matchText.length) ..updateNode(textNode, { BuiltInAttributeKey.subtype: BuiltInAttributeKey.numberList, BuiltInAttributeKey.number: numValue }) - ..afterSelection = afterSelection - ..commit(); + ..afterSelection = afterSelection; + editorState.commit(); makeFollowingNodesIncremental(editorState, insertPath, afterSelection); @@ -118,7 +117,7 @@ KeyEventResult _toBulletedList(EditorState editorState, TextNode textNode) { if (textNode.subtype == BuiltInAttributeKey.bulletedList) { return KeyEventResult.ignored; } - TransactionBuilder(editorState) + editorState.transaction ..deleteText(textNode, 0, 1) ..updateNode(textNode, { BuiltInAttributeKey.subtype: BuiltInAttributeKey.bulletedList, @@ -128,8 +127,8 @@ KeyEventResult _toBulletedList(EditorState editorState, TextNode textNode) { path: textNode.path, offset: 0, ), - ) - ..commit(); + ); + editorState.commit(); return KeyEventResult.handled; } @@ -151,7 +150,7 @@ KeyEventResult _toCheckboxList(EditorState editorState, TextNode textNode) { check = false; } - TransactionBuilder(editorState) + editorState.transaction ..deleteText(textNode, 0, symbol.length) ..updateNode(textNode, { BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox, @@ -162,8 +161,8 @@ KeyEventResult _toCheckboxList(EditorState editorState, TextNode textNode) { path: textNode.path, offset: 0, ), - ) - ..commit(); + ); + editorState.commit(); return KeyEventResult.handled; } @@ -177,7 +176,7 @@ KeyEventResult _toHeadingStyle( if (textNode.attributes.heading == hX) { return KeyEventResult.ignored; } - TransactionBuilder(editorState) + editorState.transaction ..deleteText(textNode, 0, x) ..updateNode(textNode, { BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading, @@ -188,8 +187,8 @@ KeyEventResult _toHeadingStyle( path: textNode.path, offset: 0, ), - ) - ..commit(); + ); + editorState.commit(); return KeyEventResult.handled; } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/undo_manager.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/undo_manager.dart index 6a5a59e10f..1ef8bf5c10 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/undo_manager.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/undo_manager.dart @@ -3,8 +3,7 @@ import 'dart:collection'; import 'package:appflowy_editor/src/core/location/selection.dart'; import 'package:appflowy_editor/src/infra/log.dart'; import 'package:appflowy_editor/src/core/transform/operation.dart'; -import 'package:appflowy_editor/src/operation/transaction_builder.dart'; -import 'package:appflowy_editor/src/operation/transaction.dart'; +import 'package:appflowy_editor/src/core/transform/transaction.dart'; import 'package:appflowy_editor/src/editor_state.dart'; /// A [HistoryItem] contains list of operations committed by users. @@ -39,7 +38,7 @@ class HistoryItem extends LinkedListEntry { /// Create a new [Transaction] by inverting the operations. Transaction toTransaction(EditorState state) { - final builder = TransactionBuilder(state); + final builder = Transaction(document: state.document); for (var i = operations.length - 1; i >= 0; i--) { final operation = operations[i]; final inverted = operation.invert(); @@ -47,7 +46,7 @@ class HistoryItem extends LinkedListEntry { } builder.afterSelection = beforeSelection; builder.beforeSelection = afterSelection; - return builder.finish(); + return builder; } } diff --git a/frontend/app_flowy/packages/appflowy_editor/test/legacy/operation_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/legacy/operation_test.dart index e7d9774d7d..366ab2fa0f 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/legacy/operation_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/legacy/operation_test.dart @@ -3,7 +3,6 @@ import 'dart:collection'; import 'package:appflowy_editor/src/core/document/node.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:appflowy_editor/src/core/transform/operation.dart'; -import 'package:appflowy_editor/src/operation/transaction_builder.dart'; import 'package:appflowy_editor/src/editor_state.dart'; import 'package:appflowy_editor/src/core/document/document.dart'; @@ -48,25 +47,26 @@ void main() { final item2 = Node(type: "node", attributes: {}, children: LinkedList()); final item3 = Node(type: "node", attributes: {}, children: LinkedList()); final root = Node( - type: "root", - attributes: {}, - children: LinkedList() - ..addAll([ - item1, - item2, - item3, - ])); + type: "root", + attributes: {}, + children: LinkedList() + ..addAll([ + item1, + item2, + item3, + ]), + ); final state = EditorState(document: Document(root: root)); expect(item1.path, [0]); expect(item2.path, [1]); expect(item3.path, [2]); - final tb = TransactionBuilder(state); - tb.deleteNode(item1); - tb.deleteNode(item2); - tb.deleteNode(item3); - final transaction = tb.finish(); + final transaction = state.transaction; + transaction.deleteNode(item1); + transaction.deleteNode(item2); + transaction.deleteNode(item3); + state.commit(); expect(transaction.operations[0].path, [0]); expect(transaction.operations[1].path, [0]); expect(transaction.operations[2].path, [0]); @@ -77,10 +77,9 @@ void main() { final state = EditorState(document: Document(root: root)); final item1 = Node(type: "node", attributes: {}, children: LinkedList()); - final tb = TransactionBuilder(state); - tb.insertNode([0], item1); - - final transaction = tb.finish(); + final transaction = state.transaction; + transaction.insertNode([0], item1); + state.commit(); expect(transaction.toJson(), { "operations": [ { @@ -94,16 +93,17 @@ void main() { test("delete", () { final item1 = Node(type: "node", attributes: {}, children: LinkedList()); final root = Node( - type: "root", - attributes: {}, - children: LinkedList() - ..addAll([ - item1, - ])); + type: "root", + attributes: {}, + children: LinkedList() + ..addAll([ + item1, + ]), + ); final state = EditorState(document: Document(root: root)); - final tb = TransactionBuilder(state); - tb.deleteNode(item1); - final transaction = tb.finish(); + final transaction = state.transaction; + transaction.deleteNode(item1); + state.commit(); expect(transaction.toJson(), { "operations": [ {