mirror of https://github.com/fantasticit/think.git
client: improve toc
parent
c37182f0a8
commit
4e1c464615
|
@ -132,9 +132,9 @@ export const DocumentReader: React.FC<IProps> = ({ documentId }) => {
|
|||
></Nav>
|
||||
</Header>
|
||||
<Layout className={styles.contentWrap}>
|
||||
<div ref={setContainer}>
|
||||
<div ref={setContainer} id="js-reader-container">
|
||||
<div className={cls(styles.editorWrap, editorWrapClassNames)} style={{ fontSize }}>
|
||||
<div id="js-reader-container">
|
||||
<div>
|
||||
<DataRender
|
||||
loading={docAuthLoading}
|
||||
loadingContent={
|
||||
|
|
|
@ -5,6 +5,48 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const arrToTree = (tocs) => {
|
||||
const data = [...tocs, { level: Infinity }];
|
||||
const res = [];
|
||||
|
||||
const makeChildren = (item, flattenChildren) => {
|
||||
if (!flattenChildren.length) return;
|
||||
|
||||
const stopAt = flattenChildren.findIndex((d) => d.level !== item.level + 1);
|
||||
|
||||
if (stopAt > -1) {
|
||||
const children = flattenChildren.slice(0, stopAt);
|
||||
item.children = children;
|
||||
|
||||
const remain = flattenChildren.slice(stopAt + 1);
|
||||
|
||||
if (remain.length) {
|
||||
makeChildren(children[children.length - 1], remain);
|
||||
}
|
||||
} else {
|
||||
item.children = flattenChildren;
|
||||
}
|
||||
};
|
||||
|
||||
let i = 0;
|
||||
|
||||
while (i < data.length) {
|
||||
const item = data[i];
|
||||
const stopAt = data.slice(i + 1).findIndex((d) => d.level !== item.level + 1);
|
||||
|
||||
if (stopAt > -1) {
|
||||
makeChildren(item, data.slice(i + 1).slice(0, stopAt));
|
||||
i += 1 + stopAt;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
res.push(item);
|
||||
}
|
||||
|
||||
return res.slice(0, -1);
|
||||
};
|
||||
|
||||
export const TableOfContentsWrapper = ({ editor }) => {
|
||||
const [items, setItems] = useState([]);
|
||||
const [visible, toggleVisible] = useToggle(true);
|
||||
|
@ -49,11 +91,18 @@ export const TableOfContentsWrapper = ({ editor }) => {
|
|||
editor.view.dispatch(transaction);
|
||||
|
||||
setItems(headings);
|
||||
|
||||
return headings;
|
||||
}, [editor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editor.options.editable) {
|
||||
editor.eventEmitter.emit('TableOfContents', arrToTree(handleUpdate()));
|
||||
return;
|
||||
}
|
||||
|
||||
editor.on('update', handleUpdate);
|
||||
|
|
|
@ -8,9 +8,10 @@ import { isAndroid, isIOS } from 'helpers/env';
|
|||
import { useNetwork } from 'hooks/use-network';
|
||||
import { IsOnMobile } from 'hooks/use-on-mobile';
|
||||
import { useToggle } from 'hooks/use-toggle';
|
||||
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
|
||||
import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
||||
import { Collaboration } from 'tiptap/core/extensions/collaboration';
|
||||
import { CollaborationCursor } from 'tiptap/core/extensions/collaboration-cursor';
|
||||
import { Tocs } from 'tiptap/editor/tocs';
|
||||
|
||||
import { EditorContent, useEditor } from '../../react';
|
||||
import { CollaborationKit } from '../kit';
|
||||
|
@ -70,6 +71,7 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
|
|||
},
|
||||
[editable, user, onTitleUpdate, hocuspocusProvider]
|
||||
);
|
||||
const [headings, setHeadings] = useState([]);
|
||||
|
||||
useImperativeHandle(ref, () => editor);
|
||||
|
||||
|
@ -137,6 +139,21 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
|
|||
};
|
||||
}, [isMobile]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) return;
|
||||
|
||||
const collectHeadings = (headings) => {
|
||||
console.log({ headings });
|
||||
setHeadings(headings);
|
||||
};
|
||||
|
||||
editor.eventEmitter.on('TableOfContents', collectHeadings);
|
||||
|
||||
return () => {
|
||||
editor.eventEmitter.off('TableOfContents', collectHeadings);
|
||||
};
|
||||
}, [editor]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{(!online || status === 'disconnected') && (
|
||||
|
@ -159,6 +176,7 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
|
|||
|
||||
<main ref={$mainContainer}>
|
||||
<EditorContent editor={editor} />
|
||||
{editor && <Tocs tocs={headings} editor={editor} />}
|
||||
{protals}
|
||||
</main>
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
.wrapper {
|
||||
position: fixed;
|
||||
right: 16px;
|
||||
z-index: 4;
|
||||
background-color: var(--semi-color-nav-bg);
|
||||
|
||||
> header {
|
||||
margin-bottom: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import { Anchor } from '@douyinfe/semi-ui';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { Editor } from '../react';
|
||||
import style from './index.module.scss';
|
||||
|
||||
interface IToc {
|
||||
level: number;
|
||||
id: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
const renderToc = (toc) => {
|
||||
return (
|
||||
<Anchor.Link href={`#${toc.id}`} title={toc.text}>
|
||||
{toc.children && toc.children.length && toc.children.map(renderToc)}
|
||||
</Anchor.Link>
|
||||
);
|
||||
};
|
||||
|
||||
export const Tocs: React.FC<{ tocs: Array<IToc>; editor: Editor }> = ({ tocs = [], editor }) => {
|
||||
const getContainer = useCallback(() => {
|
||||
return document.querySelector(`#js-reader-container`);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={style.wrapper}>
|
||||
<main>
|
||||
<Anchor autoCollapse getContainer={getContainer} maxWidth={8}>
|
||||
{tocs.length && tocs.map(renderToc)}
|
||||
</Anchor>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue