Kaynağa Gözat

generate tree structure and render with React

Yuki Takei 9 yıl önce
ebeveyn
işleme
ed4040d052

+ 2 - 1
packages/growi-plugin-lsx/package.json

@@ -32,7 +32,8 @@
   "dependencies": {
     "crowi-pluginkit": "^1.0.3",
     "react": "^15.4.2",
-    "react-dom": "^15.4.2"
+    "react-dom": "^15.4.2",
+    "urlgrey": "^0.4.4"
   },
   "devDependencies": {
     "babel-cli": "^6.23.0",

+ 0 - 13
packages/growi-plugin-lsx/src/lib/routes/lsx.js

@@ -3,22 +3,9 @@ module.exports = (crowi, app) => {
     , path = require('path')
     , url = require('url')
     , Page = crowi.model('Page')
-    , LsxPageListRenderer = require('../util/LsxPageListRenderer')
-    , lsx = new LsxPageListRenderer(crowi, app)
     , ApiResponse = crowi.require('../util/apiResponse')
     , actions = {};
 
-  actions.renderHtml = (req, res) => {
-    lsx.renderHtml(req.user, req.query.fromPagePath, req.query.args)
-      .then((html) => {
-        return res.json(ApiResponse.success({html}));
-      })
-      .catch((err) => {
-        debug('Error on rendering lsx.renderHtml', err);
-        return res.json(ApiResponse.error(err));
-      });
-  }
-
   actions.listPages = (req, res) => {
     let user = req.user;
     let fromPagePath = req.query.fromPagePath;

+ 0 - 64
packages/growi-plugin-lsx/src/lib/util/LsxPageListRenderer.js

@@ -1,64 +0,0 @@
-const debug = require('debug')('crowi-plugin:lsx:util:LsxPageListRenderer');
-const url = require('url');
-
-class LsxPageListRenderer {
-
-  constructor(crowi, app) {
-    this.crowi = crowi;
-    this.app = app;
-  }
-
-  renderHtml(user, fromPagePath, args) {
-    debug(`rendering html for fromPagePath='${fromPagePath}', args='${args}'`);
-
-    // TODO try-catch
-
-    // initialize
-    let lsxPrefix = args || fromPagePath;
-    let lsxOptions = {};
-
-    // if args is a String like 'key1=value1, key2=value2, ...'
-    const splittedArgs = args.split(',');
-    if (splittedArgs.length > 1) {
-      splittedArgs.forEach((arg) => {
-        arg = arg.trim();
-
-        // see https://regex101.com/r/pYHcOM/1
-        const match = arg.match(/([^=]+)=?(.+)?/);
-        const value = match[2] || true;
-        lsxOptions[match[1]] = value;
-      });
-
-      // determine prefix
-      // 'prefix=foo' pattern or the first argument
-      lsxPrefix = lsxOptions.prefix || splittedArgs[0];
-    }
-
-    // resolve url
-    const pagePath = url.resolve(fromPagePath, lsxPrefix);
-    const queryOptions = {}
-
-    // find pages
-    const Page = this.crowi.model('Page');
-    return Page.findListByStartWith(pagePath, user, queryOptions)
-      .then((pages) => {
-        let renderVars = {};
-        renderVars.pages = pages;
-        renderVars.pager = false
-        renderVars.viewConfig = {};
-
-        // app.render widget
-        return new Promise((resolve, reject) => {
-          this.app.render('widget/page_list', renderVars, (err, html) => {
-            if (err) {
-              reject(err);
-            }
-            resolve(html);
-          });
-        });
-      });
-  }
-
-}
-
-module.exports = LsxPageListRenderer;

+ 95 - 12
packages/growi-plugin-lsx/src/resource/js/components/Lsx.jsx

@@ -1,9 +1,11 @@
 import React from 'react';
+import urlgrey from 'urlgrey';
 
 import styles from '../../css/index.css';
 
 import { LsxContext } from '../util/LsxContext';
 import { LsxCacheHelper } from '../util/LsxCacheHelper';
+import { PageNode } from './PageNode';
 
 export class Lsx extends React.Component {
 
@@ -12,8 +14,8 @@ export class Lsx extends React.Component {
 
     this.state = {
       isLoading: true,
-      html: '',
       isError: false,
+      tree: undefined,
       errorMessage: '',
     };
   }
@@ -23,7 +25,7 @@ export class Lsx extends React.Component {
     if (this.props.lsxStateCache) {
       this.setState({
         isLoading: false,
-        html: this.props.lsxStateCache.html,
+        tree: this.props.lsxStateCache.tree,
         isError: this.props.lsxStateCache.isError,
         errorMessage: this.props.lsxStateCache.errorMessage,
       });
@@ -31,24 +33,23 @@ export class Lsx extends React.Component {
     }
 
     const lsxContext = this.props.lsxContext;
-    const fromPagePath = lsxContext.fromPagePath;
+    let fromPagePath = this.addSlashOfEnd(lsxContext.fromPagePath);
     const args = lsxContext.lsxArgs;
 
     this.props.crowi.apiGet('/plugins/lsx', {fromPagePath, args})
+      .catch(error => {
+        const errorMessage = error.response.data.error.message;
+        this.setState({ isError: true, errorMessage: errorMessage });
+      })
       .then((res) => {
         if (res.ok) {
-          this.setState({ html: res.html });
+          const tree = this.generatePageNodeTree(fromPagePath, res.pages);
+          this.setState({ tree });
         }
         else {
           return Promise.reject(res.error);
         }
       })
-      .catch(error => {
-        console.error(error);
-        const errorMessage = error.response.data.error.message;
-        console.error(errorMessage);
-        this.setState({ isError: true, errorMessage: errorMessage });
-      })
       // finally
       .then(() => {
         this.setState({ isLoading: false });
@@ -59,6 +60,84 @@ export class Lsx extends React.Component {
       })
   }
 
+  /**
+   * generate tree structure
+   *
+   * @param {string} fromPagePath
+   * @param {Page[]} pages Array of Page model
+   *
+   * @memberOf Lsx
+   */
+  generatePageNodeTree(fromPagePath, pages) {
+    let pathToNodeMap = {};
+
+    pages.forEach((page) => {
+      // exclude fromPagePath
+      if (page.path === this.omitSlashOfEnd(fromPagePath)) {
+        return;
+      }
+
+      const node = new PageNode(page.path, page);
+      pathToNodeMap[page.path] = node;
+
+      // get or create parent node
+      const parentPath = this.getParentPath(page.path);
+      let parentNode = pathToNodeMap[parentPath];
+      if (parentNode === undefined) {
+        parentNode = new PageNode(parentPath);
+        pathToNodeMap[parentPath] = parentNode;
+      }
+      // associate to patent
+      parentNode.children.push(node);
+    });
+
+    // remove fromPagePath
+    delete pathToNodeMap[this.omitSlashOfEnd(fromPagePath)];
+
+    // return root objects
+    return Object.values(pathToNodeMap).filter((node) => {
+      const parentPath = this.getParentPath(node.path);
+      return !(parentPath in pathToNodeMap);
+    });
+  }
+
+  /**
+   * return path that added slash to the end for specified path
+   *
+   * @param {any} path
+   * @returns
+   *
+   * @memberOf Lsx
+   */
+  addSlashOfEnd(path) {
+    let returnPath = path;
+    if (!path.match(/\/$/)) {
+      returnPath += '/';
+    }
+    return returnPath;
+  }
+
+  /**
+   * return path that omitted slash of the end from specified path
+   *
+   * @param {any} path
+   * @returns
+   *
+   * @memberOf Lsx
+   */
+  omitSlashOfEnd(path) {
+    let returnPath = path;
+    if (path.match(/\/$/)) {
+      returnPath = path.substr(0, path.length -1);
+    }
+    return returnPath;
+  }
+
+  getParentPath(path) {
+    const parent = urlgrey(path).parent();
+    return decodeURIComponent(parent.path());
+  }
+
   getInnerHTMLObj() {
     return { __html: this.state.html };
   }
@@ -82,9 +161,13 @@ export class Lsx extends React.Component {
         </div>
       )
     }
+    // render tree
     else {
-      const innerHtml = this.getInnerHTMLObj();
-      return <div dangerouslySetInnerHTML={innerHtml} />
+      let list = [];
+      this.state.tree.forEach((node) => {
+        list.push(<li>{node.path}</li>);
+      });
+      return <ul>{list}</ul>
     }
   }
 }

+ 16 - 0
packages/growi-plugin-lsx/src/resource/js/components/PageNode.js

@@ -0,0 +1,16 @@
+import urlgrey from 'urlgrey';
+
+export class PageNode {
+
+  path;
+  page;
+  pageName;
+  children;
+
+  constructor(path, page = undefined) {
+    this.path = path;
+    this.pageName = decodeURIComponent(urlgrey(path).child());
+    this.page = page;
+    this.children = [];
+  }
+}