tiptap: fix render draw.io xml

This commit is contained in:
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/** **/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

View File

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

View File

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

72527
packages/client/public/viewer.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

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; 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);

View File

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

View File

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

View File

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