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

Merge branch 'master' into feat/sync-config-cache

Yuki Takei 5 лет назад
Родитель
Сommit
91fb302105

+ 1 - 1
.devcontainer/Dockerfile

@@ -3,7 +3,7 @@
 # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
 # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
 #-------------------------------------------------------------------------------------------------------------
 #-------------------------------------------------------------------------------------------------------------
 
 
-FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-12
+FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:14
 
 
 # The node image includes a non-root user with sudo access. Use the
 # The node image includes a non-root user with sudo access. Use the
 # "remoteUser" property in devcontainer.json to use it. On Linux, update
 # "remoteUser" property in devcontainer.json to use it. On Linux, update

+ 4 - 4
.github/workflows/ci.yml

@@ -13,7 +13,7 @@ jobs:
 
 
     strategy:
     strategy:
       matrix:
       matrix:
-        node-version: [12.x]
+        node-version: [14.x]
 
 
     steps:
     steps:
     - uses: actions/checkout@v2
     - uses: actions/checkout@v2
@@ -68,7 +68,7 @@ jobs:
 
 
     strategy:
     strategy:
       matrix:
       matrix:
-        node-version: [12.x]
+        node-version: [14.x]
 
 
     steps:
     steps:
     - uses: actions/checkout@v2
     - uses: actions/checkout@v2
@@ -129,7 +129,7 @@ jobs:
 
 
     strategy:
     strategy:
       matrix:
       matrix:
-        node-version: [12.x]
+        node-version: [14.x]
 
 
     steps:
     steps:
     - uses: actions/checkout@v2
     - uses: actions/checkout@v2
@@ -200,7 +200,7 @@ jobs:
 
 
     strategy:
     strategy:
       matrix:
       matrix:
-        node-version: [10.x, 12.x]
+        node-version: [12.x, 14.x]
 
 
     steps:
     steps:
     - uses: actions/checkout@v2
     - uses: actions/checkout@v2

+ 19 - 2
CHANGES.md

@@ -1,8 +1,25 @@
 # CHANGES
 # CHANGES
 
 
-## v4.0.10-RC
+## v4.1.0-RC
+
+### BREAKING CHANGES
+
+* GROWI v4.1.x no longer support Node.js v10.x
+* GROWI v4.1.x no longer support growi-plugin-attachment-refs@v1
+
+Upgrading Guide: <https://docs.growi.org/en/admin-guide/upgrading/41x.html>
+
+### Updates
+
+* Support: Support Node.js v14
+
+
+
+## v4.0.10
+
+* Improvement: Adjust ToC height
+* Fix: Fail to rename/delete a page set as "Anyone with the link"
 
 
-* 
 
 
 ## v4.0.9
 ## v4.0.9
 
 

+ 1 - 1
README.md

@@ -92,7 +92,7 @@ Development
 
 
 ## Dependencies
 ## Dependencies
 
 
-- Node.js v12.x (DON'T USE 13.x)
+- Node.js v12.x or v14.x
 - npm 6.x
 - npm 6.x
 - yarn
 - yarn
 - MongoDB 3.x
 - MongoDB 3.x

+ 2 - 2
docker/Dockerfile

@@ -7,7 +7,7 @@ ARG flavor=default
 ##
 ##
 ## deps-resolver
 ## deps-resolver
 ##
 ##
-FROM node:12-slim AS deps-resolver
+FROM node:14-slim AS deps-resolver
 LABEL maintainer Yuki Takei <yuki@weseek.co.jp>
 LABEL maintainer Yuki Takei <yuki@weseek.co.jp>
 
 
 ENV appDir /opt/growi
 ENV appDir /opt/growi
@@ -43,7 +43,7 @@ RUN --mount=type=cache,target=/usr/local/share/.cache/yarn \
 ##
 ##
 ## prebuilder-default
 ## prebuilder-default
 ##
 ##
-FROM node:12-slim AS prebuilder-default
+FROM node:14-slim AS prebuilder-default
 LABEL maintainer Yuki Takei <yuki@weseek.co.jp>
 LABEL maintainer Yuki Takei <yuki@weseek.co.jp>
 
 
 ENV appDir /opt/growi
 ENV appDir /opt/growi

+ 3 - 3
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "growi",
   "name": "growi",
-  "version": "4.0.10-RC",
+  "version": "4.1.0-RC",
   "description": "Team collaboration software using markdown",
   "description": "Team collaboration software using markdown",
   "tags": [
   "tags": [
     "wiki",
     "wiki",
@@ -103,7 +103,7 @@
     "express-validator": "^6.1.1",
     "express-validator": "^6.1.1",
     "express-webpack-assets": "^0.1.0",
     "express-webpack-assets": "^0.1.0",
     "graceful-fs": "^4.1.11",
     "graceful-fs": "^4.1.11",
-    "growi-commons": "^4.0.8",
+    "growi-commons": "^5.0.3",
     "helmet": "^3.13.0",
     "helmet": "^3.13.0",
     "i18next": "^19.0.0",
     "i18next": "^19.0.0",
     "i18next-express-middleware": "^1.4.1",
     "i18next-express-middleware": "^1.4.1",
@@ -265,7 +265,7 @@
     "debug": "src/lib/service/logger/alias-for-debug"
     "debug": "src/lib/service/logger/alias-for-debug"
   },
   },
   "engines": {
   "engines": {
-    "node": ">=10.17.0 <13",
+    "node": "^12 || ^14",
     "npm": ">=6.11.3 <7",
     "npm": ">=6.11.3 <7",
     "yarn": ">=1.19.1 <2"
     "yarn": ">=1.19.1 <2"
   }
   }

+ 1 - 3
src/client/js/components/PageManagement/ApiErrorMessage.jsx

@@ -42,12 +42,10 @@ const ApiErrorMessage = (props) => {
         return (
         return (
           <strong><i className="icon-fw icon-ban"></i> Invalid path</strong>
           <strong><i className="icon-fw icon-ban"></i> Invalid path</strong>
         );
         );
-      case 'unknown':
+      default:
         return (
         return (
           <strong><i className="icon-fw icon-ban"></i> Unknown error occured</strong>
           <strong><i className="icon-fw icon-ban"></i> Unknown error occured</strong>
         );
         );
-      default:
-        return null;
     }
     }
   }
   }
 
 

+ 16 - 13
src/client/js/components/TableOfContents.jsx

@@ -25,27 +25,30 @@ const TableOfContents = (props) => {
     const containerElem = document.querySelector('#revision-toc');
     const containerElem = document.querySelector('#revision-toc');
     const containerTop = containerElem.getBoundingClientRect().top;
     const containerTop = containerElem.getBoundingClientRect().top;
 
 
-    // window height - revisionToc top - .system-version height
-    return window.innerHeight - containerTop - 20;
+    // window height - revisionToc top - .system-version - .grw-fab-container height
+    return window.innerHeight - containerTop - 20 - 155;
   }, []);
   }, []);
 
 
   const { tocHtml } = pageContainer.state;
   const { tocHtml } = pageContainer.state;
 
 
   return (
   return (
-    <StickyStretchableScroller
-      contentsElemSelector=".revision-toc .markdownIt-TOC"
-      stickyElemSelector="#revision-toc"
-      calcViewHeightFunc={calcViewHeight}
-    >
-      <div
-        id="revision-toc-content"
-        className="revision-toc-content"
+    <>
+      {/* TODO GW-3253 add four contents */}
+      <StickyStretchableScroller
+        contentsElemSelector=".revision-toc .markdownIt-TOC"
+        stickyElemSelector="#revision-toc"
+        calcViewHeightFunc={calcViewHeight}
+      >
+        <div
+          id="revision-toc-content"
+          className="revision-toc-content"
         // eslint-disable-next-line react/no-danger
         // eslint-disable-next-line react/no-danger
-        dangerouslySetInnerHTML={{
+          dangerouslySetInnerHTML={{
           __html: tocHtml,
           __html: tocHtml,
         }}
         }}
-      />
-    </StickyStretchableScroller>
+        />
+      </StickyStretchableScroller>
+    </>
   );
   );
 
 
 };
 };

+ 2 - 1
src/client/styles/scss/_layout_growi.scss

@@ -23,7 +23,8 @@
 
 
   .revision-toc {
   .revision-toc {
     position: sticky;
     position: sticky;
-    top: calc(46px + 5px);
+    // growisubnavigation + grw-navbar-boder
+    top: calc(100px + 4px);
     min-width: 100%;
     min-width: 100%;
     margin-top: 5px;
     margin-top: 5px;
 
 

+ 33 - 22
src/server/models/page.js

@@ -693,7 +693,7 @@ module.exports = function(crowi) {
   /**
   /**
    * find pages that is match with `path` and its descendants
    * find pages that is match with `path` and its descendants
    */
    */
-  pageSchema.statics.findListWithDescendants = async function(path, user, option) {
+  pageSchema.statics.findListWithDescendants = async function(path, user, option = {}) {
     const builder = new PageQueryBuilder(this.find());
     const builder = new PageQueryBuilder(this.find());
     builder.addConditionToListWithDescendants(path, option);
     builder.addConditionToListWithDescendants(path, option);
 
 
@@ -1094,21 +1094,18 @@ module.exports = function(crowi) {
       throw new Error('This method does NOT supports deleting trashed pages.');
       throw new Error('This method does NOT supports deleting trashed pages.');
     }
     }
 
 
-    const findOpts = { includeRedirect: true };
-    const result = await this.findListWithDescendants(targetPage.path, user, findOpts);
+    // find descendants (this array does not include GRANT_RESTRICTED)
+    const result = await this.findListWithDescendants(targetPage.path, user);
     const pages = result.pages;
     const pages = result.pages;
+    // add targetPage if 'grant' is GRANT_RESTRICTED
+    //  because findListWithDescendants excludes GRANT_RESTRICTED pages
+    if (targetPage.grant === GRANT_RESTRICTED) {
+      pages.push(targetPage);
+    }
 
 
-    let updatedPage = null;
     await Promise.all(pages.map((page) => {
     await Promise.all(pages.map((page) => {
-      const isParent = (page.path === targetPage.path);
-      const p = this.deletePage(page, user, options);
-      if (isParent) {
-        updatedPage = p;
-      }
-      return p;
+      return this.deletePage(page, user, options);
     }));
     }));
-
-    return updatedPage;
   };
   };
 
 
   pageSchema.statics.revertDeletedPage = async function(page, user, options = {}) {
   pageSchema.statics.revertDeletedPage = async function(page, user, options = {}) {
@@ -1135,7 +1132,7 @@ module.exports = function(crowi) {
   };
   };
 
 
   pageSchema.statics.revertDeletedPageRecursively = async function(targetPage, user, options = {}) {
   pageSchema.statics.revertDeletedPageRecursively = async function(targetPage, user, options = {}) {
-    const findOpts = { includeRedirect: true, includeTrashed: true };
+    const findOpts = { includeTrashed: true };
     const result = await this.findListWithDescendants(targetPage.path, user, findOpts);
     const result = await this.findListWithDescendants(targetPage.path, user, findOpts);
     const pages = result.pages;
     const pages = result.pages;
 
 
@@ -1185,17 +1182,23 @@ module.exports = function(crowi) {
   /**
   /**
    * Delete Bookmarks, Attachments, Revisions, Pages and emit delete
    * Delete Bookmarks, Attachments, Revisions, Pages and emit delete
    */
    */
-  pageSchema.statics.completelyDeletePageRecursively = async function(pagePath, user, options = {}) {
+  pageSchema.statics.completelyDeletePageRecursively = async function(targetPage, user, options = {}) {
+    const pagePath = targetPage.path;
 
 
-    const findOpts = { includeRedirect: true, includeTrashed: true };
+    const findOpts = { includeTrashed: true };
+
+    // find descendants (this array does not include GRANT_RESTRICTED)
     const result = await this.findListWithDescendants(pagePath, user, findOpts);
     const result = await this.findListWithDescendants(pagePath, user, findOpts);
     const pages = result.pages;
     const pages = result.pages;
+    // add targetPage if 'grant' is GRANT_RESTRICTED
+    //  because findListWithDescendants excludes GRANT_RESTRICTED pages
+    if (targetPage.grant === GRANT_RESTRICTED) {
+      pages.push(targetPage);
+    }
 
 
     await Promise.all(pages.map((page) => {
     await Promise.all(pages.map((page) => {
       return this.completelyDeletePage(page, user, options);
       return this.completelyDeletePage(page, user, options);
     }));
     }));
-
-    return pagePath;
   };
   };
 
 
   pageSchema.statics.removeByPath = function(path) {
   pageSchema.statics.removeByPath = function(path) {
@@ -1263,22 +1266,30 @@ module.exports = function(crowi) {
     return updatedPageData;
     return updatedPageData;
   };
   };
 
 
-  pageSchema.statics.renameRecursively = async function(pageData, newPagePathPrefix, user, options) {
+  pageSchema.statics.renameRecursively = async function(targetPage, newPagePathPrefix, user, options) {
     validateCrowi();
     validateCrowi();
 
 
-    const path = pageData.path;
+    const path = targetPage.path;
     const pathRegExp = new RegExp(`^${escapeStringRegexp(path)}`, 'i');
     const pathRegExp = new RegExp(`^${escapeStringRegexp(path)}`, 'i');
 
 
     // sanitize path
     // sanitize path
     newPagePathPrefix = crowi.xss.process(newPagePathPrefix); // eslint-disable-line no-param-reassign
     newPagePathPrefix = crowi.xss.process(newPagePathPrefix); // eslint-disable-line no-param-reassign
 
 
+    // find descendants (this array does not include GRANT_RESTRICTED)
     const result = await this.findListWithDescendants(path, user, options);
     const result = await this.findListWithDescendants(path, user, options);
-    await Promise.all(result.pages.map((page) => {
+    const pages = result.pages;
+    // add targetPage if 'grant' is GRANT_RESTRICTED
+    //  because findListWithDescendants excludes GRANT_RESTRICTED pages
+    if (targetPage.grant === GRANT_RESTRICTED) {
+      pages.push(targetPage);
+    }
+
+    await Promise.all(pages.map((page) => {
       const newPagePath = page.path.replace(pathRegExp, newPagePathPrefix);
       const newPagePath = page.path.replace(pathRegExp, newPagePathPrefix);
       return this.rename(page, newPagePath, user, options);
       return this.rename(page, newPagePath, user, options);
     }));
     }));
-    pageData.path = newPagePathPrefix;
-    return pageData;
+    targetPage.path = newPagePathPrefix;
+    return targetPage;
   };
   };
 
 
   pageSchema.statics.handlePrivatePagesForDeletedGroup = async function(deletedGroup, action, transferToUserGroupId) {
   pageSchema.statics.handlePrivatePagesForDeletedGroup = async function(deletedGroup, action, transferToUserGroupId) {

+ 0 - 3
src/server/models/user.js

@@ -21,8 +21,6 @@ module.exports = function(crowi) {
   const STATUS_INVITED = 5;
   const STATUS_INVITED = 5;
   const USER_PUBLIC_FIELDS = '_id image isEmailPublished isGravatarEnabled googleId name username email introduction'
   const USER_PUBLIC_FIELDS = '_id image isEmailPublished isGravatarEnabled googleId name username email introduction'
   + 'status lang createdAt lastLoginAt admin imageUrlCached';
   + 'status lang createdAt lastLoginAt admin imageUrlCached';
-  /* eslint-disable no-unused-vars */
-  const IMAGE_POPULATION = { path: 'imageAttachment', select: 'filePathProxied' };
 
 
   const PAGE_ITEMS = 50;
   const PAGE_ITEMS = 50;
 
 
@@ -750,7 +748,6 @@ module.exports = function(crowi) {
   userSchema.statics.STATUS_DELETED = STATUS_DELETED;
   userSchema.statics.STATUS_DELETED = STATUS_DELETED;
   userSchema.statics.STATUS_INVITED = STATUS_INVITED;
   userSchema.statics.STATUS_INVITED = STATUS_INVITED;
   userSchema.statics.USER_PUBLIC_FIELDS = USER_PUBLIC_FIELDS;
   userSchema.statics.USER_PUBLIC_FIELDS = USER_PUBLIC_FIELDS;
-  userSchema.statics.IMAGE_POPULATION = IMAGE_POPULATION;
   userSchema.statics.PAGE_ITEMS = PAGE_ITEMS;
   userSchema.statics.PAGE_ITEMS = PAGE_ITEMS;
 
 
   return mongoose.model('User', userSchema);
   return mongoose.model('User', userSchema);

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

@@ -1231,7 +1231,7 @@ module.exports = function(crowi, app) {
 
 
     const options = { socketClientId };
     const options = { socketClientId };
 
 
-    let page = await Page.findByIdAndViewer(pageId, req.user);
+    const page = await Page.findByIdAndViewer(pageId, req.user);
 
 
     if (page == null) {
     if (page == null) {
       return res.json(ApiResponse.error(`Page '${pageId}' is not found or forbidden`, 'notfound_or_forbidden'));
       return res.json(ApiResponse.error(`Page '${pageId}' is not found or forbidden`, 'notfound_or_forbidden'));
@@ -1245,10 +1245,10 @@ module.exports = function(crowi, app) {
           return res.json(ApiResponse.error('You can not delete completely', 'user_not_admin'));
           return res.json(ApiResponse.error('You can not delete completely', 'user_not_admin'));
         }
         }
         if (isRecursively) {
         if (isRecursively) {
-          await Page.completelyDeletePageRecursively(page.path, req.user, options);
+          await Page.completelyDeletePageRecursively(page, req.user, options);
         }
         }
         else {
         else {
-          page = await Page.completelyDeletePage(page, req.user, options);
+          await Page.completelyDeletePage(page, req.user, options);
         }
         }
       }
       }
       else {
       else {
@@ -1257,16 +1257,16 @@ module.exports = function(crowi, app) {
         }
         }
 
 
         if (isRecursively) {
         if (isRecursively) {
-          page = await Page.deletePageRecursively(page, req.user, options);
+          await Page.deletePageRecursively(page, req.user, options);
         }
         }
         else {
         else {
-          page = await Page.deletePage(page, req.user, options);
+          await Page.deletePage(page, req.user, options);
         }
         }
       }
       }
     }
     }
     catch (err) {
     catch (err) {
       logger.error('Error occured while get setting', err);
       logger.error('Error occured while get setting', err);
-      return res.json(ApiResponse.error('Failed to delete page.', 'unknown'));
+      return res.json(ApiResponse.error('Failed to delete page.', err.message));
     }
     }
 
 
     debug('Page deleted', page.path);
     debug('Page deleted', page.path);

+ 4 - 4
yarn.lock

@@ -6770,10 +6770,10 @@ grapheme-splitter@^1.0.4:
   resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
   resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
   integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
   integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
 
 
-growi-commons@^4.0.8:
-  version "4.0.8"
-  resolved "https://registry.yarnpkg.com/growi-commons/-/growi-commons-4.0.8.tgz#3040f2759f5eb13084b101e303c11028af2a8faf"
-  integrity sha512-AL/vm3R3LqiWwCbuEHPsDP8hapiz7ulKZXyW4399WHorx/7oEkSWb6sGdkvJiqSnRWxEcdPkfw6K0kgzilMpig==
+growi-commons@^5.0.3:
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/growi-commons/-/growi-commons-5.0.3.tgz#8ec745dcb2d3d003e3acd7d6fbec85cf0967354b"
+  integrity sha512-lV2jmqxWPiuzuVaetSY1uq7viwgRCNwLMnU78GaYix4jfXNcYzdrfedTlU3ZRiVIjLggSFb87EhYqtM/4RfjmA==
 
 
 growly@^1.3.0:
 growly@^1.3.0:
   version "1.3.0"
   version "1.3.0"