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

Merge pull request #1118 from weseek/master

release v3.5.3
Yuki Takei 6 лет назад
Родитель
Сommit
b4733666ef
37 измененных файлов с 129 добавлено и 120 удалено
  1. 0 4
      .eslintrc.js
  2. 10 1
      CHANGES.md
  3. 1 0
      README.md
  4. 1 1
      package.json
  5. 0 2
      src/client/js/app.jsx
  6. 0 0
      src/client/js/components/Admin/CustomCssEditor.jsx
  7. 0 0
      src/client/js/components/Admin/CustomHeaderEditor.jsx
  8. 0 0
      src/client/js/components/Admin/CustomScriptEditor.jsx
  9. 0 0
      src/client/js/components/Page/PagePath.jsx
  10. 0 0
      src/client/js/components/Page/RevisionBody.jsx
  11. 12 18
      src/client/js/components/Page/RevisionLoader.jsx
  12. 0 0
      src/client/js/components/PageAttachment/Attachment.jsx
  13. 0 0
      src/client/js/components/PageAttachment/DeleteAttachmentModal.jsx
  14. 0 0
      src/client/js/components/PageAttachment/PageAttachmentList.jsx
  15. 0 0
      src/client/js/components/PageEditor/MarkdownTableUtil.js
  16. 0 0
      src/client/js/components/PageList/ListView.jsx
  17. 9 1
      src/client/js/components/PageList/Page.jsx
  18. 0 0
      src/client/js/components/PageList/PageListMeta.jsx
  19. 0 0
      src/client/js/components/PageList/PagePath.jsx
  20. 0 0
      src/client/js/components/SearchForm.jsx
  21. 0 0
      src/client/js/components/SearchPage.jsx
  22. 0 0
      src/client/js/components/SearchPage/DeletePageListModal.jsx
  23. 0 0
      src/client/js/components/SearchPage/SearchPageForm.jsx
  24. 0 0
      src/client/js/components/SearchPage/SearchResult.jsx
  25. 2 2
      src/client/js/components/SearchPage/SearchResultList.jsx
  26. 0 0
      src/client/js/components/SearchTypeahead.jsx
  27. 21 21
      src/client/js/legacy/crowi.js
  28. 4 2
      src/client/js/models/MarkdownTable.js
  29. 8 5
      src/client/styles/scss/_on-edit.scss
  30. 6 4
      src/client/styles/scss/_search.scss
  31. 5 1
      src/server/models/page-tag-relation.js
  32. 6 6
      src/server/models/page.js
  33. 21 37
      src/server/models/user.js
  34. 13 10
      src/server/routes/admin.js
  35. 8 3
      src/server/routes/page.js
  36. 1 1
      src/server/views/layout/layout.html
  37. 1 1
      src/server/views/search.html

+ 0 - 4
.eslintrc.js

@@ -30,10 +30,6 @@ module.exports = {
         FunctionExpression: { body: 1, parameters: 2 },
         FunctionExpression: { body: 1, parameters: 2 },
       },
       },
     ],
     ],
-    'react/jsx-filename-extension': [
-      'warn',
-      { extensions: ['.jsx']},
-    ],
     // eslint-plugin-import rules
     // eslint-plugin-import rules
     'import/no-unresolved': [2, { ignore: ['^@'] }], // ignore @alias/..., @commons/..., ...
     'import/no-unresolved': [2, { ignore: ['^@'] }], // ignore @alias/..., @commons/..., ...
   },
   },

+ 10 - 1
CHANGES.md

@@ -1,9 +1,18 @@
 # CHANGES
 # CHANGES
 
 
-## 3.5.2-RC
+## 3.5.3-RC
+
+* Improvement: Calculate string width when save with Spreadsheet like GUI (Handsontable)
+* Fix: Search Result Page doesn't work
+* Fix: Create/Update page API returns data includes author's password hash
+* Fix: Dropdown to copy page path/URL/MarkdownLink shows under CodeMirror vscrollbar
+* Fix: Link to /trash in Dropdown menu
+
+## 3.5.2
 
 
 * Feature: Remain metadata option when Move/Rename page
 * Feature: Remain metadata option when Move/Rename page
 * Improvement: Support code highlight for Swift and Kotlin
 * Improvement: Support code highlight for Swift and Kotlin
+* Fix: Couldn't restrict page with user group permission
 * Fix: Couldn't duplicate a page when it restricted by a user group permission
 * Fix: Couldn't duplicate a page when it restricted by a user group permission
 * Fix: Consider timezone on admin page
 * Fix: Consider timezone on admin page
 * Fix: Editor doesn't work on Microsoft Edge
 * Fix: Editor doesn't work on Microsoft Edge

+ 1 - 0
README.md

@@ -176,6 +176,7 @@ Environment Variables
       * `public`  : Forces all pages to become public
       * `public`  : Forces all pages to become public
       * `private` : Forces all pages to become private
       * `private` : Forces all pages to become private
       * undefined : Publicity will be configured by the admin security page settings
       * undefined : Publicity will be configured by the admin security page settings
+    * FORMAT_NODE_LOG: If `false`, Output server log as JSON. defautl: `true` (Enabled only when `NODE_ENV=production`)
 * **Option to integrate with external systems**
 * **Option to integrate with external systems**
     * HACKMD_URI: URI to connect to [HackMD(CodiMD)](https://hackmd.io/) server.
     * HACKMD_URI: URI to connect to [HackMD(CodiMD)](https://hackmd.io/) server.
         * **This server must load the GROWI agent. [Here's how to prepare it](https://docs.growi.org/guide/admin-cookbook/integrate-with-hackmd.html).**
         * **This server must load the GROWI agent. [Here's how to prepare it](https://docs.growi.org/guide/admin-cookbook/integrate-with-hackmd.html).**

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "growi",
   "name": "growi",
-  "version": "3.5.2-RC",
+  "version": "3.5.3-RC",
   "description": "Team collaboration software using markdown",
   "description": "Team collaboration software using markdown",
   "tags": [
   "tags": [
     "wiki",
     "wiki",

+ 0 - 2
src/client/js/app.js → src/client/js/app.jsx

@@ -1,5 +1,3 @@
-/* eslint-disable max-len */
-
 import React from 'react';
 import React from 'react';
 import ReactDOM from 'react-dom';
 import ReactDOM from 'react-dom';
 import { Provider } from 'unstated';
 import { Provider } from 'unstated';

+ 0 - 0
src/client/js/components/Admin/CustomCssEditor.js → src/client/js/components/Admin/CustomCssEditor.jsx


+ 0 - 0
src/client/js/components/Admin/CustomHeaderEditor.js → src/client/js/components/Admin/CustomHeaderEditor.jsx


+ 0 - 0
src/client/js/components/Admin/CustomScriptEditor.js → src/client/js/components/Admin/CustomScriptEditor.jsx


+ 0 - 0
src/client/js/components/Page/PagePath.js → src/client/js/components/Page/PagePath.jsx


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


+ 12 - 18
src/client/js/components/Page/RevisionLoader.jsx

@@ -35,7 +35,7 @@ class RevisionLoader extends React.Component {
     }
     }
   }
   }
 
 
-  loadData() {
+  async loadData() {
     if (!this.state.isLoaded && !this.state.isLoading) {
     if (!this.state.isLoaded && !this.state.isLoading) {
       this.setState({ isLoading: true });
       this.setState({ isLoading: true });
     }
     }
@@ -46,23 +46,17 @@ class RevisionLoader extends React.Component {
     };
     };
 
 
     // load data with REST API
     // load data with REST API
-    this.props.appContainer.apiGet('/revisions.get', requestData)
-      .then((res) => {
-        if (!res.ok) {
-          throw new Error(res.error);
-        }
-
-        this.setState({
-          markdown: res.revision.body,
-          error: null,
-        });
-      })
-      .catch((err) => {
-        this.setState({ error: err });
-      })
-      .finally(() => {
-        this.setState({ isLoaded: true, isLoading: false });
-      });
+    const res = await this.props.appContainer.apiGet('/revisions.get', requestData);
+    this.setState({ isLoaded: true, isLoading: false });
+
+    if (res != null && !res.ok) {
+      throw new Error(res.error);
+    }
+
+    this.setState({
+      markdown: res.revision.body,
+      error: null,
+    });
   }
   }
 
 
   onWaypointChange(event) {
   onWaypointChange(event) {

+ 0 - 0
src/client/js/components/PageAttachment/Attachment.js → src/client/js/components/PageAttachment/Attachment.jsx


+ 0 - 0
src/client/js/components/PageAttachment/DeleteAttachmentModal.js → src/client/js/components/PageAttachment/DeleteAttachmentModal.jsx


+ 0 - 0
src/client/js/components/PageAttachment/PageAttachmentList.js → src/client/js/components/PageAttachment/PageAttachmentList.jsx


+ 0 - 0
src/client/js/components/PageEditor/MarkdownTableUtil.jsx → src/client/js/components/PageEditor/MarkdownTableUtil.js


+ 0 - 0
src/client/js/components/PageList/ListView.js → src/client/js/components/PageList/ListView.jsx


+ 9 - 1
src/client/js/components/PageList/Page.js → src/client/js/components/PageList/Page.jsx

@@ -18,6 +18,8 @@ export default class Page extends React.Component {
       flex: 1,
       flex: 1,
     };
     };
 
 
+    const hasChildren = this.props.children != null;
+
     return (
     return (
       <li className="page-list-li d-flex align-items-center">
       <li className="page-list-li d-flex align-items-center">
         <UserPicture user={page.lastUpdateUser} />
         <UserPicture user={page.lastUpdateUser} />
@@ -25,7 +27,12 @@ export default class Page extends React.Component {
           <PagePath page={page} excludePathString={this.props.excludePathString} />
           <PagePath page={page} excludePathString={this.props.excludePathString} />
         </a>
         </a>
         <PageListMeta page={page} />
         <PageListMeta page={page} />
-        <div style={styleFlex}></div>
+        { hasChildren && (
+          <React.Fragment>
+            <a style={styleFlex} href={link}>&nbsp;</a>
+            {this.props.children}
+          </React.Fragment>
+        ) }
       </li>
       </li>
     );
     );
   }
   }
@@ -36,6 +43,7 @@ Page.propTypes = {
   page: PropTypes.object.isRequired,
   page: PropTypes.object.isRequired,
   linkTo: PropTypes.string,
   linkTo: PropTypes.string,
   excludePathString: PropTypes.string,
   excludePathString: PropTypes.string,
+  children: PropTypes.array,
 };
 };
 
 
 Page.defaultProps = {
 Page.defaultProps = {

+ 0 - 0
src/client/js/components/PageList/PageListMeta.js → src/client/js/components/PageList/PageListMeta.jsx


+ 0 - 0
src/client/js/components/PageList/PagePath.js → src/client/js/components/PageList/PagePath.jsx


+ 0 - 0
src/client/js/components/SearchForm.js → src/client/js/components/SearchForm.jsx


+ 0 - 0
src/client/js/components/SearchPage.js → src/client/js/components/SearchPage.jsx


+ 0 - 0
src/client/js/components/SearchPage/DeletePageListModal.js → src/client/js/components/SearchPage/DeletePageListModal.jsx


+ 0 - 0
src/client/js/components/SearchPage/SearchPageForm.js → src/client/js/components/SearchPage/SearchPageForm.jsx


+ 0 - 0
src/client/js/components/SearchPage/SearchResult.js → src/client/js/components/SearchPage/SearchResult.jsx


+ 2 - 2
src/client/js/components/SearchPage/SearchResultList.js → src/client/js/components/SearchPage/SearchResultList.jsx

@@ -16,8 +16,8 @@ class SearchResultList extends React.Component {
   render() {
   render() {
     const resultList = this.props.pages.map((page) => {
     const resultList = this.props.pages.map((page) => {
       return (
       return (
-        <div id={page._id} key={page._id} className="search-result-page">
-          <h2 className="inline"><a href={page.path}>{page.path}</a></h2>
+        <div id={page._id} key={page._id} className="search-result-page mb-5">
+          <h2><a href={page.path}>{page.path}</a></h2>
           { page.tags.length > 0 && (
           { page.tags.length > 0 && (
             <span><i className="tag-icon icon-tag"></i> {page.tags.join(', ')}</span>
             <span><i className="tag-icon icon-tag"></i> {page.tags.join(', ')}</span>
           )}
           )}

+ 0 - 0
src/client/js/components/SearchTypeahead.js → src/client/js/components/SearchTypeahead.jsx


+ 21 - 21
src/client/js/legacy/crowi.js

@@ -1,4 +1,4 @@
-/* eslint no-restricted-globals: ['error', 'locaion'] */
+/* eslint-disable react/jsx-filename-extension */
 
 
 import React from 'react';
 import React from 'react';
 import ReactDOM from 'react-dom';
 import ReactDOM from 'react-dom';
@@ -340,7 +340,7 @@ $(() => {
     if (input2 === '') {
     if (input2 === '') {
       prefix2 = prefix2.slice(0, -1);
       prefix2 = prefix2.slice(0, -1);
     }
     }
-    top.location.href = `${prefix1 + input1 + prefix2 + input2}#edit`;
+    window.location.href = `${prefix1 + input1 + prefix2 + input2}#edit`;
     return false;
     return false;
   });
   });
 
 
@@ -352,7 +352,7 @@ $(() => {
     if (name.match(/.+\/$/)) {
     if (name.match(/.+\/$/)) {
       name = name.substr(0, name.length - 1);
       name = name.substr(0, name.length - 1);
     }
     }
-    top.location.href = `${pathUtils.encodePagePath(name)}#edit`;
+    window.location.href = `${pathUtils.encodePagePath(name)}#edit`;
     return false;
     return false;
   });
   });
 
 
@@ -387,7 +387,7 @@ $(() => {
         }
         }
         else {
         else {
           const page = res.page;
           const page = res.page;
-          top.location.href = `${page.path}?renamed=${pagePath}`;
+          window.location.href = `${page.path}?renamed=${pagePath}`;
         }
         }
       });
       });
 
 
@@ -424,7 +424,7 @@ $(() => {
       }
       }
       else {
       else {
         const page = res.page;
         const page = res.page;
-        top.location.href = `${page.path}?duplicated=${pagePath}`;
+        window.location.href = `${page.path}?duplicated=${pagePath}`;
       }
       }
     });
     });
 
 
@@ -456,7 +456,7 @@ $(() => {
       }
       }
       else {
       else {
         const page = res.page;
         const page = res.page;
-        top.location.href = page.path;
+        window.location.href = page.path;
       }
       }
     });
     });
 
 
@@ -481,7 +481,7 @@ $(() => {
       }
       }
       else {
       else {
         const page = res.page;
         const page = res.page;
-        top.location.href = page.path;
+        window.location.href = page.path;
       }
       }
     });
     });
 
 
@@ -500,7 +500,7 @@ $(() => {
           $('#delete-errors').addClass('alert-danger');
           $('#delete-errors').addClass('alert-danger');
         }
         }
         else {
         else {
-          top.location.href = `${res.path}?unlinked=true`;
+          window.location.href = `${res.path}?unlinked=true`;
         }
         }
       });
       });
 
 
@@ -527,7 +527,7 @@ $(() => {
     $('#edit').removeClass('active');
     $('#edit').removeClass('active');
     $('body').removeClass('on-edit');
     $('body').removeClass('on-edit');
     $('body').removeClass('builtin-editor');
     $('body').removeClass('builtin-editor');
-    location.hash = '#';
+    window.location.hash = '#';
   });
   });
 
 
   /*
   /*
@@ -598,7 +598,7 @@ $(() => {
 
 
       const editorContainer = appContainer.getContainer('EditorContainer');
       const editorContainer = appContainer.getContainer('EditorContainer');
       editorContainer.saveDraft(path, template);
       editorContainer.saveDraft(path, template);
-      top.location.href = `${path}#edit`;
+      window.location.href = `${path}#edit`;
     });
     });
 
 
     if (!isSeen) {
     if (!isSeen) {
@@ -675,7 +675,7 @@ $(() => {
     $('a[data-toggle="tab"][href="#revision-body"]').on('show.bs.tab', () => {
     $('a[data-toggle="tab"][href="#revision-body"]').on('show.bs.tab', () => {
       // couln't solve https://github.com/weseek/crowi-plus/issues/119 completely -- 2017.07.03 Yuki Takei
       // couln't solve https://github.com/weseek/crowi-plus/issues/119 completely -- 2017.07.03 Yuki Takei
       window.location.hash = '#';
       window.location.hash = '#';
-      window.history.replaceState('', '', location.href);
+      window.history.replaceState('', '', window.location.href);
     });
     });
   }
   }
   else {
   else {
@@ -689,7 +689,7 @@ $(() => {
       window.history.replaceState('', 'HackMD', '#hackmd');
       window.history.replaceState('', 'HackMD', '#hackmd');
     });
     });
     $('a[data-toggle="tab"][href="#revision-body"]').on('show.bs.tab', () => {
     $('a[data-toggle="tab"][href="#revision-body"]').on('show.bs.tab', () => {
-      window.history.replaceState('', '', location.href.replace(location.hash, ''));
+      window.history.replaceState('', '', window.location.href.replace(window.location.hash, ''));
     });
     });
     // replace all href="#edit" link behaviors
     // replace all href="#edit" link behaviors
     $(document).on('click', 'a[href="#edit"]', () => {
     $(document).on('click', 'a[href="#edit"]', () => {
@@ -707,8 +707,8 @@ window.addEventListener('load', (e) => {
   const { appContainer } = window;
   const { appContainer } = window;
 
 
   // hash on page
   // hash on page
-  if (location.hash) {
-    if ((location.hash === '#edit' || location.hash === '#edit-form') && $('.tab-pane#edit').length > 0) {
+  if (window.location.hash) {
+    if ((window.location.hash === '#edit' || window.location.hash === '#edit-form') && $('.tab-pane#edit').length > 0) {
       appContainer.setState({ editorMode: 'builtin' });
       appContainer.setState({ editorMode: 'builtin' });
 
 
       $('a[data-toggle="tab"][href="#edit"]').tab('show');
       $('a[data-toggle="tab"][href="#edit"]').tab('show');
@@ -718,14 +718,14 @@ window.addEventListener('load', (e) => {
       // focus
       // focus
       Crowi.setCaretLineAndFocusToEditor();
       Crowi.setCaretLineAndFocusToEditor();
     }
     }
-    else if (location.hash === '#hackmd' && $('.tab-pane#hackmd').length > 0) {
+    else if (window.location.hash === '#hackmd' && $('.tab-pane#hackmd').length > 0) {
       appContainer.setState({ editorMode: 'hackmd' });
       appContainer.setState({ editorMode: 'hackmd' });
 
 
       $('a[data-toggle="tab"][href="#hackmd"]').tab('show');
       $('a[data-toggle="tab"][href="#hackmd"]').tab('show');
       $('body').addClass('on-edit');
       $('body').addClass('on-edit');
       $('body').addClass('hackmd');
       $('body').addClass('hackmd');
     }
     }
-    else if (location.hash === '#revision-history' && $('.tab-pane#revision-history').length > 0) {
+    else if (window.location.hash === '#revision-history' && $('.tab-pane#revision-history').length > 0) {
       $('a[data-toggle="tab"][href="#revision-history"]').tab('show');
       $('a[data-toggle="tab"][href="#revision-history"]').tab('show');
     }
     }
   }
   }
@@ -767,7 +767,7 @@ window.addEventListener('load', (e) => {
     });
     });
   }
   }
 
 
-  Crowi.highlightSelectedSection(location.hash);
+  Crowi.highlightSelectedSection(window.location.hash);
   Crowi.modifyScrollTop();
   Crowi.modifyScrollTop();
   Crowi.initSlimScrollForRevisionToc();
   Crowi.initSlimScrollForRevisionToc();
   Crowi.initAffix();
   Crowi.initAffix();
@@ -780,14 +780,14 @@ window.addEventListener('hashchange', (e) => {
   Crowi.modifyScrollTop();
   Crowi.modifyScrollTop();
 
 
   // hash on page
   // hash on page
-  if (location.hash) {
-    if (location.hash === '#edit') {
+  if (window.location.hash) {
+    if (window.location.hash === '#edit') {
       $('a[data-toggle="tab"][href="#edit"]').tab('show');
       $('a[data-toggle="tab"][href="#edit"]').tab('show');
     }
     }
-    else if (location.hash === '#hackmd') {
+    else if (window.location.hash === '#hackmd') {
       $('a[data-toggle="tab"][href="#hackmd"]').tab('show');
       $('a[data-toggle="tab"][href="#hackmd"]').tab('show');
     }
     }
-    else if (location.hash === '#revision-history') {
+    else if (window.location.hash === '#revision-history') {
       $('a[data-toggle="tab"][href="#revision-history"]').tab('show');
       $('a[data-toggle="tab"][href="#revision-history"]').tab('show');
     }
     }
   }
   }

+ 4 - 2
src/client/js/models/MarkdownTable.js

@@ -11,6 +11,8 @@ const linePartOfTableRE = /^\|[^\r\n]*|[^\r\n]*\|$|([^|\r\n]+\|[^|\r\n]*)+/; //
 // set up DOMParser
 // set up DOMParser
 const domParser = new (window.DOMParser)();
 const domParser = new (window.DOMParser)();
 
 
+const defaultOptions = { stringLength: stringWidth };
+
 /**
 /**
  * markdown table class for markdown-table module
  * markdown table class for markdown-table module
  *   ref. https://github.com/wooorm/markdown-table
  *   ref. https://github.com/wooorm/markdown-table
@@ -19,7 +21,7 @@ export default class MarkdownTable {
 
 
   constructor(table, options) {
   constructor(table, options) {
     this.table = table || [];
     this.table = table || [];
-    this.options = options || {};
+    this.options = Object.assign(options || {}, defaultOptions);
 
 
     this.toString = this.toString.bind(this);
     this.toString = this.toString.bind(this);
   }
   }
@@ -139,7 +141,7 @@ export default class MarkdownTable {
         contents.push(row);
         contents.push(row);
       }
       }
     }
     }
-    return (new MarkdownTable(contents, { align: aligns, stringLength: stringWidth }));
+    return (new MarkdownTable(contents, { align: aligns }));
   }
   }
 
 
 }
 }

+ 8 - 5
src/client/styles/scss/_on-edit.scss

@@ -90,7 +90,7 @@ body.on-edit {
 
 
     position: absolute;
     position: absolute;
     left: $left-margin;
     left: $left-margin;
-    z-index: 1;
+    z-index: 7; // forward than .CodeMirror-vscrollbar
     width: calc(100% - #{$left-margin} - #{$right-margin});
     width: calc(100% - #{$left-margin} - #{$right-margin});
     padding-top: 3px;
     padding-top: 3px;
     pointer-events: none; // disable pointer-events because it becomes an obstacle
     pointer-events: none; // disable pointer-events because it becomes an obstacle
@@ -184,13 +184,16 @@ body.on-edit {
       .autoformat-markdown-table-activated .CodeMirror-cursor {
       .autoformat-markdown-table-activated .CodeMirror-cursor {
         &:after {
         &:after {
           position: relative;
           position: relative;
-          top: -16px;
-          left: 5px;
+          top: -1.1em;
+          left: 0.3em;
           display: block;
           display: block;
-          width: 14px;
-          height: 14px;
+          width: 1em;
+          height: 1em;
           content: ' ';
           content: ' ';
+
           background-image: url(/images/icons/editor/table.svg);
           background-image: url(/images/icons/editor/table.svg);
+          background-repeat: no-repeat;
+          background-size: 1em;
         }
         }
       }
       }
 
 

+ 6 - 4
src/client/styles/scss/_search.scss

@@ -148,7 +148,7 @@
       padding-right: 0;
       padding-right: 0;
 
 
       &.affix {
       &.affix {
-        top: 58px;
+        top: 64px;
         width: 33%;
         width: 33%;
         height: 100%;
         height: 100%;
         padding-right: 5px;
         padding-right: 5px;
@@ -188,9 +188,8 @@
       margin-top: -48px;
       margin-top: -48px;
 
 
       > h2 {
       > h2 {
-        display: inline;
         margin-right: 10px;
         margin-right: 10px;
-        font-size: 20px;
+        font-size: 22px;
         line-height: 1em;
         line-height: 1em;
       }
       }
 
 
@@ -212,7 +211,10 @@
   position: sticky;
   position: sticky;
   top: 0;
   top: 0;
   z-index: 99;
   z-index: 99;
-  padding: 10px 0;
+
+  // for sticky layout
+  padding-top: 15px;
+  margin-bottom: 15px;
 
 
   .input-group-btn .btn {
   .input-group-btn .btn {
     height: 34px;
     height: 34px;

+ 5 - 1
src/server/models/page-tag-relation.js

@@ -38,12 +38,15 @@ class PageTagRelation {
   }
   }
 
 
   static async createTagListWithCount(option) {
   static async createTagListWithCount(option) {
+    const Tag = mongoose.model('Tag');
     const opt = option || {};
     const opt = option || {};
     const sortOpt = opt.sortOpt || {};
     const sortOpt = opt.sortOpt || {};
     const offset = opt.offset || 0;
     const offset = opt.offset || 0;
     const limit = opt.limit || 50;
     const limit = opt.limit || 50;
 
 
+    const existTagIds = await Tag.find().distinct('_id');
     const tags = await this.aggregate()
     const tags = await this.aggregate()
+      .match({ relatedTag: { $in: existTagIds } })
       .group({ _id: '$relatedTag', count: { $sum: 1 } })
       .group({ _id: '$relatedTag', count: { $sum: 1 } })
       .sort(sortOpt);
       .sort(sortOpt);
 
 
@@ -54,7 +57,8 @@ class PageTagRelation {
   }
   }
 
 
   static async listTagsByPage(pageId) {
   static async listTagsByPage(pageId) {
-    return this.find({ relatedPage: pageId }).populate('relatedTag').select('-_id relatedTag');
+    const tags = await this.find({ relatedPage: pageId }).populate('relatedTag').select('-_id relatedTag');
+    return tags.filter((tag) => { return tag.relatedTag !== null });
   }
   }
 
 
   static async listTagNamesByPage(pageId) {
   static async listTagNamesByPage(pageId) {

+ 6 - 6
src/server/models/page.js

@@ -995,9 +995,8 @@ module.exports = function(crowi) {
     let savedPage = await page.save();
     let savedPage = await page.save();
     const newRevision = Revision.prepareRevision(savedPage, body, null, user, { format });
     const newRevision = Revision.prepareRevision(savedPage, body, null, user, { format });
     const revision = await pushRevision(savedPage, newRevision, user);
     const revision = await pushRevision(savedPage, newRevision, user);
-    savedPage = await this.findByPath(revision.path)
-      .populate('revision')
-      .populate('creator');
+    savedPage = await this.findByPath(revision.path);
+    await savedPage.populateDataToShowRevision();
 
 
     if (socketClientId != null) {
     if (socketClientId != null) {
       pageEvent.emit('create', savedPage, user, socketClientId);
       pageEvent.emit('create', savedPage, user, socketClientId);
@@ -1021,9 +1020,8 @@ module.exports = function(crowi) {
     let savedPage = await pageData.save();
     let savedPage = await pageData.save();
     const newRevision = await Revision.prepareRevision(pageData, body, previousBody, user);
     const newRevision = await Revision.prepareRevision(pageData, body, previousBody, user);
     const revision = await pushRevision(savedPage, newRevision, user);
     const revision = await pushRevision(savedPage, newRevision, user);
-    savedPage = await this.findByPath(revision.path)
-      .populate('revision')
-      .populate('creator');
+    savedPage = await this.findByPath(revision.path);
+    await savedPage.populateDataToShowRevision();
 
 
     if (isSyncRevisionToHackmd) {
     if (isSyncRevisionToHackmd) {
       savedPage = await this.syncRevisionToHackmd(savedPage);
       savedPage = await this.syncRevisionToHackmd(savedPage);
@@ -1157,6 +1155,7 @@ module.exports = function(crowi) {
     const Bookmark = crowi.model('Bookmark');
     const Bookmark = crowi.model('Bookmark');
     const Attachment = crowi.model('Attachment');
     const Attachment = crowi.model('Attachment');
     const Comment = crowi.model('Comment');
     const Comment = crowi.model('Comment');
+    const PageTagRelation = crowi.model('PageTagRelation');
     const Revision = crowi.model('Revision');
     const Revision = crowi.model('Revision');
     const pageId = pageData._id;
     const pageId = pageData._id;
     const socketClientId = options.socketClientId || null;
     const socketClientId = options.socketClientId || null;
@@ -1166,6 +1165,7 @@ module.exports = function(crowi) {
     await Bookmark.removeBookmarksByPageId(pageId);
     await Bookmark.removeBookmarksByPageId(pageId);
     await Attachment.removeAttachmentsByPageId(pageId);
     await Attachment.removeAttachmentsByPageId(pageId);
     await Comment.removeCommentsByPageId(pageId);
     await Comment.removeCommentsByPageId(pageId);
+    await PageTagRelation.remove({ relatedPage: pageId });
     await Revision.removeRevisionsByPath(pageData.path);
     await Revision.removeRevisionsByPath(pageData.path);
     await this.findByIdAndRemove(pageId);
     await this.findByIdAndRemove(pageId);
     await this.removeRedirectOriginPageByPath(pageData.path);
     await this.removeRedirectOriginPageByPath(pageData.path);

+ 21 - 37
src/server/models/user.js

@@ -65,6 +65,18 @@ module.exports = function(crowi) {
     createdAt: { type: Date, default: Date.now },
     createdAt: { type: Date, default: Date.now },
     lastLoginAt: { type: Date },
     lastLoginAt: { type: Date },
     admin: { type: Boolean, default: 0, index: true },
     admin: { type: Boolean, default: 0, index: true },
+  }, {
+    toObject: {
+      transform: (doc, ret, opt) => {
+        // omit password
+        delete ret.password;
+        // omit email
+        if (!doc.isEmailPublished) {
+          delete ret.email;
+        }
+        return ret;
+      },
+    },
   });
   });
   userSchema.plugin(mongoosePaginate);
   userSchema.plugin(mongoosePaginate);
   userSchema.plugin(uniqueValidator);
   userSchema.plugin(uniqueValidator);
@@ -374,24 +386,6 @@ module.exports = function(crowi) {
     return true;
     return true;
   };
   };
 
 
-  userSchema.statics.filterToPublicFields = function(user) {
-    debug('User is', typeof user, user);
-    if (typeof user !== 'object' || !user._id) {
-      return user;
-    }
-
-    const filteredUser = {};
-    const fields = USER_PUBLIC_FIELDS.split(' ');
-    for (let i = 0; i < fields.length; i++) {
-      const key = fields[i];
-      if (user[key]) {
-        filteredUser[key] = user[key];
-      }
-    }
-
-    return filteredUser;
-  };
-
   userSchema.statics.findUsers = function(options, callback) {
   userSchema.statics.findUsers = function(options, callback) {
     const sort = options.sort || { status: 1, createdAt: 1 };
     const sort = options.sort || { status: 1, createdAt: 1 };
 
 
@@ -607,28 +601,18 @@ module.exports = function(crowi) {
     });
     });
   };
   };
 
 
-  userSchema.statics.resetPasswordByRandomString = function(id) {
-    const User = this;
+  userSchema.statics.resetPasswordByRandomString = async function(id) {
+    const user = await this.findById(id);
 
 
-    return new Promise(((resolve, reject) => {
-      User.findById(id, (err, userData) => {
-        if (!userData) {
-          return reject(new Error('User not found'));
-        }
+    if (!user) {
+      throw new Error('User not found');
+    }
 
 
-        // is updatable check
-        // if (userData.isUp
-        const newPassword = generateRandomTempPassword();
-        userData.setPassword(newPassword);
-        userData.save((err, userData) => {
-          if (err) {
-            return reject(err);
-          }
+    const newPassword = generateRandomTempPassword();
+    user.setPassword(newPassword);
+    await user.save();
 
 
-          resolve({ user: userData, newPassword });
-        });
-      });
-    }));
+    return newPassword;
   };
   };
 
 
   userSchema.statics.createUsersByInvitation = function(emailList, toSendEmail, callback) {
   userSchema.statics.createUsersByInvitation = function(emailList, toSendEmail, callback) {

+ 13 - 10
src/server/routes/admin.js

@@ -577,19 +577,22 @@ module.exports = function(crowi, app) {
   };
   };
 
 
   // app.post('/_api/admin/users.resetPassword' , admin.api.usersResetPassword);
   // app.post('/_api/admin/users.resetPassword' , admin.api.usersResetPassword);
-  actions.user.resetPassword = function(req, res) {
+  actions.user.resetPassword = async function(req, res) {
     const id = req.body.user_id;
     const id = req.body.user_id;
     const User = crowi.model('User');
     const User = crowi.model('User');
 
 
-    User.resetPasswordByRandomString(id)
-      .then((data) => {
-        data.user = User.filterToPublicFields(data.user);
-        return res.json(ApiResponse.success(data));
-      })
-      .catch((err) => {
-        debug('Error on reseting password', err);
-        return res.json(ApiResponse.error('Error'));
-      });
+    try {
+      const newPassword = await User.resetPasswordByRandomString(id);
+
+      const user = await User.findById(id);
+
+      const result = { user: user.toObject(), newPassword };
+      return res.json(ApiResponse.success(result));
+    }
+    catch (err) {
+      debug('Error on reseting password', err);
+      return res.json(ApiResponse.error(err));
+    }
   };
   };
 
 
   actions.externalAccount = {};
   actions.externalAccount = {};

+ 8 - 3
src/server/routes/page.js

@@ -47,6 +47,14 @@ module.exports = function(crowi, app) {
     if (page.revisionHackmdSynced != null && page.revisionHackmdSynced._id != null) {
     if (page.revisionHackmdSynced != null && page.revisionHackmdSynced._id != null) {
       returnObj.revisionHackmdSynced = page.revisionHackmdSynced._id;
       returnObj.revisionHackmdSynced = page.revisionHackmdSynced._id;
     }
     }
+
+    if (page.lastUpdateUser != null && page.lastUpdateUser instanceof User) {
+      returnObj.lastUpdateUser = page.lastUpdateUser.toObject();
+    }
+    if (page.creator != null && page.creator instanceof User) {
+      returnObj.creator = page.creator.toObject();
+    }
+
     return returnObj;
     return returnObj;
   }
   }
 
 
@@ -585,8 +593,6 @@ module.exports = function(crowi, app) {
     }
     }
 
 
     const result = { page: serializeToObj(createdPage), tags: savedTags };
     const result = { page: serializeToObj(createdPage), tags: savedTags };
-    result.page.lastUpdateUser = User.filterToPublicFields(createdPage.lastUpdateUser);
-    result.page.creator = User.filterToPublicFields(createdPage.creator);
     res.json(ApiResponse.success(result));
     res.json(ApiResponse.success(result));
 
 
     // update scopes for descendants
     // update scopes for descendants
@@ -674,7 +680,6 @@ module.exports = function(crowi, app) {
     }
     }
 
 
     const result = { page: serializeToObj(page), tags: savedTags };
     const result = { page: serializeToObj(page), tags: savedTags };
-    result.page.lastUpdateUser = User.filterToPublicFields(page.lastUpdateUser);
     res.json(ApiResponse.success(result));
     res.json(ApiResponse.success(result));
 
 
     // update scopes for descendants
     // update scopes for descendants

+ 1 - 1
src/server/views/layout/layout.html

@@ -145,7 +145,7 @@
             <li><a href="/me"><i class="icon-fw icon-wrench"></i>{{ t('User Settings') }}</a></li>
             <li><a href="/me"><i class="icon-fw icon-wrench"></i>{{ t('User Settings') }}</a></li>
             <li role="separator" class="divider"></li>
             <li role="separator" class="divider"></li>
             <li><a href="/user/{{ user.username }}#user-draft-list"><i class="icon-fw icon-docs"></i>{{ t('List Drafts') }}</a></li>
             <li><a href="/user/{{ user.username }}#user-draft-list"><i class="icon-fw icon-docs"></i>{{ t('List Drafts') }}</a></li>
-            <li><a href="/user"><i class="icon-fw icon-trash"></i>{{ t('Deleted Pages') }}</a></li>
+            <li><a href="/trash"><i class="icon-fw icon-trash"></i>{{ t('Deleted Pages') }}</a></li>
             <li role="separator" class="divider"></li>
             <li role="separator" class="divider"></li>
             <li><a href="/logout"><i class="icon-fw icon-power"></i>{{ t('Sign out') }}</a></li>
             <li><a href="/logout"><i class="icon-fw icon-power"></i>{{ t('Sign out') }}</a></li>
           </ul>
           </ul>

+ 1 - 1
src/server/views/search.html

@@ -14,7 +14,7 @@
 <div class="container-fluid">
 <div class="container-fluid">
 
 
   <div class="row">
   <div class="row">
-    <div id="main" class="main m-t-15 col-md-12 search-page">
+    <div id="main" class="main col-md-12 search-page">
       <div class="" id="search-page"></div>
       <div class="" id="search-page"></div>
     </div>
     </div>
   </div>
   </div>