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/**
|
||||
.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
|
||||
|
|
|
@ -2,7 +2,5 @@ interface Window {
|
|||
// 思维导图
|
||||
MindElixir: any;
|
||||
// drawio 绘图
|
||||
mxGraph: any;
|
||||
mxUtils: any;
|
||||
mxCodec: any;
|
||||
GraphViewer: any;
|
||||
}
|
||||
|
|
|
@ -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
File diff suppressed because one or more lines are too long
|
@ -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} />
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
.mxgraph {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.geDiagramContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
||||
.renderWrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--node-border-color);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'}
|
||||
|
|
Loading…
Reference in New Issue