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

impl startIntegrationWithHackmd method

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

+ 17 - 0
lib/models/page.js

@@ -1303,6 +1303,23 @@ module.exports = function(crowi) {
       });
       });
   };
   };
 
 
+  /**
+   * associate GROWI page and HackMD page
+   * @param {Page} pageData
+   * @param {string} pageIdOnHackmd
+   */
+  pageSchema.statics.registerHackmdPage = function(pageData, pageIdOnHackmd) {
+    if (pageData.pageIdOnHackmd != null) {
+      throw new Error(`'pageIdOnHackmd' of the page '${pageData.path}' is not empty`);
+    }
+
+    pageData.pageIdOnHackmd = pageIdOnHackmd;
+    pageData.revisionHackmdSynced = pageData.revision;
+
+    // update page
+    return pageData.save();
+  };
+
   pageSchema.statics.getHistories = function() {
   pageSchema.statics.getHistories = function() {
     // TODO
     // TODO
     return;
     return;

+ 56 - 2
lib/routes/hackmd.js

@@ -1,6 +1,12 @@
+const logger = require('@alias/logger')('growi:routes:hackmd');
+const path = require('path');
+const swig = require('swig-templates');
+const axios = require('axios');
+
+const ApiResponse = require('../util/apiResponse');
+
 module.exports = function(crowi, app) {
 module.exports = function(crowi, app) {
-  const path = require('path');
-  const swig = require('swig-templates');
+  const Page = crowi.models.Page;
 
 
   // load GROWI agent script for HackMD
   // load GROWI agent script for HackMD
   const manifest = require(path.join(crowi.publicDir, 'manifest.json'));
   const manifest = require(path.join(crowi.publicDir, 'manifest.json'));
@@ -32,7 +38,55 @@ module.exports = function(crowi, app) {
     res.send(script);
     res.send(script);
   };
   };
 
 
+  /**
+   * Create page on HackMD and start to integrate
+   * @param {object} req
+   * @param {object} res
+   */
+  const integrate = async function(req, res) {
+    // validate process.env.HACKMD_URI
+    const hackMdUri = process.env.HACKMD_URI;
+    if (hackMdUri == null) {
+      return res.json(ApiResponse.error('HackMD for GROWI has not been setup'));
+    }
+    // validate pageId
+    const pageId = req.body.pageId;
+    if (pageId == null) {
+      return res.json(ApiResponse.error('pageId required'));
+    }
+    // validate page
+    const page = await Page.findOne({ _id: pageId });
+    if (page == null) {
+      return res.json(ApiResponse.error(`Page('${pageId}') does not exist`));
+    }
+    if (page.pageIdOnHackmd != null) {
+      return res.json(ApiResponse.error(`'pageIdOnHackmd' of the page '${page.path}' is not empty`));
+    }
+
+    // access to HackMD and create page
+    const response = await axios.get(`${hackMdUri}/new`);
+    logger.debug('HackMD responds', response);
+
+    // extract page id on HackMD
+    const pagePathOnHackmd = response.request.path;     // e.g. '/NC7bSRraT1CQO1TO7wjCPw'
+    const pageIdOnHackmd = pagePathOnHackmd.substr(1);  // strip the head '/'
+
+    // persist
+    try {
+      await Page.registerHackmdPage(page, pageIdOnHackmd);
+
+      const data = {
+        pageIdOnHackmd,
+      };
+      return res.json(ApiResponse.success(data));
+    }
+    catch (err) {
+      return res.json(ApiResponse.error(err));
+    }
+  };
+
   return {
   return {
     loadAgent,
     loadAgent,
+    integrate,
   };
   };
 };
 };

+ 1 - 0
lib/routes/index.js

@@ -202,6 +202,7 @@ module.exports = function(crowi, app) {
   app.get('/trash/*/$'               , loginRequired(crowi, app, false) , page.deletedPageListShowWrapper);
   app.get('/trash/*/$'               , loginRequired(crowi, app, false) , page.deletedPageListShowWrapper);
 
 
   app.get('/_hackmd/load-agent'      , hackmd.loadAgent);
   app.get('/_hackmd/load-agent'      , hackmd.loadAgent);
+  app.post('/_api/hackmd/integrate'  , accessTokenParser , loginRequired(crowi, app) , csrf, hackmd.integrate);
 
 
   app.get('/*/$'                   , loginRequired(crowi, app, false) , page.pageListShowWrapper);
   app.get('/*/$'                   , loginRequired(crowi, app, false) , page.pageListShowWrapper);
   app.get('/*'                     , loginRequired(crowi, app, false) , page.pageShowWrapper);
   app.get('/*'                     , loginRequired(crowi, app, false) , page.pageShowWrapper);

+ 61 - 6
resource/js/components/PageEditorByHackmd.jsx

@@ -1,6 +1,8 @@
 import React from 'react';
 import React from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
+import * as toastr from 'toastr';
+
 import HackmdEditor from './PageEditorByHackmd/HackmdEditor';
 import HackmdEditor from './PageEditorByHackmd/HackmdEditor';
 
 
 export default class PageEditorByHackmd extends React.PureComponent {
 export default class PageEditorByHackmd extends React.PureComponent {
@@ -9,26 +11,79 @@ export default class PageEditorByHackmd extends React.PureComponent {
     super(props);
     super(props);
 
 
     this.state = {
     this.state = {
+      isInitializing: false,
+      pageIdOnHackmd: this.props.pageIdOnHackmd,
     };
     };
+
+    this.getHackmdUri = this.getHackmdUri.bind(this);
+    this.startIntegrationWithHackmd = this.startIntegrationWithHackmd.bind(this);
+
+    this.apiErrorHandler = this.apiErrorHandler.bind(this);
   }
   }
 
 
   componentWillMount() {
   componentWillMount() {
   }
   }
 
 
+  getHackmdUri() {
+    const envVars = this.props.crowi.config.env;
+    return envVars.HACKMD_URI;
+  }
+
   syncToLatestRevision() {
   syncToLatestRevision() {
 
 
   }
   }
 
 
+  /**
+   * Start integration with HackMD
+   */
+  startIntegrationWithHackmd() {
+    const hackmdUri = this.getHackmdUri();
+
+    if (hackmdUri == null) {
+      // do nothing
+      return;
+    }
+
+    this.setState({isInitializing: true});
+
+    const params = {
+      pageId: this.props.pageId,
+    };
+    this.props.crowi.apiPost('/hackmd/integrate', params)
+      .then(res => {
+        if (!res.ok) {
+          throw new Error(res.error);
+        }
+
+        this.setState({pageIdOnHackmd: res.pageIdOnHackmd});
+      })
+      .catch(this.apiErrorHandler)
+      .then(() => {
+        this.setState({isInitializing: false});
+      });
+  }
+
+  apiErrorHandler(error) {
+    toastr.error(error.message, 'Error occured', {
+      closeButton: true,
+      progressBar: true,
+      newestOnTop: false,
+      showDuration: '100',
+      hideDuration: '100',
+      timeOut: '3000',
+    });
+  }
+
   render() {
   render() {
-    const envVars = this.props.crowi.config.env;
-    const hackMdUri = envVars.HACKMD_URI;
+    const hackmdUri = this.getHackmdUri();
 
 
-    if (hackMdUri == null || this.props.pageIdOnHackmd == null) {
+    if (hackmdUri == null || this.state.pageIdOnHackmd == null) {
       return (
       return (
         <div className="hackmd-nopage d-flex justify-content-center align-items-center">
         <div className="hackmd-nopage d-flex justify-content-center align-items-center">
           <div>
           <div>
             <p className="text-center">
             <p className="text-center">
-              <button className="btn btn-success btn-lg waves-effect waves-light" type="button">
+              <button className="btn btn-success btn-lg waves-effect waves-light" type="button"
+                  onClick={() => this.startIntegrationWithHackmd()} disabled={this.state.isInitializing}>
                 <span className="btn-label"><i className="fa fa-file-text-o"></i></span>
                 <span className="btn-label"><i className="fa fa-file-text-o"></i></span>
                 Start to edit with HackMD
                 Start to edit with HackMD
               </button>
               </button>
@@ -42,8 +97,8 @@ export default class PageEditorByHackmd extends React.PureComponent {
     return (
     return (
       <HackmdEditor
       <HackmdEditor
         markdown={this.props.markdown}
         markdown={this.props.markdown}
-        hackMdUri={hackMdUri}
-        pageIdOnHackmd={this.props.pageIdOnHackmd}
+        hackmdUri={hackmdUri}
+        pageIdOnHackmd={this.state.pageIdOnHackmd}
       >
       >
       </HackmdEditor>
       </HackmdEditor>
     );
     );

+ 2 - 2
resource/js/components/PageEditorByHackmd/HackmdEditor.jsx

@@ -24,7 +24,7 @@ export default class HackmdEditor extends React.PureComponent {
   }
   }
 
 
   render() {
   render() {
-    const src = `${this.props.hackMdUri}/${this.props.pageIdOnHackmd}`;
+    const src = `${this.props.hackmdUri}/${this.props.pageIdOnHackmd}`;
     return (
     return (
       <iframe id='iframe-hackmd'
       <iframe id='iframe-hackmd'
         ref='iframe'
         ref='iframe'
@@ -38,6 +38,6 @@ export default class HackmdEditor extends React.PureComponent {
 
 
 HackmdEditor.propTypes = {
 HackmdEditor.propTypes = {
   markdown: PropTypes.string.isRequired,
   markdown: PropTypes.string.isRequired,
-  hackMdUri: PropTypes.string.isRequired,
+  hackmdUri: PropTypes.string.isRequired,
   pageIdOnHackmd: PropTypes.string.isRequired,
   pageIdOnHackmd: PropTypes.string.isRequired,
 };
 };