client: improve tocs layout

pull/64/head
fantasticit 2022-05-29 22:53:42 +08:00
parent cd31c2c765
commit 3d5cf24ee5
13 changed files with 147 additions and 192 deletions

View File

@ -16,11 +16,9 @@ interface IProps {
user: ILoginUser;
documentId: string;
authority: IAuthority;
className: string;
style: React.CSSProperties;
}
export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, authority, className, style }) => {
export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, authority }) => {
const $hasShowUserSettingModal = useRef(false);
const $editor = useRef<ICollaborationRefProps>();
const mounted = useMount();
@ -92,7 +90,7 @@ export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, author
}, [users, currentUser, toggleMentionUsersSettingVisible]);
return (
<div className={cls(styles.editorWrap, className)} style={style}>
<div className={cls(styles.editorWrap)}>
{mounted && (
<CollaborationEditor
ref={$editor}

View File

@ -28,37 +28,6 @@
}
.editorWrap {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
> div {
> main {
padding: 24px 24px 96px;
}
}
&.isStandardWidth {
> div {
> main {
> div:first-of-type {
width: 96%;
max-width: 750px;
margin: 0 auto;
}
}
}
}
&.isFullWidth {
width: 100%;
margin: 0 auto;
> div {
> header {
justify-content: flex-start;
}
}
}
}

View File

@ -1,5 +1,5 @@
import { IconChevronLeft } from '@douyinfe/semi-icons';
import { Button, Nav, Skeleton, Space, Spin, Tooltip, Typography } from '@douyinfe/semi-ui';
import { Button, Nav, Skeleton, Space, Tooltip, Typography } from '@douyinfe/semi-ui';
import { DataRender } from 'components/data-render';
import { Divider } from 'components/divider';
import { DocumentCollaboration } from 'components/document/collaboration';
@ -14,7 +14,6 @@ import { useDocumentDetail } from 'data/document';
import { useUser } from 'data/user';
import { CHANGE_DOCUMENT_TITLE, event, triggerUseDocumentVersion } from 'event';
import { triggerRefreshTocs } from 'event';
import { useDocumentStyle } from 'hooks/use-document-style';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useWindowSize } from 'hooks/use-window-size';
import { SecureDocumentIllustration } from 'illustrations/secure-document';
@ -33,10 +32,6 @@ interface IProps {
export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
const { isMobile } = IsOnMobile.useHook();
const { width: windowWith } = useWindowSize();
const { width, fontSize } = useDocumentStyle();
const editorWrapClassNames = useMemo(() => {
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
}, [width]);
const [title, setTitle] = useState('');
const { user } = useUser();
const { data: documentAndAuth, loading: docAuthLoading, error: docAuthError } = useDocumentDetail(documentId);
@ -125,13 +120,7 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
</div>
)}
{document && <Seo title={document.title} />}
<Editor
user={user}
documentId={documentId}
authority={authority}
className={editorWrapClassNames}
style={{ fontSize }}
/>
<Editor user={user} documentId={documentId} authority={authority} />
</main>
</div>
);

View File

@ -9,34 +9,7 @@
}
.contentWrap {
flex: 1;
overflow: hidden;
> div {
height: 100%;
padding: 24px 16px 48px;
overflow: auto;
}
.editorWrap {
min-height: 100%;
padding-bottom: 24px;
margin: 0 auto;
&.isStandardWidth {
width: 96%;
max-width: 750px;
}
&.isFullWidth {
width: 100%;
}
}
.commentWrap {
padding: 16px 0 32px;
border-top: 1px solid var(--semi-color-border);
}
}
.mobileToolbar {

View File

@ -132,58 +132,45 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
></Nav>
</Header>
<Layout className={styles.contentWrap}>
<div ref={setContainer} id="js-tocs-container">
<div className={cls(styles.editorWrap, editorWrapClassNames)} style={{ fontSize }}>
<div id="js-reader-container">
<DataRender
loading={docAuthLoading}
loadingContent={
<div
style={{
minHeight: 240,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: 'auto',
}}
>
<Spin />
</div>
}
error={docAuthError}
normalContent={() => (
<>
<Seo title={document.title} />
{mounted && (
<CollaborationEditor
editable={false}
user={user}
id={documentId}
type="document"
renderInEditorPortal={renderAuthor}
onAwarenessUpdate={triggerJoinUser}
renderOnMount={
<div className={styles.commentWrap}>
<CommentEditor documentId={documentId} />
</div>
}
/>
)}
{!isMobile && authority && authority.editable && container && (
<BackTop style={editBtnStyle} onClick={gotoEdit} target={() => container} visibilityHeight={200}>
<IconEdit />
</BackTop>
)}
<ImageViewer containerSelector="#js-reader-container" />
{container && (
<BackTop style={{ bottom: 65, right: isMobile ? 16 : 100 }} target={() => container} />
)}
</>
)}
/>
<DataRender
loading={docAuthLoading}
loadingContent={
<div
style={{
minHeight: 240,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: 'auto',
}}
>
<Spin />
</div>
</div>
</div>
}
error={docAuthError}
normalContent={() => (
<>
<Seo title={document.title} />
{mounted && (
<CollaborationEditor
editable={false}
user={user}
id={documentId}
type="document"
renderInEditorPortal={renderAuthor}
onAwarenessUpdate={triggerJoinUser}
/>
)}
{/* {!isMobile && authority && authority.editable && container && (
<BackTop style={editBtnStyle} onClick={gotoEdit} target={() => container} visibilityHeight={200}>
<IconEdit />
</BackTop>
)}
<ImageViewer containerSelector="#js-reader-container" />
{container && <BackTop style={{ bottom: 65, right: isMobile ? 16 : 100 }} target={() => container} />} */}
</>
)}
/>
</Layout>
{isMobile && <div className={styles.mobileToolbar}>{actions}</div>}
</div>

View File

@ -9,28 +9,7 @@
}
.contentWrap {
position: relative;
z-index: 1;
flex: 1;
padding: 24px 24px 48px;
overflow: auto;
.editorWrap {
min-height: 100%;
padding-bottom: 24px;
&.isStandardWidth {
width: 96%;
max-width: 750px;
margin: 0 auto;
overflow: auto;
}
&.isFullWidth {
width: 100%;
margin: 0 auto;
overflow: auto;
}
}
overflow: hidden;
}
}

View File

@ -1,5 +1,4 @@
import {
BackTop,
Breadcrumb,
Button,
Form,
@ -10,10 +9,8 @@ import {
Typography,
} from '@douyinfe/semi-ui';
import { FormApi } from '@douyinfe/semi-ui/lib/es/form';
import cls from 'classnames';
import { DataRender } from 'components/data-render';
import { DocumentStyle } from 'components/document/style';
import { ImageViewer } from 'components/image-viewer';
import { LogoImage, LogoText } from 'components/logo';
import { Seo } from 'components/seo';
import { Theme } from 'components/theme';
@ -30,7 +27,7 @@ import { CollaborationEditor } from 'tiptap/editor';
import { Author } from '../author';
import styles from './index.module.scss';
const { Header, Content } = Layout;
const { Header } = Layout;
const { Text } = Typography;
interface IProps {
@ -99,11 +96,7 @@ export const DocumentPublicReader: React.FC<IProps> = ({ documentId, hideLogo =
}
return (
<div
id="js-share-document-editor-container"
className={cls(styles.editorWrap, editorWrapClassNames)}
style={{ fontSize }}
>
<>
{data && <Seo title={data.title} />}
{mounted && <CollaborationEditor
menubar={false}
@ -111,14 +104,10 @@ export const DocumentPublicReader: React.FC<IProps> = ({ documentId, hideLogo =
user={null}
id={documentId}
type="document"
hideComment
renderInEditorPortal={renderAuthor}
/>}
<ImageViewer containerSelector="#js-share-document-editor-container" />
<BackTop
style={{ bottom: 65, right: isMobile ? 16 : 100 }}
target={() => document.querySelector('#js-share-document-editor-container').parentNode}
/>
</div>
</>
)
}, [error, data, mounted, editorWrapClassNames, fontSize])

View File

@ -2,9 +2,11 @@ import { BackTop, Toast } from '@douyinfe/semi-ui';
import { HocuspocusProvider } from '@hocuspocus/provider';
import cls from 'classnames';
import { Banner } from 'components/banner';
import { CommentEditor } from 'components/document/comments';
import { LogoName } from 'components/logo';
import { getRandomColor } from 'helpers/color';
import { isAndroid, isIOS } from 'helpers/env';
import { useDocumentStyle } from 'hooks/use-document-style';
import { useNetwork } from 'hooks/use-network';
import { IsOnMobile } from 'hooks/use-on-mobile';
import { useToggle } from 'hooks/use-toggle';
@ -21,14 +23,25 @@ import { ICollaborationEditorProps, ProviderStatus } from './type';
type IProps = Pick<
ICollaborationEditorProps,
'editable' | 'user' | 'onTitleUpdate' | 'menubar' | 'renderInEditorPortal'
'editable' | 'user' | 'onTitleUpdate' | 'menubar' | 'renderInEditorPortal' | 'hideComment'
> & {
hocuspocusProvider: HocuspocusProvider;
status: ProviderStatus;
documentId: string;
};
export const EditorInstance = forwardRef((props: IProps, ref) => {
const { hocuspocusProvider, editable, user, onTitleUpdate, status, menubar, renderInEditorPortal } = props;
const {
hocuspocusProvider,
documentId,
editable,
user,
hideComment,
status,
menubar,
renderInEditorPortal,
onTitleUpdate,
} = props;
const $headerContainer = useRef<HTMLDivElement>();
const $mainContainer = useRef<HTMLDivElement>();
const { isMobile } = IsOnMobile.useHook();
@ -72,6 +85,10 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
[editable, user, onTitleUpdate, hocuspocusProvider]
);
const [headings, setHeadings] = useState([]);
const { width, fontSize } = useDocumentStyle();
const editorWrapClassNames = useMemo(() => {
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
}, [width]);
useImperativeHandle(ref, () => editor);
@ -175,9 +192,28 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
</header>
)}
<main ref={$mainContainer} id={editable ? 'js-tocs-container' : ''}>
<EditorContent editor={editor} />
{!isMobile && editor && headings.length ? <Tocs tocs={headings} editor={editor} /> : null}
<main
ref={$mainContainer}
id={'js-tocs-container'}
style={{
padding: isMobile ? '0 24px' : '0 6rem',
}}
>
<div className={cls(styles.contentWrap, editorWrapClassNames)}>
<div style={{ fontSize }}>
<EditorContent editor={editor} />
</div>
{!editable && !hideComment && (
<div className={styles.commentWrap}>
<CommentEditor documentId={documentId} />
</div>
)}
</div>
{!isMobile && editor && headings.length ? (
<div className={styles.tocsWrap}>
<Tocs tocs={headings} editor={editor} />
</div>
) : null}
{protals}
</main>

View File

@ -7,7 +7,6 @@
flex-direction: column;
> header {
position: relative;
z-index: 110;
display: flex;
justify-content: center;
@ -43,13 +42,32 @@
}
> main {
position: relative;
display: flex;
flex: 1;
overflow: auto;
flex: 1;
justify-content: center;
flex-wrap: nowrap;
> div:first-of-type {
flex: 1;
.contentWrap {
width: 100%;
&.isStandardWidth {
max-width: 750px;
}
&.isFullWidth {
max-width: 100%;
}
.commentWrap {
padding: 16px 0 32px;
border-top: 1px solid var(--semi-color-border);
}
}
.tocsWrap {
position: relative;
}
}
}

View File

@ -26,7 +26,7 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps,
onTitleUpdate,
user,
menubar,
renderOnMount,
hideComment,
renderInEditorPortal,
onAwarenessUpdate,
} = props;
@ -124,18 +124,19 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps,
normalContent={() => (
<EditorInstance
ref={$editor}
documentId={documentId}
editable={editable}
menubar={menubar}
hocuspocusProvider={hocuspocusProvider}
onTitleUpdate={onTitleUpdate}
user={user}
status={status}
hideComment={hideComment}
renderInEditorPortal={renderInEditorPortal}
/>
)}
/>
</div>
{loading || !!error ? null : renderOnMount}
</>
);
});

View File

@ -28,6 +28,11 @@ export interface ICollaborationEditorProps {
*/
user: ILoginUser | null;
/**
*
*/
hideComment?: boolean;
/**
*
*/

View File

@ -1,7 +1,8 @@
.wrapper {
position: fixed;
right: 0;
background-color: var(--semi-color-nav-bg);
padding-top: 1rem;
padding-right: 1rem;
padding-left: 2rem;
> header {
margin-bottom: 12px;
@ -18,15 +19,9 @@
.collapsedItem {
position: relative;
height: 2px;
width: 10px;
height: 10px;
background-color: #d8d8d8;
}
:global {
.semi-anchor-link-title-active {
.collapsedItem {
background-color: var(--semi-color-primary);
}
}
border-radius: 50%;
}
}

View File

@ -1,6 +1,7 @@
import { IconDoubleChevronLeft, IconDoubleChevronRight } from '@douyinfe/semi-icons';
import { Anchor, Button } from '@douyinfe/semi-ui';
import { Anchor, Button, Tooltip } from '@douyinfe/semi-ui';
import { Editor } from '@tiptap/core';
import { throttle } from 'helpers/throttle';
import { useDocumentStyle, Width } from 'hooks/use-document-style';
import { useToggle } from 'hooks/use-toggle';
import React, { useCallback, useEffect } from 'react';
@ -15,19 +16,20 @@ interface IToc {
text: string;
}
const MAX_LEVEL = 6;
const Toc = ({ toc, collapsed }) => {
return (
<Anchor.Link
href={`#${toc.id}`}
title={
collapsed ? (
<div style={{ width: 8 * (MAX_LEVEL - toc.level + 1) }} className={styles.collapsedItem}></div>
<Tooltip content={toc.text} position="right">
<div className={styles.collapsedItem}></div>
</Tooltip>
) : (
toc.text
)
}
style={{ paddingLeft: collapsed ? 16 : 8 }}
>
{toc.children && toc.children.length
? toc.children.map((toc) => <Toc key={toc.text} toc={toc} collapsed={collapsed} />)
@ -36,10 +38,12 @@ const Toc = ({ toc, collapsed }) => {
);
};
const FULL_WIDTH = 1200;
export const Tocs: React.FC<{ tocs: Array<IToc>; editor: Editor }> = ({ tocs = [], editor }) => {
const [hasToc, toggleHasToc] = useToggle(false);
const { width } = useDocumentStyle();
const [collapsed, toggleCollapsed] = useToggle(true);
const { width } = useDocumentStyle();
const getContainer = useCallback(() => {
return document.querySelector(`#js-tocs-container`);
@ -57,7 +61,6 @@ export const Tocs: React.FC<{ tocs: Array<IToc>; editor: Editor }> = ({ tocs = [
const listener = () => {
const nodes = findNode(editor, TableOfContents.name);
const hasTocNow = !!(nodes && nodes.length);
if (hasTocNow !== hasToc) {
toggleHasToc(hasTocNow);
}
@ -70,6 +73,19 @@ export const Tocs: React.FC<{ tocs: Array<IToc>; editor: Editor }> = ({ tocs = [
};
}, [editor, hasToc, toggleHasToc]);
useEffect(() => {
const el = document.querySelector(`#js-tocs-container`) as HTMLDivElement;
const handler = throttle(() => {
toggleCollapsed(el.offsetWidth <= FULL_WIDTH);
}, 200);
window.addEventListener('resize', handler);
return () => {
window.removeEventListener('resize', handler);
};
}, [toggleCollapsed]);
return (
<div className={styles.wrapper} style={{ display: hasToc ? 'block' : 'none' }}>
<header>
@ -82,7 +98,7 @@ export const Tocs: React.FC<{ tocs: Array<IToc>; editor: Editor }> = ({ tocs = [
</header>
<main>
<Anchor
railTheme={'muted'}
railTheme={collapsed ? 'muted' : 'tertiary'}
maxHeight={'calc(100vh - 360px)'}
getContainer={getContainer}
maxWidth={collapsed ? 56 : 180}