diff --git a/.github/workflows/ios_ci.yaml b/.github/workflows/ios_ci.yaml index 8c41ec70c197..dacbfd9ca12d 100644 --- a/.github/workflows/ios_ci.yaml +++ b/.github/workflows/ios_ci.yaml @@ -22,7 +22,6 @@ on: env: FLUTTER_VERSION: "3.22.0" RUST_TOOLCHAIN: "1.80.1" - CLOUD_VERSION: 0.6.51 concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} diff --git a/.github/workflows/rust_ci.yaml b/.github/workflows/rust_ci.yaml index 2702cbd365ee..dc82c45b4e73 100644 --- a/.github/workflows/rust_ci.yaml +++ b/.github/workflows/rust_ci.yaml @@ -20,7 +20,7 @@ on: env: CARGO_TERM_COLOR: always - CLOUD_VERSION: 0.6.51-amd64 + CLOUD_VERSION: 0.7.6-amd64 RUST_TOOLCHAIN: "1.77.2" jobs: @@ -137,7 +137,7 @@ jobs: run: | cp deploy.env .env sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env - sed -i 's|GOTRUE_MAILER_AUTOCONFIRM=.*|GOTRUE_MAILER_AUTOCONFIRM=true|' .env + sed -i 's|GOTRUE_MAILER_AUTOCONFIRM=.*|GOTRUE_MAILER_AUTOCONFIRM=true|' .env sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env - name: Ensure AppFlowy-Cloud is Running with Correct Version @@ -160,7 +160,7 @@ jobs: else echo "No volumes to remove." fi - + docker compose pull docker compose up -d echo "Waiting for the container to be ready..." diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_publish_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_publish_test.dart index e87cca191106..7877143116ed 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_publish_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_publish_test.dart @@ -106,9 +106,21 @@ void main() { await tester.pumpUntilFound(errorToast2); await tester.pumpUntilNotFound(errorToast2); + // rename with empty name await tester.tap(inputField); + await tester.enterText(inputField, ''); + await tester.tapButton(find.text(LocaleKeys.button_save.tr())); + await tester.pumpAndSettle(); + + // expect to see the toast with error message + final errorToast3 = find.text( + LocaleKeys.settings_sites_error_publishNameCannotBeEmpty.tr(), + ); + await tester.pumpUntilFound(errorToast3); + await tester.pumpUntilNotFound(errorToast3); // input the new path name + await tester.tap(inputField); await tester.enterText(inputField, 'new-path-name'); // click save button await tester.tapButton(find.text(LocaleKeys.button_save.tr())); @@ -137,5 +149,72 @@ void main() { isTrue, ); }); + + testWidgets('re-publish the document', (tester) async { + await tester.initializeAppFlowy( + cloudType: AuthenticatorType.appflowyCloudSelfHost, + ); + await tester.tapGoogleLoginInButton(); + await tester.expectToSeeHomePageWithGetStartedPage(); + + const pageName = 'Document'; + + await tester.createNewPageInSpace( + spaceName: Constants.generalSpaceName, + layout: ViewLayoutPB.Document, + pageName: pageName, + ); + + // open the publish menu + await tester.openPublishMenu(); + + // publish the document + final publishButton = find.byType(PublishButton); + await tester.tapButton(publishButton); + + // rename the path name + final inputField = find.descendant( + of: find.byType(ShareMenu), + matching: find.byType(TextField), + ); + + // input the new path name + const newName = 'new-path-name'; + await tester.enterText(inputField, newName); + // click save button + await tester.tapButton(find.text(LocaleKeys.button_save.tr())); + await tester.pumpAndSettle(); + + // expect to see the toast with success message + final successToast = find.text( + LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(), + ); + await tester.pumpUntilNotFound(successToast); + + // unpublish the document + final unpublishButton = find.byType(UnPublishButton); + await tester.tapButton(unpublishButton); + + final unpublishSuccessToast = find.text( + LocaleKeys.publish_unpublishSuccessfully.tr(), + ); + await tester.pumpUntilNotFound(unpublishSuccessToast); + + // re-publish the document + await tester.tapButton(publishButton); + + // expect to see the toast with success message + final rePublishSuccessToast = find.text( + LocaleKeys.publish_publishSuccessfully.tr(), + ); + await tester.pumpUntilNotFound(rePublishSuccessToast); + + // check the clipboard has the link + final content = await Clipboard.getData(Clipboard.kTextPlain); + expect( + content?.text?.contains(newName), + isTrue, + ); + }); }); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart index 219cc7a5ae91..ce5ce33d8041 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart @@ -9,6 +9,7 @@ import 'package:appflowy/plugins/shared/share/constants.dart'; import 'package:appflowy/plugins/shared/share/publish_name_generator.dart'; import 'package:appflowy/plugins/shared/share/share_bloc.dart'; import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/util/string_extension.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/view/prelude.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; @@ -97,9 +98,12 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget { Future _publish(BuildContext context) async { final id = context.read().view.id; - final publishName = await generatePublishName( - id, - view.name, + final lastPublishName = context.read().state.pathName; + final publishName = lastPublishName.orDefault( + await generatePublishName( + id, + view.name, + ), ); if (context.mounted) { context.read().add( diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart index 8ab84e7fd3d6..68d1918c7f31 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart @@ -9,6 +9,7 @@ import 'package:appflowy/plugins/shared/share/publish_name_generator.dart'; import 'package:appflowy/plugins/shared/share/share_bloc.dart'; import 'package:appflowy/shared/error_code/error_code_map.dart'; import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/util/string_extension.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/log.dart'; @@ -50,9 +51,12 @@ class PublishTab extends StatelessWidget { return _PublishWidget( onPublish: (selectedViews) async { final id = context.read().view.id; - final publishName = await generatePublishName( - id, - viewName, + final lastPublishName = context.read().state.pathName; + final publishName = lastPublishName.orDefault( + await generatePublishName( + id, + viewName, + ), ); if (selectedViews.isNotEmpty) { diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/share_bloc.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/share_bloc.dart index 1b607bfc6282..3a0b552eec30 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/share_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/share_bloc.dart @@ -7,6 +7,7 @@ import 'package:appflowy/workspace/application/view/view_listener.dart'; import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; @@ -188,39 +189,47 @@ class ShareBloc extends Bloc { (v) => v.authenticator == AuthenticatorPB.AppFlowyCloud, (p) => false, ); + + Log.info( + 'get publish info: $publishInfo for view: ${view.name}(${view.id})', + ); + String workspaceId = state.workspaceId; if (workspaceId.isEmpty) { - workspaceId = await UserBackendService.getCurrentWorkspace() - .fold((s) => s.id, (f) => ''); + workspaceId = await UserBackendService.getCurrentWorkspace().fold( + (s) => s.id, + (f) => '', + ); } - publishInfo.fold((s) { - emit( - state.copyWith( - isPublished: true, - namespace: s.namespace, - pathName: s.publishName, - url: ShareConstants.buildPublishUrl( + + final (isPublished, namespace, pathName, url) = publishInfo.fold( + (s) { + return ( + // if the unpublishedAtTimestampSec is not set, it means the view is not unpublished. + !s.hasUnpublishedAtTimestampSec(), + s.namespace, + s.publishName, + ShareConstants.buildPublishUrl( nameSpace: s.namespace, publishName: s.publishName, ), - viewName: view.name, - enablePublish: enablePublish, - workspaceId: workspaceId, - viewId: view.id, - ), - ); - }, (f) { - emit( - state.copyWith( - isPublished: false, - url: '', - viewName: view.name, - enablePublish: enablePublish, - workspaceId: workspaceId, - viewId: view.id, - ), - ); - }); + ); + }, + (f) => (false, '', '', ''), + ); + + emit( + state.copyWith( + isPublished: isPublished, + namespace: namespace, + pathName: pathName, + url: url, + viewName: view.name, + enablePublish: enablePublish, + workspaceId: workspaceId, + viewId: view.id, + ), + ); } Future _updatePathName( @@ -232,6 +241,21 @@ class ShareBloc extends Bloc { updatePathNameResult: null, ), ); + + if (pathName.isEmpty) { + emit( + state.copyWith( + updatePathNameResult: FlowyResult.failure( + FlowyError( + code: ErrorCode.ViewNameInvalid, + msg: 'Path name is invalid', + ), + ), + ), + ); + return; + } + final request = SetPublishNamePB() ..viewId = view.id ..newName = pathName; diff --git a/frontend/appflowy_flutter/lib/shared/error_code/error_code_map.dart b/frontend/appflowy_flutter/lib/shared/error_code/error_code_map.dart index a9dc55e3914c..2d54c93cbebf 100644 --- a/frontend/appflowy_flutter/lib/shared/error_code/error_code_map.dart +++ b/frontend/appflowy_flutter/lib/shared/error_code/error_code_map.dart @@ -14,6 +14,8 @@ extension PublishNameErrorCodeMap on ErrorCode { LocaleKeys.settings_sites_error_publishNameTooLong.tr(), ErrorCode.UserUnauthorized => LocaleKeys.settings_sites_error_publishPermissionDenied.tr(), + ErrorCode.ViewNameInvalid => + LocaleKeys.settings_sites_error_publishNameCannotBeEmpty.tr(), _ => null, }; } diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index c12ad66984d4..8293e20cb518 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -487,7 +487,8 @@ "publishNameTooLong": "The path name is too long, please try another one", "publishNameAlreadyInUse": "The path name is already in use, please try another one", "namespaceContainsInvalidCharacters": "The namespace contains invalid character(s), please try another one", - "publishPermissionDenied": "Only the workspace owner or page publisher can manage the publish settings" + "publishPermissionDenied": "Only the workspace owner or page publisher can manage the publish settings", + "publishNameCannotBeEmpty": "The path name cannot be empty, please try another one" }, "success": { "namespaceUpdated": "Updated namespace successfully", diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 57acdcf2279b..ece80b2bd993 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "app-error" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ed4c14d53aa77496a68df0875f5f668f278a21bf#ed4c14d53aa77496a68df0875f5f668f278a21bf" dependencies = [ "anyhow", "bincode", @@ -183,7 +183,7 @@ dependencies = [ [[package]] name = "appflowy-ai-client" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ed4c14d53aa77496a68df0875f5f668f278a21bf#ed4c14d53aa77496a68df0875f5f668f278a21bf" dependencies = [ "anyhow", "bytes", @@ -780,7 +780,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ed4c14d53aa77496a68df0875f5f668f278a21bf#ed4c14d53aa77496a68df0875f5f668f278a21bf" dependencies = [ "again", "anyhow", @@ -835,7 +835,7 @@ dependencies = [ [[package]] name = "client-api-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ed4c14d53aa77496a68df0875f5f668f278a21bf#ed4c14d53aa77496a68df0875f5f668f278a21bf" dependencies = [ "collab-entity", "collab-rt-entity", @@ -848,7 +848,7 @@ dependencies = [ [[package]] name = "client-websocket" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ed4c14d53aa77496a68df0875f5f668f278a21bf#ed4c14d53aa77496a68df0875f5f668f278a21bf" dependencies = [ "futures-channel", "futures-util", @@ -1117,7 +1117,7 @@ dependencies = [ [[package]] name = "collab-rt-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ed4c14d53aa77496a68df0875f5f668f278a21bf#ed4c14d53aa77496a68df0875f5f668f278a21bf" dependencies = [ "anyhow", "bincode", @@ -1142,7 +1142,7 @@ dependencies = [ [[package]] name = "collab-rt-protocol" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ed4c14d53aa77496a68df0875f5f668f278a21bf#ed4c14d53aa77496a68df0875f5f668f278a21bf" dependencies = [ "anyhow", "async-trait", @@ -1537,7 +1537,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ed4c14d53aa77496a68df0875f5f668f278a21bf#ed4c14d53aa77496a68df0875f5f668f278a21bf" dependencies = [ "anyhow", "app-error", @@ -2979,7 +2979,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ed4c14d53aa77496a68df0875f5f668f278a21bf#ed4c14d53aa77496a68df0875f5f668f278a21bf" dependencies = [ "anyhow", "futures-util", @@ -2996,7 +2996,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ed4c14d53aa77496a68df0875f5f668f278a21bf#ed4c14d53aa77496a68df0875f5f668f278a21bf" dependencies = [ "anyhow", "app-error", @@ -3358,7 +3358,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ed4c14d53aa77496a68df0875f5f668f278a21bf#ed4c14d53aa77496a68df0875f5f668f278a21bf" dependencies = [ "anyhow", "bytes", @@ -5663,7 +5663,7 @@ dependencies = [ [[package]] name = "shared-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a#e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ed4c14d53aa77496a68df0875f5f668f278a21bf#ed4c14d53aa77496a68df0875f5f668f278a21bf" dependencies = [ "anyhow", "app-error", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index d7ce0edd5edb..f9b1a702e9de 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -106,8 +106,8 @@ dashmap = "6.0.1" # Run the script.add_workspace_members: # scripts/tool/update_client_api_rev.sh new_rev_id # ⚠️⚠️⚠️️ -client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" } -client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "e31e541d07b8ef5e3f33b0b3dd54ebc22a79f86a" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ed4c14d53aa77496a68df0875f5f668f278a21bf" } +client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ed4c14d53aa77496a68df0875f5f668f278a21bf" } [profile.dev] opt-level = 0 diff --git a/frontend/rust-lib/flowy-folder/src/entities/publish.rs b/frontend/rust-lib/flowy-folder/src/entities/publish.rs index 7a0d91576086..ac23d810f581 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/publish.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/publish.rs @@ -74,6 +74,8 @@ pub struct PublishInfoResponsePB { pub publisher_email: String, #[pb(index = 5)] pub publish_timestamp_sec: i64, + #[pb(index = 6, one_of)] + pub unpublished_at_timestamp_sec: Option, } impl From for PublishInfoResponsePB { @@ -84,6 +86,7 @@ impl From for PublishInfoResponsePB { namespace: Some(info.namespace), publisher_email: info.publisher_email, publish_timestamp_sec: info.publish_timestamp.timestamp(), + unpublished_at_timestamp_sec: info.unpublished_timestamp.map(|t| t.timestamp()), } } }