mirror of https://github.com/fantasticit/think.git
feat: improve star, ui of document actions
This commit is contained in:
parent
a3806b2778
commit
661dc1862f
|
@ -0,0 +1,8 @@
|
||||||
|
.hoverVisible {
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.isActive {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,31 @@
|
||||||
import { IconMore, IconPlus, IconStar } from '@douyinfe/semi-icons';
|
import { IconArticle, IconBranch, IconHistory, IconMore, IconPlus, IconStar } from '@douyinfe/semi-icons';
|
||||||
import { Button, Dropdown, Popover, Space, Typography } from '@douyinfe/semi-ui';
|
import { Button, Dropdown, Space, Typography } from '@douyinfe/semi-ui';
|
||||||
|
import { ButtonProps } from '@douyinfe/semi-ui/button/Button';
|
||||||
|
import cls from 'classnames';
|
||||||
import { DocumentCreator } from 'components/document/create';
|
import { DocumentCreator } from 'components/document/create';
|
||||||
import { DocumentDeletor } from 'components/document/delete';
|
import { DocumentDeletor } from 'components/document/delete';
|
||||||
import { DocumentLinkCopyer } from 'components/document/link';
|
import { DocumentLinkCopyer } from 'components/document/link';
|
||||||
|
import { DocumentShare } from 'components/document/share';
|
||||||
import { DocumentStar } from 'components/document/star';
|
import { DocumentStar } from 'components/document/star';
|
||||||
|
import { DocumentStyle } from 'components/document/style';
|
||||||
|
import { DocumentVersionTrigger } from 'components/document/version';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
|
|
||||||
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
wikiId: string;
|
wikiId: string;
|
||||||
documentId: string;
|
documentId: string;
|
||||||
|
hoverVisible?: boolean;
|
||||||
onStar?: () => void;
|
onStar?: () => void;
|
||||||
onCreate?: () => void;
|
onCreate?: () => void;
|
||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
onVisibleChange?: () => void;
|
onVisibleChange?: () => void;
|
||||||
showCreateDocument?: boolean;
|
showCreateDocument?: boolean;
|
||||||
|
size?: ButtonProps['size'];
|
||||||
|
hideDocumentVersion?: boolean;
|
||||||
|
hideDocumentStyle?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
@ -22,12 +33,15 @@ const { Text } = Typography;
|
||||||
export const DocumentActions: React.FC<IProps> = ({
|
export const DocumentActions: React.FC<IProps> = ({
|
||||||
wikiId,
|
wikiId,
|
||||||
documentId,
|
documentId,
|
||||||
|
hoverVisible,
|
||||||
onStar,
|
onStar,
|
||||||
onCreate,
|
onCreate,
|
||||||
onDelete,
|
onDelete,
|
||||||
onVisibleChange,
|
onVisibleChange,
|
||||||
showCreateDocument,
|
showCreateDocument,
|
||||||
children,
|
size = 'default',
|
||||||
|
hideDocumentVersion = false,
|
||||||
|
hideDocumentStyle = false,
|
||||||
}) => {
|
}) => {
|
||||||
const [popoverVisible, togglePopoverVisible] = useToggle(false);
|
const [popoverVisible, togglePopoverVisible] = useToggle(false);
|
||||||
const [createVisible, toggleCreateVisible] = useToggle(false);
|
const [createVisible, toggleCreateVisible] = useToggle(false);
|
||||||
|
@ -52,9 +66,10 @@ export const DocumentActions: React.FC<IProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Popover
|
<Dropdown
|
||||||
showArrow
|
|
||||||
style={{ padding: 0 }}
|
style={{ padding: 0 }}
|
||||||
|
trigger="click"
|
||||||
|
position="bottomLeft"
|
||||||
visible={popoverVisible}
|
visible={popoverVisible}
|
||||||
onVisibleChange={wrapOnVisibleChange}
|
onVisibleChange={wrapOnVisibleChange}
|
||||||
content={
|
content={
|
||||||
|
@ -70,7 +85,25 @@ export const DocumentActions: React.FC<IProps> = ({
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<DocumentShare
|
||||||
|
key="share"
|
||||||
|
documentId={documentId}
|
||||||
|
render={({ isPublic, toggleVisible }) => {
|
||||||
|
return (
|
||||||
|
<Dropdown.Item onClick={toggleVisible}>
|
||||||
|
<Text>
|
||||||
|
<Space>
|
||||||
|
<IconBranch />
|
||||||
|
{isPublic ? '分享中' : '分享'}
|
||||||
|
</Space>
|
||||||
|
</Text>
|
||||||
|
</Dropdown.Item>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<DocumentStar
|
<DocumentStar
|
||||||
|
wikiId={wikiId}
|
||||||
documentId={documentId}
|
documentId={documentId}
|
||||||
render={({ star, toggleStar, text }) => (
|
render={({ star, toggleStar, text }) => (
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
|
@ -96,10 +129,56 @@ export const DocumentActions: React.FC<IProps> = ({
|
||||||
wikiId={wikiId}
|
wikiId={wikiId}
|
||||||
documentId={documentId}
|
documentId={documentId}
|
||||||
render={({ copy, children }) => {
|
render={({ copy, children }) => {
|
||||||
return <Dropdown.Item onClick={copy}>{children}</Dropdown.Item>;
|
return (
|
||||||
|
<Dropdown.Item onClick={copy}>
|
||||||
|
<Text>{children}</Text>
|
||||||
|
</Dropdown.Item>
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{!hideDocumentVersion && (
|
||||||
|
<DocumentVersionTrigger
|
||||||
|
key="version"
|
||||||
|
documentId={documentId}
|
||||||
|
render={({ onClick }) => {
|
||||||
|
return (
|
||||||
|
<Dropdown.Item
|
||||||
|
onClick={() => {
|
||||||
|
togglePopoverVisible(false);
|
||||||
|
onClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
<Space>
|
||||||
|
<IconHistory />
|
||||||
|
历史记录
|
||||||
|
</Space>
|
||||||
|
</Text>
|
||||||
|
</Dropdown.Item>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!hideDocumentVersion && (
|
||||||
|
<DocumentStyle
|
||||||
|
key="style"
|
||||||
|
render={({ onClick }) => {
|
||||||
|
return (
|
||||||
|
<Dropdown.Item onClick={onClick}>
|
||||||
|
<Text>
|
||||||
|
<Space>
|
||||||
|
<IconArticle />
|
||||||
|
文档排版
|
||||||
|
</Space>
|
||||||
|
</Text>
|
||||||
|
</Dropdown.Item>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Dropdown.Divider />
|
<Dropdown.Divider />
|
||||||
|
|
||||||
<DocumentDeletor
|
<DocumentDeletor
|
||||||
|
@ -113,9 +192,17 @@ export const DocumentActions: React.FC<IProps> = ({
|
||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{children || <Button icon={<IconMore />} theme="borderless" type="tertiary" />}
|
<Button
|
||||||
</Popover>
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
type="tertiary"
|
||||||
|
size={size}
|
||||||
|
className={cls(hoverVisible && styles.hoverVisible, popoverVisible && styles.isActive)}
|
||||||
|
theme={popoverVisible ? 'solid' : 'borderless'}
|
||||||
|
icon={<IconMore />}
|
||||||
|
/>
|
||||||
|
</Dropdown>
|
||||||
{showCreateDocument && (
|
{showCreateDocument && (
|
||||||
<DocumentCreator
|
<DocumentCreator
|
||||||
wikiId={wikiId}
|
wikiId={wikiId}
|
||||||
|
|
|
@ -40,7 +40,7 @@ export const DocumentCard: React.FC<{ document: IDocument }> = ({ document }) =>
|
||||||
<Tooltip key="edit" content="编辑" position="bottom">
|
<Tooltip key="edit" content="编辑" position="bottom">
|
||||||
<Button type="tertiary" theme="borderless" icon={<IconEdit />} onClick={gotoEdit} />
|
<Button type="tertiary" theme="borderless" icon={<IconEdit />} onClick={gotoEdit} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<DocumentStar documentId={document.id} />
|
<DocumentStar wikiId={document.wikiId} documentId={document.id} />
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
|
@ -51,7 +51,6 @@ const renderChecked = (onChange, authKey: 'readable' | 'editable') => (checked,
|
||||||
|
|
||||||
export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, disabled = false }) => {
|
export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, disabled = false }) => {
|
||||||
const { isMobile } = IsOnMobile.useHook();
|
const { isMobile } = IsOnMobile.useHook();
|
||||||
const toastedUsersRef = useRef<Array<IUser['id']>>([]);
|
|
||||||
const ref = useRef<HTMLInputElement>();
|
const ref = useRef<HTMLInputElement>();
|
||||||
const { user: currentUser } = useUser();
|
const { user: currentUser } = useUser();
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
const [visible, toggleVisible] = useToggle(false);
|
||||||
|
@ -86,7 +85,7 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
|
||||||
<div style={{ marginTop: 16 }}>
|
<div style={{ marginTop: 16 }}>
|
||||||
<Input ref={ref} placeholder="输入对方用户名" value={inviteUser} onChange={setInviteUser}></Input>
|
<Input ref={ref} placeholder="输入对方用户名" value={inviteUser} onChange={setInviteUser}></Input>
|
||||||
<Paragraph style={{ marginTop: 16 }}>
|
<Paragraph style={{ marginTop: 16 }}>
|
||||||
邀请成功后,请将该链接发送给对方。
|
将对方加入文档进行协作,您也可将该链接发送给对方。
|
||||||
<span style={{ verticalAlign: 'middle' }}>
|
<span style={{ verticalAlign: 'middle' }}>
|
||||||
<DocumentLinkCopyer wikiId={wikiId} documentId={documentId} />
|
<DocumentLinkCopyer wikiId={wikiId} documentId={documentId} />
|
||||||
</span>
|
</span>
|
||||||
|
@ -151,38 +150,30 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (mentionUsers) => {
|
const handler = (users) => {
|
||||||
const newCollaborationUsers = mentionUsers
|
const joinUsers = users
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.filter((state) => state.user)
|
.filter((state) => state.user)
|
||||||
.map((state) => ({ ...state.user, clientId: state.clientId }));
|
.map((state) => ({ ...state.user, clientId: state.clientId }));
|
||||||
|
|
||||||
if (
|
joinUsers
|
||||||
collaborationUsers.length === newCollaborationUsers.length &&
|
.filter(Boolean)
|
||||||
newCollaborationUsers.every((newUser) => {
|
.filter((joinUser) => {
|
||||||
return collaborationUsers.find((existUser) => existUser.id === newUser.id);
|
return joinUser.name !== currentUser.name;
|
||||||
})
|
})
|
||||||
) {
|
.forEach((joinUser) => {
|
||||||
return;
|
Toast.info(`${joinUser.name}-${joinUser.clientId}加入文档`);
|
||||||
}
|
});
|
||||||
|
|
||||||
newCollaborationUsers.forEach((newUser) => {
|
setCollaborationUsers(joinUsers);
|
||||||
if (currentUser && newUser.name !== currentUser.name && !toastedUsersRef.current.includes(newUser.id)) {
|
|
||||||
Toast.info(`${newUser.name}-${newUser.clientId}加入文档`);
|
|
||||||
toastedUsersRef.current.push(newUser.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setCollaborationUsers(newCollaborationUsers);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
event.on(JOIN_USER, handler);
|
event.on(JOIN_USER, handler);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
toastedUsersRef.current = [];
|
|
||||||
event.off(JOIN_USER, handler);
|
event.off(JOIN_USER, handler);
|
||||||
};
|
};
|
||||||
}, [collaborationUsers, currentUser]);
|
}, [currentUser]);
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -54,7 +54,6 @@ export const DocumentDeletor: React.FC<IProps> = ({ wikiId, documentId, render,
|
||||||
onConfirm={deleteAction}
|
onConfirm={deleteAction}
|
||||||
okButtonProps={{ loading }}
|
okButtonProps={{ loading }}
|
||||||
zIndex={1070}
|
zIndex={1070}
|
||||||
showArrow
|
|
||||||
>
|
>
|
||||||
{render ? render({ children: content }) : content}
|
{render ? render({ children: content }) : content}
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
|
|
|
@ -3,9 +3,7 @@ import { Button, Nav, Skeleton, Space, Tooltip, Typography } from '@douyinfe/sem
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { Divider } from 'components/divider';
|
import { Divider } from 'components/divider';
|
||||||
import { DocumentCollaboration } from 'components/document/collaboration';
|
import { DocumentCollaboration } from 'components/document/collaboration';
|
||||||
import { DocumentShare } from 'components/document/share';
|
|
||||||
import { DocumentStar } from 'components/document/star';
|
import { DocumentStar } from 'components/document/star';
|
||||||
import { DocumentStyle } from 'components/document/style';
|
|
||||||
import { DocumentVersion } from 'components/document/version';
|
import { DocumentVersion } from 'components/document/version';
|
||||||
import { Seo } from 'components/seo';
|
import { Seo } from 'components/seo';
|
||||||
import { Theme } from 'components/theme';
|
import { Theme } from 'components/theme';
|
||||||
|
@ -20,6 +18,7 @@ import { SecureDocumentIllustration } from 'illustrations/secure-document';
|
||||||
import Router from 'next/router';
|
import Router from 'next/router';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { DocumentActions } from '../actions';
|
||||||
import { Editor } from './editor';
|
import { Editor } from './editor';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
@ -49,12 +48,11 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
||||||
() => (
|
() => (
|
||||||
<Space>
|
<Space>
|
||||||
{document && authority.readable && (
|
{document && authority.readable && (
|
||||||
<DocumentCollaboration key="collaboration" wikiId={document.wikiId} documentId={documentId} />
|
<DocumentCollaboration key={documentId} wikiId={document.wikiId} documentId={documentId} />
|
||||||
)}
|
)}
|
||||||
<DocumentShare key="share" documentId={documentId} />
|
{document && <DocumentStar key="star" wikiId={document.wikiId} documentId={documentId} />}
|
||||||
<DocumentVersion key="version" documentId={documentId} onSelect={triggerUseDocumentVersion} />
|
{document && <DocumentActions wikiId={document.wikiId} documentId={documentId} />}
|
||||||
<DocumentStar key="star" documentId={documentId} />
|
<DocumentVersion documentId={documentId} onSelect={triggerUseDocumentVersion} />
|
||||||
<DocumentStyle />
|
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
[documentId, document, authority]
|
[documentId, document, authority]
|
||||||
|
|
|
@ -18,6 +18,7 @@ import React, { useCallback, useMemo } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { CollaborationEditor } from 'tiptap/editor';
|
import { CollaborationEditor } from 'tiptap/editor';
|
||||||
|
|
||||||
|
import { DocumentActions } from '../actions';
|
||||||
import { Author } from './author';
|
import { Author } from './author';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
|
@ -64,18 +65,17 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
||||||
{document && (
|
{document && (
|
||||||
<DocumentCollaboration
|
<DocumentCollaboration
|
||||||
disabled={!readable}
|
disabled={!readable}
|
||||||
key="collaboration"
|
key={documentId}
|
||||||
wikiId={document.wikiId}
|
wikiId={document.wikiId}
|
||||||
documentId={documentId}
|
documentId={documentId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{document && <DocumentStar disabled={!readable} key="star" wikiId={document.wikiId} documentId={documentId} />}
|
||||||
<Tooltip key="edit" content="编辑" position="bottom">
|
<Tooltip key="edit" content="编辑" position="bottom">
|
||||||
<Button disabled={!editable} icon={<IconEdit />} onMouseDown={gotoEdit} />
|
<Button disabled={!editable} icon={<IconEdit />} onMouseDown={gotoEdit} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<DocumentShare disabled={!readable} key="share" documentId={documentId} />
|
{document && <DocumentActions wikiId={document.wikiId} documentId={documentId} />}
|
||||||
<DocumentVersion disabled={!readable} key="version" documentId={documentId} />
|
<DocumentVersion documentId={documentId} />
|
||||||
<DocumentStar disabled={!readable} key="star" documentId={documentId} />
|
|
||||||
<DocumentStyle />
|
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
}, [document, documentId, readable, editable, gotoEdit]);
|
}, [document, documentId, readable, editable, gotoEdit]);
|
||||||
|
|
|
@ -11,7 +11,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
interface IProps {
|
interface IProps {
|
||||||
documentId: string;
|
documentId: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
render?: (arg: { isPublic: boolean; disabled: boolean; toggleVisible: (arg: boolean) => void }) => React.ReactNode;
|
render?: (arg: { isPublic: boolean; disabled: boolean; toggleVisible: () => void }) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
@ -134,6 +134,7 @@ export const DocumentShare: React.FC<IProps> = ({ documentId, disabled = false,
|
||||||
footer={null}
|
footer={null}
|
||||||
onCancel={toggleVisible}
|
onCancel={toggleVisible}
|
||||||
style={{ maxWidth: '96vw' }}
|
style={{ maxWidth: '96vw' }}
|
||||||
|
zIndex={1067}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import { IconStar } from '@douyinfe/semi-icons';
|
import { IconStar } from '@douyinfe/semi-icons';
|
||||||
import { Button, Tooltip } from '@douyinfe/semi-ui';
|
import { Button, Tooltip } from '@douyinfe/semi-ui';
|
||||||
import { useDocumentCollectToggle } from 'data/collector';
|
import { IDocument, IWiki } from '@think/domains';
|
||||||
|
import { useDocumentStarToggle } from 'data/star';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import VisibilitySensor from 'react-visibility-sensor';
|
import VisibilitySensor from 'react-visibility-sensor';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
documentId: string;
|
wikiId: IWiki['id'];
|
||||||
|
documentId: IDocument['id'];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
render?: (arg: {
|
render?: (arg: {
|
||||||
star: boolean;
|
star: boolean;
|
||||||
|
@ -16,9 +18,9 @@ interface IProps {
|
||||||
}) => React.ReactNode;
|
}) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentStar: React.FC<IProps> = ({ documentId, disabled = false, render }) => {
|
export const DocumentStar: React.FC<IProps> = ({ wikiId, documentId, disabled = false, render }) => {
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
const [visible, toggleVisible] = useToggle(false);
|
||||||
const { data, toggle: toggleStar } = useDocumentCollectToggle(documentId, { enabled: visible });
|
const { data, toggle: toggleStar } = useDocumentStarToggle(wikiId, documentId, { enabled: visible });
|
||||||
const text = data ? '取消收藏' : '收藏文档';
|
const text = data ? '取消收藏' : '收藏文档';
|
||||||
|
|
||||||
const onViewportChange = useCallback(
|
const onViewportChange = useCallback(
|
||||||
|
|
|
@ -10,7 +10,11 @@ import styles from './index.module.scss';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
export const DocumentStyle = () => {
|
interface IProps {
|
||||||
|
render?: (arg: { onClick: () => void }) => React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DocumentStyle: React.FC<IProps> = ({ render }) => {
|
||||||
const { isMobile } = IsOnMobile.useHook();
|
const { isMobile } = IsOnMobile.useHook();
|
||||||
const { width, fontSize, setWidth, setFontSize } = useDocumentStyle();
|
const { width, fontSize, setWidth, setFontSize } = useDocumentStyle();
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
const [visible, toggleVisible] = useToggle(false);
|
||||||
|
@ -30,7 +34,12 @@ export const DocumentStyle = () => {
|
||||||
onVisibleChange={toggleVisible}
|
onVisibleChange={toggleVisible}
|
||||||
onClickOutSide={toggleVisible}
|
onClickOutSide={toggleVisible}
|
||||||
content={
|
content={
|
||||||
<div className={styles.wrap}>
|
<div
|
||||||
|
className={styles.wrap}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className={styles.item}>
|
<div className={styles.item}>
|
||||||
<Text>正文大小</Text>
|
<Text>正文大小</Text>
|
||||||
<Text style={{ fontSize: '0.8em' }}> {fontSize}px</Text>
|
<Text style={{ fontSize: '0.8em' }}> {fontSize}px</Text>
|
||||||
|
@ -48,7 +57,11 @@ export const DocumentStyle = () => {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button icon={<IconArticle />} theme="borderless" type="tertiary" onMouseDown={toggleVisible} />
|
{render ? (
|
||||||
|
render({ onClick: toggleVisible })
|
||||||
|
) : (
|
||||||
|
<Button icon={<IconArticle />} theme="borderless" type="tertiary" onMouseDown={toggleVisible} />
|
||||||
|
)}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
&.isMobile {
|
&.isMobile {
|
||||||
padding: 0;
|
padding: 0 0 24px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
|
|
|
@ -6,24 +6,26 @@ import { DataRender } from 'components/data-render';
|
||||||
import { LocaleTime } from 'components/locale-time';
|
import { LocaleTime } from 'components/locale-time';
|
||||||
import { useDocumentVersion } from 'data/document';
|
import { useDocumentVersion } from 'data/document';
|
||||||
import { safeJSONParse } from 'helpers/json';
|
import { safeJSONParse } from 'helpers/json';
|
||||||
|
import { DocumentVersionControl } from 'hooks/use-document-version';
|
||||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { CollaborationKit } from 'tiptap/editor';
|
import { CollaborationKit } from 'tiptap/editor';
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
interface IProps {
|
export interface IProps {
|
||||||
documentId: string;
|
documentId: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onSelect?: (data) => void;
|
onSelect?: (data) => void;
|
||||||
|
render?: (arg: { onClick: (arg?: any) => void; disabled: boolean }) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Title, Text } = Typography;
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
export const DocumentVersion: React.FC<IProps> = ({ documentId, disabled = false, onSelect }) => {
|
export const DocumentVersion: React.FC<Partial<IProps>> = ({ documentId, onSelect }) => {
|
||||||
const { isMobile } = IsOnMobile.useHook();
|
const { isMobile } = IsOnMobile.useHook();
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
|
||||||
|
const { visible, toggleVisible } = DocumentVersionControl.useHook();
|
||||||
const { data, loading, error, refresh } = useDocumentVersion(documentId, { enabled: visible });
|
const { data, loading, error, refresh } = useDocumentVersion(documentId, { enabled: visible });
|
||||||
const [selectedVersion, setSelectedVersion] = useState(null);
|
const [selectedVersion, setSelectedVersion] = useState(null);
|
||||||
|
|
||||||
|
@ -61,9 +63,6 @@ export const DocumentVersion: React.FC<IProps> = ({ documentId, disabled = false
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button type="primary" theme="light" disabled={disabled} onClick={toggleVisible}>
|
|
||||||
文档版本
|
|
||||||
</Button>
|
|
||||||
<Modal
|
<Modal
|
||||||
title="历史记录"
|
title="历史记录"
|
||||||
fullScreen
|
fullScreen
|
||||||
|
@ -139,3 +138,21 @@ export const DocumentVersion: React.FC<IProps> = ({ documentId, disabled = false
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DocumentVersionTrigger: React.FC<Partial<IProps>> = ({ render, disabled }) => {
|
||||||
|
const { toggleVisible } = DocumentVersionControl.useHook();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{render ? (
|
||||||
|
render({ onClick: toggleVisible, disabled })
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Button type="primary" theme="light" disabled={disabled} onClick={toggleVisible}>
|
||||||
|
历史记录
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -54,7 +54,7 @@ const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.rightWrap}>
|
<div className={styles.rightWrap}>
|
||||||
<DocumentStar documentId={doc.id} />
|
<DocumentStar wikiId={doc.wikiId} documentId={doc.id} />
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Avatar, Skeleton, Space, Typography } from '@douyinfe/semi-ui';
|
||||||
import { IconDocument } from 'components/icons/IconDocument';
|
import { IconDocument } from 'components/icons/IconDocument';
|
||||||
import { LocaleTime } from 'components/locale-time';
|
import { LocaleTime } from 'components/locale-time';
|
||||||
import { WikiStar } from 'components/wiki/star';
|
import { WikiStar } from 'components/wiki/star';
|
||||||
import { IWikiWithIsMember } from 'data/collector';
|
import { IWikiWithIsMember } from 'data/star';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { TabPane, Tabs } from '@douyinfe/semi-ui';
|
import { TabPane, Tabs } from '@douyinfe/semi-ui';
|
||||||
import { IWiki } from '@think/domains';
|
import { IWiki } from '@think/domains';
|
||||||
import { Seo } from 'components/seo';
|
import { Seo } from 'components/seo';
|
||||||
|
import { WikiTocsManager } from 'components/wiki/tocs/manager';
|
||||||
import { useWikiDetail } from 'data/wiki';
|
import { useWikiDetail } from 'data/wiki';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
@ -19,6 +20,9 @@ interface IProps {
|
||||||
const TitleMap = {
|
const TitleMap = {
|
||||||
base: '基础信息',
|
base: '基础信息',
|
||||||
privacy: '隐私管理',
|
privacy: '隐私管理',
|
||||||
|
tocs: '目录管理',
|
||||||
|
share: '隐私管理',
|
||||||
|
documents: '全部文档',
|
||||||
users: '成员管理',
|
users: '成员管理',
|
||||||
import: '导入文档',
|
import: '导入文档',
|
||||||
more: '更多',
|
more: '更多',
|
||||||
|
@ -34,9 +38,15 @@ export const WikiSetting: React.FC<IProps> = ({ wikiId, tab, onNavigate }) => {
|
||||||
<TabPane tab={TitleMap['base']} itemKey="base">
|
<TabPane tab={TitleMap['base']} itemKey="base">
|
||||||
<Base wiki={data} update={update as any} />
|
<Base wiki={data} update={update as any} />
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
<TabPane tab={TitleMap['users']} itemKey="users">
|
<TabPane tab={TitleMap['users']} itemKey="users">
|
||||||
<Users wikiId={wikiId} />
|
<Users wikiId={wikiId} />
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
||||||
|
<TabPane tab={TitleMap['tocs']} itemKey="tocs">
|
||||||
|
<WikiTocsManager wikiId={wikiId} />
|
||||||
|
</TabPane>
|
||||||
|
|
||||||
<TabPane tab={TitleMap['privacy']} itemKey="privacy">
|
<TabPane tab={TitleMap['privacy']} itemKey="privacy">
|
||||||
<Privacy wikiId={wikiId} />
|
<Privacy wikiId={wikiId} />
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|
|
@ -1,13 +1,64 @@
|
||||||
/* stylelint-disable */
|
/* stylelint-disable */
|
||||||
.wrap {
|
.statusWrap {
|
||||||
.statusWrap {
|
padding: 10px 12px;
|
||||||
padding: 10px 12px;
|
margin-top: 16px;
|
||||||
margin-top: 16px;
|
border: 1px solid var(--semi-color-border);
|
||||||
border: 1px solid var(--semi-color-border);
|
border-radius: 4px;
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectedItem {
|
||||||
|
:global {
|
||||||
|
.semi-icon-close {
|
||||||
|
color: var(--semi-color-tertiary);
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
:global {
|
||||||
|
.semi-icon-close {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selectedItem,
|
||||||
|
.sourceItem {
|
||||||
|
display: flex;
|
||||||
|
height: 36px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--semi-color-fill-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
margin-left: 8px;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: var(--semi-color-text-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.transferWrap {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 16px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
|
@ -16,11 +16,65 @@ interface IProps {
|
||||||
|
|
||||||
export const Privacy: React.FC<IProps> = ({ wikiId }) => {
|
export const Privacy: React.FC<IProps> = ({ wikiId }) => {
|
||||||
const { data: wiki, toggleStatus: toggleWorkspaceStatus } = useWikiDetail(wikiId);
|
const { data: wiki, toggleStatus: toggleWorkspaceStatus } = useWikiDetail(wikiId);
|
||||||
|
const { data: tocs } = useWikiTocs(wikiId);
|
||||||
|
|
||||||
const [nextStatus, setNextStatus] = useState('');
|
const [nextStatus, setNextStatus] = useState('');
|
||||||
const isPublic = useMemo(() => wiki && isPublicWiki(wiki.status), [wiki]);
|
const isPublic = useMemo(() => wiki && isPublicWiki(wiki.status), [wiki]);
|
||||||
|
|
||||||
|
const documents = useMemo(
|
||||||
|
() =>
|
||||||
|
flattenTree2Array(tocs)
|
||||||
|
.sort((a, b) => a.index - b.index)
|
||||||
|
.map((d) => {
|
||||||
|
d.label = d.title;
|
||||||
|
d.value = d.id;
|
||||||
|
return d;
|
||||||
|
}),
|
||||||
|
[tocs]
|
||||||
|
);
|
||||||
|
const [publicDocumentIds, setPublicDocumentIds] = useState([]); // 公开的
|
||||||
|
const privateDocumentIds = useMemo(() => {
|
||||||
|
return documents.filter((doc) => !publicDocumentIds.includes(doc.id)).map((doc) => doc.id);
|
||||||
|
}, [documents, publicDocumentIds]);
|
||||||
|
|
||||||
|
const renderSourceItem = useCallback((item) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.sourceItem} key={item.id}>
|
||||||
|
<Checkbox
|
||||||
|
onChange={() => {
|
||||||
|
item.onChange();
|
||||||
|
}}
|
||||||
|
key={item.label}
|
||||||
|
checked={item.checked}
|
||||||
|
>
|
||||||
|
<Text>{item.title}</Text>
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderSelectedItem = useCallback((item) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.selectedItem} key={item.id}>
|
||||||
|
<Text>{item.title}</Text>
|
||||||
|
<IconClose onClick={item.onRemove} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const customFilter = useCallback((sugInput, item) => {
|
||||||
|
return item.title.includes(sugInput);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!documents.length) return;
|
||||||
|
const activeIds = documents.filter((doc) => isPublicDocument(doc.status)).map((doc) => doc.id);
|
||||||
|
setPublicDocumentIds(activeIds);
|
||||||
|
}, [documents]);
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
const data = { nextStatus };
|
const data = { nextStatus, publicDocumentIds, privateDocumentIds };
|
||||||
|
|
||||||
toggleWorkspaceStatus(data).then((res) => {
|
toggleWorkspaceStatus(data).then((res) => {
|
||||||
const ret = res as unknown as any & {
|
const ret = res as unknown as any & {
|
||||||
documentOperateMessage?: string;
|
documentOperateMessage?: string;
|
||||||
|
@ -64,6 +118,7 @@ export const Privacy: React.FC<IProps> = ({ wikiId }) => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles.statusWrap}>
|
<div className={styles.statusWrap}>
|
||||||
<Title className={styles.title} heading={6}>
|
<Title className={styles.title} heading={6}>
|
||||||
是否公开知识库?
|
是否公开知识库?
|
||||||
|
@ -78,6 +133,25 @@ export const Privacy: React.FC<IProps> = ({ wikiId }) => {
|
||||||
})}
|
})}
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={styles.transferWrap}
|
||||||
|
style={{
|
||||||
|
height: `calc(100vh - ${isPublic ? 426 : 342}px)`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Transfer
|
||||||
|
style={{ width: '100%', height: '100%' }}
|
||||||
|
dataSource={documents}
|
||||||
|
filter={customFilter}
|
||||||
|
value={publicDocumentIds}
|
||||||
|
renderSelectedItem={renderSelectedItem}
|
||||||
|
renderSourceItem={renderSourceItem}
|
||||||
|
inputProps={{ placeholder: '搜索文档' }}
|
||||||
|
onChange={(_, values) => setPublicDocumentIds(values.map((v) => v.id))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button style={{ marginTop: 16 }} type="primary" theme="solid" onClick={submit}>
|
<Button style={{ marginTop: 16 }} type="primary" theme="solid" onClick={submit}>
|
||||||
保存
|
保存
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -43,7 +43,9 @@ export const Users: React.FC<IProps> = ({ wikiId }) => {
|
||||||
normalContent={() => (
|
normalContent={() => (
|
||||||
<div style={{ margin: '24px 0' }}>
|
<div style={{ margin: '24px 0' }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
<Button onClick={toggleVisible}>添加用户</Button>
|
<Button onClick={toggleVisible} theme="solid">
|
||||||
|
添加用户
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Table style={{ margin: '16px 0' }} dataSource={users} size="small" pagination>
|
<Table style={{ margin: '16px 0' }} dataSource={users} size="small" pagination>
|
||||||
<Column title="用户名" dataIndex="userName" key="userName" />
|
<Column title="用户名" dataIndex="userName" key="userName" />
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { IconStar } from '@douyinfe/semi-icons';
|
import { IconStar } from '@douyinfe/semi-icons';
|
||||||
import { Button, Tooltip } from '@douyinfe/semi-ui';
|
import { Button, Tooltip } from '@douyinfe/semi-ui';
|
||||||
import { useWikiCollectToggle } from 'data/collector';
|
import { useWikiStarToggle } from 'data/star';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
@ -10,7 +10,7 @@ interface IProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WikiStar: React.FC<IProps> = ({ wikiId, render, onChange }) => {
|
export const WikiStar: React.FC<IProps> = ({ wikiId, render, onChange }) => {
|
||||||
const { data, toggle } = useWikiCollectToggle(wikiId);
|
const { data, toggle } = useWikiStarToggle(wikiId);
|
||||||
const text = data ? '取消收藏' : '收藏知识库';
|
const text = data ? '取消收藏' : '收藏知识库';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,12 +1,68 @@
|
||||||
|
/* stylelint-disable */
|
||||||
.wrap {
|
.wrap {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
padding-top: 12px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.treeWrap {
|
.titleWrap {
|
||||||
flex: 1;
|
display: flex;
|
||||||
padding: 0 8px 32px 16px;
|
padding: 8px 12px;
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--semi-color-fill-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.isActive {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--semi-color-primary);
|
||||||
|
background-color: var(--semi-color-primary-light-default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkWrap {
|
||||||
|
> a {
|
||||||
|
display: flex;
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin: 8px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--semi-color-fill-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.isActive {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--semi-color-primary);
|
||||||
|
background-color: var(--semi-color-primary-light-default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.treeWrap {
|
||||||
|
padding: 8px 12px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.semi-tree-option-list {
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,81 +86,14 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: calc(100% - 48px);
|
width: calc(100% - 48px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.right {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navItemWrap {
|
.hoverVisible {
|
||||||
display: flex;
|
opacity: 0;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--semi-color-text-0);
|
|
||||||
|
|
||||||
&.hoverable {
|
|
||||||
&:hover {
|
|
||||||
color: var(--semi-color-text-0);
|
|
||||||
background-color: var(--semi-color-fill-0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
&:hover,
|
||||||
&.isActive {
|
&.isActive {
|
||||||
font-weight: 600;
|
opacity: 1;
|
||||||
color: var(--semi-color-primary);
|
|
||||||
background-color: var(--semi-color-primary-light-default);
|
|
||||||
}
|
|
||||||
|
|
||||||
.navItem {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: var(--semi-border-radius-small);
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
> span {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-width: 24px;
|
|
||||||
min-height: 24px;
|
|
||||||
margin: 0 6px 0 0;
|
|
||||||
|
|
||||||
:global {
|
|
||||||
.semi-icon-default {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
overflow: hidden;
|
|
||||||
color: inherit;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightWrap {
|
|
||||||
padding-right: 4px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.docListTitle {
|
|
||||||
margin: 12px 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
import { IconPlus } from '@douyinfe/semi-icons';
|
import { IconPlus, IconSmallTriangleDown } from '@douyinfe/semi-icons';
|
||||||
import { Avatar, Button, Skeleton, Tooltip, Typography } from '@douyinfe/semi-ui';
|
import { Avatar, Button, Dropdown, Skeleton, Typography } from '@douyinfe/semi-ui';
|
||||||
import { isPublicWiki } from '@think/domains';
|
import cls from 'classnames';
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { IconDocument, IconGlobe, IconOverview, IconSetting } from 'components/icons';
|
import { IconOverview, IconSetting } from 'components/icons';
|
||||||
import { findParents } from 'components/wiki/tocs/utils';
|
import { findParents } from 'components/wiki/tocs/utils';
|
||||||
|
import { useStarWikis, useWikiStarDocuments } from 'data/star';
|
||||||
import { useWikiDetail, useWikiTocs } from 'data/wiki';
|
import { useWikiDetail, useWikiTocs } from 'data/wiki';
|
||||||
import { triggerCreateDocument } from 'event';
|
import { triggerCreateDocument } from 'event';
|
||||||
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
import { NavItem } from './nav-item';
|
|
||||||
import { Tree } from './tree';
|
import { Tree } from './tree';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
@ -28,9 +29,15 @@ export const WikiTocs: React.FC<IProps> = ({
|
||||||
docAsLink = '/wiki/[wikiId]/document/[documentId]',
|
docAsLink = '/wiki/[wikiId]/document/[documentId]',
|
||||||
getDocLink = (documentId) => `/wiki/${wikiId}/document/${documentId}`,
|
getDocLink = (documentId) => `/wiki/${wikiId}/document/${documentId}`,
|
||||||
}) => {
|
}) => {
|
||||||
const { pathname, query } = useRouter();
|
const { pathname } = useRouter();
|
||||||
const { data: wiki, loading: wikiLoading, error: wikiError } = useWikiDetail(wikiId);
|
const { data: wiki, loading: wikiLoading, error: wikiError } = useWikiDetail(wikiId);
|
||||||
const { data: tocs, loading: tocsLoading, error: tocsError, refresh } = useWikiTocs(wikiId);
|
const { data: tocs, loading: tocsLoading, error: tocsError } = useWikiTocs(wikiId);
|
||||||
|
const { data: starWikis } = useStarWikis();
|
||||||
|
const {
|
||||||
|
data: starDocuments,
|
||||||
|
loading: starDocumentsLoading,
|
||||||
|
error: starDocumentsError,
|
||||||
|
} = useWikiStarDocuments(wikiId);
|
||||||
const [parentIds, setParentIds] = useState<Array<string>>([]);
|
const [parentIds, setParentIds] = useState<Array<string>>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -44,134 +51,218 @@ export const WikiTocs: React.FC<IProps> = ({
|
||||||
<DataRender
|
<DataRender
|
||||||
loading={wikiLoading}
|
loading={wikiLoading}
|
||||||
loadingContent={
|
loadingContent={
|
||||||
<NavItem
|
<div className={styles.titleWrap}>
|
||||||
icon={
|
<Skeleton
|
||||||
<Skeleton.Avatar
|
placeholder={
|
||||||
size="small"
|
<div style={{ display: 'flex' }}>
|
||||||
style={{
|
<Skeleton.Avatar
|
||||||
marginRight: 8,
|
size="small"
|
||||||
width: 24,
|
style={{
|
||||||
height: 24,
|
marginRight: 8,
|
||||||
borderRadius: 4,
|
width: 24,
|
||||||
}}
|
height: 24,
|
||||||
></Skeleton.Avatar>
|
borderRadius: 4,
|
||||||
}
|
}}
|
||||||
text={<Skeleton.Title style={{ width: 120 }} />}
|
></Skeleton.Avatar>
|
||||||
/>
|
<Skeleton.Title style={{ width: 120 }} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
loading={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
error={wikiError}
|
error={wikiError}
|
||||||
normalContent={() => (
|
normalContent={() => (
|
||||||
<NavItem
|
<Dropdown
|
||||||
icon={
|
trigger={'click'}
|
||||||
<Avatar
|
position="bottomRight"
|
||||||
shape="square"
|
render={
|
||||||
size="small"
|
<Dropdown.Menu style={{ width: 180 }}>
|
||||||
src={wiki.avatar}
|
{(starWikis || [])
|
||||||
style={{
|
.filter((wiki) => wiki.id !== wikiId)
|
||||||
marginRight: 8,
|
.map((wiki) => {
|
||||||
width: 24,
|
return (
|
||||||
height: 24,
|
<Dropdown.Item key={wiki.id}>
|
||||||
borderRadius: 4,
|
<Link
|
||||||
}}
|
href={{
|
||||||
>
|
pathname: `/wiki/[wikiId]`,
|
||||||
{wiki.name.charAt(0)}
|
query: { wikiId: wiki.id },
|
||||||
</Avatar>
|
}}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<Avatar
|
||||||
|
shape="square"
|
||||||
|
size="small"
|
||||||
|
src={wiki.avatar}
|
||||||
|
style={{
|
||||||
|
marginRight: 8,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{wiki.name.charAt(0)}
|
||||||
|
</Avatar>
|
||||||
|
<Text strong>{wiki.name}</Text>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</Dropdown.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Dropdown.Menu>
|
||||||
}
|
}
|
||||||
text={<Text strong>{wiki.name}</Text>}
|
>
|
||||||
hoverable={false}
|
<div className={styles.titleWrap}>
|
||||||
/>
|
<span>
|
||||||
|
<Avatar
|
||||||
|
shape="square"
|
||||||
|
size="small"
|
||||||
|
src={wiki.avatar}
|
||||||
|
style={{
|
||||||
|
marginRight: 8,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{wiki.name.charAt(0)}
|
||||||
|
</Avatar>
|
||||||
|
<Text strong>{wiki.name}</Text>
|
||||||
|
</span>
|
||||||
|
<IconSmallTriangleDown />
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavItem
|
|
||||||
icon={<IconOverview />}
|
|
||||||
text={'概述'}
|
|
||||||
href={{
|
|
||||||
pathname: `/wiki/[wikiId]`,
|
|
||||||
query: { wikiId },
|
|
||||||
}}
|
|
||||||
isActive={pathname === '/wiki/[wikiId]' || (query && wiki && query.documentId === wiki.homeDocumentId)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<NavItem
|
|
||||||
icon={<IconSetting />}
|
|
||||||
text={'设置'}
|
|
||||||
href={{
|
|
||||||
pathname: `/wiki/[wikiId]/setting`,
|
|
||||||
query: { tab: 'base', wikiId },
|
|
||||||
}}
|
|
||||||
isActive={pathname === '/wiki/[wikiId]/setting'}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DataRender
|
<DataRender
|
||||||
loading={wikiLoading}
|
loading={wikiLoading}
|
||||||
loadingContent={
|
loadingContent={
|
||||||
<NavItem
|
<div className={styles.titleWrap}>
|
||||||
icon={
|
<Skeleton
|
||||||
<Skeleton.Avatar
|
placeholder={
|
||||||
size="small"
|
<div style={{ display: 'flex' }}>
|
||||||
style={{
|
<Skeleton.Avatar
|
||||||
marginRight: 8,
|
size="small"
|
||||||
width: 24,
|
style={{
|
||||||
height: 24,
|
marginRight: 8,
|
||||||
borderRadius: 4,
|
width: 24,
|
||||||
}}
|
height: 24,
|
||||||
></Skeleton.Avatar>
|
borderRadius: 4,
|
||||||
}
|
}}
|
||||||
text={<Skeleton.Title style={{ width: 120 }} />}
|
></Skeleton.Avatar>
|
||||||
/>
|
<Skeleton.Title style={{ width: 120 }} />
|
||||||
}
|
</div>
|
||||||
error={wikiError}
|
|
||||||
normalContent={() =>
|
|
||||||
isPublicWiki(wiki.status) ? (
|
|
||||||
<NavItem
|
|
||||||
icon={<IconGlobe />}
|
|
||||||
text={
|
|
||||||
<Tooltip content="该知识库已公开,点我查看" position="right">
|
|
||||||
公开地址
|
|
||||||
</Tooltip>
|
|
||||||
}
|
}
|
||||||
href={{
|
loading={true}
|
||||||
pathname: `/share/wiki/[wikiId]`,
|
|
||||||
query: { wikiId },
|
|
||||||
}}
|
|
||||||
isActive={pathname === '/share/wiki/[wikiId]'}
|
|
||||||
openNewTab
|
|
||||||
/>
|
/>
|
||||||
) : null
|
</div>
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DataRender
|
|
||||||
loading={wikiLoading}
|
|
||||||
loadingContent={
|
|
||||||
<NavItem
|
|
||||||
icon={
|
|
||||||
<Skeleton.Avatar
|
|
||||||
size="small"
|
|
||||||
style={{
|
|
||||||
marginRight: 8,
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
borderRadius: 4,
|
|
||||||
}}
|
|
||||||
></Skeleton.Avatar>
|
|
||||||
}
|
|
||||||
text={<Skeleton.Title style={{ width: 120 }} />}
|
|
||||||
rightNode={<IconPlus />}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
error={wikiError}
|
error={wikiError}
|
||||||
normalContent={() => (
|
normalContent={() => (
|
||||||
<NavItem
|
<div className={cls(styles.linkWrap, pathname === '/wiki/[wikiId]' && styles.isActive)}>
|
||||||
icon={<IconDocument />}
|
<Link
|
||||||
text={'文档管理'}
|
href={{
|
||||||
href={{
|
pathname: `/wiki/[wikiId]`,
|
||||||
pathname: `/wiki/[wikiId]/documents`,
|
query: { wikiId },
|
||||||
query: { wikiId },
|
}}
|
||||||
}}
|
>
|
||||||
isActive={pathname === '/wiki/[wikiId]/documents'}
|
<a>
|
||||||
rightNode={
|
<IconOverview style={{ fontSize: '1em' }} />
|
||||||
|
<span>主页</span>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<DataRender
|
||||||
|
loading={wikiLoading}
|
||||||
|
loadingContent={
|
||||||
|
<div className={styles.titleWrap}>
|
||||||
|
<Skeleton
|
||||||
|
placeholder={
|
||||||
|
<div style={{ display: 'flex' }}>
|
||||||
|
<Skeleton.Avatar
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginRight: 8,
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
></Skeleton.Avatar>
|
||||||
|
<Skeleton.Title style={{ width: 120 }} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
loading={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
error={wikiError}
|
||||||
|
normalContent={() => (
|
||||||
|
<div className={cls(styles.linkWrap, pathname === '/wiki/[wikiId]/setting' && styles.isActive)}>
|
||||||
|
<Link
|
||||||
|
href={{
|
||||||
|
pathname: `/wiki/[wikiId]/setting`,
|
||||||
|
query: { tab: 'base', wikiId },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a>
|
||||||
|
<IconSetting style={{ fontSize: '1em' }} />
|
||||||
|
<span>设置</span>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className={styles.treeWrap}>
|
||||||
|
<DataRender
|
||||||
|
loading={starDocumentsLoading}
|
||||||
|
loadingContent={<Skeleton.Title style={{ width: '100%' }} />}
|
||||||
|
error={starDocumentsError}
|
||||||
|
normalContent={() => (
|
||||||
|
<div className={styles.title}>
|
||||||
|
<Text type="tertiary" size="small">
|
||||||
|
已加星标
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<DataRender
|
||||||
|
loading={starDocumentsLoading}
|
||||||
|
loadingContent={<div>1</div>}
|
||||||
|
error={starDocumentsError}
|
||||||
|
normalContent={() => (
|
||||||
|
<Tree
|
||||||
|
data={starDocuments || []}
|
||||||
|
docAsLink={docAsLink}
|
||||||
|
getDocLink={getDocLink}
|
||||||
|
parentIds={parentIds}
|
||||||
|
activeId={documentId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.treeWrap}>
|
||||||
|
<DataRender
|
||||||
|
loading={tocsLoading}
|
||||||
|
loadingContent={<Skeleton.Title style={{ width: '100%' }} />}
|
||||||
|
error={wikiError}
|
||||||
|
normalContent={() => (
|
||||||
|
<div className={styles.title}>
|
||||||
|
<Text type="tertiary" size="small">
|
||||||
|
文档集
|
||||||
|
</Text>
|
||||||
<Button
|
<Button
|
||||||
style={{ fontSize: '1em' }}
|
style={{ fontSize: '1em' }}
|
||||||
theme="borderless"
|
theme="borderless"
|
||||||
|
@ -182,18 +273,16 @@ export const WikiTocs: React.FC<IProps> = ({
|
||||||
triggerCreateDocument({ wikiId: wiki.id, documentId: null });
|
triggerCreateDocument({ wikiId: wiki.id, documentId: null });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
</div>
|
||||||
/>
|
)}
|
||||||
)}
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={styles.treeWrap}>
|
|
||||||
<DataRender
|
<DataRender
|
||||||
loading={tocsLoading}
|
loading={tocsLoading}
|
||||||
loadingContent={<NavItem icon={null} text={<Skeleton.Title style={{ width: '100%' }} />} />}
|
loadingContent={<div>1</div>}
|
||||||
error={tocsError}
|
error={tocsError}
|
||||||
normalContent={() => (
|
normalContent={() => (
|
||||||
<Tree
|
<Tree
|
||||||
|
needAddDocument
|
||||||
data={tocs || []}
|
data={tocs || []}
|
||||||
docAsLink={docAsLink}
|
docAsLink={docAsLink}
|
||||||
getDocLink={getDocLink}
|
getDocLink={getDocLink}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
.wrap {
|
.wrap {
|
||||||
margin-top: 5px;
|
margin-top: 16px;
|
||||||
|
|
||||||
.tocsWrap {
|
.tocsWrap {
|
||||||
height: calc(100vh - 268px);
|
height: calc(100vh - 279px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: 1px solid var(--semi-color-border);
|
border: 1px solid var(--semi-color-border);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
|
|
@ -133,8 +133,8 @@ export const WikiTocsManager: React.FC<IProps> = ({ wikiId }) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.btnWrap}>
|
<div className={styles.btnWrap}>
|
||||||
<Button disabled={!changed} onClick={submit}>
|
<Button disabled={!changed} onClick={submit} theme="solid">
|
||||||
提交
|
保存
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IconMore, IconPlus } from '@douyinfe/semi-icons';
|
import { IconPlus } from '@douyinfe/semi-icons';
|
||||||
import { Button, Tree as SemiTree, Typography } from '@douyinfe/semi-ui';
|
import { Button, Tree as SemiTree, Typography } from '@douyinfe/semi-ui';
|
||||||
import { DocumentActions } from 'components/document/actions';
|
import { DocumentActions } from 'components/document/actions';
|
||||||
import { DocumentCreator as DocumenCreatorForm } from 'components/document/create';
|
import { DocumentCreator as DocumenCreatorForm } from 'components/document/create';
|
||||||
|
@ -13,18 +13,17 @@ import styles from './index.module.scss';
|
||||||
const Actions = ({ node }) => {
|
const Actions = ({ node }) => {
|
||||||
return (
|
return (
|
||||||
<span className={styles.right}>
|
<span className={styles.right}>
|
||||||
<DocumentActions wikiId={node.wikiId} documentId={node.id}>
|
<DocumentActions
|
||||||
<Button
|
key={node.id}
|
||||||
onClick={(e) => {
|
hoverVisible
|
||||||
e.stopPropagation();
|
wikiId={node.wikiId}
|
||||||
}}
|
documentId={node.id}
|
||||||
type="tertiary"
|
size="small"
|
||||||
theme="borderless"
|
hideDocumentVersion
|
||||||
icon={<IconMore />}
|
hideDocumentStyle
|
||||||
size="small"
|
></DocumentActions>
|
||||||
/>
|
|
||||||
</DocumentActions>
|
|
||||||
<Button
|
<Button
|
||||||
|
className={styles.hoverVisible}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
triggerCreateDocument({ wikiId: node.wikiId, documentId: node.id });
|
triggerCreateDocument({ wikiId: node.wikiId, documentId: node.id });
|
||||||
|
@ -67,7 +66,15 @@ const AddDocument = () => {
|
||||||
|
|
||||||
let scrollTimer;
|
let scrollTimer;
|
||||||
|
|
||||||
export const Tree = ({ data, docAsLink, getDocLink, parentIds, activeId, isShareMode = false }) => {
|
export const Tree = ({
|
||||||
|
data,
|
||||||
|
docAsLink,
|
||||||
|
getDocLink,
|
||||||
|
parentIds,
|
||||||
|
activeId,
|
||||||
|
isShareMode = false,
|
||||||
|
needAddDocument = false,
|
||||||
|
}) => {
|
||||||
const $container = useRef<HTMLDivElement>(null);
|
const $container = useRef<HTMLDivElement>(null);
|
||||||
const [expandedKeys, setExpandedKeys] = useState(parentIds);
|
const [expandedKeys, setExpandedKeys] = useState(parentIds);
|
||||||
|
|
||||||
|
@ -100,15 +107,13 @@ export const Tree = ({ data, docAsLink, getDocLink, parentIds, activeId, isShare
|
||||||
}, [parentIds]);
|
}, [parentIds]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const target = $container.current.querySelector(`#item-${activeId}`);
|
||||||
|
if (!target) return;
|
||||||
clearTimeout(scrollTimer);
|
clearTimeout(scrollTimer);
|
||||||
scrollTimer = setTimeout(() => {
|
scrollTimer = setTimeout(() => {
|
||||||
const target = $container.current.querySelector(`#item-${activeId}`);
|
|
||||||
if (!target) return;
|
|
||||||
target.scrollIntoView();
|
|
||||||
scrollIntoView(target, {
|
scrollIntoView(target, {
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
scrollMode: 'if-needed',
|
scrollMode: 'if-needed',
|
||||||
block: 'center',
|
|
||||||
});
|
});
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
|
@ -127,7 +132,7 @@ export const Tree = ({ data, docAsLink, getDocLink, parentIds, activeId, isShare
|
||||||
expandedKeys={expandedKeys}
|
expandedKeys={expandedKeys}
|
||||||
onExpand={(expandedKeys) => setExpandedKeys(expandedKeys)}
|
onExpand={(expandedKeys) => setExpandedKeys(expandedKeys)}
|
||||||
/>
|
/>
|
||||||
<AddDocument />
|
{needAddDocument && <AddDocument />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,185 +0,0 @@
|
||||||
import { CollectorApiDefinition, CollectType, IDocument, IWiki } from '@think/domains';
|
|
||||||
import {
|
|
||||||
event,
|
|
||||||
TOGGLE_COLLECT_DOUCMENT,
|
|
||||||
TOGGLE_COLLECT_WIKI,
|
|
||||||
triggerToggleCollectDocument,
|
|
||||||
triggerToggleCollectWiki,
|
|
||||||
} from 'event';
|
|
||||||
import { useCallback, useEffect } from 'react';
|
|
||||||
import { useQuery, UseQueryOptions } from 'react-query';
|
|
||||||
import { HttpClient } from 'services/http-client';
|
|
||||||
|
|
||||||
export type IWikiWithIsMember = IWiki & { isMember?: boolean };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户收藏的知识库
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const getCollectedWikis = (cookie = null): Promise<IWikiWithIsMember[]> => {
|
|
||||||
return HttpClient.request({
|
|
||||||
method: CollectorApiDefinition.wikis.method,
|
|
||||||
url: CollectorApiDefinition.wikis.client(),
|
|
||||||
cookie,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户收藏的知识库
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const useCollectedWikis = () => {
|
|
||||||
const { data, error, isLoading, refetch } = useQuery(CollectorApiDefinition.wikis.client(), getCollectedWikis, {
|
|
||||||
staleTime: 500,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
event.on(TOGGLE_COLLECT_WIKI, refetch);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
event.off(TOGGLE_COLLECT_WIKI, refetch);
|
|
||||||
};
|
|
||||||
}, [refetch]);
|
|
||||||
|
|
||||||
return { data, error, loading: isLoading, refresh: refetch };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查知识库是否收藏
|
|
||||||
* @param wikiId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const getWikiIsCollected = (wikiId, cookie = null): Promise<boolean> => {
|
|
||||||
return HttpClient.request({
|
|
||||||
method: CollectorApiDefinition.check.method,
|
|
||||||
url: CollectorApiDefinition.check.client(),
|
|
||||||
cookie,
|
|
||||||
data: {
|
|
||||||
type: CollectType.wiki,
|
|
||||||
targetId: wikiId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收藏(或取消收藏)知识库
|
|
||||||
* @param wikiId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const toggleCollectWiki = (wikiId, cookie = null): Promise<boolean> => {
|
|
||||||
return HttpClient.request({
|
|
||||||
method: CollectorApiDefinition.toggle.method,
|
|
||||||
url: CollectorApiDefinition.toggle.client(),
|
|
||||||
cookie,
|
|
||||||
data: {
|
|
||||||
type: CollectType.wiki,
|
|
||||||
targetId: wikiId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收藏知识库
|
|
||||||
* @param wikiId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const useWikiCollectToggle = (wikiId) => {
|
|
||||||
const { data, error, refetch } = useQuery([CollectorApiDefinition.check.client(), wikiId], () =>
|
|
||||||
getWikiIsCollected(wikiId)
|
|
||||||
);
|
|
||||||
|
|
||||||
const toggle = useCallback(async () => {
|
|
||||||
await toggleCollectWiki(wikiId);
|
|
||||||
refetch();
|
|
||||||
triggerToggleCollectWiki();
|
|
||||||
}, [refetch, wikiId]);
|
|
||||||
|
|
||||||
return { data, error, toggle };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户收藏的文档
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const getCollectedDocuments = (cookie = null): Promise<IDocument[]> => {
|
|
||||||
return HttpClient.request({
|
|
||||||
method: CollectorApiDefinition.documents.method,
|
|
||||||
url: CollectorApiDefinition.documents.client(),
|
|
||||||
cookie,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户收藏的文档
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const useCollectedDocuments = () => {
|
|
||||||
const { data, error, isLoading, refetch } = useQuery(
|
|
||||||
CollectorApiDefinition.documents.client(),
|
|
||||||
getCollectedDocuments,
|
|
||||||
{ staleTime: 500 }
|
|
||||||
);
|
|
||||||
useEffect(() => {
|
|
||||||
event.on(TOGGLE_COLLECT_DOUCMENT, refetch);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
event.off(TOGGLE_COLLECT_DOUCMENT, refetch);
|
|
||||||
};
|
|
||||||
}, [refetch]);
|
|
||||||
return { data, error, loading: isLoading, refresh: refetch };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查文档是否收藏
|
|
||||||
* @param documentId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const getDocumentIsCollected = (documentId, cookie = null): Promise<boolean> => {
|
|
||||||
return HttpClient.request({
|
|
||||||
method: CollectorApiDefinition.check.method,
|
|
||||||
url: CollectorApiDefinition.check.client(),
|
|
||||||
cookie,
|
|
||||||
data: {
|
|
||||||
type: CollectType.document,
|
|
||||||
targetId: documentId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收藏(或取消收藏)知识库
|
|
||||||
* @param wikiId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const toggleCollectDocument = (documentId, cookie = null): Promise<boolean> => {
|
|
||||||
return HttpClient.request({
|
|
||||||
method: CollectorApiDefinition.toggle.method,
|
|
||||||
url: CollectorApiDefinition.toggle.client(),
|
|
||||||
cookie,
|
|
||||||
data: {
|
|
||||||
type: CollectType.document,
|
|
||||||
targetId: documentId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文档收藏管理
|
|
||||||
* @param documentId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const useDocumentCollectToggle = (documentId, options?: UseQueryOptions<boolean>) => {
|
|
||||||
const { data, error, refetch } = useQuery(
|
|
||||||
[CollectorApiDefinition.check.client(), documentId],
|
|
||||||
() => getDocumentIsCollected(documentId),
|
|
||||||
options
|
|
||||||
);
|
|
||||||
|
|
||||||
const toggle = useCallback(async () => {
|
|
||||||
await toggleCollectDocument(documentId);
|
|
||||||
refetch();
|
|
||||||
triggerToggleCollectDocument();
|
|
||||||
}, [refetch, documentId]);
|
|
||||||
|
|
||||||
return { data, error, toggle };
|
|
||||||
};
|
|
|
@ -0,0 +1,212 @@
|
||||||
|
import { IDocument, IWiki, StarApiDefinition } from '@think/domains';
|
||||||
|
import { event, TOGGLE_STAR_DOUCMENT, TOGGLE_STAR_WIKI, triggerToggleStarDocument, triggerToggleStarWiki } from 'event';
|
||||||
|
import { useCallback, useEffect } from 'react';
|
||||||
|
import { useQuery, UseQueryOptions } from 'react-query';
|
||||||
|
import { HttpClient } from 'services/http-client';
|
||||||
|
|
||||||
|
export type IWikiWithIsMember = IWiki & { isMember?: boolean };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户收藏的知识库
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getStarWikis = (cookie = null): Promise<IWikiWithIsMember[]> => {
|
||||||
|
return HttpClient.request({
|
||||||
|
method: StarApiDefinition.wikis.method,
|
||||||
|
url: StarApiDefinition.wikis.client(),
|
||||||
|
cookie,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户收藏的知识库
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const useStarWikis = () => {
|
||||||
|
const { data, error, isLoading, refetch } = useQuery(StarApiDefinition.wikis.client(), getStarWikis, {
|
||||||
|
staleTime: 500,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
event.on(TOGGLE_STAR_WIKI, refetch);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
event.off(TOGGLE_STAR_WIKI, refetch);
|
||||||
|
};
|
||||||
|
}, [refetch]);
|
||||||
|
|
||||||
|
return { data, error, loading: isLoading, refresh: refetch };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查知识库是否收藏
|
||||||
|
* @param wikiId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWikiIsStar = (wikiId, cookie = null): Promise<boolean> => {
|
||||||
|
return HttpClient.request({
|
||||||
|
method: StarApiDefinition.check.method,
|
||||||
|
url: StarApiDefinition.check.client(),
|
||||||
|
cookie,
|
||||||
|
data: {
|
||||||
|
wikiId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收藏(或取消收藏)知识库
|
||||||
|
* @param wikiId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const toggleStarWiki = (wikiId, cookie = null): Promise<boolean> => {
|
||||||
|
return HttpClient.request({
|
||||||
|
method: StarApiDefinition.toggle.method,
|
||||||
|
url: StarApiDefinition.toggle.client(),
|
||||||
|
cookie,
|
||||||
|
data: {
|
||||||
|
wikiId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收藏知识库
|
||||||
|
* @param wikiId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const useWikiStarToggle = (wikiId) => {
|
||||||
|
const { data, error, refetch } = useQuery([StarApiDefinition.check.client(), wikiId], () => getWikiIsStar(wikiId));
|
||||||
|
|
||||||
|
const toggle = useCallback(async () => {
|
||||||
|
await toggleStarWiki(wikiId);
|
||||||
|
refetch();
|
||||||
|
triggerToggleStarWiki();
|
||||||
|
}, [refetch, wikiId]);
|
||||||
|
|
||||||
|
return { data, error, toggle };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户收藏的文档
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getStarDocuments = (cookie = null): Promise<IDocument[]> => {
|
||||||
|
return HttpClient.request({
|
||||||
|
method: StarApiDefinition.documents.method,
|
||||||
|
url: StarApiDefinition.documents.client(),
|
||||||
|
cookie,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户收藏的文档
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const useStarDocuments = () => {
|
||||||
|
const { data, error, isLoading, refetch } = useQuery(StarApiDefinition.documents.client(), getStarDocuments, {
|
||||||
|
staleTime: 500,
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
event.on(TOGGLE_STAR_DOUCMENT, refetch);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
event.off(TOGGLE_STAR_DOUCMENT, refetch);
|
||||||
|
};
|
||||||
|
}, [refetch]);
|
||||||
|
return { data, error, loading: isLoading, refresh: refetch };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查文档是否收藏
|
||||||
|
* @param documentId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getDocumentIsStar = (wikiId, documentId, cookie = null): Promise<boolean> => {
|
||||||
|
return HttpClient.request({
|
||||||
|
method: StarApiDefinition.check.method,
|
||||||
|
url: StarApiDefinition.check.client(),
|
||||||
|
cookie,
|
||||||
|
data: {
|
||||||
|
wikiId,
|
||||||
|
documentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收藏(或取消收藏)知识库
|
||||||
|
* @param wikiId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const toggleDocumentStar = (wikiId, documentId, cookie = null): Promise<boolean> => {
|
||||||
|
return HttpClient.request({
|
||||||
|
method: StarApiDefinition.toggle.method,
|
||||||
|
url: StarApiDefinition.toggle.client(),
|
||||||
|
cookie,
|
||||||
|
data: {
|
||||||
|
wikiId,
|
||||||
|
documentId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档收藏管理
|
||||||
|
* @param documentId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const useDocumentStarToggle = (wikiId, documentId, options?: UseQueryOptions<boolean>) => {
|
||||||
|
const { data, error, refetch } = useQuery(
|
||||||
|
[StarApiDefinition.check.client(), wikiId, documentId],
|
||||||
|
() => getDocumentIsStar(wikiId, documentId),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggle = useCallback(async () => {
|
||||||
|
await toggleDocumentStar(wikiId, documentId);
|
||||||
|
refetch();
|
||||||
|
triggerToggleStarDocument();
|
||||||
|
}, [refetch, wikiId, documentId]);
|
||||||
|
|
||||||
|
return { data, error, toggle };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取知识库加星的文档
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getWikiStarDocuments = (wikiId, cookie = null): Promise<IWikiWithIsMember[]> => {
|
||||||
|
return HttpClient.request({
|
||||||
|
method: StarApiDefinition.wikiDocuments.method,
|
||||||
|
url: StarApiDefinition.wikiDocuments.client(),
|
||||||
|
cookie,
|
||||||
|
params: {
|
||||||
|
wikiId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取知识库加星的文档
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const useWikiStarDocuments = (wikiId) => {
|
||||||
|
const { data, error, isLoading, refetch } = useQuery(
|
||||||
|
[StarApiDefinition.wikiDocuments.client(), wikiId],
|
||||||
|
() => getWikiStarDocuments(wikiId),
|
||||||
|
{
|
||||||
|
staleTime: 500,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
event.on(TOGGLE_STAR_DOUCMENT, refetch);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
event.off(TOGGLE_STAR_DOUCMENT, refetch);
|
||||||
|
};
|
||||||
|
}, [refetch]);
|
||||||
|
|
||||||
|
return { data, error, loading: isLoading, refresh: refetch };
|
||||||
|
};
|
|
@ -5,8 +5,8 @@ export const event = new EventEmitter();
|
||||||
|
|
||||||
export const REFRESH_TOCS = `REFRESH_TOCS`; // 刷新知识库目录
|
export const REFRESH_TOCS = `REFRESH_TOCS`; // 刷新知识库目录
|
||||||
export const CREATE_DOCUMENT = `CREATE_DOCUMENT`;
|
export const CREATE_DOCUMENT = `CREATE_DOCUMENT`;
|
||||||
export const TOGGLE_COLLECT_WIKI = `TOGGLE_COLLECT_WIKI`; // 收藏或取消收藏知识库
|
export const TOGGLE_STAR_WIKI = `TOGGLE_STAR_WIKI`; // 收藏或取消收藏知识库
|
||||||
export const TOGGLE_COLLECT_DOUCMENT = `TOGGLE_COLLECT_DOUCMENT`; // 收藏或取消收藏文档
|
export const TOGGLE_STAR_DOUCMENT = `TOGGLE_STAR_DOUCMENT`; // 收藏或取消收藏文档
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新知识库目录
|
* 刷新知识库目录
|
||||||
|
@ -53,10 +53,10 @@ export const triggerJoinUser = (users: Array<CollaborationUser>) => {
|
||||||
event.emit(JOIN_USER, users);
|
event.emit(JOIN_USER, users);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const triggerToggleCollectWiki = () => {
|
export const triggerToggleStarWiki = () => {
|
||||||
event.emit(TOGGLE_COLLECT_WIKI);
|
event.emit(TOGGLE_STAR_WIKI);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const triggerToggleCollectDocument = () => {
|
export const triggerToggleStarDocument = () => {
|
||||||
event.emit(TOGGLE_COLLECT_DOUCMENT);
|
event.emit(TOGGLE_STAR_DOUCMENT);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { createGlobalHook } from './create-global-hook';
|
||||||
|
import { useToggle } from './use-toggle';
|
||||||
|
|
||||||
|
const useDocumentVersion = (defaultVisible) => {
|
||||||
|
const [visible, toggleVisible] = useToggle(defaultVisible);
|
||||||
|
|
||||||
|
return {
|
||||||
|
visible,
|
||||||
|
toggleVisible,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DocumentVersionControl = createGlobalHook<
|
||||||
|
{ visible?: boolean; toggleVisible: (arg?: any) => void },
|
||||||
|
boolean
|
||||||
|
>(useDocumentVersion);
|
|
@ -62,7 +62,7 @@ export const RecentDocs = ({ visible }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.rightWrap}>
|
<div className={styles.rightWrap}>
|
||||||
<DocumentStar documentId={doc.id} />
|
<DocumentStar wikiId={doc.wikiId} documentId={doc.id} />
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Avatar, Dropdown, Modal, Space, 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 { WikiStar } from 'components/wiki/star';
|
import { WikiStar } from 'components/wiki/star';
|
||||||
import { useCollectedWikis } from 'data/collector';
|
import { useStarWikis } from 'data/star';
|
||||||
import { useWikiDetail } from 'data/wiki';
|
import { useWikiDetail } from 'data/wiki';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
@ -16,7 +16,7 @@ const { Text } = Typography;
|
||||||
|
|
||||||
const WikiContent = () => {
|
const WikiContent = () => {
|
||||||
const { query } = useRouter();
|
const { query } = useRouter();
|
||||||
const { data: starWikis, loading, error, refresh: refreshStarWikis } = useCollectedWikis();
|
const { data: starWikis, loading, error, refresh: refreshStarWikis } = useStarWikis();
|
||||||
const { data: currentWiki } = useWikiDetail(query.wikiId);
|
const { data: currentWiki } = useWikiDetail(query.wikiId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'styles/globals.scss';
|
||||||
import 'tiptap/core/styles/index.scss';
|
import 'tiptap/core/styles/index.scss';
|
||||||
|
|
||||||
import { isMobile } from 'helpers/env';
|
import { isMobile } from 'helpers/env';
|
||||||
|
import { DocumentVersionControl } from 'hooks/use-document-version';
|
||||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||||
import { Theme } from 'hooks/use-theme';
|
import { Theme } from 'hooks/use-theme';
|
||||||
import App from 'next/app';
|
import App from 'next/app';
|
||||||
|
@ -87,7 +88,9 @@ class MyApp extends App<{ isMobile: boolean }> {
|
||||||
<Hydrate state={pageProps.dehydratedState}>
|
<Hydrate state={pageProps.dehydratedState}>
|
||||||
<Theme.Provider>
|
<Theme.Provider>
|
||||||
<IsOnMobile.Provider initialState={isMobile}>
|
<IsOnMobile.Provider initialState={isMobile}>
|
||||||
<Component {...pageProps} />
|
<DocumentVersionControl.Provider initialState={false}>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</DocumentVersionControl.Provider>
|
||||||
</IsOnMobile.Provider>
|
</IsOnMobile.Provider>
|
||||||
</Theme.Provider>
|
</Theme.Provider>
|
||||||
</Hydrate>
|
</Hydrate>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Avatar, Button, List, Table, Typography } from '@douyinfe/semi-ui';
|
import { Avatar, Button, List, Table, Typography } from '@douyinfe/semi-ui';
|
||||||
import { CollectorApiDefinition, DocumentApiDefinition, IDocument } from '@think/domains';
|
import { DocumentApiDefinition, IDocument, StarApiDefinition } from '@think/domains';
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { DocumentActions } from 'components/document/actions';
|
import { DocumentActions } from 'components/document/actions';
|
||||||
import { Empty } from 'components/empty';
|
import { Empty } from 'components/empty';
|
||||||
|
@ -7,8 +7,8 @@ import { LocaleTime } from 'components/locale-time';
|
||||||
import { Seo } from 'components/seo';
|
import { Seo } from 'components/seo';
|
||||||
import { WikiCreator } from 'components/wiki/create';
|
import { WikiCreator } from 'components/wiki/create';
|
||||||
import { WikiPinCard, WikiPinCardPlaceholder } from 'components/wiki/pin-card';
|
import { WikiPinCard, WikiPinCardPlaceholder } from 'components/wiki/pin-card';
|
||||||
import { getCollectedWikis, useCollectedWikis } from 'data/collector';
|
|
||||||
import { getRecentVisitedDocuments, useRecentDocuments } from 'data/document';
|
import { getRecentVisitedDocuments, useRecentDocuments } from 'data/document';
|
||||||
|
import { getStarWikis, useStarWikis } from 'data/star';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import { SingleColumnLayout } from 'layouts/single-column';
|
import { SingleColumnLayout } from 'layouts/single-column';
|
||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
|
@ -78,7 +78,14 @@ const RecentDocs = () => {
|
||||||
key="operate"
|
key="operate"
|
||||||
width={80}
|
width={80}
|
||||||
render={(_, document) => (
|
render={(_, document) => (
|
||||||
<DocumentActions wikiId={document.wikiId} documentId={document.id} onDelete={refresh} showCreateDocument />
|
<DocumentActions
|
||||||
|
wikiId={document.wikiId}
|
||||||
|
documentId={document.id}
|
||||||
|
onDelete={refresh}
|
||||||
|
showCreateDocument
|
||||||
|
hideDocumentVersion
|
||||||
|
hideDocumentStyle
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>,
|
/>,
|
||||||
],
|
],
|
||||||
|
@ -118,7 +125,7 @@ const RecentDocs = () => {
|
||||||
|
|
||||||
const Page: NextPage = () => {
|
const Page: NextPage = () => {
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
const [visible, toggleVisible] = useToggle(false);
|
||||||
const { data: staredWikis, loading, error, refresh } = useCollectedWikis();
|
const { data: staredWikis, loading, error, refresh } = useStarWikis();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SingleColumnLayout>
|
<SingleColumnLayout>
|
||||||
|
@ -168,7 +175,7 @@ const Page: NextPage = () => {
|
||||||
|
|
||||||
Page.getInitialProps = async (ctx) => {
|
Page.getInitialProps = async (ctx) => {
|
||||||
const props = await serverPrefetcher(ctx, [
|
const props = await serverPrefetcher(ctx, [
|
||||||
{ url: CollectorApiDefinition.wikis.client(), action: (cookie) => getCollectedWikis(cookie) },
|
{ url: StarApiDefinition.wikis.client(), action: (cookie) => getStarWikis(cookie) },
|
||||||
{ url: DocumentApiDefinition.recent.client(), action: (cookie) => getRecentVisitedDocuments(cookie) },
|
{ url: DocumentApiDefinition.recent.client(), action: (cookie) => getRecentVisitedDocuments(cookie) },
|
||||||
]);
|
]);
|
||||||
return props;
|
return props;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { List, Typography } from '@douyinfe/semi-ui';
|
import { List, Typography } from '@douyinfe/semi-ui';
|
||||||
import { CollectorApiDefinition } from '@think/domains';
|
import { StarApiDefinition } from '@think/domains';
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { DocumentCard, DocumentCardPlaceholder } from 'components/document/card';
|
import { DocumentCard, DocumentCardPlaceholder } from 'components/document/card';
|
||||||
import { Empty } from 'components/empty';
|
import { Empty } from 'components/empty';
|
||||||
import { Seo } from 'components/seo';
|
import { Seo } from 'components/seo';
|
||||||
import { WikiCard, WikiCardPlaceholder } from 'components/wiki/card';
|
import { WikiCard, WikiCardPlaceholder } from 'components/wiki/card';
|
||||||
import { getCollectedDocuments, getCollectedWikis, useCollectedDocuments, useCollectedWikis } from 'data/collector';
|
import { getStarDocuments, getStarWikis, useStarDocuments, useStarWikis } from 'data/star';
|
||||||
import { SingleColumnLayout } from 'layouts/single-column';
|
import { SingleColumnLayout } from 'layouts/single-column';
|
||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
@ -25,7 +25,7 @@ const grid = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const StarDocs = () => {
|
const StarDocs = () => {
|
||||||
const { data: docs, loading, error } = useCollectedDocuments();
|
const { data: docs, loading, error } = useStarDocuments();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataRender
|
<DataRender
|
||||||
|
@ -59,7 +59,7 @@ const StarDocs = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const StarWikis = () => {
|
const StarWikis = () => {
|
||||||
const { data, loading, error } = useCollectedWikis();
|
const { data, loading, error } = useStarWikis();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataRender
|
<DataRender
|
||||||
|
@ -117,8 +117,8 @@ const Page: NextPage = () => {
|
||||||
|
|
||||||
Page.getInitialProps = async (ctx) => {
|
Page.getInitialProps = async (ctx) => {
|
||||||
const props = await serverPrefetcher(ctx, [
|
const props = await serverPrefetcher(ctx, [
|
||||||
{ url: CollectorApiDefinition.wikis.client(), action: (cookie) => getCollectedWikis(cookie) },
|
{ url: StarApiDefinition.wikis.client(), action: (cookie) => getStarWikis(cookie) },
|
||||||
{ url: CollectorApiDefinition.documents.client(), action: (cookie) => getCollectedDocuments(cookie) },
|
{ url: StarApiDefinition.documents.client(), action: (cookie) => getStarDocuments(cookie) },
|
||||||
]);
|
]);
|
||||||
return props;
|
return props;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
.cardWrap {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
max-height: 260px;
|
|
||||||
padding: 12px 16px 16px;
|
|
||||||
margin: 8px 0;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid var(--semi-color-border);
|
|
||||||
border-radius: 5px;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
> header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
color: var(--semi-color-primary);
|
|
||||||
|
|
||||||
.rightWrap {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
> header .rightWrap {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> footer {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
import { List, TabPane, Tabs } from '@douyinfe/semi-ui';
|
|
||||||
import { IWiki, WikiApiDefinition } from '@think/domains';
|
|
||||||
import { DataRender } from 'components/data-render';
|
|
||||||
import { DocumentCard, DocumentCardPlaceholder } from 'components/document/card';
|
|
||||||
import { DocumentCreator } from 'components/document-creator';
|
|
||||||
import { Empty } from 'components/empty';
|
|
||||||
import { Seo } from 'components/seo';
|
|
||||||
import { WikiDocumentsShare } from 'components/wiki/documents-share';
|
|
||||||
import { WikiTocs } from 'components/wiki/tocs';
|
|
||||||
import { WikiTocsManager } from 'components/wiki/tocs/manager';
|
|
||||||
import { getWikiTocs, useWikiDocuments } from 'data/wiki';
|
|
||||||
import { CreateDocumentIllustration } from 'illustrations/create-document';
|
|
||||||
import { DoubleColumnLayout } from 'layouts/double-column';
|
|
||||||
import { NextPage } from 'next';
|
|
||||||
import Router, { useRouter } from 'next/router';
|
|
||||||
import React, { useCallback } from 'react';
|
|
||||||
import { serverPrefetcher } from 'services/server-prefetcher';
|
|
||||||
|
|
||||||
interface IProps {
|
|
||||||
wikiId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const grid = {
|
|
||||||
gutter: 16,
|
|
||||||
xs: 24,
|
|
||||||
sm: 12,
|
|
||||||
md: 12,
|
|
||||||
lg: 8,
|
|
||||||
xl: 8,
|
|
||||||
xxl: 6,
|
|
||||||
};
|
|
||||||
|
|
||||||
const AllDocs = ({ wikiId }) => {
|
|
||||||
const { data: docs, loading, error } = useWikiDocuments(wikiId);
|
|
||||||
return (
|
|
||||||
<DataRender
|
|
||||||
loading={loading}
|
|
||||||
loadingContent={() => (
|
|
||||||
<List
|
|
||||||
grid={grid}
|
|
||||||
dataSource={Array.from({ length: 9 })}
|
|
||||||
renderItem={() => (
|
|
||||||
<List.Item style={{}}>
|
|
||||||
<DocumentCardPlaceholder />
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
error={error}
|
|
||||||
normalContent={() => (
|
|
||||||
<List
|
|
||||||
grid={grid}
|
|
||||||
dataSource={docs}
|
|
||||||
renderItem={(doc) => (
|
|
||||||
<List.Item style={{}}>
|
|
||||||
<DocumentCard document={doc} />
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
emptyContent={<Empty illustration={<CreateDocumentIllustration />} message={<DocumentCreator />} />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TitleMap = {
|
|
||||||
tocs: '目录管理',
|
|
||||||
share: '隐私管理',
|
|
||||||
documents: '全部文档',
|
|
||||||
};
|
|
||||||
|
|
||||||
const Page: NextPage<IProps> = ({ wikiId }) => {
|
|
||||||
const { query = {} } = useRouter();
|
|
||||||
const { tab = 'tocs' } = query as {
|
|
||||||
tab?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigate = useCallback(
|
|
||||||
(tab) => {
|
|
||||||
Router.push({
|
|
||||||
pathname: `/wiki/${wikiId}/documents`,
|
|
||||||
query: { tab },
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[wikiId]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DoubleColumnLayout
|
|
||||||
leftNode={<WikiTocs wikiId={wikiId} />}
|
|
||||||
rightNode={
|
|
||||||
<div style={{ padding: '16px 24px' }}>
|
|
||||||
<Seo title={TitleMap[tab]} />
|
|
||||||
<Tabs type="line" activeKey={tab} onChange={(tab) => navigate(tab)}>
|
|
||||||
<TabPane tab={TitleMap['tocs']} itemKey="tocs">
|
|
||||||
<WikiTocsManager wikiId={wikiId} />
|
|
||||||
</TabPane>
|
|
||||||
<TabPane tab={TitleMap['share']} itemKey="share">
|
|
||||||
<WikiDocumentsShare wikiId={wikiId} />
|
|
||||||
</TabPane>
|
|
||||||
<TabPane tab={TitleMap['documents']} itemKey="documents">
|
|
||||||
<AllDocs wikiId={wikiId} />
|
|
||||||
</TabPane>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
></DoubleColumnLayout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Page.getInitialProps = async (ctx) => {
|
|
||||||
const { wikiId } = ctx.query;
|
|
||||||
const res = await serverPrefetcher(ctx, [
|
|
||||||
{
|
|
||||||
url: WikiApiDefinition.getTocsById.client(wikiId as IWiki['id']),
|
|
||||||
action: (cookie) => getWikiTocs(wikiId, cookie),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
return { ...res, wikiId } as IProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Page;
|
|
|
@ -56,30 +56,28 @@ export class Awareness extends Observable {
|
||||||
* @type {Map<number, MetaClientState>}
|
* @type {Map<number, MetaClientState>}
|
||||||
*/
|
*/
|
||||||
this.meta = new Map();
|
this.meta = new Map();
|
||||||
this._checkInterval = /** @type {any} */ (
|
this._checkInterval = /** @type {any} */ setInterval(() => {
|
||||||
setInterval(() => {
|
const now = time.getUnixTime();
|
||||||
const now = time.getUnixTime();
|
if (
|
||||||
if (
|
this.getLocalState() !== null &&
|
||||||
this.getLocalState() !== null &&
|
outdatedTimeout / 2 <= now - /** @type {{lastUpdated:number}} */ this.meta.get(this.clientID).lastUpdated
|
||||||
outdatedTimeout / 2 <= now - /** @type {{lastUpdated:number}} */ (this.meta.get(this.clientID)).lastUpdated
|
) {
|
||||||
) {
|
// renew local clock
|
||||||
// renew local clock
|
this.setLocalState(this.getLocalState());
|
||||||
this.setLocalState(this.getLocalState());
|
}
|
||||||
|
/**
|
||||||
|
* @type {Array<number>}
|
||||||
|
*/
|
||||||
|
const remove = [];
|
||||||
|
this.meta.forEach((meta, clientid) => {
|
||||||
|
if (clientid !== this.clientID && outdatedTimeout <= now - meta.lastUpdated && this.states.has(clientid)) {
|
||||||
|
remove.push(clientid);
|
||||||
}
|
}
|
||||||
/**
|
});
|
||||||
* @type {Array<number>}
|
if (remove.length > 0) {
|
||||||
*/
|
removeAwarenessStates(this, remove, 'timeout');
|
||||||
const remove = [];
|
}
|
||||||
this.meta.forEach((meta, clientid) => {
|
}, math.floor(outdatedTimeout / 10));
|
||||||
if (clientid !== this.clientID && outdatedTimeout <= now - meta.lastUpdated && this.states.has(clientid)) {
|
|
||||||
remove.push(clientid);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (remove.length > 0) {
|
|
||||||
removeAwarenessStates(this, remove, 'timeout');
|
|
||||||
}
|
|
||||||
}, math.floor(outdatedTimeout / 10))
|
|
||||||
);
|
|
||||||
doc.on('destroy', () => {
|
doc.on('destroy', () => {
|
||||||
this.destroy();
|
this.destroy();
|
||||||
});
|
});
|
||||||
|
@ -176,7 +174,7 @@ export const removeAwarenessStates = (awareness, clients, origin) => {
|
||||||
if (awareness.states.has(clientID)) {
|
if (awareness.states.has(clientID)) {
|
||||||
awareness.states.delete(clientID);
|
awareness.states.delete(clientID);
|
||||||
if (clientID === awareness.clientID) {
|
if (clientID === awareness.clientID) {
|
||||||
const curMeta = /** @type {MetaClientState} */ (awareness.meta.get(clientID));
|
const curMeta = /** @type {MetaClientState} */ awareness.meta.get(clientID);
|
||||||
awareness.meta.set(clientID, {
|
awareness.meta.set(clientID, {
|
||||||
clock: curMeta.clock + 1,
|
clock: curMeta.clock + 1,
|
||||||
lastUpdated: time.getUnixTime(),
|
lastUpdated: time.getUnixTime(),
|
||||||
|
@ -203,7 +201,7 @@ export const encodeAwarenessUpdate = (awareness, clients, states = awareness.sta
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
const clientID = clients[i];
|
const clientID = clients[i];
|
||||||
const state = states.get(clientID) || null;
|
const state = states.get(clientID) || null;
|
||||||
const clock = /** @type {MetaClientState} */ (awareness.meta.get(clientID)).clock;
|
const clock = /** @type {MetaClientState} */ awareness.meta.get(clientID).clock;
|
||||||
encoding.writeVarUint(encoder, clientID);
|
encoding.writeVarUint(encoder, clientID);
|
||||||
encoding.writeVarUint(encoder, clock);
|
encoding.writeVarUint(encoder, clock);
|
||||||
encoding.writeVarString(encoder, JSON.stringify(state));
|
encoding.writeVarString(encoder, JSON.stringify(state));
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.wrap {
|
.wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: auto;
|
|
||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
.coverWrap {
|
.coverWrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Spin, Typography } from '@douyinfe/semi-ui';
|
import { Spin, Typography } from '@douyinfe/semi-ui';
|
||||||
import { HocuspocusProvider } from '@hocuspocus/provider';
|
import { HocuspocusProvider } from '@hocuspocus/provider';
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
|
import deepEqual from 'deep-equal';
|
||||||
import { throttle } from 'helpers/throttle';
|
import { throttle } from 'helpers/throttle';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import { SecureDocumentIllustration } from 'illustrations/secure-document';
|
import { SecureDocumentIllustration } from 'illustrations/secure-document';
|
||||||
|
@ -34,6 +35,7 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps,
|
||||||
const [loading, toggleLoading] = useToggle(true);
|
const [loading, toggleLoading] = useToggle(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [status, setStatus] = useState<ProviderStatus>('connecting');
|
const [status, setStatus] = useState<ProviderStatus>('connecting');
|
||||||
|
const lastAwarenessRef = useRef([]);
|
||||||
|
|
||||||
const hocuspocusProvider = useMemo(() => {
|
const hocuspocusProvider = useMemo(() => {
|
||||||
return new HocuspocusProvider({
|
return new HocuspocusProvider({
|
||||||
|
@ -48,7 +50,11 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps,
|
||||||
maxAttempts: 1,
|
maxAttempts: 1,
|
||||||
onAwarenessUpdate: throttle(({ states }) => {
|
onAwarenessUpdate: throttle(({ states }) => {
|
||||||
const users = states.map((state) => ({ clientId: state.clientId, user: state.user }));
|
const users = states.map((state) => ({ clientId: state.clientId, user: state.user }));
|
||||||
|
if (deepEqual(user, lastAwarenessRef.current)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
onAwarenessUpdate && onAwarenessUpdate(users);
|
onAwarenessUpdate && onAwarenessUpdate(users);
|
||||||
|
lastAwarenessRef.current = users;
|
||||||
}, 200),
|
}, 200),
|
||||||
onAuthenticationFailed() {
|
onAuthenticationFailed() {
|
||||||
toggleLoading(false);
|
toggleLoading(false);
|
||||||
|
|
|
@ -8,7 +8,7 @@ export declare const FileApiDefinition: {
|
||||||
client: () => string;
|
client: () => string;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* 上传分块文件
|
* 初始分块上传
|
||||||
*/
|
*/
|
||||||
initChunk: {
|
initChunk: {
|
||||||
method: "post";
|
method: "post";
|
||||||
|
|
|
@ -11,7 +11,7 @@ exports.FileApiDefinition = {
|
||||||
client: function () { return '/file/upload'; }
|
client: function () { return '/file/upload'; }
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 上传分块文件
|
* 初始分块上传
|
||||||
*/
|
*/
|
||||||
initChunk: {
|
initChunk: {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
|
|
|
@ -5,4 +5,4 @@ export * from './file';
|
||||||
export * from './message';
|
export * from './message';
|
||||||
export * from './template';
|
export * from './template';
|
||||||
export * from './comment';
|
export * from './comment';
|
||||||
export * from './collector';
|
export * from './star';
|
||||||
|
|
|
@ -17,4 +17,4 @@ __exportStar(require("./file"), exports);
|
||||||
__exportStar(require("./message"), exports);
|
__exportStar(require("./message"), exports);
|
||||||
__exportStar(require("./template"), exports);
|
__exportStar(require("./template"), exports);
|
||||||
__exportStar(require("./comment"), exports);
|
__exportStar(require("./comment"), exports);
|
||||||
__exportStar(require("./collector"), exports);
|
__exportStar(require("./star"), exports);
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
export declare const StarApiDefinition: {
|
||||||
|
/**
|
||||||
|
* 收藏(或取消收藏)
|
||||||
|
*/
|
||||||
|
toggle: {
|
||||||
|
method: "post";
|
||||||
|
server: "toggle";
|
||||||
|
client: () => string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 检测是否收藏
|
||||||
|
*/
|
||||||
|
check: {
|
||||||
|
method: "post";
|
||||||
|
server: "check";
|
||||||
|
client: () => string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取收藏的知识库
|
||||||
|
*/
|
||||||
|
wikis: {
|
||||||
|
method: "get";
|
||||||
|
server: "wikis";
|
||||||
|
client: () => string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取知识库内加星的文章
|
||||||
|
*/
|
||||||
|
wikiDocuments: {
|
||||||
|
method: "get";
|
||||||
|
server: "wiki/documents";
|
||||||
|
client: () => string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取收藏的文档
|
||||||
|
*/
|
||||||
|
documents: {
|
||||||
|
method: "get";
|
||||||
|
server: "documents";
|
||||||
|
client: () => string;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,45 @@
|
||||||
|
"use strict";
|
||||||
|
exports.__esModule = true;
|
||||||
|
exports.StarApiDefinition = void 0;
|
||||||
|
exports.StarApiDefinition = {
|
||||||
|
/**
|
||||||
|
* 收藏(或取消收藏)
|
||||||
|
*/
|
||||||
|
toggle: {
|
||||||
|
method: 'post',
|
||||||
|
server: 'toggle',
|
||||||
|
client: function () { return '/star/toggle'; }
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 检测是否收藏
|
||||||
|
*/
|
||||||
|
check: {
|
||||||
|
method: 'post',
|
||||||
|
server: 'check',
|
||||||
|
client: function () { return '/star/check'; }
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取收藏的知识库
|
||||||
|
*/
|
||||||
|
wikis: {
|
||||||
|
method: 'get',
|
||||||
|
server: 'wikis',
|
||||||
|
client: function () { return '/star/wikis'; }
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取知识库内加星的文章
|
||||||
|
*/
|
||||||
|
wikiDocuments: {
|
||||||
|
method: 'get',
|
||||||
|
server: 'wiki/documents',
|
||||||
|
client: function () { return '/star/wiki/documents'; }
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取收藏的文档
|
||||||
|
*/
|
||||||
|
documents: {
|
||||||
|
method: 'get',
|
||||||
|
server: 'documents',
|
||||||
|
client: function () { return '/star/documents'; }
|
||||||
|
}
|
||||||
|
};
|
|
@ -4,5 +4,4 @@ export * from './document';
|
||||||
export * from './message';
|
export * from './message';
|
||||||
export * from './template';
|
export * from './template';
|
||||||
export * from './comment';
|
export * from './comment';
|
||||||
export * from './collector';
|
|
||||||
export * from './pagination';
|
export * from './pagination';
|
||||||
|
|
|
@ -16,5 +16,4 @@ __exportStar(require("./document"), exports);
|
||||||
__exportStar(require("./message"), exports);
|
__exportStar(require("./message"), exports);
|
||||||
__exportStar(require("./template"), exports);
|
__exportStar(require("./template"), exports);
|
||||||
__exportStar(require("./comment"), exports);
|
__exportStar(require("./comment"), exports);
|
||||||
__exportStar(require("./collector"), exports);
|
|
||||||
__exportStar(require("./pagination"), exports);
|
__exportStar(require("./pagination"), exports);
|
||||||
|
|
|
@ -5,4 +5,4 @@ export * from './file';
|
||||||
export * from './message';
|
export * from './message';
|
||||||
export * from './template';
|
export * from './template';
|
||||||
export * from './comment';
|
export * from './comment';
|
||||||
export * from './collector';
|
export * from './star';
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import { IDocument, IWiki, CollectType } from '../models';
|
export const StarApiDefinition = {
|
||||||
|
|
||||||
export const CollectorApiDefinition = {
|
|
||||||
/**
|
/**
|
||||||
* 收藏(或取消收藏)
|
* 收藏(或取消收藏)
|
||||||
*/
|
*/
|
||||||
toggle: {
|
toggle: {
|
||||||
method: 'post' as const,
|
method: 'post' as const,
|
||||||
server: 'toggle' as const,
|
server: 'toggle' as const,
|
||||||
client: () => '/collector/toggle',
|
client: () => '/star/toggle',
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,7 +14,7 @@ export const CollectorApiDefinition = {
|
||||||
check: {
|
check: {
|
||||||
method: 'post' as const,
|
method: 'post' as const,
|
||||||
server: 'check' as const,
|
server: 'check' as const,
|
||||||
client: () => '/collector/check',
|
client: () => '/star/check',
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,7 +23,16 @@ export const CollectorApiDefinition = {
|
||||||
wikis: {
|
wikis: {
|
||||||
method: 'get' as const,
|
method: 'get' as const,
|
||||||
server: 'wikis' as const,
|
server: 'wikis' as const,
|
||||||
client: () => '/collector/wikis',
|
client: () => '/star/wikis',
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取知识库内加星的文章
|
||||||
|
*/
|
||||||
|
wikiDocuments: {
|
||||||
|
method: 'get' as const,
|
||||||
|
server: 'wiki/documents' as const,
|
||||||
|
client: () => '/star/wiki/documents',
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,6 +41,6 @@ export const CollectorApiDefinition = {
|
||||||
documents: {
|
documents: {
|
||||||
method: 'get' as const,
|
method: 'get' as const,
|
||||||
server: 'documents' as const,
|
server: 'documents' as const,
|
||||||
client: () => '/collector/documents',
|
client: () => '/star/documents',
|
||||||
},
|
},
|
||||||
};
|
};
|
|
@ -1,4 +0,0 @@
|
||||||
export enum CollectType {
|
|
||||||
document = 'document',
|
|
||||||
wiki = 'wiki',
|
|
||||||
}
|
|
|
@ -4,5 +4,4 @@ export * from './document';
|
||||||
export * from './message';
|
export * from './message';
|
||||||
export * from './template';
|
export * from './template';
|
||||||
export * from './comment';
|
export * from './comment';
|
||||||
export * from './collector';
|
|
||||||
export * from './pagination';
|
export * from './pagination';
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { CollectorEntity } from '@entities/collector.entity';
|
|
||||||
import { CommentEntity } from '@entities/comment.entity';
|
import { CommentEntity } from '@entities/comment.entity';
|
||||||
import { DocumentEntity } from '@entities/document.entity';
|
import { DocumentEntity } from '@entities/document.entity';
|
||||||
import { DocumentAuthorityEntity } from '@entities/document-authority.entity';
|
import { DocumentAuthorityEntity } from '@entities/document-authority.entity';
|
||||||
import { MessageEntity } from '@entities/message.entity';
|
import { MessageEntity } from '@entities/message.entity';
|
||||||
|
import { StarEntity } from '@entities/star.entity';
|
||||||
import { TemplateEntity } from '@entities/template.entity';
|
import { TemplateEntity } from '@entities/template.entity';
|
||||||
import { UserEntity } from '@entities/user.entity';
|
import { UserEntity } from '@entities/user.entity';
|
||||||
import { ViewEntity } from '@entities/view.entity';
|
import { ViewEntity } from '@entities/view.entity';
|
||||||
|
@ -10,11 +10,11 @@ import { WikiEntity } from '@entities/wiki.entity';
|
||||||
import { WikiUserEntity } from '@entities/wiki-user.entity';
|
import { WikiUserEntity } from '@entities/wiki-user.entity';
|
||||||
import { IS_PRODUCTION } from '@helpers/env.helper';
|
import { IS_PRODUCTION } from '@helpers/env.helper';
|
||||||
import { getLogFileName, ONE_DAY } from '@helpers/log.helper';
|
import { getLogFileName, ONE_DAY } from '@helpers/log.helper';
|
||||||
import { CollectorModule } from '@modules/collector.module';
|
|
||||||
import { CommentModule } from '@modules/comment.module';
|
import { CommentModule } from '@modules/comment.module';
|
||||||
import { DocumentModule } from '@modules/document.module';
|
import { DocumentModule } from '@modules/document.module';
|
||||||
import { FileModule } from '@modules/file.module';
|
import { FileModule } from '@modules/file.module';
|
||||||
import { MessageModule } from '@modules/message.module';
|
import { MessageModule } from '@modules/message.module';
|
||||||
|
import { StarModule } from '@modules/star.module';
|
||||||
import { TemplateModule } from '@modules/template.module';
|
import { TemplateModule } from '@modules/template.module';
|
||||||
import { UserModule } from '@modules/user.module';
|
import { UserModule } from '@modules/user.module';
|
||||||
import { ViewModule } from '@modules/view.module';
|
import { ViewModule } from '@modules/view.module';
|
||||||
|
@ -35,7 +35,7 @@ const ENTITIES = [
|
||||||
WikiUserEntity,
|
WikiUserEntity,
|
||||||
DocumentAuthorityEntity,
|
DocumentAuthorityEntity,
|
||||||
DocumentEntity,
|
DocumentEntity,
|
||||||
CollectorEntity,
|
StarEntity,
|
||||||
CommentEntity,
|
CommentEntity,
|
||||||
MessageEntity,
|
MessageEntity,
|
||||||
TemplateEntity,
|
TemplateEntity,
|
||||||
|
@ -46,7 +46,7 @@ const MODULES = [
|
||||||
UserModule,
|
UserModule,
|
||||||
WikiModule,
|
WikiModule,
|
||||||
DocumentModule,
|
DocumentModule,
|
||||||
CollectorModule,
|
StarModule,
|
||||||
FileModule,
|
FileModule,
|
||||||
CommentModule,
|
CommentModule,
|
||||||
MessageModule,
|
MessageModule,
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
import { CollectDto } from '@dtos/collect.dto';
|
|
||||||
import { JwtGuard } from '@guard/jwt.guard';
|
|
||||||
import {
|
|
||||||
Body,
|
|
||||||
ClassSerializerInterceptor,
|
|
||||||
Controller,
|
|
||||||
Get,
|
|
||||||
HttpCode,
|
|
||||||
HttpStatus,
|
|
||||||
Post,
|
|
||||||
Request,
|
|
||||||
UseGuards,
|
|
||||||
UseInterceptors,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { CollectorService } from '@services/collector.service';
|
|
||||||
import { CollectorApiDefinition } from '@think/domains';
|
|
||||||
|
|
||||||
@Controller('collector')
|
|
||||||
export class CollectorController {
|
|
||||||
constructor(private readonly collectorService: CollectorService) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 收藏(或取消收藏)
|
|
||||||
*/
|
|
||||||
@UseInterceptors(ClassSerializerInterceptor)
|
|
||||||
@Post(CollectorApiDefinition.toggle.server)
|
|
||||||
@HttpCode(HttpStatus.OK)
|
|
||||||
@UseGuards(JwtGuard)
|
|
||||||
async toggleStar(@Request() req, @Body() dto: CollectDto) {
|
|
||||||
return await this.collectorService.toggleStar(req.user, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检测是否收藏
|
|
||||||
*/
|
|
||||||
@UseInterceptors(ClassSerializerInterceptor)
|
|
||||||
@Post(CollectorApiDefinition.check.server)
|
|
||||||
@HttpCode(HttpStatus.OK)
|
|
||||||
@UseGuards(JwtGuard)
|
|
||||||
async checkStar(@Request() req, @Body() dto: CollectDto) {
|
|
||||||
return await this.collectorService.isStared(req.user, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取收藏的知识库
|
|
||||||
*/
|
|
||||||
@UseInterceptors(ClassSerializerInterceptor)
|
|
||||||
@Get(CollectorApiDefinition.wikis.server)
|
|
||||||
@HttpCode(HttpStatus.OK)
|
|
||||||
@UseGuards(JwtGuard)
|
|
||||||
async getWikis(@Request() req) {
|
|
||||||
return await this.collectorService.getWikis(req.user);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取收藏的文档
|
|
||||||
*/
|
|
||||||
@UseInterceptors(ClassSerializerInterceptor)
|
|
||||||
@Get(CollectorApiDefinition.documents.server)
|
|
||||||
@HttpCode(HttpStatus.OK)
|
|
||||||
@UseGuards(JwtGuard)
|
|
||||||
async getDocuments(@Request() req) {
|
|
||||||
return await this.collectorService.getDocuments(req.user);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { StarDto } from '@dtos/star.dto';
|
||||||
|
import { JwtGuard } from '@guard/jwt.guard';
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
ClassSerializerInterceptor,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
Request,
|
||||||
|
UseGuards,
|
||||||
|
UseInterceptors,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { StarService } from '@services/star.service';
|
||||||
|
import { StarApiDefinition } from '@think/domains';
|
||||||
|
|
||||||
|
@Controller('star')
|
||||||
|
export class StarController {
|
||||||
|
constructor(private readonly starService: StarService) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收藏(或取消收藏)
|
||||||
|
*/
|
||||||
|
@UseInterceptors(ClassSerializerInterceptor)
|
||||||
|
@Post(StarApiDefinition.toggle.server)
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@UseGuards(JwtGuard)
|
||||||
|
async toggleStar(@Request() req, @Body() dto: StarDto) {
|
||||||
|
return await this.starService.toggleStar(req.user, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测是否收藏
|
||||||
|
*/
|
||||||
|
@UseInterceptors(ClassSerializerInterceptor)
|
||||||
|
@Post(StarApiDefinition.check.server)
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@UseGuards(JwtGuard)
|
||||||
|
async checkStar(@Request() req, @Body() dto: StarDto) {
|
||||||
|
return await this.starService.isStared(req.user, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取收藏的知识库
|
||||||
|
*/
|
||||||
|
@UseInterceptors(ClassSerializerInterceptor)
|
||||||
|
@Get(StarApiDefinition.wikis.server)
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@UseGuards(JwtGuard)
|
||||||
|
async getWikis(@Request() req) {
|
||||||
|
return await this.starService.getWikis(req.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取知识库内收藏的文档
|
||||||
|
*/
|
||||||
|
@UseInterceptors(ClassSerializerInterceptor)
|
||||||
|
@Get(StarApiDefinition.wikiDocuments.server)
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@UseGuards(JwtGuard)
|
||||||
|
async getWikiDocuments(@Request() req, @Query() dto: StarDto) {
|
||||||
|
return await this.starService.getWikiDocuments(req.user, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取收藏的文档
|
||||||
|
*/
|
||||||
|
@UseInterceptors(ClassSerializerInterceptor)
|
||||||
|
@Get(StarApiDefinition.documents.server)
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@UseGuards(JwtGuard)
|
||||||
|
async getDocuments(@Request() req) {
|
||||||
|
return await this.starService.getDocuments(req.user);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
import { CollectType } from '@think/domains';
|
|
||||||
import { IsNotEmpty, IsString } from 'class-validator';
|
|
||||||
|
|
||||||
export class CollectDto {
|
|
||||||
@IsString({ message: '收藏目标Id类型错误(正确类型为:String)' })
|
|
||||||
@IsNotEmpty({ message: '收藏目标Id不能为空' })
|
|
||||||
targetId: string;
|
|
||||||
|
|
||||||
@IsString({ message: '收藏目标类型类型错误(正确类型为:String)' })
|
|
||||||
@IsNotEmpty({ message: '用户密码不能为空' })
|
|
||||||
type: CollectType;
|
|
||||||
}
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class StarDto {
|
||||||
|
@IsString({ message: '加星 wikiId 类型错误(正确类型为:String)' })
|
||||||
|
@IsNotEmpty({ message: '加星 wikiId 不能为空' })
|
||||||
|
wikiId: string;
|
||||||
|
|
||||||
|
@IsString({ message: '加星 documentId 类型错误(正确类型为:String)' })
|
||||||
|
@IsNotEmpty({ message: '加星 documentId 不能为空' })
|
||||||
|
@IsOptional()
|
||||||
|
documentId?: string;
|
||||||
|
}
|
|
@ -1,24 +1,18 @@
|
||||||
import { CollectType } from '@think/domains';
|
|
||||||
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
|
||||||
|
|
||||||
@Entity('collector')
|
@Entity('star')
|
||||||
export class CollectorEntity {
|
export class StarEntity {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
public id: string;
|
public id: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', comment: '用户 Id' })
|
@Column({ type: 'varchar', comment: '用户 Id' })
|
||||||
public userId: string;
|
public userId: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar', comment: '收藏目标 Id' })
|
@Column({ type: 'varchar', comment: '知识库 Id' })
|
||||||
public targetId: string;
|
public wikiId: string;
|
||||||
|
|
||||||
@Column({
|
@Column({ type: 'varchar', comment: '文档 Id', default: null })
|
||||||
type: 'enum',
|
public documentId: string;
|
||||||
enum: CollectType,
|
|
||||||
default: CollectType.document,
|
|
||||||
comment: '收藏目标类型',
|
|
||||||
})
|
|
||||||
public type: CollectType;
|
|
||||||
|
|
||||||
@CreateDateColumn({
|
@CreateDateColumn({
|
||||||
type: 'timestamp',
|
type: 'timestamp',
|
|
@ -1,5 +1,4 @@
|
||||||
import { HttpResponseExceptionFilter } from '@exceptions/http-response.exception';
|
import { HttpResponseExceptionFilter } from '@exceptions/http-response.exception';
|
||||||
import { IS_PRODUCTION } from '@helpers/env.helper';
|
|
||||||
import { FILE_DEST, FILE_ROOT_PATH } from '@helpers/file.helper/local.client';
|
import { FILE_DEST, FILE_ROOT_PATH } from '@helpers/file.helper/local.client';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
@ -13,7 +12,6 @@ import rateLimit from 'express-rate-limit';
|
||||||
import helmet from 'helmet';
|
import helmet from 'helmet';
|
||||||
|
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { AppClusterService } from './app-cluster.service';
|
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { DocumentController } from '@controllers/document.controller';
|
import { DocumentController } from '@controllers/document.controller';
|
||||||
import { DocumentEntity } from '@entities/document.entity';
|
import { DocumentEntity } from '@entities/document.entity';
|
||||||
import { DocumentAuthorityEntity } from '@entities/document-authority.entity';
|
import { DocumentAuthorityEntity } from '@entities/document-authority.entity';
|
||||||
import { CollectorModule } from '@modules/collector.module';
|
|
||||||
import { MessageModule } from '@modules/message.module';
|
import { MessageModule } from '@modules/message.module';
|
||||||
|
import { StarModule } from '@modules/star.module';
|
||||||
import { TemplateModule } from '@modules/template.module';
|
import { TemplateModule } from '@modules/template.module';
|
||||||
import { UserModule } from '@modules/user.module';
|
import { UserModule } from '@modules/user.module';
|
||||||
import { ViewModule } from '@modules/view.module';
|
import { ViewModule } from '@modules/view.module';
|
||||||
|
@ -20,7 +20,7 @@ import { DocumentService } from '@services/document.service';
|
||||||
forwardRef(() => WikiModule),
|
forwardRef(() => WikiModule),
|
||||||
forwardRef(() => MessageModule),
|
forwardRef(() => MessageModule),
|
||||||
forwardRef(() => TemplateModule),
|
forwardRef(() => TemplateModule),
|
||||||
forwardRef(() => CollectorModule),
|
forwardRef(() => StarModule),
|
||||||
forwardRef(() => ViewModule),
|
forwardRef(() => ViewModule),
|
||||||
],
|
],
|
||||||
providers: [DocumentService],
|
providers: [DocumentService],
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import { CollectorController } from '@controllers/collector.controller';
|
import { StarController } from '@controllers/star.controller';
|
||||||
import { CollectorEntity } from '@entities/collector.entity';
|
import { StarEntity } from '@entities/star.entity';
|
||||||
import { DocumentModule } from '@modules/document.module';
|
import { DocumentModule } from '@modules/document.module';
|
||||||
import { UserModule } from '@modules/user.module';
|
import { UserModule } from '@modules/user.module';
|
||||||
import { WikiModule } from '@modules/wiki.module';
|
import { WikiModule } from '@modules/wiki.module';
|
||||||
import { forwardRef, Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { CollectorService } from '@services/collector.service';
|
import { StarService } from '@services/star.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([CollectorEntity]),
|
TypeOrmModule.forFeature([StarEntity]),
|
||||||
forwardRef(() => UserModule),
|
forwardRef(() => UserModule),
|
||||||
forwardRef(() => WikiModule),
|
forwardRef(() => WikiModule),
|
||||||
forwardRef(() => DocumentModule),
|
forwardRef(() => DocumentModule),
|
||||||
],
|
],
|
||||||
providers: [CollectorService],
|
providers: [StarService],
|
||||||
exports: [CollectorService],
|
exports: [StarService],
|
||||||
controllers: [CollectorController],
|
controllers: [StarController],
|
||||||
})
|
})
|
||||||
export class CollectorModule {}
|
export class StarModule {}
|
|
@ -1,7 +1,7 @@
|
||||||
import { UserController } from '@controllers/user.controller';
|
import { UserController } from '@controllers/user.controller';
|
||||||
import { UserEntity } from '@entities/user.entity';
|
import { UserEntity } from '@entities/user.entity';
|
||||||
import { CollectorModule } from '@modules/collector.module';
|
|
||||||
import { MessageModule } from '@modules/message.module';
|
import { MessageModule } from '@modules/message.module';
|
||||||
|
import { StarModule } from '@modules/star.module';
|
||||||
import { WikiModule } from '@modules/wiki.module';
|
import { WikiModule } from '@modules/wiki.module';
|
||||||
import { forwardRef, Inject, Injectable, Module, UnauthorizedException } from '@nestjs/common';
|
import { forwardRef, Inject, Injectable, Module, UnauthorizedException } from '@nestjs/common';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
@ -60,7 +60,7 @@ const jwtModule = JwtModule.register({
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
forwardRef(() => WikiModule),
|
forwardRef(() => WikiModule),
|
||||||
forwardRef(() => MessageModule),
|
forwardRef(() => MessageModule),
|
||||||
forwardRef(() => CollectorModule),
|
forwardRef(() => StarModule),
|
||||||
passModule,
|
passModule,
|
||||||
jwtModule,
|
jwtModule,
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { WikiController } from '@controllers/wiki.controller';
|
import { WikiController } from '@controllers/wiki.controller';
|
||||||
import { WikiEntity } from '@entities/wiki.entity';
|
import { WikiEntity } from '@entities/wiki.entity';
|
||||||
import { WikiUserEntity } from '@entities/wiki-user.entity';
|
import { WikiUserEntity } from '@entities/wiki-user.entity';
|
||||||
import { CollectorModule } from '@modules/collector.module';
|
|
||||||
import { DocumentModule } from '@modules/document.module';
|
import { DocumentModule } from '@modules/document.module';
|
||||||
import { MessageModule } from '@modules/message.module';
|
import { MessageModule } from '@modules/message.module';
|
||||||
|
import { StarModule } from '@modules/star.module';
|
||||||
import { UserModule } from '@modules/user.module';
|
import { UserModule } from '@modules/user.module';
|
||||||
import { ViewModule } from '@modules/view.module';
|
import { ViewModule } from '@modules/view.module';
|
||||||
import { forwardRef, Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
|
@ -17,7 +17,7 @@ import { WikiService } from '@services/wiki.service';
|
||||||
forwardRef(() => DocumentModule),
|
forwardRef(() => DocumentModule),
|
||||||
forwardRef(() => MessageModule),
|
forwardRef(() => MessageModule),
|
||||||
forwardRef(() => ViewModule),
|
forwardRef(() => ViewModule),
|
||||||
forwardRef(() => CollectorModule),
|
forwardRef(() => StarModule),
|
||||||
],
|
],
|
||||||
providers: [WikiService],
|
providers: [WikiService],
|
||||||
exports: [WikiService],
|
exports: [WikiService],
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
import { CollectDto } from '@dtos/collect.dto';
|
|
||||||
import { CollectorEntity } from '@entities/collector.entity';
|
|
||||||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
import { DocumentService } from '@services/document.service';
|
|
||||||
import { OutUser, UserService } from '@services/user.service';
|
|
||||||
import { WikiService } from '@services/wiki.service';
|
|
||||||
import { CollectType } from '@think/domains';
|
|
||||||
import * as lodash from 'lodash';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CollectorService {
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(CollectorEntity)
|
|
||||||
private readonly collectorRepo: Repository<CollectorEntity>,
|
|
||||||
@Inject(forwardRef(() => UserService))
|
|
||||||
private readonly userService: UserService,
|
|
||||||
@Inject(forwardRef(() => WikiService))
|
|
||||||
private readonly wikiService: WikiService,
|
|
||||||
@Inject(forwardRef(() => DocumentService))
|
|
||||||
private readonly documentService: DocumentService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async toggleStar(user: OutUser, dto: CollectDto) {
|
|
||||||
const data = {
|
|
||||||
...dto,
|
|
||||||
userId: user.id,
|
|
||||||
};
|
|
||||||
const record = await this.collectorRepo.findOne(data);
|
|
||||||
if (record) {
|
|
||||||
await this.collectorRepo.remove(record);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
const res = await this.collectorRepo.create(data);
|
|
||||||
const ret = await this.collectorRepo.save(res);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async isStared(user: OutUser, dto: CollectDto) {
|
|
||||||
const res = await this.collectorRepo.findOne({ userId: user.id, ...dto });
|
|
||||||
return Boolean(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getWikis(user: OutUser) {
|
|
||||||
const records = await this.collectorRepo.find({
|
|
||||||
userId: user.id,
|
|
||||||
type: CollectType.wiki,
|
|
||||||
});
|
|
||||||
const res = await this.wikiService.findByIds(records.map((record) => record.targetId));
|
|
||||||
const withCreateUserRes = await Promise.all(
|
|
||||||
res.map(async (wiki) => {
|
|
||||||
const createUser = await this.userService.findById(wiki.createUserId);
|
|
||||||
const isMember = await this.wikiService.isMember(wiki.id, user.id);
|
|
||||||
return { createUser, isMember, ...wiki };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return withCreateUserRes;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDocuments(user: OutUser) {
|
|
||||||
const records = await this.collectorRepo.find({
|
|
||||||
userId: user.id,
|
|
||||||
type: CollectType.document,
|
|
||||||
});
|
|
||||||
const res = await this.documentService.findByIds(records.map((record) => record.targetId));
|
|
||||||
const withCreateUserRes = await Promise.all(
|
|
||||||
res.map(async (doc) => {
|
|
||||||
const createUser = await this.userService.findById(doc.createUserId);
|
|
||||||
return { createUser, ...doc };
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return withCreateUserRes.map((document) => {
|
|
||||||
return lodash.omit(document, ['state', 'content', 'index', 'createUserId']);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
import { StarDto } from '@dtos/star.dto';
|
||||||
|
import { StarEntity } from '@entities/star.entity';
|
||||||
|
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { DocumentService } from '@services/document.service';
|
||||||
|
import { OutUser, UserService } from '@services/user.service';
|
||||||
|
import { WikiService } from '@services/wiki.service';
|
||||||
|
import { IDocument } from '@think/domains';
|
||||||
|
import * as lodash from 'lodash';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class StarService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(StarEntity)
|
||||||
|
private readonly starRepo: Repository<StarEntity>,
|
||||||
|
@Inject(forwardRef(() => UserService))
|
||||||
|
private readonly userService: UserService,
|
||||||
|
@Inject(forwardRef(() => WikiService))
|
||||||
|
private readonly wikiService: WikiService,
|
||||||
|
@Inject(forwardRef(() => DocumentService))
|
||||||
|
private readonly documentService: DocumentService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加星或取消加星
|
||||||
|
* @param user
|
||||||
|
* @param dto
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async toggleStar(user: OutUser, dto: StarDto) {
|
||||||
|
const data = {
|
||||||
|
...dto,
|
||||||
|
userId: user.id,
|
||||||
|
};
|
||||||
|
const record = await this.starRepo.findOne(data);
|
||||||
|
if (record) {
|
||||||
|
await this.starRepo.remove(record);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
const res = await this.starRepo.create(data);
|
||||||
|
const ret = await this.starRepo.save(res);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否加星
|
||||||
|
* @param user
|
||||||
|
* @param dto
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async isStared(user: OutUser, dto: StarDto) {
|
||||||
|
const res = await this.starRepo.findOne({ userId: user.id, ...dto });
|
||||||
|
return Boolean(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取加星的知识库
|
||||||
|
* @param user
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getWikis(user: OutUser) {
|
||||||
|
const records = await this.starRepo.find({
|
||||||
|
userId: user.id,
|
||||||
|
documentId: null,
|
||||||
|
});
|
||||||
|
const res = await this.wikiService.findByIds(records.map((record) => record.wikiId));
|
||||||
|
const withCreateUserRes = await Promise.all(
|
||||||
|
res.map(async (wiki) => {
|
||||||
|
const createUser = await this.userService.findById(wiki.createUserId);
|
||||||
|
const isMember = await this.wikiService.isMember(wiki.id, user.id);
|
||||||
|
return { createUser, isMember, ...wiki };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return withCreateUserRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取知识库加星的文档
|
||||||
|
* @param user
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getWikiDocuments(user: OutUser, dto: StarDto) {
|
||||||
|
const records = await this.starRepo.find({
|
||||||
|
userId: user.id,
|
||||||
|
wikiId: dto.wikiId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await this.documentService.findByIds(
|
||||||
|
records.filter((record) => record.documentId).map((record) => record.documentId)
|
||||||
|
);
|
||||||
|
const withCreateUserRes = (await Promise.all(
|
||||||
|
res.map(async (doc) => {
|
||||||
|
const createUser = await this.userService.findById(doc.createUserId);
|
||||||
|
return { createUser, ...doc };
|
||||||
|
})
|
||||||
|
)) as Array<IDocument & { createUser: OutUser }>;
|
||||||
|
|
||||||
|
return withCreateUserRes
|
||||||
|
.map((document) => {
|
||||||
|
return lodash.omit(document, ['state', 'content', 'index', 'createUserId']);
|
||||||
|
})
|
||||||
|
.map((doc) => {
|
||||||
|
return {
|
||||||
|
...doc,
|
||||||
|
key: doc.id,
|
||||||
|
label: doc.title,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取加星的文档(平铺)
|
||||||
|
* @param user
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getDocuments(user: OutUser) {
|
||||||
|
const records = await this.starRepo.find({
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
const res = await this.documentService.findByIds(records.map((record) => record.documentId));
|
||||||
|
const withCreateUserRes = await Promise.all(
|
||||||
|
res.map(async (doc) => {
|
||||||
|
const createUser = await this.userService.findById(doc.createUserId);
|
||||||
|
return { createUser, ...doc };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return withCreateUserRes.map((document) => {
|
||||||
|
return lodash.omit(document, ['state', 'content', 'index', 'createUserId']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,10 +6,10 @@ import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nest
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { CollectorService } from '@services/collector.service';
|
|
||||||
import { MessageService } from '@services/message.service';
|
import { MessageService } from '@services/message.service';
|
||||||
|
import { StarService } from '@services/star.service';
|
||||||
import { WikiService } from '@services/wiki.service';
|
import { WikiService } from '@services/wiki.service';
|
||||||
import { CollectType, UserStatus } from '@think/domains';
|
import { UserStatus } from '@think/domains';
|
||||||
import { instanceToPlain } from 'class-transformer';
|
import { instanceToPlain } from 'class-transformer';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@ -29,8 +29,8 @@ export class UserService {
|
||||||
@Inject(forwardRef(() => MessageService))
|
@Inject(forwardRef(() => MessageService))
|
||||||
private readonly messageService: MessageService,
|
private readonly messageService: MessageService,
|
||||||
|
|
||||||
@Inject(forwardRef(() => CollectorService))
|
@Inject(forwardRef(() => StarService))
|
||||||
private readonly collectorService: CollectorService,
|
private readonly starService: StarService,
|
||||||
|
|
||||||
@Inject(forwardRef(() => WikiService))
|
@Inject(forwardRef(() => WikiService))
|
||||||
private readonly wikiService: WikiService
|
private readonly wikiService: WikiService
|
||||||
|
@ -94,9 +94,8 @@ export class UserService {
|
||||||
name: createdUser.name,
|
name: createdUser.name,
|
||||||
description: `${createdUser.name}的个人空间`,
|
description: `${createdUser.name}的个人空间`,
|
||||||
});
|
});
|
||||||
await this.collectorService.toggleStar(createdUser, {
|
await this.starService.toggleStar(createdUser, {
|
||||||
targetId: wiki.id,
|
wikiId: wiki.id,
|
||||||
type: CollectType.wiki,
|
|
||||||
});
|
});
|
||||||
await this.messageService.notify(createdUser, {
|
await this.messageService.notify(createdUser, {
|
||||||
title: `欢迎「${createdUser.name}」`,
|
title: `欢迎「${createdUser.name}」`,
|
||||||
|
|
|
@ -7,13 +7,13 @@ import { WikiUserEntity } from '@entities/wiki-user.entity';
|
||||||
import { array2tree } from '@helpers/tree.helper';
|
import { array2tree } from '@helpers/tree.helper';
|
||||||
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
|
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { CollectorService } from '@services/collector.service';
|
|
||||||
import { DocumentService } from '@services/document.service';
|
import { DocumentService } from '@services/document.service';
|
||||||
import { MessageService } from '@services/message.service';
|
import { MessageService } from '@services/message.service';
|
||||||
|
import { StarService } from '@services/star.service';
|
||||||
import { UserService } from '@services/user.service';
|
import { UserService } from '@services/user.service';
|
||||||
import { OutUser } from '@services/user.service';
|
import { OutUser } from '@services/user.service';
|
||||||
import { ViewService } from '@services/view.service';
|
import { ViewService } from '@services/view.service';
|
||||||
import { CollectType, DocumentStatus, IPagination, WikiStatus, WikiUserRole } from '@think/domains';
|
import { DocumentStatus, IPagination, WikiStatus, WikiUserRole } from '@think/domains';
|
||||||
import { instanceToPlain } from 'class-transformer';
|
import { instanceToPlain } from 'class-transformer';
|
||||||
import * as lodash from 'lodash';
|
import * as lodash from 'lodash';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
@ -30,8 +30,8 @@ export class WikiService {
|
||||||
@Inject(forwardRef(() => MessageService))
|
@Inject(forwardRef(() => MessageService))
|
||||||
private readonly messageService: MessageService,
|
private readonly messageService: MessageService,
|
||||||
|
|
||||||
@Inject(forwardRef(() => CollectorService))
|
@Inject(forwardRef(() => StarService))
|
||||||
private readonly collectorService: CollectorService,
|
private readonly starService: StarService,
|
||||||
|
|
||||||
@Inject(forwardRef(() => DocumentService))
|
@Inject(forwardRef(() => DocumentService))
|
||||||
private readonly documentService: DocumentService,
|
private readonly documentService: DocumentService,
|
||||||
|
@ -320,7 +320,7 @@ export class WikiService {
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
await this.collectorService.toggleStar(user, { type: CollectType.wiki, targetId: wiki.id }),
|
await this.starService.toggleStar(user, { wikiId: wiki.id }),
|
||||||
]);
|
]);
|
||||||
const homeDocumentId = doc.id;
|
const homeDocumentId = doc.id;
|
||||||
const withHomeDocumentIdWiki = await this.wikiRepo.merge(wiki, { homeDocumentId });
|
const withHomeDocumentIdWiki = await this.wikiRepo.merge(wiki, { homeDocumentId });
|
||||||
|
|
Loading…
Reference in New Issue