diff --git a/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/text_node_widget.dart b/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/text_node_widget.dart index bc57241a51..517c0cda4b 100644 --- a/frontend/app_flowy/packages/flowy_editor/example/lib/plugin/text_node_widget.dart +++ b/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 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 diff --git a/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart b/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart index 51b05f187b..9635c0ebc5 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/operation/transaction_builder.dart +++ b/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() {