Browse Source

feat: support align and upgrade appflowy_editor (#2712)

* feat: support align and upgrade appflowy_editor

* chore: try to fix linux analyze error

* fix: error after inserting callout block

* feat: add inline board / grid plugin

* feat: refactor insert_page

* fix: ref view in document

* chore: add asset name and description to option align type

* fix: linux flutter analyze

* chore: disable file export and log

* fix: the window always back to center after relaunching

* fix: potential data lost in nested list

* feat: re-design login page

* fix: can't remove background color

* chore: rename bundle id and change the macos app to non sandbox app

---------

Co-authored-by: nathan <[email protected]>
Lucas.Xu 1 year ago
parent
commit
e3eee76609
35 changed files with 573 additions and 400 deletions
  1. 2 0
      frontend/appflowy_flutter/assets/images/common/archive.svg
  2. 0 0
      frontend/appflowy_flutter/assets/images/editor/align/center.svg
  3. 0 0
      frontend/appflowy_flutter/assets/images/editor/align/left.svg
  4. 0 0
      frontend/appflowy_flutter/assets/images/editor/align/right.svg
  5. 10 1
      frontend/appflowy_flutter/assets/translations/en.json
  6. 8 2
      frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart
  7. 26 11
      frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart
  8. 10 8
      frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart
  9. 244 107
      frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart
  10. 1 0
      frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action_button.dart
  11. 25 1
      frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/insert_page_command.dart
  12. 1 1
      frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart
  13. 10 32
      frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/board/board_view_menu_item.dart
  14. 1 1
      frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart
  15. 3 3
      frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/grid/grid_menu_item.dart
  16. 11 32
      frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/grid/grid_view_menu_item.dart
  17. 9 9
      frontend/appflowy_flutter/lib/startup/tasks/app_window_size_manager.dart
  18. 0 1
      frontend/appflowy_flutter/lib/startup/tasks/windows.dart
  19. 44 49
      frontend/appflowy_flutter/lib/user/presentation/folder/folder_widget.dart
  20. 52 30
      frontend/appflowy_flutter/lib/user/presentation/skip_log_in_screen.dart
  21. 6 3
      frontend/appflowy_flutter/lib/user/presentation/widgets/background.dart
  22. 3 1
      frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_system_view.dart
  23. 30 18
      frontend/appflowy_flutter/lib/workspace/presentation/widgets/pop_up_action.dart
  24. 3 3
      frontend/appflowy_flutter/macos/Runner.xcodeproj/project.pbxproj
  25. 2 10
      frontend/appflowy_flutter/macos/Runner/DebugProfile.entitlements
  26. 11 11
      frontend/appflowy_flutter/macos/Runner/Info.plist
  27. 0 6
      frontend/appflowy_flutter/macos/Runner/Release.entitlements
  28. 4 0
      frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart
  29. 14 9
      frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/color_picker.dart
  30. 7 0
      frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart
  31. 10 2
      frontend/appflowy_flutter/pubspec.lock
  32. 2 1
      frontend/appflowy_flutter/pubspec.yaml
  33. 4 4
      frontend/rust-lib/flowy-database2/src/manager.rs
  34. 0 1
      frontend/rust-lib/flowy-document2/src/manager.rs
  35. 20 43
      frontend/rust-lib/flowy-folder2/src/manager.rs

File diff suppressed because it is too large
+ 2 - 0
frontend/appflowy_flutter/assets/images/common/archive.svg


+ 0 - 0
frontend/appflowy_flutter/assets/images/editor/Align/center.svg → frontend/appflowy_flutter/assets/images/editor/align/center.svg


+ 0 - 0
frontend/appflowy_flutter/assets/images/editor/Align/left.svg → frontend/appflowy_flutter/assets/images/editor/align/left.svg


+ 0 - 0
frontend/appflowy_flutter/assets/images/editor/Align/right.svg → frontend/appflowy_flutter/assets/images/editor/align/right.svg


+ 10 - 1
frontend/appflowy_flutter/assets/translations/en.json

@@ -6,6 +6,8 @@
   "subscribeNewsletterText": "Subscribe to Newsletter",
   "letsGoButtonText": "Quick Start",
   "title": "Title",
+  "youCanAlso": "You can also",
+  "and": "and",
   "signUp": {
     "buttonText": "Sign Up",
     "title": "Sign Up to @:appName",
@@ -196,6 +198,7 @@
       "selectFiles": "Select the files that need to be export",
       "createNewFolder": "Create a new folder",
       "createNewFolderDesc": "Tell us where you want to store your data",
+      "defineWhereYourDataIsStored": "Define where your data is stored",
       "open": "Open",
       "openFolder": "Open an existing folder",
       "openFolderDesc": "Read and write it to your existing AppFlowy folder",
@@ -204,6 +207,7 @@
       "locationDesc": "Pick a name for your AppFlowy data folder",
       "browser": "Browse",
       "create": "Create",
+      "set": "Set",
       "folderPath": "Path to store your folder",
       "locationCannotBeEmpty": "Path cannot be empty",
       "pathCopiedSnackbar": "File storage path copied to clipboard!",
@@ -420,7 +424,12 @@
         "turnInto": "Turn into",
         "moveUp": "Move up",
         "moveDown": "Move down",
-        "color": "Color"
+        "color": "Color",
+        "align": "Align",
+        "left": "Left",
+        "center": "Center",
+        "right": "Right",
+        "defaultColor": "Default"
       }
     }
   },

+ 8 - 2
frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart

@@ -1,6 +1,5 @@
 import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
 import 'package:appflowy/plugins/document/application/doc_service.dart';
-import 'package:appflowy_backend/log.dart';
 import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
 import 'package:appflowy_editor/appflowy_editor.dart'
     show
@@ -31,11 +30,12 @@ class TransactionAdapter {
   final String documentId;
 
   Future<void> apply(Transaction transaction, EditorState editorState) async {
+    // Log.debug('transaction => ${transaction.toJson()}');
     final actions = transaction.operations
         .map((op) => op.toBlockAction(editorState))
         .whereNotNull()
         .expand((element) => element);
-    Log.debug('actions => $actions');
+    // Log.debug('actions => $actions');
     await documentService.applyAction(
       documentId: documentId,
       actions: actions,
@@ -85,6 +85,12 @@ extension on InsertOperation {
           ..action = BlockActionTypePB.Insert
           ..payload = payload,
       );
+      if (node.children.isNotEmpty) {
+        final childrenActions = node.children
+            .map((e) => InsertOperation(e.path, [e]).toBlockAction(editorState))
+            .expand((element) => element);
+        actions.addAll(childrenActions);
+      }
       previousNode = node;
     }
     return actions;

+ 26 - 11
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart

@@ -24,16 +24,6 @@ class AppFlowyEditorPage extends StatefulWidget {
 
 class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
   final scrollController = ScrollController();
-  final slashMenuItems = [
-    boardMenuItem,
-    gridMenuItem,
-    calloutItem,
-    dividerMenuItem,
-    mathEquationItem,
-    codeBlockItem,
-    emojiMenuItem,
-    autoGeneratorMenuItem,
-  ];
 
   final List<CommandShortcutEvent> commandShortcutEvents = [
     ...codeBlockCommands,
@@ -53,6 +43,19 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
     highlightColorItem,
   ];
 
+  late final slashMenuItems = [
+    dividerMenuItem,
+    inlineGridMenuItem(documentBloc),
+    referenceGridMenuItem,
+    inlineBoardMenuItem(documentBloc),
+    boardMenuItem,
+    calloutItem,
+    mathEquationItem,
+    codeBlockItem,
+    emojiMenuItem,
+    autoGeneratorMenuItem,
+  ];
+
   late final Map<String, BlockComponentBuilder> blockComponentBuilders =
       _customAppFlowyBlockComponentBuilders();
   late final List<CharacterShortcutEvent> characterShortcutEvents = [
@@ -170,7 +173,9 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
         ),
         textStyleBuilder: (level) => styleCustomizer.headingStyleBuilder(level),
       ),
-      ImageBlockKeys.type: ImageBlockComponentBuilder(),
+      ImageBlockKeys.type: ImageBlockComponentBuilder(
+        configuration: configuration,
+      ),
       BoardBlockKeys.type: BoardBlockComponentBuilder(
         configuration: configuration,
       ),
@@ -225,14 +230,24 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
         CalloutBlockKeys.type
       ];
 
+      final supportAlignBuilderType = [
+        ImageBlockKeys.type,
+      ];
+
       final colorAction = [
         OptionAction.divider,
         OptionAction.color,
       ];
 
+      final alignAction = [
+        OptionAction.divider,
+        OptionAction.align,
+      ];
+
       final List<OptionAction> actions = [
         ...standardActions,
         if (supportColorBuilderTypes.contains(entry.key)) ...colorAction,
+        if (supportAlignBuilderType.contains(entry.key)) ...alignAction,
       ];
 
       builder.showActions = (_) => true;

+ 10 - 8
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart

@@ -24,14 +24,15 @@ class BlockOptionButton extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     final popoverActions = actions.map((e) {
-      if (e == OptionAction.divider) {
-        return DividerOptionAction();
-      } else if (e == OptionAction.color) {
-        return ColorOptionAction(
-          editorState: editorState,
-        );
-      } else {
-        return OptionActionWrapper(e);
+      switch (e) {
+        case OptionAction.divider:
+          return DividerOptionAction();
+        case OptionAction.color:
+          return ColorOptionAction(editorState: editorState);
+        case OptionAction.align:
+          return AlignOptionAction(editorState: editorState);
+        default:
+          return OptionActionWrapper(e);
       }
     }).toList();
 
@@ -119,6 +120,7 @@ class BlockOptionButton extends StatelessWidget {
       case OptionAction.moveDown:
         transaction.moveNode(node.path.next.next, node);
         break;
+      case OptionAction.align:
       case OptionAction.color:
       case OptionAction.divider:
         throw UnimplementedError();

+ 244 - 107
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart

@@ -1,12 +1,12 @@
 import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/select_option_editor.dart';
-import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
 import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
-import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.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/theme_extension.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/material.dart';
 import 'package:styled_widget/styled_widget.dart';
 
@@ -18,6 +18,90 @@ enum OptionAction {
   moveDown,
   color,
   divider,
+  align;
+
+  String get assetName {
+    switch (this) {
+      case OptionAction.delete:
+        return 'editor/delete';
+      case OptionAction.duplicate:
+        return 'editor/duplicate';
+      case OptionAction.turnInto:
+        return 'editor/turn_into';
+      case OptionAction.moveUp:
+        return 'editor/move_up';
+      case OptionAction.moveDown:
+        return 'editor/move_down';
+      case OptionAction.color:
+        return 'editor/color';
+      case OptionAction.divider:
+        return 'editor/divider';
+      case OptionAction.align:
+        return 'editor/align/center';
+    }
+  }
+
+  String get description {
+    switch (this) {
+      case OptionAction.delete:
+        return LocaleKeys.document_plugins_optionAction_delete.tr();
+      case OptionAction.duplicate:
+        return LocaleKeys.document_plugins_optionAction_duplicate.tr();
+      case OptionAction.turnInto:
+        return LocaleKeys.document_plugins_optionAction_turnInto.tr();
+      case OptionAction.moveUp:
+        return LocaleKeys.document_plugins_optionAction_moveUp.tr();
+      case OptionAction.moveDown:
+        return LocaleKeys.document_plugins_optionAction_moveDown.tr();
+      case OptionAction.color:
+        return LocaleKeys.document_plugins_optionAction_color.tr();
+      case OptionAction.align:
+        return LocaleKeys.document_plugins_optionAction_align.tr();
+      case OptionAction.divider:
+        throw UnsupportedError('Divider does not have description');
+    }
+  }
+}
+
+enum OptionAlignType {
+  left,
+  center,
+  right;
+
+  static OptionAlignType fromString(String? value) {
+    switch (value) {
+      case 'left':
+        return OptionAlignType.left;
+      case 'center':
+        return OptionAlignType.center;
+      case 'right':
+        return OptionAlignType.right;
+      default:
+        return OptionAlignType.center;
+    }
+  }
+
+  String get assetName {
+    switch (this) {
+      case OptionAlignType.left:
+        return 'editor/align/left';
+      case OptionAlignType.center:
+        return 'editor/align/center';
+      case OptionAlignType.right:
+        return 'editor/align/right';
+    }
+  }
+
+  String get description {
+    switch (this) {
+      case OptionAlignType.left:
+        return LocaleKeys.document_plugins_optionAction_left.tr();
+      case OptionAlignType.center:
+        return LocaleKeys.document_plugins_optionAction_center.tr();
+      case OptionAlignType.right:
+        return LocaleKeys.document_plugins_optionAction_right.tr();
+    }
+  }
 }
 
 class DividerOptionAction extends CustomActionCell {
@@ -30,8 +114,8 @@ class DividerOptionAction extends CustomActionCell {
   }
 }
 
-class ColorOptionAction extends PopoverActionCell {
-  ColorOptionAction({
+class AlignOptionAction extends PopoverActionCell {
+  AlignOptionAction({
     required this.editorState,
   });
 
@@ -39,129 +123,182 @@ class ColorOptionAction extends PopoverActionCell {
 
   @override
   Widget? leftIcon(Color iconColor) {
-    return const FlowySvg(
-      name: 'editor/color_formatter',
-      size: Size.square(12),
+    return FlowySvg(
+      name: align.assetName,
+      size: const Size.square(12),
     ).padding(all: 2.0);
   }
 
   @override
   String get name {
-    return LocaleKeys.toolbar_color.tr();
+    return LocaleKeys.document_plugins_optionAction_align.tr();
   }
 
   @override
-  Widget Function(BuildContext context, PopoverController controller)
-      get builder => (context, controller) {
-            final selection = editorState.selection?.normalized;
-            if (selection == null) {
-              return const SizedBox.shrink();
-            }
-            final node = editorState.getNodeAtPath(selection.start.path);
-            if (node == null) {
-              return const SizedBox.shrink();
-            }
-            final bgColor =
-                node.attributes[blockComponentBackgroundColor] as String?;
-            final selectedColor = convertHexToSelectOptionColorPB(
-              bgColor,
-              context,
-            );
-
-            return SelectOptionColorList(
-              selectedColor: selectedColor,
-              onSelectedColor: (color) {
-                controller.close();
-
-                final nodes = editorState.getNodesInSelection(selection);
-                final transaction = editorState.transaction;
-                for (final node in nodes) {
-                  transaction.updateNode(node, {
-                    blockComponentBackgroundColor:
-                        color.toColor(context).toHex(),
-                  });
-                }
-                editorState.apply(transaction);
-              },
-            );
-          };
-
-  SelectOptionColorPB? convertHexToSelectOptionColorPB(
-    String? hexColor,
+  PopoverActionCellBuilder get builder =>
+      (context, parentController, controller) {
+        final selection = editorState.selection?.normalized;
+        if (selection == null) {
+          return const SizedBox.shrink();
+        }
+        final node = editorState.getNodeAtPath(selection.start.path);
+        if (node == null) {
+          return const SizedBox.shrink();
+        }
+        final children = buildAlignOptions(context, (align) async {
+          await onAlignChanged(align);
+          controller.close();
+          parentController.close();
+        });
+        return IntrinsicHeight(
+          child: IntrinsicWidth(
+            child: Column(
+              children: children,
+            ),
+          ),
+        );
+      };
+
+  List<Widget> buildAlignOptions(
     BuildContext context,
+    void Function(OptionAlignType) onTap,
   ) {
-    if (hexColor == null) {
-      return null;
+    return OptionAlignType.values.map((e) => OptionAlignWrapper(e)).map((e) {
+      final leftIcon = e.leftIcon(Theme.of(context).colorScheme.onSurface);
+      final rightIcon = e.rightIcon(Theme.of(context).colorScheme.onSurface);
+      return HoverButton(
+        onTap: () => onTap(e.inner),
+        itemHeight: ActionListSizes.itemHeight,
+        leftIcon: leftIcon,
+        name: e.name,
+        rightIcon: rightIcon,
+      );
+    }).toList();
+  }
+
+  OptionAlignType get align {
+    final selection = editorState.selection;
+    if (selection == null) {
+      return OptionAlignType.center;
+    }
+    final node = editorState.getNodeAtPath(selection.start.path);
+    final align = node?.attributes['align'];
+    return OptionAlignType.fromString(align);
+  }
+
+  Future<void> onAlignChanged(OptionAlignType align) async {
+    if (align == this.align) {
+      return;
+    }
+    final selection = editorState.selection;
+    if (selection == null) {
+      return;
     }
-    for (final value in SelectOptionColorPB.values) {
-      if (value.toColor(context).toHex() == hexColor) {
-        return value;
-      }
+    final node = editorState.getNodeAtPath(selection.start.path);
+    if (node == null) {
+      return;
     }
-    return null;
+    final transaction = editorState.transaction;
+    transaction.updateNode(node, {
+      'align': align.name,
+    });
+    await editorState.apply(transaction);
   }
 }
 
+class ColorOptionAction extends PopoverActionCell {
+  ColorOptionAction({
+    required this.editorState,
+  });
+
+  final EditorState editorState;
+
+  @override
+  Widget? leftIcon(Color iconColor) {
+    return const FlowySvg(
+      name: 'editor/color_formatter',
+      size: Size.square(12),
+    ).padding(all: 2.0);
+  }
+
+  @override
+  String get name => LocaleKeys.document_plugins_optionAction_color.tr();
+
+  @override
+  Widget Function(
+    BuildContext context,
+    PopoverController parentController,
+    PopoverController controller,
+  ) get builder => (context, parentController, controller) {
+        final selection = editorState.selection?.normalized;
+        if (selection == null) {
+          return const SizedBox.shrink();
+        }
+        final node = editorState.getNodeAtPath(selection.start.path);
+        if (node == null) {
+          return const SizedBox.shrink();
+        }
+        final bgColor =
+            node.attributes[blockComponentBackgroundColor] as String?;
+        final selectedColor = bgColor?.toColor();
+
+        final colors = [
+          // clear background color.
+          FlowyColorOption(
+            color: Colors.transparent,
+            name: LocaleKeys.document_plugins_optionAction_defaultColor.tr(),
+          ),
+          ...FlowyTint.values.map(
+            (e) => FlowyColorOption(
+              color: e.color(context),
+              name: e.tintName(AppFlowyEditorLocalizations.current),
+            ),
+          ),
+        ];
+
+        return FlowyColorPicker(
+          colors: colors,
+          selected: selectedColor,
+          border: Border.all(
+            color: Theme.of(context).colorScheme.onBackground,
+            width: 1,
+          ),
+          onTap: (color, index) async {
+            final transaction = editorState.transaction;
+            final backgroundColor =
+                color == Colors.transparent ? null : color.toHex();
+            transaction.updateNode(node, {
+              blockComponentBackgroundColor: backgroundColor,
+            });
+            await editorState.apply(transaction);
+
+            controller.close();
+            parentController.close();
+          },
+        );
+      };
+}
+
 class OptionActionWrapper extends ActionCell {
   OptionActionWrapper(this.inner);
 
   final OptionAction inner;
 
   @override
-  Widget? leftIcon(Color iconColor) {
-    var name = '';
-    // TODO: add icons.
-    switch (inner) {
-      case OptionAction.delete:
-        name = 'editor/delete';
-        break;
-      case OptionAction.duplicate:
-        name = 'editor/duplicate';
-        break;
-      case OptionAction.turnInto:
-        name = 'editor/turn_into';
-        break;
-      case OptionAction.moveUp:
-        name = 'editor/move_up';
-        break;
-      case OptionAction.moveDown:
-        name = 'editor/move_down';
-        break;
-      default:
-        throw UnimplementedError();
-    }
-    if (name.isEmpty) {
-      return null;
-    }
-    return FlowySvg(name: name);
-  }
+  Widget? leftIcon(Color iconColor) => FlowySvg(name: inner.assetName);
 
   @override
-  String get name {
-    var description = '';
-    switch (inner) {
-      // TODO: l10n
-      case OptionAction.delete:
-        description = LocaleKeys.document_plugins_optionAction_delete.tr();
-        break;
-      case OptionAction.duplicate:
-        description = LocaleKeys.document_plugins_optionAction_duplicate.tr();
-        break;
-      case OptionAction.turnInto:
-        description = LocaleKeys.document_plugins_optionAction_turnInto.tr();
-        break;
-      case OptionAction.moveUp:
-        description = LocaleKeys.document_plugins_optionAction_moveUp.tr();
-        break;
-      case OptionAction.moveDown:
-        description = LocaleKeys.document_plugins_optionAction_moveDown.tr();
-        break;
-      case OptionAction.color:
-        description = LocaleKeys.document_plugins_optionAction_color.tr();
-        break;
-      case OptionAction.divider:
-        throw UnimplementedError();
-    }
-    return description;
-  }
+  String get name => inner.description;
+}
+
+class OptionAlignWrapper extends ActionCell {
+  OptionAlignWrapper(this.inner);
+
+  final OptionAlignType inner;
+
+  @override
+  Widget? leftIcon(Color iconColor) => FlowySvg(name: inner.assetName);
+
+  @override
+  String get name => inner.description;
 }

+ 1 - 0
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action_button.dart

@@ -102,6 +102,7 @@ class OptionActionList extends StatelessWidget {
       case OptionAction.moveDown:
         transaction.moveNode(node.path.next.next, node);
         break;
+      case OptionAction.align:
       case OptionAction.color:
       case OptionAction.divider:
         throw UnimplementedError();

+ 25 - 1
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/insert_page_command.dart

@@ -15,7 +15,31 @@ class DatabaseBlockKeys {
 }
 
 extension InsertDatabase on EditorState {
-  Future<void> insertPage(ViewPB parentView, ViewPB childView) async {
+  Future<void> insertInlinePage(String parentViewId, ViewPB childView) async {
+    final selection = this.selection;
+    if (selection == null || !selection.isCollapsed) {
+      return;
+    }
+    final node = getNodeAtPath(selection.end.path);
+    if (node == null) {
+      return;
+    }
+
+    final transaction = this.transaction;
+    transaction.insertNode(
+      selection.end.path,
+      Node(
+        type: _convertPageType(childView),
+        attributes: {
+          DatabaseBlockKeys.parentID: parentViewId,
+          DatabaseBlockKeys.viewID: childView.id,
+        },
+      ),
+    );
+    await apply(transaction);
+  }
+
+  Future<void> insertReferencePage(ViewPB childView) async {
     final selection = this.selection;
     if (selection == null || !selection.isCollapsed) {
       return;

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart

@@ -35,7 +35,7 @@ void showLinkToPageMenu(
         layoutType: pageType,
         hintText: pageType.toHintText(),
         onSelected: (appPB, viewPB) {
-          editorState.insertPage(appPB, viewPB);
+          editorState.insertReferencePage(viewPB);
           linkToPageMenuEntry.remove();
         },
       ),

+ 10 - 32
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/board/board_view_menu_item.dart

@@ -7,7 +7,7 @@ import 'package:appflowy/plugins/document/application/prelude.dart';
 import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart';
 import 'package:easy_localization/easy_localization.dart';
 
-SelectionMenuItem boardViewMenuItem(DocumentBloc documentBloc) =>
+SelectionMenuItem inlineBoardMenuItem(DocumentBloc documentBloc) =>
     SelectionMenuItem(
       name: LocaleKeys.document_slashMenu_board_createANewBoard.tr(),
       icon: (editorState, onSelected, style) => SelectableSvgWidget(
@@ -15,43 +15,21 @@ SelectionMenuItem boardViewMenuItem(DocumentBloc documentBloc) =>
         isSelected: onSelected,
         style: style,
       ),
-      keywords: ['board', 'kanban'],
+      keywords: ['board', 'kanban', 'database'],
       handler: (editorState, menuService, context) async {
         if (!documentBloc.view.hasParentViewId()) {
           return;
         }
 
-        final appId = documentBloc.view.parentViewId;
-        final service = ViewBackendService();
-
-        final result = (await ViewBackendService.createView(
-          parentViewId: appId,
+        final parentViewId = documentBloc.view.parentViewId;
+        ViewBackendService.createView(
+          parentViewId: parentViewId,
           name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
           layoutType: ViewLayoutPB.Board,
-        ))
-            .getLeftOrNull();
-
-        // If the result is null, then something went wrong here.
-        if (result == null) {
-          return;
-        }
-
-        final app = (await service.getView(result.viewId)).getLeftOrNull();
-        // We should show an error dialog.
-        if (app == null) {
-          return;
-        }
-
-        final view = (await service.getChildView(
-          parentViewId: result.viewId,
-          childViewId: result.id,
-        ))
-            .getLeftOrNull();
-        // As this.
-        if (view == null) {
-          return;
-        }
-
-        editorState.insertPage(app, view);
+        ).then(
+          (value) => value
+              .swap()
+              .map((r) => editorState.insertInlinePage(parentViewId, r)),
+        );
       },
     );

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart

@@ -51,7 +51,7 @@ SelectionMenuItem calloutItem = SelectionMenuItem.node(
   nodeBuilder: (editorState) => calloutNode(),
   replace: (_, node) => node.delta?.isEmpty ?? false,
   updateSelection: (_, path, __, ___) {
-    return Selection.single(path: [...path, 0], startOffset: 0);
+    return Selection.single(path: path, startOffset: 0);
   },
 );
 

+ 3 - 3
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/grid/grid_menu_item.dart

@@ -6,14 +6,14 @@ import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 
-SelectionMenuItem gridMenuItem = SelectionMenuItem(
+SelectionMenuItem referenceGridMenuItem = SelectionMenuItem(
   name: LocaleKeys.document_plugins_referencedGrid.tr(),
   icon: (editorState, onSelected, style) => SelectableSvgWidget(
-    name: 'editor/board',
+    name: 'editor/grid',
     isSelected: onSelected,
     style: style,
   ),
-  keywords: ['referenced', 'grid'],
+  keywords: ['referenced', 'grid', 'database'],
   handler: (editorState, menuService, context) {
     final container = Overlay.of(context);
     showLinkToPageMenu(

+ 11 - 32
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/grid/grid_view_menu_item.dart

@@ -7,7 +7,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:easy_localization/easy_localization.dart';
 
-SelectionMenuItem gridViewMenuItem(DocumentBloc documentBloc) =>
+SelectionMenuItem inlineGridMenuItem(DocumentBloc documentBloc) =>
     SelectionMenuItem(
       name: LocaleKeys.document_slashMenu_grid_createANewGrid.tr(),
       icon: (editorState, onSelected, style) => SelectableSvgWidget(
@@ -15,43 +15,22 @@ SelectionMenuItem gridViewMenuItem(DocumentBloc documentBloc) =>
         isSelected: onSelected,
         style: style,
       ),
-      keywords: ['grid'],
+      keywords: ['grid', 'database'],
       handler: (editorState, menuService, context) async {
         if (!documentBloc.view.hasParentViewId()) {
           return;
         }
 
-        final appId = documentBloc.view.parentViewId;
-        final service = ViewBackendService();
-
-        final result = (await ViewBackendService.createView(
-          parentViewId: appId,
+        final parentViewId = documentBloc.view.parentViewId;
+        ViewBackendService.createView(
+          parentViewId: parentViewId,
+          openAfterCreate: false,
           name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
           layoutType: ViewLayoutPB.Grid,
-        ))
-            .getLeftOrNull();
-
-        // If the result is null, then something went wrong here.
-        if (result == null) {
-          return;
-        }
-
-        final app = (await service.getView(result.viewId)).getLeftOrNull();
-        // We should show an error dialog.
-        if (app == null) {
-          return;
-        }
-
-        final view = (await service.getChildView(
-          parentViewId: result.viewId,
-          childViewId: result.id,
-        ))
-            .getLeftOrNull();
-        // As this.
-        if (view == null) {
-          return;
-        }
-
-        editorState.insertPage(app, view);
+        ).then(
+          (value) => value
+              .swap()
+              .map((r) => editorState.insertInlinePage(parentViewId, r)),
+        );
       },
     );

+ 9 - 9
frontend/appflowy_flutter/lib/startup/tasks/app_window_size_manager.dart

@@ -8,12 +8,15 @@ import 'package:appflowy/startup/startup.dart';
 
 class WindowSizeManager {
   static const double minWindowHeight = 400.0;
-  static const double minWindowWidth = 600.0;
+  static const double minWindowWidth = 800.0;
+
+  static const width = 'width';
+  static const height = 'height';
 
   Future<void> saveSize(Size size) async {
     final windowSize = {
-      'height': max(size.height, minWindowHeight),
-      'width': max(size.width, minWindowWidth),
+      height: max(size.height, minWindowHeight),
+      width: max(size.width, minWindowWidth),
     };
 
     await getIt<KeyValueStorage>().set(
@@ -23,14 +26,11 @@ class WindowSizeManager {
   }
 
   Future<Size> getSize() async {
-    const defaultWindowSize = '{"height": 600.0, "width": 800.0}';
+    final defaultWindowSize = jsonEncode({height: 600.0, width: 800.0});
     final windowSize = await getIt<KeyValueStorage>().get(KVKeys.windowSize);
     final size = json.decode(
-      windowSize.fold(
-        (l) => defaultWindowSize,
-        (r) => r,
-      ),
+      windowSize.getOrElse(() => defaultWindowSize),
     );
-    return Size(size['width']!, size['height']!);
+    return Size(size[width]!, size[height]!);
   }
 }

+ 0 - 1
frontend/appflowy_flutter/lib/startup/tasks/windows.dart

@@ -34,7 +34,6 @@ class InitAppWindowTask extends LaunchTask with WindowListener {
         WindowSizeManager.minWindowWidth,
         WindowSizeManager.minWindowHeight,
       ),
-      center: true,
       title: title,
     );
 

+ 44 - 49
frontend/appflowy_flutter/lib/user/presentation/folder/folder_widget.dart

@@ -2,9 +2,13 @@ import 'dart:io';
 
 import 'package:appflowy/util/file_picker/file_picker_service.dart';
 import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
 import 'package:flutter/material.dart';
 import 'package:fluttertoast/fluttertoast.dart';
+import 'package:google_fonts/google_fonts.dart';
 
 import '../../../generated/locale_keys.g.dart';
 import '../../../startup/startup.dart';
@@ -19,9 +23,9 @@ enum _FolderPage {
 
 class FolderWidget extends StatefulWidget {
   const FolderWidget({
-    Key? key,
+    super.key,
     required this.createFolderCallback,
-  }) : super(key: key);
+  });
 
   final Future<void> Function() createFolderCallback;
 
@@ -41,9 +45,6 @@ class _FolderWidgetState extends State<FolderWidget> {
     switch (page) {
       case _FolderPage.options:
         return FolderOptionsWidget(
-          onPressedCreate: () {
-            setState(() => page = _FolderPage.create);
-          },
           onPressedOpen: () {
             _openFolder();
           },
@@ -65,43 +66,36 @@ class _FolderWidgetState extends State<FolderWidget> {
     if (path != null) {
       await getIt<LocalFileStorage>().setPath(path);
       await widget.createFolderCallback();
+      setState(() {});
     }
   }
 }
 
 class FolderOptionsWidget extends StatelessWidget {
   const FolderOptionsWidget({
-    Key? key,
-    required this.onPressedCreate,
+    super.key,
     required this.onPressedOpen,
-  }) : super(key: key);
+  });
 
-  final VoidCallback onPressedCreate;
   final VoidCallback onPressedOpen;
 
   @override
   Widget build(BuildContext context) {
-    return Column(
-      children: [
-        _FolderCard(
-          title: LocaleKeys.settings_files_createNewFolder.tr(),
-          subtitle: LocaleKeys.settings_files_createNewFolderDesc.tr(),
-          trailing: _buildTextButton(
-            context,
-            LocaleKeys.settings_files_create.tr(),
-            onPressedCreate,
-          ),
-        ),
-        _FolderCard(
-          title: LocaleKeys.settings_files_openFolder.tr(),
-          subtitle: LocaleKeys.settings_files_openFolderDesc.tr(),
+    return FutureBuilder(
+      future: getIt<LocalFileStorage>().getPath(),
+      builder: (context, result) {
+        final subtitle = result.hasData ? result.data! : '';
+        return _FolderCard(
+          icon: const FlowySvg(name: 'common/archive'),
+          title: LocaleKeys.settings_files_defineWhereYourDataIsStored.tr(),
+          subtitle: subtitle,
           trailing: _buildTextButton(
             context,
-            LocaleKeys.settings_files_open.tr(),
+            LocaleKeys.settings_files_set.tr(),
             onPressedOpen,
           ),
-        ),
-      ],
+        );
+      },
     );
   }
 }
@@ -224,27 +218,24 @@ Widget _buildTextButton(
   String title,
   VoidCallback onPressed,
 ) {
-  return FlowyTextButton(
+  return SecondaryTextButton(
     title,
+    mode: SecondaryTextButtonMode.small,
     onPressed: onPressed,
-    fillColor: Theme.of(context).colorScheme.primary,
-    fontColor: Theme.of(context).colorScheme.onPrimary,
-    hoverColor: Theme.of(context).colorScheme.primaryContainer,
   );
 }
 
 class _FolderCard extends StatelessWidget {
   const _FolderCard({
-    Key? key,
     required this.title,
     required this.subtitle,
     this.trailing,
-  }) : super(key: key);
+    this.icon,
+  });
 
   final String title;
-
   final String subtitle;
-
+  final Widget? icon;
   final Widget? trailing;
 
   @override
@@ -252,31 +243,35 @@ class _FolderCard extends StatelessWidget {
     return Card(
       child: Padding(
         padding: const EdgeInsets.symmetric(
-          vertical: 4.0,
+          vertical: 16.0,
           horizontal: 16.0,
         ),
         child: Row(
           children: [
+            if (icon != null)
+              Padding(
+                padding: const EdgeInsets.only(right: 20),
+                child: icon!,
+              ),
             Expanded(
               child: Column(
                 crossAxisAlignment: CrossAxisAlignment.start,
                 children: [
-                  FlowyText.medium(
+                  FlowyText.regular(
                     title,
+                    fontSize: FontSizes.s14,
+                    fontFamily: GoogleFonts.poppins(
+                      fontWeight: FontWeight.w500,
+                    ).fontFamily,
                   ),
-                  Row(
-                    children: [
-                      Flexible(
-                        child: Text(
-                          subtitle,
-                          overflow: TextOverflow.ellipsis,
-                          style:
-                              Theme.of(context).textTheme.bodyMedium!.copyWith(
-                                    fontWeight: FontWeight.w400,
-                                  ),
-                        ),
-                      ),
-                    ],
+                  const VSpace(4),
+                  FlowyText.regular(
+                    subtitle,
+                    overflow: TextOverflow.ellipsis,
+                    fontSize: FontSizes.s12,
+                    fontFamily: GoogleFonts.poppins(
+                      fontWeight: FontWeight.w300,
+                    ).fontFamily,
                   ),
                 ],
               ),

+ 52 - 30
frontend/appflowy_flutter/lib/user/presentation/skip_log_in_screen.dart

@@ -1,5 +1,7 @@
 import 'package:appflowy/core/frameless_window.dart';
 import 'package:appflowy/startup/entry_point.dart';
+import 'package:appflowy/startup/launch_configuration.dart';
+import 'package:appflowy/startup/startup.dart';
 import 'package:appflowy/user/application/auth/auth_service.dart';
 import 'package:dartz/dartz.dart' as dartz;
 import 'package:easy_localization/easy_localization.dart';
@@ -11,11 +13,10 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
 import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
 import 'package:flutter/material.dart';
+import 'package:google_fonts/google_fonts.dart';
 import 'package:url_launcher/url_launcher.dart';
 
 import '../../generated/locale_keys.g.dart';
-import '../../startup/launch_configuration.dart';
-import '../../startup/startup.dart';
 import 'folder/folder_widget.dart';
 import 'router.dart';
 import 'widgets/background.dart';
@@ -35,6 +36,8 @@ class SkipLogInScreen extends StatefulWidget {
 }
 
 class _SkipLogInScreenState extends State<SkipLogInScreen> {
+  var _didCustomizeFolder = false;
+
   @override
   Widget build(BuildContext context) {
     return Scaffold(
@@ -48,60 +51,64 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
   Widget _renderBody(BuildContext context) {
     return Column(
       mainAxisAlignment: MainAxisAlignment.center,
+      crossAxisAlignment: CrossAxisAlignment.center,
       children: [
         FlowyLogoTitle(
           title: LocaleKeys.welcomeText.tr(),
-          logoSize: const Size.square(60),
+          logoSize: const Size.square(40),
         ),
-        const VSpace(40),
-        Row(
-          mainAxisAlignment: MainAxisAlignment.center,
-          children: [
-            GoButton(
-              onPressed: () => _autoRegister(context),
-            ),
-          ],
+        const VSpace(32),
+        GoButton(
+          onPressed: () {
+            if (_didCustomizeFolder) {
+              _relaunchAppAndAutoRegister();
+            } else {
+              _autoRegister(context);
+            }
+          },
         ),
-        const VSpace(20),
+        const VSpace(32),
+        _buildSubscribeButtons(context),
+        const VSpace(32),
         SizedBox(
-          width: MediaQuery.of(context).size.width * 0.8,
+          width: MediaQuery.of(context).size.width * 0.5,
           child: FolderWidget(
             createFolderCallback: () async {
-              await FlowyRunner.run(
-                FlowyApp(),
-                config: const LaunchConfiguration(
-                  autoRegistrationSupported: true,
-                ),
-              );
+              _didCustomizeFolder = true;
             },
           ),
         ),
-        const Spacer(),
-        _buildSubscribeButtons(context),
+        const VSpace(64),
       ],
     );
   }
 
-  Row _buildSubscribeButtons(BuildContext context) {
+  Widget _buildSubscribeButtons(BuildContext context) {
     return Row(
       mainAxisAlignment: MainAxisAlignment.center,
       children: [
+        FlowyText.regular(
+          LocaleKeys.youCanAlso.tr(),
+          fontSize: FontSizes.s12,
+        ),
         FlowyTextButton(
           LocaleKeys.githubStarText.tr(),
           fontWeight: FontWeight.w500,
           fontColor: Theme.of(context).colorScheme.primary,
-          decoration: TextDecoration.underline,
           hoverColor: Colors.transparent,
           fillColor: Colors.transparent,
-          onPressed: () =>
-              _launchURL('https://github.com/AppFlowy-IO/appflowy'),
+          onPressed: () => _launchURL(
+            'https://github.com/AppFlowy-IO/appflowy',
+          ),
+        ),
+        FlowyText.regular(
+          LocaleKeys.and.tr(),
+          fontSize: FontSizes.s12,
         ),
-        const HSpace(20),
         FlowyTextButton(
           LocaleKeys.subscribeNewsletterText.tr(),
           fontWeight: FontWeight.w500,
           fontColor: Theme.of(context).colorScheme.primary,
-          decoration: TextDecoration.underline,
           hoverColor: Colors.transparent,
           fillColor: Colors.transparent,
           onPressed: () => _launchURL('https://www.appflowy.io/blog'),
@@ -110,7 +117,7 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
     );
   }
 
-  _launchURL(String url) async {
+  Future<void> _launchURL(String url) async {
     final uri = Uri.parse(url);
     if (await canLaunchUrl(uri)) {
       await launchUrl(uri);
@@ -133,6 +140,15 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
     );
   }
 
+  Future<void> _relaunchAppAndAutoRegister() async {
+    await FlowyRunner.run(
+      FlowyApp(),
+      config: const LaunchConfiguration(
+        autoRegistrationSupported: true,
+      ),
+    );
+  }
+
   void _openCurrentWorkspace(
     BuildContext context,
     UserProfilePB user,
@@ -162,8 +178,14 @@ class GoButton extends StatelessWidget {
   Widget build(BuildContext context) {
     return FlowyTextButton(
       LocaleKeys.letsGoButtonText.tr(),
-      fontSize: FontSizes.s16,
-      padding: const EdgeInsets.symmetric(horizontal: 80, vertical: 12.0),
+      constraints: const BoxConstraints(
+        maxWidth: 340,
+        maxHeight: 48,
+      ),
+      mainAxisAlignment: MainAxisAlignment.center,
+      fontSize: FontSizes.s14,
+      fontFamily: GoogleFonts.poppins(fontWeight: FontWeight.w500).fontFamily,
+      padding: const EdgeInsets.symmetric(vertical: 14.0),
       onPressed: onPressed,
       fillColor: Theme.of(context).colorScheme.primary,
       fontColor: Theme.of(context).colorScheme.onPrimary,

+ 6 - 3
frontend/appflowy_flutter/lib/user/presentation/widgets/background.dart

@@ -5,6 +5,7 @@ import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flutter/material.dart';
+import 'package:google_fonts/google_fonts.dart';
 
 class AuthFormContainer extends StatelessWidget {
   final List<Widget> children;
@@ -43,12 +44,14 @@ class FlowyLogoTitle extends StatelessWidget {
         children: [
           SizedBox.fromSize(
             size: logoSize,
-            child: svgWidget("flowy_logo"),
+            child: svgWidget('flowy_logo'),
           ),
-          const VSpace(30),
-          FlowyText.semibold(
+          const VSpace(40),
+          FlowyText.regular(
             title,
             fontSize: FontSizes.s24,
+            fontFamily:
+                GoogleFonts.poppins(fontWeight: FontWeight.w500).fontFamily,
             color: Theme.of(context).colorScheme.tertiary,
           ),
         ],

+ 3 - 1
frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_system_view.dart

@@ -1,5 +1,6 @@
 import 'package:appflowy/workspace/presentation/settings/widgets/settings_export_file_widget.dart';
 import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart';
+import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 
 class SettingsFileSystemView extends StatefulWidget {
@@ -14,7 +15,8 @@ class SettingsFileSystemView extends StatefulWidget {
 class _SettingsFileSystemViewState extends State<SettingsFileSystemView> {
   late final _items = [
     const SettingsFileLocationCustomizer(),
-    const SettingsExportFileWidget()
+    // disable export data for v0.2.0 in release mode.
+    if (kDebugMode) const SettingsExportFileWidget()
   ];
 
   @override

+ 30 - 18
frontend/appflowy_flutter/lib/workspace/presentation/widgets/pop_up_action.dart

@@ -74,8 +74,7 @@ class _PopoverActionListState<T extends PopoverAction>
             );
           } else if (action is PopoverActionCell) {
             return PopoverActionCellWidget<T>(
-              mutex: widget.mutex,
-              // popoverController: popoverController,
+              popoverController: popoverController,
               action: action,
               itemHeight: ActionListSizes.itemHeight,
             );
@@ -104,13 +103,18 @@ abstract class ActionCell extends PopoverAction {
   String get name;
 }
 
+typedef PopoverActionCellBuilder = Widget Function(
+  BuildContext context,
+  PopoverController parentController,
+  PopoverController controller,
+);
+
 abstract class PopoverActionCell extends PopoverAction {
   Widget? leftIcon(Color iconColor) => null;
   Widget? rightIcon(Color iconColor) => null;
   String get name;
 
-  Widget Function(BuildContext context, PopoverController controller)
-      get builder;
+  PopoverActionCellBuilder get builder;
 }
 
 abstract class CustomActionCell extends PopoverAction {
@@ -146,7 +150,7 @@ class ActionCellWidget<T extends PopoverAction> extends StatelessWidget {
     final rightIcon =
         actionCell.rightIcon(Theme.of(context).colorScheme.onSurface);
 
-    return _HoverButton(
+    return HoverButton(
       itemHeight: itemHeight,
       leftIcon: leftIcon,
       rightIcon: rightIcon,
@@ -156,24 +160,30 @@ class ActionCellWidget<T extends PopoverAction> extends StatelessWidget {
   }
 }
 
-class PopoverActionCellWidget<T extends PopoverAction> extends StatelessWidget {
-  PopoverActionCellWidget({
-    Key? key,
-    this.mutex,
-    // required this.popoverController,
+class PopoverActionCellWidget<T extends PopoverAction> extends StatefulWidget {
+  const PopoverActionCellWidget({
+    super.key,
+    required this.popoverController,
     required this.action,
     required this.itemHeight,
-  }) : super(key: key);
+  });
 
   final T action;
   final double itemHeight;
 
-  final PopoverMutex? mutex;
-  final PopoverController popoverController = PopoverController();
+  final PopoverController popoverController;
+
+  @override
+  State<PopoverActionCellWidget> createState() =>
+      _PopoverActionCellWidgetState();
+}
 
+class _PopoverActionCellWidgetState<T extends PopoverAction>
+    extends State<PopoverActionCellWidget<T>> {
+  final popoverController = PopoverController();
   @override
   Widget build(BuildContext context) {
-    final actionCell = action as PopoverActionCell;
+    final actionCell = widget.action as PopoverActionCell;
     final leftIcon =
         actionCell.leftIcon(Theme.of(context).colorScheme.onSurface);
     final rightIcon =
@@ -183,10 +193,11 @@ class PopoverActionCellWidget<T extends PopoverAction> extends StatelessWidget {
       asBarrier: true,
       popupBuilder: (context) => actionCell.builder(
         context,
+        widget.popoverController,
         popoverController,
       ),
-      child: _HoverButton(
-        itemHeight: itemHeight,
+      child: HoverButton(
+        itemHeight: widget.itemHeight,
         leftIcon: leftIcon,
         rightIcon: rightIcon,
         name: actionCell.name,
@@ -196,8 +207,9 @@ class PopoverActionCellWidget<T extends PopoverAction> extends StatelessWidget {
   }
 }
 
-class _HoverButton extends StatelessWidget {
-  const _HoverButton({
+class HoverButton extends StatelessWidget {
+  const HoverButton({
+    super.key,
     required this.onTap,
     required this.itemHeight,
     required this.leftIcon,

+ 3 - 3
frontend/appflowy_flutter/macos/Runner.xcodeproj/project.pbxproj

@@ -434,7 +434,7 @@
 					"@executable_path/../Frameworks",
 				);
 				ONLY_ACTIVE_ARCH = YES;
-				PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.macos;
+				PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.appflowy.flutter;
 				PRODUCT_NAME = AppFlowy;
 				PROVISIONING_PROFILE_SPECIFIER = "";
 				STRIP_STYLE = "non-global";
@@ -567,7 +567,7 @@
 					"$(inherited)",
 					"@executable_path/../Frameworks",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.macos;
+				PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.appflowy.flutter;
 				PRODUCT_NAME = AppFlowy;
 				PROVISIONING_PROFILE_SPECIFIER = "";
 				STRIP_STYLE = "non-global";
@@ -593,7 +593,7 @@
 					"@executable_path/../Frameworks",
 				);
 				ONLY_ACTIVE_ARCH = YES;
-				PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.macos;
+				PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.appflowy.flutter;
 				PRODUCT_NAME = AppFlowy;
 				PROVISIONING_PROFILE_SPECIFIER = "";
 				STRIP_STYLE = "non-global";

+ 2 - 10
frontend/appflowy_flutter/macos/Runner/DebugProfile.entitlements

@@ -2,19 +2,11 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
+	<key>com.apple.security.cs.allow-jit</key>
+	<true/>
 	<key>com.apple.security.temporary-exception.files.absolute-path.read-write</key>
 	<array>
 		<string>/</string>
 	</array>
-	<key>com.apple.security.files.user-selected.read-write</key>
-	<true/>
-	<key>com.apple.security.app-sandbox</key>
-	<true/>
-	<key>com.apple.security.cs.allow-jit</key>
-	<true/>
-	<key>com.apple.security.network.server</key>
-	<true/>
-	<key>com.apple.security.network.client</key>
-	<true/>
 </dict>
 </plist>

+ 11 - 11
frontend/appflowy_flutter/macos/Runner/Info.plist

@@ -25,6 +25,17 @@
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
 	<string>$(FLUTTER_BUILD_NAME)</string>
+	<key>CFBundleURLTypes</key>
+	<array>
+		<dict>
+			<key>CFBundleURLName</key>
+			<string></string>
+			<key>CFBundleURLSchemes</key>
+			<array>
+				<string>io.appflowy.appflowy-flutter</string>
+			</array>
+		</dict>
+	</array>
 	<key>CFBundleVersion</key>
 	<string>$(FLUTTER_BUILD_NUMBER)</string>
 	<key>LSMinimumSystemVersion</key>
@@ -40,16 +51,5 @@
 	<string>MainMenu</string>
 	<key>NSPrincipalClass</key>
 	<string>NSApplication</string>
-	<key>CFBundleURLTypes</key>
-	<array>
-		<dict>
-			<key>CFBundleURLName</key>
-			<string></string>
-			<key>CFBundleURLSchemes</key>
-			<array>
-				<string>io.appflowy.appflowy-flutter</string>
-			</array>
-		</dict>
-	</array>
 </dict>
 </plist>

+ 0 - 6
frontend/appflowy_flutter/macos/Runner/Release.entitlements

@@ -6,11 +6,5 @@
 	<array>
 		<string>/</string>
 	</array>
-	<key>com.apple.security.files.user-selected.read-write</key>
-	<true/>
-	<key>com.apple.security.app-sandbox</key>
-	<true/>
-	<key>com.apple.security.network.client</key>
-	<true/>
 </dict>
 </plist>

+ 4 - 0
frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart

@@ -121,6 +121,8 @@ class FlowyTextButton extends StatelessWidget {
 
   final TextDecoration? decoration;
 
+  final String? fontFamily;
+
   // final HoverDisplayConfig? hoverDisplay;
   const FlowyTextButton(
     this.text, {
@@ -139,6 +141,7 @@ class FlowyTextButton extends StatelessWidget {
     this.tooltip,
     this.constraints = const BoxConstraints(minWidth: 58.0, minHeight: 30.0),
     this.decoration,
+    this.fontFamily,
   }) : super(key: key);
 
   @override
@@ -157,6 +160,7 @@ class FlowyTextButton extends StatelessWidget {
         color: fontColor,
         textAlign: TextAlign.center,
         decoration: decoration,
+        fontFamily: fontFamily,
       ),
     );
 

+ 14 - 9
frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/color_picker.dart

@@ -2,8 +2,8 @@ import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/material.dart';
 
-class ColorOption {
-  const ColorOption({
+class FlowyColorOption {
+  const FlowyColorOption({
     required this.color,
     required this.name,
   });
@@ -13,12 +13,13 @@ class ColorOption {
 }
 
 class FlowyColorPicker extends StatelessWidget {
-  final List<ColorOption> colors;
+  final List<FlowyColorOption> colors;
   final Color? selected;
   final Function(Color color, int index)? onTap;
   final double separatorSize;
   final double iconSize;
   final double itemHeight;
+  final Border? border;
 
   const FlowyColorPicker({
     Key? key,
@@ -28,6 +29,7 @@ class FlowyColorPicker extends StatelessWidget {
     this.separatorSize = 4,
     this.iconSize = 16,
     this.itemHeight = 32,
+    this.border,
   }) : super(key: key);
 
   @override
@@ -46,7 +48,10 @@ class FlowyColorPicker extends StatelessWidget {
     );
   }
 
-  Widget _buildColorOption(ColorOption option, int i) {
+  Widget _buildColorOption(
+    FlowyColorOption option,
+    int i,
+  ) {
     Widget? checkmark;
     if (selected == option.color) {
       checkmark = svgWidget("grid/checkmark");
@@ -55,11 +60,11 @@ class FlowyColorPicker extends StatelessWidget {
     final colorIcon = SizedBox.square(
       dimension: iconSize,
       child: Container(
-        decoration: BoxDecoration(
-          color: option.color,
-          shape: BoxShape.circle,
-        ),
-      ),
+          decoration: BoxDecoration(
+        color: option.color,
+        shape: BoxShape.circle,
+        border: border,
+      )),
     );
 
     return SizedBox(

+ 7 - 0
frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart

@@ -10,6 +10,7 @@ class FlowyText extends StatelessWidget {
   final Color? color;
   final TextDecoration? decoration;
   final bool selectable;
+  final String? fontFamily;
 
   const FlowyText(
     this.title, {
@@ -22,6 +23,7 @@ class FlowyText extends StatelessWidget {
     this.maxLines = 1,
     this.decoration,
     this.selectable = false,
+    this.fontFamily,
   }) : super(key: key);
 
   const FlowyText.regular(
@@ -34,6 +36,7 @@ class FlowyText extends StatelessWidget {
     this.maxLines = 1,
     this.decoration,
     this.selectable = false,
+    this.fontFamily,
   })  : fontWeight = FontWeight.w400,
         super(key: key);
 
@@ -47,6 +50,7 @@ class FlowyText extends StatelessWidget {
     this.maxLines = 1,
     this.decoration,
     this.selectable = false,
+    this.fontFamily,
   })  : fontWeight = FontWeight.w500,
         super(key: key);
 
@@ -60,6 +64,7 @@ class FlowyText extends StatelessWidget {
     this.maxLines = 1,
     this.decoration,
     this.selectable = false,
+    this.fontFamily,
   })  : fontWeight = FontWeight.w600,
         super(key: key);
 
@@ -78,6 +83,7 @@ class FlowyText extends StatelessWidget {
               fontWeight: fontWeight,
               color: color,
               decoration: decoration,
+              fontFamily: fontFamily,
             ),
       );
     } else {
@@ -91,6 +97,7 @@ class FlowyText extends StatelessWidget {
               fontWeight: fontWeight,
               color: color,
               decoration: decoration,
+              fontFamily: fontFamily,
             ),
       );
     }

+ 10 - 2
frontend/appflowy_flutter/pubspec.lock

@@ -53,10 +53,10 @@ packages:
     dependency: "direct main"
     description:
       name: appflowy_editor
-      sha256: "3561bd7bd99541508353034130a98ab2d9be54f690bb982f85c2b3eedb8fe63e"
+      sha256: "5fa08d19842b402482aff5044b25d164dbef3eb6155015d123ef02925ba5b52d"
       url: "https://pub.dev"
     source: hosted
-    version: "1.0.0-dev.1"
+    version: "1.0.0-dev.3"
   appflowy_popover:
     dependency: "direct main"
     description:
@@ -1605,6 +1605,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.4"
+  visibility_detector:
+    dependency: transitive
+    description:
+      name: visibility_detector
+      sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.4.0+2"
   vm_service:
     dependency: transitive
     description:

+ 2 - 1
frontend/appflowy_flutter/pubspec.yaml

@@ -42,7 +42,7 @@ dependencies:
     git:
       url: https://github.com/AppFlowy-IO/appflowy-board.git
       ref: a183c57
-  appflowy_editor: 1.0.0-dev.1
+  appflowy_editor: 1.0.0-dev.3
   appflowy_popover:
     path: packages/appflowy_popover
 
@@ -163,6 +163,7 @@ flutter:
   assets:
     - assets/images/
     - assets/images/home/
+    - assets/images/editor/align/
     - assets/images/editor/
     - assets/images/grid/
     - assets/images/emoji/

+ 4 - 4
frontend/rust-lib/flowy-database2/src/manager.rs

@@ -84,6 +84,7 @@ impl DatabaseManager2 {
     self.get_database(&database_id).await
   }
 
+  #[tracing::instrument(level = "debug", skip(self), err)]
   pub async fn get_database_id_with_view_id(&self, view_id: &str) -> FlowyResult<String> {
     let database_id = self.with_user_database(Err(FlowyError::internal()), |database| {
       database
@@ -98,6 +99,7 @@ impl DatabaseManager2 {
       return Ok(editor.clone());
     }
 
+    tracing::trace!("create new editor for database {}", database_id);
     let mut editors = self.editors.write().await;
     let database = MutexDatabase::new(self.with_user_database(
       Err(FlowyError::record_not_found()),
@@ -178,6 +180,7 @@ impl DatabaseManager2 {
     Ok(())
   }
 
+  #[tracing::instrument(level = "trace", skip(self), err)]
   pub async fn create_linked_view(
     &self,
     name: String,
@@ -188,11 +191,8 @@ impl DatabaseManager2 {
     self.with_user_database(
       Err(FlowyError::internal().context("Create database view failed")),
       |user_database| {
-        let database = user_database
-          .get_database(&database_id)
-          .ok_or_else(FlowyError::record_not_found)?;
         let params = CreateViewParams::new(database_id, database_view_id, name, layout.into());
-        database.create_linked_view(params)?;
+        user_database.create_database_linked_view(params)?;
         Ok(())
       },
     )?;

+ 0 - 1
frontend/rust-lib/flowy-document2/src/manager.rs

@@ -55,7 +55,6 @@ impl DocumentManager {
   }
 
   pub fn open_document(&self, doc_id: String) -> FlowyResult<Arc<Document>> {
-    tracing::debug!("open a document: {:?}", &doc_id);
     if let Some(doc) = self.documents.read().get(&doc_id) {
       return Ok(doc.clone());
     }

+ 20 - 43
frontend/rust-lib/flowy-folder2/src/manager.rs

@@ -1,4 +1,4 @@
-use std::collections::{HashMap, HashSet};
+use std::collections::HashSet;
 use std::ops::Deref;
 use std::sync::{Arc, Weak};
 
@@ -203,27 +203,26 @@ impl Folder2Manager {
     let handler = self.get_handler(&view_layout)?;
     let user_id = self.user.user_id()?;
     let meta = params.meta.clone();
-    match params.initial_data.is_empty() {
-      true => {
-        tracing::trace!("Create view with build-in data");
-        handler
-          .create_built_in_view(user_id, &params.view_id, &params.name, view_layout.clone())
-          .await?;
-      },
-      false => {
-        tracing::trace!("Create view with view data");
-        handler
-          .create_view_with_view_data(
-            user_id,
-            &params.view_id,
-            &params.name,
-            params.initial_data.clone(),
-            view_layout.clone(),
-            meta,
-          )
-          .await?;
-      },
+
+    if meta.is_empty() && params.initial_data.is_empty() {
+      tracing::trace!("Create view with build-in data");
+      handler
+        .create_built_in_view(user_id, &params.view_id, &params.name, view_layout.clone())
+        .await?;
+    } else {
+      tracing::trace!("Create view with view data");
+      handler
+        .create_view_with_view_data(
+          user_id,
+          &params.view_id,
+          &params.name,
+          params.initial_data.clone(),
+          view_layout.clone(),
+          meta,
+        )
+        .await?;
     }
+
     let view = create_view(params, view_layout);
     self.with_folder((), |folder| {
       folder.insert_view(view.clone());
@@ -245,28 +244,6 @@ impl Folder2Manager {
     Ok(())
   }
 
-  pub async fn create_view_with_data(
-    &self,
-    view_id: &str,
-    name: &str,
-    view_layout: ViewLayout,
-    data: Vec<u8>,
-  ) -> FlowyResult<()> {
-    let user_id = self.user.user_id()?;
-    let handler = self.get_handler(&view_layout)?;
-    handler
-      .create_view_with_view_data(
-        user_id,
-        view_id,
-        name,
-        data,
-        view_layout,
-        HashMap::default(),
-      )
-      .await?;
-    Ok(())
-  }
-
   /// Returns the view with the given view id.
   /// The child views of the view will only access the first. So if you want to get the child view's
   /// child view, you need to call this method again.

Some files were not shown because too many files changed in this diff