Преглед изворни кода

Merge commit 'c51a22a529bfd78f51a6211da8ee0d9d9428d62f' into imprv/remove-PageTagRelation-when-complete-delete-page

yusuketk пре 6 година
родитељ
комит
0f6c172f19

+ 7 - 1
CHANGES.md

@@ -1,9 +1,15 @@
 # CHANGES
 
-## 3.5.2-RC
+## 3.5.3-RC
+
+* Fix: Search Result Page doesn't work
+* Fix: Create/Update page API returns data includes author's password hash
+
+## 3.5.2
 
 * Feature: Remain metadata option when Move/Rename page
 * Improvement: Support code highlight for Swift and Kotlin
+* Fix: Couldn't duplicate a page when it restricted by a user group permission
 * Fix: Consider timezone on admin page
 * Fix: Editor doesn't work on Microsoft Edge
 * Support: Upgrade libs

+ 1 - 0
README.md

@@ -176,6 +176,7 @@ Environment Variables
       * `public`  : Forces all pages to become public
       * `private` : Forces all pages to become private
       * 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**
     * 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).**

+ 1 - 1
package.json

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

+ 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) {
       this.setState({ isLoading: true });
     }
@@ -46,23 +46,17 @@ class RevisionLoader extends React.Component {
     };
 
     // 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) {

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


+ 1 - 1
src/client/js/components/SavePageControls/GrantSelector.jsx

@@ -277,7 +277,7 @@ class GrantSelector extends React.Component {
     return (
       <React.Fragment>
         { this.renderGrantSelector() }
-        { this.props.disabled && this.renderSelectGroupModal() }
+        { !this.props.disabled && this.renderSelectGroupModal() }
       </React.Fragment>
     );
   }

+ 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() {
     const resultList = this.props.pages.map((page) => {
       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 && (
             <span><i className="tag-icon icon-tag"></i> {page.tags.join(', ')}</span>
           )}

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

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

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

@@ -995,9 +995,8 @@ module.exports = function(crowi) {
     let savedPage = await page.save();
     const newRevision = Revision.prepareRevision(savedPage, body, null, user, { format });
     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) {
       pageEvent.emit('create', savedPage, user, socketClientId);
@@ -1021,9 +1020,8 @@ module.exports = function(crowi) {
     let savedPage = await pageData.save();
     const newRevision = await Revision.prepareRevision(pageData, body, previousBody, 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) {
       savedPage = await this.syncRevisionToHackmd(savedPage);

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

@@ -65,6 +65,18 @@ module.exports = function(crowi) {
     createdAt: { type: Date, default: Date.now },
     lastLoginAt: { type: Date },
     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(uniqueValidator);
@@ -374,24 +386,6 @@ module.exports = function(crowi) {
     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) {
     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) {

+ 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);
-  actions.user.resetPassword = function(req, res) {
+  actions.user.resetPassword = async function(req, res) {
     const id = req.body.user_id;
     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 = {};

+ 9 - 4
src/server/routes/page.js

@@ -47,6 +47,14 @@ module.exports = function(crowi, app) {
     if (page.revisionHackmdSynced != null && page.revisionHackmdSynced._id != null) {
       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;
   }
 
@@ -585,8 +593,6 @@ module.exports = function(crowi, app) {
     }
 
     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));
 
     // update scopes for descendants
@@ -674,7 +680,6 @@ module.exports = function(crowi, app) {
     }
 
     const result = { page: serializeToObj(page), tags: savedTags };
-    result.page.lastUpdateUser = User.filterToPublicFields(page.lastUpdateUser);
     res.json(ApiResponse.success(result));
 
     // update scopes for descendants
@@ -1124,7 +1129,7 @@ module.exports = function(crowi, app) {
     req.body.body = page.revision.body;
     req.body.grant = page.grant;
     req.body.grantedUsers = page.grantedUsers;
-    req.body.grantedGroup = page.grantedGroup;
+    req.body.grantUserGroupId = page.grantedGroup;
     req.body.pageTags = originTags;
 
     return api.create(req, res);

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

@@ -14,7 +14,7 @@
 <div class="container-fluid">
 
   <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>
   </div>