From a38fab1a174f2901b9932a46fc7264e3b515b16e Mon Sep 17 00:00:00 2001 From: vam Date: Tue, 8 Sep 2026 10:42:43 +0800 Subject: [PATCH 1/2] fix: preserve pages when moving document blocks --- .../sub_page_transaction_handler.dart | 43 ++++++++++++++++--- .../sub_page_transaction_handler_test.dart | 26 +++++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 frontend/appflowy_flutter/test/unit_test/document/sub_page_transaction_handler_test.dart diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/sub_page_transaction_handler.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/sub_page_transaction_handler.dart index c5c7398bdb056..da6740b22f77d 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/sub_page_transaction_handler.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/sub_page_transaction_handler.dart @@ -13,6 +13,15 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/style_widget/snap_bar.dart'; +bool documentContainsSubPageReference(Node node, String viewId) { + if (node.attributes[SubPageBlockKeys.viewId] == viewId) { + return true; + } + return node.children.any( + (child) => documentContainsSubPageReference(child, viewId), + ); +} + class SubPageTransactionHandler extends BlockTransactionHandler { SubPageTransactionHandler() : super(type: SubPageBlockKeys.type); @@ -38,7 +47,12 @@ class SubPageTransactionHandler extends BlockTransactionHandler { for (final node in removed) { if (!context.mounted) return; - await _subPageDeleted(context, node); + await _subPageDeleted( + context, + editorState, + node, + parentViewId: viewId, + ); } for (final node in added) { @@ -56,18 +70,35 @@ class SubPageTransactionHandler extends BlockTransactionHandler { Future _subPageDeleted( BuildContext context, - Node node, - ) async { + EditorState editorState, + Node node, { + required String parentViewId, + }) async { if (node.type != type) { return; } - final view = node.attributes[SubPageBlockKeys.viewId]; - if (view == null) { + final viewId = node.attributes[SubPageBlockKeys.viewId] as String?; + if (viewId == null) { + return; + } + + // A moved block is represented by deleting the old node and inserting a + // copy. Do not delete the page while another block still references it. + if (documentContainsSubPageReference(editorState.document.root, viewId)) { + return; + } + + // Moving a page from the sidebar updates its parent before the old block + // is removed from the document. That removal must not move the page to + // trash. + final viewOrResult = await ViewBackendService.getView(viewId); + final view = viewOrResult.toNullable(); + if (view == null || view.parentViewId != parentViewId) { return; } - final result = await ViewBackendService.deleteView(viewId: view); + final result = await ViewBackendService.deleteView(viewId: viewId); result.fold( (_) {}, (error) { diff --git a/frontend/appflowy_flutter/test/unit_test/document/sub_page_transaction_handler_test.dart b/frontend/appflowy_flutter/test/unit_test/document/sub_page_transaction_handler_test.dart new file mode 100644 index 0000000000000..d0ced42f16d6f --- /dev/null +++ b/frontend/appflowy_flutter/test/unit_test/document/sub_page_transaction_handler_test.dart @@ -0,0 +1,26 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/sub_page/sub_page_transaction_handler.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('finds sub-page references in nested nodes', () { + final root = Node( + type: 'page', + children: [ + Node( + type: 'column', + children: [ + Node( + type: SubPageBlockKeys.type, + attributes: {SubPageBlockKeys.viewId: 'page-id'}, + ), + ], + ), + ], + ); + + expect(documentContainsSubPageReference(root, 'page-id'), isTrue); + expect(documentContainsSubPageReference(root, 'other-page'), isFalse); + }); +} From 386e72848f375f88479a596fd0f270cf9a924c5e Mon Sep 17 00:00:00 2001 From: vam Date: Wed, 9 Sep 2026 09:54:13 +0800 Subject: [PATCH 2/2] fix: only match sub-page references --- .../sub_page/sub_page_transaction_handler.dart | 3 ++- .../document/sub_page_transaction_handler_test.dart | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/sub_page_transaction_handler.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/sub_page_transaction_handler.dart index da6740b22f77d..5d45153b8eb3b 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/sub_page_transaction_handler.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/sub_page_transaction_handler.dart @@ -14,7 +14,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/style_widget/snap_bar.dart'; bool documentContainsSubPageReference(Node node, String viewId) { - if (node.attributes[SubPageBlockKeys.viewId] == viewId) { + if (node.type == SubPageBlockKeys.type && + node.attributes[SubPageBlockKeys.viewId] == viewId) { return true; } return node.children.any( diff --git a/frontend/appflowy_flutter/test/unit_test/document/sub_page_transaction_handler_test.dart b/frontend/appflowy_flutter/test/unit_test/document/sub_page_transaction_handler_test.dart index d0ced42f16d6f..d95ff4ad087c4 100644 --- a/frontend/appflowy_flutter/test/unit_test/document/sub_page_transaction_handler_test.dart +++ b/frontend/appflowy_flutter/test/unit_test/document/sub_page_transaction_handler_test.dart @@ -22,5 +22,15 @@ void main() { expect(documentContainsSubPageReference(root, 'page-id'), isTrue); expect(documentContainsSubPageReference(root, 'other-page'), isFalse); + expect( + documentContainsSubPageReference( + Node( + type: 'grid', + attributes: {SubPageBlockKeys.viewId: 'page-id'}, + ), + 'page-id', + ), + isFalse, + ); }); }