mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: insert text at cursor
This commit is contained in:
parent
ee5c9d410b
commit
abe0658cd3
@ -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
|
||||
|
@ -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() {
|
||||
|
Loading…
Reference in New Issue
Block a user