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",
|
||||
"autoCompletionMenuItemName": "Auto Completion",
|
||||
"autoGeneratorMenuItemName": "Auto Generator",
|
||||
"autoGeneratorTitleName": "Open AI: Auto Generator",
|
||||
"autoGeneratorTitleName": "OpenAI: Ask AI to write anything...",
|
||||
"autoGeneratorLearnMore": "Learn more",
|
||||
"autoGeneratorGenerate": "Generate",
|
||||
"autoGeneratorHintText": "Tell us what you want to generate by OpenAI ...",
|
||||
"autoGeneratorCantGetOpenAIKey": "Can't get OpenAI key",
|
||||
"smartEditTitleName": "Open AI: Smart Edit",
|
||||
"smartEdit": "Smart Edit",
|
||||
"smartEditTitleName": "OpenAI: Smart Edit",
|
||||
"smartEditFixSpelling": "Fix spelling",
|
||||
"smartEditSummarize": "Summarize",
|
||||
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
|
||||
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
|
||||
"smartEditDisabled": "Connect OpenAI in Settings",
|
||||
"cover": {
|
||||
"changeCover": "Change Cover",
|
||||
"colors": "Colors",
|
||||
|
@ -183,9 +183,7 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
|
||||
],
|
||||
],
|
||||
toolbarItems: [
|
||||
if (openAIKey != null && openAIKey!.isNotEmpty) ...[
|
||||
smartEditItem,
|
||||
]
|
||||
smartEditItem,
|
||||
],
|
||||
themeData: theme.copyWith(extensions: [
|
||||
...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:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
const String kLocalImagesKey = 'local_images';
|
||||
|
||||
@ -263,7 +264,7 @@ class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
|
||||
if (path != null) {
|
||||
final directory = await _coverPath();
|
||||
final newPath = await File(path).copy(
|
||||
'$directory/${path.split('/').last}',
|
||||
'$directory/${path.split(path).last}}',
|
||||
);
|
||||
imageNames.add(newPath.path);
|
||||
}
|
||||
@ -274,7 +275,7 @@ class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
|
||||
|
||||
Future<String> _coverPath() async {
|
||||
final directory = await getIt<SettingsLocationCubit>().fetchLocation();
|
||||
return Directory('$directory/covers')
|
||||
return Directory(path.join(directory, 'covers'))
|
||||
.create(recursive: true)
|
||||
.then((value) => value.path);
|
||||
}
|
||||
|
@ -10,9 +10,9 @@ enum SmartEditAction {
|
||||
String get toInstruction {
|
||||
switch (this) {
|
||||
case SmartEditAction.summarize:
|
||||
return 'Make it shorter';
|
||||
return 'Make this shorter and more concise:';
|
||||
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) {
|
||||
final loading = SizedBox.fromSize(
|
||||
size: const Size.square(14),
|
||||
child: const CircularProgressIndicator(),
|
||||
final loading = Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: SizedBox.fromSize(
|
||||
size: const Size.square(14),
|
||||
child: const CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
if (result == null) {
|
||||
return loading;
|
||||
@ -222,7 +225,6 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
||||
|
||||
final texts = result!.asRight().choices.first.text.split('\n')
|
||||
..removeWhere((element) => element.isEmpty);
|
||||
assert(texts.length == selectedNodes.length);
|
||||
final transaction = widget.editorState.transaction;
|
||||
transaction.replaceTexts(
|
||||
selectedNodes.toList(growable: false),
|
||||
@ -254,7 +256,7 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
||||
final edits = await openAIRepository.getEdits(
|
||||
input: input,
|
||||
instruction: instruction,
|
||||
n: input.split('\n').length,
|
||||
n: 1,
|
||||
);
|
||||
return edits.fold((error) async {
|
||||
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_node_widget.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.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/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
ToolbarItem smartEditItem = ToolbarItem(
|
||||
id: 'appflowy.toolbar.smart_edit',
|
||||
@ -33,6 +37,20 @@ class _SmartEditWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return PopoverActionList<SmartEditActionWrapper>(
|
||||
@ -43,7 +61,9 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
|
||||
buildChild: (controller) {
|
||||
return FlowyIconButton(
|
||||
hoverColor: Colors.transparent,
|
||||
tooltipText: 'Smart Edit',
|
||||
tooltipText: isOpenAIEnabled
|
||||
? LocaleKeys.document_plugins_smartEdit.tr()
|
||||
: LocaleKeys.document_plugins_smartEditDisabled.tr(),
|
||||
preferBelow: false,
|
||||
icon: const Icon(
|
||||
Icons.lightbulb_outline,
|
||||
@ -51,7 +71,11 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () {
|
||||
controller.show();
|
||||
if (isOpenAIEnabled) {
|
||||
controller.show();
|
||||
} else {
|
||||
_showError(LocaleKeys.document_plugins_smartEditDisabled.tr());
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -97,4 +121,18 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
|
||||
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:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import '../startup.dart';
|
||||
|
||||
@ -35,11 +36,11 @@ Future<Directory> appFlowyDocumentDirectory() async {
|
||||
switch (integrationEnv()) {
|
||||
case IntegrationMode.develop:
|
||||
Directory documentsDir = await getApplicationDocumentsDirectory();
|
||||
return Directory('${documentsDir.path}/flowy_dev').create();
|
||||
return Directory(path.join(documentsDir.path, 'flowy_dev')).create();
|
||||
case IntegrationMode.release:
|
||||
Directory documentsDir = await getApplicationDocumentsDirectory();
|
||||
return Directory('${documentsDir.path}/flowy').create();
|
||||
return Directory(path.join(documentsDir.path, 'flowy')).create();
|
||||
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,
|
||||
) {
|
||||
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++) {
|
||||
final plainText = textNodes[i].toPlainText();
|
||||
if (i == 0) {
|
||||
|
@ -291,46 +291,125 @@ extension TextTransaction on Transaction {
|
||||
Selection selection,
|
||||
List<String> texts,
|
||||
) {
|
||||
if (textNodes.isEmpty) {
|
||||
if (textNodes.isEmpty || texts.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selection.isSingle) {
|
||||
assert(textNodes.length == 1 && texts.length == 1);
|
||||
replaceText(
|
||||
textNodes.first,
|
||||
selection.startIndex,
|
||||
selection.length,
|
||||
texts.first,
|
||||
);
|
||||
} else {
|
||||
if (textNodes.length == texts.length) {
|
||||
final length = textNodes.length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
|
||||
if (length == 1) {
|
||||
replaceText(
|
||||
textNodes.first,
|
||||
selection.startIndex,
|
||||
selection.endIndex - selection.startIndex,
|
||||
texts.first,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < textNodes.length; i++) {
|
||||
final textNode = textNodes[i];
|
||||
final text = texts[i];
|
||||
if (i == 0) {
|
||||
replaceText(
|
||||
textNode,
|
||||
selection.startIndex,
|
||||
textNode.toPlainText().length,
|
||||
text,
|
||||
texts.first,
|
||||
);
|
||||
} else if (i == length - 1) {
|
||||
replaceText(
|
||||
textNode,
|
||||
0,
|
||||
selection.endIndex,
|
||||
text,
|
||||
texts.last,
|
||||
);
|
||||
} else {
|
||||
replaceText(
|
||||
textNode,
|
||||
0,
|
||||
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;
|
||||
this.showCursor = showCursor;
|
||||
_focusNode.unfocus(disposition: disposition);
|
||||
_onFocusChange(false);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -347,8 +347,9 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
|
||||
|
||||
void _onPanStart(DragStartDetails details) {
|
||||
clearSelection();
|
||||
_clearToolbar();
|
||||
|
||||
_panStartOffset = details.globalPosition;
|
||||
_panStartOffset = details.globalPosition.translate(-3.0, 0);
|
||||
_panStartScrollDy = editorState.service.scrollService?.dy;
|
||||
|
||||
_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
|
||||
version: "1.0.5"
|
||||
path:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
url: "https://pub.dartlang.org"
|
||||
|
@ -95,6 +95,7 @@ dependencies:
|
||||
window_manager: ^0.3.0
|
||||
http: ^0.13.5
|
||||
json_annotation: ^4.7.0
|
||||
path: ^1.8.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^2.0.1
|
||||
|
Loading…
Reference in New Issue
Block a user