mirror of https://github.com/fantasticit/think.git
client: imporve reader
parent
3337ea5593
commit
cfadcf22a0
|
@ -15,17 +15,19 @@ import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
} from '@douyinfe/semi-ui';
|
} from '@douyinfe/semi-ui';
|
||||||
|
import { IUser } from '@think/domains';
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { DocumentLinkCopyer } from 'components/document/link';
|
import { DocumentLinkCopyer } from 'components/document/link';
|
||||||
import { useDoumentMembers } from 'data/document';
|
import { useDoumentMembers } from 'data/document';
|
||||||
import { useUser } from 'data/user';
|
import { useUser } from 'data/user';
|
||||||
import { event, JOIN_USER } from 'event';
|
import { event, JOIN_USER } from 'event';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
wikiId: string;
|
wikiId: string;
|
||||||
documentId: string;
|
documentId: string;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Paragraph } = Typography;
|
const { Paragraph } = Typography;
|
||||||
|
@ -45,7 +47,8 @@ const renderChecked = (onChange, authKey: 'readable' | 'editable') => (checked,
|
||||||
return <Checkbox style={{ display: 'inline-block' }} checked={checked} onChange={handle} />;
|
return <Checkbox style={{ display: 'inline-block' }} checked={checked} onChange={handle} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId }) => {
|
export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId, disabled = false }) => {
|
||||||
|
const toastedUsersRef = useRef<Array<IUser['id']>>([]);
|
||||||
const { user: currentUser } = useUser();
|
const { user: currentUser } = useUser();
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
const [visible, toggleVisible] = useToggle(false);
|
||||||
const { users, loading, error, addUser, updateUser, deleteUser } = useDoumentMembers(documentId);
|
const { users, loading, error, addUser, updateUser, deleteUser } = useDoumentMembers(documentId);
|
||||||
|
@ -84,8 +87,9 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId })
|
||||||
}
|
}
|
||||||
|
|
||||||
newCollaborationUsers.forEach((newUser) => {
|
newCollaborationUsers.forEach((newUser) => {
|
||||||
if (currentUser && newUser.name !== currentUser.name) {
|
if (currentUser && newUser.name !== currentUser.name && !toastedUsersRef.current.includes(newUser.id)) {
|
||||||
Toast.info(`${newUser.name}加入文档`);
|
Toast.info(`${newUser.name}加入文档`);
|
||||||
|
toastedUsersRef.current.push(newUser.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -95,6 +99,7 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId })
|
||||||
event.on(JOIN_USER, handler);
|
event.on(JOIN_USER, handler);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
toastedUsersRef.current = [];
|
||||||
event.off(JOIN_USER, handler);
|
event.off(JOIN_USER, handler);
|
||||||
};
|
};
|
||||||
}, [collaborationUsers, currentUser]);
|
}, [collaborationUsers, currentUser]);
|
||||||
|
@ -120,7 +125,13 @@ export const DocumentCollaboration: React.FC<IProps> = ({ wikiId, documentId })
|
||||||
})}
|
})}
|
||||||
</AvatarGroup>
|
</AvatarGroup>
|
||||||
<Tooltip content="邀请他人协作" position="bottom">
|
<Tooltip content="邀请他人协作" position="bottom">
|
||||||
<Button theme="borderless" type="tertiary" icon={<IconUserAdd />} onClick={toggleVisible}></Button>
|
<Button
|
||||||
|
theme="borderless"
|
||||||
|
type="tertiary"
|
||||||
|
disabled={disabled}
|
||||||
|
icon={<IconUserAdd />}
|
||||||
|
onClick={toggleVisible}
|
||||||
|
></Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Modal
|
<Modal
|
||||||
title={'文档协作'}
|
title={'文档协作'}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { IconArticle, IconEdit } from '@douyinfe/semi-icons';
|
import { IconEdit } from '@douyinfe/semi-icons';
|
||||||
import { BackTop, Button, Layout, Nav, Popover, Skeleton, Space, Spin, Tooltip, Typography } from '@douyinfe/semi-ui';
|
import { BackTop, Button, Layout, Nav, Skeleton, Space, Tooltip, Typography } from '@douyinfe/semi-ui';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { DocumentCollaboration } from 'components/document/collaboration';
|
import { DocumentCollaboration } from 'components/document/collaboration';
|
||||||
|
@ -55,11 +55,14 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { data: documentAndAuth, loading: docAuthLoading, error: docAuthError } = useDocumentDetail(documentId);
|
const { data: documentAndAuth, loading: docAuthLoading, error: docAuthError } = useDocumentDetail(documentId);
|
||||||
const { document, authority } = documentAndAuth || {};
|
const { document, authority } = documentAndAuth || {};
|
||||||
|
const [readable, editable] = useMemo(() => {
|
||||||
|
if (!authority) return [false, false];
|
||||||
|
return [authority.readable, authority.editable];
|
||||||
|
}, [authority]);
|
||||||
|
|
||||||
const renderAuthor = useCallback(
|
const renderAuthor = useCallback(
|
||||||
(element) => {
|
(element) => {
|
||||||
if (!document) return null;
|
if (!document) return null;
|
||||||
|
|
||||||
const target = element && element.querySelector('.ProseMirror .title');
|
const target = element && element.querySelector('.ProseMirror .title');
|
||||||
|
|
||||||
if (target) {
|
if (target) {
|
||||||
|
@ -75,29 +78,28 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
||||||
Router.push(`/wiki/${document.wikiId}/document/${document.id}/edit`);
|
Router.push(`/wiki/${document.wikiId}/document/${document.id}/edit`);
|
||||||
}, [document]);
|
}, [document]);
|
||||||
|
|
||||||
const actions = useMemo(
|
const actions = useMemo(() => {
|
||||||
() => (
|
console.log({ readable, editable });
|
||||||
|
return (
|
||||||
<Space>
|
<Space>
|
||||||
{document && authority.readable && (
|
{document && (
|
||||||
<DocumentCollaboration key="collaboration" wikiId={document.wikiId} documentId={documentId} />
|
<DocumentCollaboration
|
||||||
)}
|
disabled={!readable}
|
||||||
{authority && authority.editable && (
|
key="collaboration"
|
||||||
<Tooltip key="edit" content="编辑" position="bottom">
|
wikiId={document.wikiId}
|
||||||
<Button icon={<IconEdit />} onMouseDown={gotoEdit} />
|
documentId={documentId}
|
||||||
</Tooltip>
|
/>
|
||||||
)}
|
|
||||||
{authority && authority.readable && (
|
|
||||||
<>
|
|
||||||
<DocumentShare key="share" documentId={documentId} />
|
|
||||||
<DocumentVersion key="version" documentId={documentId} />
|
|
||||||
<DocumentStar key="star" documentId={documentId} />
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
<Tooltip key="edit" content="编辑" position="bottom">
|
||||||
|
<Button disabled={!editable} icon={<IconEdit />} onMouseDown={gotoEdit} />
|
||||||
|
</Tooltip>
|
||||||
|
<DocumentShare disabled={!readable} key="share" documentId={documentId} />
|
||||||
|
<DocumentVersion disabled={!readable} key="version" documentId={documentId} />
|
||||||
|
<DocumentStar disabled={!readable} key="star" documentId={documentId} />
|
||||||
<DocumentStyle />
|
<DocumentStyle />
|
||||||
</Space>
|
</Space>
|
||||||
),
|
);
|
||||||
[document, documentId, authority, gotoEdit]
|
}, [document, documentId, readable, editable, gotoEdit]);
|
||||||
);
|
|
||||||
|
|
||||||
const editBtnStyle = useMemo(() => getEditBtnStyle(isMobile ? 16 : 100), [isMobile]);
|
const editBtnStyle = useMemo(() => getEditBtnStyle(isMobile ? 16 : 100), [isMobile]);
|
||||||
|
|
||||||
|
@ -133,7 +135,7 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
||||||
<div className={cls(styles.editorWrap, editorWrapClassNames)} style={{ fontSize }}>
|
<div className={cls(styles.editorWrap, editorWrapClassNames)} style={{ fontSize }}>
|
||||||
<div id="js-reader-container">
|
<div id="js-reader-container">
|
||||||
{document && <Seo title={document.title} />}
|
{document && <Seo title={document.title} />}
|
||||||
{user && (
|
{user && readable && (
|
||||||
<CollaborationEditor
|
<CollaborationEditor
|
||||||
editable={false}
|
editable={false}
|
||||||
user={user}
|
user={user}
|
||||||
|
|
|
@ -10,12 +10,13 @@ import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
documentId: string;
|
documentId: string;
|
||||||
render?: (arg: { isPublic: boolean; toggleVisible: (arg: boolean) => void }) => React.ReactNode;
|
disabled?: boolean;
|
||||||
|
render?: (arg: { isPublic: boolean; disabled: boolean; toggleVisible: (arg: boolean) => void }) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
export const DocumentShare: React.FC<IProps> = ({ documentId, render }) => {
|
export const DocumentShare: React.FC<IProps> = ({ documentId, disabled = false, render }) => {
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
const [visible, toggleVisible] = useToggle(false);
|
||||||
const { data, loading, error, toggleStatus } = useDocumentDetail(documentId);
|
const { data, loading, error, toggleStatus } = useDocumentDetail(documentId);
|
||||||
const [sharePassword, setSharePassword] = useState('');
|
const [sharePassword, setSharePassword] = useState('');
|
||||||
|
@ -34,9 +35,9 @@ export const DocumentShare: React.FC<IProps> = ({ documentId, render }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{render ? (
|
{render ? (
|
||||||
render({ isPublic, toggleVisible })
|
render({ isPublic, disabled, toggleVisible })
|
||||||
) : (
|
) : (
|
||||||
<Button type="primary" theme="light" onClick={toggleVisible}>
|
<Button disabled={disabled} type="primary" theme="light" onClick={toggleVisible}>
|
||||||
{isPublic ? '分享中' : '分享'}
|
{isPublic ? '分享中' : '分享'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -5,17 +5,23 @@ import React from 'react';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
documentId: string;
|
documentId: string;
|
||||||
render?: (arg: { star: boolean; text: string; toggleStar: () => Promise<void> }) => React.ReactNode;
|
disabled?: boolean;
|
||||||
|
render?: (arg: {
|
||||||
|
star: boolean;
|
||||||
|
disabled: boolean;
|
||||||
|
text: string;
|
||||||
|
toggleStar: () => Promise<void>;
|
||||||
|
}) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DocumentStar: React.FC<IProps> = ({ documentId, render }) => {
|
export const DocumentStar: React.FC<IProps> = ({ documentId, disabled = false, render }) => {
|
||||||
const { data, toggle: toggleStar } = useDocumentCollectToggle(documentId);
|
const { data, toggle: toggleStar } = useDocumentCollectToggle(documentId);
|
||||||
const text = data ? '取消收藏' : '收藏文档';
|
const text = data ? '取消收藏' : '收藏文档';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{render ? (
|
{render ? (
|
||||||
render({ star: data, toggleStar, text })
|
render({ star: data, disabled, toggleStar, text })
|
||||||
) : (
|
) : (
|
||||||
<Tooltip content={text} position="bottom">
|
<Tooltip content={text} position="bottom">
|
||||||
<Button
|
<Button
|
||||||
|
@ -24,6 +30,7 @@ export const DocumentStar: React.FC<IProps> = ({ documentId, render }) => {
|
||||||
style={{
|
style={{
|
||||||
color: data ? 'rgba(var(--semi-amber-4), 1)' : 'rgba(var(--semi-grey-3), 1)',
|
color: data ? 'rgba(var(--semi-amber-4), 1)' : 'rgba(var(--semi-grey-3), 1)',
|
||||||
}}
|
}}
|
||||||
|
disabled={disabled}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
|
@ -14,13 +14,14 @@ import styles from './index.module.scss';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
documentId: string;
|
documentId: string;
|
||||||
|
disabled?: boolean;
|
||||||
onSelect?: (data) => void;
|
onSelect?: (data) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { Sider, Content } = Layout;
|
const { Sider, Content } = Layout;
|
||||||
const { Title } = Typography;
|
const { Title } = Typography;
|
||||||
|
|
||||||
export const DocumentVersion: React.FC<IProps> = ({ documentId, onSelect }) => {
|
export const DocumentVersion: React.FC<IProps> = ({ documentId, disabled = false, onSelect }) => {
|
||||||
const [visible, toggleVisible] = useToggle(false);
|
const [visible, toggleVisible] = useToggle(false);
|
||||||
const { data, loading, error, refresh } = useDocumentVersion(documentId);
|
const { data, loading, error, refresh } = useDocumentVersion(documentId);
|
||||||
const [selectedVersion, setSelectedVersion] = useState(null);
|
const [selectedVersion, setSelectedVersion] = useState(null);
|
||||||
|
@ -65,7 +66,7 @@ export const DocumentVersion: React.FC<IProps> = ({ documentId, onSelect }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button type="primary" theme="light" onClick={toggleVisible}>
|
<Button type="primary" theme="light" disabled={disabled} onClick={toggleVisible}>
|
||||||
文档版本
|
文档版本
|
||||||
</Button>
|
</Button>
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -87,7 +88,7 @@ export const DocumentVersion: React.FC<IProps> = ({ documentId, onSelect }) => {
|
||||||
theme="light"
|
theme="light"
|
||||||
type="primary"
|
type="primary"
|
||||||
style={{ marginRight: 8 }}
|
style={{ marginRight: 8 }}
|
||||||
disabled={loading || error}
|
disabled={loading || !!error}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onClick={() => refresh()}
|
onClick={() => refresh()}
|
||||||
>
|
>
|
||||||
|
|
|
@ -139,7 +139,7 @@ export const useDocumentDetail = (documentId) => {
|
||||||
const { data, error, isLoading, refetch } = useQuery(
|
const { data, error, isLoading, refetch } = useQuery(
|
||||||
DocumentApiDefinition.getDetailById.client(documentId),
|
DocumentApiDefinition.getDetailById.client(documentId),
|
||||||
() => getDocumentDetail(documentId),
|
() => getDocumentDetail(documentId),
|
||||||
{ staleTime: 3000 }
|
{ staleTime: 3000, refetchOnReconnect: true, refetchOnMount: true, refetchOnWindowFocus: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-height: 100px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue