mirror of
https://github.com/docmost/docmost.git
synced 2025-11-18 05:31:15 +10:00
refactor layout
* ui polishing * frontend and backend fixes
This commit is contained in:
@ -5,17 +5,18 @@ import {
|
||||
} from "@/features/search/services/search-service";
|
||||
import {
|
||||
IPageSearch,
|
||||
IPageSearchParams,
|
||||
ISuggestionResult,
|
||||
SearchSuggestionParams,
|
||||
} from "@/features/search/types/search.types";
|
||||
|
||||
export function usePageSearchQuery(
|
||||
query: string,
|
||||
params: IPageSearchParams,
|
||||
): UseQueryResult<IPageSearch[], Error> {
|
||||
return useQuery({
|
||||
queryKey: ["page-search", query],
|
||||
queryFn: () => searchPage(query),
|
||||
enabled: !!query,
|
||||
queryKey: ["page-search", params],
|
||||
queryFn: () => searchPage(params),
|
||||
enabled: !!params.query,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,65 +1,92 @@
|
||||
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';
|
||||
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, useParams } from "react-router-dom";
|
||||
import { useDebouncedValue } from "@mantine/hooks";
|
||||
import { usePageSearchQuery } from "@/features/search/queries/search-query";
|
||||
import { useGetSpaceBySlugQuery } from "@/features/space/queries/space-query.ts";
|
||||
import { useRecentChangesQuery } from "@/features/page/queries/page-query.ts";
|
||||
import { buildPageUrl } from "@/features/page/page.utils.ts";
|
||||
|
||||
|
||||
export function SearchSpotlight() {
|
||||
interface SearchSpotlightProps {
|
||||
spaceId?: string;
|
||||
}
|
||||
export function SearchSpotlight({ spaceId }: SearchSpotlightProps) {
|
||||
const navigate = useNavigate();
|
||||
const [query, setQuery] = useState('');
|
||||
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>
|
||||
const {
|
||||
data: searchResults,
|
||||
isLoading,
|
||||
error,
|
||||
} = usePageSearchQuery({ query: debouncedSearchQuery, spaceId });
|
||||
|
||||
<div style={{ flex: 1 }}>
|
||||
<Text>{item.title}</Text>
|
||||
const pages = (
|
||||
searchResults && searchResults.length > 0 ? searchResults : []
|
||||
).map((page) => (
|
||||
<Spotlight.Action
|
||||
key={page.id}
|
||||
onClick={() =>
|
||||
navigate(buildPageUrl(page.space.slug, page.slugId, page.title))
|
||||
}
|
||||
>
|
||||
<Group wrap="nowrap" w="100%">
|
||||
<Center>
|
||||
{page?.icon ? (
|
||||
<span style={{ fontSize: "20px" }}>{page.icon}</span>
|
||||
) : (
|
||||
<IconFileDescription size={20} />
|
||||
)}
|
||||
</Center>
|
||||
|
||||
{item?.highlight && (
|
||||
<Text opacity={0.6} size="xs" dangerouslySetInnerHTML={{ __html: item.highlight }}/>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<Text>{page.title}</Text>
|
||||
|
||||
</Group>
|
||||
</Spotlight.Action>
|
||||
));
|
||||
{page?.highlight && (
|
||||
<Text
|
||||
opacity={0.6}
|
||||
size="xs"
|
||||
dangerouslySetInnerHTML={{ __html: page.highlight }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Group>
|
||||
</Spotlight.Action>
|
||||
));
|
||||
|
||||
return (
|
||||
<>
|
||||
<Spotlight.Root query={query}
|
||||
onQueryChange={setQuery}
|
||||
scrollable
|
||||
overlayProps={{
|
||||
backgroundOpacity: 0.55,
|
||||
}}>
|
||||
<Spotlight.Search placeholder="Search..."
|
||||
leftSection={
|
||||
<IconSearch size={20} stroke={1.5} />
|
||||
} />
|
||||
<Spotlight.Root
|
||||
query={query}
|
||||
onQueryChange={setQuery}
|
||||
scrollable
|
||||
overlayProps={{
|
||||
backgroundOpacity: 0.55,
|
||||
}}
|
||||
>
|
||||
<Spotlight.Search
|
||||
placeholder="Search..."
|
||||
leftSection={<IconSearch size={20} stroke={1.5} />}
|
||||
/>
|
||||
<Spotlight.ActionsList>
|
||||
{query.length === 0 && items.length === 0 && <Spotlight.Empty>Start typing to search...</Spotlight.Empty>}
|
||||
{query.length === 0 && pages.length === 0 && (
|
||||
<Spotlight.Empty>Start typing to search...</Spotlight.Empty>
|
||||
)}
|
||||
|
||||
{query.length > 0 && items.length === 0 && <Spotlight.Empty>No results found...</Spotlight.Empty>}
|
||||
{query.length > 0 && pages.length === 0 && (
|
||||
<Spotlight.Empty>No results found...</Spotlight.Empty>
|
||||
)}
|
||||
|
||||
{items.length > 0 && items}
|
||||
{pages.length > 0 && pages}
|
||||
</Spotlight.ActionsList>
|
||||
|
||||
|
||||
</Spotlight.Root>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
import api from "@/lib/api-client";
|
||||
import {
|
||||
IPageSearch,
|
||||
IPageSearchParams,
|
||||
ISuggestionResult,
|
||||
SearchSuggestionParams,
|
||||
} from "@/features/search/types/search.types";
|
||||
|
||||
export async function searchPage(query: string): Promise<IPageSearch[]> {
|
||||
const req = await api.post<IPageSearch[]>("/search", { query });
|
||||
export async function searchPage(
|
||||
params: IPageSearchParams,
|
||||
): Promise<IPageSearch[]> {
|
||||
const req = await api.post<IPageSearch[]>("/search", params);
|
||||
return req.data;
|
||||
}
|
||||
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
import { IUser } from "@/features/user/types/user.types.ts";
|
||||
import { IGroup } from "@/features/group/types/group.types.ts";
|
||||
import { ISpace } from "@/features/space/types/space.types.ts";
|
||||
|
||||
export interface IPageSearch {
|
||||
id: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
parentPageId: string;
|
||||
slugId: string;
|
||||
creatorId: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
rank: string;
|
||||
highlight: string;
|
||||
space: Partial<ISpace>;
|
||||
}
|
||||
|
||||
export interface SearchSuggestionParams {
|
||||
@ -23,3 +26,8 @@ export interface ISuggestionResult {
|
||||
users?: Partial<IUser[]>;
|
||||
groups?: Partial<IGroup[]>;
|
||||
}
|
||||
|
||||
export interface IPageSearchParams {
|
||||
query: string;
|
||||
spaceId?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user