Bläddra i källkod

Merge pull request #831 from AppFlowy-IO/doc/transaction-and-deltas

Doc: transaction and deltas
Vincent Chan 2 år sedan
förälder
incheckning
68a1acc9f2

+ 35 - 1
frontend/app_flowy/packages/flowy_editor/lib/src/document/text_delta.dart

@@ -256,7 +256,12 @@ TextOperation? _textOperationFromJson(Map<String, dynamic> json) {
   return result;
 }
 
-// basically copy from: https://github.com/quilljs/delta
+/// Deltas are a simple, yet expressive format that can be used to describe contents and changes.
+/// The format is JSON based, and is human readable, yet easily parsible by machines.
+/// Deltas can describe any rich text document, includes all text and formatting information, without the ambiguity and complexity of HTML.
+///
+
+/// Basically borrowed from: https://github.com/quilljs/delta
 class Delta extends Iterable<TextOperation> {
   final List<TextOperation> _operations;
   String? _rawString;
@@ -316,6 +321,9 @@ class Delta extends Iterable<TextOperation> {
     _operations.add(textOp);
   }
 
+  /// The slice() method does not change the original string.
+  /// The start and end parameters specifies the part of the string to extract.
+  /// The end position is optional.
   Delta slice(int start, [int? end]) {
     final result = Delta();
     final iterator = _OpIterator(_operations);
@@ -336,19 +344,29 @@ class Delta extends Iterable<TextOperation> {
     return result;
   }
 
+  /// Insert operations have an `insert` key defined.
+  /// A String value represents inserting text.
   void insert(String content, [Attributes? attributes]) =>
       add(TextInsert(content, attributes));
 
+  /// Retain operations have a Number `retain` key defined representing the number of characters to keep (other libraries might use the name keep or skip).
+  /// An optional `attributes` key can be defined with an Object to describe formatting changes to the character range.
+  /// A value of `null` in the `attributes` Object represents removal of that key.
+  ///
+  /// *Note: It is not necessary to retain the last characters of a document as this is implied.*
   void retain(int length, [Attributes? attributes]) =>
       add(TextRetain(length, attributes));
 
+  /// Delete operations have a Number `delete` key defined representing the number of characters to delete.
   void delete(int length) => add(TextDelete(length));
 
+  /// The length of the string fo the [Delta].
   int get length {
     return _operations.fold(
         0, (previousValue, element) => previousValue + element.length);
   }
 
+  /// Returns a Delta that is equivalent to applying the operations of own Delta, followed by another Delta.
   Delta compose(Delta other) {
     final thisIter = _OpIterator(_operations);
     final otherIter = _OpIterator(other._operations);
@@ -412,6 +430,7 @@ class Delta extends Iterable<TextOperation> {
     return delta..chop();
   }
 
+  /// This method joins two Delta together.
   Delta operator +(Delta other) {
     var ops = [..._operations];
     if (other._operations.isNotEmpty) {
@@ -445,6 +464,7 @@ class Delta extends Iterable<TextOperation> {
     return hashList(_operations);
   }
 
+  /// Returned an inverted delta that has the opposite effect of against a base document delta.
   Delta invert(Delta base) {
     final inverted = Delta();
     _operations.fold(0, (int previousValue, op) {
@@ -475,6 +495,13 @@ class Delta extends Iterable<TextOperation> {
     return _operations.map((e) => e.toJson()).toList();
   }
 
+  /// This method will return the position of the previous rune.
+  ///
+  /// Since the encoding of the [String] in Dart is UTF-16.
+  /// If you want to find the previous character of a position,
+  /// you can' just use the `position - 1` simply.
+  ///
+  /// This method can help you to compute the position of the previous character.
   int prevRunePosition(int pos) {
     if (pos == 0) {
       return pos - 1;
@@ -485,6 +512,13 @@ class Delta extends Iterable<TextOperation> {
     return _runeIndexes![pos - 1];
   }
 
+  /// This method will return the position of the next rune.
+  ///
+  /// Since the encoding of the [String] in Dart is UTF-16.
+  /// If you want to find the previous character of a position,
+  /// you can' just use the `position + 1` simply.
+  ///
+  /// This method can help you to compute the position of the next character.
   int nextRunePosition(int pos) {
     final stringContent = toRawString();
     if (pos >= stringContent.length - 1) {

+ 22 - 4
frontend/app_flowy/packages/flowy_editor/lib/src/editor_state.dart

@@ -2,7 +2,6 @@ import 'dart:async';
 import 'package:flowy_editor/src/service/service.dart';
 import 'package:flutter/material.dart';
 
-import 'package:flowy_editor/src/document/node.dart';
 import 'package:flowy_editor/src/document/selection.dart';
 import 'package:flowy_editor/src/document/state_tree.dart';
 import 'package:flowy_editor/src/operation/operation.dart';
@@ -26,11 +25,26 @@ enum CursorUpdateReason {
   others,
 }
 
+/// The state of the editor.
+///
+/// The state including:
+/// - The document to render
+/// - The state of the selection.
+///
+/// [EditorState] also includes the services of the editor:
+/// - Selection service
+/// - Scroll service
+/// - Keyboard service
+/// - Input service
+/// - Toolbar service
+///
+/// In consideration of collaborative editing.
+/// All the mutations should be applied through [Transaction].
+///
+/// Mutating the document with document's API is not recommended.
 class EditorState {
   final StateTree document;
 
-  List<Node> selectedNodes = [];
-
   // Service reference.
   final service = FlowyService();
 
@@ -41,7 +55,6 @@ class EditorState {
     return _cursorSelection;
   }
 
-  /// add the set reason in the future, don't use setter
   updateCursorSelection(Selection? cursorSelection,
       [CursorUpdateReason reason = CursorUpdateReason.others]) {
     // broadcast to other users here
@@ -59,8 +72,13 @@ class EditorState {
     undoManager.state = this;
   }
 
+  /// Apply the transaction to the state.
+  ///
+  /// 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()]) {
+    // TODO: validate the transation.
     for (final op in transaction.operations) {
       _applyOperation(op);
     }

+ 18 - 1
frontend/app_flowy/packages/flowy_editor/lib/src/operation/transaction_builder.dart

@@ -14,7 +14,6 @@ import 'package:flowy_editor/src/operation/transaction.dart';
 /// A [TransactionBuilder] is used to build the transaction from the state.
 /// It will save make a snapshot of the cursor selection state automatically.
 /// The cursor can be resorted if the transaction is undo.
-
 class TransactionBuilder {
   final List<Operation> operations = [];
   EditorState state;
@@ -29,15 +28,18 @@ class TransactionBuilder {
     state.apply(transaction);
   }
 
+  /// Insert the nodes at the position of path.
   insertNode(Path path, Node node) {
     insertNodes(path, [node]);
   }
 
+  /// Insert a sequence of nodes at the position of path.
   insertNodes(Path path, List<Node> nodes) {
     beforeSelection = state.cursorSelection;
     add(InsertOperation(path, nodes));
   }
 
+  /// Update the attributes of nodes.
   updateNode(Node node, Attributes attributes) {
     beforeSelection = state.cursorSelection;
 
@@ -49,6 +51,7 @@ class TransactionBuilder {
     ));
   }
 
+  /// Delete a node in the document.
   deleteNode(Node node) {
     deleteNodesAtPath(node.path);
   }
@@ -57,6 +60,9 @@ class TransactionBuilder {
     nodes.forEach(deleteNode);
   }
 
+  /// Delete a sequence of nodes at the path of the document.
+  /// The length specific the length of the following nodes to delete(
+  /// including the start one).
   deleteNodesAtPath(Path path, [int length = 1]) {
     if (path.isEmpty) {
       return;
@@ -106,6 +112,9 @@ class TransactionBuilder {
     );
   }
 
+  /// Insert 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;
@@ -126,6 +135,7 @@ class TransactionBuilder {
         Position(path: node.path, offset: index + content.length));
   }
 
+  /// Assign formatting attributes to a range of text.
   formatText(TextNode node, int index, int length, Attributes attributes) {
     textEdit(
         node,
@@ -135,6 +145,7 @@ class TransactionBuilder {
     afterSelection = beforeSelection;
   }
 
+  /// Delete length characters starting from index.
   deleteText(TextNode node, int index, int length) {
     textEdit(
         node,
@@ -169,6 +180,11 @@ class TransactionBuilder {
     );
   }
 
+  /// Add 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) {
     final Operation? last = operations.isEmpty ? null : operations.last;
     if (last != null) {
@@ -190,6 +206,7 @@ class TransactionBuilder {
     operations.add(op);
   }
 
+  /// Generate a immutable [Transaction] to apply or transmit.
   Transaction finish() {
     return Transaction(
       operations: UnmodifiableListView(operations),

+ 0 - 2
frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/default_key_event_handlers.dart

@@ -1,6 +1,5 @@
 import 'package:flowy_editor/src/service/internal_key_event_handlers/arrow_keys_handler.dart';
 import 'package:flowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
-import 'package:flowy_editor/src/service/internal_key_event_handlers/delete_nodes_handler.dart';
 import 'package:flowy_editor/src/service/internal_key_event_handlers/delete_text_handler.dart';
 import 'package:flowy_editor/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart';
 import 'package:flowy_editor/src/service/internal_key_event_handlers/redo_undo_handler.dart';
@@ -14,7 +13,6 @@ import 'package:flowy_editor/src/service/keyboard_service.dart';
 List<FlowyKeyEventHandler> defaultKeyEventHandlers = [
   deleteTextHandler,
   slashShortcutHandler,
-  flowyDeleteNodesHandler,
   arrowKeysHandler,
   copyPasteKeysHandler,
   redoUndoKeysHandler,

+ 0 - 21
frontend/app_flowy/packages/flowy_editor/lib/src/service/internal_key_event_handlers/delete_nodes_handler.dart

@@ -1,21 +0,0 @@
-import 'package:flowy_editor/flowy_editor.dart';
-import 'package:flowy_editor/src/service/keyboard_service.dart';
-import 'package:flutter/material.dart';
-
-FlowyKeyEventHandler flowyDeleteNodesHandler = (editorState, event) {
-  // Handle delete nodes.
-  final nodes = editorState.selectedNodes;
-  if (nodes.length <= 1) {
-    return KeyEventResult.ignored;
-  }
-
-  debugPrint('delete nodes = $nodes');
-
-  nodes
-      .fold<TransactionBuilder>(
-        TransactionBuilder(editorState),
-        (previousValue, node) => previousValue..deleteNode(node),
-      )
-      .commit();
-  return KeyEventResult.handled;
-};

+ 6 - 0
frontend/app_flowy/packages/flowy_editor/lib/src/undo_manager.dart

@@ -18,6 +18,11 @@ class HistoryItem extends LinkedListEntry<HistoryItem> {
 
   HistoryItem();
 
+  /// Seal the history item.
+  /// When an item is sealed, no more operations can be added
+  /// to the item.
+  ///
+  /// The caller should create a new [HistoryItem].
   seal() {
     _sealed = true;
   }
@@ -32,6 +37,7 @@ class HistoryItem extends LinkedListEntry<HistoryItem> {
     operations.addAll(iterable);
   }
 
+  /// Create a new [Transaction] by inverting the operations.
   Transaction toTransaction(EditorState state) {
     final builder = TransactionBuilder(state);
     for (var i = operations.length - 1; i >= 0; i--) {