浏览代码

feat: insert text at cursor

Vincent Chan 3 年之前
父节点
当前提交
abe0658cd3

+ 37 - 8
frontend/app_flowy/packages/flowy_editor/example/lib/plugin/text_node_widget.dart

@@ -95,11 +95,20 @@ class _TextNodeWidget extends StatefulWidget {
   State<_TextNodeWidget> createState() => __TextNodeWidgetState();
 }
 
+String _textContentOfDelta(Delta delta) {
+  return delta.operations.fold("", (previousValue, element) {
+    if (element is TextInsert) {
+      return previousValue + element.content;
+    }
+    return previousValue;
+  });
+}
+
 class __TextNodeWidgetState extends State<_TextNodeWidget>
-    implements TextInputClient {
+    implements DeltaTextInputClient {
   TextNode get node => widget.node as TextNode;
   EditorState get editorState => widget.editorState;
-  TextEditingValue get textEditingValue => const TextEditingValue();
+  TextSelection? _localSelection;
 
   TextInputConnection? _textInputConnection;
 
@@ -112,20 +121,22 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
           TextSpan(
             children: node.toTextSpans(),
           ),
-          onTap: () {
+          onSelectionChanged: ((selection, cause) {
             _textInputConnection?.close();
             _textInputConnection = TextInput.attach(
               this,
               const TextInputConfiguration(
-                enableDeltaModel: false,
+                enableDeltaModel: true,
                 inputType: TextInputType.multiline,
                 textCapitalization: TextCapitalization.sentences,
               ),
             );
+            debugPrint('selection: $selection');
             _textInputConnection
               ?..show()
-              ..setEditingState(textEditingValue);
-          },
+              ..setEditingState(TextEditingValue(
+                  text: _textContentOfDelta(node.delta), selection: selection));
+          }),
         ),
         if (node.children.isNotEmpty)
           ...node.children.map(
@@ -152,7 +163,9 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
 
   @override
   // TODO: implement currentTextEditingValue
-  TextEditingValue? get currentTextEditingValue => textEditingValue;
+  TextEditingValue? get currentTextEditingValue => TextEditingValue(
+      text: _textContentOfDelta(node.delta),
+      selection: _localSelection ?? const TextSelection.collapsed(offset: -1));
 
   @override
   void insertTextPlaceholder(Size size) {
@@ -186,7 +199,23 @@ class __TextNodeWidgetState extends State<_TextNodeWidget>
 
   @override
   void updateEditingValue(TextEditingValue value) {
-    debugPrint(value.text);
+    debugPrint('offset: ${value.selection}');
+  }
+
+  @override
+  void updateEditingValueWithDeltas(List<TextEditingDelta> textEditingDeltas) {
+    for (final textDelta in textEditingDeltas) {
+      if (textDelta is TextEditingDeltaInsertion) {
+        TransactionBuilder(editorState)
+          ..insertText(node, textDelta.insertionOffset, textDelta.textInserted)
+          ..commit();
+      } else if (textDelta is TextEditingDeltaDeletion) {
+        TransactionBuilder(editorState)
+          ..deleteText(node, textDelta.deletedRange.start,
+              textDelta.deletedRange.end - textDelta.deletedRange.start)
+          ..commit();
+      }
+    }
   }
 
   @override

+ 40 - 9
frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart

@@ -26,39 +26,70 @@ class TransactionBuilder {
 
   TransactionBuilder(this.state);
 
+  /// Commit the operations to the state
   commit() {
     final transaction = _finish();
     state.apply(transaction);
   }
 
-  void insertNode(Path path, Node node) {
+  insertNode(Path path, Node node) {
     cursorSelection = state.cursorSelection;
-    operations.add(InsertOperation(path: path, value: node));
+    add(InsertOperation(path: path, value: node));
   }
 
-  void updateNode(Node node, Attributes attributes) {
+  updateNode(Node node, Attributes attributes) {
     cursorSelection = state.cursorSelection;
-    operations.add(UpdateOperation(
+    add(UpdateOperation(
       path: node.path,
       attributes: Attributes.from(node.attributes)..addAll(attributes),
       oldAttributes: node.attributes,
     ));
   }
 
-  void deleteNode(Node node) {
+  deleteNode(Node node) {
     cursorSelection = state.cursorSelection;
-    operations.add(DeleteOperation(path: node.path, removedValue: node));
+    add(DeleteOperation(path: node.path, removedValue: node));
   }
 
-  void textEdit(TextNode node, Delta Function() f) {
+  textEdit(TextNode node, Delta Function() f) {
     cursorSelection = state.cursorSelection;
     final path = node.path;
 
     final delta = f();
 
     final inverted = delta.invert(node.delta);
-    operations
-        .add(TextEditOperation(path: path, delta: delta, inverted: inverted));
+
+    add(TextEditOperation(path: path, delta: delta, inverted: inverted));
+  }
+
+  insertText(TextNode node, int index, String content) {
+    textEdit(node, () => Delta().retain(index).insert(content));
+  }
+
+  formatText(TextNode node, int index, int length, Attributes attributes) {
+    textEdit(node, () => Delta().retain(index).retain(length, attributes));
+  }
+
+  deleteText(TextNode node, int index, int length) {
+    textEdit(node, () => Delta().retain(index).delete(length));
+  }
+
+  add(Operation op) {
+    final Operation? last = operations.isEmpty ? null : operations.last;
+    if (last != null) {
+      if (op is TextEditOperation &&
+          last is TextEditOperation &&
+          pathEquals(op.path, last.path)) {
+        final newOp = TextEditOperation(
+          path: op.path,
+          delta: last.delta.compose(op.delta),
+          inverted: op.inverted.compose(last.inverted),
+        );
+        operations[operations.length - 1] = newOp;
+        return;
+      }
+    }
+    operations.add(op);
   }
 
   Transaction _finish() {