feat: search mvp (#5064)

* feat: implement folder indexer

* feat: sqlite search views using fts5

* feat: add view indexing to user manager

* feat: implement folder indexer

* feat: add sqlite search documents

* feat: add document indexing to user manager

* feat: add document indexing to folder indexer

* chore: update collab rev

* feat: search frontend integration

* refactor: search index

* test: add event test

* chore: fix ci

* feat: initial command palette overlay impl (#4619)

* chore: test search engine

* chore: initial structure

* chore: replace old search request

* chore: enable log for lib-dispatch

* chore: move search manager to core

* feat: move traits and responsibility to search crate

* feat: move search to search crate

* feat: replace sqlite with tantivy

* feat: deserialize tantivy documents

* chore: fixes after rebase

* chore: clean code

* feat: fetch and sort results

* fix: code review + cleaning

* feat: support custom icons

* feat: support view layout icons

* feat: rename bloc and fix indexing

* fix: prettify dialog

* feat: score results

* chore: update collab rev

* feat: add recent view history to command palette

* test: add integration_tests

* fix: clippy changes

* fix: focus traversal in cmd palette

* fix: remove file after merging main

* chore: code review and panic-safe

* feat: index all views if index does not exist

* chore: improve logic with conditional

* chore: add is_empty check

* chore: abstract logic from folder manager init

* chore: update collab rev

* chore: code review

* chore: fixes after merge + update lock file

* chore: revert cargo lock

* fix: set icon type when removing icon

* fix: code review + dependency inversion

* fix: remove icon fix for not persisting icon type

* test: simple tests manipulating views

* test: create 100 views

* fix: tauri build

* chore: create 1000 views

* chore: create util methods

* chore: test

* chore: test

* chore: remove logs

* chore: fix build.rs

* chore: export models

* chore: enable clear cache on Rust-CI

* fix: navigate to newly created views

* fix: force disable setting workspace listener on rebuilds

* fix: remove late final

* fix: missing returns

* fix: localization and minor fixes

* test: add index assert to large test

* fix: missing section param after merging main

* chore: try fix unzip file error

* chore: lower the test

* feat: show hint when result is in trash

* feat: one index_writer per index

* fix: minor changes after merge

* fix: make create_log_filter public after merge

* chore: fix test

* chore: fix test

* chore: flutter analyze

* chore: flutter analyze

* chore: fix tauri build

---------

Co-authored-by: nathan <nathan@appflowy.io>
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com>
This commit is contained in:
Mathias Mogensen 2024-04-12 10:21:41 +02:00 committed by GitHub
parent 39d8d428a6
commit b4d22bab14
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
121 changed files with 4045 additions and 503 deletions

View File

@ -25,21 +25,21 @@ jobs:
test-on-ubuntu:
runs-on: ubuntu-latest
steps:
# - name: Maximize build space
# uses: easimon/maximize-build-space@master
# with:
# root-reserve-mb: 2048
# swap-size-mb: 1024
# remove-dotnet: 'true'
#
# # the following step is required to avoid running out of space
# - name: Maximize build space
# run: |
# sudo rm -rf /usr/share/dotnet
# sudo rm -rf /opt/ghc
# sudo rm -rf "/usr/local/share/boost"
# sudo rm -rf "$AGENT_TOOLSDIRECTORY"
# sudo docker image prune --all --force
- name: Maximize build space
uses: easimon/maximize-build-space@master
with:
root-reserve-mb: 2048
swap-size-mb: 1024
remove-dotnet: 'true'
# the following step is required to avoid running out of space
- name: Maximize build space
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
sudo docker image prune --all --force
- name: Checkout source code
uses: actions/checkout@v4

View File

@ -0,0 +1,22 @@
import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Command Palette', () {
testWidgets('Toggle command palette', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.toggleCommandPalette();
expect(find.byType(CommandPaletteModal), findsOneWidget);
await tester.toggleCommandPalette();
expect(find.byType(CommandPaletteModal), findsNothing);
});
});
}

View File

@ -0,0 +1,14 @@
import 'package:integration_test/integration_test.dart';
import 'command_palette_test.dart' as command_palette_test;
import 'folder_search_test.dart' as folder_search_test;
import 'recent_history_test.dart' as recent_history_test;
void startTesting() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Command Palette integration tests
command_palette_test.main();
folder_search_test.main();
recent_history_test.main();
}

View File

@ -0,0 +1,53 @@
import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/search_field.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/search_result_tile.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Folder Search', () {
testWidgets('Search for views', (tester) async {
const firstDocument = "ViewOne";
const secondDocument = "ViewOna";
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent(name: firstDocument);
await tester.createNewPageWithNameUnderParent(name: secondDocument);
await tester.toggleCommandPalette();
expect(find.byType(CommandPaletteModal), findsOneWidget);
final searchFieldFinder = find.descendant(
of: find.byType(SearchField),
matching: find.byType(FlowyTextField),
);
await tester.enterText(searchFieldFinder, secondDocument);
await tester.pumpAndSettle(const Duration(milliseconds: 200));
// Expect two search results "ViewOna" and "ViewOne" (Distance 1 to ViewOna)
expect(find.byType(SearchResultTile), findsNWidgets(2));
// The score should be higher for "ViewOna" thus it should be shown first
final secondDocumentWidget = tester
.widget(find.byType(SearchResultTile).first) as SearchResultTile;
expect(secondDocumentWidget.result.data, secondDocument);
// Change search to "ViewOne"
await tester.enterText(searchFieldFinder, firstDocument);
await tester.pumpAndSettle(const Duration(seconds: 1));
// The score should be higher for "ViewOne" thus it should be shown first
final firstDocumentWidget = tester
.widget(find.byType(SearchResultTile).first) as SearchResultTile;
expect(firstDocumentWidget.result.data, firstDocument);
});
});
}

View File

@ -0,0 +1,38 @@
import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/recent_view_tile.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/recent_views_list.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Recent History', () {
testWidgets('Search for views', (tester) async {
const firstDocument = "First";
const secondDocument = "Second";
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent(name: firstDocument);
await tester.createNewPageWithNameUnderParent(name: secondDocument);
await tester.toggleCommandPalette();
expect(find.byType(CommandPaletteModal), findsOneWidget);
// Expect history list
expect(find.byType(RecentViewsList), findsOneWidget);
// Expect three recent history items
expect(find.byType(RecentViewTile), findsNWidgets(3));
// Expect the first item to be the last viewed document
final firstDocumentWidget =
tester.widget(find.byType(RecentViewTile).first) as RecentViewTile;
expect(firstDocumentWidget.view.name, secondDocument);
});
});
}

View File

@ -1,5 +1,9 @@
import 'dart:io';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
@ -26,9 +30,6 @@ import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'emoji.dart';
@ -520,6 +521,16 @@ extension CommonOperations on WidgetTester {
}
}
Future<void> toggleCommandPalette() async {
// Press CMD+P or CTRL+P to open the command palette
await simulateKeyEvent(
LogicalKeyboardKey.keyP,
isControlPressed: !Platform.isMacOS,
isMetaPressed: Platform.isMacOS,
);
await pumpAndSettle();
}
Future<void> openCollaborativeWorkspaceMenu() async {
if (!FeatureFlag.collaborativeWorkspace.isOn) {
throw UnsupportedError('Collaborative workspace is not enabled');

View File

@ -0,0 +1,55 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-notification/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-search/entities.pbenum.dart';
import 'package:appflowy_backend/rust_stream.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'notification_helper.dart';
// This value must be identical to the value in the backend (SEARCH_OBSERVABLE_SOURCE)
const _source = 'Search';
typedef SearchNotificationCallback = void Function(
SearchNotification,
FlowyResult<Uint8List, FlowyError>,
);
class SearchNotificationParser
extends NotificationParser<SearchNotification, FlowyError> {
SearchNotificationParser({
super.id,
required super.callback,
}) : super(
tyParser: (ty, source) =>
source == _source ? SearchNotification.valueOf(ty) : null,
errorParser: (bytes) => FlowyError.fromBuffer(bytes),
);
}
typedef SearchNotificationHandler = Function(
SearchNotification ty,
FlowyResult<Uint8List, FlowyError> result,
);
class SearchNotificationListener {
SearchNotificationListener({
required String objectId,
required SearchNotificationHandler handler,
}) : _parser = SearchNotificationParser(id: objectId, callback: handler) {
_subscription =
RustStreamReceiver.listen((observable) => _parser?.parse(observable));
}
StreamSubscription<SubscribeObject>? _subscription;
SearchNotificationParser? _parser;
Future<void> stop() async {
_parser = null;
await _subscription?.cancel();
_subscription = null;
}
}

View File

@ -1,13 +1,12 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'widgets/widgets.dart';
class NotificationsSettingGroup extends StatefulWidget {
const NotificationsSettingGroup({
super.key,
});
const NotificationsSettingGroup({super.key});
@override
State<NotificationsSettingGroup> createState() =>
@ -15,7 +14,6 @@ class NotificationsSettingGroup extends StatefulWidget {
}
class _NotificationsSettingGroupState extends State<NotificationsSettingGroup> {
// TODO:remove this after notification page is implemented
bool isPushNotificationOn = false;
@override

View File

@ -1,11 +1,26 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/icon.pbenum.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flutter/material.dart';
extension ToProto on FlowyIconType {
ViewIconTypePB toProto() {
switch (this) {
case FlowyIconType.emoji:
return ViewIconTypePB.Emoji;
case FlowyIconType.icon:
return ViewIconTypePB.Icon;
case FlowyIconType.custom:
return ViewIconTypePB.Url;
}
}
}
enum FlowyIconType {
emoji,
@ -14,6 +29,12 @@ enum FlowyIconType {
}
class EmojiPickerResult {
factory EmojiPickerResult.none() =>
const EmojiPickerResult(FlowyIconType.icon, '');
factory EmojiPickerResult.emoji(String emoji) =>
EmojiPickerResult(FlowyIconType.emoji, emoji);
const EmojiPickerResult(
this.type,
this.emoji,
@ -23,7 +44,7 @@ class EmojiPickerResult {
final String emoji;
}
class FlowyIconPicker extends StatefulWidget {
class FlowyIconPicker extends StatelessWidget {
const FlowyIconPicker({
super.key,
required this.onSelected,
@ -31,17 +52,6 @@ class FlowyIconPicker extends StatefulWidget {
final void Function(EmojiPickerResult result) onSelected;
@override
State<FlowyIconPicker> createState() => _FlowyIconPickerState();
}
class _FlowyIconPickerState extends State<FlowyIconPicker>
with SingleTickerProviderStateMixin {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
// ONLY supports emoji picker for now
@ -55,33 +65,18 @@ class _FlowyIconPickerState extends State<FlowyIconPicker>
_buildTabs(context),
const Spacer(),
_RemoveIconButton(
onTap: () {
widget.onSelected(
const EmojiPickerResult(
FlowyIconType.icon,
'',
),
);
},
onTap: () => onSelected(EmojiPickerResult.none()),
),
],
),
const Divider(
height: 2,
),
const Divider(height: 2),
Expanded(
child: TabBarView(
children: [
FlowyEmojiPicker(
emojiPerLine: _getEmojiPerLine(),
onEmojiSelected: (_, emoji) {
widget.onSelected(
EmojiPickerResult(
FlowyIconType.emoji,
emoji,
),
);
},
emojiPerLine: _getEmojiPerLine(context),
onEmojiSelected: (_, emoji) =>
onSelected(EmojiPickerResult.emoji(emoji)),
),
],
),
@ -109,9 +104,7 @@ class _FlowyIconPickerState extends State<FlowyIconPicker>
horizontal: 12.0,
vertical: 8.0,
),
child: FlowyText(
LocaleKeys.emoji_emojiTab.tr(),
),
child: FlowyText(LocaleKeys.emoji_emojiTab.tr()),
),
),
],
@ -119,7 +112,7 @@ class _FlowyIconPickerState extends State<FlowyIconPicker>
);
}
int _getEmojiPerLine() {
int _getEmojiPerLine(BuildContext context) {
if (PlatformExtension.isDesktopOrWeb) {
return 9;
}
@ -129,11 +122,10 @@ class _FlowyIconPickerState extends State<FlowyIconPicker>
}
class _RemoveIconButton extends StatelessWidget {
const _RemoveIconButton({
required this.onTap,
});
const _RemoveIconButton({required this.onTap});
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return SizedBox(

View File

@ -1,8 +1,9 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/app_bar.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
class IconPickerPage extends StatelessWidget {
const IconPickerPage({
@ -21,9 +22,7 @@ class IconPickerPage extends StatelessWidget {
titleText: title ?? LocaleKeys.titleBar_pageIcon.tr(),
),
body: SafeArea(
child: FlowyIconPicker(
onSelected: onSelected,
),
child: FlowyIconPicker(onSelected: onSelected),
),
);
}

View File

@ -6,8 +6,8 @@ import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations
import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/grid_setting_bar.dart';
import 'package:appflowy/plugins/database/tab_bar/desktop/setting_menu.dart';
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
import 'package:appflowy/workspace/application/notifications/notification_action.dart';
import 'package:appflowy/workspace/application/notifications/notification_action_bloc.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:collection/collection.dart';
@ -123,7 +123,7 @@ class _GridPageState extends State<GridPage> {
view: widget.view,
databaseController: widget.databaseController,
)..add(const GridEvent.initial()),
child: BlocListener<NotificationActionBloc, NotificationActionState>(
child: BlocListener<ActionNavigationBloc, ActionNavigationState>(
listener: (context, state) {
final action = state.action;
if (action?.type == ActionType.openRow &&

View File

@ -10,7 +10,7 @@ import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/shortcuts.dart';
import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/notifications/notification_action_bloc.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
@ -87,8 +87,8 @@ class _MobileGridPageState extends State<MobileGridPage> {
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<NotificationActionBloc>.value(
value: getIt<NotificationActionBloc>(),
BlocProvider<ActionNavigationBloc>.value(
value: getIt<ActionNavigationBloc>(),
),
BlocProvider<GridBloc>(
create: (context) => GridBloc(

View File

@ -1,3 +1,5 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/banner.dart';
@ -6,15 +8,14 @@ import 'package:appflowy/plugins/document/presentation/editor_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/notifications/notification_action.dart';
import 'package:appflowy/workspace/application/notifications/notification_action_bloc.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class DocumentPage extends StatefulWidget {
@ -52,7 +53,7 @@ class _DocumentPageState extends State<DocumentPage> {
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider.value(value: getIt<NotificationActionBloc>()),
BlocProvider.value(value: getIt<ActionNavigationBloc>()),
BlocProvider(
create: (_) => DocumentBloc(view: widget.view)
..add(const DocumentEvent.initial()),
@ -80,9 +81,9 @@ class _DocumentPageState extends State<DocumentPage> {
return const SizedBox.shrink();
}
return BlocListener<NotificationActionBloc, NotificationActionState>(
listener: _onNotificationAction,
return BlocListener<ActionNavigationBloc, ActionNavigationState>(
listenWhen: (_, curr) => curr.action != null,
listener: _onNotificationAction,
child: _buildEditorPage(context, state),
);
},
@ -156,7 +157,7 @@ class _DocumentPageState extends State<DocumentPage> {
void _onNotificationAction(
BuildContext context,
NotificationActionState state,
ActionNavigationState state,
) {
if (state.action != null && state.action!.type == ActionType.jumpToBlock) {
final path = state.action?.arguments?[ActionArgumentKeys.nodePath];

View File

@ -1,5 +1,7 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
@ -19,7 +21,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:string_validator/string_validator.dart';

View File

@ -18,9 +18,9 @@ import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy/user/application/user_listener.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy/user/presentation/router.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
import 'package:appflowy/workspace/application/edit_panel/edit_panel_bloc.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/notifications/notification_action_bloc.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy/workspace/application/settings/appearance/desktop_appearance.dart';
import 'package:appflowy/workspace/application/settings/appearance/mobile_appearance.dart';
@ -193,7 +193,7 @@ void _resolveHomeDeps(GetIt getIt) {
(view, _) => DocumentShareBloc(view: view),
);
getIt.registerSingleton<NotificationActionBloc>(NotificationActionBloc());
getIt.registerSingleton<ActionNavigationBloc>(ActionNavigationBloc());
getIt.registerLazySingleton<TabsBloc>(() => TabsBloc());

View File

@ -1,25 +1,27 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy/user/application/user_settings_service.dart';
import 'package:appflowy/workspace/application/notifications/notification_action.dart';
import 'package:appflowy/workspace/application/notifications/notification_action_bloc.dart';
import 'package:appflowy/workspace/application/notifications/notification_service.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
import 'package:appflowy/workspace/application/notification/notification_service.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/notifications/notification_settings_cubit.dart';
import 'package:appflowy/workspace/application/sidebar/rename_view/rename_view_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
@ -150,12 +152,15 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
create: (_) => DocumentAppearanceCubit()..fetch(),
),
BlocProvider.value(value: getIt<RenameViewBloc>()),
BlocProvider.value(value: getIt<NotificationActionBloc>()),
BlocProvider.value(
value: getIt<ActionNavigationBloc>()
..add(const ActionNavigationEvent.initialize()),
),
BlocProvider.value(
value: getIt<ReminderBloc>()..add(const ReminderEvent.started()),
),
],
child: BlocListener<NotificationActionBloc, NotificationActionState>(
child: BlocListener<ActionNavigationBloc, ActionNavigationState>(
listenWhen: (_, curr) => curr.action != null,
listener: (context, state) {
final action = state.action;
@ -189,7 +194,13 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(state.textScaleFactor),
),
child: overlayManagerBuilder(context, child),
child: overlayManagerBuilder(
context,
CommandPalette(
toggleNotifier: ValueNotifier<bool>(false),
child: child,
),
),
),
debugShowCheckedModeBanner: false,
theme: state.lightTheme,

View File

@ -6,9 +6,9 @@ import 'package:appflowy/user/application/reminder/reminder_extension.dart';
import 'package:appflowy/user/application/reminder/reminder_service.dart';
import 'package:appflowy/user/application/user_settings_service.dart';
import 'package:appflowy/util/int64_extension.dart';
import 'package:appflowy/workspace/application/notifications/notification_action.dart';
import 'package:appflowy/workspace/application/notifications/notification_action_bloc.dart';
import 'package:appflowy/workspace/application/notifications/notification_service.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
import 'package:appflowy/workspace/application/notification/notification_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
@ -22,14 +22,14 @@ part 'reminder_bloc.freezed.dart';
class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
ReminderBloc() : super(ReminderState()) {
_actionBloc = getIt<NotificationActionBloc>();
_actionBloc = getIt<ActionNavigationBloc>();
_reminderService = const ReminderService();
timer = _periodicCheck();
_dispatch();
}
late final NotificationActionBloc _actionBloc;
late final ActionNavigationBloc _actionBloc;
late final ReminderService _reminderService;
late final Timer timer;
@ -147,7 +147,7 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
rowId = reminder.meta[ReminderMetaKeys.rowId];
}
final action = NotificationAction(
final action = NavigationAction(
objectId: reminder.objectId,
arguments: {
ActionArgumentKeys.view: view,
@ -158,7 +158,7 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
if (!isClosed) {
_actionBloc.add(
NotificationActionEvent.performAction(
ActionNavigationEvent.performAction(
action: action,
nextActions: [
action.copyWith(
@ -198,8 +198,8 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
title: LocaleKeys.reminderNotification_title.tr(),
body: LocaleKeys.reminderNotification_message.tr(),
onClick: () => _actionBloc.add(
NotificationActionEvent.performAction(
action: NotificationAction(objectId: reminder.objectId),
ActionNavigationEvent.performAction(
action: NavigationAction(objectId: reminder.objectId),
),
),
);

View File

@ -0,0 +1,134 @@
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy/workspace/application/workspace/workspace_listener.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'action_navigation_bloc.freezed.dart';
class ActionNavigationBloc
extends Bloc<ActionNavigationEvent, ActionNavigationState> {
ActionNavigationBloc() : super(const ActionNavigationState.initial()) {
on<ActionNavigationEvent>((event, emit) async {
await event.when(
initialize: () async {
final views = await ViewBackendService().fetchViews();
emit(state.copyWith(views: views));
await initializeListeners();
},
viewsChanged: (views) {
emit(state.copyWith(views: views));
},
performAction: (action, nextActions) {
emit(state.copyWith(action: action, nextActions: nextActions));
if (nextActions.isNotEmpty) {
final newActions = [...nextActions];
final next = newActions.removeAt(0);
add(
ActionNavigationEvent.performAction(
action: next,
nextActions: newActions,
),
);
} else {
emit(state.setNoAction());
}
},
);
});
}
WorkspaceListener? _workspaceListener;
@override
Future<void> close() async {
await _workspaceListener?.stop();
return super.close();
}
Future<void> initializeListeners() async {
if (_workspaceListener != null) {
return;
}
final userOrFailure = await getIt<AuthService>().getUser();
final user = userOrFailure.fold((s) => s, (f) => null);
if (user == null) {
_workspaceListener = null;
return;
}
final workspaceSettingsOrFailure =
await FolderEventGetCurrentWorkspaceSetting().send();
final workspaceId = workspaceSettingsOrFailure.fold(
(s) => s.workspaceId,
(f) => null,
);
if (workspaceId == null) {
_workspaceListener = null;
return;
}
_workspaceListener = WorkspaceListener(
user: user,
workspaceId: workspaceId,
);
_workspaceListener?.start(
appsChanged: (_) async {
final views = await ViewBackendService().fetchViews();
add(ActionNavigationEvent.viewsChanged(views));
},
);
}
}
@freezed
class ActionNavigationEvent with _$ActionNavigationEvent {
const factory ActionNavigationEvent.initialize() = _Initialize;
const factory ActionNavigationEvent.performAction({
required NavigationAction action,
@Default([]) List<NavigationAction> nextActions,
}) = _PerformAction;
const factory ActionNavigationEvent.viewsChanged(List<ViewPB> views) =
_ViewsChanged;
}
class ActionNavigationState {
const ActionNavigationState.initial()
: action = null,
nextActions = const [],
views = const [];
const ActionNavigationState({
required this.action,
this.nextActions = const [],
this.views = const [],
});
final NavigationAction? action;
final List<NavigationAction> nextActions;
final List<ViewPB> views;
ActionNavigationState copyWith({
NavigationAction? action,
List<NavigationAction>? nextActions,
List<ViewPB>? views,
}) =>
ActionNavigationState(
action: action ?? this.action,
nextActions: nextActions ?? this.nextActions,
views: views ?? this.views,
);
ActionNavigationState setNoAction() =>
ActionNavigationState(action: null, nextActions: [], views: views);
}

View File

@ -10,13 +10,13 @@ class ActionArgumentKeys {
static String rowId = "row_id";
}
/// A [NotificationAction] is used to communicate with the
/// [NotificationActionBloc] to perform actions based on an event
/// A [NavigationAction] is used to communicate with the
/// [ActionNavigationBloc] to perform actions based on an event
/// triggered by pressing a notification, such as opening a specific
/// view and jumping to a specific block.
///
class NotificationAction {
const NotificationAction({
class NavigationAction {
const NavigationAction({
this.type = ActionType.openView,
this.arguments,
required this.objectId,
@ -27,12 +27,12 @@ class NotificationAction {
final String objectId;
final Map<String, dynamic>? arguments;
NotificationAction copyWith({
NavigationAction copyWith({
ActionType? type,
String? objectId,
Map<String, dynamic>? arguments,
}) =>
NotificationAction(
NavigationAction(
type: type ?? this.type,
objectId: objectId ?? this.objectId,
arguments: arguments ?? this.arguments,

View File

@ -0,0 +1,180 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:appflowy/plugins/trash/application/trash_listener.dart';
import 'package:appflowy/plugins/trash/application/trash_service.dart';
import 'package:appflowy/workspace/application/command_palette/search_listener.dart';
import 'package:appflowy/workspace/application/command_palette/search_service.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/trash.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-search/entities.pb.dart';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'command_palette_bloc.freezed.dart';
class CommandPaletteBloc
extends Bloc<CommandPaletteEvent, CommandPaletteState> {
CommandPaletteBloc() : super(CommandPaletteState.initial()) {
_searchListener.start(
onResultsChanged: _onResultsChanged,
onResultsClosed: _onResultsClosed,
);
_initTrash();
_dispatch();
}
Timer? _debounceOnChanged;
final TrashService _trashService = TrashService();
final SearchListener _searchListener = SearchListener();
final TrashListener _trashListener = TrashListener();
String? _oldQuery;
@override
Future<void> close() {
_trashListener.close();
_searchListener.stop();
return super.close();
}
void _dispatch() {
on<CommandPaletteEvent>((event, emit) async {
event.when(
searchChanged: _debounceOnSearchChanged,
trashChanged: (trash) async {
if (trash != null) {
emit(state.copyWith(trash: trash));
return;
}
final trashOrFailure = await _trashService.readTrash();
final trashRes = trashOrFailure.fold(
(trash) => trash,
(error) => null,
);
if (trashRes != null) {
emit(state.copyWith(trash: trashRes.items));
}
},
performSearch: (search) async {
if (search.isNotEmpty) {
_oldQuery = state.query;
emit(state.copyWith(query: search, isLoading: true));
await SearchBackendService.performSearch(search);
} else {
emit(state.copyWith(query: null, isLoading: false, results: []));
}
},
resultsChanged: (results, didClose) {
if (state.query != _oldQuery) {
emit(state.copyWith(results: []));
}
final searchResults = _filterDuplicates(results.items);
searchResults.sort((a, b) => b.score.compareTo(a.score));
emit(
state.copyWith(
results: searchResults,
isLoading: !didClose,
),
);
},
);
});
}
Future<void> _initTrash() async {
_trashListener.start(
trashUpdated: (trashOrFailed) {
final trash = trashOrFailed.fold(
(trash) => trash,
(error) => null,
);
add(CommandPaletteEvent.trashChanged(trash: trash));
},
);
final trashOrFailure = await _trashService.readTrash();
final trashRes = trashOrFailure.fold(
(trash) => trash,
(error) => null,
);
add(CommandPaletteEvent.trashChanged(trash: trashRes?.items));
}
void _debounceOnSearchChanged(String value) {
_debounceOnChanged?.cancel();
_debounceOnChanged = Timer(
const Duration(milliseconds: 300),
() => _performSearch(value),
);
}
List<SearchResultPB> _filterDuplicates(List<SearchResultPB> results) {
final currentItems = [...state.results];
final res = [...results];
for (final item in results) {
final duplicateIndex = currentItems.indexWhere((a) => a.id == item.id);
if (duplicateIndex == -1) {
continue;
}
final duplicate = currentItems[duplicateIndex];
if (item.score < duplicate.score) {
res.remove(item);
} else {
currentItems.remove(duplicate);
}
}
return res..addAll(currentItems);
}
void _performSearch(String value) =>
add(CommandPaletteEvent.performSearch(search: value));
void _onResultsChanged(RepeatedSearchResultPB results) =>
add(CommandPaletteEvent.resultsChanged(results: results));
void _onResultsClosed(RepeatedSearchResultPB results) =>
add(CommandPaletteEvent.resultsChanged(results: results, didClose: true));
}
@freezed
class CommandPaletteEvent with _$CommandPaletteEvent {
const factory CommandPaletteEvent.searchChanged({required String search}) =
_SearchChanged;
const factory CommandPaletteEvent.performSearch({required String search}) =
_PerformSearch;
const factory CommandPaletteEvent.resultsChanged({
required RepeatedSearchResultPB results,
@Default(false) bool didClose,
}) = _ResultsChanged;
const factory CommandPaletteEvent.trashChanged({
@Default(null) List<TrashPB>? trash,
}) = _TrashChanged;
}
@freezed
class CommandPaletteState with _$CommandPaletteState {
const CommandPaletteState._();
const factory CommandPaletteState({
@Default(null) String? query,
required List<SearchResultPB> results,
required bool isLoading,
@Default([]) List<TrashPB> trash,
}) = _CommandPaletteState;
factory CommandPaletteState.initial() =>
const CommandPaletteState(results: [], isLoading: false);
}

View File

@ -0,0 +1,65 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:appflowy/core/notification/search_notification.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-search/entities.pb.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:flowy_infra/notifier.dart';
// Do not modify!
const _searchObjectId = "SEARCH_IDENTIFIER";
class SearchListener {
SearchListener();
PublishNotifier<RepeatedSearchResultPB>? _updateNotifier = PublishNotifier();
PublishNotifier<RepeatedSearchResultPB>? _updateDidCloseNotifier =
PublishNotifier();
SearchNotificationListener? _listener;
void start({
required void Function(RepeatedSearchResultPB) onResultsChanged,
required void Function(RepeatedSearchResultPB) onResultsClosed,
}) {
_updateNotifier?.addPublishListener(onResultsChanged);
_updateDidCloseNotifier?.addPublishListener(onResultsClosed);
_listener = SearchNotificationListener(
objectId: _searchObjectId,
handler: _handler,
);
}
void _handler(
SearchNotification ty,
FlowyResult<Uint8List, FlowyError> result,
) {
switch (ty) {
case SearchNotification.DidUpdateResults:
result.fold(
(payload) => _updateNotifier?.value =
RepeatedSearchResultPB.fromBuffer(payload),
(err) => Log.error(err),
);
break;
case SearchNotification.DidCloseResults:
result.fold(
(payload) => _updateDidCloseNotifier?.value =
RepeatedSearchResultPB.fromBuffer(payload),
(err) => Log.error(err),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_updateNotifier?.dispose();
_updateNotifier = null;
_updateDidCloseNotifier?.dispose();
_updateDidCloseNotifier = null;
}
}

View File

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy_backend/protobuf/flowy-search/entities.pb.dart';
extension GetIcon on SearchResultPB {
Widget? getIcon() {
if (icon.ty == ResultIconTypePB.Emoji) {
return icon.value.isNotEmpty
? Text(
icon.value,
style: const TextStyle(fontSize: 18.0),
)
: null;
} else if (icon.ty == ResultIconTypePB.Icon) {
return FlowySvg(icon.getViewSvg(), size: const Size.square(20));
}
return null;
}
}
extension _ToViewIcon on ResultIconPB {
FlowySvgData getViewSvg() => switch (value) {
"0" => FlowySvgs.document_s,
"1" => FlowySvgs.grid_s,
"2" => FlowySvgs.board_s,
"3" => FlowySvgs.date_s,
_ => FlowySvgs.document_s,
};
}

View File

@ -0,0 +1,14 @@
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-search/entities.pb.dart';
import 'package:appflowy_result/appflowy_result.dart';
class SearchBackendService {
static Future<FlowyResult<void, FlowyError>> performSearch(
String keyword,
) async {
final request = SearchQueryPB(search: keyword);
return SearchEventSearch(request).send();
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/foundation.dart';
import 'package:local_notifier/local_notifier.dart';
const _appName = "AppFlowy";
@ -12,9 +13,7 @@ const _appName = "AppFlowy";
///
class NotificationService {
static Future<void> initialize() async {
await localNotifier.setup(
appName: _appName,
);
await localNotifier.setup(appName: _appName);
}
}

View File

@ -1,61 +0,0 @@
import 'package:appflowy/workspace/application/notifications/notification_action.dart';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'notification_action_bloc.freezed.dart';
class NotificationActionBloc
extends Bloc<NotificationActionEvent, NotificationActionState> {
NotificationActionBloc() : super(const NotificationActionState.initial()) {
on<NotificationActionEvent>((event, emit) async {
event.when(
performAction: (action, nextActions) {
emit(state.copyWith(action: action, nextActions: nextActions));
if (nextActions.isNotEmpty) {
final newActions = [...nextActions];
final next = newActions.removeAt(0);
add(
NotificationActionEvent.performAction(
action: next,
nextActions: newActions,
),
);
}
},
);
});
}
}
@freezed
class NotificationActionEvent with _$NotificationActionEvent {
const factory NotificationActionEvent.performAction({
required NotificationAction action,
@Default([]) List<NotificationAction> nextActions,
}) = _PerformAction;
}
class NotificationActionState {
const NotificationActionState.initial()
: action = null,
nextActions = const [];
const NotificationActionState({
required this.action,
this.nextActions = const [],
});
final NotificationAction? action;
final List<NotificationAction> nextActions;
NotificationActionState copyWith({
NotificationAction? action,
List<NotificationAction>? nextActions,
}) =>
NotificationActionState(
action: action ?? this.action,
nextActions: nextActions ?? this.nextActions,
);
}

View File

@ -29,9 +29,7 @@ class RecentViewsBloc extends Bloc<RecentViewsEvent, RecentViewsState> {
await event.map(
initial: (e) async {
_listener.start(
recentViewsUpdated: (result) => _onRecentViewsUpdated(
result,
),
recentViewsUpdated: (result) => _onRecentViewsUpdated(result),
);
add(const RecentViewsEvent.fetchRecentViews());
},

View File

@ -167,9 +167,10 @@ class ViewBackendService {
static Future<FlowyResult<void, FlowyError>> updateViewIcon({
required String viewId,
required String viewIcon,
ViewIconTypePB iconType = ViewIconTypePB.Emoji,
}) {
final icon = ViewIconPB()
..ty = ViewIconTypePB.Emoji
..ty = iconType
..value = viewIcon;
final payload = UpdateViewIconPayloadPB.create()
..viewId = viewId

View File

@ -0,0 +1,243 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/command_palette/command_palette_bloc.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/recent_views_list.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/search_field.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/search_results_list.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CommandPalette extends InheritedWidget {
CommandPalette({
super.key,
required Widget? child,
required ValueNotifier<bool> toggleNotifier,
}) : _toggleNotifier = toggleNotifier,
super(
child: _CommandPaletteController(
toggleNotifier: toggleNotifier,
child: child,
),
);
final ValueNotifier<bool> _toggleNotifier;
void toggle() => _toggleNotifier.value = !_toggleNotifier.value;
static CommandPalette of(BuildContext context) {
final CommandPalette? result =
context.dependOnInheritedWidgetOfExactType<CommandPalette>();
assert(result != null, "CommandPalette could not be found");
return result!;
}
@override
bool updateShouldNotify(covariant InheritedWidget oldWidget) => false;
}
class _ToggleCommandPaletteIntent extends Intent {
const _ToggleCommandPaletteIntent();
}
class _CommandPaletteController extends StatefulWidget {
const _CommandPaletteController({
required this.toggleNotifier,
required this.child,
});
final Widget? child;
final ValueNotifier<bool> toggleNotifier;
@override
State<_CommandPaletteController> createState() =>
_CommandPaletteControllerState();
}
class _CommandPaletteControllerState extends State<_CommandPaletteController> {
late final CommandPaletteBloc _commandPaletteBloc;
late ValueNotifier<bool> _toggleNotifier = widget.toggleNotifier;
bool _isOpen = false;
@override
void didUpdateWidget(covariant _CommandPaletteController oldWidget) {
if (oldWidget.toggleNotifier != widget.toggleNotifier) {
_toggleNotifier.removeListener(_onToggle);
_toggleNotifier.dispose();
_toggleNotifier = widget.toggleNotifier;
// If widget is changed, eg. on theme mode hotkey used
// while modal is shown, set the value before listening
_toggleNotifier.value = _isOpen;
_toggleNotifier.addListener(_onToggle);
}
super.didUpdateWidget(oldWidget);
}
@override
void initState() {
super.initState();
_toggleNotifier.addListener(_onToggle);
_commandPaletteBloc = CommandPaletteBloc();
}
@override
void dispose() {
_toggleNotifier.removeListener(_onToggle);
_toggleNotifier.dispose();
_commandPaletteBloc.close();
super.dispose();
}
void _onToggle() {
if (widget.toggleNotifier.value && !_isOpen) {
_isOpen = true;
FlowyOverlay.show(
context: context,
builder: (_) => BlocProvider.value(
value: _commandPaletteBloc,
child: CommandPaletteModal(shortcutBuilder: _buildShortcut),
),
).then((_) {
_isOpen = false;
widget.toggleNotifier.value = false;
});
} else if (!widget.toggleNotifier.value && _isOpen) {
FlowyOverlay.pop(context);
_isOpen = false;
}
}
@override
Widget build(BuildContext context) =>
_buildShortcut(widget.child ?? const SizedBox.shrink());
Widget _buildShortcut(Widget child) => FocusableActionDetector(
actions: {
_ToggleCommandPaletteIntent:
CallbackAction<_ToggleCommandPaletteIntent>(
onInvoke: (intent) =>
_toggleNotifier.value = !_toggleNotifier.value,
),
},
shortcuts: {
LogicalKeySet(
PlatformExtension.isMacOS
? LogicalKeyboardKey.meta
: LogicalKeyboardKey.control,
LogicalKeyboardKey.keyP,
): const _ToggleCommandPaletteIntent(),
},
child: child,
);
}
class CommandPaletteModal extends StatelessWidget {
const CommandPaletteModal({super.key, required this.shortcutBuilder});
final Widget Function(Widget) shortcutBuilder;
@override
Widget build(BuildContext context) {
return BlocBuilder<CommandPaletteBloc, CommandPaletteState>(
builder: (context, state) {
return FlowyDialog(
alignment: Alignment.topCenter,
insetPadding: const EdgeInsets.only(top: 100),
constraints: const BoxConstraints(maxHeight: 420, maxWidth: 510),
expandHeight: false,
child: shortcutBuilder(
Column(
mainAxisSize: MainAxisSize.min,
children: [
SearchField(query: state.query, isLoading: state.isLoading),
if ((state.query?.isEmpty ?? true) ||
state.isLoading && state.results.isEmpty) ...[
const Divider(height: 0),
Flexible(
child: RecentViewsList(
onSelected: () => FlowyOverlay.pop(context),
),
),
],
if (state.results.isNotEmpty) ...[
const Divider(height: 0),
Flexible(
child: SearchResultsList(
trash: state.trash,
results: state.results,
),
),
],
_CommandPaletteFooter(
shouldShow: state.results.isNotEmpty &&
(state.query?.isNotEmpty ?? false),
),
],
),
),
);
},
);
}
}
class _CommandPaletteFooter extends StatelessWidget {
const _CommandPaletteFooter({
required this.shouldShow,
});
final bool shouldShow;
@override
Widget build(BuildContext context) {
if (!shouldShow) {
return const SizedBox.shrink();
}
return Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 4,
),
decoration: BoxDecoration(
border: Border(
top: BorderSide(
color: Theme.of(context).dividerColor,
),
),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 5,
vertical: 1,
),
decoration: BoxDecoration(
color: AFThemeExtension.of(context).lightGreyHover,
borderRadius: BorderRadius.circular(4),
),
child: const FlowyText.semibold(
'TAB',
fontSize: 10,
),
),
const HSpace(4),
FlowyText(
LocaleKeys.commandPalette_navigateHint.tr(),
fontSize: 11,
),
],
),
);
}
}

View File

@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
class RecentViewTile extends StatelessWidget {
const RecentViewTile({
super.key,
required this.icon,
required this.view,
required this.onSelected,
});
final Widget icon;
final ViewPB view;
final VoidCallback onSelected;
@override
Widget build(BuildContext context) {
return ListTile(
dense: true,
title: Row(
children: [
icon,
const HSpace(4),
FlowyText(view.name),
],
),
focusColor: Theme.of(context).colorScheme.primary.withOpacity(0.5),
hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.5),
onTap: () {
onSelected();
getIt<ActionNavigationBloc>().add(
ActionNavigationEvent.performAction(
action: NavigationAction(objectId: view.id),
),
);
},
);
}
}

View File

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/recent_view_tile.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class RecentViewsList extends StatelessWidget {
const RecentViewsList({super.key, required this.onSelected});
final VoidCallback onSelected;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
RecentViewsBloc()..add(const RecentViewsEvent.initial()),
child: BlocBuilder<RecentViewsBloc, RecentViewsState>(
builder: (context, state) {
// We remove duplicates by converting the list to a set first
final List<ViewPB> recentViews =
state.views.reversed.toSet().toList();
return ListView.separated(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemCount: recentViews.length + 1,
itemBuilder: (_, index) {
if (index == 0) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: FlowyText(
LocaleKeys.commandPalette_recentHistory.tr(),
),
);
}
final view = recentViews[index - 1];
final icon = view.icon.value.isNotEmpty
? Text(
view.icon.value,
style: const TextStyle(fontSize: 18.0),
)
: FlowySvg(view.iconData, size: const Size.square(20));
return RecentViewTile(
icon: icon,
view: view,
onSelected: onSelected,
);
},
separatorBuilder: (_, __) => const Divider(height: 0),
);
},
),
);
}
}

View File

@ -0,0 +1,102 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/command_palette/command_palette_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/style_widget/text_field.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class SearchField extends StatelessWidget {
const SearchField({super.key, this.query, this.isLoading = false});
final String? query;
final bool isLoading;
@override
Widget build(BuildContext context) {
return Row(
children: [
const HSpace(12),
FlowySvg(
FlowySvgs.search_m,
color: Theme.of(context).hintColor,
),
Expanded(
child: FlowyTextField(
controller: TextEditingController(text: query),
textStyle:
Theme.of(context).textTheme.bodySmall?.copyWith(fontSize: 14),
decoration: InputDecoration(
constraints: const BoxConstraints(maxHeight: 48),
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.transparent),
borderRadius: Corners.s8Border,
),
isDense: false,
hintText: LocaleKeys.commandPalette_placeholder.tr(),
hintStyle: Theme.of(context).textTheme.bodySmall?.copyWith(
fontSize: 14,
color: Theme.of(context).hintColor,
),
errorStyle: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(color: Theme.of(context).colorScheme.error),
// TODO(Mathias): Remove beta when support document/database search
suffix: FlowyTooltip(
message: LocaleKeys.commandPalette_betaTooltip.tr(),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 5,
vertical: 1,
),
decoration: BoxDecoration(
color: AFThemeExtension.of(context).lightGreyHover,
borderRadius: BorderRadius.circular(4),
),
child: FlowyText.semibold(
LocaleKeys.commandPalette_betaLabel.tr(),
fontSize: 10,
),
),
),
counterText: "",
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(color: Colors.transparent),
borderRadius: Corners.s8Border,
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.error,
),
borderRadius: Corners.s8Border,
),
),
onChanged: (value) => context
.read<CommandPaletteBloc>()
.add(CommandPaletteEvent.searchChanged(search: value)),
),
),
if (isLoading) ...[
const HSpace(12),
FlowyTooltip(
message: LocaleKeys.commandPalette_loadingTooltip.tr(),
child: const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2.5),
),
),
const HSpace(12),
],
],
);
}
}

View File

@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
import 'package:appflowy/workspace/application/command_palette/search_result_ext.dart';
import 'package:appflowy_backend/protobuf/flowy-search/entities.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
class SearchResultTile extends StatelessWidget {
const SearchResultTile({
super.key,
required this.result,
required this.onSelected,
this.isTrashed = false,
});
final SearchResultPB result;
final VoidCallback onSelected;
final bool isTrashed;
@override
Widget build(BuildContext context) {
final icon = result.getIcon();
return ListTile(
dense: true,
title: Row(
children: [
if (icon != null) ...[icon, const HSpace(6)],
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (isTrashed) ...[
FlowyText(
LocaleKeys.commandPalette_fromTrashHint.tr(),
color: AFThemeExtension.of(context).textColor.withAlpha(175),
fontSize: 10,
),
],
FlowyText(result.data),
],
),
],
),
focusColor: Theme.of(context).colorScheme.primary.withOpacity(0.5),
hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.5),
onTap: () {
onSelected();
getIt<ActionNavigationBloc>().add(
ActionNavigationEvent.performAction(
action: NavigationAction(objectId: result.viewId),
),
);
},
);
}
}

View File

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/search_result_tile.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/trash.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-search/entities.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
class SearchResultsList extends StatelessWidget {
const SearchResultsList({
super.key,
required this.trash,
required this.results,
});
final List<TrashPB> trash;
final List<SearchResultPB> results;
@override
Widget build(BuildContext context) {
return ListView.separated(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
separatorBuilder: (_, __) => const Divider(height: 0),
itemCount: results.length + 1,
itemBuilder: (_, index) {
if (index == 0) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8) +
const EdgeInsets.only(left: 16),
child: FlowyText(
LocaleKeys.commandPalette_bestMatches.tr(),
),
);
}
final result = results[index - 1];
return SearchResultTile(
result: result,
onSelected: () => FlowyOverlay.pop(context),
isTrashed: trash.any((t) => t.id == result.viewId),
);
},
);
}
}

View File

@ -1,12 +1,13 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/application/sidebar/rename_view/rename_view_bloc.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_setting.dart';
import 'package:flutter/material.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
import 'package:provider/provider.dart';

View File

@ -1,11 +1,12 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/favorite/prelude.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/application/notifications/notification_action.dart';
import 'package:appflowy/workspace/application/notifications/notification_action_bloc.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
@ -20,7 +21,6 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
show UserProfilePB;
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
/// Home Sidebar is the left side bar of the home page.
@ -74,9 +74,7 @@ class HomeSideBar extends StatelessWidget {
}
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => getIt<NotificationActionBloc>(),
),
BlocProvider(create: (_) => getIt<ActionNavigationBloc>()),
BlocProvider(
create: (_) => SidebarSectionsBloc()
..add(
@ -99,7 +97,7 @@ class HomeSideBar extends StatelessWidget {
),
),
),
BlocListener<NotificationActionBloc, NotificationActionState>(
BlocListener<ActionNavigationBloc, ActionNavigationState>(
listenWhen: (_, curr) => curr.action != null,
listener: _onNotificationAction,
),
@ -134,35 +132,28 @@ class HomeSideBar extends StatelessWidget {
void _onNotificationAction(
BuildContext context,
NotificationActionState state,
ActionNavigationState state,
) {
final action = state.action;
if (action != null) {
if (action.type == ActionType.openView) {
final view = context
.read<SidebarSectionsBloc>()
.state
.section
.publicViews
.findView(action.objectId);
if (action?.type == ActionType.openView) {
final view = state.views.findView(action!.objectId);
if (view != null) {
final Map<String, dynamic> arguments = {};
if (view != null) {
final Map<String, dynamic> arguments = {};
final nodePath = action.arguments?[ActionArgumentKeys.nodePath];
if (nodePath != null) {
arguments[PluginArgumentKeys.selection] = Selection.collapsed(
Position(path: [nodePath]),
);
}
final rowId = action.arguments?[ActionArgumentKeys.rowId];
if (rowId != null) {
arguments[PluginArgumentKeys.rowId] = rowId;
}
context.read<TabsBloc>().openPlugin(view, arguments: arguments);
final nodePath = action.arguments?[ActionArgumentKeys.nodePath];
if (nodePath != null) {
arguments[PluginArgumentKeys.selection] = Selection.collapsed(
Position(path: [nodePath]),
);
}
final rowId = action.arguments?[ActionArgumentKeys.rowId];
if (rowId != null) {
arguments[PluginArgumentKeys.rowId] = rowId;
}
context.read<TabsBloc>().openPlugin(view, arguments: arguments);
}
}
}

View File

@ -1,3 +1,5 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
@ -17,13 +19,13 @@ import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.d
import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy/workspace/presentation/widgets/rename_view_popover.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
typedef ViewItemOnSelected = void Function(ViewPB);
@ -485,6 +487,7 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
ViewBackendService.updateViewIcon(
viewId: widget.view.id,
viewIcon: result.emoji,
iconType: result.type.toProto(),
);
controller.close();
},

View File

@ -3,6 +3,8 @@ import 'dart:convert' show utf8;
import 'dart:ffi';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:appflowy_backend/ffi.dart' as ffi;
import 'package:appflowy_backend/log.dart';
// ignore: unnecessary_import
@ -15,7 +17,6 @@ import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/services.dart';
import 'package:isolates/isolates.dart';
import 'package:isolates/ports.dart';
import 'package:protobuf/protobuf.dart';
@ -24,14 +25,18 @@ import '../protobuf/flowy-config/entities.pb.dart';
import '../protobuf/flowy-config/event_map.pb.dart';
import '../protobuf/flowy-date/entities.pb.dart';
import '../protobuf/flowy-date/event_map.pb.dart';
import '../protobuf/flowy-search/entities.pb.dart';
import '../protobuf/flowy-search/event_map.pb.dart';
import 'error.dart';
part 'dart_event/flowy-config/dart_event.dart';
part 'dart_event/flowy-database2/dart_event.dart';
part 'dart_event/flowy-date/dart_event.dart';
part 'dart_event/flowy-document/dart_event.dart';
part 'dart_event/flowy-folder/dart_event.dart';
part 'dart_event/flowy-user/dart_event.dart';
part 'dart_event/flowy-database2/dart_event.dart';
part 'dart_event/flowy-document/dart_event.dart';
part 'dart_event/flowy-config/dart_event.dart';
part 'dart_event/flowy-date/dart_event.dart';
part 'dart_event/flowy-search/dart_event.dart';
enum FFIException {
RequestIsEmpty,

View File

@ -5,6 +5,8 @@ import 'package:flutter/material.dart';
const _overlayContainerPadding = EdgeInsets.symmetric(vertical: 12);
const overlayContainerMaxWidth = 760.0;
const overlayContainerMinWidth = 320.0;
const _defaultInsetPadding =
EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0);
class FlowyDialog extends StatelessWidget {
const FlowyDialog({
@ -15,6 +17,9 @@ class FlowyDialog extends StatelessWidget {
this.constraints,
this.padding = _overlayContainerPadding,
this.backgroundColor,
this.expandHeight = true,
this.alignment,
this.insetPadding,
this.width,
});
@ -24,32 +29,43 @@ class FlowyDialog extends StatelessWidget {
final BoxConstraints? constraints;
final EdgeInsets padding;
final Color? backgroundColor;
final bool expandHeight;
// Position of the Dialog
final Alignment? alignment;
// Inset of the Dialog
final EdgeInsets? insetPadding;
final double? width;
@override
Widget build(BuildContext context) {
final windowSize = MediaQuery.of(context).size;
final size = windowSize * 0.6;
final size = windowSize * 0.7;
return SimpleDialog(
contentPadding: EdgeInsets.zero,
backgroundColor: backgroundColor ?? Theme.of(context).cardColor,
title: title,
shape: shape ??
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
children: [
Material(
type: MaterialType.transparency,
child: Container(
height: size.height,
width: width ??
max(
min(size.width, overlayContainerMaxWidth),
overlayContainerMinWidth,
),
constraints: constraints,
child: child,
),
)
]);
alignment: alignment,
insetPadding: insetPadding ?? _defaultInsetPadding,
contentPadding: EdgeInsets.zero,
backgroundColor: backgroundColor ?? Theme.of(context).cardColor,
title: title,
shape: shape ??
RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
clipBehavior: Clip.hardEdge,
children: [
Material(
type: MaterialType.transparency,
child: Container(
height: expandHeight ? size.height : null,
width: width ??
max(min(size.width, overlayContainerMaxWidth),
overlayContainerMinWidth),
constraints: constraints,
child: child,
),
)
],
);
}
}

View File

@ -132,6 +132,12 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "android-tzdata"
version = "0.1.1"
@ -185,6 +191,7 @@ dependencies = [
"flowy-document",
"flowy-error",
"flowy-notification",
"flowy-search",
"flowy-user",
"lib-dispatch",
"serde",
@ -197,6 +204,12 @@ dependencies = [
"uuid",
]
[[package]]
name = "arc-swap"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
[[package]]
name = "arrayvec"
version = "0.7.4"
@ -362,6 +375,15 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]]
name = "bitpacking"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8c7d2ac73c167c06af4a5f37e6e59d84148d57ccbe4480b76f0273eefea82d7"
dependencies = [
"crunchy",
]
[[package]]
name = "bitvec"
version = "1.0.1"
@ -578,6 +600,12 @@ dependencies = [
"jobserver",
]
[[package]]
name = "census"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0"
[[package]]
name = "cesu8"
version = "1.1.0"
@ -1221,6 +1249,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -1332,7 +1366,7 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim",
"strsim 0.10.0",
"syn 2.0.47",
]
@ -1547,6 +1581,12 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "downcast-rs"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "dtoa"
version = "1.0.6"
@ -1628,23 +1668,12 @@ checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1"
[[package]]
name = "errno"
version = "0.3.1"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
"windows-sys 0.52.0",
]
[[package]]
@ -1690,6 +1719,12 @@ dependencies = [
"syn 2.0.47",
]
[[package]]
name = "fastdivide"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25c7df09945d65ea8d70b3321547ed414bbc540aad5bac6883d021b970f35b04"
[[package]]
name = "fastrand"
version = "2.0.1"
@ -1760,7 +1795,7 @@ dependencies = [
"console",
"fancy-regex 0.10.0",
"flowy-ast",
"itertools",
"itertools 0.10.5",
"lazy_static",
"log",
"phf 0.8.0",
@ -1812,6 +1847,7 @@ dependencies = [
"flowy-error",
"flowy-folder",
"flowy-folder-pub",
"flowy-search",
"flowy-server",
"flowy-server-pub",
"flowy-sqlite",
@ -2007,6 +2043,7 @@ dependencies = [
"serde",
"serde_json",
"serde_repr",
"tantivy",
"thiserror",
"tokio",
"url",
@ -2021,6 +2058,7 @@ dependencies = [
"bytes",
"chrono",
"collab",
"collab-document",
"collab-entity",
"collab-folder",
"collab-integrate",
@ -2030,6 +2068,7 @@ dependencies = [
"flowy-error",
"flowy-folder-pub",
"flowy-notification",
"flowy-search-pub",
"lazy_static",
"lib-dispatch",
"lib-infra",
@ -2072,6 +2111,47 @@ dependencies = [
"tracing",
]
[[package]]
name = "flowy-search"
version = "0.1.0"
dependencies = [
"async-stream",
"bytes",
"collab",
"collab-folder",
"diesel",
"diesel_derives",
"diesel_migrations",
"flowy-codegen",
"flowy-derive",
"flowy-error",
"flowy-notification",
"flowy-search-pub",
"flowy-sqlite",
"flowy-user",
"futures",
"lib-dispatch",
"protobuf",
"serde",
"serde_json",
"strsim 0.11.0",
"strum_macros 0.26.1",
"tantivy",
"tempfile",
"tokio",
"tracing",
"validator",
]
[[package]]
name = "flowy-search-pub"
version = "0.1.0"
dependencies = [
"collab",
"collab-folder",
"flowy-error",
]
[[package]]
name = "flowy-server"
version = "0.1.0"
@ -2263,6 +2343,16 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fs4"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eeb4ed9e12f43b7fa0baae3f9cdda28352770132ef2e09a23760c29cae8bd47"
dependencies = [
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "funty"
version = "2.0.0"
@ -2795,6 +2885,10 @@ name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
dependencies = [
"ahash 0.8.6",
"allocator-api2",
]
[[package]]
name = "heck"
@ -2863,6 +2957,12 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "htmlescape"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
[[package]]
name = "http"
version = "0.2.9"
@ -3142,6 +3242,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
@ -3172,6 +3275,15 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.8"
@ -3308,6 +3420,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "levenshtein_automata"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25"
[[package]]
name = "lib-dispatch"
version = "0.1.0"
@ -3374,9 +3492,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.150"
version = "0.2.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]]
name = "libloading"
@ -3442,9 +3560,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.4.11"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lock_api"
@ -3470,6 +3588,7 @@ checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
dependencies = [
"cfg-if",
"generator",
"pin-utils",
"scoped-tls",
"serde",
"serde_json",
@ -3477,6 +3596,21 @@ dependencies = [
"tracing-subscriber",
]
[[package]]
name = "lru"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21"
dependencies = [
"hashbrown 0.14.3",
]
[[package]]
name = "lz4_flex"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "912b45c753ff5f7f5208307e8ace7d2a2e30d024e26d3509f3dce546c044ce15"
[[package]]
name = "mac"
version = "0.1.1"
@ -3588,12 +3722,31 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "measure_time"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852"
dependencies = [
"instant",
"log",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memmap2"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6"
dependencies = [
"libc",
]
[[package]]
name = "memoffset"
version = "0.9.0"
@ -3682,6 +3835,12 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
[[package]]
name = "murmurhash32"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9380db4c04d219ac5c51d14996bbf2c2e9a15229771b53f8671eb6c83cf44df"
[[package]]
name = "nanoid"
version = "0.4.0"
@ -3926,6 +4085,15 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "oneshot"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f6640c6bda7731b1fdbab747981a0f896dd1fedaf9f4a53fa237a04a84431f4"
dependencies = [
"loom",
]
[[package]]
name = "opaque-debug"
version = "0.3.0"
@ -4018,6 +4186,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "ownedbytes"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e8a72b918ae8198abb3a18c190288123e1d442b6b9a7d709305fd194688b4b7"
dependencies = [
"stable_deref_trait",
]
[[package]]
name = "pango"
version = "0.15.10"
@ -4543,7 +4720,7 @@ checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
dependencies = [
"bytes",
"heck 0.4.1",
"itertools",
"itertools 0.11.0",
"log",
"multimap",
"once_cell",
@ -4564,7 +4741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
dependencies = [
"anyhow",
"itertools",
"itertools 0.11.0",
"proc-macro2",
"quote",
"syn 2.0.47",
@ -5055,6 +5232,16 @@ dependencies = [
"librocksdb-sys",
]
[[package]]
name = "rust-stemmers"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54"
dependencies = [
"serde",
"serde_derive",
]
[[package]]
name = "rust_decimal"
version = "1.30.0"
@ -5106,15 +5293,15 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.25"
version = "0.38.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
dependencies = [
"bitflags 2.4.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@ -5591,6 +5778,15 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]]
name = "sketches-ddsketch"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c"
dependencies = [
"serde",
]
[[package]]
name = "slab"
version = "0.4.8"
@ -5725,6 +5921,12 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strsim"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]]
name = "strum"
version = "0.25.0"
@ -5756,6 +5958,19 @@ dependencies = [
"syn 2.0.47",
]
[[package]]
name = "strum_macros"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.47",
]
[[package]]
name = "subtle"
version = "2.5.0"
@ -5852,6 +6067,146 @@ dependencies = [
"version-compare 0.1.1",
]
[[package]]
name = "tantivy"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6083cd777fa94271b8ce0fe4533772cb8110c3044bab048d20f70108329a1f2"
dependencies = [
"aho-corasick 1.0.2",
"arc-swap",
"async-trait",
"base64 0.21.5",
"bitpacking",
"byteorder",
"census",
"crc32fast",
"crossbeam-channel",
"downcast-rs",
"fastdivide",
"fs4",
"htmlescape",
"itertools 0.11.0",
"levenshtein_automata",
"log",
"lru",
"lz4_flex",
"measure_time",
"memmap2",
"murmurhash32",
"num_cpus",
"once_cell",
"oneshot",
"rayon",
"regex",
"rust-stemmers",
"rustc-hash",
"serde",
"serde_json",
"sketches-ddsketch",
"smallvec",
"tantivy-bitpacker",
"tantivy-columnar",
"tantivy-common",
"tantivy-fst",
"tantivy-query-grammar",
"tantivy-stacker",
"tantivy-tokenizer-api",
"tempfile",
"thiserror",
"time",
"uuid",
"winapi",
]
[[package]]
name = "tantivy-bitpacker"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cecb164321482301f514dd582264fa67f70da2d7eb01872ccd71e35e0d96655a"
dependencies = [
"bitpacking",
]
[[package]]
name = "tantivy-columnar"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d85f8019af9a78b3118c11298b36ffd21c2314bd76bbcd9d12e00124cbb7e70"
dependencies = [
"fastdivide",
"fnv",
"itertools 0.11.0",
"serde",
"tantivy-bitpacker",
"tantivy-common",
"tantivy-sstable",
"tantivy-stacker",
]
[[package]]
name = "tantivy-common"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af4a3a975e604a2aba6b1106a04505e1e7a025e6def477fab6e410b4126471e1"
dependencies = [
"async-trait",
"byteorder",
"ownedbytes",
"serde",
"time",
]
[[package]]
name = "tantivy-fst"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc3c506b1a8443a3a65352df6382a1fb6a7afe1a02e871cee0d25e2c3d5f3944"
dependencies = [
"byteorder",
"regex-syntax 0.6.29",
"utf8-ranges",
]
[[package]]
name = "tantivy-query-grammar"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d39c5a03100ac10c96e0c8b07538e2ab8b17da56434ab348309b31f23fada77"
dependencies = [
"nom",
]
[[package]]
name = "tantivy-sstable"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0c1bb43e5e8b8e05eb8009610344dbf285f06066c844032fbb3e546b3c71df"
dependencies = [
"tantivy-common",
"tantivy-fst",
"zstd 0.12.4",
]
[[package]]
name = "tantivy-stacker"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2c078595413f13f218cf6f97b23dcfd48936838f1d3d13a1016e05acd64ed6c"
dependencies = [
"murmurhash32",
"tantivy-common",
]
[[package]]
name = "tantivy-tokenizer-api"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "347b6fb212b26d3505d224f438e3c4b827ab8bd847fe9953ad5ac6b8f9443b66"
dependencies = [
"serde",
]
[[package]]
name = "tao"
version = "0.16.2"
@ -6139,15 +6494,15 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.8.1"
version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall 0.4.1",
"rustix",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
@ -6304,6 +6659,7 @@ dependencies = [
"signal-hook-registry",
"socket2 0.5.5",
"tokio-macros",
"tracing",
"windows-sys 0.48.0",
]
@ -6772,6 +7128,12 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8-ranges"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba"
[[package]]
name = "uuid"
version = "1.6.1"
@ -7249,6 +7611,15 @@ dependencies = [
"windows-targets 0.48.0",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
@ -7637,7 +8008,7 @@ dependencies = [
"pbkdf2 0.11.0",
"sha1",
"time",
"zstd",
"zstd 0.11.2+zstd.1.5.2",
]
[[package]]
@ -7646,7 +8017,16 @@ version = "0.11.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
dependencies = [
"zstd-safe",
"zstd-safe 5.0.2+zstd.1.5.2",
]
[[package]]
name = "zstd"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c"
dependencies = [
"zstd-safe 6.0.6",
]
[[package]]
@ -7659,6 +8039,16 @@ dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-safe"
version = "6.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581"
dependencies = [
"libc",
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.8+zstd.1.5.5"

View File

@ -60,6 +60,7 @@ flowy-error = { path = "../../rust-lib/flowy-error", features = [
"impl_from_serde",
"tauri_ts",
] }
flowy-search = { path = "../../rust-lib/flowy-search", features = ["tauri_ts"] }
flowy-document = { path = "../../rust-lib/flowy-document", features = [
"tauri_ts",
] }

View File

@ -5,3 +5,4 @@ export * from "./models/flowy-document";
export * from "./models/flowy-error";
export * from "./models/flowy-config";
export * from "./models/flowy-date";
export * from "./models/flowy-search";

View File

@ -1371,6 +1371,7 @@ dependencies = [
"bytes",
"chrono",
"collab",
"collab-document",
"collab-entity",
"collab-folder",
"collab-integrate",
@ -1380,6 +1381,7 @@ dependencies = [
"flowy-error",
"flowy-folder-pub",
"flowy-notification",
"flowy-search-pub",
"lazy_static",
"lib-dispatch",
"lib-infra",
@ -1422,6 +1424,15 @@ dependencies = [
"tracing",
]
[[package]]
name = "flowy-search-pub"
version = "0.1.0"
dependencies = [
"collab",
"collab-folder",
"flowy-error",
]
[[package]]
name = "flowy-server"
version = "0.1.0"

View File

@ -1463,5 +1463,15 @@
"synced": "Synced",
"noNetworkConnected": "No network connected"
}
},
"commandPalette": {
"placeholder": "Type to search for views...",
"bestMatches": "Best matches",
"recentHistory": "Recent history",
"navigateHint": "to navigate",
"loadingTooltip": "We are looking for results...",
"betaLabel": "BETA",
"betaTooltip": "We currently only support searching for pages",
"fromTrashHint": "From trash"
}
}

View File

@ -133,6 +133,12 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "android-tzdata"
version = "0.1.1"
@ -174,6 +180,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "arc-swap"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
[[package]]
name = "arrayvec"
version = "0.7.4"
@ -422,6 +434,15 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]]
name = "bitpacking"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8c7d2ac73c167c06af4a5f37e6e59d84148d57ccbe4480b76f0273eefea82d7"
dependencies = [
"crunchy",
]
[[package]]
name = "bitvec"
version = "1.0.1"
@ -593,6 +614,12 @@ dependencies = [
"libc",
]
[[package]]
name = "census"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0"
[[package]]
name = "cexpr"
version = "0.6.0"
@ -1138,6 +1165,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -1222,7 +1255,7 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim",
"strsim 0.10.0",
"syn 1.0.109",
]
@ -1335,6 +1368,9 @@ name = "deranged"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
dependencies = [
"serde",
]
[[package]]
name = "derivative"
@ -1395,6 +1431,12 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d95203a6a50906215a502507c0f879a0ce7ff205a6111e2db2a5ef8e4bb92e43"
[[package]]
name = "deunicode"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6e854126756c496b8c81dec88f9a706b15b875c5849d4097a3854476b9fdf94"
[[package]]
name = "diesel"
version = "2.1.4"
@ -1457,6 +1499,12 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "downcast-rs"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "dtoa"
version = "1.0.9"
@ -1523,23 +1571,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.3"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
"windows-sys 0.52.0",
]
[[package]]
@ -1566,6 +1603,7 @@ dependencies = [
"flowy-folder",
"flowy-folder-pub",
"flowy-notification",
"flowy-search",
"flowy-server",
"flowy-server-pub",
"flowy-storage",
@ -1586,6 +1624,7 @@ dependencies = [
"tokio-postgres",
"tracing",
"uuid",
"walkdir",
"zip",
]
@ -1613,12 +1652,12 @@ dependencies = [
[[package]]
name = "fake"
version = "2.8.0"
version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9af7b0c58ac9d03169e27f080616ce9f64004edca3d2ef4147a811c21b23b319"
checksum = "1c25829bde82205da46e1823b2259db6273379f626fc211f126f65654a2669be"
dependencies = [
"deunicode 1.4.3",
"rand 0.8.5",
"unidecode",
]
[[package]]
@ -1660,10 +1699,16 @@ dependencies = [
]
[[package]]
name = "fastrand"
version = "2.0.0"
name = "fastdivide"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
checksum = "25c7df09945d65ea8d70b3321547ed414bbc540aad5bac6883d021b970f35b04"
[[package]]
name = "fastrand"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "finl_unicode"
@ -1718,7 +1763,7 @@ dependencies = [
"console",
"fancy-regex 0.10.0",
"flowy-ast",
"itertools",
"itertools 0.10.5",
"lazy_static",
"log",
"phf 0.8.0",
@ -1771,6 +1816,7 @@ dependencies = [
"flowy-error",
"flowy-folder",
"flowy-folder-pub",
"flowy-search",
"flowy-server",
"flowy-server-pub",
"flowy-sqlite",
@ -1969,6 +2015,7 @@ dependencies = [
"serde",
"serde_json",
"serde_repr",
"tantivy",
"thiserror",
"tokio",
"url",
@ -1983,6 +2030,7 @@ dependencies = [
"bytes",
"chrono",
"collab",
"collab-document",
"collab-entity",
"collab-folder",
"collab-integrate",
@ -1992,6 +2040,7 @@ dependencies = [
"flowy-error",
"flowy-folder-pub",
"flowy-notification",
"flowy-search-pub",
"lazy_static",
"lib-dispatch",
"lib-infra",
@ -2035,6 +2084,47 @@ dependencies = [
"tracing",
]
[[package]]
name = "flowy-search"
version = "0.1.0"
dependencies = [
"async-stream",
"bytes",
"collab",
"collab-folder",
"diesel",
"diesel_derives",
"diesel_migrations",
"flowy-codegen",
"flowy-derive",
"flowy-error",
"flowy-notification",
"flowy-search-pub",
"flowy-sqlite",
"flowy-user",
"futures",
"lib-dispatch",
"protobuf",
"serde",
"serde_json",
"strsim 0.11.0",
"strum_macros 0.26.1",
"tantivy",
"tempfile",
"tokio",
"tracing",
"validator",
]
[[package]]
name = "flowy-search-pub"
version = "0.1.0"
dependencies = [
"collab",
"collab-folder",
"flowy-error",
]
[[package]]
name = "flowy-server"
version = "0.1.0"
@ -2238,6 +2328,16 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fs4"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eeb4ed9e12f43b7fa0baae3f9cdda28352770132ef2e09a23760c29cae8bd47"
dependencies = [
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
@ -2364,6 +2464,19 @@ dependencies = [
"byteorder",
]
[[package]]
name = "generator"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e"
dependencies = [
"cc",
"libc",
"log",
"rustversion",
"windows 0.48.0",
]
[[package]]
name = "generic-array"
version = "0.14.7"
@ -2551,6 +2664,10 @@ name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
dependencies = [
"ahash 0.8.6",
"allocator-api2",
]
[[package]]
name = "hdrhistogram"
@ -2607,7 +2724,7 @@ version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
dependencies = [
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@ -2624,6 +2741,12 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "htmlescape"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
[[package]]
name = "http"
version = "0.2.9"
@ -2883,6 +3006,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
@ -2900,6 +3026,15 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.9"
@ -2950,6 +3085,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "levenshtein_automata"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25"
[[package]]
name = "lib-dispatch"
version = "0.1.0"
@ -3019,9 +3160,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.150"
version = "0.2.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]]
name = "libloading"
@ -3077,9 +3218,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.4.11"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lock_api"
@ -3097,6 +3238,35 @@ version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "loom"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
dependencies = [
"cfg-if",
"generator",
"pin-utils",
"scoped-tls",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "lru"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21"
dependencies = [
"hashbrown 0.14.3",
]
[[package]]
name = "lz4_flex"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "912b45c753ff5f7f5208307e8ace7d2a2e30d024e26d3509f3dce546c044ce15"
[[package]]
name = "mac"
version = "0.1.1"
@ -3194,12 +3364,31 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "measure_time"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852"
dependencies = [
"instant",
"log",
]
[[package]]
name = "memchr"
version = "2.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
[[package]]
name = "memmap2"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6"
dependencies = [
"libc",
]
[[package]]
name = "memoffset"
version = "0.9.0"
@ -3269,7 +3458,7 @@ checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
dependencies = [
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@ -3278,6 +3467,12 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
[[package]]
name = "murmurhash32"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9380db4c04d219ac5c51d14996bbf2c2e9a15229771b53f8671eb6c83cf44df"
[[package]]
name = "nanoid"
version = "0.4.0"
@ -3395,6 +3590,15 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "oneshot"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f6640c6bda7731b1fdbab747981a0f896dd1fedaf9f4a53fa237a04a84431f4"
dependencies = [
"loom",
]
[[package]]
name = "opaque-debug"
version = "0.3.0"
@ -3471,6 +3675,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "ownedbytes"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e8a72b918ae8198abb3a18c190288123e1d442b6b9a7d709305fd194688b4b7"
dependencies = [
"stable_deref_trait",
]
[[package]]
name = "parking_lot"
version = "0.11.2"
@ -3933,7 +4146,7 @@ checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
dependencies = [
"bytes",
"heck 0.4.1",
"itertools",
"itertools 0.11.0",
"log",
"multimap",
"once_cell",
@ -3954,7 +4167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
dependencies = [
"anyhow",
"itertools",
"itertools 0.11.0",
"proc-macro2",
"quote",
"syn 2.0.47",
@ -4302,15 +4515,6 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "regex"
version = "1.9.5"
@ -4491,6 +4695,16 @@ dependencies = [
"librocksdb-sys",
]
[[package]]
name = "rust-stemmers"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54"
dependencies = [
"serde",
"serde_derive",
]
[[package]]
name = "rust_decimal"
version = "1.32.0"
@ -4531,15 +4745,15 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.25"
version = "0.38.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [
"bitflags 2.4.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -4622,7 +4836,7 @@ version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
dependencies = [
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@ -4634,6 +4848,12 @@ dependencies = [
"parking_lot 0.12.1",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -4940,6 +5160,15 @@ version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "sketches-ddsketch"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c"
dependencies = [
"serde",
]
[[package]]
name = "slab"
version = "0.4.9"
@ -4955,7 +5184,7 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373"
dependencies = [
"deunicode",
"deunicode 0.4.4",
]
[[package]]
@ -4990,7 +5219,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@ -5048,6 +5277,12 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strsim"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]]
name = "strum"
version = "0.25.0"
@ -5079,6 +5314,19 @@ dependencies = [
"syn 2.0.47",
]
[[package]]
name = "strum_macros"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.47",
]
[[package]]
name = "subtle"
version = "2.5.0"
@ -5149,6 +5397,146 @@ dependencies = [
"libc",
]
[[package]]
name = "tantivy"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6083cd777fa94271b8ce0fe4533772cb8110c3044bab048d20f70108329a1f2"
dependencies = [
"aho-corasick",
"arc-swap",
"async-trait",
"base64 0.21.5",
"bitpacking",
"byteorder",
"census",
"crc32fast",
"crossbeam-channel",
"downcast-rs",
"fastdivide",
"fs4",
"htmlescape",
"itertools 0.11.0",
"levenshtein_automata",
"log",
"lru",
"lz4_flex",
"measure_time",
"memmap2",
"murmurhash32",
"num_cpus",
"once_cell",
"oneshot",
"rayon",
"regex",
"rust-stemmers",
"rustc-hash",
"serde",
"serde_json",
"sketches-ddsketch",
"smallvec",
"tantivy-bitpacker",
"tantivy-columnar",
"tantivy-common",
"tantivy-fst",
"tantivy-query-grammar",
"tantivy-stacker",
"tantivy-tokenizer-api",
"tempfile",
"thiserror",
"time",
"uuid",
"winapi",
]
[[package]]
name = "tantivy-bitpacker"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cecb164321482301f514dd582264fa67f70da2d7eb01872ccd71e35e0d96655a"
dependencies = [
"bitpacking",
]
[[package]]
name = "tantivy-columnar"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d85f8019af9a78b3118c11298b36ffd21c2314bd76bbcd9d12e00124cbb7e70"
dependencies = [
"fastdivide",
"fnv",
"itertools 0.11.0",
"serde",
"tantivy-bitpacker",
"tantivy-common",
"tantivy-sstable",
"tantivy-stacker",
]
[[package]]
name = "tantivy-common"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af4a3a975e604a2aba6b1106a04505e1e7a025e6def477fab6e410b4126471e1"
dependencies = [
"async-trait",
"byteorder",
"ownedbytes",
"serde",
"time",
]
[[package]]
name = "tantivy-fst"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc3c506b1a8443a3a65352df6382a1fb6a7afe1a02e871cee0d25e2c3d5f3944"
dependencies = [
"byteorder",
"regex-syntax 0.6.29",
"utf8-ranges",
]
[[package]]
name = "tantivy-query-grammar"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d39c5a03100ac10c96e0c8b07538e2ab8b17da56434ab348309b31f23fada77"
dependencies = [
"nom",
]
[[package]]
name = "tantivy-sstable"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0c1bb43e5e8b8e05eb8009610344dbf285f06066c844032fbb3e546b3c71df"
dependencies = [
"tantivy-common",
"tantivy-fst",
"zstd 0.12.4",
]
[[package]]
name = "tantivy-stacker"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2c078595413f13f218cf6f97b23dcfd48936838f1d3d13a1016e05acd64ed6c"
dependencies = [
"murmurhash32",
"tantivy-common",
]
[[package]]
name = "tantivy-tokenizer-api"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "347b6fb212b26d3505d224f438e3c4b827ab8bd847fe9953ad5ac6b8f9443b66"
dependencies = [
"serde",
]
[[package]]
name = "tap"
version = "1.0.1"
@ -5167,15 +5555,14 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.8.1"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall 0.4.1",
"rustix",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -5322,7 +5709,7 @@ dependencies = [
"socket2 0.5.5",
"tokio-macros",
"tracing",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@ -5827,12 +6214,6 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "unidecode"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "402bb19d8e03f1d1a7450e2bd613980869438e0666331be3e073089124aa1adc"
[[package]]
name = "universal-hash"
version = "0.5.1"
@ -5872,6 +6253,12 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "utf8-ranges"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba"
[[package]]
name = "uuid"
version = "1.6.1"
@ -5946,9 +6333,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.4.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
@ -6175,6 +6562,15 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
@ -6305,7 +6701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys",
"windows-sys 0.48.0",
]
[[package]]
@ -6368,7 +6764,7 @@ dependencies = [
"pbkdf2 0.11.0",
"sha1",
"time",
"zstd",
"zstd 0.11.2+zstd.1.5.2",
]
[[package]]
@ -6377,7 +6773,16 @@ version = "0.11.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
dependencies = [
"zstd-safe",
"zstd-safe 5.0.2+zstd.1.5.2",
]
[[package]]
name = "zstd"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c"
dependencies = [
"zstd-safe 6.0.6",
]
[[package]]
@ -6390,6 +6795,16 @@ dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-safe"
version = "6.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581"
dependencies = [
"libc",
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.8+zstd.1.5.5"

View File

@ -24,10 +24,12 @@ members = [
"collab-integrate",
"flowy-ai",
"flowy-date",
"flowy-search",
"lib-infra",
"build-tool/flowy-ast",
"build-tool/flowy-codegen",
"build-tool/flowy-derive",
"flowy-search-pub",
]
resolver = "2"
@ -56,6 +58,8 @@ flowy-server-pub = { workspace = true, path = "flowy-server-pub" }
flowy-config = { workspace = true, path = "flowy-config" }
flowy-encrypt = { workspace = true, path = "flowy-encrypt" }
flowy-storage = { workspace = true, path = "flowy-storage" }
flowy-search = { workspace = true, path = "flowy-search" }
flowy-search-pub = { workspace = true, path = "flowy-search-pub" }
collab-integrate = { workspace = true, path = "collab-integrate" }
flowy-ai = { workspace = true, path = "flowy-ai" }
flowy-date = { workspace = true, path = "flowy-date" }

View File

@ -17,9 +17,9 @@ anyhow.workspace = true
tracing.workspace = true
parking_lot.workspace = true
async-trait.workspace = true
tokio = { workspace = true, features = ["sync"]}
tokio = { workspace = true, features = ["sync"] }
lib-infra = { workspace = true }
futures = "0.3"
[features]
default = []
default = []

View File

@ -23,6 +23,7 @@ flowy-server-pub = { workspace = true }
flowy-notification = { workspace = true }
anyhow.workspace = true
flowy-storage = { workspace = true }
flowy-search = { workspace = true }
serde.workspace = true
serde_json.workspace = true
@ -51,6 +52,7 @@ assert-json-diff = "2.0.2"
tokio-postgres = { version = "0.7.8" }
chrono = "0.4.31"
zip = "0.6.6"
walkdir = "2.5.0"
[features]
default = ["supabase_cloud_test"]

View File

@ -29,7 +29,7 @@ pub struct OpenDocumentData {
impl DocumentEventTest {
pub async fn new() -> Self {
let sdk = EventIntegrationTest::new_with_guest_user().await;
let sdk = EventIntegrationTest::new_anon().await;
Self { event_test: sdk }
}

View File

@ -1,7 +1,9 @@
use collab_folder::{FolderData, View};
use flowy_folder::entities::icon::UpdateViewIconPayloadPB;
use flowy_folder::entities::*;
use flowy_folder::event_map::FolderEvent;
use flowy_folder::event_map::FolderEvent::*;
use flowy_folder::{entities::*, ViewLayout};
use flowy_search::services::manager::{SearchHandler, SearchType};
use flowy_user::entities::{
AcceptWorkspaceInvitationPB, AddWorkspaceMemberPB, QueryWorkspacePB, RemoveWorkspaceMemberPB,
RepeatedWorkspaceInvitationPB, RepeatedWorkspaceMemberPB, WorkspaceMemberInvitationPB,
@ -9,6 +11,7 @@ use flowy_user::entities::{
};
use flowy_user::errors::FlowyError;
use flowy_user::event_map::UserEvent;
use std::sync::Arc;
use flowy_user_pub::entities::Role;
use crate::event_builder::EventBuilder;
@ -99,6 +102,49 @@ impl EventIntegrationTest {
.parse::<WorkspacePB>()
}
pub fn get_folder_search_handler(&self) -> &Arc<dyn SearchHandler> {
self
.appflowy_core
.search_manager
.get_handler(SearchType::Folder)
.unwrap()
}
/// create views in the folder.
pub async fn create_views(&self, views: Vec<View>) {
let create_view_params = views
.into_iter()
.map(|view| CreateViewParams {
parent_view_id: view.parent_view_id,
name: view.name,
desc: "".to_string(),
layout: view.layout.into(),
view_id: view.id,
initial_data: vec![],
meta: Default::default(),
set_as_current: false,
index: None,
section: None,
})
.collect::<Vec<_>>();
for params in create_view_params {
self
.appflowy_core
.folder_manager
.create_view_with_params(params)
.await
.unwrap();
}
}
pub fn get_folder_data(&self) -> FolderData {
let mutex_folder = self.appflowy_core.folder_manager.get_mutex_folder().clone();
let folder_lock_guard = mutex_folder.lock();
let folder = folder_lock_guard.as_ref().unwrap();
folder.get_folder_data().clone().unwrap()
}
pub async fn get_all_workspace_views(&self) -> Vec<ViewPB> {
EventBuilder::new(self.clone())
.event(FolderEvent::ReadCurrentWorkspaceViews)
@ -201,7 +247,7 @@ pub struct ViewTest {
}
impl ViewTest {
#[allow(dead_code)]
pub async fn new(sdk: &EventIntegrationTest, layout: ViewLayoutPB, data: Vec<u8>) -> Self {
pub async fn new(sdk: &EventIntegrationTest, layout: ViewLayout, data: Vec<u8>) -> Self {
let workspace = sdk.folder_manager.get_current_workspace().await.unwrap();
let payload = CreateViewPayloadPB {
@ -209,7 +255,7 @@ impl ViewTest {
name: "View A".to_string(),
desc: "".to_string(),
thumbnail: Some("http://1.png".to_string()),
layout,
layout: layout.into(),
initial_data: data,
meta: Default::default(),
set_as_current: true,
@ -223,6 +269,7 @@ impl ViewTest {
.async_send()
.await
.parse::<ViewPB>();
Self {
sdk: sdk.clone(),
workspace,
@ -231,15 +278,15 @@ impl ViewTest {
}
pub async fn new_grid_view(sdk: &EventIntegrationTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Grid, data).await
Self::new(sdk, ViewLayout::Grid, data).await
}
pub async fn new_board_view(sdk: &EventIntegrationTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Board, data).await
Self::new(sdk, ViewLayout::Board, data).await
}
pub async fn new_calendar_view(sdk: &EventIntegrationTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewLayoutPB::Calendar, data).await
Self::new(sdk, ViewLayout::Calendar, data).await
}
}

View File

@ -14,6 +14,7 @@ use tokio::select;
use tokio::time::sleep;
use flowy_core::config::AppFlowyCoreConfig;
use flowy_core::integrate::log::create_log_filter;
use flowy_core::AppFlowyCore;
use flowy_notification::register_notification_sender;
use flowy_server::AppFlowyServer;
@ -86,6 +87,14 @@ impl EventIntegrationTest {
}
}
pub fn instance_name(&self) -> String {
self.appflowy_core.config.name.clone()
}
pub fn user_data_path(&self) -> String {
self.appflowy_core.config.application_path.clone()
}
pub fn get_server(&self) -> Arc<dyn AppFlowyServer> {
self.appflowy_core.server_provider.get_server().unwrap()
}

View File

@ -51,13 +51,14 @@ impl EventIntegrationTest {
config.encrypt_secret
}
pub async fn new_with_guest_user() -> Self {
/// Create a anonymous user for given test.
pub async fn new_anon() -> Self {
let test = Self::new().await;
test.sign_up_as_guest().await;
test.sign_up_as_anon().await;
test
}
pub async fn sign_up_as_guest(&self) -> SignUpContext {
pub async fn sign_up_as_anon(&self) -> SignUpContext {
let password = login_password();
let email = unique_email();
let payload = SignUpPayloadPB {
@ -116,7 +117,7 @@ impl EventIntegrationTest {
}
pub async fn init_anon_user(&self) -> UserProfilePB {
self.sign_up_as_guest().await.user_profile
self.sign_up_as_anon().await.user_profile
}
pub async fn get_user_profile(&self) -> Result<UserProfilePB, FlowyError> {

View File

@ -3,7 +3,7 @@ use event_integration::EventIntegrationTest;
// The number of groups should be 0 if there is no group by field in grid
#[tokio::test]
async fn get_groups_event_with_grid_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my board view".to_owned(), vec![])
@ -15,7 +15,7 @@ async fn get_groups_event_with_grid_test() {
#[tokio::test]
async fn get_groups_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -27,7 +27,7 @@ async fn get_groups_event_test() {
#[tokio::test]
async fn move_group_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -61,7 +61,7 @@ async fn move_group_event_test() {
#[tokio::test]
async fn move_group_event_with_invalid_id_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -83,7 +83,7 @@ async fn move_group_event_with_invalid_id_test() {
#[tokio::test]
async fn rename_group_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -104,7 +104,7 @@ async fn rename_group_event_test() {
#[tokio::test]
async fn hide_group_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -132,7 +132,7 @@ async fn hide_group_event_test() {
#[tokio::test]
async fn update_group_name_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -157,7 +157,7 @@ async fn update_group_name_test() {
#[tokio::test]
async fn delete_group_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![])

View File

@ -13,7 +13,7 @@ use lib_infra::util::timestamp;
#[tokio::test]
async fn get_database_id_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -35,7 +35,7 @@ async fn get_database_id_event_test() {
#[tokio::test]
async fn get_database_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -49,7 +49,7 @@ async fn get_database_event_test() {
#[tokio::test]
async fn get_field_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -64,7 +64,7 @@ async fn get_field_event_test() {
#[tokio::test]
async fn create_field_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -78,7 +78,7 @@ async fn create_field_event_test() {
#[tokio::test]
async fn delete_field_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -99,7 +99,7 @@ async fn delete_field_event_test() {
// The primary field is not allowed to be deleted.
#[tokio::test]
async fn delete_primary_field_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -114,7 +114,7 @@ async fn delete_primary_field_event_test() {
#[tokio::test]
async fn update_field_type_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -132,7 +132,7 @@ async fn update_field_type_event_test() {
#[tokio::test]
async fn update_primary_field_type_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -151,7 +151,7 @@ async fn update_primary_field_type_event_test() {
#[tokio::test]
async fn duplicate_field_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -169,7 +169,7 @@ async fn duplicate_field_event_test() {
// The primary field is not allowed to be duplicated. So this test should return an error.
#[tokio::test]
async fn duplicate_primary_field_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -183,7 +183,7 @@ async fn duplicate_primary_field_test() {
#[tokio::test]
async fn get_primary_field_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -196,7 +196,7 @@ async fn get_primary_field_event_test() {
#[tokio::test]
async fn create_row_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -211,7 +211,7 @@ async fn create_row_event_test() {
#[tokio::test]
async fn delete_row_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -234,7 +234,7 @@ async fn delete_row_event_test() {
#[tokio::test]
async fn get_row_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -250,7 +250,7 @@ async fn get_row_event_test() {
#[tokio::test]
async fn update_row_meta_event_with_url_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -279,7 +279,7 @@ async fn update_row_meta_event_with_url_test() {
#[tokio::test]
async fn update_row_meta_event_with_cover_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -308,7 +308,7 @@ async fn update_row_meta_event_with_cover_test() {
#[tokio::test]
async fn delete_row_event_with_invalid_row_id_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -321,7 +321,7 @@ async fn delete_row_event_with_invalid_row_id_test() {
#[tokio::test]
async fn duplicate_row_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -338,7 +338,7 @@ async fn duplicate_row_event_test() {
#[tokio::test]
async fn duplicate_row_event_with_invalid_row_id_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -355,7 +355,7 @@ async fn duplicate_row_event_with_invalid_row_id_test() {
#[tokio::test]
async fn move_row_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -375,7 +375,7 @@ async fn move_row_event_test() {
#[tokio::test]
async fn move_row_event_test2() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -395,7 +395,7 @@ async fn move_row_event_test2() {
#[tokio::test]
async fn move_row_event_with_invalid_row_id_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -422,7 +422,7 @@ async fn move_row_event_with_invalid_row_id_test() {
#[tokio::test]
async fn update_text_cell_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -452,7 +452,7 @@ async fn update_text_cell_event_test() {
#[tokio::test]
async fn update_checkbox_cell_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -483,7 +483,7 @@ async fn update_checkbox_cell_event_test() {
#[tokio::test]
async fn update_single_select_cell_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -510,7 +510,7 @@ async fn update_single_select_cell_event_test() {
#[tokio::test]
async fn update_date_cell_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -547,7 +547,7 @@ async fn update_date_cell_event_test() {
#[tokio::test]
async fn update_date_cell_event_with_empty_time_str_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -583,7 +583,7 @@ async fn update_date_cell_event_with_empty_time_str_test() {
#[tokio::test]
async fn create_checklist_field_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -604,7 +604,7 @@ async fn create_checklist_field_test() {
#[tokio::test]
async fn update_checklist_cell_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -661,7 +661,7 @@ async fn update_checklist_cell_test() {
// Update the database layout type from grid to board
#[tokio::test]
async fn update_database_layout_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -683,7 +683,7 @@ async fn update_database_layout_event_test() {
// Update the database layout type from grid to board. Set the checkbox field as the grouping field
#[tokio::test]
async fn update_database_layout_event_test2() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -715,7 +715,7 @@ async fn update_database_layout_event_test2() {
// Create a checkbox field in the default board and then set it as the grouping field.
#[tokio::test]
async fn set_group_by_checkbox_field_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let board_view = test
.create_board(&current_workspace.id, "my board view".to_owned(), vec![])
@ -732,7 +732,7 @@ async fn set_group_by_checkbox_field_test() {
#[tokio::test]
async fn get_all_calendar_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let calendar_view = test
.create_calendar(&current_workspace.id, "my calendar view".to_owned(), vec![])
@ -745,7 +745,7 @@ async fn get_all_calendar_event_test() {
#[tokio::test]
async fn create_calendar_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let calendar_view = test
.create_calendar(&current_workspace.id, "my calendar view".to_owned(), vec![])
@ -781,7 +781,7 @@ async fn create_calendar_event_test() {
#[tokio::test]
async fn update_relation_cell_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let grid_view = test
.create_grid(&current_workspace.id, "my grid view".to_owned(), vec![])
@ -840,7 +840,7 @@ async fn update_relation_cell_test() {
#[tokio::test]
async fn get_detailed_relation_cell_data() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let origin_grid_view = test

View File

@ -8,7 +8,7 @@ use event_integration::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use flowy_document::entities::{DocumentSyncState, DocumentSyncStatePB};
use crate::util::{receive_with_timeout, unzip_history_user_db};
use crate::util::{receive_with_timeout, unzip};
#[tokio::test]
async fn af_cloud_edit_document_test() {
@ -43,8 +43,7 @@ async fn af_cloud_edit_document_test() {
#[tokio::test]
async fn af_cloud_sync_anon_user_document_test() {
let (cleaner, user_db_path) =
unzip_history_user_db("./tests/asset", "040_sync_local_document").unwrap();
let (cleaner, user_db_path) = unzip("./tests/asset", "040_sync_local_document").unwrap();
user_localhost_af_cloud().await;
let test =
EventIntegrationTest::new_with_user_data_path(user_db_path.clone(), DEFAULT_NAME.to_string())

View File

@ -1,4 +1,4 @@
use crate::util::unzip_history_user_db;
use crate::util::unzip;
use event_integration::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use flowy_folder::entities::{ImportPB, ImportTypePB, ViewLayoutPB};
@ -7,11 +7,11 @@ use flowy_folder::entities::{ImportPB, ImportTypePB, ViewLayoutPB};
async fn import_492_row_csv_file_test() {
// csv_500r_15c.csv is a file with 492 rows and 17 columns
let file_name = "csv_492r_17c.csv".to_string();
let (cleaner, csv_file_path) = unzip_history_user_db("./tests/asset", &file_name).unwrap();
let (cleaner, csv_file_path) = unzip("./tests/asset", &file_name).unwrap();
let csv_string = std::fs::read_to_string(csv_file_path).unwrap();
let test = EventIntegrationTest::new_with_name(DEFAULT_NAME).await;
test.sign_up_as_guest().await;
test.sign_up_as_anon().await;
let workspace_id = test.get_current_workspace().await.id;
let import_data = gen_import_data(file_name, csv_string, workspace_id);
@ -26,11 +26,11 @@ async fn import_492_row_csv_file_test() {
async fn import_10240_row_csv_file_test() {
// csv_22577r_15c.csv is a file with 10240 rows and 15 columns
let file_name = "csv_10240r_15c.csv".to_string();
let (cleaner, csv_file_path) = unzip_history_user_db("./tests/asset", &file_name).unwrap();
let (cleaner, csv_file_path) = unzip("./tests/asset", &file_name).unwrap();
let csv_string = std::fs::read_to_string(csv_file_path).unwrap();
let test = EventIntegrationTest::new_with_name(DEFAULT_NAME).await;
test.sign_up_as_guest().await;
test.sign_up_as_anon().await;
let workspace_id = test.get_current_workspace().await.id;
let import_data = gen_import_data(file_name, csv_string, workspace_id);

View File

@ -16,8 +16,6 @@ pub enum FolderScript {
AssertWorkspace(WorkspacePB),
#[allow(dead_code)]
ReadWorkspace(String),
// App
CreateParentView {
name: String,
desc: String,
@ -81,16 +79,16 @@ impl FolderTest {
let parent_view = create_view(
&sdk,
&workspace.id,
"Folder App",
"Folder test app",
"first level view",
"",
ViewLayout::Document,
)
.await;
let view = create_view(
&sdk,
&parent_view.id,
"Folder View",
"Folder test view",
"second level view",
"",
ViewLayout::Document,
)
.await;

View File

@ -16,7 +16,7 @@ use crate::util::receive_with_timeout;
/// 5. Await the notification for workspace view updates with a timeout of 30 seconds.
/// 6. Ensure that the received views contain the newly created "test_view".
async fn create_child_view_in_workspace_subscription_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let workspace = test.get_current_workspace().await;
let rx = test
.notification_sender
@ -40,7 +40,7 @@ async fn create_child_view_in_workspace_subscription_test() {
#[tokio::test]
async fn create_child_view_in_view_subscription_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let mut workspace = test.get_current_workspace().await;
let workspace_child_view = workspace.views.pop().unwrap();
let rx = test.notification_sender.subscribe::<ChildViewUpdatePB>(
@ -72,7 +72,7 @@ async fn create_child_view_in_view_subscription_test() {
#[tokio::test]
async fn delete_view_subscription_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let workspace = test.get_current_workspace().await;
let rx = test
.notification_sender
@ -103,7 +103,7 @@ async fn delete_view_subscription_test() {
#[tokio::test]
async fn update_view_subscription_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let mut workspace = test.get_current_workspace().await;
let rx = test
.notification_sender

View File

@ -6,7 +6,7 @@ use flowy_user::errors::ErrorCode;
#[tokio::test]
async fn create_workspace_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let request = CreateWorkspacePayloadPB {
name: "my second workspace".to_owned(),
desc: "".to_owned(),
@ -53,7 +53,7 @@ async fn create_workspace_event_test() {
#[tokio::test]
async fn create_view_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let view = test
.create_view(&current_workspace.id, "My first view".to_string())
@ -65,7 +65,7 @@ async fn create_view_event_test() {
#[tokio::test]
async fn update_view_event_with_name_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let view = test
.create_view(&current_workspace.id, "My first view".to_string())
@ -86,7 +86,7 @@ async fn update_view_event_with_name_test() {
#[tokio::test]
async fn update_view_icon_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let view = test
.create_view(&current_workspace.id, "My first view".to_string())
@ -110,7 +110,7 @@ async fn update_view_icon_event_test() {
#[tokio::test]
async fn delete_view_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let view = test
.create_view(&current_workspace.id, "My first view".to_string())
@ -133,7 +133,7 @@ async fn delete_view_event_test() {
#[tokio::test]
async fn put_back_trash_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let view = test
.create_view(&current_workspace.id, "My first view".to_string())
@ -176,7 +176,7 @@ async fn put_back_trash_event_test() {
#[tokio::test]
async fn delete_view_permanently_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let view = test
.create_view(&current_workspace.id, "My first view".to_string())
@ -225,7 +225,7 @@ async fn delete_view_permanently_event_test() {
#[tokio::test]
async fn delete_all_trash_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
for i in 0..3 {
@ -269,7 +269,7 @@ async fn delete_all_trash_test() {
#[tokio::test]
async fn multiple_hierarchy_view_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
for i in 1..4 {
let parent = test
@ -345,7 +345,7 @@ async fn multiple_hierarchy_view_test() {
#[tokio::test]
async fn move_view_event_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
for i in 1..4 {
let parent = test
@ -383,7 +383,7 @@ async fn move_view_event_test() {
#[tokio::test]
async fn move_view_event_after_delete_view_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
for i in 1..6 {
let _ = test
@ -425,7 +425,7 @@ async fn move_view_event_after_delete_view_test() {
#[tokio::test]
async fn move_view_event_after_delete_view_test2() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let parent = test
.create_view(&current_workspace.id, "My view".to_string())
@ -495,7 +495,7 @@ fn invalid_workspace_name_test_case() -> Vec<(String, ErrorCode)> {
#[tokio::test]
async fn move_view_across_parent_test() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let current_workspace = test.get_current_workspace().await;
let parent_1 = test
.create_view(&current_workspace.id, "My view 1".to_string())

View File

@ -3,3 +3,5 @@ mod document;
mod folder;
mod user;
pub mod util;
mod search;

View File

@ -0,0 +1,215 @@
use crate::util::{unzip_test_asset, zip};
use collab_folder::View;
use event_integration::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use flowy_folder::entities::UpdateViewPayloadPB;
use flowy_folder_pub::folder_builder::{FlattedViews, NestedViewBuilder};
use std::time::Duration;
use tokio::time::sleep;
#[tokio::test]
async fn test_folder_index_all_startup() {
let folder_name = "folder_1000_view";
// comment out the following line to create a test asset if you modify the test data
// don't forget to delete unnecessary test assets
// create_folder_test_data(folder_name).await;
let (cleaner, user_db_path) = unzip_test_asset(folder_name).unwrap();
let test =
EventIntegrationTest::new_with_user_data_path(user_db_path.clone(), DEFAULT_NAME.to_string())
.await;
let first_level_views = test.get_all_workspace_views().await;
assert_eq!(first_level_views.len(), 3);
assert_eq!(first_level_views[1].name, "1");
assert_eq!(first_level_views[2].name, "2");
let view_1 = test.get_view(&first_level_views[1].id).await;
assert_eq!(view_1.child_views.len(), 500);
let folder_data = test.get_folder_data();
// Get started + 1002 Views
assert_eq!(folder_data.views.len(), 1003);
// Wait for the index to be created/updated
sleep(Duration::from_secs(1)).await;
let folder_search_manager = test.get_folder_search_handler();
let num_docs = folder_search_manager.index_count();
assert_eq!(num_docs, 1004);
drop(cleaner);
}
#[tokio::test]
async fn test_folder_index_create_20_views() {
let test = EventIntegrationTest::new_anon().await;
let folder_search_manager = test.get_folder_search_handler();
// Wait for the index to be created/updated
sleep(Duration::from_secs(1)).await;
let workspace_id = test.get_current_workspace().await.id;
for i in 0..20 {
let view = test.create_view(&workspace_id, format!("View {}", i)).await;
sleep(Duration::from_millis(500)).await;
assert_eq!(view.name, format!("View {}", i));
}
// Wait for the index update to finish
sleep(Duration::from_secs(2)).await;
let num_docs = folder_search_manager.index_count();
// Workspace + Get started + 20 Views
assert_eq!(num_docs, 22);
}
#[tokio::test]
async fn test_folder_index_create_view() {
let test = EventIntegrationTest::new_anon().await;
let folder_search_manager = test.get_folder_search_handler();
// Wait for the index to be created/updated
sleep(Duration::from_secs(1)).await;
let workspace_id = test.get_current_workspace().await.id;
let view = test.create_view(&workspace_id, "Flowers".to_owned()).await;
// Wait for the index to be updated
sleep(Duration::from_millis(500)).await;
let results = folder_search_manager.perform_search(view.name.clone());
if let Err(e) = results {
panic!("Error performing search: {:?}", e);
}
let results = results.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].data, view.name);
}
#[tokio::test]
async fn test_folder_index_rename_view() {
let test = EventIntegrationTest::new_anon().await;
let folder_search_manager = test.get_folder_search_handler();
// Wait for the index to be created/updated
sleep(Duration::from_secs(1)).await;
let workspace_id = test.get_current_workspace().await.id;
let view = test.create_view(&workspace_id, "Flowers".to_owned()).await;
// Wait for the index to be updated
sleep(Duration::from_millis(500)).await;
let new_view_name = "Bouquets".to_string();
let update_payload = UpdateViewPayloadPB {
view_id: view.id,
name: Some(new_view_name.clone()),
..Default::default()
};
test.update_view(update_payload).await;
// Wait for the index to be updated
sleep(Duration::from_millis(500)).await;
let first = folder_search_manager.perform_search(view.name);
if let Err(e) = first {
panic!("Error performing search: {:?}", e);
}
let second = folder_search_manager.perform_search(new_view_name.clone());
if let Err(e) = second {
panic!("Error performing search: {:?}", e);
}
let first = first.unwrap();
assert_eq!(first.len(), 0);
let second = second.unwrap();
assert_eq!(second.len(), 1);
assert_eq!(second[0].data, new_view_name);
}
/// Using this method to create a folder test asset. Only use when you want to create a new asset.
/// The file will be created at tests/asset/{file_name}.zip and it will be committed to the repo.
///
#[allow(dead_code)]
async fn create_folder_test_data(file_name: &str) {
let test = EventIntegrationTest::new_with_name(DEFAULT_NAME).await;
test.sign_up_as_anon().await;
let uid = test.get_user_profile().await.unwrap().id;
let workspace_id = test.get_current_workspace().await.id;
let views = create_1002_views(uid, workspace_id.clone()).await;
test.create_views(views).await;
let first_level_views = test.get_all_workspace_views().await;
assert_eq!(first_level_views.len(), 3);
assert_eq!(first_level_views[1].name, "1");
assert_eq!(first_level_views[2].name, "2");
let view_1 = test.get_view(&first_level_views[1].id).await;
assert_eq!(view_1.child_views.len(), 500);
let folder_data = test.get_folder_data();
// Get started + 1002 Views
assert_eq!(folder_data.views.len(), 1003);
let data_path = test.config.application_path.clone();
zip(
data_path.into(),
format!("tests/asset/{}.zip", file_name).into(),
)
.unwrap();
sleep(Duration::from_secs(2)).await;
}
/// Create view without create the view's content(document/database).
/// workspace
/// - get_started
/// - view_1
/// - view_1_1
/// - view_1_2
/// - view_2
/// - view_2_1
/// - view_2_2
async fn create_1002_views(uid: i64, workspace_id: String) -> Vec<View> {
let mut builder = NestedViewBuilder::new(workspace_id.clone(), uid);
builder
.with_view_builder(|view_builder| async {
let mut builder = view_builder.with_name("1");
for i in 0..500 {
builder = builder
.with_child_view_builder(|child_view_builder| async {
child_view_builder.with_name(format!("1_{}", i)).build()
})
.await;
}
builder.build()
})
.await;
builder
.with_view_builder(|view_builder| async {
let mut builder = view_builder.with_name("2");
for i in 0..500 {
builder = builder
.with_child_view_builder(|child_view_builder| async {
child_view_builder.with_name(format!("2_{}", i)).build()
})
.await;
}
builder.build()
})
.await;
// The output views should be:
// view_1
// view_1_1
// view_1_x
// view_2
// view_2_1
// view_2_x
let views = builder.build();
FlattedViews::flatten_views(views)
}

View File

@ -0,0 +1 @@
mod folder_search_test;

View File

@ -0,0 +1 @@
mod local_test;

View File

@ -3,11 +3,11 @@ use event_integration::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use flowy_user::entities::AuthenticatorPB;
use crate::util::unzip_history_user_db;
use crate::util::unzip;
#[tokio::test]
async fn reading_039_anon_user_data_test() {
let (cleaner, user_db_path) = unzip_history_user_db("./tests/asset", "039_local").unwrap();
let (cleaner, user_db_path) = unzip("./tests/asset", "039_local").unwrap();
let test =
EventIntegrationTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string()).await;
let first_level_views = test.get_all_workspace_views().await;
@ -42,7 +42,7 @@ async fn reading_039_anon_user_data_test() {
#[tokio::test]
async fn migrate_anon_user_data_to_af_cloud_test() {
let (cleaner, user_db_path) = unzip_history_user_db("./tests/asset", "040_local").unwrap();
let (cleaner, user_db_path) = unzip("./tests/asset", "040_local").unwrap();
// In the 040_local, the structure is:
// workspace:
// view: Document1

View File

@ -1,4 +1,4 @@
use crate::util::unzip_history_user_db;
use crate::util::unzip;
use assert_json_diff::assert_json_include;
use collab_database::rows::database_row_document_id_from_row_id;
use event_integration::user_event::user_localhost_af_cloud;
@ -12,8 +12,7 @@ use std::env::temp_dir;
async fn import_appflowy_data_need_migration_test() {
// In 037, the workspace array will be migrated to view.
let import_container_name = "037_local".to_string();
let (cleaner, user_db_path) =
unzip_history_user_db("./tests/asset", &import_container_name).unwrap();
let (cleaner, user_db_path) = unzip("./tests/asset", &import_container_name).unwrap();
// Getting started
// Document1
// Document2(fav)
@ -52,8 +51,7 @@ async fn import_appflowy_data_need_migration_test() {
#[tokio::test]
async fn import_appflowy_data_folder_into_new_view_test() {
let import_container_name = "040_local".to_string();
let (cleaner, user_db_path) =
unzip_history_user_db("./tests/asset", &import_container_name).unwrap();
let (cleaner, user_db_path) = unzip("./tests/asset", &import_container_name).unwrap();
// In the 040_local, the structure is:
// workspace:
// view: Document1
@ -121,8 +119,7 @@ async fn import_appflowy_data_folder_into_new_view_test() {
#[tokio::test]
async fn import_appflowy_data_folder_into_current_workspace_test() {
let import_container_name = "040_local".to_string();
let (cleaner, user_db_path) =
unzip_history_user_db("./tests/asset", &import_container_name).unwrap();
let (cleaner, user_db_path) = unzip("./tests/asset", &import_container_name).unwrap();
// In the 040_local, the structure is:
// workspace:
// view: Document1
@ -169,8 +166,7 @@ async fn import_appflowy_data_folder_into_current_workspace_test() {
#[tokio::test]
async fn import_appflowy_data_folder_into_new_view_test2() {
let import_container_name = "040_local_2".to_string();
let (cleaner, user_db_path) =
unzip_history_user_db("./tests/asset", &import_container_name).unwrap();
let (cleaner, user_db_path) = unzip("./tests/asset", &import_container_name).unwrap();
user_localhost_af_cloud().await;
let test = EventIntegrationTest::new_with_name(DEFAULT_NAME).await;
let _ = test.af_cloud_sign_up().await;
@ -209,8 +205,7 @@ async fn import_empty_appflowy_data_folder_test() {
#[tokio::test]
async fn import_appflowy_data_folder_multiple_times_test() {
let import_container_name = "040_local_2".to_string();
let (cleaner, user_db_path) =
unzip_history_user_db("./tests/asset", &import_container_name).unwrap();
let (cleaner, user_db_path) = unzip("./tests/asset", &import_container_name).unwrap();
// In the 040_local_2, the structure is:
// Getting Started
// Doc1

View File

@ -1,4 +1,4 @@
use crate::util::unzip_history_user_db;
use crate::util::unzip;
use event_integration::user_event::user_localhost_af_cloud;
use event_integration::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
@ -7,10 +7,9 @@ use std::time::Duration;
#[tokio::test]
async fn import_appflowy_data_folder_into_new_view_test() {
let import_container_name = "040_local".to_string();
let (cleaner, user_db_path) =
unzip_history_user_db("./tests/asset", &import_container_name).unwrap();
let (cleaner, user_db_path) = unzip("./tests/asset", &import_container_name).unwrap();
let (imported_af_folder_cleaner, imported_af_data_path) =
unzip_history_user_db("./tests/asset", &import_container_name).unwrap();
unzip("./tests/asset", &import_container_name).unwrap();
user_localhost_af_cloud().await;
let test =

View File

@ -8,7 +8,7 @@ use flowy_user::event_map::UserEvent::*;
#[tokio::test]
async fn user_update_with_reminder() {
let sdk = EventIntegrationTest::new().await;
let _ = sdk.sign_up_as_guest().await;
let _ = sdk.sign_up_as_anon().await;
let mut meta = HashMap::new();
meta.insert("object_id".to_string(), "".to_string());

View File

@ -1,11 +1,11 @@
use event_integration::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use crate::util::unzip_history_user_db;
use crate::util::unzip;
#[tokio::test]
async fn collab_db_restore_test() {
let (cleaner, user_db_path) = unzip_history_user_db(
let (cleaner, user_db_path) = unzip(
"./tests/user/migration_test/history_user_db",
"038_collab_db_corrupt_restore",
)

View File

@ -2,11 +2,11 @@ use event_integration::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use flowy_folder::entities::ViewLayoutPB;
use crate::util::unzip_history_user_db;
use crate::util::unzip;
#[tokio::test]
async fn migrate_historical_empty_document_test() {
let (cleaner, user_db_path) = unzip_history_user_db(
let (cleaner, user_db_path) = unzip(
"./tests/user/migration_test/history_user_db",
"historical_empty_document",
)

View File

@ -3,11 +3,11 @@ use flowy_core::DEFAULT_NAME;
use flowy_folder::entities::ViewLayoutPB;
use std::time::Duration;
use crate::util::unzip_history_user_db;
use crate::util::unzip;
#[tokio::test]
async fn migrate_020_historical_empty_document_test() {
let (cleaner, user_db_path) = unzip_history_user_db(
let (cleaner, user_db_path) = unzip(
"./tests/user/migration_test/history_user_db",
"020_historical_user_data",
)
@ -43,7 +43,7 @@ async fn migrate_020_historical_empty_document_test() {
#[tokio::test]
async fn migrate_036_fav_v1_workspace_array_test() {
// Used to test migration: FavoriteV1AndWorkspaceArrayMigration
let (cleaner, user_db_path) = unzip_history_user_db(
let (cleaner, user_db_path) = unzip(
"./tests/user/migration_test/history_user_db",
"036_fav_v1_workspace_array",
)
@ -65,7 +65,7 @@ async fn migrate_036_fav_v1_workspace_array_test() {
#[tokio::test]
async fn migrate_038_trash_test() {
// Used to test migration: WorkspaceTrashMapToSectionMigration
let (cleaner, user_db_path) = unzip_history_user_db("./tests/asset", "038_local").unwrap();
let (cleaner, user_db_path) = unzip("./tests/asset", "038_local").unwrap();
// Getting started
// Document1
// Document2(deleted)
@ -102,8 +102,7 @@ async fn migrate_038_trash_test() {
#[tokio::test]
async fn migrate_038_trash_test2() {
// Used to test migration: WorkspaceTrashMapToSectionMigration
let (cleaner, user_db_path) =
unzip_history_user_db("./tests/asset", "038_document_with_grid").unwrap();
let (cleaner, user_db_path) = unzip("./tests/asset", "038_document_with_grid").unwrap();
// Getting started
// document
// grid
@ -131,7 +130,7 @@ async fn migrate_038_trash_test2() {
#[tokio::test]
async fn collab_db_backup_test() {
// Used to test migration: WorkspaceTrashMapToSectionMigration
let (cleaner, user_db_path) = unzip_history_user_db("./tests/asset", "038_local").unwrap();
let (cleaner, user_db_path) = unzip("./tests/asset", "038_local").unwrap();
let test =
EventIntegrationTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string()).await;
@ -149,8 +148,7 @@ async fn collab_db_backup_test() {
#[tokio::test]
async fn delete_outdated_collab_db_backup_test() {
// Used to test migration: WorkspaceTrashMapToSectionMigration
let (cleaner, user_db_path) =
unzip_history_user_db("./tests/asset", "040_collab_backups").unwrap();
let (cleaner, user_db_path) = unzip("./tests/asset", "040_collab_backups").unwrap();
let test =
EventIntegrationTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string()).await;

View File

@ -120,7 +120,7 @@ async fn third_party_sign_up_with_duplicated_email() {
#[tokio::test]
async fn sign_up_as_guest_and_then_update_to_new_cloud_user_test() {
if get_supabase_config().is_some() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let old_views = test
.folder_manager
.get_current_workspace_public_views()
@ -151,7 +151,7 @@ async fn sign_up_as_guest_and_then_update_to_new_cloud_user_test() {
#[tokio::test]
async fn sign_up_as_guest_and_then_update_to_existing_cloud_user_test() {
if get_supabase_config().is_some() {
let test = EventIntegrationTest::new_with_guest_user().await;
let test = EventIntegrationTest::new_anon().await;
let uuid = uuid::Uuid::new_v4().to_string();
let email = format!("{}@appflowy.io", nanoid!(6));
@ -172,7 +172,7 @@ async fn sign_up_as_guest_and_then_update_to_existing_cloud_user_test() {
// sign out and then sign in as a guest
test.sign_out().await;
let _sign_up_context = test.sign_up_as_guest().await;
let _sign_up_context = test.sign_up_as_anon().await;
let new_workspace = test.folder_manager.get_current_workspace().await.unwrap();
test
.create_view(&new_workspace.id, "new workspace child view".to_string())
@ -253,7 +253,7 @@ async fn update_user_profile_with_existing_email_test() {
async fn migrate_anon_document_on_cloud_signup() {
if get_supabase_config().is_some() {
let test = EventIntegrationTest::new().await;
let user_profile = test.sign_up_as_guest().await.user_profile;
let user_profile = test.sign_up_as_anon().await.user_profile;
let view = test
.create_view(&user_profile.workspace_id, "My first view".to_string())
@ -292,7 +292,7 @@ async fn migrate_anon_document_on_cloud_signup() {
#[tokio::test]
async fn migrate_anon_data_on_cloud_signup() {
if get_supabase_config().is_some() {
let (cleaner, user_db_path) = unzip_history_user_db(
let (cleaner, user_db_path) = unzip(
"./tests/user/supabase_test/history_user_db",
"workspace_sync",
)

View File

@ -1,9 +1,10 @@
use std::fs::{create_dir_all, File};
use std::fs::{create_dir_all, File, OpenOptions};
use std::io::copy;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
use std::{fs, io};
use anyhow::Error;
use collab_folder::FolderData;
@ -13,7 +14,9 @@ use tokio::sync::mpsc::Receiver;
use tokio::time::timeout;
use uuid::Uuid;
use zip::ZipArchive;
use walkdir::WalkDir;
use zip::write::FileOptions;
use zip::{CompressionMethod, ZipArchive, ZipWriter};
use event_integration::event_builder::EventBuilder;
use event_integration::Cleaner;
@ -163,7 +166,78 @@ pub fn appflowy_server(
(SupabaseServerServiceImpl::new(server), encryption_impl)
}
pub fn unzip_history_user_db(root: &str, folder_name: &str) -> std::io::Result<(Cleaner, PathBuf)> {
/// zip the asset to the destination
/// Zips the specified directory into a zip file.
///
/// # Arguments
/// - `src_dir`: Path to the directory to zip.
/// - `output_file`: Path to the output zip file.
///
/// # Errors
/// Returns `io::Result<()>` indicating the operation's success or failure.
pub fn zip(src_dir: PathBuf, output_file_path: PathBuf) -> io::Result<()> {
// Ensure the output directory exists
if let Some(parent) = output_file_path.parent() {
if !parent.exists() {
fs::create_dir_all(parent)?;
}
}
// Open or create the output file, truncating it if it exists
let file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(&output_file_path)?;
let options = FileOptions::default().compression_method(CompressionMethod::Deflated);
let mut zip = ZipWriter::new(file);
// Calculate the name of the new folder within the ZIP file based on the last component of the output path
let new_folder_name = output_file_path
.file_stem()
.and_then(|name| name.to_str())
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid output file name"))?;
let src_dir_str = src_dir.to_str().expect("Invalid source directory path");
for entry in WalkDir::new(&src_dir).into_iter().filter_map(|e| e.ok()) {
let path = entry.path();
let relative_path = path
.strip_prefix(src_dir_str)
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Error calculating relative path"))?;
// Construct the path within the ZIP, prefixing with the new folder's name
let zip_path = Path::new(new_folder_name).join(relative_path);
if path.is_file() {
zip.start_file(
zip_path
.to_str()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid file name"))?,
options,
)?;
let mut f = File::open(path)?;
io::copy(&mut f, &mut zip)?;
} else if entry.file_type().is_dir() && !relative_path.as_os_str().is_empty() {
zip.add_directory(
zip_path
.to_str()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid directory name"))?,
options,
)?;
}
}
zip.finish()?;
Ok(())
}
pub fn unzip_test_asset(folder_name: &str) -> io::Result<(Cleaner, PathBuf)> {
unzip("./tests/asset", folder_name)
}
pub fn unzip(root: &str, folder_name: &str) -> io::Result<(Cleaner, PathBuf)> {
// Open the zip file
let zip_file_path = format!("{}/{}.zip", root, folder_name);
let reader = File::open(zip_file_path)?;

View File

@ -23,6 +23,7 @@ flowy-server-pub = { workspace = true }
flowy-config = { workspace = true }
flowy-date = { workspace = true }
collab-integrate = { workspace = true }
flowy-search = { workspace = true }
collab-entity = { version = "0.1.0" }
collab-plugins = { version = "0.1.0" }
collab = { version = "0.1.0" }
@ -35,7 +36,7 @@ tracing.workspace = true
futures-core = { version = "0.3", default-features = false }
bytes.workspace = true
tokio = { workspace = true, features = ["full"] }
tokio-stream = { workspace = true, features = ["sync"]}
tokio-stream = { workspace = true, features = ["sync"] }
console-subscriber = { version = "0.2", optional = true }
parking_lot.workspace = true
anyhow.workspace = true
@ -57,12 +58,16 @@ http_sync = []
native_sync = []
use_bunyan = ["lib-log/use_bunyan"]
dart = [
"flowy-user/dart",
"flowy-date/dart",
"flowy-search/dart",
"flowy-folder/dart",
"flowy-database2/dart",
]
ts = [
"flowy-user/tauri_ts",
"flowy-folder/tauri_ts",
"flowy-search/tauri_ts",
"flowy-database2/ts",
"flowy-config/tauri_ts",
]
@ -71,6 +76,6 @@ openssl_vendored = ["flowy-sqlite/openssl_vendored"]
# Enable/Disable AppFlowy Verbose Log Configuration
verbose_log = [
# "flowy-document/verbose_log",
# "flowy-document/verbose_log",
"client-api/sync_verbose_log"
]
]

View File

@ -16,7 +16,7 @@ use crate::integrate::log::create_log_filter;
pub struct AppFlowyCoreConfig {
/// Different `AppFlowyCoreConfig` instance should have different name
pub(crate) app_version: String,
pub(crate) name: String,
pub name: String,
pub(crate) device_id: String,
pub platform: String,
/// Used to store the user data

View File

@ -1,10 +1,4 @@
use std::collections::HashMap;
use std::convert::TryFrom;
use std::sync::{Arc, Weak};
use bytes::Bytes;
use tokio::sync::RwLock;
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
use collab_integrate::CollabKVDB;
use flowy_database2::entities::DatabaseLayoutPB;
@ -21,10 +15,15 @@ use flowy_folder::share::ImportType;
use flowy_folder::view_operation::{FolderOperationHandler, FolderOperationHandlers, View};
use flowy_folder::ViewLayout;
use flowy_folder_pub::folder_builder::NestedViewBuilder;
use flowy_search::folder::indexer::FolderIndexManagerImpl;
use flowy_user::services::authenticate_user::AuthenticateUser;
use lib_dispatch::prelude::ToBytes;
use lib_infra::async_trait::async_trait;
use lib_infra::future::FutureResult;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::sync::{Arc, Weak};
use tokio::sync::RwLock;
use crate::integrate::server::ServerProvider;
@ -36,6 +35,7 @@ impl FolderDepsResolver {
database_manager: &Arc<DatabaseManager>,
collab_builder: Arc<AppFlowyCollabBuilder>,
server_provider: Arc<ServerProvider>,
folder_indexer: Arc<FolderIndexManagerImpl>,
) -> Arc<FolderManager> {
let user: Arc<dyn FolderUser> = Arc::new(FolderUserImpl {
authenticate_user: authenticate_user.clone(),
@ -48,6 +48,7 @@ impl FolderDepsResolver {
collab_builder,
handlers,
server_provider.clone(),
folder_indexer,
)
.await
.unwrap(),

View File

@ -2,12 +2,13 @@ pub use collab_deps::*;
pub use database_deps::*;
pub use document_deps::*;
pub use folder_deps::*;
pub use search_deps::*;
pub use user_deps::*;
mod collab_deps;
mod document_deps;
mod folder_deps;
mod util;
mod database_deps;
mod search_deps;
mod user_deps;

View File

@ -0,0 +1,12 @@
use flowy_search::folder::handler::FolderSearchHandler;
use flowy_search::folder::indexer::FolderIndexManagerImpl;
use flowy_search::services::manager::SearchManager;
use std::sync::Arc;
pub struct SearchDepsResolver();
impl SearchDepsResolver {
pub async fn resolve(folder_indexer: Arc<FolderIndexManagerImpl>) -> Arc<SearchManager> {
let folder_handler = Arc::new(FolderSearchHandler::new(folder_indexer));
Arc::new(SearchManager::new(vec![folder_handler]))
}
}

View File

@ -25,11 +25,7 @@ pub(crate) fn init_log(
}
}
pub(crate) fn create_log_filter(
level: String,
with_crates: Vec<String>,
platform: Platform,
) -> String {
pub fn create_log_filter(level: String, with_crates: Vec<String>, platform: Platform) -> String {
let mut level = std::env::var("RUST_LOG").unwrap_or(level);
#[cfg(debug_assertions)]
@ -55,18 +51,15 @@ pub(crate) fn create_log_filter(
filters.push(format!("flowy_server={}", level));
filters.push(format!("flowy_notification={}", "info"));
filters.push(format!("lib_infra={}", level));
filters.push(format!("dart_ffi={}", level));
filters.push(format!("flowy_search={}", level));
// ⚠Enable debug log for dart_ffi, flowy_sqlite and lib_dispatch as needed. Don't enable them by default.
{
// filters.push(format!("flowy_sqlite={}", "info"));
// filters.push(format!("lib_dispatch={}", level));
}
// Most of the time, we don't need to see the logs from the following crates
// filters.push(format!("flowy_sqlite={}", "info"));
// filters.push(format!("lib_dispatch={}", level));
filters.push(format!("client_api={}", level));
#[cfg(feature = "profiling")]
filters.push(format!("tokio={}", level));
#[cfg(feature = "profiling")]
filters.push(format!("runtime={}", level));

View File

@ -1,5 +1,5 @@
pub(crate) mod collab_interact;
pub(crate) mod log;
pub mod log;
pub(crate) mod server;
mod trait_impls;
pub(crate) mod user;

View File

@ -1,5 +1,7 @@
#![allow(unused_doc_comments)]
use flowy_search::folder::indexer::FolderIndexManagerImpl;
use flowy_search::services::manager::SearchManager;
use flowy_storage::ObjectStorageService;
use semver::Version;
use std::sync::Arc;
@ -12,6 +14,7 @@ use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabPluginProvid
use flowy_database2::DatabaseManager;
use flowy_document::manager::DocumentManager;
use flowy_folder::manager::FolderManager;
use flowy_sqlite::kv::StorePreferences;
use flowy_user::services::authenticate_user::AuthenticateUser;
use flowy_user::services::entities::UserConfig;
@ -33,7 +36,7 @@ use crate::integrate::user::UserStatusCallbackImpl;
pub mod config;
mod deps_resolve;
mod integrate;
pub mod integrate;
pub mod module;
/// This name will be used as to identify the current [AppFlowyCore] instance.
@ -52,6 +55,7 @@ pub struct AppFlowyCore {
pub server_provider: Arc<ServerProvider>,
pub task_dispatcher: Arc<RwLock<TaskDispatcher>>,
pub store_preference: Arc<StorePreferences>,
pub search_manager: Arc<SearchManager>,
}
impl AppFlowyCore {
@ -117,6 +121,7 @@ impl AppFlowyCore {
database_manager,
document_manager,
collab_builder,
search_manager,
) = async {
/// The shared collab builder is used to build the [Collab] instance. The plugins will be loaded
/// on demand based on the [CollabPluginConfig].
@ -157,17 +162,21 @@ impl AppFlowyCore {
Arc::downgrade(&(server_provider.clone() as Arc<dyn ObjectStorageService>)),
);
let folder_indexer = Arc::new(FolderIndexManagerImpl::new(Arc::downgrade(
&authenticate_user,
)));
let folder_manager = FolderDepsResolver::resolve(
Arc::downgrade(&authenticate_user),
&document_manager,
&database_manager,
collab_builder.clone(),
server_provider.clone(),
folder_indexer.clone(),
)
.await;
let user_manager = UserDepsResolver::resolve(
authenticate_user,
authenticate_user.clone(),
collab_builder.clone(),
server_provider.clone(),
store_preference.clone(),
@ -176,6 +185,8 @@ impl AppFlowyCore {
)
.await;
let search_manager = SearchDepsResolver::resolve(folder_indexer).await;
(
user_manager,
folder_manager,
@ -183,6 +194,7 @@ impl AppFlowyCore {
database_manager,
document_manager,
collab_builder,
search_manager,
)
}
.await;
@ -217,6 +229,7 @@ impl AppFlowyCore {
Arc::downgrade(&database_manager),
Arc::downgrade(&user_manager),
Arc::downgrade(&document_manager),
Arc::downgrade(&search_manager),
),
));
@ -230,6 +243,7 @@ impl AppFlowyCore {
server_provider,
task_dispatcher,
store_preference,
search_manager,
}
}

View File

@ -3,6 +3,7 @@ use std::sync::Weak;
use flowy_database2::DatabaseManager;
use flowy_document::manager::DocumentManager as DocumentManager2;
use flowy_folder::manager::FolderManager;
use flowy_search::services::manager::SearchManager;
use flowy_user::user_manager::UserManager;
use lib_dispatch::prelude::AFPlugin;
@ -11,6 +12,7 @@ pub fn make_plugins(
database_manager: Weak<DatabaseManager>,
user_session: Weak<UserManager>,
document_manager2: Weak<DocumentManager2>,
search_manager: Weak<SearchManager>,
) -> Vec<AFPlugin> {
let store_preferences = user_session
.upgrade()
@ -22,6 +24,7 @@ pub fn make_plugins(
let document_plugin2 = flowy_document::event_map::init(document_manager2);
let config_plugin = flowy_config::event_map::init(store_preferences);
let date_plugin = flowy_date::event_map::init();
let search_plugin = flowy_search::event_map::init(search_manager);
vec![
user_plugin,
folder_plugin,
@ -29,5 +32,6 @@ pub fn make_plugins(
document_plugin2,
config_plugin,
date_plugin,
search_plugin,
]
}

View File

@ -162,7 +162,7 @@ where
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) fn delete_group(&mut self, deleted_group_id: &str) -> FlowyResult<()> {
self.group_by_id.remove(deleted_group_id);
self.group_by_id.shift_remove(deleted_group_id);
self.mut_configuration(|configuration| {
configuration
.groups

View File

@ -19,9 +19,9 @@ date_time_parser = { version = "0.2.0" }
chrono.workspace = true
fancy-regex = { version = "0.11.0" }
[build-dependencies]
flowy-codegen.workspace = true
[features]
dart = ["flowy-codegen/dart"]
tauri_ts = ["flowy-codegen/ts"]
[build-dependencies]
flowy-codegen.workspace = true

View File

@ -3,6 +3,7 @@ use crate::entities::{
};
use crate::notification::{send_notification, DocumentNotification};
use collab::core::collab::MutexCollab;
use collab_document::document::DocumentIndexContent;
use collab_document::{blocks::DocumentData, document::Document};
use flowy_error::FlowyResult;
use futures::StreamExt;
@ -141,3 +142,10 @@ impl DerefMut for MutexDocument {
&mut self.0
}
}
impl From<&MutexDocument> for DocumentIndexContent {
fn from(doc: &MutexDocument) -> Self {
let doc = doc.lock();
DocumentIndexContent::from(&*doc)
}
}

View File

@ -11,3 +11,4 @@ pub mod deps;
pub mod notification;
mod parse;
pub mod reminder;
pub use collab_document::document::DocumentIndexContent;

View File

@ -14,7 +14,7 @@ bytes.workspace = true
anyhow.workspace = true
thiserror = "1.0"
validator = "0.16.0"
tokio = { workspace = true, features = ["sync"]}
tokio = { workspace = true, features = ["sync", "rt"] }
fancy-regex = { version = "0.11.0" }
lib-dispatch = { workspace = true, optional = true }
@ -32,16 +32,23 @@ collab-document = { version = "0.1.0", optional = true }
collab-plugins = { version = "0.1.0", optional = true }
collab-folder = { version = "0.1.0", optional = true }
client-api = { version = "0.1.0", optional = true }
tantivy = { version = "0.21.1", optional = true }
[features]
impl_from_dispatch_error = ["lib-dispatch"]
impl_from_serde = []
impl_from_reqwest = ["reqwest"]
impl_from_collab_persistence = ["collab-plugins"]
impl_from_collab_document = ["collab-document", "impl_from_reqwest", "collab-plugins"]
impl_from_collab_document = [
"collab-document",
"impl_from_reqwest",
"collab-plugins",
]
impl_from_collab_folder = ["collab-folder"]
impl_from_collab_database= ["collab-database"]
impl_from_collab_database = ["collab-database"]
impl_from_url = ["url"]
impl_from_tantivy = ["tantivy"]
impl_from_sqlite = ["flowy-sqlite", "r2d2"]
impl_from_appflowy_cloud = ["client-api"]
@ -50,6 +57,4 @@ tauri_ts = ["flowy-codegen/ts"]
web_ts = ["flowy-codegen/ts"]
[build-dependencies]
flowy-codegen = { workspace = true, features = [
"proto_gen",
] }
flowy-codegen = { workspace = true, features = ["proto_gen"] }

View File

@ -265,6 +265,18 @@ pub enum ErrorCode {
#[error("Workspace member limit exceeded")]
WorkspaceMemberLimitExceeded = 92,
#[error("IndexWriter failed to commit")]
IndexWriterFailedCommit = 93,
#[error("Failed to open Index directory")]
FailedToOpenIndexDir = 94,
#[error("Failed to parse query")]
FailedToParseQuery = 95,
#[error("FolderIndexManager or its dependencies are unavailable")]
FolderIndexManagerUnavailable = 96,
}
impl ErrorCode {

View File

@ -113,6 +113,10 @@ impl FlowyError {
static_flowy_error!(server_error, ErrorCode::InternalServerError);
static_flowy_error!(not_support, ErrorCode::NotSupportYet);
static_flowy_error!(local_version_not_support, ErrorCode::LocalVersionNotSupport);
static_flowy_error!(
folder_index_manager_unavailable,
ErrorCode::FolderIndexManagerUnavailable
);
}
impl std::convert::From<ErrorCode> for FlowyError {

View File

@ -24,3 +24,6 @@ mod cloud;
#[cfg(feature = "impl_from_url")]
mod url;
#[cfg(feature = "impl_from_tantivy")]
mod tantivy;

View File

@ -0,0 +1,21 @@
use tantivy::{directory::error::OpenDirectoryError, query::QueryParserError, TantivyError};
use crate::{ErrorCode, FlowyError};
impl std::convert::From<TantivyError> for FlowyError {
fn from(error: TantivyError) -> Self {
FlowyError::new(ErrorCode::IndexWriterFailedCommit, error)
}
}
impl std::convert::From<OpenDirectoryError> for FlowyError {
fn from(error: OpenDirectoryError) -> Self {
FlowyError::new(ErrorCode::FailedToOpenIndexDir, error)
}
}
impl std::convert::From<QueryParserError> for FlowyError {
fn from(error: QueryParserError) -> Self {
FlowyError::new(ErrorCode::FailedToParseQuery, error)
}
}

View File

@ -23,3 +23,19 @@ pub struct ImportViews {
/// Used to update the [DatabaseViewTrackerList] when importing the database.
pub database_view_ids_by_database_id: HashMap<String, Vec<String>>,
}
pub struct SearchData {
/// The type of data that is stored in the search index row.
pub index_type: String,
/// The `View` that the row references.
pub view_id: String,
/// The ID that corresponds to the type that is stored.
/// View: view_id
/// Document: page_id
pub id: String,
/// The data that is stored in the search index row.
pub data: String,
}

View File

@ -1,4 +1,3 @@
pub mod cloud;
pub mod entities;
pub mod folder_builder;
mod folder_service;

View File

@ -8,24 +8,29 @@ edition = "2021"
[dependencies]
collab = { version = "0.1.0" }
collab-folder = { version = "0.1.0" }
collab-document = { version = "0.1.0" }
collab-entity = { version = "0.1.0" }
collab-plugins = { version = "0.1.0" }
collab-integrate = { workspace = true }
flowy-folder-pub = { workspace = true }
flowy-search-pub = { workspace = true }
flowy-derive.workspace = true
flowy-notification = { workspace = true }
flowy-notification = { workspace = true }
parking_lot.workspace = true
unicode-segmentation = "1.10"
tracing.workspace = true
flowy-error = { path = "../flowy-error", features = ["impl_from_dispatch_error", "impl_from_collab_folder"]}
flowy-error = { path = "../flowy-error", features = [
"impl_from_dispatch_error",
"impl_from_collab_folder",
] }
lib-dispatch = { workspace = true }
bytes.workspace = true
lib-infra = { workspace = true }
tokio = { workspace = true, features = ["sync"] }
nanoid = "0.4.0"
lazy_static = "1.4.0"
chrono = { workspace = true, default-features = false, features = ["clock"] }
chrono = { workspace = true, default-features = false, features = ["clock"] }
strum_macros = "0.21"
protobuf.workspace = true
uuid.workspace = true

View File

@ -28,6 +28,7 @@ use collab_integrate::{CollabKVDB, CollabPersistenceConfig};
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use flowy_folder_pub::cloud::{gen_view_id, FolderCloudService};
use flowy_folder_pub::folder_builder::ParentChildViews;
use flowy_search_pub::entities::FolderIndexManager;
use lib_infra::conditional_send_sync_trait;
use parking_lot::{Mutex, RwLock};
use std::fmt::{Display, Formatter};
@ -44,12 +45,16 @@ conditional_send_sync_trait! {
}
pub struct FolderManager {
/// workspace_id represents as the id of the Folder.
pub(crate) workspace_id: RwLock<Option<String>>,
/// MutexFolder is the folder that is used to store the data.
pub(crate) mutex_folder: Arc<MutexFolder>,
pub(crate) collab_builder: Arc<AppFlowyCollabBuilder>,
pub(crate) user: Arc<dyn FolderUser>,
pub(crate) operation_handlers: FolderOperationHandlers,
pub cloud_service: Arc<dyn FolderCloudService>,
pub(crate) folder_indexer: Arc<dyn FolderIndexManager>,
}
impl FolderManager {
@ -58,6 +63,7 @@ impl FolderManager {
collab_builder: Arc<AppFlowyCollabBuilder>,
operation_handlers: FolderOperationHandlers,
cloud_service: Arc<dyn FolderCloudService>,
folder_indexer: Arc<dyn FolderIndexManager>,
) -> FlowyResult<Self> {
let mutex_folder = Arc::new(MutexFolder::default());
let manager = Self {
@ -67,6 +73,7 @@ impl FolderManager {
operation_handlers,
cloud_service,
workspace_id: Default::default(),
folder_indexer,
};
Ok(manager)
@ -134,7 +141,7 @@ impl FolderManager {
if let Some(workspace_id) = workspace_id {
self.get_workspace_public_views(&workspace_id).await
} else {
tracing::warn!("Can't get current workspace views");
tracing::warn!("Can't get the workspace id from the folder. Return empty list.");
Ok(vec![])
}
}
@ -463,6 +470,13 @@ impl FolderManager {
},
);
if let Ok(workspace_id) = self.get_current_workspace_id().await {
let folder = &self.mutex_folder.lock();
if let Some(folder) = folder.as_ref() {
notify_did_update_workspace(&workspace_id, folder);
}
}
Ok(view)
}
@ -1301,6 +1315,8 @@ pub(crate) fn get_workspace_private_view_pbs(_workspace_id: &str, folder: &Folde
.collect()
}
/// The MutexFolder is a wrapper of the [Folder] that is used to share the folder between different
/// threads.
#[derive(Clone, Default)]
pub struct MutexFolder(Arc<Mutex<Option<Folder>>>);
impl Deref for MutexFolder {

View File

@ -1,6 +1,8 @@
use collab_entity::CollabType;
use collab_folder::{Folder, FolderNotify, UserId};
use tokio::task::spawn_blocking;
use tracing::{event, Level};
use collab_integrate::CollabKVDB;
@ -8,7 +10,6 @@ use flowy_error::{FlowyError, FlowyResult};
use collab::core::collab::DocStateSource;
use std::sync::{Arc, Weak};
use tracing::{event, Level};
use crate::manager::{FolderInitDataSource, FolderManager};
use crate::manager_observer::{
@ -129,6 +130,22 @@ impl FolderManager {
};
let folder_state_rx = folder.subscribe_sync_state();
let index_content_rx = folder.subscribe_index_content();
self
.folder_indexer
.set_index_content_receiver(index_content_rx);
// Index all views in the folder if needed
if !self.folder_indexer.is_indexed() {
let views = folder.get_all_views_recursively();
let folder_indexer = self.folder_indexer.clone();
// We spawn a blocking task to index all views in the folder
spawn_blocking(move || {
folder_indexer.index_all_views(views);
});
}
*self.mutex_folder.lock() = Some(folder);
let weak_mutex_folder = Arc::downgrade(&self.mutex_folder);

View File

@ -0,0 +1,12 @@
[package]
name = "flowy-search-pub"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
collab = { version = "0.1.0" }
collab-folder = { version = "0.1.0" }
flowy-error = { workspace = true }

View File

@ -0,0 +1,26 @@
use std::any::Any;
use collab::core::collab::IndexContentReceiver;
use collab_folder::{View, ViewIcon, ViewLayout};
use flowy_error::FlowyError;
pub struct IndexableData {
pub id: String,
pub data: String,
pub icon: Option<ViewIcon>,
pub layout: ViewLayout,
}
pub trait IndexManager: Send + Sync {
fn set_index_content_receiver(&self, rx: IndexContentReceiver);
fn add_index(&self, data: IndexableData) -> Result<(), FlowyError>;
fn update_index(&self, data: IndexableData) -> Result<(), FlowyError>;
fn remove_indices(&self, ids: Vec<String>) -> Result<(), FlowyError>;
fn is_indexed(&self) -> bool;
fn as_any(&self) -> &dyn Any;
}
pub trait FolderIndexManager: IndexManager {
fn index_all_views(&self, views: Vec<View>);
}

View File

@ -0,0 +1 @@
pub mod entities;

Some files were not shown because too many files have changed in this diff Show More