refactor: abstract the common transaction to commands
This commit is contained in:
parent
64bab90f1a
commit
d4c9a21425
32 changed files with 470 additions and 510 deletions
|
|
@ -26,9 +26,9 @@ ShortcutEventHandler _enterInCodeBlockHandler = (editorState, event) {
|
|||
return KeyEventResult.ignored;
|
||||
}
|
||||
if (selection.isCollapsed) {
|
||||
editorState.transaction
|
||||
.insertText(codeBlockNode.first, selection.end.offset, '\n');
|
||||
editorState.commit();
|
||||
final transaction = editorState.transaction
|
||||
..insertText(codeBlockNode.first, selection.end.offset, '\n');
|
||||
editorState.apply(transaction);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
|
|
@ -61,16 +61,16 @@ SelectionMenuItem codeBlockMenuItem = SelectionMenuItem(
|
|||
return;
|
||||
}
|
||||
if (textNodes.first.toPlainText().isEmpty) {
|
||||
editorState.transaction
|
||||
final transaction = editorState.transaction
|
||||
..updateNode(textNodes.first, {
|
||||
'subtype': 'code_block',
|
||||
'theme': 'vs',
|
||||
'language': null,
|
||||
})
|
||||
..afterSelection = selection;
|
||||
editorState.commit();
|
||||
editorState.apply(transaction);
|
||||
} else {
|
||||
editorState.transaction
|
||||
final transaction = editorState.transaction
|
||||
..insertNode(
|
||||
selection.end.path.next,
|
||||
TextNode(
|
||||
|
|
@ -84,7 +84,7 @@ SelectionMenuItem codeBlockMenuItem = SelectionMenuItem(
|
|||
),
|
||||
)
|
||||
..afterSelection = selection;
|
||||
editorState.commit();
|
||||
editorState.apply(transaction);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
@ -181,10 +181,11 @@ class __CodeBlockNodeWidgeState extends State<_CodeBlockNodeWidge>
|
|||
child: DropdownButton<String>(
|
||||
value: _detectLanguage,
|
||||
onChanged: (value) {
|
||||
widget.editorState.transaction.updateNode(widget.textNode, {
|
||||
final transaction = widget.editorState.transaction
|
||||
..updateNode(widget.textNode, {
|
||||
'language': value,
|
||||
});
|
||||
widget.editorState.commit();
|
||||
widget.editorState.apply(transaction);
|
||||
},
|
||||
items: allLanguages.keys.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ ShortcutEventHandler _insertHorzaontalRule = (editorState, event) {
|
|||
}
|
||||
final textNode = textNodes.first;
|
||||
if (textNode.toPlainText() == '--') {
|
||||
editorState.transaction
|
||||
final transaction = editorState.transaction
|
||||
..deleteText(textNode, 0, 2)
|
||||
..insertNode(
|
||||
textNode.path,
|
||||
|
|
@ -30,7 +30,7 @@ ShortcutEventHandler _insertHorzaontalRule = (editorState, event) {
|
|||
)
|
||||
..afterSelection =
|
||||
Selection.single(path: textNode.path.next, startOffset: 0);
|
||||
editorState.commit();
|
||||
editorState.apply(transaction);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
|
|
@ -54,7 +54,7 @@ SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
|
|||
}
|
||||
final textNode = textNodes.first;
|
||||
if (textNode.toPlainText().isEmpty) {
|
||||
editorState.transaction
|
||||
final transaction = editorState.transaction
|
||||
..insertNode(
|
||||
textNode.path,
|
||||
Node(
|
||||
|
|
@ -65,9 +65,9 @@ SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
|
|||
)
|
||||
..afterSelection =
|
||||
Selection.single(path: textNode.path.next, startOffset: 0);
|
||||
editorState.commit();
|
||||
editorState.apply(transaction);
|
||||
} else {
|
||||
editorState.transaction
|
||||
final transaction = editorState.transaction
|
||||
..insertNode(
|
||||
selection.end.path.next,
|
||||
TextNode(
|
||||
|
|
@ -79,7 +79,7 @@ SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
|
|||
),
|
||||
)
|
||||
..afterSelection = selection;
|
||||
editorState.commit();
|
||||
editorState.apply(transaction);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ SelectionMenuItem teXBlockMenuItem = SelectionMenuItem(
|
|||
final Path texNodePath;
|
||||
if (textNodes.first.toPlainText().isEmpty) {
|
||||
texNodePath = selection.end.path;
|
||||
editorState.transaction
|
||||
final transaction = editorState.transaction
|
||||
..insertNode(
|
||||
selection.end.path,
|
||||
Node(
|
||||
|
|
@ -34,10 +34,10 @@ SelectionMenuItem teXBlockMenuItem = SelectionMenuItem(
|
|||
)
|
||||
..deleteNode(textNodes.first)
|
||||
..afterSelection = selection;
|
||||
editorState.commit();
|
||||
editorState.apply(transaction);
|
||||
} else {
|
||||
texNodePath = selection.end.path.next;
|
||||
editorState.transaction
|
||||
final transaction = editorState.transaction
|
||||
..insertNode(
|
||||
selection.end.path.next,
|
||||
Node(
|
||||
|
|
@ -47,7 +47,7 @@ SelectionMenuItem teXBlockMenuItem = SelectionMenuItem(
|
|||
),
|
||||
)
|
||||
..afterSelection = selection;
|
||||
editorState.commit();
|
||||
editorState.apply(transaction);
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
final texState =
|
||||
|
|
@ -142,8 +142,9 @@ class __TeXBlockNodeWidgetState extends State<_TeXBlockNodeWidget> {
|
|||
size: 16,
|
||||
),
|
||||
onPressed: () {
|
||||
widget.editorState.transaction.deleteNode(widget.node);
|
||||
widget.editorState.commit();
|
||||
final transaction = widget.editorState.transaction
|
||||
..deleteNode(widget.node);
|
||||
widget.editorState.apply(transaction);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
@ -174,11 +175,12 @@ class __TeXBlockNodeWidgetState extends State<_TeXBlockNodeWidget> {
|
|||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
if (controller.text != _tex) {
|
||||
widget.editorState.transaction.updateNode(
|
||||
final transaction = widget.editorState.transaction
|
||||
..updateNode(
|
||||
widget.node,
|
||||
{'tex': controller.text},
|
||||
);
|
||||
widget.editorState.commit();
|
||||
widget.editorState.apply(transaction);
|
||||
}
|
||||
},
|
||||
child: const Text('OK'),
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
editorState.transaction
|
||||
final transaction = editorState.transaction
|
||||
..deleteText(textNode, firstUnderscore, 1)
|
||||
..formatText(
|
||||
textNode,
|
||||
|
|
@ -47,7 +47,7 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
|
|||
offset: selection.end.offset - 1,
|
||||
),
|
||||
);
|
||||
editorState.commit();
|
||||
editorState.apply(transaction);
|
||||
|
||||
return KeyEventResult.handled;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import 'package:appflowy_editor/src/commands/command_extension.dart';
|
||||
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/editor_state.dart';
|
||||
|
||||
extension TextCommands on EditorState {
|
||||
Future<void> updateNodeAttributes(
|
||||
Attributes attributes, {
|
||||
Path? path,
|
||||
Node? node,
|
||||
}) {
|
||||
return futureCommand(() {
|
||||
final n = getNode(path: path, node: node);
|
||||
apply(
|
||||
transaction..updateNode(n, attributes),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import 'dart:async';
|
||||
|
||||
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:flutter/widgets.dart';
|
||||
|
||||
extension CommandExtension on EditorState {
|
||||
Future<void> futureCommand(void Function() fn) async {
|
||||
final completer = Completer<void>();
|
||||
fn();
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
completer.complete();
|
||||
});
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Node getNode({
|
||||
Path? path,
|
||||
Node? node,
|
||||
}) {
|
||||
if (node != null) {
|
||||
return node;
|
||||
} else if (path != null) {
|
||||
return document.nodeAtPath(path)!;
|
||||
}
|
||||
throw Exception('path and node cannot be null at the same time');
|
||||
}
|
||||
|
||||
TextNode getTextNode({
|
||||
Path? path,
|
||||
TextNode? textNode,
|
||||
}) {
|
||||
if (textNode != null) {
|
||||
return textNode;
|
||||
} else if (path != null) {
|
||||
return document.nodeAtPath(path)! as TextNode;
|
||||
}
|
||||
throw Exception('path and node cannot be null at the same time');
|
||||
}
|
||||
|
||||
Selection getSelection(
|
||||
Selection? selection,
|
||||
) {
|
||||
final currentSelection = service.selectionService.currentSelection.value;
|
||||
if (selection != null) {
|
||||
return selection;
|
||||
} else if (currentSelection != null) {
|
||||
return currentSelection;
|
||||
}
|
||||
throw Exception('path and textNode cannot be null at the same time');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
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/core/transform/transaction.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
Future<void> insertContextInText(
|
||||
EditorState editorState,
|
||||
int index,
|
||||
String content, {
|
||||
Path? path,
|
||||
TextNode? textNode,
|
||||
}) async {
|
||||
final result = getTextNodeToBeFormatted(
|
||||
editorState,
|
||||
path: path,
|
||||
textNode: textNode,
|
||||
);
|
||||
|
||||
final completer = Completer<void>();
|
||||
|
||||
editorState.transaction.insertText(result, index, content);
|
||||
editorState.commit();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
completer.complete();
|
||||
});
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
import 'package:appflowy_editor/src/commands/format_text.dart';
|
||||
import 'package:appflowy_editor/src/commands/text_command_infra.dart';
|
||||
import 'package:appflowy_editor/src/core/document/attributes.dart';
|
||||
import 'package:appflowy_editor/src/core/legacy/built_in_attribute_keys.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/selection.dart';
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
|
||||
Future<void> formatBuiltInTextAttributes(
|
||||
EditorState editorState,
|
||||
String key,
|
||||
Attributes attributes, {
|
||||
Selection? selection,
|
||||
Path? path,
|
||||
TextNode? textNode,
|
||||
}) async {
|
||||
final result = getTextNodeToBeFormatted(
|
||||
editorState,
|
||||
path: path,
|
||||
textNode: textNode,
|
||||
);
|
||||
if (BuiltInAttributeKey.globalStyleKeys.contains(key)) {
|
||||
// remove all the existing style
|
||||
final newAttributes = result.attributes
|
||||
..removeWhere((key, value) {
|
||||
if (BuiltInAttributeKey.globalStyleKeys.contains(key)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
..addAll(attributes)
|
||||
..addAll({
|
||||
BuiltInAttributeKey.subtype: key,
|
||||
});
|
||||
return updateTextNodeAttributes(
|
||||
editorState,
|
||||
newAttributes,
|
||||
textNode: textNode,
|
||||
);
|
||||
} else if (BuiltInAttributeKey.partialStyleKeys.contains(key)) {
|
||||
return updateTextNodeDeltaAttributes(
|
||||
editorState,
|
||||
selection,
|
||||
attributes,
|
||||
textNode: textNode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> formatTextToCheckbox(
|
||||
EditorState editorState,
|
||||
bool check, {
|
||||
Path? path,
|
||||
TextNode? textNode,
|
||||
}) async {
|
||||
return formatBuiltInTextAttributes(
|
||||
editorState,
|
||||
BuiltInAttributeKey.checkbox,
|
||||
{
|
||||
BuiltInAttributeKey.checkbox: check,
|
||||
},
|
||||
path: path,
|
||||
textNode: textNode,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> formatLinkInText(
|
||||
EditorState editorState,
|
||||
String? link, {
|
||||
Path? path,
|
||||
TextNode? textNode,
|
||||
}) async {
|
||||
return formatBuiltInTextAttributes(
|
||||
editorState,
|
||||
BuiltInAttributeKey.href,
|
||||
{
|
||||
BuiltInAttributeKey.href: link,
|
||||
},
|
||||
path: path,
|
||||
textNode: textNode,
|
||||
);
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy_editor/src/commands/text_command_infra.dart';
|
||||
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/selection.dart';
|
||||
import 'package:appflowy_editor/src/editor_state.dart';
|
||||
import 'package:appflowy_editor/src/core/transform/transaction.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
Future<void> updateTextNodeAttributes(
|
||||
EditorState editorState,
|
||||
Attributes attributes, {
|
||||
Path? path,
|
||||
TextNode? textNode,
|
||||
}) async {
|
||||
final result = getTextNodeToBeFormatted(
|
||||
editorState,
|
||||
path: path,
|
||||
textNode: textNode,
|
||||
);
|
||||
|
||||
final completer = Completer<void>();
|
||||
|
||||
editorState.transaction.updateNode(result, attributes);
|
||||
editorState.commit();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
completer.complete();
|
||||
});
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
Future<void> updateTextNodeDeltaAttributes(
|
||||
EditorState editorState,
|
||||
Selection? selection,
|
||||
Attributes attributes, {
|
||||
Path? path,
|
||||
TextNode? textNode,
|
||||
}) {
|
||||
final result = getTextNodeToBeFormatted(
|
||||
editorState,
|
||||
path: path,
|
||||
textNode: textNode,
|
||||
);
|
||||
final newSelection = getSelection(editorState, selection: selection);
|
||||
|
||||
final completer = Completer<void>();
|
||||
editorState.transaction.formatText(
|
||||
result,
|
||||
newSelection.startIndex,
|
||||
newSelection.length,
|
||||
attributes,
|
||||
);
|
||||
editorState.commit();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
completer.complete();
|
||||
});
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/commands/command_extension.dart';
|
||||
|
||||
extension TextCommands on EditorState {
|
||||
/// Insert text at the given index of the given [TextNode] or the [Path].
|
||||
///
|
||||
/// [Path] and [TextNode] are mutually exclusive.
|
||||
/// One of these two parameters must have a value.
|
||||
Future<void> insertText(
|
||||
int index,
|
||||
String text, {
|
||||
Path? path,
|
||||
TextNode? textNode,
|
||||
}) async {
|
||||
return futureCommand(() {
|
||||
final n = getTextNode(path: path, textNode: textNode);
|
||||
apply(
|
||||
transaction..insertText(n, index, text),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> formatText(
|
||||
EditorState editorState,
|
||||
Selection? selection,
|
||||
Attributes attributes, {
|
||||
Path? path,
|
||||
TextNode? textNode,
|
||||
}) async {
|
||||
return futureCommand(() {
|
||||
final n = getTextNode(path: path, textNode: textNode);
|
||||
final s = getSelection(selection);
|
||||
apply(
|
||||
transaction..formatText(n, s.startIndex, s.length, attributes),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> formatTextWithBuiltInAttribute(
|
||||
EditorState editorState,
|
||||
String key,
|
||||
Attributes attributes, {
|
||||
Selection? selection,
|
||||
Path? path,
|
||||
TextNode? textNode,
|
||||
}) async {
|
||||
return futureCommand(() {
|
||||
final n = getTextNode(path: path, textNode: textNode);
|
||||
if (BuiltInAttributeKey.globalStyleKeys.contains(key)) {
|
||||
final attr = n.attributes
|
||||
..removeWhere(
|
||||
(key, _) => BuiltInAttributeKey.globalStyleKeys.contains(key))
|
||||
..addAll(attributes)
|
||||
..addAll({
|
||||
BuiltInAttributeKey.subtype: key,
|
||||
});
|
||||
apply(
|
||||
transaction..updateNode(n, attr),
|
||||
);
|
||||
} else if (BuiltInAttributeKey.partialStyleKeys.contains(key)) {
|
||||
final s = getSelection(selection);
|
||||
apply(
|
||||
transaction..formatText(n, s.startIndex, s.length, attributes),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> formatTextToCheckbox(
|
||||
EditorState editorState,
|
||||
bool check, {
|
||||
Path? path,
|
||||
TextNode? textNode,
|
||||
}) async {
|
||||
return formatTextWithBuiltInAttribute(
|
||||
editorState,
|
||||
BuiltInAttributeKey.checkbox,
|
||||
{BuiltInAttributeKey.checkbox: check},
|
||||
path: path,
|
||||
textNode: textNode,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> formatLinkInText(
|
||||
EditorState editorState,
|
||||
String? link, {
|
||||
Path? path,
|
||||
TextNode? textNode,
|
||||
}) async {
|
||||
return formatTextWithBuiltInAttribute(
|
||||
editorState,
|
||||
BuiltInAttributeKey.href,
|
||||
{BuiltInAttributeKey.href: link},
|
||||
path: path,
|
||||
textNode: textNode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
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';
|
||||
|
||||
// get formatted [TextNode]
|
||||
TextNode getTextNodeToBeFormatted(
|
||||
EditorState editorState, {
|
||||
Path? path,
|
||||
TextNode? textNode,
|
||||
}) {
|
||||
final currentSelection =
|
||||
editorState.service.selectionService.currentSelection.value;
|
||||
TextNode result;
|
||||
if (textNode != null) {
|
||||
result = textNode;
|
||||
} else if (path != null) {
|
||||
result = editorState.document.nodeAtPath(path) as TextNode;
|
||||
} else if (currentSelection != null && currentSelection.isCollapsed) {
|
||||
result = editorState.document.nodeAtPath(currentSelection.start.path)
|
||||
as TextNode;
|
||||
} else {
|
||||
throw Exception('path and textNode cannot be null at the same time');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Selection getSelection(
|
||||
EditorState editorState, {
|
||||
Selection? selection,
|
||||
}) {
|
||||
final currentSelection =
|
||||
editorState.service.selectionService.currentSelection.value;
|
||||
Selection result;
|
||||
if (selection != null) {
|
||||
result = selection;
|
||||
} else if (currentSelection != null) {
|
||||
result = currentSelection;
|
||||
} else {
|
||||
throw Exception('path and textNode cannot be null at the same time');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -119,7 +119,7 @@ class Transaction {
|
|||
///
|
||||
/// Also, this method will transform the path of the operations
|
||||
/// to avoid conflicts.
|
||||
add(Operation op, {bool transform = true}) {
|
||||
void add(Operation op, {bool transform = true}) {
|
||||
final Operation? last = operations.isEmpty ? null : operations.last;
|
||||
if (last != null) {
|
||||
if (op is UpdateTextOperation &&
|
||||
|
|
@ -199,7 +199,7 @@ extension TextTransaction on Transaction {
|
|||
}
|
||||
|
||||
/// Assigns a formatting attributes to a range of text.
|
||||
formatText(
|
||||
void formatText(
|
||||
TextNode textNode,
|
||||
int index,
|
||||
int length,
|
||||
|
|
@ -215,7 +215,7 @@ extension TextTransaction on Transaction {
|
|||
}
|
||||
|
||||
/// Deletes the text of specified length starting at index.
|
||||
deleteText(
|
||||
void deleteText(
|
||||
TextNode textNode,
|
||||
int index,
|
||||
int length,
|
||||
|
|
@ -235,7 +235,7 @@ extension TextTransaction on Transaction {
|
|||
///
|
||||
/// 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(
|
||||
void replaceText(
|
||||
TextNode textNode,
|
||||
int index,
|
||||
int length,
|
||||
|
|
|
|||
|
|
@ -75,21 +75,9 @@ 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;
|
||||
}
|
||||
final transaction = Transaction(document: document);
|
||||
transaction.beforeSelection = _cursorSelection;
|
||||
return transaction;
|
||||
}
|
||||
|
||||
Selection? get cursorSelection {
|
||||
|
|
@ -131,7 +119,7 @@ class EditorState {
|
|||
/// 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()]) {
|
||||
[ApplyOptions options = const ApplyOptions(recordUndo: true)]) {
|
||||
if (!editable) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,20 +24,23 @@ class ImageNodeBuilder extends NodeWidgetBuilder<Node> {
|
|||
RichClipboard.setData(RichClipboardData(text: src));
|
||||
},
|
||||
onDelete: () {
|
||||
context.editorState.transaction.deleteNode(context.node);
|
||||
context.editorState.commit();
|
||||
final transaction = context.editorState.transaction
|
||||
..deleteNode(context.node);
|
||||
context.editorState.apply(transaction);
|
||||
},
|
||||
onAlign: (alignment) {
|
||||
context.editorState.transaction.updateNode(context.node, {
|
||||
final transaction = context.editorState.transaction
|
||||
..updateNode(context.node, {
|
||||
'align': _alignmentToText(alignment),
|
||||
});
|
||||
context.editorState.commit();
|
||||
context.editorState.apply(transaction);
|
||||
},
|
||||
onResize: (width) {
|
||||
context.editorState.transaction.updateNode(context.node, {
|
||||
final transaction = context.editorState.transaction
|
||||
..updateNode(context.node, {
|
||||
'width': width,
|
||||
});
|
||||
context.editorState.commit();
|
||||
context.editorState.apply(transaction);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,6 +195,6 @@ extension on EditorState {
|
|||
selection.start.path,
|
||||
imageNode,
|
||||
);
|
||||
commit();
|
||||
apply(transaction);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/commands/format_built_in_text.dart';
|
||||
import 'package:appflowy_editor/src/commands/text/text_commands.dart';
|
||||
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart';
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ class _CheckboxNodeWidgetState extends State<CheckboxNodeWidget>
|
|||
name: check ? 'check' : 'uncheck',
|
||||
),
|
||||
onTap: () async {
|
||||
await formatTextToCheckbox(
|
||||
await widget.editorState.formatTextToCheckbox(
|
||||
widget.editorState,
|
||||
!check,
|
||||
textNode: widget.textNode,
|
||||
|
|
|
|||
|
|
@ -45,12 +45,13 @@ class SelectionMenuItem {
|
|||
final node = nodes.first as TextNode;
|
||||
final end = selection.start.offset;
|
||||
final start = node.toPlainText().substring(0, end).lastIndexOf('/');
|
||||
editorState.transaction.deleteText(
|
||||
final transaction = editorState.transaction
|
||||
..deleteText(
|
||||
node,
|
||||
start,
|
||||
selection.start.offset - start,
|
||||
);
|
||||
editorState.commit();
|
||||
editorState.apply(transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -277,12 +278,13 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
|
|||
final nodes = selectionService.currentSelectedNodes;
|
||||
if (selection != null && nodes.length == 1) {
|
||||
widget.onSelectionUpdate();
|
||||
widget.editorState.transaction.deleteText(
|
||||
final transaction = widget.editorState.transaction
|
||||
..deleteText(
|
||||
nodes.first as TextNode,
|
||||
selection.start.offset - length,
|
||||
length,
|
||||
);
|
||||
widget.editorState.commit();
|
||||
widget.editorState.apply(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -293,12 +295,13 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
|
|||
widget.editorState.service.selectionService.currentSelectedNodes;
|
||||
if (selection != null && nodes.length == 1) {
|
||||
widget.onSelectionUpdate();
|
||||
widget.editorState.transaction.insertText(
|
||||
final transaction = widget.editorState.transaction
|
||||
..insertText(
|
||||
nodes.first as TextNode,
|
||||
selection.end.offset,
|
||||
text,
|
||||
);
|
||||
widget.editorState.commit();
|
||||
widget.editorState.apply(transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/commands/format_built_in_text.dart';
|
||||
import 'package:appflowy_editor/src/commands/text/text_commands.dart';
|
||||
import 'package:appflowy_editor/src/extensions/url_launcher_extension.dart';
|
||||
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
||||
import 'package:appflowy_editor/src/render/link_menu/link_menu.dart';
|
||||
|
|
@ -349,7 +349,11 @@ void showLinkMenu(
|
|||
await safeLaunchUrl(linkText);
|
||||
},
|
||||
onSubmitted: (text) async {
|
||||
await formatLinkInText(editorState, text, textNode: textNode);
|
||||
await editorState.formatLinkInText(
|
||||
editorState,
|
||||
text,
|
||||
textNode: textNode,
|
||||
);
|
||||
_dismissLinkMenu();
|
||||
},
|
||||
onCopyLink: () {
|
||||
|
|
@ -357,9 +361,14 @@ void showLinkMenu(
|
|||
_dismissLinkMenu();
|
||||
},
|
||||
onRemoveLink: () {
|
||||
editorState.transaction.formatText(
|
||||
textNode, index, length, {BuiltInAttributeKey.href: null});
|
||||
editorState.commit();
|
||||
final transaction = editorState.transaction
|
||||
..formatText(
|
||||
textNode,
|
||||
index,
|
||||
length,
|
||||
{BuiltInAttributeKey.href: null},
|
||||
);
|
||||
editorState.apply(transaction);
|
||||
_dismissLinkMenu();
|
||||
},
|
||||
onFocusChange: (value) {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ bool insertTextNodeAfterSelection(
|
|||
formatTextNodes(editorState, attributes);
|
||||
} else {
|
||||
final next = selection.end.path.next;
|
||||
editorState.transaction
|
||||
final transaction = editorState.transaction
|
||||
..insertNode(
|
||||
next,
|
||||
TextNode.empty(attributes: attributes),
|
||||
|
|
@ -55,7 +55,7 @@ bool insertTextNodeAfterSelection(
|
|||
..afterSelection = Selection.collapsed(
|
||||
Position(path: next, offset: 0),
|
||||
);
|
||||
editorState.commit();
|
||||
editorState.apply(transaction);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -122,7 +122,7 @@ bool formatTextNodes(EditorState editorState, Attributes attributes) {
|
|||
);
|
||||
}
|
||||
|
||||
editorState.commit();
|
||||
editorState.apply(transaction);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -240,7 +240,7 @@ bool formatRichTextStyle(EditorState editorState, Attributes attributes) {
|
|||
}
|
||||
}
|
||||
|
||||
editorState.commit();
|
||||
editorState.apply(transaction);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -160,12 +160,13 @@ class _AppFlowyInputState extends State<AppFlowyInput>
|
|||
}
|
||||
if (currentSelection.isSingle) {
|
||||
final textNode = selectionService.currentSelectedNodes.first as TextNode;
|
||||
_editorState.transaction.insertText(
|
||||
final transaction = _editorState.transaction;
|
||||
transaction.insertText(
|
||||
textNode,
|
||||
delta.insertionOffset,
|
||||
delta.textInserted,
|
||||
);
|
||||
_editorState.commit();
|
||||
_editorState.apply(transaction);
|
||||
} else {
|
||||
// TODO: implement
|
||||
}
|
||||
|
|
@ -180,9 +181,9 @@ class _AppFlowyInputState extends State<AppFlowyInput>
|
|||
if (currentSelection.isSingle) {
|
||||
final textNode = selectionService.currentSelectedNodes.first as TextNode;
|
||||
final length = delta.deletedRange.end - delta.deletedRange.start;
|
||||
_editorState.transaction
|
||||
.deleteText(textNode, delta.deletedRange.start, length);
|
||||
_editorState.commit();
|
||||
final transaction = _editorState.transaction;
|
||||
transaction.deleteText(textNode, delta.deletedRange.start, length);
|
||||
_editorState.apply(transaction);
|
||||
} else {
|
||||
// TODO: implement
|
||||
}
|
||||
|
|
@ -197,9 +198,10 @@ class _AppFlowyInputState extends State<AppFlowyInput>
|
|||
if (currentSelection.isSingle) {
|
||||
final textNode = selectionService.currentSelectedNodes.first as TextNode;
|
||||
final length = delta.replacedRange.end - delta.replacedRange.start;
|
||||
_editorState.transaction.replaceText(
|
||||
final transaction = _editorState.transaction;
|
||||
transaction.replaceText(
|
||||
textNode, delta.replacedRange.start, length, delta.replacementText);
|
||||
_editorState.commit();
|
||||
_editorState.apply(transaction);
|
||||
} else {
|
||||
// TODO: implement
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue