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

rewrite HackmdEditor and GROWI agent for HackMD using 'penpal'

Yuki Takei 7 лет назад
Родитель
Сommit
047179d26d

+ 1 - 0
package.json

@@ -167,6 +167,7 @@
     "null-loader": "^0.1.1",
     "null-loader": "^0.1.1",
     "on-headers": "^1.0.1",
     "on-headers": "^1.0.1",
     "optimize-css-assets-webpack-plugin": "^5.0.0",
     "optimize-css-assets-webpack-plugin": "^5.0.0",
+    "penpal": "^3.0.3",
     "plantuml-encoder": "^1.2.5",
     "plantuml-encoder": "^1.2.5",
     "postcss-loader": "^2.1.3",
     "postcss-loader": "^2.1.3",
     "react": "^16.4.1",
     "react": "^16.4.1",

+ 54 - 49
resource/js/agent-for-hackmd.js

@@ -9,25 +9,16 @@
  *
  *
  * @author Yuki Takei <yuki@weseek.co.jp>
  * @author Yuki Takei <yuki@weseek.co.jp>
  */
  */
-import { debounce } from 'throttle-debounce';
+import Penpal from 'penpal';
+// Penpal.debug = true;
 
 
-const JSON = window.JSON;
+import { debounce } from 'throttle-debounce';
 
 
 /* eslint-disable no-console  */
 /* eslint-disable no-console  */
 
 
 const allowedOrigin = '{{origin}}';         // will be replaced by swig
 const allowedOrigin = '{{origin}}';         // will be replaced by swig
 const styleFilePath = '{{styleFilePath}}';  // will be replaced by swig
 const styleFilePath = '{{styleFilePath}}';  // will be replaced by swig
 
 
-/**
- * Validate origin
- * @param {object} event
- */
-function validateOrigin(event) {
-  if (event.origin !== allowedOrigin) {
-    console.error('[HackMD] Message is rejected.', 'Cause: "event.origin" and "allowedOrigin" does not match');
-    return;
-  }
-}
 
 
 /**
 /**
  * Insert link tag to load style file
  * Insert link tag to load style file
@@ -39,20 +30,41 @@ function insertStyle() {
   document.getElementsByTagName('head')[0].appendChild(element);
   document.getElementsByTagName('head')[0].appendChild(element);
 }
 }
 
 
-function postMessageOnChange(body) {
-  const data = {
-    operation: 'notifyBodyChanges',
-    body
-  };
-  window.parent.postMessage(JSON.stringify(data), allowedOrigin);
+/**
+ * return the value of CodeMirror
+ */
+function getValueOfCodemirror() {
+  // get CodeMirror instance
+  const editor = window.editor;
+  return editor.doc.getValue();
 }
 }
 
 
-function postMessageOnSave(body) {
-  const data = {
-    operation: 'save',
-    body
-  };
-  window.parent.postMessage(JSON.stringify(data), allowedOrigin);
+/**
+ * set the specified document to CodeMirror
+ * @param {string} document
+ */
+function setValueToCodemirror(document) {
+  // get CodeMirror instance
+  const editor = window.editor;
+  editor.doc.setValue(document);
+}
+
+/**
+ * postMessage to GROWI to notify body changes
+ * @param {string} body
+ */
+function postParentToNotifyBodyChanges(body) {
+  window.growi.notifyBodyChanges(body);
+}
+// generate debounced function
+const debouncedPostParentToNotifyBodyChanges = debounce(1500, postParentToNotifyBodyChanges);
+
+/**
+ * postMessage to GROWI to save with shortcut
+ * @param {string} document
+ */
+function postParentToSaveWithShortcut(document) {
+  window.growi.saveWithShortcut(document);
 }
 }
 
 
 function addEventListenersToCodemirror() {
 function addEventListenersToCodemirror() {
@@ -62,27 +74,19 @@ function addEventListenersToCodemirror() {
   const editor = window.editor;
   const editor = window.editor;
 
 
   //// change event
   //// change event
-  // generate debounced function
-  const debouncedPostMessageOnChange = debounce(1500, postMessageOnChange);
   editor.on('change', (cm, change) => {
   editor.on('change', (cm, change) => {
-    debouncedPostMessageOnChange(cm.doc.getValue());
+    debouncedPostParentToNotifyBodyChanges(cm.doc.getValue());
   });
   });
 
 
   //// save event
   //// save event
   // Reset save commands and Cmd-S/Ctrl-S shortcuts that initialized by HackMD
   // Reset save commands and Cmd-S/Ctrl-S shortcuts that initialized by HackMD
   codemirror.commands.save = function(cm) {
   codemirror.commands.save = function(cm) {
-    postMessageOnSave(cm.doc.getValue());
+    postParentToSaveWithShortcut(cm.doc.getValue());
   };
   };
   delete editor.options.extraKeys['Cmd-S'];
   delete editor.options.extraKeys['Cmd-S'];
   delete editor.options.extraKeys['Ctrl-S'];
   delete editor.options.extraKeys['Ctrl-S'];
 }
 }
 
 
-function setValue(document) {
-  // get CodeMirror instance
-  const editor = window.editor;
-  editor.doc.setValue(document);
-}
-
 
 
 /**
 /**
  * main
  * main
@@ -98,26 +102,27 @@ function setValue(document) {
 
 
   insertStyle();
   insertStyle();
 
 
-  // Add event listeners
-  window.addEventListener('message', (event) => {
-    validateOrigin(event);
-
-    const data = JSON.parse(event.data);
-    switch (data.operation) {
-      case 'getValue':
-        console.log('getValue called');
-        break;
-      case 'setValue':
-        setValue(data.document);
-        break;
-    }
-  });
-
   window.addEventListener('load', (event) => {
   window.addEventListener('load', (event) => {
     console.log('loaded');
     console.log('loaded');
     addEventListenersToCodemirror();
     addEventListenersToCodemirror();
   });
   });
 
 
+  const connection = Penpal.connectToParent({
+    parentOrigin: allowedOrigin,
+    // Methods child is exposing to parent
+    methods: {
+      getValue() {
+        return getValueOfCodemirror();
+      },
+      setValue(newValue) {
+        setValueToCodemirror(newValue);
+      },
+    }
+  });
+  connection.promise.then(parent => {
+    window.growi = parent;
+  });
+
   console.log('[HackMD] GROWI agent for HackMD has successfully loaded.');
   console.log('[HackMD] GROWI agent for HackMD has successfully loaded.');
 }());
 }());
 
 

+ 38 - 53
resource/js/components/PageEditorByHackmd/HackmdEditor.jsx

@@ -1,6 +1,9 @@
 import React from 'react';
 import React from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
+import Penpal from 'penpal';
+// Penpal.debug = true;
+
 export default class HackmdEditor extends React.PureComponent {
 export default class HackmdEditor extends React.PureComponent {
 
 
   constructor(props) {
   constructor(props) {
@@ -9,57 +12,48 @@ export default class HackmdEditor extends React.PureComponent {
     this.state = {
     this.state = {
     };
     };
 
 
-    this.listenMessages = this.listenMessages.bind(this);
-    this.notifyBodyChangesHandler = this.notifyBodyChangesHandler.bind(this);
+    this.hackmd = null;
 
 
-    this.loadHandler = this.loadHandler.bind(this);
-    this.saveHandler = this.saveHandler.bind(this);
-  }
+    this.initHackmdWithPenpal = this.initHackmdWithPenpal.bind(this);
 
 
-  componentDidMount() {
-    const contentWindow = this.refs.iframe.contentWindow;
-    this.listenMessages(contentWindow);
+    this.notifyBodyChangesHandler = this.notifyBodyChangesHandler.bind(this);
+    this.saveWithShortcutHandler = this.saveWithShortcutHandler.bind(this);
   }
   }
 
 
-  setValue(newValue) {
-    const data = { operation: 'setValue' };
-    if (newValue != null) {
-      data.document = newValue;
-    }
-    this.postMessageToHackmd(data);
+  componentDidMount() {
+    // append iframe with penpal
+    this.initHackmdWithPenpal();
   }
   }
 
 
-  /**
-   *
-   * @param {object} targetWindow
-   */
-  listenMessages(targetWindow) {
-    window.addEventListener('message', (e) => {
-      if (targetWindow !== e.source) {
-        return;
-      }
-
-      const data = JSON.parse(e.data);
-      const operation = data.operation;
-      const body = data.body;
-
-      switch (operation) {
-        case 'notifyBodyChanges':
-          this.notifyBodyChangesHandler(body);
-          break;
-        case 'save':
-          this.saveHandler(body);
-          break;
+  initHackmdWithPenpal() {
+    const _this = this;   // for in methods scope
+
+    const url = `${this.props.hackmdUri}/${this.props.pageIdOnHackmd}?both`;
+
+    const connection = Penpal.connectToChild({
+      url,
+      appendTo: this.refs.iframeContainer,
+      methods: {  // expose methods to HackMD
+        notifyBodyChanges(document) {
+          _this.notifyBodyChangesHandler(document);
+        },
+        saveWithShortcut(document) {
+          _this.saveWithShortcutHandler(document);
+        }
+      },
+    });
+    connection.promise.then(child => {
+      this.hackmd = child;
+    });
+    connection.iframe.addEventListener('load', () => {
+      if (this.props.initializationMarkdown != null) {
+        this.setValue(this.props.initializationMarkdown);
       }
       }
     });
     });
   }
   }
 
 
-  /**
-   *
-   * @param {object} data
-   */
-  postMessageToHackmd(data) {
-    this.refs.iframe.contentWindow.postMessage(JSON.stringify(data), this.props.hackmdUri);
+  setValue(newValue) {
+    this.hackmd.setValue(newValue);
   }
   }
 
 
   notifyBodyChangesHandler(body) {
   notifyBodyChangesHandler(body) {
@@ -69,25 +63,16 @@ export default class HackmdEditor extends React.PureComponent {
     }
     }
   }
   }
 
 
-  loadHandler() {
-    this.setValue(this.props.initializationMarkdown);
-  }
-
-  saveHandler(document) {
+  saveWithShortcutHandler(document) {
     if (this.props.onSaveWithShortcut != null) {
     if (this.props.onSaveWithShortcut != null) {
       this.props.onSaveWithShortcut(document);
       this.props.onSaveWithShortcut(document);
     }
     }
   }
   }
 
 
   render() {
   render() {
-    const src = `${this.props.hackmdUri}/${this.props.pageIdOnHackmd}?both`;
     return (
     return (
-      <iframe id='iframe-hackmd'
-        ref='iframe'
-        src={src}
-        onLoad={this.loadHandler}
-      >
-      </iframe>
+      // will be rendered in componentDidMount
+      <div id='iframe-hackmd-container' ref='iframeContainer'></div>
     );
     );
   }
   }
 }
 }

+ 2 - 2
resource/styles/scss/_on-edit.scss

@@ -111,7 +111,7 @@ body.on-edit {
 
 
       #page-editor-with-hackmd {
       #page-editor-with-hackmd {
         &,
         &,
-        .hackmd-preinit, #iframe-hackmd {
+        .hackmd-preinit, #iframe-hackmd-container > iframe {
           width: 100vw;
           width: 100vw;
           min-height: calc(100vh - #{$header-plus-footer});   // for IE11
           min-height: calc(100vh - #{$header-plus-footer});   // for IE11
           height: calc(100vh - #{$header-plus-footer});
           height: calc(100vh - #{$header-plus-footer});
@@ -255,7 +255,7 @@ body.on-edit {
       display: none;
       display: none;
     }
     }
 
 
-    .hackmd-preinit, #iframe-hackmd {
+    .hackmd-preinit, #iframe-hackmd-container > iframe {
       border: none;
       border: none;
     }
     }
 
 

+ 4 - 0
yarn.lock

@@ -6333,6 +6333,10 @@ pbkdf2@^3.0.3:
     safe-buffer "^5.0.1"
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
     sha.js "^2.4.8"
 
 
+penpal@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/penpal/-/penpal-3.0.3.tgz#6cdbd99d8c5dadb73be16d9fe6807826b0d9a715"
+
 performance-now@^0.2.0:
 performance-now@^0.2.0:
   version "0.2.0"
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"