fix: openAI image expiration (#3660)

* feat: save the openAI image to local storage

* feat: support rendering error block

* fix: enter on Toggle list moves heading down without contents
This commit is contained in:
Lucas.Xu 2023-10-10 12:43:31 +08:00 committed by GitHub
parent b247a49417
commit ffdf5d24a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 186 additions and 19 deletions

View File

@ -151,6 +151,9 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
effectiveScrollController = widget.scrollController ?? ScrollController();
// keep the previous font style when typing new text.
supportSlashMenuNodeWhiteList.addAll([
ToggleListBlockKeys.type,
]);
AppFlowyRichTextKeys.supportSliced.add(AppFlowyRichTextKeys.fontFamily);
}
@ -353,6 +356,11 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
styleCustomizer.outlineBlockPlaceholderStyleBuilder(),
),
),
errorBlockComponentBuilderKey: ErrorBlockComponentBuilder(
configuration: configuration.copyWith(
padding: (_) => const EdgeInsets.symmetric(vertical: 10),
),
),
};
final builders = {

View File

@ -0,0 +1,100 @@
import 'dart:convert';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
class ErrorBlockComponentBuilder extends BlockComponentBuilder {
ErrorBlockComponentBuilder({
super.configuration,
});
@override
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
final node = blockComponentContext.node;
return ErrorBlockComponentWidget(
key: node.key,
node: node,
configuration: configuration,
showActions: showActions(node),
actionBuilder: (context, state) => actionBuilder(
blockComponentContext,
state,
),
);
}
@override
bool validate(Node node) => true;
}
class ErrorBlockComponentWidget extends BlockComponentStatefulWidget {
const ErrorBlockComponentWidget({
super.key,
required super.node,
super.showActions,
super.actionBuilder,
super.configuration = const BlockComponentConfiguration(),
});
@override
State<ErrorBlockComponentWidget> createState() =>
_DividerBlockComponentWidgetState();
}
class _DividerBlockComponentWidgetState extends State<ErrorBlockComponentWidget>
with BlockComponentConfigurable {
@override
BlockComponentConfiguration get configuration => widget.configuration;
@override
Node get node => widget.node;
@override
Widget build(BuildContext context) {
Widget child = DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(4),
),
child: FlowyButton(
onTap: () async {
showSnackBarMessage(
context,
LocaleKeys.document_errorBlock_blockContentHasBeenCopied.tr(),
);
await getIt<ClipboardService>().setData(
ClipboardServiceData(plainText: jsonEncode(node.toJson())),
);
},
text: Container(
height: 48,
alignment: Alignment.center,
child: FlowyText(
LocaleKeys.document_errorBlock_theBlockIsNotSupported.tr(),
),
),
),
);
child = Padding(
padding: padding,
child: child,
);
if (widget.showActions && widget.actionBuilder != null) {
child = BlockComponentActionWrapper(
node: node,
actionBuilder: widget.actionBuilder!,
child: child,
);
}
return child;
}
}

View File

@ -15,6 +15,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:http/http.dart';
import 'package:path/path.dart' as p;
import 'package:string_validator/string_validator.dart';
@ -47,16 +48,22 @@ class _ImagePlaceholderState extends State<ImagePlaceholder> {
clickHandler: PopoverClickHandler.gestureDetector,
popupBuilder: (context) {
return UploadImageMenu(
onPickFile: (path) {
onSelectedLocalImage: (path) {
controller.close();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
insertLocalImage(path);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
await insertLocalImage(path);
});
},
onSubmit: (url) {
onSelectedAIImage: (url) {
controller.close();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
insertNetworkImage(url);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
await insertAIImage(url);
});
},
onSelectedNetworkImage: (url) {
controller.close();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
await insertNetworkImage(url);
});
},
);
@ -123,7 +130,46 @@ class _ImagePlaceholderState extends State<ImagePlaceholder> {
} catch (e) {
Log.error('cannot copy image file', e);
}
controller.close();
}
Future<void> insertAIImage(String url) async {
if (url.isEmpty || !isURL(url)) {
// show error
showSnackBarMessage(
context,
LocaleKeys.document_imageBlock_error_invalidImage.tr(),
);
return;
}
final path = await getIt<ApplicationDataStorage>().getPath();
final imagePath = p.join(
path,
'images',
);
try {
// create the directory if not exists
final directory = Directory(imagePath);
if (!directory.existsSync()) {
await directory.create(recursive: true);
}
final uri = Uri.parse(url);
final copyToPath = p.join(
imagePath,
'${uuid()}${p.extension(uri.path)}',
);
final response = await get(uri);
await File(copyToPath).writeAsBytes(response.bodyBytes);
final transaction = editorState.transaction;
transaction.updateNode(widget.node, {
ImageBlockKeys.url: copyToPath,
});
await editorState.apply(transaction);
} catch (e) {
Log.error('cannot save image file', e);
}
}
Future<void> insertNetworkImage(String url) async {

View File

@ -36,12 +36,14 @@ enum UploadImageType {
class UploadImageMenu extends StatefulWidget {
const UploadImageMenu({
super.key,
required this.onPickFile,
required this.onSubmit,
required this.onSelectedLocalImage,
required this.onSelectedAIImage,
required this.onSelectedNetworkImage,
});
final void Function(String? path) onPickFile;
final void Function(String url) onSubmit;
final void Function(String? path) onSelectedLocalImage;
final void Function(String url) onSelectedAIImage;
final void Function(String url) onSelectedNetworkImage;
@override
State<UploadImageMenu> createState() => _UploadImageMenuState();
@ -127,14 +129,14 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
return Padding(
padding: const EdgeInsets.all(8.0),
child: UploadImageFileWidget(
onPickFile: widget.onPickFile,
onPickFile: widget.onSelectedLocalImage,
),
);
case UploadImageType.url:
return Padding(
padding: const EdgeInsets.all(8.0),
child: EmbedImageUrlWidget(
onSubmit: widget.onSubmit,
onSubmit: widget.onSelectedNetworkImage,
),
);
case UploadImageType.unsplash:
@ -142,7 +144,7 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
child: Padding(
padding: const EdgeInsets.all(8.0),
child: UnsplashImageWidget(
onSelectUnsplashImage: widget.onSubmit,
onSelectUnsplashImage: widget.onSelectedNetworkImage,
),
),
);
@ -152,7 +154,7 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
child: Padding(
padding: const EdgeInsets.all(8.0),
child: OpenAIImageWidget(
onSelectNetworkImage: widget.onSubmit,
onSelectNetworkImage: widget.onSelectedAIImage,
),
),
)
@ -168,7 +170,7 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
child: Padding(
padding: const EdgeInsets.all(8.0),
child: StabilityAIImageWidget(
onSelectImage: widget.onPickFile,
onSelectImage: widget.onSelectedLocalImage,
),
),
)

View File

@ -12,6 +12,7 @@ export 'copy_and_paste/custom_paste_command.dart';
export 'database/database_view_block_component.dart';
export 'database/inline_database_menu_item.dart';
export 'database/referenced_database_menu_item.dart';
export 'error/error_block_component_builder.dart';
export 'extensions/flowy_tint_extension.dart';
export 'find_and_replace/find_and_replace_menu.dart';
export 'font/customize_font_toolbar_item.dart';

View File

@ -60,6 +60,12 @@ CharacterShortcutEvent insertChildNodeInsideToggleList = CharacterShortcutEvent(
..afterSelection = Selection.collapsed(
Position(path: selection.start.path, offset: 0),
);
} else if (selection.startIndex == 0) {
// insert a paragraph block above the current toggle list block
transaction.insertNode(selection.start.path, paragraphNode());
transaction.afterSelection = Selection.collapsed(
Position(path: selection.start.path.next, offset: 0),
);
} else {
// insert a toggle list block below the current toggle list block
transaction

View File

@ -54,8 +54,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "0abcf7f"
resolved-ref: "0abcf7f6d273b838c895abdc17f6833540613729"
ref: adb05d4
resolved-ref: adb05d4c49fe2f518e5554cc7d6c2fbe3b01670d
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
source: git
version: "1.4.3"

View File

@ -47,7 +47,7 @@ dependencies:
appflowy_editor:
git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: "0abcf7f"
ref: "adb05d4"
appflowy_popover:
path: packages/appflowy_popover

View File

@ -705,6 +705,10 @@
},
"toolbar": {
"resetToDefaultFont": "Reset to default"
},
"errorBlock": {
"theBlockIsNotSupported": "The current version does not support this block.",
"blockContentHasBeenCopied": "The block content has been copied."
}
},
"board": {