@dxos/plugin-search 0.8.4-main.c85a9c8dae → 0.8.4-main.d9fc60f731
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +102 -5
- package/PLUGIN.mdl +291 -0
- package/dist/lib/neutral/SearchArticle-CQMZ46ZS.mjs +51 -0
- package/dist/lib/neutral/SearchArticle-CQMZ46ZS.mjs.map +7 -0
- package/dist/lib/neutral/SearchDialog-IFMA5UP5.mjs +76 -0
- package/dist/lib/neutral/SearchDialog-IFMA5UP5.mjs.map +7 -0
- package/dist/lib/neutral/SearchPlugin.mjs +10 -0
- package/dist/lib/neutral/SearchPlugin.mjs.map +7 -0
- package/dist/lib/{browser/app-graph-builder-NUJ2FOVQ.mjs → neutral/app-graph-builder-ADB5LBS5.mjs} +23 -34
- package/dist/lib/neutral/app-graph-builder-ADB5LBS5.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +13 -0
- package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
- package/dist/lib/neutral/chunk-2XHA22US.mjs +9 -0
- package/dist/lib/neutral/chunk-2XHA22US.mjs.map +7 -0
- package/dist/lib/neutral/chunk-BST37VRI.mjs +34 -0
- package/dist/lib/neutral/chunk-BST37VRI.mjs.map +7 -0
- package/dist/lib/{browser/chunk-NB4JZIOE.mjs → neutral/chunk-CZU523NP.mjs} +22 -22
- package/dist/lib/neutral/chunk-CZU523NP.mjs.map +7 -0
- package/dist/lib/neutral/chunk-GT3AGVU6.mjs +28 -0
- package/dist/lib/neutral/chunk-GT3AGVU6.mjs.map +7 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/neutral/chunk-Q2F6YOOZ.mjs +41 -0
- package/dist/lib/neutral/chunk-Q2F6YOOZ.mjs.map +7 -0
- package/dist/lib/neutral/chunk-TN4G3MZZ.mjs +8 -0
- package/dist/lib/neutral/chunk-TN4G3MZZ.mjs.map +7 -0
- package/dist/lib/neutral/components/index.mjs +97 -0
- package/dist/lib/neutral/components/index.mjs.map +7 -0
- package/dist/lib/neutral/containers/index.mjs +11 -0
- package/dist/lib/neutral/containers/index.mjs.map +7 -0
- package/dist/lib/neutral/hooks/index.mjs +30 -0
- package/dist/lib/neutral/hooks/index.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +40 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/meta.mjs +10 -0
- package/dist/lib/neutral/meta.mjs.map +7 -0
- package/dist/lib/neutral/open-search-Y6OUELN3.mjs +23 -0
- package/dist/lib/neutral/open-search-Y6OUELN3.mjs.map +7 -0
- package/dist/lib/neutral/operation-handler-DY624P2R.mjs +13 -0
- package/dist/lib/neutral/operation-handler-DY624P2R.mjs.map +7 -0
- package/dist/lib/neutral/operations/index.mjs +8 -0
- package/dist/lib/neutral/operations/index.mjs.map +7 -0
- package/dist/lib/neutral/plugin.mjs +16 -0
- package/dist/lib/neutral/plugin.mjs.map +7 -0
- package/dist/lib/neutral/react-surface-3IXL3EOJ.mjs +61 -0
- package/dist/lib/neutral/react-surface-3IXL3EOJ.mjs.map +7 -0
- package/dist/lib/neutral/testing.mjs +8 -0
- package/dist/lib/neutral/testing.mjs.map +7 -0
- package/dist/lib/neutral/translations.mjs +25 -0
- package/dist/lib/neutral/translations.mjs.map +7 -0
- package/dist/lib/neutral/types/index.mjs +8 -0
- package/dist/lib/neutral/types/index.mjs.map +7 -0
- package/dist/types/src/SearchPlugin.d.ts +1 -0
- package/dist/types/src/SearchPlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +5 -3
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
- package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
- package/dist/types/src/components/SearchResultStack/SearchResultStack.d.ts +9 -0
- package/dist/types/src/components/SearchResultStack/SearchResultStack.d.ts.map +1 -0
- package/dist/types/src/components/SearchResultStack/index.d.ts +2 -0
- package/dist/types/src/components/SearchResultStack/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/constants.d.ts +2 -1
- package/dist/types/src/constants.d.ts.map +1 -1
- package/dist/types/src/containers/SearchArticle/SearchArticle.d.ts +4 -0
- package/dist/types/src/containers/SearchArticle/SearchArticle.d.ts.map +1 -0
- package/dist/types/src/containers/SearchArticle/SearchArticle.stories.d.ts +14 -0
- package/dist/types/src/containers/SearchArticle/SearchArticle.stories.d.ts.map +1 -0
- package/dist/types/src/containers/SearchArticle/index.d.ts +2 -0
- package/dist/types/src/containers/SearchArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/SearchDialog/SearchDialog.d.ts +5 -4
- package/dist/types/src/containers/SearchDialog/SearchDialog.d.ts.map +1 -1
- package/dist/types/src/containers/SearchDialog/SearchDialog.stories.d.ts +16 -0
- package/dist/types/src/containers/SearchDialog/SearchDialog.stories.d.ts.map +1 -0
- package/dist/types/src/containers/SearchDialog/index.d.ts +1 -2
- package/dist/types/src/containers/SearchDialog/index.d.ts.map +1 -1
- package/dist/types/src/containers/index.d.ts +1 -3
- package/dist/types/src/containers/index.d.ts.map +1 -1
- package/dist/types/src/hooks/sync.d.ts +2 -2
- package/dist/types/src/hooks/sync.d.ts.map +1 -1
- package/dist/types/src/hooks/useGlobalSearch.d.ts +2 -2
- package/dist/types/src/hooks/useGlobalSearch.d.ts.map +1 -1
- package/dist/types/src/hooks/useSearchResults.d.ts.map +1 -1
- package/dist/types/src/hooks/useWebSearch.d.ts +1 -1
- package/dist/types/src/hooks/useWebSearch.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +0 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +1 -1
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/operations/index.d.ts +3 -0
- package/dist/types/src/operations/index.d.ts.map +1 -0
- package/dist/types/src/operations/open-search.d.ts +5 -0
- package/dist/types/src/operations/open-search.d.ts.map +1 -0
- package/dist/types/src/plugin.d.ts +4 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- package/dist/types/src/search/exa.d.ts +4 -5
- package/dist/types/src/search/exa.d.ts.map +1 -1
- package/dist/types/src/testing.d.ts +2 -0
- package/dist/types/src/testing.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +15 -10
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/SearchOperation.d.ts +3 -0
- package/dist/types/src/types/SearchOperation.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +1 -1
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/types/search.d.ts +1 -16
- package/dist/types/src/types/search.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +102 -46
- package/src/SearchPlugin.tsx +12 -22
- package/src/capabilities/{app-graph-builder/app-graph-builder.ts → app-graph-builder.ts} +14 -19
- package/src/capabilities/index.ts +9 -3
- package/src/capabilities/operation-handler.ts +16 -0
- package/src/capabilities/react-surface.tsx +72 -0
- package/src/components/SearchResultStack/SearchResultStack.tsx +118 -0
- package/src/components/SearchResultStack/index.ts +5 -0
- package/src/components/index.ts +2 -0
- package/src/constants.ts +4 -2
- package/src/containers/SearchArticle/SearchArticle.stories.tsx +79 -0
- package/src/containers/SearchArticle/SearchArticle.tsx +60 -0
- package/src/containers/SearchArticle/index.ts +5 -0
- package/src/containers/SearchDialog/SearchDialog.stories.tsx +108 -0
- package/src/containers/SearchDialog/SearchDialog.tsx +67 -94
- package/src/containers/SearchDialog/index.ts +1 -3
- package/src/containers/index.ts +1 -4
- package/src/hooks/sync.test.ts +4 -4
- package/src/hooks/sync.ts +11 -8
- package/src/hooks/useGlobalSearch.tsx +3 -3
- package/src/hooks/useSearchResults.ts +2 -2
- package/src/hooks/useWebSearch.ts +6 -6
- package/src/index.ts +2 -2
- package/src/meta.ts +25 -7
- package/src/operations/index.ts +7 -0
- package/src/operations/open-search.ts +24 -0
- package/src/plugin.ts +11 -0
- package/src/search/exa.test.ts +6 -6
- package/src/search/exa.ts +6 -6
- package/src/testing.ts +7 -0
- package/src/translations.ts +10 -9
- package/src/types/SearchOperation.ts +20 -0
- package/src/types/index.ts +2 -1
- package/src/types/search.ts +1 -19
- package/src/vite-env.d.ts +10 -0
- package/dist/lib/browser/SearchDialog-E6336HCX.mjs +0 -110
- package/dist/lib/browser/SearchDialog-E6336HCX.mjs.map +0 -7
- package/dist/lib/browser/SearchMain-24ZNJB24.mjs +0 -100
- package/dist/lib/browser/SearchMain-24ZNJB24.mjs.map +0 -7
- package/dist/lib/browser/SpaceMain-IGCYHL7R.mjs +0 -154
- package/dist/lib/browser/SpaceMain-IGCYHL7R.mjs.map +0 -7
- package/dist/lib/browser/app-graph-builder-NUJ2FOVQ.mjs.map +0 -7
- package/dist/lib/browser/chunk-HRIWTRJV.mjs +0 -22
- package/dist/lib/browser/chunk-HRIWTRJV.mjs.map +0 -7
- package/dist/lib/browser/chunk-I7YWQSUZ.mjs +0 -26
- package/dist/lib/browser/chunk-I7YWQSUZ.mjs.map +0 -7
- package/dist/lib/browser/chunk-NB4JZIOE.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -105
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/operation-resolver-QEGN4ZKN.mjs +0 -26
- package/dist/lib/browser/operation-resolver-QEGN4ZKN.mjs.map +0 -7
- package/dist/lib/browser/react-surface-FLO4YTFP.mjs +0 -73
- package/dist/lib/browser/react-surface-FLO4YTFP.mjs.map +0 -7
- package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +0 -1
- package/dist/types/src/capabilities/app-graph-builder/index.d.ts +0 -3
- package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
- package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
- package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
- package/dist/types/src/containers/SearchMain/SearchMain.d.ts +0 -6
- package/dist/types/src/containers/SearchMain/SearchMain.d.ts.map +0 -1
- package/dist/types/src/containers/SearchMain/index.d.ts +0 -3
- package/dist/types/src/containers/SearchMain/index.d.ts.map +0 -1
- package/dist/types/src/containers/SpaceMain/SpaceMain.d.ts +0 -6
- package/dist/types/src/containers/SpaceMain/SpaceMain.d.ts.map +0 -1
- package/dist/types/src/containers/SpaceMain/index.d.ts +0 -3
- package/dist/types/src/containers/SpaceMain/index.d.ts.map +0 -1
- package/dist/types/src/types/action.d.ts +0 -16
- package/dist/types/src/types/action.d.ts.map +0 -1
- package/src/capabilities/app-graph-builder/index.ts +0 -7
- package/src/capabilities/operation-resolver/index.ts +0 -7
- package/src/capabilities/operation-resolver/operation-resolver.ts +0 -24
- package/src/capabilities/react-surface/index.ts +0 -7
- package/src/capabilities/react-surface/react-surface.tsx +0 -72
- package/src/containers/SearchMain/SearchMain.tsx +0 -103
- package/src/containers/SearchMain/index.ts +0 -7
- package/src/containers/SpaceMain/SpaceMain.tsx +0 -180
- package/src/containers/SpaceMain/index.ts +0 -7
- package/src/types/action.tsx +0 -18
- /package/dist/types/src/capabilities/{app-graph-builder/app-graph-builder.d.ts → app-graph-builder.d.ts} +0 -0
- /package/dist/types/src/capabilities/{react-surface/react-surface.d.ts → react-surface.d.ts} +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { AppSurface } from '@dxos/app-toolkit/ui';
|
|
8
|
+
import { Entity, Filter, Query } from '@dxos/echo';
|
|
9
|
+
import { useQuery } from '@dxos/react-client/echo';
|
|
10
|
+
import { Panel, Toolbar } from '@dxos/react-ui';
|
|
11
|
+
import { SearchList } from '@dxos/react-ui-search';
|
|
12
|
+
import { Text } from '@dxos/schema';
|
|
13
|
+
import { isTauri, getHostPlatform } from '@dxos/util';
|
|
14
|
+
|
|
15
|
+
import { SearchResultStack } from '#components';
|
|
16
|
+
import { useGlobalSearch, useGlobalSearchResults, useWebSearch } from '#hooks';
|
|
17
|
+
|
|
18
|
+
export const SearchArticle = ({ space }: AppSurface.SpaceArticleProps) => {
|
|
19
|
+
// TODO(burdon): Option to query across spaces.
|
|
20
|
+
const [query, setQuery] = useState<string>();
|
|
21
|
+
// TODO(burdon): Re-enable full-text search when indexer is available in all environments.
|
|
22
|
+
const objects = useQuery(
|
|
23
|
+
space.db,
|
|
24
|
+
query === undefined ? Query.select(Filter.nothing()) : Query.select(Filter.not(Filter.type(Text.Text))),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const { setMatch } = useGlobalSearch();
|
|
28
|
+
const results = useGlobalSearchResults(objects);
|
|
29
|
+
const { results: webResults } = useWebSearch({ query });
|
|
30
|
+
const allResults = useMemo(
|
|
31
|
+
() => [...results, ...webResults].filter(({ object }) => object && Entity.getLabel(object)),
|
|
32
|
+
[results, webResults],
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const handleSearch = useCallback(
|
|
36
|
+
(text: string) => {
|
|
37
|
+
setQuery(text);
|
|
38
|
+
setMatch?.(text);
|
|
39
|
+
},
|
|
40
|
+
[setMatch],
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const autoFocus = !isTauri() || getHostPlatform() !== 'ios';
|
|
44
|
+
|
|
45
|
+
// TODO(burdon): Move current up/down without losing focus.
|
|
46
|
+
return (
|
|
47
|
+
<SearchList.Root onSearch={handleSearch}>
|
|
48
|
+
<Panel.Root>
|
|
49
|
+
<Panel.Content asChild>
|
|
50
|
+
<SearchResultStack results={allResults} />
|
|
51
|
+
</Panel.Content>
|
|
52
|
+
<Panel.Statusbar asChild>
|
|
53
|
+
<Toolbar.Root>
|
|
54
|
+
<SearchList.Input placeholder='Search...' autoFocus={autoFocus} />
|
|
55
|
+
</Toolbar.Root>
|
|
56
|
+
</Panel.Statusbar>
|
|
57
|
+
</Panel.Root>
|
|
58
|
+
</SearchList.Root>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import * as Effect from 'effect/Effect';
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { expect, userEvent, waitFor, within } from 'storybook/test';
|
|
9
|
+
|
|
10
|
+
import { Capability } from '@dxos/app-framework';
|
|
11
|
+
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
12
|
+
import { AppCapabilities } from '@dxos/app-toolkit';
|
|
13
|
+
import { ClientPlugin } from '@dxos/plugin-client/testing';
|
|
14
|
+
import { initializeIdentity } from '@dxos/plugin-client/testing';
|
|
15
|
+
import { StorybookPlugin, corePlugins } from '@dxos/plugin-testing';
|
|
16
|
+
import { random } from '@dxos/random';
|
|
17
|
+
import { useSpaces } from '@dxos/react-client/echo';
|
|
18
|
+
import { Dialog } from '@dxos/react-ui';
|
|
19
|
+
import { Loading, withLayout } from '@dxos/react-ui/testing';
|
|
20
|
+
import { createObjectFactory } from '@dxos/schema/testing';
|
|
21
|
+
import { Organization, Person } from '@dxos/types';
|
|
22
|
+
|
|
23
|
+
import { SearchContextProvider } from '#hooks';
|
|
24
|
+
import { translations } from '#translations';
|
|
25
|
+
|
|
26
|
+
import { SearchDialog } from './SearchDialog';
|
|
27
|
+
|
|
28
|
+
random.seed(0);
|
|
29
|
+
|
|
30
|
+
const DefaultStory = () => {
|
|
31
|
+
const spaces = useSpaces();
|
|
32
|
+
const space = spaces[spaces.length - 1];
|
|
33
|
+
if (!space) {
|
|
34
|
+
return <Loading />;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<SearchContextProvider>
|
|
39
|
+
<Dialog.Root defaultOpen>
|
|
40
|
+
<Dialog.Overlay>
|
|
41
|
+
<SearchDialog role='article' space={space} attendableId={space.id} pivotId='storybook' />
|
|
42
|
+
</Dialog.Overlay>
|
|
43
|
+
</Dialog.Root>
|
|
44
|
+
</SearchContextProvider>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const meta = {
|
|
49
|
+
title: 'plugins/plugin-search/containers/SearchDialog',
|
|
50
|
+
render: DefaultStory,
|
|
51
|
+
decorators: [
|
|
52
|
+
withLayout({ layout: 'fullscreen' }),
|
|
53
|
+
withPluginManager({
|
|
54
|
+
capabilities: [Capability.contributes(AppCapabilities.Translations, translations)],
|
|
55
|
+
plugins: [
|
|
56
|
+
...corePlugins(),
|
|
57
|
+
StorybookPlugin({}),
|
|
58
|
+
ClientPlugin({
|
|
59
|
+
types: [Organization.Organization, Person.Person],
|
|
60
|
+
onClientInitialized: ({ client }) =>
|
|
61
|
+
Effect.gen(function* () {
|
|
62
|
+
const { personalSpace } = yield* initializeIdentity(client);
|
|
63
|
+
|
|
64
|
+
const factory = createObjectFactory(personalSpace.db, random as any);
|
|
65
|
+
yield* Effect.promise(() =>
|
|
66
|
+
factory([
|
|
67
|
+
{ type: Organization.Organization, count: 10 },
|
|
68
|
+
{ type: Person.Person, count: 50 },
|
|
69
|
+
]),
|
|
70
|
+
);
|
|
71
|
+
}),
|
|
72
|
+
}),
|
|
73
|
+
],
|
|
74
|
+
}),
|
|
75
|
+
],
|
|
76
|
+
tags: ['test'],
|
|
77
|
+
parameters: {
|
|
78
|
+
layout: 'fullscreen',
|
|
79
|
+
},
|
|
80
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
81
|
+
|
|
82
|
+
export default meta;
|
|
83
|
+
|
|
84
|
+
type Story = StoryObj<typeof meta>;
|
|
85
|
+
|
|
86
|
+
export const Default: Story = {};
|
|
87
|
+
|
|
88
|
+
export const Test: Story = {
|
|
89
|
+
play: async () => {
|
|
90
|
+
const body = within(document.body);
|
|
91
|
+
|
|
92
|
+
// Wait for the dialog to render and the search input to appear.
|
|
93
|
+
const searchInput = await body.findByRole('textbox', undefined, { timeout: 10_000 });
|
|
94
|
+
await expect(searchInput).toBeInTheDocument();
|
|
95
|
+
|
|
96
|
+
// Type a search term that should match generated Person/Organization names.
|
|
97
|
+
await userEvent.type(searchInput, 'a');
|
|
98
|
+
|
|
99
|
+
// Wait for search results to appear as options in the listbox.
|
|
100
|
+
await waitFor(
|
|
101
|
+
async () => {
|
|
102
|
+
const options = body.queryAllByRole('option');
|
|
103
|
+
await expect(options.length).toBeGreaterThan(0);
|
|
104
|
+
},
|
|
105
|
+
{ timeout: 10_000 },
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
};
|
|
@@ -2,119 +2,92 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import React, { forwardRef, useCallback, useState } from 'react';
|
|
5
|
+
import React, { useCallback, useMemo, useState } from 'react';
|
|
7
6
|
|
|
8
7
|
import { useOperationInvoker } from '@dxos/app-framework/ui';
|
|
9
|
-
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { mx, osTranslations } from '@dxos/ui-theme';
|
|
8
|
+
import { getObjectPathFromObject, LayoutOperation } from '@dxos/app-toolkit';
|
|
9
|
+
import { type AppSurface } from '@dxos/app-toolkit/ui';
|
|
10
|
+
import { useLayout } from '@dxos/app-toolkit/ui';
|
|
11
|
+
import { Entity, Filter, Obj, Query } from '@dxos/echo';
|
|
12
|
+
import { useQuery } from '@dxos/react-client/echo';
|
|
13
|
+
import { Dialog, useTranslation } from '@dxos/react-ui';
|
|
14
|
+
import { SearchList } from '@dxos/react-ui-search';
|
|
15
|
+
import { Text } from '@dxos/schema';
|
|
18
16
|
|
|
19
|
-
import {
|
|
20
|
-
import { meta } from '
|
|
17
|
+
import { useGlobalSearch, useGlobalSearchResults } from '#hooks';
|
|
18
|
+
import { meta } from '#meta';
|
|
19
|
+
import { type SearchResult } from '#types';
|
|
21
20
|
|
|
22
|
-
type
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
21
|
+
export type SearchDialogProps = AppSurface.SpaceArticleProps<{
|
|
22
|
+
pivotId?: string;
|
|
23
|
+
}>;
|
|
26
24
|
|
|
27
|
-
const
|
|
25
|
+
export const SearchDialog = ({ space, pivotId: pivotIdProp }: SearchDialogProps) => {
|
|
28
26
|
const { t } = useTranslation(meta.id);
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
onSelect={handleSelect}
|
|
40
|
-
ref={forwardedRef}
|
|
41
|
-
/>
|
|
27
|
+
const { invokePromise } = useOperationInvoker();
|
|
28
|
+
const { setMatch } = useGlobalSearch();
|
|
29
|
+
const layout = useLayout();
|
|
30
|
+
const pivotId = pivotIdProp ?? layout.active[layout.active.length - 1];
|
|
31
|
+
const [query, setQuery] = useState<string>();
|
|
32
|
+
|
|
33
|
+
// TODO(burdon): Re-enable full-text search when indexer is available in all environments.
|
|
34
|
+
const objects = useQuery(
|
|
35
|
+
space?.db,
|
|
36
|
+
query === undefined ? Query.select(Filter.nothing()) : Query.select(Filter.not(Filter.type(Text.Text))),
|
|
42
37
|
);
|
|
43
|
-
});
|
|
44
38
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
39
|
+
const results = useGlobalSearchResults(objects);
|
|
40
|
+
const allResults = useMemo(() => results.filter(({ object }) => object && Entity.getLabel(object)), [results]);
|
|
48
41
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const [queryString, setQueryString] = useState('');
|
|
57
|
-
const client = useClient();
|
|
58
|
-
const dangerouslyLoadAllObjects = useQuery(client.spaces, Filter.everything());
|
|
59
|
-
const [pending, results] = useSearchResults(queryString, dangerouslyLoadAllObjects);
|
|
60
|
-
const resultObjects = Array.from(results.keys());
|
|
61
|
-
const { invokePromise } = useOperationInvoker();
|
|
42
|
+
const handleSearch = useCallback(
|
|
43
|
+
(text: string) => {
|
|
44
|
+
setQuery(text);
|
|
45
|
+
setMatch?.(text);
|
|
46
|
+
},
|
|
47
|
+
[setMatch],
|
|
48
|
+
);
|
|
62
49
|
|
|
63
50
|
const handleSelect = useCallback(
|
|
64
|
-
async (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// If node is already present in the active parts, scroll to it and close the dialog.
|
|
68
|
-
const index = layout.active.findIndex((id) => id === nodeId);
|
|
69
|
-
if (index !== -1) {
|
|
70
|
-
await invokePromise(LayoutOperation.ScrollIntoView, { subject: nodeId });
|
|
71
|
-
} else {
|
|
72
|
-
await invokePromise(LayoutOperation.Open, {
|
|
73
|
-
subject: [nodeId],
|
|
74
|
-
pivotId,
|
|
75
|
-
positioning: 'end',
|
|
76
|
-
});
|
|
51
|
+
async (result: SearchResult) => {
|
|
52
|
+
if (!result.object || !Obj.isObject(result.object)) {
|
|
53
|
+
return;
|
|
77
54
|
}
|
|
55
|
+
|
|
56
|
+
const qualifiedPath = getObjectPathFromObject(result.object);
|
|
57
|
+
await invokePromise(LayoutOperation.UpdateDialog, { state: false });
|
|
58
|
+
await invokePromise(LayoutOperation.Open, {
|
|
59
|
+
subject: [qualifiedPath],
|
|
60
|
+
pivotId,
|
|
61
|
+
positioning: 'end',
|
|
62
|
+
});
|
|
78
63
|
},
|
|
79
|
-
[pivotId, invokePromise
|
|
64
|
+
[pivotId, invokePromise],
|
|
80
65
|
);
|
|
81
66
|
|
|
82
|
-
const handleSearch = useCallback((query: string) => {
|
|
83
|
-
setQueryString(query);
|
|
84
|
-
}, []);
|
|
85
|
-
|
|
86
67
|
return (
|
|
87
68
|
<Dialog.Content>
|
|
88
|
-
<Dialog.
|
|
69
|
+
<Dialog.Header>
|
|
70
|
+
<Dialog.Title>{t('search-dialog.title')}</Dialog.Title>
|
|
71
|
+
<Dialog.Close asChild>
|
|
72
|
+
<Dialog.ActionIconButton action='close' />
|
|
73
|
+
</Dialog.Close>
|
|
74
|
+
</Dialog.Header>
|
|
89
75
|
<SearchList.Root onSearch={handleSearch}>
|
|
90
|
-
<SearchList.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
{closed.length > 0 && (
|
|
105
|
-
<h2 className={mx('my-1', 'text-description')}>{t('recently closed heading')}</h2>
|
|
106
|
-
)}
|
|
107
|
-
{closed.filter(Option.isSome).map((node) => (
|
|
108
|
-
<SearchListResult key={node.value.id} node={node.value} onSelect={handleSelect} />
|
|
109
|
-
))}
|
|
110
|
-
</>
|
|
111
|
-
)}
|
|
112
|
-
</SearchList.Viewport>
|
|
113
|
-
</SearchList.Content>
|
|
76
|
+
<SearchList.Input classNames='px-0' autoFocus placeholder={t('search.placeholder')} />
|
|
77
|
+
<SearchList.Viewport classNames='max-h-[24rem]'>
|
|
78
|
+
{allResults.map((result) => (
|
|
79
|
+
<SearchList.Item
|
|
80
|
+
key={result.id}
|
|
81
|
+
classNames='flex gap-2 items-center'
|
|
82
|
+
value={result.id}
|
|
83
|
+
label={result.label ?? (result.object ? Entity.getLabel(result.object) : undefined) ?? result.id}
|
|
84
|
+
icon={result.icon}
|
|
85
|
+
onSelect={() => void handleSelect(result)}
|
|
86
|
+
/>
|
|
87
|
+
))}
|
|
88
|
+
{query && allResults.length === 0 && <SearchList.Empty />}
|
|
89
|
+
</SearchList.Viewport>
|
|
114
90
|
</SearchList.Root>
|
|
115
|
-
<Dialog.Close asChild>
|
|
116
|
-
<Button variant='primary'>{t('close label', { ns: osTranslations })}</Button>
|
|
117
|
-
</Dialog.Close>
|
|
118
91
|
</Dialog.Content>
|
|
119
92
|
);
|
|
120
93
|
};
|
package/src/containers/index.ts
CHANGED
|
@@ -4,8 +4,5 @@
|
|
|
4
4
|
|
|
5
5
|
import { type ComponentType, lazy } from 'react';
|
|
6
6
|
|
|
7
|
-
export type { SearchDialogProps } from './SearchDialog/SearchDialog';
|
|
8
|
-
|
|
9
7
|
export const SearchDialog: ComponentType<any> = lazy(() => import('./SearchDialog'));
|
|
10
|
-
export const
|
|
11
|
-
export const SpaceMain: ComponentType<any> = lazy(() => import('./SpaceMain'));
|
|
8
|
+
export const SearchArticle: ComponentType<any> = lazy(() => import('./SearchArticle'));
|
package/src/hooks/sync.test.ts
CHANGED
|
@@ -7,11 +7,11 @@ import { describe, expect, test } from 'vitest';
|
|
|
7
7
|
import { Client } from '@dxos/client';
|
|
8
8
|
import { Filter, Obj } from '@dxos/echo';
|
|
9
9
|
import { TestSchema } from '@dxos/echo/testing';
|
|
10
|
-
import {
|
|
10
|
+
import { random } from '@dxos/random';
|
|
11
11
|
|
|
12
12
|
import { filterObjectsSync } from './sync';
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
random.seed(1);
|
|
15
15
|
|
|
16
16
|
// TODO(burdon): Reconcile with agent/minisearch.
|
|
17
17
|
|
|
@@ -26,8 +26,8 @@ describe('Search', () => {
|
|
|
26
26
|
const space = await client.spaces.create();
|
|
27
27
|
Array.from({ length: 20 }).map((_, i) => {
|
|
28
28
|
const content =
|
|
29
|
-
i === 10 ?
|
|
30
|
-
return space.db.add(Obj.make(TestSchema.Expando, { title:
|
|
29
|
+
i === 10 ? random.lorem.sentence() + ` ${match}}. ` + random.lorem.sentence() : random.lorem.sentences();
|
|
30
|
+
return space.db.add(Obj.make(TestSchema.Expando, { title: random.lorem.sentence(), content }));
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
const objects = await space.db.query(Filter.everything()).run();
|
package/src/hooks/sync.ts
CHANGED
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import type * as Schema from 'effect/Schema';
|
|
6
5
|
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
6
|
|
|
8
|
-
import {
|
|
7
|
+
import { Entity, Obj, Type } from '@dxos/echo';
|
|
9
8
|
import { Text } from '@dxos/schema';
|
|
10
9
|
|
|
11
|
-
import { type SearchResult } from '
|
|
10
|
+
import { type SearchResult } from '#types';
|
|
12
11
|
|
|
13
12
|
export const queryStringToMatch = (queryString?: string): RegExp | undefined => {
|
|
14
13
|
const trimmed = queryString?.trim();
|
|
@@ -16,8 +15,12 @@ export const queryStringToMatch = (queryString?: string): RegExp | undefined =>
|
|
|
16
15
|
};
|
|
17
16
|
|
|
18
17
|
// TODO(burdon): Type name registry linked to schema?
|
|
19
|
-
const getIcon = (
|
|
20
|
-
if (!
|
|
18
|
+
const getIcon = (type: Type.AnyEntity | undefined): string | undefined => {
|
|
19
|
+
if (!type) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const schema = Type.getSchema(type);
|
|
23
|
+
if (!SchemaAST.isTypeLiteral(schema.ast)) {
|
|
21
24
|
return undefined;
|
|
22
25
|
}
|
|
23
26
|
const keys = schema.ast.propertySignatures.map((p) => p.name);
|
|
@@ -46,7 +49,7 @@ export const filterObjectsSync = <T extends Entity.Unknown>(objects: T[], match?
|
|
|
46
49
|
|
|
47
50
|
return objects.reduce<SearchResult<T>[]>((results, object) => {
|
|
48
51
|
// TODO(burdon): Hack to ignore Text objects.
|
|
49
|
-
if (
|
|
52
|
+
if (Obj.instanceOf(Text.Text, object)) {
|
|
50
53
|
return results;
|
|
51
54
|
}
|
|
52
55
|
|
|
@@ -57,7 +60,7 @@ export const filterObjectsSync = <T extends Entity.Unknown>(objects: T[], match?
|
|
|
57
60
|
|
|
58
61
|
results.push({
|
|
59
62
|
id: object.id,
|
|
60
|
-
type: getIcon(
|
|
63
|
+
type: getIcon(Entity.getType(object)),
|
|
61
64
|
label,
|
|
62
65
|
match,
|
|
63
66
|
// TODO(burdon): Truncate.
|
|
@@ -107,7 +110,7 @@ const getKeys = <T extends Entity.Unknown>(object: T): string[] => {
|
|
|
107
110
|
export const mapObjectToTextFields = <T extends Entity.Unknown>(object: T): TextFields => {
|
|
108
111
|
return getKeys(object).reduce<TextFields>((fields, key) => {
|
|
109
112
|
const value = (object as any)[key];
|
|
110
|
-
if (typeof value === 'string' ||
|
|
113
|
+
if (typeof value === 'string' || Obj.instanceOf(Text.Text, value)) {
|
|
111
114
|
try {
|
|
112
115
|
fields[key] = String(value);
|
|
113
116
|
} catch {
|
|
@@ -6,14 +6,14 @@ import React, { type PropsWithChildren, createContext, useCallback, useContext,
|
|
|
6
6
|
|
|
7
7
|
import { raise } from '@dxos/debug';
|
|
8
8
|
import { type Entity } from '@dxos/echo';
|
|
9
|
-
import { GlobalFilterProvider } from '@dxos/react-ui-
|
|
9
|
+
import { GlobalFilterProvider } from '@dxos/react-ui-search';
|
|
10
10
|
|
|
11
|
-
import { type SearchResult } from '
|
|
11
|
+
import { type SearchResult } from '#types';
|
|
12
12
|
|
|
13
13
|
import { filterObjectsSync, queryStringToMatch } from './sync';
|
|
14
14
|
|
|
15
15
|
// Re-export for backward compatibility.
|
|
16
|
-
export { useGlobalFilteredObjects, GlobalFilterProvider } from '@dxos/react-ui-
|
|
16
|
+
export { useGlobalFilteredObjects, GlobalFilterProvider } from '@dxos/react-ui-search';
|
|
17
17
|
|
|
18
18
|
type SearchContextType = {
|
|
19
19
|
match?: RegExp;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { yieldOrContinue } from 'main-thread-scheduling';
|
|
6
6
|
import { useEffect, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import { type Entity } from '@dxos/echo';
|
|
8
|
+
import { type Entity, Obj } from '@dxos/echo';
|
|
9
9
|
import { Text } from '@dxos/schema';
|
|
10
10
|
|
|
11
11
|
// TODO(thure): Deprecate search-sync, move still-relevant utilities elsewhere (here, probably).
|
|
@@ -22,7 +22,7 @@ export const filterObjects = async <T extends Entity.Unknown>(
|
|
|
22
22
|
|
|
23
23
|
await Promise.all(
|
|
24
24
|
objects
|
|
25
|
-
.filter((object) => !(
|
|
25
|
+
.filter((object) => !Obj.instanceOf(Text.Text, object))
|
|
26
26
|
.map(async (object) => {
|
|
27
27
|
await yieldOrContinue('interactive');
|
|
28
28
|
const fields = mapObjectToTextFields<T>(object);
|
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
import { useCallback, useState } from 'react';
|
|
6
6
|
|
|
7
7
|
import { EXA_API_KEY } from '@dxos/ai/testing';
|
|
8
|
-
import {
|
|
8
|
+
import { Entity, Type } from '@dxos/echo';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
10
10
|
import { getIconAnnotation } from '@dxos/schema';
|
|
11
11
|
import { TestSchema } from '@dxos/schema/testing';
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
import { type SearchResult } from '../types';
|
|
13
|
+
import { type SearchResult } from '#types';
|
|
15
14
|
|
|
15
|
+
import { search } from '../search';
|
|
16
16
|
import { getStringProperty } from './sync';
|
|
17
17
|
|
|
18
18
|
export type UseWebSearchProps = {
|
|
@@ -39,18 +39,18 @@ export const useWebSearch = ({ query, context }: UseWebSearchProps): UseWebSearc
|
|
|
39
39
|
const results = await search({
|
|
40
40
|
query,
|
|
41
41
|
context,
|
|
42
|
-
|
|
42
|
+
types: [TestSchema.Person, TestSchema.Project, TestSchema.Organization], // TODO(burdon): ???
|
|
43
43
|
exaApiKey: EXA_API_KEY,
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
const mappedResults = results.data.map((result): SearchResult => {
|
|
47
|
-
const schema =
|
|
47
|
+
const schema = Entity.getType(result);
|
|
48
48
|
return {
|
|
49
49
|
id: result.id,
|
|
50
50
|
label: getStringProperty(result, ['name', 'title', 'label']),
|
|
51
51
|
snippet: getStringProperty(result, ['description', 'content', 'website', 'email']),
|
|
52
52
|
object: result,
|
|
53
|
-
icon: schema
|
|
53
|
+
icon: schema ? getIconAnnotation(Type.getSchema(schema)) : undefined,
|
|
54
54
|
};
|
|
55
55
|
});
|
|
56
56
|
|
package/src/index.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
// TODO(wittjosiah): Hooks should not be exported from the plugin package at all.
|
|
6
|
+
// Either refactor callers to not need them or factor them out to a shared package.
|
|
5
7
|
export * from './hooks';
|
|
6
8
|
export * from './meta';
|
|
7
9
|
export * from './types';
|
|
8
|
-
|
|
9
|
-
export * from './SearchPlugin';
|
package/src/meta.ts
CHANGED
|
@@ -2,19 +2,37 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Plugin } from '@dxos/app-framework';
|
|
6
|
+
import { DXN } from '@dxos/keys';
|
|
6
7
|
import { trim } from '@dxos/util';
|
|
7
8
|
|
|
8
|
-
export const meta
|
|
9
|
-
|
|
9
|
+
export const meta = Plugin.makeMeta({
|
|
10
|
+
key: DXN.make('org.dxos.plugin.search'),
|
|
10
11
|
name: 'Search',
|
|
12
|
+
author: 'DXOS',
|
|
11
13
|
description: trim`
|
|
12
|
-
Full-text search engine for finding content across all spaces and object types.
|
|
13
|
-
|
|
14
|
+
Full-text search engine for finding content across all spaces and object types in DXOS Composer.
|
|
15
|
+
The plugin provides a global command-palette style dialog (invoked via the OpenSearch operation)
|
|
16
|
+
that debounces user input and matches it against every field of every ECHO object in the active
|
|
17
|
+
space, returning ranked results with field-level match context.
|
|
18
|
+
|
|
19
|
+
A persistent SearchArticle panel can be pinned in the deck companion sidebar so the query stays
|
|
20
|
+
visible while navigating documents. Both surfaces share a SearchContextProvider that drives the
|
|
21
|
+
GlobalFilterProvider, allowing any view that uses useGlobalFilteredObjects to automatically show
|
|
22
|
+
only the objects matching the current query.
|
|
23
|
+
|
|
24
|
+
Web search is available through the Exa integration: when an API key is configured, the plugin
|
|
25
|
+
can fetch and rank external web results alongside local objects. The results are structured with
|
|
26
|
+
URL, title, and snippet fields ready for use in Composer documents.
|
|
27
|
+
|
|
28
|
+
All search operations are registered through the standard DXOS operations system and can be
|
|
29
|
+
wired into keyboard shortcuts, command palettes, and AI assistants via the OperationHandlerSet.
|
|
14
30
|
`,
|
|
15
31
|
icon: 'ph--magnifying-glass--regular',
|
|
32
|
+
iconHue: 'rose',
|
|
16
33
|
source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-search',
|
|
34
|
+
spec: 'PLUGIN.mdl',
|
|
17
35
|
tags: ['labs'],
|
|
18
|
-
};
|
|
36
|
+
});
|
|
19
37
|
|
|
20
|
-
export const SEARCH_RESULT = `${meta.id}
|
|
38
|
+
export const SEARCH_RESULT = `${meta.id}.result`;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
8
|
+
import { Operation } from '@dxos/compute';
|
|
9
|
+
|
|
10
|
+
import { SEARCH_DIALOG } from '../constants';
|
|
11
|
+
import { SearchOperation } from '../types';
|
|
12
|
+
|
|
13
|
+
const handler: Operation.WithHandler<typeof SearchOperation.OpenSearch> = SearchOperation.OpenSearch.pipe(
|
|
14
|
+
Operation.withHandler(
|
|
15
|
+
Effect.fnUntraced(function* () {
|
|
16
|
+
yield* Operation.invoke(LayoutOperation.UpdateDialog, {
|
|
17
|
+
subject: SEARCH_DIALOG,
|
|
18
|
+
blockAlign: 'start',
|
|
19
|
+
});
|
|
20
|
+
}),
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export default handler;
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Plugin } from '@dxos/app-framework';
|
|
6
|
+
|
|
7
|
+
import { meta } from './meta';
|
|
8
|
+
|
|
9
|
+
export const SearchPlugin = Plugin.lazy(meta, () => import('#plugin'));
|
|
10
|
+
|
|
11
|
+
export { SearchOperationHandlerSet } from './operations';
|