mirror of https://github.com/fantasticit/think.git
Merge pull request #194 from fantasticit/fix/0915
This commit is contained in:
commit
b71195780f
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 />
|
||||||
|
|
|
@ -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>
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,11 +37,7 @@ 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) => {
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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)' })
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue