@dxos/plugin-search 0.8.4-main.d05673bc65 → 0.8.4-main.dfabb4ec29

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.
Files changed (169) hide show
  1. package/dist/lib/neutral/SearchArticle-DBLOFRIL.mjs +51 -0
  2. package/dist/lib/neutral/SearchArticle-DBLOFRIL.mjs.map +7 -0
  3. package/dist/lib/neutral/SearchDialog-55XQUL6R.mjs +74 -0
  4. package/dist/lib/neutral/SearchDialog-55XQUL6R.mjs.map +7 -0
  5. package/dist/lib/neutral/SearchPlugin.mjs +23 -0
  6. package/dist/lib/neutral/SearchPlugin.mjs.map +7 -0
  7. package/dist/lib/{browser/app-graph-builder-FHVM5QV7.mjs → neutral/app-graph-builder-B3W3UWHV.mjs} +22 -29
  8. package/dist/lib/neutral/app-graph-builder-B3W3UWHV.mjs.map +7 -0
  9. package/dist/lib/neutral/capabilities/index.mjs +13 -0
  10. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  11. package/dist/lib/neutral/chunk-EBXWOGAD.mjs +26 -0
  12. package/dist/lib/neutral/chunk-EBXWOGAD.mjs.map +7 -0
  13. package/dist/lib/neutral/chunk-IXRPYAX5.mjs +8 -0
  14. package/dist/lib/neutral/chunk-IXRPYAX5.mjs.map +7 -0
  15. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  16. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
  17. package/dist/lib/{browser/chunk-NB4JZIOE.mjs → neutral/chunk-LUDADYMK.mjs} +9 -14
  18. package/dist/lib/neutral/chunk-LUDADYMK.mjs.map +7 -0
  19. package/dist/lib/neutral/chunk-NAKPT2JM.mjs +8 -0
  20. package/dist/lib/neutral/chunk-NAKPT2JM.mjs.map +7 -0
  21. package/dist/lib/{browser/chunk-PZ67KFRU.mjs → neutral/chunk-NC3YYBHS.mjs} +1 -1
  22. package/dist/lib/{browser/chunk-PZ67KFRU.mjs.map → neutral/chunk-NC3YYBHS.mjs.map} +2 -2
  23. package/dist/lib/neutral/components/index.mjs +97 -0
  24. package/dist/lib/neutral/components/index.mjs.map +7 -0
  25. package/dist/lib/neutral/containers/index.mjs +11 -0
  26. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  27. package/dist/lib/neutral/hooks/index.mjs +30 -0
  28. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  29. package/dist/lib/neutral/index.mjs +40 -0
  30. package/dist/lib/neutral/index.mjs.map +7 -0
  31. package/dist/lib/neutral/meta.json +1 -0
  32. package/dist/lib/neutral/meta.mjs +10 -0
  33. package/dist/lib/neutral/meta.mjs.map +7 -0
  34. package/dist/lib/neutral/open-search-DY6ZGVKR.mjs +23 -0
  35. package/dist/lib/neutral/open-search-DY6ZGVKR.mjs.map +7 -0
  36. package/dist/lib/neutral/operation-handler-DY624P2R.mjs +13 -0
  37. package/dist/lib/neutral/operation-handler-DY624P2R.mjs.map +7 -0
  38. package/dist/lib/neutral/operations/index.mjs +8 -0
  39. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  40. package/dist/lib/neutral/plugin.mjs +16 -0
  41. package/dist/lib/neutral/plugin.mjs.map +7 -0
  42. package/dist/lib/{browser/react-surface-IPH2UPKO.mjs → neutral/react-surface-ER7Q4W6Y.mjs} +24 -23
  43. package/dist/lib/neutral/react-surface-ER7Q4W6Y.mjs.map +7 -0
  44. package/dist/lib/neutral/translations.mjs +25 -0
  45. package/dist/lib/neutral/translations.mjs.map +7 -0
  46. package/dist/lib/neutral/types/index.mjs +8 -0
  47. package/dist/lib/neutral/types/index.mjs.map +7 -0
  48. package/dist/types/src/SearchPlugin.d.ts +1 -0
  49. package/dist/types/src/SearchPlugin.d.ts.map +1 -1
  50. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  51. package/dist/types/src/capabilities/index.d.ts +5 -3
  52. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  53. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  54. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  55. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  56. package/dist/types/src/components/SearchResultStack/SearchResultStack.d.ts +9 -0
  57. package/dist/types/src/components/SearchResultStack/SearchResultStack.d.ts.map +1 -0
  58. package/dist/types/src/components/SearchResultStack/index.d.ts +2 -0
  59. package/dist/types/src/components/SearchResultStack/index.d.ts.map +1 -0
  60. package/dist/types/src/components/index.d.ts +1 -0
  61. package/dist/types/src/components/index.d.ts.map +1 -1
  62. package/dist/types/src/containers/SearchArticle/SearchArticle.d.ts +4 -0
  63. package/dist/types/src/containers/SearchArticle/SearchArticle.d.ts.map +1 -0
  64. package/dist/types/src/containers/SearchArticle/SearchArticle.stories.d.ts +14 -0
  65. package/dist/types/src/containers/SearchArticle/SearchArticle.stories.d.ts.map +1 -0
  66. package/dist/types/src/containers/SearchArticle/index.d.ts +2 -0
  67. package/dist/types/src/containers/SearchArticle/index.d.ts.map +1 -0
  68. package/dist/types/src/containers/SearchDialog/SearchDialog.d.ts +5 -4
  69. package/dist/types/src/containers/SearchDialog/SearchDialog.d.ts.map +1 -1
  70. package/dist/types/src/containers/SearchDialog/SearchDialog.stories.d.ts +16 -0
  71. package/dist/types/src/containers/SearchDialog/SearchDialog.stories.d.ts.map +1 -0
  72. package/dist/types/src/containers/SearchDialog/index.d.ts +0 -1
  73. package/dist/types/src/containers/SearchDialog/index.d.ts.map +1 -1
  74. package/dist/types/src/containers/index.d.ts +1 -2
  75. package/dist/types/src/containers/index.d.ts.map +1 -1
  76. package/dist/types/src/hooks/sync.d.ts +2 -2
  77. package/dist/types/src/hooks/sync.d.ts.map +1 -1
  78. package/dist/types/src/hooks/useGlobalSearch.d.ts +2 -2
  79. package/dist/types/src/hooks/useGlobalSearch.d.ts.map +1 -1
  80. package/dist/types/src/hooks/useSearchResults.d.ts.map +1 -1
  81. package/dist/types/src/hooks/useWebSearch.d.ts +1 -1
  82. package/dist/types/src/hooks/useWebSearch.d.ts.map +1 -1
  83. package/dist/types/src/index.d.ts +0 -1
  84. package/dist/types/src/index.d.ts.map +1 -1
  85. package/dist/types/src/operations/index.d.ts +3 -0
  86. package/dist/types/src/operations/index.d.ts.map +1 -0
  87. package/dist/types/src/operations/open-search.d.ts +5 -0
  88. package/dist/types/src/operations/open-search.d.ts.map +1 -0
  89. package/dist/types/src/plugin.d.ts +4 -0
  90. package/dist/types/src/plugin.d.ts.map +1 -0
  91. package/dist/types/src/search/exa.d.ts.map +1 -1
  92. package/dist/types/src/translations.d.ts +15 -10
  93. package/dist/types/src/translations.d.ts.map +1 -1
  94. package/dist/types/src/types/SearchOperation.d.ts +3 -0
  95. package/dist/types/src/types/SearchOperation.d.ts.map +1 -0
  96. package/dist/types/src/types/index.d.ts +1 -1
  97. package/dist/types/src/types/index.d.ts.map +1 -1
  98. package/dist/types/src/types/search.d.ts +1 -16
  99. package/dist/types/src/types/search.d.ts.map +1 -1
  100. package/dist/types/tsconfig.tsbuildinfo +1 -1
  101. package/package.json +98 -41
  102. package/src/SearchPlugin.tsx +6 -22
  103. package/src/capabilities/{app-graph-builder/app-graph-builder.ts → app-graph-builder.ts} +13 -17
  104. package/src/capabilities/index.ts +9 -3
  105. package/src/capabilities/operation-handler.ts +16 -0
  106. package/src/capabilities/{react-surface/react-surface.tsx → react-surface.tsx} +24 -14
  107. package/src/components/SearchResultStack/SearchResultStack.tsx +118 -0
  108. package/src/components/SearchResultStack/index.ts +5 -0
  109. package/src/components/index.ts +2 -0
  110. package/src/constants.ts +1 -1
  111. package/src/containers/SearchArticle/SearchArticle.stories.tsx +79 -0
  112. package/src/containers/SearchArticle/SearchArticle.tsx +60 -0
  113. package/src/containers/SearchArticle/index.ts +5 -0
  114. package/src/containers/SearchDialog/SearchDialog.stories.tsx +108 -0
  115. package/src/containers/SearchDialog/SearchDialog.tsx +66 -93
  116. package/src/containers/SearchDialog/index.ts +0 -1
  117. package/src/containers/index.ts +1 -3
  118. package/src/hooks/sync.test.ts +4 -4
  119. package/src/hooks/sync.ts +3 -3
  120. package/src/hooks/useGlobalSearch.tsx +3 -3
  121. package/src/hooks/useWebSearch.ts +4 -4
  122. package/src/index.ts +2 -2
  123. package/src/operations/index.ts +7 -0
  124. package/src/operations/open-search.ts +24 -0
  125. package/src/plugin.ts +11 -0
  126. package/src/search/exa.ts +1 -1
  127. package/src/translations.ts +10 -9
  128. package/src/types/SearchOperation.ts +19 -0
  129. package/src/types/index.ts +2 -1
  130. package/src/types/search.ts +1 -19
  131. package/dist/lib/browser/SearchDialog-KJL6C2OF.mjs +0 -108
  132. package/dist/lib/browser/SearchDialog-KJL6C2OF.mjs.map +0 -7
  133. package/dist/lib/browser/SearchMain-JCNONOF3.mjs +0 -100
  134. package/dist/lib/browser/SearchMain-JCNONOF3.mjs.map +0 -7
  135. package/dist/lib/browser/app-graph-builder-FHVM5QV7.mjs.map +0 -7
  136. package/dist/lib/browser/chunk-I3633KKK.mjs +0 -26
  137. package/dist/lib/browser/chunk-I3633KKK.mjs.map +0 -7
  138. package/dist/lib/browser/chunk-NB4JZIOE.mjs.map +0 -7
  139. package/dist/lib/browser/index.mjs +0 -105
  140. package/dist/lib/browser/index.mjs.map +0 -7
  141. package/dist/lib/browser/meta.json +0 -1
  142. package/dist/lib/browser/operation-resolver-H4X6BNWR.mjs +0 -26
  143. package/dist/lib/browser/operation-resolver-H4X6BNWR.mjs.map +0 -7
  144. package/dist/lib/browser/react-surface-IPH2UPKO.mjs.map +0 -7
  145. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +0 -1
  146. package/dist/types/src/capabilities/app-graph-builder/index.d.ts +0 -3
  147. package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +0 -1
  148. package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
  149. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
  150. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
  151. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
  152. package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
  153. package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
  154. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
  155. package/dist/types/src/containers/SearchMain/SearchMain.d.ts +0 -6
  156. package/dist/types/src/containers/SearchMain/SearchMain.d.ts.map +0 -1
  157. package/dist/types/src/containers/SearchMain/index.d.ts +0 -3
  158. package/dist/types/src/containers/SearchMain/index.d.ts.map +0 -1
  159. package/dist/types/src/types/action.d.ts +0 -16
  160. package/dist/types/src/types/action.d.ts.map +0 -1
  161. package/src/capabilities/app-graph-builder/index.ts +0 -7
  162. package/src/capabilities/operation-resolver/index.ts +0 -7
  163. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -24
  164. package/src/capabilities/react-surface/index.ts +0 -7
  165. package/src/containers/SearchMain/SearchMain.tsx +0 -103
  166. package/src/containers/SearchMain/index.ts +0 -7
  167. package/src/types/action.tsx +0 -18
  168. /package/dist/types/src/capabilities/{app-graph-builder/app-graph-builder.d.ts → app-graph-builder.d.ts} +0 -0
  169. /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, Query } from '@dxos/echo';
9
+ import { Filter, 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,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export { SearchArticle as default } from './SearchArticle';
@@ -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/plugin';
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 * as Option from 'effect/Option';
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 { useAppGraph, useLayout } from '@dxos/app-toolkit/ui';
11
- import { Obj } from '@dxos/echo';
12
- import { Graph, type Node } from '@dxos/plugin-graph';
13
- import { useClient } from '@dxos/react-client';
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, Obj, Query } from '@dxos/echo';
14
12
  import { Filter, useQuery } from '@dxos/react-client/echo';
15
- import { Button, Dialog, toLocalizedString, useTranslation } from '@dxos/react-ui';
16
- import { SearchList } from '@dxos/react-ui-searchlist';
17
- import { mx, osTranslations } from '@dxos/ui-theme';
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 { useSearchResults } from '../../hooks';
20
- import { meta } from '../../meta';
17
+ import { useGlobalSearch, useGlobalSearchResults } from '#hooks';
18
+ import { meta } from '#meta';
19
+ import { type SearchResult } from '#types';
21
20
 
22
- type SearchListResultProps = {
23
- node: Node.Node;
24
- onSelect?: (nodeId: string) => void;
25
- };
21
+ export type SearchDialogProps = AppSurface.SpaceArticleProps<{
22
+ pivotId?: string;
23
+ }>;
26
24
 
27
- const SearchListResult = forwardRef<HTMLDivElement, SearchListResultProps>(({ node, onSelect }, forwardedRef) => {
25
+ export const SearchDialog = ({ space, pivotId: pivotIdProp }: SearchDialogProps) => {
28
26
  const { t } = useTranslation(meta.id);
29
- const label = toLocalizedString(node?.properties.label ?? 'never', t);
30
- const handleSelect = useCallback(() => {
31
- onSelect?.(node!.id);
32
- }, [node, onSelect]);
33
- return (
34
- <SearchList.Item
35
- value={node!.id}
36
- label={label}
37
- icon={node?.properties.icon}
38
- classNames='flex gap-2 items-center px-2'
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
- export type SearchDialogProps = {
46
- pivotId: string;
47
- };
39
+ const results = useGlobalSearchResults(objects);
40
+ const allResults = useMemo(() => results.filter(({ object }) => object && Entity.getLabel(object)), [results]);
48
41
 
49
- export const SearchDialog = ({ pivotId }: SearchDialogProps) => {
50
- const { t } = useTranslation(meta.id);
51
- const { graph } = useAppGraph();
52
- const layout = useLayout();
53
- const closed = (Array.isArray(layout.inactive) ? layout.inactive : [layout.inactive])
54
- .map((id) => (graph ? Graph.getNode(graph, id) : Option.none()))
55
- .filter(Boolean);
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 (nodeId: string) => {
65
- await invokePromise(LayoutOperation.UpdateDialog, { state: false });
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, layout],
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.Title>{t('search dialog title')}</Dialog.Title>
69
+ <Dialog.Header>
70
+ <Dialog.Title>{t('search-dialog.title')}</Dialog.Title>
71
+ <Dialog.Close asChild>
72
+ <Dialog.CloseIconButton />
73
+ </Dialog.Close>
74
+ </Dialog.Header>
89
75
  <SearchList.Root onSearch={handleSearch}>
90
- <SearchList.Content classNames='max-h-[24rem] overflow-auto'>
91
- <SearchList.Input placeholder={t('search placeholder')} />
92
- <SearchList.Viewport>
93
- {queryString.length > 0 ? (
94
- resultObjects.length > 0 ? (
95
- resultObjects
96
- .map((object) => Graph.getNode(graph, Obj.getDXN(object).toString()))
97
- .filter(Option.isSome)
98
- .map((node) => <SearchListResult key={node.value.id} node={node.value} onSelect={handleSelect} />)
99
- ) : (
100
- <p className='px-1'>{t(pending ? 'pending results message' : 'empty results message')}</p>
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
  };
@@ -2,5 +2,4 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export { SearchDialog, type SearchDialogProps } from './SearchDialog';
6
5
  export { SearchDialog as default } from './SearchDialog';
@@ -4,7 +4,5 @@
4
4
 
5
5
  import { type ComponentType, lazy } from 'react';
6
6
 
7
- export type { SearchDialogProps } from './SearchDialog';
8
-
9
7
  export const SearchDialog: ComponentType<any> = lazy(() => import('./SearchDialog'));
10
- export const SearchMain: ComponentType<any> = lazy(() => import('./SearchMain'));
8
+ export const SearchArticle: ComponentType<any> = lazy(() => import('./SearchArticle'));
@@ -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 { faker } from '@dxos/random';
10
+ import { random } from '@dxos/random';
11
11
 
12
12
  import { filterObjectsSync } from './sync';
13
13
 
14
- faker.seed(1);
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 ? faker.lorem.sentence() + ` ${match}}. ` + faker.lorem.sentence() : faker.lorem.sentences();
30
- return space.db.add(Obj.make(TestSchema.Expando, { title: faker.lorem.sentence(), content }));
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
@@ -5,10 +5,10 @@
5
5
  import type * as Schema from 'effect/Schema';
6
6
  import * as SchemaAST from 'effect/SchemaAST';
7
7
 
8
- import { type Entity, Obj } from '@dxos/echo';
8
+ import { Entity, Obj } from '@dxos/echo';
9
9
  import { Text } from '@dxos/schema';
10
10
 
11
- import { type SearchResult } from '../types';
11
+ import { type SearchResult } from '#types';
12
12
 
13
13
  export const queryStringToMatch = (queryString?: string): RegExp | undefined => {
14
14
  const trimmed = queryString?.trim();
@@ -57,7 +57,7 @@ export const filterObjectsSync = <T extends Entity.Unknown>(objects: T[], match?
57
57
 
58
58
  results.push({
59
59
  id: object.id,
60
- type: getIcon(Obj.getSchema(object)),
60
+ type: getIcon(Entity.getSchema(object)),
61
61
  label,
62
62
  match,
63
63
  // TODO(burdon): Truncate.
@@ -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-searchlist';
9
+ import { GlobalFilterProvider } from '@dxos/react-ui-search';
10
10
 
11
- import { type SearchResult } from '../types';
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-searchlist';
16
+ export { useGlobalFilteredObjects, GlobalFilterProvider } from '@dxos/react-ui-search';
17
17
 
18
18
  type SearchContextType = {
19
19
  match?: RegExp;
@@ -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 { Obj } from '@dxos/echo';
8
+ import { Entity } 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 { search } from '../search';
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 = {
@@ -44,7 +44,7 @@ export const useWebSearch = ({ query, context }: UseWebSearchProps): UseWebSearc
44
44
  });
45
45
 
46
46
  const mappedResults = results.data.map((result): SearchResult => {
47
- const schema = Obj.getSchema(result);
47
+ const schema = Entity.getSchema(result);
48
48
  return {
49
49
  id: result.id,
50
50
  label: getStringProperty(result, ['name', 'title', 'label']),
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';
@@ -0,0 +1,7 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { OperationHandlerSet } from '@dxos/compute';
6
+
7
+ export const SearchOperationHandlerSet = OperationHandlerSet.lazy(() => import('./open-search'));
@@ -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';
package/src/search/exa.ts CHANGED
@@ -9,8 +9,8 @@ import * as Schema from 'effect/Schema';
9
9
  import * as SchemaAST from 'effect/SchemaAST';
10
10
 
11
11
  import { type Entity, Key, Obj, type Type } from '@dxos/echo';
12
- import { ReferenceAnnotationId } from '@dxos/echo/internal';
13
12
  import { isEncodedReference } from '@dxos/echo-protocol';
13
+ import { ReferenceAnnotationId } from '@dxos/echo/internal';
14
14
  import { mapAst } from '@dxos/effect';
15
15
  import { deepMapValues, trim } from '@dxos/util';
16
16
 
@@ -3,21 +3,22 @@
3
3
  //
4
4
 
5
5
  import { type Resource } from '@dxos/react-ui';
6
+ import { translations as searchTranslations } from '@dxos/react-ui-search/translations';
6
7
 
7
- import { meta } from './meta';
8
+ import { meta } from '#meta';
8
9
 
9
10
  export const translations = [
11
+ ...searchTranslations,
10
12
  {
11
13
  'en-US': {
12
14
  [meta.id]: {
13
- 'plugin name': 'Search',
14
- 'search action label': 'Search spaces',
15
- 'search placeholder': 'Search…',
16
- 'search dialog title': 'Open',
17
- 'recently closed heading': 'Recently closed',
18
- 'pending results message': 'Searching…',
19
- 'empty results message': 'No matching objects',
20
- 'search label': 'Search',
15
+ 'plugin.name': 'Search',
16
+ 'search-action.label': 'Search spaces',
17
+ 'search.placeholder': 'Search…',
18
+ 'search-dialog.title': 'Search',
19
+ 'recently-closed.heading': 'Recently closed',
20
+ 'pending-results.message': 'Searching…',
21
+ 'search.label': 'Search',
21
22
  },
22
23
  },
23
24
  },
@@ -0,0 +1,19 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ // @import-as-namespace
6
+
7
+ import * as Schema from 'effect/Schema';
8
+
9
+ import { Operation } from '@dxos/compute';
10
+
11
+ import { meta } from '#meta';
12
+
13
+ const SEARCH_OPERATION = `${meta.id}.operation`;
14
+
15
+ export const OpenSearch = Operation.make({
16
+ meta: { key: `${SEARCH_OPERATION}.open-search`, name: 'Open Search' },
17
+ input: Schema.Void,
18
+ output: Schema.Void,
19
+ });
@@ -2,5 +2,6 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './action';
6
5
  export type * from './search';
6
+
7
+ export * as SearchOperation from './SearchOperation';
@@ -2,22 +2,4 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import type { Entity } from '@dxos/echo';
6
-
7
- export type SearchResult<T extends Entity.Unknown = Entity.Unknown> = {
8
- id: string;
9
-
10
- /**
11
- * Type kind: "contact", "project", "org", etc.
12
- */
13
- type?: string;
14
- label?: string;
15
- match?: RegExp;
16
- snippet?: string;
17
-
18
- /**
19
- * Icon id.
20
- */
21
- icon?: string;
22
- object?: T;
23
- };
5
+ export type { SearchResult } from '@dxos/react-ui-search';