mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
refactor: abstract the common transaction to commands
This commit is contained in:
parent
71b1769eee
commit
f3eeb471e7
@ -26,9 +26,9 @@ ShortcutEventHandler _enterInCodeBlockHandler = (editorState, event) {
|
|||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
if (selection.isCollapsed) {
|
if (selection.isCollapsed) {
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
.insertText(codeBlockNode.first, selection.end.offset, '\n');
|
..insertText(codeBlockNode.first, selection.end.offset, '\n');
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
@ -61,16 +61,16 @@ SelectionMenuItem codeBlockMenuItem = SelectionMenuItem(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (textNodes.first.toPlainText().isEmpty) {
|
if (textNodes.first.toPlainText().isEmpty) {
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..updateNode(textNodes.first, {
|
..updateNode(textNodes.first, {
|
||||||
'subtype': 'code_block',
|
'subtype': 'code_block',
|
||||||
'theme': 'vs',
|
'theme': 'vs',
|
||||||
'language': null,
|
'language': null,
|
||||||
})
|
})
|
||||||
..afterSelection = selection;
|
..afterSelection = selection;
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
} else {
|
} else {
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..insertNode(
|
..insertNode(
|
||||||
selection.end.path.next,
|
selection.end.path.next,
|
||||||
TextNode(
|
TextNode(
|
||||||
@ -84,7 +84,7 @@ SelectionMenuItem codeBlockMenuItem = SelectionMenuItem(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
..afterSelection = selection;
|
..afterSelection = selection;
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -181,10 +181,11 @@ class __CodeBlockNodeWidgeState extends State<_CodeBlockNodeWidge>
|
|||||||
child: DropdownButton<String>(
|
child: DropdownButton<String>(
|
||||||
value: _detectLanguage,
|
value: _detectLanguage,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
widget.editorState.transaction.updateNode(widget.textNode, {
|
final transaction = widget.editorState.transaction
|
||||||
|
..updateNode(widget.textNode, {
|
||||||
'language': value,
|
'language': value,
|
||||||
});
|
});
|
||||||
widget.editorState.commit();
|
widget.editorState.apply(transaction);
|
||||||
},
|
},
|
||||||
items: allLanguages.keys.map<DropdownMenuItem<String>>((String value) {
|
items: allLanguages.keys.map<DropdownMenuItem<String>>((String value) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
|
@ -18,7 +18,7 @@ ShortcutEventHandler _insertHorzaontalRule = (editorState, event) {
|
|||||||
}
|
}
|
||||||
final textNode = textNodes.first;
|
final textNode = textNodes.first;
|
||||||
if (textNode.toPlainText() == '--') {
|
if (textNode.toPlainText() == '--') {
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..deleteText(textNode, 0, 2)
|
..deleteText(textNode, 0, 2)
|
||||||
..insertNode(
|
..insertNode(
|
||||||
textNode.path,
|
textNode.path,
|
||||||
@ -30,7 +30,7 @@ ShortcutEventHandler _insertHorzaontalRule = (editorState, event) {
|
|||||||
)
|
)
|
||||||
..afterSelection =
|
..afterSelection =
|
||||||
Selection.single(path: textNode.path.next, startOffset: 0);
|
Selection.single(path: textNode.path.next, startOffset: 0);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
@ -54,7 +54,7 @@ SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
|
|||||||
}
|
}
|
||||||
final textNode = textNodes.first;
|
final textNode = textNodes.first;
|
||||||
if (textNode.toPlainText().isEmpty) {
|
if (textNode.toPlainText().isEmpty) {
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..insertNode(
|
..insertNode(
|
||||||
textNode.path,
|
textNode.path,
|
||||||
Node(
|
Node(
|
||||||
@ -65,9 +65,9 @@ SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
|
|||||||
)
|
)
|
||||||
..afterSelection =
|
..afterSelection =
|
||||||
Selection.single(path: textNode.path.next, startOffset: 0);
|
Selection.single(path: textNode.path.next, startOffset: 0);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
} else {
|
} else {
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..insertNode(
|
..insertNode(
|
||||||
selection.end.path.next,
|
selection.end.path.next,
|
||||||
TextNode(
|
TextNode(
|
||||||
@ -79,7 +79,7 @@ SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
..afterSelection = selection;
|
..afterSelection = selection;
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -23,7 +23,7 @@ SelectionMenuItem teXBlockMenuItem = SelectionMenuItem(
|
|||||||
final Path texNodePath;
|
final Path texNodePath;
|
||||||
if (textNodes.first.toPlainText().isEmpty) {
|
if (textNodes.first.toPlainText().isEmpty) {
|
||||||
texNodePath = selection.end.path;
|
texNodePath = selection.end.path;
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..insertNode(
|
..insertNode(
|
||||||
selection.end.path,
|
selection.end.path,
|
||||||
Node(
|
Node(
|
||||||
@ -34,10 +34,10 @@ SelectionMenuItem teXBlockMenuItem = SelectionMenuItem(
|
|||||||
)
|
)
|
||||||
..deleteNode(textNodes.first)
|
..deleteNode(textNodes.first)
|
||||||
..afterSelection = selection;
|
..afterSelection = selection;
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
} else {
|
} else {
|
||||||
texNodePath = selection.end.path.next;
|
texNodePath = selection.end.path.next;
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..insertNode(
|
..insertNode(
|
||||||
selection.end.path.next,
|
selection.end.path.next,
|
||||||
Node(
|
Node(
|
||||||
@ -47,7 +47,7 @@ SelectionMenuItem teXBlockMenuItem = SelectionMenuItem(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
..afterSelection = selection;
|
..afterSelection = selection;
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
final texState =
|
final texState =
|
||||||
@ -142,8 +142,9 @@ class __TeXBlockNodeWidgetState extends State<_TeXBlockNodeWidget> {
|
|||||||
size: 16,
|
size: 16,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
widget.editorState.transaction.deleteNode(widget.node);
|
final transaction = widget.editorState.transaction
|
||||||
widget.editorState.commit();
|
..deleteNode(widget.node);
|
||||||
|
widget.editorState.apply(transaction);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -174,11 +175,12 @@ class __TeXBlockNodeWidgetState extends State<_TeXBlockNodeWidget> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
if (controller.text != _tex) {
|
if (controller.text != _tex) {
|
||||||
widget.editorState.transaction.updateNode(
|
final transaction = widget.editorState.transaction
|
||||||
|
..updateNode(
|
||||||
widget.node,
|
widget.node,
|
||||||
{'tex': controller.text},
|
{'tex': controller.text},
|
||||||
);
|
);
|
||||||
widget.editorState.commit();
|
widget.editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Text('OK'),
|
child: const Text('OK'),
|
||||||
|
@ -31,7 +31,7 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
|
|||||||
// Delete the previous 'underscore',
|
// Delete the previous 'underscore',
|
||||||
// update the style of the text surrounded by the two underscores to 'italic',
|
// update the style of the text surrounded by the two underscores to 'italic',
|
||||||
// and update the cursor position.
|
// and update the cursor position.
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..deleteText(textNode, firstUnderscore, 1)
|
..deleteText(textNode, firstUnderscore, 1)
|
||||||
..formatText(
|
..formatText(
|
||||||
textNode,
|
textNode,
|
||||||
@ -47,7 +47,7 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
|
|||||||
offset: selection.end.offset - 1,
|
offset: selection.end.offset - 1,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
return KeyEventResult.handled;
|
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
|
/// Also, this method will transform the path of the operations
|
||||||
/// to avoid conflicts.
|
/// to avoid conflicts.
|
||||||
add(Operation op, {bool transform = true}) {
|
void add(Operation op, {bool transform = true}) {
|
||||||
final Operation? last = operations.isEmpty ? null : operations.last;
|
final Operation? last = operations.isEmpty ? null : operations.last;
|
||||||
if (last != null) {
|
if (last != null) {
|
||||||
if (op is UpdateTextOperation &&
|
if (op is UpdateTextOperation &&
|
||||||
@ -199,7 +199,7 @@ extension TextTransaction on Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Assigns a formatting attributes to a range of text.
|
/// Assigns a formatting attributes to a range of text.
|
||||||
formatText(
|
void formatText(
|
||||||
TextNode textNode,
|
TextNode textNode,
|
||||||
int index,
|
int index,
|
||||||
int length,
|
int length,
|
||||||
@ -215,7 +215,7 @@ extension TextTransaction on Transaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes the text of specified length starting at index.
|
/// Deletes the text of specified length starting at index.
|
||||||
deleteText(
|
void deleteText(
|
||||||
TextNode textNode,
|
TextNode textNode,
|
||||||
int index,
|
int index,
|
||||||
int length,
|
int length,
|
||||||
@ -235,7 +235,7 @@ extension TextTransaction on Transaction {
|
|||||||
///
|
///
|
||||||
/// Optionally, you may specify formatting attributes that are applied to the inserted string.
|
/// 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.
|
/// By default, the formatting attributes before the insert position will be reused.
|
||||||
replaceText(
|
void replaceText(
|
||||||
TextNode textNode,
|
TextNode textNode,
|
||||||
int index,
|
int index,
|
||||||
int length,
|
int length,
|
||||||
|
@ -75,21 +75,9 @@ class EditorState {
|
|||||||
bool editable = true;
|
bool editable = true;
|
||||||
|
|
||||||
Transaction get transaction {
|
Transaction get transaction {
|
||||||
if (_transaction != null) {
|
final transaction = Transaction(document: document);
|
||||||
return _transaction!;
|
transaction.beforeSelection = _cursorSelection;
|
||||||
}
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Selection? get cursorSelection {
|
Selection? get cursorSelection {
|
||||||
@ -131,7 +119,7 @@ class EditorState {
|
|||||||
/// The options can be used to determine whether the editor
|
/// The options can be used to determine whether the editor
|
||||||
/// should record the transaction in undo/redo stack.
|
/// should record the transaction in undo/redo stack.
|
||||||
apply(Transaction transaction,
|
apply(Transaction transaction,
|
||||||
[ApplyOptions options = const ApplyOptions()]) {
|
[ApplyOptions options = const ApplyOptions(recordUndo: true)]) {
|
||||||
if (!editable) {
|
if (!editable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -24,20 +24,23 @@ class ImageNodeBuilder extends NodeWidgetBuilder<Node> {
|
|||||||
RichClipboard.setData(RichClipboardData(text: src));
|
RichClipboard.setData(RichClipboardData(text: src));
|
||||||
},
|
},
|
||||||
onDelete: () {
|
onDelete: () {
|
||||||
context.editorState.transaction.deleteNode(context.node);
|
final transaction = context.editorState.transaction
|
||||||
context.editorState.commit();
|
..deleteNode(context.node);
|
||||||
|
context.editorState.apply(transaction);
|
||||||
},
|
},
|
||||||
onAlign: (alignment) {
|
onAlign: (alignment) {
|
||||||
context.editorState.transaction.updateNode(context.node, {
|
final transaction = context.editorState.transaction
|
||||||
|
..updateNode(context.node, {
|
||||||
'align': _alignmentToText(alignment),
|
'align': _alignmentToText(alignment),
|
||||||
});
|
});
|
||||||
context.editorState.commit();
|
context.editorState.apply(transaction);
|
||||||
},
|
},
|
||||||
onResize: (width) {
|
onResize: (width) {
|
||||||
context.editorState.transaction.updateNode(context.node, {
|
final transaction = context.editorState.transaction
|
||||||
|
..updateNode(context.node, {
|
||||||
'width': width,
|
'width': width,
|
||||||
});
|
});
|
||||||
context.editorState.commit();
|
context.editorState.apply(transaction);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -195,6 +195,6 @@ extension on EditorState {
|
|||||||
selection.start.path,
|
selection.start.path,
|
||||||
imageNode,
|
imageNode,
|
||||||
);
|
);
|
||||||
commit();
|
apply(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
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/infra/flowy_svg.dart';
|
||||||
import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.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',
|
name: check ? 'check' : 'uncheck',
|
||||||
),
|
),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
await formatTextToCheckbox(
|
await widget.editorState.formatTextToCheckbox(
|
||||||
widget.editorState,
|
widget.editorState,
|
||||||
!check,
|
!check,
|
||||||
textNode: widget.textNode,
|
textNode: widget.textNode,
|
||||||
|
@ -45,12 +45,13 @@ class SelectionMenuItem {
|
|||||||
final node = nodes.first as TextNode;
|
final node = nodes.first as TextNode;
|
||||||
final end = selection.start.offset;
|
final end = selection.start.offset;
|
||||||
final start = node.toPlainText().substring(0, end).lastIndexOf('/');
|
final start = node.toPlainText().substring(0, end).lastIndexOf('/');
|
||||||
editorState.transaction.deleteText(
|
final transaction = editorState.transaction
|
||||||
|
..deleteText(
|
||||||
node,
|
node,
|
||||||
start,
|
start,
|
||||||
selection.start.offset - start,
|
selection.start.offset - start,
|
||||||
);
|
);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,12 +278,13 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
|
|||||||
final nodes = selectionService.currentSelectedNodes;
|
final nodes = selectionService.currentSelectedNodes;
|
||||||
if (selection != null && nodes.length == 1) {
|
if (selection != null && nodes.length == 1) {
|
||||||
widget.onSelectionUpdate();
|
widget.onSelectionUpdate();
|
||||||
widget.editorState.transaction.deleteText(
|
final transaction = widget.editorState.transaction
|
||||||
|
..deleteText(
|
||||||
nodes.first as TextNode,
|
nodes.first as TextNode,
|
||||||
selection.start.offset - length,
|
selection.start.offset - length,
|
||||||
length,
|
length,
|
||||||
);
|
);
|
||||||
widget.editorState.commit();
|
widget.editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,12 +295,13 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
|
|||||||
widget.editorState.service.selectionService.currentSelectedNodes;
|
widget.editorState.service.selectionService.currentSelectedNodes;
|
||||||
if (selection != null && nodes.length == 1) {
|
if (selection != null && nodes.length == 1) {
|
||||||
widget.onSelectionUpdate();
|
widget.onSelectionUpdate();
|
||||||
widget.editorState.transaction.insertText(
|
final transaction = widget.editorState.transaction
|
||||||
|
..insertText(
|
||||||
nodes.first as TextNode,
|
nodes.first as TextNode,
|
||||||
selection.end.offset,
|
selection.end.offset,
|
||||||
text,
|
text,
|
||||||
);
|
);
|
||||||
widget.editorState.commit();
|
widget.editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
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/extensions/url_launcher_extension.dart';
|
||||||
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
import 'package:appflowy_editor/src/infra/flowy_svg.dart';
|
||||||
import 'package:appflowy_editor/src/render/link_menu/link_menu.dart';
|
import 'package:appflowy_editor/src/render/link_menu/link_menu.dart';
|
||||||
@ -349,7 +349,11 @@ void showLinkMenu(
|
|||||||
await safeLaunchUrl(linkText);
|
await safeLaunchUrl(linkText);
|
||||||
},
|
},
|
||||||
onSubmitted: (text) async {
|
onSubmitted: (text) async {
|
||||||
await formatLinkInText(editorState, text, textNode: textNode);
|
await editorState.formatLinkInText(
|
||||||
|
editorState,
|
||||||
|
text,
|
||||||
|
textNode: textNode,
|
||||||
|
);
|
||||||
_dismissLinkMenu();
|
_dismissLinkMenu();
|
||||||
},
|
},
|
||||||
onCopyLink: () {
|
onCopyLink: () {
|
||||||
@ -357,9 +361,14 @@ void showLinkMenu(
|
|||||||
_dismissLinkMenu();
|
_dismissLinkMenu();
|
||||||
},
|
},
|
||||||
onRemoveLink: () {
|
onRemoveLink: () {
|
||||||
editorState.transaction.formatText(
|
final transaction = editorState.transaction
|
||||||
textNode, index, length, {BuiltInAttributeKey.href: null});
|
..formatText(
|
||||||
editorState.commit();
|
textNode,
|
||||||
|
index,
|
||||||
|
length,
|
||||||
|
{BuiltInAttributeKey.href: null},
|
||||||
|
);
|
||||||
|
editorState.apply(transaction);
|
||||||
_dismissLinkMenu();
|
_dismissLinkMenu();
|
||||||
},
|
},
|
||||||
onFocusChange: (value) {
|
onFocusChange: (value) {
|
||||||
|
@ -47,7 +47,7 @@ bool insertTextNodeAfterSelection(
|
|||||||
formatTextNodes(editorState, attributes);
|
formatTextNodes(editorState, attributes);
|
||||||
} else {
|
} else {
|
||||||
final next = selection.end.path.next;
|
final next = selection.end.path.next;
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..insertNode(
|
..insertNode(
|
||||||
next,
|
next,
|
||||||
TextNode.empty(attributes: attributes),
|
TextNode.empty(attributes: attributes),
|
||||||
@ -55,7 +55,7 @@ bool insertTextNodeAfterSelection(
|
|||||||
..afterSelection = Selection.collapsed(
|
..afterSelection = Selection.collapsed(
|
||||||
Position(path: next, offset: 0),
|
Position(path: next, offset: 0),
|
||||||
);
|
);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -122,7 +122,7 @@ bool formatTextNodes(EditorState editorState, Attributes attributes) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,7 +240,7 @@ bool formatRichTextStyle(EditorState editorState, Attributes attributes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -160,12 +160,13 @@ class _AppFlowyInputState extends State<AppFlowyInput>
|
|||||||
}
|
}
|
||||||
if (currentSelection.isSingle) {
|
if (currentSelection.isSingle) {
|
||||||
final textNode = selectionService.currentSelectedNodes.first as TextNode;
|
final textNode = selectionService.currentSelectedNodes.first as TextNode;
|
||||||
_editorState.transaction.insertText(
|
final transaction = _editorState.transaction;
|
||||||
|
transaction.insertText(
|
||||||
textNode,
|
textNode,
|
||||||
delta.insertionOffset,
|
delta.insertionOffset,
|
||||||
delta.textInserted,
|
delta.textInserted,
|
||||||
);
|
);
|
||||||
_editorState.commit();
|
_editorState.apply(transaction);
|
||||||
} else {
|
} else {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
@ -180,9 +181,9 @@ class _AppFlowyInputState extends State<AppFlowyInput>
|
|||||||
if (currentSelection.isSingle) {
|
if (currentSelection.isSingle) {
|
||||||
final textNode = selectionService.currentSelectedNodes.first as TextNode;
|
final textNode = selectionService.currentSelectedNodes.first as TextNode;
|
||||||
final length = delta.deletedRange.end - delta.deletedRange.start;
|
final length = delta.deletedRange.end - delta.deletedRange.start;
|
||||||
_editorState.transaction
|
final transaction = _editorState.transaction;
|
||||||
.deleteText(textNode, delta.deletedRange.start, length);
|
transaction.deleteText(textNode, delta.deletedRange.start, length);
|
||||||
_editorState.commit();
|
_editorState.apply(transaction);
|
||||||
} else {
|
} else {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
@ -197,9 +198,10 @@ class _AppFlowyInputState extends State<AppFlowyInput>
|
|||||||
if (currentSelection.isSingle) {
|
if (currentSelection.isSingle) {
|
||||||
final textNode = selectionService.currentSelectedNodes.first as TextNode;
|
final textNode = selectionService.currentSelectedNodes.first as TextNode;
|
||||||
final length = delta.replacedRange.end - delta.replacedRange.start;
|
final length = delta.replacedRange.end - delta.replacedRange.start;
|
||||||
_editorState.transaction.replaceText(
|
final transaction = _editorState.transaction;
|
||||||
|
transaction.replaceText(
|
||||||
textNode, delta.replacedRange.start, length, delta.replacementText);
|
textNode, delta.replacedRange.start, length, delta.replacementText);
|
||||||
_editorState.commit();
|
_editorState.apply(transaction);
|
||||||
} else {
|
} else {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
|
@ -86,13 +86,13 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) {
|
|||||||
if (nonTextNodes.isNotEmpty) {
|
if (nonTextNodes.isNotEmpty) {
|
||||||
transaction.afterSelection = Selection.collapsed(selection.start);
|
transaction.afterSelection = Selection.collapsed(selection.start);
|
||||||
}
|
}
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
final startPosition = selection.start;
|
final startPosition = selection.start;
|
||||||
final nodeAtStart = editorState.document.nodeAtPath(startPosition.path)!;
|
final nodeAtStart = editorState.document.nodeAtPath(startPosition.path)!;
|
||||||
_deleteTextNodes(transaction, textNodes, selection);
|
_deleteTextNodes(transaction, textNodes, selection);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
if (nodeAtStart is TextNode &&
|
if (nodeAtStart is TextNode &&
|
||||||
nodeAtStart.subtype == BuiltInAttributeKey.numberList) {
|
nodeAtStart.subtype == BuiltInAttributeKey.numberList) {
|
||||||
@ -109,7 +109,7 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) {
|
|||||||
if (nonTextNodes.isNotEmpty) {
|
if (nonTextNodes.isNotEmpty) {
|
||||||
transaction.afterSelection = Selection.collapsed(selection.start);
|
transaction.afterSelection = Selection.collapsed(selection.start);
|
||||||
}
|
}
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancelNumberListPath != null) {
|
if (cancelNumberListPath != null) {
|
||||||
@ -140,7 +140,7 @@ KeyEventResult _backDeleteToPreviousTextNode(
|
|||||||
..afterSelection = Selection.collapsed(
|
..afterSelection = Selection.collapsed(
|
||||||
Position(path: textNode.parent!.path.next, offset: 0),
|
Position(path: textNode.parent!.path.next, offset: 0),
|
||||||
);
|
);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ KeyEventResult _backDeleteToPreviousTextNode(
|
|||||||
if (nonTextNodes.isNotEmpty) {
|
if (nonTextNodes.isNotEmpty) {
|
||||||
transaction.afterSelection = Selection.collapsed(selection.start);
|
transaction.afterSelection = Selection.collapsed(selection.start);
|
||||||
}
|
}
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prevIsNumberList) {
|
if (prevIsNumberList) {
|
||||||
@ -223,12 +223,12 @@ KeyEventResult _handleDelete(EditorState editorState, RawKeyEvent event) {
|
|||||||
selection.end.offset - selection.start.offset,
|
selection.end.offset - selection.start.offset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
} else {
|
} else {
|
||||||
final startPosition = selection.start;
|
final startPosition = selection.start;
|
||||||
final nodeAtStart = editorState.document.nodeAtPath(startPosition.path)!;
|
final nodeAtStart = editorState.document.nodeAtPath(startPosition.path)!;
|
||||||
_deleteTextNodes(transaction, textNodes, selection);
|
_deleteTextNodes(transaction, textNodes, selection);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
if (nodeAtStart is TextNode &&
|
if (nodeAtStart is TextNode &&
|
||||||
nodeAtStart.subtype == BuiltInAttributeKey.numberList) {
|
nodeAtStart.subtype == BuiltInAttributeKey.numberList) {
|
||||||
@ -250,7 +250,7 @@ KeyEventResult _mergeNextLineIntoThisLine(EditorState editorState,
|
|||||||
transaction.mergeText(textNode, nextNode);
|
transaction.mergeText(textNode, nextNode);
|
||||||
}
|
}
|
||||||
transaction.deleteNode(nextNode);
|
transaction.deleteNode(nextNode);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
if (textNode.subtype == BuiltInAttributeKey.numberList) {
|
if (textNode.subtype == BuiltInAttributeKey.numberList) {
|
||||||
makeFollowingNodesIncremental(editorState, textNode.path, selection);
|
makeFollowingNodesIncremental(editorState, textNode.path, selection);
|
||||||
|
@ -94,7 +94,7 @@ void _pasteHTML(EditorState editorState, String html) {
|
|||||||
textNodeAtPath, (Delta()..retain(startOffset)) + firstTextNode.delta);
|
textNodeAtPath, (Delta()..retain(startOffset)) + firstTextNode.delta);
|
||||||
tb.afterSelection = (Selection.collapsed(Position(
|
tb.afterSelection = (Selection.collapsed(Position(
|
||||||
path: path, offset: startOffset + firstTextNode.delta.length)));
|
path: path, offset: startOffset + firstTextNode.delta.length)));
|
||||||
editorState.commit();
|
editorState.apply(tb);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,7 +147,7 @@ void _pasteMultipleLinesInText(
|
|||||||
|
|
||||||
tb.afterSelection = afterSelection;
|
tb.afterSelection = afterSelection;
|
||||||
tb.insertNodes(path, tailNodes);
|
tb.insertNodes(path, tailNodes);
|
||||||
editorState.commit();
|
editorState.apply(tb);
|
||||||
|
|
||||||
if (startNumber != null) {
|
if (startNumber != null) {
|
||||||
makeFollowingNodesIncremental(editorState, originalPath, afterSelection,
|
makeFollowingNodesIncremental(editorState, originalPath, afterSelection,
|
||||||
@ -162,7 +162,7 @@ void _pasteMultipleLinesInText(
|
|||||||
path[path.length - 1]++;
|
path[path.length - 1]++;
|
||||||
tb.afterSelection = afterSelection;
|
tb.afterSelection = afterSelection;
|
||||||
tb.insertNodes(path, nodes);
|
tb.insertNodes(path, nodes);
|
||||||
editorState.commit();
|
editorState.apply(tb);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handlePaste(EditorState editorState) async {
|
void _handlePaste(EditorState editorState) async {
|
||||||
@ -195,7 +195,7 @@ void _pasteSingleLine(
|
|||||||
EditorState editorState, Selection selection, String line) {
|
EditorState editorState, Selection selection, String line) {
|
||||||
final node = editorState.document.nodeAtPath(selection.end.path)! as TextNode;
|
final node = editorState.document.nodeAtPath(selection.end.path)! as TextNode;
|
||||||
final beginOffset = selection.end.offset;
|
final beginOffset = selection.end.offset;
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..updateText(
|
..updateText(
|
||||||
node,
|
node,
|
||||||
Delta()
|
Delta()
|
||||||
@ -203,7 +203,7 @@ void _pasteSingleLine(
|
|||||||
..addAll(_lineContentToDelta(line)))
|
..addAll(_lineContentToDelta(line)))
|
||||||
..afterSelection = (Selection.collapsed(
|
..afterSelection = (Selection.collapsed(
|
||||||
Position(path: selection.end.path, offset: beginOffset + line.length)));
|
Position(path: selection.end.path, offset: beginOffset + line.length)));
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// parse url from the line text
|
/// parse url from the line text
|
||||||
@ -287,7 +287,7 @@ void _handlePastePlainText(EditorState editorState, String plainText) {
|
|||||||
// insert remains
|
// insert remains
|
||||||
tb.insertNodes(path, nodes);
|
tb.insertNodes(path, nodes);
|
||||||
tb.afterSelection = afterSelection;
|
tb.afterSelection = afterSelection;
|
||||||
editorState.commit();
|
editorState.apply(tb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,7 +316,7 @@ void _deleteSelectedContent(EditorState editorState) {
|
|||||||
..retain(selection.start.offset)
|
..retain(selection.start.offset)
|
||||||
..delete(len));
|
..delete(len));
|
||||||
tb.afterSelection = Selection.collapsed(selection.start);
|
tb.afterSelection = Selection.collapsed(selection.start);
|
||||||
editorState.commit();
|
editorState.apply(tb);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final traverser = NodeIterator(
|
final traverser = NodeIterator(
|
||||||
@ -347,7 +347,7 @@ void _deleteSelectedContent(EditorState editorState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tb.afterSelection = Selection.collapsed(selection.start);
|
tb.afterSelection = Selection.collapsed(selection.start);
|
||||||
editorState.commit();
|
editorState.apply(tb);
|
||||||
}
|
}
|
||||||
|
|
||||||
ShortcutEventHandler copyEventHandler = (editorState, event) {
|
ShortcutEventHandler copyEventHandler = (editorState, event) {
|
||||||
|
@ -39,7 +39,7 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler =
|
|||||||
final afterSelection = Selection.collapsed(
|
final afterSelection = Selection.collapsed(
|
||||||
Position(path: textNodes.first.path.next, offset: 0),
|
Position(path: textNodes.first.path.next, offset: 0),
|
||||||
);
|
);
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..deleteText(
|
..deleteText(
|
||||||
textNodes.first,
|
textNodes.first,
|
||||||
selection.start.offset,
|
selection.start.offset,
|
||||||
@ -52,7 +52,7 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler =
|
|||||||
selection.end.offset,
|
selection.end.offset,
|
||||||
)
|
)
|
||||||
..afterSelection = afterSelection;
|
..afterSelection = afterSelection;
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
if (startNode is TextNode &&
|
if (startNode is TextNode &&
|
||||||
startNode.subtype == BuiltInAttributeKey.numberList) {
|
startNode.subtype == BuiltInAttributeKey.numberList) {
|
||||||
@ -77,12 +77,12 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler =
|
|||||||
final afterSelection = Selection.collapsed(
|
final afterSelection = Selection.collapsed(
|
||||||
Position(path: textNode.path, offset: 0),
|
Position(path: textNode.path, offset: 0),
|
||||||
);
|
);
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..updateNode(textNode, {
|
..updateNode(textNode, {
|
||||||
BuiltInAttributeKey.subtype: null,
|
BuiltInAttributeKey.subtype: null,
|
||||||
})
|
})
|
||||||
..afterSelection = afterSelection;
|
..afterSelection = afterSelection;
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
final nextNode = textNode.next;
|
final nextNode = textNode.next;
|
||||||
if (nextNode is TextNode &&
|
if (nextNode is TextNode &&
|
||||||
@ -105,13 +105,13 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler =
|
|||||||
BuiltInAttributeKey.numberList;
|
BuiltInAttributeKey.numberList;
|
||||||
newNode.attributes[BuiltInAttributeKey.number] = prevNumber;
|
newNode.attributes[BuiltInAttributeKey.number] = prevNumber;
|
||||||
final insertPath = textNode.path;
|
final insertPath = textNode.path;
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..insertNode(
|
..insertNode(
|
||||||
insertPath,
|
insertPath,
|
||||||
newNode,
|
newNode,
|
||||||
)
|
)
|
||||||
..afterSelection = afterSelection;
|
..afterSelection = afterSelection;
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
makeFollowingNodesIncremental(editorState, insertPath, afterSelection,
|
makeFollowingNodesIncremental(editorState, insertPath, afterSelection,
|
||||||
beginNum: prevNumber);
|
beginNum: prevNumber);
|
||||||
@ -120,7 +120,7 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler =
|
|||||||
BuiltInAttributeKey.heading,
|
BuiltInAttributeKey.heading,
|
||||||
BuiltInAttributeKey.quote,
|
BuiltInAttributeKey.quote,
|
||||||
].contains(subtype);
|
].contains(subtype);
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..insertNode(
|
..insertNode(
|
||||||
textNode.path,
|
textNode.path,
|
||||||
textNode.copyWith(
|
textNode.copyWith(
|
||||||
@ -130,7 +130,7 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler =
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
..afterSelection = afterSelection;
|
..afterSelection = afterSelection;
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
@ -163,7 +163,7 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler =
|
|||||||
transaction.deleteNodes(children);
|
transaction.deleteNodes(children);
|
||||||
}
|
}
|
||||||
transaction.afterSelection = afterSelection;
|
transaction.afterSelection = afterSelection;
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
// If the new type of a text node is number list,
|
// If the new type of a text node is number list,
|
||||||
// the numbers of the following nodes should be incremental.
|
// the numbers of the following nodes should be incremental.
|
||||||
|
@ -73,7 +73,7 @@ ShortcutEventHandler backquoteToCodeHandler = (editorState, event) {
|
|||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
|
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..deleteText(textNode, lastBackquoteIndex, 1)
|
..deleteText(textNode, lastBackquoteIndex, 1)
|
||||||
..deleteText(textNode, firstBackquoteIndex, 2)
|
..deleteText(textNode, firstBackquoteIndex, 2)
|
||||||
..formatText(
|
..formatText(
|
||||||
@ -90,7 +90,7 @@ ShortcutEventHandler backquoteToCodeHandler = (editorState, event) {
|
|||||||
offset: endIndex - 3,
|
offset: endIndex - 3,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
@ -104,7 +104,7 @@ ShortcutEventHandler backquoteToCodeHandler = (editorState, event) {
|
|||||||
// delete the backquote.
|
// delete the backquote.
|
||||||
// update the style of the text surround by ` ` to code.
|
// update the style of the text surround by ` ` to code.
|
||||||
// and update the cursor position.
|
// and update the cursor position.
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..deleteText(textNode, startIndex, 1)
|
..deleteText(textNode, startIndex, 1)
|
||||||
..formatText(
|
..formatText(
|
||||||
textNode,
|
textNode,
|
||||||
@ -120,7 +120,7 @@ ShortcutEventHandler backquoteToCodeHandler = (editorState, event) {
|
|||||||
offset: endIndex - 1,
|
offset: endIndex - 1,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
};
|
};
|
||||||
@ -166,7 +166,7 @@ ShortcutEventHandler doubleTildeToStrikethrough = (editorState, event) {
|
|||||||
// delete the last three tildes.
|
// delete the last three tildes.
|
||||||
// update the style of the text surround by `~~ ~~` to strikethrough.
|
// update the style of the text surround by `~~ ~~` to strikethrough.
|
||||||
// and update the cursor position.
|
// and update the cursor position.
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..deleteText(textNode, lastTildeIndex, 1)
|
..deleteText(textNode, lastTildeIndex, 1)
|
||||||
..deleteText(textNode, thirdToLastTildeIndex, 2)
|
..deleteText(textNode, thirdToLastTildeIndex, 2)
|
||||||
..formatText(
|
..formatText(
|
||||||
@ -183,7 +183,7 @@ ShortcutEventHandler doubleTildeToStrikethrough = (editorState, event) {
|
|||||||
offset: selection.end.offset - 3,
|
offset: selection.end.offset - 3,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
};
|
};
|
||||||
@ -220,7 +220,7 @@ ShortcutEventHandler markdownLinkToLinkHandler = (editorState, event) {
|
|||||||
// update the href attribute of the text surrounded by [ ] to the url,
|
// update the href attribute of the text surrounded by [ ] to the url,
|
||||||
// delete everything after the text,
|
// delete everything after the text,
|
||||||
// and update the cursor position.
|
// and update the cursor position.
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..deleteText(textNode, firstOpeningBracket, 1)
|
..deleteText(textNode, firstOpeningBracket, 1)
|
||||||
..formatText(
|
..formatText(
|
||||||
textNode,
|
textNode,
|
||||||
@ -238,7 +238,137 @@ ShortcutEventHandler markdownLinkToLinkHandler = (editorState, event) {
|
|||||||
offset: firstOpeningBracket + linkText!.length,
|
offset: firstOpeningBracket + linkText!.length,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
};
|
||||||
|
|
||||||
|
// convert **abc** to bold abc.
|
||||||
|
ShortcutEventHandler doubleAsterisksToBold = (editorState, event) {
|
||||||
|
final selectionService = editorState.service.selectionService;
|
||||||
|
final selection = selectionService.currentSelection.value;
|
||||||
|
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
|
||||||
|
if (selection == null || !selection.isSingle || textNodes.length != 1) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
final textNode = textNodes.first;
|
||||||
|
final text = textNode.toPlainText().substring(0, selection.end.offset);
|
||||||
|
|
||||||
|
// make sure the last two characters are **.
|
||||||
|
if (text.length < 2 || text[selection.end.offset - 1] != '*') {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find all the index of `*`.
|
||||||
|
final asteriskIndexes = <int>[];
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
if (text[i] == '*') {
|
||||||
|
asteriskIndexes.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asteriskIndexes.length < 3) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the second to last and third to last asterisks are connected.
|
||||||
|
final thirdToLastAsteriskIndex = asteriskIndexes[asteriskIndexes.length - 3];
|
||||||
|
final secondToLastAsteriskIndex = asteriskIndexes[asteriskIndexes.length - 2];
|
||||||
|
final lastAsterisIndex = asteriskIndexes[asteriskIndexes.length - 1];
|
||||||
|
if (secondToLastAsteriskIndex != thirdToLastAsteriskIndex + 1 ||
|
||||||
|
lastAsterisIndex == secondToLastAsteriskIndex + 1) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the last three asterisks.
|
||||||
|
// update the style of the text surround by `** **` to bold.
|
||||||
|
// and update the cursor position.
|
||||||
|
final transaction = editorState.transaction
|
||||||
|
..deleteText(textNode, lastAsterisIndex, 1)
|
||||||
|
..deleteText(textNode, thirdToLastAsteriskIndex, 2)
|
||||||
|
..formatText(
|
||||||
|
textNode,
|
||||||
|
thirdToLastAsteriskIndex,
|
||||||
|
selection.end.offset - thirdToLastAsteriskIndex - 3,
|
||||||
|
{
|
||||||
|
BuiltInAttributeKey.bold: true,
|
||||||
|
BuiltInAttributeKey.defaultFormating: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
..afterSelection = Selection.collapsed(
|
||||||
|
Position(
|
||||||
|
path: textNode.path,
|
||||||
|
offset: selection.end.offset - 3,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
editorState.apply(transaction);
|
||||||
|
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
};
|
||||||
|
|
||||||
|
// convert __abc__ to bold abc.
|
||||||
|
ShortcutEventHandler doubleUnderscoresToBold = (editorState, event) {
|
||||||
|
final selectionService = editorState.service.selectionService;
|
||||||
|
final selection = selectionService.currentSelection.value;
|
||||||
|
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
|
||||||
|
if (selection == null || !selection.isSingle || textNodes.length != 1) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
final textNode = textNodes.first;
|
||||||
|
final text = textNode.toPlainText().substring(0, selection.end.offset);
|
||||||
|
|
||||||
|
// make sure the last two characters are __.
|
||||||
|
if (text.length < 2 || text[selection.end.offset - 1] != '_') {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find all the index of `_`.
|
||||||
|
final underscoreIndexes = <int>[];
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
if (text[i] == '_') {
|
||||||
|
underscoreIndexes.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (underscoreIndexes.length < 3) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the second to last and third to last underscores are connected.
|
||||||
|
final thirdToLastUnderscoreIndex =
|
||||||
|
underscoreIndexes[underscoreIndexes.length - 3];
|
||||||
|
final secondToLastUnderscoreIndex =
|
||||||
|
underscoreIndexes[underscoreIndexes.length - 2];
|
||||||
|
final lastAsterisIndex = underscoreIndexes[underscoreIndexes.length - 1];
|
||||||
|
if (secondToLastUnderscoreIndex != thirdToLastUnderscoreIndex + 1 ||
|
||||||
|
lastAsterisIndex == secondToLastUnderscoreIndex + 1) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete the last three underscores.
|
||||||
|
// update the style of the text surround by `__ __` to bold.
|
||||||
|
// and update the cursor position.
|
||||||
|
final transaction = editorState.transaction
|
||||||
|
..deleteText(textNode, lastAsterisIndex, 1)
|
||||||
|
..deleteText(textNode, thirdToLastUnderscoreIndex, 2)
|
||||||
|
..formatText(
|
||||||
|
textNode,
|
||||||
|
thirdToLastUnderscoreIndex,
|
||||||
|
selection.end.offset - thirdToLastUnderscoreIndex - 3,
|
||||||
|
{
|
||||||
|
BuiltInAttributeKey.bold: true,
|
||||||
|
BuiltInAttributeKey.defaultFormating: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
..afterSelection = Selection.collapsed(
|
||||||
|
Position(
|
||||||
|
path: textNode.path,
|
||||||
|
offset: selection.end.offset - 3,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
editorState.apply(transaction);
|
||||||
|
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
};
|
};
|
||||||
|
@ -1,132 +0,0 @@
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
// convert **abc** to bold abc.
|
|
||||||
ShortcutEventHandler doubleAsterisksToBold = (editorState, event) {
|
|
||||||
final selectionService = editorState.service.selectionService;
|
|
||||||
final selection = selectionService.currentSelection.value;
|
|
||||||
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
|
|
||||||
if (selection == null || !selection.isSingle || textNodes.length != 1) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
final textNode = textNodes.first;
|
|
||||||
final text = textNode.toPlainText().substring(0, selection.end.offset);
|
|
||||||
|
|
||||||
// make sure the last two characters are **.
|
|
||||||
if (text.length < 2 || text[selection.end.offset - 1] != '*') {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find all the index of `*`.
|
|
||||||
final asteriskIndexes = <int>[];
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
if (text[i] == '*') {
|
|
||||||
asteriskIndexes.add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (asteriskIndexes.length < 3) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the second to last and third to last asterisks are connected.
|
|
||||||
final thirdToLastAsteriskIndex = asteriskIndexes[asteriskIndexes.length - 3];
|
|
||||||
final secondToLastAsteriskIndex = asteriskIndexes[asteriskIndexes.length - 2];
|
|
||||||
final lastAsterisIndex = asteriskIndexes[asteriskIndexes.length - 1];
|
|
||||||
if (secondToLastAsteriskIndex != thirdToLastAsteriskIndex + 1 ||
|
|
||||||
lastAsterisIndex == secondToLastAsteriskIndex + 1) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the last three asterisks.
|
|
||||||
// update the style of the text surround by `** **` to bold.
|
|
||||||
// and update the cursor position.
|
|
||||||
editorState.transaction
|
|
||||||
..deleteText(textNode, lastAsterisIndex, 1)
|
|
||||||
..deleteText(textNode, thirdToLastAsteriskIndex, 2)
|
|
||||||
..formatText(
|
|
||||||
textNode,
|
|
||||||
thirdToLastAsteriskIndex,
|
|
||||||
selection.end.offset - thirdToLastAsteriskIndex - 3,
|
|
||||||
{
|
|
||||||
BuiltInAttributeKey.bold: true,
|
|
||||||
BuiltInAttributeKey.defaultFormating: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
..afterSelection = Selection.collapsed(
|
|
||||||
Position(
|
|
||||||
path: textNode.path,
|
|
||||||
offset: selection.end.offset - 3,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
editorState.commit();
|
|
||||||
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
};
|
|
||||||
|
|
||||||
// convert __abc__ to bold abc.
|
|
||||||
ShortcutEventHandler doubleUnderscoresToBold = (editorState, event) {
|
|
||||||
final selectionService = editorState.service.selectionService;
|
|
||||||
final selection = selectionService.currentSelection.value;
|
|
||||||
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
|
|
||||||
if (selection == null || !selection.isSingle || textNodes.length != 1) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
final textNode = textNodes.first;
|
|
||||||
final text = textNode.toPlainText().substring(0, selection.end.offset);
|
|
||||||
|
|
||||||
// make sure the last two characters are __.
|
|
||||||
if (text.length < 2 || text[selection.end.offset - 1] != '_') {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find all the index of `_`.
|
|
||||||
final underscoreIndexes = <int>[];
|
|
||||||
for (var i = 0; i < text.length; i++) {
|
|
||||||
if (text[i] == '_') {
|
|
||||||
underscoreIndexes.add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (underscoreIndexes.length < 3) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the second to last and third to last underscores are connected.
|
|
||||||
final thirdToLastUnderscoreIndex =
|
|
||||||
underscoreIndexes[underscoreIndexes.length - 3];
|
|
||||||
final secondToLastUnderscoreIndex =
|
|
||||||
underscoreIndexes[underscoreIndexes.length - 2];
|
|
||||||
final lastAsterisIndex = underscoreIndexes[underscoreIndexes.length - 1];
|
|
||||||
if (secondToLastUnderscoreIndex != thirdToLastUnderscoreIndex + 1 ||
|
|
||||||
lastAsterisIndex == secondToLastUnderscoreIndex + 1) {
|
|
||||||
return KeyEventResult.ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the last three underscores.
|
|
||||||
// update the style of the text surround by `__ __` to bold.
|
|
||||||
// and update the cursor position.
|
|
||||||
editorState.transaction
|
|
||||||
..deleteText(textNode, lastAsterisIndex, 1)
|
|
||||||
..deleteText(textNode, thirdToLastUnderscoreIndex, 2)
|
|
||||||
..formatText(
|
|
||||||
textNode,
|
|
||||||
thirdToLastUnderscoreIndex,
|
|
||||||
selection.end.offset - thirdToLastUnderscoreIndex - 3,
|
|
||||||
{
|
|
||||||
BuiltInAttributeKey.bold: true,
|
|
||||||
BuiltInAttributeKey.defaultFormating: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
..afterSelection = Selection.collapsed(
|
|
||||||
Position(
|
|
||||||
path: textNode.path,
|
|
||||||
offset: selection.end.offset - 3,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
editorState.commit();
|
|
||||||
|
|
||||||
return KeyEventResult.handled;
|
|
||||||
};
|
|
@ -15,7 +15,7 @@ void makeFollowingNodesIncremental(
|
|||||||
int numPtr = beginNum + 1;
|
int numPtr = beginNum + 1;
|
||||||
var ptr = insertNode.next;
|
var ptr = insertNode.next;
|
||||||
|
|
||||||
final builder = editorState.transaction;
|
final transaction = editorState.transaction;
|
||||||
|
|
||||||
while (ptr != null) {
|
while (ptr != null) {
|
||||||
if (ptr.subtype != BuiltInAttributeKey.numberList) {
|
if (ptr.subtype != BuiltInAttributeKey.numberList) {
|
||||||
@ -25,13 +25,13 @@ void makeFollowingNodesIncremental(
|
|||||||
if (currentNum != numPtr) {
|
if (currentNum != numPtr) {
|
||||||
Attributes updateAttributes = {};
|
Attributes updateAttributes = {};
|
||||||
updateAttributes[BuiltInAttributeKey.number] = numPtr;
|
updateAttributes[BuiltInAttributeKey.number] = numPtr;
|
||||||
builder.updateNode(ptr, updateAttributes);
|
transaction.updateNode(ptr, updateAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr = ptr.next;
|
ptr = ptr.next;
|
||||||
numPtr++;
|
numPtr++;
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.afterSelection = afterSelection;
|
transaction.afterSelection = afterSelection;
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
}
|
}
|
||||||
|
@ -25,9 +25,10 @@ ShortcutEventHandler slashShortcutHandler = (editorState, event) {
|
|||||||
if (selection == null || context == null || selectable == null) {
|
if (selection == null || context == null || selectable == null) {
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
editorState.transaction.replaceText(textNode, selection.start.offset,
|
final transaction = editorState.transaction
|
||||||
|
..replaceText(textNode, selection.start.offset,
|
||||||
selection.end.offset - selection.start.offset, event.character ?? '');
|
selection.end.offset - selection.start.offset, event.character ?? '');
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
_selectionMenuService =
|
_selectionMenuService =
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_editor/src/commands/edit_text.dart';
|
import 'package:appflowy_editor/src/commands/text/text_commands.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@ -15,7 +15,11 @@ ShortcutEventHandler spaceOnWebHandler = (editorState, event) {
|
|||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
|
|
||||||
insertContextInText(editorState, selection.startIndex, ' ');
|
editorState.insertText(
|
||||||
|
selection.startIndex,
|
||||||
|
' ',
|
||||||
|
textNode: textNodes.first,
|
||||||
|
);
|
||||||
|
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
};
|
};
|
||||||
|
@ -15,8 +15,9 @@ ShortcutEventHandler tabHandler = (editorState, event) {
|
|||||||
final previous = textNode.previous;
|
final previous = textNode.previous;
|
||||||
|
|
||||||
if (textNode.subtype != BuiltInAttributeKey.bulletedList) {
|
if (textNode.subtype != BuiltInAttributeKey.bulletedList) {
|
||||||
editorState.transaction.insertText(textNode, selection.end.offset, ' ' * 4);
|
final transaction = editorState.transaction
|
||||||
editorState.commit();
|
..insertText(textNode, selection.end.offset, ' ' * 4);
|
||||||
|
editorState.apply(transaction);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,11 +31,11 @@ ShortcutEventHandler tabHandler = (editorState, event) {
|
|||||||
start: selection.start.copyWith(path: path),
|
start: selection.start.copyWith(path: path),
|
||||||
end: selection.end.copyWith(path: path),
|
end: selection.end.copyWith(path: path),
|
||||||
);
|
);
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..deleteNode(textNode)
|
..deleteNode(textNode)
|
||||||
..insertNode(path, textNode)
|
..insertNode(path, textNode)
|
||||||
..afterSelection = afterSelection;
|
..afterSelection = afterSelection;
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
};
|
};
|
||||||
|
@ -99,14 +99,14 @@ KeyEventResult _toNumberList(EditorState editorState, TextNode textNode,
|
|||||||
));
|
));
|
||||||
|
|
||||||
final insertPath = textNode.path;
|
final insertPath = textNode.path;
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..deleteText(textNode, 0, matchText.length)
|
..deleteText(textNode, 0, matchText.length)
|
||||||
..updateNode(textNode, {
|
..updateNode(textNode, {
|
||||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.numberList,
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.numberList,
|
||||||
BuiltInAttributeKey.number: numValue
|
BuiltInAttributeKey.number: numValue
|
||||||
})
|
})
|
||||||
..afterSelection = afterSelection;
|
..afterSelection = afterSelection;
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
|
|
||||||
makeFollowingNodesIncremental(editorState, insertPath, afterSelection);
|
makeFollowingNodesIncremental(editorState, insertPath, afterSelection);
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ KeyEventResult _toBulletedList(EditorState editorState, TextNode textNode) {
|
|||||||
if (textNode.subtype == BuiltInAttributeKey.bulletedList) {
|
if (textNode.subtype == BuiltInAttributeKey.bulletedList) {
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..deleteText(textNode, 0, 1)
|
..deleteText(textNode, 0, 1)
|
||||||
..updateNode(textNode, {
|
..updateNode(textNode, {
|
||||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.bulletedList,
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.bulletedList,
|
||||||
@ -128,7 +128,7 @@ KeyEventResult _toBulletedList(EditorState editorState, TextNode textNode) {
|
|||||||
offset: 0,
|
offset: 0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +150,7 @@ KeyEventResult _toCheckboxList(EditorState editorState, TextNode textNode) {
|
|||||||
check = false;
|
check = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..deleteText(textNode, 0, symbol.length)
|
..deleteText(textNode, 0, symbol.length)
|
||||||
..updateNode(textNode, {
|
..updateNode(textNode, {
|
||||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox,
|
||||||
@ -162,7 +162,7 @@ KeyEventResult _toCheckboxList(EditorState editorState, TextNode textNode) {
|
|||||||
offset: 0,
|
offset: 0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ KeyEventResult _toHeadingStyle(
|
|||||||
if (textNode.attributes.heading == hX) {
|
if (textNode.attributes.heading == hX) {
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
editorState.transaction
|
final transaction = editorState.transaction
|
||||||
..deleteText(textNode, 0, x)
|
..deleteText(textNode, 0, x)
|
||||||
..updateNode(textNode, {
|
..updateNode(textNode, {
|
||||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
|
BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading,
|
||||||
@ -188,7 +188,7 @@ KeyEventResult _toHeadingStyle(
|
|||||||
offset: 0,
|
offset: 0,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
editorState.commit();
|
editorState.apply(transaction);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import 'package:appflowy_editor/src/service/internal_key_event_handlers/arrow_ke
|
|||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/backspace_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/backspace_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/copy_paste_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text_handler.dart';
|
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/page_up_down_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/page_up_down_handler.dart';
|
||||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/redo_undo_handler.dart';
|
import 'package:appflowy_editor/src/service/internal_key_event_handlers/redo_undo_handler.dart';
|
||||||
|
@ -66,7 +66,7 @@ void main() {
|
|||||||
transaction.deleteNode(item1);
|
transaction.deleteNode(item1);
|
||||||
transaction.deleteNode(item2);
|
transaction.deleteNode(item2);
|
||||||
transaction.deleteNode(item3);
|
transaction.deleteNode(item3);
|
||||||
state.commit();
|
state.apply(transaction);
|
||||||
expect(transaction.operations[0].path, [0]);
|
expect(transaction.operations[0].path, [0]);
|
||||||
expect(transaction.operations[1].path, [0]);
|
expect(transaction.operations[1].path, [0]);
|
||||||
expect(transaction.operations[2].path, [0]);
|
expect(transaction.operations[2].path, [0]);
|
||||||
@ -79,7 +79,7 @@ void main() {
|
|||||||
final item1 = Node(type: "node", attributes: {}, children: LinkedList());
|
final item1 = Node(type: "node", attributes: {}, children: LinkedList());
|
||||||
final transaction = state.transaction;
|
final transaction = state.transaction;
|
||||||
transaction.insertNode([0], item1);
|
transaction.insertNode([0], item1);
|
||||||
state.commit();
|
state.apply(transaction);
|
||||||
expect(transaction.toJson(), {
|
expect(transaction.toJson(), {
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
@ -103,7 +103,7 @@ void main() {
|
|||||||
final state = EditorState(document: Document(root: root));
|
final state = EditorState(document: Document(root: root));
|
||||||
final transaction = state.transaction;
|
final transaction = state.transaction;
|
||||||
transaction.deleteNode(item1);
|
transaction.deleteNode(item1);
|
||||||
state.commit();
|
state.apply(transaction);
|
||||||
expect(transaction.toJson(), {
|
expect(transaction.toJson(), {
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user