refactor: support org

pull/113/head
fantasticit 2022-06-30 00:03:02 +08:00
parent 1b4b33458b
commit 3e0eb5dc92
155 changed files with 5082 additions and 1753 deletions

View File

@ -1,7 +1,7 @@
import { IconArticle, IconBranch, IconExport, 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 { IDocument } from '@think/domains';
import { IDocument, IOrganization, IWiki } from '@think/domains';
import cls from 'classnames';
import { DocumentCreator } from 'components/document/create';
import { DocumentDeletor } from 'components/document/delete';
@ -17,8 +17,9 @@ import React, { useCallback } from 'react';
import styles from './index.module.scss';
interface IProps {
wikiId: string;
documentId: string;
organizationId: IOrganization['id'];
wikiId: IWiki['id'];
documentId: IDocument['id'];
document?: IDocument;
hoverVisible?: boolean;
onStar?: () => void;
@ -34,6 +35,7 @@ interface IProps {
const { Text } = Typography;
export const DocumentActions: React.FC<IProps> = ({
organizationId,
wikiId,
documentId,
hoverVisible,
@ -107,6 +109,7 @@ export const DocumentActions: React.FC<IProps> = ({
/>
<DocumentStar
organizationId={organizationId}
wikiId={wikiId}
documentId={documentId}
render={({ star, toggleStar, text }) => (

View File

@ -22,8 +22,8 @@ export const DocumentCard: React.FC<{ document: IDocument }> = ({ document }) =>
<div className={styles.cardWrap}>
<Link
href={{
pathname: `/wiki/[wikiId]/document/[documentId]`,
query: { wikiId: document.wikiId, documentId: document.id },
pathname: `/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]`,
query: { organizationId: document.organizationId, wikiId: document.wikiId, documentId: document.id },
}}
>
<a>
@ -40,7 +40,11 @@ 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 wikiId={document.wikiId} documentId={document.id} />
<DocumentStar
organizationId={document.organizationId}
wikiId={document.wikiId}
documentId={document.id}
/>
</Space>
</div>
</header>

View File

@ -1,29 +1,12 @@
import { IconDelete, IconUserAdd } from '@douyinfe/semi-icons';
import {
Avatar,
AvatarGroup,
Button,
Checkbox,
Dropdown,
Input,
Modal,
Popconfirm,
Spin,
Table,
TabPane,
Tabs,
Toast,
Tooltip,
Typography,
} from '@douyinfe/semi-ui';
import { DataRender } from 'components/data-render';
import { DocumentLinkCopyer } from 'components/document/link';
import { IconUserAdd } from '@douyinfe/semi-icons';
import { Avatar, AvatarGroup, Button, Dropdown, Modal, Toast, Tooltip } from '@douyinfe/semi-ui';
import { Members } from 'components/members';
import { useDoumentMembers } from 'data/document';
import { useUser } from 'data/user';
import { event, JOIN_USER } from 'event';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useToggle } from 'hooks/use-toggle';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
interface IProps {
wikiId: string;
@ -31,111 +14,13 @@ interface IProps {
disabled?: boolean;
}
const { Paragraph } = Typography;
const { Column } = Table;
// eslint-disable-next-line react/display-name
const renderChecked = (onChange, authKey: 'readable' | 'editable') => (checked, docAuth) => {
const handle = (evt) => {
const data = {
...docAuth.auth,
userName: docAuth.user.name,
};
data[authKey] = evt.target.checked;
onChange(data);
};
return <Checkbox style={{ display: 'inline-block' }} checked={checked} onChange={handle} />;
};
export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, disabled = false }) => {
const { isMobile } = IsOnMobile.useHook();
const ref = useRef<HTMLInputElement>();
const toastedUsersRef = useRef([]);
const { user: currentUser } = useUser();
const [visible, toggleVisible] = useToggle(false);
const { users, loading, error, addUser, updateUser, deleteUser } = useDoumentMembers(documentId, {
enabled: visible,
});
const [inviteUser, setInviteUser] = useState('');
const [collaborationUsers, setCollaborationUsers] = useState([]);
const handleOk = useCallback(() => {
addUser(inviteUser).then(() => {
Toast.success('添加成功');
setInviteUser('');
});
}, [addUser, inviteUser]);
const handleDelete = useCallback(
(docAuth) => {
const data = {
...docAuth.auth,
userName: docAuth.user.name,
};
deleteUser(data);
},
[deleteUser]
);
const content = useMemo(
() => (
<Tabs type="line">
<TabPane tab="添加成员" itemKey="add">
<div style={{ marginTop: 16 }}>
<Input ref={ref} placeholder="输入对方用户名" value={inviteUser} onChange={setInviteUser}></Input>
<Paragraph style={{ marginTop: 16 }}>
<span style={{ verticalAlign: 'middle' }}>
<DocumentLinkCopyer wikiId={wikiId} documentId={documentId} />
</span>
</Paragraph>
<Button theme="solid" block style={{ margin: '24px 0' }} disabled={!inviteUser} onClick={handleOk}>
</Button>
</div>
</TabPane>
<TabPane tab="协作成员" itemKey="list">
<DataRender
loading={loading}
error={error}
loadingContent={<Spin />}
normalContent={() => (
<Table dataSource={users} size="small" pagination>
<Column title="用户名" dataIndex="user.name" key="name" />
<Column
title="是否可读"
dataIndex="auth.readable"
key="readable"
render={renderChecked(updateUser, 'readable')}
align="center"
/>
<Column
title="是否可编辑"
dataIndex="auth.editable"
key="editable"
render={renderChecked(updateUser, 'editable')}
align="center"
/>
<Column
title="操作"
dataIndex="operate"
key="operate"
render={(_, document) => (
<Popconfirm showArrow title="确认删除该成员?" onConfirm={() => handleDelete(document)}>
<Button type="tertiary" theme="borderless" icon={<IconDelete />} />
</Popconfirm>
)}
/>
</Table>
)}
/>
</TabPane>
</Tabs>
),
[documentId, error, handleDelete, handleOk, inviteUser, loading, updateUser, users, wikiId]
);
const content = useMemo(() => <Members id={documentId} hook={useDoumentMembers} />, [documentId]);
const btn = useMemo(
() => (
<Button theme="borderless" type="tertiary" disabled={disabled} icon={<IconUserAdd />} onClick={toggleVisible} />
@ -143,12 +28,6 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
[disabled, toggleVisible]
);
useEffect(() => {
if (visible) {
setTimeout(() => ref.current?.focus(), 100);
}
}, [visible]);
useEffect(() => {
const handler = (users) => {
const joinUsers = users
@ -179,13 +58,6 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, di
};
}, [currentUser]);
if (error)
return (
<Tooltip content="邀请他人协作" position="bottom">
<Button theme="borderless" type="tertiary" icon={<IconUserAdd />}></Button>
</Tooltip>
);
return (
<>
<AvatarGroup maxCount={5} size="extra-small">

View File

@ -1,28 +1,32 @@
import { Checkbox, Modal, TabPane, Tabs } from '@douyinfe/semi-ui';
import { IDocument, IWiki } from '@think/domains';
import { TemplateCardEmpty } from 'components/template/card';
import { TemplateList } from 'components/template/list';
import { useCreateDocument } from 'data/document';
import { useOwnTemplates, usePublicTemplates } from 'data/template';
import { useRouterQuery } from 'hooks/use-router-query';
import Router from 'next/router';
import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react';
import styles from './index.module.scss';
interface IProps {
wikiId: string;
parentDocumentId?: string;
wikiId: IWiki['id'];
parentDocumentId?: IDocument['id'];
visible: boolean;
toggleVisible: Dispatch<SetStateAction<boolean>>;
onCreate?: () => void;
}
export const DocumentCreator: React.FC<IProps> = ({ wikiId, parentDocumentId, visible, toggleVisible, onCreate }) => {
export const DocumentCreator: React.FC<IProps> = ({ parentDocumentId, wikiId, visible, toggleVisible, onCreate }) => {
const { organizationId } = useRouterQuery<{ organizationId?: string }>();
const { loading, create } = useCreateDocument();
const [createChildDoc, setCreateChildDoc] = useState(false);
const [templateId, setTemplateId] = useState('');
const handleOk = () => {
const data = {
organizationId,
wikiId,
parentDocumentId: createChildDoc ? parentDocumentId : null,
templateId,
@ -32,7 +36,8 @@ export const DocumentCreator: React.FC<IProps> = ({ wikiId, parentDocumentId, vi
onCreate && onCreate();
setTemplateId('');
Router.push({
pathname: `/wiki/${wikiId}/document/${res.id}/edit`,
pathname: `/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]/edit`,
query: { organizationId, wikiId, documentId: res.id },
});
});
};

View File

@ -38,11 +38,12 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
const goback = useCallback(() => {
Router.push({
pathname: `/wiki/${document.wikiId}/document/${documentId}`,
pathname: `/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]`,
query: { organizationId: document.organizationId, wikiId: document.wikiId, documentId: document.id },
}).then(() => {
triggerRefreshTocs();
});
}, [document, documentId]);
}, [document]);
const actions = useMemo(
() => (
@ -50,8 +51,17 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
{document && authority.readable && (
<DocumentCollaboration key={documentId} wikiId={document.wikiId} documentId={documentId} />
)}
{document && <DocumentStar key="star" wikiId={document.wikiId} documentId={documentId} />}
{document && <DocumentActions wikiId={document.wikiId} documentId={documentId} />}
{document && (
<DocumentStar
key="star"
organizationId={document.organizationId}
wikiId={document.wikiId}
documentId={documentId}
/>
)}
{document && (
<DocumentActions organizationId={document.organizationId} wikiId={document.wikiId} documentId={documentId} />
)}
<DocumentVersion documentId={documentId} onSelect={triggerUseDocumentVersion} />
</Space>
),

View File

@ -56,7 +56,10 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
);
const gotoEdit = useCallback(() => {
Router.push(`/wiki/${document.wikiId}/document/${document.id}/edit`);
Router.push({
pathname: `/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]/edit`,
query: { organizationId: document.organizationId, wikiId: document.wikiId, documentId: document.id },
});
}, [document]);
const actions = useMemo(() => {
@ -70,11 +73,26 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
documentId={documentId}
/>
)}
{document && <DocumentStar disabled={!readable} key="star" wikiId={document.wikiId} documentId={documentId} />}
{document && (
<DocumentStar
disabled={!readable}
key="star"
organizationId={document.organizationId}
wikiId={document.wikiId}
documentId={documentId}
/>
)}
<Tooltip key="edit" content="编辑" position="bottom">
<Button disabled={!editable} icon={<IconEdit />} onMouseDown={gotoEdit} />
</Tooltip>
{document && <DocumentActions wikiId={document.wikiId} documentId={documentId} document={document} />}
{document && (
<DocumentActions
organizationId={document.organizationId}
wikiId={document.wikiId}
documentId={documentId}
document={document}
/>
)}
<DocumentVersion documentId={documentId} />
</Space>
);

View File

@ -1,12 +1,13 @@
import { IconStar } from '@douyinfe/semi-icons';
import { Button, Tooltip } from '@douyinfe/semi-ui';
import { IDocument, IWiki } from '@think/domains';
import { IDocument, IOrganization, 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 {
organizationId: IOrganization['id'];
wikiId: IWiki['id'];
documentId: IDocument['id'];
disabled?: boolean;
@ -18,9 +19,9 @@ interface IProps {
}) => React.ReactNode;
}
export const DocumentStar: React.FC<IProps> = ({ wikiId, documentId, disabled = false, render }) => {
export const DocumentStar: React.FC<IProps> = ({ organizationId, wikiId, documentId, disabled = false, render }) => {
const [visible, toggleVisible] = useToggle(false);
const { data, toggle: toggleStar } = useDocumentStarToggle(wikiId, documentId, { enabled: visible });
const { data, toggle: toggleStar } = useDocumentStarToggle(organizationId, wikiId, documentId, { enabled: visible });
const text = data ? '取消收藏' : '收藏文档';
const onViewportChange = useCallback(

View File

@ -7,9 +7,11 @@
img {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
margin: auto;
border-radius: 0.25rem;
}
}

View File

@ -9,6 +9,8 @@ import { LazyLoadImage } from 'react-lazy-load-image-component';
import styles from './index.module.scss';
interface IProps {
width?: number;
mobileHeight?: number;
images: Array<{
key: string;
title: React.ReactNode;
@ -52,7 +54,7 @@ const UploadTab = ({ selectImage }) => {
);
};
export const ImageUploader: React.FC<IProps> = ({ images, selectImage, children }) => {
export const ImageUploader: React.FC<IProps> = ({ width = 360, mobileHeight = 370, images, selectImage, children }) => {
const { isMobile } = IsOnMobile.useHook();
const [visible, toggleVisible] = useToggle(false);
@ -131,7 +133,7 @@ export const ImageUploader: React.FC<IProps> = ({ images, selectImage, children
title={'图片'}
visible={visible}
onCancel={toggleVisible}
height={370}
height={mobileHeight}
mask={false}
>
{content}
@ -146,7 +148,7 @@ export const ImageUploader: React.FC<IProps> = ({ images, selectImage, children
position="bottomRight"
visible={visible}
onVisibleChange={toggleVisible}
content={<div style={{ width: 360, maxWidth: '96vw' }}>{content}</div>}
content={<div style={{ width, maxWidth: '96vw' }}>{content}</div>}
>
{children}
</Popover>

View File

@ -1,26 +1,24 @@
import { Banner, Button, Input, Modal, Select, Space, Typography } from '@douyinfe/semi-ui';
import { WIKI_USER_ROLES, WikiUserRole } from '@think/domains';
import { IWikiUserOpeateData } from 'data/wiki';
import { Banner, Button, Input, Modal, Select, Space } from '@douyinfe/semi-ui';
import { AuthEnum, AuthEnumArray } from '@think/domains';
import React, { useCallback, useState } from 'react';
interface IProps {
visible: boolean;
toggleVisible: (arg) => void;
onOk: (arg: IWikiUserOpeateData) => any;
onOk: (arg) => any;
}
const { Paragraph } = Typography;
export const AddUser: React.FC<IProps> = ({ visible, toggleVisible, onOk }) => {
const [userRole, setUserRole] = useState(WikiUserRole.normal);
const [userAuth, setUserAuth] = useState(AuthEnum.noAccess);
const [userName, setUserName] = useState('');
const handleOk = useCallback(() => {
onOk({ userName, userRole } as unknown as IWikiUserOpeateData).then(() => {
setUserRole(WikiUserRole.normal);
onOk({ userName, userAuth }).then(() => {
setUserAuth(AuthEnum.noAccess);
setUserName('');
toggleVisible(false);
});
}, [onOk, userName, userRole, toggleVisible]);
}, [onOk, userName, userAuth, toggleVisible]);
return (
<Modal
@ -34,12 +32,12 @@ export const AddUser: React.FC<IProps> = ({ visible, toggleVisible, onOk }) => {
footer={null}
>
<div style={{ marginTop: 16 }}>
{userRole === WikiUserRole.admin ? (
{[AuthEnum.creator, AuthEnum.admin].includes(userAuth) ? (
<Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
) : null}
<Space>
<Select value={userRole} onChange={setUserRole} style={{ width: 120 }}>
{WIKI_USER_ROLES.map((wikiStatus) => {
<Select value={userAuth} onChange={setUserAuth} style={{ width: 120 }}>
{AuthEnumArray.map((wikiStatus) => {
return (
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
{wikiStatus.label}

View File

@ -1,25 +1,33 @@
import { Banner, Button, Modal, Select } from '@douyinfe/semi-ui';
import { WIKI_USER_ROLES, WikiUserRole } from '@think/domains';
import React, { useCallback, useState } from 'react';
import { AuthEnum, AuthEnumArray, IAuth, IUser } from '@think/domains';
import React, { useCallback, useEffect, useState } from 'react';
interface IProps {
visible: boolean;
toggleVisible: (arg) => void;
onOk: (arg: WikiUserRole) => any;
currentUser: { user: IUser; auth: IAuth };
onOk: (arg) => any;
}
export const EditUser: React.FC<IProps> = ({ visible, toggleVisible, onOk }) => {
const [userRole, setUserRole] = useState(WikiUserRole.normal);
export const EditUser: React.FC<IProps> = ({ visible, toggleVisible, currentUser, onOk }) => {
const [userAuth, setUserAuth] = useState(AuthEnum.noAccess);
const handleOk = useCallback(() => {
onOk(userRole).then(() => {
setUserRole(WikiUserRole.normal);
onOk(userAuth).then(() => {
setUserAuth(AuthEnum.noAccess);
toggleVisible(false);
});
}, [onOk, userRole, toggleVisible]);
}, [onOk, userAuth, toggleVisible]);
useEffect(() => {
if (!visible) {
setUserAuth(AuthEnum.noAccess);
}
}, [visible]);
return (
<Modal
title={'修改角色'}
title={`修改用户${currentUser && currentUser.user.name}权限`}
visible={visible}
onOk={handleOk}
onCancel={() => toggleVisible(false)}
@ -28,11 +36,12 @@ export const EditUser: React.FC<IProps> = ({ visible, toggleVisible, onOk }) =>
footer={null}
>
<div style={{ marginTop: 16 }}>
{userRole === WikiUserRole.admin ? (
{[AuthEnum.creator, AuthEnum.admin].includes(userAuth) ? (
<Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
) : null}
<Select value={userRole} onChange={setUserRole} style={{ width: '100%' }}>
{WIKI_USER_ROLES.map((wikiStatus) => {
{}
<Select value={userAuth} onChange={setUserAuth} style={{ width: '100%' }}>
{AuthEnumArray.map((wikiStatus) => {
return (
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
{wikiStatus.label}

View File

@ -0,0 +1,6 @@
.wrap {
> header {
display: flex;
justify-content: flex-end;
}
}

View File

@ -0,0 +1,113 @@
import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
import { Banner, Button, Popconfirm, Table, Typography } from '@douyinfe/semi-ui';
import { AuthEnumTextMap, IOrganization } from '@think/domains';
import { DataRender } from 'components/data-render';
import { LocaleTime } from 'components/locale-time';
import { useOrganizationMembers } from 'data/organization';
import { useToggle } from 'hooks/use-toggle';
import React, { useState } from 'react';
import { AddUser } from './add';
import { EditUser } from './edit';
import styles from './index.module.scss';
interface IProps {
id: string;
hook: any;
}
const { Title, Paragraph } = Typography;
const { Column } = Table;
export const Members: React.FC<IProps> = ({ id, hook }) => {
const { data, loading, error, addUser, updateUser, deleteUser } = hook(id);
const [visible, toggleVisible] = useToggle(false);
const [editVisible, toggleEditVisible] = useToggle(false);
const [currentUser, setCurrentUser] = useState(null);
const editUser = (user) => {
setCurrentUser(user);
toggleEditVisible(true);
};
const handleEdit = (userAuth) => {
return updateUser({ userName: currentUser.user.name, userAuth }).then(() => {
setCurrentUser(null);
});
};
console.log(data);
return (
<div className={styles.wrap}>
<header>{/* <MemberAdder /> */}</header>
<DataRender
loading={loading}
error={error}
normalContent={() => (
<div>
<Banner
fullMode={false}
type="info"
bordered
icon={null}
style={{ margin: '16px 0' }}
title={<Title heading={6}></Title>}
description={
<div>
<Paragraph></Paragraph>
<Paragraph></Paragraph>
<Paragraph>访</Paragraph>
</div>
}
/>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button theme="solid" onClick={toggleVisible}>
</Button>
</div>
<Table style={{ margin: '16px 0' }} dataSource={data.data} size="small" pagination={false}>
<Column title="用户名" dataIndex="user.name" key="user.name" />
<Column
title="成员权限"
dataIndex="auth.auth"
key="auth.auth"
align="center"
render={(auth) => AuthEnumTextMap[auth]}
/>
<Column
title="加入时间"
dataIndex="auth.createdAt"
key="auth.createdAt"
align="center"
render={(d) => <LocaleTime date={d} />}
/>
<Column
title="操作"
dataIndex="operate"
key="operate"
align="center"
render={(_, data) => (
<>
<Button type="tertiary" theme="borderless" icon={<IconEdit />} onClick={() => editUser(data)} />
<Popconfirm
showArrow
title="确认删除该成员?"
onConfirm={() => deleteUser({ userName: data.user.name, userAuth: data.auth.auth })}
>
<Button type="tertiary" theme="borderless" icon={<IconDelete />} />
</Popconfirm>
</>
)}
/>
</Table>
</div>
)}
/>
<AddUser visible={visible} toggleVisible={toggleVisible} onOk={addUser} />
<EditUser visible={editVisible} toggleVisible={toggleEditVisible} currentUser={currentUser} onOk={handleEdit} />
</div>
);
};

View File

@ -0,0 +1,16 @@
.wrap {
display: inline-flex !important;
flex-wrap: nowrap;
align-items: center;
> svg {
width: 32px;
height: 32px;
}
> span {
margin-left: 4px;
font-size: 1.2rem;
font-weight: 500;
}
}

View File

@ -0,0 +1,66 @@
import { Typography } from '@douyinfe/semi-ui';
import { Avatar } from '@douyinfe/semi-ui';
import { DataRender } from 'components/data-render';
import { useOrganizationDetail } from 'data/organization';
import { useRouterQuery } from 'hooks/use-router-query';
import Link from 'next/link';
import styles from './index.module.scss';
const { Text } = Typography;
export const OrganizationImage = () => {
const { organizationId } = useRouterQuery<{ organizationId: string }>();
const { data, loading, error } = useOrganizationDetail(organizationId);
return (
<DataRender
loading={loading}
error={error}
normalContent={() => {
return (
<Link
href={{
pathname: '/app/org/[organizationId]',
query: {
organizationId: data.id,
},
}}
>
<a style={{ width: 36, height: 36 }}>
<Avatar alt="cute cat" size="small" src={data.logo} style={{ margin: 4 }} />
</a>
</Link>
);
}}
/>
);
};
export const OrganizationText = () => {
const { organizationId } = useRouterQuery<{ organizationId: string }>();
const { data, loading, error } = useOrganizationDetail(organizationId);
return (
<DataRender
loading={loading}
error={error}
normalContent={() => {
return (
<Link
href={{
pathname: '/app/org/[organizationId]',
query: {
organizationId: data.id,
},
}}
>
<a style={{ width: 36, height: 36 }}>
<Text>{data.name}</Text>
</a>
</Link>
);
}}
/>
);
};

View File

@ -0,0 +1,4 @@
.nameWrap {
display: flex;
align-items: center;
}

View File

@ -0,0 +1,84 @@
import { IconSmallTriangleDown } from '@douyinfe/semi-icons';
import { Avatar, Button, Dropdown, Space, Typography } from '@douyinfe/semi-ui';
import { DataRender } from 'components/data-render';
import { LogoImage, LogoText } from 'components/logo';
import { useUserOrganizations } from 'data/organization';
import Link from 'next/link';
import Router from 'next/router';
import { useCallback } from 'react';
import styles from './index.module.scss';
const { Text, Paragraph } = Typography;
export const OrganizationPublicSwitcher = () => {
const {
data: userOrganizations,
loading: userOrganizationsLoading,
error: userOrganizationsError,
} = useUserOrganizations();
const gotoCreate = useCallback(() => {
Router.push(`/app/org/create`);
}, []);
return (
<span className={styles.nameWrap}>
<Space>
<LogoImage />
<LogoText />
</Space>
<Dropdown
trigger="click"
render={
<DataRender
loading={userOrganizationsLoading}
error={userOrganizationsError}
normalContent={() => {
return (
<Dropdown.Menu>
{(userOrganizations || []).map((org) => {
return (
<Dropdown.Item key={org.id}>
<Link
href={{
pathname: '/app/org/[organizationId]',
query: {
organizationId: org.id,
},
}}
>
<a style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Avatar size="extra-small" src={org.logo} style={{ marginRight: 8 }} />
<Paragraph
style={{
maxWidth: 100,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
}}
>
{org.name}
</Paragraph>
</a>
</Link>
</Dropdown.Item>
);
})}
<Dropdown.Divider />
<Dropdown.Item onClick={gotoCreate}>
<Text>
<Space></Space>
</Text>
</Dropdown.Item>
</Dropdown.Menu>
);
}}
/>
}
>
<Button size="small" icon={<IconSmallTriangleDown />} style={{ marginLeft: 12 }} />
</Dropdown>
</span>
);
};

View File

@ -0,0 +1,9 @@
.cover {
margin-right: 12px;
}
@media (max-width: 768px) {
.cover {
width: 100%;
}
}

View File

@ -0,0 +1,118 @@
import { Avatar, Button, Form, Toast, Typography } from '@douyinfe/semi-ui';
import { FormApi } from '@douyinfe/semi-ui/lib/es/form';
import { ORGANIZATION_LOGOS } from '@think/constants';
import { IOrganization } from '@think/domains';
import { DataRender } from 'components/data-render';
import { ImageUploader } from 'components/image-uploader';
import { useCreateOrganization, useOrganizationDetail } from 'data/organization';
import { useToggle } from 'hooks/use-toggle';
import { SingleColumnLayout } from 'layouts/single-column';
import Router from 'next/router';
import { useCallback, useEffect, useRef, useState } from 'react';
import styles from './index.module.scss';
const images = [
{
key: 'placeholers',
title: '图库',
images: ORGANIZATION_LOGOS,
},
];
interface IProps {
organizationId: IOrganization['id'];
}
export const Base: React.FC<IProps> = ({ organizationId }) => {
const $form = useRef<FormApi>();
const { data: organization, loading, error, update } = useOrganizationDetail(organizationId);
const [changed, toggleChanged] = useToggle(false);
const [currentCover, setCurrentCover] = useState('');
const setCover = useCallback((url) => {
$form.current.setValue('logo', url);
setCurrentCover(url);
}, []);
const onFormChange = useCallback(() => {
toggleChanged(true);
}, [toggleChanged]);
const onSubmit = useCallback(() => {
$form.current.validate().then((values) => {
update(values).then((res) => {
Toast.success('操作成功');
});
});
}, [update]);
useEffect(() => {
if (!organization) return;
setCurrentCover(organization.logo);
$form.current.setValues(organization);
}, [organization]);
return (
<DataRender
loading={loading}
error={error}
normalContent={() => (
<Form
style={{ width: '100%' }}
getFormApi={(formApi) => ($form.current = formApi)}
onChange={onFormChange}
onSubmit={onSubmit}
>
<Form.Slot label="Logo">
<div style={{ display: 'flex', alignItems: 'end' }}>
<div className={styles.cover}>
<Avatar
shape="square"
src={currentCover}
style={{
width: 64,
height: 64,
borderRadius: 4,
}}
/>
</div>
<ImageUploader width={260} images={images} selectImage={setCover}>
<Button>Logo</Button>
</ImageUploader>
</div>
</Form.Slot>
<Form.Input
label="名称"
field="name"
style={{ width: '100%' }}
placeholder="请输入组织名称"
rules={[{ required: true, message: '请输入组织名称' }]}
/>
<Form.TextArea
label="描述"
field="description"
style={{ width: '100%' }}
placeholder="请输入组织简介"
autosize
rules={[{ required: true, message: '请输入组织简介' }]}
/>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Button htmlType="submit" type="primary" theme="solid" disabled={!changed} loading={loading}>
</Button>
</div>
</Form>
)}
/>
);
};

View File

@ -0,0 +1,65 @@
import { TabPane, Tabs } from '@douyinfe/semi-ui';
import { IOrganization, 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';
import { Base } from './base';
import { OrganizationMembers } from './members';
import { More } from './more';
interface IProps {
organizationId: IOrganization['id'];
tab?: string;
onNavigate: (arg: string) => void;
}
const TitleMap = {
base: '基础信息',
members: '成员管理',
more: '更多',
};
export const OrganizationSetting: React.FC<IProps> = ({ organizationId, tab, onNavigate }) => {
// const { data, update } = useWikiDetail(wikiId);
return (
<>
<Seo title={TitleMap[tab]} />
<Tabs lazyRender type="line" activeKey={tab} onChange={onNavigate}>
<TabPane tab={TitleMap['base']} itemKey="base">
<Base organizationId={organizationId} />
</TabPane>
<TabPane tab={TitleMap['members']} itemKey="members">
<OrganizationMembers organizationId={organizationId} />
</TabPane>
{/* <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>
<TabPane tab={TitleMap['import']} itemKey="import">
<Import wikiId={wikiId} />
</TabPane>
<TabPane tab={TitleMap['more']} itemKey="more">
<More wikiId={wikiId} />
</TabPane> */}
</Tabs>
</>
);
};

View File

@ -0,0 +1,62 @@
import { Banner, Button, Input, Modal, Select, Space } from '@douyinfe/semi-ui';
import { AuthEnum, AuthEnumArray } from '@think/domains';
import React, { useCallback, useState } from 'react';
interface IProps {
visible: boolean;
toggleVisible: (arg) => void;
onOk: (arg) => any;
}
export const AddUser: React.FC<IProps> = ({ visible, toggleVisible, onOk }) => {
const [userAuth, setUserAuth] = useState(AuthEnum.noAccess);
const [userName, setUserName] = useState('');
const handleOk = useCallback(() => {
onOk({ userName, userAuth }).then(() => {
setUserAuth(AuthEnum.noAccess);
setUserName('');
toggleVisible(false);
});
}, [onOk, userName, userAuth, toggleVisible]);
return (
<Modal
title={'添加成员'}
okText={'邀请对方'}
visible={visible}
onOk={handleOk}
onCancel={() => toggleVisible(false)}
maskClosable={false}
style={{ maxWidth: '96vw' }}
footer={null}
>
<div style={{ marginTop: 16 }}>
{[AuthEnum.creator, AuthEnum.admin].includes(userAuth) ? (
<Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
) : null}
<Space>
<Select value={userAuth} onChange={setUserAuth} style={{ width: 120 }}>
{AuthEnumArray.map((wikiStatus) => {
return (
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
{wikiStatus.label}
</Select.Option>
);
})}
</Select>
<Input
autofocus
placeholder="输入对方用户名"
value={userName}
onChange={setUserName}
style={{ width: 270 }}
></Input>
</Space>
<Button theme="solid" block style={{ margin: '24px 0' }} onClick={handleOk} disabled={!userName}>
</Button>
</div>
</Modal>
);
};

View File

@ -0,0 +1,58 @@
import { Banner, Button, Modal, Select } from '@douyinfe/semi-ui';
import { AuthEnum, AuthEnumArray, IAuth, IUser } from '@think/domains';
import React, { useCallback, useEffect, useState } from 'react';
interface IProps {
visible: boolean;
toggleVisible: (arg) => void;
currentUser: { user: IUser; auth: IAuth };
onOk: (arg) => any;
}
export const EditUser: React.FC<IProps> = ({ visible, toggleVisible, currentUser, onOk }) => {
const [userAuth, setUserAuth] = useState(AuthEnum.noAccess);
const handleOk = useCallback(() => {
onOk(userAuth).then(() => {
setUserAuth(AuthEnum.noAccess);
toggleVisible(false);
});
}, [onOk, userAuth, toggleVisible]);
useEffect(() => {
if (!visible) {
setUserAuth(AuthEnum.noAccess);
}
}, [visible]);
return (
<Modal
title={`修改用户${currentUser && currentUser.user.name}权限`}
visible={visible}
onOk={handleOk}
onCancel={() => toggleVisible(false)}
maskClosable={false}
style={{ maxWidth: '96vw' }}
footer={null}
>
<div style={{ marginTop: 16 }}>
{[AuthEnum.creator, AuthEnum.admin].includes(userAuth) ? (
<Banner style={{ marginBottom: 16 }} type="warning" description="请谨慎操作管理员权限!" />
) : null}
{}
<Select value={userAuth} onChange={setUserAuth} style={{ width: '100%' }}>
{AuthEnumArray.map((wikiStatus) => {
return (
<Select.Option key={wikiStatus.value} value={wikiStatus.value}>
{wikiStatus.label}
</Select.Option>
);
})}
</Select>
<Button theme="solid" block style={{ margin: '24px 0' }} onClick={handleOk}>
</Button>
</div>
</Modal>
);
};

View File

@ -0,0 +1,6 @@
.wrap {
> header {
display: flex;
justify-content: flex-end;
}
}

View File

@ -0,0 +1,110 @@
import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
import { Banner, Button, Popconfirm, Table, Typography } from '@douyinfe/semi-ui';
import { AuthEnumTextMap, IOrganization } from '@think/domains';
import { DataRender } from 'components/data-render';
import { LocaleTime } from 'components/locale-time';
import { useOrganizationMembers } from 'data/organization';
import { useToggle } from 'hooks/use-toggle';
import React, { useState } from 'react';
import { AddUser } from './add';
import { EditUser } from './edit';
import styles from './index.module.scss';
interface IProps {
organizationId: IOrganization['id'];
}
const { Title, Paragraph } = Typography;
const { Column } = Table;
export const OrganizationMembers: React.FC<IProps> = ({ organizationId }) => {
const { data, loading, error, refresh, addUser, updateUser, deleteUser } = useOrganizationMembers(organizationId);
const [visible, toggleVisible] = useToggle(false);
const [editVisible, toggleEditVisible] = useToggle(false);
const [currentUser, setCurrentUser] = useState(null);
const editUser = (user) => {
setCurrentUser(user);
toggleEditVisible(true);
};
const handleEdit = (userAuth) => {
return updateUser({ userName: currentUser.user.name, userAuth }).then(() => {
setCurrentUser(null);
});
};
return (
<div className={styles.wrap}>
<header>{/* <MemberAdder /> */}</header>
<DataRender
loading={loading}
error={error}
normalContent={() => (
<div>
<Banner
fullMode={false}
type="info"
bordered
icon={null}
style={{ margin: '16px 0' }}
title={<Title heading={6}></Title>}
description={
<div>
<Paragraph></Paragraph>
<Paragraph></Paragraph>
<Paragraph>访</Paragraph>
</div>
}
/>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button theme="solid" onClick={toggleVisible}>
</Button>
</div>
<Table style={{ margin: '16px 0' }} dataSource={data.data} size="small" pagination={false}>
<Column title="用户名" dataIndex="user.name" key="user.name" />
<Column
title="成员权限"
dataIndex="auth.auth"
key="auth.auth"
align="center"
render={(auth) => AuthEnumTextMap[auth]}
/>
<Column
title="加入时间"
dataIndex="auth.createdAt"
key="auth.createdAt"
align="center"
render={(d) => <LocaleTime date={d} />}
/>
<Column
title="操作"
dataIndex="operate"
key="operate"
align="center"
render={(_, data) => (
<>
<Button type="tertiary" theme="borderless" icon={<IconEdit />} onClick={() => editUser(data)} />
<Popconfirm
showArrow
title="确认删除该成员?"
onConfirm={() => deleteUser({ userName: data.user.name, userAuth: data.auth.auth })}
>
<Button type="tertiary" theme="borderless" icon={<IconDelete />} />
</Popconfirm>
</>
)}
/>
</Table>
</div>
)}
/>
<AddUser visible={visible} toggleVisible={toggleVisible} onOk={addUser} />
<EditUser visible={editVisible} toggleVisible={toggleEditVisible} currentUser={currentUser} onOk={handleEdit} />
</div>
);
};

View File

@ -0,0 +1,27 @@
import { Banner, Button, Typography } from '@douyinfe/semi-ui';
import { WorkspaceDeletor } from 'components/wiki/delete';
interface IProps {
wikiId: string;
}
const { Paragraph } = Typography;
export const More: React.FC<IProps> = ({ wikiId }) => {
return (
<div style={{ marginTop: 16 }}>
<Banner
fullMode={false}
type="danger"
closeIcon={null}
description={<Paragraph></Paragraph>}
style={{ marginBottom: 16 }}
/>
<WorkspaceDeletor wikiId={wikiId}>
<Button type="danger" theme="solid">
</Button>
</WorkspaceDeletor>
</div>
);
};

View File

@ -0,0 +1,4 @@
.nameWrap {
display: flex;
align-items: center;
}

View File

@ -0,0 +1,128 @@
import { IconAppCenter, IconSmallTriangleDown } from '@douyinfe/semi-icons';
import { Button, Dropdown, Space, Typography } from '@douyinfe/semi-ui';
import { Avatar } from '@douyinfe/semi-ui';
import { DataRender } from 'components/data-render';
import { useOrganizationDetail, useUserOrganizations } from 'data/organization';
import { useRouterQuery } from 'hooks/use-router-query';
import Link from 'next/link';
import Router from 'next/router';
import { useCallback } from 'react';
import styles from './index.module.scss';
const { Text, Paragraph } = Typography;
export const OrganizationSwitcher = () => {
const { organizationId } = useRouterQuery<{ organizationId: string }>();
const { data, loading, error } = useOrganizationDetail(organizationId);
const {
data: userOrganizations,
loading: userOrganizationsLoading,
error: userOrganizationsError,
} = useUserOrganizations();
const gotoCreate = useCallback(() => {
Router.push(`/app/org/create`);
}, []);
return (
<DataRender
loading={loading}
error={error}
normalContent={() => {
return (
<Link
href={{
pathname: '/app/org/[organizationId]',
query: {
organizationId: data.id,
},
}}
>
<a className={styles.nameWrap}>
<span style={{ display: 'flex', alignItems: 'center' }}>
<Avatar size="small" src={data.logo} style={{ marginRight: 8 }} />
<Paragraph
style={{
maxWidth: 100,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
}}
strong
>
{data.name}
</Paragraph>
</span>
<Dropdown
trigger="click"
render={
<DataRender
loading={userOrganizationsLoading}
error={userOrganizationsError}
normalContent={() => {
return (
<Dropdown.Menu>
{(userOrganizations || []).map((org) => {
return (
<Dropdown.Item key={org.id}>
<Link
href={{
pathname: '/app/org/[organizationId]',
query: {
organizationId: org.id,
},
}}
>
<a style={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Avatar size="extra-small" src={org.logo} style={{ marginRight: 8 }} />
<Paragraph
style={{
maxWidth: 100,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
}}
>
{org.name}
</Paragraph>
</a>
</Link>
</Dropdown.Item>
);
})}
<Dropdown.Divider />
<Dropdown.Item onClick={gotoCreate}>
<Text>
<Space>
<Avatar size="extra-small">
<IconAppCenter />
</Avatar>
</Space>
</Text>
</Dropdown.Item>
</Dropdown.Menu>
);
}}
/>
}
>
<Button
size="small"
icon={<IconSmallTriangleDown />}
style={{ marginLeft: 12 }}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
/>
</Dropdown>
</a>
</Link>
);
}}
/>
);
};

View File

@ -7,8 +7,10 @@ import { Empty } from 'components/empty';
import { IconSearch } from 'components/icons';
import { IconDocumentFill } from 'components/icons/IconDocumentFill';
import { LocaleTime } from 'components/locale-time';
import { useSearchDocuments } from 'data/document';
import { useAsyncLoading } from 'hooks/use-async-loading';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useRouterQuery } from 'hooks/use-router-query';
import { useToggle } from 'hooks/use-toggle';
import Link from 'next/link';
import Router from 'next/router';
@ -19,8 +21,8 @@ import styles from './index.module.scss';
const { Text } = Typography;
const searchDocument = (keyword: string): Promise<IDocument[]> => {
return HttpClient.get('/document/search', { params: { keyword } });
const searchDocument = (organizationId, keyword: string): Promise<IDocument[]> => {
return HttpClient.get(`/document/search/${organizationId}`, { params: { keyword } });
};
const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
@ -32,11 +34,8 @@ const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
<div className={styles.itemWrap} key={doc.id}>
<Link
href={{
pathname: '/wiki/[wikiId]/document/[documentId]',
query: {
wikiId: doc.wikiId,
documentId: doc.id,
},
pathname: `/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]`,
query: { organizationId: doc.organizationId, wikiId: doc.wikiId, documentId: doc.id },
}}
>
<a className={styles.item}>
@ -54,7 +53,7 @@ const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
</div>
</div>
<div className={styles.rightWrap}>
<DocumentStar wikiId={doc.wikiId} documentId={doc.id} />
<DocumentStar organizationId={doc.organizationId} wikiId={doc.wikiId} documentId={doc.id} />
</div>
</a>
</Link>
@ -69,10 +68,12 @@ const List: React.FC<{ data: IDocument[] }> = ({ data }) => {
};
export const Search = () => {
const { organizationId } = useRouterQuery<{ organizationId: string }>();
const { search: searchApi, loading } = useSearchDocuments(organizationId);
const ref = useRef<HTMLInputElement>();
const { isMobile } = IsOnMobile.useHook();
const [visible, toggleVisible] = useToggle(false);
const [searchApi, loading] = useAsyncLoading(searchDocument, 10);
const [keyword, setKeyword] = useState('');
const [error, setError] = useState(null);
const [searchDocs, setSearchDocs] = useState<IDocument[]>([]);

View File

@ -1,8 +1,7 @@
import { IconChevronDown, IconPlus } from '@douyinfe/semi-icons';
import { IconPlus } from '@douyinfe/semi-icons';
import { Button, Dropdown } from '@douyinfe/semi-ui';
import { DocumentCreator } from 'components/document/create';
import { WikiCreator } from 'components/wiki/create';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useRouterQuery } from 'hooks/use-router-query';
import { useToggle } from 'hooks/use-toggle';
import React from 'react';
@ -12,7 +11,6 @@ interface IProps {
}
export const WikiOrDocumentCreator: React.FC<IProps> = ({ onCreateDocument, children }) => {
const { isMobile } = IsOnMobile.useHook();
const { wikiId, documentId } = useRouterQuery<{ wikiId?: string; documentId?: string }>();
const [dropdownVisible, toggleDropdownVisible] = useToggle(false);
const [visible, toggleVisible] = useToggle(false);

View File

@ -15,8 +15,8 @@ export const WikiCard: React.FC<{ wiki: IWikiWithIsMember; shareMode?: boolean }
<div className={styles.cardWrap}>
<Link
href={{
pathname: `${shareMode || !wiki.isMember ? '/share' : ''}/wiki/[wikiId]`,
query: { wikiId: wiki.id },
pathname: shareMode || !wiki.isMember ? '/share/wiki/[wikiId]' : `/app/org/[organizationId]/wiki/[wikiId]`,
query: { organizationId: wiki.organizationId, wikiId: wiki.id },
}}
>
<a target={shareMode ? '_blank' : '_self'}>
@ -36,7 +36,7 @@ export const WikiCard: React.FC<{ wiki: IWikiWithIsMember; shareMode?: boolean }
</Avatar>
<div className={styles.rightWrap}>
<Space>
<WikiStar wikiId={wiki.id} />
<WikiStar organizationId={wiki.organizationId} wikiId={wiki.id} />
</Space>
</div>
</header>

View File

@ -2,6 +2,7 @@ import { Form, Modal } from '@douyinfe/semi-ui';
import { FormApi } from '@douyinfe/semi-ui/lib/es/form';
import type { IWiki } from '@think/domains';
import { ICreateWiki, useOwnWikis } from 'data/wiki';
import { useRouterQuery } from 'hooks/use-router-query';
import Router from 'next/router';
import { Dispatch, SetStateAction, useRef } from 'react';
@ -12,14 +13,16 @@ interface IProps {
export const WikiCreator: React.FC<IProps> = ({ visible, toggleVisible }) => {
const $form = useRef<FormApi>();
const { createWiki } = useOwnWikis();
const { organizationId } = useRouterQuery<{ organizationId: string }>();
const { createWiki } = useOwnWikis(organizationId);
const handleOk = () => {
$form.current.validate().then((values) => {
createWiki(values as ICreateWiki).then((res) => {
toggleVisible(false);
Router.push({
pathname: `/wiki/${(res as unknown as IWiki).id}`,
pathname: `/app/org/[organizationId]/wiki/[wikiId]`,
query: { organizationId: res.organizationId, wikiId: res.id },
});
});
});

View File

@ -12,7 +12,12 @@ const { Text, Paragraph } = Typography;
export const WikiPinCard: React.FC<{ wiki: IWiki }> = ({ wiki }) => {
return (
<div className={styles.cardWrap}>
<Link href={{ pathname: `/wiki/[wikiId]`, query: { wikiId: wiki.id } }}>
<Link
href={{
pathname: `/app/org/[organizationId]/wiki/[wikiId]`,
query: { organizationId: wiki.organizationId, wikiId: wiki.id },
}}
>
<a>
<header>
<Avatar
@ -30,7 +35,7 @@ export const WikiPinCard: React.FC<{ wiki: IWiki }> = ({ wiki }) => {
</Avatar>
<div className={styles.rightWrap}>
<Space>
<WikiStar wikiId={wiki.id} />
<WikiStar organizationId={wiki.organizationId} wikiId={wiki.id} />
</Space>
</div>
</header>

View File

@ -1,6 +1,7 @@
import { Button, Toast, Typography, Upload } from '@douyinfe/semi-ui';
import type { IWiki } from '@think/domains';
import { useCreateDocument } from 'data/document';
import { useRouterQuery } from 'hooks/use-router-query';
import { useToggle } from 'hooks/use-toggle';
import { useCallback, useEffect, useRef, useState } from 'react';
@ -14,6 +15,7 @@ const { Text } = Typography;
export const Import: React.FC<IProps> = ({ wikiId }) => {
const { create } = useCreateDocument();
const { organizationId } = useRouterQuery<{ organizationId: string }>();
const $upload = useRef<Upload>();
const [loading, toggleLoading] = useToggle(false);
const [markdownParser, setMarkdownParser] = useState<MarkdownParse>();
@ -56,7 +58,7 @@ export const Import: React.FC<IProps> = ({ wikiId }) => {
for (const file of fileList) {
const payload = markdownParser.parse(file.name, file.text);
create({ ...payload, wikiId })
create({ ...payload, organizationId, wikiId })
.then(() => {
success += 1;
})
@ -77,7 +79,7 @@ export const Import: React.FC<IProps> = ({ wikiId }) => {
}
});
}
}, [markdownParser, fileList, toggleLoading, create, wikiId]);
}, [markdownParser, fileList, toggleLoading, create, organizationId, wikiId]);
useEffect(() => {
const markdownParser = createMarkdownParser();

View File

@ -1,88 +1,11 @@
import { IconDelete, IconEdit } from '@douyinfe/semi-icons';
import { Button, Popconfirm, Table } from '@douyinfe/semi-ui';
import { getWikiUserRoleText } from '@think/domains';
import { DataRender } from 'components/data-render';
import { LocaleTime } from 'components/locale-time';
import { Members } from 'components/members';
import { useWikiMembers } from 'data/wiki';
import { useToggle } from 'hooks/use-toggle';
import React, { useState } from 'react';
import { AddUser } from './add';
import { EditUser } from './edit';
import { Placeholder } from './placeholder';
import React from 'react';
interface IProps {
wikiId: string;
}
const { Column } = Table;
export const Users: React.FC<IProps> = ({ wikiId }) => {
const [visible, toggleVisible] = useToggle(false);
const [editVisible, toggleEditVisible] = useToggle(false);
const [currentUser, setCurrentUser] = useState(null);
const { users, loading, error, addUser, updateUser, deleteUser } = useWikiMembers(wikiId);
const editUser = (user) => {
setCurrentUser(user);
toggleEditVisible(true);
};
const handleEdit = (userRole) => {
return updateUser({ userName: currentUser.userName, userRole }).then(() => {
setCurrentUser(null);
});
};
return (
<>
<DataRender
loading={loading}
error={error}
loadingContent={<Placeholder />}
normalContent={() => (
<div style={{ margin: '24px 0' }}>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button onClick={toggleVisible} theme="solid">
</Button>
</div>
<Table style={{ margin: '16px 0' }} dataSource={users} size="small" pagination>
<Column title="用户名" dataIndex="userName" key="userName" />
<Column
title="成员角色"
dataIndex="userRole"
key="userRole"
align="center"
render={getWikiUserRoleText}
/>
<Column
title="加入时间"
dataIndex="createdAt"
key="createdAt"
align="center"
render={(d) => <LocaleTime date={d} />}
/>
<Column
title="操作"
dataIndex="operate"
key="operate"
align="center"
render={(_, data) => (
<>
<Button type="tertiary" theme="borderless" icon={<IconEdit />} onClick={() => editUser(data)} />
<Popconfirm showArrow title="确认删除该成员?" onConfirm={() => deleteUser(data)}>
<Button type="tertiary" theme="borderless" icon={<IconDelete />} />
</Popconfirm>
</>
)}
/>
</Table>
</div>
)}
/>
<AddUser visible={visible} toggleVisible={toggleVisible} onOk={addUser} />
<EditUser visible={editVisible} toggleVisible={toggleEditVisible} onOk={handleEdit} />
</>
);
return <Members id={wikiId} hook={useWikiMembers} />;
};

View File

@ -1,47 +0,0 @@
import { Skeleton, Table } from '@douyinfe/semi-ui';
const columns = [
{
title: '用户名',
dataIndex: 'userName',
key: 'userName',
},
{
title: '成员角色',
dataIndex: 'userRole',
key: 'userRole',
},
{
title: '加入时间',
dataIndex: 'createdAt',
key: 'createdAt',
},
{
title: '操作',
dataIndex: 'actions',
key: 'actions',
},
];
const PLACEHOLDER_DATA = Array.from({ length: 3 }).fill({
name: <Skeleton.Paragraph style={{ width: 50 }} rows={1} />,
userRole: <Skeleton.Paragraph style={{ width: 100 }} rows={1} />,
createdAt: <Skeleton.Paragraph style={{ width: 120 }} rows={1} />,
});
export const Placeholder = () => {
return (
<Skeleton
placeholder={
<Table
size="small"
style={{ margin: '24px 0' }}
columns={columns}
dataSource={PLACEHOLDER_DATA}
pagination={false}
/>
}
loading={true}
></Skeleton>
);
};

View File

@ -1,16 +1,18 @@
import { IconStar } from '@douyinfe/semi-icons';
import { Button, Tooltip } from '@douyinfe/semi-ui';
import { IOrganization, IWiki } from '@think/domains';
import { useWikiStarToggle } from 'data/star';
import React from 'react';
interface IProps {
wikiId: string;
organizationId: IOrganization['id'];
wikiId: IWiki['id'];
render?: (arg: { star: boolean; text: string; toggleStar: () => Promise<void> }) => React.ReactNode;
onChange?: () => void;
}
export const WikiStar: React.FC<IProps> = ({ wikiId, render, onChange }) => {
const { data, toggle } = useWikiStarToggle(wikiId);
export const WikiStar: React.FC<IProps> = ({ organizationId, wikiId, render, onChange }) => {
const { data, toggle } = useWikiStarToggle(organizationId, wikiId);
const text = data ? '取消收藏' : '收藏知识库';
return (

View File

@ -1,10 +1,11 @@
import { IconPlus, IconSmallTriangleDown } from '@douyinfe/semi-icons';
import { Avatar, Button, Dropdown, Skeleton, Typography } from '@douyinfe/semi-ui';
import { IDocument } from '@think/domains';
import cls from 'classnames';
import { DataRender } from 'components/data-render';
import { IconOverview, IconSetting } from 'components/icons';
import { findParents } from 'components/wiki/tocs/utils';
import { useStarWikis, useWikiStarDocuments } from 'data/star';
import { useStarDocumentsInWiki, useStarWikisInOrganization } from 'data/star';
import { useWikiDetail, useWikiTocs } from 'data/wiki';
import { triggerCreateDocument } from 'event';
import Link from 'next/link';
@ -18,7 +19,7 @@ interface IProps {
wikiId: string;
documentId?: string;
docAsLink?: string;
getDocLink?: (arg: string) => string;
getDocLink?: (arg: IDocument) => string;
}
const { Text } = Typography;
@ -26,18 +27,18 @@ const { Text } = Typography;
export const WikiTocs: React.FC<IProps> = ({
wikiId,
documentId = null,
docAsLink = '/wiki/[wikiId]/document/[documentId]',
getDocLink = (documentId) => `/wiki/${wikiId}/document/${documentId}`,
docAsLink = '/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]',
getDocLink = (document) => `/app/org/${document.organizationId}/wiki/${document.wikiId}/doc/${document.id}`,
}) => {
const { pathname, query } = useRouter();
const { data: wiki, loading: wikiLoading, error: wikiError } = useWikiDetail(wikiId);
const { data: tocs, loading: tocsLoading, error: tocsError } = useWikiTocs(wikiId);
const { data: starWikis } = useStarWikis();
const { data: starWikis } = useStarWikisInOrganization(query.organizationId);
const {
data: starDocuments,
loading: starDocumentsLoading,
error: starDocumentsError,
} = useWikiStarDocuments(wikiId);
} = useStarDocumentsInWiki(query.organizationId, wikiId);
const [parentIds, setParentIds] = useState<Array<string>>([]);
const otherStarWikis = useMemo(() => (starWikis || []).filter((wiki) => wiki.id !== wikiId), [starWikis, wikiId]);
@ -86,8 +87,8 @@ export const WikiTocs: React.FC<IProps> = ({
<Dropdown.Item key={wiki.id}>
<Link
href={{
pathname: `/wiki/[wikiId]`,
query: { wikiId: wiki.id },
pathname: `/app/org/[organizationId]/wiki/[wikiId]`,
query: { organizationId: wiki.organizationId, wikiId: wiki.id },
}}
>
<a
@ -192,13 +193,14 @@ export const WikiTocs: React.FC<IProps> = ({
<div
className={cls(
styles.linkWrap,
(pathname === '/wiki/[wikiId]' || query.documentId === wiki.homeDocumentId) && styles.isActive
(pathname === '/app/org/[organizationId]/wiki/[wikiId]' || query.documentId === wiki.homeDocumentId) &&
styles.isActive
)}
>
<Link
href={{
pathname: `/wiki/[wikiId]`,
query: { wikiId },
pathname: `/app/org/[organizationId]/wiki/[wikiId]`,
query: { organizationId: wiki.organizationId, wikiId },
}}
>
<a>
@ -235,11 +237,16 @@ export const WikiTocs: React.FC<IProps> = ({
}
error={wikiError}
normalContent={() => (
<div className={cls(styles.linkWrap, pathname === '/wiki/[wikiId]/setting' && styles.isActive)}>
<div
className={cls(
styles.linkWrap,
pathname === '/app/org/[organizationId]/wiki/[wikiId]/setting' && styles.isActive
)}
>
<Link
href={{
pathname: `/wiki/[wikiId]/setting`,
query: { tab: 'base', wikiId },
pathname: `/app/org/[organizationId]/wiki/[wikiId]/setting`,
query: { organizationId: wiki.organizationId, wikiId, tab: 'base' },
}}
>
<a>

View File

@ -1,5 +1,6 @@
import { IconPlus } from '@douyinfe/semi-icons';
import { Avatar, Skeleton, Space, Typography } from '@douyinfe/semi-ui';
import { IDocument } from '@think/domains';
import { DataRender } from 'components/data-render';
import { IconOverview } from 'components/icons';
import { LogoImage, LogoText } from 'components/logo';
@ -17,7 +18,7 @@ interface IProps {
wikiId: string;
documentId?: string;
docAsLink?: string;
getDocLink?: (arg: string) => string;
getDocLink?: (arg: IDocument) => string;
pageTitle: string;
}
@ -28,7 +29,7 @@ export const WikiPublicTocs: React.FC<IProps> = ({
wikiId,
documentId = null,
docAsLink = '/share/wiki/[wikiId]/document/[documentId]',
getDocLink = (documentId) => `/share/wiki/${wikiId}/document/${documentId}`,
getDocLink = (document) => `/share/wiki/${document.wikiId}/document/${document.id}`,
}) => {
const { pathname } = useRouter();
const { data: wiki, loading: wikiLoading, error: wikiError } = usePublicWikiDetail(wikiId);

View File

@ -16,6 +16,7 @@ const Actions = ({ node }) => {
<DocumentActions
key={node.id}
hoverVisible
organizationId={node.organizationId}
wikiId={node.wikiId}
documentId={node.id}
size="small"
@ -83,7 +84,7 @@ export const Tree = ({
const renderLabel = useCallback(
(label, item) => (
<div className={styles.treeItemWrap} id={`item-${item.id}`}>
<Link href={docAsLink} as={getDocLink(item.id)}>
<Link href={docAsLink} as={getDocLink(item)}>
<a className={styles.left}>
<Typography.Text
ellipsis={{

View File

@ -6,18 +6,37 @@ import { QueriesOptions, useQuery, UseQueryOptions } from 'react-query';
import { HttpClient } from 'services/http-client';
type IDocumentWithVisitedAt = IDocument & { visitedAt: string };
type ICreateDocument = Partial<Pick<IDocument, 'wikiId' | 'parentDocumentId'>>;
type ICreateDocument = Partial<Pick<IDocument, 'organizationId' | 'wikiId' | 'parentDocumentId'>>;
type IDocumentWithAuth = { document: IDocument; authority: IAuthority };
type IUpdateDocument = Partial<Pick<IDocument, 'title' | 'content'>>;
/**
*
* @returns
*/
export const useSearchDocuments = (organizationId) => {
const [apiWithLoading, loading] = useAsyncLoading((keyword) =>
HttpClient.request({
method: DocumentApiDefinition.search.method,
url: DocumentApiDefinition.search.client(organizationId),
params: { keyword },
})
);
return {
search: apiWithLoading,
loading,
};
};
/**
* 访
* @returns
*/
export const getRecentVisitedDocuments = (cookie = null): Promise<IDocumentWithVisitedAt[]> => {
export const getRecentVisitedDocuments = (organizationId, cookie = null): Promise<IDocumentWithVisitedAt[]> => {
return HttpClient.request({
method: DocumentApiDefinition.recent.method,
url: DocumentApiDefinition.recent.client(),
url: DocumentApiDefinition.recent.client(organizationId),
cookie,
});
};
@ -26,10 +45,10 @@ export const getRecentVisitedDocuments = (cookie = null): Promise<IDocumentWithV
* 访
* @returns
*/
export const useRecentDocuments = () => {
export const useRecentDocuments = (organizationId) => {
const { data, error, isLoading, refetch } = useQuery(
DocumentApiDefinition.recent.client(),
getRecentVisitedDocuments,
DocumentApiDefinition.recent.client(organizationId),
() => getRecentVisitedDocuments(organizationId),
{ refetchOnWindowFocus: false, enabled: false }
);
return { data, error, loading: isLoading, refresh: refetch };
@ -67,16 +86,11 @@ export const useDoumentMembers = (documentId, options?: UseQueryOptions<Array<{
);
const addUser = useCallback(
async (userName) => {
async (data) => {
const ret = await HttpClient.request({
method: DocumentApiDefinition.addMemberById.method,
url: DocumentApiDefinition.addMemberById.client(documentId),
data: {
documentId,
userName,
readable: true,
editable: false,
},
data,
});
refetch();
return ret;
@ -85,14 +99,11 @@ export const useDoumentMembers = (documentId, options?: UseQueryOptions<Array<{
);
const updateUser = useCallback(
async (docAuth: DocAuth) => {
async (data) => {
const ret = await HttpClient.request({
method: DocumentApiDefinition.updateMemberById.method,
url: DocumentApiDefinition.updateMemberById.client(documentId),
data: {
documentId,
...docAuth,
},
data,
});
refetch();
return ret;
@ -101,14 +112,11 @@ export const useDoumentMembers = (documentId, options?: UseQueryOptions<Array<{
);
const deleteUser = useCallback(
async (docAuth: DocAuth) => {
async (data) => {
const ret = await HttpClient.request({
method: DocumentApiDefinition.deleteMemberById.method,
url: DocumentApiDefinition.deleteMemberById.client(documentId),
data: {
documentId,
...docAuth,
},
data,
});
refetch();
return ret;
@ -116,7 +124,7 @@ export const useDoumentMembers = (documentId, options?: UseQueryOptions<Array<{
[refetch, documentId]
);
return { users: data, loading: isLoading, error, addUser, updateUser, deleteUser };
return { data, loading: isLoading, error, addUser, updateUser, deleteUser };
};
/**

View File

@ -0,0 +1,174 @@
import { IAuth, IOrganization, IUser, OrganizationApiDefinition } from '@think/domains';
import { event, REFRESH_ORGANIZATIONS, triggerRefreshOrganizations } from 'event';
import { useAsyncLoading } from 'hooks/use-async-loading';
import { useCallback, useEffect, useState } from 'react';
import { useQuery } from 'react-query';
import { HttpClient } from 'services/http-client';
/**
*
* @returns
*/
export const useCreateOrganization = () => {
const [apiWithLoading, loading] = useAsyncLoading((data) =>
HttpClient.request({
method: OrganizationApiDefinition.createOrganization.method,
url: OrganizationApiDefinition.createOrganization.client(),
data,
})
);
return {
create: apiWithLoading,
loading,
};
};
export const getPersonalOrganization = (cookie = null): Promise<IOrganization> => {
return HttpClient.request({
method: OrganizationApiDefinition.getPersonalOrganization.method,
url: OrganizationApiDefinition.getPersonalOrganization.client(),
cookie,
});
};
/**
*
* @returns
*/
export const usePeronalOrganization = () => {
const { data, error, isLoading, refetch } = useQuery(
OrganizationApiDefinition.getPersonalOrganization.client(),
getPersonalOrganization
);
return { data, error, loading: isLoading, refresh: refetch };
};
export const getUserOrganizations = (cookie = null): Promise<Array<IOrganization>> => {
return HttpClient.request({
method: OrganizationApiDefinition.getUserOrganizations.method,
url: OrganizationApiDefinition.getUserOrganizations.client(),
cookie,
});
};
/**
* 访
* @returns
*/
export const useUserOrganizations = () => {
const { data, error, isLoading, refetch } = useQuery(
OrganizationApiDefinition.getUserOrganizations.client(),
getUserOrganizations
);
useEffect(() => {
event.on(REFRESH_ORGANIZATIONS, refetch);
return () => {
event.off(REFRESH_ORGANIZATIONS, refetch);
};
}, [refetch]);
return { data, error, loading: isLoading, refresh: refetch };
};
export const getOrganizationDetail = (id, cookie = null): Promise<IOrganization> => {
return HttpClient.request({
method: OrganizationApiDefinition.getOrganizationDetail.method,
url: OrganizationApiDefinition.getOrganizationDetail.client(id),
cookie,
});
};
/**
*
* @returns
*/
export const useOrganizationDetail = (id) => {
const { data, error, isLoading, refetch } = useQuery(OrganizationApiDefinition.getOrganizationDetail.client(id), () =>
getOrganizationDetail(id)
);
/**
*
* @param data
* @returns
*/
const update = useCallback(
async (data) => {
const res = await HttpClient.request({
method: OrganizationApiDefinition.updateOrganization.method,
url: OrganizationApiDefinition.updateOrganization.client(id),
data,
});
refetch();
triggerRefreshOrganizations();
return res;
},
[refetch, id]
);
return { data, error, loading: isLoading, refresh: refetch, update };
};
export const getOrganizationMembers = (
id,
cookie = null
): Promise<{ data: Array<{ auth: IAuth; user: IUser }>; total: number }> => {
return HttpClient.request({
method: OrganizationApiDefinition.getMembers.method,
url: OrganizationApiDefinition.getMembers.client(id),
cookie,
});
};
/**
*
* @returns
*/
export const useOrganizationMembers = (id) => {
const { data, error, isLoading, refetch } = useQuery(OrganizationApiDefinition.getMembers.client(id), () =>
getOrganizationMembers(id)
);
const addUser = useCallback(
async (data) => {
const ret = await HttpClient.request({
method: OrganizationApiDefinition.addMemberById.method,
url: OrganizationApiDefinition.addMemberById.client(id),
data,
});
refetch();
return ret;
},
[refetch, id]
);
const updateUser = useCallback(
async (data) => {
const ret = await HttpClient.request({
method: OrganizationApiDefinition.updateMemberById.method,
url: OrganizationApiDefinition.updateMemberById.client(id),
data,
});
refetch();
return ret;
},
[refetch, id]
);
const deleteUser = useCallback(
async (data) => {
const ret = await HttpClient.request({
method: OrganizationApiDefinition.deleteMemberById.method,
url: OrganizationApiDefinition.deleteMemberById.client(id),
data,
});
refetch();
return ret;
},
[refetch, id]
);
return { data, error, loading: isLoading, refresh: refetch, addUser, updateUser, deleteUser };
};

View File

@ -7,25 +7,29 @@ import { HttpClient } from 'services/http-client';
export type IWikiWithIsMember = IWiki & { isMember?: boolean };
/**
*
*
* @returns
*/
export const getStarWikis = (cookie = null): Promise<IWikiWithIsMember[]> => {
export const getStarWikisInOrganization = (organizationId, cookie = null): Promise<IWikiWithIsMember[]> => {
return HttpClient.request({
method: StarApiDefinition.wikis.method,
url: StarApiDefinition.wikis.client(),
method: StarApiDefinition.getStarWikisInOrganization.method,
url: StarApiDefinition.getStarWikisInOrganization.client(organizationId),
cookie,
});
};
/**
*
*
* @returns
*/
export const useStarWikis = () => {
const { data, error, isLoading, refetch } = useQuery(StarApiDefinition.wikis.client(), getStarWikis, {
staleTime: 500,
});
export const useStarWikisInOrganization = (organizationId) => {
const { data, error, isLoading, refetch } = useQuery(
StarApiDefinition.getStarWikisInOrganization.client(organizationId),
() => getStarWikisInOrganization(organizationId),
{
staleTime: 500,
}
);
useEffect(() => {
event.on(TOGGLE_STAR_WIKI, refetch);
@ -43,12 +47,13 @@ export const useStarWikis = () => {
* @param wikiId
* @returns
*/
export const getWikiIsStar = (wikiId, cookie = null): Promise<boolean> => {
export const getWikiIsStar = (organizationId, wikiId, cookie = null): Promise<boolean> => {
return HttpClient.request({
method: StarApiDefinition.check.method,
url: StarApiDefinition.check.client(),
method: StarApiDefinition.isStared.method,
url: StarApiDefinition.isStared.client(),
cookie,
data: {
organizationId,
wikiId,
},
});
@ -59,30 +64,33 @@ export const getWikiIsStar = (wikiId, cookie = null): Promise<boolean> => {
* @param wikiId
* @returns
*/
export const toggleStarWiki = (wikiId, cookie = null): Promise<boolean> => {
export const toggleStarWiki = (organizationId, wikiId, cookie = null): Promise<boolean> => {
return HttpClient.request({
method: StarApiDefinition.toggle.method,
url: StarApiDefinition.toggle.client(),
method: StarApiDefinition.toggleStar.method,
url: StarApiDefinition.toggleStar.client(),
cookie,
data: {
organizationId,
wikiId,
},
});
};
/**
*
*
* @param wikiId
* @returns
*/
export const useWikiStarToggle = (wikiId) => {
const { data, error, refetch } = useQuery([StarApiDefinition.check.client(), wikiId], () => getWikiIsStar(wikiId));
export const useWikiStarToggle = (organizationId, wikiId) => {
const { data, error, refetch } = useQuery([StarApiDefinition.toggleStar.client(), organizationId, wikiId], () =>
getWikiIsStar(organizationId, wikiId)
);
const toggle = useCallback(async () => {
await toggleStarWiki(wikiId);
await toggleStarWiki(organizationId, wikiId);
refetch();
triggerToggleStarWiki();
}, [refetch, wikiId]);
}, [refetch, organizationId, wikiId]);
return { data, error, toggle };
};
@ -91,10 +99,10 @@ export const useWikiStarToggle = (wikiId) => {
*
* @returns
*/
export const getStarDocuments = (cookie = null): Promise<IDocument[]> => {
export const getStarDocumentsInOrganization = (organizationId, cookie = null): Promise<IDocument[]> => {
return HttpClient.request({
method: StarApiDefinition.documents.method,
url: StarApiDefinition.documents.client(),
method: StarApiDefinition.getStarDocumentsInOrganization.method,
url: StarApiDefinition.getStarDocumentsInOrganization.client(organizationId),
cookie,
});
};
@ -103,10 +111,14 @@ export const getStarDocuments = (cookie = null): Promise<IDocument[]> => {
*
* @returns
*/
export const useStarDocuments = () => {
const { data, error, isLoading, refetch } = useQuery(StarApiDefinition.documents.client(), getStarDocuments, {
staleTime: 500,
});
export const useStarDocumentsInOrganization = (organizationId) => {
const { data, error, isLoading, refetch } = useQuery(
StarApiDefinition.getStarDocumentsInOrganization.client(organizationId),
() => getStarDocumentsInOrganization(organizationId),
{
staleTime: 500,
}
);
useEffect(() => {
event.on(TOGGLE_STAR_DOUCMENT, refetch);
@ -122,12 +134,13 @@ export const useStarDocuments = () => {
* @param documentId
* @returns
*/
export const getDocumentIsStar = (wikiId, documentId, cookie = null): Promise<boolean> => {
export const getDocumentIsStar = (organizationId, wikiId, documentId, cookie = null): Promise<boolean> => {
return HttpClient.request({
method: StarApiDefinition.check.method,
url: StarApiDefinition.check.client(),
method: StarApiDefinition.isStared.method,
url: StarApiDefinition.isStared.client(),
cookie,
data: {
organizationId,
wikiId,
documentId,
},
@ -139,12 +152,13 @@ export const getDocumentIsStar = (wikiId, documentId, cookie = null): Promise<bo
* @param wikiId
* @returns
*/
export const toggleDocumentStar = (wikiId, documentId, cookie = null): Promise<boolean> => {
export const toggleDocumentStar = (organizationId, wikiId, documentId, cookie = null): Promise<boolean> => {
return HttpClient.request({
method: StarApiDefinition.toggle.method,
url: StarApiDefinition.toggle.client(),
method: StarApiDefinition.toggleStar.method,
url: StarApiDefinition.toggleStar.client(),
cookie,
data: {
organizationId,
wikiId,
documentId,
},
@ -156,18 +170,18 @@ export const toggleDocumentStar = (wikiId, documentId, cookie = null): Promise<b
* @param documentId
* @returns
*/
export const useDocumentStarToggle = (wikiId, documentId, options?: UseQueryOptions<boolean>) => {
export const useDocumentStarToggle = (organizationId, wikiId, documentId, options?: UseQueryOptions<boolean>) => {
const { data, error, refetch } = useQuery(
[StarApiDefinition.check.client(), wikiId, documentId],
() => getDocumentIsStar(wikiId, documentId),
[StarApiDefinition.isStared.client(), organizationId, wikiId, documentId],
() => getDocumentIsStar(organizationId, wikiId, documentId),
options
);
const toggle = useCallback(async () => {
await toggleDocumentStar(wikiId, documentId);
await toggleDocumentStar(organizationId, wikiId, documentId);
refetch();
triggerToggleStarDocument();
}, [refetch, wikiId, documentId]);
}, [refetch, organizationId, wikiId, documentId]);
return { data, error, toggle };
};
@ -176,12 +190,13 @@ export const useDocumentStarToggle = (wikiId, documentId, options?: UseQueryOpti
*
* @returns
*/
export const getWikiStarDocuments = (wikiId, cookie = null): Promise<IWikiWithIsMember[]> => {
export const getStarDocumentsInWiki = (organizationId, wikiId, cookie = null): Promise<IWikiWithIsMember[]> => {
return HttpClient.request({
method: StarApiDefinition.wikiDocuments.method,
url: StarApiDefinition.wikiDocuments.client(),
method: StarApiDefinition.getStarDocumentsInWiki.method,
url: StarApiDefinition.getStarDocumentsInWiki.client(),
cookie,
params: {
organizationId,
wikiId,
},
});
@ -191,10 +206,10 @@ export const getWikiStarDocuments = (wikiId, cookie = null): Promise<IWikiWithIs
*
* @returns
*/
export const useWikiStarDocuments = (wikiId) => {
export const useStarDocumentsInWiki = (organizationId, wikiId) => {
const { data, error, isLoading, refetch } = useQuery(
[StarApiDefinition.wikiDocuments.client(), wikiId],
() => getWikiStarDocuments(wikiId),
[StarApiDefinition.getStarDocumentsInWiki.client(), organizationId, wikiId],
() => getStarDocumentsInWiki(organizationId, wikiId),
{
staleTime: 500,
}

View File

@ -109,7 +109,7 @@ export const useUser = () => {
refetch();
setStorage('user', JSON.stringify(user));
user.token && setStorage('token,', user.token);
const next = router.query?.redirect || '/';
const next = router.query?.redirect || '/app';
Router.replace(next as string);
});
},

View File

@ -1,4 +1,4 @@
import { IDocument, IUser, IWiki, IWikiUser, WikiApiDefinition } from '@think/domains';
import { IAuth, IDocument, IUser, IWiki, IWikiUser, WikiApiDefinition } from '@think/domains';
import { event, REFRESH_TOCS } from 'event';
import { useCallback, useEffect, useState } from 'react';
import { useQuery } from 'react-query';
@ -6,20 +6,16 @@ import { HttpClient } from 'services/http-client';
export type ICreateWiki = Pick<IWiki, 'name' | 'description'>;
export type IUpdateWiki = Partial<IWiki>;
export type IWikiUserOpeateData = {
userName: Pick<IUser, 'name'>;
userRole: Pick<IWikiUser, 'userRole'>;
};
export type IWikiWithIsMember = IWiki & { isMember: boolean };
/**
*
* @returns
*/
export const getAllWikis = (cookie = null): Promise<{ data: IWiki[]; total: number }> => {
export const getAllWikis = (organizationId, cookie = null): Promise<{ data: IWiki[]; total: number }> => {
return HttpClient.request({
method: WikiApiDefinition.getAllWikis.method,
url: WikiApiDefinition.getAllWikis.client(),
url: WikiApiDefinition.getAllWikis.client(organizationId),
cookie,
});
};
@ -28,8 +24,10 @@ export const getAllWikis = (cookie = null): Promise<{ data: IWiki[]; total: numb
*
* @returns
*/
export const useAllWikis = () => {
const { data, error, isLoading } = useQuery(WikiApiDefinition.getAllWikis.client(), getAllWikis);
export const useAllWikis = (organizationId) => {
const { data, error, isLoading } = useQuery(WikiApiDefinition.getAllWikis.client(organizationId), () =>
getAllWikis(organizationId)
);
const list = (data && data.data) || [];
const total = (data && data.total) || 0;
return { data: list, total, error, loading: isLoading };
@ -39,10 +37,10 @@ export const useAllWikis = () => {
*
* @returns
*/
export const getJoinWikis = (cookie = null): Promise<{ data: IWiki[]; total: number }> => {
export const getJoinWikis = (organizationId, cookie = null): Promise<{ data: IWiki[]; total: number }> => {
return HttpClient.request({
method: WikiApiDefinition.getJoinWikis.method,
url: WikiApiDefinition.getJoinWikis.client(),
url: WikiApiDefinition.getJoinWikis.client(organizationId),
cookie,
});
};
@ -51,8 +49,10 @@ export const getJoinWikis = (cookie = null): Promise<{ data: IWiki[]; total: num
*
* @returns
*/
export const useJoinWikis = () => {
const { data, error, isLoading } = useQuery(WikiApiDefinition.getJoinWikis.client(), getJoinWikis);
export const useJoinWikis = (organizationId) => {
const { data, error, isLoading } = useQuery(WikiApiDefinition.getJoinWikis.client(organizationId), () =>
getJoinWikis(organizationId)
);
const list = (data && data.data) || [];
const total = (data && data.total) || 0;
@ -63,10 +63,10 @@ export const useJoinWikis = () => {
*
* @returns
*/
export const getOwnWikis = (cookie = null): Promise<{ data: IWiki[]; total: number }> => {
export const getOwnWikis = (organizationId, cookie = null): Promise<{ data: IWiki[]; total: number }> => {
return HttpClient.request({
method: WikiApiDefinition.getOwnWikis.method,
url: WikiApiDefinition.getOwnWikis.client(),
url: WikiApiDefinition.getOwnWikis.client(organizationId),
cookie,
});
};
@ -75,20 +75,25 @@ export const getOwnWikis = (cookie = null): Promise<{ data: IWiki[]; total: numb
*
* @returns
*/
export const useOwnWikis = () => {
const { data, error, refetch } = useQuery(WikiApiDefinition.getOwnWikis.client(), getOwnWikis);
export const useOwnWikis = (organizationId) => {
const { data, error, refetch } = useQuery(WikiApiDefinition.getOwnWikis.client(organizationId), () =>
getOwnWikis(organizationId)
);
const createWiki = useCallback(
async (data: ICreateWiki) => {
const res = await HttpClient.request({
const res = await HttpClient.request<IWiki>({
method: WikiApiDefinition.add.method,
url: WikiApiDefinition.add.client(),
data,
data: {
organizationId,
...data,
},
});
refetch();
return res;
},
[refetch]
[organizationId, refetch]
);
/**
@ -311,7 +316,10 @@ export const useWikiDocuments = (wikiId) => {
* @param cookie
* @returns
*/
export const getWikiMembers = (wikiId, cookie = null): Promise<IWikiUser[]> => {
export const getWikiMembers = (
wikiId,
cookie = null
): Promise<{ data: Array<{ auth: IAuth; user: IUser }>; total: number }> => {
return HttpClient.request({
method: WikiApiDefinition.getMemberById.method,
url: WikiApiDefinition.getMemberById.client(wikiId),
@ -330,7 +338,7 @@ export const useWikiMembers = (wikiId) => {
);
const addUser = useCallback(
async (data: IWikiUserOpeateData) => {
async (data) => {
const ret = await HttpClient.request({
method: WikiApiDefinition.addMemberById.method,
url: WikiApiDefinition.addMemberById.client(wikiId),
@ -343,7 +351,7 @@ export const useWikiMembers = (wikiId) => {
);
const updateUser = useCallback(
async (data: IWikiUserOpeateData) => {
async (data) => {
const ret = await HttpClient.request({
method: WikiApiDefinition.updateMemberById.method,
url: WikiApiDefinition.updateMemberById.client(wikiId),
@ -356,7 +364,7 @@ export const useWikiMembers = (wikiId) => {
);
const deleteUser = useCallback(
async (data: IWikiUserOpeateData) => {
async (data) => {
const ret = await HttpClient.request({
method: WikiApiDefinition.deleteMemberById.method,
url: WikiApiDefinition.deleteMemberById.client(wikiId),
@ -368,7 +376,7 @@ export const useWikiMembers = (wikiId) => {
[refetch, wikiId]
);
return { users: data, loading: isLoading, error, addUser, updateUser, deleteUser };
return { data, loading: isLoading, error, addUser, updateUser, deleteUser };
};
/**

View File

@ -3,6 +3,7 @@ import { EventEmitter } from 'helpers/event-emitter';
export const event = new EventEmitter();
export const REFRESH_ORGANIZATIONS = 'REFRESH_ORGANIZATIONS'; // 刷新组织列表
export const REFRESH_TOCS = `REFRESH_TOCS`; // 刷新知识库目录
export const CREATE_DOCUMENT = `CREATE_DOCUMENT`;
export const TOGGLE_STAR_WIKI = `TOGGLE_STAR_WIKI`; // 收藏或取消收藏知识库
@ -60,3 +61,7 @@ export const triggerToggleStarWiki = () => {
export const triggerToggleStarDocument = () => {
event.emit(TOGGLE_STAR_DOUCMENT);
};
export const triggerRefreshOrganizations = () => {
event.emit(REFRESH_ORGANIZATIONS);
};

View File

@ -2,5 +2,6 @@ import { Router, useRouter } from 'next/router';
export function useRouterQuery<T extends Router['query']>() {
const router = useRouter();
return router.query as T;
}

View File

@ -0,0 +1,46 @@
.wrap {
display: flex;
flex-direction: column;
height: 100%;
background-color: var(--semi-color-nav-bg);
.contentWrap {
display: flex;
flex: 1;
flex-wrap: nowrap;
overflow: hidden;
> div {
height: calc(100% - 60px) !important;
:global {
.Pane2 {
height: 100%;
overflow: auto;
}
}
}
.leftWrap {
position: relative;
border-right: 1px solid var(--semi-color-border);
.leftContentWrap {
height: 100%;
}
.collapseBtn {
position: absolute;
top: 1rem;
right: 0;
z-index: 100;
transform: translate(50%);
}
}
.rightWrap {
height: 100%;
overflow: auto;
}
}
}

View File

@ -0,0 +1,49 @@
import { IconChevronLeft, IconChevronRight } from '@douyinfe/semi-icons';
import { Button, Layout as SemiLayout } from '@douyinfe/semi-ui';
import cls from 'classnames';
import { throttle } from 'helpers/throttle';
import { useDragableWidth } from 'hooks/use-dragable-width';
import React, { useMemo } from 'react';
import SplitPane from 'react-split-pane';
import { AppRouterHeader } from '../app-router-header';
import styles from './index.module.scss';
const { Sider, Content } = SemiLayout;
interface IProps {
leftNode: React.ReactNode;
rightNode: React.ReactNode;
}
export const AppDoubleColumnLayout: React.FC<IProps> = ({ leftNode, rightNode }) => {
const { minWidth, maxWidth, width, isCollapsed, updateWidth, toggleCollapsed } = useDragableWidth();
const debounceUpdate = useMemo(() => throttle(updateWidth, 200), [updateWidth]);
return (
<SemiLayout className={styles.wrap}>
<AppRouterHeader />
<SemiLayout className={styles.contentWrap}>
<SplitPane minSize={minWidth} maxSize={maxWidth} size={width} onChange={debounceUpdate}>
<Sider style={{ width: '100%', height: '100%' }} className={styles.leftWrap}>
<Button
size="small"
icon={isCollapsed ? <IconChevronRight /> : <IconChevronLeft />}
className={cls(styles.collapseBtn, isCollapsed && styles.isCollapsed)}
onClick={toggleCollapsed}
/>
<div
style={{
opacity: isCollapsed ? 0 : 1,
}}
className={styles.leftContentWrap}
>
{leftNode}
</div>
</Sider>
<Content className={styles.rightWrap}>{rightNode}</Content>
</SplitPane>
</SemiLayout>
</SemiLayout>
);
};

View File

@ -0,0 +1,42 @@
.titleWrap {
padding: 8px 16px;
}
.itemsWrap {
max-height: 240px;
overflow: auto;
}
.itemWrap {
border-radius: var(--semi-border-radius-small);
&:hover {
color: var(--semi-color-text-0);
background-color: var(--semi-color-fill-0);
}
.item {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 8px 16px;
.leftWrap {
display: flex;
align-items: center;
padding-left: 8px;
color: var(--semi-color-primary);
svg {
fill: var(--semi-color-primary);
color: var(--semi-color-primary);
}
> div {
display: flex;
flex-direction: column;
}
}
}
}

View File

@ -0,0 +1,145 @@
import { IconMenu } from '@douyinfe/semi-icons';
import { Button, Dropdown, Layout as SemiLayout, Nav, Space } from '@douyinfe/semi-ui';
import { Message } from 'components/message';
import { OrganizationSwitcher } from 'components/organization/switcher';
import { Search } from 'components/search';
import { Theme } from 'components/theme';
import { User } from 'components/user';
import { WikiOrDocumentCreator } from 'components/wiki-or-document-creator';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useRouterQuery } from 'hooks/use-router-query';
import { useToggle } from 'hooks/use-toggle';
import Router, { useRouter } from 'next/router';
import React, { useMemo } from 'react';
import { Recent, RecentModal } from './recent';
import { Wiki, WikiModal } from './wiki';
const { Header: SemiHeader } = SemiLayout;
export const AppRouterHeader: React.FC = () => {
const { organizationId } = useRouterQuery<{ organizationId: string }>();
const { pathname } = useRouter();
const { isMobile } = IsOnMobile.useHook();
const [dropdownVisible, toggleDropdownVisible] = useToggle(false);
const [recentModalVisible, toggleRecentModalVisible] = useToggle(false);
const [wikiModalVisible, toggleWikiModalVisible] = useToggle(false);
const menus = useMemo(
() => [
{
itemKey: '/app/org/[organizationId]',
text: '主页',
onClick: () => {
Router.push({
pathname: `/app/org/${organizationId}`,
});
},
},
{
itemKey: 'recent',
text: <Recent />,
},
{
itemKey: '/app/org/[organizationId]/wiki',
text: <Wiki />,
},
{
itemKey: '/app/org/[organizationId]/star',
text: '星标',
onClick: () => {
Router.push({
pathname: `/app/org/${organizationId}/star`,
});
},
},
{
itemKey: '/app/org/[organizationId]/setting',
text: '设置',
onClick: () => {
Router.push({
pathname: `/app/org/${organizationId}/setting`,
});
},
},
],
[organizationId]
);
return (
<SemiHeader>
{isMobile ? (
<Nav
mode="horizontal"
style={{ overflow: 'auto' }}
header={
<Space>
<OrganizationSwitcher key={organizationId} />
<RecentModal visible={recentModalVisible} toggleVisible={toggleRecentModalVisible} />
<WikiModal visible={wikiModalVisible} toggleVisible={toggleWikiModalVisible} />
<Dropdown
trigger="click"
position="bottomRight"
visible={dropdownVisible}
onVisibleChange={toggleDropdownVisible}
render={
// @ts-ignore
<Dropdown.Menu onClick={toggleDropdownVisible}>
{menus.slice(0, 1).map((menu) => {
return (
<Dropdown.Item key={menu.itemKey} onClick={menu.onClick}>
{menu.text}
</Dropdown.Item>
);
})}
<Dropdown.Item onClick={toggleRecentModalVisible}></Dropdown.Item>
<Dropdown.Item onClick={toggleWikiModalVisible}></Dropdown.Item>
{menus.slice(3).map((menu) => {
return (
<Dropdown.Item key={menu.itemKey} onClick={menu.onClick}>
{menu.text}
</Dropdown.Item>
);
})}
</Dropdown.Menu>
}
>
<Button icon={<IconMenu />} type="tertiary" theme="borderless" onMouseDown={toggleDropdownVisible} />
</Dropdown>
</Space>
}
footer={
<Space>
<WikiOrDocumentCreator />
<Search />
<Message />
<Theme />
<User />
</Space>
}
></Nav>
) : (
<Nav
mode="horizontal"
style={{ overflow: 'auto' }}
header={
<Space style={{ marginRight: 12 }}>
<OrganizationSwitcher key={organizationId} />
</Space>
}
selectedKeys={[pathname || '/']}
items={menus}
footer={
<Space>
<WikiOrDocumentCreator />
<Search />
<Message />
<Theme />
<User />
</Space>
}
></Nav>
)}
</SemiHeader>
);
};

View File

@ -8,6 +8,7 @@ import { LocaleTime } from 'components/locale-time';
import { useRecentDocuments } from 'data/document';
import { useToggle } from 'hooks/use-toggle';
import Link from 'next/link';
import { useRouter } from 'next/router';
import React, { useEffect } from 'react';
import styles from './index.module.scss';
@ -16,7 +17,8 @@ import { Placeholder } from './placeholder';
const { Text } = Typography;
export const RecentDocs = ({ visible }) => {
const { data: recentDocs, loading, error, refresh } = useRecentDocuments();
const { query } = useRouter();
const { data: recentDocs, loading, error, refresh } = useRecentDocuments(query.organizationId);
useEffect(() => {
if (visible) {
@ -40,8 +42,9 @@ export const RecentDocs = ({ visible }) => {
<div className={styles.itemWrap} key={doc.id}>
<Link
href={{
pathname: '/wiki/[wikiId]/document/[documentId]',
pathname: '/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]',
query: {
organizationId: doc.organizationId,
wikiId: doc.wikiId,
documentId: doc.id,
},
@ -62,7 +65,11 @@ export const RecentDocs = ({ visible }) => {
</div>
</div>
<div className={styles.rightWrap}>
<DocumentStar wikiId={doc.wikiId} documentId={doc.id} />
<DocumentStar
organizationId={doc.organizationId}
wikiId={doc.wikiId}
documentId={doc.id}
/>
</div>
</a>
</Link>

View File

@ -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 { useStarWikis } from 'data/star';
import { useStarWikisInOrganization } from 'data/star';
import { useWikiDetail } from 'data/wiki';
import Link from 'next/link';
import { useRouter } from 'next/router';
@ -16,7 +16,12 @@ const { Text } = Typography;
const WikiContent = () => {
const { query } = useRouter();
const { data: starWikis, loading, error, refresh: refreshStarWikis } = useStarWikis();
const {
data: starWikis,
loading,
error,
refresh: refreshStarWikis,
} = useStarWikisInOrganization(query.organizationId);
const { data: currentWiki } = useWikiDetail(query.wikiId);
return (
@ -31,8 +36,9 @@ const WikiContent = () => {
<div className={styles.itemWrap}>
<Link
href={{
pathname: '/wiki/[wikiId]',
pathname: '/app/org/[organizationId]/wiki/[wikiId]',
query: {
organizationId: currentWiki.organizationId,
wikiId: currentWiki.id,
},
}}
@ -59,7 +65,11 @@ const WikiContent = () => {
</div>
</div>
<div className={styles.rightWrap}>
<WikiStar wikiId={currentWiki.id} onChange={refreshStarWikis} />
<WikiStar
organizationId={currentWiki.organizationId}
wikiId={currentWiki.id}
onChange={refreshStarWikis}
/>
</div>
</a>
</Link>
@ -84,8 +94,9 @@ const WikiContent = () => {
<div className={styles.itemWrap} key={wiki.id}>
<Link
href={{
pathname: '/wiki/[wikiId]',
pathname: '/app/org/[organizationId]/wiki/[wikiId]',
query: {
organizationId: wiki.organizationId,
wikiId: wiki.id,
},
}}
@ -112,7 +123,11 @@ const WikiContent = () => {
</div>
</div>
<div className={styles.rightWrap}>
<WikiStar wikiId={wiki.id} onChange={refreshStarWikis} />
<WikiStar
organizationId={wiki.organizationId}
wikiId={wiki.id}
onChange={refreshStarWikis}
/>
</div>
</a>
</Link>
@ -130,7 +145,10 @@ const WikiContent = () => {
<div className={styles.itemWrap}>
<Link
href={{
pathname: '/wiki',
pathname: '/app/org/[organizationId]/wiki',
query: {
organizationId: query.organizationId,
},
}}
>
<a className={styles.item} style={{ padding: '12px 16px' }}>

View File

@ -0,0 +1,11 @@
.wrap {
display: flex;
flex-direction: column;
height: 100%;
background-color: var(--semi-color-nav-bg);
.contentWrap {
flex: 1;
overflow: auto;
}
}

View File

@ -0,0 +1,25 @@
import { Layout as SemiLayout } from '@douyinfe/semi-ui';
import React from 'react';
import { AppRouterHeader } from '../app-router-header';
import styles from './index.module.scss';
const { Content } = SemiLayout;
export const AppSingleColumnLayout: React.FC = ({ children }) => {
return (
<SemiLayout className={styles.wrap}>
<AppRouterHeader />
<SemiLayout className={styles.contentWrap}>
<Content
style={{
padding: '16px 24px',
backgroundColor: 'var(--semi-color-bg-0)',
}}
>
<div>{children}</div>
</Content>
</SemiLayout>
</SemiLayout>
);
};

View File

@ -1,22 +1,13 @@
import { IconMenu } from '@douyinfe/semi-icons';
import { Button, Dropdown, Layout as SemiLayout, Nav, Space, Typography } from '@douyinfe/semi-ui';
import { LogoImage, LogoText } from 'components/logo';
import { Layout as SemiLayout, Nav, Space } from '@douyinfe/semi-ui';
import { Message } from 'components/message';
import { Search } from 'components/search';
import { OrganizationPublicSwitcher } from 'components/organization/public-switcher';
import { Theme } from 'components/theme';
import { User } from 'components/user';
import { WikiOrDocumentCreator } from 'components/wiki-or-document-creator';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useToggle } from 'hooks/use-toggle';
import { useWindowSize } from 'hooks/use-window-size';
import Router, { useRouter } from 'next/router';
import React from 'react';
import { Recent, RecentModal } from './recent';
import { Wiki, WikiModal } from './wiki';
const { Header: SemiHeader } = SemiLayout;
const { Text } = Typography;
const menus = [
{
@ -28,32 +19,6 @@ const menus = [
});
},
},
{
itemKey: '/recent',
text: <Recent />,
},
{
itemKey: '/wiki',
text: <Wiki />,
},
{
itemKey: '/star',
text: '星标',
onClick: () => {
Router.push({
pathname: `/star`,
});
},
},
{
itemKey: '/template',
text: '模板',
onClick: () => {
Router.push({
pathname: `/template`,
});
},
},
{
itemKey: '/find',
text: '发现',
@ -68,10 +33,6 @@ const menus = [
export const RouterHeader: React.FC = () => {
const { pathname } = useRouter();
const { isMobile } = IsOnMobile.useHook();
const { width } = useWindowSize();
const [dropdownVisible, toggleDropdownVisible] = useToggle(false);
const [recentModalVisible, toggleRecentModalVisible] = useToggle(false);
const [wikiModalVisible, toggleWikiModalVisible] = useToggle(false);
return (
<SemiHeader>
@ -81,45 +42,11 @@ export const RouterHeader: React.FC = () => {
style={{ overflow: 'auto' }}
header={
<Space>
<LogoImage />
<RecentModal visible={recentModalVisible} toggleVisible={toggleRecentModalVisible} />
<WikiModal visible={wikiModalVisible} toggleVisible={toggleWikiModalVisible} />
<Dropdown
trigger="click"
position="bottomRight"
visible={dropdownVisible}
onVisibleChange={toggleDropdownVisible}
render={
// @ts-ignore
<Dropdown.Menu onClick={toggleDropdownVisible}>
{menus.slice(0, 1).map((menu) => {
return (
<Dropdown.Item key={menu.itemKey} onClick={menu.onClick}>
{menu.text}
</Dropdown.Item>
);
})}
<Dropdown.Item onClick={toggleRecentModalVisible}></Dropdown.Item>
<Dropdown.Item onClick={toggleWikiModalVisible}></Dropdown.Item>
{menus.slice(3).map((menu) => {
return (
<Dropdown.Item key={menu.itemKey} onClick={menu.onClick}>
{menu.text}
</Dropdown.Item>
);
})}
</Dropdown.Menu>
}
>
<Button icon={<IconMenu />} type="tertiary" theme="borderless" onMouseDown={toggleDropdownVisible} />
</Dropdown>
<OrganizationPublicSwitcher />
</Space>
}
footer={
<Space>
<WikiOrDocumentCreator />
<Search />
<Message />
<Theme />
<User />
</Space>
@ -131,16 +58,13 @@ export const RouterHeader: React.FC = () => {
style={{ overflow: 'auto' }}
header={
<Space style={{ marginRight: 12 }}>
<LogoImage />
{width >= 890 && <LogoText />}
<OrganizationPublicSwitcher />
</Space>
}
selectedKeys={[pathname || '/']}
items={menus}
footer={
<Space>
<WikiOrDocumentCreator />
<Search />
<Message />
<Theme />
<User />

View File

@ -0,0 +1,35 @@
import { Spin } from '@douyinfe/semi-ui';
import { usePeronalOrganization } from 'data/organization';
import { SingleColumnLayout } from 'layouts/single-column';
import Router from 'next/router';
import { useEffect } from 'react';
const Page = () => {
const { data: organization } = usePeronalOrganization();
useEffect(() => {
if (!organization) return;
Router.replace({
pathname: `/app/org/[organizationId]`,
query: { organizationId: organization.id },
});
}, [organization]);
return (
<SingleColumnLayout>
<div className="container">
<div
style={{
padding: '10vh',
textAlign: 'center',
}}
>
<Spin></Spin>
</div>
</div>
</SingleColumnLayout>
);
};
export default Page;

View File

@ -0,0 +1,200 @@
import { Avatar, Button, List, Table, Typography } from '@douyinfe/semi-ui';
import { DocumentApiDefinition, IDocument, IOrganization, StarApiDefinition } from '@think/domains';
import { DataRender } from 'components/data-render';
import { DocumentActions } from 'components/document/actions';
import { Empty } from 'components/empty';
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 { getRecentVisitedDocuments, useRecentDocuments } from 'data/document';
import { getStarWikisInOrganization, useStarWikisInOrganization } from 'data/star';
import { useRouterQuery } from 'hooks/use-router-query';
import { useToggle } from 'hooks/use-toggle';
import { AppSingleColumnLayout } from 'layouts/app-single-column';
import type { NextPage } from 'next';
import Link from 'next/link';
import React, { useEffect, useMemo } from 'react';
import { serverPrefetcher } from 'services/server-prefetcher';
import styles from './index.module.scss';
const { Title } = Typography;
const { Column } = Table;
const grid = {
gutter: 16,
xs: 24,
sm: 12,
md: 12,
lg: 8,
xl: 8,
};
const RecentDocs = () => {
const { organizationId } = useRouterQuery<{ organizationId: string }>();
const { data, error, loading, refresh } = useRecentDocuments(organizationId);
const columns = useMemo(
() => [
<Column
title="标题"
dataIndex="title"
key="title"
width={200}
render={(_, document: IDocument) => {
return (
<Link
href={{
pathname: `/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]`,
query: { organizationId: document.organizationId, wikiId: document.wikiId, documentId: document.id },
}}
>
<a style={{ color: 'inherit', textDecoration: 'none' }}>{document.title}</a>
</Link>
);
}}
/>,
<Column width={100} title="阅读量" dataIndex="views" key="views" />,
<Column
title="创建者"
dataIndex="createUser"
key="createUser"
width={160}
render={(createUser) => {
return (
<span>
<Avatar size="extra-extra-small" src={createUser.avatar} style={{ marginRight: 4 }}>
{createUser.name.slice(0, 1)}
</Avatar>
{createUser.name}
</span>
);
}}
/>,
<Column
width={120}
title="访问时间"
dataIndex="visitedAt"
key="visitedAt"
render={(date) => <LocaleTime date={date} />}
/>,
<Column
title="操作"
dataIndex="operate"
key="operate"
width={80}
render={(_, document) => (
<DocumentActions
organizationId={document.organizationId}
wikiId={document.wikiId}
documentId={document.id}
onDelete={refresh}
showCreateDocument
hideDocumentVersion
hideDocumentStyle
/>
)}
/>,
],
[refresh]
);
useEffect(() => {
refresh();
}, [refresh]);
return (
<>
<Title heading={3} style={{ margin: '24px 0 0' }}>
访
</Title>
<DataRender
loading={loading}
loadingContent={
<Table dataSource={[]} loading={true} pagination={false} size="small" style={{ marginTop: 16 }}>
{columns}
</Table>
}
error={error}
normalContent={() =>
data && data.length ? (
<Table dataSource={data} loading={loading} pagination={false} size="small" style={{ marginTop: 16 }}>
{columns}
</Table>
) : (
<Empty message="最近访问的文档会出现在此处" />
)
}
/>
</>
);
};
const Page: NextPage<{ organizationId: IOrganization['id'] }> = ({ organizationId }) => {
const [visible, toggleVisible] = useToggle(false);
const { data: staredWikis, loading, error, refresh } = useStarWikisInOrganization(organizationId);
return (
<AppSingleColumnLayout>
<Seo title="主页" />
<div className="container">
<div className={styles.titleWrap}>
<Title heading={3} style={{ margin: '8px 0' }}>
访
</Title>
<>
<Button onClick={toggleVisible}></Button>
<WikiCreator visible={visible} toggleVisible={toggleVisible} />
</>
</div>
<DataRender
loading={loading}
loadingContent={() => (
<List
grid={grid}
dataSource={[1, 2, 3]}
renderItem={() => (
<List.Item>
<WikiPinCardPlaceholder />
</List.Item>
)}
/>
)}
error={error}
normalContent={() => (
<List
grid={grid}
dataSource={staredWikis}
renderItem={(wiki) => (
<List.Item>
<WikiPinCard wiki={wiki} />
</List.Item>
)}
emptyContent={<Empty message="收藏的知识库会出现在此处" />}
/>
)}
/>
<RecentDocs />
</div>
</AppSingleColumnLayout>
);
};
Page.getInitialProps = async (ctx) => {
const { organizationId } = ctx.query;
const props = await serverPrefetcher(ctx, [
{
url: StarApiDefinition.getStarWikisInOrganization.client(organizationId),
action: (cookie) => getStarWikisInOrganization(organizationId, cookie),
},
{
url: DocumentApiDefinition.recent.client(organizationId),
action: (cookie) => getRecentVisitedDocuments(organizationId, cookie),
},
]);
return { ...props, organizationId } as any;
};
export default Page;

View File

@ -0,0 +1,43 @@
import { IOrganization } from '@think/domains';
import { OrganizationSetting } from 'components/organization/setting';
import { AppSingleColumnLayout } from 'layouts/app-single-column';
import { NextPage } from 'next';
import Router, { useRouter } from 'next/router';
import React, { useCallback } from 'react';
interface IProps {
organizationId: IOrganization['id'];
}
const Page: NextPage<IProps> = ({ organizationId }) => {
const { query = {} } = useRouter();
const { tab = 'base' } = query as {
tab?: string;
};
const navigate = useCallback(
(tab = 'base') => {
console.log(tab);
Router.push({
pathname: `/app/org/[organizationId]/setting`,
query: { organizationId, tab },
});
},
[organizationId]
);
return (
<AppSingleColumnLayout>
<div className="container" style={{ padding: '16px 24px' }}>
<OrganizationSetting organizationId={organizationId} tab={tab} onNavigate={navigate} />
</div>
</AppSingleColumnLayout>
);
};
Page.getInitialProps = async (ctx) => {
const { organizationId } = ctx.query;
return { organizationId } as IProps;
};
export default Page;

View File

@ -5,8 +5,14 @@ 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 { getStarDocuments, getStarWikis, useStarDocuments, useStarWikis } from 'data/star';
import { SingleColumnLayout } from 'layouts/single-column';
import {
getStarDocumentsInOrganization,
getStarWikisInOrganization,
useStarDocumentsInOrganization,
useStarWikisInOrganization,
} from 'data/star';
import { useRouterQuery } from 'hooks/use-router-query';
import { AppSingleColumnLayout } from 'layouts/app-single-column';
import type { NextPage } from 'next';
import React from 'react';
import { serverPrefetcher } from 'services/server-prefetcher';
@ -25,7 +31,8 @@ const grid = {
};
const StarDocs = () => {
const { data: docs, loading, error } = useStarDocuments();
const { organizationId } = useRouterQuery<{ organizationId: string }>();
const { data: docs, loading, error } = useStarDocumentsInOrganization(organizationId);
return (
<DataRender
@ -59,7 +66,8 @@ const StarDocs = () => {
};
const StarWikis = () => {
const { data, loading, error } = useStarWikis();
const { organizationId } = useRouterQuery<{ organizationId: string }>();
const { data, loading, error } = useStarWikisInOrganization(organizationId);
return (
<DataRender
@ -94,7 +102,7 @@ const StarWikis = () => {
const Page: NextPage = () => {
return (
<SingleColumnLayout>
<AppSingleColumnLayout>
<Seo title="主页" />
<div className="container">
<div className={styles.titleWrap}>
@ -111,14 +119,22 @@ const Page: NextPage = () => {
</div>
<StarDocs />
</div>
</SingleColumnLayout>
</AppSingleColumnLayout>
);
};
Page.getInitialProps = async (ctx) => {
const { organizationId } = ctx.query;
const props = await serverPrefetcher(ctx, [
{ url: StarApiDefinition.wikis.client(), action: (cookie) => getStarWikis(cookie) },
{ url: StarApiDefinition.documents.client(), action: (cookie) => getStarDocuments(cookie) },
{
url: StarApiDefinition.getStarWikisInOrganization.client(organizationId),
action: (cookie) => getStarWikisInOrganization(organizationId, cookie),
},
{
url: StarApiDefinition.getStarDocumentsInOrganization.client(organizationId),
action: (cookie) => getStarDocumentsInOrganization(organizationId, cookie),
},
]);
return props;
};

View File

@ -3,7 +3,7 @@ import { DocumentReader } from 'components/document/reader';
import { WikiTocs } from 'components/wiki/tocs';
import { getDocumentDetail } from 'data/document';
import { getWikiTocs } from 'data/wiki';
import { DoubleColumnLayout } from 'layouts/double-column';
import { AppDoubleColumnLayout } from 'layouts/app-double-column';
import { NextPage } from 'next';
import React from 'react';
import { serverPrefetcher } from 'services/server-prefetcher';
@ -15,7 +15,7 @@ interface IProps {
const Page: NextPage<IProps> = ({ wikiId, documentId }) => {
return (
<DoubleColumnLayout
<AppDoubleColumnLayout
leftNode={<WikiTocs wikiId={wikiId} documentId={documentId} />}
rightNode={<DocumentReader key={documentId} documentId={documentId} />}
/>

View File

@ -4,7 +4,7 @@ import { DataRender } from 'components/data-render';
import { DocumentReader } from 'components/document/reader';
import { WikiTocs } from 'components/wiki/tocs';
import { getWikiDetail, getWikiTocs, useWikiDetail } from 'data/wiki';
import { DoubleColumnLayout } from 'layouts/double-column';
import { AppDoubleColumnLayout } from 'layouts/app-double-column';
import { NextPage } from 'next';
import React from 'react';
import { serverPrefetcher } from 'services/server-prefetcher';
@ -17,7 +17,7 @@ const Page: NextPage<IProps> = ({ wikiId }) => {
const { data: wiki, loading, error } = useWikiDetail(wikiId);
return (
<DoubleColumnLayout
<AppDoubleColumnLayout
leftNode={<WikiTocs wikiId={wikiId} />}
rightNode={
<DataRender

View File

@ -2,7 +2,7 @@ import { IWiki, WikiApiDefinition } from '@think/domains';
import { WikiSetting } from 'components/wiki/setting';
import { WikiTocs } from 'components/wiki/tocs';
import { getWikiMembers, getWikiTocs } from 'data/wiki';
import { DoubleColumnLayout } from 'layouts/double-column';
import { AppDoubleColumnLayout } from 'layouts/app-double-column';
import { NextPage } from 'next';
import Router, { useRouter } from 'next/router';
import React, { useCallback } from 'react';
@ -22,16 +22,16 @@ const Page: NextPage<IProps> = ({ wikiId }) => {
(tab = 'base') => {
return () => {
Router.push({
pathname: `/wiki/${wikiId}/setting`,
query: { tab },
pathname: `/app/org/[organizationId]/wiki/[wikiId]/setting`,
query: { organizationId: query.organizationId, wikiId, tab },
});
};
},
[wikiId]
[query, wikiId]
);
return (
<DoubleColumnLayout
<AppDoubleColumnLayout
leftNode={<WikiTocs wikiId={wikiId} />}
rightNode={
<div style={{ padding: '16px 24px' }}>
@ -56,4 +56,5 @@ Page.getInitialProps = async (ctx) => {
]);
return { ...res, wikiId } as IProps;
};
export default Page;

View File

@ -0,0 +1,12 @@
.wikiItemWrap {
padding: 12px 16px !important;
margin: 8px 2px;
cursor: pointer;
background-color: var(--semi-color-bg-2);
border: 1px solid var(--semi-color-border) !important;
}
.titleWrap {
display: flex;
justify-content: space-between;
}

View File

@ -6,8 +6,9 @@ import { Seo } from 'components/seo';
import { WikiCard, WikiCardPlaceholder } from 'components/wiki/card';
import { WikiCreator } from 'components/wiki-creator';
import { getAllWikis, getJoinWikis, getOwnWikis, useAllWikis, useJoinWikis, useOwnWikis } from 'data/wiki';
import { useRouterQuery } from 'hooks/use-router-query';
import { CreateWikiIllustration } from 'illustrations/create-wiki';
import { SingleColumnLayout } from 'layouts/single-column';
import { AppSingleColumnLayout } from 'layouts/app-single-column';
import type { NextPage } from 'next';
import React from 'react';
import { serverPrefetcher } from 'services/server-prefetcher';
@ -24,7 +25,8 @@ const grid = {
const { Title } = Typography;
const Wikis = ({ hook }) => {
const { data, loading, error } = hook();
const { organizationId } = useRouterQuery<{ organizationId: string }>();
const { data, loading, error } = hook(organizationId);
return (
<DataRender
@ -59,7 +61,7 @@ const Wikis = ({ hook }) => {
const Page: NextPage = () => {
return (
<SingleColumnLayout>
<AppSingleColumnLayout>
<Seo title="知识库" />
<div className="container">
<Title heading={3} style={{ margin: '8px 0' }}>
@ -77,15 +79,17 @@ const Page: NextPage = () => {
</TabPane>
</Tabs>
</div>
</SingleColumnLayout>
</AppSingleColumnLayout>
);
};
Page.getInitialProps = async (ctx) => {
const { orgId: organizationId } = ctx.query;
const props = await serverPrefetcher(ctx, [
{ url: WikiApiDefinition.getAllWikis.client(), action: (cookie) => getAllWikis(cookie) },
{ url: WikiApiDefinition.getJoinWikis.client(), action: (cookie) => getJoinWikis(cookie) },
{ url: WikiApiDefinition.getOwnWikis.client(), action: (cookie) => getOwnWikis(cookie) },
{ url: WikiApiDefinition.getAllWikis.client(organizationId), action: (cookie) => getAllWikis(cookie) },
{ url: WikiApiDefinition.getJoinWikis.client(organizationId), action: (cookie) => getJoinWikis(cookie) },
{ url: WikiApiDefinition.getOwnWikis.client(organizationId), action: (cookie) => getOwnWikis(cookie) },
]);
return props;
};

View File

@ -0,0 +1,9 @@
.cover {
margin-right: 12px;
}
@media (max-width: 768px) {
.cover {
width: 100%;
}
}

View File

@ -0,0 +1,119 @@
import { Avatar, Button, Form, Typography } from '@douyinfe/semi-ui';
import { FormApi } from '@douyinfe/semi-ui/lib/es/form';
import { ORGANIZATION_LOGOS } from '@think/constants';
import { ImageUploader } from 'components/image-uploader';
import { useCreateOrganization } from 'data/organization';
import { useToggle } from 'hooks/use-toggle';
import { SingleColumnLayout } from 'layouts/single-column';
import Router from 'next/router';
import { useCallback, useRef, useState } from 'react';
import styles from './index.module.scss';
const images = [
{
key: 'placeholers',
title: '图库',
images: ORGANIZATION_LOGOS,
},
];
const { Title } = Typography;
const Page: React.FC = () => {
const $form = useRef<FormApi>();
const [changed, toggleChanged] = useToggle(false);
const [currentCover, setCurrentCover] = useState('');
const { create, loading } = useCreateOrganization();
const setCover = useCallback((url) => {
$form.current.setValue('logo', url);
setCurrentCover(url);
}, []);
const onFormChange = useCallback(() => {
toggleChanged(true);
}, [toggleChanged]);
const onSubmit = useCallback(() => {
$form.current.validate().then((values) => {
create(values).then((res) => {
Router.push({
pathname: `/app/org/[organizationId]`,
query: { organizationId: res.id },
});
});
});
}, [create]);
return (
<SingleColumnLayout>
<div className="container">
<div style={{ marginBottom: 24 }}>
<Title heading={3} style={{ margin: '8px 0' }}>
</Title>
</div>
<Form
style={{ width: '100%' }}
initValues={{
logo: ORGANIZATION_LOGOS[0],
}}
getFormApi={(formApi) => ($form.current = formApi)}
onChange={onFormChange}
onSubmit={onSubmit}
>
<Form.Slot label="Logo">
<div style={{ display: 'flex', alignItems: 'end' }}>
<div className={styles.cover}>
<Avatar
shape="square"
src={currentCover}
style={{
width: 64,
height: 64,
borderRadius: 4,
}}
/>
</div>
<ImageUploader width={260} images={images} selectImage={setCover}>
<Button>Logo</Button>
</ImageUploader>
</div>
</Form.Slot>
<Form.Input
label="名称"
field="name"
style={{ width: '100%' }}
placeholder="请输入组织名称"
rules={[{ required: true, message: '请输入组织名称' }]}
/>
<Form.TextArea
label="描述"
field="description"
style={{ width: '100%' }}
placeholder="请输入组织简介"
autosize
rules={[{ required: true, message: '请输入组织简介' }]}
/>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Button htmlType="submit" type="primary" theme="solid" disabled={!changed} loading={loading}>
</Button>
</div>
</Form>
</div>
</SingleColumnLayout>
);
};
export default Page;

View File

@ -75,7 +75,7 @@ const Page: NextPage = () => {
Page.getInitialProps = async (ctx) => {
const props = await serverPrefetcher(ctx, [
{ url: [WikiApiDefinition.getAllWikis.client(), 1], action: (cookie) => getAllPublicWikis(1, cookie) },
{ url: [WikiApiDefinition.getPublicWikis.client(), 1], action: (cookie) => getAllPublicWikis(1, cookie) },
]);
return props;
};

View File

@ -1,184 +1,39 @@
import { Avatar, Button, List, Table, Typography } from '@douyinfe/semi-ui';
import { DocumentApiDefinition, IDocument, StarApiDefinition } from '@think/domains';
import { DataRender } from 'components/data-render';
import { DocumentActions } from 'components/document/actions';
import { Empty } from 'components/empty';
import { LocaleTime } from 'components/locale-time';
import { Button, Typography } from '@douyinfe/semi-ui';
import { Seo } from 'components/seo';
import { WikiCreator } from 'components/wiki/create';
import { WikiPinCard, WikiPinCardPlaceholder } from 'components/wiki/pin-card';
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';
import Link from 'next/link';
import React, { useEffect, useMemo } from 'react';
import { serverPrefetcher } from 'services/server-prefetcher';
import styles from './index.module.scss';
import Router from 'next/router';
import React, { useCallback } from 'react';
const { Title } = Typography;
const { Column } = Table;
const grid = {
gutter: 16,
xs: 24,
sm: 12,
md: 12,
lg: 8,
xl: 8,
};
const RecentDocs = () => {
const { data, error, loading, refresh } = useRecentDocuments();
const columns = useMemo(
() => [
<Column
title="标题"
dataIndex="title"
key="title"
width={200}
render={(_, document: IDocument) => {
return (
<Link href={'/wiki/[wikiId]/document/[documentId]'} as={`/wiki/${document.wikiId}/document/${document.id}`}>
<a style={{ color: 'inherit', textDecoration: 'none' }}>{document.title}</a>
</Link>
);
}}
/>,
<Column width={100} title="阅读量" dataIndex="views" key="views" />,
<Column
title="创建者"
dataIndex="createUser"
key="createUser"
width={160}
render={(createUser) => {
return (
<span>
<Avatar size="extra-extra-small" src={createUser.avatar} style={{ marginRight: 4 }}>
{createUser.name.slice(0, 1)}
</Avatar>
{createUser.name}
</span>
);
}}
/>,
<Column
width={120}
title="访问时间"
dataIndex="visitedAt"
key="visitedAt"
render={(date) => <LocaleTime date={date} />}
/>,
<Column
title="操作"
dataIndex="operate"
key="operate"
width={80}
render={(_, document) => (
<DocumentActions
wikiId={document.wikiId}
documentId={document.id}
onDelete={refresh}
showCreateDocument
hideDocumentVersion
hideDocumentStyle
/>
)}
/>,
],
[refresh]
);
useEffect(() => {
refresh();
}, [refresh]);
return (
<>
<Title heading={3} style={{ margin: '24px 0 0' }}>
访
</Title>
<DataRender
loading={loading}
loadingContent={
<Table dataSource={[]} loading={true} pagination={false} size="small" style={{ marginTop: 16 }}>
{columns}
</Table>
}
error={error}
normalContent={() =>
data && data.length ? (
<Table dataSource={data} loading={loading} pagination={false} size="small" style={{ marginTop: 16 }}>
{columns}
</Table>
) : (
<Empty message="最近访问的文档会出现在此处" />
)
}
/>
</>
);
};
const Page: NextPage = () => {
const [visible, toggleVisible] = useToggle(false);
const { data: staredWikis, loading, error, refresh } = useStarWikis();
const gotoApp = useCallback(() => {
Router.push(`/app`);
}, []);
return (
<SingleColumnLayout>
<Seo title="主页" />
<div className="container">
<div className={styles.titleWrap}>
<div style={{ marginBottom: 24 }}>
<Title heading={3} style={{ margin: '8px 0' }}>
访
</Title>
<>
<Button onClick={toggleVisible}></Button>
<WikiCreator visible={visible} toggleVisible={toggleVisible} />
</>
</div>
<DataRender
loading={loading}
loadingContent={() => (
<List
grid={grid}
dataSource={[1, 2, 3]}
renderItem={() => (
<List.Item>
<WikiPinCardPlaceholder />
</List.Item>
)}
/>
)}
error={error}
normalContent={() => (
<List
grid={grid}
dataSource={staredWikis}
renderItem={(wiki) => (
<List.Item>
<WikiPinCard wiki={wiki} />
</List.Item>
)}
emptyContent={<Empty message="收藏的知识库会出现在此处" />}
/>
)}
/>
<RecentDocs />
<div
style={{
padding: '10vh',
textAlign: 'center',
}}
>
<Button theme="solid" onClick={gotoApp}>
</Button>
</div>
</div>
</SingleColumnLayout>
);
};
Page.getInitialProps = async (ctx) => {
const props = await serverPrefetcher(ctx, [
{ url: StarApiDefinition.wikis.client(), action: (cookie) => getStarWikis(cookie) },
{ url: DocumentApiDefinition.recent.client(), action: (cookie) => getRecentVisitedDocuments(cookie) },
]);
return props;
};
export default Page;

View File

@ -10,7 +10,7 @@ interface AxiosInstance extends Axios {
export const HttpClient = axios.create({
baseURL: process.env.SERVER_API_URL,
timeout: 10 * 60 * 1000,
timeout: 1 * 3 * 1000,
withCredentials: true,
}) as AxiosInstance;

View File

@ -1,6 +1,7 @@
/// <reference types="node" />
export declare const DEFAULT_WIKI_AVATAR = "https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default0-96.png";
export declare const WIKI_AVATARS: string[];
export declare const ORGANIZATION_LOGOS: string[];
export declare const EMPTY_DOCUMNENT: {
content: string;
state: Buffer;

View File

@ -1,6 +1,6 @@
"use strict";
exports.__esModule = true;
exports.DOCUMENT_COVERS = exports.EMPTY_DOCUMNENT = exports.WIKI_AVATARS = exports.DEFAULT_WIKI_AVATAR = void 0;
exports.DOCUMENT_COVERS = exports.EMPTY_DOCUMNENT = exports.ORGANIZATION_LOGOS = exports.WIKI_AVATARS = exports.DEFAULT_WIKI_AVATAR = void 0;
exports.DEFAULT_WIKI_AVATAR = 'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default0-96.png';
exports.WIKI_AVATARS = [
exports.DEFAULT_WIKI_AVATAR,
@ -16,6 +16,20 @@ exports.WIKI_AVATARS = [
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default17-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default18-96.png',
];
exports.ORGANIZATION_LOGOS = [
exports.DEFAULT_WIKI_AVATAR,
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default2-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default7-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default8-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default14-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default21-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default23-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default1-96%20(1).png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default4-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default12-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default17-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default18-96.png',
];
exports.EMPTY_DOCUMNENT = {
content: JSON.stringify({
"default": {

View File

@ -15,6 +15,21 @@ export const WIKI_AVATARS = [
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default18-96.png',
];
export const ORGANIZATION_LOGOS = [
DEFAULT_WIKI_AVATAR,
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default2-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default7-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default8-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default14-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default21-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default23-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default1-96%20(1).png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default4-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default12-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default17-96.png',
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default18-96.png',
];
export const EMPTY_DOCUMNENT = {
content: JSON.stringify({
default: {

View File

@ -5,16 +5,16 @@ export declare const DocumentApiDefinition: {
*/
search: {
method: "get";
server: "search";
client: () => string;
server: "/:organizationId/search";
client: (organizationId: any) => string;
};
/**
* 访
*/
recent: {
method: "get";
server: "recent";
client: () => string;
server: "/:organizationId/recent";
client: (organizationId: any) => string;
};
/**
*

View File

@ -7,16 +7,16 @@ exports.DocumentApiDefinition = {
*/
search: {
method: 'get',
server: 'search',
client: function () { return '/document/search'; }
server: '/:organizationId/search',
client: function (organizationId) { return "/document/".concat(organizationId, "/search"); }
},
/**
* 访
*/
recent: {
method: 'get',
server: 'recent',
client: function () { return '/document/recent'; }
server: '/:organizationId/recent',
client: function (organizationId) { return "/document/".concat(organizationId, "/recent"); }
},
/**
*

View File

@ -6,3 +6,4 @@ export * from './message';
export * from './template';
export * from './comment';
export * from './star';
export * from './organization';

View File

@ -18,3 +18,4 @@ __exportStar(require("./message"), exports);
__exportStar(require("./template"), exports);
__exportStar(require("./comment"), exports);
__exportStar(require("./star"), exports);
__exportStar(require("./organization"), exports);

View File

@ -0,0 +1,75 @@
import { IOrganization } from '../models';
export declare const OrganizationApiDefinition: {
/**
*
*/
createOrganization: {
method: "post";
server: "/create";
client: () => string;
};
/**
*
*/
getPersonalOrganization: {
method: "get";
server: "/personal";
client: () => string;
};
/**
*
*/
getUserOrganizations: {
method: "get";
server: "/list/personal";
client: () => string;
};
/**
*
*/
getOrganizationDetail: {
method: "get";
server: "/detail/:id";
client: (id: IOrganization['id']) => string;
};
/**
*
*/
updateOrganization: {
method: "post";
server: "/update/:id";
client: (id: IOrganization['id']) => string;
};
/**
*
*/
getMembers: {
method: "get";
server: "/member/:id";
client: (id: IOrganization['id']) => string;
};
/**
*
*/
addMemberById: {
method: "post";
server: "member/:id/add";
client: (id: IOrganization['id']) => string;
};
/**
*
*/
updateMemberById: {
method: "patch";
server: "member/:id/update";
client: (id: IOrganization['id']) => string;
};
/**
*
*/
deleteMemberById: {
method: "delete";
server: "member/:id/delete";
client: (id: IOrganization['id']) => string;
};
};

View File

@ -0,0 +1,77 @@
"use strict";
exports.__esModule = true;
exports.OrganizationApiDefinition = void 0;
exports.OrganizationApiDefinition = {
/**
*
*/
createOrganization: {
method: 'post',
server: '/create',
client: function () { return '/organization/create'; }
},
/**
*
*/
getPersonalOrganization: {
method: 'get',
server: '/personal',
client: function () { return '/organization/personal'; }
},
/**
*
*/
getUserOrganizations: {
method: 'get',
server: '/list/personal',
client: function () { return '/organization/list/personal'; }
},
/**
*
*/
getOrganizationDetail: {
method: 'get',
server: '/detail/:id',
client: function (id) { return "/organization/detail/".concat(id); }
},
/**
*
*/
updateOrganization: {
method: 'post',
server: '/update/:id',
client: function (id) { return "/organization/update/".concat(id); }
},
/**
*
*/
getMembers: {
method: 'get',
server: '/member/:id',
client: function (id) { return "/organization/member/".concat(id); }
},
/**
*
*/
addMemberById: {
method: 'post',
server: 'member/:id/add',
client: function (id) { return "/organization/member/".concat(id, "/add"); }
},
/**
*
*/
updateMemberById: {
method: 'patch',
server: 'member/:id/update',
client: function (id) { return "/organization/member/".concat(id, "/update"); }
},
/**
*
*/
deleteMemberById: {
method: 'delete',
server: 'member/:id/delete',
client: function (id) { return "/organization/member/".concat(id, "/delete"); }
}
};

View File

@ -1,8 +1,8 @@
export declare const StarApiDefinition: {
/**
*
*
*/
toggle: {
toggleStar: {
method: "post";
server: "toggle";
client: () => string;
@ -10,33 +10,33 @@ export declare const StarApiDefinition: {
/**
*
*/
check: {
isStared: {
method: "post";
server: "check";
server: "isStared";
client: () => string;
};
/**
*
*
*/
wikis: {
getStarWikisInOrganization: {
method: "get";
server: "wikis";
client: () => string;
server: "/:organizationId/wikis";
client: (organizationId: any) => string;
};
/**
*
*/
wikiDocuments: {
getStarDocumentsInWiki: {
method: "get";
server: "wiki/documents";
server: "/wiki/documents";
client: () => string;
};
/**
*
*
*/
documents: {
getStarDocumentsInOrganization: {
method: "get";
server: "documents";
client: () => string;
server: "/:organizationId/documents";
client: (organizationId: any) => string;
};
};

View File

@ -3,9 +3,9 @@ exports.__esModule = true;
exports.StarApiDefinition = void 0;
exports.StarApiDefinition = {
/**
*
*
*/
toggle: {
toggleStar: {
method: 'post',
server: 'toggle',
client: function () { return '/star/toggle'; }
@ -13,33 +13,33 @@ exports.StarApiDefinition = {
/**
*
*/
check: {
isStared: {
method: 'post',
server: 'check',
client: function () { return '/star/check'; }
server: 'isStared',
client: function () { return '/star/isStared'; }
},
/**
*
*
*/
wikis: {
getStarWikisInOrganization: {
method: 'get',
server: 'wikis',
client: function () { return '/star/wikis'; }
server: '/:organizationId/wikis',
client: function (organizationId) { return "/star/".concat(organizationId, "/wikis"); }
},
/**
*
*/
wikiDocuments: {
getStarDocumentsInWiki: {
method: 'get',
server: 'wiki/documents',
client: function () { return '/star/wiki/documents'; }
server: '/wiki/documents',
client: function () { return "/star/wiki/documents"; }
},
/**
*
*
*/
documents: {
getStarDocumentsInOrganization: {
method: 'get',
server: 'documents',
client: function () { return '/star/documents'; }
server: '/:organizationId/documents',
client: function (organizationId) { return "/star/".concat(organizationId, "/documents"); }
}
};

View File

@ -5,24 +5,24 @@ export declare const WikiApiDefinition: {
*/
getAllWikis: {
method: "get";
server: "list/all";
client: () => string;
server: "list/all/:organizationId";
client: (organizationId: any) => string;
};
/**
*
*/
getOwnWikis: {
method: "get";
server: "list/own";
client: () => string;
server: "list/own/:organizationId";
client: (organizationId: any) => string;
};
/**
*
*/
getJoinWikis: {
method: "get";
server: "list/join";
client: () => string;
server: "list/join/:organizationId";
client: (organizationId: any) => string;
};
/**
*

View File

@ -7,24 +7,24 @@ exports.WikiApiDefinition = {
*/
getAllWikis: {
method: 'get',
server: 'list/all',
client: function () { return '/wiki/list/all'; }
server: 'list/all/:organizationId',
client: function (organizationId) { return "/wiki/list/all/".concat(organizationId); }
},
/**
*
*/
getOwnWikis: {
method: 'get',
server: 'list/own',
client: function () { return '/wiki/list/own'; }
server: 'list/own/:organizationId',
client: function (organizationId) { return "/wiki/list/own/".concat(organizationId); }
},
/**
*
*/
getJoinWikis: {
method: 'get',
server: 'list/join',
client: function () { return '/wiki/list/join'; }
server: 'list/join/:organizationId',
client: function (organizationId) { return "/wiki/list/join/".concat(organizationId); }
},
/**
*

View File

@ -0,0 +1,26 @@
import { IOrganization } from './organization';
export declare enum AuthEnum {
creator = "creator",
admin = "admin",
member = "member",
noAccess = "noAccess"
}
export declare const AuthEnumTextMap: {
creator: string;
admin: string;
member: string;
noAccess: string;
};
export declare const AuthEnumArray: {
label: any;
value: string;
}[];
export interface IAuth {
id: string;
type: AuthEnum;
organizationId: IOrganization['id'];
wikiId?: string;
documentId?: string;
createdAt: string;
updatedAt: string;
}

View File

@ -0,0 +1,21 @@
"use strict";
var _a;
exports.__esModule = true;
exports.AuthEnumArray = exports.AuthEnumTextMap = exports.AuthEnum = void 0;
var AuthEnum;
(function (AuthEnum) {
AuthEnum["creator"] = "creator";
AuthEnum["admin"] = "admin";
AuthEnum["member"] = "member";
AuthEnum["noAccess"] = "noAccess";
})(AuthEnum = exports.AuthEnum || (exports.AuthEnum = {}));
exports.AuthEnumTextMap = (_a = {},
_a[AuthEnum.creator] = '创建者',
_a[AuthEnum.admin] = '管理员',
_a[AuthEnum.member] = '成员',
_a[AuthEnum.noAccess] = '无权限',
_a);
exports.AuthEnumArray = Object.keys(exports.AuthEnumTextMap).map(function (value) { return ({
label: exports.AuthEnumTextMap[value],
value: value
}); });

View File

@ -1,3 +1,4 @@
import { IOrganization } from './organization';
import { IUser } from './user';
import { IWiki } from './wiki';
/**
@ -12,6 +13,7 @@ export declare enum DocumentStatus {
*/
export interface IDocument {
id: string;
organizationId: IOrganization['id'];
wikiId: IWiki['id'];
isWikiHome: boolean;
createUserId: IUser['id'];

View File

@ -6,3 +6,5 @@ export * from './template';
export * from './comment';
export * from './pagination';
export * from './system';
export * from './organization';
export * from './auth';

View File

@ -18,3 +18,5 @@ __exportStar(require("./template"), exports);
__exportStar(require("./comment"), exports);
__exportStar(require("./pagination"), exports);
__exportStar(require("./system"), exports);
__exportStar(require("./organization"), exports);
__exportStar(require("./auth"), exports);

View File

@ -1,4 +1,7 @@
import { IUser } from './user';
import { IOrganization } from './organization';
import { IWiki } from './wiki';
import { IDocument } from './document';
/**
*
*/
@ -12,3 +15,10 @@ export interface IMessage {
createdAt: Date;
updatedAt: Date;
}
declare type MessageType = 'toOrganization' | 'toWiki' | 'toDocument';
export declare const buildMessageURL: (type: MessageType) => (arg: {
organizationId: IOrganization['id'];
wikiId?: IWiki['id'];
documentId?: IDocument['id'];
}) => string;
export {};

View File

@ -1,2 +1,25 @@
"use strict";
exports.__esModule = true;
exports.buildMessageURL = void 0;
var buildMessageURL = function (type) {
switch (type) {
case 'toDocument':
return function (_a) {
var organizationId = _a.organizationId, wikiId = _a.wikiId, documentId = _a.documentId;
return "/app/org/".concat(organizationId, "/wiki/").concat(wikiId, "/doc/").concat(documentId);
};
case 'toWiki':
return function (_a) {
var organizationId = _a.organizationId, wikiId = _a.wikiId;
return "/app/org/".concat(organizationId, "/wiki/").concat(wikiId);
};
case 'toOrganization':
return function (_a) {
var organizationId = _a.organizationId;
return "/app/org/".concat(organizationId);
};
default:
throw new Error();
}
};
exports.buildMessageURL = buildMessageURL;

View File

@ -0,0 +1,12 @@
import { IUser } from './user';
/**
*
*/
export interface IOrganization {
id: string;
name: string;
description: string;
logo: string;
createUserId: IUser['id'];
isPersonal: boolean;
}

View File

@ -0,0 +1,33 @@
"use strict";
exports.__esModule = true;
// /**
// * 创建组织数据定义
// */
// export interface CreateOrganizationDto {
// name: string;
// description: string;
// logo: string;
// }
// export enum OrganizationAuthEnum {
// superAdmin = 'superAdmin',
// admin = 'admin',
// member = 'member',
// noAccess = 'noAccess',
// }
// export const AuthEnumTextMap = {
// [OrganizationAuthEnum.superAdmin]: '超级管理员',
// [OrganizationAuthEnum.admin]: '管理员',
// [OrganizationAuthEnum.member]: '成员',
// [OrganizationAuthEnum.noAccess]: '无权限',
// };
// export const OrganizationAuthEnumArray = Object.keys(AuthEnumTextMap).map((value) => ({
// label: AuthEnumTextMap[value],
// value,
// }));
// export interface IOrganizationAuth {
// id: string;
// auth: OrganizationAuthEnum;
// organizationId: IOrganization['id'];
// createdAt: string;
// updatedAt: string;
// }

View File

@ -1,5 +1,6 @@
import { IUser } from './user';
import { IDocument } from './document';
import { IOrganization } from './organization';
/**
*
*/
@ -27,6 +28,7 @@ export declare enum WikiUserRole {
*/
export interface IWiki {
id: string;
organizationId: IOrganization['id'];
name: string;
avatar: string;
description: string;

View File

@ -6,8 +6,8 @@ export const DocumentApiDefinition = {
*/
search: {
method: 'get' as const,
server: 'search' as const,
client: () => '/document/search',
server: '/:organizationId/search' as const,
client: (organizationId) => `/document/${organizationId}/search`,
},
/**
@ -15,8 +15,8 @@ export const DocumentApiDefinition = {
*/
recent: {
method: 'get' as const,
server: 'recent' as const,
client: () => '/document/recent',
server: '/:organizationId/recent' as const,
client: (organizationId) => `/document/${organizationId}/recent`,
},
/**

View File

@ -6,3 +6,4 @@ export * from './message';
export * from './template';
export * from './comment';
export * from './star';
export * from './organization';

View File

@ -0,0 +1,84 @@
import { IOrganization } from '../models';
export const OrganizationApiDefinition = {
/**
*
*/
createOrganization: {
method: 'post' as const,
server: '/create' as const,
client: () => '/organization/create',
},
/**
*
*/
getPersonalOrganization: {
method: 'get' as const,
server: '/personal' as const,
client: () => '/organization/personal',
},
/**
*
*/
getUserOrganizations: {
method: 'get' as const,
server: '/list/personal' as const,
client: () => '/organization/list/personal',
},
/**
*
*/
getOrganizationDetail: {
method: 'get' as const,
server: '/detail/:id' as const,
client: (id: IOrganization['id']) => `/organization/detail/${id}`,
},
/**
*
*/
updateOrganization: {
method: 'post' as const,
server: '/update/:id' as const,
client: (id: IOrganization['id']) => `/organization/update/${id}`,
},
/**
*
*/
getMembers: {
method: 'get' as const,
server: '/member/:id' as const,
client: (id: IOrganization['id']) => `/organization/member/${id}`,
},
/**
*
*/
addMemberById: {
method: 'post' as const,
server: 'member/:id/add' as const,
client: (id: IOrganization['id']) => `/organization/member/${id}/add`,
},
/**
*
*/
updateMemberById: {
method: 'patch' as const,
server: 'member/:id/update' as const,
client: (id: IOrganization['id']) => `/organization/member/${id}/update`,
},
/**
*
*/
deleteMemberById: {
method: 'delete' as const,
server: 'member/:id/delete' as const,
client: (id: IOrganization['id']) => `/organization/member/${id}/delete`,
},
};

Some files were not shown because too many files have changed in this diff Show More