tiptap: improve tocs

add resizeobserver, scroll active anchor into view
This commit is contained in:
fantasticit 2022-06-03 01:51:44 +08:00
parent 9c9211c055
commit 86755013b4
1 changed files with 24 additions and 4 deletions

View File

@ -2,7 +2,8 @@ import { Anchor, Tooltip } from '@douyinfe/semi-ui';
import cls from 'classnames'; import cls from 'classnames';
import { throttle } from 'helpers/throttle'; import { throttle } from 'helpers/throttle';
import { useToggle } from 'hooks/use-toggle'; import { useToggle } from 'hooks/use-toggle';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import scrollIntoView from 'scroll-into-view-if-needed';
import { TableOfContents } from 'tiptap/core/extensions/table-of-contents'; import { TableOfContents } from 'tiptap/core/extensions/table-of-contents';
import { Editor } from 'tiptap/editor/react'; import { Editor } from 'tiptap/editor/react';
import { findNode } from 'tiptap/prose-utils'; import { findNode } from 'tiptap/prose-utils';
@ -42,6 +43,7 @@ const Toc = ({ toc, collapsed }) => {
const TOCS_WIDTH = 156 + 16 * 2; // 目录展开的宽度 const TOCS_WIDTH = 156 + 16 * 2; // 目录展开的宽度
export const Tocs: React.FC<{ editor: Editor; getContainer: () => HTMLElement }> = ({ editor, getContainer }) => { export const Tocs: React.FC<{ editor: Editor; getContainer: () => HTMLElement }> = ({ editor, getContainer }) => {
const $container = useRef<HTMLDivElement>();
const [collapsed, toggleCollapsed] = useToggle(true); const [collapsed, toggleCollapsed] = useToggle(true);
const [headings, setHeadings] = useState<IHeading[]>([]); const [headings, setHeadings] = useState<IHeading[]>([]);
const [nestedHeadings, setNestedHeadings] = useState<IHeading[]>([]); const [nestedHeadings, setNestedHeadings] = useState<IHeading[]>([]);
@ -51,19 +53,36 @@ export const Tocs: React.FC<{ editor: Editor; getContainer: () => HTMLElement }>
if (!el) return; if (!el) return;
const handler = () => { const handler = throttle(() => {
const diffWidth = (el.offsetWidth - (el.firstChild as HTMLDivElement).offsetWidth) / 2; const diffWidth = (el.offsetWidth - (el.firstChild as HTMLDivElement).offsetWidth) / 2;
toggleCollapsed(diffWidth <= TOCS_WIDTH); toggleCollapsed(diffWidth <= TOCS_WIDTH);
}; }, 200);
handler(); handler();
const observer = new MutationObserver(handler); const observer = new MutationObserver(handler);
observer.observe(el, { attributes: true, childList: true, subtree: true }); observer.observe(el, { attributes: true, childList: true, subtree: true });
const resizeObserver = new ResizeObserver(handler);
resizeObserver.observe(el);
window.addEventListener('resize', handler); window.addEventListener('resize', handler);
const scrollHandler = throttle(() => {
const container = $container.current;
if (!container) return;
const activeAnchor = container.querySelector('.semi-anchor-link-title-active');
if (!activeAnchor) return;
scrollIntoView(activeAnchor, { behavior: 'smooth', scrollMode: 'if-needed' });
}, 200);
el.addEventListener('scroll', scrollHandler);
return () => { return () => {
observer.disconnect(); observer.disconnect();
resizeObserver.disconnect();
window.removeEventListener('resize', handler); window.removeEventListener('resize', handler);
el.removeEventListener('scroll', scrollHandler);
}; };
}, [getContainer, toggleCollapsed]); }, [getContainer, toggleCollapsed]);
@ -125,7 +144,7 @@ export const Tocs: React.FC<{ editor: Editor; getContainer: () => HTMLElement }>
if (!headings || !headings.length) return null; if (!headings || !headings.length) return null;
return ( return (
<div className={cls(styles.wrapper, 'hidden-scrollbar ')}> <div className={cls(styles.wrapper, 'hidden-scrollbar ')} ref={$container}>
<Anchor <Anchor
railTheme={collapsed ? 'muted' : 'tertiary'} railTheme={collapsed ? 'muted' : 'tertiary'}
maxHeight={'calc(100vh - 360px)'} maxHeight={'calc(100vh - 360px)'}
@ -134,6 +153,7 @@ export const Tocs: React.FC<{ editor: Editor; getContainer: () => HTMLElement }>
width: collapsed ? 56 : 156, width: collapsed ? 56 : 156,
overflow: 'auto', overflow: 'auto',
}} }}
scrollMotion
> >
{collapsed {collapsed
? headings.map((toc) => { ? headings.map((toc) => {