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

copy PageList components from crowi

Yuki Takei 9 лет назад
Родитель
Сommit
35ff2897ce

+ 2 - 29
packages/growi-plugin-lsx/src/lib/routes/lsx.js

@@ -1,40 +1,13 @@
 module.exports = (crowi, app) => {
   var debug = require('debug')('crowi-plugin:lsx:routes:lsx')
-    , path = require('path')
-    , url = require('url')
     , Page = crowi.model('Page')
     , ApiResponse = crowi.require('../util/apiResponse')
     , actions = {};
 
   actions.listPages = (req, res) => {
     let user = req.user;
-    let fromPagePath = req.query.fromPagePath;
-    let args = req.query.args;
-
-    // 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 = {}
+    let pagePath = req.query.pagePath;
+    let queryOptions = req.query.queryOptions;
 
     // find pages
     Page.generateQueryToListByStartWith(pagePath, user, queryOptions)

+ 27 - 31
packages/growi-plugin-lsx/src/resource/js/components/Lsx.jsx

@@ -6,6 +6,7 @@ import styles from '../../css/index.css';
 import { LsxContext } from '../util/LsxContext';
 import { LsxCacheHelper } from '../util/LsxCacheHelper';
 import { PageNode } from './PageNode';
+import { ListView } from './PageList/ListView';
 
 export class Lsx extends React.Component {
 
@@ -15,7 +16,7 @@ export class Lsx extends React.Component {
     this.state = {
       isLoading: true,
       isError: false,
-      tree: undefined,
+      nodeTree: undefined,
       errorMessage: '',
     };
   }
@@ -25,7 +26,7 @@ export class Lsx extends React.Component {
     if (this.props.lsxStateCache) {
       this.setState({
         isLoading: false,
-        tree: this.props.lsxStateCache.tree,
+        nodeTree: this.props.lsxStateCache.nodeTree,
         isError: this.props.lsxStateCache.isError,
         errorMessage: this.props.lsxStateCache.errorMessage,
       });
@@ -33,18 +34,21 @@ export class Lsx extends React.Component {
     }
 
     const lsxContext = this.props.lsxContext;
-    let fromPagePath = this.addSlashOfEnd(lsxContext.fromPagePath);
-    const args = lsxContext.lsxArgs;
+    lsxContext.parse();
 
-    this.props.crowi.apiGet('/plugins/lsx', {fromPagePath, args})
+    // ensure not to forward match to another page
+    // ex: ensure '/Java/' not to match to '/JavaScript'
+    let pagePath = this.addSlashOfEnd(lsxContext.pagePath);
+
+    this.props.crowi.apiGet('/plugins/lsx', {pagePath, queryOptions: ''})
       .catch(error => {
         const errorMessage = error.response.data.error.message;
         this.setState({ isError: true, errorMessage: errorMessage });
       })
       .then((res) => {
         if (res.ok) {
-          const tree = this.generatePageNodeTree(fromPagePath, res.pages);
-          this.setState({ tree });
+          const nodeTree = this.generatePageNodeTree(pagePath, res.pages);
+          this.setState({ nodeTree });
         }
         else {
           return Promise.reject(res.error);
@@ -63,40 +67,40 @@ export class Lsx extends React.Component {
   /**
    * generate tree structure
    *
-   * @param {string} fromPagePath
+   * @param {string} pagePath
    * @param {Page[]} pages Array of Page model
    *
    * @memberOf Lsx
    */
-  generatePageNodeTree(fromPagePath, pages) {
+  generatePageNodeTree(pagePath, pages) {
     let pathToNodeMap = {};
 
     pages.forEach((page) => {
-      // exclude fromPagePath
-      if (page.path === this.omitSlashOfEnd(fromPagePath)) {
+      // exclude pagePath itself
+      if (page.path === this.omitSlashOfEnd(pagePath)) {
         return;
       }
 
-      const node = new PageNode(page.path, page);
+      const node = pathToNodeMap[page.path] || new PageNode();
+      node.page = 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;
+      if (parentPath !== this.omitSlashOfEnd(pagePath)) {
+        let parentNode = pathToNodeMap[parentPath];
+        if (parentNode === undefined) {
+          parentNode = new PageNode();
+          pathToNodeMap[parentPath] = parentNode;
+        }
+        // associate to patent
+        parentNode.children.push(node);
       }
-      // 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);
+      const parentPath = this.getParentPath(node.page.path);
       return !(parentPath in pathToNodeMap);
     });
   }
@@ -138,10 +142,6 @@ export class Lsx extends React.Component {
     return decodeURIComponent(parent.path());
   }
 
-  getInnerHTMLObj() {
-    return { __html: this.state.html };
-  }
-
   render() {
     const lsxContext = this.props.lsxContext;
 
@@ -163,11 +163,7 @@ export class Lsx extends React.Component {
     }
     // render tree
     else {
-      let list = [];
-      this.state.tree.forEach((node) => {
-        list.push(<li>{node.path}</li>);
-      });
-      return <ul>{list}</ul>
+      return <ListView nodeTree={this.state.nodeTree} options={lsxContext.options} />
     }
   }
 }

+ 26 - 0
packages/growi-plugin-lsx/src/resource/js/components/PageList/ListView.js

@@ -0,0 +1,26 @@
+import React from 'react';
+
+import { Page } from './Page';
+import { PageNode } from '../PageNode';
+
+export class ListView extends React.Component {
+
+  render() {
+    const listView = this.props.nodeTree.map((pageNode) => {
+      return <Page pageNode={pageNode} options={this.props.options} />;
+    });
+
+    return (
+      <div className="page-list">
+        <ul className="page-list-ul">
+        {listView}
+        </ul>
+      </div>
+    );
+  }
+}
+
+ListView.propTypes = {
+  nodeTree: React.PropTypes.arrayOf(PageNode).isRequired,
+  options: React.PropTypes.object.isRequired,
+};

+ 31 - 0
packages/growi-plugin-lsx/src/resource/js/components/PageList/Page.js

@@ -0,0 +1,31 @@
+import React from 'react';
+
+import { PageListMeta } from './PageListMeta';
+import { PagePath } from './PagePath';
+import { PageNode } from '../PageNode';
+
+export class Page extends React.Component {
+
+  render() {
+    const pageNode = this.props.pageNode;
+    const page = pageNode.page;
+    const childPages = pageNode.children.map((pageNode) => {
+      return <Page pageNode={pageNode} options={this.props.options} />;
+    });
+
+    return (
+      <li className="page-list-li">
+        <a className="page-list-link" href={page.path}>
+          <PagePath page={page}/>
+        </a>
+        <PageListMeta page={page} />
+        <ul>{childPages}</ul>
+      </li>
+    );
+  }
+}
+
+Page.propTypes = {
+  pageNode: React.PropTypes.instanceOf(PageNode).isRequired,
+  options: React.PropTypes.object.isRequired,
+};

+ 51 - 0
packages/growi-plugin-lsx/src/resource/js/components/PageList/PageListMeta.js

@@ -0,0 +1,51 @@
+import React from 'react';
+
+export class PageListMeta extends React.Component {
+
+  isPortalPath(path) {
+    if (path.match(/.*\/$/)) {
+      return true;
+    }
+
+    return false;
+  }
+
+  render() {
+    // TODO isPortal()
+    const page = this.props.page;
+
+    // portal check
+    let PortalLabel;
+    if (this.isPortalPath(page.path)) {
+      PortalLabel = <span className="label label-info">PORTAL</span>;
+    }
+
+    let CommentCount;
+    if (page.commentCount > 0) {
+      CommentCount = <span><i className="fa fa-comment" />{page.commentCount}</span>;
+    }
+
+    let LikerCount;
+    if (page.liker.length > 0) {
+      LikerCount = <span><i className="fa fa-thumbs-up" />{page.liker.length}</span>;
+    }
+
+
+    return (
+      <span className="page-list-meta">
+        {PortalLabel}
+        {CommentCount}
+        {LikerCount}
+      </span>
+    );
+  }
+}
+
+PageListMeta.propTypes = {
+  page: React.PropTypes.object.isRequired,
+};
+
+PageListMeta.defaultProps = {
+  page: {},
+};
+

+ 49 - 0
packages/growi-plugin-lsx/src/resource/js/components/PageList/PagePath.js

@@ -0,0 +1,49 @@
+import React from 'react';
+
+export class PagePath extends React.Component {
+
+  getShortPath(path) {
+    let name = path.replace(/(\/)$/, '');
+
+    // /.../hoge/YYYY/MM/DD 形式のページ
+    if (name.match(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/)) {
+      return name.replace(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/, '$1');
+    }
+
+    // /.../hoge/YYYY/MM 形式のページ
+    if (name.match(/.+\/([^/]+\/\d{4}\/\d{2})$/)) {
+      return name.replace(/.+\/([^/]+\/\d{4}\/\d{2})$/, '$1');
+    }
+
+    // /.../hoge/YYYY 形式のページ
+    if (name.match(/.+\/([^/]+\/\d{4})$/)) {
+      return name.replace(/.+\/([^/]+\/\d{4})$/, '$1');
+    }
+
+    // ページの末尾を拾う
+    return name.replace(/.+\/(.+)?$/, '$1');
+  }
+
+  render() {
+    const page = this.props.page;
+    const pagePath = page.path.replace(this.props.excludePathString.replace(/^\//, ''), '');
+    const shortPath = this.getShortPath(pagePath);
+    const shortPathEscaped = shortPath.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+    const pathPrefix = pagePath.replace(new RegExp(shortPathEscaped + '(/)?$'), '');
+
+    return (
+      <span className="page-path">
+        {shortPath}
+      </span>
+    );
+  }
+}
+
+PagePath.propTypes = {
+  page: React.PropTypes.object.isRequired,
+};
+
+PagePath.defaultProps = {
+  page: {},
+  excludePathString: '',
+};

+ 1 - 6
packages/growi-plugin-lsx/src/resource/js/components/PageNode.js

@@ -2,15 +2,10 @@ 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;
+  constructor() {
     this.children = [];
   }
 }

+ 38 - 0
packages/growi-plugin-lsx/src/resource/js/util/LsxContext.js

@@ -1,3 +1,5 @@
+import * as url from 'url';
+
 export class LsxContext {
 
   currentPagePath;
@@ -5,4 +7,40 @@ export class LsxContext {
   fromPagePath;
   lsxArgs;
 
+  // initialized after parse()
+  isParsed;
+  pagePath;
+  options;
+
+  parse() {
+    if (this.isParsed) {
+      return;
+    }
+
+    // initialize
+    let lsxPrefix = this.lsxArgs || this.fromPagePath;
+    this.options = {};
+
+    // if args is a String like 'key1=value1, key2=value2, ...'
+    const splittedArgs = this.lsxArgs.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;
+        this.options[match[1]] = value;
+      });
+
+      // determine prefix
+      // 'prefix=foo' pattern or the first argument
+      lsxPrefix = this.options.prefix || splittedArgs[0];
+    }
+
+    // resolve url
+    this.pagePath = url.resolve(this.fromPagePath, lsxPrefix);
+
+    this.isParsed = true;
+  }
 }