diff --git a/frontend/appflowy_flutter/integration_test/desktop/board/board_add_row_test.dart b/frontend/appflowy_flutter/integration_test/desktop/board/board_add_row_test.dart index 84db6a5be00b4..52d1c00f119e4 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/board/board_add_row_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/board/board_add_row_test.dart @@ -3,12 +3,16 @@ import 'package:appflowy/plugins/database/board/presentation/board_page.dart'; import 'package:appflowy/plugins/database/board/presentation/widgets/board_column_header.dart'; import 'package:appflowy/plugins/database/widgets/card/card.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_board/appflowy_board.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; import '../../shared/util.dart'; +import '../../shared/database_test_op.dart'; const defaultFirstCardName = 'Card 1'; const defaultLastCardName = 'Card 3'; @@ -109,5 +113,68 @@ void main() { findsOneWidget, ); }); + + testWidgets('on adding row fetch url meta data', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board); + + final card1 = find.text('Card 1'); + await tester.tapButton(card1); + const urlFieldName = 'url'; + await tester.createField( + FieldType.URL, + name: urlFieldName, + layout: ViewLayoutPB.Board, + ); + await tester.dismissRowDetailPage(); + + await tester.tapDatabaseSettingButton(); + await tester.tapDatabaseGroupSettingsButton(); + await tester.toggleFetchURLMetaData(); + await tester.tapButtonWithName(LocaleKeys.board_urlFieldToFill.tr()); + final findListView = find.ancestor( + of: find.text(LocaleKeys.board_urlFieldNone.tr()), + matching: find.byType(ListView), + ); + await tester.tapButton( + find.descendant(of: findListView, matching: find.text(urlFieldName)), + ); + await tester.dismissRowDetailPage(); + + await tester.tapButton( + find.byType(BoardColumnFooter).at(1), + ); + + const newCardName = 'https://appflowy.io/'; + await tester.enterText( + find.descendant( + of: find.byType(BoardColumnFooter), + matching: find.byType(TextField), + ), + newCardName, + ); + await tester.testTextInput.receiveAction(TextInputAction.done); + await tester.pumpAndSettle(const Duration(milliseconds: 3000)); + + await tester.tap(find.byType(AppFlowyBoard)); + await tester.pumpAndSettle(); + + expect( + find.descendant( + of: find.byType(RowCard).last, + matching: find.text("AppFlowy.IO"), + ), + findsOneWidget, + ); + expect( + find.descendant( + of: find.byType(RowCard).last, + matching: find.text(newCardName), + ), + findsOneWidget, + ); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart b/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart index ff4ec8e52d5f7..3d826a058074f 100644 --- a/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart +++ b/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart @@ -1165,6 +1165,44 @@ extension AppFlowyDatabaseTest on WidgetTester { await tapButton(button); } + /// Should call [tapDatabaseSettingButton] first. + Future tapDatabaseGroupSettingsButton() async { + final findSettingItem = find.byType(DatabaseSettingsList); + final findLayoutButton = find.byWidgetPredicate( + (widget) => + widget is FlowyText && + widget.text == DatabaseSettingAction.showGroup.title(), + ); + + final button = find.descendant( + of: findSettingItem, + matching: findLayoutButton, + ); + + await tapButton(button); + } + + /// Should call [tapDatabaseGroupSettingsButton] first. + Future toggleFetchURLMetaData() async { + final findFetchURLText = find.byWidgetPredicate( + (widget) => + widget is FlowyText && + widget.text == LocaleKeys.board_fetchURLMetaData.tr(), + ); + + final findRow = find.ancestor( + of: findFetchURLText, + matching: find.byType(Row), + ); + + final findToggle = find.descendant( + of: findRow, + matching: find.byType(Toggle), + ); + + await tapButton(findToggle); + } + Future tapCalendarLayoutSettingButton() async { final findSettingItem = find.byType(DatabaseSettingsList); final findLayoutButton = find.byWidgetPredicate( diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/field/type_option/type_option_data_parser.dart b/frontend/appflowy_flutter/lib/plugins/database/application/field/type_option/type_option_data_parser.dart index c76e6d095c9d4..d77e88f2a1e57 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/field/type_option/type_option_data_parser.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/field/type_option/type_option_data_parser.dart @@ -58,6 +58,13 @@ class TranslateTypeOptionDataParser } } +class URLTypeOptionDataParser extends TypeOptionParser { + @override + URLTypeOptionPB fromBuffer(List buffer) { + return URLTypeOptionPB.fromBuffer(buffer); + } +} + class MediaTypeOptionDataParser extends TypeOptionParser { @override MediaTypeOptionPB fromBuffer(List buffer) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_service.dart b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_service.dart index 151a32d9619f0..f0413be3fbb9e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_service.dart @@ -155,6 +155,11 @@ class RowDataBuilder { _cellDataByFieldId[fieldInfo.field.id] = timestamp.toString(); } + void insertURL(FieldInfo fieldInfo, String url) { + assert(fieldInfo.fieldType == FieldType.URL); + _cellDataByFieldId[fieldInfo.field.id] = url; + } + Map build() { return _cellDataByFieldId; } diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/application/board_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/board/application/board_bloc.dart index ad6b82e7df256..48ce1edf78d08 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/board/application/board_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/board/application/board_bloc.dart @@ -106,12 +106,24 @@ class BoardBloc extends Bloc { (err) => Log.error('Failed to fetch user profile: ${err.msg}'), ); }, - createRow: (groupId, position, title, targetRowId) async { + createRow: (groupId, position, title, targetRowId, url) async { final primaryField = databaseController.fieldController.fieldInfos .firstWhereOrNull((element) => element.isPrimary)!; + final urlField = + databaseController.fieldController.fieldInfos.firstWhereOrNull( + (field) => + field.id == + databaseController + .databaseLayoutSetting?.board.urlFieldToFillId, + ); final void Function(RowDataBuilder)? cellBuilder = title == null ? null - : (builder) => builder.insertText(primaryField, title); + : (builder) { + builder.insertText(primaryField, title); + if (urlField != null && url != null) { + builder.insertURL(urlField, url); + } + }; final result = await RowBackendService.createRow( viewId: databaseController.viewId, @@ -551,8 +563,9 @@ class BoardEvent with _$BoardEvent { String groupId, OrderObjectPositionTypePB position, String? title, - String? targetRowId, - ) = _CreateRow; + String? targetRowId, { + String? url, + }) = _CreateRow; const factory BoardEvent.createGroup(String name) = _CreateGroup; const factory BoardEvent.startEditingHeader(String groupId) = _StartEditingHeader; diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart index 7c31879e2b067..0bea9e7236244 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart @@ -27,6 +27,8 @@ 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_bloc/flutter_bloc.dart'; +import 'package:string_validator/string_validator.dart'; +import 'package:metadata_fetch_plus/metadata_fetch_plus.dart'; import 'package:universal_platform/universal_platform.dart'; import '../../widgets/card/card.dart'; @@ -412,6 +414,7 @@ class _BoardColumnFooterState extends State { final TextEditingController _textController = TextEditingController(); late final FocusNode _focusNode; bool _isCreating = false; + bool _isFetchingURL = false; @override void initState() { @@ -426,7 +429,7 @@ class _BoardColumnFooterState extends State { return KeyEventResult.ignored; }, )..addListener(() { - if (!_focusNode.hasFocus) { + if (!_focusNode.hasFocus && !_isFetchingURL) { setState(() => _isCreating = false); } }); @@ -483,23 +486,58 @@ class _BoardColumnFooterState extends State { hintTextConstraints: const BoxConstraints(maxHeight: 36), controller: _textController, focusNode: _focusNode, + readOnly: _isFetchingURL, + suffixIcon: _isFetchingURL + ? Transform.scale( + scale: 0.5, + child: const CircularProgressIndicator(), + ) + : null, onSubmitted: (name) { - context.read().add( - BoardEvent.createRow( - widget.columnData.id, - OrderObjectPositionTypePB.End, - name, - null, - ), - ); - widget.scrollManager.scrollToBottom(widget.columnData.id); - _textController.clear(); - _focusNode.requestFocus(); + final boardBloc = context.read(); + final fetchURL = boardBloc.databaseController.databaseLayoutSetting + ?.board.fetchUrlMetaData ?? + false; + + String? url; + if (fetchURL && isURL(name)) { + setState(() => _isFetchingURL = true); + MetadataFetch.extract(name) + .then((data) { + if (data != null && data.title != null) { + url = name; + name = data.title!; + } + }) + .whenComplete(() { + setState(() => _isFetchingURL = false); + _createRowExec(boardBloc, name, url); + }) + .timeout(const Duration(seconds: 3)) + .catchError((e) {}); + } else { + _createRowExec(boardBloc, name, url); + } }, ), ); } + void _createRowExec(BoardBloc bloc, String name, [String? url]) { + bloc.add( + BoardEvent.createRow( + widget.columnData.id, + OrderObjectPositionTypePB.End, + name, + null, + url: url, + ), + ); + widget.scrollManager.scrollToBottom(widget.columnData.id); + _textController.clear(); + _focusNode.requestFocus(); + } + Widget _startCreatingCardsButton() { return BlocListener( listener: (context, state) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/date/date_time_format.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/date/date_time_format.dart index c04bcab92b1ed..91685d8f8ea5e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/date/date_time_format.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/date/date_time_format.dart @@ -242,6 +242,34 @@ class IncludeTimeButton extends StatelessWidget { final Function(bool value) onChanged; final bool value; + @override + Widget build(BuildContext context) { + return ToggleButton( + title: LocaleKeys.grid_field_includeTime.tr(), + icon: FlowySvg( + FlowySvgs.clock_alarm_s, + color: Theme.of(context).iconTheme.color, + ), + onChanged: onChanged, + value: value, + ); + } +} + +class ToggleButton extends StatelessWidget { + const ToggleButton({ + super.key, + required this.title, + required this.icon, + required this.onChanged, + required this.value, + }); + + final String title; + final FlowySvg icon; + final Function(bool value) onChanged; + final bool value; + @override Widget build(BuildContext context) { return SizedBox( @@ -250,12 +278,9 @@ class IncludeTimeButton extends StatelessWidget { padding: GridSize.typeOptionContentInsets, child: Row( children: [ - FlowySvg( - FlowySvgs.clock_alarm_s, - color: Theme.of(context).iconTheme.color, - ), + icon, const HSpace(6), - FlowyText.medium(LocaleKeys.grid_field_includeTime.tr()), + FlowyText.medium(title), const Spacer(), Toggle( value: value, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/group/database_group.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/group/database_group.dart index af8b60b8db901..647ddecf0ebde 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/group/database_group.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/group/database_group.dart @@ -13,11 +13,10 @@ import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:protobuf/protobuf.dart' hide FieldInfo; +import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; class DatabaseGroupList extends StatelessWidget { const DatabaseGroupList({ @@ -56,7 +55,7 @@ class DatabaseGroupList extends StatelessWidget { } final children = [ - if (showHideUngroupedToggle) ...[ + if (showHideUngroupedToggle) SizedBox( height: GridSize.popoverItemHeight, child: Padding( @@ -71,16 +70,132 @@ class DatabaseGroupList extends StatelessWidget { ), Toggle( value: !state.layoutSettings.hideUngroupedColumn, - onChanged: (value) => - _updateLayoutSettings(state.layoutSettings, value), + onChanged: (value) => _updateLayoutSettings( + state.layoutSettings, + (message) => message.hideUngroupedColumn = value, + ), padding: EdgeInsets.zero, ), ], ), ), ), - const TypeOptionSeparator(spacing: 0), - ], + SizedBox( + height: GridSize.popoverItemHeight, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + child: Row( + children: [ + Expanded( + child: FlowyText.medium( + LocaleKeys.board_fetchURLMetaData.tr(), + ), + ), + Toggle( + value: state.layoutSettings.fetchUrlMetaData, + onChanged: (value) => _updateLayoutSettings( + state.layoutSettings, + (message) => message.fetchUrlMetaData = !value, + ), + padding: EdgeInsets.zero, + ), + ], + ), + ), + ), + if (state.layoutSettings.fetchUrlMetaData) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: AppFlowyPopover( + triggerActions: + PopoverTriggerFlags.hover | PopoverTriggerFlags.click, + margin: EdgeInsets.zero, + offset: const Offset(14, 0), + constraints: BoxConstraints.loose(const Size(120, 300)), + child: SizedBox( + height: GridSize.popoverItemHeight, + child: FlowyButton( + hoverColor: AFThemeExtension.of(context).lightGreyHover, + text: FlowyText.medium( + LocaleKeys.board_urlFieldToFill.tr(), + lineHeight: 1.0, + color: AFThemeExtension.of(context).textColor, + ), + rightIcon: FlowySvg( + FlowySvgs.more_s, + color: Theme.of(context).iconTheme.color, + ), + ), + ), + popupBuilder: (popoverContext) { + final menuItems = state.fieldInfos + .where((fi) => fi.fieldType == FieldType.URL); + final selectedField = state.fieldInfos.firstWhereOrNull( + (fi) => fi.id == state.layoutSettings.urlFieldToFillId, + ); + return SizedBox( + width: 180, + child: ListView( + shrinkWrap: true, + physics: StyledScrollPhysics(), + padding: const EdgeInsets.symmetric(vertical: 3.0), + children: [ + Container( + padding: + const EdgeInsets.symmetric(horizontal: 6.0), + margin: const EdgeInsets.symmetric(vertical: 3.0), + child: FlowyButton( + text: FlowyText.medium( + LocaleKeys.board_urlFieldNone.tr(), + lineHeight: 1.0, + overflow: TextOverflow.ellipsis, + ), + rightIcon: selectedField == null + ? const FlowySvg(FlowySvgs.check_s) + : null, + onTap: () { + _updateLayoutSettings( + state.layoutSettings, + (message) => + message.clearOneOfUrlFieldToFillId(), + ); + PopoverContainer.of(popoverContext).close(); + }, + ), + ), + ...menuItems.map( + (item) => Container( + padding: + const EdgeInsets.symmetric(horizontal: 6.0), + margin: const EdgeInsets.symmetric(vertical: 3.0), + child: FlowyButton( + text: FlowyText.medium( + item.name, + lineHeight: 1.0, + overflow: TextOverflow.ellipsis, + ), + rightIcon: selectedField?.id == item.id + ? const FlowySvg(FlowySvgs.check_s) + : null, + onTap: () { + _updateLayoutSettings( + state.layoutSettings, + (message) => + message.urlFieldToFillId = item.id, + ); + PopoverContainer.of(popoverContext).close(); + }, + ), + ), + ), + ], + ), + ); + }, + ), + ), + const TypeOptionSeparator(spacing: 0), SizedBox( height: GridSize.popoverItemHeight, child: Padding( @@ -144,11 +259,11 @@ class DatabaseGroupList extends StatelessWidget { Future _updateLayoutSettings( BoardLayoutSettingPB layoutSettings, - bool hideUngrouped, + Function(BoardLayoutSettingPB) update, ) { layoutSettings.freeze(); final newLayoutSetting = layoutSettings.rebuild((message) { - message.hideUngroupedColumn = hideUngrouped; + update(message); }); return databaseController.updateLayoutSetting( boardLayoutSetting: newLayoutSetting, diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 500791e3f1e6e..2051dafe109c3 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -1283,6 +1283,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.0" + metadata_fetch_plus: + dependency: "direct main" + description: + name: metadata_fetch_plus + sha256: f4a833f0dc00f50203fa97a780d10025fb3703c4f7b1fbbe9c635b48dc7a41fe + url: "https://pub.dev" + source: hosted + version: "1.0.0" mime: dependency: "direct main" description: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index d5e96b47efa75..819a060811e3d 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -139,6 +139,8 @@ dependencies: # Window Manager for MacOS and Linux window_manager: ^0.3.9 + metadata_fetch_plus: ^1.0.0 + dev_dependencies: bloc_test: ^9.1.2 build_runner: ^2.4.9 diff --git a/frontend/appflowy_flutter/test/bloc_test/board_test/create_card_test.dart b/frontend/appflowy_flutter/test/bloc_test/board_test/create_card_test.dart index 4f855d4fb3101..9f7b5c90a581d 100644 --- a/frontend/appflowy_flutter/test/bloc_test/board_test/create_card_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/board_test/create_card_test.dart @@ -1,7 +1,9 @@ import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/board/application/board_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; +import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:protobuf/protobuf.dart'; import 'util.dart'; @@ -54,4 +56,108 @@ void main() { 'but receive ${boardBloc.groupControllers[lastGroupId]!.group.rows.length}', ); }); + + test('create kanban baord card with url', () async { + final context = await boardTest.createTestBoard(); + final databaseController = DatabaseController(view: context.gridView); + final boardBloc = BoardBloc( + databaseController: databaseController, + )..add(const BoardEvent.initial()); + await boardResponseFuture(); + + await context.createField(FieldType.URL); + await boardResponseFuture(); + final urlField = context.fieldContexts.last.field; + assert(urlField.fieldType == FieldType.URL); + + var boardSettings = databaseController.databaseLayoutSetting!.board; + boardSettings.freeze(); + await databaseController.updateLayoutSetting( + boardLayoutSetting: boardSettings + .rebuild((message) => message.urlFieldToFillId = urlField.id), + ); + await boardResponseFuture(); + assert( + databaseController.databaseLayoutSetting!.board.urlFieldToFillId == + urlField.id, + ); + + final groupIds = boardBloc.state.maybeMap( + orElse: () => const [], + ready: (value) => value.groupIds, + ); + final groupID = groupIds[3]; + const url = "https://appflowy.io/"; + boardBloc.add( + BoardEvent.createRow( + groupID, + OrderObjectPositionTypePB.End, + "Test", + null, + url: url, + ), + ); + await boardResponseFuture(); + + URLCellController urlCellController = + context.makeCellControllerFromFieldId(urlField.id) as URLCellController; + urlCellController.getCellData(); + await boardResponseFuture(); + assert( + urlCellController.getCellData()?.content == url, + 'but receive ${urlCellController.getCellData()?.content}', + ); + + // Don't fill url if the row title is null + boardBloc.add( + BoardEvent.createRow( + groupID, + OrderObjectPositionTypePB.End, + null, + null, + url: url, + ), + ); + await boardResponseFuture(); + + urlCellController = + context.makeCellControllerFromFieldId(urlField.id) as URLCellController; + urlCellController.getCellData(); + await boardResponseFuture(); + assert( + urlCellController.getCellData() == null, + 'but receive ${urlCellController.getCellData()}', + ); + + // Don't fill url if its set to none in board settings + boardSettings = databaseController.databaseLayoutSetting!.board; + boardSettings.freeze(); + await databaseController.updateLayoutSetting( + boardLayoutSetting: boardSettings + .rebuild((message) => message.clearOneOfUrlFieldToFillId()), + ); + await boardResponseFuture(); + assert( + databaseController.databaseLayoutSetting!.board.urlFieldToFillId == "", + ); + boardBloc.add( + BoardEvent.createRow( + groupID, + OrderObjectPositionTypePB.End, + "Another test", + null, + url: url, + ), + ); + await boardResponseFuture(); + + urlCellController = + context.makeCellControllerFromFieldId(urlField.id) as URLCellController; + urlCellController.getCellData(); + await boardResponseFuture(); + assert( + urlCellController.getCellData() == null, + 'but receive ${urlCellController.getCellData()}', + ); + }); } diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 110ad23b90627..58100bc27d57f 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -1876,6 +1876,9 @@ "propertyName": "Property name", "menuName": "Board", "showUngrouped": "Show ungrouped items", + "fetchURLMetaData": "Fetch url meta data", + "urlFieldToFill": "URL field to fill", + "urlFieldNone": "None", "ungroupedButtonText": "Ungrouped", "ungroupedButtonTooltip": "Contains cards that don't belong in any group", "ungroupedItemsTitle": "Click to add to the board", diff --git a/frontend/rust-lib/flowy-database2/src/entities/board_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/board_entities.rs index 5edefeb09e3c7..0f4609e9e95de 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/board_entities.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/board_entities.rs @@ -9,6 +9,12 @@ pub struct BoardLayoutSettingPB { #[pb(index = 2)] pub collapse_hidden_groups: bool, + + #[pb(index = 3)] + pub fetch_url_meta_data: bool, + + #[pb(index = 4, one_of)] + pub url_field_to_fill_id: Option, } impl From for BoardLayoutSettingPB { @@ -16,6 +22,8 @@ impl From for BoardLayoutSettingPB { Self { hide_ungrouped_column: setting.hide_ungrouped_column, collapse_hidden_groups: setting.collapse_hidden_groups, + fetch_url_meta_data: setting.fetch_url_meta_data, + url_field_to_fill_id: setting.url_field_to_fill_id, } } } @@ -25,6 +33,8 @@ impl From for BoardLayoutSetting { Self { hide_ungrouped_column: setting.hide_ungrouped_column, collapse_hidden_groups: setting.collapse_hidden_groups, + fetch_url_meta_data: setting.fetch_url_meta_data, + url_field_to_fill_id: setting.url_field_to_fill_id, } } } diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index 767e6c6bd1730..356ece140e6e8 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -419,7 +419,7 @@ impl DatabaseEditor { self.notify_did_update_database(notified_changeset).await?; for view in self.database_views.editors().await { - view.v_did_delete_field(field_id).await; + view.v_did_delete_field(field_id).await?; } Ok(()) diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index fbfcf3c28c7e5..38250c8bcdd95 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -838,7 +838,7 @@ impl DatabaseViewEditor { Ok(()) } - pub async fn v_did_delete_field(&self, deleted_field_id: &str) { + pub async fn v_did_delete_field(&self, deleted_field_id: &str) -> FlowyResult<()> { let changeset = FilterChangeset::DeleteAllWithFieldId { field_id: deleted_field_id.to_string(), }; @@ -864,6 +864,27 @@ impl DatabaseViewEditor { .calculations_controller .did_receive_field_deleted(deleted_field_id.to_string()) .await; + + let layout_settings = self + .v_get_layout_settings(&self.v_get_layout_type().await) + .await; + if let Some(mut board_settings) = layout_settings.board { + if board_settings + .url_field_to_fill_id + .is_some_and(|s| s == deleted_field_id) + { + board_settings.url_field_to_fill_id = None; + let params = LayoutSettingChangeset { + view_id: self.view_id.clone(), + layout_type: DatabaseLayout::Board, + board: Some(board_settings), + calendar: None, + }; + self.v_set_layout_settings(params).await?; + } + } + + Ok(()) } pub async fn v_did_update_field_type(&self, field_id: &str, new_field_type: FieldType) { diff --git a/frontend/rust-lib/flowy-database2/src/services/setting/entities.rs b/frontend/rust-lib/flowy-database2/src/services/setting/entities.rs index 8535f46024c8b..3b801451c79fc 100644 --- a/frontend/rust-lib/flowy-database2/src/services/setting/entities.rs +++ b/frontend/rust-lib/flowy-database2/src/services/setting/entities.rs @@ -90,6 +90,10 @@ pub struct BoardLayoutSetting { pub hide_ungrouped_column: bool, #[serde(default)] pub collapse_hidden_groups: bool, + #[serde(default)] + pub fetch_url_meta_data: bool, + #[serde(default)] + pub url_field_to_fill_id: Option, } impl BoardLayoutSetting { @@ -115,6 +119,14 @@ impl From for LayoutSetting { "collapse_hidden_groups".into(), setting.collapse_hidden_groups.into(), ), + ( + "fetch_url_meta_data".into(), + setting.fetch_url_meta_data.into(), + ), + ( + "url_field_to_fill_id".into(), + setting.url_field_to_fill_id.into(), + ), ]) } } diff --git a/frontend/rust-lib/flowy-database2/tests/database/layout_test/test.rs b/frontend/rust-lib/flowy-database2/tests/database/layout_test/test.rs index 41f2f88d0eef0..5e2379f17154a 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/layout_test/test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/layout_test/test.rs @@ -11,7 +11,7 @@ async fn board_layout_setting_test() { let default_board_setting = BoardLayoutSetting::new(); let new_board_setting = BoardLayoutSetting { hide_ungrouped_column: true, - ..default_board_setting + ..default_board_setting.clone() }; let scripts = vec![ AssertBoardLayoutSetting {