mirror of https://github.com/fantasticit/think.git
client: use dropdown instad of popover
This commit is contained in:
parent
ef8d3924b9
commit
232c818c81
|
@ -4,6 +4,7 @@ import {
|
||||||
AvatarGroup,
|
AvatarGroup,
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
Dropdown,
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
Popconfirm,
|
Popconfirm,
|
||||||
|
@ -24,7 +25,7 @@ import { useUser } from 'data/user';
|
||||||
import { event, JOIN_USER } from 'event';
|
import { event, JOIN_USER } from 'event';
|
||||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
wikiId: string;
|
wikiId: string;
|
||||||
|
@ -79,6 +80,71 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
|
||||||
[deleteUser]
|
[deleteUser]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const content = useMemo(
|
||||||
|
() => (
|
||||||
|
<Tabs type="line">
|
||||||
|
<TabPane tab="添加成员" itemKey="add">
|
||||||
|
<div style={{ marginTop: 16 }}>
|
||||||
|
<Input ref={ref} placeholder="输入对方用户名" value={inviteUser} onChange={setInviteUser}></Input>
|
||||||
|
<Paragraph style={{ marginTop: 16 }}>
|
||||||
|
邀请成功后,请将该链接发送给对方。
|
||||||
|
<span style={{ verticalAlign: 'middle' }}>
|
||||||
|
<DocumentLinkCopyer wikiId={wikiId} documentId={documentId} />
|
||||||
|
</span>
|
||||||
|
</Paragraph>
|
||||||
|
<Button theme="solid" block style={{ margin: '24px 0' }} disabled={!inviteUser} onClick={handleOk}>
|
||||||
|
添加用户
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane tab="协作成员" itemKey="list">
|
||||||
|
<DataRender
|
||||||
|
loading={loading}
|
||||||
|
error={error}
|
||||||
|
loadingContent={<Spin />}
|
||||||
|
normalContent={() => (
|
||||||
|
<Table dataSource={users} size="small" pagination>
|
||||||
|
<Column title="用户名" dataIndex="user.name" key="name" />
|
||||||
|
<Column
|
||||||
|
title="是否可读"
|
||||||
|
dataIndex="auth.readable"
|
||||||
|
key="readable"
|
||||||
|
render={renderChecked(updateUser, 'readable')}
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="是否可编辑"
|
||||||
|
dataIndex="auth.editable"
|
||||||
|
key="editable"
|
||||||
|
render={renderChecked(updateUser, 'editable')}
|
||||||
|
align="center"
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="操作"
|
||||||
|
dataIndex="operate"
|
||||||
|
key="operate"
|
||||||
|
render={(_, document) => (
|
||||||
|
<Popconfirm showArrow title="确认删除该成员?" onConfirm={() => handleDelete(document)}>
|
||||||
|
<Button type="tertiary" theme="borderless" icon={<IconDelete />} />
|
||||||
|
</Popconfirm>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Table>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
),
|
||||||
|
[documentId, error, handleDelete, handleOk, inviteUser, loading, updateUser, users, wikiId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const btn = useMemo(
|
||||||
|
() => (
|
||||||
|
<Button theme="borderless" type="tertiary" disabled={disabled} icon={<IconUserAdd />} onClick={toggleVisible} />
|
||||||
|
),
|
||||||
|
[disabled, toggleVisible]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
setTimeout(() => ref.current?.focus(), 100);
|
setTimeout(() => ref.current?.focus(), 100);
|
||||||
|
@ -139,70 +205,41 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</AvatarGroup>
|
</AvatarGroup>
|
||||||
<Popover
|
{isMobile ? (
|
||||||
showArrow
|
<>
|
||||||
visible={visible}
|
<Modal
|
||||||
onVisibleChange={toggleVisible}
|
centered
|
||||||
trigger="click"
|
title="文档协作"
|
||||||
position={isMobile ? 'topRight' : 'bottomLeft'}
|
visible={visible}
|
||||||
style={{ width: 376, maxWidth: '80vw' }}
|
footer={null}
|
||||||
content={
|
onCancel={toggleVisible}
|
||||||
<Tabs type="line">
|
style={{ maxWidth: '96vw' }}
|
||||||
<TabPane tab="添加成员" itemKey="add">
|
>
|
||||||
<div style={{ marginTop: 16 }}>
|
{content}
|
||||||
<Input ref={ref} placeholder="输入对方用户名" value={inviteUser} onChange={setInviteUser}></Input>
|
</Modal>
|
||||||
<Paragraph style={{ marginTop: 16 }}>
|
{btn}
|
||||||
邀请成功后,请将该链接发送给对方。
|
</>
|
||||||
<span style={{ verticalAlign: 'middle' }}>
|
) : (
|
||||||
<DocumentLinkCopyer wikiId={wikiId} documentId={documentId} />
|
<Dropdown
|
||||||
</span>
|
visible={visible}
|
||||||
</Paragraph>
|
onVisibleChange={toggleVisible}
|
||||||
<Button theme="solid" block style={{ margin: '24px 0' }} disabled={!inviteUser} onClick={handleOk}>
|
trigger="click"
|
||||||
添加用户
|
position="bottomRight"
|
||||||
</Button>
|
content={
|
||||||
</div>
|
<div
|
||||||
</TabPane>
|
style={{
|
||||||
<TabPane tab="协作成员" itemKey="list">
|
width: 412,
|
||||||
<DataRender
|
maxWidth: '96vw',
|
||||||
loading={loading}
|
padding: '0 24px',
|
||||||
error={error}
|
}}
|
||||||
loadingContent={<Spin />}
|
>
|
||||||
normalContent={() => (
|
{content}
|
||||||
<Table dataSource={users} size="small" pagination>
|
</div>
|
||||||
<Column title="用户名" dataIndex="user.name" key="name" />
|
}
|
||||||
<Column
|
>
|
||||||
title="是否可读"
|
{btn}
|
||||||
dataIndex="auth.readable"
|
</Dropdown>
|
||||||
key="readable"
|
)}
|
||||||
render={renderChecked(updateUser, 'readable')}
|
|
||||||
align="center"
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
title="是否可编辑"
|
|
||||||
dataIndex="auth.editable"
|
|
||||||
key="editable"
|
|
||||||
render={renderChecked(updateUser, 'editable')}
|
|
||||||
align="center"
|
|
||||||
/>
|
|
||||||
<Column
|
|
||||||
title="操作"
|
|
||||||
dataIndex="operate"
|
|
||||||
key="operate"
|
|
||||||
render={(_, document) => (
|
|
||||||
<Popconfirm showArrow title="确认删除该成员?" onConfirm={() => handleDelete(document)}>
|
|
||||||
<Button type="tertiary" theme="borderless" icon={<IconDelete />} />
|
|
||||||
</Popconfirm>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Table>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button theme="borderless" type="tertiary" disabled={disabled} icon={<IconUserAdd />} />
|
|
||||||
</Popover>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { IconLink } from '@douyinfe/semi-icons';
|
import { IconLink } from '@douyinfe/semi-icons';
|
||||||
import { Button, Input, Popover, Space, Toast, Typography } from '@douyinfe/semi-ui';
|
import { Button, Dropdown, Input, Modal, Space, Toast, Typography } from '@douyinfe/semi-ui';
|
||||||
import { isPublicDocument } from '@think/domains';
|
import { isPublicDocument } from '@think/domains';
|
||||||
import { useDocumentDetail } from 'data/document';
|
import { useDocumentDetail } from 'data/document';
|
||||||
import { getDocumentShareURL } from 'helpers/url';
|
import { getDocumentShareURL } from 'helpers/url';
|
||||||
|
@ -28,6 +28,7 @@ export const DocumentShare: React.FC<IProps> = ({ documentId, disabled = false,
|
||||||
const copyable = useMemo(
|
const copyable = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
onCopy: () => Toast.success({ content: '复制文本成功' }),
|
onCopy: () => Toast.success({ content: '复制文本成功' }),
|
||||||
|
successTip: '已复制',
|
||||||
}),
|
}),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
@ -44,6 +45,73 @@ export const DocumentShare: React.FC<IProps> = ({ documentId, disabled = false,
|
||||||
toggleStatus({ sharePassword: isPublic ? '' : sharePassword });
|
toggleStatus({ sharePassword: isPublic ? '' : sharePassword });
|
||||||
}, [isPublic, sharePassword, toggleStatus]);
|
}, [isPublic, sharePassword, toggleStatus]);
|
||||||
|
|
||||||
|
const content = useMemo(
|
||||||
|
() => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxWidth: '96vw',
|
||||||
|
overflow: 'auto',
|
||||||
|
}}
|
||||||
|
onClick={prevent}
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<ShareIllustration />
|
||||||
|
</div>
|
||||||
|
{isPublic ? (
|
||||||
|
<Text
|
||||||
|
ellipsis
|
||||||
|
icon={<IconLink />}
|
||||||
|
copyable={copyable}
|
||||||
|
style={{
|
||||||
|
width: 280,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{shareUrl}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<Input
|
||||||
|
ref={ref}
|
||||||
|
mode="password"
|
||||||
|
placeholder="设置访问密码"
|
||||||
|
value={sharePassword}
|
||||||
|
onChange={setSharePassword}
|
||||||
|
></Input>
|
||||||
|
)}
|
||||||
|
<div style={{ marginTop: 16 }}>
|
||||||
|
<Text type="tertiary">
|
||||||
|
{isPublic
|
||||||
|
? '分享开启后,该页面包含的所有内容均可访问,请谨慎开启'
|
||||||
|
: ' 分享关闭后,非协作成员将不能继续访问该页面'}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<Space style={{ width: '100%', justifyContent: 'end', margin: '12px 0' }}>
|
||||||
|
<Button onClick={() => toggleVisible(false)}>取消</Button>
|
||||||
|
<Button theme="solid" type={isPublic ? 'danger' : 'primary'} onClick={handleOk}>
|
||||||
|
{isPublic ? '关闭分享' : '开启分享'}
|
||||||
|
</Button>
|
||||||
|
{isPublic && (
|
||||||
|
<Button theme="solid" type="primary" onClick={viewUrl}>
|
||||||
|
查看文档
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
[copyable, handleOk, isPublic, prevent, sharePassword, shareUrl, toggleVisible, viewUrl]
|
||||||
|
);
|
||||||
|
|
||||||
|
const btn = useMemo(
|
||||||
|
() =>
|
||||||
|
render ? (
|
||||||
|
render({ isPublic, disabled, toggleVisible })
|
||||||
|
) : (
|
||||||
|
<Button disabled={disabled} type="primary" theme="light" onClick={toggleVisible}>
|
||||||
|
{isPublic ? '分享中' : '分享'}
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
[disabled, isPublic, render, toggleVisible]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading || !data) return;
|
if (loading || !data) return;
|
||||||
setSharePassword(data.document && data.document.sharePassword);
|
setSharePassword(data.document && data.document.sharePassword);
|
||||||
|
@ -56,72 +124,42 @@ export const DocumentShare: React.FC<IProps> = ({ documentId, disabled = false,
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<>
|
||||||
showArrow
|
{isMobile ? (
|
||||||
visible={visible}
|
<>
|
||||||
onVisibleChange={toggleVisible}
|
<Modal
|
||||||
trigger="click"
|
centered
|
||||||
position={isMobile ? 'top' : 'bottomLeft'}
|
title="文档分享"
|
||||||
style={{ width: 376, maxWidth: '80vw' }}
|
visible={visible}
|
||||||
content={
|
footer={null}
|
||||||
<div
|
onCancel={toggleVisible}
|
||||||
style={{
|
style={{ maxWidth: '96vw' }}
|
||||||
maxHeight: '70vh',
|
>
|
||||||
overflow: 'auto',
|
{content}
|
||||||
}}
|
</Modal>
|
||||||
onClick={prevent}
|
{btn}
|
||||||
>
|
</>
|
||||||
<div style={{ textAlign: 'center' }}>
|
) : (
|
||||||
<ShareIllustration />
|
<Dropdown
|
||||||
</div>
|
visible={visible}
|
||||||
{isPublic ? (
|
onVisibleChange={toggleVisible}
|
||||||
<Text
|
trigger="click"
|
||||||
ellipsis
|
position="bottomRight"
|
||||||
icon={<IconLink />}
|
content={
|
||||||
copyable={copyable}
|
<div
|
||||||
style={{
|
style={{
|
||||||
width: 240,
|
width: 412,
|
||||||
|
maxWidth: '96vw',
|
||||||
|
padding: '0 24px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{shareUrl}
|
{content}
|
||||||
</Text>
|
</div>
|
||||||
) : (
|
}
|
||||||
<Input
|
>
|
||||||
ref={ref}
|
{btn}
|
||||||
mode="password"
|
</Dropdown>
|
||||||
placeholder="设置访问密码"
|
|
||||||
value={sharePassword}
|
|
||||||
onChange={setSharePassword}
|
|
||||||
></Input>
|
|
||||||
)}
|
|
||||||
<div style={{ marginTop: 16 }}>
|
|
||||||
<Text type="tertiary">
|
|
||||||
{isPublic
|
|
||||||
? '分享开启后,该页面包含的所有内容均可访问,请谨慎开启'
|
|
||||||
: ' 分享关闭后,其他人将不能继续访问该页面'}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
<Space style={{ width: '100%', justifyContent: 'end', margin: '12px 0' }}>
|
|
||||||
<Button onClick={() => toggleVisible(false)}>取消</Button>
|
|
||||||
<Button theme="solid" type={isPublic ? 'danger' : 'primary'} onClick={handleOk}>
|
|
||||||
{isPublic ? '关闭分享' : '开启分享'}
|
|
||||||
</Button>
|
|
||||||
{isPublic && (
|
|
||||||
<Button theme="solid" type="primary" onClick={viewUrl}>
|
|
||||||
查看文档
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{render ? (
|
|
||||||
render({ isPublic, disabled, toggleVisible })
|
|
||||||
) : (
|
|
||||||
<Button disabled={disabled} type="primary" theme="light" onClick={toggleVisible}>
|
|
||||||
{isPublic ? '分享中' : '分享'}
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</Popover>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { IconArticle } from '@douyinfe/semi-icons';
|
import { IconArticle } from '@douyinfe/semi-icons';
|
||||||
import { Button, Popover, Radio, RadioGroup, Slider, Typography } from '@douyinfe/semi-ui';
|
import { Button, Dropdown, Radio, RadioGroup, Slider, Typography } from '@douyinfe/semi-ui';
|
||||||
import { throttle } from 'helpers/throttle';
|
import { throttle } from 'helpers/throttle';
|
||||||
import { useDocumentStyle } from 'hooks/use-document-style';
|
import { useDocumentStyle } from 'hooks/use-document-style';
|
||||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||||
|
@ -21,15 +21,14 @@ export const DocumentStyle = () => {
|
||||||
}, [setWidth]);
|
}, [setWidth]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Dropdown
|
||||||
key="style"
|
key="style"
|
||||||
showArrow
|
|
||||||
trigger="click"
|
trigger="click"
|
||||||
zIndex={1061}
|
zIndex={1061}
|
||||||
position={isMobile ? 'topRight' : 'bottomLeft'}
|
position={isMobile ? 'topRight' : 'bottomLeft'}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onVisibleChange={toggleVisible}
|
onVisibleChange={toggleVisible}
|
||||||
style={{ padding: 0 }}
|
onClickOutSide={toggleVisible}
|
||||||
content={
|
content={
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
<div className={styles.item}>
|
<div className={styles.item}>
|
||||||
|
@ -50,6 +49,6 @@ export const DocumentStyle = () => {
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button icon={<IconArticle />} theme="borderless" type="tertiary" onMouseDown={toggleVisible} />
|
<Button icon={<IconArticle />} theme="borderless" type="tertiary" onMouseDown={toggleVisible} />
|
||||||
</Popover>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Badge, Button, Dropdown, Modal, Pagination, Popover, TabPane, Tabs, Typography } from '@douyinfe/semi-ui';
|
import { Badge, Button, Dropdown, Modal, Pagination, TabPane, Tabs, Typography } from '@douyinfe/semi-ui';
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { Empty } from 'components/empty';
|
import { Empty } from 'components/empty';
|
||||||
import { IconMessage } from 'components/icons/IconMessage';
|
import { IconMessage } from 'components/icons/IconMessage';
|
||||||
|
@ -196,15 +196,13 @@ const MessageBox = () => {
|
||||||
{btn}
|
{btn}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Popover
|
<Dropdown
|
||||||
showArrow
|
|
||||||
style={{ padding: 0 }}
|
|
||||||
position="bottomRight"
|
position="bottomRight"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
content={<div style={{ width: 300, padding: '16px 16px 0' }}>{content}</div>}
|
content={<div style={{ width: 300, padding: '16px 16px 0' }}>{content}</div>}
|
||||||
>
|
>
|
||||||
{btn}
|
{btn}
|
||||||
</Popover>
|
</Dropdown>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { IconSearch as SemiIconSearch } from '@douyinfe/semi-icons';
|
import { IconSearch as SemiIconSearch } from '@douyinfe/semi-icons';
|
||||||
import { Button, Input, Modal, Spin, Typography } from '@douyinfe/semi-ui';
|
import { Button, Dropdown, Input, Modal, Spin, Typography } from '@douyinfe/semi-ui';
|
||||||
import { IDocument } from '@think/domains';
|
import { IDocument } from '@think/domains';
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { DocumentStar } from 'components/document/star';
|
import { DocumentStar } from 'components/document/star';
|
||||||
|
@ -8,10 +8,11 @@ import { IconSearch } from 'components/icons';
|
||||||
import { IconDocumentFill } from 'components/icons/IconDocumentFill';
|
import { IconDocumentFill } from 'components/icons/IconDocumentFill';
|
||||||
import { LocaleTime } from 'components/locale-time';
|
import { LocaleTime } from 'components/locale-time';
|
||||||
import { useAsyncLoading } from 'hooks/use-async-loading';
|
import { useAsyncLoading } from 'hooks/use-async-loading';
|
||||||
|
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Router from 'next/router';
|
import Router from 'next/router';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { HttpClient } from 'services/http-client';
|
import { HttpClient } from 'services/http-client';
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
@ -68,6 +69,8 @@ const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Search = () => {
|
export const Search = () => {
|
||||||
|
const ref = useRef<HTMLInputElement>();
|
||||||
|
const { isMobile } = IsOnMobile.useHook();
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
const [visible, toggleVisible] = useToggle(false);
|
||||||
const [searchApi, loading] = useAsyncLoading(searchDocument, 10);
|
const [searchApi, loading] = useAsyncLoading(searchDocument, 10);
|
||||||
const [keyword, setKeyword] = useState('');
|
const [keyword, setKeyword] = useState('');
|
||||||
|
@ -85,6 +88,60 @@ export const Search = () => {
|
||||||
});
|
});
|
||||||
}, [searchApi, keyword]);
|
}, [searchApi, keyword]);
|
||||||
|
|
||||||
|
const onKeywordChange = useCallback((val) => {
|
||||||
|
setSearchDocs([]);
|
||||||
|
setKeyword(val);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const content = useMemo(
|
||||||
|
() => (
|
||||||
|
<div style={{ paddingBottom: 24 }}>
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
showClear
|
||||||
|
ref={ref}
|
||||||
|
placeholder={'搜索文档'}
|
||||||
|
value={keyword}
|
||||||
|
onChange={onKeywordChange}
|
||||||
|
onEnterPress={search}
|
||||||
|
suffix={<SemiIconSearch onClick={search} style={{ cursor: 'pointer' }} />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ height: 'calc(68vh - 40px)', paddingBottom: 36, 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>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
[error, keyword, loading, onKeywordChange, search, searchDocs]
|
||||||
|
);
|
||||||
|
|
||||||
|
const btn = useMemo(
|
||||||
|
() => <Button type="tertiary" theme="borderless" icon={<IconSearch />} onClick={toggleVisible} />,
|
||||||
|
[toggleVisible]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
setTimeout(() => ref.current?.focus(), 100);
|
||||||
|
}
|
||||||
|
}, [visible]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fn = () => {
|
const fn = () => {
|
||||||
toggleVisible(false);
|
toggleVisible(false);
|
||||||
|
@ -99,56 +156,39 @@ export const Search = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button type="tertiary" theme="borderless" icon={<IconSearch />} onClick={toggleVisible} />
|
{!isMobile ? (
|
||||||
<Modal
|
<Dropdown
|
||||||
visible={visible}
|
position="bottomRight"
|
||||||
title="文档搜索"
|
trigger="click"
|
||||||
footer={null}
|
visible={visible}
|
||||||
onCancel={toggleVisible}
|
onVisibleChange={toggleVisible}
|
||||||
style={{
|
content={
|
||||||
maxWidth: '96vw',
|
<div style={{ width: 360, maxWidth: '96vw', maxHeight: '70vh', overflow: 'auto', padding: '16px 16px 0' }}>
|
||||||
}}
|
{content}
|
||||||
bodyStyle={{
|
</div>
|
||||||
height: '68vh',
|
}
|
||||||
}}
|
>
|
||||||
>
|
{btn}
|
||||||
<div style={{ paddingBottom: 24 }}>
|
</Dropdown>
|
||||||
<div>
|
) : (
|
||||||
<Input
|
<>
|
||||||
autofocus
|
<Modal
|
||||||
placeholder={'搜索文档'}
|
visible={visible}
|
||||||
size="large"
|
title="文档搜索"
|
||||||
value={keyword}
|
footer={null}
|
||||||
onChange={(val) => {
|
onCancel={toggleVisible}
|
||||||
setSearchDocs([]);
|
style={{
|
||||||
setKeyword(val);
|
maxWidth: '96vw',
|
||||||
}}
|
}}
|
||||||
onEnterPress={search}
|
bodyStyle={{
|
||||||
suffix={<SemiIconSearch onClick={search} style={{ cursor: 'pointer' }} />}
|
height: '68vh',
|
||||||
showClear
|
}}
|
||||||
/>
|
>
|
||||||
</div>
|
{content}
|
||||||
<div style={{ height: 'calc(68vh - 40px)', paddingBottom: 36, overflow: 'auto' }}>
|
</Modal>
|
||||||
<DataRender
|
{btn}
|
||||||
loading={loading}
|
</>
|
||||||
loadingContent={
|
)}
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
paddingTop: 30,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Spin />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
error={error}
|
|
||||||
normalContent={() => <List data={searchDocs} />}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,6 +23,12 @@ html:focus-within {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion) {
|
||||||
|
html:focus-within {
|
||||||
|
scroll-behavior: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -194,9 +194,11 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.tocsWrap}>
|
{!isMobile && (
|
||||||
<Tocs editor={editor} getContainer={getTocsContainer} />
|
<div className={styles.tocsWrap}>
|
||||||
</div>
|
<Tocs editor={editor} getContainer={getTocsContainer} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{protals}
|
{protals}
|
||||||
{!editable && <ImageViewer container={$mainContainer.current} />}
|
{!editable && <ImageViewer container={$mainContainer.current} />}
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -36,9 +36,11 @@ export const ReaderEditor: React.FC<IProps> = ({ content }) => {
|
||||||
<div className={styles.contentWrap}>
|
<div className={styles.contentWrap}>
|
||||||
<EditorContent editor={editor} />
|
<EditorContent editor={editor} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.tocsWrap}>
|
{!isMobile && (
|
||||||
<Tocs editor={editor} getContainer={getTocsContainer} />
|
<div className={styles.tocsWrap}>
|
||||||
</div>
|
<Tocs editor={editor} getContainer={getTocsContainer} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<ImageViewer container={$mainContainer.current} />
|
<ImageViewer container={$mainContainer.current} />
|
||||||
</main>
|
</main>
|
||||||
<BackTop
|
<BackTop
|
||||||
|
|
|
@ -4,8 +4,8 @@ import { throttle } from 'helpers/throttle';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import scrollIntoView from 'scroll-into-view-if-needed';
|
import scrollIntoView from 'scroll-into-view-if-needed';
|
||||||
|
import { Editor } from 'tiptap/core';
|
||||||
import { TableOfContents } from 'tiptap/core/extensions/table-of-contents';
|
import { TableOfContents } from 'tiptap/core/extensions/table-of-contents';
|
||||||
import { Editor } from 'tiptap/editor/react';
|
|
||||||
import { findNode } from 'tiptap/prose-utils';
|
import { findNode } from 'tiptap/prose-utils';
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
Loading…
Reference in New Issue