Przeglądaj źródła

feat: support inserting local image (#2913)

Lucas.Xu 1 rok temu
rodzic
commit
11d05b303d

+ 6 - 0
frontend/appflowy_flutter/assets/translations/en.json

@@ -452,6 +452,12 @@
         "center": "Center",
         "center": "Center",
         "right": "Right",
         "right": "Right",
         "defaultColor": "Default"
         "defaultColor": "Default"
+      },
+      "image": {
+        "copiedToPasteBoard": "The image link has been copied to the clipboard"
+      },
+      "outline": {
+        "addHeadingToCreateOutline": "Add headings to create a table of contents."
       }
       }
     }
     }
   },
   },

+ 1 - 1
frontend/appflowy_flutter/integration_test/document/edit_document_test.dart

@@ -118,7 +118,7 @@ const _sample = r'''
 ---
 ---
 [] Highlight any text, and use the editing menu to _style_ **your** writing `however` you ~~like.~~
 [] Highlight any text, and use the editing menu to _style_ **your** writing `however` you ~~like.~~
 
 
-[] Type / followed by /bullet or /num to create a list.
+[] Type followed by bullet or num to create a list.
 
 
 [x] Click `+ New Page` button at the bottom of your sidebar to add a new page.
 [x] Click `+ New Page` button at the bottom of your sidebar to add a new page.
 
 

+ 37 - 1
frontend/appflowy_flutter/lib/plugins/document/application/doc_bloc.dart

@@ -9,7 +9,7 @@ import 'package:appflowy/plugins/document/application/doc_service.dart';
 import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
 import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
 import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart';
 import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart';
 import 'package:appflowy_editor/appflowy_editor.dart'
 import 'package:appflowy_editor/appflowy_editor.dart'
-    show EditorState, LogLevel, TransactionTime;
+    show EditorState, LogLevel, TransactionTime, Selection, paragraphNode;
 import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
@@ -155,6 +155,9 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
         return;
         return;
       }
       }
       await _transactionAdapter.apply(event.$2, editorState);
       await _transactionAdapter.apply(event.$2, editorState);
+
+      // check if the document is empty.
+      applyRules();
     });
     });
 
 
     // output the log from the editor when debug mode
     // output the log from the editor when debug mode
@@ -166,6 +169,39 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
         };
         };
     }
     }
   }
   }
+
+  Future<void> applyRules() async {
+    ensureAtLeastOneParagraphExists();
+    ensureLastNodeIsEditable();
+  }
+
+  Future<void> ensureLastNodeIsEditable() async {
+    final editorState = this.editorState;
+    if (editorState == null) {
+      return;
+    }
+    final document = editorState.document;
+    final lastNode = document.root.children.lastOrNull;
+    if (lastNode == null || lastNode.delta == null) {
+      final transaction = editorState.transaction;
+      transaction.insertNode([document.root.children.length], paragraphNode());
+      await editorState.apply(transaction);
+    }
+  }
+
+  Future<void> ensureAtLeastOneParagraphExists() async {
+    final editorState = this.editorState;
+    if (editorState == null) {
+      return;
+    }
+    final document = editorState.document;
+    if (document.root.children.isEmpty) {
+      final transaction = editorState.transaction;
+      transaction.insertNode([0], paragraphNode());
+      transaction.afterSelection = Selection.collapse([0], 0);
+      await editorState.apply(transaction);
+    }
+  }
 }
 }
 
 
 @freezed
 @freezed

+ 51 - 18
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart

@@ -1,10 +1,9 @@
 import 'package:appflowy/plugins/document/application/doc_bloc.dart';
 import 'package:appflowy/plugins/document/application/doc_bloc.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_list.dart';
 import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
 import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
 import 'package:appflowy/plugins/document/presentation/editor_style.dart';
 import 'package:appflowy/plugins/document/presentation/editor_style.dart';
 import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart';
 import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:collection/collection.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.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';
@@ -55,20 +54,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
     highlightColorItem,
     highlightColorItem,
   ];
   ];
 
 
-  late final slashMenuItems = [
-    inlineGridMenuItem(documentBloc),
-    referencedGridMenuItem,
-    inlineBoardMenuItem(documentBloc),
-    referencedBoardMenuItem,
-    inlineCalendarMenuItem(documentBloc),
-    referencedCalendarMenuItem,
-    calloutItem,
-    mathEquationItem,
-    codeBlockItem,
-    emojiMenuItem,
-    autoGeneratorMenuItem,
-    outlineItem,
-  ];
+  late final List<SelectionMenuItem> slashMenuItems;
 
 
   late final Map<String, BlockComponentBuilder> blockComponentBuilders =
   late final Map<String, BlockComponentBuilder> blockComponentBuilders =
       _customAppFlowyBlockComponentBuilders();
       _customAppFlowyBlockComponentBuilders();
@@ -119,6 +105,9 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
   @override
   @override
   void initState() {
   void initState() {
     super.initState();
     super.initState();
+
+    slashMenuItems = _customSlashMenuItems();
+
     effectiveScrollController = widget.scrollController ?? ScrollController();
     effectiveScrollController = widget.scrollController ?? ScrollController();
   }
   }
 
 
@@ -219,6 +208,15 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
       ),
       ),
       ImageBlockKeys.type: ImageBlockComponentBuilder(
       ImageBlockKeys.type: ImageBlockComponentBuilder(
         configuration: configuration,
         configuration: configuration,
+        showMenu: true,
+        menuBuilder: (node, state) => Positioned(
+          top: 0,
+          right: 10,
+          child: ImageMenu(
+            node: node,
+            state: state,
+          ),
+        ),
       ),
       ),
       DatabaseBlockKeys.gridType: DatabaseViewBlockComponentBuilder(
       DatabaseBlockKeys.gridType: DatabaseViewBlockComponentBuilder(
         configuration: configuration,
         configuration: configuration,
@@ -254,8 +252,15 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
       ),
       ),
       AutoCompletionBlockKeys.type: AutoCompletionBlockComponentBuilder(),
       AutoCompletionBlockKeys.type: AutoCompletionBlockComponentBuilder(),
       SmartEditBlockKeys.type: SmartEditBlockComponentBuilder(),
       SmartEditBlockKeys.type: SmartEditBlockComponentBuilder(),
-      ToggleListBlockKeys.type: ToggleListBlockComponentBuilder(),
-      OutlineBlockKeys.type: OutlineBlockComponentBuilder(),
+      ToggleListBlockKeys.type: ToggleListBlockComponentBuilder(
+        configuration: configuration,
+      ),
+      OutlineBlockKeys.type: OutlineBlockComponentBuilder(
+        configuration: configuration.copyWith(
+          placeholderTextStyle: (_) =>
+              styleCustomizer.outlineBlockPlaceholderStyleBuilder(),
+        ),
+      ),
     };
     };
 
 
     final builders = {
     final builders = {
@@ -325,6 +330,34 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
     return builders;
     return builders;
   }
   }
 
 
+  List<SelectionMenuItem> _customSlashMenuItems() {
+    final items = [...standardSelectionMenuItems];
+    final imageItem = items.firstWhereOrNull(
+      (element) => element.name == AppFlowyEditorLocalizations.current.image,
+    );
+    if (imageItem != null) {
+      final imageItemIndex = items.indexOf(imageItem);
+      if (imageItemIndex != -1) {
+        items[imageItemIndex] = customImageMenuItem;
+      }
+    }
+    return [
+      ...items,
+      inlineGridMenuItem(documentBloc),
+      referencedGridMenuItem,
+      inlineBoardMenuItem(documentBloc),
+      referencedBoardMenuItem,
+      inlineCalendarMenuItem(documentBloc),
+      referencedCalendarMenuItem,
+      calloutItem,
+      outlineItem,
+      mathEquationItem,
+      codeBlockItem,
+      emojiMenuItem,
+      autoGeneratorMenuItem,
+    ];
+  }
+
   (bool, Selection?) _computeAutoFocusParameters() {
   (bool, Selection?) _computeAutoFocusParameters() {
     if (widget.editorState.document.isEmpty) {
     if (widget.editorState.document.isEmpty) {
       return (true, Selection.collapse([0], 0));
       return (true, Selection.collapse([0], 0));

+ 290 - 0
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_menu.dart

@@ -0,0 +1,290 @@
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
+import 'package:appflowy_popover/appflowy_popover.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:provider/provider.dart';
+
+class ImageMenu extends StatefulWidget {
+  const ImageMenu({
+    super.key,
+    required this.node,
+    required this.state,
+  });
+
+  final Node node;
+  final ImageBlockComponentWidgetState state;
+
+  @override
+  State<ImageMenu> createState() => _ImageMenuState();
+}
+
+class _ImageMenuState extends State<ImageMenu> {
+  @override
+  Widget build(BuildContext context) {
+    final theme = Theme.of(context);
+    return Container(
+      height: 32,
+      decoration: BoxDecoration(
+        color: theme.cardColor,
+        boxShadow: [
+          BoxShadow(
+            blurRadius: 5,
+            spreadRadius: 1,
+            color: Colors.black.withOpacity(0.1),
+          ),
+        ],
+        borderRadius: BorderRadius.circular(4.0),
+      ),
+      child: Row(
+        children: [
+          const HSpace(4),
+          _ImageCopyLinkButton(
+            onTap: copyImageLink,
+          ),
+          const HSpace(4),
+          _ImageAlignButton(
+            node: widget.node,
+            state: widget.state,
+          ),
+          const _Divider(),
+          _ImageDeleteButton(
+            onTap: () => deleteImage(),
+          ),
+          const HSpace(4),
+        ],
+      ),
+    );
+  }
+
+  void copyImageLink() {
+    final url = widget.node.attributes[ImageBlockKeys.url];
+    if (url != null) {
+      Clipboard.setData(ClipboardData(text: url));
+      ScaffoldMessenger.of(context).showSnackBar(
+        SnackBar(
+          content: FlowyText(
+            LocaleKeys.document_plugins_image_copiedToPasteBoard.tr(),
+          ),
+        ),
+      );
+    }
+  }
+
+  Future<void> deleteImage() async {
+    final node = widget.node;
+    final editorState = context.read<EditorState>();
+    final transaction = editorState.transaction;
+    transaction.deleteNode(node);
+    transaction.afterSelection = null;
+    await editorState.apply(transaction);
+  }
+}
+
+class _ImageCopyLinkButton extends StatelessWidget {
+  const _ImageCopyLinkButton({
+    required this.onTap,
+  });
+
+  final VoidCallback onTap;
+
+  @override
+  Widget build(BuildContext context) {
+    return GestureDetector(
+      onTap: onTap,
+      child: const FlowySvg(
+        name: 'editor/copy',
+        size: Size.square(16),
+      ),
+    );
+  }
+}
+
+class _ImageAlignButton extends StatefulWidget {
+  const _ImageAlignButton({
+    required this.node,
+    required this.state,
+  });
+
+  final Node node;
+  final ImageBlockComponentWidgetState state;
+
+  @override
+  State<_ImageAlignButton> createState() => _ImageAlignButtonState();
+}
+
+const interceptorKey = 'image-align';
+
+class _ImageAlignButtonState extends State<_ImageAlignButton> {
+  final gestureInterceptor = SelectionGestureInterceptor(
+    key: interceptorKey,
+    canTap: (details) => false,
+  );
+
+  String get align => widget.node.attributes['align'] ?? 'center';
+  final popoverController = PopoverController();
+  late final EditorState editorState;
+
+  @override
+  void initState() {
+    super.initState();
+
+    editorState = context.read<EditorState>();
+  }
+
+  @override
+  void dispose() {
+    allowMenuClose();
+
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return IgnoreParentGestureWidget(
+      child: AppFlowyPopover(
+        onClose: allowMenuClose,
+        controller: popoverController,
+        windowPadding: const EdgeInsets.all(0),
+        margin: const EdgeInsets.all(0),
+        direction: PopoverDirection.bottomWithCenterAligned,
+        offset: const Offset(0, 10),
+        child: buildAlignIcon(),
+        popupBuilder: (_) {
+          preventMenuClose();
+          return _AlignButtons(
+            onAlignChanged: onAlignChanged,
+          );
+        },
+      ),
+    );
+  }
+
+  void onAlignChanged(String align) {
+    popoverController.close();
+
+    final transaction = editorState.transaction;
+    transaction.updateNode(widget.node, {
+      ImageBlockKeys.align: align,
+    });
+    editorState.apply(transaction);
+
+    allowMenuClose();
+  }
+
+  void preventMenuClose() {
+    widget.state.alwaysShowMenu = true;
+    editorState.service.selectionService.registerGestureInterceptor(
+      gestureInterceptor,
+    );
+  }
+
+  void allowMenuClose() {
+    widget.state.alwaysShowMenu = false;
+    editorState.service.selectionService.unregisterGestureInterceptor(
+      interceptorKey,
+    );
+  }
+
+  Widget buildAlignIcon() {
+    return FlowySvg(
+      name: 'editor/align/$align',
+      size: const Size.square(16),
+    );
+  }
+}
+
+class _AlignButtons extends StatelessWidget {
+  const _AlignButtons({
+    required this.onAlignChanged,
+  });
+
+  final Function(String align) onAlignChanged;
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      height: 32,
+      child: Row(
+        mainAxisSize: MainAxisSize.min,
+        children: [
+          const HSpace(4),
+          _AlignButton(
+            align: 'left',
+            onTap: () => onAlignChanged('left'),
+          ),
+          const _Divider(),
+          _AlignButton(
+            align: 'left',
+            onTap: () => onAlignChanged('center'),
+          ),
+          const _Divider(),
+          _AlignButton(
+            align: 'left',
+            onTap: () => onAlignChanged('right'),
+          ),
+          const HSpace(4),
+        ],
+      ),
+    );
+  }
+}
+
+class _AlignButton extends StatelessWidget {
+  const _AlignButton({
+    required this.align,
+    required this.onTap,
+  });
+
+  final String align;
+  final VoidCallback onTap;
+
+  @override
+  Widget build(BuildContext context) {
+    return GestureDetector(
+      onTap: onTap,
+      child: FlowySvg(
+        name: 'editor/align/$align',
+        size: const Size.square(16),
+      ),
+    );
+  }
+}
+
+class _ImageDeleteButton extends StatelessWidget {
+  const _ImageDeleteButton({
+    required this.onTap,
+  });
+
+  final VoidCallback onTap;
+
+  @override
+  Widget build(BuildContext context) {
+    return GestureDetector(
+      onTap: onTap,
+      child: const FlowySvg(
+        name: 'editor/delete',
+        size: Size.square(16),
+      ),
+    );
+  }
+}
+
+class _Divider extends StatelessWidget {
+  const _Divider();
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.all(8),
+      child: Container(
+        width: 1,
+        color: Colors.grey,
+      ),
+    );
+  }
+}

+ 59 - 0
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_selection_menu.dart

@@ -0,0 +1,59 @@
+import 'dart:io';
+
+import 'package:appflowy/startup/startup.dart';
+import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
+import 'package:flowy_infra/uuid.dart';
+import 'package:flutter/material.dart';
+import 'package:path/path.dart' as p;
+
+final customImageMenuItem = SelectionMenuItem(
+  name: AppFlowyEditorLocalizations.current.image,
+  icon: (editorState, isSelected, style) => SelectionMenuIconWidget(
+    name: 'image',
+    isSelected: isSelected,
+    style: style,
+  ),
+  keywords: ['image', 'picture', 'img', 'photo'],
+  handler: (editorState, menuService, context) {
+    final container = Overlay.of(context);
+    showImageMenu(
+      container,
+      editorState,
+      menuService,
+      onInsertImage: (url) async {
+        // if the url is http, we can insert it directly
+        // otherwise, if it's a file url, we need to copy the file to the app's document directory
+
+        final regex = RegExp('^(http|https)://');
+        if (regex.hasMatch(url)) {
+          await editorState.insertImageNode(url);
+        } else {
+          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 copyToPath = p.join(
+              imagePath,
+              '${uuid()}${p.extension(url)}',
+            );
+            await File(url).copy(
+              copyToPath,
+            );
+            await editorState.insertImageNode(copyToPath);
+          } catch (e) {
+            Log.error('cannot copy image file', e);
+          }
+        }
+      },
+    );
+  },
+);

+ 10 - 1
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart

@@ -120,6 +120,15 @@ class _OutlineBlockWidgetState extends State<OutlineBlockWidget>
           ),
           ),
         )
         )
         .toList();
         .toList();
+    if (children.isEmpty) {
+      return Align(
+        alignment: Alignment.centerLeft,
+        child: Text(
+          LocaleKeys.document_plugins_outline_addHeadingToCreateOutline.tr(),
+          style: configuration.placeholderTextStyle(node),
+        ),
+      );
+    }
     return Container(
     return Container(
       decoration: BoxDecoration(
       decoration: BoxDecoration(
         borderRadius: const BorderRadius.all(Radius.circular(8.0)),
         borderRadius: const BorderRadius.all(Radius.circular(8.0)),
@@ -184,7 +193,7 @@ class OutlineItemWidget extends StatelessWidget {
 
 
 extension on Node {
 extension on Node {
   double get leftIndent {
   double get leftIndent {
-    assert(type != HeadingBlockKeys.type);
+    assert(type == HeadingBlockKeys.type);
     if (type != HeadingBlockKeys.type) {
     if (type != HeadingBlockKeys.type) {
       return 0.0;
       return 0.0;
     }
     }

+ 4 - 0
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart

@@ -16,3 +16,7 @@ export 'openai/widgets/smart_edit_toolbar_item.dart';
 export 'toggle/toggle_block_component.dart';
 export 'toggle/toggle_block_component.dart';
 export 'toggle/toggle_block_shortcut_event.dart';
 export 'toggle/toggle_block_shortcut_event.dart';
 export 'outline/outline_block_component.dart';
 export 'outline/outline_block_component.dart';
+export 'image/image_menu.dart';
+export 'image/image_selection_menu.dart';
+export 'actions/option_action.dart';
+export 'actions/block_action_list.dart';

+ 11 - 0
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart

@@ -127,6 +127,17 @@ class EditorStyleCustomizer {
     );
     );
   }
   }
 
 
+  TextStyle outlineBlockPlaceholderStyleBuilder() {
+    final theme = Theme.of(context);
+    final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
+    return TextStyle(
+      fontFamily: 'poppins',
+      fontSize: fontSize,
+      height: 1.5,
+      color: theme.colorScheme.onBackground.withOpacity(0.6),
+    );
+  }
+
   SelectionMenuStyle selectionMenuStyleBuilder() {
   SelectionMenuStyle selectionMenuStyleBuilder() {
     final theme = Theme.of(context);
     final theme = Theme.of(context);
     return SelectionMenuStyle(
     return SelectionMenuStyle(

+ 10 - 0
frontend/appflowy_flutter/lib/plugins/document/presentation/more/cubit/document_appearance_cubit.dart

@@ -25,6 +25,11 @@ class DocumentAppearanceCubit extends Cubit<DocumentAppearance> {
   void fetch() async {
   void fetch() async {
     final prefs = await SharedPreferences.getInstance();
     final prefs = await SharedPreferences.getInstance();
     final fontSize = prefs.getDouble(_kDocumentAppearanceFontSize) ?? 16.0;
     final fontSize = prefs.getDouble(_kDocumentAppearanceFontSize) ?? 16.0;
+
+    if (isClosed) {
+      return;
+    }
+
     emit(
     emit(
       state.copyWith(
       state.copyWith(
         fontSize: fontSize,
         fontSize: fontSize,
@@ -35,6 +40,11 @@ class DocumentAppearanceCubit extends Cubit<DocumentAppearance> {
   void syncFontSize(double fontSize) async {
   void syncFontSize(double fontSize) async {
     final prefs = await SharedPreferences.getInstance();
     final prefs = await SharedPreferences.getInstance();
     prefs.setDouble(_kDocumentAppearanceFontSize, fontSize);
     prefs.setDouble(_kDocumentAppearanceFontSize, fontSize);
+
+    if (isClosed) {
+      return;
+    }
+
     emit(
     emit(
       state.copyWith(
       state.copyWith(
         fontSize: fontSize,
         fontSize: fontSize,

+ 3 - 3
frontend/appflowy_flutter/pubspec.lock

@@ -53,9 +53,9 @@ packages:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
       path: "."
       path: "."
-      ref: "250b1a5"
-      resolved-ref: "250b1a59856b337fc2d4b26a1dabdec265e80acf"
-      url: "https://github.com/AppFlowy-IO/appflowy-editor"
+      ref: "572a174"
+      resolved-ref: "572a174892267e2f78f9c3d7f1fe4ca71c9be0db"
+      url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
     source: git
     source: git
     version: "1.0.4"
     version: "1.0.4"
   appflowy_popover:
   appflowy_popover:

+ 2 - 3
frontend/appflowy_flutter/pubspec.yaml

@@ -45,9 +45,8 @@ dependencies:
   # appflowy_editor: ^1.0.4
   # appflowy_editor: ^1.0.4
   appflowy_editor:
   appflowy_editor:
     git:
     git:
-      url: https://github.com/AppFlowy-IO/appflowy-editor
-      ref: 250b1a5
-
+      url: https://github.com/AppFlowy-IO/appflowy-editor.git
+      ref: 572a174
   appflowy_popover:
   appflowy_popover:
     path: packages/appflowy_popover
     path: packages/appflowy_popover