mirror of https://github.com/fantasticit/think.git
client: improve tocs layout
This commit is contained in:
parent
cd31c2c765
commit
3d5cf24ee5
|
@ -16,11 +16,9 @@ interface IProps {
|
||||||
user: ILoginUser;
|
user: ILoginUser;
|
||||||
documentId: string;
|
documentId: string;
|
||||||
authority: IAuthority;
|
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 $hasShowUserSettingModal = useRef(false);
|
||||||
const $editor = useRef<ICollaborationRefProps>();
|
const $editor = useRef<ICollaborationRefProps>();
|
||||||
const mounted = useMount();
|
const mounted = useMount();
|
||||||
|
@ -92,7 +90,7 @@ export const Editor: React.FC<IProps> = ({ user: currentUser, documentId, author
|
||||||
}, [users, currentUser, toggleMentionUsersSettingVisible]);
|
}, [users, currentUser, toggleMentionUsersSettingVisible]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cls(styles.editorWrap, className)} style={style}>
|
<div className={cls(styles.editorWrap)}>
|
||||||
{mounted && (
|
{mounted && (
|
||||||
<CollaborationEditor
|
<CollaborationEditor
|
||||||
ref={$editor}
|
ref={$editor}
|
||||||
|
|
|
@ -28,37 +28,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.editorWrap {
|
.editorWrap {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { IconChevronLeft } from '@douyinfe/semi-icons';
|
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 { DataRender } from 'components/data-render';
|
||||||
import { Divider } from 'components/divider';
|
import { Divider } from 'components/divider';
|
||||||
import { DocumentCollaboration } from 'components/document/collaboration';
|
import { DocumentCollaboration } from 'components/document/collaboration';
|
||||||
|
@ -14,7 +14,6 @@ import { useDocumentDetail } from 'data/document';
|
||||||
import { useUser } from 'data/user';
|
import { useUser } from 'data/user';
|
||||||
import { CHANGE_DOCUMENT_TITLE, event, triggerUseDocumentVersion } from 'event';
|
import { CHANGE_DOCUMENT_TITLE, event, triggerUseDocumentVersion } from 'event';
|
||||||
import { triggerRefreshTocs } from 'event';
|
import { triggerRefreshTocs } from 'event';
|
||||||
import { useDocumentStyle } from 'hooks/use-document-style';
|
|
||||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||||
import { useWindowSize } from 'hooks/use-window-size';
|
import { useWindowSize } from 'hooks/use-window-size';
|
||||||
import { SecureDocumentIllustration } from 'illustrations/secure-document';
|
import { SecureDocumentIllustration } from 'illustrations/secure-document';
|
||||||
|
@ -33,10 +32,6 @@ interface IProps {
|
||||||
export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
||||||
const { isMobile } = IsOnMobile.useHook();
|
const { isMobile } = IsOnMobile.useHook();
|
||||||
const { width: windowWith } = useWindowSize();
|
const { width: windowWith } = useWindowSize();
|
||||||
const { width, fontSize } = useDocumentStyle();
|
|
||||||
const editorWrapClassNames = useMemo(() => {
|
|
||||||
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
|
|
||||||
}, [width]);
|
|
||||||
const [title, setTitle] = useState('');
|
const [title, setTitle] = useState('');
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { data: documentAndAuth, loading: docAuthLoading, error: docAuthError } = useDocumentDetail(documentId);
|
const { data: documentAndAuth, loading: docAuthLoading, error: docAuthError } = useDocumentDetail(documentId);
|
||||||
|
@ -125,13 +120,7 @@ export const DocumentEditor: React.FC<IProps> = ({ documentId }) => {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{document && <Seo title={document.title} />}
|
{document && <Seo title={document.title} />}
|
||||||
<Editor
|
<Editor user={user} documentId={documentId} authority={authority} />
|
||||||
user={user}
|
|
||||||
documentId={documentId}
|
|
||||||
authority={authority}
|
|
||||||
className={editorWrapClassNames}
|
|
||||||
style={{ fontSize }}
|
|
||||||
/>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,34 +9,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.contentWrap {
|
.contentWrap {
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
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 {
|
.mobileToolbar {
|
||||||
|
|
|
@ -132,9 +132,6 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
||||||
></Nav>
|
></Nav>
|
||||||
</Header>
|
</Header>
|
||||||
<Layout className={styles.contentWrap}>
|
<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
|
<DataRender
|
||||||
loading={docAuthLoading}
|
loading={docAuthLoading}
|
||||||
loadingContent={
|
loadingContent={
|
||||||
|
@ -162,28 +159,18 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
||||||
type="document"
|
type="document"
|
||||||
renderInEditorPortal={renderAuthor}
|
renderInEditorPortal={renderAuthor}
|
||||||
onAwarenessUpdate={triggerJoinUser}
|
onAwarenessUpdate={triggerJoinUser}
|
||||||
renderOnMount={
|
|
||||||
<div className={styles.commentWrap}>
|
|
||||||
<CommentEditor documentId={documentId} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!isMobile && authority && authority.editable && container && (
|
{/* {!isMobile && authority && authority.editable && container && (
|
||||||
<BackTop style={editBtnStyle} onClick={gotoEdit} target={() => container} visibilityHeight={200}>
|
<BackTop style={editBtnStyle} onClick={gotoEdit} target={() => container} visibilityHeight={200}>
|
||||||
<IconEdit />
|
<IconEdit />
|
||||||
</BackTop>
|
</BackTop>
|
||||||
)}
|
)}
|
||||||
<ImageViewer containerSelector="#js-reader-container" />
|
<ImageViewer containerSelector="#js-reader-container" />
|
||||||
{container && (
|
{container && <BackTop style={{ bottom: 65, right: isMobile ? 16 : 100 }} target={() => container} />} */}
|
||||||
<BackTop style={{ bottom: 65, right: isMobile ? 16 : 100 }} target={() => container} />
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
{isMobile && <div className={styles.mobileToolbar}>{actions}</div>}
|
{isMobile && <div className={styles.mobileToolbar}>{actions}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,28 +9,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.contentWrap {
|
.contentWrap {
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 24px 24px 48px;
|
overflow: hidden;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {
|
import {
|
||||||
BackTop,
|
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
Button,
|
Button,
|
||||||
Form,
|
Form,
|
||||||
|
@ -10,10 +9,8 @@ import {
|
||||||
Typography,
|
Typography,
|
||||||
} from '@douyinfe/semi-ui';
|
} from '@douyinfe/semi-ui';
|
||||||
import { FormApi } from '@douyinfe/semi-ui/lib/es/form';
|
import { FormApi } from '@douyinfe/semi-ui/lib/es/form';
|
||||||
import cls from 'classnames';
|
|
||||||
import { DataRender } from 'components/data-render';
|
import { DataRender } from 'components/data-render';
|
||||||
import { DocumentStyle } from 'components/document/style';
|
import { DocumentStyle } from 'components/document/style';
|
||||||
import { ImageViewer } from 'components/image-viewer';
|
|
||||||
import { LogoImage, LogoText } from 'components/logo';
|
import { LogoImage, LogoText } from 'components/logo';
|
||||||
import { Seo } from 'components/seo';
|
import { Seo } from 'components/seo';
|
||||||
import { Theme } from 'components/theme';
|
import { Theme } from 'components/theme';
|
||||||
|
@ -30,7 +27,7 @@ import { CollaborationEditor } from 'tiptap/editor';
|
||||||
import { Author } from '../author';
|
import { Author } from '../author';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
const { Header, Content } = Layout;
|
const { Header } = Layout;
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
|
@ -99,11 +96,7 @@ export const DocumentPublicReader: React.FC<IProps> = ({ documentId, hideLogo =
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
id="js-share-document-editor-container"
|
|
||||||
className={cls(styles.editorWrap, editorWrapClassNames)}
|
|
||||||
style={{ fontSize }}
|
|
||||||
>
|
|
||||||
{data && <Seo title={data.title} />}
|
{data && <Seo title={data.title} />}
|
||||||
{mounted && <CollaborationEditor
|
{mounted && <CollaborationEditor
|
||||||
menubar={false}
|
menubar={false}
|
||||||
|
@ -111,14 +104,10 @@ export const DocumentPublicReader: React.FC<IProps> = ({ documentId, hideLogo =
|
||||||
user={null}
|
user={null}
|
||||||
id={documentId}
|
id={documentId}
|
||||||
type="document"
|
type="document"
|
||||||
|
hideComment
|
||||||
renderInEditorPortal={renderAuthor}
|
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])
|
}, [error, data, mounted, editorWrapClassNames, fontSize])
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,11 @@ import { BackTop, Toast } from '@douyinfe/semi-ui';
|
||||||
import { HocuspocusProvider } from '@hocuspocus/provider';
|
import { HocuspocusProvider } from '@hocuspocus/provider';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { Banner } from 'components/banner';
|
import { Banner } from 'components/banner';
|
||||||
|
import { CommentEditor } from 'components/document/comments';
|
||||||
import { LogoName } from 'components/logo';
|
import { LogoName } from 'components/logo';
|
||||||
import { getRandomColor } from 'helpers/color';
|
import { getRandomColor } from 'helpers/color';
|
||||||
import { isAndroid, isIOS } from 'helpers/env';
|
import { isAndroid, isIOS } from 'helpers/env';
|
||||||
|
import { useDocumentStyle } from 'hooks/use-document-style';
|
||||||
import { useNetwork } from 'hooks/use-network';
|
import { useNetwork } from 'hooks/use-network';
|
||||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
|
@ -21,14 +23,25 @@ import { ICollaborationEditorProps, ProviderStatus } from './type';
|
||||||
|
|
||||||
type IProps = Pick<
|
type IProps = Pick<
|
||||||
ICollaborationEditorProps,
|
ICollaborationEditorProps,
|
||||||
'editable' | 'user' | 'onTitleUpdate' | 'menubar' | 'renderInEditorPortal'
|
'editable' | 'user' | 'onTitleUpdate' | 'menubar' | 'renderInEditorPortal' | 'hideComment'
|
||||||
> & {
|
> & {
|
||||||
hocuspocusProvider: HocuspocusProvider;
|
hocuspocusProvider: HocuspocusProvider;
|
||||||
status: ProviderStatus;
|
status: ProviderStatus;
|
||||||
|
documentId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EditorInstance = forwardRef((props: IProps, ref) => {
|
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 $headerContainer = useRef<HTMLDivElement>();
|
||||||
const $mainContainer = useRef<HTMLDivElement>();
|
const $mainContainer = useRef<HTMLDivElement>();
|
||||||
const { isMobile } = IsOnMobile.useHook();
|
const { isMobile } = IsOnMobile.useHook();
|
||||||
|
@ -72,6 +85,10 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
|
||||||
[editable, user, onTitleUpdate, hocuspocusProvider]
|
[editable, user, onTitleUpdate, hocuspocusProvider]
|
||||||
);
|
);
|
||||||
const [headings, setHeadings] = useState([]);
|
const [headings, setHeadings] = useState([]);
|
||||||
|
const { width, fontSize } = useDocumentStyle();
|
||||||
|
const editorWrapClassNames = useMemo(() => {
|
||||||
|
return width === 'standardWidth' ? styles.isStandardWidth : styles.isFullWidth;
|
||||||
|
}, [width]);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => editor);
|
useImperativeHandle(ref, () => editor);
|
||||||
|
|
||||||
|
@ -175,9 +192,28 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
|
||||||
</header>
|
</header>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<main ref={$mainContainer} id={editable ? 'js-tocs-container' : ''}>
|
<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} />
|
<EditorContent editor={editor} />
|
||||||
{!isMobile && editor && headings.length ? <Tocs tocs={headings} editor={editor} /> : null}
|
</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}
|
{protals}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
> header {
|
> header {
|
||||||
position: relative;
|
|
||||||
z-index: 110;
|
z-index: 110;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -43,13 +42,32 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
> main {
|
> main {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
> div:first-of-type {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
|
.contentWrap {
|
||||||
width: 100%;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps,
|
||||||
onTitleUpdate,
|
onTitleUpdate,
|
||||||
user,
|
user,
|
||||||
menubar,
|
menubar,
|
||||||
renderOnMount,
|
hideComment,
|
||||||
renderInEditorPortal,
|
renderInEditorPortal,
|
||||||
onAwarenessUpdate,
|
onAwarenessUpdate,
|
||||||
} = props;
|
} = props;
|
||||||
|
@ -124,18 +124,19 @@ export const CollaborationEditor = forwardRef((props: ICollaborationEditorProps,
|
||||||
normalContent={() => (
|
normalContent={() => (
|
||||||
<EditorInstance
|
<EditorInstance
|
||||||
ref={$editor}
|
ref={$editor}
|
||||||
|
documentId={documentId}
|
||||||
editable={editable}
|
editable={editable}
|
||||||
menubar={menubar}
|
menubar={menubar}
|
||||||
hocuspocusProvider={hocuspocusProvider}
|
hocuspocusProvider={hocuspocusProvider}
|
||||||
onTitleUpdate={onTitleUpdate}
|
onTitleUpdate={onTitleUpdate}
|
||||||
user={user}
|
user={user}
|
||||||
status={status}
|
status={status}
|
||||||
|
hideComment={hideComment}
|
||||||
renderInEditorPortal={renderInEditorPortal}
|
renderInEditorPortal={renderInEditorPortal}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{loading || !!error ? null : renderOnMount}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,6 +28,11 @@ export interface ICollaborationEditorProps {
|
||||||
*/
|
*/
|
||||||
user: ILoginUser | null;
|
user: ILoginUser | null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要评论
|
||||||
|
*/
|
||||||
|
hideComment?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 文档标题变动
|
* 文档标题变动
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
.wrapper {
|
.wrapper {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
padding-top: 1rem;
|
||||||
background-color: var(--semi-color-nav-bg);
|
padding-right: 1rem;
|
||||||
|
padding-left: 2rem;
|
||||||
|
|
||||||
> header {
|
> header {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
|
@ -18,15 +19,9 @@
|
||||||
|
|
||||||
.collapsedItem {
|
.collapsedItem {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 2px;
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
background-color: #d8d8d8;
|
background-color: #d8d8d8;
|
||||||
}
|
border-radius: 50%;
|
||||||
|
|
||||||
:global {
|
|
||||||
.semi-anchor-link-title-active {
|
|
||||||
.collapsedItem {
|
|
||||||
background-color: var(--semi-color-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { IconDoubleChevronLeft, IconDoubleChevronRight } from '@douyinfe/semi-icons';
|
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 { Editor } from '@tiptap/core';
|
||||||
|
import { throttle } from 'helpers/throttle';
|
||||||
import { useDocumentStyle, Width } from 'hooks/use-document-style';
|
import { useDocumentStyle, Width } from 'hooks/use-document-style';
|
||||||
import { useToggle } from 'hooks/use-toggle';
|
import { useToggle } from 'hooks/use-toggle';
|
||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
|
@ -15,19 +16,20 @@ interface IToc {
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_LEVEL = 6;
|
|
||||||
|
|
||||||
const Toc = ({ toc, collapsed }) => {
|
const Toc = ({ toc, collapsed }) => {
|
||||||
return (
|
return (
|
||||||
<Anchor.Link
|
<Anchor.Link
|
||||||
href={`#${toc.id}`}
|
href={`#${toc.id}`}
|
||||||
title={
|
title={
|
||||||
collapsed ? (
|
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
|
toc.text
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
style={{ paddingLeft: collapsed ? 16 : 8 }}
|
||||||
>
|
>
|
||||||
{toc.children && toc.children.length
|
{toc.children && toc.children.length
|
||||||
? toc.children.map((toc) => <Toc key={toc.text} toc={toc} collapsed={collapsed} />)
|
? 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 }) => {
|
export const Tocs: React.FC<{ tocs: Array<IToc>; editor: Editor }> = ({ tocs = [], editor }) => {
|
||||||
const [hasToc, toggleHasToc] = useToggle(false);
|
const [hasToc, toggleHasToc] = useToggle(false);
|
||||||
const { width } = useDocumentStyle();
|
|
||||||
const [collapsed, toggleCollapsed] = useToggle(true);
|
const [collapsed, toggleCollapsed] = useToggle(true);
|
||||||
|
const { width } = useDocumentStyle();
|
||||||
|
|
||||||
const getContainer = useCallback(() => {
|
const getContainer = useCallback(() => {
|
||||||
return document.querySelector(`#js-tocs-container`);
|
return document.querySelector(`#js-tocs-container`);
|
||||||
|
@ -57,7 +61,6 @@ export const Tocs: React.FC<{ tocs: Array<IToc>; editor: Editor }> = ({ tocs = [
|
||||||
const listener = () => {
|
const listener = () => {
|
||||||
const nodes = findNode(editor, TableOfContents.name);
|
const nodes = findNode(editor, TableOfContents.name);
|
||||||
const hasTocNow = !!(nodes && nodes.length);
|
const hasTocNow = !!(nodes && nodes.length);
|
||||||
|
|
||||||
if (hasTocNow !== hasToc) {
|
if (hasTocNow !== hasToc) {
|
||||||
toggleHasToc(hasTocNow);
|
toggleHasToc(hasTocNow);
|
||||||
}
|
}
|
||||||
|
@ -70,6 +73,19 @@ export const Tocs: React.FC<{ tocs: Array<IToc>; editor: Editor }> = ({ tocs = [
|
||||||
};
|
};
|
||||||
}, [editor, hasToc, toggleHasToc]);
|
}, [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 (
|
return (
|
||||||
<div className={styles.wrapper} style={{ display: hasToc ? 'block' : 'none' }}>
|
<div className={styles.wrapper} style={{ display: hasToc ? 'block' : 'none' }}>
|
||||||
<header>
|
<header>
|
||||||
|
@ -82,7 +98,7 @@ export const Tocs: React.FC<{ tocs: Array<IToc>; editor: Editor }> = ({ tocs = [
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<Anchor
|
<Anchor
|
||||||
railTheme={'muted'}
|
railTheme={collapsed ? 'muted' : 'tertiary'}
|
||||||
maxHeight={'calc(100vh - 360px)'}
|
maxHeight={'calc(100vh - 360px)'}
|
||||||
getContainer={getContainer}
|
getContainer={getContainer}
|
||||||
maxWidth={collapsed ? 56 : 180}
|
maxWidth={collapsed ? 56 : 180}
|
||||||
|
|
Loading…
Reference in New Issue