diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bfa8ade1b..35b228e447 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Release Notes +## Version 0.3.0 - 08/22/2023 + +### New Features + +- Improve paste features: + - Paste HTML content from website. + - Paste image from clipboard. + +- Support Group by Date in Kanban Board. +- Notarize the macOS package, which is now verified by Apple. +- Add Persian language translations. + +### Bug fixes + +- Some UI issues + ## Version 0.2.9 - 08/08/2023 ### New Features diff --git a/frontend/Makefile.toml b/frontend/Makefile.toml index 1602aeda2f..c721155462 100644 --- a/frontend/Makefile.toml +++ b/frontend/Makefile.toml @@ -24,7 +24,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.2.9" +CURRENT_APP_VERSION = "0.3.0" FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite" PRODUCT_NAME = "AppFlowy" # CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html diff --git a/frontend/appflowy_flutter/integration_test/document/document_copy_and_paste_test.dart b/frontend/appflowy_flutter/integration_test/document/document_copy_and_paste_test.dart index c5862c1957..6d4945f715 100644 --- a/frontend/appflowy_flutter/integration_test/document/document_copy_and_paste_test.dart +++ b/frontend/appflowy_flutter/integration_test/document/document_copy_and_paste_test.dart @@ -245,7 +245,7 @@ extension on WidgetTester { await beforeTest?.call(editor.getCurrentEditorState()); // mock the clipboard - getIt().setData( + await getIt().setData( ClipboardServiceData( plainText: plainText, html: html, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart index f80525d737..562784c0ea 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -52,6 +52,7 @@ class _AppFlowyEditorPageState extends State { ...codeBlockCommands, customCopyCommand, customPasteCommand, + customCutCommand, ...standardCommandShortcutEvents, ]; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart index 84185f763a..27d0745093 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart @@ -22,6 +22,7 @@ final inAppJsonFormat = CustomValueFormat( } return null; }, + onEncode: (value, platformType) => utf8.encode(value), ); class ClipboardServiceData { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_cut_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_cut_command.dart new file mode 100644 index 0000000000..0ec9244771 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_cut_command.dart @@ -0,0 +1,24 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +/// cut. +/// +/// - support +/// - desktop +/// - web +/// - mobile +/// +final CommandShortcutEvent customCutCommand = CommandShortcutEvent( + key: 'cut the selected content', + command: 'ctrl+x', + macOSCommand: 'cmd+x', + handler: _cutCommandHandler, +); + +CommandShortcutEventHandler _cutCommandHandler = (editorState) { + customCopyCommand.execute(editorState); + editorState.deleteSelectionIfNeeded(); + return KeyEventResult.handled; +}; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart index feca93e8dd..1d72a72e04 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart @@ -14,25 +14,29 @@ extension PasteNodes on EditorState { final transaction = this.transaction; final insertedDelta = insertedNode.delta; // if the node is empty, replace it with the inserted node. - if (delta.isEmpty || insertedDelta == null) { + if (delta.isEmpty) { transaction.insertNode( selection.end.path.next, - node.copyWith( - type: node.type, - attributes: { - ...node.attributes, - ...insertedNode.attributes, - }, - ), + insertedDelta == null + ? node.copyWith( + type: node.type, + attributes: { + ...node.attributes, + ...insertedNode.attributes, + }, + ) + : insertedNode, ); transaction.deleteNode(node); + final path = calculatePath(selection.end.path, [insertedNode]); + final offset = calculateLength([insertedNode]); transaction.afterSelection = Selection.collapsed( Position( - path: selection.end.path, - offset: insertedDelta?.length ?? 0, + path: path, + offset: offset, ), ); - } else { + } else if (insertedDelta != null) { // if the node is not empty, insert the delta from inserted node after the selection. transaction.insertTextDelta(node, selection.endIndex, insertedDelta); } @@ -53,7 +57,7 @@ extension PasteNodes on EditorState { } final transaction = this.transaction; - final lastNodeLength = nodes.last.delta?.length ?? 0; + final lastNodeLength = calculateLength(nodes); // merge the current selected node delta into the nodes. if (delta.isNotEmpty) { nodes.first.insertDelta( @@ -86,13 +90,10 @@ extension PasteNodes on EditorState { // delete the current node. transaction.deleteNode(node); - var path = selection.end.path; - for (var i = 0; i < nodes.length; i++) { - path = path.next; - } + final path = calculatePath(selection.start.path, nodes); transaction.afterSelection = Selection.collapsed( Position( - path: path.previous, // because a node is deleted. + path: path, offset: lastNodeLength, ), ); @@ -116,6 +117,28 @@ extension PasteNodes on EditorState { assert(this.selection?.isCollapsed == true); return this.selection; } + + Path calculatePath(Path start, List nodes) { + var path = start; + for (var i = 0; i < nodes.length; i++) { + path = path.next; + } + path = path.previous; + if (nodes.last.children.isNotEmpty) { + return [ + ...path, + ...calculatePath([0], nodes.last.children.toList()) + ]; + } + return path; + } + + int calculateLength(List nodes) { + if (nodes.last.children.isNotEmpty) { + return calculateLength(nodes.last.children.toList()); + } + return nodes.last.delta?.length ?? 0; + } } extension on Node { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart index cfa482f91f..cfa32a363a 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart @@ -4,6 +4,7 @@ export 'callout/callout_block_component.dart'; export 'code_block/code_block_component.dart'; export 'code_block/code_block_shortcut_event.dart'; export 'copy_and_paste/custom_copy_command.dart'; +export 'copy_and_paste/custom_cut_command.dart'; export 'copy_and_paste/custom_paste_command.dart'; export 'database/database_view_block_component.dart'; export 'database/inline_database_menu_item.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_user_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_user_view.dart index 8f5eb012c2..9e4980ceca 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_user_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_user_view.dart @@ -1,5 +1,5 @@ -import 'dart:convert'; import 'dart:async'; +import 'dart:convert'; import 'package:appflowy/env/env.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; @@ -65,7 +65,7 @@ class SettingsUserView extends StatelessWidget { ); } - /// Renders either a login or logout button based on the user's authentication status. + /// Renders either a login or logout button based on the user's authentication status, or nothing if Supabase is not enabled. /// /// This function checks the current user's authentication type and Supabase /// configuration to determine whether to render a third-party login button @@ -74,14 +74,17 @@ class SettingsUserView extends StatelessWidget { BuildContext context, SettingsUserState state, ) { - if (isSupabaseEnabled) { - // If the user is logged in locally, render a third-party login button. - if (state.userProfile.authType == AuthTypePB.Local) { - return SettingThirdPartyLogin( - didLogin: didLogin, - ); - } + if (!isSupabaseEnabled) { + return const SizedBox.shrink(); } + + // If the user is logged in locally, render a third-party login button. + if (state.userProfile.authType == AuthTypePB.Local) { + return SettingThirdPartyLogin( + didLogin: didLogin, + ); + } + return SettingLogoutButton(user: user, didLogout: didLogout); } diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 8ae7d71c4a..00c4a1f6b8 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -54,8 +54,8 @@ packages: dependency: "direct main" description: path: "." - ref: "56474e8b" - resolved-ref: "56474e8bd9f08be7090495c255eea20a694ab1f3" + ref: f4db21c + resolved-ref: f4db21c3678290d133ae6cffa015d3d79920bf84 url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: git version: "1.2.3" @@ -1451,18 +1451,18 @@ packages: dependency: "direct main" description: name: super_clipboard - sha256: "204284b1a721d33a65bcab077b191a3b7379b46a231f05688d17220153338ede" + sha256: ba484eb42ce621b69241d18d2a8494109589e8796eb6a3399e6c8f9cdaab8303 url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.3" super_native_extensions: dependency: transitive description: name: super_native_extensions - sha256: "1f15e9b1adb0bc59cf9b889a0b248f3c192fa17e2d5c923aeeec6d4fa2eeffd6" + sha256: dfe0a1c74430db946be973878da3f8611b8f124137f1dcbdf883e4605db40bd8 url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.3" sync_http: dependency: transitive description: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 16f807250a..c7453ab9b1 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/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.2.9 +version: 0.3.0 environment: sdk: ">=3.0.0 <4.0.0" @@ -48,7 +48,7 @@ dependencies: appflowy_editor: git: url: https://github.com/AppFlowy-IO/appflowy-editor.git - ref: 56474e8b + ref: f4db21c appflowy_popover: path: packages/appflowy_popover @@ -107,7 +107,7 @@ dependencies: url_protocol: hive: ^2.2.3 hive_flutter: ^1.1.0 - super_clipboard: ^0.6.0 + super_clipboard: ^0.6.3 dev_dependencies: flutter_lints: ^2.0.1