chore: fix error, improve perf

This commit is contained in:
fantasticit 2022-09-15 23:09:57 +08:00
parent cdd02c55a1
commit bb060b2729
18 changed files with 144 additions and 115 deletions

View File

@ -74,11 +74,14 @@ export const DocumentFullscreen: React.FC<IProps> = ({ data }) => {
const [isDrawing, toggleDrawing] = useToggle(false); const [isDrawing, toggleDrawing] = useToggle(false);
const [cover, setCover] = useState(''); const [cover, setCover] = useState('');
const editor = useEditor({ const editor = useEditor(
editable: false, {
extensions: CollaborationKit.filter((ext) => ['title', 'doc'].indexOf(ext.name) < 0).concat(Document), editable: false,
content: { type: 'doc', content: [] }, extensions: CollaborationKit.filter((ext) => ['title', 'doc'].indexOf(ext.name) < 0).concat(Document),
}); content: { type: 'doc', content: [] },
},
[]
);
const startPowerpoint = useCallback(() => { const startPowerpoint = useCallback(() => {
toggleVisible(true); toggleVisible(true);

View File

@ -14,6 +14,8 @@ interface IProps {
const { Text } = Typography; const { Text } = Typography;
const style = { cursor: 'pointer' };
export const DocumentLinkCopyer: React.FC<IProps> = ({ organizationId, wikiId, documentId, render }) => { export const DocumentLinkCopyer: React.FC<IProps> = ({ organizationId, wikiId, documentId, render }) => {
const handle = useCallback(() => { const handle = useCallback(() => {
copy(buildUrl(`/app/org/${organizationId}/wiki/${wikiId}/doc/${documentId}`)); copy(buildUrl(`/app/org/${organizationId}/wiki/${wikiId}/doc/${documentId}`));
@ -29,7 +31,7 @@ export const DocumentLinkCopyer: React.FC<IProps> = ({ organizationId, wikiId, d
return render ? ( return render ? (
<>{render({ copy: handle, children: content })}</> <>{render({ copy: handle, children: content })}</>
) : ( ) : (
<Text onClick={handle} style={{ cursor: 'pointer' }}> <Text onClick={handle} style={style}>
{content} {content}
</Text> </Text>
); );

View File

@ -8,18 +8,18 @@ interface IProps {
document: IDocument; document: IDocument;
} }
const style = {
borderTop: '1px solid var(--semi-color-border)',
marginTop: '0.75em',
padding: '16px 0',
fontSize: 13,
fontWeight: 'normal',
color: 'var(--semi-color-text-0)',
};
export const Author: React.FC<IProps> = ({ document }) => { export const Author: React.FC<IProps> = ({ document }) => {
return ( return (
<div <div style={style}>
style={{
borderTop: '1px solid var(--semi-color-border)',
marginTop: '0.75em',
padding: '16px 0',
fontSize: 13,
fontWeight: 'normal',
color: 'var(--semi-color-text-0)',
}}
>
<Space> <Space>
<Avatar size="small" src={document && document.createUser && document.createUser.avatar}> <Avatar size="small" src={document && document.createUser && document.createUser.avatar}>
<IconUser /> <IconUser />

View File

@ -29,6 +29,14 @@ interface IProps {
documentId: string; documentId: string;
} }
const loadingStyle = {
minHeight: 240,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: 'auto',
};
export const DocumentReader: React.FC<IProps> = ({ documentId }) => { export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
const { isMobile } = IsOnMobile.useHook(); const { isMobile } = IsOnMobile.useHook();
const mounted = useMount(); const mounted = useMount();
@ -132,15 +140,7 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
<DataRender <DataRender
loading={docAuthLoading} loading={docAuthLoading}
loadingContent={ loadingContent={
<div <div style={loadingStyle}>
style={{
minHeight: 240,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: 'auto',
}}
>
<Spin /> <Spin />
</div> </div>
} }

View File

@ -9,7 +9,6 @@ import { Seo } from 'components/seo';
import { Theme } from 'components/theme'; import { Theme } from 'components/theme';
import { User } from 'components/user'; import { User } from 'components/user';
import { usePublicDocumentDetail } from 'data/document'; import { usePublicDocumentDetail } from 'data/document';
import { useDocumentStyle } from 'hooks/use-document-style';
import { useMount } from 'hooks/use-mount'; import { useMount } from 'hooks/use-mount';
import { IsOnMobile } from 'hooks/use-on-mobile'; import { IsOnMobile } from 'hooks/use-on-mobile';
import { SecureDocumentIllustration } from 'illustrations/secure-document'; import { SecureDocumentIllustration } from 'illustrations/secure-document';
@ -38,12 +37,8 @@ export const DocumentPublicReader: React.FC<IProps> = ({ documentId, hideLogo =
const mounted = useMount(); const mounted = useMount();
const { wikiId: currentWikiId } = useRouterQuery<{ wikiId: IWiki['id']; documentId: IDocument['id'] }>(); const { wikiId: currentWikiId } = useRouterQuery<{ wikiId: IWiki['id']; documentId: IDocument['id'] }>();
const { data, loading, error, query } = usePublicDocumentDetail(documentId); const { data, loading, error, query } = usePublicDocumentDetail(documentId);
const { width, fontSize } = useDocumentStyle();
const { isMobile } = IsOnMobile.useHook(); const { isMobile } = IsOnMobile.useHook();
const editorWrapClassNames = useMemo(() => {
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
}, [width]);
const renderAuthor = useCallback( const renderAuthor = useCallback(
(element) => { (element) => {
if (!document) return null; if (!document) return null;

View File

@ -33,6 +33,15 @@ export const DocumentStar: React.FC<IProps> = ({ organizationId, wikiId, documen
[toggleVisible] [toggleVisible]
); );
const toggleStarAction = useCallback(
(e) => {
e.stopPropagation();
e.preventDefault();
toggleStar();
},
[toggleStar]
);
return ( return (
<VisibilitySensor onChange={onViewportChange}> <VisibilitySensor onChange={onViewportChange}>
{render ? ( {render ? (
@ -46,11 +55,7 @@ export const DocumentStar: React.FC<IProps> = ({ organizationId, wikiId, documen
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} disabled={disabled}
onClick={(e) => { onClick={toggleStarAction}
e.stopPropagation();
e.preventDefault();
toggleStar();
}}
/> />
</Tooltip> </Tooltip>
)} )}

View File

@ -31,16 +31,19 @@ export const DocumentVersion: React.FC<Partial<IProps>> = ({ documentId, onSelec
const [selectedVersion, setSelectedVersion] = useState(null); const [selectedVersion, setSelectedVersion] = useState(null);
const [diffVersion, setDiffVersion] = useState(null); const [diffVersion, setDiffVersion] = useState(null);
const editor = useEditor({ const editor = useEditor(
editable: false, {
editorProps: { editable: false,
attributes: { editorProps: {
class: 'is-editable', attributes: {
class: 'is-editable',
},
}, },
extensions: CollaborationKit,
content: { type: 'doc', content: [] },
}, },
extensions: CollaborationKit, []
content: { type: 'doc', content: [] }, );
});
const close = useCallback(() => { const close = useCallback(() => {
toggleVisible(false); toggleVisible(false);

View File

@ -94,7 +94,7 @@ export const Import: React.FC<IProps> = ({ wikiId }) => {
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
<Upload <Upload
action="" action=""
accept="text/markdown" accept=".md,.MD,.Md,.mD"
draggable draggable
multiple multiple
ref={$upload} ref={$upload}

View File

@ -22,10 +22,13 @@ interface IProps {
const { Text } = Typography; const { Text } = Typography;
const defaultGetDocLink = (document) =>
`/app/org/${document.organizationId}/wiki/${document.wikiId}/doc/${document.id}`;
export const WikiTocs: React.FC<IProps> = ({ export const WikiTocs: React.FC<IProps> = ({
wikiId, wikiId,
docAsLink = '/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]', docAsLink = '/app/org/[organizationId]/wiki/[wikiId]/doc/[documentId]',
getDocLink = (document) => `/app/org/${document.organizationId}/wiki/${document.wikiId}/doc/${document.id}`, getDocLink = defaultGetDocLink,
}) => { }) => {
const { pathname, query } = useRouter(); const { pathname, query } = useRouter();
const { data: wiki, loading: wikiLoading, error: wikiError } = useWikiDetail(wikiId); const { data: wiki, loading: wikiLoading, error: wikiError } = useWikiDetail(wikiId);
@ -129,7 +132,9 @@ export const WikiTocs: React.FC<IProps> = ({
</Avatar> </Avatar>
<Text strong>{wiki.name}</Text> <Text strong>{wiki.name}</Text>
</span> </span>
<IconSmallTriangleDown /> <Text>
<IconSmallTriangleDown />
</Text>
</div> </div>
</Dropdown> </Dropdown>
) : ( ) : (

View File

@ -1,6 +1,5 @@
import { Banner, Button, Toast, Tree, Typography } from '@douyinfe/semi-ui'; import { Banner, Button, Toast, Tree, Typography } from '@douyinfe/semi-ui';
import { DataRender } from 'components/data-render'; import { DataRender } from 'components/data-render';
import { Resizeable } from 'components/resizeable';
import { useWikiTocs } from 'data/wiki'; import { useWikiTocs } from 'data/wiki';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
@ -18,7 +17,7 @@ interface IDataNode {
children?: Array<IDataNode>; children?: Array<IDataNode>;
} }
const { Title, Text } = Typography; const { Text } = Typography;
const extractRelation = (treeData: Array<IDataNode>) => { const extractRelation = (treeData: Array<IDataNode>) => {
const res = []; const res = [];
@ -41,6 +40,8 @@ const extractRelation = (treeData: Array<IDataNode>) => {
return res; return res;
}; };
const marginBottomStyle = { marginBottom: 16 };
export const WikiTocsManager: React.FC<IProps> = ({ wikiId }) => { export const WikiTocsManager: React.FC<IProps> = ({ wikiId }) => {
const { data: tocs, loading: tocsLoading, error: tocsError, update: updateTocs } = useWikiTocs(wikiId); const { data: tocs, loading: tocsLoading, error: tocsError, update: updateTocs } = useWikiTocs(wikiId);
@ -105,6 +106,10 @@ export const WikiTocsManager: React.FC<IProps> = ({ wikiId }) => {
[treeData] [treeData]
); );
const renderNorContent = useCallback(() => {
return <Tree treeData={treeData} draggable onDrop={onDrop} expandAll />;
}, [treeData, onDrop]);
const submit = useCallback(() => { const submit = useCallback(() => {
const data = extractRelation(treeData); const data = extractRelation(treeData);
updateTocs(data).then(() => { updateTocs(data).then(() => {
@ -121,16 +126,10 @@ export const WikiTocsManager: React.FC<IProps> = ({ wikiId }) => {
icon={null} icon={null}
closeIcon={null} closeIcon={null}
description={<Text></Text>} description={<Text></Text>}
style={{ marginBottom: 16 }} style={marginBottomStyle}
/> />
<div className={styles.tocsWrap}> <div className={styles.tocsWrap}>
<DataRender <DataRender loading={tocsLoading} error={tocsError} normalContent={renderNorContent} />
loading={tocsLoading}
error={tocsError}
normalContent={() => {
return <Tree treeData={treeData} draggable onDrop={onDrop} expandAll />;
}}
/>
</div> </div>
<div className={styles.btnWrap}> <div className={styles.btnWrap}>
<Button disabled={!changed} onClick={submit} theme="solid"> <Button disabled={!changed} onClick={submit} theme="solid">

View File

@ -18,6 +18,8 @@ interface IProps {
openNewTab?: boolean; openNewTab?: boolean;
} }
const marginTopStyle = { marginTop: 4 };
export const NavItem: React.FC<IProps> = ({ export const NavItem: React.FC<IProps> = ({
icon, icon,
text, text,
@ -38,7 +40,7 @@ export const NavItem: React.FC<IProps> = ({
return ( return (
<div <div
className={cls(styles.navItemWrap, isActive && styles.isActive, hoverable && styles.hoverable)} className={cls(styles.navItemWrap, isActive && styles.isActive, hoverable && styles.hoverable)}
style={{ marginTop: 4 }} style={marginTopStyle}
> >
{href ? ( {href ? (
<Link href={href as UrlObject}> <Link href={href as UrlObject}>

View File

@ -22,11 +22,13 @@ interface IProps {
const { Text } = Typography; const { Text } = Typography;
const defaultGetDocLink = (document) => `/share/wiki/${document.wikiId}/document/${document.id}`;
export const WikiPublicTocs: React.FC<IProps> = ({ export const WikiPublicTocs: React.FC<IProps> = ({
pageTitle, pageTitle,
wikiId, wikiId,
docAsLink = '/share/wiki/[wikiId]/document/[documentId]', docAsLink = '/share/wiki/[wikiId]/document/[documentId]',
getDocLink = (document) => `/share/wiki/${document.wikiId}/document/${document.id}`, getDocLink = defaultGetDocLink,
}) => { }) => {
const { pathname } = useRouter(); const { pathname } = useRouter();
const { data: wiki, loading: wikiLoading, error: wikiError } = usePublicWikiDetail(wikiId); const { data: wiki, loading: wikiLoading, error: wikiError } = usePublicWikiDetail(wikiId);

View File

@ -14,6 +14,14 @@ import styles from './index.module.scss';
import { findParents } from './utils'; import { findParents } from './utils';
const Actions = ({ node }) => { const Actions = ({ node }) => {
const createDocument = useCallback(
(e) => {
e.stopPropagation();
triggerCreateDocument({ wikiId: node.wikiId, documentId: node.id });
},
[node.wikiId, node.id]
);
return ( return (
<span className={styles.right}> <span className={styles.right}>
<DocumentActions <DocumentActions
@ -28,10 +36,7 @@ const Actions = ({ node }) => {
></DocumentActions> ></DocumentActions>
<Button <Button
className={styles.hoverVisible} className={styles.hoverVisible}
onClick={(e) => { onClick={createDocument}
e.stopPropagation();
triggerCreateDocument({ wikiId: node.wikiId, documentId: node.id });
}}
type="tertiary" type="tertiary"
theme="borderless" theme="borderless"
icon={<IconPlus />} icon={<IconPlus />}
@ -70,6 +75,8 @@ const AddDocument = () => {
let scrollTimer; let scrollTimer;
const inheritColorStyle = { color: 'inherit' };
export const _Tree = ({ data, docAsLink, getDocLink, isShareMode = false, needAddDocument = false }) => { export const _Tree = ({ data, docAsLink, getDocLink, isShareMode = false, needAddDocument = false }) => {
const { query } = useRouter(); const { query } = useRouter();
const $container = useRef<HTMLDivElement>(null); const $container = useRef<HTMLDivElement>(null);
@ -92,7 +99,7 @@ export const _Tree = ({ data, docAsLink, getDocLink, isShareMode = false, needAd
ellipsis={{ ellipsis={{
showTooltip: { opts: { content: label, style: { wordBreak: 'break-all' }, position: 'right' } }, showTooltip: { opts: { content: label, style: { wordBreak: 'break-all' }, position: 'right' } },
}} }}
style={{ color: 'inherit' }} style={inheritColorStyle}
> >
{label} {label}
</Typography.Text> </Typography.Text>
@ -128,7 +135,7 @@ export const _Tree = ({ data, docAsLink, getDocLink, isShareMode = false, needAd
value={query.documentId} value={query.documentId}
defaultExpandedKeys={expandedKeys} defaultExpandedKeys={expandedKeys}
expandedKeys={expandedKeys} expandedKeys={expandedKeys}
onExpand={(expandedKeys) => setExpandedKeys(expandedKeys)} onExpand={setExpandedKeys}
/> />
{needAddDocument && <AddDocument />} {needAddDocument && <AddDocument />}
</div> </div>

View File

@ -11,7 +11,7 @@ import { HttpClient } from 'services/http-client';
*/ */
export const useCreateOrganization = () => { export const useCreateOrganization = () => {
const [apiWithLoading, loading] = useAsyncLoading((data) => const [apiWithLoading, loading] = useAsyncLoading((data) =>
HttpClient.request({ HttpClient.request<IOrganization>({
method: OrganizationApiDefinition.createOrganization.method, method: OrganizationApiDefinition.createOrganization.method,
url: OrganizationApiDefinition.createOrganization.client(), url: OrganizationApiDefinition.createOrganization.client(),
data, data,

View File

@ -319,7 +319,7 @@ export const useWikiDocuments = (wikiId) => {
export const getWikiMembers = ( export const getWikiMembers = (
wikiId, wikiId,
page, page,
pageSize, pageSize = 12,
cookie = null cookie = null
): Promise<{ data: Array<{ auth: IAuth; user: IUser }>; total: number }> => { ): Promise<{ data: Array<{ auth: IAuth; user: IUser }>; total: number }> => {
return HttpClient.request({ return HttpClient.request({

View File

@ -4,7 +4,7 @@ import { DataRender } from 'components/data-render';
import deepEqual from 'deep-equal'; import deepEqual from 'deep-equal';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import { SecureDocumentIllustration } from 'illustrations/secure-document'; import { SecureDocumentIllustration } from 'illustrations/secure-document';
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
import { Editor } from 'tiptap/core'; import { Editor } from 'tiptap/core';
import { IndexeddbPersistence } from 'tiptap/core/thritypart/y-indexeddb'; import { IndexeddbPersistence } from 'tiptap/core/thritypart/y-indexeddb';
@ -18,6 +18,14 @@ export type ICollaborationRefProps = {
getEditor: () => Editor; getEditor: () => Editor;
}; };
const errorContainerStyle = {
margin: '10%',
display: 'flex',
justifyContent: 'center',
flexDirection: 'column',
alignItems: 'center',
} as React.CSSProperties;
export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps, ref) => { export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps, ref) => {
const { const {
id: documentId, id: documentId,
@ -56,9 +64,9 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps,
onAwarenessUpdate && onAwarenessUpdate(users); onAwarenessUpdate && onAwarenessUpdate(users);
lastAwarenessRef.current = users; lastAwarenessRef.current = users;
}, },
onAuthenticationFailed() { onAuthenticationFailed(e) {
toggleLoading(false); toggleLoading(false);
setError(new Error('鉴权失败!暂时无法提供服务')); setError(e || new Error('鉴权失败!暂时无法提供服务'));
}, },
onSynced() { onSynced() {
toggleLoading(false); toggleLoading(false);
@ -69,6 +77,34 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps,
} as any); } as any);
}, [documentId, user, type, editable, onAwarenessUpdate, toggleLoading]); }, [documentId, user, type, editable, onAwarenessUpdate, toggleLoading]);
const renderEditor = useCallback(
() => (
<EditorInstance
ref={$editor}
documentId={documentId}
editable={editable}
menubar={menubar}
hocuspocusProvider={hocuspocusProvider}
onTitleUpdate={onTitleUpdate}
user={user}
status={status}
hideComment={hideComment}
renderInEditorPortal={renderInEditorPortal}
/>
),
[documentId, editable, hideComment, hocuspocusProvider, menubar, onTitleUpdate, renderInEditorPortal, status, user]
);
const renderError = useCallback(
(error) => (
<div style={errorContainerStyle}>
<SecureDocumentIllustration />
<Text type="danger">{(error && error.message) || '未知错误'}</Text>
</div>
),
[]
);
useImperativeHandle( useImperativeHandle(
ref, ref,
() => () =>
@ -101,49 +137,19 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps,
}, [hocuspocusProvider]); }, [hocuspocusProvider]);
return ( return (
<> <div className={styles.wrap}>
<div className={styles.wrap}> <DataRender
<DataRender loading={loading}
loading={loading} loadingContent={
loadingContent={ <div style={{ margin: 'auto' }}>
<div style={{ margin: 'auto' }}> <Spin />
<Spin /> </div>
</div> }
} error={error}
error={error} errorContent={renderError}
errorContent={(error) => ( normalContent={renderEditor}
<div />
style={{ </div>
margin: '10%',
display: 'flex',
justifyContent: 'center',
flexDirection: 'column',
alignItems: 'center',
}}
>
<SecureDocumentIllustration />
<Text style={{ marginTop: 12 }} type="danger">
{(error && error.message) || '未知错误'}
</Text>
</div>
)}
normalContent={() => (
<EditorInstance
ref={$editor}
documentId={documentId}
editable={editable}
menubar={menubar}
hocuspocusProvider={hocuspocusProvider}
onTitleUpdate={onTitleUpdate}
user={user}
status={status}
hideComment={hideComment}
renderInEditorPortal={renderInEditorPortal}
/>
)}
/>
</div>
</>
); );
}); });

View File

@ -3,7 +3,7 @@ import { IsNotEmpty, IsString, MinLength } from 'class-validator';
export class LoginUserDto { export class LoginUserDto {
@IsString({ message: '用户名称类型错误正确类型为String' }) @IsString({ message: '用户名称类型错误正确类型为String' })
@IsNotEmpty({ message: '用户账号不能为空' }) @IsNotEmpty({ message: '用户账号不能为空' })
@MinLength(5, { message: '用户账号至少5个字符' }) @MinLength(1, { message: '用户账号至少1个字符' })
readonly name: string; readonly name: string;
@IsString({ message: '用户密码类型错误正确类型为String' }) @IsString({ message: '用户密码类型错误正确类型为String' })

View File

@ -253,7 +253,7 @@ export class UserService {
const currentSystemConfig = await this.systemService.getConfigFromDatabase(); const currentSystemConfig = await this.systemService.getConfigFromDatabase();
const oldData = await this.userRepo.findOne(user.id); const oldData = await this.userRepo.findOne(user.id);
if (oldData.email !== dto.email) { if (oldData && dto && oldData.email !== dto.email) {
if (await this.userRepo.findOne({ where: { email: dto.email } })) { if (await this.userRepo.findOne({ where: { email: dto.email } })) {
throw new HttpException('该邮箱已被注册', HttpStatus.BAD_REQUEST); throw new HttpException('该邮箱已被注册', HttpStatus.BAD_REQUEST);
} }