mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: release/0.1.1 known issues. (#1984)
* fix: #1976 Adding a cover image via upload doesn't work * fix: #1973 Using the mouse to highlight text very easy to miss the first letter * fix: #1962 Disable but still show the AI assistants icon in the toolbar menu when an OpenAI key is not provided * fix: #1964 Text on the UI * fix: #1966 the loading icon too close to the edge * fix: #1967 the summarize feature generates duplicate answers * fix: flutter analyze
This commit is contained in:
parent
ad5213cfad
commit
3686eabcb3
@ -344,16 +344,18 @@
|
|||||||
"referencedGrid": "Referenced Grid",
|
"referencedGrid": "Referenced Grid",
|
||||||
"autoCompletionMenuItemName": "Auto Completion",
|
"autoCompletionMenuItemName": "Auto Completion",
|
||||||
"autoGeneratorMenuItemName": "Auto Generator",
|
"autoGeneratorMenuItemName": "Auto Generator",
|
||||||
"autoGeneratorTitleName": "Open AI: Auto Generator",
|
"autoGeneratorTitleName": "OpenAI: Ask AI to write anything...",
|
||||||
"autoGeneratorLearnMore": "Learn more",
|
"autoGeneratorLearnMore": "Learn more",
|
||||||
"autoGeneratorGenerate": "Generate",
|
"autoGeneratorGenerate": "Generate",
|
||||||
"autoGeneratorHintText": "Tell us what you want to generate by OpenAI ...",
|
"autoGeneratorHintText": "Tell us what you want to generate by OpenAI ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Can't get OpenAI key",
|
"autoGeneratorCantGetOpenAIKey": "Can't get OpenAI key",
|
||||||
"smartEditTitleName": "Open AI: Smart Edit",
|
"smartEdit": "Smart Edit",
|
||||||
|
"smartEditTitleName": "OpenAI: Smart Edit",
|
||||||
"smartEditFixSpelling": "Fix spelling",
|
"smartEditFixSpelling": "Fix spelling",
|
||||||
"smartEditSummarize": "Summarize",
|
"smartEditSummarize": "Summarize",
|
||||||
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
|
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
|
||||||
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
|
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
|
||||||
|
"smartEditDisabled": "Connect OpenAI in Settings",
|
||||||
"cover": {
|
"cover": {
|
||||||
"changeCover": "Change Cover",
|
"changeCover": "Change Cover",
|
||||||
"colors": "Colors",
|
"colors": "Colors",
|
||||||
|
@ -183,9 +183,7 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
toolbarItems: [
|
toolbarItems: [
|
||||||
if (openAIKey != null && openAIKey!.isNotEmpty) ...[
|
|
||||||
smartEditItem,
|
smartEditItem,
|
||||||
]
|
|
||||||
],
|
],
|
||||||
themeData: theme.copyWith(extensions: [
|
themeData: theme.copyWith(extensions: [
|
||||||
...theme.extensions.values,
|
...theme.extensions.values,
|
||||||
|
@ -16,6 +16,7 @@ import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
|||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
const String kLocalImagesKey = 'local_images';
|
const String kLocalImagesKey = 'local_images';
|
||||||
|
|
||||||
@ -263,7 +264,7 @@ class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
|
|||||||
if (path != null) {
|
if (path != null) {
|
||||||
final directory = await _coverPath();
|
final directory = await _coverPath();
|
||||||
final newPath = await File(path).copy(
|
final newPath = await File(path).copy(
|
||||||
'$directory/${path.split('/').last}',
|
'$directory/${path.split(path).last}}',
|
||||||
);
|
);
|
||||||
imageNames.add(newPath.path);
|
imageNames.add(newPath.path);
|
||||||
}
|
}
|
||||||
@ -274,7 +275,7 @@ class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
|
|||||||
|
|
||||||
Future<String> _coverPath() async {
|
Future<String> _coverPath() async {
|
||||||
final directory = await getIt<SettingsLocationCubit>().fetchLocation();
|
final directory = await getIt<SettingsLocationCubit>().fetchLocation();
|
||||||
return Directory('$directory/covers')
|
return Directory(path.join(directory, 'covers'))
|
||||||
.create(recursive: true)
|
.create(recursive: true)
|
||||||
.then((value) => value.path);
|
.then((value) => value.path);
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@ enum SmartEditAction {
|
|||||||
String get toInstruction {
|
String get toInstruction {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case SmartEditAction.summarize:
|
case SmartEditAction.summarize:
|
||||||
return 'Make it shorter';
|
return 'Make this shorter and more concise:';
|
||||||
case SmartEditAction.fixSpelling:
|
case SmartEditAction.fixSpelling:
|
||||||
return 'Fix all the spelling mistakes';
|
return 'Correct this to standard English:';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,9 +140,12 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildResultWidget(BuildContext context) {
|
Widget _buildResultWidget(BuildContext context) {
|
||||||
final loading = SizedBox.fromSize(
|
final loading = Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
|
child: SizedBox.fromSize(
|
||||||
size: const Size.square(14),
|
size: const Size.square(14),
|
||||||
child: const CircularProgressIndicator(),
|
child: const CircularProgressIndicator(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
return loading;
|
return loading;
|
||||||
@ -222,7 +225,6 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
|
|
||||||
final texts = result!.asRight().choices.first.text.split('\n')
|
final texts = result!.asRight().choices.first.text.split('\n')
|
||||||
..removeWhere((element) => element.isEmpty);
|
..removeWhere((element) => element.isEmpty);
|
||||||
assert(texts.length == selectedNodes.length);
|
|
||||||
final transaction = widget.editorState.transaction;
|
final transaction = widget.editorState.transaction;
|
||||||
transaction.replaceTexts(
|
transaction.replaceTexts(
|
||||||
selectedNodes.toList(growable: false),
|
selectedNodes.toList(growable: false),
|
||||||
@ -254,7 +256,7 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
final edits = await openAIRepository.getEdits(
|
final edits = await openAIRepository.getEdits(
|
||||||
input: input,
|
input: input,
|
||||||
instruction: instruction,
|
instruction: instruction,
|
||||||
n: input.split('\n').length,
|
n: 1,
|
||||||
);
|
);
|
||||||
return edits.fold((error) async {
|
return edits.fold((error) async {
|
||||||
return dartz.Left(
|
return dartz.Left(
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_action.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_action.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_node_widget.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_node_widget.dart';
|
||||||
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
ToolbarItem smartEditItem = ToolbarItem(
|
ToolbarItem smartEditItem = ToolbarItem(
|
||||||
id: 'appflowy.toolbar.smart_edit',
|
id: 'appflowy.toolbar.smart_edit',
|
||||||
@ -33,6 +37,20 @@ class _SmartEditWidget extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SmartEditWidgetState extends State<_SmartEditWidget> {
|
class _SmartEditWidgetState extends State<_SmartEditWidget> {
|
||||||
|
bool isOpenAIEnabled = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
UserBackendService.getCurrentUserProfile().then((value) {
|
||||||
|
setState(() {
|
||||||
|
isOpenAIEnabled =
|
||||||
|
value.fold((l) => l.openaiKey.isNotEmpty, (r) => false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return PopoverActionList<SmartEditActionWrapper>(
|
return PopoverActionList<SmartEditActionWrapper>(
|
||||||
@ -43,7 +61,9 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
|
|||||||
buildChild: (controller) {
|
buildChild: (controller) {
|
||||||
return FlowyIconButton(
|
return FlowyIconButton(
|
||||||
hoverColor: Colors.transparent,
|
hoverColor: Colors.transparent,
|
||||||
tooltipText: 'Smart Edit',
|
tooltipText: isOpenAIEnabled
|
||||||
|
? LocaleKeys.document_plugins_smartEdit.tr()
|
||||||
|
: LocaleKeys.document_plugins_smartEditDisabled.tr(),
|
||||||
preferBelow: false,
|
preferBelow: false,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.lightbulb_outline,
|
Icons.lightbulb_outline,
|
||||||
@ -51,7 +71,11 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
if (isOpenAIEnabled) {
|
||||||
controller.show();
|
controller.show();
|
||||||
|
} else {
|
||||||
|
_showError(LocaleKeys.document_plugins_smartEditDisabled.tr());
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -97,4 +121,18 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
|
|||||||
withUpdateCursor: false,
|
withUpdateCursor: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _showError(String message) async {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
action: SnackBarAction(
|
||||||
|
label: LocaleKeys.button_Cancel.tr(),
|
||||||
|
onPressed: () {
|
||||||
|
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
content: FlowyText(message),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:appflowy_backend/appflowy_backend.dart';
|
import 'package:appflowy_backend/appflowy_backend.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
import '../startup.dart';
|
import '../startup.dart';
|
||||||
|
|
||||||
@ -35,11 +36,11 @@ Future<Directory> appFlowyDocumentDirectory() async {
|
|||||||
switch (integrationEnv()) {
|
switch (integrationEnv()) {
|
||||||
case IntegrationMode.develop:
|
case IntegrationMode.develop:
|
||||||
Directory documentsDir = await getApplicationDocumentsDirectory();
|
Directory documentsDir = await getApplicationDocumentsDirectory();
|
||||||
return Directory('${documentsDir.path}/flowy_dev').create();
|
return Directory(path.join(documentsDir.path, 'flowy_dev')).create();
|
||||||
case IntegrationMode.release:
|
case IntegrationMode.release:
|
||||||
Directory documentsDir = await getApplicationDocumentsDirectory();
|
Directory documentsDir = await getApplicationDocumentsDirectory();
|
||||||
return Directory('${documentsDir.path}/flowy').create();
|
return Directory(path.join(documentsDir.path, 'flowy')).create();
|
||||||
case IntegrationMode.test:
|
case IntegrationMode.test:
|
||||||
return Directory("${Directory.current.path}/.sandbox");
|
return Directory(path.join(Directory.current.path, '.sandbox'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,10 @@ extension CommandExtension on EditorState {
|
|||||||
Selection selection,
|
Selection selection,
|
||||||
) {
|
) {
|
||||||
List<String> res = [];
|
List<String> res = [];
|
||||||
if (!selection.isCollapsed) {
|
if (selection.isSingle) {
|
||||||
|
final plainText = textNodes.first.toPlainText();
|
||||||
|
res.add(plainText.substring(selection.startIndex, selection.endIndex));
|
||||||
|
} else if (!selection.isCollapsed) {
|
||||||
for (var i = 0; i < textNodes.length; i++) {
|
for (var i = 0; i < textNodes.length; i++) {
|
||||||
final plainText = textNodes[i].toPlainText();
|
final plainText = textNodes[i].toPlainText();
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
|
@ -291,46 +291,125 @@ extension TextTransaction on Transaction {
|
|||||||
Selection selection,
|
Selection selection,
|
||||||
List<String> texts,
|
List<String> texts,
|
||||||
) {
|
) {
|
||||||
if (textNodes.isEmpty) {
|
if (textNodes.isEmpty || texts.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selection.isSingle) {
|
if (textNodes.length == texts.length) {
|
||||||
assert(textNodes.length == 1 && texts.length == 1);
|
final length = textNodes.length;
|
||||||
|
|
||||||
|
if (length == 1) {
|
||||||
replaceText(
|
replaceText(
|
||||||
textNodes.first,
|
textNodes.first,
|
||||||
selection.startIndex,
|
selection.startIndex,
|
||||||
selection.length,
|
selection.endIndex - selection.startIndex,
|
||||||
texts.first,
|
texts.first,
|
||||||
);
|
);
|
||||||
} else {
|
return;
|
||||||
final length = textNodes.length;
|
}
|
||||||
for (var i = 0; i < length; i++) {
|
|
||||||
|
for (var i = 0; i < textNodes.length; i++) {
|
||||||
final textNode = textNodes[i];
|
final textNode = textNodes[i];
|
||||||
final text = texts[i];
|
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
replaceText(
|
replaceText(
|
||||||
textNode,
|
textNode,
|
||||||
selection.startIndex,
|
selection.startIndex,
|
||||||
textNode.toPlainText().length,
|
textNode.toPlainText().length,
|
||||||
text,
|
texts.first,
|
||||||
);
|
);
|
||||||
} else if (i == length - 1) {
|
} else if (i == length - 1) {
|
||||||
replaceText(
|
replaceText(
|
||||||
textNode,
|
textNode,
|
||||||
0,
|
0,
|
||||||
selection.endIndex,
|
selection.endIndex,
|
||||||
text,
|
texts.last,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
replaceText(
|
replaceText(
|
||||||
textNode,
|
textNode,
|
||||||
0,
|
0,
|
||||||
textNode.toPlainText().length,
|
textNode.toPlainText().length,
|
||||||
text,
|
texts[i],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textNodes.length > texts.length) {
|
||||||
|
final length = textNodes.length;
|
||||||
|
for (var i = 0; i < textNodes.length; i++) {
|
||||||
|
final textNode = textNodes[i];
|
||||||
|
if (i == 0) {
|
||||||
|
replaceText(
|
||||||
|
textNode,
|
||||||
|
selection.startIndex,
|
||||||
|
textNode.toPlainText().length,
|
||||||
|
texts.first,
|
||||||
|
);
|
||||||
|
} else if (i == length - 1) {
|
||||||
|
replaceText(
|
||||||
|
textNode,
|
||||||
|
0,
|
||||||
|
selection.endIndex,
|
||||||
|
texts.last,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (i < texts.length - 1) {
|
||||||
|
replaceText(
|
||||||
|
textNode,
|
||||||
|
0,
|
||||||
|
textNode.toPlainText().length,
|
||||||
|
texts[i],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
deleteNode(textNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
afterSelection = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (textNodes.length < texts.length) {
|
||||||
|
final length = texts.length;
|
||||||
|
for (var i = 0; i < texts.length; i++) {
|
||||||
|
final text = texts[i];
|
||||||
|
if (i == 0) {
|
||||||
|
replaceText(
|
||||||
|
textNodes.first,
|
||||||
|
selection.startIndex,
|
||||||
|
textNodes.first.toPlainText().length,
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
} else if (i == length - 1) {
|
||||||
|
replaceText(
|
||||||
|
textNodes.last,
|
||||||
|
0,
|
||||||
|
selection.endIndex,
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (i < textNodes.length - 1) {
|
||||||
|
replaceText(
|
||||||
|
textNodes[i],
|
||||||
|
0,
|
||||||
|
textNodes[i].toPlainText().length,
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
var path = textNodes.first.path;
|
||||||
|
var j = i - textNodes.length + length - 1;
|
||||||
|
while (j > 0) {
|
||||||
|
path = path.next;
|
||||||
|
j--;
|
||||||
|
}
|
||||||
|
insertNode(path, TextNode(delta: Delta()..insert(text)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
afterSelection = null;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,6 +112,7 @@ class _AppFlowyKeyboardState extends State<AppFlowyKeyboard>
|
|||||||
isFocus = false;
|
isFocus = false;
|
||||||
this.showCursor = showCursor;
|
this.showCursor = showCursor;
|
||||||
_focusNode.unfocus(disposition: disposition);
|
_focusNode.unfocus(disposition: disposition);
|
||||||
|
_onFocusChange(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -347,8 +347,9 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
|
|||||||
|
|
||||||
void _onPanStart(DragStartDetails details) {
|
void _onPanStart(DragStartDetails details) {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
|
_clearToolbar();
|
||||||
|
|
||||||
_panStartOffset = details.globalPosition;
|
_panStartOffset = details.globalPosition.translate(-3.0, 0);
|
||||||
_panStartScrollDy = editorState.service.scrollService?.dy;
|
_panStartScrollDy = editorState.service.scrollService?.dy;
|
||||||
|
|
||||||
_enableInteraction();
|
_enableInteraction();
|
||||||
|
@ -0,0 +1,132 @@
|
|||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import '../../infra/test_editor.dart';
|
||||||
|
|
||||||
|
Document createEmptyDocument() {
|
||||||
|
return Document(
|
||||||
|
root: Node(
|
||||||
|
type: 'editor',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
group('transaction.dart', () {
|
||||||
|
testWidgets('test replaceTexts, textNodes.length == texts.length',
|
||||||
|
(tester) async {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertTextNode('0123456789')
|
||||||
|
..insertTextNode('0123456789')
|
||||||
|
..insertTextNode('0123456789')
|
||||||
|
..insertTextNode('0123456789');
|
||||||
|
await editor.startTesting();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(editor.documentLength, 4);
|
||||||
|
|
||||||
|
final selection = Selection(
|
||||||
|
start: Position(path: [0], offset: 4),
|
||||||
|
end: Position(path: [3], offset: 4),
|
||||||
|
);
|
||||||
|
final transaction = editor.editorState.transaction;
|
||||||
|
var textNodes = [0, 1, 2, 3]
|
||||||
|
.map((e) => editor.nodeAtPath([e])!)
|
||||||
|
.whereType<TextNode>()
|
||||||
|
.toList(growable: false);
|
||||||
|
final texts = ['ABC', 'ABC', 'ABC', 'ABC'];
|
||||||
|
transaction.replaceTexts(textNodes, selection, texts);
|
||||||
|
editor.editorState.apply(transaction);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(editor.documentLength, 4);
|
||||||
|
textNodes = [0, 1, 2, 3]
|
||||||
|
.map((e) => editor.nodeAtPath([e])!)
|
||||||
|
.whereType<TextNode>()
|
||||||
|
.toList(growable: false);
|
||||||
|
expect(textNodes[0].toPlainText(), '0123ABC');
|
||||||
|
expect(textNodes[1].toPlainText(), 'ABC');
|
||||||
|
expect(textNodes[2].toPlainText(), 'ABC');
|
||||||
|
expect(textNodes[3].toPlainText(), 'ABC456789');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('test replaceTexts, textNodes.length > texts.length',
|
||||||
|
(tester) async {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertTextNode('0123456789')
|
||||||
|
..insertTextNode('0123456789')
|
||||||
|
..insertTextNode('0123456789')
|
||||||
|
..insertTextNode('0123456789')
|
||||||
|
..insertTextNode('0123456789');
|
||||||
|
await editor.startTesting();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(editor.documentLength, 5);
|
||||||
|
|
||||||
|
final selection = Selection(
|
||||||
|
start: Position(path: [0], offset: 4),
|
||||||
|
end: Position(path: [4], offset: 4),
|
||||||
|
);
|
||||||
|
final transaction = editor.editorState.transaction;
|
||||||
|
var textNodes = [0, 1, 2, 3, 4]
|
||||||
|
.map((e) => editor.nodeAtPath([e])!)
|
||||||
|
.whereType<TextNode>()
|
||||||
|
.toList(growable: false);
|
||||||
|
final texts = ['ABC', 'ABC', 'ABC', 'ABC'];
|
||||||
|
transaction.replaceTexts(textNodes, selection, texts);
|
||||||
|
editor.editorState.apply(transaction);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(editor.documentLength, 4);
|
||||||
|
textNodes = [0, 1, 2, 3]
|
||||||
|
.map((e) => editor.nodeAtPath([e])!)
|
||||||
|
.whereType<TextNode>()
|
||||||
|
.toList(growable: false);
|
||||||
|
expect(textNodes[0].toPlainText(), '0123ABC');
|
||||||
|
expect(textNodes[1].toPlainText(), 'ABC');
|
||||||
|
expect(textNodes[2].toPlainText(), 'ABC');
|
||||||
|
expect(textNodes[3].toPlainText(), 'ABC456789');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('test replaceTexts, textNodes.length < texts.length',
|
||||||
|
(tester) async {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
final editor = tester.editor
|
||||||
|
..insertTextNode('0123456789')
|
||||||
|
..insertTextNode('0123456789')
|
||||||
|
..insertTextNode('0123456789');
|
||||||
|
await editor.startTesting();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(editor.documentLength, 3);
|
||||||
|
|
||||||
|
final selection = Selection(
|
||||||
|
start: Position(path: [0], offset: 4),
|
||||||
|
end: Position(path: [2], offset: 4),
|
||||||
|
);
|
||||||
|
final transaction = editor.editorState.transaction;
|
||||||
|
var textNodes = [0, 1, 2]
|
||||||
|
.map((e) => editor.nodeAtPath([e])!)
|
||||||
|
.whereType<TextNode>()
|
||||||
|
.toList(growable: false);
|
||||||
|
final texts = ['ABC', 'ABC', 'ABC', 'ABC'];
|
||||||
|
transaction.replaceTexts(textNodes, selection, texts);
|
||||||
|
editor.editorState.apply(transaction);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(editor.documentLength, 4);
|
||||||
|
textNodes = [0, 1, 2, 3]
|
||||||
|
.map((e) => editor.nodeAtPath([e])!)
|
||||||
|
.whereType<TextNode>()
|
||||||
|
.toList(growable: false);
|
||||||
|
expect(textNodes[0].toPlainText(), '0123ABC');
|
||||||
|
expect(textNodes[1].toPlainText(), 'ABC');
|
||||||
|
expect(textNodes[2].toPlainText(), 'ABC');
|
||||||
|
expect(textNodes[3].toPlainText(), 'ABC456789');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -830,7 +830,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.5"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
|
@ -95,6 +95,7 @@ dependencies:
|
|||||||
window_manager: ^0.3.0
|
window_manager: ^0.3.0
|
||||||
http: ^0.13.5
|
http: ^0.13.5
|
||||||
json_annotation: ^4.7.0
|
json_annotation: ^4.7.0
|
||||||
|
path: ^1.8.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^2.0.1
|
flutter_lints: ^2.0.1
|
||||||
|
Loading…
Reference in New Issue
Block a user