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

[WIP] Add draw.io integration.

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

+ 1 - 0
package.json

@@ -192,6 +192,7 @@
     "lodash-webpack-plugin": "^0.11.5",
     "markdown-it": "^10.0.0",
     "markdown-it-blockdiag": "^1.0.2",
+    "markdown-it-drawio-viewer": "^1.1.0",
     "markdown-it-emoji": "^1.4.0",
     "markdown-it-footnote": "^3.0.1",
     "markdown-it-mathjax": "^2.0.0",

+ 8 - 0
resource/cdn-manifests.js

@@ -79,6 +79,14 @@ module.exports = {
         integrity: '',
       },
     },
+    {
+      name: 'drawio-viewer',
+      url: 'https://www.draw.io/js/viewer.min.js',
+      groups: ['basis'],
+      args: {
+        integrity: '',
+      },
+    },
   ],
   style: [
     {

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

@@ -10,6 +10,9 @@ export default class RevisionBody extends React.PureComponent {
 
     // create debounced method for rendering MathJax
     this.renderMathJaxWithDebounce = debounce(200, this.renderMathJax);
+
+    // create debounced method for rendering draw.io
+    this.renderDrawioWithDebounce = debounce(200, this.renderDrawio);
   }
 
   componentDidMount() {
@@ -17,6 +20,11 @@ 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() {
@@ -24,6 +32,11 @@ 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) {
@@ -31,6 +44,11 @@ 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() {
@@ -38,6 +56,11 @@ 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 };
   }

+ 19 - 0
src/client/js/components/PageEditor/CodeMirrorEditor.jsx

@@ -17,8 +17,10 @@ import EmojiAutoCompleteHelper from './EmojiAutoCompleteHelper';
 import PreventMarkdownListInterceptor from './PreventMarkdownListInterceptor';
 import MarkdownTableInterceptor from './MarkdownTableInterceptor';
 import mtu from './MarkdownTableUtil';
+import mdu from './MarkdownDrawioUtil';
 import HandsontableModal from './HandsontableModal';
 import EditorIcon from './EditorIcon';
+import DrawioModal from './DrawioModal';
 
 const loadScript = require('simple-load-script');
 const loadCssSync = require('load-css-file');
@@ -94,6 +96,7 @@ export default class CodeMirrorEditor extends AbstractEditor {
 
     this.makeHeaderHandler = this.makeHeaderHandler.bind(this);
     this.showHandsonTableHandler = this.showHandsonTableHandler.bind(this);
+    this.showDrawioHandler = this.showDrawioHandler.bind(this);
   }
 
   init() {
@@ -647,6 +650,10 @@ export default class CodeMirrorEditor extends AbstractEditor {
     this.handsontableModal.show(mtu.getMarkdownTable(this.getCodeMirror()));
   }
 
+  showDrawioHandler() {
+    this.drawioModal.show(mdu.getMarkdownDrawioMxfile(this.getCodeMirror()));
+  }
+
   getNavbarItems() {
     return [
       /* eslint-disable max-len */
@@ -746,6 +753,14 @@ export default class CodeMirrorEditor extends AbstractEditor {
       >
         <EditorIcon icon="Table" />
       </Button>,
+      <Button
+        key="nav-item-table"
+        bsSize="small"
+        title="draw.io"
+        onClick={this.showDrawioHandler}
+      >
+        <EditorIcon icon="Drawio" />
+      </Button>,
       /* eslint-able max-len */
     ];
   }
@@ -824,6 +839,10 @@ export default class CodeMirrorEditor extends AbstractEditor {
           ref={(c) => { this.handsontableModal = c }}
           onSave={(table) => { return mtu.replaceFocusedMarkdownTableWithEditor(this.getCodeMirror(), table) }}
         />
+        <DrawioModal
+          ref={(c) => { this.drawioModal = c }}
+          onSave={(drawioData) => { return mdu.replaceFocusedDrawioWithEditor(this.getCodeMirror(), drawioData) }}
+        />
 
       </React.Fragment>
     );

+ 111 - 0
src/client/js/components/PageEditor/DrawioModal.jsx

@@ -0,0 +1,111 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import Modal from 'react-bootstrap/es/Modal';
+import Button from 'react-bootstrap/es/Button';
+
+export default class DrawioModal extends React.PureComponent {
+
+  constructor(props) {
+    super(props);
+
+    /*
+     * ## Note ##
+     * Currently, this component try to synchronize the cells data and alignment data of state.markdownTable with these of the HotTable.
+     * However, changes made by the following operations are not synchronized.
+     *
+     * 1. move columns: Alignment changes are synchronized but data changes are not.
+     * 2. move rows: Data changes are not synchronized.
+     * 3. insert columns or rows: Data changes are synchronized but alignment changes are not.
+     * 4. delete columns or rows: Data changes are synchronized but alignment changes are not.
+     *
+     * However, all operations are reflected in the data to be saved because the HotTable data is used when the save method is called.
+     */
+    this.state = {
+      show: false,
+      drawioMxFileOnInit: '',
+      drawioMxFile: '',
+      drawioIFrame: null,
+    };
+
+    this.drawioIFrame = React.createRef();
+
+    this.init = this.init.bind(this);
+    this.cancel = this.cancel.bind(this);
+    this.receiveFromDrawio = this.receiveFromDrawio.bind(this);
+  }
+
+  init(drawioMxFile) {
+    const initDrawioMxFile = drawioMxFile;
+    this.setState(
+      {
+        drawioMxFile: initDrawioMxFile,
+      },
+    );
+  }
+
+  show(drawioMxFile) {
+    this.init(drawioMxFile);
+
+    const iframe = document.createElement('iframe');
+    iframe.src = 'https://www.draw.io/?embed=1&lang=ja&title=FUGA&diagramName=HOGE';
+    iframe.style = 'position: absolute; z-index: 9999; top: 0; left: 0;';
+    iframe.width = document.body.clientWidth;
+    iframe.height = document.body.clientHeight;
+
+    window.addEventListener('message', this.receiveFromDrawio);
+    this.setState({ show: true });
+
+    this.setState(
+      {
+        drawioIFrame: iframe,
+      },
+    );
+
+    document.body.appendChild(iframe);
+  }
+
+  hide() {
+    this.setState({
+      show: false,
+    });
+  }
+
+  cancel() {
+    this.hide();
+  }
+
+  receiveFromDrawio(event) {
+    if (event.data === 'ready') {
+      event.source.postMessage(this.state.drawioMxFile, '*');
+    }
+    else {
+      if (event.data.length > 0) {
+        const parser = new DOMParser();
+        const dom = parser.parseFromString(event.data, 'text/xml');
+        const value = dom.getElementsByTagName('diagram')[0].innerHTML;
+        console.log(value);
+        this.props.onSave(value);
+      }
+
+      // window.removeEventListener('resize', resize);
+      window.removeEventListener('message', this.receiveFromDrawio);
+      document.body.removeChild(this.state.drawioIFrame);
+    }
+  }
+
+  render() {
+    const dialogClassNames = ['handsontable-modal'];
+    if (this.state.isWindowExpanded) {
+      dialogClassNames.push('handsontable-modal-expanded');
+    }
+
+    return (
+      <div />
+    );
+  }
+
+}
+
+DrawioModal.propTypes = {
+  onSave: PropTypes.func,
+};

Разница между файлами не показана из-за своего большого размера
+ 3 - 0
src/client/js/components/PageEditor/EditorIcon.jsx


+ 36 - 0
src/client/js/components/PageEditor/MarkdownDrawioUtil.js

@@ -0,0 +1,36 @@
+/**
+ * Utility for markdown drawio
+ */
+class MarkdownDrawioUtil {
+
+  constructor() {
+    // https://github.com/markdown-it/markdown-it/blob/d29f421927e93e88daf75f22089a3e732e195bd2/lib/rules_block/table.js#L83
+    this.tableAlignmentLineRE = /^[-:|][-:|\s]*$/;
+    this.tableAlignmentLineNegRE = /^[^-:]*$/; // it is need to check to ignore empty row which is matched above RE
+    // https://regex101.com/r/7BN2fR/10
+    this.linePartOfTableRE = /^([^\r\n|]*)\|(([^\r\n|]*\|)+)$/;
+    // https://regex101.com/r/1UuWBJ/3
+    this.emptyLineOfTableRE = /^([^\r\n|]*)\|((\s*\|)+)$/;
+  }
+
+  /**
+   * return MarkdownTable instance of the table where the cursor is
+   * (If the cursor is not in a table, return null)
+   */
+  getMarkdownDrawioMxfile(editor) {
+    const curPos = editor.getCursor();
+    return editor.getDoc().getLine(curPos.line);
+  }
+
+  replaceFocusedDrawioWithEditor(editor, drawioData) {
+    const curPos = editor.getCursor();
+    const line = editor.getDoc().getLine(curPos.line);
+    editor.getDoc().replaceRange(drawioData.toString(), { line: curPos.line, ch: 0 }, { line: curPos.line, ch: line.length });
+  }
+
+}
+
+// singleton pattern
+const instance = new MarkdownDrawioUtil();
+Object.freeze(instance);
+export default instance;

+ 2 - 0
src/client/js/util/GrowiRenderer.js

@@ -14,6 +14,7 @@ import TableConfigurer from './markdown-it/table';
 import TaskListsConfigurer from './markdown-it/task-lists';
 import TocAndAnchorConfigurer from './markdown-it/toc-and-anchor';
 import BlockdiagConfigurer from './markdown-it/blockdiag';
+import DrawioViewerConfigurer from './markdown-it/drawio-viewer';
 import TableWithHandsontableButtonConfigurer from './markdown-it/table-with-handsontable-button';
 import HeaderWithEditLinkConfigurer from './markdown-it/header-with-edit-link';
 
@@ -67,6 +68,7 @@ export default class GrowiRenderer {
       new HeaderConfigurer(appContainer),
       new EmojiConfigurer(appContainer),
       new MathJaxConfigurer(appContainer),
+      new DrawioViewerConfigurer(appContainer),
       new PlantUMLConfigurer(appContainer),
       new BlockdiagConfigurer(appContainer),
     ];

+ 17 - 0
src/client/js/util/markdown-it/drawio-viewer.js

@@ -0,0 +1,17 @@
+export default class DrawioViewerConfigurer {
+
+  constructor(crowi) {
+    // this.crowi = crowi;
+    // const config = crowi.getConfig();
+
+    this.drawioViewerURL = 'https://www.draw.io/js/viewer.min.js';
+  }
+
+  configure(md) {
+    md.use(require('markdown-it-drawio-viewer'), {
+      drawioViewerURL: this.drawioViewerURL,
+      marker: ':::',
+    });
+  }
+
+}

+ 17 - 1
yarn.lock

@@ -8331,6 +8331,14 @@ markdown-it-blockdiag@^1.0.2:
     url-join "^4.0.0"
     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==
+  dependencies:
+    markdown-it-fence "0.1.3"
+    xmldoc "^1.1.2"
+
 markdown-it-emoji@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz#9bee0e9a990a963ba96df6980c4fddb05dfb4dcc"
@@ -8338,6 +8346,7 @@ markdown-it-emoji@^1.4.0:
 markdown-it-fence@0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/markdown-it-fence/-/markdown-it-fence-0.1.3.tgz#26e90149b7de5658cdb27096b2e2b5ec4e72d6ce"
+  integrity sha512-mdZbkwJUyZXhyDX4QzD+WnIhxWBq9oIIJU22E6jCT7MHgIp79oEOJfwXIl/HqmfIxnXEdC47sBGn4h6chM4DKw==
 
 markdown-it-footnote@^3.0.1:
   version "3.0.1"
@@ -11944,7 +11953,7 @@ sax@1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
 
-sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4:
+sax@>=0.6.0, sax@^1.2.1, sax@^1.2.4, sax@~1.2.4:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
 
@@ -14424,6 +14433,13 @@ xmlchars@^2.1.1:
   resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
   integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
 
+xmldoc@^1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/xmldoc/-/xmldoc-1.1.2.tgz#6666e029fe25470d599cd30e23ff0d1ed50466d7"
+  integrity sha512-ruPC/fyPNck2BD1dpz0AZZyrEwMOrWTO5lDdIXS91rs3wtm4j+T8Rp2o+zoOYkkAxJTZRPOSnOGei1egoRmKMQ==
+  dependencies:
+    sax "^1.2.1"
+
 xmldom@0.1.27, xmldom@0.1.x, xmldom@~0.1.15:
   version "0.1.27"
   resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"

Некоторые файлы не были показаны из-за большого количества измененных файлов