mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: integrate cloud document search (#5523)
This commit is contained in:
parent
4f4be7eac7
commit
bd5f5f8b9e
@ -20,7 +20,6 @@ class CommandPaletteBloc
|
|||||||
CommandPaletteBloc() : super(CommandPaletteState.initial()) {
|
CommandPaletteBloc() : super(CommandPaletteState.initial()) {
|
||||||
_searchListener.start(
|
_searchListener.start(
|
||||||
onResultsChanged: _onResultsChanged,
|
onResultsChanged: _onResultsChanged,
|
||||||
onResultsClosed: _onResultsClosed,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
_initTrash();
|
_initTrash();
|
||||||
@ -35,6 +34,7 @@ class CommandPaletteBloc
|
|||||||
final TrashListener _trashListener = TrashListener();
|
final TrashListener _trashListener = TrashListener();
|
||||||
String? _oldQuery;
|
String? _oldQuery;
|
||||||
String? _workspaceId;
|
String? _workspaceId;
|
||||||
|
int _messagesReceived = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() {
|
Future<void> close() {
|
||||||
@ -75,18 +75,22 @@ class CommandPaletteBloc
|
|||||||
emit(state.copyWith(query: null, isLoading: false, results: []));
|
emit(state.copyWith(query: null, isLoading: false, results: []));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resultsChanged: (results, didClose) {
|
resultsChanged: (results, max) {
|
||||||
if (state.query != _oldQuery) {
|
if (state.query != _oldQuery) {
|
||||||
emit(state.copyWith(results: []));
|
emit(state.copyWith(results: []));
|
||||||
|
_oldQuery = state.query;
|
||||||
|
_messagesReceived = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_messagesReceived++;
|
||||||
|
|
||||||
final searchResults = _filterDuplicates(results.items);
|
final searchResults = _filterDuplicates(results.items);
|
||||||
searchResults.sort((a, b) => b.score.compareTo(a.score));
|
searchResults.sort((a, b) => b.score.compareTo(a.score));
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
results: searchResults,
|
results: searchResults,
|
||||||
isLoading: !didClose,
|
isLoading: _messagesReceived != max,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -94,6 +98,9 @@ class CommandPaletteBloc
|
|||||||
_workspaceId = workspaceId;
|
_workspaceId = workspaceId;
|
||||||
emit(state.copyWith(results: [], query: '', isLoading: false));
|
emit(state.copyWith(results: [], query: '', isLoading: false));
|
||||||
},
|
},
|
||||||
|
clearSearch: () {
|
||||||
|
emit(state.copyWith(results: [], query: '', isLoading: false));
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -125,6 +132,10 @@ class CommandPaletteBloc
|
|||||||
final res = [...results];
|
final res = [...results];
|
||||||
|
|
||||||
for (final item in results) {
|
for (final item in results) {
|
||||||
|
if (item.data.trim().isEmpty) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
final duplicateIndex = currentItems.indexWhere((a) => a.id == item.id);
|
final duplicateIndex = currentItems.indexWhere((a) => a.id == item.id);
|
||||||
if (duplicateIndex == -1) {
|
if (duplicateIndex == -1) {
|
||||||
continue;
|
continue;
|
||||||
@ -145,10 +156,7 @@ class CommandPaletteBloc
|
|||||||
add(CommandPaletteEvent.performSearch(search: value));
|
add(CommandPaletteEvent.performSearch(search: value));
|
||||||
|
|
||||||
void _onResultsChanged(RepeatedSearchResultPB results) =>
|
void _onResultsChanged(RepeatedSearchResultPB results) =>
|
||||||
add(CommandPaletteEvent.resultsChanged(results: results));
|
add(CommandPaletteEvent.resultsChanged(results: results, max: 2));
|
||||||
|
|
||||||
void _onResultsClosed(RepeatedSearchResultPB results) =>
|
|
||||||
add(CommandPaletteEvent.resultsChanged(results: results, didClose: true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -161,7 +169,7 @@ class CommandPaletteEvent with _$CommandPaletteEvent {
|
|||||||
|
|
||||||
const factory CommandPaletteEvent.resultsChanged({
|
const factory CommandPaletteEvent.resultsChanged({
|
||||||
required RepeatedSearchResultPB results,
|
required RepeatedSearchResultPB results,
|
||||||
@Default(false) bool didClose,
|
@Default(1) int max,
|
||||||
}) = _ResultsChanged;
|
}) = _ResultsChanged;
|
||||||
|
|
||||||
const factory CommandPaletteEvent.trashChanged({
|
const factory CommandPaletteEvent.trashChanged({
|
||||||
@ -171,6 +179,8 @@ class CommandPaletteEvent with _$CommandPaletteEvent {
|
|||||||
const factory CommandPaletteEvent.workspaceChanged({
|
const factory CommandPaletteEvent.workspaceChanged({
|
||||||
@Default(null) String? workspaceId,
|
@Default(null) String? workspaceId,
|
||||||
}) = _WorkspaceChanged;
|
}) = _WorkspaceChanged;
|
||||||
|
|
||||||
|
const factory CommandPaletteEvent.clearSearch() = _ClearSearch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
@ -59,13 +59,6 @@ class SearchListener {
|
|||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case SearchNotification.DidCloseResults:
|
|
||||||
result.fold(
|
|
||||||
(payload) => _updateDidCloseNotifier?.value =
|
|
||||||
RepeatedSearchResultPB.fromBuffer(payload),
|
|
||||||
(err) => Log.error(err),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -133,8 +133,7 @@ class CommandPaletteModal extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
SearchField(query: state.query, isLoading: state.isLoading),
|
SearchField(query: state.query, isLoading: state.isLoading),
|
||||||
if ((state.query?.isEmpty ?? true) ||
|
if (state.query?.isEmpty ?? true) ...[
|
||||||
state.isLoading && state.results.isEmpty) ...[
|
|
||||||
const Divider(height: 0),
|
const Divider(height: 0),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: RecentViewsList(
|
child: RecentViewsList(
|
||||||
@ -150,6 +149,9 @@ class CommandPaletteModal extends StatelessWidget {
|
|||||||
results: state.results,
|
results: state.results,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
] else if ((state.query?.isNotEmpty ?? false) &&
|
||||||
|
!state.isLoading) ...[
|
||||||
|
const _NoResultsHint(),
|
||||||
],
|
],
|
||||||
_CommandPaletteFooter(
|
_CommandPaletteFooter(
|
||||||
shouldShow: state.results.isNotEmpty &&
|
shouldShow: state.results.isNotEmpty &&
|
||||||
@ -163,6 +165,27 @@ class CommandPaletteModal extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _NoResultsHint extends StatelessWidget {
|
||||||
|
const _NoResultsHint();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Divider(height: 0),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: FlowyText.regular(
|
||||||
|
LocaleKeys.commandPalette_noResultsHint.tr(),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _CommandPaletteFooter extends StatelessWidget {
|
class _CommandPaletteFooter extends StatelessWidget {
|
||||||
const _CommandPaletteFooter({required this.shouldShow});
|
const _CommandPaletteFooter({required this.shouldShow});
|
||||||
|
|
||||||
@ -177,6 +200,7 @@ class _CommandPaletteFooter extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).cardColor,
|
||||||
border: Border(top: BorderSide(color: Theme.of(context).dividerColor)),
|
border: Border(top: BorderSide(color: Theme.of(context).dividerColor)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -26,12 +26,12 @@ class RecentViewTile extends StatelessWidget {
|
|||||||
title: Row(
|
title: Row(
|
||||||
children: [
|
children: [
|
||||||
icon,
|
icon,
|
||||||
const HSpace(4),
|
const HSpace(6),
|
||||||
FlowyText(view.name),
|
FlowyText(view.name),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
focusColor: Theme.of(context).colorScheme.primary.withOpacity(0.5),
|
focusColor: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||||
hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.5),
|
hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onSelected();
|
onSelected();
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.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/recent/recent_views_bloc.dart';
|
||||||
@ -6,7 +8,6 @@ import 'package:appflowy/workspace/presentation/command_palette/widgets/recent_v
|
|||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class RecentViewsList extends StatelessWidget {
|
class RecentViewsList extends StatelessWidget {
|
||||||
@ -51,7 +52,7 @@ class RecentViewsList extends StatelessWidget {
|
|||||||
: FlowySvg(view.iconData, size: const Size.square(20));
|
: FlowySvg(view.iconData, size: const Size.square(20));
|
||||||
|
|
||||||
return RecentViewTile(
|
return RecentViewTile(
|
||||||
icon: icon,
|
icon: SizedBox(width: 24, child: icon),
|
||||||
view: view,
|
view: view,
|
||||||
onSelected: onSelected,
|
onSelected: onSelected,
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
@ -23,12 +24,24 @@ class SearchField extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SearchFieldState extends State<SearchField> {
|
class _SearchFieldState extends State<SearchField> {
|
||||||
final focusNode = FocusNode();
|
late final FocusNode focusNode;
|
||||||
late final controller = TextEditingController(text: widget.query);
|
late final controller = TextEditingController(text: widget.query);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
focusNode = FocusNode(
|
||||||
|
onKeyEvent: (node, event) {
|
||||||
|
if (node.hasFocus &&
|
||||||
|
event is KeyDownEvent &&
|
||||||
|
event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||||
|
node.nextFocus();
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
},
|
||||||
|
);
|
||||||
focusNode.requestFocus();
|
focusNode.requestFocus();
|
||||||
controller.selection = TextSelection(
|
controller.selection = TextSelection(
|
||||||
baseOffset: 0,
|
baseOffset: 0,
|
||||||
@ -75,13 +88,53 @@ class _SearchFieldState extends State<SearchField> {
|
|||||||
.textTheme
|
.textTheme
|
||||||
.bodySmall!
|
.bodySmall!
|
||||||
.copyWith(color: Theme.of(context).colorScheme.error),
|
.copyWith(color: Theme.of(context).colorScheme.error),
|
||||||
// TODO(Mathias): Remove beta when support document/database search
|
suffix: Row(
|
||||||
suffix: FlowyTooltip(
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
AnimatedOpacity(
|
||||||
|
opacity: controller.text.trim().isNotEmpty ? 1 : 0,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final icon = Container(
|
||||||
|
padding: const EdgeInsets.all(1),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: AFThemeExtension.of(context).lightGreyHover,
|
||||||
|
),
|
||||||
|
child: const FlowySvg(
|
||||||
|
FlowySvgs.close_s,
|
||||||
|
size: Size.square(16),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (controller.text.isEmpty) {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FlowyTooltip(
|
||||||
|
message:
|
||||||
|
LocaleKeys.commandPalette_clearSearchTooltip.tr(),
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: controller.text.trim().isNotEmpty
|
||||||
|
? _clearSearch
|
||||||
|
: null,
|
||||||
|
child: icon,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const HSpace(8),
|
||||||
|
// TODO(Mathias): Remove beta when support database search
|
||||||
|
FlowyTooltip(
|
||||||
message: LocaleKeys.commandPalette_betaTooltip.tr(),
|
message: LocaleKeys.commandPalette_betaTooltip.tr(),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 5,
|
horizontal: 5,
|
||||||
vertical: 1,
|
vertical: 2,
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AFThemeExtension.of(context).lightGreyHover,
|
color: AFThemeExtension.of(context).lightGreyHover,
|
||||||
@ -89,20 +142,23 @@ class _SearchFieldState extends State<SearchField> {
|
|||||||
),
|
),
|
||||||
child: FlowyText.semibold(
|
child: FlowyText.semibold(
|
||||||
LocaleKeys.commandPalette_betaLabel.tr(),
|
LocaleKeys.commandPalette_betaLabel.tr(),
|
||||||
fontSize: 10,
|
fontSize: 11,
|
||||||
|
lineHeight: 1.2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
counterText: "",
|
counterText: "",
|
||||||
focusedBorder: const OutlineInputBorder(
|
focusedBorder: const OutlineInputBorder(
|
||||||
borderSide: BorderSide(color: Colors.transparent),
|
|
||||||
borderRadius: Corners.s8Border,
|
borderRadius: Corners.s8Border,
|
||||||
|
borderSide: BorderSide(color: Colors.transparent),
|
||||||
),
|
),
|
||||||
errorBorder: OutlineInputBorder(
|
errorBorder: OutlineInputBorder(
|
||||||
|
borderRadius: Corners.s8Border,
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.error,
|
color: Theme.of(context).colorScheme.error,
|
||||||
),
|
),
|
||||||
borderRadius: Corners.s8Border,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onChanged: (value) => context
|
onChanged: (value) => context
|
||||||
@ -111,7 +167,6 @@ class _SearchFieldState extends State<SearchField> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.isLoading) ...[
|
if (widget.isLoading) ...[
|
||||||
const HSpace(12),
|
|
||||||
FlowyTooltip(
|
FlowyTooltip(
|
||||||
message: LocaleKeys.commandPalette_loadingTooltip.tr(),
|
message: LocaleKeys.commandPalette_loadingTooltip.tr(),
|
||||||
child: const SizedBox(
|
child: const SizedBox(
|
||||||
@ -125,4 +180,11 @@ class _SearchFieldState extends State<SearchField> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _clearSearch() {
|
||||||
|
controller.clear();
|
||||||
|
context
|
||||||
|
.read<CommandPaletteBloc>()
|
||||||
|
.add(const CommandPaletteEvent.clearSearch());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
@ -8,10 +9,11 @@ import 'package:appflowy/workspace/application/command_palette/search_result_ext
|
|||||||
import 'package:appflowy_backend/protobuf/flowy-search/result.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-search/result.pb.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
|
||||||
class SearchResultTile extends StatelessWidget {
|
class SearchResultTile extends StatefulWidget {
|
||||||
const SearchResultTile({
|
const SearchResultTile({
|
||||||
super.key,
|
super.key,
|
||||||
required this.result,
|
required this.result,
|
||||||
@ -24,40 +26,123 @@ class SearchResultTile extends StatelessWidget {
|
|||||||
final bool isTrashed;
|
final bool isTrashed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
State<SearchResultTile> createState() => _SearchResultTileState();
|
||||||
final icon = result.getIcon();
|
}
|
||||||
|
|
||||||
return ListTile(
|
class _SearchResultTileState extends State<SearchResultTile> {
|
||||||
dense: true,
|
bool _hasFocus = false;
|
||||||
title: Row(
|
|
||||||
children: [
|
final focusNode = FocusNode();
|
||||||
if (icon != null) ...[icon, const HSpace(6)],
|
|
||||||
Column(
|
@override
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
void dispose() {
|
||||||
children: [
|
focusNode.dispose();
|
||||||
if (isTrashed) ...[
|
super.dispose();
|
||||||
FlowyText(
|
}
|
||||||
LocaleKeys.commandPalette_fromTrashHint.tr(),
|
|
||||||
color: AFThemeExtension.of(context).textColor.withAlpha(175),
|
@override
|
||||||
fontSize: 10,
|
Widget build(BuildContext context) {
|
||||||
),
|
final icon = widget.result.getIcon();
|
||||||
],
|
final cleanedPreview = _cleanPreview(widget.result.preview);
|
||||||
FlowyText(result.data),
|
|
||||||
],
|
return GestureDetector(
|
||||||
),
|
behavior: HitTestBehavior.opaque,
|
||||||
],
|
|
||||||
),
|
|
||||||
focusColor: Theme.of(context).colorScheme.primary.withOpacity(0.5),
|
|
||||||
hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.5),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onSelected();
|
widget.onSelected();
|
||||||
|
|
||||||
getIt<ActionNavigationBloc>().add(
|
getIt<ActionNavigationBloc>().add(
|
||||||
ActionNavigationEvent.performAction(
|
ActionNavigationEvent.performAction(
|
||||||
action: NavigationAction(objectId: result.viewId),
|
action: NavigationAction(objectId: widget.result.viewId),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
child: Focus(
|
||||||
|
onKeyEvent: (node, event) {
|
||||||
|
if (event is! KeyDownEvent) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.logicalKey == LogicalKeyboardKey.enter) {
|
||||||
|
widget.onSelected();
|
||||||
|
|
||||||
|
getIt<ActionNavigationBloc>().add(
|
||||||
|
ActionNavigationEvent.performAction(
|
||||||
|
action: NavigationAction(objectId: widget.result.viewId),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return KeyEventResult.handled;
|
||||||
|
}
|
||||||
|
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
},
|
||||||
|
onFocusChange: (hasFocus) => setState(() => _hasFocus = hasFocus),
|
||||||
|
child: FlowyHover(
|
||||||
|
isSelected: () => _hasFocus,
|
||||||
|
style: HoverStyle(
|
||||||
|
hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
||||||
|
foregroundColorOnHover: AFThemeExtension.of(context).textColor,
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (icon != null) ...[
|
||||||
|
SizedBox(width: 24, child: icon),
|
||||||
|
const HSpace(6),
|
||||||
|
],
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (widget.isTrashed) ...[
|
||||||
|
FlowyText(
|
||||||
|
LocaleKeys.commandPalette_fromTrashHint.tr(),
|
||||||
|
color: AFThemeExtension.of(context)
|
||||||
|
.textColor
|
||||||
|
.withAlpha(175),
|
||||||
|
fontSize: 10,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
FlowyText(widget.result.data),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (cleanedPreview.isNotEmpty) ...[
|
||||||
|
const VSpace(4),
|
||||||
|
_DocumentPreview(preview: cleanedPreview),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _cleanPreview(String preview) {
|
||||||
|
return preview.replaceAll('\n', ' ').trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DocumentPreview extends StatelessWidget {
|
||||||
|
const _DocumentPreview({required this.preview});
|
||||||
|
|
||||||
|
final String preview;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16) +
|
||||||
|
const EdgeInsets.only(left: 14),
|
||||||
|
child: FlowyText.regular(
|
||||||
|
preview,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
fontSize: 12,
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
12
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -1330,7 +1330,7 @@ dependencies = [
|
|||||||
"cssparser-macros",
|
"cssparser-macros",
|
||||||
"dtoa-short",
|
"dtoa-short",
|
||||||
"itoa 1.0.6",
|
"itoa 1.0.6",
|
||||||
"phf 0.11.2",
|
"phf 0.8.0",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1927,6 +1927,7 @@ dependencies = [
|
|||||||
"flowy-folder",
|
"flowy-folder",
|
||||||
"flowy-folder-pub",
|
"flowy-folder-pub",
|
||||||
"flowy-search",
|
"flowy-search",
|
||||||
|
"flowy-search-pub",
|
||||||
"flowy-server",
|
"flowy-server",
|
||||||
"flowy-server-pub",
|
"flowy-server-pub",
|
||||||
"flowy-sqlite",
|
"flowy-sqlite",
|
||||||
@ -2216,6 +2217,7 @@ dependencies = [
|
|||||||
"flowy-user",
|
"flowy-user",
|
||||||
"futures",
|
"futures",
|
||||||
"lib-dispatch",
|
"lib-dispatch",
|
||||||
|
"lib-infra",
|
||||||
"protobuf",
|
"protobuf",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -2232,9 +2234,12 @@ dependencies = [
|
|||||||
name = "flowy-search-pub"
|
name = "flowy-search-pub"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"client-api",
|
||||||
"collab",
|
"collab",
|
||||||
"collab-folder",
|
"collab-folder",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
|
"futures",
|
||||||
|
"lib-infra",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2256,6 +2261,7 @@ dependencies = [
|
|||||||
"flowy-encrypt",
|
"flowy-encrypt",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
"flowy-folder-pub",
|
"flowy-folder-pub",
|
||||||
|
"flowy-search-pub",
|
||||||
"flowy-server-pub",
|
"flowy-server-pub",
|
||||||
"flowy-storage",
|
"flowy-storage",
|
||||||
"flowy-user-pub",
|
"flowy-user-pub",
|
||||||
@ -4808,7 +4814,7 @@ checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
"itertools 0.11.0",
|
"itertools 0.10.5",
|
||||||
"log",
|
"log",
|
||||||
"multimap",
|
"multimap",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -4829,7 +4835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools 0.11.0",
|
"itertools 0.10.5",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.47",
|
"syn 2.0.47",
|
||||||
|
4
frontend/appflowy_web/wasm-libs/Cargo.lock
generated
4
frontend/appflowy_web/wasm-libs/Cargo.lock
generated
@ -1519,9 +1519,12 @@ dependencies = [
|
|||||||
name = "flowy-search-pub"
|
name = "flowy-search-pub"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"client-api",
|
||||||
"collab",
|
"collab",
|
||||||
"collab-folder",
|
"collab-folder",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
|
"futures",
|
||||||
|
"lib-infra",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1543,6 +1546,7 @@ dependencies = [
|
|||||||
"flowy-encrypt",
|
"flowy-encrypt",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
"flowy-folder-pub",
|
"flowy-folder-pub",
|
||||||
|
"flowy-search-pub",
|
||||||
"flowy-server-pub",
|
"flowy-server-pub",
|
||||||
"flowy-storage",
|
"flowy-storage",
|
||||||
"flowy-user-pub",
|
"flowy-user-pub",
|
||||||
|
6
frontend/appflowy_web_app/src-tauri/Cargo.lock
generated
6
frontend/appflowy_web_app/src-tauri/Cargo.lock
generated
@ -1964,6 +1964,7 @@ dependencies = [
|
|||||||
"flowy-folder",
|
"flowy-folder",
|
||||||
"flowy-folder-pub",
|
"flowy-folder-pub",
|
||||||
"flowy-search",
|
"flowy-search",
|
||||||
|
"flowy-search-pub",
|
||||||
"flowy-server",
|
"flowy-server",
|
||||||
"flowy-server-pub",
|
"flowy-server-pub",
|
||||||
"flowy-sqlite",
|
"flowy-sqlite",
|
||||||
@ -2253,6 +2254,7 @@ dependencies = [
|
|||||||
"flowy-user",
|
"flowy-user",
|
||||||
"futures",
|
"futures",
|
||||||
"lib-dispatch",
|
"lib-dispatch",
|
||||||
|
"lib-infra",
|
||||||
"protobuf",
|
"protobuf",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -2269,9 +2271,12 @@ dependencies = [
|
|||||||
name = "flowy-search-pub"
|
name = "flowy-search-pub"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"client-api",
|
||||||
"collab",
|
"collab",
|
||||||
"collab-folder",
|
"collab-folder",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
|
"futures",
|
||||||
|
"lib-infra",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2293,6 +2298,7 @@ dependencies = [
|
|||||||
"flowy-encrypt",
|
"flowy-encrypt",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
"flowy-folder-pub",
|
"flowy-folder-pub",
|
||||||
|
"flowy-search-pub",
|
||||||
"flowy-server-pub",
|
"flowy-server-pub",
|
||||||
"flowy-storage",
|
"flowy-storage",
|
||||||
"flowy-user-pub",
|
"flowy-user-pub",
|
||||||
|
@ -1876,13 +1876,15 @@
|
|||||||
"image": "Image"
|
"image": "Image"
|
||||||
},
|
},
|
||||||
"commandPalette": {
|
"commandPalette": {
|
||||||
"placeholder": "Type to search for views...",
|
"placeholder": "Type to search...",
|
||||||
"bestMatches": "Best matches",
|
"bestMatches": "Best matches",
|
||||||
"recentHistory": "Recent history",
|
"recentHistory": "Recent history",
|
||||||
"navigateHint": "to navigate",
|
"navigateHint": "to navigate",
|
||||||
"loadingTooltip": "We are looking for results...",
|
"loadingTooltip": "We are looking for results...",
|
||||||
"betaLabel": "BETA",
|
"betaLabel": "BETA",
|
||||||
"betaTooltip": "We currently only support searching for pages",
|
"betaTooltip": "We currently only support searching for pages and content in documents",
|
||||||
"fromTrashHint": "From trash"
|
"fromTrashHint": "From trash",
|
||||||
|
"noResultsHint": "We didn't find what you're looking for, try searching for another term.",
|
||||||
|
"clearSearchTooltip": "Clear search field"
|
||||||
}
|
}
|
||||||
}
|
}
|
7
frontend/rust-lib/Cargo.lock
generated
7
frontend/rust-lib/Cargo.lock
generated
@ -1761,6 +1761,7 @@ dependencies = [
|
|||||||
"flowy-folder",
|
"flowy-folder",
|
||||||
"flowy-folder-pub",
|
"flowy-folder-pub",
|
||||||
"flowy-search",
|
"flowy-search",
|
||||||
|
"flowy-search-pub",
|
||||||
"flowy-server",
|
"flowy-server",
|
||||||
"flowy-server-pub",
|
"flowy-server-pub",
|
||||||
"flowy-sqlite",
|
"flowy-sqlite",
|
||||||
@ -2048,12 +2049,14 @@ dependencies = [
|
|||||||
"flowy-codegen",
|
"flowy-codegen",
|
||||||
"flowy-derive",
|
"flowy-derive",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
|
"flowy-folder",
|
||||||
"flowy-notification",
|
"flowy-notification",
|
||||||
"flowy-search-pub",
|
"flowy-search-pub",
|
||||||
"flowy-sqlite",
|
"flowy-sqlite",
|
||||||
"flowy-user",
|
"flowy-user",
|
||||||
"futures",
|
"futures",
|
||||||
"lib-dispatch",
|
"lib-dispatch",
|
||||||
|
"lib-infra",
|
||||||
"protobuf",
|
"protobuf",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -2070,9 +2073,12 @@ dependencies = [
|
|||||||
name = "flowy-search-pub"
|
name = "flowy-search-pub"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"client-api",
|
||||||
"collab",
|
"collab",
|
||||||
"collab-folder",
|
"collab-folder",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
|
"futures",
|
||||||
|
"lib-infra",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2097,6 +2103,7 @@ dependencies = [
|
|||||||
"flowy-encrypt",
|
"flowy-encrypt",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
"flowy-folder-pub",
|
"flowy-folder-pub",
|
||||||
|
"flowy-search-pub",
|
||||||
"flowy-server-pub",
|
"flowy-server-pub",
|
||||||
"flowy-storage",
|
"flowy-storage",
|
||||||
"flowy-user-pub",
|
"flowy-user-pub",
|
||||||
|
@ -24,6 +24,7 @@ flowy-config = { workspace = true }
|
|||||||
flowy-date = { workspace = true }
|
flowy-date = { workspace = true }
|
||||||
collab-integrate = { workspace = true }
|
collab-integrate = { workspace = true }
|
||||||
flowy-search = { workspace = true }
|
flowy-search = { workspace = true }
|
||||||
|
flowy-search-pub = { workspace = true }
|
||||||
collab-entity = { workspace = true }
|
collab-entity = { workspace = true }
|
||||||
collab-plugins = { workspace = true }
|
collab-plugins = { workspace = true }
|
||||||
collab = { workspace = true }
|
collab = { workspace = true }
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
|
use flowy_folder::manager::FolderManager;
|
||||||
|
use flowy_search::document::handler::DocumentSearchHandler;
|
||||||
use flowy_search::folder::handler::FolderSearchHandler;
|
use flowy_search::folder::handler::FolderSearchHandler;
|
||||||
use flowy_search::folder::indexer::FolderIndexManagerImpl;
|
use flowy_search::folder::indexer::FolderIndexManagerImpl;
|
||||||
use flowy_search::services::manager::SearchManager;
|
use flowy_search::services::manager::SearchManager;
|
||||||
|
use flowy_search_pub::cloud::SearchCloudService;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct SearchDepsResolver();
|
pub struct SearchDepsResolver();
|
||||||
impl SearchDepsResolver {
|
impl SearchDepsResolver {
|
||||||
pub async fn resolve(folder_indexer: Arc<FolderIndexManagerImpl>) -> Arc<SearchManager> {
|
pub async fn resolve(
|
||||||
|
folder_indexer: Arc<FolderIndexManagerImpl>,
|
||||||
|
cloud_service: Arc<dyn SearchCloudService>,
|
||||||
|
folder_manager: Arc<FolderManager>,
|
||||||
|
) -> Arc<SearchManager> {
|
||||||
let folder_handler = Arc::new(FolderSearchHandler::new(folder_indexer));
|
let folder_handler = Arc::new(FolderSearchHandler::new(folder_indexer));
|
||||||
Arc::new(SearchManager::new(vec![folder_handler]))
|
let document_handler = Arc::new(DocumentSearchHandler::new(cloud_service, folder_manager));
|
||||||
|
Arc::new(SearchManager::new(vec![folder_handler, document_handler]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use client_api::entity::search_dto::SearchDocumentResponseItem;
|
||||||
|
use flowy_search_pub::cloud::SearchCloudService;
|
||||||
use flowy_storage::{ObjectIdentity, ObjectStorageService};
|
use flowy_storage::{ObjectIdentity, ObjectStorageService};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -601,3 +603,18 @@ impl ChatCloudService for ServerProvider {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl SearchCloudService for ServerProvider {
|
||||||
|
async fn document_search(
|
||||||
|
&self,
|
||||||
|
workspace_id: &str,
|
||||||
|
query: String,
|
||||||
|
) -> Result<Vec<SearchDocumentResponseItem>, FlowyError> {
|
||||||
|
let server = self.get_server()?;
|
||||||
|
match server.search_service() {
|
||||||
|
Some(search_service) => search_service.document_search(workspace_id, query).await,
|
||||||
|
None => Err(FlowyError::internal().with_context("SearchCloudService not found")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -200,7 +200,12 @@ impl AppFlowyCore {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let search_manager = SearchDepsResolver::resolve(folder_indexer).await;
|
let search_manager = SearchDepsResolver::resolve(
|
||||||
|
folder_indexer,
|
||||||
|
server_provider.clone(),
|
||||||
|
folder_manager.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
(
|
(
|
||||||
user_manager,
|
user_manager,
|
||||||
|
@ -525,8 +525,8 @@ impl DatabaseManager {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|value| {
|
.map(|value| {
|
||||||
value
|
value
|
||||||
.into_iter()
|
.into_values()
|
||||||
.map(|(_k, v)| v.to_string())
|
.map(|v| v.to_string())
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join(", ")
|
.join(", ")
|
||||||
})
|
})
|
||||||
|
@ -6,7 +6,9 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
lib-infra = { workspace = true }
|
||||||
collab = { workspace = true }
|
collab = { workspace = true }
|
||||||
collab-folder = { workspace = true }
|
collab-folder = { workspace = true }
|
||||||
|
|
||||||
flowy-error = { workspace = true }
|
flowy-error = { workspace = true }
|
||||||
|
client-api = { workspace = true }
|
||||||
|
futures = { workspace = true }
|
||||||
|
12
frontend/rust-lib/flowy-search-pub/src/cloud.rs
Normal file
12
frontend/rust-lib/flowy-search-pub/src/cloud.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use client_api::entity::search_dto::SearchDocumentResponseItem;
|
||||||
|
use flowy_error::FlowyError;
|
||||||
|
use lib_infra::async_trait::async_trait;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait SearchCloudService: Send + Sync + 'static {
|
||||||
|
async fn document_search(
|
||||||
|
&self,
|
||||||
|
workspace_id: &str,
|
||||||
|
query: String,
|
||||||
|
) -> Result<Vec<SearchDocumentResponseItem>, FlowyError>;
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
|
pub mod cloud;
|
||||||
pub mod entities;
|
pub mod entities;
|
||||||
|
@ -21,10 +21,12 @@ flowy-notification.workspace = true
|
|||||||
flowy-sqlite.workspace = true
|
flowy-sqlite.workspace = true
|
||||||
flowy-user.workspace = true
|
flowy-user.workspace = true
|
||||||
flowy-search-pub.workspace = true
|
flowy-search-pub.workspace = true
|
||||||
|
flowy-folder = { workspace = true }
|
||||||
|
|
||||||
bytes.workspace = true
|
bytes.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
lib-dispatch.workspace = true
|
lib-dispatch.workspace = true
|
||||||
|
lib-infra = { workspace = true }
|
||||||
protobuf.workspace = true
|
protobuf.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
99
frontend/rust-lib/flowy-search/src/document/handler.rs
Normal file
99
frontend/rust-lib/flowy-search/src/document/handler.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use flowy_error::FlowyResult;
|
||||||
|
use flowy_folder::{manager::FolderManager, ViewLayout};
|
||||||
|
use flowy_search_pub::cloud::SearchCloudService;
|
||||||
|
use lib_infra::async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
entities::{IndexTypePB, ResultIconPB, ResultIconTypePB, SearchFilterPB, SearchResultPB},
|
||||||
|
services::manager::{SearchHandler, SearchType},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct DocumentSearchHandler {
|
||||||
|
pub cloud_service: Arc<dyn SearchCloudService>,
|
||||||
|
pub folder_manager: Arc<FolderManager>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocumentSearchHandler {
|
||||||
|
pub fn new(
|
||||||
|
cloud_service: Arc<dyn SearchCloudService>,
|
||||||
|
folder_manager: Arc<FolderManager>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
cloud_service,
|
||||||
|
folder_manager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl SearchHandler for DocumentSearchHandler {
|
||||||
|
fn search_type(&self) -> SearchType {
|
||||||
|
SearchType::Document
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn perform_search(
|
||||||
|
&self,
|
||||||
|
query: String,
|
||||||
|
filter: Option<SearchFilterPB>,
|
||||||
|
) -> FlowyResult<Vec<SearchResultPB>> {
|
||||||
|
let filter = match filter {
|
||||||
|
Some(filter) => filter,
|
||||||
|
None => return Ok(vec![]),
|
||||||
|
};
|
||||||
|
|
||||||
|
let workspace_id = match filter.workspace_id {
|
||||||
|
Some(workspace_id) => workspace_id,
|
||||||
|
None => return Ok(vec![]),
|
||||||
|
};
|
||||||
|
|
||||||
|
let results = self
|
||||||
|
.cloud_service
|
||||||
|
.document_search(&workspace_id, query)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Grab all views from folder cache
|
||||||
|
// Notice that `get_all_view_pb` returns Views that don't include trashed and private views
|
||||||
|
let mut views = self.folder_manager.get_all_views_pb().await?.into_iter();
|
||||||
|
let mut search_results: Vec<SearchResultPB> = vec![];
|
||||||
|
|
||||||
|
for result in results {
|
||||||
|
if let Some(view) = views.find(|v| v.id == result.object_id) {
|
||||||
|
// If there is no View for the result, we don't add it to the results
|
||||||
|
// If possible we will extract the icon to display for the result
|
||||||
|
let icon: Option<ResultIconPB> = match view.icon.clone() {
|
||||||
|
Some(view_icon) => Some(ResultIconPB::from(view_icon)),
|
||||||
|
None => {
|
||||||
|
let view_layout_ty: i64 = ViewLayout::from(view.layout.clone()).into();
|
||||||
|
Some(ResultIconPB {
|
||||||
|
ty: ResultIconTypePB::Icon,
|
||||||
|
value: view_layout_ty.to_string(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
search_results.push(SearchResultPB {
|
||||||
|
index_type: IndexTypePB::Document,
|
||||||
|
view_id: result.object_id.clone(),
|
||||||
|
id: result.object_id.clone(),
|
||||||
|
data: view.name.clone(),
|
||||||
|
icon,
|
||||||
|
// We reverse the score, the cloud search score is based on
|
||||||
|
// 1 being the worst result, and closer to 0 being good result, that is
|
||||||
|
// the opposite of local search.
|
||||||
|
score: 1.0 - result.score,
|
||||||
|
workspace_id: result.workspace_id,
|
||||||
|
preview: result.preview,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(search_results)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ignore for [DocumentSearchHandler]
|
||||||
|
fn index_count(&self) -> u64 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
1
frontend/rust-lib/flowy-search/src/document/mod.rs
Normal file
1
frontend/rust-lib/flowy-search/src/document/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod handler;
|
@ -3,8 +3,9 @@ use flowy_derive::ProtoBuf_Enum;
|
|||||||
#[derive(ProtoBuf_Enum, Eq, PartialEq, Debug, Clone)]
|
#[derive(ProtoBuf_Enum, Eq, PartialEq, Debug, Clone)]
|
||||||
pub enum IndexTypePB {
|
pub enum IndexTypePB {
|
||||||
View = 0,
|
View = 0,
|
||||||
DocumentBlock = 1,
|
Document = 1,
|
||||||
DatabaseRow = 2,
|
DocumentBlock = 2,
|
||||||
|
DatabaseRow = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for IndexTypePB {
|
impl Default for IndexTypePB {
|
||||||
|
@ -8,7 +8,7 @@ pub struct SearchResultNotificationPB {
|
|||||||
pub items: Vec<SearchResultPB>,
|
pub items: Vec<SearchResultPB>,
|
||||||
|
|
||||||
#[pb(index = 2)]
|
#[pb(index = 2)]
|
||||||
pub closed: bool,
|
pub sends: u64,
|
||||||
|
|
||||||
#[pb(index = 3, one_of)]
|
#[pb(index = 3, one_of)]
|
||||||
pub channel: Option<String>,
|
pub channel: Option<String>,
|
||||||
@ -19,7 +19,6 @@ pub enum SearchNotification {
|
|||||||
#[default]
|
#[default]
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
DidUpdateResults = 1,
|
DidUpdateResults = 1,
|
||||||
DidCloseResults = 2,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<SearchNotification> for i32 {
|
impl std::convert::From<SearchNotification> for i32 {
|
||||||
@ -32,7 +31,6 @@ impl std::convert::From<i32> for SearchNotification {
|
|||||||
fn from(notification: i32) -> Self {
|
fn from(notification: i32) -> Self {
|
||||||
match notification {
|
match notification {
|
||||||
1 => SearchNotification::DidUpdateResults,
|
1 => SearchNotification::DidUpdateResults,
|
||||||
2 => SearchNotification::DidCloseResults,
|
|
||||||
_ => SearchNotification::Unknown,
|
_ => SearchNotification::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use collab_folder::{IconType, ViewIcon};
|
use collab_folder::{IconType, ViewIcon};
|
||||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||||
|
use flowy_folder::entities::ViewIconPB;
|
||||||
|
|
||||||
use super::IndexTypePB;
|
use super::IndexTypePB;
|
||||||
|
|
||||||
@ -31,6 +32,9 @@ pub struct SearchResultPB {
|
|||||||
|
|
||||||
#[pb(index = 7)]
|
#[pb(index = 7)]
|
||||||
pub workspace_id: String,
|
pub workspace_id: String,
|
||||||
|
|
||||||
|
#[pb(index = 8, one_of)]
|
||||||
|
pub preview: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SearchResultPB {
|
impl SearchResultPB {
|
||||||
@ -43,6 +47,7 @@ impl SearchResultPB {
|
|||||||
icon: self.icon.clone(),
|
icon: self.icon.clone(),
|
||||||
score,
|
score,
|
||||||
workspace_id: self.workspace_id.clone(),
|
workspace_id: self.workspace_id.clone(),
|
||||||
|
preview: self.preview.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,3 +127,12 @@ impl From<ViewIcon> for ResultIconPB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ViewIconPB> for ResultIconPB {
|
||||||
|
fn from(val: ViewIconPB) -> Self {
|
||||||
|
ResultIconPB {
|
||||||
|
ty: IconType::from(val.ty).into(),
|
||||||
|
value: val.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -30,6 +30,7 @@ impl From<FolderIndexData> for SearchResultPB {
|
|||||||
score: 0.0,
|
score: 0.0,
|
||||||
icon,
|
icon,
|
||||||
workspace_id: data.workspace_id,
|
workspace_id: data.workspace_id,
|
||||||
|
preview: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ use crate::{
|
|||||||
services::manager::{SearchHandler, SearchType},
|
services::manager::{SearchHandler, SearchType},
|
||||||
};
|
};
|
||||||
use flowy_error::FlowyResult;
|
use flowy_error::FlowyResult;
|
||||||
|
use lib_infra::async_trait::async_trait;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::indexer::FolderIndexManagerImpl;
|
use super::indexer::FolderIndexManagerImpl;
|
||||||
@ -17,12 +18,13 @@ impl FolderSearchHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl SearchHandler for FolderSearchHandler {
|
impl SearchHandler for FolderSearchHandler {
|
||||||
fn search_type(&self) -> SearchType {
|
fn search_type(&self) -> SearchType {
|
||||||
SearchType::Folder
|
SearchType::Folder
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_search(
|
async fn perform_search(
|
||||||
&self,
|
&self,
|
||||||
query: String,
|
query: String,
|
||||||
filter: Option<SearchFilterPB>,
|
filter: Option<SearchFilterPB>,
|
||||||
|
@ -298,12 +298,9 @@ impl IndexManager for FolderIndexManagerImpl {
|
|||||||
let wid = workspace_id.clone();
|
let wid = workspace_id.clone();
|
||||||
af_spawn(async move {
|
af_spawn(async move {
|
||||||
while let Ok(msg) = rx.recv().await {
|
while let Ok(msg) = rx.recv().await {
|
||||||
tracing::warn!("[Indexer] Message received: {:?}", msg);
|
|
||||||
match msg {
|
match msg {
|
||||||
IndexContent::Create(value) => match serde_json::from_value::<ViewIndexContent>(value) {
|
IndexContent::Create(value) => match serde_json::from_value::<ViewIndexContent>(value) {
|
||||||
Ok(view) => {
|
Ok(view) => {
|
||||||
tracing::warn!("[Indexer] CREATE: {:?}", view);
|
|
||||||
|
|
||||||
let _ = indexer.add_index(IndexableData {
|
let _ = indexer.add_index(IndexableData {
|
||||||
id: view.id,
|
id: view.id,
|
||||||
data: view.name,
|
data: view.name,
|
||||||
@ -316,7 +313,6 @@ impl IndexManager for FolderIndexManagerImpl {
|
|||||||
},
|
},
|
||||||
IndexContent::Update(value) => match serde_json::from_value::<ViewIndexContent>(value) {
|
IndexContent::Update(value) => match serde_json::from_value::<ViewIndexContent>(value) {
|
||||||
Ok(view) => {
|
Ok(view) => {
|
||||||
tracing::warn!("[Indexer] UPDATE: {:?}", view);
|
|
||||||
let _ = indexer.update_index(IndexableData {
|
let _ = indexer.update_index(IndexableData {
|
||||||
id: view.id,
|
id: view.id,
|
||||||
data: view.name,
|
data: view.name,
|
||||||
@ -328,7 +324,6 @@ impl IndexManager for FolderIndexManagerImpl {
|
|||||||
Err(err) => tracing::error!("FolderIndexManager error deserialize: {:?}", err),
|
Err(err) => tracing::error!("FolderIndexManager error deserialize: {:?}", err),
|
||||||
},
|
},
|
||||||
IndexContent::Delete(ids) => {
|
IndexContent::Delete(ids) => {
|
||||||
tracing::warn!("[Indexer] DELETE: {:?}", ids);
|
|
||||||
if let Err(e) = indexer.remove_indices(ids) {
|
if let Err(e) = indexer.remove_indices(ids) {
|
||||||
tracing::error!("FolderIndexManager error deserialize: {:?}", e);
|
tracing::error!("FolderIndexManager error deserialize: {:?}", e);
|
||||||
}
|
}
|
||||||
@ -459,7 +454,6 @@ impl FolderIndexManager for FolderIndexManagerImpl {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
FolderViewChange::Deleted { view_ids } => {
|
FolderViewChange::Deleted { view_ids } => {
|
||||||
tracing::warn!("[Indexer] ViewChange Reached Deleted: {:?}", view_ids);
|
|
||||||
let _ = self.remove_indices(view_ids);
|
let _ = self.remove_indices(view_ids);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
pub mod document;
|
||||||
pub mod entities;
|
pub mod entities;
|
||||||
pub mod event_handler;
|
pub mod event_handler;
|
||||||
pub mod event_map;
|
pub mod event_map;
|
||||||
|
@ -5,21 +5,27 @@ use super::notifier::{SearchNotifier, SearchResultChanged, SearchResultReceiverR
|
|||||||
use crate::entities::{SearchFilterPB, SearchResultNotificationPB, SearchResultPB};
|
use crate::entities::{SearchFilterPB, SearchResultNotificationPB, SearchResultPB};
|
||||||
use flowy_error::FlowyResult;
|
use flowy_error::FlowyResult;
|
||||||
use lib_dispatch::prelude::af_spawn;
|
use lib_dispatch::prelude::af_spawn;
|
||||||
use tokio::{sync::broadcast, task::spawn_blocking};
|
use lib_infra::async_trait::async_trait;
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum SearchType {
|
pub enum SearchType {
|
||||||
Folder,
|
Folder,
|
||||||
|
Document,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
pub trait SearchHandler: Send + Sync + 'static {
|
pub trait SearchHandler: Send + Sync + 'static {
|
||||||
/// returns the type of search this handler is responsible for
|
/// returns the type of search this handler is responsible for
|
||||||
fn search_type(&self) -> SearchType;
|
fn search_type(&self) -> SearchType;
|
||||||
|
|
||||||
/// performs a search and returns the results
|
/// performs a search and returns the results
|
||||||
fn perform_search(
|
async fn perform_search(
|
||||||
&self,
|
&self,
|
||||||
query: String,
|
query: String,
|
||||||
filter: Option<SearchFilterPB>,
|
filter: Option<SearchFilterPB>,
|
||||||
) -> FlowyResult<Vec<SearchResultPB>>;
|
) -> FlowyResult<Vec<SearchResultPB>>;
|
||||||
|
|
||||||
/// returns the number of indexed objects
|
/// returns the number of indexed objects
|
||||||
fn index_count(&self) -> u64;
|
fn index_count(&self) -> u64;
|
||||||
}
|
}
|
||||||
@ -57,25 +63,22 @@ impl SearchManager {
|
|||||||
filter: Option<SearchFilterPB>,
|
filter: Option<SearchFilterPB>,
|
||||||
channel: Option<String>,
|
channel: Option<String>,
|
||||||
) {
|
) {
|
||||||
let mut sends: usize = 0;
|
|
||||||
let max: usize = self.handlers.len();
|
let max: usize = self.handlers.len();
|
||||||
let handlers = self.handlers.clone();
|
let handlers = self.handlers.clone();
|
||||||
|
|
||||||
for (_, handler) in handlers {
|
for (_, handler) in handlers {
|
||||||
let q = query.clone();
|
let q = query.clone();
|
||||||
let f = filter.clone();
|
let f = filter.clone();
|
||||||
let ch = channel.clone();
|
let ch = channel.clone();
|
||||||
let notifier = self.notifier.clone();
|
let notifier = self.notifier.clone();
|
||||||
|
|
||||||
spawn_blocking(move || {
|
af_spawn(async move {
|
||||||
let res = handler.perform_search(q, f);
|
let res = handler.perform_search(q, f).await;
|
||||||
sends += 1;
|
|
||||||
|
|
||||||
let close = sends == max;
|
|
||||||
let items = res.unwrap_or_default();
|
let items = res.unwrap_or_default();
|
||||||
|
|
||||||
let notification = SearchResultNotificationPB {
|
let notification = SearchResultNotificationPB {
|
||||||
items,
|
items,
|
||||||
closed: close,
|
sends: max as u64,
|
||||||
channel: ch,
|
channel: ch,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -31,13 +31,11 @@ impl SearchResultReceiverRunner {
|
|||||||
.for_each(|changed| async {
|
.for_each(|changed| async {
|
||||||
match changed {
|
match changed {
|
||||||
SearchResultChanged::SearchResultUpdate(notification) => {
|
SearchResultChanged::SearchResultUpdate(notification) => {
|
||||||
let ty = if notification.closed {
|
send_notification(
|
||||||
SearchNotification::DidCloseResults
|
SEARCH_ID,
|
||||||
} else {
|
SearchNotification::DidUpdateResults,
|
||||||
SearchNotification::DidUpdateResults
|
notification.channel.clone(),
|
||||||
};
|
)
|
||||||
|
|
||||||
send_notification(SEARCH_ID, ty, notification.channel.clone())
|
|
||||||
.payload(notification)
|
.payload(notification)
|
||||||
.send();
|
.send();
|
||||||
},
|
},
|
||||||
|
@ -40,6 +40,7 @@ flowy-document-pub = { workspace = true }
|
|||||||
appflowy-cloud-billing-client = { workspace = true }
|
appflowy-cloud-billing-client = { workspace = true }
|
||||||
flowy-error = { workspace = true, features = ["impl_from_serde", "impl_from_reqwest", "impl_from_url", "impl_from_appflowy_cloud"] }
|
flowy-error = { workspace = true, features = ["impl_from_serde", "impl_from_reqwest", "impl_from_url", "impl_from_appflowy_cloud"] }
|
||||||
flowy-server-pub = { workspace = true }
|
flowy-server-pub = { workspace = true }
|
||||||
|
flowy-search-pub = { workspace = true }
|
||||||
flowy-encrypt = { workspace = true }
|
flowy-encrypt = { workspace = true }
|
||||||
flowy-storage = { workspace = true }
|
flowy-storage = { workspace = true }
|
||||||
flowy-chat-pub = { workspace = true }
|
flowy-chat-pub = { workspace = true }
|
||||||
|
@ -3,6 +3,7 @@ pub(crate) use database::*;
|
|||||||
pub(crate) use document::*;
|
pub(crate) use document::*;
|
||||||
pub(crate) use file_storage::*;
|
pub(crate) use file_storage::*;
|
||||||
pub(crate) use folder::*;
|
pub(crate) use folder::*;
|
||||||
|
pub(crate) use search::*;
|
||||||
pub(crate) use user::*;
|
pub(crate) use user::*;
|
||||||
|
|
||||||
mod chat;
|
mod chat;
|
||||||
@ -10,5 +11,6 @@ mod database;
|
|||||||
mod document;
|
mod document;
|
||||||
mod file_storage;
|
mod file_storage;
|
||||||
mod folder;
|
mod folder;
|
||||||
|
mod search;
|
||||||
mod user;
|
mod user;
|
||||||
mod util;
|
mod util;
|
||||||
|
40
frontend/rust-lib/flowy-server/src/af_cloud/impls/search.rs
Normal file
40
frontend/rust-lib/flowy-server/src/af_cloud/impls/search.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
use client_api::entity::search_dto::SearchDocumentResponseItem;
|
||||||
|
use flowy_error::FlowyError;
|
||||||
|
use flowy_search_pub::cloud::SearchCloudService;
|
||||||
|
use lib_infra::async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::af_cloud::AFServer;
|
||||||
|
|
||||||
|
pub(crate) struct AFCloudSearchCloudServiceImpl<T> {
|
||||||
|
pub inner: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
// The limit of what the score should be for results, used to
|
||||||
|
// filter out irrelevant results.
|
||||||
|
const SCORE_LIMIT: f64 = 0.8;
|
||||||
|
const DEFAULT_PREVIEW: u32 = 80;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T> SearchCloudService for AFCloudSearchCloudServiceImpl<T>
|
||||||
|
where
|
||||||
|
T: AFServer,
|
||||||
|
{
|
||||||
|
async fn document_search(
|
||||||
|
&self,
|
||||||
|
workspace_id: &str,
|
||||||
|
query: String,
|
||||||
|
) -> Result<Vec<SearchDocumentResponseItem>, FlowyError> {
|
||||||
|
let client = self.inner.try_get_client()?;
|
||||||
|
let result = client
|
||||||
|
.search_documents(workspace_id, &query, 10, DEFAULT_PREVIEW)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Filter out irrelevant results
|
||||||
|
let result = result
|
||||||
|
.into_iter()
|
||||||
|
.filter(|r| r.score < SCORE_LIMIT)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ use client_api::ws::{
|
|||||||
};
|
};
|
||||||
use client_api::{Client, ClientConfiguration};
|
use client_api::{Client, ClientConfiguration};
|
||||||
use flowy_chat_pub::cloud::ChatCloudService;
|
use flowy_chat_pub::cloud::ChatCloudService;
|
||||||
|
use flowy_search_pub::cloud::SearchCloudService;
|
||||||
use flowy_storage::ObjectStorageService;
|
use flowy_storage::ObjectStorageService;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
@ -38,6 +39,8 @@ use crate::af_cloud::impls::{
|
|||||||
|
|
||||||
use crate::AppFlowyServer;
|
use crate::AppFlowyServer;
|
||||||
|
|
||||||
|
use super::impls::AFCloudSearchCloudServiceImpl;
|
||||||
|
|
||||||
pub(crate) type AFCloudClient = Client;
|
pub(crate) type AFCloudClient = Client;
|
||||||
|
|
||||||
pub struct AppFlowyCloudServer {
|
pub struct AppFlowyCloudServer {
|
||||||
@ -255,6 +258,14 @@ impl AppFlowyServer for AppFlowyCloudServer {
|
|||||||
};
|
};
|
||||||
Some(Arc::new(AFCloudFileStorageServiceImpl::new(client)))
|
Some(Arc::new(AFCloudFileStorageServiceImpl::new(client)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_service(&self) -> Option<Arc<dyn SearchCloudService>> {
|
||||||
|
let server = AFServerImpl {
|
||||||
|
client: self.get_client(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Arc::new(AFCloudSearchCloudServiceImpl { inner: server }))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawns a new asynchronous task to handle WebSocket connections based on token state.
|
/// Spawns a new asynchronous task to handle WebSocket connections based on token state.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use flowy_search_pub::cloud::SearchCloudService;
|
||||||
use flowy_storage::ObjectStorageService;
|
use flowy_storage::ObjectStorageService;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -70,4 +71,8 @@ impl AppFlowyServer for LocalServer {
|
|||||||
fn file_storage(&self) -> Option<Arc<dyn ObjectStorageService>> {
|
fn file_storage(&self) -> Option<Arc<dyn ObjectStorageService>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_service(&self) -> Option<Arc<dyn SearchCloudService>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use client_api::ws::ConnectState;
|
use client_api::ws::ConnectState;
|
||||||
use client_api::ws::WSConnectStateReceiver;
|
use client_api::ws::WSConnectStateReceiver;
|
||||||
use client_api::ws::WebSocketChannel;
|
use client_api::ws::WebSocketChannel;
|
||||||
|
use flowy_search_pub::cloud::SearchCloudService;
|
||||||
use flowy_storage::ObjectStorageService;
|
use flowy_storage::ObjectStorageService;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -100,6 +101,10 @@ pub trait AppFlowyServer: Send + Sync + 'static {
|
|||||||
Arc::new(DefaultChatCloudServiceImpl)
|
Arc::new(DefaultChatCloudServiceImpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bridge for the Cloud AI Search features
|
||||||
|
///
|
||||||
|
fn search_service(&self) -> Option<Arc<dyn SearchCloudService>>;
|
||||||
|
|
||||||
/// Manages collaborative objects within a remote storage system. This includes operations such as
|
/// Manages collaborative objects within a remote storage system. This includes operations such as
|
||||||
/// checking storage status, retrieving updates and snapshots, and dispatching updates. The service
|
/// checking storage status, retrieving updates and snapshots, and dispatching updates. The service
|
||||||
/// also provides subscription capabilities for real-time updates.
|
/// also provides subscription capabilities for real-time updates.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use flowy_search_pub::cloud::SearchCloudService;
|
||||||
use flowy_storage::ObjectStorageService;
|
use flowy_storage::ObjectStorageService;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
@ -194,4 +195,8 @@ impl AppFlowyServer for SupabaseServer {
|
|||||||
.clone()
|
.clone()
|
||||||
.map(|s| s as Arc<dyn ObjectStorageService>)
|
.map(|s| s as Arc<dyn ObjectStorageService>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search_service(&self) -> Option<Arc<dyn SearchCloudService>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user