tiptap: improve mind

This commit is contained in:
fantasticit 2022-04-30 22:03:28 +08:00
parent 2f6713144e
commit 803612a5c5
4 changed files with 38 additions and 146 deletions

View File

@ -19,3 +19,4 @@ export * from './text';
export * from './type';
export * from './upload';
export * from './url';
export * from './uuid';

View File

@ -41,6 +41,7 @@ export function isInCustomNode(state: EditorState, nodeName: string): boolean {
return true;
}
}
return false;
}
export function isInCodeBlock(state: EditorState): boolean {

View File

@ -15,4 +15,4 @@
@import './selection.scss';
@import './table.scss';
@import './title.scss';
@import './kityminder.scss';
@import './mind/index.scss';

View File

@ -2,14 +2,10 @@ import { NodeViewWrapper } from '@tiptap/react';
import cls from 'classnames';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Spin, Typography } from '@douyinfe/semi-ui';
import deepEqual from 'deep-equal';
import { Resizeable } from 'components/resizeable';
import { useToggle } from 'hooks/use-toggle';
import { clamp, getEditorContainerDOMSize } from 'tiptap/prose-utils';
import { getEditorContainerDOMSize, uuid } from 'tiptap/prose-utils';
import { Mind } from 'tiptap/extensions/mind';
import { loadKityMinder } from './kityminder';
import { Toolbar } from './toolbar';
import { MIN_ZOOM, MAX_ZOOM, ZOOM_STEP } from './toolbar/constant';
import styles from './index.module.scss';
const { Text } = Typography;
@ -17,12 +13,13 @@ const { Text } = Typography;
const INHERIT_SIZE_STYLE = { width: '100%', height: '100%' };
export const MindWrapper = ({ editor, node, updateAttributes }) => {
const $container = useRef();
const $mind = useRef<any>();
const $container = useRef<HTMLDivElement>();
const $mind = useRef(null);
const containerId = useRef(`js-mind-container-${uuid()}`);
const isEditable = editor.isEditable;
const isActive = editor.isActive(Mind.name);
const { width: maxWidth } = getEditorContainerDOMSize(editor);
const { data, template, theme, zoom, width, height } = node.attrs;
const { data, width, height } = node.attrs;
const [loading, toggleLoading] = useToggle(true);
const [error, setError] = useState<Error | null>(null);
@ -42,9 +39,10 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
return (
<div
ref={$container}
id={containerId.current}
className={cls(styles.renderWrap, 'render-wrapper')}
tabIndex={0}
style={INHERIT_SIZE_STYLE}
style={{ ...INHERIT_SIZE_STYLE, overflow: 'hidden' }}
></div>
);
}, [loading, error, width, height]);
@ -52,68 +50,19 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
const onResize = useCallback(
(size) => {
updateAttributes({ width: size.width, height: size.height });
setCenter();
setTimeout(() => {
$mind.current && $mind.current.toCenter();
});
},
[updateAttributes]
);
const setZoom = useCallback(
(type: 'minus' | 'plus') => {
return () => {
const minder = $mind.current;
if (!minder) return;
const currentZoom = minder.getZoomValue();
const nextZoom = clamp(
type === 'minus' ? currentZoom - ZOOM_STEP : currentZoom + ZOOM_STEP,
MIN_ZOOM,
MAX_ZOOM
);
minder.execCommand('zoom', nextZoom);
isEditable && updateAttributes({ zoom: nextZoom });
};
},
[editor, zoom, isEditable, updateAttributes]
);
const setCenter = useCallback(() => {
const minder = $mind.current;
if (!minder) return;
minder.execCommand('camera');
}, []);
// 布局
const setTemplate = useCallback(
(template) => {
const minder = $mind.current;
if (!minder) return;
minder.execCommand('template', template);
isEditable && updateAttributes({ template });
},
[updateAttributes, isEditable]
);
// 主题
const setTheme = useCallback(
(theme) => {
const minder = $mind.current;
if (!minder) return;
minder.execCommand('theme', theme);
isEditable && updateAttributes({ theme });
},
[updateAttributes, isEditable]
);
const saveData = useCallback(() => {
const minder = $mind.current;
if (!minder) return;
isEditable && updateAttributes({ data: minder.exportJson() });
}, [updateAttributes, isEditable]);
// 加载依赖
useEffect(() => {
loadKityMinder()
.then(() => {
import('./mind-elixir')
.then((module) => {
toggleLoading(false);
window.MindElixir = module.default;
})
.catch((e) => {
setError(e);
@ -125,104 +74,45 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => {
if (loading || !$container.current) return;
const onChange = () => {
saveData();
updateAttributes({ data: mind.getAllData() });
};
let mind = null;
try {
const Editor = window.kityminder.Editor;
const minder = new Editor($container.current).minder;
minder.importJson(data);
minder.execCommand('template', template);
minder.execCommand('theme', theme);
minder.execCommand('zoom', parseInt(zoom));
if (!isEditable) {
minder.preventEdit = true;
} else {
minder.preventEdit = false;
minder.enable();
}
$mind.current = minder;
minder.on('contentChange', onChange);
mind = new window.MindElixir({
el: `#${containerId.current}`,
direction: window.MindElixir.SIDE,
data: JSON.parse(JSON.stringify(data)),
editable: editor.isEditable,
draggable: editor.isEditable,
contextMenu: editor.isEditable,
toolBar: true,
keypress: editor.isEditable,
nodeMenu: true,
locale: 'zh_CN',
});
mind.shouldPreventDefault = () => editor.isActive('mind');
mind.init();
mind.bus.addListener('operation', onChange);
$mind.current = mind;
toggleLoading(false);
} catch (e) {
setError(e);
}
return () => {
if ($mind.current) {
$mind.current.off('contentChange', onChange);
$mind.current.destroy();
if (mind) {
mind.bus.removeListener('operation', onChange);
}
};
}, [loading]);
// 数据同步渲染
useEffect(() => {
const minder = $mind.current;
if (!minder) return;
const currentData = minder.exportJson();
const isEqual = deepEqual(currentData, data);
if (isEqual) return;
// TODO: 也许刷新更好些
minder.importJson(data);
}, [data]);
// 启用/禁用
useEffect(() => {
const minder = $mind.current;
if (!minder) return;
if (!isEditable) {
minder.preventEdit = true;
} else {
minder.preventEdit = false;
minder.enable();
}
}, [isEditable]);
// 缩放
useEffect(() => {
const minder = $mind.current;
if (!minder) return;
minder.execCommand('zoom', parseInt(zoom));
}, [zoom]);
// 布局
useEffect(() => {
const minder = $mind.current;
if (!minder) return;
minder.execCommand('template', template);
}, [template]);
// 主题
useEffect(() => {
const minder = $mind.current;
if (!minder) return;
minder.execCommand('theme', theme);
}, [theme]);
}, [loading, editor, updateAttributes]);
return (
<NodeViewWrapper className={cls(styles.wrap, isActive && styles.isActive)}>
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
{content}
</Resizeable>
<div className={styles.toolbarWrap}>
<Toolbar
isEditable={isEditable}
maxHeight={height * 0.8}
template={template}
theme={theme}
zoom={zoom}
setZoomMinus={setZoom('minus')}
setZoomPlus={setZoom('plus')}
setCenter={setCenter}
setTemplate={setTemplate}
setTheme={setTheme}
/>
</div>
</NodeViewWrapper>
);
};