Просмотр исходного кода

Implement draw.io render by DrawioInterpreter

Koki Oyatsu 6 лет назад
Родитель
Сommit
56abcb9d5b

+ 48 - 0
src/client/js/components/Drawio.jsx

@@ -0,0 +1,48 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+export default class Drawio extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.drawioContainer = React.createRef();
+    const DrawioViewer = window.GraphViewer;
+    if (DrawioViewer != null) {
+      // viewer.min.js の Resize による Scroll イベントを抑止するために無効化する
+      DrawioViewer.useResizeSensor = false;
+    }
+  }
+
+  componentDidMount() {
+    const DrawioViewer = window.GraphViewer;
+    if (DrawioViewer != null) {
+      DrawioViewer.processElements();
+    }
+  }
+
+  renderContents() {
+    return this.props.drawioContent;
+  }
+
+  render() {
+    return (
+      <div
+        className="drawio"
+        ref={(c) => { this.drawioContainer = c }}
+        onScroll={(event) => {
+          event.preventDefault();
+        }}
+        dangerouslySetInnerHTML={{ __html: this.renderContents() }}
+      >
+      </div>
+    );
+  }
+
+}
+
+Drawio.propTypes = {
+  appContainer: PropTypes.object.isRequired,
+  drawioContent: PropTypes.any,
+  isPreview: PropTypes.bool,
+};

+ 0 - 20
src/client/js/components/Page/RevisionBody.jsx

@@ -20,11 +20,6 @@ export default class RevisionBody extends React.PureComponent {
     if (MathJax != null && this.props.isMathJaxEnabled && this.props.renderMathJaxOnInit) {
       this.renderMathJaxWithDebounce();
     }
-
-    const DrawioViewer = window.GraphViewer;
-    if (DrawioViewer != null) {
-      this.renderDrawioWithDebounce();
-    }
   }
 
   componentDidUpdate() {
@@ -32,11 +27,6 @@ export default class RevisionBody extends React.PureComponent {
     if (MathJax != null && this.props.isMathJaxEnabled && this.props.renderMathJaxInRealtime) {
       this.renderMathJaxWithDebounce();
     }
-
-    const DrawioViewer = window.GraphViewer;
-    if (DrawioViewer != null) {
-      this.renderDrawioWithDebounce();
-    }
   }
 
   componentWillReceiveProps(nextProps) {
@@ -44,11 +34,6 @@ export default class RevisionBody extends React.PureComponent {
     if (MathJax != null && this.props.isMathJaxEnabled && this.props.renderMathJaxOnInit) {
       this.renderMathJaxWithDebounce();
     }
-
-    const DrawioViewer = window.GraphViewer;
-    if (DrawioViewer != null) {
-      this.renderDrawioWithDebounce();
-    }
   }
 
   renderMathJax() {
@@ -56,11 +41,6 @@ export default class RevisionBody extends React.PureComponent {
     MathJax.Hub.Queue(['Typeset', MathJax.Hub, this.element]);
   }
 
-  renderDrawio() {
-    const DrawioViewer = window.GraphViewer;
-    DrawioViewer.processElements();
-  }
-
   generateInnerHtml(html) {
     return { __html: html };
   }

+ 5 - 0
src/client/js/services/AppContainer.js

@@ -13,6 +13,10 @@ import {
   RestoreCodeBlockInterceptor,
 } from '../util/interceptor/detach-code-blocks';
 
+import {
+  DrawioInterceptor,
+} from '../util/interceptor/drawio-interceptor';
+
 import i18nFactory from '../util/i18n';
 import apiv3ErrorHandler from '../util/apiv3ErrorHandler';
 
@@ -48,6 +52,7 @@ export default class AppContainer extends Container {
 
     this.interceptorManager = new InterceptorManager();
     this.interceptorManager.addInterceptor(new DetachCodeBlockInterceptor(this), 10); // process as soon as possible
+    this.interceptorManager.addInterceptor(new DrawioInterceptor(this), 20);
     this.interceptorManager.addInterceptor(new RestoreCodeBlockInterceptor(this), 900); // process as late as possible
 
     const userlang = body.dataset.userlang;

+ 134 - 0
src/client/js/util/interceptor/drawio-interceptor.js

@@ -0,0 +1,134 @@
+/* eslint-disable import/prefer-default-export */
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { BasicInterceptor } from 'growi-commons';
+import Drawio from '../../components/Drawio';
+
+/**
+ * The interceptor for lsx
+ *
+ *  replace lsx tag to a React target element
+ */
+export class DrawioInterceptor extends BasicInterceptor {
+
+  constructor(appContainer) {
+    super();
+
+    this.previousPreviewContext = null;
+    this.appContainer = appContainer;
+  }
+
+  /**
+   * @inheritdoc
+   */
+  isInterceptWhen(contextName) {
+    return (
+      contextName === 'preRenderHtml'
+      || contextName === 'preRenderPreviewHtml'
+      || contextName === 'postRenderHtml'
+      || contextName === 'postRenderPreviewHtml'
+    );
+  }
+
+  /**
+   * @inheritdoc
+   */
+  isProcessableParallel() {
+    return false;
+  }
+
+  /**
+   * @inheritdoc
+   */
+  async process(contextName, ...args) {
+    const context = Object.assign(args[0]); // clone
+
+    if (contextName === 'preRenderHtml' || contextName === 'preRenderPreviewHtml') {
+      return this.drawioPreRender(contextName, context);
+    }
+
+    if (contextName === 'postRenderHtml' || contextName === 'postRenderPreviewHtml') {
+      this.drawioPostRender(contextName, context);
+      return;
+    }
+  }
+
+  /**
+   * @inheritdoc
+   */
+  createRandomStr(length) {
+    const bag = 'abcdefghijklmnopqrstuvwxyz0123456789';
+    let generated = '';
+    for (let i = 0; i < length; i++) {
+      generated += bag[Math.floor(Math.random() * bag.length)];
+    }
+    return generated;
+  }
+
+  /**
+   * @inheritdoc
+   */
+  drawioPreRender(contextName, context) {
+    const div = document.createElement('div');
+    div.innerHTML = context.parsedHTML;
+
+    context.DrawioMap = {};
+    Array.from(div.querySelectorAll('.mxgraph')).forEach((element) => {
+      const domId = `mxgraph-${this.createRandomStr(8)}`;
+      context.DrawioMap[domId] = element.outerHTML;
+      element.outerHTML = `<div id="${domId}"></div>`;
+    });
+    context.parsedHTML = div.innerHTML;
+
+    // unmount
+    if (contextName === 'preRenderPreviewHtml') {
+      this.unmountPreviousReactDOMs(context);
+    }
+
+    // resolve
+    return context;
+  }
+
+  /**
+   * @inheritdoc
+   */
+  drawioPostRender(contextName, context) {
+    const isPreview = (contextName === 'postRenderPreviewHtml');
+
+    Object.keys(context.DrawioMap).forEach((domId) => {
+      const elem = document.getElementById(domId);
+      if (elem) {
+        this.renderReactDOM(context.DrawioMap[domId], elem, isPreview);
+      }
+    });
+  }
+
+  /**
+   * @inheritdoc
+   */
+  renderReactDOM(drawioContent, elem, isPreview) {
+    ReactDOM.render(
+      // eslint-disable-next-line react/jsx-filename-extension
+      <Drawio
+        appContainer={this.appContainer}
+        drawioContent={drawioContent}
+        isPreview={isPreview}
+      />,
+      elem,
+    );
+  }
+
+  /**
+   * @inheritdoc
+   */
+  unmountPreviousReactDOMs(newContext) {
+    if (this.previousPreviewContext != null) {
+      Array.from(document.querySelectorAll('.mxgraph')).forEach((element) => {
+        ReactDOM.unmountComponentAtNode(element);
+      });
+    }
+
+    this.previousPreviewContext = newContext;
+  }
+
+}

+ 3 - 3
yarn.lock

@@ -8332,9 +8332,9 @@ markdown-it-blockdiag@^1.0.2:
     utf8-bytes "0.0.1"
 
 markdown-it-drawio-viewer@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/markdown-it-drawio-viewer/-/markdown-it-drawio-viewer-1.1.0.tgz#000a9ab161671e843d21bf839fcbc3435bbeaab3"
-  integrity sha512-Z2yCMblAnk0n9t3SR1aNTfhNnKUOC7oJ0rIBmTZN+dBo29vVZp5+tNgY7O2n37JHrDOLX9WRGcs/XHl/tJg+5A==
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/markdown-it-drawio-viewer/-/markdown-it-drawio-viewer-1.1.1.tgz#3fb622060ff76c21a63e9b4310c98a5c61942422"
+  integrity sha512-+PTPfOQlqHm3bpZgweADJ/KEhXBQFxqEYkYUkLyR2GRQ9iPbrbOmCVhTFsvMhpSEIwRkDflyYAfO4YsmIKioZg==
   dependencies:
     markdown-it-fence "0.1.3"
     xmldoc "^1.1.2"