mirror of
https://github.com/docmost/docmost.git
synced 2025-11-12 16:02:35 +10:00
feat: search
This commit is contained in:
11
apps/client/src/features/search/queries/search-query.ts
Normal file
11
apps/client/src/features/search/queries/search-query.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { useQuery, UseQueryResult } from '@tanstack/react-query';
|
||||
import { searchPage } from '@/features/search/services/search-service';
|
||||
import { IPageSearch } from '@/features/search/types/search.types';
|
||||
|
||||
export function usePageSearchQuery(query: string): UseQueryResult<IPageSearch[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ['page-history', query],
|
||||
queryFn: () => searchPage(query),
|
||||
enabled: !!query,
|
||||
});
|
||||
}
|
||||
@ -1,43 +1,59 @@
|
||||
import { rem } from '@mantine/core';
|
||||
import { Spotlight, SpotlightActionData } from '@mantine/spotlight';
|
||||
import { IconHome, IconDashboard, IconSettings, IconSearch } from '@tabler/icons-react';
|
||||
import { Group, Center, Text } from '@mantine/core';
|
||||
import { Spotlight } from '@mantine/spotlight';
|
||||
import { IconFileDescription, IconHome, IconSearch, IconSettings } from '@tabler/icons-react';
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { usePageSearchQuery } from '@/features/search/queries/search-query';
|
||||
|
||||
const actions: SpotlightActionData[] = [
|
||||
{
|
||||
id: 'home',
|
||||
label: 'Home',
|
||||
description: 'Get to home page',
|
||||
onClick: () => console.log('Home'),
|
||||
leftSection: <IconHome style={{ width: rem(24), height: rem(24) }} stroke={1.5} />,
|
||||
},
|
||||
{
|
||||
id: 'dashboard',
|
||||
label: 'Dashboard',
|
||||
description: 'Get full information about current system status',
|
||||
onClick: () => console.log('Dashboard'),
|
||||
leftSection: <IconDashboard style={{ width: rem(24), height: rem(24) }} stroke={1.5} />,
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
label: 'Settings',
|
||||
description: 'Account settings and workspace management',
|
||||
onClick: () => console.log('Settings'),
|
||||
leftSection: <IconSettings style={{ width: rem(24), height: rem(24) }} stroke={1.5} />,
|
||||
},
|
||||
];
|
||||
|
||||
export function SearchSpotlight() {
|
||||
const navigate = useNavigate();
|
||||
const [query, setQuery] = useState('');
|
||||
const [debouncedSearchQuery] = useDebouncedValue(query, 300);
|
||||
const { data: searchResults, isLoading, error } = usePageSearchQuery(debouncedSearchQuery)
|
||||
|
||||
const items = (searchResults && searchResults.length > 0 ? searchResults : [])
|
||||
.map((item) => (
|
||||
<Spotlight.Action key={item.title} onClick={() => navigate(`/p/${item.id}`)}>
|
||||
<Group wrap="nowrap" w="100%">
|
||||
<Center>
|
||||
{item?.icon ? (
|
||||
<span style={{ fontSize: "20px" }}>{ item.icon }</span>
|
||||
) : (
|
||||
<IconFileDescription size={20} />
|
||||
)}
|
||||
</Center>
|
||||
|
||||
<div style={{ flex: 1 }}>
|
||||
<Text>{item.title}</Text>
|
||||
|
||||
{item?.highlight && (
|
||||
<Text opacity={0.6} size="xs" dangerouslySetInnerHTML={{ __html: item.highlight }}/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
</Group>
|
||||
</Spotlight.Action>
|
||||
));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Spotlight
|
||||
actions={actions}
|
||||
nothingFound="Nothing found..."
|
||||
highlightQuery
|
||||
searchProps={{
|
||||
leftSection: <IconSearch style={{ width: rem(20), height: rem(20) }} stroke={1.5} />,
|
||||
placeholder: 'Search...',
|
||||
}}
|
||||
/>
|
||||
<Spotlight.Root query={query}
|
||||
onQueryChange={setQuery}
|
||||
scrollable
|
||||
overlayProps={{
|
||||
backgroundOpacity: 0.55,
|
||||
}}>
|
||||
<Spotlight.Search placeholder="Search..."
|
||||
leftSection={
|
||||
<IconSearch size={20} stroke={1.5} />
|
||||
} />
|
||||
<Spotlight.ActionsList>
|
||||
{items.length > 0 ? items : <Spotlight.Empty>No results found...</Spotlight.Empty>}
|
||||
</Spotlight.ActionsList>
|
||||
</Spotlight.Root>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
import api from '@/lib/api-client';
|
||||
import { IPageSearch } from '@/features/search/types/search.types';
|
||||
|
||||
export async function searchPage(query: string): Promise<IPageSearch[]> {
|
||||
const req = await api.post<IPageSearch[]>('/search', { query });
|
||||
return req.data as any;
|
||||
}
|
||||
12
apps/client/src/features/search/types/search.types.ts
Normal file
12
apps/client/src/features/search/types/search.types.ts
Normal file
@ -0,0 +1,12 @@
|
||||
|
||||
export interface IPageSearch {
|
||||
id: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
parentPageId: string;
|
||||
creatorId: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
rank: string;
|
||||
highlight: string;
|
||||
}
|
||||
Reference in New Issue
Block a user