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();
|
effectiveScrollController = widget.scrollController ?? ScrollController();
|
||||||
|
|
||||||
// keep the previous font style when typing new text.
|
// keep the previous font style when typing new text.
|
||||||
|
supportSlashMenuNodeWhiteList.addAll([
|
||||||
|
ToggleListBlockKeys.type,
|
||||||
|
]);
|
||||||
AppFlowyRichTextKeys.supportSliced.add(AppFlowyRichTextKeys.fontFamily);
|
AppFlowyRichTextKeys.supportSliced.add(AppFlowyRichTextKeys.fontFamily);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,6 +356,11 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
styleCustomizer.outlineBlockPlaceholderStyleBuilder(),
|
styleCustomizer.outlineBlockPlaceholderStyleBuilder(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
errorBlockComponentBuilderKey: ErrorBlockComponentBuilder(
|
||||||
|
configuration: configuration.copyWith(
|
||||||
|
padding: (_) => const EdgeInsets.symmetric(vertical: 10),
|
||||||
|
),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
final builders = {
|
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:flowy_infra_ui/style_widget/hover.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:string_validator/string_validator.dart';
|
import 'package:string_validator/string_validator.dart';
|
||||||
|
|
||||||
@ -47,16 +48,22 @@ class _ImagePlaceholderState extends State<ImagePlaceholder> {
|
|||||||
clickHandler: PopoverClickHandler.gestureDetector,
|
clickHandler: PopoverClickHandler.gestureDetector,
|
||||||
popupBuilder: (context) {
|
popupBuilder: (context) {
|
||||||
return UploadImageMenu(
|
return UploadImageMenu(
|
||||||
onPickFile: (path) {
|
onSelectedLocalImage: (path) {
|
||||||
controller.close();
|
controller.close();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||||
insertLocalImage(path);
|
await insertLocalImage(path);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSubmit: (url) {
|
onSelectedAIImage: (url) {
|
||||||
controller.close();
|
controller.close();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||||
insertNetworkImage(url);
|
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) {
|
} catch (e) {
|
||||||
Log.error('cannot copy image file', 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 {
|
Future<void> insertNetworkImage(String url) async {
|
||||||
|
@ -36,12 +36,14 @@ enum UploadImageType {
|
|||||||
class UploadImageMenu extends StatefulWidget {
|
class UploadImageMenu extends StatefulWidget {
|
||||||
const UploadImageMenu({
|
const UploadImageMenu({
|
||||||
super.key,
|
super.key,
|
||||||
required this.onPickFile,
|
required this.onSelectedLocalImage,
|
||||||
required this.onSubmit,
|
required this.onSelectedAIImage,
|
||||||
|
required this.onSelectedNetworkImage,
|
||||||
});
|
});
|
||||||
|
|
||||||
final void Function(String? path) onPickFile;
|
final void Function(String? path) onSelectedLocalImage;
|
||||||
final void Function(String url) onSubmit;
|
final void Function(String url) onSelectedAIImage;
|
||||||
|
final void Function(String url) onSelectedNetworkImage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<UploadImageMenu> createState() => _UploadImageMenuState();
|
State<UploadImageMenu> createState() => _UploadImageMenuState();
|
||||||
@ -127,14 +129,14 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: UploadImageFileWidget(
|
child: UploadImageFileWidget(
|
||||||
onPickFile: widget.onPickFile,
|
onPickFile: widget.onSelectedLocalImage,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
case UploadImageType.url:
|
case UploadImageType.url:
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: EmbedImageUrlWidget(
|
child: EmbedImageUrlWidget(
|
||||||
onSubmit: widget.onSubmit,
|
onSubmit: widget.onSelectedNetworkImage,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
case UploadImageType.unsplash:
|
case UploadImageType.unsplash:
|
||||||
@ -142,7 +144,7 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: UnsplashImageWidget(
|
child: UnsplashImageWidget(
|
||||||
onSelectUnsplashImage: widget.onSubmit,
|
onSelectUnsplashImage: widget.onSelectedNetworkImage,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -152,7 +154,7 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: OpenAIImageWidget(
|
child: OpenAIImageWidget(
|
||||||
onSelectNetworkImage: widget.onSubmit,
|
onSelectNetworkImage: widget.onSelectedAIImage,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -168,7 +170,7 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: StabilityAIImageWidget(
|
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/database_view_block_component.dart';
|
||||||
export 'database/inline_database_menu_item.dart';
|
export 'database/inline_database_menu_item.dart';
|
||||||
export 'database/referenced_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 'extensions/flowy_tint_extension.dart';
|
||||||
export 'find_and_replace/find_and_replace_menu.dart';
|
export 'find_and_replace/find_and_replace_menu.dart';
|
||||||
export 'font/customize_font_toolbar_item.dart';
|
export 'font/customize_font_toolbar_item.dart';
|
||||||
|
@ -60,6 +60,12 @@ CharacterShortcutEvent insertChildNodeInsideToggleList = CharacterShortcutEvent(
|
|||||||
..afterSelection = Selection.collapsed(
|
..afterSelection = Selection.collapsed(
|
||||||
Position(path: selection.start.path, offset: 0),
|
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 {
|
} else {
|
||||||
// insert a toggle list block below the current toggle list block
|
// insert a toggle list block below the current toggle list block
|
||||||
transaction
|
transaction
|
||||||
|
@ -54,8 +54,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "0abcf7f"
|
ref: adb05d4
|
||||||
resolved-ref: "0abcf7f6d273b838c895abdc17f6833540613729"
|
resolved-ref: adb05d4c49fe2f518e5554cc7d6c2fbe3b01670d
|
||||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||||
source: git
|
source: git
|
||||||
version: "1.4.3"
|
version: "1.4.3"
|
||||||
|
@ -47,7 +47,7 @@ dependencies:
|
|||||||
appflowy_editor:
|
appflowy_editor:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||||
ref: "0abcf7f"
|
ref: "adb05d4"
|
||||||
appflowy_popover:
|
appflowy_popover:
|
||||||
path: packages/appflowy_popover
|
path: packages/appflowy_popover
|
||||||
|
|
||||||
|
@ -705,6 +705,10 @@
|
|||||||
},
|
},
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"resetToDefaultFont": "Reset to default"
|
"resetToDefaultFont": "Reset to default"
|
||||||
|
},
|
||||||
|
"errorBlock": {
|
||||||
|
"theBlockIsNotSupported": "The current version does not support this block.",
|
||||||
|
"blockContentHasBeenCopied": "The block content has been copied."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"board": {
|
"board": {
|
||||||
|
Loading…
Reference in New Issue
Block a user