mirror of https://github.com/fantasticit/think.git
feat: improve search
parent
c017d5d351
commit
b13dd26d9a
|
@ -1,83 +1,33 @@
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import Link from "next/link";
|
import Link from 'next/link';
|
||||||
import Router from "next/router";
|
import Router from 'next/router';
|
||||||
import { Typography, Button, Modal, Input } from "@douyinfe/semi-ui";
|
import { Typography, Button, Modal, Input, Spin } from '@douyinfe/semi-ui';
|
||||||
import { IconSearch } from "components/icons";
|
import { IconSearch as SemiIconSearch } from '@douyinfe/semi-icons';
|
||||||
import { IDocument } from "@think/domains";
|
import { IconSearch } from 'components/icons';
|
||||||
import { useRecentDocuments } from "data/document";
|
import { IDocument } from '@think/domains';
|
||||||
import { useToggle } from "hooks/useToggle";
|
import { useRecentDocuments } from 'data/document';
|
||||||
import { searchDocument } from "services/document";
|
import { useToggle } from 'hooks/useToggle';
|
||||||
import { Empty } from "components/empty";
|
import { useAsyncLoading } from 'hooks/useAsyncLoading';
|
||||||
import { DataRender } from "components/data-render";
|
import { searchDocument } from 'services/document';
|
||||||
import { LocaleTime } from "components/locale-time";
|
import { Empty } from 'components/empty';
|
||||||
import { DocumentStar } from "components/document/star";
|
import { DataRender } from 'components/data-render';
|
||||||
import { IconDocumentFill } from "components/icons/IconDocumentFill";
|
import { LocaleTime } from 'components/locale-time';
|
||||||
import styles from "./index.module.scss";
|
import { DocumentStar } from 'components/document/star';
|
||||||
|
import { IconDocumentFill } from 'components/icons/IconDocumentFill';
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
export const Search = () => {
|
const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
|
||||||
const { data: recentDocs, loading, error } = useRecentDocuments();
|
|
||||||
const [keyword, setKeyword] = useState("");
|
|
||||||
const [searchDocs, setSearchDocs] = useState<IDocument[]>([]);
|
|
||||||
const data = useMemo(
|
|
||||||
() => (searchDocs.length ? searchDocs : recentDocs),
|
|
||||||
[searchDocs.length, recentDocs]
|
|
||||||
);
|
|
||||||
|
|
||||||
const search = useCallback(() => {
|
|
||||||
searchDocument(keyword).then((res) => {
|
|
||||||
setSearchDocs(res);
|
|
||||||
});
|
|
||||||
}, [keyword]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fn = () => {
|
|
||||||
toggleVisible(false);
|
|
||||||
};
|
|
||||||
Router.events.on("routeChangeStart", fn);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
Router.events.off("routeChangeStart", fn);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
type="tertiary"
|
|
||||||
theme="borderless"
|
|
||||||
icon={<IconSearch />}
|
|
||||||
onClick={toggleVisible}
|
|
||||||
/>
|
|
||||||
<Modal
|
|
||||||
visible={visible}
|
|
||||||
title="文档搜索"
|
|
||||||
footer={null}
|
|
||||||
onCancel={toggleVisible}
|
|
||||||
>
|
|
||||||
<div style={{ paddingBottom: 24 }}>
|
|
||||||
<Input
|
|
||||||
placeholder={"搜索文档"}
|
|
||||||
size="large"
|
|
||||||
value={keyword}
|
|
||||||
onChange={(val) => setKeyword(val)}
|
|
||||||
onEnterPress={search}
|
|
||||||
/>
|
|
||||||
<DataRender
|
|
||||||
loading={loading}
|
|
||||||
error={error}
|
|
||||||
normalContent={() => {
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.itemsWrap}>
|
<div className={styles.itemsWrap}>
|
||||||
{data.length ? (
|
{data.length ? (
|
||||||
data.map((doc) => {
|
data.map((doc) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.itemWrap}>
|
<div className={styles.itemWrap} key={doc.id}>
|
||||||
<Link
|
<Link
|
||||||
href={{
|
href={{
|
||||||
pathname: "/wiki/[wikiId]/document/[documentId]",
|
pathname: '/wiki/[wikiId]/document/[documentId]',
|
||||||
query: {
|
query: {
|
||||||
wikiId: doc.wikiId,
|
wikiId: doc.wikiId,
|
||||||
documentId: doc.id,
|
documentId: doc.id,
|
||||||
|
@ -97,8 +47,7 @@ export const Search = () => {
|
||||||
|
|
||||||
<Text size="small" type="tertiary">
|
<Text size="small" type="tertiary">
|
||||||
创建者:
|
创建者:
|
||||||
{doc.createUser &&
|
{doc.createUser && doc.createUser.name} •{' '}
|
||||||
doc.createUser.name} •{" "}
|
|
||||||
<LocaleTime date={doc.updatedAt} timeago />
|
<LocaleTime date={doc.updatedAt} timeago />
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
@ -112,12 +61,103 @@ export const Search = () => {
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
<Empty message="最近访问的文档会出现在此处" />
|
<Empty message="暂无搜索结果" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
};
|
||||||
|
|
||||||
|
export const Search = () => {
|
||||||
|
const [visible, toggleVisible] = useToggle(false);
|
||||||
|
const { data: recentDocs } = useRecentDocuments();
|
||||||
|
const [searchApi, loading] = useAsyncLoading(searchDocument, 10);
|
||||||
|
const [keyword, setKeyword] = useState('');
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [searchDocs, setSearchDocs] = useState<IDocument[]>([]);
|
||||||
|
|
||||||
|
const search = useCallback(() => {
|
||||||
|
setError(null);
|
||||||
|
searchApi(keyword)
|
||||||
|
.then((res) => {
|
||||||
|
setSearchDocs(res);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setError(err);
|
||||||
|
});
|
||||||
|
}, [searchApi, keyword]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fn = () => {
|
||||||
|
toggleVisible(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
Router.events.on('routeChangeStart', fn);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
Router.events.off('routeChangeStart', fn);
|
||||||
|
};
|
||||||
|
}, [toggleVisible]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
type="tertiary"
|
||||||
|
theme="borderless"
|
||||||
|
icon={<IconSearch />}
|
||||||
|
onClick={toggleVisible}
|
||||||
/>
|
/>
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
title="文档搜索"
|
||||||
|
footer={null}
|
||||||
|
onCancel={toggleVisible}
|
||||||
|
style={{
|
||||||
|
maxWidth: '96vw',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ paddingBottom: 24 }}>
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
placeholder={'搜索文档'}
|
||||||
|
size="large"
|
||||||
|
value={keyword}
|
||||||
|
onChange={(val) => {
|
||||||
|
setSearchDocs([]);
|
||||||
|
setKeyword(val);
|
||||||
|
}}
|
||||||
|
onEnterPress={search}
|
||||||
|
suffix={
|
||||||
|
<SemiIconSearch
|
||||||
|
onClick={search}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
showClear
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ maxHeight: '70vh', overflow: 'auto' }}>
|
||||||
|
<DataRender
|
||||||
|
loading={loading}
|
||||||
|
loadingContent={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
paddingTop: 30,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Spin />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
error={error}
|
||||||
|
normalContent={() => <List data={searchDocs} />}
|
||||||
|
/>
|
||||||
|
<div style={{ marginTop: 16 }}>
|
||||||
|
<Text type="tertiary">最近访问的文档</Text>
|
||||||
|
<List data={recentDocs} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
|
|
Loading…
Reference in New Issue