mirror of https://github.com/fantasticit/think.git
tiptap: fix render draw.io xml
This commit is contained in:
parent
ce5f88fa0d
commit
cd894ef446
|
@ -4,4 +4,4 @@ node_modules
|
||||||
**/dist/**
|
**/dist/**
|
||||||
.eslintrc.js
|
.eslintrc.js
|
||||||
./packages/client/src/tiptap/wrappers/mind/mind-elixir/iconfont/iconfont.js
|
./packages/client/src/tiptap/wrappers/mind/mind-elixir/iconfont/iconfont.js
|
||||||
./packages/client/public/drawio.embed.js
|
./packages/client/public/viewer.min.js
|
||||||
|
|
|
@ -2,7 +2,5 @@ interface Window {
|
||||||
// 思维导图
|
// 思维导图
|
||||||
MindElixir: any;
|
MindElixir: any;
|
||||||
// drawio 绘图
|
// drawio 绘图
|
||||||
mxGraph: any;
|
GraphViewer: any;
|
||||||
mxUtils: any;
|
|
||||||
mxCodec: any;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,6 @@
|
||||||
"markdown-it-sub": "^1.0.0",
|
"markdown-it-sub": "^1.0.0",
|
||||||
"markdown-it-sup": "^1.0.0",
|
"markdown-it-sup": "^1.0.0",
|
||||||
"next": "12.0.10",
|
"next": "12.0.10",
|
||||||
"pako": "^2.0.4",
|
|
||||||
"prosemirror-markdown": "^1.7.0",
|
"prosemirror-markdown": "^1.7.0",
|
||||||
"prosemirror-tables": "^1.1.1",
|
"prosemirror-tables": "^1.1.1",
|
||||||
"prosemirror-utils": "^0.9.6",
|
"prosemirror-utils": "^0.9.6",
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -14,7 +14,8 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||||
<>
|
<>
|
||||||
<Head>
|
<Head>
|
||||||
<meta name="viewport" content="viewport-fit=cover" />
|
<meta name="viewport" content="viewport-fit=cover" />
|
||||||
<script src="/drawio.embed.js"></script>
|
{/* <script src="/drawio.embed.js"></script> */}
|
||||||
|
<script src="/viewer.min.js"></script>
|
||||||
</Head>
|
</Head>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
.mxgraph {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.geDiagramContainer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
@import './code.scss';
|
@import './code.scss';
|
||||||
@import './collaboration.scss';
|
@import './collaboration.scss';
|
||||||
@import './color.scss';
|
@import './color.scss';
|
||||||
|
@import './flow.scss';
|
||||||
@import './heading.scss';
|
@import './heading.scss';
|
||||||
@import './katex.scss';
|
@import './katex.scss';
|
||||||
@import './list.scss';
|
@import './list.scss';
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
import pako from 'pako';
|
|
||||||
|
|
||||||
function parseXml(xml) {
|
|
||||||
if (window.DOMParser) {
|
|
||||||
const parser = new DOMParser();
|
|
||||||
|
|
||||||
return parser.parseFromString(xml, 'text/xml');
|
|
||||||
} else {
|
|
||||||
const result = createXmlDocument();
|
|
||||||
|
|
||||||
result.async = 'false';
|
|
||||||
result.loadXML(xml);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createXmlDocument() {
|
|
||||||
let doc = null;
|
|
||||||
|
|
||||||
if (document.implementation && document.implementation.createDocument) {
|
|
||||||
doc = document.implementation.createDocument('', '', null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTextContent(node) {
|
|
||||||
return node != null ? node[node.textContent === undefined ? 'text' : 'textContent'] : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function decode(data) {
|
|
||||||
try {
|
|
||||||
const node = parseXml(data).documentElement;
|
|
||||||
if (node != null && node.nodeName == 'mxfile') {
|
|
||||||
const diagrams = node.getElementsByTagName('diagram');
|
|
||||||
if (diagrams.length > 0) {
|
|
||||||
data = getTextContent(diagrams[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = atob(data);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
alert('atob failed: ' + e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = pako.inflateRaw(
|
|
||||||
Uint8Array.from(data, (c) => String(c).charCodeAt(0)),
|
|
||||||
{
|
|
||||||
to: 'string',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
alert('inflateRaw failed: ' + e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = decodeURIComponent(data);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
alert('decodeURIComponent failed: ' + e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
|
@ -5,6 +5,9 @@
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
|
|
||||||
.renderWrap {
|
.renderWrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
border: 1px solid var(--node-border-color);
|
border: 1px solid var(--node-border-color);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
|
||||||
|
|
|
@ -1,120 +1,67 @@
|
||||||
import { NodeViewWrapper } from '@tiptap/react';
|
import { NodeViewWrapper } from '@tiptap/react';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { Button, Space } from '@douyinfe/semi-ui';
|
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
import { IconMindCenter, IconZoomOut, IconZoomIn } from 'components/icons';
|
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
|
||||||
import { Resizeable } from 'components/resizeable';
|
import { Resizeable } from 'components/resizeable';
|
||||||
import { getEditorContainerDOMSize, uuid } from 'tiptap/prose-utils';
|
import { getEditorContainerDOMSize } from 'tiptap/prose-utils';
|
||||||
import { Flow } from 'tiptap/core/extensions/flow';
|
import { Flow } from 'tiptap/core/extensions/flow';
|
||||||
import { decode } from './decode';
|
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
|
|
||||||
const INHERIT_SIZE_STYLE = { width: '100%', height: '100%', maxWidth: '100%', overflow: 'hidden', padding: '1rem' };
|
const INHERIT_SIZE_STYLE = { width: '100%', height: '100%', maxWidth: '100%', overflow: 'hidden', padding: '1rem' };
|
||||||
const ICON_STYLE = { fontSize: '0.85em' };
|
|
||||||
|
|
||||||
export const FlowWrapper = ({ editor, node, updateAttributes }) => {
|
export const FlowWrapper = ({ editor, node, updateAttributes }) => {
|
||||||
const $container = useRef<HTMLDivElement>();
|
|
||||||
const $graph = useRef(null);
|
|
||||||
const containerId = useRef(`js-flow-container-${uuid()}`);
|
|
||||||
const isEditable = editor.isEditable;
|
const isEditable = editor.isEditable;
|
||||||
const isActive = editor.isActive(Flow.name);
|
const isActive = editor.isActive(Flow.name);
|
||||||
const { width: maxWidth } = getEditorContainerDOMSize(editor);
|
const { width: maxWidth } = getEditorContainerDOMSize(editor);
|
||||||
const { data, width, height } = node.attrs;
|
const { data, width, height } = node.attrs;
|
||||||
|
const $container = useRef<HTMLElement>();
|
||||||
|
|
||||||
const center = useCallback(() => {
|
const graphData = useMemo(() => {
|
||||||
const graph = $graph.current;
|
const content = data.replace(/<!--.*?-->/gs, '').trim();
|
||||||
if (!graph) return;
|
const config = JSON.stringify({
|
||||||
graph.fit();
|
highlight: '#00afff',
|
||||||
graph.center(true, false);
|
lightbox: false,
|
||||||
}, []);
|
nav: false,
|
||||||
|
resize: true,
|
||||||
const zoomOut = useCallback(() => {
|
xml: content,
|
||||||
const graph = $graph.current;
|
zoom: 0.8,
|
||||||
if (!graph) return;
|
});
|
||||||
graph.zoomOut();
|
return config;
|
||||||
}, []);
|
}, [data]);
|
||||||
|
|
||||||
const zoomIn = useCallback(() => {
|
|
||||||
const graph = $graph.current;
|
|
||||||
if (!graph) return;
|
|
||||||
graph.zoomIn();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onResize = useCallback(
|
const onResize = useCallback(
|
||||||
(size) => {
|
(size) => {
|
||||||
updateAttributes({ width: size.width, height: size.height });
|
updateAttributes({ width: size.width, height: size.height });
|
||||||
setTimeout(() => {
|
|
||||||
const graph = $graph.current;
|
|
||||||
if (!graph) return;
|
|
||||||
graph.fit();
|
|
||||||
graph.center(true, false);
|
|
||||||
}, 0);
|
|
||||||
},
|
},
|
||||||
[updateAttributes]
|
[updateAttributes]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const render = useCallback((div) => {
|
||||||
let graph = $graph.current;
|
if (!div) return;
|
||||||
|
// @ts-ignore
|
||||||
if (!graph) {
|
const DrawioViewer = window.GraphViewer;
|
||||||
// @ts-ignore
|
if (DrawioViewer) {
|
||||||
graph = new mxGraph($container.current);
|
div.innerHTML = '';
|
||||||
graph.resetViewOnRootChange = false;
|
DrawioViewer.createViewerForElement(div);
|
||||||
graph.foldingEnabled = false;
|
|
||||||
graph.setTooltips(false);
|
|
||||||
graph.setEnabled(false);
|
|
||||||
graph.centerZoom = true;
|
|
||||||
|
|
||||||
$graph.current = graph;
|
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const text = decode(data);
|
const setMxgraph = useCallback(
|
||||||
// @ts-ignore
|
(div) => {
|
||||||
const xmlDoc = mxUtils.parseXml(text);
|
$container.current = div;
|
||||||
// @ts-ignore
|
render(div);
|
||||||
const codec = new mxCodec(xmlDoc);
|
},
|
||||||
codec.decode(codec.document.documentElement, graph.getModel());
|
[render]
|
||||||
setTimeout(() => {
|
);
|
||||||
graph.fit();
|
|
||||||
graph.center(true, false);
|
useEffect(() => {
|
||||||
}, 0);
|
render($container.current);
|
||||||
}, [data]);
|
}, [graphData, render]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper className={cls(styles.wrap, isActive && styles.isActive)}>
|
<NodeViewWrapper className={cls(styles.wrap, isActive && styles.isActive)}>
|
||||||
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
|
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
|
||||||
<div
|
<div className={cls(styles.renderWrap, 'render-wrapper')} style={INHERIT_SIZE_STYLE}>
|
||||||
ref={$container}
|
<div className="mxgraph" ref={setMxgraph} data-mxgraph={graphData}></div>
|
||||||
id={containerId.current}
|
|
||||||
className={cls(styles.renderWrap, 'render-wrapper')}
|
|
||||||
style={INHERIT_SIZE_STYLE}
|
|
||||||
/>
|
|
||||||
<div className={styles.toolbarWrap}>
|
|
||||||
<Space spacing={2}>
|
|
||||||
<Button
|
|
||||||
type="tertiary"
|
|
||||||
theme="borderless"
|
|
||||||
size="small"
|
|
||||||
onClick={center}
|
|
||||||
icon={<IconMindCenter style={ICON_STYLE} />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="tertiary"
|
|
||||||
theme="borderless"
|
|
||||||
size="small"
|
|
||||||
onClick={zoomOut}
|
|
||||||
icon={<IconZoomOut style={ICON_STYLE} />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="tertiary"
|
|
||||||
theme="borderless"
|
|
||||||
size="small"
|
|
||||||
onClick={zoomIn}
|
|
||||||
icon={<IconZoomIn style={ICON_STYLE} />}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
</div>
|
</div>
|
||||||
</Resizeable>
|
</Resizeable>
|
||||||
</NodeViewWrapper>
|
</NodeViewWrapper>
|
||||||
|
|
|
@ -32,7 +32,7 @@ export const FlowSettingModal: React.FC<IProps> = ({ editor }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evt.data == 'ready') {
|
if (evt.data == 'ready') {
|
||||||
$iframe.current.contentWindow.postMessage(initialData, '*');
|
$iframe.current && $iframe.current.contentWindow.postMessage(initialData, '*');
|
||||||
} else {
|
} else {
|
||||||
if (evt.data.length > 0) {
|
if (evt.data.length > 0) {
|
||||||
const data = evt.data;
|
const data = evt.data;
|
||||||
|
@ -53,7 +53,7 @@ export const FlowSettingModal: React.FC<IProps> = ({ editor }) => {
|
||||||
<div style={{ height: '100%', margin: '0 -24px' }}>
|
<div style={{ height: '100%', margin: '0 -24px' }}>
|
||||||
<iframe
|
<iframe
|
||||||
ref={$iframe}
|
ref={$iframe}
|
||||||
src={`${process.env.DRAWIO_URL}?embed=1&lang=zh&hide-pages=1&drafts=0&client=1&spin=0`}
|
src={`${process.env.DRAWIO_URL}?embed=1&lang=zh&hide-pages=1&drafts=0&client=1&spin=0&grid=1`}
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
frameBorder={0}
|
frameBorder={0}
|
||||||
></iframe>
|
></iframe>
|
||||||
|
|
|
@ -122,7 +122,6 @@ importers:
|
||||||
markdown-it-sub: ^1.0.0
|
markdown-it-sub: ^1.0.0
|
||||||
markdown-it-sup: ^1.0.0
|
markdown-it-sup: ^1.0.0
|
||||||
next: 12.0.10
|
next: 12.0.10
|
||||||
pako: ^2.0.4
|
|
||||||
prosemirror-markdown: ^1.7.0
|
prosemirror-markdown: ^1.7.0
|
||||||
prosemirror-tables: ^1.1.1
|
prosemirror-tables: ^1.1.1
|
||||||
prosemirror-utils: ^0.9.6
|
prosemirror-utils: ^0.9.6
|
||||||
|
@ -207,7 +206,6 @@ importers:
|
||||||
markdown-it-sub: 1.0.0
|
markdown-it-sub: 1.0.0
|
||||||
markdown-it-sup: 1.0.0
|
markdown-it-sup: 1.0.0
|
||||||
next: 12.0.10_react-dom@17.0.2+react@17.0.2
|
next: 12.0.10_react-dom@17.0.2+react@17.0.2
|
||||||
pako: 2.0.4
|
|
||||||
prosemirror-markdown: 1.7.0
|
prosemirror-markdown: 1.7.0
|
||||||
prosemirror-tables: 1.1.1
|
prosemirror-tables: 1.1.1
|
||||||
prosemirror-utils: 0.9.6_prosemirror-tables@1.1.1
|
prosemirror-utils: 0.9.6_prosemirror-tables@1.1.1
|
||||||
|
@ -6988,10 +6986,6 @@ packages:
|
||||||
netmask: 2.0.2
|
netmask: 2.0.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/pako/2.0.4:
|
|
||||||
resolution: {integrity: sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/parent-module/1.0.1:
|
/parent-module/1.0.1:
|
||||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
Loading…
Reference in New Issue