mirror of https://github.com/fantasticit/think.git
tiptap: improve mind
This commit is contained in:
parent
2f6713144e
commit
803612a5c5
|
@ -19,3 +19,4 @@ export * from './text';
|
|||
export * from './type';
|
||||
export * from './upload';
|
||||
export * from './url';
|
||||
export * from './uuid';
|
||||
|
|
|
@ -41,6 +41,7 @@ export function isInCustomNode(state: EditorState, nodeName: string): boolean {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isInCodeBlock(state: EditorState): boolean {
|
||||
|
|
|
@ -15,4 +15,4 @@
|
|||
@import './selection.scss';
|
||||
@import './table.scss';
|
||||
@import './title.scss';
|
||||
@import './kityminder.scss';
|
||||
@import './mind/index.scss';
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue