mirror of https://github.com/fantasticit/think.git
feat: improve star, ui of document actions
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 { Button, Dropdown, Popover, Space, Typography } from '@douyinfe/semi-ui';
|
||||
import { IconArticle, IconBranch, IconHistory, IconMore, IconPlus, IconStar } from '@douyinfe/semi-icons';
|
||||
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 { DocumentDeletor } from 'components/document/delete';
|
||||
import { DocumentLinkCopyer } from 'components/document/link';
|
||||
import { DocumentShare } from 'components/document/share';
|
||||
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 React, { useCallback } from 'react';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
interface IProps {
|
||||
wikiId: string;
|
||||
documentId: string;
|
||||
hoverVisible?: boolean;
|
||||
onStar?: () => void;
|
||||
onCreate?: () => void;
|
||||
onDelete?: () => void;
|
||||
onVisibleChange?: () => void;
|
||||
showCreateDocument?: boolean;
|
||||
size?: ButtonProps['size'];
|
||||
hideDocumentVersion?: boolean;
|
||||
hideDocumentStyle?: boolean;
|
||||
}
|
||||
|
||||
const { Text } = Typography;
|
||||
|
@ -22,12 +33,15 @@ const { Text } = Typography;
|
|||
export const DocumentActions: React.FC<IProps> = ({
|
||||
wikiId,
|
||||
documentId,
|
||||
hoverVisible,
|
||||
onStar,
|
||||
onCreate,
|
||||
onDelete,
|
||||
onVisibleChange,
|
||||
showCreateDocument,
|
||||
children,
|
||||
size = 'default',
|
||||
hideDocumentVersion = false,
|
||||
hideDocumentStyle = false,
|
||||
}) => {
|
||||
const [popoverVisible, togglePopoverVisible] = useToggle(false);
|
||||
const [createVisible, toggleCreateVisible] = useToggle(false);
|
||||
|
@ -52,9 +66,10 @@ export const DocumentActions: React.FC<IProps> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<Popover
|
||||
showArrow
|
||||
<Dropdown
|
||||
style={{ padding: 0 }}
|
||||
trigger="click"
|
||||
position="bottomLeft"
|
||||
visible={popoverVisible}
|
||||
onVisibleChange={wrapOnVisibleChange}
|
||||
content={
|
||||
|
@ -70,7 +85,25 @@ export const DocumentActions: React.FC<IProps> = ({
|
|||
</Dropdown.Item>
|
||||
)}
|
||||
|
||||
<DocumentShare
|
||||
key="share"
|
||||
documentId={documentId}
|
||||
render={({ isPublic, toggleVisible }) => {
|
||||
return (
|
||||
<Dropdown.Item onClick={toggleVisible}>
|
||||
<Text>
|
||||
<Space>
|
||||
<IconBranch />
|
||||
{isPublic ? '分享中' : '分享'}
|
||||
</Space>
|
||||
</Text>
|
||||
</Dropdown.Item>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<DocumentStar
|
||||
wikiId={wikiId}
|
||||
documentId={documentId}
|
||||
render={({ star, toggleStar, text }) => (
|
||||
<Dropdown.Item
|
||||
|
@ -96,10 +129,56 @@ export const DocumentActions: React.FC<IProps> = ({
|
|||
wikiId={wikiId}
|
||||
documentId={documentId}
|
||||
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 />
|
||||
|
||||
<DocumentDeletor
|
||||
|
@ -113,9 +192,17 @@ export const DocumentActions: React.FC<IProps> = ({
|
|||
</Dropdown.Menu>
|
||||
}
|
||||
>
|
||||
{children || <Button icon={<IconMore />} theme="borderless" type="tertiary" />}
|
||||
</Popover>
|
||||
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
type="tertiary"
|
||||
size={size}
|
||||
className={cls(hoverVisible && styles.hoverVisible, popoverVisible && styles.isActive)}
|
||||
theme={popoverVisible ? 'solid' : 'borderless'}
|
||||
icon={<IconMore />}
|
||||
/>
|
||||
</Dropdown>
|
||||
{showCreateDocument && (
|
||||
<DocumentCreator
|
||||
wikiId={wikiId}
|
||||
|
|
|
@ -40,7 +40,7 @@ export const DocumentCard: React.FC<{ document: IDocument }> = ({ document }) =>
|
|||
<Tooltip key="edit" content="编辑" position="bottom">
|
||||
<Button type="tertiary" theme="borderless" icon={<IconEdit />} onClick={gotoEdit} />
|
||||
</Tooltip>
|
||||
<DocumentStar documentId={document.id} />
|
||||
<DocumentStar wikiId={document.wikiId} documentId={document.id} />
|
||||
</Space>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
@ -51,7 +51,6 @@ const renderChecked = (onChange, authKey: 'readable' | 'editable') => (checked,
|
|||
|
||||
export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, disabled = false }) => {
|
||||
const { isMobile } = IsOnMobile.useHook();
|
||||
const toastedUsersRef = useRef<Array<IUser['id']>>([]);
|
||||
const ref = useRef<HTMLInputElement>();
|
||||
const { user: currentUser } = useUser();
|
||||
const [visible, toggleVisible] = useToggle(false);
|
||||
|
@ -86,7 +85,7 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
|
|||
<div style={{ marginTop: 16 }}>
|
||||
<Input ref={ref} placeholder="输入对方用户名" value={inviteUser} onChange={setInviteUser}></Input>
|
||||
<Paragraph style={{ marginTop: 16 }}>
|
||||
邀请成功后,请将该链接发送给对方。
|
||||
将对方加入文档进行协作,您也可将该链接发送给对方。
|
||||
<span style={{ verticalAlign: 'middle' }}>
|
||||
<DocumentLinkCopyer wikiId={wikiId} documentId={documentId} />
|
||||
</span>
|
||||
|
@ -151,38 +150,30 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
|
|||
}, [visible]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (mentionUsers) => {
|
||||
const newCollaborationUsers = mentionUsers
|
||||
const handler = (users) => {
|
||||
const joinUsers = users
|
||||
.filter(Boolean)
|
||||
.filter((state) => state.user)
|
||||
.map((state) => ({ ...state.user, clientId: state.clientId }));
|
||||
|
||||
if (
|
||||
collaborationUsers.length === newCollaborationUsers.length &&
|
||||
newCollaborationUsers.every((newUser) => {
|
||||
return collaborationUsers.find((existUser) => existUser.id === newUser.id);
|
||||
joinUsers
|
||||
.filter(Boolean)
|
||||
.filter((joinUser) => {
|
||||
return joinUser.name !== currentUser.name;
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
newCollaborationUsers.forEach((newUser) => {
|
||||
if (currentUser && newUser.name !== currentUser.name && !toastedUsersRef.current.includes(newUser.id)) {
|
||||
Toast.info(`${newUser.name}-${newUser.clientId}加入文档`);
|
||||
toastedUsersRef.current.push(newUser.id);
|
||||
}
|
||||
.forEach((joinUser) => {
|
||||
Toast.info(`${joinUser.name}-${joinUser.clientId}加入文档`);
|
||||
});
|
||||
|
||||
setCollaborationUsers(newCollaborationUsers);
|
||||
setCollaborationUsers(joinUsers);
|
||||
};
|
||||
|
||||
event.on(JOIN_USER, handler);
|
||||
|
||||
return () => {
|
||||
toastedUsersRef.current = [];
|
||||
event.off(JOIN_USER, handler);
|
||||
};
|
||||
}, [collaborationUsers, currentUser]);
|
||||
}, [currentUser]);
|
||||
|
||||
if (error)
|
||||
return (
|
||||
|
|
|
@ -54,7 +54,6 @@ export const DocumentDeletor: React.FC<IProps> = ({ wikiId, documentId, render,
|
|||
onConfirm={deleteAction}
|
||||
okButtonProps={{ loading }}
|
||||
zIndex={1070}
|
||||
showArrow
|
||||
>
|
||||
{render ? render({ children: content }) : content}
|
||||
</Popconfirm>
|
||||
|
|
|
@ -3,9 +3,7 @@ import { Button, Nav, Skeleton, Space, Tooltip, Typography } from '@douyinfe/sem
|
|||
import { DataRender } from 'components/data-render';
|
||||
import { Divider } from 'components/divider';
|
||||
import { DocumentCollaboration } from 'components/document/collaboration';
|
||||
import { DocumentShare } from 'components/document/share';
|
||||
import { DocumentStar } from 'components/document/star';
|
||||
import { DocumentStyle } from 'components/document/style';
|
||||
import { DocumentVersion } from 'components/document/version';
|
||||
import { Seo } from 'components/seo';
|
||||
import { Theme } from 'components/theme';
|
||||
|
@ -20,6 +18,7 @@ import { SecureDocumentIllustration } from 'illustrations/secure-document';
|
|||
import Router from 'next/router';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { DocumentActions } from '../actions';
|
||||
import { Editor } from './editor';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
|
@ -49,12 +48,11 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
|||
() => (
|
||||
<Space>
|
||||
{document && authority.readable && (
|
||||
<DocumentCollaboration key="collaboration" wikiId={document.wikiId} documentId={documentId} />
|
||||
<DocumentCollaboration key={documentId} wikiId={document.wikiId} documentId={documentId} />
|
||||
)}
|
||||
<DocumentShare key="share" documentId={documentId} />
|
||||
<DocumentVersion key="version" documentId={documentId} onSelect={triggerUseDocumentVersion} />
|
||||
<DocumentStar key="star" documentId={documentId} />
|
||||
<DocumentStyle />
|
||||
{document && <DocumentStar key="star" wikiId={document.wikiId} documentId={documentId} />}
|
||||
{document && <DocumentActions wikiId={document.wikiId} documentId={documentId} />}
|
||||
<DocumentVersion documentId={documentId} onSelect={triggerUseDocumentVersion} />
|
||||
</Space>
|
||||
),
|
||||
[documentId, document, authority]
|
||||
|
|
|
@ -18,6 +18,7 @@ import React, { useCallback, useMemo } from 'react';
|
|||
import { createPortal } from 'react-dom';
|
||||
import { CollaborationEditor } from 'tiptap/editor';
|
||||
|
||||
import { DocumentActions } from '../actions';
|
||||
import { Author } from './author';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
|
@ -64,18 +65,17 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
|||
{document && (
|
||||
<DocumentCollaboration
|
||||
disabled={!readable}
|
||||
key="collaboration"
|
||||
key={documentId}
|
||||
wikiId={document.wikiId}
|
||||
documentId={documentId}
|
||||
/>
|
||||
)}
|
||||
{document && <DocumentStar disabled={!readable} key="star" wikiId={document.wikiId} documentId={documentId} />}
|
||||
<Tooltip key="edit" content="编辑" position="bottom">
|
||||
<Button disabled={!editable} icon={<IconEdit />} onMouseDown={gotoEdit} />
|
||||
</Tooltip>
|
||||
<DocumentShare disabled={!readable} key="share" documentId={documentId} />
|
||||
<DocumentVersion disabled={!readable} key="version" documentId={documentId} />
|
||||
<DocumentStar disabled={!readable} key="star" documentId={documentId} />
|
||||
<DocumentStyle />
|
||||
{document && <DocumentActions wikiId={document.wikiId} documentId={documentId} />}
|
||||
<DocumentVersion documentId={documentId} />
|
||||
</Space>
|
||||
);
|
||||
}, [document, documentId, readable, editable, gotoEdit]);
|
||||
|
|
|
@ -11,7 +11,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|||
interface IProps {
|
||||
documentId: string;
|
||||
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;
|
||||
|
@ -134,6 +134,7 @@ export const DocumentShare: React.FC<IProps> = ({ documentId, disabled = false,
|
|||
footer={null}
|
||||
onCancel={toggleVisible}
|
||||
style={{ maxWidth: '96vw' }}
|
||||
zIndex={1067}
|
||||
>
|
||||
{content}
|
||||
</Modal>
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import { IconStar } from '@douyinfe/semi-icons';
|
||||
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 React, { useCallback } from 'react';
|
||||
import VisibilitySensor from 'react-visibility-sensor';
|
||||
|
||||
interface IProps {
|
||||
documentId: string;
|
||||
wikiId: IWiki['id'];
|
||||
documentId: IDocument['id'];
|
||||
disabled?: boolean;
|
||||
render?: (arg: {
|
||||
star: boolean;
|
||||
|
@ -16,9 +18,9 @@ interface IProps {
|
|||
}) => 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 { data, toggle: toggleStar } = useDocumentCollectToggle(documentId, { enabled: visible });
|
||||
const { data, toggle: toggleStar } = useDocumentStarToggle(wikiId, documentId, { enabled: visible });
|
||||
const text = data ? '取消收藏' : '收藏文档';
|
||||
|
||||
const onViewportChange = useCallback(
|
||||
|
|
|
@ -10,7 +10,11 @@ import styles from './index.module.scss';
|
|||
|
||||
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 { width, fontSize, setWidth, setFontSize } = useDocumentStyle();
|
||||
const [visible, toggleVisible] = useToggle(false);
|
||||
|
@ -30,7 +34,12 @@ export const DocumentStyle = () => {
|
|||
onVisibleChange={toggleVisible}
|
||||
onClickOutSide={toggleVisible}
|
||||
content={
|
||||
<div className={styles.wrap}>
|
||||
<div
|
||||
className={styles.wrap}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div className={styles.item}>
|
||||
<Text>正文大小</Text>
|
||||
<Text style={{ fontSize: '0.8em' }}> {fontSize}px</Text>
|
||||
|
@ -48,7 +57,11 @@ export const DocumentStyle = () => {
|
|||
</div>
|
||||
}
|
||||
>
|
||||
{render ? (
|
||||
render({ onClick: toggleVisible })
|
||||
) : (
|
||||
<Button icon={<IconArticle />} theme="borderless" type="tertiary" onMouseDown={toggleVisible} />
|
||||
)}
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
flex: 1;
|
||||
|
||||
&.isMobile {
|
||||
padding: 0;
|
||||
padding: 0 0 24px;
|
||||
overflow: hidden;
|
||||
|
||||
> div {
|
||||
|
|
|
@ -6,24 +6,26 @@ import { DataRender } from 'components/data-render';
|
|||
import { LocaleTime } from 'components/locale-time';
|
||||
import { useDocumentVersion } from 'data/document';
|
||||
import { safeJSONParse } from 'helpers/json';
|
||||
import { DocumentVersionControl } from 'hooks/use-document-version';
|
||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||
import { useToggle } from 'hooks/use-toggle';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { CollaborationKit } from 'tiptap/editor';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
interface IProps {
|
||||
export interface IProps {
|
||||
documentId: string;
|
||||
disabled?: boolean;
|
||||
onSelect?: (data) => void;
|
||||
render?: (arg: { onClick: (arg?: any) => void; disabled: boolean }) => React.ReactNode;
|
||||
}
|
||||
|
||||
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 [visible, toggleVisible] = useToggle(false);
|
||||
|
||||
const { visible, toggleVisible } = DocumentVersionControl.useHook();
|
||||
const { data, loading, error, refresh } = useDocumentVersion(documentId, { enabled: visible });
|
||||
const [selectedVersion, setSelectedVersion] = useState(null);
|
||||
|
||||
|
@ -61,9 +63,6 @@ export const DocumentVersion: React.FC<IProps> = ({ documentId, disabled = false
|
|||
|
||||
return (
|
||||
<>
|
||||
<Button type="primary" theme="light" disabled={disabled} onClick={toggleVisible}>
|
||||
文档版本
|
||||
</Button>
|
||||
<Modal
|
||||
title="历史记录"
|
||||
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 className={styles.rightWrap}>
|
||||
<DocumentStar documentId={doc.id} />
|
||||
<DocumentStar wikiId={doc.wikiId} documentId={doc.id} />
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Avatar, Skeleton, Space, Typography } from '@douyinfe/semi-ui';
|
|||
import { IconDocument } from 'components/icons/IconDocument';
|
||||
import { LocaleTime } from 'components/locale-time';
|
||||
import { WikiStar } from 'components/wiki/star';
|
||||
import { IWikiWithIsMember } from 'data/collector';
|
||||
import { IWikiWithIsMember } from 'data/star';
|
||||
import Link from 'next/link';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { TabPane, Tabs } from '@douyinfe/semi-ui';
|
||||
import { IWiki } from '@think/domains';
|
||||
import { Seo } from 'components/seo';
|
||||
import { WikiTocsManager } from 'components/wiki/tocs/manager';
|
||||
import { useWikiDetail } from 'data/wiki';
|
||||
import React from 'react';
|
||||
|
||||
|
@ -19,6 +20,9 @@ interface IProps {
|
|||
const TitleMap = {
|
||||
base: '基础信息',
|
||||
privacy: '隐私管理',
|
||||
tocs: '目录管理',
|
||||
share: '隐私管理',
|
||||
documents: '全部文档',
|
||||
users: '成员管理',
|
||||
import: '导入文档',
|
||||
more: '更多',
|
||||
|
@ -34,9 +38,15 @@ export const WikiSetting: React.FC<IProps> = ({ wikiId, tab, onNavigate }) => {
|
|||
<TabPane tab={TitleMap['base']} itemKey="base">
|
||||
<Base wiki={data} update={update as any} />
|
||||
</TabPane>
|
||||
|
||||
<TabPane tab={TitleMap['users']} itemKey="users">
|
||||
<Users wikiId={wikiId} />
|
||||
</TabPane>
|
||||
|
||||
<TabPane tab={TitleMap['tocs']} itemKey="tocs">
|
||||
<WikiTocsManager wikiId={wikiId} />
|
||||
</TabPane>
|
||||
|
||||
<TabPane tab={TitleMap['privacy']} itemKey="privacy">
|
||||
<Privacy wikiId={wikiId} />
|
||||
</TabPane>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
/* stylelint-disable */
|
||||
.wrap {
|
||||
.statusWrap {
|
||||
padding: 10px 12px;
|
||||
margin-top: 16px;
|
||||
|
@ -10,4 +9,56 @@
|
|||
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 }) => {
|
||||
const { data: wiki, toggleStatus: toggleWorkspaceStatus } = useWikiDetail(wikiId);
|
||||
const { data: tocs } = useWikiTocs(wikiId);
|
||||
|
||||
const [nextStatus, setNextStatus] = useState('');
|
||||
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 data = { nextStatus };
|
||||
const data = { nextStatus, publicDocumentIds, privateDocumentIds };
|
||||
|
||||
toggleWorkspaceStatus(data).then((res) => {
|
||||
const ret = res as unknown as any & {
|
||||
documentOperateMessage?: string;
|
||||
|
@ -64,6 +118,7 @@ export const Privacy: React.FC<IProps> = ({ wikiId }) => {
|
|||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className={styles.statusWrap}>
|
||||
<Title className={styles.title} heading={6}>
|
||||
是否公开知识库?
|
||||
|
@ -78,6 +133,25 @@ export const Privacy: React.FC<IProps> = ({ wikiId }) => {
|
|||
})}
|
||||
</RadioGroup>
|
||||
</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>
|
||||
|
|
|
@ -43,7 +43,9 @@ export const Users: React.FC<IProps> = ({ wikiId }) => {
|
|||
normalContent={() => (
|
||||
<div style={{ margin: '24px 0' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Button onClick={toggleVisible}>添加用户</Button>
|
||||
<Button onClick={toggleVisible} theme="solid">
|
||||
添加用户
|
||||
</Button>
|
||||
</div>
|
||||
<Table style={{ margin: '16px 0' }} dataSource={users} size="small" pagination>
|
||||
<Column title="用户名" dataIndex="userName" key="userName" />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { IconStar } from '@douyinfe/semi-icons';
|
||||
import { Button, Tooltip } from '@douyinfe/semi-ui';
|
||||
import { useWikiCollectToggle } from 'data/collector';
|
||||
import { useWikiStarToggle } from 'data/star';
|
||||
import React from 'react';
|
||||
|
||||
interface IProps {
|
||||
|
@ -10,7 +10,7 @@ interface IProps {
|
|||
}
|
||||
|
||||
export const WikiStar: React.FC<IProps> = ({ wikiId, render, onChange }) => {
|
||||
const { data, toggle } = useWikiCollectToggle(wikiId);
|
||||
const { data, toggle } = useWikiStarToggle(wikiId);
|
||||
const text = data ? '取消收藏' : '收藏知识库';
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,12 +1,68 @@
|
|||
/* stylelint-disable */
|
||||
.wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding-top: 12px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.titleWrap {
|
||||
display: flex;
|
||||
padding: 8px 12px;
|
||||
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 {
|
||||
flex: 1;
|
||||
padding: 0 8px 32px 16px;
|
||||
overflow: auto;
|
||||
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;
|
||||
width: calc(100% - 48px);
|
||||
}
|
||||
|
||||
.right {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.right {
|
||||
.hoverVisible {
|
||||
opacity: 0;
|
||||
|
||||
&:hover,
|
||||
&.isActive {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navItemWrap {
|
||||
display: flex;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
font-weight: 600;
|
||||
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 { Avatar, Button, Skeleton, Tooltip, Typography } from '@douyinfe/semi-ui';
|
||||
import { isPublicWiki } from '@think/domains';
|
||||
import { IconPlus, IconSmallTriangleDown } from '@douyinfe/semi-icons';
|
||||
import { Avatar, Button, Dropdown, Skeleton, Typography } from '@douyinfe/semi-ui';
|
||||
import cls from 'classnames';
|
||||
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 { useStarWikis, useWikiStarDocuments } from 'data/star';
|
||||
import { useWikiDetail, useWikiTocs } from 'data/wiki';
|
||||
import { triggerCreateDocument } from 'event';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import styles from './index.module.scss';
|
||||
import { NavItem } from './nav-item';
|
||||
import { Tree } from './tree';
|
||||
|
||||
interface IProps {
|
||||
|
@ -28,9 +29,15 @@ export const WikiTocs: React.FC<IProps> = ({
|
|||
docAsLink = '/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: 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>>([]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -44,8 +51,10 @@ export const WikiTocs: React.FC<IProps> = ({
|
|||
<DataRender
|
||||
loading={wikiLoading}
|
||||
loadingContent={
|
||||
<NavItem
|
||||
icon={
|
||||
<div className={styles.titleWrap}>
|
||||
<Skeleton
|
||||
placeholder={
|
||||
<div style={{ display: 'flex' }}>
|
||||
<Skeleton.Avatar
|
||||
size="small"
|
||||
style={{
|
||||
|
@ -55,14 +64,38 @@ export const WikiTocs: React.FC<IProps> = ({
|
|||
borderRadius: 4,
|
||||
}}
|
||||
></Skeleton.Avatar>
|
||||
<Skeleton.Title style={{ width: 120 }} />
|
||||
</div>
|
||||
}
|
||||
text={<Skeleton.Title style={{ width: 120 }} />}
|
||||
loading={true}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
error={wikiError}
|
||||
normalContent={() => (
|
||||
<NavItem
|
||||
icon={
|
||||
<Dropdown
|
||||
trigger={'click'}
|
||||
position="bottomRight"
|
||||
render={
|
||||
<Dropdown.Menu style={{ width: 180 }}>
|
||||
{(starWikis || [])
|
||||
.filter((wiki) => wiki.id !== wikiId)
|
||||
.map((wiki) => {
|
||||
return (
|
||||
<Dropdown.Item key={wiki.id}>
|
||||
<Link
|
||||
href={{
|
||||
pathname: `/wiki/[wikiId]`,
|
||||
query: { wikiId: wiki.id },
|
||||
}}
|
||||
>
|
||||
<a
|
||||
style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<Avatar
|
||||
shape="square"
|
||||
size="small"
|
||||
|
@ -76,102 +109,160 @@ export const WikiTocs: React.FC<IProps> = ({
|
|||
>
|
||||
{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={'概述'}
|
||||
<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]' && styles.isActive)}>
|
||||
<Link
|
||||
href={{
|
||||
pathname: `/wiki/[wikiId]`,
|
||||
query: { wikiId },
|
||||
}}
|
||||
isActive={pathname === '/wiki/[wikiId]' || (query && wiki && query.documentId === wiki.homeDocumentId)}
|
||||
>
|
||||
<a>
|
||||
<IconOverview style={{ fontSize: '1em' }} />
|
||||
<span>主页</span>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<NavItem
|
||||
icon={<IconSetting />}
|
||||
text={'设置'}
|
||||
<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 },
|
||||
}}
|
||||
isActive={pathname === '/wiki/[wikiId]/setting'}
|
||||
>
|
||||
<a>
|
||||
<IconSetting style={{ fontSize: '1em' }} />
|
||||
<span>设置</span>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className={styles.treeWrap}>
|
||||
<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 }} />}
|
||||
loading={starDocumentsLoading}
|
||||
loadingContent={<Skeleton.Title style={{ width: '100%' }} />}
|
||||
error={starDocumentsError}
|
||||
normalContent={() => (
|
||||
<div className={styles.title}>
|
||||
<Text type="tertiary" size="small">
|
||||
已加星标
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
error={wikiError}
|
||||
normalContent={() =>
|
||||
isPublicWiki(wiki.status) ? (
|
||||
<NavItem
|
||||
icon={<IconGlobe />}
|
||||
text={
|
||||
<Tooltip content="该知识库已公开,点我查看" position="right">
|
||||
公开地址
|
||||
</Tooltip>
|
||||
}
|
||||
href={{
|
||||
pathname: `/share/wiki/[wikiId]`,
|
||||
query: { wikiId },
|
||||
}}
|
||||
isActive={pathname === '/share/wiki/[wikiId]'}
|
||||
openNewTab
|
||||
<DataRender
|
||||
loading={starDocumentsLoading}
|
||||
loadingContent={<div>1</div>}
|
||||
error={starDocumentsError}
|
||||
normalContent={() => (
|
||||
<Tree
|
||||
data={starDocuments || []}
|
||||
docAsLink={docAsLink}
|
||||
getDocLink={getDocLink}
|
||||
parentIds={parentIds}
|
||||
activeId={documentId}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.treeWrap}>
|
||||
<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 />}
|
||||
/>
|
||||
}
|
||||
loading={tocsLoading}
|
||||
loadingContent={<Skeleton.Title style={{ width: '100%' }} />}
|
||||
error={wikiError}
|
||||
normalContent={() => (
|
||||
<NavItem
|
||||
icon={<IconDocument />}
|
||||
text={'文档管理'}
|
||||
href={{
|
||||
pathname: `/wiki/[wikiId]/documents`,
|
||||
query: { wikiId },
|
||||
}}
|
||||
isActive={pathname === '/wiki/[wikiId]/documents'}
|
||||
rightNode={
|
||||
<div className={styles.title}>
|
||||
<Text type="tertiary" size="small">
|
||||
文档集
|
||||
</Text>
|
||||
<Button
|
||||
style={{ fontSize: '1em' }}
|
||||
theme="borderless"
|
||||
|
@ -182,18 +273,16 @@ export const WikiTocs: React.FC<IProps> = ({
|
|||
triggerCreateDocument({ wikiId: wiki.id, documentId: null });
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className={styles.treeWrap}>
|
||||
<DataRender
|
||||
loading={tocsLoading}
|
||||
loadingContent={<NavItem icon={null} text={<Skeleton.Title style={{ width: '100%' }} />} />}
|
||||
loadingContent={<div>1</div>}
|
||||
error={tocsError}
|
||||
normalContent={() => (
|
||||
<Tree
|
||||
needAddDocument
|
||||
data={tocs || []}
|
||||
docAsLink={docAsLink}
|
||||
getDocLink={getDocLink}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
.wrap {
|
||||
margin-top: 5px;
|
||||
margin-top: 16px;
|
||||
|
||||
.tocsWrap {
|
||||
height: calc(100vh - 268px);
|
||||
height: calc(100vh - 279px);
|
||||
overflow: auto;
|
||||
border: 1px solid var(--semi-color-border);
|
||||
border-radius: var(--border-radius);
|
||||
|
|
|
@ -133,8 +133,8 @@ export const WikiTocsManager: React.FC<IProps> = ({ wikiId }) => {
|
|||
/>
|
||||
</div>
|
||||
<div className={styles.btnWrap}>
|
||||
<Button disabled={!changed} onClick={submit}>
|
||||
提交
|
||||
<Button disabled={!changed} onClick={submit} theme="solid">
|
||||
保存
|
||||
</Button>
|
||||
</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 { DocumentActions } from 'components/document/actions';
|
||||
import { DocumentCreator as DocumenCreatorForm } from 'components/document/create';
|
||||
|
@ -13,18 +13,17 @@ import styles from './index.module.scss';
|
|||
const Actions = ({ node }) => {
|
||||
return (
|
||||
<span className={styles.right}>
|
||||
<DocumentActions wikiId={node.wikiId} documentId={node.id}>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
icon={<IconMore />}
|
||||
<DocumentActions
|
||||
key={node.id}
|
||||
hoverVisible
|
||||
wikiId={node.wikiId}
|
||||
documentId={node.id}
|
||||
size="small"
|
||||
/>
|
||||
</DocumentActions>
|
||||
hideDocumentVersion
|
||||
hideDocumentStyle
|
||||
></DocumentActions>
|
||||
<Button
|
||||
className={styles.hoverVisible}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
triggerCreateDocument({ wikiId: node.wikiId, documentId: node.id });
|
||||
|
@ -67,7 +66,15 @@ const AddDocument = () => {
|
|||
|
||||
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 [expandedKeys, setExpandedKeys] = useState(parentIds);
|
||||
|
||||
|
@ -100,15 +107,13 @@ export const Tree = ({ data, docAsLink, getDocLink, parentIds, activeId, isShare
|
|||
}, [parentIds]);
|
||||
|
||||
useEffect(() => {
|
||||
clearTimeout(scrollTimer);
|
||||
scrollTimer = setTimeout(() => {
|
||||
const target = $container.current.querySelector(`#item-${activeId}`);
|
||||
if (!target) return;
|
||||
target.scrollIntoView();
|
||||
clearTimeout(scrollTimer);
|
||||
scrollTimer = setTimeout(() => {
|
||||
scrollIntoView(target, {
|
||||
behavior: 'smooth',
|
||||
scrollMode: 'if-needed',
|
||||
block: 'center',
|
||||
});
|
||||
}, 500);
|
||||
|
||||
|
@ -127,7 +132,7 @@ export const Tree = ({ data, docAsLink, getDocLink, parentIds, activeId, isShare
|
|||
expandedKeys={expandedKeys}
|
||||
onExpand={(expandedKeys) => setExpandedKeys(expandedKeys)}
|
||||
/>
|
||||
<AddDocument />
|
||||
{needAddDocument && <AddDocument />}
|
||||
</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 CREATE_DOCUMENT = `CREATE_DOCUMENT`;
|
||||
export const TOGGLE_COLLECT_WIKI = `TOGGLE_COLLECT_WIKI`; // 收藏或取消收藏知识库
|
||||
export const TOGGLE_COLLECT_DOUCMENT = `TOGGLE_COLLECT_DOUCMENT`; // 收藏或取消收藏文档
|
||||
export const TOGGLE_STAR_WIKI = `TOGGLE_STAR_WIKI`; // 收藏或取消收藏知识库
|
||||
export const TOGGLE_STAR_DOUCMENT = `TOGGLE_STAR_DOUCMENT`; // 收藏或取消收藏文档
|
||||
|
||||
/**
|
||||
* 刷新知识库目录
|
||||
|
@ -53,10 +53,10 @@ export const triggerJoinUser = (users: Array<CollaborationUser>) => {
|
|||
event.emit(JOIN_USER, users);
|
||||
};
|
||||
|
||||
export const triggerToggleCollectWiki = () => {
|
||||
event.emit(TOGGLE_COLLECT_WIKI);
|
||||
export const triggerToggleStarWiki = () => {
|
||||
event.emit(TOGGLE_STAR_WIKI);
|
||||
};
|
||||
|
||||
export const triggerToggleCollectDocument = () => {
|
||||
event.emit(TOGGLE_COLLECT_DOUCMENT);
|
||||
export const triggerToggleStarDocument = () => {
|
||||
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 className={styles.rightWrap}>
|
||||
<DocumentStar documentId={doc.id} />
|
||||
<DocumentStar wikiId={doc.wikiId} documentId={doc.id} />
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Avatar, Dropdown, Modal, Space, Typography } from '@douyinfe/semi-ui';
|
|||
import { DataRender } from 'components/data-render';
|
||||
import { Empty } from 'components/empty';
|
||||
import { WikiStar } from 'components/wiki/star';
|
||||
import { useCollectedWikis } from 'data/collector';
|
||||
import { useStarWikis } from 'data/star';
|
||||
import { useWikiDetail } from 'data/wiki';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
|
@ -16,7 +16,7 @@ const { Text } = Typography;
|
|||
|
||||
const WikiContent = () => {
|
||||
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);
|
||||
|
||||
return (
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'styles/globals.scss';
|
|||
import 'tiptap/core/styles/index.scss';
|
||||
|
||||
import { isMobile } from 'helpers/env';
|
||||
import { DocumentVersionControl } from 'hooks/use-document-version';
|
||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||
import { Theme } from 'hooks/use-theme';
|
||||
import App from 'next/app';
|
||||
|
@ -87,7 +88,9 @@ class MyApp extends App<{ isMobile: boolean }> {
|
|||
<Hydrate state={pageProps.dehydratedState}>
|
||||
<Theme.Provider>
|
||||
<IsOnMobile.Provider initialState={isMobile}>
|
||||
<DocumentVersionControl.Provider initialState={false}>
|
||||
<Component {...pageProps} />
|
||||
</DocumentVersionControl.Provider>
|
||||
</IsOnMobile.Provider>
|
||||
</Theme.Provider>
|
||||
</Hydrate>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 { DocumentActions } from 'components/document/actions';
|
||||
import { Empty } from 'components/empty';
|
||||
|
@ -7,8 +7,8 @@ import { LocaleTime } from 'components/locale-time';
|
|||
import { Seo } from 'components/seo';
|
||||
import { WikiCreator } from 'components/wiki/create';
|
||||
import { WikiPinCard, WikiPinCardPlaceholder } from 'components/wiki/pin-card';
|
||||
import { getCollectedWikis, useCollectedWikis } from 'data/collector';
|
||||
import { getRecentVisitedDocuments, useRecentDocuments } from 'data/document';
|
||||
import { getStarWikis, useStarWikis } from 'data/star';
|
||||
import { useToggle } from 'hooks/use-toggle';
|
||||
import { SingleColumnLayout } from 'layouts/single-column';
|
||||
import type { NextPage } from 'next';
|
||||
|
@ -78,7 +78,14 @@ const RecentDocs = () => {
|
|||
key="operate"
|
||||
width={80}
|
||||
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 [visible, toggleVisible] = useToggle(false);
|
||||
const { data: staredWikis, loading, error, refresh } = useCollectedWikis();
|
||||
const { data: staredWikis, loading, error, refresh } = useStarWikis();
|
||||
|
||||
return (
|
||||
<SingleColumnLayout>
|
||||
|
@ -168,7 +175,7 @@ const Page: NextPage = () => {
|
|||
|
||||
Page.getInitialProps = async (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) },
|
||||
]);
|
||||
return props;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { List, Typography } from '@douyinfe/semi-ui';
|
||||
import { CollectorApiDefinition } from '@think/domains';
|
||||
import { StarApiDefinition } from '@think/domains';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { DocumentCard, DocumentCardPlaceholder } from 'components/document/card';
|
||||
import { Empty } from 'components/empty';
|
||||
import { Seo } from 'components/seo';
|
||||
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 type { NextPage } from 'next';
|
||||
import React from 'react';
|
||||
|
@ -25,7 +25,7 @@ const grid = {
|
|||
};
|
||||
|
||||
const StarDocs = () => {
|
||||
const { data: docs, loading, error } = useCollectedDocuments();
|
||||
const { data: docs, loading, error } = useStarDocuments();
|
||||
|
||||
return (
|
||||
<DataRender
|
||||
|
@ -59,7 +59,7 @@ const StarDocs = () => {
|
|||
};
|
||||
|
||||
const StarWikis = () => {
|
||||
const { data, loading, error } = useCollectedWikis();
|
||||
const { data, loading, error } = useStarWikis();
|
||||
|
||||
return (
|
||||
<DataRender
|
||||
|
@ -117,8 +117,8 @@ const Page: NextPage = () => {
|
|||
|
||||
Page.getInitialProps = async (ctx) => {
|
||||
const props = await serverPrefetcher(ctx, [
|
||||
{ url: CollectorApiDefinition.wikis.client(), action: (cookie) => getCollectedWikis(cookie) },
|
||||
{ url: CollectorApiDefinition.documents.client(), action: (cookie) => getCollectedDocuments(cookie) },
|
||||
{ url: StarApiDefinition.wikis.client(), action: (cookie) => getStarWikis(cookie) },
|
||||
{ url: StarApiDefinition.documents.client(), action: (cookie) => getStarDocuments(cookie) },
|
||||
]);
|
||||
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,12 +56,11 @@ export class Awareness extends Observable {
|
|||
* @type {Map<number, MetaClientState>}
|
||||
*/
|
||||
this.meta = new Map();
|
||||
this._checkInterval = /** @type {any} */ (
|
||||
setInterval(() => {
|
||||
this._checkInterval = /** @type {any} */ setInterval(() => {
|
||||
const now = time.getUnixTime();
|
||||
if (
|
||||
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
|
||||
this.setLocalState(this.getLocalState());
|
||||
|
@ -78,8 +77,7 @@ export class Awareness extends Observable {
|
|||
if (remove.length > 0) {
|
||||
removeAwarenessStates(this, remove, 'timeout');
|
||||
}
|
||||
}, math.floor(outdatedTimeout / 10))
|
||||
);
|
||||
}, math.floor(outdatedTimeout / 10));
|
||||
doc.on('destroy', () => {
|
||||
this.destroy();
|
||||
});
|
||||
|
@ -176,7 +174,7 @@ export const removeAwarenessStates = (awareness, clients, origin) => {
|
|||
if (awareness.states.has(clientID)) {
|
||||
awareness.states.delete(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, {
|
||||
clock: curMeta.clock + 1,
|
||||
lastUpdated: time.getUnixTime(),
|
||||
|
@ -203,7 +201,7 @@ export const encodeAwarenessUpdate = (awareness, clients, states = awareness.sta
|
|||
for (let i = 0; i < len; i++) {
|
||||
const clientID = clients[i];
|
||||
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, clock);
|
||||
encoding.writeVarString(encoder, JSON.stringify(state));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.wrap {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
margin-top: 24px;
|
||||
overflow: auto;
|
||||
|
||||
.coverWrap {
|
||||
position: relative;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Spin, Typography } from '@douyinfe/semi-ui';
|
||||
import { HocuspocusProvider } from '@hocuspocus/provider';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import deepEqual from 'deep-equal';
|
||||
import { throttle } from 'helpers/throttle';
|
||||
import { useToggle } from 'hooks/use-toggle';
|
||||
import { SecureDocumentIllustration } from 'illustrations/secure-document';
|
||||
|
@ -34,6 +35,7 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps,
|
|||
const [loading, toggleLoading] = useToggle(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [status, setStatus] = useState<ProviderStatus>('connecting');
|
||||
const lastAwarenessRef = useRef([]);
|
||||
|
||||
const hocuspocusProvider = useMemo(() => {
|
||||
return new HocuspocusProvider({
|
||||
|
@ -48,7 +50,11 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps,
|
|||
maxAttempts: 1,
|
||||
onAwarenessUpdate: throttle(({ states }) => {
|
||||
const users = states.map((state) => ({ clientId: state.clientId, user: state.user }));
|
||||
if (deepEqual(user, lastAwarenessRef.current)) {
|
||||
return;
|
||||
}
|
||||
onAwarenessUpdate && onAwarenessUpdate(users);
|
||||
lastAwarenessRef.current = users;
|
||||
}, 200),
|
||||
onAuthenticationFailed() {
|
||||
toggleLoading(false);
|
||||
|
|
|
@ -8,7 +8,7 @@ export declare const FileApiDefinition: {
|
|||
client: () => string;
|
||||
};
|
||||
/**
|
||||
* 上传分块文件
|
||||
* 初始分块上传
|
||||
*/
|
||||
initChunk: {
|
||||
method: "post";
|
||||
|
|
|
@ -11,7 +11,7 @@ exports.FileApiDefinition = {
|
|||
client: function () { return '/file/upload'; }
|
||||
},
|
||||
/**
|
||||
* 上传分块文件
|
||||
* 初始分块上传
|
||||
*/
|
||||
initChunk: {
|
||||
method: 'post',
|
||||
|
|
|
@ -5,4 +5,4 @@ export * from './file';
|
|||
export * from './message';
|
||||
export * from './template';
|
||||
export * from './comment';
|
||||
export * from './collector';
|
||||
export * from './star';
|
||||
|
|
|
@ -17,4 +17,4 @@ __exportStar(require("./file"), exports);
|
|||
__exportStar(require("./message"), exports);
|
||||
__exportStar(require("./template"), 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 './template';
|
||||
export * from './comment';
|
||||
export * from './collector';
|
||||
export * from './pagination';
|
||||
|
|
|
@ -16,5 +16,4 @@ __exportStar(require("./document"), exports);
|
|||
__exportStar(require("./message"), exports);
|
||||
__exportStar(require("./template"), exports);
|
||||
__exportStar(require("./comment"), exports);
|
||||
__exportStar(require("./collector"), exports);
|
||||
__exportStar(require("./pagination"), exports);
|
||||
|
|
|
@ -5,4 +5,4 @@ export * from './file';
|
|||
export * from './message';
|
||||
export * from './template';
|
||||
export * from './comment';
|
||||
export * from './collector';
|
||||
export * from './star';
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import { IDocument, IWiki, CollectType } from '../models';
|
||||
|
||||
export const CollectorApiDefinition = {
|
||||
export const StarApiDefinition = {
|
||||
/**
|
||||
* 收藏(或取消收藏)
|
||||
*/
|
||||
toggle: {
|
||||
method: 'post' as const,
|
||||
server: 'toggle' as const,
|
||||
client: () => '/collector/toggle',
|
||||
client: () => '/star/toggle',
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -16,7 +14,7 @@ export const CollectorApiDefinition = {
|
|||
check: {
|
||||
method: 'post' as const,
|
||||
server: 'check' as const,
|
||||
client: () => '/collector/check',
|
||||
client: () => '/star/check',
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -25,7 +23,16 @@ export const CollectorApiDefinition = {
|
|||
wikis: {
|
||||
method: 'get' 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: {
|
||||
method: 'get' 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 './template';
|
||||
export * from './comment';
|
||||
export * from './collector';
|
||||
export * from './pagination';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { CollectorEntity } from '@entities/collector.entity';
|
||||
import { CommentEntity } from '@entities/comment.entity';
|
||||
import { DocumentEntity } from '@entities/document.entity';
|
||||
import { DocumentAuthorityEntity } from '@entities/document-authority.entity';
|
||||
import { MessageEntity } from '@entities/message.entity';
|
||||
import { StarEntity } from '@entities/star.entity';
|
||||
import { TemplateEntity } from '@entities/template.entity';
|
||||
import { UserEntity } from '@entities/user.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 { IS_PRODUCTION } from '@helpers/env.helper';
|
||||
import { getLogFileName, ONE_DAY } from '@helpers/log.helper';
|
||||
import { CollectorModule } from '@modules/collector.module';
|
||||
import { CommentModule } from '@modules/comment.module';
|
||||
import { DocumentModule } from '@modules/document.module';
|
||||
import { FileModule } from '@modules/file.module';
|
||||
import { MessageModule } from '@modules/message.module';
|
||||
import { StarModule } from '@modules/star.module';
|
||||
import { TemplateModule } from '@modules/template.module';
|
||||
import { UserModule } from '@modules/user.module';
|
||||
import { ViewModule } from '@modules/view.module';
|
||||
|
@ -35,7 +35,7 @@ const ENTITIES = [
|
|||
WikiUserEntity,
|
||||
DocumentAuthorityEntity,
|
||||
DocumentEntity,
|
||||
CollectorEntity,
|
||||
StarEntity,
|
||||
CommentEntity,
|
||||
MessageEntity,
|
||||
TemplateEntity,
|
||||
|
@ -46,7 +46,7 @@ const MODULES = [
|
|||
UserModule,
|
||||
WikiModule,
|
||||
DocumentModule,
|
||||
CollectorModule,
|
||||
StarModule,
|
||||
FileModule,
|
||||
CommentModule,
|
||||
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';
|
||||
|
||||
@Entity('collector')
|
||||
export class CollectorEntity {
|
||||
@Entity('star')
|
||||
export class StarEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
public id: string;
|
||||
|
||||
@Column({ type: 'varchar', comment: '用户 Id' })
|
||||
public userId: string;
|
||||
|
||||
@Column({ type: 'varchar', comment: '收藏目标 Id' })
|
||||
public targetId: string;
|
||||
@Column({ type: 'varchar', comment: '知识库 Id' })
|
||||
public wikiId: string;
|
||||
|
||||
@Column({
|
||||
type: 'enum',
|
||||
enum: CollectType,
|
||||
default: CollectType.document,
|
||||
comment: '收藏目标类型',
|
||||
})
|
||||
public type: CollectType;
|
||||
@Column({ type: 'varchar', comment: '文档 Id', default: null })
|
||||
public documentId: string;
|
||||
|
||||
@CreateDateColumn({
|
||||
type: 'timestamp',
|
|
@ -1,5 +1,4 @@
|
|||
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 { ConfigService } from '@nestjs/config';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
|
@ -13,7 +12,6 @@ import rateLimit from 'express-rate-limit';
|
|||
import helmet from 'helmet';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
import { AppClusterService } from './app-cluster.service';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { DocumentController } from '@controllers/document.controller';
|
||||
import { DocumentEntity } from '@entities/document.entity';
|
||||
import { DocumentAuthorityEntity } from '@entities/document-authority.entity';
|
||||
import { CollectorModule } from '@modules/collector.module';
|
||||
import { MessageModule } from '@modules/message.module';
|
||||
import { StarModule } from '@modules/star.module';
|
||||
import { TemplateModule } from '@modules/template.module';
|
||||
import { UserModule } from '@modules/user.module';
|
||||
import { ViewModule } from '@modules/view.module';
|
||||
|
@ -20,7 +20,7 @@ import { DocumentService } from '@services/document.service';
|
|||
forwardRef(() => WikiModule),
|
||||
forwardRef(() => MessageModule),
|
||||
forwardRef(() => TemplateModule),
|
||||
forwardRef(() => CollectorModule),
|
||||
forwardRef(() => StarModule),
|
||||
forwardRef(() => ViewModule),
|
||||
],
|
||||
providers: [DocumentService],
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import { CollectorController } from '@controllers/collector.controller';
|
||||
import { CollectorEntity } from '@entities/collector.entity';
|
||||
import { StarController } from '@controllers/star.controller';
|
||||
import { StarEntity } from '@entities/star.entity';
|
||||
import { DocumentModule } from '@modules/document.module';
|
||||
import { UserModule } from '@modules/user.module';
|
||||
import { WikiModule } from '@modules/wiki.module';
|
||||
import { forwardRef, Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { CollectorService } from '@services/collector.service';
|
||||
import { StarService } from '@services/star.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([CollectorEntity]),
|
||||
TypeOrmModule.forFeature([StarEntity]),
|
||||
forwardRef(() => UserModule),
|
||||
forwardRef(() => WikiModule),
|
||||
forwardRef(() => DocumentModule),
|
||||
],
|
||||
providers: [CollectorService],
|
||||
exports: [CollectorService],
|
||||
controllers: [CollectorController],
|
||||
providers: [StarService],
|
||||
exports: [StarService],
|
||||
controllers: [StarController],
|
||||
})
|
||||
export class CollectorModule {}
|
||||
export class StarModule {}
|
|
@ -1,7 +1,7 @@
|
|||
import { UserController } from '@controllers/user.controller';
|
||||
import { UserEntity } from '@entities/user.entity';
|
||||
import { CollectorModule } from '@modules/collector.module';
|
||||
import { MessageModule } from '@modules/message.module';
|
||||
import { StarModule } from '@modules/star.module';
|
||||
import { WikiModule } from '@modules/wiki.module';
|
||||
import { forwardRef, Inject, Injectable, Module, UnauthorizedException } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
@ -60,7 +60,7 @@ const jwtModule = JwtModule.register({
|
|||
ConfigModule,
|
||||
forwardRef(() => WikiModule),
|
||||
forwardRef(() => MessageModule),
|
||||
forwardRef(() => CollectorModule),
|
||||
forwardRef(() => StarModule),
|
||||
passModule,
|
||||
jwtModule,
|
||||
],
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { WikiController } from '@controllers/wiki.controller';
|
||||
import { WikiEntity } from '@entities/wiki.entity';
|
||||
import { WikiUserEntity } from '@entities/wiki-user.entity';
|
||||
import { CollectorModule } from '@modules/collector.module';
|
||||
import { DocumentModule } from '@modules/document.module';
|
||||
import { MessageModule } from '@modules/message.module';
|
||||
import { StarModule } from '@modules/star.module';
|
||||
import { UserModule } from '@modules/user.module';
|
||||
import { ViewModule } from '@modules/view.module';
|
||||
import { forwardRef, Module } from '@nestjs/common';
|
||||
|
@ -17,7 +17,7 @@ import { WikiService } from '@services/wiki.service';
|
|||
forwardRef(() => DocumentModule),
|
||||
forwardRef(() => MessageModule),
|
||||
forwardRef(() => ViewModule),
|
||||
forwardRef(() => CollectorModule),
|
||||
forwardRef(() => StarModule),
|
||||
],
|
||||
providers: [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 { JwtService } from '@nestjs/jwt';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { CollectorService } from '@services/collector.service';
|
||||
import { MessageService } from '@services/message.service';
|
||||
import { StarService } from '@services/star.service';
|
||||
import { WikiService } from '@services/wiki.service';
|
||||
import { CollectType, UserStatus } from '@think/domains';
|
||||
import { UserStatus } from '@think/domains';
|
||||
import { instanceToPlain } from 'class-transformer';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
|
@ -29,8 +29,8 @@ export class UserService {
|
|||
@Inject(forwardRef(() => MessageService))
|
||||
private readonly messageService: MessageService,
|
||||
|
||||
@Inject(forwardRef(() => CollectorService))
|
||||
private readonly collectorService: CollectorService,
|
||||
@Inject(forwardRef(() => StarService))
|
||||
private readonly starService: StarService,
|
||||
|
||||
@Inject(forwardRef(() => WikiService))
|
||||
private readonly wikiService: WikiService
|
||||
|
@ -94,9 +94,8 @@ export class UserService {
|
|||
name: createdUser.name,
|
||||
description: `${createdUser.name}的个人空间`,
|
||||
});
|
||||
await this.collectorService.toggleStar(createdUser, {
|
||||
targetId: wiki.id,
|
||||
type: CollectType.wiki,
|
||||
await this.starService.toggleStar(createdUser, {
|
||||
wikiId: wiki.id,
|
||||
});
|
||||
await this.messageService.notify(createdUser, {
|
||||
title: `欢迎「${createdUser.name}」`,
|
||||
|
|
|
@ -7,13 +7,13 @@ import { WikiUserEntity } from '@entities/wiki-user.entity';
|
|||
import { array2tree } from '@helpers/tree.helper';
|
||||
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { CollectorService } from '@services/collector.service';
|
||||
import { DocumentService } from '@services/document.service';
|
||||
import { MessageService } from '@services/message.service';
|
||||
import { StarService } from '@services/star.service';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { OutUser } from '@services/user.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 * as lodash from 'lodash';
|
||||
import { Repository } from 'typeorm';
|
||||
|
@ -30,8 +30,8 @@ export class WikiService {
|
|||
@Inject(forwardRef(() => MessageService))
|
||||
private readonly messageService: MessageService,
|
||||
|
||||
@Inject(forwardRef(() => CollectorService))
|
||||
private readonly collectorService: CollectorService,
|
||||
@Inject(forwardRef(() => StarService))
|
||||
private readonly starService: StarService,
|
||||
|
||||
@Inject(forwardRef(() => DocumentService))
|
||||
private readonly documentService: DocumentService,
|
||||
|
@ -320,7 +320,7 @@ export class WikiService {
|
|||
},
|
||||
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 withHomeDocumentIdWiki = await this.wikiRepo.merge(wiki, { homeDocumentId });
|
||||
|
|
Loading…
Reference in New Issue