mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
parent
b247a49417
commit
ffdf5d24a0
@ -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 = {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -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';
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
||||
|
@ -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": {
|
||||
|
Loading…
Reference in New Issue
Block a user