tiptap: fix render draw.io xml

pull/44/head
fantasticit 2022-05-11 19:44:23 +08:00
parent ce5f88fa0d
commit cd894ef446
13 changed files with 72581 additions and 36056 deletions

View File

@ -4,4 +4,4 @@ node_modules
**/dist/**
.eslintrc.js
./packages/client/src/tiptap/wrappers/mind/mind-elixir/iconfont/iconfont.js
./packages/client/public/drawio.embed.js
./packages/client/public/viewer.min.js

View File

@ -2,7 +2,5 @@ interface Window {
// 思维导图
MindElixir: any;
// drawio 绘图
mxGraph: any;
mxUtils: any;
mxCodec: any;
GraphViewer: any;
}

View File

@ -70,7 +70,6 @@
"markdown-it-sub": "^1.0.0",
"markdown-it-sup": "^1.0.0",
"next": "12.0.10",
"pako": "^2.0.4",
"prosemirror-markdown": "^1.7.0",
"prosemirror-tables": "^1.1.1",
"prosemirror-utils": "^0.9.6",

File diff suppressed because one or more lines are too long

72527
packages/client/public/viewer.min.js vendored 100644

File diff suppressed because one or more lines are too long

View File

@ -14,7 +14,8 @@ function MyApp({ Component, pageProps }: AppProps) {
<>
<Head>
<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>
<Component {...pageProps} />
</>

View File

@ -0,0 +1,8 @@
.mxgraph {
width: 100%;
}
.geDiagramContainer {
width: 100%;
height: 100%;
}

View File

@ -3,6 +3,7 @@
@import './code.scss';
@import './collaboration.scss';
@import './color.scss';
@import './flow.scss';
@import './heading.scss';
@import './katex.scss';
@import './list.scss';

View File

@ -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;
}

View File

@ -5,6 +5,9 @@
line-height: 0;
.renderWrap {
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--node-border-color);
border-radius: var(--border-radius);

View File

@ -1,120 +1,67 @@
import { NodeViewWrapper } from '@tiptap/react';
import cls from 'classnames';
import { Button, Space } from '@douyinfe/semi-ui';
import { IconMindCenter, IconZoomOut, IconZoomIn } from 'components/icons';
import { useCallback, useEffect, useRef } from 'react';
import { useCallback, useEffect, useMemo, useRef } from 'react';
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 { decode } from './decode';
import styles from './index.module.scss';
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 }) => {
const $container = useRef<HTMLDivElement>();
const $graph = useRef(null);
const containerId = useRef(`js-flow-container-${uuid()}`);
const isEditable = editor.isEditable;
const isActive = editor.isActive(Flow.name);
const { width: maxWidth } = getEditorContainerDOMSize(editor);
const { data, width, height } = node.attrs;
const $container = useRef<HTMLElement>();
const center = useCallback(() => {
const graph = $graph.current;
if (!graph) return;
graph.fit();
graph.center(true, false);
}, []);
const zoomOut = useCallback(() => {
const graph = $graph.current;
if (!graph) return;
graph.zoomOut();
}, []);
const zoomIn = useCallback(() => {
const graph = $graph.current;
if (!graph) return;
graph.zoomIn();
}, []);
const graphData = useMemo(() => {
const content = data.replace(/<!--.*?-->/gs, '').trim();
const config = JSON.stringify({
highlight: '#00afff',
lightbox: false,
nav: false,
resize: true,
xml: content,
zoom: 0.8,
});
return config;
}, [data]);
const onResize = useCallback(
(size) => {
updateAttributes({ width: size.width, height: size.height });
setTimeout(() => {
const graph = $graph.current;
if (!graph) return;
graph.fit();
graph.center(true, false);
}, 0);
},
[updateAttributes]
);
useEffect(() => {
let graph = $graph.current;
if (!graph) {
// @ts-ignore
graph = new mxGraph($container.current);
graph.resetViewOnRootChange = false;
graph.foldingEnabled = false;
graph.setTooltips(false);
graph.setEnabled(false);
graph.centerZoom = true;
$graph.current = graph;
const render = useCallback((div) => {
if (!div) return;
// @ts-ignore
const DrawioViewer = window.GraphViewer;
if (DrawioViewer) {
div.innerHTML = '';
DrawioViewer.createViewerForElement(div);
}
}, []);
const text = decode(data);
// @ts-ignore
const xmlDoc = mxUtils.parseXml(text);
// @ts-ignore
const codec = new mxCodec(xmlDoc);
codec.decode(codec.document.documentElement, graph.getModel());
setTimeout(() => {
graph.fit();
graph.center(true, false);
}, 0);
}, [data]);
const setMxgraph = useCallback(
(div) => {
$container.current = div;
render(div);
},
[render]
);
useEffect(() => {
render($container.current);
}, [graphData, render]);
return (
<NodeViewWrapper className={cls(styles.wrap, isActive && styles.isActive)}>
<Resizeable isEditable={isEditable} width={width} height={height} maxWidth={maxWidth} onChangeEnd={onResize}>
<div
ref={$container}
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 className={cls(styles.renderWrap, 'render-wrapper')} style={INHERIT_SIZE_STYLE}>
<div className="mxgraph" ref={setMxgraph} data-mxgraph={graphData}></div>
</div>
</Resizeable>
</NodeViewWrapper>

View File

@ -32,7 +32,7 @@ export const FlowSettingModal: React.FC<IProps> = ({ editor }) => {
}
if (evt.data == 'ready') {
$iframe.current.contentWindow.postMessage(initialData, '*');
$iframe.current && $iframe.current.contentWindow.postMessage(initialData, '*');
} else {
if (evt.data.length > 0) {
const data = evt.data;
@ -53,7 +53,7 @@ export const FlowSettingModal: React.FC<IProps> = ({ editor }) => {
<div style={{ height: '100%', margin: '0 -24px' }}>
<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%' }}
frameBorder={0}
></iframe>

View File

@ -122,7 +122,6 @@ importers:
markdown-it-sub: ^1.0.0
markdown-it-sup: ^1.0.0
next: 12.0.10
pako: ^2.0.4
prosemirror-markdown: ^1.7.0
prosemirror-tables: ^1.1.1
prosemirror-utils: ^0.9.6
@ -207,7 +206,6 @@ importers:
markdown-it-sub: 1.0.0
markdown-it-sup: 1.0.0
next: 12.0.10_react-dom@17.0.2+react@17.0.2
pako: 2.0.4
prosemirror-markdown: 1.7.0
prosemirror-tables: 1.1.1
prosemirror-utils: 0.9.6_prosemirror-tables@1.1.1
@ -6988,10 +6986,6 @@ packages:
netmask: 2.0.2
dev: false
/pako/2.0.4:
resolution: {integrity: sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==}
dev: false
/parent-module/1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}