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 [cover, setCover] = useState('');
|
||||
|
||||
const editor = useEditor({
|
||||
editable: false,
|
||||
extensions: CollaborationKit.filter((ext) => ['title', 'doc'].indexOf(ext.name) < 0).concat(Document),
|
||||
content: { type: 'doc', content: [] },
|
||||
});
|
||||
const editor = useEditor(
|
||||
{
|
||||
editable: false,
|
||||
extensions: CollaborationKit.filter((ext) => ['title', 'doc'].indexOf(ext.name) < 0).concat(Document),
|
||||
content: { type: 'doc', content: [] },
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const startPowerpoint = useCallback(() => {
|
||||
toggleVisible(true);
|
||||
|
|
|
@ -14,6 +14,8 @@ interface IProps {
|
|||
|
||||
const { Text } = Typography;
|
||||
|
||||
const style = { cursor: 'pointer' };
|
||||
|
||||
export const DocumentLinkCopyer: React.FC<IProps> = ({ organizationId, wikiId, documentId, render }) => {
|
||||
const handle = useCallback(() => {
|
||||
copy(buildUrl(`/app/org/${organizationId}/wiki/${wikiId}/doc/${documentId}`));
|
||||
|
@ -29,7 +31,7 @@ export const DocumentLinkCopyer: React.FC<IProps> = ({ organizationId, wikiId, d
|
|||
return render ? (
|
||||
<>{render({ copy: handle, children: content })}</>
|
||||
) : (
|
||||
<Text onClick={handle} style={{ cursor: 'pointer' }}>
|
||||
<Text onClick={handle} style={style}>
|
||||
{content}
|
||||
</Text>
|
||||
);
|
||||
|
|
|
@ -8,18 +8,18 @@ interface IProps {
|
|||
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 }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
borderTop: '1px solid var(--semi-color-border)',
|
||||
marginTop: '0.75em',
|
||||
padding: '16px 0',
|
||||
fontSize: 13,
|
||||
fontWeight: 'normal',
|
||||
color: 'var(--semi-color-text-0)',
|
||||
}}
|
||||
>
|
||||
<div style={style}>
|
||||
<Space>
|
||||
<Avatar size="small" src={document && document.createUser && document.createUser.avatar}>
|
||||
<IconUser />
|
||||
|
|
|
@ -29,6 +29,14 @@ interface IProps {
|
|||
documentId: string;
|
||||
}
|
||||
|
||||
const loadingStyle = {
|
||||
minHeight: 240,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: 'auto',
|
||||
};
|
||||
|
||||
export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
||||
const { isMobile } = IsOnMobile.useHook();
|
||||
const mounted = useMount();
|
||||
|
@ -132,15 +140,7 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
|||
<DataRender
|
||||
loading={docAuthLoading}
|
||||
loadingContent={
|
||||
<div
|
||||
style={{
|
||||
minHeight: 240,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: 'auto',
|
||||
}}
|
||||
>
|
||||
<div style={loadingStyle}>
|
||||
<Spin />
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import { Seo } from 'components/seo';
|
|||
import { Theme } from 'components/theme';
|
||||
import { User } from 'components/user';
|
||||
import { usePublicDocumentDetail } from 'data/document';
|
||||
import { useDocumentStyle } from 'hooks/use-document-style';
|
||||
import { useMount } from 'hooks/use-mount';
|
||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||
import { SecureDocumentIllustration } from 'illustrations/secure-document';
|
||||
|
@ -38,11 +37,7 @@ export const DocumentPublicReader: React.FC<IProps> = ({ documentId, hideLogo =
|
|||
const mounted = useMount();
|
||||
const { wikiId: currentWikiId } = useRouterQuery<{ wikiId: IWiki['id']; documentId: IDocument['id'] }>();
|
||||
const { data, loading, error, query } = usePublicDocumentDetail(documentId);
|
||||
const { width, fontSize } = useDocumentStyle();
|
||||
const { isMobile } = IsOnMobile.useHook();
|
||||
const editorWrapClassNames = useMemo(() => {
|
||||
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
|
||||
}, [width]);
|
||||
|
||||
const renderAuthor = useCallback(
|
||||
(element) => {
|
||||
|
|
|
@ -33,6 +33,15 @@ export const DocumentStar: React.FC<IProps> = ({ organizationId, wikiId, documen
|
|||
[toggleVisible]
|
||||
);
|
||||
|
||||
const toggleStarAction = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
toggleStar();
|
||||
},
|
||||
[toggleStar]
|
||||
);
|
||||
|
||||
return (
|
||||
<VisibilitySensor onChange={onViewportChange}>
|
||||
{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)',
|
||||
}}
|
||||
disabled={disabled}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
toggleStar();
|
||||
}}
|
||||
onClick={toggleStarAction}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
|
|
@ -31,16 +31,19 @@ export const DocumentVersion: React.FC<Partial<IProps>> = ({ documentId, onSelec
|
|||
const [selectedVersion, setSelectedVersion] = useState(null);
|
||||
const [diffVersion, setDiffVersion] = useState(null);
|
||||
|
||||
const editor = useEditor({
|
||||
editable: false,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: 'is-editable',
|
||||
const editor = useEditor(
|
||||
{
|
||||
editable: false,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: 'is-editable',
|
||||
},
|
||||
},
|
||||
extensions: CollaborationKit,
|
||||
content: { type: 'doc', content: [] },
|
||||
},
|
||||
extensions: CollaborationKit,
|
||||
content: { type: 'doc', content: [] },
|
||||
});
|
||||
[]
|
||||
);
|
||||
|
||||
const close = useCallback(() => {
|
||||
toggleVisible(false);
|
||||
|
|
|
@ -94,7 +94,7 @@ export const Import: React.FC<IProps> = ({ wikiId }) => {
|
|||
<div style={{ marginTop: 16 }}>
|
||||
<Upload
|
||||
action=""
|
||||
accept="text/markdown"
|
||||
accept=".md,.MD,.Md,.mD"
|
||||
draggable
|
||||
multiple
|
||||
ref={$upload}
|
||||
|
|
|
@ -22,10 +22,13 @@ interface IProps {
|
|||
|
||||
const { Text } = Typography;
|
||||
|
||||
const defaultGetDocLink = (document) =>
|
||||
`/app/org/${document.organizationId}/wiki/${document.wikiId}/doc/${document.id}`;
|
||||
|
||||
export const WikiTocs: React.FC<IProps> = ({
|
||||
wikiId,
|
||||
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 { data: wiki, loading: wikiLoading, error: wikiError } = useWikiDetail(wikiId);
|
||||
|
@ -129,7 +132,9 @@ export const WikiTocs: React.FC<IProps> = ({
|
|||
</Avatar>
|
||||
<Text strong>{wiki.name}</Text>
|
||||
</span>
|
||||
<IconSmallTriangleDown />
|
||||
<Text>
|
||||
<IconSmallTriangleDown />
|
||||
</Text>
|
||||
</div>
|
||||
</Dropdown>
|
||||
) : (
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { Banner, Button, Toast, Tree, Typography } from '@douyinfe/semi-ui';
|
||||
import { DataRender } from 'components/data-render';
|
||||
import { Resizeable } from 'components/resizeable';
|
||||
import { useWikiTocs } from 'data/wiki';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
|
@ -18,7 +17,7 @@ interface IDataNode {
|
|||
children?: Array<IDataNode>;
|
||||
}
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
const { Text } = Typography;
|
||||
|
||||
const extractRelation = (treeData: Array<IDataNode>) => {
|
||||
const res = [];
|
||||
|
@ -41,6 +40,8 @@ const extractRelation = (treeData: Array<IDataNode>) => {
|
|||
return res;
|
||||
};
|
||||
|
||||
const marginBottomStyle = { marginBottom: 16 };
|
||||
|
||||
export const WikiTocsManager: React.FC<IProps> = ({ wikiId }) => {
|
||||
const { data: tocs, loading: tocsLoading, error: tocsError, update: updateTocs } = useWikiTocs(wikiId);
|
||||
|
||||
|
@ -105,6 +106,10 @@ export const WikiTocsManager: React.FC<IProps> = ({ wikiId }) => {
|
|||
[treeData]
|
||||
);
|
||||
|
||||
const renderNorContent = useCallback(() => {
|
||||
return <Tree treeData={treeData} draggable onDrop={onDrop} expandAll />;
|
||||
}, [treeData, onDrop]);
|
||||
|
||||
const submit = useCallback(() => {
|
||||
const data = extractRelation(treeData);
|
||||
updateTocs(data).then(() => {
|
||||
|
@ -121,16 +126,10 @@ export const WikiTocsManager: React.FC<IProps> = ({ wikiId }) => {
|
|||
icon={null}
|
||||
closeIcon={null}
|
||||
description={<Text>在下方进行拖拽以重新整理目录结构</Text>}
|
||||
style={{ marginBottom: 16 }}
|
||||
style={marginBottomStyle}
|
||||
/>
|
||||
<div className={styles.tocsWrap}>
|
||||
<DataRender
|
||||
loading={tocsLoading}
|
||||
error={tocsError}
|
||||
normalContent={() => {
|
||||
return <Tree treeData={treeData} draggable onDrop={onDrop} expandAll />;
|
||||
}}
|
||||
/>
|
||||
<DataRender loading={tocsLoading} error={tocsError} normalContent={renderNorContent} />
|
||||
</div>
|
||||
<div className={styles.btnWrap}>
|
||||
<Button disabled={!changed} onClick={submit} theme="solid">
|
||||
|
|
|
@ -18,6 +18,8 @@ interface IProps {
|
|||
openNewTab?: boolean;
|
||||
}
|
||||
|
||||
const marginTopStyle = { marginTop: 4 };
|
||||
|
||||
export const NavItem: React.FC<IProps> = ({
|
||||
icon,
|
||||
text,
|
||||
|
@ -38,7 +40,7 @@ export const NavItem: React.FC<IProps> = ({
|
|||
return (
|
||||
<div
|
||||
className={cls(styles.navItemWrap, isActive && styles.isActive, hoverable && styles.hoverable)}
|
||||
style={{ marginTop: 4 }}
|
||||
style={marginTopStyle}
|
||||
>
|
||||
{href ? (
|
||||
<Link href={href as UrlObject}>
|
||||
|
|
|
@ -22,11 +22,13 @@ interface IProps {
|
|||
|
||||
const { Text } = Typography;
|
||||
|
||||
const defaultGetDocLink = (document) => `/share/wiki/${document.wikiId}/document/${document.id}`;
|
||||
|
||||
export const WikiPublicTocs: React.FC<IProps> = ({
|
||||
pageTitle,
|
||||
wikiId,
|
||||
docAsLink = '/share/wiki/[wikiId]/document/[documentId]',
|
||||
getDocLink = (document) => `/share/wiki/${document.wikiId}/document/${document.id}`,
|
||||
getDocLink = defaultGetDocLink,
|
||||
}) => {
|
||||
const { pathname } = useRouter();
|
||||
const { data: wiki, loading: wikiLoading, error: wikiError } = usePublicWikiDetail(wikiId);
|
||||
|
|
|
@ -14,6 +14,14 @@ import styles from './index.module.scss';
|
|||
import { findParents } from './utils';
|
||||
|
||||
const Actions = ({ node }) => {
|
||||
const createDocument = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
triggerCreateDocument({ wikiId: node.wikiId, documentId: node.id });
|
||||
},
|
||||
[node.wikiId, node.id]
|
||||
);
|
||||
|
||||
return (
|
||||
<span className={styles.right}>
|
||||
<DocumentActions
|
||||
|
@ -28,10 +36,7 @@ const Actions = ({ node }) => {
|
|||
></DocumentActions>
|
||||
<Button
|
||||
className={styles.hoverVisible}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
triggerCreateDocument({ wikiId: node.wikiId, documentId: node.id });
|
||||
}}
|
||||
onClick={createDocument}
|
||||
type="tertiary"
|
||||
theme="borderless"
|
||||
icon={<IconPlus />}
|
||||
|
@ -70,6 +75,8 @@ const AddDocument = () => {
|
|||
|
||||
let scrollTimer;
|
||||
|
||||
const inheritColorStyle = { color: 'inherit' };
|
||||
|
||||
export const _Tree = ({ data, docAsLink, getDocLink, isShareMode = false, needAddDocument = false }) => {
|
||||
const { query } = useRouter();
|
||||
const $container = useRef<HTMLDivElement>(null);
|
||||
|
@ -92,7 +99,7 @@ export const _Tree = ({ data, docAsLink, getDocLink, isShareMode = false, needAd
|
|||
ellipsis={{
|
||||
showTooltip: { opts: { content: label, style: { wordBreak: 'break-all' }, position: 'right' } },
|
||||
}}
|
||||
style={{ color: 'inherit' }}
|
||||
style={inheritColorStyle}
|
||||
>
|
||||
{label}
|
||||
</Typography.Text>
|
||||
|
@ -128,7 +135,7 @@ export const _Tree = ({ data, docAsLink, getDocLink, isShareMode = false, needAd
|
|||
value={query.documentId}
|
||||
defaultExpandedKeys={expandedKeys}
|
||||
expandedKeys={expandedKeys}
|
||||
onExpand={(expandedKeys) => setExpandedKeys(expandedKeys)}
|
||||
onExpand={setExpandedKeys}
|
||||
/>
|
||||
{needAddDocument && <AddDocument />}
|
||||
</div>
|
||||
|
|
|
@ -11,7 +11,7 @@ import { HttpClient } from 'services/http-client';
|
|||
*/
|
||||
export const useCreateOrganization = () => {
|
||||
const [apiWithLoading, loading] = useAsyncLoading((data) =>
|
||||
HttpClient.request({
|
||||
HttpClient.request<IOrganization>({
|
||||
method: OrganizationApiDefinition.createOrganization.method,
|
||||
url: OrganizationApiDefinition.createOrganization.client(),
|
||||
data,
|
||||
|
|
|
@ -319,7 +319,7 @@ export const useWikiDocuments = (wikiId) => {
|
|||
export const getWikiMembers = (
|
||||
wikiId,
|
||||
page,
|
||||
pageSize,
|
||||
pageSize = 12,
|
||||
cookie = null
|
||||
): Promise<{ data: Array<{ auth: IAuth; user: IUser }>; total: number }> => {
|
||||
return HttpClient.request({
|
||||
|
|
|
@ -4,7 +4,7 @@ import { DataRender } from 'components/data-render';
|
|||
import deepEqual from 'deep-equal';
|
||||
import { useToggle } from 'hooks/use-toggle';
|
||||
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 { IndexeddbPersistence } from 'tiptap/core/thritypart/y-indexeddb';
|
||||
|
||||
|
@ -18,6 +18,14 @@ export type ICollaborationRefProps = {
|
|||
getEditor: () => Editor;
|
||||
};
|
||||
|
||||
const errorContainerStyle = {
|
||||
margin: '10%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
} as React.CSSProperties;
|
||||
|
||||
export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps, ref) => {
|
||||
const {
|
||||
id: documentId,
|
||||
|
@ -56,9 +64,9 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps,
|
|||
onAwarenessUpdate && onAwarenessUpdate(users);
|
||||
lastAwarenessRef.current = users;
|
||||
},
|
||||
onAuthenticationFailed() {
|
||||
onAuthenticationFailed(e) {
|
||||
toggleLoading(false);
|
||||
setError(new Error('鉴权失败!暂时无法提供服务'));
|
||||
setError(e || new Error('鉴权失败!暂时无法提供服务'));
|
||||
},
|
||||
onSynced() {
|
||||
toggleLoading(false);
|
||||
|
@ -69,6 +77,34 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps,
|
|||
} as any);
|
||||
}, [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(
|
||||
ref,
|
||||
() =>
|
||||
|
@ -101,49 +137,19 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps,
|
|||
}, [hocuspocusProvider]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.wrap}>
|
||||
<DataRender
|
||||
loading={loading}
|
||||
loadingContent={
|
||||
<div style={{ margin: 'auto' }}>
|
||||
<Spin />
|
||||
</div>
|
||||
}
|
||||
error={error}
|
||||
errorContent={(error) => (
|
||||
<div
|
||||
style={{
|
||||
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>
|
||||
</>
|
||||
<div className={styles.wrap}>
|
||||
<DataRender
|
||||
loading={loading}
|
||||
loadingContent={
|
||||
<div style={{ margin: 'auto' }}>
|
||||
<Spin />
|
||||
</div>
|
||||
}
|
||||
error={error}
|
||||
errorContent={renderError}
|
||||
normalContent={renderEditor}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { IsNotEmpty, IsString, MinLength } from 'class-validator';
|
|||
export class LoginUserDto {
|
||||
@IsString({ message: '用户名称类型错误(正确类型为:String)' })
|
||||
@IsNotEmpty({ message: '用户账号不能为空' })
|
||||
@MinLength(5, { message: '用户账号至少5个字符' })
|
||||
@MinLength(1, { message: '用户账号至少1个字符' })
|
||||
readonly name: string;
|
||||
|
||||
@IsString({ message: '用户密码类型错误(正确类型为:String)' })
|
||||
|
|
|
@ -253,7 +253,7 @@ export class UserService {
|
|||
const currentSystemConfig = await this.systemService.getConfigFromDatabase();
|
||||
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 } })) {
|
||||
throw new HttpException('该邮箱已被注册', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue