Pārlūkot izejas kodu

chore: merge remote-tracking branch 'main' into develop (#2530)

* feat: show unscheduled events in calendar toolbar (#2411)

* refactor: use same show row detail function

* fix: adjust popover offset

* feat: show unscheduled events in toolbar

* chore: apply suggestions from Xazin

* refactor: refactor list item into separate widget

---------

Co-authored-by: Nathan.fooo <[email protected]>

* fix: default include time (#2444)

* fix: default include time

* chore: clarify logic and add comments

* chore: bump version 0.1.4 (#2455)

* chore: Update README.md

Update product screenshots

* fix: wrong day of week (#2468)

* feat: select which properties to show in calendar (#2482)

* feat: improve sidebar item dragged appearance (#2471)

* fix: show delete icon for document icon properly (#2475)

* feat: add hover effect on an event card (#2487)

* chore: delete unncessary openCard method in RowCardContainer

* chore: delete unnessary code and add comment

* chore: update editor v0.1.12 and format the readme (#2489)

* fix: number sort (#2507)

* bump version 0.1.5 (#2506)

* chore: bump version 0.1.5

* fix: could not trigger slash menu after inserting an emoji

* Revert "feat: add hover effect on an event card (#2487)"

This reverts commit f0a4b4b77d9e88637dd04a4a2377396b825f2b94.

* feat: add hover effect on an event card

* fix: #2469 duplicated cover

* chore: update changelog.md (#2510)

* chore: Update README.md

Add a screenshot of the calendar view

* fix: some regressions

---------

Co-authored-by: Nathan.fooo <[email protected]>
Co-authored-by: Lucas.Xu <[email protected]>
Co-authored-by: Annie <[email protected]>
Co-authored-by: Yijing Huang <[email protected]>
Richard Shiue 2 gadi atpakaļ
vecāks
revīzija
a978b29748
30 mainītis faili ar 516 papildinājumiem un 473 dzēšanām
  1. 1 1
      .github/workflows/release.yml
  2. 22 0
      CHANGELOG.md
  3. 5 3
      README.md
  4. 1 1
      frontend/Makefile.toml
  5. 3 1
      frontend/appflowy_flutter/assets/translations/en.json
  6. 1 0
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_setting_bloc.dart
  7. 23 39
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_day.dart
  8. 34 22
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart
  9. 8 5
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart
  10. 25 0
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_setting.dart
  11. 103 7
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_toolbar.dart
  12. 1 0
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/container/card_container.dart
  13. 20 2
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cal_bloc.dart
  14. 14 5
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart
  15. 3 0
      frontend/appflowy_flutter/lib/plugins/document/document_page.dart
  16. 41 46
      frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/cover/emoji_popover.dart
  17. 23 19
      frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/emoji_picker/emoji_menu_item.dart
  18. 4 0
      frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/section/section.dart
  19. 2 0
      frontend/appflowy_flutter/lib/workspace/presentation/home/menu/menu.dart
  20. 1 0
      frontend/appflowy_flutter/macos/Runner.xcodeproj/project.pbxproj
  21. 17 13
      frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/hover.dart
  22. 2 2
      frontend/appflowy_flutter/pubspec.lock
  23. 2 2
      frontend/appflowy_flutter/pubspec.yaml
  24. 11 1
      frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option.rs
  25. 1 1
      frontend/rust-lib/flowy-database/tests/database/mock_data/grid_mock_data.rs
  26. 2 2
      frontend/rust-lib/flowy-database/tests/database/sort_test/single_sort_test.rs
  27. 130 295
      frontend/rust-lib/flowy-document/src/editor/READ_ME.json
  28. 7 2
      frontend/scripts/flutter_release_build/build_flowy.dart
  29. 5 2
      frontend/scripts/flutter_release_build/tool.dart
  30. 4 2
      frontend/scripts/windows_installer/inno_setup_config.iss

+ 1 - 1
.github/workflows/release.yml

@@ -138,7 +138,7 @@ jobs:
         job:
           - {
               target: x86_64-apple-darwin,
-              os: macos-10.15,
+              os: macos-11,
               extra-build-args: "",
             }
     steps:

+ 22 - 0
CHANGELOG.md

@@ -1,5 +1,27 @@
 # Release Notes
 
+## Version 0.1.5 - 11/05/2023
+
+### Bug Fixes
+- Fix: calendar dates don't match with weekdays.
+- Fix: sort numbers in Grid.
+
+## Version 0.1.4 - 04/05/2023
+
+### New features
+- Use AppFlowy’s calendar views to plan and manage tasks and deadlines.
+- Writing can be improved with the help of OpenAI.
+
+## Version 0.1.3 - 24/04/2023
+
+### New features
+- Launch the official Dark Mode.
+- Customize the font color and highlight color by setting a hex color value and an opacity level.
+
+### Bug Fixes
+- Fix: the slash menu can be triggered by all other keyboards than English.
+- Fix: convert the single asterisk to italic text and the double asterisks to bold text.
+
 ## Version 0.1.2 - 03/28/2023
 
 ### Bug Fixes

+ 5 - 3
README.md

@@ -23,9 +23,11 @@ You are in charge of your data and customizations.
     <a href="https://twitter.com/appflowy"><b>Twitter</b></a>
 </p>
 
-<p align="center"><img src="https://user-images.githubusercontent.com/12026239/200787830-96be260b-d0a0-4152-864e-6730b19095cd.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
-<p align="center"><img src="https://user-images.githubusercontent.com/12026239/174753177-98e4c899-2356-4137-bb42-374bba2b127b.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
-<p align="center"><img src="https://user-images.githubusercontent.com/12026239/190650183-a940f1e0-a2c5-4797-ab3a-56758f6f696c.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
+<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664610-fc209a97-815e-4716-af07-d94a859d1907.png" alt="AppFlowy Docs & Notes & Wikis" width="1000px" /></p>
+<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664628-5def2450-914a-4b2d-b907-92b7476b9863.png" alt="AppFlowy Databases for Tasks and Projects" width="1000px" /></p>
+<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664642-22e26c1b-5eae-4635-9aa6-b12ecf1c3c46.png" alt="AppFlowy Kanban Board for To-Dos" width="1000px" /></p>
+<p align="center"><img src="https://github.com/AppFlowy-IO/AppFlowy/assets/12026239/6be93d2b-a5c5-48a9-b7cf-c599d5f5140c" alt="AppFlowy Calendars for Plan and Manage Content" width="1000px" /></p>
+<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664657-dc5291f3-67b0-4a43-a818-640e92735deb.png" alt="AppFlowy OpenAI GPT Writers" width="1000px" /></p>
 
 ## User Installation
 

+ 1 - 1
frontend/Makefile.toml

@@ -23,7 +23,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
 CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
 CARGO_MAKE_CRATE_NAME = "dart-ffi"
 LIB_NAME = "dart_ffi"
-CURRENT_APP_VERSION = "0.1.3"
+CURRENT_APP_VERSION = "0.1.5"
 FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
 PRODUCT_NAME = "AppFlowy"
 # CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html

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

@@ -418,7 +418,9 @@
       "showWeekNumbers": "Show week numbers",
       "showWeekends": "Show weekends",
       "firstDayOfWeek": "Start week on",
-      "layoutDateField": "Layout calendar by"
+      "layoutDateField": "Layout calendar by",
+      "noDateTitle": "No Date",
+      "emptyNoDate": "No unscheduled events"
     }
   }
 }

+ 1 - 0
frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_setting_bloc.dart

@@ -51,5 +51,6 @@ class CalendarSettingEvent with _$CalendarSettingEvent {
 }
 
 enum CalendarSettingAction {
+  properties,
   layout,
 }

+ 23 - 39
frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_day.dart

@@ -1,24 +1,23 @@
 import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
-import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
 import 'package:appflowy/plugins/database_view/widgets/card/card.dart';
 import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart';
 import 'package:appflowy/plugins/database_view/widgets/card/cells/card_cell.dart';
 import 'package:appflowy/plugins/database_view/widgets/card/cells/number_card_cell.dart';
 import 'package:appflowy/plugins/database_view/widgets/card/cells/url_card_cell.dart';
-import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
-import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra/theme_extension.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 
 import '../../grid/presentation/layout/sizes.dart';
 import '../../widgets/row/cells/select_option_cell/extension.dart';
 import '../application/calendar_bloc.dart';
+import 'calendar_page.dart';
 
 class CalendarDayCard extends StatelessWidget {
   final String viewId;
@@ -102,7 +101,7 @@ class CalendarDayCard extends StatelessWidget {
     );
   }
 
-  GestureDetector _buildCard(BuildContext context, CalendarDayEvent event) {
+  Widget _buildCard(BuildContext context, CalendarDayEvent event) {
     final styles = <FieldType, CardCellStyle>{
       FieldType.Number: NumberCardCellStyle(10),
       FieldType.URL: URLCardCellStyle(10),
@@ -193,7 +192,12 @@ class CalendarDayCard extends StatelessWidget {
       cardData: event.dateFieldId,
       isEditing: false,
       cellBuilder: cellBuilder,
-      openCard: (context) => _showRowDetailPage(event, context),
+      openCard: (context) => showEventDetails(
+        context: context,
+        event: event,
+        viewId: viewId,
+        rowCache: _rowCache,
+      ),
       styleConfiguration: const RowCardStyleConfiguration(
         showAccessory: false,
         cellPadding: EdgeInsets.zero,
@@ -203,47 +207,27 @@ class CalendarDayCard extends StatelessWidget {
       onEndEditing: () {},
     );
 
-    return GestureDetector(
-      onTap: () => _showRowDetailPage(event, context),
-      child: MouseRegion(
-        cursor: SystemMouseCursors.click,
-        child: Container(
-          padding: const EdgeInsets.symmetric(horizontal: 2),
-          decoration: BoxDecoration(
-            border: Border.fromBorderSide(
-              BorderSide(
-                color: Theme.of(context).dividerColor,
-                width: 1.5,
-              ),
+    return FlowyHover(
+      style: HoverStyle(
+        hoverColor: Theme.of(context).colorScheme.tertiaryContainer,
+        foregroundColorOnHover: Theme.of(context).colorScheme.onBackground,
+      ),
+      child: Container(
+        padding: const EdgeInsets.symmetric(horizontal: 2),
+        decoration: BoxDecoration(
+          border: Border.fromBorderSide(
+            BorderSide(
+              color: Theme.of(context).dividerColor,
+              width: 1.5,
             ),
-            borderRadius: Corners.s6Border,
           ),
-          child: card,
+          borderRadius: Corners.s6Border,
         ),
+        child: card,
       ),
     );
   }
 
-  void _showRowDetailPage(CalendarDayEvent event, BuildContext context) {
-    final dataController = RowController(
-      rowId: event.eventId,
-      viewId: viewId,
-      rowCache: _rowCache,
-    );
-
-    FlowyOverlay.show(
-      context: context,
-      builder: (BuildContext context) {
-        return RowDetailPage(
-          cellBuilder: GridCellBuilder(
-            cellCache: _rowCache.cellCache,
-          ),
-          dataController: dataController,
-        );
-      },
-    );
-  }
-
   notifyEnter(BuildContext context, bool isEnter) {
     Provider.of<_CardEnterNotifier>(
       context,

+ 34 - 22
frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart

@@ -9,6 +9,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
+import '../../application/row/row_cache.dart';
 import '../../application/row/row_data_controller.dart';
 import '../../widgets/row/cell_builder.dart';
 import '../../widgets/row/row_detail.dart';
@@ -76,7 +77,12 @@ class _CalendarPageState extends State<CalendarPage> {
               listenWhen: (p, c) => p.editEvent != c.editEvent,
               listener: (context, state) {
                 if (state.editEvent != null) {
-                  _showEditEventPage(state.editEvent!.event!, context);
+                  showEventDetails(
+                    context: context,
+                    event: state.editEvent!.event!,
+                    viewId: widget.view.id,
+                    rowCache: _calendarBloc.rowCache,
+                  );
                 }
               },
             ),
@@ -165,8 +171,9 @@ class _CalendarPageState extends State<CalendarPage> {
   }
 
   Widget _headerWeekDayBuilder(day) {
+    // incoming day starts from Monday, the symbols start from Sunday
     final symbols = DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
-    final weekDayString = symbols.WEEKDAYS[day];
+    final weekDayString = symbols.WEEKDAYS[(day + 1) % 7];
     return Center(
       child: Padding(
         padding: CalendarSize.daysOfWeekInsets,
@@ -210,27 +217,32 @@ class _CalendarPageState extends State<CalendarPage> {
   }
 
   WeekDays _weekdayFromInt(int dayOfWeek) {
-    // MonthView places the first day of week on the second column for some reason.
-    return WeekDays.values[(dayOfWeek + 1) % 7];
+    // dayOfWeek starts from Sunday, WeekDays starts from Monday
+    return WeekDays.values[(dayOfWeek - 1) % 7];
   }
+}
 
-  void _showEditEventPage(CalendarDayEvent event, BuildContext context) {
-    final dataController = RowController(
-      rowId: event.eventId,
-      viewId: widget.view.id,
-      rowCache: _calendarBloc.rowCache,
-    );
+void showEventDetails({
+  required BuildContext context,
+  required CalendarDayEvent event,
+  required String viewId,
+  required RowCache rowCache,
+}) {
+  final dataController = RowController(
+    rowId: event.eventId,
+    viewId: viewId,
+    rowCache: rowCache,
+  );
 
-    FlowyOverlay.show(
-      context: context,
-      builder: (BuildContext context) {
-        return RowDetailPage(
-          cellBuilder: GridCellBuilder(
-            cellCache: _calendarBloc.rowCache.cellCache,
-          ),
-          dataController: dataController,
-        );
-      },
-    );
-  }
+  FlowyOverlay.show(
+    context: context,
+    builder: (BuildContext context) {
+      return RowDetailPage(
+        cellBuilder: GridCellBuilder(
+          cellCache: rowCache.cellCache,
+        ),
+        dataController: dataController,
+      );
+    },
+  );
 }

+ 8 - 5
frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart

@@ -216,6 +216,7 @@ class LayoutDateField extends StatelessWidget {
       direction: PopoverDirection.leftWithTopAligned,
       constraints: BoxConstraints.loose(const Size(300, 400)),
       mutex: popoverMutex,
+      offset: const Offset(-16, 0),
       popupBuilder: (context) {
         return BlocProvider(
           create: (context) => getIt<DatabasePropertyBloc>(
@@ -236,9 +237,9 @@ class LayoutDateField extends StatelessWidget {
                         onUpdated(fieldInfo.id);
                         popoverMutex.close();
                       },
-                      leftIcon: svgWidget('grid/field/date'),
+                      leftIcon: const FlowySvg(name: 'grid/field/date'),
                       rightIcon: fieldInfo.id == fieldId
-                          ? svgWidget('grid/checkmark')
+                          ? const FlowySvg(name: 'grid/checkmark')
                           : null,
                     ),
                   );
@@ -332,12 +333,13 @@ class FirstDayOfWeek extends StatelessWidget {
       direction: PopoverDirection.leftWithTopAligned,
       constraints: BoxConstraints.loose(const Size(300, 400)),
       mutex: popoverMutex,
+      offset: const Offset(-16, 0),
       popupBuilder: (context) {
         final symbols =
             DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
         // starts from sunday
         final items = symbols.WEEKDAYS.asMap().entries.map((entry) {
-          final index = (entry.key - 1) % 7;
+          final index = entry.key;
           final string = entry.value;
           return SizedBox(
             height: GridSize.popoverItemHeight,
@@ -347,8 +349,9 @@ class FirstDayOfWeek extends StatelessWidget {
                 onUpdated(index);
                 popoverMutex.close();
               },
-              rightIcon:
-                  firstDayOfWeek == index ? svgWidget('grid/checkmark') : null,
+              rightIcon: firstDayOfWeek == index
+                  ? const FlowySvg(name: 'grid/checkmark')
+                  : null,
             ),
           );
         }).toList();

+ 25 - 0
frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_setting.dart

@@ -2,8 +2,10 @@ import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
 import 'package:appflowy/plugins/database_view/calendar/application/calendar_setting_bloc.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
 import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
@@ -38,6 +40,11 @@ class CalendarSetting extends StatelessWidget {
           final CalendarSettingAction? action =
               state.selectedAction.foldLeft(null, (previous, action) => action);
           switch (action) {
+            case CalendarSettingAction.properties:
+              return GridPropertyList(
+                viewId: settingContext.viewId,
+                fieldController: settingContext.fieldController,
+              );
             case CalendarSettingAction.layout:
               return CalendarLayoutSetting(
                 onUpdated: onUpdated,
@@ -78,9 +85,16 @@ class AllCalendarSettings extends StatelessWidget {
   }
 
   Widget _settingItem(BuildContext context, CalendarSettingAction action) {
+    Widget? icon;
+    if (action.iconName() != null) {
+      icon = FlowySvg(
+        name: action.iconName()!,
+      );
+    }
     return SizedBox(
       height: GridSize.popoverItemHeight,
       child: FlowyButton(
+        leftIcon: icon,
         text: FlowyText.medium(action.title()),
         onTap: () {
           context
@@ -93,8 +107,19 @@ class AllCalendarSettings extends StatelessWidget {
 }
 
 extension _SettingExtension on CalendarSettingAction {
+  String? iconName() {
+    switch (this) {
+      case CalendarSettingAction.properties:
+        return 'grid/setting/properties';
+      case CalendarSettingAction.layout:
+        return null;
+    }
+  }
+
   String title() {
     switch (this) {
+      case CalendarSettingAction.properties:
+        return LocaleKeys.grid_settings_Properties.tr();
       case CalendarSettingAction.layout:
         return LocaleKeys.grid_settings_layout.tr();
     }

+ 103 - 7
frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_toolbar.dart

@@ -1,6 +1,8 @@
 import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
 import 'package:appflowy_popover/appflowy_popover.dart';
+import 'package:calendar_view/calendar_view.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/theme_extension.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -19,7 +21,8 @@ class CalendarToolbar extends StatelessWidget {
       height: 40,
       child: Row(
         mainAxisAlignment: MainAxisAlignment.end,
-        children: [
+        children: const [
+          _UnscheduleEventsButton(),
           _SettingButton(),
         ],
       ),
@@ -28,25 +31,22 @@ class CalendarToolbar extends StatelessWidget {
 }
 
 class _SettingButton extends StatefulWidget {
+  const _SettingButton({Key? key}) : super(key: key);
+
   @override
   State<StatefulWidget> createState() => _SettingButtonState();
 }
 
 class _SettingButtonState extends State<_SettingButton> {
-  late PopoverController popoverController;
-
   @override
   void initState() {
-    popoverController = PopoverController();
     super.initState();
   }
 
   @override
   Widget build(BuildContext context) {
     return AppFlowyPopover(
-      controller: popoverController,
       direction: PopoverDirection.bottomWithRightAligned,
-      triggerActions: PopoverTriggerFlags.none,
       constraints: BoxConstraints.loose(const Size(300, 400)),
       margin: EdgeInsets.zero,
       child: FlowyTextButton(
@@ -54,7 +54,6 @@ class _SettingButtonState extends State<_SettingButton> {
         fillColor: Colors.transparent,
         hoverColor: AFThemeExtension.of(context).lightGreyHover,
         padding: GridSize.typeOptionContentInsets,
-        onPressed: () => popoverController.show(),
       ),
       popupBuilder: (BuildContext popoverContext) {
         final bloc = context.watch<CalendarBloc>();
@@ -81,3 +80,100 @@ class _SettingButtonState extends State<_SettingButton> {
     );
   }
 }
+
+class _UnscheduleEventsButton extends StatefulWidget {
+  const _UnscheduleEventsButton({Key? key}) : super(key: key);
+
+  @override
+  State<_UnscheduleEventsButton> createState() =>
+      _UnscheduleEventsButtonState();
+}
+
+class _UnscheduleEventsButtonState extends State<_UnscheduleEventsButton> {
+  late final PopoverController _controller;
+
+  @override
+  void initState() {
+    super.initState();
+    _controller = PopoverController();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<CalendarBloc, CalendarState>(
+      builder: (context, state) {
+        final unscheduledEvents = state.allEvents
+            .where((e) => e.date == DateTime.fromMillisecondsSinceEpoch(0))
+            .toList();
+        final viewId = context.read<CalendarBloc>().viewId;
+        final rowCache = context.read<CalendarBloc>().rowCache;
+        return AppFlowyPopover(
+          direction: PopoverDirection.bottomWithCenterAligned,
+          controller: _controller,
+          offset: const Offset(0, 8),
+          child: FlowyTextButton(
+            "${LocaleKeys.calendar_settings_noDateTitle.tr()} (${unscheduledEvents.length})",
+            fillColor: Colors.transparent,
+            hoverColor: AFThemeExtension.of(context).lightGreyHover,
+            padding: GridSize.typeOptionContentInsets,
+          ),
+          popupBuilder: (context) {
+            if (unscheduledEvents.isEmpty) {
+              return SizedBox(
+                height: GridSize.popoverItemHeight,
+                child: Center(
+                  child: FlowyText.medium(
+                    LocaleKeys.calendar_settings_emptyNoDate.tr(),
+                    color: Theme.of(context).hintColor,
+                  ),
+                ),
+              );
+            }
+            return ListView.separated(
+              itemBuilder: (context, index) => _UnscheduledEventItem(
+                event: unscheduledEvents[index],
+                onPressed: () {
+                  showEventDetails(
+                    context: context,
+                    event: unscheduledEvents[index].event!,
+                    viewId: viewId,
+                    rowCache: rowCache,
+                  );
+                  _controller.close();
+                },
+              ),
+              itemCount: unscheduledEvents.length,
+              separatorBuilder: (context, index) =>
+                  VSpace(GridSize.typeOptionSeparatorHeight),
+              shrinkWrap: true,
+            );
+          },
+        );
+      },
+    );
+  }
+}
+
+class _UnscheduledEventItem extends StatelessWidget {
+  final CalendarEventData<CalendarDayEvent> event;
+  final VoidCallback onPressed;
+  const _UnscheduledEventItem({
+    required this.event,
+    required this.onPressed,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      height: GridSize.popoverItemHeight,
+      child: FlowyTextButton(
+        event.title,
+        fillColor: Colors.transparent,
+        hoverColor: AFThemeExtension.of(context).lightGreyHover,
+        padding: GridSize.typeOptionContentInsets,
+        onPressed: onPressed,
+      ),
+    );
+  }
+}

+ 1 - 0
frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/container/card_container.dart

@@ -43,6 +43,7 @@ class RowCardContainer extends StatelessWidget {
           }
 
           return GestureDetector(
+            behavior: HitTestBehavior.opaque,
             onTap: () => openCard(context),
             child: Padding(
               padding: const EdgeInsets.all(8),

+ 20 - 2
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cal_bloc.dart

@@ -75,7 +75,7 @@ class DateCellCalendarBloc
     String? time,
     bool? includeTime,
   }) async {
-    // make sure date and time are not updated together from the UI
+    // make sure that not both date and time are updated at the same time
     assert(
       date == null && time == null ||
           date == null && time != null ||
@@ -83,7 +83,7 @@ class DateCellCalendarBloc
     );
     String? newTime = time ?? state.time;
 
-    DateTime? newDate = date;
+    DateTime? newDate = _utcToLocalAddTime(date);
     if (time != null && time.isNotEmpty) {
       newDate = state.dateTime ?? DateTime.now();
     }
@@ -122,6 +122,24 @@ class DateCellCalendarBloc
     );
   }
 
+  DateTime? _utcToLocalAddTime(DateTime? date) {
+    if (date == null) {
+      return null;
+    }
+    final now = DateTime.now();
+    // the incoming date is Utc. this trick converts it into Local
+    // and add the current time, though the time may be overwritten by
+    // explicitly provided time string
+    return DateTime(
+      date.year,
+      date.month,
+      date.day,
+      now.hour,
+      now.minute,
+      now.second,
+    );
+  }
+
   String timeFormatPrompt(FlowyError error) {
     String msg = "${LocaleKeys.grid_field_invalidTimeFormat.tr()}.";
     switch (state.dateTypeOptionPB.timeFormat) {

+ 14 - 5
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart

@@ -115,8 +115,11 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
             AnimatedSwitcher(
               duration: const Duration(milliseconds: 300),
               child: state.includeTime
-                  ? _TimeTextField(popoverMutex: popoverMutex)
-                  : const SizedBox(),
+                  ? _TimeTextField(
+                      timeStr: state.time,
+                      popoverMutex: popoverMutex,
+                    )
+                  : const SizedBox.shrink(),
             ),
             const TypeOptionSeparator(spacing: 12.0),
             const _IncludeTimeButton(),
@@ -265,9 +268,11 @@ class _IncludeTimeButton extends StatelessWidget {
 }
 
 class _TimeTextField extends StatefulWidget {
+  final String? timeStr;
   final PopoverMutex popoverMutex;
 
   const _TimeTextField({
+    required this.timeStr,
     required this.popoverMutex,
     Key? key,
   }) : super(key: key);
@@ -278,10 +283,12 @@ class _TimeTextField extends StatefulWidget {
 
 class _TimeTextFieldState extends State<_TimeTextField> {
   late final FocusNode _focusNode;
+  late final TextEditingController _textController;
 
   @override
   void initState() {
     _focusNode = FocusNode();
+    _textController = TextEditingController()..text = widget.timeStr ?? "";
 
     _focusNode.addListener(() {
       if (_focusNode.hasFocus) {
@@ -300,7 +307,8 @@ class _TimeTextFieldState extends State<_TimeTextField> {
 
   @override
   Widget build(BuildContext context) {
-    return BlocBuilder<DateCellCalendarBloc, DateCellCalendarState>(
+    return BlocConsumer<DateCellCalendarBloc, DateCellCalendarState>(
+      listener: (context, state) => _textController.text = state.time ?? "",
       builder: (context, state) {
         return Column(
           children: [
@@ -310,13 +318,14 @@ class _TimeTextFieldState extends State<_TimeTextField> {
               child: FlowyTextField(
                 text: state.time ?? "",
                 focusNode: _focusNode,
+                controller: _textController,
                 submitOnLeave: true,
                 hintText: state.timeHintText,
                 errorText: state.timeFormatError,
-                onSubmitted: (timeString) {
+                onSubmitted: (timeStr) {
                   context
                       .read<DateCellCalendarBloc>()
-                      .add(DateCellCalendarEvent.setTime(timeString));
+                      .add(DateCellCalendarEvent.setTime(timeStr));
                 },
               ),
             ),

+ 3 - 0
frontend/appflowy_flutter/lib/plugins/document/document_page.dart

@@ -226,6 +226,9 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
       if (temporaryNodeTypes.contains(node.type)) {
         transaction.deleteNode(node);
       }
+      if (kCoverType == node.type && !node.path.equals([0])) {
+        transaction.deleteNode(node);
+      }
     }
     if (transaction.operations.isNotEmpty) {
       await editorState.apply(transaction, withUpdateCursor: false);

+ 41 - 46
frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/cover/emoji_popover.dart

@@ -1,11 +1,10 @@
 import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
-import 'package:appflowy/workspace/presentation/widgets/emoji_picker/src/default_emoji_picker_view.dart';
-import 'package:appflowy/workspace/presentation/widgets/emoji_picker/src/emoji_view_state.dart';
-import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_infra/image.dart';
 import 'package:flutter/material.dart';
 
 class EmojiPopover extends StatefulWidget {
@@ -33,57 +32,53 @@ class _EmojiPopoverState extends State<EmojiPopover> {
   Widget build(BuildContext context) {
     return Padding(
       padding: const EdgeInsets.all(15),
-      child: EmojiPicker(
-        onEmojiSelected: (category, emoji) {
-          widget.onEmojiChanged(emoji);
-        },
-        customWidget: (Config config, EmojiViewState state) {
-          return Stack(
-            alignment: Alignment.topRight,
-            children: [
-              Container(
-                padding: EdgeInsets.only(top: widget.showRemoveButton ? 25 : 0),
-                child: DefaultEmojiPickerView(config, state),
+      child: Column(
+        children: [
+          if (widget.showRemoveButton)
+            Padding(
+              padding: const EdgeInsets.only(bottom: 4.0),
+              child: Align(
+                alignment: Alignment.centerRight,
+                child: DeleteButton(onPressed: widget.removeIcon),
+              ),
+            ),
+          Expanded(
+            child: EmojiPicker(
+              onEmojiSelected: (category, emoji) {
+                widget.onEmojiChanged(emoji);
+              },
+              config: Config(
+                columns: 8,
+                emojiSizeMax: 28,
+                bgColor: Colors.transparent,
+                iconColor: Theme.of(context).iconTheme.color!,
+                iconColorSelected: Theme.of(context).colorScheme.onSurface,
+                selectedHoverColor: Theme.of(context).colorScheme.secondary,
+                progressIndicatorColor: Theme.of(context).iconTheme.color!,
+                buttonMode: ButtonMode.CUPERTINO,
+                initCategory: Category.RECENT,
               ),
-              _buildDeleteButtonIfNeed(),
-            ],
-          );
-        },
-        config: Config(
-          columns: 8,
-          emojiSizeMax: 28,
-          bgColor: Colors.transparent,
-          iconColor: Theme.of(context).iconTheme.color!,
-          iconColorSelected: Theme.of(context).colorScheme.onSurface,
-          selectedHoverColor: Theme.of(context).colorScheme.secondary,
-          progressIndicatorColor: Theme.of(context).iconTheme.color!,
-          buttonMode: ButtonMode.CUPERTINO,
-          initCategory: Category.RECENT,
-        ),
+            ),
+          ),
+        ],
       ),
     );
   }
+}
+
+class DeleteButton extends StatelessWidget {
+  final VoidCallback onPressed;
+  const DeleteButton({required this.onPressed, Key? key}) : super(key: key);
 
-  Widget _buildDeleteButtonIfNeed() {
-    if (!widget.showRemoveButton) {
-      return const SizedBox();
-    }
+  @override
+  Widget build(BuildContext context) {
     return FlowyButton(
-      onTap: () => widget.removeIcon(),
+      onTap: () => onPressed,
       useIntrinsicWidth: true,
-      text: Row(
-        mainAxisSize: MainAxisSize.min,
-        mainAxisAlignment: MainAxisAlignment.end,
-        children: [
-          const FlowySvg(name: 'editor/delete'),
-          const SizedBox(
-            width: 5,
-          ),
-          FlowyText(
-            LocaleKeys.document_plugins_cover_removeIcon.tr(),
-          ),
-        ],
+      text: FlowyText(
+        LocaleKeys.document_plugins_cover_removeIcon.tr(),
       ),
+      leftIcon: const FlowySvg(name: 'editor/delete'),
     );
   }
 }

+ 23 - 19
frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/emoji_picker/emoji_menu_item.dart

@@ -29,29 +29,32 @@ void _showEmojiSelectionMenu(
   menuService.dismiss();
 
   _emojiSelectionMenu?.remove();
-  _emojiSelectionMenu = OverlayEntry(builder: (context) {
-    return Positioned(
-      top: alignment == Alignment.bottomLeft ? offset.dy : null,
-      bottom: alignment == Alignment.topLeft ? offset.dy : null,
-      left: offset.dx,
-      child: Material(
-        child: EmojiSelectionMenu(
-          editorState: editorState,
-          onSubmitted: (text) {
-            // insert emoji
-            editorState.insertEmoji(text);
-          },
-          onExit: () {
-            _dismissEmojiSelectionMenu();
-            //close emoji panel
-          },
+  _emojiSelectionMenu = OverlayEntry(
+    builder: (context) {
+      return Positioned(
+        top: alignment == Alignment.bottomLeft ? offset.dy : null,
+        bottom: alignment == Alignment.topLeft ? offset.dy : null,
+        left: offset.dx,
+        child: Material(
+          child: EmojiSelectionMenu(
+            editorState: editorState,
+            onSubmitted: (text) {
+              // insert emoji
+              editorState.insertEmoji(text);
+            },
+            onExit: () {
+              _dismissEmojiSelectionMenu();
+              //close emoji panel
+            },
+          ),
         ),
-      ),
-    );
-  },);
+      );
+    },
+  );
 
   Overlay.of(context).insert(_emojiSelectionMenu!);
 
+  _editorState = editorState;
   editorState.service.selectionService.currentSelection
       .addListener(_dismissEmojiSelectionMenu);
 }
@@ -62,6 +65,7 @@ void _dismissEmojiSelectionMenu() {
 
   _editorState?.service.selectionService.currentSelection
       .removeListener(_dismissEmojiSelectionMenu);
+  _editorState?.service.keyboardService?.enable();
   _editorState = null;
 }
 

+ 4 - 0
frontend/appflowy_flutter/lib/workspace/presentation/home/menu/app/section/section.dart

@@ -61,6 +61,10 @@ class ViewSection extends StatelessWidget {
             .add(ViewSectionEvent.moveView(oldIndex, index));
       },
       ignorePrimaryScrollController: true,
+      buildDraggableFeedback: (context, constraints, child) => ConstrainedBox(
+        constraints: constraints,
+        child: Material(color: Colors.transparent, child: child),
+      ),
       children: children,
     );
   }

+ 2 - 0
frontend/appflowy_flutter/lib/workspace/presentation/home/menu/menu.dart

@@ -149,6 +149,8 @@ class HomeMenu extends StatelessWidget {
                     ),
                   );
                 },
+                proxyDecorator: (child, index, animation) =>
+                    Material(color: Colors.transparent, child: child),
               );
             },
           ),

+ 1 - 0
frontend/appflowy_flutter/macos/Runner.xcodeproj/project.pbxproj

@@ -592,6 +592,7 @@
 					"$(inherited)",
 					"@executable_path/../Frameworks",
 				);
+				ONLY_ACTIVE_ARCH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.macos;
 				PRODUCT_NAME = AppFlowy;
 				PROVISIONING_PROFILE_SPECIFIER = "";

+ 17 - 13
frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/hover.dart

@@ -132,6 +132,21 @@ class FlowyHoverContainer extends StatelessWidget {
       width: style.borderWidth,
     );
 
+    final theme = Theme.of(context);
+    final textTheme = theme.textTheme;
+    final iconTheme = theme.iconTheme;
+    // override text's theme with foregroundColorOnHover when it is hovered
+    final hoverTheme = theme.copyWith(
+      textTheme: textTheme.copyWith(
+        bodyMedium: textTheme.bodyMedium?.copyWith(
+          color: style.foregroundColorOnHover ?? theme.colorScheme.onSurface,
+        ),
+      ),
+      iconTheme: iconTheme.copyWith(
+        color: style.foregroundColorOnHover ?? theme.colorScheme.onSurface,
+      ),
+    );
+
     return Container(
       margin: style.contentMargin,
       decoration: BoxDecoration(
@@ -139,19 +154,8 @@ class FlowyHoverContainer extends StatelessWidget {
         color: style.hoverColor ?? Theme.of(context).colorScheme.secondary,
         borderRadius: style.borderRadius,
       ),
-      child:
-          //override text's theme with foregroundColorOnHover when it is hovered
-          Theme(
-        data: Theme.of(context).copyWith(
-          textTheme: Theme.of(context).textTheme.copyWith(
-                bodyMedium: Theme.of(context).textTheme.bodyMedium?.copyWith(
-                    color: style.foregroundColorOnHover ??
-                        Theme.of(context).colorScheme.onSurface),
-              ),
-          iconTheme: Theme.of(context).iconTheme.copyWith(
-              color: style.foregroundColorOnHover ??
-                  Theme.of(context).colorScheme.onSurface),
-        ),
+      child: Theme(
+        data: hoverTheme,
         child: child,
       ),
     );

+ 2 - 2
frontend/appflowy_flutter/pubspec.lock

@@ -45,10 +45,10 @@ packages:
     dependency: "direct main"
     description:
       name: appflowy_editor
-      sha256: a1dbca3d7d33f4669f1d44bfa1e06b6646f46726c219921b8a59d9ee22bf252d
+      sha256: "6f7d2b0b54ca1049cb396229549d228b5bbd7ea6d09f1f7325a20db2d7586a5f"
       url: "https://pub.dev"
     source: hosted
-    version: "0.1.9"
+    version: "0.1.12"
   appflowy_popover:
     dependency: "direct main"
     description:

+ 2 - 2
frontend/appflowy_flutter/pubspec.yaml

@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
 # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
 # Read more about iOS versioning at
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
-version: 0.1.2
+version: 0.1.5
 
 environment:
   sdk: ">=2.19.0 <3.0.0"
@@ -42,7 +42,7 @@ dependencies:
     git:
       url: https://github.com/AppFlowy-IO/appflowy-board.git
       ref: a183c57
-  appflowy_editor: ^0.1.9
+  appflowy_editor: ^0.1.12
   appflowy_popover:
     path: packages/appflowy_popover
 

+ 11 - 1
frontend/rust-lib/flowy-database/src/services/field/type_options/number_type_option/number_type_option.rs

@@ -216,7 +216,17 @@ impl TypeOptionCellDataCompare for NumberTypeOptionPB {
     cell_data: &<Self as TypeOption>::CellData,
     other_cell_data: &<Self as TypeOption>::CellData,
   ) -> Ordering {
-    cell_data.0.cmp(&other_cell_data.0)
+    let left = NumberCellData::from_format_str(&cell_data.0, self.sign_positive, &self.format);
+    let right =
+      NumberCellData::from_format_str(&other_cell_data.0, self.sign_positive, &self.format);
+    match (left, right) {
+      (Ok(left), Ok(right)) => {
+        return left.decimal().cmp(&right.decimal());
+      },
+      (Ok(_), Err(_)) => Ordering::Greater,
+      (Err(_), Ok(_)) => Ordering::Less,
+      (Err(_), Err(_)) => Ordering::Equal,
+    }
   }
 }
 impl std::default::Default for NumberTypeOptionPB {

+ 1 - 1
frontend/rust-lib/flowy-database/tests/database/mock_data/grid_mock_data.rs

@@ -161,7 +161,7 @@ pub fn make_test_grid() -> BuildDatabaseContext {
         for field_type in FieldType::iter() {
           match field_type {
             FieldType::RichText => row_builder.insert_text_cell("DA"),
-            FieldType::Number => row_builder.insert_number_cell("4"),
+            FieldType::Number => row_builder.insert_number_cell("14"),
             FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
             FieldType::SingleSelect => {
               row_builder.insert_single_select_cell(|mut options| options.remove(0))

+ 2 - 2
frontend/rust-lib/flowy-database/tests/database/sort_test/single_sort_test.rs

@@ -212,7 +212,7 @@ async fn sort_number_by_descending_test() {
   let scripts = vec![
     AssertCellContentOrder {
       field_id: number_field.id.clone(),
-      orders: vec!["$1", "$2", "$3", "$4", "", "$5"],
+      orders: vec!["$1", "$2", "$3", "$14", "", "$5"],
     },
     InsertSort {
       field_rev: number_field.clone(),
@@ -220,7 +220,7 @@ async fn sort_number_by_descending_test() {
     },
     AssertCellContentOrder {
       field_id: number_field.id.clone(),
-      orders: vec!["$5", "$4", "$3", "$2", "$1", ""],
+      orders: vec!["$14", "$5", "$3", "$2", "$1", ""],
     },
   ];
   test.run_scripts(scripts).await;

+ 130 - 295
frontend/rust-lib/flowy-document/src/editor/READ_ME.json

@@ -2,175 +2,78 @@
   "document": {
     "type": "editor",
     "children": [
+      { "type": "cover" },
       {
         "type": "text",
-        "attributes": {
-          "subtype": "heading",
-          "heading": "h1"
-        },
-        "delta": [
-          {
-            "insert": "🌟 Welcome to AppFlowy!"
-          }
-        ]
+        "attributes": { "subtype": "heading", "heading": "h1" },
+        "delta": [{ "insert": "Welcome to AppFlowy!" }]
       },
       {
         "type": "text",
-        "attributes": {
-          "subtype": "heading",
-          "heading": "h2"
-        },
-        "delta": [
-          {
-            "insert": "Here are the basics"
-          }
-        ]
+        "attributes": { "subtype": "heading", "heading": "h2" },
+        "delta": [{ "insert": "Here are the basics" }]
       },
       {
         "type": "text",
-        "attributes": {
-          "subtype": "checkbox",
-          "checkbox": null
-        },
-        "delta": [
-          {
-            "insert": "Click anywhere and just start typing."
-          }
-        ]
+        "attributes": { "subtype": "checkbox", "checkbox": null },
+        "delta": [{ "insert": "Click anywhere and just start typing." }]
       },
       {
         "type": "text",
-        "attributes": {
-          "subtype": "checkbox",
-          "checkbox": null
-        },
+        "attributes": { "subtype": "checkbox", "checkbox": false },
         "delta": [
           {
-            "insert": "Highlight",
-            "attributes": {
-              "backgroundColor": "0x6000BCF0"
-            }
-          },
-          {
-            "insert": " any text, and use the editing menu to "
+            "insert": "Highlight ",
+            "attributes": { "backgroundColor": "0x4dffeb3b" }
           },
-          {
-            "insert": "style",
-            "attributes": {
-              "italic": true
-            }
-          },
-          {
-            "insert": " "
-          },
-          {
-            "insert": "your",
-            "attributes": {
-              "bold": true
-            }
-          },
-          {
-            "insert": " "
-          },
-          {
-            "insert": "writing",
-            "attributes": {
-              "underline": true
-            }
-          },
-          {
-            "insert": " "
-          },
-          {
-            "insert": "however",
-            "attributes": {
-              "code": true
-            }
-          },
-          {
-            "insert": " you "
-          },
-          {
-            "insert": "like.",
-            "attributes": {
-              "strikethrough": true
-            }
-          }
+          { "insert": "any text, and use the editing menu to " },
+          { "insert": "style", "attributes": { "italic": true } },
+          { "insert": " " },
+          { "insert": "your", "attributes": { "bold": true } },
+          { "insert": " " },
+          { "insert": "writing", "attributes": { "underline": true } },
+          { "insert": " " },
+          { "insert": "however", "attributes": { "code": true } },
+          { "insert": " you " },
+          { "insert": "like.", "attributes": { "strikethrough": true } }
         ]
       },
       {
         "type": "text",
-        "attributes": {
-          "subtype": "checkbox",
-          "checkbox": null
-        },
+        "attributes": { "subtype": "checkbox", "checkbox": null },
         "delta": [
-          {
-            "insert": "As soon as you type "
-          },
+          { "insert": "As soon as you type " },
           {
             "insert": "/",
-            "attributes": {
-              "code": true
-            }
+            "attributes": { "code": true, "color": "0xff00b5ff" }
           },
+          { "insert": " a menu will pop up. Select " },
           {
-            "insert": " a menu will pop up. Select different types of content blocks you can add."
-          }
+            "insert": "different types",
+            "attributes": { "backgroundColor": "0x4d9c27b0" }
+          },
+          { "insert": " of content blocks you can add." }
         ]
       },
       {
         "type": "text",
-        "attributes": {
-          "subtype": "checkbox",
-          "checkbox": null
-        },
+        "attributes": { "subtype": "checkbox", "checkbox": null },
         "delta": [
-          {
-            "insert": "Type "
-          },
-          {
-            "insert": "/",
-            "attributes": {
-              "code": true
-            }
-          },
-          {
-            "insert": " followed by "
-          },
-          {
-            "insert": "/bullet",
-            "attributes": {
-              "code": true
-            }
-          },
-          {
-            "insert": " or "
-          },
-          {
-            "insert": "/c.",
-            "attributes": {
-              "code": true
-            }
-          }
+          { "insert": "Type " },
+          { "insert": "/", "attributes": { "code": true } },
+          { "insert": " followed by " },
+          { "insert": "/bullet", "attributes": { "code": true } },
+          { "insert": " or " },
+          { "insert": "/num", "attributes": { "code": true } },
+          { "insert": " to create a list.", "attributes": { "code": false } }
         ]
       },
       {
         "type": "text",
-        "attributes": {
-          "subtype": "checkbox",
-          "checkbox": true
-        },
+        "attributes": { "subtype": "checkbox", "checkbox": true },
         "delta": [
-          {
-            "insert": "Click "
-          },
-          {
-            "insert": "+ New Page ",
-            "attributes": {
-              "code": true
-            }
-          },
+          { "insert": "Click " },
+          { "insert": "+ New Page ", "attributes": { "code": true } },
           {
             "insert": "button at the bottom of your sidebar to add a new page."
           }
@@ -178,32 +81,24 @@
       },
       {
         "type": "text",
-        "attributes": {
-          "subtype": "checkbox",
-          "checkbox": null
-        },
+        "attributes": { "subtype": "checkbox", "checkbox": null },
         "delta": [
-          {
-            "insert": "Click "
-          },
-          {
-            "insert": "+",
-            "attributes": {
-              "code": true
-            }
-          },
-          {
-            "insert": " next to any page title in the sidebar to quickly add a new subpage."
-          }
+          { "insert": "Click " },
+          { "insert": "+", "attributes": { "code": true } },
+          { "insert": " next to any page title in the sidebar to " },
+          { "insert": "quickly", "attributes": { "color": "0xff8427e0" } },
+          { "insert": " add a new subpage, " },
+          { "insert": "Document", "attributes": { "code": true } },
+          { "insert": ", ", "attributes": { "code": false } },
+          { "insert": "Grid", "attributes": { "code": true } },
+          { "insert": ", or ", "attributes": { "code": false } },
+          { "insert": "Kanban Board", "attributes": { "code": true } },
+          { "insert": ".", "attributes": { "code": false } }
         ]
       },
-      {
-        "type": "text",
-        "attributes": {
-          "checkbox": null
-        },
-        "delta": []
-      },
+      { "type": "text", "delta": [] },
+      { "type": "divider" },
+      { "type": "text", "attributes": { "checkbox": null }, "delta": [] },
       {
         "type": "text",
         "attributes": {
@@ -211,11 +106,7 @@
           "checkbox": null,
           "heading": "h2"
         },
-        "delta": [
-          {
-            "insert": "Markdown"
-          }
-        ]
+        "delta": [{ "insert": "Keyboard shortcuts, markdown, and code block" }]
       },
       {
         "type": "text",
@@ -225,98 +116,64 @@
           "heading": null
         },
         "delta": [
+          { "insert": "Keyboard shortcuts " },
           {
-            "insert": "Heading "
-          }
-        ]
-      },
-      {
-        "type": "text",
-        "attributes": {
-          "subtype": "number-list",
-          "number": 2
-        },
-        "delta": [
-          {
-            "insert": "bold text",
+            "insert": "guide",
             "attributes": {
-              "bold": true,
-              "defaultFormatting": true
+              "href": "https://appflowy.gitbook.io/docs/essential-documentation/shortcuts"
             }
-          }
+          },
+          { "retain": 1, "attributes": { "strikethrough": true } }
         ]
       },
       {
         "type": "text",
         "attributes": {
           "subtype": "number-list",
-          "number": 3
+          "number": 2,
+          "heading": null
         },
         "delta": [
+          { "insert": "Markdown " },
           {
-            "insert": "italicized text",
+            "insert": "reference",
             "attributes": {
-              "italic": true
+              "href": "https://appflowy.gitbook.io/docs/essential-documentation/markdown"
             }
-          }
-        ]
-      },
-      {
-        "type": "text",
-        "attributes": {
-          "subtype": "number-list",
-          "number": 4,
-          "number-list": null
-        },
-        "delta": [
-          {
-            "insert": "Ordered List"
-          }
+          },
+          { "retain": 1, "attributes": { "strikethrough": true } }
         ]
       },
       {
         "type": "text",
-        "attributes": {
-          "number": 5,
-          "subtype": "number-list"
-        },
+        "attributes": { "number": 3, "subtype": "number-list" },
         "delta": [
+          { "insert": "Type " },
+          { "insert": "/code", "attributes": { "code": true } },
           {
-            "insert": "code",
-            "attributes": {
-              "code": true
-            }
+            "insert": " to insert a code block",
+            "attributes": { "code": false }
           }
         ]
       },
       {
         "type": "text",
         "attributes": {
-          "number": 6,
-          "subtype": "number-list"
+          "subtype": "code_block",
+          "number": 3,
+          "heading": null,
+          "number-list": null,
+          "theme": "vs",
+          "language": "rust"
         },
         "delta": [
           {
-            "insert": "Strikethrough",
-            "attributes": {
-              "strikethrough": true
-            }
+            "insert": "// This is the main function.\nfn main() {\n    // Print text to the console.\n    println!(\"Hello World!\");\n}"
           },
-          {
-            "retain": 1,
-            "attributes": {
-              "strikethrough": true
-            }
-          }
+          { "retain": 1, "attributes": { "strikethrough": true } }
         ]
       },
-      {
-        "type": "text",
-        "attributes": {
-          "checkbox": null
-        },
-        "delta": []
-      },
+      { "type": "text", "attributes": { "checkbox": null }, "delta": [] },
       {
         "type": "text",
         "attributes": {
@@ -324,94 +181,72 @@
           "checkbox": null,
           "heading": "h2"
         },
-        "delta": [
-          {
-            "insert": "Have a question?"
-          }
-        ]
+        "delta": [{ "insert": "Have a question❓" }]
       },
       {
         "type": "text",
-        "attributes": {
-          "subtype": "quote"
-        },
+        "attributes": { "subtype": "quote" },
         "delta": [
-          {
-            "insert": "Click "
-          },
-          {
-            "insert": "?",
-            "attributes": {
-              "code": true
-            }
-          },
-          {
-            "insert": " at the bottom right for help and support."
-          }
+          { "insert": "Click " },
+          { "insert": "?", "attributes": { "code": true } },
+          { "insert": " at the bottom right for help and support." }
         ]
       },
+      { "type": "text", "delta": [] },
       {
-        "type": "text",
-        "delta": []
-      },
-      {
-        "type": "text",
-        "attributes": {
-          "subtype": "heading",
-          "heading": "h2"
-        },
-        "delta": [
+        "type": "callout",
+        "children": [
+          { "type": "text", "delta": [] },
           {
-            "insert": "Like AppFlowy? Follow us:"
-          }
-        ]
-      },
-      {
-        "type": "text",
-        "attributes": {
-          "subtype": "bulleted-list",
-          "quote": null
-        },
-        "delta": [
+            "type": "text",
+            "attributes": { "subtype": "heading", "heading": "h2" },
+            "delta": [{ "insert": "Like AppFlowy? Follow us:" }]
+          },
           {
-            "insert": "GitHub",
-            "attributes": {
-              "href": "https://github.com/AppFlowy-IO/AppFlowy"
-            }
-          }
-        ]
-      },
-      {
-        "type": "text",
-        "attributes": {
-          "subtype": "bulleted-list"
-        },
-        "delta": [
+            "type": "text",
+            "attributes": { "subtype": "bulleted-list" },
+            "delta": [
+              {
+                "insert": "GitHub",
+                "attributes": {
+                  "href": "https://github.com/AppFlowy-IO/AppFlowy"
+                }
+              }
+            ]
+          },
+          {
+            "type": "text",
+            "attributes": { "subtype": "bulleted-list" },
+            "delta": [
+              {
+                "insert": "Twitter",
+                "attributes": { "href": "https://twitter.com/appflowy" }
+              },
+              { "insert": ": @appflowy" }
+            ]
+          },
           {
-            "insert": "Twitter: @appflowy"
+            "type": "text",
+            "attributes": { "subtype": "bulleted-list" },
+            "delta": [
+              {
+                "insert": "Newsletter",
+                "attributes": { "href": "https://blog-appflowy.ghost.io/" }
+              }
+            ]
           }
-        ]
+        ],
+        "attributes": { "emoji": "🥰" }
       },
+      { "type": "text", "delta": [] },
       {
         "type": "text",
-        "attributes": {
-          "subtype": "bulleted-list"
-        },
-        "delta": [
-          {
-            "insert": "Newsletter",
-            "attributes": {
-              "href": "https://blog-appflowy.ghost.io/"
-            }
-          }
-        ]
+        "attributes": { "subtype": null, "heading": null },
+        "delta": []
       },
       {
         "type": "text",
-        "attributes": {
-          "subtype": null,
-          "heading": null
-        },
+        "attributes": { "subtype": null, "heading": null },
         "delta": []
       }
     ]

+ 7 - 2
frontend/scripts/flutter_release_build/build_flowy.dart

@@ -23,6 +23,11 @@ args[1]: The appflowy version to be built (github ref_name).
   assert(await repositoryRoot.exists(),
       '$repositoryRoot is an invalid directory. Please try again with a valid directory.\n\n$help');
   final appVersion = args[1];
-  await _BuildTool(repositoryRoot: repositoryRoot.path, appVersion: appVersion)
-      .run();
+  String? arch;
+  if (args.length > 2) arch = args[2];
+  await _BuildTool(
+    repositoryRoot: repositoryRoot.path,
+    appVersion: appVersion,
+    arch: arch,
+  ).run();
 }

+ 5 - 2
frontend/scripts/flutter_release_build/tool.dart

@@ -14,10 +14,12 @@ class _BuildTool {
   const _BuildTool({
     required this.repositoryRoot,
     required this.appVersion,
+    this.arch,
   });
 
   final String repositoryRoot;
   final String appVersion;
+  final String? arch;
 
   String get projectRoot =>
       [repositoryRoot, 'appflowy_flutter'].join(Platform.pathSeparator);
@@ -30,8 +32,9 @@ class _BuildTool {
 
   Future<String> get _commandForOS async {
     // Check the operating system and CPU architecture
-    var os = Platform.operatingSystem;
-    var arch = Platform.isMacOS ? await _architecture : Platform.localHostname;
+    final os = Platform.operatingSystem;
+    final arch = this.arch ??
+        (Platform.isMacOS ? await _architecture : Platform.localHostname);
 
     // Determine the appropriate command based on the OS and architecture
     if (os == 'windows') {

+ 4 - 2
frontend/scripts/windows_installer/inno_setup_config.iss

@@ -1,6 +1,7 @@
 [Setup]
 AppName=AppFlowy
 AppVersion={#AppVersion}
+AppPublisher=AppFlowy-IO
 WizardStyle=modern
 Compression=lzma2
 SolidCompression=yes
@@ -9,8 +10,8 @@ DefaultGroupName=AppFlowy
 SetupIconFile=flowy_logo.ico
 UninstallDisplayIcon={app}\AppFlowy.exe
 UninstallDisplayName=AppFlowy
-AppPublisher=AppFlowy-IO
 VersionInfoVersion={#AppVersion}
+UsePreviousAppDir=no
 
 [Files]
 Source: "AppFlowy\AppFlowy.exe";DestDir: "{app}";DestName: "AppFlowy.exe"
@@ -18,4 +19,5 @@ Source: "AppFlowy\*";DestDir: "{app}"
 Source: "AppFlowy\data\*";DestDir: "{app}\data\"; Flags: recursesubdirs
 
 [Icons]
-Name: "{group}\AppFlowy";Filename: "{app}\AppFlowy.exe"
+Name: "{userdesktop}\AppFlowy"; Filename: "{app}\AppFlowy.exe"
+Name: "{group}\AppFlowy"; Filename: "{app}\AppFlowy.exe"