@dxos/plugin-search 0.8.4-main.5ad4a44 → 0.8.4-main.66e292d
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/dist/lib/browser/{app-graph-builder-YME7SN7O.mjs → app-graph-builder-BFLYNCSJ.mjs} +9 -8
- package/dist/lib/browser/app-graph-builder-BFLYNCSJ.mjs.map +7 -0
- package/dist/lib/browser/{chunk-5N6LDSFF.mjs → chunk-GN2FSWTM.mjs} +6 -2
- package/dist/lib/browser/chunk-GN2FSWTM.mjs.map +7 -0
- package/dist/lib/browser/{chunk-WN3TVQLV.mjs → chunk-MBISM5WN.mjs} +108 -7
- package/dist/lib/browser/chunk-MBISM5WN.mjs.map +7 -0
- package/dist/lib/browser/{chunk-YJGHPWUO.mjs → chunk-TCVYYI44.mjs} +2 -2
- package/dist/lib/browser/index.mjs +11 -9
- package/dist/lib/browser/index.mjs.map +2 -2
- package/dist/lib/browser/{intent-resolver-XNVR4XRX.mjs → intent-resolver-UYQA6UUQ.mjs} +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/react-surface-D7STUIQS.mjs +306 -0
- package/dist/lib/browser/react-surface-D7STUIQS.mjs.map +7 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/components/Search.stories.d.ts +1 -4
- package/dist/types/src/components/Search.stories.d.ts.map +1 -1
- package/dist/types/src/components/SearchDialog.d.ts.map +1 -1
- package/dist/types/src/components/SearchMain.d.ts +4 -5
- package/dist/types/src/components/SearchMain.d.ts.map +1 -1
- package/dist/types/src/components/SearchResults/SearchResults.d.ts +9 -9
- package/dist/types/src/components/SearchResults/SearchResults.d.ts.map +1 -1
- package/dist/types/src/components/SearchResults/SearchResults.stories.d.ts +1 -1
- package/dist/types/src/components/SearchResults/SearchResults.stories.d.ts.map +1 -1
- package/dist/types/src/components/Searchbar/Searchbar.d.ts +5 -9
- package/dist/types/src/components/Searchbar/Searchbar.d.ts.map +1 -1
- package/dist/types/src/components/Searchbar/Searchbar.stories.d.ts +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +31 -30
- package/src/capabilities/app-graph-builder.ts +6 -5
- package/src/capabilities/react-surface.tsx +0 -3
- package/src/components/Search.stories.tsx +3 -6
- package/src/components/SearchDialog.tsx +18 -6
- package/src/components/SearchMain.tsx +15 -24
- package/src/components/SearchResults/SearchResults.stories.tsx +3 -2
- package/src/components/SearchResults/SearchResults.tsx +46 -49
- package/src/components/Searchbar/Searchbar.tsx +39 -44
- package/src/hooks/index.ts +2 -0
- package/src/hooks/sync.ts +3 -3
- package/src/hooks/useSearchResults.ts +2 -2
- package/src/hooks/useWebSearch.ts +2 -2
- package/src/meta.ts +5 -1
- package/src/search/exa.test.ts +9 -11
- package/src/search/exa.ts +2 -2
- package/src/translations.ts +1 -1
- package/dist/lib/browser/app-graph-builder-YME7SN7O.mjs.map +0 -7
- package/dist/lib/browser/chunk-5N6LDSFF.mjs.map +0 -7
- package/dist/lib/browser/chunk-WN3TVQLV.mjs.map +0 -7
- package/dist/lib/browser/react-surface-YOGLADC4.mjs +0 -470
- package/dist/lib/browser/react-surface-YOGLADC4.mjs.map +0 -7
- /package/dist/lib/browser/{chunk-YJGHPWUO.mjs.map → chunk-TCVYYI44.mjs.map} +0 -0
- /package/dist/lib/browser/{intent-resolver-XNVR4XRX.mjs.map → intent-resolver-UYQA6UUQ.mjs.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/plugin-search",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.66e292d",
|
|
4
4
|
"description": "Braneframe sketch plugin",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -27,49 +27,50 @@
|
|
|
27
27
|
"@preact-signals/safe-react": "^0.9.0",
|
|
28
28
|
"exa-js": "^1.5.12",
|
|
29
29
|
"main-thread-scheduling": "^14.1.1",
|
|
30
|
-
"@dxos/ai": "0.8.4-main.
|
|
31
|
-
"@dxos/app-framework": "0.8.4-main.
|
|
32
|
-
"@dxos/assistant": "0.8.4-main.
|
|
33
|
-
"@dxos/
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/live-object": "0.8.4-main.
|
|
39
|
-
"@dxos/log": "0.8.4-main.
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/plugin-deck": "0.8.4-main.
|
|
42
|
-
"@dxos/plugin-
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/react-
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
-
"@dxos/react-ui-searchlist": "0.8.4-main.
|
|
49
|
-
"@dxos/
|
|
30
|
+
"@dxos/ai": "0.8.4-main.66e292d",
|
|
31
|
+
"@dxos/app-framework": "0.8.4-main.66e292d",
|
|
32
|
+
"@dxos/assistant": "0.8.4-main.66e292d",
|
|
33
|
+
"@dxos/debug": "0.8.4-main.66e292d",
|
|
34
|
+
"@dxos/echo": "0.8.4-main.66e292d",
|
|
35
|
+
"@dxos/effect": "0.8.4-main.66e292d",
|
|
36
|
+
"@dxos/invariant": "0.8.4-main.66e292d",
|
|
37
|
+
"@dxos/echo-protocol": "0.8.4-main.66e292d",
|
|
38
|
+
"@dxos/live-object": "0.8.4-main.66e292d",
|
|
39
|
+
"@dxos/log": "0.8.4-main.66e292d",
|
|
40
|
+
"@dxos/plugin-client": "0.8.4-main.66e292d",
|
|
41
|
+
"@dxos/plugin-deck": "0.8.4-main.66e292d",
|
|
42
|
+
"@dxos/plugin-graph": "0.8.4-main.66e292d",
|
|
43
|
+
"@dxos/react-client": "0.8.4-main.66e292d",
|
|
44
|
+
"@dxos/blueprints": "0.8.4-main.66e292d",
|
|
45
|
+
"@dxos/react-ui-stack": "0.8.4-main.66e292d",
|
|
46
|
+
"@dxos/plugin-space": "0.8.4-main.66e292d",
|
|
47
|
+
"@dxos/util": "0.8.4-main.66e292d",
|
|
48
|
+
"@dxos/react-ui-searchlist": "0.8.4-main.66e292d",
|
|
49
|
+
"@dxos/types": "0.8.4-main.66e292d",
|
|
50
|
+
"@dxos/schema": "0.8.4-main.66e292d"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
|
-
"@effect-
|
|
53
|
+
"@effect-atom/atom-react": "^0.3.4",
|
|
53
54
|
"@effect/platform": "0.92.1",
|
|
54
55
|
"@types/react": "~19.2.2",
|
|
55
|
-
"@types/react-dom": "~19.2.
|
|
56
|
+
"@types/react-dom": "~19.2.2",
|
|
56
57
|
"effect": "3.18.3",
|
|
57
58
|
"react": "~19.2.0",
|
|
58
59
|
"react-dom": "~19.2.0",
|
|
59
60
|
"vite": "7.1.9",
|
|
60
|
-
"@dxos/random": "0.8.4-main.
|
|
61
|
-
"@dxos/react-ui
|
|
62
|
-
"@dxos/
|
|
63
|
-
"@dxos/
|
|
61
|
+
"@dxos/random": "0.8.4-main.66e292d",
|
|
62
|
+
"@dxos/react-ui": "0.8.4-main.66e292d",
|
|
63
|
+
"@dxos/react-ui-theme": "0.8.4-main.66e292d",
|
|
64
|
+
"@dxos/storybook-utils": "0.8.4-main.66e292d"
|
|
64
65
|
},
|
|
65
66
|
"peerDependencies": {
|
|
66
|
-
"@effect-
|
|
67
|
+
"@effect-atom/atom-react": "^0.3.4",
|
|
67
68
|
"@effect/platform": "^0.80.12",
|
|
68
69
|
"effect": "^3.13.3",
|
|
69
70
|
"react": "^19.0.0",
|
|
70
71
|
"react-dom": "^19.0.0",
|
|
71
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
72
|
-
"@dxos/react-ui-theme": "0.8.4-main.
|
|
72
|
+
"@dxos/react-ui": "0.8.4-main.66e292d",
|
|
73
|
+
"@dxos/react-ui-theme": "0.8.4-main.66e292d"
|
|
73
74
|
},
|
|
74
75
|
"publishConfig": {
|
|
75
76
|
"access": "public"
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Atom } from '@effect-atom/atom-react';
|
|
6
6
|
import * as Function from 'effect/Function';
|
|
7
7
|
import * as Option from 'effect/Option';
|
|
8
8
|
|
|
9
9
|
import { Capabilities, type PluginContext, contributes, createIntent } from '@dxos/app-framework';
|
|
10
10
|
import { ClientCapabilities } from '@dxos/plugin-client';
|
|
11
11
|
import { ATTENDABLE_PATH_SEPARATOR, DECK_COMPANION_TYPE } from '@dxos/plugin-deck/types';
|
|
12
|
-
import { ROOT_ID,
|
|
12
|
+
import { ROOT_ID, atomFromSignal, createExtension } from '@dxos/plugin-graph';
|
|
13
13
|
import { parseId } from '@dxos/react-client/echo';
|
|
14
14
|
|
|
15
15
|
import { meta } from '../meta';
|
|
@@ -20,12 +20,12 @@ export default (context: PluginContext) =>
|
|
|
20
20
|
createExtension({
|
|
21
21
|
id: `${meta.id}/space-search`,
|
|
22
22
|
connector: (node) =>
|
|
23
|
-
|
|
23
|
+
Atom.make((get) =>
|
|
24
24
|
Function.pipe(
|
|
25
25
|
get(node),
|
|
26
26
|
Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),
|
|
27
27
|
Option.map((node) => {
|
|
28
|
-
const workspace = get(
|
|
28
|
+
const workspace = get(atomFromSignal(() => context.getCapability(Capabilities.Layout).workspace));
|
|
29
29
|
const client = context.getCapability(ClientCapabilities.Client);
|
|
30
30
|
const { spaceId } = parseId(workspace);
|
|
31
31
|
const space = spaceId ? client.spaces.get(spaceId) : null;
|
|
@@ -50,7 +50,7 @@ export default (context: PluginContext) =>
|
|
|
50
50
|
createExtension({
|
|
51
51
|
id: meta.id,
|
|
52
52
|
actions: (node) =>
|
|
53
|
-
|
|
53
|
+
Atom.make((get) =>
|
|
54
54
|
Function.pipe(
|
|
55
55
|
get(node),
|
|
56
56
|
Option.flatMap((node) => (node.id === ROOT_ID ? Option.some(node) : Option.none())),
|
|
@@ -61,6 +61,7 @@ export default (context: PluginContext) =>
|
|
|
61
61
|
data: async () => {
|
|
62
62
|
const { dispatchPromise: dispatch } = context.getCapability(Capabilities.IntentDispatcher);
|
|
63
63
|
await dispatch(createIntent(SearchAction.OpenSearch));
|
|
64
|
+
return false;
|
|
64
65
|
},
|
|
65
66
|
properties: {
|
|
66
67
|
label: ['search action label', { ns: meta.id }],
|
|
@@ -6,7 +6,7 @@ import { type Decorator, type Meta, type StoryObj } from '@storybook/react-vite'
|
|
|
6
6
|
import React, { useState } from 'react';
|
|
7
7
|
|
|
8
8
|
import { faker } from '@dxos/random';
|
|
9
|
-
import { withTheme } from '@dxos/react-ui/testing';
|
|
9
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
10
10
|
|
|
11
11
|
import { SearchContextProvider, useGlobalSearch, useGlobalSearchResults } from '../hooks';
|
|
12
12
|
|
|
@@ -26,7 +26,7 @@ const DefaultStory = ({ objects, ...props }: StoryProps) => {
|
|
|
26
26
|
|
|
27
27
|
return (
|
|
28
28
|
<div className='flex grow justify-center overflow-hidden'>
|
|
29
|
-
<div className='flex flex-col
|
|
29
|
+
<div className='flex flex-col is-[300px] m-4 overflow-hidden'>
|
|
30
30
|
<Searchbar variant='subdued' placeholder='Enter regular expression...' onChange={setMatch} {...props} />
|
|
31
31
|
<SearchResults items={filteredItems} selected={selected} onSelect={setSelected} />
|
|
32
32
|
</div>
|
|
@@ -56,10 +56,7 @@ const meta = {
|
|
|
56
56
|
title: 'plugins/plugin-search/Search',
|
|
57
57
|
component: Searchbar,
|
|
58
58
|
render: DefaultStory,
|
|
59
|
-
decorators: [withTheme, withSearchContext()],
|
|
60
|
-
parameters: {
|
|
61
|
-
layout: 'column',
|
|
62
|
-
},
|
|
59
|
+
decorators: [withTheme, withLayout({ container: 'column' }), withSearchContext()],
|
|
63
60
|
} satisfies Meta<typeof Searchbar>;
|
|
64
61
|
|
|
65
62
|
export default meta;
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
import * as Option from 'effect/Option';
|
|
6
6
|
import React, { forwardRef, useCallback, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import { LayoutAction, createIntent
|
|
8
|
+
import { LayoutAction, createIntent } from '@dxos/app-framework';
|
|
9
|
+
import { useAppGraph, useIntentDispatcher, useLayout } from '@dxos/app-framework/react';
|
|
10
|
+
import { Obj } from '@dxos/echo';
|
|
9
11
|
import { type Node } from '@dxos/plugin-graph';
|
|
10
12
|
import { useClient } from '@dxos/react-client';
|
|
11
|
-
import { Filter,
|
|
13
|
+
import { Filter, useQuery } from '@dxos/react-client/echo';
|
|
12
14
|
import { Button, Dialog, Icon, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
13
15
|
import { SearchList, type SearchListItemProps } from '@dxos/react-ui-searchlist';
|
|
14
16
|
import { descriptionText, mx } from '@dxos/react-ui-theme';
|
|
@@ -59,12 +61,22 @@ export const SearchDialog = ({ pivotId }: SearchDialogProps) => {
|
|
|
59
61
|
|
|
60
62
|
const handleSelect = useCallback(
|
|
61
63
|
async (nodeId: string) => {
|
|
62
|
-
await dispatch(
|
|
64
|
+
await dispatch(
|
|
65
|
+
createIntent(LayoutAction.UpdateDialog, {
|
|
66
|
+
part: 'dialog',
|
|
67
|
+
options: { state: false },
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
63
70
|
|
|
64
71
|
// If node is already present in the active parts, scroll to it and close the dialog.
|
|
65
72
|
const index = layout.active.findIndex((id) => id === nodeId);
|
|
66
73
|
if (index !== -1) {
|
|
67
|
-
await dispatch(
|
|
74
|
+
await dispatch(
|
|
75
|
+
createIntent(LayoutAction.ScrollIntoView, {
|
|
76
|
+
part: 'current',
|
|
77
|
+
subject: nodeId,
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
68
80
|
} else {
|
|
69
81
|
await dispatch(
|
|
70
82
|
createIntent(LayoutAction.Open, {
|
|
@@ -91,13 +103,13 @@ export const SearchDialog = ({ pivotId }: SearchDialogProps) => {
|
|
|
91
103
|
setQueryString(nextValue);
|
|
92
104
|
}}
|
|
93
105
|
placeholder={t('search placeholder')}
|
|
94
|
-
classNames='
|
|
106
|
+
classNames='pli-1 my-2'
|
|
95
107
|
/>
|
|
96
108
|
<SearchList.Content classNames='max-bs-[24rem] overflow-auto'>
|
|
97
109
|
{queryString.length > 0 ? (
|
|
98
110
|
resultObjects.length > 0 ? (
|
|
99
111
|
resultObjects
|
|
100
|
-
.map((object) => graph.getNode(
|
|
112
|
+
.map((object) => graph.getNode(Obj.getDXN(object).toString()))
|
|
101
113
|
.filter(Option.isSome)
|
|
102
114
|
.map((node) => <SearchListResult key={node.value.id} node={node.value} onSelect={handleSelect} />)
|
|
103
115
|
) : (
|
|
@@ -2,67 +2,58 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, {
|
|
5
|
+
import React, { useState } from 'react';
|
|
6
6
|
|
|
7
|
-
import { log } from '@dxos/log';
|
|
8
7
|
import { useClient } from '@dxos/react-client';
|
|
9
8
|
import { Filter, type Space, useQuery } from '@dxos/react-client/echo';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { useTranslation } from '@dxos/react-ui';
|
|
10
|
+
import { StackItem } from '@dxos/react-ui-stack';
|
|
11
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
12
12
|
|
|
13
|
-
import { useGlobalSearch, useGlobalSearchResults } from '../hooks';
|
|
14
|
-
import { useWebSearch } from '../hooks/useWebSearch';
|
|
13
|
+
import { useGlobalSearch, useGlobalSearchResults, useWebSearch } from '../hooks';
|
|
15
14
|
import { meta } from '../meta';
|
|
16
15
|
|
|
17
16
|
import { Searchbar } from './Searchbar';
|
|
18
17
|
import { SearchResults } from './SearchResults';
|
|
19
18
|
|
|
20
|
-
export const SearchMain
|
|
19
|
+
export const SearchMain = ({ space }: { space?: Space }) => {
|
|
21
20
|
const { t } = useTranslation(meta.id);
|
|
22
21
|
const client = useClient();
|
|
23
22
|
const { setMatch } = useGlobalSearch();
|
|
24
23
|
const [query, setQuery] = useState<string>();
|
|
25
|
-
// TODO(burdon):
|
|
24
|
+
// TODO(burdon): Option to search across spaces.
|
|
26
25
|
const allSpaces = false;
|
|
27
|
-
|
|
28
|
-
// TODO(burdon): Returns ALL objects (e.g., incl. Text objects that are fields of parent objects).
|
|
29
26
|
const objects = useQuery(allSpaces ? client.spaces : space, Filter.everything());
|
|
30
27
|
const results = useGlobalSearchResults(objects);
|
|
31
28
|
|
|
32
|
-
const {
|
|
33
|
-
runSearch,
|
|
34
|
-
results: webResults,
|
|
35
|
-
isLoading,
|
|
36
|
-
} = useWebSearch({
|
|
37
|
-
query,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const [selected, setSelected] = useState<string>();
|
|
29
|
+
const { runSearch, results: webResults } = useWebSearch({ query });
|
|
41
30
|
|
|
42
31
|
// TODO(burdon): Activate and/or filter current main (set context).
|
|
32
|
+
const [selected, setSelected] = useState<string>();
|
|
43
33
|
const handleSelect = (id: string) => {
|
|
44
34
|
setSelected((selected) => (selected === id ? undefined : id));
|
|
45
35
|
};
|
|
46
36
|
|
|
47
37
|
const allResults = [...results, ...webResults];
|
|
48
|
-
log.info('results', { results, webResults });
|
|
49
38
|
|
|
50
39
|
return (
|
|
51
|
-
<
|
|
40
|
+
<StackItem.Content toolbar>
|
|
52
41
|
<Searchbar
|
|
42
|
+
classNames='pli-2'
|
|
53
43
|
placeholder={t('search placeholder')}
|
|
44
|
+
delay={300}
|
|
54
45
|
onChange={(text) => {
|
|
55
46
|
setQuery(text);
|
|
56
47
|
setMatch?.(text);
|
|
57
48
|
}}
|
|
58
49
|
onSubmit={runSearch}
|
|
59
50
|
/>
|
|
60
|
-
|
|
51
|
+
|
|
61
52
|
{allResults.length > 0 && (
|
|
62
|
-
<div className={mx('flex flex-col
|
|
53
|
+
<div className={mx('flex flex-col bs-full overflow-hidden')}>
|
|
63
54
|
<SearchResults items={allResults} selected={selected} onSelect={handleSelect} />
|
|
64
55
|
</div>
|
|
65
56
|
)}
|
|
66
|
-
</
|
|
57
|
+
</StackItem.Content>
|
|
67
58
|
);
|
|
68
59
|
};
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
6
|
import React, { useState } from 'react';
|
|
7
7
|
|
|
8
|
+
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
8
9
|
import { faker } from '@dxos/random';
|
|
9
10
|
import { withTheme } from '@dxos/react-ui/testing';
|
|
10
11
|
|
|
@@ -19,7 +20,7 @@ const DefaultStory = (props: SearchResultsProps) => {
|
|
|
19
20
|
|
|
20
21
|
return (
|
|
21
22
|
<div className='flex grow justify-center overflow-hidden'>
|
|
22
|
-
<div className='flex
|
|
23
|
+
<div className='flex is-[300px] m-4 overflow-hidden'>
|
|
23
24
|
<SearchResults {...props} selected={selected} onSelect={setSelected} />
|
|
24
25
|
</div>
|
|
25
26
|
</div>
|
|
@@ -40,7 +41,7 @@ const meta = {
|
|
|
40
41
|
title: 'plugins/plugin-search/SearchResults',
|
|
41
42
|
component: SearchResults,
|
|
42
43
|
render: DefaultStory,
|
|
43
|
-
decorators: [withTheme],
|
|
44
|
+
decorators: [withTheme, withPluginManager({ plugins: [] })],
|
|
44
45
|
parameters: {
|
|
45
46
|
layout: 'fullscreen',
|
|
46
47
|
},
|
|
@@ -4,12 +4,57 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { type FC, forwardRef } from 'react';
|
|
6
6
|
|
|
7
|
+
import { Surface } from '@dxos/app-framework/react';
|
|
8
|
+
import { Obj } from '@dxos/echo';
|
|
7
9
|
import { Card } from '@dxos/react-ui-stack';
|
|
8
10
|
import { ghostHover } from '@dxos/react-ui-theme';
|
|
9
11
|
|
|
10
|
-
import { SEARCH_RESULT } from '../../meta';
|
|
11
12
|
import { type SearchResult } from '../../types';
|
|
12
13
|
|
|
14
|
+
export type SearchResultsProps = {
|
|
15
|
+
items: SearchResult[];
|
|
16
|
+
match?: RegExp;
|
|
17
|
+
selected?: string;
|
|
18
|
+
onSelect?: (id: string) => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// TODO(burdon): Key cursor up/down.
|
|
22
|
+
// TODO(burdon): Use stack (should be virtualized).
|
|
23
|
+
export const SearchResults = ({ items }: SearchResultsProps) => {
|
|
24
|
+
return (
|
|
25
|
+
<div className='overflow-y-auto'>
|
|
26
|
+
<div className='flex flex-col'>
|
|
27
|
+
{items
|
|
28
|
+
// TODO(burdon): Hack to reduce noise from system objects.
|
|
29
|
+
.filter((item) => Obj.getLabel(item.object))
|
|
30
|
+
.map((item) => (
|
|
31
|
+
<div key={item.id} role='none' className='pli-2 first:pbs-2 pbe-2'>
|
|
32
|
+
<Surface role='card' data={{ subject: item.object }} limit={1} />
|
|
33
|
+
</div>
|
|
34
|
+
))}
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type SearchItemProps = SearchResult & { selected: boolean } & Pick<SearchResultsProps, 'onSelect'>;
|
|
41
|
+
|
|
42
|
+
export const SearchItem = forwardRef<HTMLDivElement, SearchItemProps>((item, forwardRef) => {
|
|
43
|
+
const { id, objectType, label, snippet, match, selected, onSelect } = item;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Card.StaticRoot
|
|
47
|
+
ref={forwardRef}
|
|
48
|
+
classNames={['mx-2 mt-2 cursor-pointer', selected && '!bg-activeSurface', ghostHover]}
|
|
49
|
+
onClick={() => onSelect?.(id)}
|
|
50
|
+
>
|
|
51
|
+
{objectType && <div className='text-xs text-neutral-400 ml-4'>{objectType}</div>}
|
|
52
|
+
<Card.Heading>{label ?? 'Untitled'}</Card.Heading>
|
|
53
|
+
{snippet && <Snippet text={snippet} match={match} />}
|
|
54
|
+
</Card.StaticRoot>
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
13
58
|
export const Snippet: FC<{ text: string; match?: RegExp }> = ({ text, match }) => {
|
|
14
59
|
let content = <>{text}</>;
|
|
15
60
|
if (match) {
|
|
@@ -36,51 +81,3 @@ export const Snippet: FC<{ text: string; match?: RegExp }> = ({ text, match }) =
|
|
|
36
81
|
|
|
37
82
|
return <span className='text-xs mb-1 line-clamp-3 text-neutral-300'>{content}</span>;
|
|
38
83
|
};
|
|
39
|
-
|
|
40
|
-
export type SearchItemProps = SearchResult & { selected: boolean } & Pick<SearchResultsProps, 'onSelect'>;
|
|
41
|
-
|
|
42
|
-
export const SearchItem = forwardRef<HTMLDivElement, SearchItemProps>((item, forwardRef) => {
|
|
43
|
-
const { id, objectType, label, snippet, match, selected, onSelect } = item;
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<Card.StaticRoot
|
|
47
|
-
ref={forwardRef}
|
|
48
|
-
classNames={['mx-2 mt-2 cursor-pointer', selected && '!bg-activeSurface', ghostHover]}
|
|
49
|
-
onClick={() => onSelect?.(id)}
|
|
50
|
-
>
|
|
51
|
-
{objectType && (
|
|
52
|
-
<>
|
|
53
|
-
<div className='text-xs text-neutral-400 ml-4'>{objectType}</div>
|
|
54
|
-
</>
|
|
55
|
-
)}
|
|
56
|
-
<Card.Heading>{label ?? 'Untitled'}</Card.Heading>
|
|
57
|
-
{snippet && <Snippet text={snippet} match={match} />}
|
|
58
|
-
</Card.StaticRoot>
|
|
59
|
-
);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
export type SearchResultsProps = {
|
|
63
|
-
match?: RegExp;
|
|
64
|
-
items: SearchResult[];
|
|
65
|
-
selected?: string;
|
|
66
|
-
onSelect?: (id: string) => void;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// TODO(burdon): Key cursor up/down.
|
|
70
|
-
export const SearchResults = ({ items, selected, onSelect }: SearchResultsProps) => {
|
|
71
|
-
return (
|
|
72
|
-
<div className='flex flex-col grow overflow-y-auto'>
|
|
73
|
-
<div className='flex flex-col'>
|
|
74
|
-
{items.map((item) => (
|
|
75
|
-
<SearchItem
|
|
76
|
-
key={item.id}
|
|
77
|
-
type={SEARCH_RESULT}
|
|
78
|
-
{...item}
|
|
79
|
-
onSelect={onSelect}
|
|
80
|
-
selected={selected === item.id}
|
|
81
|
-
/>
|
|
82
|
-
))}
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
|
-
);
|
|
86
|
-
};
|
|
@@ -2,66 +2,61 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React, {
|
|
5
|
+
import React, { useCallback, useEffect, useRef } from 'react';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { Input, type TextInputProps, type ThemedClassName, Toolbar, useControlledState } from '@dxos/react-ui';
|
|
8
8
|
|
|
9
|
-
export type SearchbarProps =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
delay?: number;
|
|
18
|
-
};
|
|
9
|
+
export type SearchbarProps = ThemedClassName<
|
|
10
|
+
Pick<TextInputProps, 'variant' | 'placeholder'> & {
|
|
11
|
+
delay?: number;
|
|
12
|
+
value?: string;
|
|
13
|
+
onChange?: (text?: string) => void;
|
|
14
|
+
onSubmit?: (text?: string) => void;
|
|
15
|
+
}
|
|
16
|
+
>;
|
|
19
17
|
|
|
20
|
-
export const Searchbar = ({
|
|
18
|
+
export const Searchbar = ({ classNames, variant, placeholder, value, onChange, onSubmit }: SearchbarProps) => {
|
|
19
|
+
const [text, setText] = useControlledState(value, onChange);
|
|
21
20
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
22
|
-
const [text, setText] = useState(value);
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
setText(value);
|
|
25
|
-
}, [value]);
|
|
26
|
-
const handleChange = (text?: string) => {
|
|
27
|
-
setText(text);
|
|
28
|
-
onChange?.(text);
|
|
29
|
-
};
|
|
30
21
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
const handleWindowKeyDown = useCallback(
|
|
23
|
+
(event: KeyboardEvent) => {
|
|
24
|
+
if (event.key === 'f' && event.metaKey && event.shiftKey) {
|
|
25
|
+
inputRef.current?.focus();
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
[inputRef],
|
|
29
|
+
);
|
|
35
30
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
window.addEventListener('keydown', handleWindowKeyDown);
|
|
33
|
+
return () => window.removeEventListener('keydown', handleWindowKeyDown);
|
|
34
|
+
}, [handleWindowKeyDown]);
|
|
35
|
+
|
|
36
|
+
const handleKeyDown = useCallback(
|
|
37
|
+
(event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
38
|
+
switch (event.key) {
|
|
39
|
+
case 'Enter':
|
|
40
|
+
onSubmit?.(text);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
[text, onSubmit],
|
|
45
|
+
);
|
|
46
46
|
|
|
47
47
|
return (
|
|
48
|
-
<
|
|
48
|
+
<Toolbar.Root classNames={classNames}>
|
|
49
49
|
<Input.Root>
|
|
50
50
|
<Input.TextInput
|
|
51
51
|
ref={inputRef}
|
|
52
52
|
placeholder={placeholder}
|
|
53
|
+
autoFocus
|
|
53
54
|
variant={variant}
|
|
54
55
|
value={text ?? ''}
|
|
55
|
-
|
|
56
|
-
onChange={({ target }) => handleChange(target.value)}
|
|
56
|
+
onChange={({ target }) => setText(target.value)}
|
|
57
57
|
onKeyDown={handleKeyDown}
|
|
58
58
|
/>
|
|
59
|
-
|
|
60
|
-
{/* TODO(burdon): Margin should be density specific. */}
|
|
61
|
-
<div role='button' className='-ml-7 p-0 cursor-pointer' onClick={handleReset}>
|
|
62
|
-
<Icon icon='ph--magnifying-glass--regular' size={5} />
|
|
63
|
-
</div>
|
|
64
59
|
</Input.Root>
|
|
65
|
-
</
|
|
60
|
+
</Toolbar.Root>
|
|
66
61
|
);
|
|
67
62
|
};
|
package/src/hooks/index.ts
CHANGED
package/src/hooks/sync.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type * as Schema from 'effect/Schema';
|
|
|
6
6
|
import * as SchemaAST from 'effect/SchemaAST';
|
|
7
7
|
|
|
8
8
|
import { Obj } from '@dxos/echo';
|
|
9
|
-
import {
|
|
9
|
+
import { Text } from '@dxos/schema';
|
|
10
10
|
|
|
11
11
|
import { type SearchResult } from '../types';
|
|
12
12
|
|
|
@@ -46,7 +46,7 @@ export const filterObjectsSync = <T extends Record<string, any>>(objects: T[], m
|
|
|
46
46
|
|
|
47
47
|
return objects.reduce<SearchResult[]>((results, object) => {
|
|
48
48
|
// TODO(burdon): Hack to ignore Text objects.
|
|
49
|
-
if (object instanceof
|
|
49
|
+
if (object instanceof Text.Text) {
|
|
50
50
|
return results;
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -112,7 +112,7 @@ const getKeys = (object: Record<string, unknown>): string[] => {
|
|
|
112
112
|
export const mapObjectToTextFields = <T extends Record<string, unknown>>(object: T): TextFields => {
|
|
113
113
|
return getKeys(object).reduce<TextFields>((fields, key) => {
|
|
114
114
|
const value = object[key] as any;
|
|
115
|
-
if (typeof value === 'string' || value instanceof
|
|
115
|
+
if (typeof value === 'string' || value instanceof Text.Text) {
|
|
116
116
|
try {
|
|
117
117
|
fields[key] = String(value);
|
|
118
118
|
} catch (err) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { yieldOrContinue } from 'main-thread-scheduling';
|
|
6
6
|
import { useEffect, useState } from 'react';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { Text } from '@dxos/schema';
|
|
9
9
|
|
|
10
10
|
// TODO(thure): Deprecate search-sync, move still-relevant utilities elsewhere (here, probably).
|
|
11
11
|
import { mapObjectToTextFields, queryStringToMatch } from './sync';
|
|
@@ -20,7 +20,7 @@ export const filterObjects = async <T extends Record<string, any>>(
|
|
|
20
20
|
}
|
|
21
21
|
await Promise.all(
|
|
22
22
|
objects
|
|
23
|
-
.filter((object) => !(object instanceof
|
|
23
|
+
.filter((object) => !(object instanceof Text.Text))
|
|
24
24
|
.map(async (object) => {
|
|
25
25
|
await yieldOrContinue('interactive');
|
|
26
26
|
const fields = mapObjectToTextFields<T>(object);
|
|
@@ -9,7 +9,7 @@ import { Obj } from '@dxos/echo';
|
|
|
9
9
|
import { type DXN } from '@dxos/keys';
|
|
10
10
|
import { log } from '@dxos/log';
|
|
11
11
|
import { getIconAnnotation } from '@dxos/schema';
|
|
12
|
-
import {
|
|
12
|
+
import { TestSchema } from '@dxos/schema/testing';
|
|
13
13
|
|
|
14
14
|
import { search } from '../search';
|
|
15
15
|
import { type SearchResult } from '../types';
|
|
@@ -27,7 +27,7 @@ export const useWebSearch = ({ query, context }: { query?: string; context?: str
|
|
|
27
27
|
const results = await search({
|
|
28
28
|
query,
|
|
29
29
|
context,
|
|
30
|
-
schema: [
|
|
30
|
+
schema: [TestSchema.Project, TestSchema.Organization, TestSchema.Person],
|
|
31
31
|
exaApiKey: EXA_API_KEY,
|
|
32
32
|
});
|
|
33
33
|
|
package/src/meta.ts
CHANGED
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type PluginMeta } from '@dxos/app-framework';
|
|
6
|
+
import { trim } from '@dxos/util';
|
|
6
7
|
|
|
7
8
|
export const meta: PluginMeta = {
|
|
8
9
|
id: 'dxos.org/plugin/search',
|
|
9
10
|
name: 'Search',
|
|
10
|
-
description:
|
|
11
|
+
description: trim`
|
|
12
|
+
Full-text search engine for finding content across all spaces and object types.
|
|
13
|
+
Quickly locate documents, tables, and other objects with instant results and relevance ranking.
|
|
14
|
+
`,
|
|
11
15
|
icon: 'ph--magnifying-glass--regular',
|
|
12
16
|
source: 'https://github.com/dxos/dxos/tree/main/packages/plugins/plugin-search',
|
|
13
17
|
tags: ['labs'],
|