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

Merge branch 'master' into feat/sidebar-RecentChanges

kaho-y 4 лет назад
Родитель
Сommit
3f3fd7c5e5
70 измененных файлов с 4133 добавлено и 2798 удалено
  1. 3 0
      .github/git-pr-release-template.erb
  2. 50 0
      .github/release-drafter.yml
  3. 21 72
      .github/workflows/ci-slackbot-proxy.yml
  4. 29 112
      .github/workflows/ci.yml
  5. 62 0
      .github/workflows/draft-release.yml
  6. 46 0
      .github/workflows/pr-to-master.yml
  7. 5 7
      .github/workflows/release-rc.yml
  8. 8 8
      .github/workflows/release-slackbot-proxy.yml
  9. 104 37
      .github/workflows/release.yml
  10. 2603 0
      CHANGELOG.md
  11. 0 2009
      CHANGES.md
  12. 4 0
      bin/github-actions/bump-versions/README.md
  13. 18 0
      bin/github-actions/bump-versions/cli.js
  14. 71 0
      bin/github-actions/bump-versions/flow/bump-versions.js
  15. 16 0
      bin/github-actions/bump-versions/index.js
  16. 54 0
      bin/github-actions/bump-versions/step/printHelp.js
  17. 12 0
      bump-versions.config.js
  18. 2 2
      lerna.json
  19. 3 3
      package.json
  20. 2 2
      packages/app/bin/github-actions/update-readme.sh
  21. 6 6
      packages/app/config/migrate.js
  22. 0 2
      packages/app/docker/Dockerfile
  23. 5 5
      packages/app/docker/README.md
  24. 11 13
      packages/app/package.json
  25. 33 0
      packages/app/src/migrations/20210906194521-slack-app-integration-set-default-value.js
  26. 0 1
      packages/app/src/server/.node-dev.json
  27. 3 1
      packages/app/src/server/console.js
  28. 4 2
      packages/app/src/server/crowi/index.js
  29. 2 1
      packages/app/src/server/middlewares/admin-required.js
  30. 3 2
      packages/app/src/server/models/slack-app-integration.js
  31. 4 10
      packages/app/src/server/routes/apiv3/slack-integration-settings.js
  32. 8 4
      packages/app/src/server/service/search.js
  33. 16 16
      packages/app/src/server/service/slack-command-handler/togetter.js
  34. 21 2
      packages/app/src/server/service/slack-integration.ts
  35. 19 12
      packages/app/src/server/service/socket-io.js
  36. 11 1
      packages/app/src/server/util/mongoose-utils.ts
  37. 4 0
      packages/app/src/test/config/migrate.test.js
  38. 3 1
      packages/app/src/test/global-setup.js
  39. 17 8
      packages/app/src/test/service/page.test.js
  40. 2 1
      packages/app/src/test/setup.js
  41. 5 4
      packages/core/package.json
  42. 0 1
      packages/plugin-attachment-refs/index.js
  43. 6 5
      packages/plugin-attachment-refs/package.json
  44. 0 0
      packages/plugin-attachment-refs/src/index.js
  45. 25 10
      packages/plugin-attachment-refs/src/server/routes/refs.js
  46. 5 4
      packages/plugin-lsx/package.json
  47. 2 0
      packages/plugin-lsx/src/client-entry.js
  48. 7 2
      packages/plugin-lsx/src/client/css/index.css
  49. 76 46
      packages/plugin-lsx/src/client/js/components/Lsx.jsx
  50. 10 6
      packages/plugin-lsx/src/client/js/components/LsxPageList/LsxListView.jsx
  51. 3 1
      packages/plugin-lsx/src/client/js/components/LsxPageList/LsxPage.jsx
  52. 1 0
      packages/plugin-lsx/src/client/js/components/LsxPageList/PagePathWrapper.jsx
  53. 33 0
      packages/plugin-lsx/src/client/js/util/Interceptor/LsxLogoutInterceptor.js
  54. 12 17
      packages/plugin-lsx/src/client/js/util/Interceptor/LsxPostRenderInterceptor.js
  55. 33 62
      packages/plugin-lsx/src/client/js/util/Interceptor/LsxPreRenderInterceptor.js
  56. 0 87
      packages/plugin-lsx/src/client/js/util/LsxCacheHelper.js
  57. 23 83
      packages/plugin-lsx/src/client/js/util/LsxContext.js
  58. 39 0
      packages/plugin-lsx/src/client/js/util/TagCacheManagerFactory.js
  59. 11 1
      packages/plugin-lsx/src/index.js
  60. 0 11
      packages/plugin-lsx/src/meta.js
  61. 10 8
      packages/plugin-lsx/src/server/routes/index.js
  62. 81 15
      packages/plugin-lsx/src/server/routes/lsx.js
  63. 4 2
      packages/plugin-pukiwiki-like-linker/package.json
  64. 8 1
      packages/plugin-pukiwiki-like-linker/src/index.js
  65. 0 8
      packages/plugin-pukiwiki-like-linker/src/meta.js
  66. 1 1
      packages/slack/package.json
  67. 3 4
      packages/slackbot-proxy/package.json
  68. 5 3
      packages/slackbot-proxy/src/middlewares/slack-to-growi/join-to-conversation.ts
  69. 4 2
      packages/ui/package.json
  70. 441 74
      yarn.lock

+ 3 - 0
.github/git-pr-release-template.erb

@@ -0,0 +1,3 @@
+<%= ENV['GIT_PR_RELEASE_TITLE'] %>
+
+<%= ENV['GIT_PR_RELEASE_BODY'] %>

+ 50 - 0
.github/release-drafter.yml

@@ -0,0 +1,50 @@
+categories:
+  - title: 'BREAKING CHANGES'
+    labels:
+      - 'breaking'
+  - title: '💎 Features'
+    labels:
+      - 'feature'
+  - title: '🚀 Improvement'
+    labels:
+      - 'improvement'
+  - title: '🐛 Bug Fixes'
+    labels:
+      - 'bug'
+  - title: '🧰 Maintenance'
+    labels:
+      - 'support'
+      - 'dependencies'
+category-template: '### $TITLE'
+change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
+autolabeler:
+  - label: 'feature'
+    branch:
+      - '/^feat\/.+/'
+  - label: 'improvement'
+    branch:
+      - '/^imprv\/.+/'
+  - label: 'bug'
+    branch:
+      - '/^fix\/.+/'
+    title:
+      - '/^fix/i'
+  - label: 'support'
+    branch:
+      - '/^support\/.+/'
+    title:
+      - '/^ci/i'
+      - '/^docs/i'
+      - '/^test/i'
+  - label: 'exclude from changelog'
+    branch:
+      - '/^chore\/.+/'
+    title:
+      - '/^chore/i'
+
+exclude-labels:
+  - 'exclude from changelog'
+template: |
+  ### Changes
+
+  $CHANGES

+ 21 - 72
.github/workflows/ci-slackbot-proxy.yml

@@ -5,7 +5,8 @@ on:
     branches-ignore:
       - release/**
       - rc/**
-      - tmp/**
+      - chore/**
+      - support/prepare-v**
 
 jobs:
 
@@ -18,30 +19,14 @@ jobs:
 
     steps:
     - uses: actions/checkout@v2
-    - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v1
+
+    - uses: actions/setup-node@v2
       with:
         node-version: ${{ matrix.node-version }}
-    - name: Cache/Restore node_modules
-      id: cache-dependencies
-      uses: actions/cache@v2
-      with:
-        path: '**/node_modules'
-        key: ${{ runner.OS }}-node_modules-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
-    - name: Get yarn cache dir
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
-      id: cache-yarn
-      run: echo "::set-output name=dir::$(yarn cache dir)"
-    - name: Cache/Restore yarn cache
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
-      uses: actions/cache@v2
-      with:
-        path: ${{ steps.cache-yarn.outputs.dir }}
-        key: ${{ runner.os }}-yarn-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
-        restore-keys: |
-          ${{ runner.os }}-yarn-${{ matrix.node-version }}-
-    - name: Install dependencies
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
+        cache: 'yarn'
+        cache-dependency-path: '**/yarn.lock'
+
+    - name: lerna bootstrap
       run: |
         npx lerna bootstrap
     - name: Print dependencies
@@ -49,6 +34,7 @@ jobs:
         echo -n "node " && node -v
         echo -n "npm " && npm -v
         yarn list --depth=0
+
     - name: yarn lint
       run: |
         yarn lerna run lint --scope @growi/slack --scope @growi/slackbot-proxy
@@ -86,30 +72,14 @@ jobs:
 
     steps:
     - uses: actions/checkout@v2
-    - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v1
+
+    - uses: actions/setup-node@v2
       with:
         node-version: ${{ matrix.node-version }}
-    - name: Cache/Restore node_modules
-      id: cache-dependencies
-      uses: actions/cache@v2
-      with:
-        path: '**/node_modules'
-        key: ${{ runner.OS }}-node_modules_dev-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
-    - name: Get yarn cache dir
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
-      id: cache-yarn
-      run: echo "::set-output name=dir::$(yarn cache dir)"
-    - name: Cache/Restore yarn cache
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
-      uses: actions/cache@v2
-      with:
-        path: ${{ steps.cache-yarn.outputs.dir }}
-        key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
-        restore-keys: |
-          ${{ runner.os }}-yarn-
+        cache: 'yarn'
+        cache-dependency-path: '**/yarn.lock'
+
     - name: lerna bootstrap
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
       run: |
         npx lerna bootstrap
     - name: Print dependencies
@@ -117,6 +87,7 @@ jobs:
         echo -n "node " && node -v
         echo -n "npm " && npm -v
         yarn list --depth=0
+
     - name: yarn dev:ci
       working-directory: ./packages/slackbot-proxy
       run: |
@@ -129,6 +100,7 @@ jobs:
         TYPEORM_DATABASE: growi-slackbot-proxy
         TYPEORM_USERNAME: root
         TYPEORM_PASSWORD:
+
     - name: Slack Notification
       uses: weseek/ghaction-slack-notification@master
       if: failure()
@@ -159,36 +131,13 @@ jobs:
 
     steps:
     - uses: actions/checkout@v2
-    - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v1
+
+    - uses: actions/setup-node@v2
       with:
         node-version: ${{ matrix.node-version }}
-    - name: Get Date
-      id: date
-      run: |
-        echo ::set-output name=YmdH::$(date '+%Y%m%d%H')
-        echo ::set-output name=Ymd::$(date '+%Y%m%d')
-        echo ::set-output name=Ym::$(date '+%Y%m')
-        echo ::set-output name=Y::$(date '+%Y')
-    - name: Cache/Restore node_modules
-      uses: actions/cache@v2
-      with:
-        path: '**/node_modules'
-        key: ${{ runner.OS }}-node_modules_prod-${{ matrix.node-version }}-${{ steps.date.outputs.YmdH }}
-        restore-keys: |
-          ${{ runner.os }}-node_modules_prod-${{ matrix.node-version }}-${{ steps.date.outputs.Ymd }}
-          ${{ runner.os }}-node_modules_prod-${{ matrix.node-version }}-${{ steps.date.outputs.Ym }}
-          ${{ runner.os }}-node_modules_prod-${{ matrix.node-version }}-${{ steps.date.outputs.Y }}
-    - name: Get yarn cache dir
-      id: cache-yarn
-      run: echo "::set-output name=dir::$(yarn cache dir)"
-    - name: Cache/Restore yarn cache
-      uses: actions/cache@v2
-      with:
-        path: ${{ steps.cache-yarn.outputs.dir }}
-        key: ${{ runner.os }}-yarn-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
-        restore-keys: |
-          ${{ runner.os }}-yarn-
+        cache: 'yarn'
+        cache-dependency-path: '**/yarn.lock'
+
     - name: lerna bootstrap
       run: |
         npx lerna bootstrap

+ 29 - 112
.github/workflows/ci.yml

@@ -5,7 +5,8 @@ on:
     branches-ignore:
       - release/**
       - rc/**
-      - tmp/**
+      - chore/**
+      - support/prepare-v**
 
 jobs:
 
@@ -18,30 +19,14 @@ jobs:
 
     steps:
     - uses: actions/checkout@v2
-    - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v1
+
+    - uses: actions/setup-node@v2
       with:
         node-version: ${{ matrix.node-version }}
-    - name: Cache/Restore node_modules
-      id: cache-dependencies
-      uses: actions/cache@v2
-      with:
-        path: '**/node_modules'
-        key: ${{ runner.OS }}-node_modules-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
-    - name: Get yarn cache dir
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
-      id: cache-yarn
-      run: echo "::set-output name=dir::$(yarn cache dir)"
-    - name: Cache/Restore yarn cache
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
-      uses: actions/cache@v2
-      with:
-        path: ${{ steps.cache-yarn.outputs.dir }}
-        key: ${{ runner.os }}-yarn-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
-        restore-keys: |
-          ${{ runner.os }}-yarn-${{ matrix.node-version }}-
-    - name: Install dependencies
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
+        cache: 'yarn'
+        cache-dependency-path: '**/yarn.lock'
+
+    - name: lerna bootstrap
       run: |
         npx lerna bootstrap
     - name: Print dependencies
@@ -49,6 +34,7 @@ jobs:
         echo -n "node " && node -v
         echo -n "npm " && npm -v
         yarn list --depth=0
+
     - name: lerna run lint for plugins
       run: |
         yarn lerna run lint --scope @growi/plugin-*
@@ -86,30 +72,14 @@ jobs:
 
     steps:
     - uses: actions/checkout@v2
-    - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v1
+
+    - uses: actions/setup-node@v2
       with:
         node-version: ${{ matrix.node-version }}
-    - name: Cache/Restore node_modules
-      id: cache-dependencies
-      uses: actions/cache@v2
-      with:
-        path: '**/node_modules'
-        key: ${{ runner.OS }}-node_modules-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
-    - name: Get yarn cache dir
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
-      id: cache-yarn
-      run: echo "::set-output name=dir::$(yarn cache dir)"
-    - name: Cache/Restore yarn cache
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
-      uses: actions/cache@v2
-      with:
-        path: ${{ steps.cache-yarn.outputs.dir }}
-        key: ${{ runner.os }}-yarn-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
-        restore-keys: |
-          ${{ runner.os }}-yarn-${{ matrix.node-version }}-
-    - name: Install dependencies
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
+        cache: 'yarn'
+        cache-dependency-path: '**/yarn.lock'
+
+    - name: lerna bootstrap
       run: |
         npx lerna bootstrap
     - name: Print dependencies
@@ -117,6 +87,7 @@ jobs:
         echo -n "node " && node -v
         echo -n "npm " && npm -v
         yarn list --depth=0
+
     - name: yarn test
       working-directory: ./packages/app
       run: |
@@ -162,46 +133,14 @@ jobs:
 
     steps:
     - uses: actions/checkout@v2
-    - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v1
+
+    - uses: actions/setup-node@v2
       with:
         node-version: ${{ matrix.node-version }}
-    - name: Cache/Restore node_modules
-      id: cache-dependencies
-      uses: actions/cache@v2
-      with:
-        path: '**/node_modules'
-        key: ${{ runner.OS }}-node_modules_dev-${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}
-    - name: Get Date
-      id: date
-      run: |
-        echo ::set-output name=YmdH::$(date '+%Y%m%d%H')
-        echo ::set-output name=Ymd::$(date '+%Y%m%d')
-        echo ::set-output name=Ym::$(date '+%Y%m')
-        echo ::set-output name=Y::$(date '+%Y')
-    - name: Cache/Restore node_modules/.cache/hard-source
-      uses: actions/cache@v2
-      with:
-        path: node_modules/.cache
-        key: ${{ runner.OS }}-hard_source_webpack-${{ matrix.node-version }}-${{ steps.date.outputs.YmdH }}
-        restore-keys: |
-          ${{ runner.os }}-hard_source_webpack-${{ matrix.node-version }}-${{ steps.date.outputs.Ymd }}
-          ${{ runner.os }}-hard_source_webpack-${{ matrix.node-version }}-${{ steps.date.outputs.Ym }}
-          ${{ runner.os }}-hard_source_webpack-${{ matrix.node-version }}-${{ steps.date.outputs.Y }}
-    - name: Get yarn cache dir
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
-      id: cache-yarn
-      run: echo "::set-output name=dir::$(yarn cache dir)"
-    - name: Cache/Restore yarn cache
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
-      uses: actions/cache@v2
-      with:
-        path: ${{ steps.cache-yarn.outputs.dir }}
-        key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
-        restore-keys: |
-          ${{ runner.os }}-yarn-
-    - name: Install dependencies
-      if: steps.cache-dependencies.outputs.cache-hit != 'true'
+        cache: 'yarn'
+        cache-dependency-path: '**/yarn.lock'
+
+    - name: lerna bootstrap
       run: |
         npx lerna bootstrap
     - name: Print dependencies
@@ -209,6 +148,7 @@ jobs:
         echo -n "node " && node -v
         echo -n "npm " && npm -v
         yarn list --depth=0
+
     - name: yarn dev:ci
       working-directory: ./packages/app
       run: |
@@ -247,37 +187,14 @@ jobs:
 
     steps:
     - uses: actions/checkout@v2
-    - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v1
+
+    - uses: actions/setup-node@v2
       with:
         node-version: ${{ matrix.node-version }}
-    - name: Get Date
-      id: date
-      run: |
-        echo ::set-output name=YmdH::$(date '+%Y%m%d%H')
-        echo ::set-output name=Ymd::$(date '+%Y%m%d')
-        echo ::set-output name=Ym::$(date '+%Y%m')
-        echo ::set-output name=Y::$(date '+%Y')
-    - name: Cache/Restore node_modules
-      uses: actions/cache@v2
-      with:
-        path: '**/node_modules'
-        key: ${{ runner.OS }}-node_modules_prod-${{ matrix.node-version }}-${{ steps.date.outputs.YmdH }}
-        restore-keys: |
-          ${{ runner.os }}-node_modules_prod-${{ matrix.node-version }}-${{ steps.date.outputs.Ymd }}
-          ${{ runner.os }}-node_modules_prod-${{ matrix.node-version }}-${{ steps.date.outputs.Ym }}
-          ${{ runner.os }}-node_modules_prod-${{ matrix.node-version }}-${{ steps.date.outputs.Y }}
-    - name: Get yarn cache dir
-      id: cache-yarn
-      run: echo "::set-output name=dir::$(yarn cache dir)"
-    - name: Cache/Restore yarn cache
-      uses: actions/cache@v2
-      with:
-        path: ${{ steps.cache-yarn.outputs.dir }}
-        key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
-        restore-keys: |
-          ${{ runner.os }}-yarn-
-    - name: Install dependencies
+        cache: 'yarn'
+        cache-dependency-path: '**/yarn.lock'
+
+    - name: lerna bootstrap
       run: |
         npx lerna bootstrap
     - name: Print dependencies

+ 62 - 0
.github/workflows/draft-release.yml

@@ -0,0 +1,62 @@
+name: Draft Release
+
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+
+  # Refs: https://github.com/release-drafter/release-drafter
+  update-release-draft:
+    runs-on: ubuntu-latest
+
+    outputs:
+      CURRENT_VERSION: ${{ steps.package-json.outputs.packageVersion }}
+      RELEASE_DRAFT_BODY: ${{ steps.release-drafter.outputs.body }}
+
+    steps:
+      - uses: actions/checkout@v2
+
+      - name: Retrieve information from package.json
+        uses: myrotvorets/info-from-package-json-action@0.0.2
+        id: package-json
+
+      # Drafts your next Release notes as Pull Requests are merged into "master"
+      - uses: release-drafter/release-drafter@v5
+        id: release-drafter
+        with:
+          name: v${{ steps.package-json.outputs.packageVersion }}
+          tag: v${{ steps.package-json.outputs.packageVersion }}
+          version: ${{ steps.package-json.outputs.packageVersion }}
+          disable-autolabeler: true
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+  # Refs: https://github.com/bakunyo/git-pr-release-action
+  update-release-pr:
+    needs: update-release-draft
+
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+
+      - name: Get release version
+        id: release-version
+        run: |
+          RELEASE_VERSION=`npx semver -i patch ${{ needs.update-release-draft.outputs.CURRENT_VERSION }}`
+          echo ::set-output name=RELEASE_VERSION::$RELEASE_VERSION
+
+      - name: Create/Update Pull Request
+        uses: bakunyo/git-pr-release-action@master
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          GIT_PR_RELEASE_BRANCH_PRODUCTION: release/current
+          GIT_PR_RELEASE_BRANCH_STAGING: master
+          GIT_PR_RELEASE_TEMPLATE: .github/git-pr-release-template.erb
+          GIT_PR_RELEASE_TITLE: Release v${{ steps.release-version.outputs.RELEASE_VERSION }}
+          GIT_PR_RELEASE_BODY: ${{ needs.update-release-draft.outputs.RELEASE_DRAFT_BODY }}
+

+ 46 - 0
.github/workflows/pr-to-master.yml

@@ -0,0 +1,46 @@
+name: PR to master
+
+on:
+  pull_request:
+    branches:
+      - master
+    # Only following types are handled by the action, but one can default to all as well
+    types: [opened, reopened, edited, synchronize]
+
+jobs:
+
+  # Refs: https://github.com/release-drafter/release-drafter
+  auto-labeling:
+    runs-on: ubuntu-latest
+
+    if: ${{ !contains(github.event.pull_request.labels.*.name, 'exclude from changelog') }}
+
+    steps:
+      - uses: release-drafter/release-drafter@v5
+        with:
+          disable-releaser: true
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+  check-title:
+    runs-on: ubuntu-latest
+
+    if: |
+      (!contains( github.event.pull_request.labels.*.name, 'exclude from changelog' ) &&
+        !startsWith( github.ref, 'refs/heads/chore/' ))
+
+    steps:
+      - uses: amannn/action-semantic-pull-request@v3.4.2
+        with:
+          types: |
+            feat
+            imprv
+            fix
+            support
+            chore
+            ci
+            docs
+            test
+          requireScope: false
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

+ 5 - 7
.github/workflows/release-rc.yml

@@ -14,11 +14,9 @@ jobs:
     steps:
     - uses: actions/checkout@v2
 
-    - name: Setup semver
-      id: semver
-      run: |
-        semver=`npm run version --silent`
-        echo "::set-output name=SEMVER::$semver"
+    - name: Retrieve information from package.json
+      uses: myrotvorets/info-from-package-json-action@0.0.2
+      id: package-json
 
     - name: Docker meta
       id: meta
@@ -26,8 +24,8 @@ jobs:
       with:
         images: weseek/growi,ghcr.io/weseek/growi
         tags: |
-          type=raw,value=${{ steps.semver.outputs.SEMVER }}
-          type=raw,value=${{ steps.semver.outputs.SEMVER }}.{{sha}}
+          type=raw,value=${{ steps.package-json.outputs.packageVersion }}
+          type=raw,value=${{ steps.package-json.outputs.packageVersion }}.{{sha}}
 
     - name: Login to docker.io registry
       run: |

+ 8 - 8
.github/workflows/release-slackbot-proxy.yml

@@ -1,9 +1,10 @@
 name: Release Docker Image for @growi/slackbot-proxy
 
 on:
-  push:
+  pull_request:
     branches:
       - release/slackbot-proxy/**
+    types: [closed]
 
 jobs:
 
@@ -12,13 +13,12 @@ jobs:
 
     steps:
     - uses: actions/checkout@v2
+      with:
+        ref: ${{ github.event.pull_request.base.ref }}
 
-    - name: Setup semver
-      id: semver
-      working-directory: ./packages/slackbot-proxy
-      run: |
-        semver=`npm run version --silent`
-        echo "::set-output name=SEMVER::$semver"
+    - name: Retrieve information from package.json
+      uses: myrotvorets/info-from-package-json-action@0.0.2
+      id: package-json
 
     - name: Docker meta
       id: meta
@@ -27,7 +27,7 @@ jobs:
         images: weseek/growi-slackbot-proxy,ghcr.io/weseek/growi-slackbot-proxy,asia.gcr.io/${{ secrets.GCP_PRJ_ID_SLACKBOT_PROXY }}/growi-slackbot-proxy
         tags: |
           type=raw,value=latest
-          type=raw,value=${{ steps.semver.outputs.SEMVER }}
+          type=raw,value=${{ steps.package-json.outputs.packageVersion }}
 
     - name: Login to docker.io registry
       run: |

+ 104 - 37
.github/workflows/release.yml

@@ -1,57 +1,127 @@
 name: Release
 
 on:
-  push:
+  pull_request:
     branches:
       - release/current
       - release/*.*.*
+    types: [closed]
 
 jobs:
-  github-release:
+  create-github-release:
 
     runs-on: ubuntu-latest
 
+    if: github.event.pull_request.merged == true
+
     outputs:
-      RELEASE_VERSION: ${{ steps.bump-version.outputs.RELEASE_VERSION }}
+      RELEASED_VERSION: ${{ steps.package-json.outputs.packageVersion }}
 
     steps:
     - uses: actions/checkout@v2
+      with:
+        ref: ${{ github.event.pull_request.base.ref }}
 
-    - name: Init Git
+    - uses: actions/setup-node@v2
+      with:
+        node-version: '14'
+        cache: 'yarn'
+        cache-dependency-path: '**/yarn.lock'
+
+    - name: Install dependencies
       run: |
-        git config --local user.name "GitHub Action"
-        git config --local user.email "info@weseek.co.jp"
-        git remote set-url origin "https://$GITHUB_ACTOR:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY"
+        npx lerna bootstrap
 
-    - name: Bump version
-      id: bump-version
+    - name: Bump versions
       run: |
-        npm --no-git-tag-version version patch
-        export RELEASE_VERSION=`npm run version --silent`
-        sh ./bin/github-actions/update-readme.sh
-        echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV
-        echo ::set-output name=RELEASE_VERSION::$RELEASE_VERSION
+        node ./bin/github-actions/bump-versions -i patch
+        sh ./packages/app/bin/github-actions/update-readme.sh
 
-    - name: Checkout, Commit, Tag and Push
+    - name: Retrieve information from package.json
+      uses: myrotvorets/info-from-package-json-action@0.0.2
+      id: package-json
+
+    - name: Update Changelog
+      uses: stefanzweifel/changelog-updater-action@v1
+      with:
+        latest-version: v${{ steps.package-json.outputs.packageVersion }}
+        release-notes: ${{ github.event.pull_request.body }}
+
+    - name: Update README.md for docker image
+      working-directory: ./packages/app
       run: |
-        TMP_RELEASE_BRANCH=tmp/release-${{ env.RELEASE_VERSION }}
-        git checkout -B $TMP_RELEASE_BRANCH
-        git commit -am "Release v${{ env.RELEASE_VERSION }}"
-        git tag -a v${{ env.RELEASE_VERSION }} -m "v${{ env.RELEASE_VERSION }}"
-        git push --follow-tags origin $TMP_RELEASE_BRANCH
-        git push --delete origin $TMP_RELEASE_BRANCH
-
-    - name: Upload release notes
-      uses: Roang-zero1/github-create-release-action@master
-      with:
-        created_tag: v${{ env.RELEASE_VERSION }}
-        changelog_file: CHANGES.md
+        sh ./bin/github-actions/update-readme.sh
+      env:
+        RELEASED_VERSION: ${{ steps.package-json.outputs.packageVersion }}
+
+    - name: Commit, Tag and Push
+      uses: stefanzweifel/git-auto-commit-action@v4
+      with:
+        branch: ${{ github.event.pull_request.base.ref }}
+        commit_message: Release v${{ steps.package-json.outputs.packageVersion }}
+        tagging_message: v${{ steps.package-json.outputs.packageVersion }}
+
+    - uses: ncipollo/release-action@v1
+      with:
+        body: ${{ github.event.pull_request.body }}
+        tag: v${{ steps.package-json.outputs.packageVersion }}
+        token: ${{ secrets.GITHUB_TOKEN }}
+
+    - name: Delete drafts
+      uses: hugo19941994/delete-draft-releases@v1.0.0
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
 
+  create-pr-for-next-rc:
+    needs: create-github-release
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        ref: v${{ needs.create-github-release.outputs.RELEASED_VERSION }}
+
+    - uses: actions/setup-node@v2
+      with:
+        node-version: '14'
+        cache: 'yarn'
+        cache-dependency-path: '**/yarn.lock'
+
+    - name: Install dependencies
+      run: |
+        npx lerna bootstrap
+
+    - name: Bump versions for next RC
+      run: |
+        node ./bin/github-actions/bump-versions -i prerelease
+
+    - name: Retrieve information from package.json
+      uses: myrotvorets/info-from-package-json-action@0.0.2
+      id: package-json
+
+    - name: Commit
+      uses: github-actions-x/commit@v2.8
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}
+        push-branch: support/prepare-v${{ steps.package-json.outputs.packageVersion }}
+        commit-message: 'Bump version'
+        name: GitHub Action
+
+    - name: Create PR
+      uses: repo-sync/pull-request@v2
+      with:
+        source_branch: support/prepare-v${{ steps.package-json.outputs.packageVersion }}
+        destination_branch: master
+        pr_title: Prepare v${{ steps.package-json.outputs.packageVersion }}
+        pr_label: exclude from changelog
+        pr_body: "An automated PR generated by ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
+        github_token: ${{ secrets.GITHUB_TOKEN }}
+
+
   build-image:
-    needs: github-release
+    needs: create-github-release
 
     runs-on: ubuntu-latest
 
@@ -61,11 +131,8 @@ jobs:
 
     steps:
     - uses: actions/checkout@v2
-
-    - name: Checkout released tag
-      run: |
-        git fetch --tags
-        git checkout refs/tags/v${{ needs.github-release.outputs.RELEASE_VERSION }}
+      with:
+        ref: v${{ needs.create-github-release.outputs.RELEASED_VERSION }}
 
     - name: Setup suffix
       id: suffix
@@ -82,9 +149,9 @@ jobs:
           suffix=${{ steps.suffix.outputs.SUFFIX }}
         tags: |
           type=raw,value=latest
-          type=semver,value=${{ needs.github-release.outputs.RELEASE_VERSION }},pattern={{major}}
-          type=semver,value=${{ needs.github-release.outputs.RELEASE_VERSION }},pattern={{major}}.{{minor}}
-          type=semver,value=${{ needs.github-release.outputs.RELEASE_VERSION }},pattern={{major}}.{{minor}}.{{patch}}
+          type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}
+          type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}.{{minor}}
+          type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}.{{minor}}.{{patch}}
 
     - name: Login to docker.io registry
       run: |
@@ -138,7 +205,7 @@ jobs:
       with:
         channel: '#release'
         url: ${{ secrets.SLACK_WEBHOOK_URL }}
-        created_tag: 'v${{ needs.github-release.outputs.RELEASE_VERSION }}${{ steps.suffix.outputs.SUFFIX }}'
+        created_tag: 'v${{ needs.create-github-release.outputs.RELEASED_VERSION }}${{ steps.suffix.outputs.SUFFIX }}'
 
     - name: Check whether workspace is clean
       run: |

+ 2603 - 0
CHANGELOG.md

@@ -0,0 +1,2603 @@
+# Changelog
+
+## [Unreleased](https://github.com/weseek/growi/compare/v4.4.2...HEAD)
+
+*Please do not manually update this file. We've automated the process.*
+
+## [v4.4.2](https://github.com/weseek/growi/compare/v4.4.0...v4.4.2) - 2021-09-07
+
+### Changes
+
+- Release v4.4.1 (#4262) @github-actions
+
+### 🐛 Bug Fixes
+
+- fix: Plugin backend's permission (#4271) @yuki-takei
+
+### 🧰 Maintenance
+
+- support: Make lerna mode fixed (#4274) @yuki-takei
+- support: Make lerna mode fixed (#4263) @yuki-takei
+
+## v4.4.1 (Missing number)
+
+## [v4.4.0](https://github.com/weseek/growi/compare/v4.3.3...v4.4.0) (Discontinued) - 2021-09-06
+
+### Changes
+
+### BREAKING CHANGES
+
+- Official plugins are now preinstalled
+- It is no longer compatible with previous versions of official bots
+
+Upgrading Guide: [https://docs.growi.org/en/admin-guide/upgrading/44x.html](https://docs.growi.org/en/admin-guide/upgrading/44x.html)
+
+### 💎 Features
+
+- feat: Password resetting by users (#4135) @kaoritokashiki
+- feat: Copy bug report btn (#4200) @Mxchaeltrxn
+- Feature: User trigger notification and Global notification are available by new Slack integration
+
+### 🚀 Improvement
+
+- imprv: Add attachment button in editor navbar
+- imprv: Modified with proxy app installation tutorial (#4174) @hakumizuki
+- imprv: Slackbot proxy respone (#4194) @hakumizuki
+- imprv: Slackbot proxy respone (#4175, #4201) @zahmis
+- Imprv: Admin slack integration (#4190) @yuki-takei
+
+### 🐛 Bug Fixes
+
+- fix: Recursive rename operation from `/parent` to `/parent/child` (#4101) @miya
+- fix: adminRequired middleware for socket.io (#4245) @yuki-takei
+- fix: Encode spaces in page path in LinkEditModal
+
+### 🧰 Maintenance
+
+- support: Supress warnings for mongo (#4247) @yuki-takei
+- support: Add bump-versions script (#4241) @yuki-takei
+- support: New release workflow (#4236) @yuki-takei
+- Support: Create @growi/core package
+- Support: Create @growi/ui package
+- Support: Include official plugins as sub packages
+- Support: Upgrade libs
+- - @slack/web-api
+- 
+- - date-fns
+- 
+- - helmet
+- 
+- - morgan
+- 
+- - socket.io
+- 
+- 
+
+## v4.3.3
+
+- Improvement: Welcome page markdown
+- Fix: Some recursive operation exclude descendant pages that are restricted for groups
+- - Rename / Delete / Delete completely / Put back / Duplicate
+- 
+- 
+- Fix: Layout is broken when editing users page ([#4128](https://github.com/weseek/growi/issues/4128))
+- Support: Upgrade libs
+- - @slack/web-api
+- 
+- - date-fns
+- 
+- - escape-string-regexp
+- 
+- 
+
+## v4.3.2
+
+- Feature: Hufflpuff theme
+- Improvement: CodeMirror header styles
+- Improvement: CodeMirror syntax-highlighting fenced code blocks
+- Improvement: Slack Integration Settings
+- - Error behavior when getting connection statuses
+- 
+- - Add links to docs
+- 
+- 
+- Improvement: /_api/v3/recent can be accessed with access token
+- Support: Using http-errors
+
+## v4.3.1
+
+- Fix: Build script for production
+
+## v4.3.0
+
+### BREAKING CHANGES
+
+- GROWI manages dependencies with `lerna`
+- - Use `npx lerna bootstrap` instead of `yarn install`
+- 
+- 
+- GROWI includes some official plugins in default
+- - Users no longer need to install [growi-plugin-lsx](https://github.com/weseek/growi-plugin-lsx), [growi-plugin-pukiwiki-like-linker](https://github.com/weseek/growi-plugin-pukiwiki-like-linker) and [growi-plugin-attachment-refs](https://github.com/weseek/growi-plugin-attachment-refs) before build client.
+- 
+- 
+
+Upgrading Guide: [https://docs.growi.org/en/admin-guide/upgrading/43x.html](https://docs.growi.org/en/admin-guide/upgrading/43x.html)
+
+### Updates
+
+- Feature: New Slack Integration with Slack Bot
+- - Searching GROWI pages from Slack
+- 
+- - Creating GROWI pages from Slack
+- 
+- - - Easy record conversations
+- - 
+- 
+- - 
+- 
+- 
+- Feature: Enable/Disable option for share link
+- Feature: Re-send invitation mail from user management page
+- Improvement: Mark users who failed to send invitation emails
+- Fix: lsx plugin in the custom sidebar does not work when showing search result page
+- Support: Switch the official docker base image from Alpine based to Ubuntu based
+- Support: Upgrade libs
+- - striptags
+- 
+- 
+
+## v4.2.21
+
+- Improvement: Headers style on built-in editor
+- Improvement: Codemirror is now scrollable one editor height of empty space into view at the bottom of the editor
+- Improvement: Upgrade mongodb driver to fix [NODE-2784](https://jira.mongodb.org/browse/NODE-2784)
+- Support: Upgrade libs
+- - connect-mongo
+- 
+- - i18next
+- 
+- - migrate-mongo
+- 
+- - mongoose
+- 
+- - stream-to-promise
+- 
+- - validator
+- 
+- - ws
+- 
+- - nodemailer
+- 
+- - i18next-express-middleware
+- 
+- - growi-commons
+- 
+- - growi-plugin-attachment-refs
+- 
+- - growi-plugin-lsx
+- 
+- 
+
+## v4.2.20
+
+- Improvement: Error message when the password is too short
+- Improvement: Repeat XSS processing as a countermeasure against nesting
+- Fix: NoSQL injection of access-token-parser
+- Fix: Checking permission when operating share links
+- Fix: Invalid NaN label is shown when deletedAt of the page is undefined
+- - Introduced by v4.2.8
+- 
+- 
+
+## v4.2.19
+
+- Feature: Set max-age of the user's cookie with the env var `SESSION_MAX_AGE`
+- Feature: Set max-age of the user's cookie in admin page
+- Improvement: Change the first accessing page after installation to the top page
+- Support: Upgrade libs
+- - string-width
+- 
+- - diff
+- 
+- - archiver
+- 
+- 
+
+## v4.2.18
+
+- Feature: Convertible page contents width
+- Fix: Group selector of User Group Delete Modal does not show all groups
+- Fix: Global notification to Slack does not encode spaces of page path
+- Support: Upgrade libs
+- - @google-cloud/storage
+- 
+- 
+
+## v4.2.17
+
+- Improvement: Invoke garbage collection when reindex all pages by elasticsearch
+- Improvement: Hide Sidebar at shared pages
+- Fix: No unsaved alert is displayed without difference the latest markdown and editor value
+- Support: Update libs
+- - eslint-config-weseek
+- 
+- 
+
+## v4.2.16
+
+- Fix: "Only inside the group" causes an error
+- - Introduced by v4.2.15
+- 
+- 
+
+## v4.2.15
+
+- Improvement: toastr location for editing
+- Improvement: Handsontable with static backdrop to prevent from closing when backdrop is clicked
+- Fix: Accept invalid page path like `..%2f`
+- Fix: Pages updated date is corrupted after recursive operation
+- - Introduced by v4.2.8
+- 
+- 
+- Support: Upgrade libs
+- - reactstrap
+- 
+- 
+
+## v4.2.14
+
+- Feature: Add an option to restrict publishing email property for new users
+- Improvement: Invite modal in admin page without email server settings
+- Improvement: Global notification settings in admin page without email server settings
+- Fix: Can create pages on the share route
+- - Introduced by v4.2.8
+- 
+- 
+- Fix: Pages restrected by group are excluded for recurrence operation
+- - Introduced by v4.2.8
+- 
+- 
+- Fix: Rename and duplicate to descendants path does not work correctly
+- - Introduced by v4.2.8
+- 
+- 
+- Support: Update libs
+- - bunyan
+- 
+- - browser-bunyan
+- 
+- 
+
+## v4.2.13
+
+- Feature: Detect indent size automatically
+- Fix: Some API responses includes email unintentionally
+- Fix: An error always displayed in admin pages
+
+## v4.2.12
+
+- Feature: Custom Sidebar
+- Fix: Set language correctly for draw.io (diagrams.net)
+
+## v4.2.11
+
+- Fix: Rename decendants is not working
+- - Introduced by v4.2.8
+- 
+- 
+
+## v4.2.10
+
+- Feature: Staff Credits for apps on GROWI.cloud
+- Improvement: Hackmd button behavior when disabled
+- Improvement: Layout of comparing revisions
+- Fix: Empty trash is not working
+
+## v4.2.9
+
+- Feature: Comparing revisions
+- Improvement: Memory consumption when re-indexing for full text searching
+- Improvement: Site URL settings valildation
+- Fix: Show comfirmation when transiting page without save
+- Fix: Save slack channels history when user trigger notification is invoked
+- Fix: The label of alerts for move/rename/delete are borken
+
+## v4.2.8
+
+- Improvement: Performance for pages to rename/duplicate/delete/revert pages
+- Fix: Preview scrollbar doesn't sync to editor
+- - Introduced by v4.2.6
+- 
+- 
+- Fix: Failed to save temporaryUrlCached with using gcs
+- - Introduced by v4.2.3
+- 
+- 
+- Fix: Fixed not being able to update ses settings
+- - Introduced by v4.2.0
+- 
+- 
+- Fix: Fixed the display of updtedAt and createdAt being reversed
+- Fix: Pass app title value through the XSS filter
+
+## v4.2.7
+
+- Fix: Installer doesn't work on Chrome
+
+## v4.2.6
+
+- Feature: Add a button to jump to Comments section
+- Feature: Paste Bootstrap4 Grid HTML with GUI
+- Feature: Disable auto formating table option
+- Improvement: Layout of Edit Link Modal
+- Improvement: Focus to the first input when modal is opened
+- Improvement: Preview layout in edit mode
+- Improvement: Install process under redundant environment
+- Improvement: Add contributors
+- Fix: Upgrading to v4.x failed when the user uses Kibela Layout
+- - Introduced by v4.2.0
+- 
+- 
+- Fix: diagrams.net (draw.io) errors
+- Fix: Navbar is not rendered on old iOS
+- Support: Expose metrics with Promster
+- Support: Upgrade libs
+- - axios
+- 
+- 
+
+## v4.2.5
+
+- Improvement: Invoke garbage collection when reindex all pages by elasticsearch
+- - Turned out not working -- 2021.05.01
+- 
+- 
+- Fix: MathJax rendering does not work
+
+## v4.2.4
+
+- Fix: Fixed an error when creating a new page with `Ctrl-S`
+- - Introduced by v4.2.2
+- 
+- 
+- Fix: Fixed a strange diff in PageHistory due to Pagination
+- Fix: Fixed that the user group page could not be found when using api from the outside
+
+## v4.2.3
+
+- Feature: Insert/edit links with GUI
+- Feature: Auto reconnecting to search service
+- Improvement: New style of params for Healthcheck API
+- Fix: Referencing attachments when `FILE_UPLOAD_DISABLED` is true
+- Fix: The message of timeline for restricted pages
+- Fix: Parameter validation for Import/Export Archive API
+- Fix: Prevent regexp for Search Tags API
+- Fix: Add `Content-Security-Policy` when referencing attachments
+- Fix: Sanitize at presentation time
+- Fix: Remove page path string from message for page lists and timeline when there is no contents
+
+## v4.2.2
+
+- Fix: Consecutive save operations with built-in editor fail
+- - Introduced by v4.2.1
+- 
+- 
+
+## v4.2.1
+
+- Fix: Consecutive save operations with HackMD fail
+- - Introduced by v4.2.0
+- 
+- 
+- Fix: Switching theme to kibela fail
+- - Introduced by v4.2.0
+- 
+- 
+
+## v4.2.0
+
+### BREAKING CHANGES
+
+- GROWI v4.2.x no longer support Kibela layout
+- - Kibela theme is newly added and the configuration will migrate to it automatically
+- 
+- 
+
+### Updates
+
+- Feature: File Upload Settings on admin pages
+- Improvement: Basic layout of page
+- Support: Support MongoDB 4.0, 4.2 and 4.4
+- Support: Upgrade libs
+- - migrate-mongo
+- 
+- - mongoose
+- 
+- 
+
+## v4.1.13
+
+- Fix: MathJax rendering does not work
+
+## v4.1.12
+
+- Fix: Adjust line-height for pre under li
+- Fix: Emptying trash process is broken
+
+## v4.1.11
+
+- Improvement: Generating draft DOM id strategy
+- Fix: GROWI version downgrade causes a validation error for user.lang
+
+## v4.1.10
+
+- Fix: Make listing users API secure
+- Fix: Error message when the server denies guest user connecting with socket.io
+
+## v4.1.9
+
+- Feature: Environment variables to set max connection size to deliver push messages to all clients
+
+## v4.1.8
+
+- Improvement: Rebuilding progress bar colors for Full Text Search Management
+- Improvement: Support operations on page data with a null value for author
+
+## v4.1.7
+
+- Improvement: Fire global notification when a new page is created by uploading file
+- Fix: Change default `DRAWIO_URI` to embed.diagrams.net
+- Fix: An unhandled rejection occures when a user who does not send referer accesses
+
+## v4.1.6
+
+- Improvement: Hide Fab at admin pages
+- Fix: Presentation does not work
+- Fix: Update GrantSelector status when uploading a file to a new page
+- Fix: CopyDropdown origin refs draw.io host wrongly
+
+## v4.1.5
+
+- Feature: Independent S3 configuration and SES configuration for AWS
+- Fix: Author name does not displayed in page history
+- Fix: Hide unnecessary component when pringing
+
+## v4.1.4 (Discontinued)
+
+## v4.1.3
+
+- Feature: Create/edit linker with GUI
+- Improvement: Paging page histories
+- Improvement: Avoid using `cursor.snapshot()` in preparation for MongoDB version upgrade
+- Improvement: Allow to save "From e-mail address" only in App Settings
+- Improvement: Allow to empty "From e-mail address" in App Settings
+- Improvement: Export/Import archive data serially so as not to waste memory
+- Fix: To be able to delete attachment metadata even when the actual data does not exist
+- Fix: Limit the attrubutes of user data for `/_api/v3/users`
+- Fix: Prevent XSS with SVG
+- Upgrade libs
+- - optimize-css-assets-webpack-plugin
+- 
+- - terser-webpack-plugin
+- 
+- 
+
+## v4.1.2
+
+- Fix: Uploaded images do not displayed
+- - Introduced by v4.1.1
+- 
+- 
+
+## v4.1.1
+
+- Feature: External share link
+- Improvement: Optimize some features that operate revision data
+- - Page history
+- 
+- - Renaming pages
+- 
+- - Deleting pages
+- 
+- 
+- Fix: Cmd+c/v/... does not work on Mac
+- - Introduced by v4.1.0
+- 
+- 
+- Fix: "Append params" switch of CopyDropdown does not work when multiple CopyDropdown instance exists
+- Fix: "Append params" switch of CopyDropdown escapes spaces
+- Fix: Blockdiag does not be rendered
+- Fix: Access token parser
+
+## v4.1.0
+
+### 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](https://docs.growi.org/en/admin-guide/upgrading/41x.html)
+
+### Updates
+
+- Feature: Server settings synchronization for multiple GROWI Apps
+- Feature: Page status alert synchronization for multiple GROWI Apps
+- Feature: Smooth scroll for anchor links
+- Feature: Mirror Mode with [Konami Code](https://en.wikipedia.org/wiki/Konami_Code)
+- Improvement: Determine whether the "In Use" badge is displayed or not by attachment ID
+- Improvement: draw.io under NO_CDN environment
+- Fix: Deleting/renaming with recursive option affects pages that are inaccessible to active users
+- Fix: DrawioModal cuts without beginning/ending line
+- Fix: New settings of SMTP and AWS SES are not reflected when server is running
+- Fix: Sidebar layout broken when using Kibela layout
+- Support: Support Node.js v14
+- Support: Update libs
+- - mathjax
+- 
+- 
+
+## v4.0.11
+
+- Fix: Fab on search result page does not displayed
+- Fix: Adjust margin/padding for search result page
+- Fix: PageAlert broken
+- - Introduced by v4.0.9
+- 
+- 
+
+## v4.0.10
+
+- Improvement: Adjust ToC height
+- Fix: Fail to rename/delete a page set as "Anyone with the link"
+
+## v4.0.9
+
+- Feature: Detailed configurations for OpenID Connect
+- - Authorization Endpoint
+- 
+- - Token Endpoint
+- 
+- - Revocation Endpoint
+- 
+- - Introspection Endpoint
+- 
+- - UserInfo Endpoint
+- 
+- - Registration Endpoint
+- 
+- - JSON Web Key Set URI
+- 
+- 
+- Improvement: Navigations
+- - New floating subnavigation
+- 
+- - New open drawer button
+- 
+- - New fixed bottom navbar on mobile
+- 
+- - New fixed bottom navbar for editor on mobile
+- 
+- - FAB (Floating action button)
+- 
+- 
+- Improvement: Sticky admin navigation
+- Fix: Reseting password doesn't work
+- Fix: Styles for printing
+- Fix: Unable to create page with original path after emptying trash
+- I18n: Support zh-CN
+
+## v4.0.8 (Discontinued)
+
+## v4.0.7
+
+- Feature: Set request timeout for Elasticsearch with env var `ELASTICSEARCH_REQUEST_TIMEOUT`
+- Improvement: Apply styles faster on booting client
+- Fix: Styles are not applyed on installer
+- Fix: Remove last-resort `next()`
+- Fix: Enable/disable Notification settings couldn't change when either of the params is undefined
+- Fix: Text overflow
+
+## v4.0.6
+
+- Fix: Avatar images in Recent Changes are not shown
+- Fix: Full screen modal of Handsontable and Draw.io don't work
+- Fix: Shortcut for creating page respond with modifier key wrongly
+- - Introduced by v4.0.5
+- 
+- 
+
+## v4.0.5
+
+- Improvement: Return pre-defined session id when healthcheck
+- Improvement: Refactor caching for profile image
+- Improvement: Layout for global search help on mobile
+- Improvement: Layout for confidential notation
+- Fix: Shortcut for creating page doesn't work
+- Support: Dev in container
+- Support: Upgrade libs
+- - ldapjs
+- 
+- - node-sass
+- 
+- 
+
+## v4.0.4
+
+- Feature: Drawer/Dock mode selector
+- Improvement: Admin pages navigation
+- Improvement: Ensure not to avoid session management even when accessing to healthcheck
+- Support: Refactor unstated utils
+- Support: Upgrade libs
+- - connect-mongo
+- 
+- - connect-redis
+- 
+- - mongoose
+- 
+- - mongoose-gridfs
+- 
+- - mongoose-paginate-v2
+- 
+- 
+
+## v4.0.3
+
+- Feature: Copy page path dropdown with Append params switch
+- Improvement: Truncate overflowed user browsing history
+- Improvement: Tabs appearance on mobile
+- Improvement: Search help appearance on mobile
+- Improvement: Accessibility of login page
+- Fix: Editor was broken by long lines
+- Fix: Editor doesn't work on mobile
+- Fix: Word break in Recent Updated contents
+- Fix: navbar is broken on Safari
+
+## v4.0.2
+
+- Fix: Internal Server Error occurred when the guest user access to the pages that has likes
+- Fix: Some buttons are broken on Safari
+
+## v4.0.1
+
+- Improvement: Accessibility for Handsontable under dark mode
+- Improvement: Refactor '/pages.exist' API
+- Fix: Storing the state of sidebar
+- Fix: Comments order should be asc
+- Fix: Show/Hide replies button doesn't work
+- Fix: Tooltip doesn't work
+- Fix: Change the display of the scroll bar when modal is shown
+- Fix: Submit with enter key on Create/Rename modals
+- Fix: Show/Hide Unlink redirection button conditions
+- Fix: Link color in alerts
+- Support: Upgrade libs
+- - @atlaskit/drawer
+- 
+- - @atlaskit/navigation-next
+- 
+- 
+
+## v4.0.0
+
+### BREAKING CHANGES
+
+- Crowi Classic Behavior is removed
+- Crowi Classic Layout is removed
+- 'default-dark' theme is now merged as a dark mode variant of 'default' theme
+- 'blue-night' theme is now merged as a dark mode variant of 'mono-blue' theme
+
+Upgrading Guide: [https://docs.growi.org/en/admin-guide/upgrading/40x.html](https://docs.growi.org/en/admin-guide/upgrading/40x.html)
+
+### Updates
+
+- Feature: Sidebar
+- Feature: Recent changes on Sidebar
+- Feature: Switch Light/Dark Mode
+- Improvement: Migrate to Bootstrap 4
+- Improvement: Copy Page URL menu item to copy path dropdown
+- Improvement: Show contributors by Bootstrap Modal
+- Support: Upgrade libs
+- - bootstrap
+- 
+- 
+
+## v3.8.1
+
+### BREAKING CHANGES
+
+- Now Elasticsearch requires the privilege `cluster:monitor/health` instead of `cluster:monitor/nodes/info`
+
+Upgrading Guide: [https://docs.growi.org/en/admin-guide/upgrading/38x.html](https://docs.growi.org/en/admin-guide/upgrading/38x.html)
+
+### Updates
+
+- Improvement: Change the health check method for Elasticsearch
+- Fix: Unset overflow-y style for Edit Tags Modal
+- Fix: Duplicate page source is overwrited
+- - Introduced by 3.7.6
+- 
+- 
+
+## v3.8.0  (Discontinued)
+
+## v3.7.7
+
+- Feature: Empty trash pages
+- Improvement: Behavior of Reconnect to Elasticsearch button
+- Fix: Duplicate page source is overwrited
+- - Introduced by 3.7.6
+- 
+- 
+
+## v3.7.6  (Discontinued)
+
+## v3.7.5
+
+- Fix: Draw.io diagrams rendered twice
+- Fix: Behavior of password reset modal is strange
+- Fix: Import GROWI Archive doesn't restore some data correctly
+- Fix: Attachments list on root page and users top pages
+- Fix: Trash page is no longer editable
+- Fix: Rendering Timeline on /trash
+
+## v3.7.4
+
+- Fix: Broken by displaying user image
+
+## v3.7.3
+
+- Feature: Profile Image Cropping
+- Improvement: Reactify users pages
+- Improvement: Detect language and adjust the order of first and last names when creating accounts in OAuth
+- Fix: Installation is broken when selecting Japanese
+- - Introduced by 3.7.0
+- 
+- 
+- Fix: Mathjax Rendering is unstable (workaround)
+- - Introduced by 3.7.0
+- 
+- 
+- Fix: Notification Setting couldn't update without slack token
+- - Introduced by 3.6.6
+- 
+- 
+- Support: Add GROWI Contributers
+
+## v3.7.2
+
+- Feature: User Management Filtering/Sort
+- Feature: Show env vars on Admin pages
+- Fix: Attachment row z-index
+- I18n: HackMD integration alert
+
+## v3.7.1
+
+- Improvement: Add an option that make it possible to choose what to send notifications
+- Improvement: Add the env var `DRAWIO_URI`
+- Improvement: Accessibility for 'spring' theme
+- Improvement: Editor scroll sync behaves strangely when using draw.io blocks
+- Fix: Coudn't upload file on Comment Editor
+- - Introduced by 3.5.8
+- 
+- 
+- I18n: HackMD integration
+
+## v3.7.0
+
+### BREAKING CHANGES
+
+None.
+
+Upgrading Guide: [https://docs.growi.org/en/admin-guide/upgrading/37x.html](https://docs.growi.org/en/admin-guide/upgrading/37x.html)
+
+### Updates
+
+- Feature: [Draw.io](https://www.draw.io/) Integration
+- Feature: SAML Attribute-based Login Control
+- Improvement: Reactify admin pages (Security)
+- Improvement: Behavior of pre-editing screen of HackMD when user needs to resume
+
+## v3.6.10
+
+- Fix: Redirect logic for users except for actives
+- - Introduced by 3.6.9
+- 
+- 
+
+## v3.6.9
+
+- Improvement: Redirection when login/logout
+- Improvement: Add home icon before '/'
+- Fix: Client crashed when the first login
+- - Introduced by 3.6.8
+- 
+- 
+
+## v3.6.8
+
+- Improvement: Show page history side-by-side
+- Improvement: Optimize markdown rendering
+- Improvement: Reactify admin pages (Navigation)
+- Fix: Reply comments collapsed are broken
+- - Introduced by 3.6.7
+- 
+- 
+- Support: Update libs
+- - cross-env
+- 
+- - mkdirp
+- 
+- - diff2html
+- 
+- - jest
+- 
+- - stylelint
+- 
+- 
+
+## v3.6.7
+
+- Feature: Anchor link for comments
+- Improvement: Show error toastr when saving page is failed because of empty document
+- Fix: Admin Customise couldn't restore stored config value
+- - Introduced by 3.6.2
+- 
+- 
+- Fix: Admin Customise missed preview functions
+- - Introduced by 3.6.2
+- 
+- 
+- Fix: AWS doesn't work
+- - Introduced by 3.6.4
+- 
+- 
+- Fix: Ensure not to get unrelated indices information in Elasticsearch Management
+- - Introduced by 3.6.6
+- 
+- 
+- Support: Optimize bundles
+- Support: Optimize build-prod job with caching node_modules/.cache
+
+## v3.6.6
+
+- Feature: Reconnect to Elasticsearch from Full Text Search Management
+- Feature: Normalize indices of Elasticsearch from Full Text Search Management
+- Improvement: Add 'spring' theme
+- Improvement: Reactify admin pages (Notification)
+- Impromvement: Add `checkMiddlewaresStrictly` option to Healthcheck API
+- Improvement: Accessibility for History component under dark themes
+- Fix: Warning on client console when developing /admin/app
+- Support: Upgrade libs
+- - react-bootstrap-typeahead
+- 
+- 
+
+## v3.6.5 (Discontinued)
+
+## v3.6.4
+
+- Feature: Alert for stale page
+- Improvement: Reactify admin pages (Home)
+- Improvement: Reactify admin pages (App)
+- Improvement: Accessibility for editor icons of dark themes
+- Improvement: Accessibility for importing table data pane
+- Improvement: Resolve username and email when logging in with Google OAuth
+
+## v3.6.3
+
+- Improvement: Searching users in UserGroup Management
+- Fix: Repair google authentication by migrating to jaredhanson/passport-google-oauth2
+- Fix: Markdown Settings are broken by the button to import recommended settings
+- Support: Upgrade libs
+- - check-node-version
+- 
+- - file-loader
+- 
+- - mini-css-extract-plugin
+- 
+- 
+
+## v3.6.2
+
+- Improvement: Reactify admin pages (Customize)
+- Improvement: Ensure not to consider `[text|site](https://example.com]` as a row in the table
+- Improvement: Enter key behavior in markdown table
+- Fix: Pre-installed plugins in official docker image are not detected
+- - Introduced by 3.6.0
+- 
+- 
+- Fix: Emoji Autocomplete window does not float correctly
+- - Introduced by 3.5.0
+- 
+- 
+
+## v3.6.1
+
+### BREAKING CHANGES
+
+- GROWI v3.6.x no longer support Node.js v8.x
+- The name of database that is storing migrations meta data has been changed
+- - This affects **only when `MONGO_URI` has parameters**
+- 
+- - v3.5.x or above has a bug ([#1361](https://github.com/weseek/growi/issues/1361))
+- 
+- 
+
+Upgrading Guide: [https://docs.growi.org/en/admin-guide/upgrading/36x.html](https://docs.growi.org/en/admin-guide/upgrading/36x.html)
+
+### Updates
+
+- Improvement: Drop unnecessary MongoDB collection indexes
+- Improvement: Accessibility of Antarctic theme
+- Improvement: Reactify admin pages (Markdown Settings)
+- Fix: Appending tag is failed by wrong index of PageTagRelation
+- - Introduced by 3.5.20
+- 
+- 
+- Fix: Pages without heading slash is invalid but creatable
+- Fix: Connect to Elasticsearch with `httpAuth` param
+- Support: Support Node.js v12
+- Support: Optimize build in dev with hard-source-webpack-plugin
+- Support: Upgrade libs
+- - growi-commons
+- 
+- 
+
+## v3.6.0 (Discontinued)
+
+## v3.5.25
+
+- Improvement: Disable ESC key to close Handsontable Modal
+- Fix: Exported data of empty collection is broken
+- Fix: Some components crash after when the page with attachment has exported/imported
+
+## v3.5.24
+
+- Fix: Plugins are not working on Heroku
+
+## v3.5.23
+
+- Fix: Global Notification failed to send e-mail
+- Fix: Pagination is not working for trash list
+- Fix: Healthcheck API with `?connectToMiddlewares` returns error
+- Support: Upgrade libs
+- - growi-commons
+- 
+- 
+
+## v3.5.22
+
+- Improvement: Add `FILE_UPLOAD_DISABLED` env var
+
+## v3.5.21
+
+- Improvement: Cache control when retrieving attachment data
+- Fix: Inviting user doesn't work
+- - Introduced by 3.5.20
+- 
+- 
+
+## v3.5.20
+
+- Improvement: Organize MongoDB collection indexes uniqueness
+- Improvement: Reactify admin pages (External Account Management)
+- Fix: Search result or Timeline shows loading icon eternally when retrieving not accessible page
+- Support: Use SearchBox Elasticsearch Addon on Heroku
+- Support: Upgrade libs
+- - cross-env
+- 
+- - eslint-plugin-jest
+- 
+- - i18next
+- 
+- - i18next-browser-languagedetector
+- 
+- - migrate-mongo
+- 
+- - react-i18next
+- 
+- - validator
+- 
+- 
+
+## v3.5.19 (Discontinued)
+
+## v3.5.18
+
+- Improvement: Import GROWI Archive
+- - Process asynchronously
+- 
+- - Collection configurations
+- 
+- - Selectable mode (insert/upsert/flush and insert)
+- 
+- - Safely mode settings for configs and users collections
+- 
+- - Show errors view
+- 
+- 
+- Improvement: Optimize handling promise of stream when exporting archive
+- Improvement: Optimize handling promise of stream when building indices
+- Improvement: Add link to [docs.growi.org](https://docs.growi.org)
+- Fix: Monospace font code is broken when printing on Mac
+
+## v3.5.17
+
+- Feature: Upload to GCS (Google Cloud Storage)
+- Feature: Statistics API
+- Improvement: Optimize exporting
+- Improvement: Show progress bar when exporting
+- Improvement: Validate collection combinations when importing
+- Improvement: Reactify admin pages
+- Fix: Use HTTP PlantUML URL in default
+- - Introduced by 3.5.12
+- 
+- 
+- Fix: Config default values
+- Support: REPL with `console` npm scripts
+
+## v3.5.16
+
+- Fix: Full Text Search doesn't work after when building indices
+- - Introduced by 3.5.12
+- 
+- 
+
+## v3.5.15
+
+- Feature: Import/Export Page data
+- Fix: The link to Sandbox on Markdown Help Modal doesn't work
+- Support: Upgrade libs
+- - codemirror
+- 
+- 
+
+## v3.5.14 (Discontinued)
+
+## v3.5.13
+
+- Feature: Re-edit comments
+- Support: [growi-plugin-attachment-refs](https://github.com/weseek/growi-plugin-attachment-refs)
+- Support: Upgrade libs
+- - entities
+- 
+- - markdown-it
+- 
+- 
+
+## v3.5.12
+
+- Improvement: Use Elasticsearch Alias
+- Improvement: Connect to HTTPS PlantUML URL in default
+- Fix: Global Notification doesn't work after updating Webhook URL
+- Fix: User Trigger Notification is not be sent when channel is not specified
+- Support: Upgrade libs
+- - terser-webpack-plugin
+- 
+- 
+
+## v3.5.11
+
+- Fix: HackMD Editor shows 404 error when HackMD redirect to fqdn URI
+- - Introduced by 3.5.8
+- 
+- 
+- Fix: Timeline doesn't work
+- - Introduced by 3.5.1
+- 
+- 
+- Fix: Last Login field does not shown in /admin/user
+- Support: Upgrade libs
+- - env-cmd
+- 
+- - sass-loader
+- 
+- - webpack
+- 
+- - webpack-cli
+- 
+- - webpack-merge
+- 
+- 
+
+## v3.5.10
+
+- Feature: Send Global Notification with Slack
+- Improvement: Show loading spinner when fetching page history data
+- Improvement: Hierarchical page link when the page is in /Trash
+- Fix: Code Highlight Theme does not change
+- - Introduced by 3.5.2
+- 
+- 
+- Support: Upgrade libs
+- - date-fns
+- 
+- - eslint-config-weseek
+- 
+- 
+
+## v3.5.9
+
+- Fix: Editing table with Spreadsheet like GUI (Handsontable) is failed
+- Fix: Plugins are not initialized when first launching
+- - Introduced by 3.5.0
+- 
+- 
+- Support: Upgrade libs
+- - entities
+- 
+- - growi-commons
+- 
+- - openid-client
+- 
+- - rimraf
+- 
+- - style-loader
+- 
+- 
+
+## v3.5.8
+
+- Improvement: Controls when HackMD/CodiMD has unsaved draft
+- Improvement: Show hints if HackMD/CodiMD integration is not working
+- Improvement: GROWI server obtains HackMD/CodiMD page id from the 302 response header
+- Improvement: Comment Thread Layout
+- Improvement: Show commented date with date distance format
+
+## v3.5.7 (Discontinued)
+
+## v3.5.6
+
+- Fix: Saving new page is failed when empty string tag is set
+- Fix: Link of Create template page button in New Page Modal is broken
+- Fix: Global Notification dows not work when creating/moving/deleting/like/comment
+
+## v3.5.5
+
+- Feature: Support S3-compatible object storage (e.g. MinIO)
+- Feature: Enable/Disable ID/Password Authentication
+- Improvement: Login Mechanism with HTTP Basic Authentication header
+- Improvement: Reactify Table Of Contents
+- Fix: Profile images are broken in User Management
+- Fix: Template page under root page doesn't work
+- Support: Upgrade libs
+- - csv-to-markdown-table
+- 
+- - express-validator
+- 
+- - markdown-it
+- 
+- - mini-css-extract-plugin
+- 
+- - react-hotkeys
+- 
+- 
+
+## v3.5.4
+
+- Fix: List private pages wrongly
+- Fix: Global Notification Trigger Path does not parse glob correctly
+- Fix: Consecutive page deletion requests cause unexpected complete page deletion
+
+## v3.5.3
+
+- 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
+
+## v3.5.2
+
+- Feature: Remain metadata option when Move/Rename page
+- 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: Consider timezone on admin page
+- Fix: Editor doesn't work on Microsoft Edge
+- Support: Upgrade libs
+- - growi-commons
+- 
+- 
+
+## v3.5.1
+
+### BREAKING CHANGES
+
+- GROWI no longer supports
+- - Protection system with Basic Authentication
+- 
+- - Crowi Classic Authentication Mechanism
+- 
+- - [Crowi Template syntax](https://medium.com/crowi-book/crowi-v1-5-0-5a62e7c6be90)
+- 
+- 
+- GROWI no lonnger supports plugins with schema version 2
+- - Upgrade [weseek/growi-plugin-lsx](https://github.com/weseek/growi-plugin-lsx) to v3.0.0 or above
+- 
+- - Upgrade [weseek/growi-plugin-pukiwiki-like-linker
+- 
+- - ](https://github.com/weseek/growi-plugin-pukiwiki-like-linker) to v3.0.0 or above
+- 
+- 
+- The restriction mode of the root page (`/`) will be set 'Public'
+- The restriction mode of the root page (`/`) can not be changed after v 3.5.1
+
+Upgrading Guide: [https://docs.growi.org/en/admin-guide/upgrading/35x.html](https://docs.growi.org/en/admin-guide/upgrading/35x.html)
+
+### Updates
+
+- Feature: Comment Thread
+- Feature: OpenID Connect authentication
+- Feature: HTTP Basic authentication
+- Feature: Staff Credits with [Konami Code](https://en.wikipedia.org/wiki/Konami_Code)
+- Feature: Restricte Complete Deletion of Pages
+- Improvement Draft list
+- Fix: Deleting page completely
+- Fix: Search with `prefix:` param with CJK pathname
+- Fix: Could not edit UserGroup even if `PUBLIC_WIKI_ONLY` is not set
+- I18n: User Management Details
+- I18n: Group Management Details
+- Support: Apply unstated
+- Support: Use Babel 7
+- Support: Support plugins with schema version 3
+- Support: Abolish Old Config API
+- Support: Apply Jest for Tests
+- Support: Upgrade libs
+- - async
+- 
+- - axios
+- 
+- - connect-mongo
+- 
+- - css-loader
+- 
+- - eslint
+- 
+- - eslint-config-weseek
+- 
+- - eslint-plugin-import
+- 
+- - eslint-plugin-jest
+- 
+- - eslint-plugin-react
+- 
+- - file-loader
+- 
+- - googleapis
+- 
+- - i18next
+- 
+- - migrate-mongo
+- 
+- - mini-css-extract-plugin
+- 
+- - mongoose
+- 
+- - mongoose-gridfs
+- 
+- - mongoose-unique-validator
+- 
+- - null-loader
+- 
+- 
+
+## v3.5.0 (Discontinued)
+
+## v3.4.7
+
+- Improvement: Handle private pages on group deletion
+- Fix: Searching with `tag:xxx` syntax doesn't work
+- Fix: Check CSRF when updating user data
+- Fix: `createdAt` field initialization
+- I18n: Import data page
+- I18n: Group Management page
+
+## v3.4.6
+
+- Feature: Tags
+- Feature: Dropdown to copy page path/URL/MarkdownLink
+- Feature: List of drafts
+- Improvement: Replace icons of Editor Tool Bar
+- Improvement: Show display name when mouse hover to user image
+- Fix: URL in slack message is broken on Safari
+- Fix: Registration does not work when basic auth is enabled
+- Support: Publish API docs with swagger-jsdoc and ReDoc
+- Support: Upgrade libs
+- - cmd-env
+- 
+- - elasticsearch
+- 
+- - mongoose-gridfs
+- 
+- - node-dev
+- 
+- - null-loader
+- 
+- - react-codemirror
+- 
+- 
+
+## v3.4.5
+
+- Improvement: Pass autolink through the XSS filter according to CommonMark Spec
+- Fix: Update ElasticSearch index when deleting/duplicating pages
+- Fix: Xss filter breaks PlantUML arrows
+- Support: Support growi-plugin-lsx@2.2.0
+- Support: Upgrade libs
+- - growi-commons
+- 
+- - xss
+- 
+- 
+
+## v3.4.4
+
+- Fix: Comment component doesn't work
+
+## v3.4.3
+
+- Improvement: Add 'antarctic' theme
+- Support Apply eslint-config-airbnb based rules
+- Support Apply prettier and stylelint
+- Support: Upgrade libs
+- - csrf
+- 
+- - escape-string-regexp
+- 
+- - eslint
+- 
+- - express-session
+- 
+- - googleapis
+- 
+- - growi-commons
+- 
+- - i18next
+- 
+- - mini-css-extract-plugin
+- 
+- - nodemailer
+- 
+- - penpal
+- 
+- - react-i18next
+- 
+- - string-width
+- 
+- 
+
+## v3.4.2
+
+- Fix: Nofitication to Slack doesn't work
+- - Introduced by 3.4.0
+- 
+- 
+
+## v3.4.1
+
+- Fix: "Cannot find module 'stream-to-promise'" occured when build client with `FILE_UPLOAD=local`
+
+## v3.4.0
+
+### BREAKING CHANGES
+
+None.
+
+Upgrading Guide: [https://docs.growi.org/en/admin-guide/upgrading/34x.html](https://docs.growi.org/en/admin-guide/upgrading/34x.html)
+
+### Updates
+
+- Improvement: Restrict to access attachments when the user is not allowed to see page
+- Improvement: Show fans and visitors of page
+- Improvement: Full text search tokenizing
+- Improvement: Markdown comment on Crowi Classic Layout
+- Fix: Profile image is not displayed when `FILE_UPLOAD=mongodb`
+- Fix: Posting comment doesn't work under Crowi Classic Layout
+- - Introduced by 3.1.5
+- 
+- 
+- Fix: HackMD doesn't work when `siteUrl` ends with slash
+- Fix: Ensure not to be able to move/duplicate page to the path which has trailing slash
+- Support: Launch with Node.js v10
+- Support: Launch with MongoDB 3.6
+- Support: Launch with Elasticsearch 6.6
+- Support: Upgrade libs
+- - bootstrap-sass
+- 
+- - browser-sync
+- 
+- - react
+- 
+- - react-dom
+- 
+- 
+
+## v3.3.10
+
+- Feature: PlantUML and Blockdiag on presentation
+- Improvement: Render slides of presentation with GrowiRenderer
+- Fix: Unportalizing doesn't work
+- Support: Use mini-css-extract-plugin instead of extract extract-text-webpack-plugin
+- Support: Use terser-webpack-plugin instead of uglifyjs-webpack-plugin
+- Support: Upgrade libs
+- - csv-to-markdown-table
+- 
+- - file-loader
+- 
+- - googleapis
+- 
+- - i18next-browser-languagedetector
+- 
+- - mocha
+- 
+- - react-waypoint
+- 
+- - webpack
+- 
+- - webpack-assets-manifest
+- 
+- - webpack-cli
+- 
+- - webpack-merge
+- 
+- 
+
+## v3.3.9
+
+- Fix: Import from Qiita:Team doesn't work
+- - Introduced by 3.3.0
+- 
+- 
+- Fix: Typeahead shows autocomplete wrongly
+- - Introduced by 3.3.8
+- 
+- 
+- Support: Upgrade libs
+- - react-bootstrap-typeahead
+- 
+- 
+
+## v3.3.8
+
+- Fix: Move/Duplicate don't work
+- - Introduced by 3.3.7
+- 
+- 
+- Fix: Server doesn't respond when root page is restricted
+- Support: Upgrade libs
+- - react
+- 
+- - react-bootstrap-typeahead
+- 
+- 
+
+## v3.3.7
+
+- Feature: Editor toolbar
+- Feature: `prefix:/path` searching syntax to filter with page path prefix
+- Feature: Add an option to filter only children to searching box of navbar
+- Improvement: Suggest page path when moving/duplicating/searching
+- Fix: Anonymous users couldn't search
+- - Introduced by 3.3.6
+- 
+- 
+- I18n: Searching help
+- Support: Prepare to suppoert Node.js v10
+- Support: Upgrade libs
+- - node-sass
+- 
+- 
+
+## v3.3.6
+
+- Improvement: Site URL settings must be set
+- Improvement: Site URL settings can be set with environment variable
+- Fix: "Anyone with the link" ACL doesn't work correctly
+- - Introduced by 3.3.0
+- 
+- 
+- Fix: Related pages list of /admin/user-group-detail/xxx doesn't show anything
+- - Introduced by 3.3.0
+- 
+- 
+- Fix: Diff of revision contents doesn't appeared when notifing with slack
+- Fix: NPE occured on /admin/security when Crowi Classic Auth Mechanism is set
+- Fix: Coudn't render Timing Diagram with PlantUML
+- I18n: Cheatsheet for editor
+- I18n: Some admin pages
+- Support: Upgrade libs
+- - diff
+- 
+- - markdown-it-plantuml
+- 
+- - mongoose
+- 
+- - nodemailer
+- 
+- - mongoose-gridfs
+- 
+- - sinon
+- 
+- - sinon-chai
+- 
+- 
+
+## v3.3.5 (Discontinued)
+
+## v3.3.4
+
+- Improvement: SAML configuration with environment variables
+- Improvement: Upload file with pasting from clipboard
+- Fix: `/_api/revisions.get` doesn't populate author data correctly
+- Fix: Wrong OAuth callback url are shown at admin page
+- Fix: Connecting to MongoDB failed when processing migration
+- Support: Get ready to use new config management system
+
+## v3.3.3
+
+- Feature: Show line numbers to a code block
+- Feature: Bulk update the scope of descendant pages when create/update page
+- Improvement: The scope of ascendant page will be retrieved and set to controls in advance when creating a new page
+- Fix: Pages that is restricted by groups couldn't be shown in search result page
+- Fix: Pages order in search result page was wrong
+- Fix: Guest user can't search
+- Fix: Possibility that ExternalAccount deletion processing selects incorrect data
+- Support: Upgrade libs
+- - bootstrap-sass
+- 
+- - i18next
+- 
+- - migrate-mongo
+- 
+- - string-width
+- 
+- 
+
+## v3.3.2
+
+- Fix: Specified Group ACL is not persisted correctly
+- - Introduced by 3.3.0
+- 
+- 
+
+## v3.3.1
+
+- Feature: NO_CDN Mode
+- Feature: Add option to show/hide restricted pages in list
+- Feature: MongoDB GridFS quota
+- Improvement: Refactor Access Control
+- Improvement: Checkbox behavior of task list
+- Improvement: Fixed search input on search result page
+- Improvement: Add 'christmas' theme
+- Improvement: Select default language of new users
+- Fix: Hide restricted pages contents in timeline
+- Support: Upgrade libs
+- - googleapis
+- 
+- - passport-saml
+- 
+- 
+
+## v3.3.0 (Discontinued)
+
+## v3.2.10
+
+- Fix: Pages in trash are available to create
+- Fix: Couldn't create portal page under Crowi Classic Behavior
+- Fix: Table tag in Timeline/SearchResult missed border and BS3 styles
+- I18n: Installer
+
+## v3.2.9
+
+- Feature: Attachment Storing to MongoDB GridFS
+- Fix: row/col moving of Spreadsheet like GUI (Handsontable) doesn't work
+- Fix: Emoji AutoComplete dialog pops up at wrong position
+- Support: Upgrade libs
+- - codemirror
+- 
+- - react-codemirror2
+- 
+- 
+
+## v3.2.8
+
+- Improvement: Add an option to use email for account link when using SAML federation
+- Fix: Editor layout is sometimes broken
+- Fix: Normalize table data for Spreadsheet like GUI (Handsontable) when import
+- Support: Improve development environment
+- Support: Upgrade libs
+- - googleapis
+- 
+- - react-dropzone
+- 
+- 
+
+## v3.2.7
+
+- Feature: Import CSV/TSV/HTML table on Spreadsheet like GUI (Handsontable)
+- Fix: Pasting table data copied from Excel includes unnecessary line breaks
+- Fix: Page break Preset 1 for Presentation mode is broken
+- Fix: Login Form when LDAP login failed caused 500 Internal Server Error
+
+## v3.2.6
+
+- Feature: Add select alignment buttons of Spreadsheet like GUI (Handsontable)
+- Improvement: Shrink the rows that have no diff of revision history page
+- Fix: Login form rejects weak password
+- Fix: An error occured by uploading attachment file when the page is not exists
+- - Introduced by 2.3.5
+- 
+- 
+- Support: Upgrade libs
+- - i18next-express-middleware
+- 
+- - i18next-node-fs-backend
+- 
+- - i18next-sprintf-postprocessor
+- 
+- 
+
+## v3.2.5
+
+- Improvement: Expandable Spreadsheet like GUI (Handsontable)
+- Improvement: Move/Resize rows/columns of Spreadsheet like GUI (Handsontable)
+- Improvement: Prevent XSS of New Page modal
+- Fix: Recent Created tab of user home shows wrong page list
+- - Introduced by 3.2.4
+- 
+- 
+- Support: Upgrade libs
+- - @handsontable/react
+- 
+- - handsontable
+- 
+- - metismenu
+- 
+- - sinon
+- 
+- 
+
+## v3.2.4
+
+- Feature: Edit table with Spreadsheet like GUI (Handsontable)
+- Feature: Paging recent created in users home
+- Improvement: Specify certificate for SAML Authentication
+- Fix: SAML Authentication didn't work
+- - Introduced by 3.2.2
+- 
+- 
+- Fix: Failed to create new page with title which includes RegEx special characters
+- Fix: Preventing XSS Settings are not applied in default
+- - Introduced by 3.1.12
+- 
+- 
+- Support: Mongoose migration mechanism
+- Support: Upgrade libs
+- - googleapis
+- 
+- - mocha
+- 
+- - mongoose
+- 
+- - mongoose-paginate
+- 
+- - mongoose-unique-validator
+- 
+- - multer
+- 
+- 
+
+## v3.2.3
+
+- Feature: Kibela like layout
+- Improvement: Custom newpage separator for presentation view
+- Support: Shrink image size for themes which recently added
+
+## v3.2.2
+
+- Feature: SAML Authentication (SSO)
+- Improvement: Add 'wood' theme
+- Improvement: Add 'halloween' theme
+- Improvement: Add 'island' theme
+- Fix: Sending email function doesn't work
+- Support Upgrade libs
+- - style-loader
+- 
+- 
+
+## v3.2.1
+
+- Feature: Import data from esa.io
+- Feature: Import data from Qiita:Team
+- Feature: Add the endpoint for health check
+- Improvement: Adjust styles when printing
+- Fix: Renaming page doesn't work if the page was saved with shortcut
+- Support: Refactor directory structure
+- Support Upgrade libs
+- - file-loader
+- 
+- - googleapis
+- 
+- - postcss-loader
+- 
+- - sass-loader
+- 
+- - style-loader
+- 
+- 
+
+## v3.2.0
+
+- Feature: HackMD integration so that user will be able to simultaneously edit with multiple people
+- Feature: Login with Twitter Account (OAuth)
+- Fix: The Initial scroll position is wrong when reloading the page
+
+## v3.1.14
+
+- Improvement: Show help for header search box
+- Improvement: Add Markdown Cheatsheet to Editor component
+- Fix: Couldn't delete page completely from search result page
+- Fix: Tabs of trash page are broken
+
+## v3.1.13
+
+- Feature: Global Notification
+- Feature: Send Global Notification with E-mail
+- Improvement: Add attribute mappings for email to LDAP settings
+- Support: Upgrade libs
+- - autoprefixer
+- 
+- - css-loader
+- 
+- - method-override
+- 
+- - optimize-css-assets-webpack-plugin
+- 
+- - react
+- 
+- - react-bootstrap-typeahead
+- 
+- - react-dom
+- 
+- 
+
+## v3.1.12
+
+- Feature: Add XSS Settings
+- Feature: Notify to Slack when comment
+- Improvement: Prevent XSS in various situations
+- Improvement: Show forbidden message when the user accesses to ungranted page
+- Improvement: Add overlay styles for pasting file to comment form
+- Fix: Omit unnecessary css link
+- - Introduced by 3.1.10
+- 
+- 
+- Fix: Invitation mail do not be sent
+- Fix: Edit template button on New Page modal doesn't work
+
+## v3.1.11
+
+- Fix: OAuth doesn't work in production because callback URL field cannot be specified
+- - Introduced by 3.1.9
+- 
+- 
+
+## v3.1.10
+
+- Fix: Enter key on react-bootstrap-typeahead doesn't submit
+- - Introduced by 3.1.9
+- 
+- 
+- Fix: CodeMirror of `/admin/customize` is broken
+- - Introduced by 3.1.9
+- 
+- 
+
+## v3.1.9
+
+- Feature: Login with Google Account (OAuth)
+- Feature: Login with GitHub Account (OAuth)
+- Feature: Attach files in Comment
+- Improvement: Write comment with CodeMirror Editor
+- Improvement: Post comment with `Ctrl-Enter`
+- Improvement: Place the commented page at the beginning of the list
+- Improvement: Resolve errors on IE11 (Experimental)
+- Support: Migrate to webpack 4
+- Support: Upgrade libs
+- - eslint
+- 
+- - react-bootstrap-typeahead
+- 
+- - react-codemirror2
+- 
+- - webpack
+- 
+- 
+
+## v3.1.8 (Discontinued)
+
+## v3.1.7
+
+- Fix: Update hidden input 'pageForm[grant]' when save with `Ctrl-S`
+- Fix: Show alert message when conflict
+- Fix: `BLOCKDIAG_URI` environment variable doesn't work
+- Fix: Paste in markdown list doesn't work correctly
+- Support: Ensure to inject logger configuration from environment variables
+- Support: Upgrade libs
+- - sinon
+- 
+- - sinon-chai
+- 
+- 
+
+## v3.1.6
+
+- Feature: Support [blockdiag](http://blockdiag.com)
+- Feature: Add `BLOCKDIAG_URI` environment variable
+- Fix: Select modal for group is not shown
+- Support: Upgrade libs
+- - googleapis
+- 
+- - throttle-debounce
+- 
+- 
+
+## v3.1.5
+
+- Feature: Write comment with Markdown
+- Improvement: Support some placeholders for template page
+- Improvement: Omit unnecessary response header
+- Improvement: Support LDAP attribute mappings for user's full name
+- Improvement: Enable to scroll revision-toc
+- Fix: Posting to Slack doesn't work
+- - Introduced by 3.1.0
+- 
+- 
+- Fix: page.rename api doesn't work
+- Fix: HTML escaped characters in markdown are unescaped unexpectedly after page is saved
+- Fix: sanitize `#raw-text-original` content with 'entities'
+- Fix: Double newline character posted
+- - Introduced by 3.1.4
+- 
+- 
+- Fix: List and Comment components do not displayed
+- - Introduced by 3.1.4
+- 
+- 
+- Support: Upgrade libs
+- - markdown-it-toc-and-anchor-with-slugid
+- 
+- 
+
+## v3.1.4 (Discontinued)
+
+## v3.1.3 (Discontinued)
+
+## v3.1.2
+
+- Feature: Template page
+- 
+- Improvement: Add 'future' theme
+- 
+- Improvement: Modify syntax for Crowi compatible template feature
+- 
+- - *before*
+- 
+- - 
+- 
+- - ```~~~markdown
+- - 
+- - ```
+- 
+- - ```template:/page/name
+- - 
+- - ```
+- 
+- - page contents
+- 
+- - ```
+- - 
+- - ```
+- 
+- - ```
+- - 
+- - ```
+- 
+- - ```
+- - 
+- - ```
+- 
+- - *after*
+- 
+- - 
+- 
+- - ```~~~plane
+- - 
+- - ```
+- 
+- - ::: template:/page/name
+- 
+- - page contents
+- 
+- - :::
+- 
+- - ```
+- - 
+- - ```
+- 
+- - ```
+- - 
+- - ```
+- 
+- 
+- Improvement: Escape iframe tag in block codes
+- 
+- Support: Upgrade libs
+- 
+- - assets-webpack-plugin
+- 
+- - googleapis
+- 
+- - react-clipboard.js
+- 
+- - xss
+- 
+- 
+
+## v3.1.1
+
+- Improvement: Add 'blue-night' theme
+- Improvement: List up pages which restricted for Group ACL
+- Fix: PageGroupRelation didn't remove when page is removed completely
+
+## v3.1.0
+
+- Improvement: Group Access Control List - Select group modal
+- Improvement: Better input on mobile
+- Improvement: Detach code blocks correctly
+- Improvement: Auto-format markdown table which includes multibyte text
+- Improvement: Show icon when auto-format markdown table is activated
+- Improvement: Enable to switch show/hide border for highlight.js
+- Improvement: BindDN field allows also ActiveDirectory styles
+- Improvement: Show LDAP logs when testing login
+- Fix: Comment body doesn't break long terms
+- Fix: lsx plugin lists up pages that hit by forward match wrongly
+- - Introduced by 3.0.4
+- 
+- 
+- Fix: Editor is broken on IE11
+- Support: Multilingualize React components with i18next
+- Support: Organize dependencies
+- Support: Upgrade libs
+- - elasticsearch
+- 
+- - googleapis
+- 
+- 
+
+## v3.0.13
+
+- Improvement: Add Vim/Emacs/Sublime-Text icons for keybindings menu
+- Improvement: Add 'mono-blue' theme
+- Fix: Unportalize process failed silently
+- Fix: Sidebar breaks editor layouts
+- Support: Switch the logger from 'pino' to 'bunyan'
+- Support: Set the alias for 'debug' to the debug function of 'bunyan'
+- Support: Translate `/admin/security`
+- Support: Optimize bundles
+- - upgrade 'markdown-it-toc-and-anchor-with-slugid' and omit 'uslug'
+- 
+- 
+- Support: Optimize .eslintrc.js
+
+## v3.0.12
+
+- Feature: Support Vim/Emacs/Sublime-Text keybindings
+- Improvement: Add some CodeMirror themes (Eclipse, Dracula)
+- Improvement: Dynamic loading for CodeMirror theme files from CDN
+- Improvement: Prevent XSS when move/redirect/duplicate
+
+## v3.0.11
+
+- Fix: login.html is broken in iOS
+- Fix: Removing attachment is crashed
+- Fix: File-attaching error after new page creation
+- Support: Optimize development build
+- Support: Upgrade libs
+- - env-cmd
+- 
+- - googleapis
+- 
+- - sinon
+- 
+- 
+
+## v3.0.10
+
+- Improvement: Add 'nature' theme
+- Fix: Page list and Timeline layout for layout-growi
+- Fix: Adjust theme colors
+- - Introduced by 3.0.9
+- 
+- 
+
+## v3.0.9
+
+- Fix: Registering new LDAP User is failed
+- - Introduced by 3.0.6
+- 
+- 
+- Support: Organize scss for overriding bootstrap variables
+- Support: Upgrade libs
+- - codemirror
+- 
+- - react-codemirror2
+- 
+- - normalize-path
+- 
+- - style-loader
+- 
+- 
+
+## v3.0.8
+
+- Improvement: h1#revision-path occupies most of the screen when the page path is long
+- Improvement: Ensure not to save concealed email field to localStorage
+- Fix: Cannot input "c" and "e" on iOS
+
+## v3.0.7
+
+- Improvement: Enable to download an attached file with original name
+- Improvement: Use MongoDB for session store instead of Redis
+- Improvement: Update dropzone overlay icons and styles
+- Fix: Dropzone overlay elements doesn't show
+- - Introduced by 3.0.0
+- 
+- 
+- Fix: Broken page path of timeline
+- - Introduced by 3.0.4
+- 
+- 
+
+## v3.0.6
+
+- Improvement: Automatically bind external accounts newly logged in to local accounts when username match
+- Improvement: Simplify configuration for Slack Web API
+- Support: Use 'slack-node' instead of '@slack/client'
+- Support: Upgrade libs
+- - googleapis
+- 
+- - i18next
+- 
+- - i18next-express-middleware
+- 
+- - react-bootstrap-typeahead
+- 
+- - sass-loader
+- 
+- - uglifycss
+- 
+- 
+
+## v3.0.5
+
+- Improvement: Update lsx icons and styles
+- Fix: lsx plugins doesn't show page names
+
+## v3.0.4
+
+- Improvement: The option that switch whether add h1 section when create new page
+- Improvement: Encode page path that includes special character
+- Fix: Page-saving error after new page creation
+
+## v3.0.3
+
+- Fix: Login page is broken in iOS
+- Fix: Hide presentation tab if portal page
+- Fix: A few checkboxes doesn't work
+- - Invite user check with email in `/admin/user`
+- 
+- - Recursively check in rename modal
+- 
+- - Redirect check in rename modal
+- 
+- 
+- Fix: Activating invited user form url is wrong
+- Support: Use postcss-loader and autoprefixer
+
+## v3.0.2
+
+- Feature: Group Access Control List
+- Feature: Add site theme selector
+- Feature: Add a control to switch whether email shown or hidden by user
+- Feature: Custom title tag content
+- Fix: bosai version
+- Support: Rename to GROWI
+- Support: Add dark theme
+- Support: Refreshing bootstrap theme and icons
+- Support: Use Browsersync instead of easy-livereload
+- Support: Upgrade libs
+- - react-bootstrap
+- 
+- - react-bootstrap-typeahead
+- 
+- - react-clipboard.js
+- 
+- 
+
+## v3.0.1 (Discontinued)
+
+## v3.0.0 (Discontinued)
+
+## v2.4.4
+
+- Feature: Autoformat Markdown Table
+- Feature: highlight.js Theme Selector
+- Fix: The bug of updating numbering list by codemirror
+- Fix: Template LangProcessor doesn't work
+- - Introduced by 2.4.0
+- 
+- 
+- Support: Apply ESLint
+- Support: Upgrade libs
+- - react, react-dom
+- 
+- - codemirror, react-codemirror2
+- 
+- 
+
+## v2.4.3
+
+- Improvement: i18n in `/admin`
+- Improvement: Add `SESSION_NAME` environment variable
+- Fix: All Elements are cleared when the Check All button in DeletionMode
+- Support: Upgrade libs
+- - uglifycss
+- 
+- - sinon-chai
+- 
+- 
+
+## v2.4.2
+
+- Improvement: Ensure to set absolute url from root when attaching files when `FILE_UPLOAD=local`
+- Fix: Inline code blocks that includes doller sign are broken
+- Fix: Comment count is not updated when a comment of the page is deleted
+- Improvement: i18n in `/admin` (WIP)
+- Support: Upgrade libs
+- - googleapis
+- 
+- - markdown-it-plantuml
+- 
+- 
+
+## v2.4.1
+
+- Feature: Custom Header HTML
+- Improvement: Add highlight.js languages
+- - dockerfile, go, gradle, json, less, scss, typescript, yaml
+- 
+- 
+- Fix: Couldn't connect to PLANTUML_URI
+- - Introduced by 2.4.0
+- 
+- 
+- Fix: Couldn't render UML which includes CJK
+- - Introduced by 2.4.0
+- 
+- 
+- Support: Upgrade libs
+- - axios
+- 
+- - diff2html
+- 
+- 
+
+## v2.4.0
+
+- Feature: Support Footnotes
+- Feature: Support Task lists
+- Feature: Support Table with CSV
+- Feature: Enable to render UML diagrams with public plantuml.com server
+- Feature: Enable to switch whether rendering MathJax in realtime or not
+- Improvement: Replace markdown parser with markdown-it
+- Improvement: Generate anchor of headers with header strings
+- Improvement: Enhanced Scroll Sync on Markdown Editor/Preview
+- Improvement: Update `#revision-body` tab contents after saving with `Ctrl-S`
+- Fix: 500 Internal Server Error occures when basic-auth configuration is set
+
+## v2.3.9
+
+- Fix: `Ctrl-/` doesn't work on Chrome
+- Fix: Close Shortcuts help with `Ctrl-/`, ESC key
+- Fix: Jump to last line wrongly when `.revision-head-edit-button` clicked
+- Support: Upgrade libs
+- - googleapis
+- 
+- 
+
+## v2.3.8
+
+- Feature: Suggest page path when creating pages
+- Improvement: Prevent keyboard shortcuts when modal is opened
+- Improvement: PageHistory UI
+- Improvement: Ensure to scroll when edit button of section clicked
+- Improvement: Enabled to toggle the style for active line
+- Support: Upgrade libs
+- - style-loader
+- 
+- - react-codemirror2
+- 
+- 
+
+## v2.3.7
+
+- Fix: Open popups when `Ctrl+C` pressed
+- - Introduced by 2.3.5
+- 
+- 
+
+## v2.3.6
+
+- Feature: Theme Selector for Editor
+- Improvement: Remove unportalize button from crowi-plus layout
+- Fix: CSS for admin pages
+- Support: Shrink the size of libraries to include
+
+## v2.3.5
+
+- Feature: Enhanced Editor by CodeMirror
+- Feature: Emoji AutoComplete
+- Feature: Add keyboard shortcuts
+- Improvement: Attaching file with Dropzone.js
+- Improvement: Show shortcuts help with `Ctrl-/`
+- Fix: DOMs that has `.alert-info` class don't be displayed
+- Support: Switch and upgrade libs
+- - 8fold-marked -> marked
+- 
+- - react-bootstrap
+- 
+- - googleapis
+- 
+- - mongoose
+- 
+- - mongoose-unique-validator
+- 
+- - etc..
+- 
+- 
+
+## v2.3.4 (Discontinued)
+
+## v2.3.3
+
+- Fix: The XSS Library escapes inline code blocks
+- - Degraded by 2.3.0
+- 
+- 
+- Fix: NPE occurs on Elasticsearch when initial access
+- Fix: Couldn't invite users(failed to create)
+
+## v2.3.2
+
+- Improvement: Add LDAP group search options
+
+## v2.3.1
+
+- Fix: Blockquote doesn't work
+- - Degraded by 2.3.0
+- 
+- 
+- Fix: Couldn't create user with first LDAP logging in
+
+## v2.3.0
+
+- Feature: LDAP Authentication
+- Improvement: Prevent XSS
+- Fix: node versions couldn't be shown
+- Support: Upgrade libs
+- - express-pino-logger
+- 
+- 
+
+## v2.2.4
+
+- Fix: googleapis v23.0.0 lost the function `oauth2Client.setCredentials`
+- - Degraded by 2.2.2 updates
+- 
+- 
+- Fix: HeaderSearchBox didn't append 'q=' param when searching
+- - Degraded by 2.2.3 updates
+- 
+- 
+
+## v2.2.3
+
+- Fix: The server responds anything when using passport
+- - Degraded by 2.2.2 updates
+- 
+- 
+- Fix: Update `lastLoginAt` when login is success
+- Support: Replace moment with date-fns
+- Support: Upgrade react-bootstrap-typeahead
+- Improvement: Replace emojify.js with emojione
+
+## v2.2.2 (Discontinued)
+
+## v2.2.1
+
+- Feature: Duplicate page
+- Improve: Ensure that admin users can remove users waiting for approval
+- Fix: Modal doesn't work with React v16
+- Support: Upgrade React to 16
+- Support: Upgrade outdated libs
+
+## v2.2.0
+
+- Support: Merge official Crowi v1.6.3
+
+## v2.1.2
+
+- Improvement: Ensure to prevent suspending own account
+- Fix: Ensure to be able to use `.` for username when invited
+- Fix: monospace font for `&amp;lt;code&amp;gt;&amp;lt;/code&amp;gt;`
+
+## v2.1.1
+
+- Fix: The problem that React Modal doesn't work
+- Support: Lock some packages(react, react-dom, mongoose)
+
+## v2.1.0
+
+- Feature: Adopt Passport the authentication middleware
+- Feature: Selective batch deletion in search result page
+- Improvement: Ensure to be able to login with both of username or email
+- Fix: The problem that couldn't update user data in /me
+- Support: Upgrade outdated libs
+
+## v2.0.9
+
+- Fix: Server is down when a guest user accesses to someone's private pages
+- Support: Merge official Crowi (master branch)
+- Support: Upgrade outdated libs
+
+## v2.0.8
+
+- Fix: The problem that path including round bracket makes something bad
+- Fix: Recursively option processes also unexpedted pages
+- Fix: en_US translation
+
+## v2.0.7
+
+- Improvement: Add recursively option for Delete/Move/Putback operation
+- Improvement: Comment layout and sort order (crowi-plus Enhanced Layout)
+
+## v2.0.6
+
+- Fix: check whether `$APP_DIR/public/uploads` exists before creating symlink
+- - Fixed in weseek/crowi-plus-docker
+- 
+- 
+
+## v2.0.5
+
+- Improvement: Adjust styles for CodeMirror
+- Fix: File upload does not work when using crowi-plus-docker-compose and `FILE_UPLOAD=local` is set
+- - Fixed in weseek/crowi-plus-docker
+- 
+- 
+
+## v2.0.2 - 2.0.4 (Discontinued)
+
+## v2.0.1
+
+- Feature: Custom Script
+- Improvement: Adjust layout and styles for admin pages
+- Improvement: Record and show last updated date in user list page
+- Fix: Ignore Ctrl+(Shift+)Tab when editing (cherry-pick from the official)
+
+## v2.0.0
+
+- Feature: Enabled to integrate with Slack using Incoming Webhooks
+- Support: Upgrade all outdated libs
+
+## v1.2.16
+
+- Improvement: Condition for creating portal
+- Fix: Couldn't create new page after installation cleanly
+
+## v1.2.15
+
+- Improvement: Optimize cache settings for express server
+- Improvement: Add a logo link to the affix header
+- Fix: Child pages under `/trash` are not shown when applying crowi-plus Simplified Behavior
+
+## v1.2.14
+
+- Fix: Tabs(`a[data-toggle=&amp;quot;tab&amp;quot;][href=&amp;quot;#...&amp;quot;]`) push browser history twice
+- Fix: `a[href=&amp;quot;#edit-form&amp;quot;]` still save history even when disabling pushing states option
+
+## v1.2.13
+
+- Improvement: Enabled to switch whether to push states with History API when tabs changes
+- Fix: Layout of the Not Found page
+
+## v1.2.12 (Discontinued)
+
+## v1.2.11
+
+- Improvement: Enabled to open editing form from affix header
+- Improvement: Enabled to open editing form from each section headers
+
+## v1.2.10
+
+- Fix: Revise `server:prod:container` script for backward compatibility
+
+## v1.2.9
+
+- Improvement: Enabled to save with <kbd>⌘+S</kbd> on Mac
+- Improvement: Adopt the fastest logger 'pino'
+- Fix: The problem that can't upload profile image
+
+## v1.2.8
+
+- Fix: The problem that redirect doesn't work when using 'crowi-plus Simplified Behavior'
+
+## v1.2.7 (Discontinued)
+
+## v1.2.6
+
+- Fix: The problem that page_list widget doesn't show the picture of revision.author
+- Fix: Change implementation of Bootstrap3 toggle switch for admin pages
+
+## v1.2.5
+
+- Feature: crowi-plus Simplified Behavior
+- - `/page` and `/page/` both shows the page
+- 
+- - `/nonexistent_page` shows editing form
+- 
+- - All pages shows the list of sub pages
+- 
+- 
+- Improvement: Ensure to be able to disable Timeline feature
+
+## v1.2.4
+
+- Fix: Internal Server Error has occurred when a guest user visited the page someone added "liked"
+
+## v1.2.3
+
+- Improvement: Ensure to be able to use Presentation Mode even when not logged in
+- Improvement: Presentation Mode on IE11 (Experimental)
+- Fix: Broken Presentation Mode
+
+## v1.2.2
+
+- Support: Merge official Crowi (master branch)
+
+## v1.2.1
+
+- Fix: buildIndex error occured when access to installer
+
+## v1.2.0
+
+- Support: Merge official Crowi v1.6.2
+
+## v1.1.12
+
+- Feature: Remove Comment Button
+
+## v1.1.11
+
+- Fix: Omit Comment form from page_list (crowi-plus Enhanced Layout)
+- Fix: .search-box is broken on sm/xs screen
+
+## v1.1.10
+
+- Fix: .search-box is broken on sm/xs screen
+- Support: Browsable with IE11 (Experimental)
+
+## v1.1.9
+
+- Improvement: Ensure to generate indices of Elasticsearch when installed
+- Fix: Specify the version of Bonsai Elasticsearch on Heroku
+
+## v1.1.8
+
+- Fix: Depth of dropdown-menu when `.on-edit`
+- Fix: Error occured on saveing with `Ctrl-S`
+- Fix: Guest users browsing
+
+## v1.1.7
+
+- Feature: Add option to allow guest users to browse
+- Fix: crowi-plus Enhanced Layout
+
+## v1.1.6
+
+- Fix: crowi-plus Enhanced Layout
+
+## v1.1.5
+
+- Fix: crowi-plus Enhanced Layout
+- Support: Merge official Crowi v1.6.1 master branch [573144b]
+
+## v1.1.4
+
+- Feature: Ensure to select layout type from Admin Page
+- Feature: Add crowi-plus Enhanced Layout
+
+## v1.1.3
+
+- Improvement: Use POSIX-style paths (bollowed crowi/crowi#219 by @Tomasom)
+
+## v1.1.2
+
+- Imprv: Brushup fonts and styles
+- Fix: Ensure to specity revision id when saving with `Ctrl-S`
+
+## v1.1.1
+
+- Feature: Save with `Ctrl-S`
+- Imprv: Brushup fonts and styles
+
+## v1.1.0
+
+- Support: Merge official Crowi v1.6.1
+
+## v1.0.9
+
+- Feature: Delete user
+- Feature: Upload other than images
+
+## v1.0.8
+
+- Feature: Ensure to delete page completely
+- Feature: Ensure to delete redirect page
+- Fix: https access to Gravatar (this time for sure)
+
+## v1.0.7
+
+- Feature: Keyboard navigation for search box
+- Improvement: Intelligent Search
+
+## v1.0.6
+
+- Feature: Copy button that copies page path to clipboard
+- Fix: https access to Gravatar
+- Fix: server watching crash with `Error: read ECONNRESET` on Google Chrome
+
+## v1.0.5
+
+- Feature: Ensure to use Gravatar for profile image
+
+## v1.0.4
+
+- Improvement: Detach code blocks before preProcess
+- Support: Ensure to deploy to Heroku with INSTALL_PLUGINS env
+- Support: Ensure to load plugins easily when development
+
+## v1.0.3
+
+- Improvement: Adjust styles
+
+## v1.0.2
+
+- Improvement: For lsx
+
+## v1.0.1
+
+- Feature: Custom CSS
+- Support: Notify build failure to Slask
+
+## v1.0.0
+
+- Feature: Plugin mechanism
+- Feature: Switchable LineBreaks ON/OFF from admin page
+- Improvement: Exclude Environment-dependency
+- Improvement: Enhanced linker
+- Support: Add Dockerfile
+- Support: Abolish gulp
+- Support: LiveReload
+- Support: Update libs

+ 0 - 2009
CHANGES.md

@@ -1,2009 +0,0 @@
-# CHANGES
-
-## v4.4.0-RC
-
-### BREAKING CHANGES
-
-* Official plugins are now preinstalled
-
-### Updates
-
-* Feature: Password resetting by user
-* Feature: User trigger notification and Global notification are available by new Slack integration
-* Improvement: Add attachment button in editor navbar
-* Fix: Recursive rename operation from `/parent` to `/parent/child` ([#4101](https://github.com/weseek/growi/pull/4101))
-* Fix: Encode spaces in page path in LinkEditModal
-* Support: Create @growi/core package
-* Support: Create @growi/ui package
-* Support: Improve error handling for @growi/slackbot-proxy
-* Support: Include official plugins as sub packages
-* Support: Upgrade libs
-    * @slack/web-api
-    * date-fns
-    * helmet
-    * morgan
-    * socket.io
-
-## v4.3.3
-
-* Improvement: Welcome page markdown
-* Fix: Some recursive operation exclude descendant pages that are restricted for groups
-    * Rename / Delete / Delete completely / Put back / Duplicate
-* Fix: Layout is broken when editing users page ([#4128](https://github.com/weseek/growi/issues/4128))
-* Support: Upgrade libs
-    * @slack/web-api
-    * date-fns
-    * escape-string-regexp
-
-## v4.3.2
-
-* Feature: Hufflpuff theme
-* Improvement: CodeMirror header styles
-* Improvement: CodeMirror syntax-highlighting fenced code blocks
-* Improvement: Slack Integration Settings
-    * Error behavior when getting connection statuses
-    * Add links to docs
-* Improvement: /_api/v3/recent can be accessed with access token
-* Support: Using http-errors
-
-## v4.3.1
-
-* Fix: Build script for production
-
-## v4.3.0
-
-### BREAKING CHANGES
-
-* GROWI manages dependencies with `lerna`
-    * Use `npx lerna bootstrap` instead of `yarn install`
-* GROWI includes some official plugins in default
-    * Users no longer need to install [growi-plugin-lsx](https://github.com/weseek/growi-plugin-lsx), [growi-plugin-pukiwiki-like-linker](https://github.com/weseek/growi-plugin-pukiwiki-like-linker) and [growi-plugin-attachment-refs](https://github.com/weseek/growi-plugin-attachment-refs) before build client.
-
-
-Upgrading Guide: <https://docs.growi.org/en/admin-guide/upgrading/43x.html>
-
-### Updates
-
-* Feature: New Slack Integration with Slack Bot
-    * Searching GROWI pages from Slack
-    * Creating GROWI pages from Slack
-        * Easy record conversations
-* Feature: Enable/Disable option for share link
-* Feature: Re-send invitation mail from user management page
-* Improvement: Mark users who failed to send invitation emails
-* Fix: lsx plugin in the custom sidebar does not work when showing search result page
-* Support: Switch the official docker base image from Alpine based to Ubuntu based
-* Support: Upgrade libs
-    * striptags
-
-## v4.2.21
-
-* Improvement: Headers style on built-in editor
-* Improvement: Codemirror is now scrollable one editor height of empty space into view at the bottom of the editor
-* Improvement: Upgrade mongodb driver to fix [NODE-2784](https://jira.mongodb.org/browse/NODE-2784)
-* Support: Upgrade libs
-    * connect-mongo
-    * i18next
-    * migrate-mongo
-    * mongoose
-    * stream-to-promise
-    * validator
-    * ws
-    * nodemailer
-    * i18next-express-middleware
-    * growi-commons
-    * growi-plugin-attachment-refs
-    * growi-plugin-lsx
-
-## v4.2.20
-
-* Improvement: Error message when the password is too short
-* Improvement: Repeat XSS processing as a countermeasure against nesting 
-* Fix: NoSQL injection of access-token-parser
-* Fix: Checking permission when operating share links
-* Fix: Invalid NaN label is shown when deletedAt of the page is undefined
-    * Introduced by v4.2.8
-
-## v4.2.19
-
-* Feature: Set max-age of the user's cookie with the env var `SESSION_MAX_AGE`
-* Feature: Set max-age of the user's cookie in admin page
-* Improvement: Change the first accessing page after installation to the top page
-* Support: Upgrade libs
-    * string-width
-    * diff
-    * archiver
-
-## v4.2.18
-
-* Feature: Convertible page contents width
-* Fix: Group selector of User Group Delete Modal does not show all groups
-* Fix: Global notification to Slack does not encode spaces of page path
-* Support: Upgrade libs
-    * @google-cloud/storage
-
-## v4.2.17
-
-* Improvement: Invoke garbage collection when reindex all pages by elasticsearch
-* Improvement: Hide Sidebar at shared pages
-* Fix: No unsaved alert is displayed without difference the latest markdown and editor value
-* Support: Update libs
-    * eslint-config-weseek
-
-## v4.2.16
-
-* Fix: "Only inside the group" causes an error
-    * Introduced by v4.2.15
-
-## v4.2.15
-
-* Improvement: toastr location for editing
-* Improvement: Handsontable with static backdrop to prevent from closing when backdrop is clicked
-* Fix: Accept invalid page path like `..%2f`
-* Fix: Pages updated date is corrupted after recursive operation
-    * Introduced by v4.2.8
-* Support: Upgrade libs
-    * reactstrap
-
-
-## v4.2.14
-
-* Feature: Add an option to restrict publishing email property for new users
-* Improvement: Invite modal in admin page without email server settings
-* Improvement: Global notification settings in admin page without email server settings
-* Fix: Can create pages on the share route
-    * Introduced by v4.2.8
-* Fix: Pages restrected by group are excluded for recurrence operation
-    * Introduced by v4.2.8
-* Fix: Rename and duplicate to descendants path does not work correctly
-    * Introduced by v4.2.8
-* Support: Update libs
-    * bunyan
-    * browser-bunyan
-
-## v4.2.13
-
-* Feature: Detect indent size automatically
-* Fix: Some API responses includes email unintentionally
-* Fix: An error always displayed in admin pages
-
-## v4.2.12
-
-* Feature: Custom Sidebar
-* Fix: Set language correctly for draw.io (diagrams.net)
-
-## v4.2.11
-
-* Fix: Rename decendants is not working
-    * Introduced by v4.2.8
-
-
-## v4.2.10
-
-* Feature: Staff Credits for apps on GROWI.cloud
-* Improvement: Hackmd button behavior when disabled
-* Improvement: Layout of comparing revisions
-* Fix: Empty trash is not working
-
-## v4.2.9
-
-* Feature: Comparing revisions
-* Improvement: Memory consumption when re-indexing for full text searching
-* Improvement: Site URL settings valildation
-* Fix: Show comfirmation when transiting page without save
-* Fix: Save slack channels history when user trigger notification is invoked
-* Fix: The label of alerts for move/rename/delete are borken
-
-## v4.2.8
-
-* Improvement: Performance for pages to rename/duplicate/delete/revert pages
-* Fix: Preview scrollbar doesn't sync to editor
-    * Introduced by v4.2.6
-* Fix: Failed to save temporaryUrlCached with using gcs
-    * Introduced by v4.2.3
-* Fix: Fixed not being able to update ses settings
-    * Introduced by v4.2.0
-* Fix: Fixed the display of updtedAt and createdAt being reversed
-* Fix: Pass app title value through the XSS filter
-
-## v4.2.7
-
-* Fix: Installer doesn't work on Chrome
-
-## v4.2.6
-
-* Feature: Add a button to jump to Comments section
-* Feature: Paste Bootstrap4 Grid HTML with GUI
-* Feature: Disable auto formating table option
-* Improvement: Layout of Edit Link Modal
-* Improvement: Focus to the first input when modal is opened
-* Improvement: Preview layout in edit mode
-* Improvement: Install process under redundant environment
-* Improvement: Add contributors
-* Fix: Upgrading to v4.x failed when the user uses Kibela Layout
-    * Introduced by v4.2.0
-* Fix: diagrams.net (draw.io) errors
-* Fix: Navbar is not rendered on old iOS
-* Support: Expose metrics with Promster
-* Support: Upgrade libs
-    * axios
-
-## v4.2.5
-
-* Improvement: Invoke garbage collection when reindex all pages by elasticsearch
-    * Turned out not working -- 2021.05.01
-* Fix: MathJax rendering does not work
-
-## v4.2.4
-
-* Fix: Fixed an error when creating a new page with `Ctrl-S`
-    * Introduced by v4.2.2
-* Fix: Fixed a strange diff in PageHistory due to Pagination
-* Fix: Fixed that the user group page could not be found when using api from the outside
-
-## v4.2.3
-
-* Feature: Insert/edit links with GUI
-* Feature: Auto reconnecting to search service
-* Improvement: New style of params for Healthcheck API
-* Fix: Referencing attachments when `FILE_UPLOAD_DISABLED` is true
-* Fix: The message of timeline for restricted pages
-* Fix: Parameter validation for Import/Export Archive API
-* Fix: Prevent regexp for Search Tags API
-* Fix: Add `Content-Security-Policy` when referencing attachments
-* Fix: Sanitize at presentation time
-* Fix: Remove page path string from message for page lists and timeline when there is no contents
-
-## v4.2.2
-
-* Fix: Consecutive save operations with built-in editor fail
-    * Introduced by v4.2.1
-
-## v4.2.1
-
-* Fix: Consecutive save operations with HackMD fail
-    * Introduced by v4.2.0
-* Fix: Switching theme to kibela fail
-    * Introduced by v4.2.0
-
-## v4.2.0
-
-### BREAKING CHANGES
-
-* GROWI v4.2.x no longer support Kibela layout
-    * Kibela theme is newly added and the configuration will migrate to it automatically
-
-### Updates
-
-* Feature: File Upload Settings on admin pages
-* Improvement: Basic layout of page
-* Support: Support MongoDB 4.0, 4.2 and 4.4
-* Support: Upgrade libs
-    * migrate-mongo
-    * mongoose
-
-## v4.1.13
-
-* Fix: MathJax rendering does not work
-
-## v4.1.12
-
-* Fix: Adjust line-height for pre under li
-* Fix: Emptying trash process is broken
-
-## v4.1.11
-
-* Improvement: Generating draft DOM id strategy
-* Fix: GROWI version downgrade causes a validation error for user.lang
-
-## v4.1.10
-
-* Fix: Make listing users API secure
-* Fix: Error message when the server denies guest user connecting with socket.io
-
-## v4.1.9
-
-* Feature: Environment variables to set max connection size to deliver push messages to all clients
-
-## v4.1.8
-
-* Improvement: Rebuilding progress bar colors for Full Text Search Management
-* Improvement: Support operations on page data with a null value for author
-
-## v4.1.7
-
-* Improvement: Fire global notification when a new page is created by uploading file
-* Fix: Change default `DRAWIO_URI` to embed.diagrams.net
-* Fix: An unhandled rejection occures when a user who does not send referer accesses
-
-## v4.1.6
-
-* Improvement: Hide Fab at admin pages
-* Fix: Presentation does not work
-* Fix: Update GrantSelector status when uploading a file to a new page
-* Fix: CopyDropdown origin refs draw.io host wrongly
-
-## v4.1.5
-
-* Feature: Independent S3 configuration and SES configuration for AWS
-* Fix: Author name does not displayed in page history
-* Fix: Hide unnecessary component when pringing
-
-## v4.1.4 (Missing number)
-
-## v4.1.3
-
-* Feature: Create/edit linker with GUI
-* Improvement: Paging page histories
-* Improvement: Avoid using `cursor.snapshot()` in preparation for MongoDB version upgrade
-* Improvement: Allow to save "From e-mail address" only in App Settings
-* Improvement: Allow to empty "From e-mail address" in App Settings
-* Improvement: Export/Import archive data serially so as not to waste memory
-* Fix: To be able to delete attachment metadata even when the actual data does not exist
-* Fix: Limit the attrubutes of user data for `/_api/v3/users`
-* Fix: Prevent XSS with SVG
-* Upgrade libs
-    * optimize-css-assets-webpack-plugin
-    * terser-webpack-plugin
-
-## v4.1.2
-
-* Fix: Uploaded images do not displayed
-    * Introduced by v4.1.1
-
-## v4.1.1
-
-* Feature: External share link
-* Improvement: Optimize some features that operate revision data
-    * Page history
-    * Renaming pages
-    * Deleting pages
-* Fix: Cmd+c/v/... does not work on Mac
-    * Introduced by v4.1.0
-* Fix: "Append params" switch of CopyDropdown does not work when multiple CopyDropdown instance exists
-* Fix: "Append params" switch of CopyDropdown escapes spaces
-* Fix: Blockdiag does not be rendered
-* Fix: Access token parser
-
-## v4.1.0
-
-### 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
-
-* Feature: Server settings synchronization for multiple GROWI Apps
-* Feature: Page status alert synchronization for multiple GROWI Apps
-* Feature: Smooth scroll for anchor links
-* Feature: Mirror Mode with [Konami Code](https://en.wikipedia.org/wiki/Konami_Code)
-* Improvement: Determine whether the "In Use" badge is displayed or not by attachment ID
-* Improvement: draw.io under NO_CDN environment
-* Fix: Deleting/renaming with recursive option affects pages that are inaccessible to active users
-* Fix: DrawioModal cuts without beginning/ending line
-* Fix: New settings of SMTP and AWS SES are not reflected when server is running
-* Fix: Sidebar layout broken when using Kibela layout
-* Support: Support Node.js v14
-* Support: Update libs
-    * mathjax
-
-## v4.0.11
-
-* Fix: Fab on search result page does not displayed
-* Fix: Adjust margin/padding for search result page
-* Fix: PageAlert broken
-    * Introduced by v4.0.9
-
-## v4.0.10
-
-* Improvement: Adjust ToC height
-* Fix: Fail to rename/delete a page set as "Anyone with the link"
-
-## v4.0.9
-
-* Feature: Detailed configurations for OpenID Connect
-    * Authorization Endpoint
-    * Token Endpoint
-    * Revocation Endpoint
-    * Introspection Endpoint
-    * UserInfo Endpoint
-    * Registration Endpoint
-    * JSON Web Key Set URI
-* Improvement: Navigations
-    * New floating subnavigation
-    * New open drawer button
-    * New fixed bottom navbar on mobile
-    * New fixed bottom navbar for editor on mobile
-    * FAB (Floating action button)
-* Improvement: Sticky admin navigation
-* Fix: Reseting password doesn't work
-* Fix: Styles for printing
-* Fix: Unable to create page with original path after emptying trash
-* I18n: Support zh-CN
-
-## v4.0.8 (Missing number)
-
-## v4.0.7
-
-* Feature: Set request timeout for Elasticsearch with env var `ELASTICSEARCH_REQUEST_TIMEOUT`
-* Improvement: Apply styles faster on booting client
-* Fix: Styles are not applyed on installer
-* Fix: Remove last-resort `next()`
-* Fix: Enable/disable Notification settings couldn't change when either of the params is undefined
-* Fix: Text overflow
-
-## v4.0.6
-
-* Fix: Avatar images in Recent Changes are not shown
-* Fix: Full screen modal of Handsontable and Draw.io don't work
-* Fix: Shortcut for creating page respond with modifier key wrongly
-    * Introduced by v4.0.5
-
-## v4.0.5
-
-* Improvement: Return pre-defined session id when healthcheck
-* Improvement: Refactor caching for profile image
-* Improvement: Layout for global search help on mobile
-* Improvement: Layout for confidential notation
-* Fix: Shortcut for creating page doesn't work
-* Support: Dev in container
-* Support: Upgrade libs
-    * ldapjs
-    * node-sass
-
-
-## v4.0.4
-
-* Feature: Drawer/Dock mode selector
-* Improvement: Admin pages navigation
-* Improvement: Ensure not to avoid session management even when accessing to healthcheck
-* Support: Refactor unstated utils
-* Support: Upgrade libs
-    * connect-mongo
-    * connect-redis
-    * mongoose
-    * mongoose-gridfs
-    * mongoose-paginate-v2
-
-## v4.0.3
-
-* Feature: Copy page path dropdown with Append params switch
-* Improvement: Truncate overflowed user browsing history
-* Improvement: Tabs appearance on mobile
-* Improvement: Search help appearance on mobile
-* Improvement: Accessibility of login page
-* Fix: Editor was broken by long lines
-* Fix: Editor doesn't work on mobile
-* Fix: Word break in Recent Updated contents
-* Fix: navbar is broken on Safari
-
-## v4.0.2
-
-* Fix: Internal Server Error occurred when the guest user access to the pages that has likes
-* Fix: Some buttons are broken on Safari
-
-## v4.0.1
-
-* Improvement: Accessibility for Handsontable under dark mode
-* Improvement: Refactor '/pages.exist' API
-* Fix: Storing the state of sidebar
-* Fix: Comments order should be asc
-* Fix: Show/Hide replies button doesn't work
-* Fix: Tooltip doesn't work
-* Fix: Change the display of the scroll bar when modal is shown
-* Fix: Submit with enter key on Create/Rename modals
-* Fix: Show/Hide Unlink redirection button conditions
-* Fix: Link color in alerts
-* Support: Upgrade libs
-    * @atlaskit/drawer
-    * @atlaskit/navigation-next
-
-## v4.0.0
-
-### BREAKING CHANGES
-
-* Crowi Classic Behavior is removed
-* Crowi Classic Layout is removed
-* 'default-dark' theme is now merged as a dark mode variant of 'default' theme
-* 'blue-night' theme is now merged as a dark mode variant of 'mono-blue' theme
-
-Upgrading Guide: <https://docs.growi.org/en/admin-guide/upgrading/40x.html>
-
-### Updates
-
-* Feature: Sidebar
-* Feature: Recent changes on Sidebar
-* Feature: Switch Light/Dark Mode
-* Improvement: Migrate to Bootstrap 4
-* Improvement: Copy Page URL menu item to copy path dropdown
-* Improvement: Show contributors by Bootstrap Modal
-* Support: Upgrade libs
-    * bootstrap
-
-## v3.8.1
-
-### BREAKING CHANGES
-
-- Now Elasticsearch requires the privilege `cluster:monitor/health` instead of `cluster:monitor/nodes/info`
-
-Upgrading Guide: <https://docs.growi.org/en/admin-guide/upgrading/38x.html>
-
-### Updates
-
-* Improvement: Change the health check method for Elasticsearch
-* Fix: Unset overflow-y style for Edit Tags Modal
-* Fix: Duplicate page source is overwrited
-    * Introduced by 3.7.6
-
-## v3.8.0  (Missing number)
-
-## v3.7.7
-
-* Feature: Empty trash pages
-* Improvement: Behavior of Reconnect to Elasticsearch button
-* Fix: Duplicate page source is overwrited
-    * Introduced by 3.7.6
-
-## v3.7.6  (Missing number)
-
-## v3.7.5
-
-* Fix: Draw.io diagrams rendered twice
-* Fix: Behavior of password reset modal is strange
-* Fix: Import GROWI Archive doesn't restore some data correctly
-* Fix: Attachments list on root page and users top pages
-* Fix: Trash page is no longer editable
-* Fix: Rendering Timeline on /trash
-
-## v3.7.4
-
-* Fix: Broken by displaying user image
-
-## v3.7.3
-
-* Feature: Profile Image Cropping
-* Improvement: Reactify users pages
-* Improvement: Detect language and adjust the order of first and last names when creating accounts in OAuth
-* Fix: Installation is broken when selecting Japanese
-    * Introduced by 3.7.0
-* Fix: Mathjax Rendering is unstable (workaround)
-    * Introduced by 3.7.0
-* Fix: Notification Setting couldn't update without slack token
-    * Introduced by 3.6.6
-* Support: Add GROWI Contributers
-
-## v3.7.2
-
-* Feature: User Management Filtering/Sort
-* Feature: Show env vars on Admin pages
-* Fix: Attachment row z-index
-* I18n: HackMD integration alert
-
-## v3.7.1
-
-* Improvement: Add an option that make it possible to choose what to send notifications
-* Improvement: Add the env var `DRAWIO_URI`
-* Improvement: Accessibility for 'spring' theme
-* Improvement: Editor scroll sync behaves strangely when using draw.io blocks
-* Fix: Coudn't upload file on Comment Editor
-    * Introduced by 3.5.8
-* I18n: HackMD integration
-
-## v3.7.0
-
-### BREAKING CHANGES
-
-None.
-
-Upgrading Guide: <https://docs.growi.org/en/admin-guide/upgrading/37x.html>
-
-### Updates
-
-* Feature: [Draw.io](https://www.draw.io/) Integration
-* Feature: SAML Attribute-based Login Control
-* Improvement: Reactify admin pages (Security)
-* Improvement: Behavior of pre-editing screen of HackMD when user needs to resume
-
-## v3.6.10
-
-* Fix: Redirect logic for users except for actives
-    * Introduced by 3.6.9
-
-## v3.6.9
-
-* Improvement: Redirection when login/logout
-* Improvement: Add home icon before '/'
-* Fix: Client crashed when the first login
-    * Introduced by 3.6.8
-
-## v3.6.8
-
-* Improvement: Show page history side-by-side
-* Improvement: Optimize markdown rendering
-* Improvement: Reactify admin pages (Navigation)
-* Fix: Reply comments collapsed are broken
-    * Introduced by 3.6.7
-* Support: Update libs
-    * cross-env
-    * mkdirp
-    * diff2html
-    * jest
-    * stylelint
-
-## v3.6.7
-
-* Feature: Anchor link for comments
-* Improvement: Show error toastr when saving page is failed because of empty document
-* Fix: Admin Customise couldn't restore stored config value
-    * Introduced by 3.6.2
-* Fix: Admin Customise missed preview functions
-    * Introduced by 3.6.2
-* Fix: AWS doesn't work
-    * Introduced by 3.6.4
-* Fix: Ensure not to get unrelated indices information in Elasticsearch Management
-    * Introduced by 3.6.6
-* Support: Optimize bundles
-* Support: Optimize build-prod job with caching node_modules/.cache
-
-## v3.6.6
-
-* Feature: Reconnect to Elasticsearch from Full Text Search Management
-* Feature: Normalize indices of Elasticsearch from Full Text Search Management
-* Improvement: Add 'spring' theme
-* Improvement: Reactify admin pages (Notification)
-* Impromvement: Add `checkMiddlewaresStrictly` option to Healthcheck API
-* Improvement: Accessibility for History component under dark themes
-* Fix: Warning on client console when developing /admin/app
-* Support: Upgrade libs
-    * react-bootstrap-typeahead
-
-## v3.6.5 (Missing number)
-
-## v3.6.4
-
-* Feature: Alert for stale page
-* Improvement: Reactify admin pages (Home)
-* Improvement: Reactify admin pages (App)
-* Improvement: Accessibility for editor icons of dark themes
-* Improvement: Accessibility for importing table data pane
-* Improvement: Resolve username and email when logging in with Google OAuth
-
-## v3.6.3
-
-* Improvement: Searching users in UserGroup Management
-* Fix: Repair google authentication by migrating to jaredhanson/passport-google-oauth2
-* Fix: Markdown Settings are broken by the button to import recommended settings
-* Support: Upgrade libs
-    * check-node-version
-    * file-loader
-    * mini-css-extract-plugin
-
-## v3.6.2
-
-* Improvement: Reactify admin pages (Customize)
-* Improvement: Ensure not to consider `[text|site](https://example.com]` as a row in the table
-* Improvement: Enter key behavior in markdown table
-* Fix: Pre-installed plugins in official docker image are not detected
-    * Introduced by 3.6.0
-* Fix: Emoji Autocomplete window does not float correctly
-    * Introduced by 3.5.0
-
-## v3.6.1
-
-### BREAKING CHANGES
-
-* GROWI v3.6.x no longer support Node.js v8.x
-* The name of database that is storing migrations meta data has been changed
-    * This affects **only when `MONGO_URI` has parameters**
-    * v3.5.x or above has a bug ([#1361](https://github.com/weseek/growi/issues/1361))
-
-Upgrading Guide: <https://docs.growi.org/en/admin-guide/upgrading/36x.html>
-
-### Updates
-
-* Improvement: Drop unnecessary MongoDB collection indexes
-* Improvement: Accessibility of Antarctic theme
-* Improvement: Reactify admin pages (Markdown Settings)
-* Fix: Appending tag is failed by wrong index of PageTagRelation
-    * Introduced by 3.5.20
-* Fix: Pages without heading slash is invalid but creatable
-* Fix: Connect to Elasticsearch with `httpAuth` param
-* Support: Support Node.js v12
-* Support: Optimize build in dev with hard-source-webpack-plugin
-* Support: Upgrade libs
-    * growi-commons
-
-## v3.6.0 (Missing number)
-
-## v3.5.25
-
-* Improvement: Disable ESC key to close Handsontable Modal
-* Fix: Exported data of empty collection is broken
-* Fix: Some components crash after when the page with attachment has exported/imported
-
-## v3.5.24
-
-* Fix: Plugins are not working on Heroku
-
-## v3.5.23
-
-* Fix: Global Notification failed to send e-mail
-* Fix: Pagination is not working for trash list
-* Fix: Healthcheck API with `?connectToMiddlewares` returns error
-* Support: Upgrade libs
-    * growi-commons
-
-## v3.5.22
-
-* Improvement: Add `FILE_UPLOAD_DISABLED` env var
-
-## v3.5.21
-
-* Improvement: Cache control when retrieving attachment data
-* Fix: Inviting user doesn't work
-    * Introduced by 3.5.20
-
-## v3.5.20
-
-* Improvement: Organize MongoDB collection indexes uniqueness
-* Improvement: Reactify admin pages (External Account Management)
-* Fix: Search result or Timeline shows loading icon eternally when retrieving not accessible page
-* Support: Use SearchBox Elasticsearch Addon on Heroku
-* Support: Upgrade libs
-    * cross-env
-    * eslint-plugin-jest
-    * i18next
-    * i18next-browser-languagedetector
-    * migrate-mongo
-    * react-i18next
-    * validator
-
-## v3.5.19 (Missing number)
-
-## v3.5.18
-
-* Improvement: Import GROWI Archive
-    * Process asynchronously
-    * Collection configurations
-    * Selectable mode (insert/upsert/flush and insert)
-    * Safely mode settings for configs and users collections
-    * Show errors view
-* Improvement: Optimize handling promise of stream when exporting archive
-* Improvement: Optimize handling promise of stream when building indices
-* Improvement: Add link to [docs.growi.org](https://docs.growi.org)
-* Fix: Monospace font code is broken when printing on Mac
-
-## v3.5.17
-
-* Feature: Upload to GCS (Google Cloud Storage)
-* Feature: Statistics API
-* Improvement: Optimize exporting
-* Improvement: Show progress bar when exporting
-* Improvement: Validate collection combinations when importing
-* Improvement: Reactify admin pages
-* Fix: Use HTTP PlantUML URL in default
-    * Introduced by 3.5.12
-* Fix: Config default values
-* Support: REPL with `console` npm scripts
-
-## v3.5.16
-
-* Fix: Full Text Search doesn't work after when building indices
-    * Introduced by 3.5.12
-
-## v3.5.15
-
-* Feature: Import/Export Page data
-* Fix: The link to Sandbox on Markdown Help Modal doesn't work
-* Support: Upgrade libs
-    * codemirror
-
-## v3.5.14 (Missing number)
-
-## v3.5.13
-
-* Feature: Re-edit comments
-* Support: [growi-plugin-attachment-refs](https://github.com/weseek/growi-plugin-attachment-refs)
-* Support: Upgrade libs
-    * entities
-    * markdown-it
-
-## v3.5.12
-
-* Improvement: Use Elasticsearch Alias
-* Improvement: Connect to HTTPS PlantUML URL in default
-* Fix: Global Notification doesn't work after updating Webhook URL
-* Fix: User Trigger Notification is not be sent when channel is not specified
-* Support: Upgrade libs
-    * terser-webpack-plugin
-
-## v3.5.11
-
-* Fix: HackMD Editor shows 404 error when HackMD redirect to fqdn URI
-    * Introduced by 3.5.8
-* Fix: Timeline doesn't work
-    * Introduced by 3.5.1
-* Fix: Last Login field does not shown in /admin/user
-* Support: Upgrade libs
-    * env-cmd
-    * sass-loader
-    * webpack
-    * webpack-cli
-    * webpack-merge
-
-## v3.5.10
-
-* Feature: Send Global Notification with Slack
-* Improvement: Show loading spinner when fetching page history data
-* Improvement: Hierarchical page link when the page is in /Trash
-* Fix: Code Highlight Theme does not change
-    * Introduced by 3.5.2
-* Support: Upgrade libs
-    * date-fns
-    * eslint-config-weseek
-
-## v3.5.9
-
-* Fix: Editing table with Spreadsheet like GUI (Handsontable) is failed
-* Fix: Plugins are not initialized when first launching
-    * Introduced by 3.5.0
-* Support: Upgrade libs
-    * entities
-    * growi-commons
-    * openid-client
-    * rimraf
-    * style-loader
-
-## v3.5.8
-
-* Improvement: Controls when HackMD/CodiMD has unsaved draft
-* Improvement: Show hints if HackMD/CodiMD integration is not working
-* Improvement: GROWI server obtains HackMD/CodiMD page id from the 302 response header
-* Improvement: Comment Thread Layout
-* Improvement: Show commented date with date distance format
-
-## v3.5.7 (Missing number)
-
-## v3.5.6
-
-* Fix: Saving new page is failed when empty string tag is set
-* Fix: Link of Create template page button in New Page Modal is broken
-* Fix: Global Notification dows not work when creating/moving/deleting/like/comment
-
-## v3.5.5
-
-* Feature: Support S3-compatible object storage (e.g. MinIO)
-* Feature: Enable/Disable ID/Password Authentication
-* Improvement: Login Mechanism with HTTP Basic Authentication header
-* Improvement: Reactify Table Of Contents
-* Fix: Profile images are broken in User Management
-* Fix: Template page under root page doesn't work
-* Support: Upgrade libs
-    * csv-to-markdown-table
-    * express-validator
-    * markdown-it
-    * mini-css-extract-plugin
-    * react-hotkeys
-
-## v3.5.4
-
-* Fix: List private pages wrongly
-* Fix: Global Notification Trigger Path does not parse glob correctly
-* Fix: Consecutive page deletion requests cause unexpected complete page deletion
-
-## v3.5.3
-
-* 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
-
-## v3.5.2
-
-* Feature: Remain metadata option when Move/Rename page
-* 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: Consider timezone on admin page
-* Fix: Editor doesn't work on Microsoft Edge
-* Support: Upgrade libs
-    * growi-commons
-
-## v3.5.1
-
-### BREAKING CHANGES
-
-* GROWI no longer supports
-    * Protection system with Basic Authentication
-    * Crowi Classic Authentication Mechanism
-    * [Crowi Template syntax](https://medium.com/crowi-book/crowi-v1-5-0-5a62e7c6be90)
-* GROWI no lonnger supports plugins with schema version 2
-    * Upgrade [weseek/growi-plugin-lsx](https://github.com/weseek/growi-plugin-lsx) to v3.0.0 or above
-    * Upgrade [weseek/growi-plugin-pukiwiki-like-linker
-](https://github.com/weseek/growi-plugin-pukiwiki-like-linker
-) to v3.0.0 or above
-* The restriction mode of the root page (`/`) will be set 'Public'
-* The restriction mode of the root page (`/`) can not be changed after v 3.5.1
-
-Upgrading Guide: <https://docs.growi.org/en/admin-guide/upgrading/35x.html>
-
-### Updates
-
-* Feature: Comment Thread
-* Feature: OpenID Connect authentication
-* Feature: HTTP Basic authentication
-* Feature: Staff Credits with [Konami Code](https://en.wikipedia.org/wiki/Konami_Code)
-* Feature: Restricte Complete Deletion of Pages
-* Improvement Draft list
-* Fix: Deleting page completely
-* Fix: Search with `prefix:` param with CJK pathname
-* Fix: Could not edit UserGroup even if `PUBLIC_WIKI_ONLY` is not set
-* I18n: User Management Details
-* I18n: Group Management Details
-* Support: Apply unstated
-* Support: Use Babel 7
-* Support: Support plugins with schema version 3
-* Support: Abolish Old Config API
-* Support: Apply Jest for Tests
-* Support: Upgrade libs
-    * async
-    * axios
-    * connect-mongo
-    * css-loader
-    * eslint
-    * eslint-config-weseek
-    * eslint-plugin-import
-    * eslint-plugin-jest
-    * eslint-plugin-react
-    * file-loader
-    * googleapis
-    * i18next
-    * migrate-mongo
-    * mini-css-extract-plugin
-    * mongoose
-    * mongoose-gridfs
-    * mongoose-unique-validator
-    * null-loader
-
-## v3.5.0 (Missing number)
-
-## v3.4.7
-
-* Improvement: Handle private pages on group deletion
-* Fix: Searching with `tag:xxx` syntax doesn't work
-* Fix: Check CSRF when updating user data
-* Fix: `createdAt` field initialization
-* I18n: Import data page
-* I18n: Group Management page
-
-## v3.4.6
-
-* Feature: Tags
-* Feature: Dropdown to copy page path/URL/MarkdownLink
-* Feature: List of drafts
-* Improvement: Replace icons of Editor Tool Bar
-* Improvement: Show display name when mouse hover to user image
-* Fix: URL in slack message is broken on Safari
-* Fix: Registration does not work when basic auth is enabled
-* Support: Publish API docs with swagger-jsdoc and ReDoc
-* Support: Upgrade libs
-    * cmd-env
-    * elasticsearch
-    * mongoose-gridfs
-    * node-dev
-    * null-loader
-    * react-codemirror
-
-## v3.4.5
-
-* Improvement: Pass autolink through the XSS filter according to CommonMark Spec
-* Fix: Update ElasticSearch index when deleting/duplicating pages
-* Fix: Xss filter breaks PlantUML arrows
-* Support: Support growi-plugin-lsx@2.2.0
-* Support: Upgrade libs
-    * growi-commons
-    * xss
-
-## v3.4.4
-
-* Fix: Comment component doesn't work
-
-## v3.4.3
-
-* Improvement: Add 'antarctic' theme
-* Support Apply eslint-config-airbnb based rules
-* Support Apply prettier and stylelint
-* Support: Upgrade libs
-    * csrf
-    * escape-string-regexp
-    * eslint
-    * express-session
-    * googleapis
-    * growi-commons
-    * i18next
-    * mini-css-extract-plugin
-    * nodemailer
-    * penpal
-    * react-i18next
-    * string-width
-
-## v3.4.2
-
-* Fix: Nofitication to Slack doesn't work
-    * Introduced by 3.4.0
-
-## v3.4.1
-
-* Fix: "Cannot find module 'stream-to-promise'" occured when build client with `FILE_UPLOAD=local`
-
-## v3.4.0
-
-### BREAKING CHANGES
-
-None.
-
-Upgrading Guide: <https://docs.growi.org/en/admin-guide/upgrading/34x.html>
-
-### Updates
-
-* Improvement: Restrict to access attachments when the user is not allowed to see page
-* Improvement: Show fans and visitors of page
-* Improvement: Full text search tokenizing
-* Improvement: Markdown comment on Crowi Classic Layout
-* Fix: Profile image is not displayed when `FILE_UPLOAD=mongodb`
-* Fix: Posting comment doesn't work under Crowi Classic Layout
-    * Introduced by 3.1.5
-* Fix: HackMD doesn't work when `siteUrl` ends with slash
-* Fix: Ensure not to be able to move/duplicate page to the path which has trailing slash
-* Support: Launch with Node.js v10
-* Support: Launch with MongoDB 3.6
-* Support: Launch with Elasticsearch 6.6
-* Support: Upgrade libs
-    * bootstrap-sass
-    * browser-sync
-    * react
-    * react-dom
-
-
-## v3.3.10
-
-* Feature: PlantUML and Blockdiag on presentation
-* Improvement: Render slides of presentation with GrowiRenderer
-* Fix: Unportalizing doesn't work
-* Support: Use mini-css-extract-plugin instead of extract extract-text-webpack-plugin
-* Support: Use terser-webpack-plugin instead of uglifyjs-webpack-plugin
-* Support: Upgrade libs
-    * csv-to-markdown-table
-    * file-loader
-    * googleapis
-    * i18next-browser-languagedetector
-    * mocha
-    * react-waypoint
-    * webpack
-    * webpack-assets-manifest
-    * webpack-cli
-    * webpack-merge
-
-## v3.3.9
-
-* Fix: Import from Qiita:Team doesn't work
-    * Introduced by 3.3.0
-* Fix: Typeahead shows autocomplete wrongly
-    * Introduced by 3.3.8
-* Support: Upgrade libs
-    * react-bootstrap-typeahead
-
-## v3.3.8
-
-* Fix: Move/Duplicate don't work
-    * Introduced by 3.3.7
-* Fix: Server doesn't respond when root page is restricted
-* Support: Upgrade libs
-    * react
-    * react-bootstrap-typeahead
-
-## v3.3.7
-
-* Feature: Editor toolbar
-* Feature: `prefix:/path` searching syntax to filter with page path prefix
-* Feature: Add an option to filter only children to searching box of navbar
-* Improvement: Suggest page path when moving/duplicating/searching
-* Fix: Anonymous users couldn't search
-    * Introduced by 3.3.6
-* I18n: Searching help
-* Support: Prepare to suppoert Node.js v10
-* Support: Upgrade libs
-    * node-sass
-
-## v3.3.6
-
-* Improvement: Site URL settings must be set
-* Improvement: Site URL settings can be set with environment variable
-* Fix: "Anyone with the link" ACL doesn't work correctly
-    * Introduced by 3.3.0
-* Fix: Related pages list of /admin/user-group-detail/xxx doesn't show anything
-    * Introduced by 3.3.0
-* Fix: Diff of revision contents doesn't appeared when notifing with slack
-* Fix: NPE occured on /admin/security when Crowi Classic Auth Mechanism is set
-* Fix: Coudn't render Timing Diagram with PlantUML
-* I18n: Cheatsheet for editor
-* I18n: Some admin pages
-* Support: Upgrade libs
-    * diff
-    * markdown-it-plantuml
-    * mongoose
-    * nodemailer
-    * mongoose-gridfs
-    * sinon
-    * sinon-chai
-
-## v3.3.5 (Missing number)
-
-## v3.3.4
-
-* Improvement: SAML configuration with environment variables
-* Improvement: Upload file with pasting from clipboard
-* Fix: `/_api/revisions.get` doesn't populate author data correctly
-* Fix: Wrong OAuth callback url are shown at admin page
-* Fix: Connecting to MongoDB failed when processing migration
-* Support: Get ready to use new config management system
-
-## v3.3.3
-
-* Feature: Show line numbers to a code block
-* Feature: Bulk update the scope of descendant pages when create/update page
-* Improvement: The scope of ascendant page will be retrieved and set to controls in advance when creating a new page
-* Fix: Pages that is restricted by groups couldn't be shown in search result page
-* Fix: Pages order in search result page was wrong
-* Fix: Guest user can't search
-* Fix: Possibility that ExternalAccount deletion processing selects incorrect data
-* Support: Upgrade libs
-    * bootstrap-sass
-    * i18next
-    * migrate-mongo
-    * string-width
-
-## v3.3.2
-
-* Fix: Specified Group ACL is not persisted correctly
-    * Introduced by 3.3.0
-
-## v3.3.1
-
-* Feature: NO_CDN Mode
-* Feature: Add option to show/hide restricted pages in list
-* Feature: MongoDB GridFS quota
-* Improvement: Refactor Access Control
-* Improvement: Checkbox behavior of task list
-* Improvement: Fixed search input on search result page
-* Improvement: Add 'christmas' theme
-* Improvement: Select default language of new users
-* Fix: Hide restricted pages contents in timeline
-* Support: Upgrade libs
-    * googleapis
-    * passport-saml
-
-## v3.3.0 (Missing number)
-
-## v3.2.10
-
-* Fix: Pages in trash are available to create
-* Fix: Couldn't create portal page under Crowi Classic Behavior
-* Fix: Table tag in Timeline/SearchResult missed border and BS3 styles
-* I18n: Installer
-
-
-## v3.2.9
-
-* Feature: Attachment Storing to MongoDB GridFS
-* Fix: row/col moving of Spreadsheet like GUI (Handsontable) doesn't work
-* Fix: Emoji AutoComplete dialog pops up at wrong position
-* Support: Upgrade libs
-    * codemirror
-    * react-codemirror2
-
-## v3.2.8
-
-* Improvement: Add an option to use email for account link when using SAML federation
-* Fix: Editor layout is sometimes broken
-* Fix: Normalize table data for Spreadsheet like GUI (Handsontable) when import
-* Support: Improve development environment
-* Support: Upgrade libs
-    * googleapis
-    * react-dropzone
-
-## v3.2.7
-
-* Feature: Import CSV/TSV/HTML table on Spreadsheet like GUI (Handsontable)
-* Fix: Pasting table data copied from Excel includes unnecessary line breaks
-* Fix: Page break Preset 1 for Presentation mode is broken
-* Fix: Login Form when LDAP login failed caused 500 Internal Server Error
-
-## v3.2.6
-
-* Feature: Add select alignment buttons of Spreadsheet like GUI (Handsontable)
-* Improvement: Shrink the rows that have no diff of revision history page
-* Fix: Login form rejects weak password
-* Fix: An error occured by uploading attachment file when the page is not exists
-    * Introduced by 2.3.5
-* Support: Upgrade libs
-    * i18next-express-middleware
-    * i18next-node-fs-backend
-    * i18next-sprintf-postprocessor
-
-## v3.2.5
-
-* Improvement: Expandable Spreadsheet like GUI (Handsontable)
-* Improvement: Move/Resize rows/columns of Spreadsheet like GUI (Handsontable)
-* Improvement: Prevent XSS of New Page modal
-* Fix: Recent Created tab of user home shows wrong page list
-    * Introduced by 3.2.4
-* Support: Upgrade libs
-    * @handsontable/react
-    * handsontable
-    * metismenu
-    * sinon
-
-## v3.2.4
-
-* Feature: Edit table with Spreadsheet like GUI (Handsontable)
-* Feature: Paging recent created in users home
-* Improvement: Specify certificate for SAML Authentication
-* Fix: SAML Authentication didn't work
-    * Introduced by 3.2.2
-* Fix: Failed to create new page with title which includes RegEx special characters
-* Fix: Preventing XSS Settings are not applied in default
-    * Introduced by 3.1.12
-* Support: Mongoose migration mechanism
-* Support: Upgrade libs
-    * googleapis
-    * mocha
-    * mongoose
-    * mongoose-paginate
-    * mongoose-unique-validator
-    * multer
-
-## v3.2.3
-
-* Feature: Kibela like layout
-* Improvement: Custom newpage separator for presentation view
-* Support: Shrink image size for themes which recently added
-
-## v3.2.2
-
-* Feature: SAML Authentication (SSO)
-* Improvement: Add 'wood' theme
-* Improvement: Add 'halloween' theme
-* Improvement: Add 'island' theme
-* Fix: Sending email function doesn't work
-* Support Upgrade libs
-    * style-loader
-
-## v3.2.1
-
-* Feature: Import data from esa.io
-* Feature: Import data from Qiita:Team
-* Feature: Add the endpoint for health check
-* Improvement: Adjust styles when printing
-* Fix: Renaming page doesn't work if the page was saved with shortcut
-* Support: Refactor directory structure
-* Support Upgrade libs
-    * file-loader
-    * googleapis
-    * postcss-loader
-    * sass-loader
-    * style-loader
-
-## v3.2.0
-
-* Feature: HackMD integration so that user will be able to simultaneously edit with multiple people
-* Feature: Login with Twitter Account (OAuth)
-* Fix: The Initial scroll position is wrong when reloading the page
-
-## v3.1.14
-
-* Improvement: Show help for header search box
-* Improvement: Add Markdown Cheatsheet to Editor component
-* Fix: Couldn't delete page completely from search result page
-* Fix: Tabs of trash page are broken
-
-## v3.1.13
-
-* Feature: Global Notification
-* Feature: Send Global Notification with E-mail
-* Improvement: Add attribute mappings for email to LDAP settings
-* Support: Upgrade libs
-    * autoprefixer
-    * css-loader
-    * method-override
-    * optimize-css-assets-webpack-plugin
-    * react
-    * react-bootstrap-typeahead
-    * react-dom
-
-
-## v3.1.12
-
-* Feature: Add XSS Settings
-* Feature: Notify to Slack when comment
-* Improvement: Prevent XSS in various situations
-* Improvement: Show forbidden message when the user accesses to ungranted page
-* Improvement: Add overlay styles for pasting file to comment form
-* Fix: Omit unnecessary css link
-    * Introduced by 3.1.10
-* Fix: Invitation mail do not be sent
-* Fix: Edit template button on New Page modal doesn't work
-
-## v3.1.11
-
-* Fix: OAuth doesn't work in production because callback URL field cannot be specified
-    * Introduced by 3.1.9
-
-## v3.1.10
-
-* Fix: Enter key on react-bootstrap-typeahead doesn't submit
-    * Introduced by 3.1.9
-* Fix: CodeMirror of `/admin/customize` is broken
-    * Introduced by 3.1.9
-
-## v3.1.9
-
-* Feature: Login with Google Account (OAuth)
-* Feature: Login with GitHub Account (OAuth)
-* Feature: Attach files in Comment
-* Improvement: Write comment with CodeMirror Editor
-* Improvement: Post comment with `Ctrl-Enter`
-* Improvement: Place the commented page at the beginning of the list
-* Improvement: Resolve errors on IE11 (Experimental)
-* Support: Migrate to webpack 4
-* Support: Upgrade libs
-    * eslint
-    * react-bootstrap-typeahead
-    * react-codemirror2
-    * webpack
-
-## v3.1.8 (Missing number)
-
-## v3.1.7
-
-* Fix: Update hidden input 'pageForm[grant]' when save with `Ctrl-S`
-* Fix: Show alert message when conflict
-* Fix: `BLOCKDIAG_URI` environment variable doesn't work
-* Fix: Paste in markdown list doesn't work correctly
-* Support: Ensure to inject logger configuration from environment variables
-* Support: Upgrade libs
-    * sinon
-    * sinon-chai
-
-## v3.1.6
-
-* Feature: Support [blockdiag](http://blockdiag.com)
-* Feature: Add `BLOCKDIAG_URI` environment variable
-* Fix: Select modal for group is not shown
-* Support: Upgrade libs
-    * googleapis
-    * throttle-debounce
-
-## v3.1.5
-
-* Feature: Write comment with Markdown
-* Improvement: Support some placeholders for template page
-* Improvement: Omit unnecessary response header
-* Improvement: Support LDAP attribute mappings for user's full name
-* Improvement: Enable to scroll revision-toc
-* Fix: Posting to Slack doesn't work
-    * Introduced by 3.1.0
-* Fix: page.rename api doesn't work
-* Fix: HTML escaped characters in markdown are unescaped unexpectedly after page is saved
-* Fix: sanitize `#raw-text-original` content with 'entities'
-* Fix: Double newline character posted
-    * Introduced by 3.1.4
-* Fix: List and Comment components do not displayed
-    * Introduced by 3.1.4
-* Support: Upgrade libs
-    * markdown-it-toc-and-anchor-with-slugid
-
-
-## v3.1.4 (Missing number)
-
-
-## v3.1.3 (Missing number)
-
-
-## v3.1.2
-
-* Feature: Template page
-* Improvement: Add 'future' theme
-* Improvement: Modify syntax for Crowi compatible template feature
-    * *before*
-
-        ~~~markdown
-        ``` template:/page/name
-        page contents
-        ```
-        ~~~
-
-    * *after*
-
-        ~~~plane
-        ::: template:/page/name
-        page contents
-        :::
-        ~~~
-
-* Improvement: Escape iframe tag in block codes
-* Support: Upgrade libs
-    * assets-webpack-plugin
-    * googleapis
-    * react-clipboard.js
-    * xss
-
-## v3.1.1
-
-* Improvement: Add 'blue-night' theme
-* Improvement: List up pages which restricted for Group ACL
-* Fix: PageGroupRelation didn't remove when page is removed completely
-
-
-## v3.1.0
-
-* Improvement: Group Access Control List - Select group modal
-* Improvement: Better input on mobile
-* Improvement: Detach code blocks correctly
-* Improvement: Auto-format markdown table which includes multibyte text
-* Improvement: Show icon when auto-format markdown table is activated
-* Improvement: Enable to switch show/hide border for highlight.js
-* Improvement: BindDN field allows also ActiveDirectory styles
-* Improvement: Show LDAP logs when testing login
-* Fix: Comment body doesn't break long terms
-* Fix: lsx plugin lists up pages that hit by forward match wrongly
-    * Introduced by 3.0.4
-* Fix: Editor is broken on IE11
-* Support: Multilingualize React components with i18next
-* Support: Organize dependencies
-* Support: Upgrade libs
-    * elasticsearch
-    * googleapis
-
-## v3.0.13
-
-* Improvement: Add Vim/Emacs/Sublime-Text icons for keybindings menu
-* Improvement: Add 'mono-blue' theme
-* Fix: Unportalize process failed silently
-* Fix: Sidebar breaks editor layouts
-* Support: Switch the logger from 'pino' to 'bunyan'
-* Support: Set the alias for 'debug' to the debug function of 'bunyan'
-* Support: Translate `/admin/security`
-* Support: Optimize bundles
-    * upgrade 'markdown-it-toc-and-anchor-with-slugid' and omit 'uslug'
-* Support: Optimize .eslintrc.js
-
-## v3.0.12
-
-* Feature: Support Vim/Emacs/Sublime-Text keybindings
-* Improvement: Add some CodeMirror themes (Eclipse, Dracula)
-* Improvement: Dynamic loading for CodeMirror theme files from CDN
-* Improvement: Prevent XSS when move/redirect/duplicate
-
-## v3.0.11
-
-* Fix: login.html is broken in iOS
-* Fix: Removing attachment is crashed
-* Fix: File-attaching error after new page creation
-* Support: Optimize development build
-* Support: Upgrade libs
-    * env-cmd
-    * googleapis
-    * sinon
-
-## v3.0.10
-
-* Improvement: Add 'nature' theme
-* Fix: Page list and Timeline layout for layout-growi
-* Fix: Adjust theme colors
-    * Introduced by 3.0.9
-
-## v3.0.9
-
-* Fix: Registering new LDAP User is failed
-    * Introduced by 3.0.6
-* Support: Organize scss for overriding bootstrap variables
-* Support: Upgrade libs
-    * codemirror
-    * react-codemirror2
-    * normalize-path
-    * style-loader
-
-## v3.0.8
-
-* Improvement: h1#revision-path occupies most of the screen when the page path is long
-* Improvement: Ensure not to save concealed email field to localStorage
-* Fix: Cannot input "c" and "e" on iOS
-
-## v3.0.7
-
-* Improvement: Enable to download an attached file with original name
-* Improvement: Use MongoDB for session store instead of Redis
-* Improvement: Update dropzone overlay icons and styles
-* Fix: Dropzone overlay elements doesn't show
-    * Introduced by 3.0.0
-* Fix: Broken page path of timeline
-    * Introduced by 3.0.4
-
-## v3.0.6
-
-* Improvement: Automatically bind external accounts newly logged in to local accounts when username match
-* Improvement: Simplify configuration for Slack Web API
-* Support: Use 'slack-node' instead of '@slack/client'
-* Support: Upgrade libs
-    * googleapis
-    * i18next
-    * i18next-express-middleware
-    * react-bootstrap-typeahead
-    * sass-loader
-    * uglifycss
-
-## v3.0.5
-
-* Improvement: Update lsx icons and styles
-* Fix: lsx plugins doesn't show page names
-
-## v3.0.4
-
-* Improvement: The option that switch whether add h1 section when create new page
-* Improvement: Encode page path that includes special character
-* Fix: Page-saving error after new page creation
-
-## v3.0.3
-
-* Fix: Login page is broken in iOS
-* Fix: Hide presentation tab if portal page
-* Fix: A few checkboxes doesn't work
-    * Invite user check with email in `/admin/user`
-    * Recursively check in rename modal
-    * Redirect check in rename modal
-* Fix: Activating invited user form url is wrong
-* Support: Use postcss-loader and autoprefixer
-
-## v3.0.2
-
-* Feature: Group Access Control List
-* Feature: Add site theme selector
-* Feature: Add a control to switch whether email shown or hidden by user
-* Feature: Custom title tag content
-* Fix: bosai version
-* Support: Rename to GROWI
-* Support: Add dark theme
-* Support: Refreshing bootstrap theme and icons
-* Support: Use Browsersync instead of easy-livereload
-* Support: Upgrade libs
-    * react-bootstrap
-    * react-bootstrap-typeahead
-    * react-clipboard.js
-
-## v3.0.1 (Missing number)
-
-## v3.0.0 (Missing number)
-
-## v2.4.4
-
-* Feature: Autoformat Markdown Table
-* Feature: highlight.js Theme Selector
-* Fix: The bug of updating numbering list by codemirror
-* Fix: Template LangProcessor doesn't work
-    * Introduced by 2.4.0
-* Support: Apply ESLint
-* Support: Upgrade libs
-    * react, react-dom
-    * codemirror, react-codemirror2
-
-## v2.4.3
-
-* Improvement: i18n in `/admin`
-* Improvement: Add `SESSION_NAME` environment variable
-* Fix: All Elements are cleared when the Check All button in DeletionMode
-* Support: Upgrade libs
-    * uglifycss
-    * sinon-chai
-
-## v2.4.2
-
-* Improvement: Ensure to set absolute url from root when attaching files when `FILE_UPLOAD=local`
-* Fix: Inline code blocks that includes doller sign are broken
-* Fix: Comment count is not updated when a comment of the page is deleted
-* Improvement: i18n in `/admin` (WIP)
-* Support: Upgrade libs
-    * googleapis
-    * markdown-it-plantuml
-
-## v2.4.1
-
-* Feature: Custom Header HTML
-* Improvement: Add highlight.js languages
-    * dockerfile, go, gradle, json, less, scss, typescript, yaml
-* Fix: Couldn't connect to PLANTUML_URI
-    * Introduced by 2.4.0
-* Fix: Couldn't render UML which includes CJK
-    * Introduced by 2.4.0
-* Support: Upgrade libs
-    * axios
-    * diff2html
-
-## v2.4.0
-
-* Feature: Support Footnotes
-* Feature: Support Task lists
-* Feature: Support Table with CSV
-* Feature: Enable to render UML diagrams with public plantuml.com server
-* Feature: Enable to switch whether rendering MathJax in realtime or not
-* Improvement: Replace markdown parser with markdown-it
-* Improvement: Generate anchor of headers with header strings
-* Improvement: Enhanced Scroll Sync on Markdown Editor/Preview
-* Improvement: Update `#revision-body` tab contents after saving with `Ctrl-S`
-* Fix: 500 Internal Server Error occures when basic-auth configuration is set
-
-## v2.3.9
-
-* Fix: `Ctrl-/` doesn't work on Chrome
-* Fix: Close Shortcuts help with `Ctrl-/`, ESC key
-* Fix: Jump to last line wrongly when `.revision-head-edit-button` clicked
-* Support: Upgrade libs
-    * googleapis
-
-## v2.3.8
-
-* Feature: Suggest page path when creating pages
-* Improvement: Prevent keyboard shortcuts when modal is opened
-* Improvement: PageHistory UI
-* Improvement: Ensure to scroll when edit button of section clicked
-* Improvement: Enabled to toggle the style for active line
-* Support: Upgrade libs
-    * style-loader
-    * react-codemirror2
-
-## v2.3.7
-
-* Fix: Open popups when `Ctrl+C` pressed
-    * Introduced by 2.3.5
-
-## v2.3.6
-
-* Feature: Theme Selector for Editor
-* Improvement: Remove unportalize button from crowi-plus layout
-* Fix: CSS for admin pages
-* Support: Shrink the size of libraries to include
-
-## v2.3.5
-
-* Feature: Enhanced Editor by CodeMirror
-* Feature: Emoji AutoComplete
-* Feature: Add keyboard shortcuts
-* Improvement: Attaching file with Dropzone.js
-* Improvement: Show shortcuts help with `Ctrl-/`
-* Fix: DOMs that has `.alert-info` class don't be displayed
-* Support: Switch and upgrade libs
-    * 8fold-marked -> marked
-    * react-bootstrap
-    * googleapis
-    * mongoose
-    * mongoose-unique-validator
-    * etc..
-
-## v2.3.4 (Missing number)
-
-## v2.3.3
-
-* Fix: The XSS Library escapes inline code blocks
-    * Degraded by 2.3.0
-* Fix: NPE occurs on Elasticsearch when initial access
-* Fix: Couldn't invite users(failed to create)
-
-## v2.3.2
-
-* Improvement: Add LDAP group search options
-
-## v2.3.1
-
-* Fix: Blockquote doesn't work
-    * Degraded by 2.3.0
-* Fix: Couldn't create user with first LDAP logging in
-
-## v2.3.0
-
-* Feature: LDAP Authentication
-* Improvement: Prevent XSS
-* Fix: node versions couldn't be shown
-* Support: Upgrade libs
-    * express-pino-logger
-
-## v2.2.4
-
-* Fix: googleapis v23.0.0 lost the function `oauth2Client.setCredentials`
-    * Degraded by 2.2.2 updates
-* Fix: HeaderSearchBox didn't append 'q=' param when searching
-    * Degraded by 2.2.3 updates
-
-## v2.2.3
-
-* Fix: The server responds anything when using passport
-    * Degraded by 2.2.2 updates
-* Fix: Update `lastLoginAt` when login is success
-* Support: Replace moment with date-fns
-* Support: Upgrade react-bootstrap-typeahead
-* Improvement: Replace emojify.js with emojione
-
-## v2.2.2 (Missing number)
-
-## v2.2.1
-
-* Feature: Duplicate page
-* Improve: Ensure that admin users can remove users waiting for approval
-* Fix: Modal doesn't work with React v16
-* Support: Upgrade React to 16
-* Support: Upgrade outdated libs
-
-## v2.2.0
-
-* Support: Merge official Crowi v1.6.3
-
-## v2.1.2
-
-* Improvement: Ensure to prevent suspending own account
-* Fix: Ensure to be able to use `.` for username when invited
-* Fix: monospace font for `<code></code>`
-
-## v2.1.1
-
-* Fix: The problem that React Modal doesn't work
-* Support: Lock some packages(react, react-dom, mongoose)
-
-## v2.1.0
-
-* Feature: Adopt Passport the authentication middleware
-* Feature: Selective batch deletion in search result page
-* Improvement: Ensure to be able to login with both of username or email
-* Fix: The problem that couldn't update user data in /me
-* Support: Upgrade outdated libs
-
-## v2.0.9
-
-* Fix: Server is down when a guest user accesses to someone's private pages
-* Support: Merge official Crowi (master branch)
-* Support: Upgrade outdated libs
-
-## v2.0.8
-
-* Fix: The problem that path including round bracket makes something bad
-* Fix: Recursively option processes also unexpedted pages
-* Fix: en_US translation
-
-## v2.0.7
-
-* Improvement: Add recursively option for Delete/Move/Putback operation
-* Improvement: Comment layout and sort order (crowi-plus Enhanced Layout)
-
-## v2.0.6
-
-* Fix: check whether `$APP_DIR/public/uploads` exists before creating symlink
-    * Fixed in weseek/crowi-plus-docker
-
-## v2.0.5
-
-* Improvement: Adjust styles for CodeMirror
-* Fix: File upload does not work when using crowi-plus-docker-compose and `FILE_UPLOAD=local` is set  
-    * Fixed in weseek/crowi-plus-docker
-
-## v2.0.2 - 2.0.4 (Missing number)
-
-## v2.0.1
-
-* Feature: Custom Script
-* Improvement: Adjust layout and styles for admin pages
-* Improvement: Record and show last updated date in user list page
-* Fix: Ignore Ctrl+(Shift+)Tab when editing (cherry-pick from the official)
-
-## v2.0.0
-
-* Feature: Enabled to integrate with Slack using Incoming Webhooks
-* Support: Upgrade all outdated libs
-
-## v1.2.16
-
-* Improvement: Condition for creating portal
-* Fix: Couldn't create new page after installation cleanly
-
-## v1.2.15
-
-* Improvement: Optimize cache settings for express server
-* Improvement: Add a logo link to the affix header
-* Fix: Child pages under `/trash` are not shown when applying crowi-plus Simplified Behavior
-
-## v1.2.14
-
-* Fix: Tabs(`a[data-toggle="tab"][href="#..."]`) push browser history twice
-* Fix: `a[href="#edit-form"]` still save history even when disabling pushing states option
-
-## v1.2.13
-
-* Improvement: Enabled to switch whether to push states with History API when tabs changes
-* Fix: Layout of the Not Found page
-
-## v1.2.12 (Missing number)
-
-## v1.2.11
-
-* Improvement: Enabled to open editing form from affix header
-* Improvement: Enabled to open editing form from each section headers
-
-## v1.2.10
-
-* Fix: Revise `server:prod:container` script for backward compatibility
-
-## v1.2.9
-
-* Improvement: Enabled to save with <kbd>⌘+S</kbd> on Mac
-* Improvement: Adopt the fastest logger 'pino'
-* Fix: The problem that can't upload profile image
-
-## v1.2.8
-
-* Fix: The problem that redirect doesn't work when using 'crowi-plus Simplified Behavior'
-
-## v1.2.7 (Missing number)
-
-## v1.2.6
-
-* Fix: The problem that page_list widget doesn't show the picture of revision.author
-* Fix: Change implementation of Bootstrap3 toggle switch for admin pages
-
-## v1.2.5
-
-* Feature: crowi-plus Simplified Behavior
-    * `/page` and `/page/` both shows the page
-    * `/nonexistent_page` shows editing form
-    * All pages shows the list of sub pages
-* Improvement: Ensure to be able to disable Timeline feature
-
-## v1.2.4
-
-* Fix: Internal Server Error has occurred when a guest user visited the page someone added "liked"
-
-## v1.2.3
-
-* Improvement: Ensure to be able to use Presentation Mode even when not logged in
-* Improvement: Presentation Mode on IE11 (Experimental)
-* Fix: Broken Presentation Mode
-
-## v1.2.2
-
-* Support: Merge official Crowi (master branch)
-
-## v1.2.1
-
-* Fix: buildIndex error occured when access to installer
-
-## v1.2.0
-
-* Support: Merge official Crowi v1.6.2
-
-## v1.1.12
-
-* Feature: Remove Comment Button
-
-## v1.1.11
-
-* Fix: Omit Comment form from page_list (crowi-plus Enhanced Layout)
-* Fix: .search-box is broken on sm/xs screen
-
-## v1.1.10
-
-* Fix: .search-box is broken on sm/xs screen
-* Support: Browsable with IE11 (Experimental)
-
-## v1.1.9
-
-* Improvement: Ensure to generate indices of Elasticsearch when installed
-* Fix: Specify the version of Bonsai Elasticsearch on Heroku
-
-## v1.1.8
-
-* Fix: Depth of dropdown-menu when `.on-edit`
-* Fix: Error occured on saveing with `Ctrl-S`
-* Fix: Guest users browsing
-
-## v1.1.7
-
-* Feature: Add option to allow guest users to browse
-* Fix: crowi-plus Enhanced Layout
-
-## v1.1.6
-
-* Fix: crowi-plus Enhanced Layout
-
-## v1.1.5
-
-* Fix: crowi-plus Enhanced Layout
-* Support: Merge official Crowi v1.6.1 master branch [573144b]
-
-## v1.1.4
-
-* Feature: Ensure to select layout type from Admin Page
-* Feature: Add crowi-plus Enhanced Layout
-
-## v1.1.3
-
-* Improvement: Use POSIX-style paths (bollowed crowi/crowi#219 by @Tomasom)
-
-## v1.1.2
-
-* Imprv: Brushup fonts and styles
-* Fix: Ensure to specity revision id when saving with `Ctrl-S`
-
-## v1.1.1
-
-* Feature: Save with `Ctrl-S`
-* Imprv: Brushup fonts and styles
-
-## v1.1.0
-
-* Support: Merge official Crowi v1.6.1
-
-## v1.0.9
-
-* Feature: Delete user
-* Feature: Upload other than images
-
-## v1.0.8
-
-* Feature: Ensure to delete page completely
-* Feature: Ensure to delete redirect page
-* Fix: https access to Gravatar (this time for sure)
-
-## v1.0.7
-
-* Feature: Keyboard navigation for search box
-* Improvement: Intelligent Search
-
-## v1.0.6
-
-* Feature: Copy button that copies page path to clipboard
-* Fix: https access to Gravatar
-* Fix: server watching crash with `Error: read ECONNRESET` on Google Chrome
-
-## v1.0.5
-
-* Feature: Ensure to use Gravatar for profile image
-
-## v1.0.4
-
-* Improvement: Detach code blocks before preProcess
-* Support: Ensure to deploy to Heroku with INSTALL_PLUGINS env
-* Support: Ensure to load plugins easily when development
-
-## v1.0.3
-
-* Improvement: Adjust styles
-
-## v1.0.2
-
-* Improvement: For lsx
-
-## v1.0.1
-
-* Feature: Custom CSS
-* Support: Notify build failure to Slask
-
-## v1.0.0
-
-* Feature: Plugin mechanism
-* Feature: Switchable LineBreaks ON/OFF from admin page
-* Improvement: Exclude Environment-dependency
-* Improvement: Enhanced linker
-* Support: Add Dockerfile
-* Support: Abolish gulp
-* Support: LiveReload
-* Support: Update libs

+ 4 - 0
bin/github-actions/bump-versions/README.md

@@ -0,0 +1,4 @@
+bump-versions.js
+==============
+
+Custom cli for bumping package versions based on [algolia/shipjs@0.23.3](https://github.com/algolia/shipjs/tree/v0.23.3/packages/shipjs)

+ 18 - 0
bin/github-actions/bump-versions/cli.js

@@ -0,0 +1,18 @@
+import { print, parseArgs } from 'shipjs/src/util';
+import bumpVersions from './flow/bump-versions';
+
+export async function cli(argv) {
+  const { fn, arg: argSpec } = bumpVersions;
+  try {
+    const opts = parseArgs(argSpec, argv);
+    await fn(opts);
+  }
+  catch (error) {
+    if (error.code === 'ARG_UNKNOWN_OPTION') {
+      print(error);
+    }
+    else {
+      throw error;
+    }
+  }
+}

+ 71 - 0
bin/github-actions/bump-versions/flow/bump-versions.js

@@ -0,0 +1,71 @@
+import semver from 'semver';
+import { loadConfig, getCurrentVersion, getReleaseType } from 'shipjs-lib';
+
+import printDryRunBanner from 'shipjs/src/step/printDryRunBanner';
+import confirmNextVersion from 'shipjs/src/step/prepare/confirmNextVersion';
+import updateVersion from 'shipjs/src/step/prepare/updateVersion';
+import updateVersionMonorepo from 'shipjs/src/step/prepare/updateVersionMonorepo';
+import installDependencies from 'shipjs/src/step/prepare/installDependencies';
+
+import printHelp from '../step/printHelp';
+
+async function bumpVersions({
+  help = false,
+  dir = '.',
+  dryRun = false,
+  increment = 'patch',
+  preid = 'RC',
+}) {
+  if (help) {
+    printHelp();
+    return;
+  }
+  if (dryRun) {
+    printDryRunBanner();
+  }
+
+  const config = await loadConfig(dir, 'bump-versions.config');
+
+  // get current version
+  const { monorepo } = config;
+  const currentVersion = monorepo && monorepo.mainVersionFile
+    ? getCurrentVersion(dir, monorepo.mainVersionFile)
+    : getCurrentVersion(dir);
+
+  // determine next version
+  let nextVersion = semver.inc(currentVersion, increment, preid); // set preid if type is 'prerelease'
+  nextVersion = await confirmNextVersion({
+    yes: true,
+    currentVersion,
+    nextVersion,
+    dryRun,
+  });
+  const releaseType = getReleaseType(nextVersion);
+
+  // update
+  const updateVersionFn = monorepo
+    ? updateVersionMonorepo
+    : updateVersion;
+  await updateVersionFn({
+    config, nextVersion, releaseType, dir, dryRun,
+  });
+}
+
+const arg = {
+  '--dir': String,
+  '--help': Boolean,
+  '--dry-run': Boolean,
+  '--increment': String,
+  '--preid': String,
+
+  // Aliases
+  '-d': '--dir',
+  '-h': '--help',
+  '-D': '--dry-run',
+  '-i': '--increment',
+};
+
+export default {
+  arg,
+  fn: bumpVersions,
+};

+ 16 - 0
bin/github-actions/bump-versions/index.js

@@ -0,0 +1,16 @@
+#!/usr/bin/env node
+
+// eslint-disable-next-line no-global-assign
+require = require('esm')(module);
+
+(async function() {
+  try {
+    process.env.SHIPJS = true;
+    await require('./cli').cli(process.argv);
+  }
+  catch (e) {
+    // eslint-disable-next-line no-console
+    console.error(e);
+    process.exit(1);
+  }
+}());

+ 54 - 0
bin/github-actions/bump-versions/step/printHelp.js

@@ -0,0 +1,54 @@
+import runStep from 'shipjs/src/step/runStep';
+import { print } from 'shipjs/src/util';
+import { bold, underline } from 'shipjs/src/color';
+
+export default () => runStep({}, () => {
+  const indent = line => `\t${line}`;
+
+  const help = '--help';
+  const dir = `--dir ${underline('PATH')}`;
+  const increment = `--increment ${underline('LEVEL')}`;
+  const preId = `--preid ${underline('IDENTIFIER')}`;
+  const dryRun = '--dry-run';
+  const all = [help, dir, increment, preId, dryRun]
+    .map(x => `[${x}]`)
+    .join(' ');
+
+  const messages = [
+    bold('NAME'),
+    indent('bump-versions - Bump versions of packages.'),
+    '',
+    bold('USAGE'),
+    indent(`node ./bin/github-actions/bump-versions ${all}`),
+    '',
+    bold('OPTIONS'),
+    indent(`-h, ${help}`),
+    indent('  Print this help'),
+    '',
+    indent(`-d, ${dir}`),
+    indent(
+      `  Specify the ${underline(
+        'PATH',
+      )} of the repository (default: the current directory).`,
+    ),
+    '',
+    indent(`-i, ${increment}`),
+    indent(
+      `  Specify the ${underline(
+        'LEVEL',
+      )} for semver.inc() to increment a version (default: 'patch').`,
+    ),
+    '',
+    indent(`${preId}`),
+    indent(
+      `  Specify the ${underline(
+        'IDENTIFIER',
+      )} for semver.inc() with 'prerelease' type (default: 'RC').`,
+    ),
+    '',
+    indent(`-D, ${dryRun}`),
+    indent('  Displays the steps without actually doing them.'),
+    '',
+  ];
+  print(messages.join('\n'));
+});

+ 12 - 0
bump-versions.config.js

@@ -0,0 +1,12 @@
+/*
+ * Reference: https://community.algolia.com/shipjs/
+ */
+module.exports = {
+  monorepo: {
+    mainVersionFile: 'lerna.json',
+    packagesToBump: [
+      './',
+      'packages/*',
+    ],
+  },
+};

+ 2 - 2
lerna.json

@@ -1,8 +1,8 @@
 {
   "npmClient": "yarn",
   "useWorkspaces": true,
+  "version": "4.4.3-RC.0",
   "packages": [
     "packages/*"
-  ],
-  "version": "independent"
+  ]
 }

+ 3 - 3
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "4.4.0-RC",
+  "version": "4.4.3-RC.0",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",
@@ -31,11 +31,10 @@
   "scripts": {
     "start": "yarn app:server",
     "prestart": "yarn app:build",
-    "app:build": "yarn lerna run build --scope @growi/app --scope @growi/slack --scope @growi/plugin-*",
+    "app:build": "yarn lerna run build",
     "app:server": "yarn lerna run server --scope @growi/app",
     "slackbot-proxy:build": "yarn lerna run build --scope @growi/slackbot-proxy --scope @growi/slack",
     "slackbot-proxy:server": "yarn lerna run start:prod --scope @growi/slackbot-proxy",
-    "version": "node -p \"require('./package.json').version\"",
     "//// scripts for backward compatibility": "",
     "build:prod": "echo !!! CAUTION !!! ==> The script 'build:prod' is deprecated. Use 'yarn app:build' instead. && yarn app:build",
     "server:prod": "echo !!! CAUTION !!! ==> The script 'server:prod' is deprecated. Use 'yarn app:build' instead. && yarn app:server"
@@ -66,6 +65,7 @@
     "jest-localstorage-mock": "^2.4.14",
     "lerna": "^4.0.0",
     "rewire": "^5.0.0",
+    "shipjs": "^0.23.3",
     "ts-jest": "^27.0.4"
   },
   "engines": {

+ 2 - 2
packages/app/bin/github-actions/update-readme.sh

@@ -2,5 +2,5 @@
 
 cd docker
 
-sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`4\.3\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/docker\/Dockerfile.\+\)$/\1${RELEASE_VERSION}\2\3${RELEASE_VERSION}\4/" README.md
-sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`4\.3-nocdn\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/docker\/Dockerfile.\+\)$/\1${RELEASE_VERSION}-nocdn\2\3${RELEASE_VERSION}\4/" README.md
+sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`4\.4\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/docker\/Dockerfile.\+\)$/\1${RELEASED_VERSION}\2\3${RELEASED_VERSION}\4/" README.md
+sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`4\.4-nocdn\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/docker\/Dockerfile.\+\)$/\1${RELEASED_VERSION}-nocdn\2\3${RELEASED_VERSION}\4/" README.md

+ 6 - 6
packages/app/config/migrate.js

@@ -5,9 +5,13 @@
  * @author Yuki Takei <yuki@weseek.co.jp>
  */
 
+import mongoose from 'mongoose';
+
+import { initMongooseGlobalSettings, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+
 const { URL } = require('url');
 
-const { getMongoUri } = require('~/server/util/mongoose-utils');
+initMongooseGlobalSettings();
 
 const mongoUri = getMongoUri();
 
@@ -17,11 +21,7 @@ const url = new URL(mongoUri);
 const mongodb = {
   url: mongoUri,
   databaseName: url.pathname.substring(1), // omit heading slash
-  options: {
-    useNewUrlParser: true, // removes a deprecation warning when connecting
-    useUnifiedTopology: true,
-    useFindAndModify: false,
-  },
+  options: mongoOptions,
 };
 
 module.exports = {

+ 0 - 2
packages/app/docker/Dockerfile

@@ -110,9 +110,7 @@ RUN tar cf packages.tar \
   package.json \
   yarn.lock \
   tsconfig.base.json \
-  packages/app/package.json \
   packages/app/config \
-  packages/app/dist \
   packages/app/public \
   packages/app/resource \
   packages/app/tmp \

+ 5 - 5
packages/app/docker/README.md

@@ -2,7 +2,7 @@
 GROWI Official docker image
 ========================
 
-[![Actions Status](https://github.com/weseek/growi/workflows/Release%20Docker%20Images/badge.svg)](https://github.com/weseek/growi/actions) [![docker-pulls](https://img.shields.io/docker/pulls/weseek/growi.svg)](https://hub.docker.com/r/weseek/growi/) [![](https://images.microbadger.com/badges/image/weseek/growi.svg)](https://microbadger.com/images/weseek/growi)
+[![Actions Status](https://github.com/weseek/growi/workflows/Release/badge.svg)](https://github.com/weseek/growi/actions) [![docker-pulls](https://img.shields.io/docker/pulls/weseek/growi.svg)](https://hub.docker.com/r/weseek/growi/) [![](https://images.microbadger.com/badges/image/weseek/growi.svg)](https://microbadger.com/images/weseek/growi)
 
 ![GROWI-x-docker](https://user-images.githubusercontent.com/1638767/38307565-105956e2-384f-11e8-8534-b1128522d68d.png)
 
@@ -10,10 +10,10 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 
-* [`4.3.0`, `4.3`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.3.0/docker/Dockerfile)
-* [`4.3.0-nocdn`, `4.3-nocdn`, `4-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.3.0/docker/Dockerfile)
-* [`4.2.0`, `4.2` (Dockerfile)](https://github.com/weseek/growi/blob/v4.2.0/docker/Dockerfile)
-* [`4.2.0-nocdn`, `4.2-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.2.0/docker/Dockerfile)
+* [`4.4.2`, `4.4`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.4.2/docker/Dockerfile)
+* [`4.4.2-nocdn`, `4.4-nocdn`, `4-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.4.2/docker/Dockerfile)
+* [`4.3.3`, `4.3` (Dockerfile)](https://github.com/weseek/growi/blob/v4.3.3/docker/Dockerfile)
+* [`4.3.3-nocdn`, `4.3-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.3.3/docker/Dockerfile)
 
 
 What is GROWI?

+ 11 - 13
packages/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "4.4.0-RC",
+  "version": "4.4.3-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -10,7 +10,7 @@
     "build:server": "yarn cross-env NODE_ENV=production tsc -p tsconfig.build.server.json && tsc-alias -p tsconfig.build.server.json",
     "clean": "npx shx rm -rf dist transpiled",
     "prebuild": "yarn cross-env NODE_ENV=production run-p clean resources:*",
-    "postbuild": "npx shx mv transpiled/src dist && npx shx rm -rf transpiled",
+    "postbuild": "npx shx mv transpiled/src dist && npx shx cp -r src/server/views dist/server/ && npx shx rm -rf transpiled",
     "server": "yarn cross-env NODE_ENV=production node -r dotenv-flow/config --expose_gc dist/server/app.js",
     "server:ci": "yarn server --ci",
     "preserver": "yarn cross-env NODE_ENV=production yarn migrate",
@@ -18,7 +18,7 @@
     "dev": "run-p dev:client dev:server",
     "dev:client": "yarn cross-env NODE_ENV=development webpack --config config/webpack.dev.js --progress --watch",
     "dev:client:nowatch": "yarn cross-env NODE_ENV=development webpack --config config/webpack.dev.js",
-    "dev:server": "yarn cross-env NODE_ENV=development yarn ts-node-dev src/server/app.ts --expose_gc",
+    "dev:server": "yarn cross-env NODE_ENV=development ts-node-dev --inspect --expose-gc -r tsconfig-paths/register -r dotenv-flow/config --transpile-only src/server/app.ts",
     "predev:client": "yarn cross-env NODE_ENV=development run-p resources:*",
     "predev:server": "yarn cross-env NODE_ENV=development yarn migrate",
     "//// for CI": "",
@@ -44,8 +44,7 @@
     "migrate:status": "yarn ts-node node_modules/.bin/migrate-mongo status -f config/migrate.js",
     "migrate:up": "yarn ts-node node_modules/.bin/migrate-mongo up -f config/migrate.js",
     "migrate:down": "yarn ts-node node_modules/.bin/migrate-mongo down -f config/migrate.js",
-    "ts-node": "ts-node -r tsconfig-paths/register -r dotenv-flow/config --transpile-only",
-    "ts-node-dev": "ts-node-dev -r tsconfig-paths/register -r dotenv-flow/config --inspect --transpile-only"
+    "ts-node": "ts-node -r tsconfig-paths/register -r dotenv-flow/config --transpile-only"
   },
   "// comments for dependencies": {
     "openid-client": "Node.js 12 or higher is required for openid-client@3 and above.",
@@ -55,11 +54,10 @@
   "dependencies": {
     "@browser-bunyan/console-formatted-stream": "^1.6.2",
     "@google-cloud/storage": "^5.8.5",
-    "@growi/plugin-attachment-refs": "^4.4.0-RC",
-    "@growi/plugin-pukiwiki-like-linker": "^4.4.0-RC",
-    "@growi/plugin-lsx": "^4.4.0-RC",
-    "@growi/slack": "^4.4.0-RC",
-    "@kobalab/socket.io-session": "^1.0.3",
+    "@growi/plugin-attachment-refs": "^4.4.3-RC.0",
+    "@growi/plugin-pukiwiki-like-linker": "^4.4.3-RC.0",
+    "@growi/plugin-lsx": "^4.4.3-RC.0",
+    "@growi/slack": "^4.4.3-RC.0",
     "@promster/express": "^5.0.1",
     "@promster/server": "^6.0.0",
     "@slack/events-api": "^3.0.0",
@@ -133,7 +131,7 @@
     "reconnecting-websocket": "^4.4.0",
     "redis": "^3.0.2",
     "rimraf": "^3.0.0",
-    "socket.io": "^4.0.0",
+    "socket.io": "^4.2.0",
     "stream-to-promise": "^3.0.0",
     "string-width": "=4.2.2",
     "swagger-jsdoc": "^3.4.0",
@@ -155,7 +153,7 @@
     "@alienfast/i18next-loader": "^1.0.16",
     "@atlaskit/drawer": "^5.3.7",
     "@atlaskit/navigation-next": "^8.0.5",
-    "@growi/ui": "^4.4.0-RC",
+    "@growi/ui": "^4.4.3-RC.0",
     "@handsontable/react": "=2.1.0",
     "@types/compression": "^1.7.0",
     "@types/express": "^4.17.11",
@@ -222,7 +220,7 @@
     "reveal.js": "^3.5.0",
     "sass-loader": "^8.0.0",
     "simple-load-script": "^1.0.2",
-    "socket.io-client": "^4.0.0",
+    "socket.io-client": "^4.2.0",
     "sticky-events": "^3.1.3",
     "style-loader": "^1.0.0",
     "styled-components": "^5.0.1",

+ 33 - 0
packages/app/src/migrations/20210906194521-slack-app-integration-set-default-value.js

@@ -0,0 +1,33 @@
+import mongoose from 'mongoose';
+
+import { defaultSupportedCommandsNameForBroadcastUse, defaultSupportedCommandsNameForSingleUse } from '@growi/slack';
+import { getModelSafely } from '~/server/util/mongoose-utils';
+import config from '^/config/migrate';
+import loggerFactory from '~/utils/logger';
+
+const logger = loggerFactory('growi:migrate:slack-app-integration-set-default-value');
+
+module.exports = {
+  async up(db) {
+    logger.info('Apply migration');
+    mongoose.connect(config.mongoUri, config.mongodb.options);
+
+    // Add columns + set all default commands if supportedCommandsForBroadcastUse column does not exist
+    const SlackAppIntegration = getModelSafely('SlackAppIntegration') || require('~/server/models/slack-app-integration')();
+
+    // Add togetter command if supportedCommandsForBroadcastUse already exists
+    const slackAppIntegrations = await SlackAppIntegration.find();
+    slackAppIntegrations.forEach(async(doc) => {
+      if (!doc.supportedCommandsForSingleUse.includes('togetter')) {
+        doc.supportedCommandsForSingleUse.push('togetter');
+      }
+      await doc.save();
+    });
+
+    logger.info('Migration has successfully applied');
+  },
+
+  async down() {
+    // no rollback
+  },
+};

+ 0 - 1
packages/app/src/server/.node-dev.json

@@ -2,7 +2,6 @@
   "ignore": [
     "package.json",
     "public/manifest.json",
-    "config/env.",
     "config/webpack."
   ]
 }

+ 3 - 1
packages/app/src/server/console.js

@@ -2,7 +2,7 @@ const repl = require('repl');
 const fs = require('fs');
 const path = require('path');
 const mongoose = require('mongoose');
-const { getMongoUri, mongoOptions } = require('~/server/util/mongoose-utils');
+const { initMongooseGlobalSettings, getMongoUri, mongoOptions } = require('~/server/util/mongoose-utils');
 
 const models = require('./models');
 
@@ -32,6 +32,8 @@ fs.readFile(replHistoryPath, 'utf8', (err, data) => {
 replServer.context.mongoose = mongoose;
 replServer.context.models = models;
 
+initMongooseGlobalSettings();
+
 mongoose.connect(getMongoUri(), mongoOptions)
   .then(() => {
     replServer.context.db = mongoose.connection.db;

+ 4 - 2
packages/app/src/server/crowi/index.js

@@ -9,7 +9,7 @@ import CdnResourcesService from '~/services/cdn-resources-service';
 import InterceptorManager from '~/services/interceptor-manager';
 import Xss from '~/services/xss';
 import loggerFactory from '~/utils/logger';
-import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import { initMongooseGlobalSettings, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import { projectRoot } from '~/utils/project-dir-utils';
 
 import ConfigManager from '../service/config-manager';
@@ -35,7 +35,7 @@ function Crowi() {
   this.publicDir = path.join(projectRoot, 'public') + sep;
   this.resourceDir = path.join(projectRoot, 'resource') + sep;
   this.localeDir = path.join(this.resourceDir, 'locales') + sep;
-  this.viewsDir = path.join(projectRoot, 'src', 'server', 'views') + sep;
+  this.viewsDir = path.resolve(__dirname, '../views') + sep;
   this.tmpDir = path.join(projectRoot, 'tmp') + sep;
   this.cacheDir = path.join(this.tmpDir, 'cache');
 
@@ -214,6 +214,8 @@ Crowi.prototype.setupDatabase = function() {
   // mongoUri = mongodb://user:password@host/dbname
   const mongoUri = getMongoUri();
 
+  initMongooseGlobalSettings();
+
   return mongoose.connect(mongoUri, mongoOptions);
 };
 

+ 2 - 1
packages/app/src/server/middlewares/admin-required.js

@@ -4,7 +4,8 @@ const logger = loggerFactory('growi:middleware:admin-required');
 
 module.exports = (crowi, fallback = null) => {
 
-  return async(req, res, next) => {
+  return function(req, res, next) {
+
     if (req.user != null && (req.user instanceof Object) && '_id' in req.user) {
       if (req.user.admin) {
         return next();

+ 3 - 2
packages/app/src/server/models/slack-app-integration.js

@@ -1,12 +1,13 @@
 const crypto = require('crypto');
 const mongoose = require('mongoose');
+const { defaultSupportedCommandsNameForBroadcastUse, defaultSupportedCommandsNameForSingleUse } = require('@growi/slack');
 
 const schema = new mongoose.Schema({
   tokenGtoP: { type: String, required: true, unique: true },
   tokenPtoG: { type: String, required: true, unique: true },
   isPrimary: { type: Boolean, unique: true, sparse: true },
-  supportedCommandsForBroadcastUse: { type: [String], default: [] },
-  supportedCommandsForSingleUse: { type: [String], default: [] },
+  supportedCommandsForBroadcastUse: { type: [String], default: defaultSupportedCommandsNameForBroadcastUse },
+  supportedCommandsForSingleUse: { type: [String], default: defaultSupportedCommandsNameForSingleUse },
 });
 
 class SlackAppIntegration {

+ 4 - 10
packages/app/src/server/routes/apiv3/slack-integration-settings.js

@@ -22,7 +22,6 @@ const logger = loggerFactory('growi:routes:apiv3:slack-integration-settings');
 
 const router = express.Router();
 
-const OFFICIAL_SLACKBOT_PROXY_URI = 'https://slackbot-proxy.growi.org';
 
 /**
  * @swagger
@@ -110,17 +109,12 @@ module.exports = (crowi) => {
       'slackbot:proxyUri': null,
     };
 
-    // set url if officialBot is specified
-    if (initializedType === SlackbotType.OFFICIAL) {
-      params['slackbot:proxyUri'] = OFFICIAL_SLACKBOT_PROXY_URI;
-    }
-
     return updateSlackBotSettings(params);
   }
 
   async function getConnectionStatusesFromProxy(tokens) {
     const csv = tokens.join(',');
-    const proxyUri = crowi.configManager.getConfig('crowi', 'slackbot:proxyUri');
+    const proxyUri = crowi.slackIntegrationService.proxyUriForCurrentType;
 
     const result = await axios.get(urljoin(proxyUri, '/g2s/connection-status'), {
       headers: {
@@ -133,7 +127,7 @@ module.exports = (crowi) => {
   }
 
   async function requestToProxyServer(token, method, endpoint, body) {
-    const proxyUri = crowi.configManager.getConfig('crowi', 'slackbot:proxyUri');
+    const proxyUri = crowi.slackIntegrationService.proxyUriForCurrentType;
     if (proxyUri == null) {
       throw new Error('Proxy URL is not registered');
     }
@@ -557,7 +551,7 @@ module.exports = (crowi) => {
         { new: true },
       );
 
-      const proxyUri = crowi.configManager.getConfig('crowi', 'slackbot:proxyUri');
+      const proxyUri = crowi.slackIntegrationService.proxyUriForCurrentType;
       if (proxyUri != null) {
         await requestToProxyServer(
           slackAppIntegration.tokenGtoP,
@@ -600,7 +594,7 @@ module.exports = (crowi) => {
       return res.apiv3Err(new ErrorV3(msg, 'not-proxy-type'), 400);
     }
 
-    const proxyUri = crowi.configManager.getConfig('crowi', 'slackbot:proxyUri');
+    const proxyUri = crowi.slackIntegrationService.proxyUriForCurrentType;
     if (proxyUri == null) {
       return res.apiv3Err(new ErrorV3('Proxy URL is null.', 'not-proxy-Uri'), 400);
     }

+ 8 - 4
packages/app/src/server/service/search.js

@@ -34,26 +34,30 @@ class SearchService {
   }
 
   get isSearchboxEnabled() {
-    return this.configManager.getConfig('crowi', 'app:searchboxSslUrl') != null;
+    const uri = this.configManager.getConfig('crowi', 'app:searchboxSslUrl');
+    return uri != null && uri.length > 0;
   }
 
   get isElasticsearchEnabled() {
-    return this.configManager.getConfig('crowi', 'app:elasticsearchUri') != null;
+    const uri = this.configManager.getConfig('crowi', 'app:elasticsearchUri');
+    return uri != null && uri.length > 0;
   }
 
   generateDelegator() {
     logger.info('Initializing search delegator');
 
     if (this.isSearchboxEnabled) {
-      logger.info('Searchbox is enabled');
       const SearchboxDelegator = require('./search-delegator/searchbox');
+      logger.info('Searchbox is enabled');
       return new SearchboxDelegator(this.configManager, this.crowi.socketIoService);
     }
     if (this.isElasticsearchEnabled) {
-      logger.info('Elasticsearch (not Searchbox) is enabled');
       const ElasticsearchDelegator = require('./search-delegator/elasticsearch');
+      logger.info('Elasticsearch (not Searchbox) is enabled');
       return new ElasticsearchDelegator(this.configManager, this.crowi.socketIoService);
     }
+
+    logger.info('No elasticsearch URI is specified so that full text search is disabled.');
   }
 
   registerUpdateEvent() {

+ 16 - 16
packages/app/src/server/service/slack-command-handler/togetter.js

@@ -46,9 +46,9 @@ module.exports = (crowi) => {
     const channel = payload.channel.id;
     try {
       // validate form
-      const { path, oldest, latest } = await this.togetterValidateForm(client, payload);
+      const { path, oldest, newest } = await this.togetterValidateForm(client, payload);
       // get messages
-      result = await this.togetterGetMessages(client, payload, channel, path, latest, oldest);
+      result = await this.togetterGetMessages(client, payload, channel, path, newest, oldest);
       // clean messages
       const cleanedContents = await this.togetterCleanMessages(result.messages);
 
@@ -66,9 +66,9 @@ module.exports = (crowi) => {
     const grwTzoffset = crowi.appService.getTzoffset() * 60;
     const path = payload.state.values.page_path.page_path.value;
     let oldest = payload.state.values.oldest.oldest.value;
-    let latest = payload.state.values.latest.latest.value;
+    let newest = payload.state.values.newest.newest.value;
     oldest = oldest.trim();
-    latest = latest.trim();
+    newest = newest.trim();
     if (!path) {
       throw new SlackbotError({
         method: 'postMessage',
@@ -91,34 +91,34 @@ module.exports = (crowi) => {
         mainMessage: 'Datetime format for oldest must be yyyy/MM/dd-HH:mm',
       });
     }
-    if (!regexpDatetime.test(latest)) {
+    if (!regexpDatetime.test(newest)) {
       throw new SlackbotError({
         method: 'postMessage',
         to: 'dm',
-        popupMessage: 'Datetime format for latest must be yyyy/MM/dd-HH:mm',
-        mainMessage: 'Datetime format for latest must be yyyy/MM/dd-HH:mm',
+        popupMessage: 'Datetime format for newest must be yyyy/MM/dd-HH:mm',
+        mainMessage: 'Datetime format for newest must be yyyy/MM/dd-HH:mm',
       });
     }
     oldest = parse(oldest, 'yyyy/MM/dd-HH:mm', new Date()).getTime() / 1000 + grwTzoffset;
     // + 60s in order to include messages between hh:mm.00s and hh:mm.59s
-    latest = parse(latest, 'yyyy/MM/dd-HH:mm', new Date()).getTime() / 1000 + grwTzoffset + 60;
+    newest = parse(newest, 'yyyy/MM/dd-HH:mm', new Date()).getTime() / 1000 + grwTzoffset + 60;
 
-    if (oldest > latest) {
+    if (oldest > newest) {
       throw new SlackbotError({
         method: 'postMessage',
         to: 'dm',
-        popupMessage: 'Oldest datetime must be older than the latest date time.',
-        mainMessage: 'Oldest datetime must be older than the latest date time.',
+        popupMessage: 'Oldest datetime must be older than the newest date time.',
+        mainMessage: 'Oldest datetime must be older than the newest date time.',
       });
     }
 
-    return { path, oldest, latest };
+    return { path, oldest, newest };
   };
 
-  handler.togetterGetMessages = async function(client, payload, channel, path, latest, oldest) {
+  handler.togetterGetMessages = async function(client, payload, channel, path, newest, oldest) {
     const result = await client.conversations.history({
       channel,
-      latest,
+      newest,
       oldest,
       limit: 100,
       inclusive: true,
@@ -196,9 +196,9 @@ module.exports = (crowi) => {
 
   handler.togetterMessageBlocks = function(messages, body, args, limit) {
     return [
-      markdownSectionBlock('Select the oldest and latest datetime of the messages to use.'),
+      markdownSectionBlock('Select the oldest and newest datetime of the messages to use.'),
       inputBlock(this.plainTextInputElementWithInitialTime('oldest'), 'oldest', 'Oldest datetime'),
-      inputBlock(this.plainTextInputElementWithInitialTime('latest'), 'latest', 'Latest datetime'),
+      inputBlock(this.plainTextInputElementWithInitialTime('newest'), 'newest', 'Newest datetime'),
       inputBlock(this.togetterInputBlockElement('page_path', '/'), 'page_path', 'Page path'),
       actionsBlock(
         buttonElement({ text: 'Cancel', actionId: 'togetter:cancel' }),

+ 21 - 2
packages/app/src/server/service/slack-integration.ts

@@ -16,6 +16,7 @@ import { S2sMessageHandlable } from './s2s-messaging/handlable';
 
 const logger = loggerFactory('growi:service:SlackBotService');
 
+const OFFICIAL_SLACKBOT_PROXY_URI = 'https://slackbot-proxy.growi.org';
 
 type S2sMessageForSlackIntegration = S2sMessage & { updatedAt: Date };
 
@@ -106,6 +107,25 @@ export class SlackIntegrationService implements S2sMessageHandlable {
     return true;
   }
 
+  get proxyUriForCurrentType(): string {
+    const currentBotType = this.configManager.getConfig('crowi', 'slackbot:currentBotType');
+
+    // TODO assert currentBotType is not null and CUSTOM_WITHOUT_PROXY
+
+    let proxyUri: string;
+
+    switch (currentBotType) {
+      case SlackbotType.OFFICIAL:
+        proxyUri = OFFICIAL_SLACKBOT_PROXY_URI;
+        break;
+      default:
+        proxyUri = this.configManager.getConfig('crowi', 'slackbot:proxyUri');
+        break;
+    }
+
+    return proxyUri;
+  }
+
   /**
    * generate WebClient instance for CUSTOM_WITHOUT_PROXY type
    */
@@ -171,8 +191,7 @@ export class SlackIntegrationService implements S2sMessageHandlable {
     this.isCheckTypeValid();
 
     // connect to proxy
-    const proxyServerUri = this.configManager.getConfig('crowi', 'slackbot:proxyUri');
-    const serverUri = new URL('/g2s', proxyServerUri);
+    const serverUri = new URL('/g2s', this.proxyUriForCurrentType);
     const headers = {
       'x-growi-gtop-tokens': slackAppIntegration.tokenGtoP,
     };

+ 19 - 12
packages/app/src/server/service/socket-io.js

@@ -3,7 +3,6 @@ import loggerFactory from '~/utils/logger';
 const socketIo = require('socket.io');
 const expressSession = require('express-session');
 const passport = require('passport');
-const socketioSession = require('@kobalab/socket.io-session');
 
 const logger = loggerFactory('growi:service:socket-io');
 
@@ -24,7 +23,8 @@ class SocketIoService {
     return (this.io != null);
   }
 
-  attachServer(server) {
+  // Since the Order is important, attachServer() should be async
+  async attachServer(server) {
     this.io = socketIo(server, {
       transports: ['websocket'],
     });
@@ -34,12 +34,12 @@ class SocketIoService {
 
     // setup middlewares
     // !!CAUTION!! -- ORDER IS IMPORTANT
-    this.setupSessionMiddleware();
-    this.setupLoginRequiredMiddleware();
-    this.setupAdminRequiredMiddleware();
-    this.setupCheckConnectionLimitsMiddleware();
+    await this.setupSessionMiddleware();
+    await this.setupLoginRequiredMiddleware();
+    await this.setupAdminRequiredMiddleware();
+    await this.setupCheckConnectionLimitsMiddleware();
 
-    this.setupStoreGuestIdEventHandler();
+    await this.setupStoreGuestIdEventHandler();
   }
 
   getDefaultSocket() {
@@ -59,13 +59,20 @@ class SocketIoService {
 
   /**
    * use passport session
-   * @see https://qiita.com/kobalab/items/083e507fb01159fe9774
+   * @see https://socket.io/docs/v4/middlewares/#Compatibility-with-Express-middleware
    */
   setupSessionMiddleware() {
-    const sessionMiddleware = socketioSession(expressSession(this.crowi.sessionConfig), passport);
-    this.io.use(sessionMiddleware.express_session);
-    this.io.use(sessionMiddleware.passport_initialize);
-    this.io.use(sessionMiddleware.passport_session);
+    const wrap = middleware => (socket, next) => middleware(socket.request, {}, next);
+
+    this.io.use(wrap(expressSession(this.crowi.sessionConfig)));
+    this.io.use(wrap(passport.initialize()));
+    this.io.use(wrap(passport.session()));
+
+    // express and passport session on main socket doesn't shared to child namespace socket
+    // need to define the session for specific namespace
+    this.getAdminSocket().use(wrap(expressSession(this.crowi.sessionConfig)));
+    this.getAdminSocket().use(wrap(passport.initialize()));
+    this.getAdminSocket().use(wrap(passport.session()));
   }
 
   /**

+ 11 - 1
packages/app/src/server/util/mongoose-utils.ts

@@ -2,6 +2,13 @@ import mongoose, {
   Model, Document, ConnectionOptions, Schema,
 } from 'mongoose';
 
+export const initMongooseGlobalSettings = (): void => {
+  // supress deprecation warnings
+  // see: https://mongoosejs.com/docs/deprecations.html
+  mongoose.set('useFindAndModify', false);
+  mongoose.set('useCreateIndex', true);
+};
+
 export const getMongoUri = (): string => {
   const { env } = process;
 
@@ -26,6 +33,9 @@ export const getOrCreateModel = <Interface, Method>(modelName: string, schema: S
   return mongoose.model<Interface & Document, Method & Model<Interface & Document>>(modelName, schema);
 };
 
+// supress deprecation warnings
+// see: https://mongoosejs.com/docs/deprecations.html
 export const mongoOptions: ConnectionOptions = {
-  useFindAndModify: false,
+  useNewUrlParser: true,
+  useUnifiedTopology: true,
 };

+ 4 - 0
packages/app/src/test/config/migrate.test.js

@@ -13,9 +13,12 @@ describe('config/migrate.js', () => {
   `('returns', ({ MONGO_URI, expectedUrl, expectedDbName }) => {
     test(`when 'MONGO_URI' is '${MONGO_URI}`, () => {
 
+      const initMongooseGlobalSettingsMock = jest.fn();
+
       // mock for mongoose-utils
       jest.doMock('~/server/util/mongoose-utils', () => {
         return {
+          initMongooseGlobalSettings: initMongooseGlobalSettingsMock,
           getMongoUri: () => {
             return MONGO_URI;
           },
@@ -26,6 +29,7 @@ describe('config/migrate.js', () => {
 
       jest.dontMock('~/server/util/mongoose-utils');
 
+      expect(initMongooseGlobalSettingsMock).toHaveBeenCalledTimes(1);
       expect(mongoUri).toBe(MONGO_URI);
       expect(mongodb.url).toBe(expectedUrl);
       expect(mongodb.databaseName).toBe(expectedDbName);

+ 3 - 1
packages/app/src/test/global-setup.js

@@ -9,7 +9,7 @@ import 'tsconfig-paths/register';
 
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import { initMongooseGlobalSettings, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 
 // check env
 if (process.env.NODE_ENV !== 'test') {
@@ -21,6 +21,8 @@ if (process.env.NODE_ENV !== 'test') {
 // const { getInstance } = require('./setup-crowi');
 
 module.exports = async() => {
+  initMongooseGlobalSettings();
+
   await mongoose.connect(getMongoUri(), mongoOptions);
 
   // drop database

+ 17 - 8
packages/app/src/test/service/page.test.js

@@ -305,18 +305,27 @@ describe('PageService', () => {
     });
 
     test('rename page with different tree with isRecursively [shallower]', async() => {
+      // setup
+      expect(await Page.findOne({ path: '/level1' })).toBeNull();
+      expect(await Page.findOne({ path: '/level1/level2' })).not.toBeNull();
+      expect(await Page.findOne({ path: '/level1/level2/child' })).not.toBeNull();
+      expect(await Page.findOne({ path: '/level1/level2/level2' })).not.toBeNull();
+      expect(await Page.findOne({ path: '/level1-2021H1' })).not.toBeNull();
+
+      // when
+      //   rename /level1/level2 --> /level1
       await crowi.pageService.renamePage(parentForRename7, '/level1', testUser1, {}, true);
-      const expectPage1 = await Page.findOne({ path: '/level1' });
-      const expectPage2 = await Page.findOne({ path: '/level1/child' });
-      const expectPage3 = await Page.findOne({ path: '/level1/level2/level2' });
-      const expectPage4 = await Page.findOne({ path: '/level1-2021H1' });
 
-      expect(expectPage1).not.toBeNull();
-      expect(expectPage2).not.toBeNull();
-      expect(expectPage3).not.toBeNull();
+      // then
+      expect(await Page.findOne({ path: '/level1' })).not.toBeNull();
+      expect(await Page.findOne({ path: '/level1/child' })).not.toBeNull();
+      expect(await Page.findOne({ path: '/level1/level2' })).toBeNull();
+      expect(await Page.findOne({ path: '/level1/level2/child' })).toBeNull();
+      // The changed path is duplicated with the existing path (/level1/level2), so it will not be changed
+      expect(await Page.findOne({ path: '/level1/level2/level2' })).not.toBeNull();
 
       // Check that pages that are not to be renamed have not been renamed
-      expect(expectPage4).not.toBeNull();
+      expect(await Page.findOne({ path: '/level1-2021H1' })).not.toBeNull();
     });
   });
 

+ 2 - 1
packages/app/src/test/setup.js

@@ -7,13 +7,14 @@
 
 const mongoose = require('mongoose');
 
-const { getMongoUri, mongoOptions } = require('~/server/util/mongoose-utils');
+const { initMongooseGlobalSettings, getMongoUri, mongoOptions } = require('~/server/util/mongoose-utils');
 
 mongoose.Promise = global.Promise;
 
 jest.setTimeout(30000); // default 5000
 
 beforeAll(async() => {
+  initMongooseGlobalSettings();
   await mongoose.connect(getMongoUri(), mongoOptions);
 });
 

+ 5 - 4
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/core",
-  "version": "4.4.0-RC",
+  "version": "4.4.3-RC.0",
   "description": "GROWI Core Libraries",
   "license": "MIT",
   "keywords": [
@@ -8,7 +8,9 @@
   ],
   "main": "dist/cjs/index.js",
   "module": "dist/esm/index.js",
-  "files": ["dist"],
+  "files": [
+    "dist"
+  ],
   "scripts": {
     "build": "run-p build:*",
     "build:cjs": "tsc -p tsconfig.build.cjs.json && tsc-alias -p tsconfig.build.cjs.json",
@@ -19,6 +21,5 @@
     "test": "jest --verbose"
   },
   "dependencies": {},
-  "devDependencies": {
-  }
+  "devDependencies": {}
 }

+ 0 - 1
packages/plugin-attachment-refs/index.js

@@ -1 +0,0 @@
-module.exports = require('./src/meta');

+ 6 - 5
packages/plugin-attachment-refs/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/plugin-attachment-refs",
-  "version": "4.4.0-RC",
+  "version": "4.4.3-RC.0",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "license": "MIT",
   "keywords": [
@@ -9,7 +9,9 @@
   ],
   "main": "dist/cjs/index.js",
   "module": "dist/esm/index.js",
-  "files": ["dist"],
+  "files": [
+    "dist"
+  ],
   "scripts": {
     "build": "run-p build:*",
     "build:cjs": "tsc -p tsconfig.build.cjs.json && tsc-alias -p tsconfig.build.cjs.json",
@@ -22,7 +24,6 @@
   "dependencies": {
     "browser-bunyan": "^1.6.3",
     "bunyan": "^1.8.15",
-    "growi-commons": "^5.0.4",
     "http-errors": "^1.8.0",
     "react-images": "~1.0.0",
     "react-motion": "^0.5.2",
@@ -31,7 +32,7 @@
   "devDependencies": {
     "npm-run-all": "^4.1.5",
     "prettier-stylelint": "^0.4.2",
-    "react": "^16.4.1",
-    "react-dom": "^16.4.1"
+    "react": "^16.8.3",
+    "react-dom": "^16.8.3"
   }
 }

+ 0 - 0
packages/plugin-attachment-refs/src/meta.js → packages/plugin-attachment-refs/src/index.js


+ 25 - 10
packages/plugin-attachment-refs/src/server/routes/refs.js

@@ -6,9 +6,20 @@ const { OptionParser } = customTagUtils;
 
 const logger = loggerFactory('growi-plugin:attachment-refs:routes:refs');
 
+
+const loginRequiredFallback = (req, res) => {
+  return res.status(403).send('login required');
+};
+
+
 module.exports = (crowi) => {
   const express = crowi.require('express');
   const mongoose = crowi.require('mongoose');
+
+  const loginRequired = crowi.require('../middlewares/login-required')(crowi, true, loginRequiredFallback);
+  const accessTokenParser = crowi.require('../middlewares/access-token-parser')(crowi);
+  const { serializeUserSecurely } = crowi.require('../models/serializers/user-serializer');
+
   const router = express.Router();
 
   const ObjectId = mongoose.Types.ObjectId;
@@ -68,7 +79,7 @@ module.exports = (crowi) => {
   /**
    * return an Attachment model
    */
-  router.get('/ref', async(req, res) => {
+  router.get('/ref', accessTokenParser, loginRequired, async(req, res) => {
     const user = req.user;
     const { pagePath, fileNameOrId } = req.query;
     // eslint-disable-next-line no-unused-vars
@@ -87,12 +98,6 @@ module.exports = (crowi) => {
       return;
     }
 
-    let creatorPopulateOpt;
-    // set populate option for backward compatibility against to GROWI <= v4.0.x
-    if (User.IMAGE_POPULATION != null) {
-      creatorPopulateOpt = User.IMAGE_POPULATION;
-    }
-
     // convert ObjectId
     const orConditions = [{ originalName: fileNameOrId }];
     if (ObjectId.isValid(fileNameOrId)) {
@@ -104,7 +109,7 @@ module.exports = (crowi) => {
         page: page._id,
         $or: orConditions,
       })
-      .populate({ path: 'creator', select: User.USER_PUBLIC_FIELDS, populate: creatorPopulateOpt });
+      .populate('creator');
 
     // not found
     if (attachment == null) {
@@ -122,13 +127,16 @@ module.exports = (crowi) => {
       return;
     }
 
+    // serialize User data
+    attachment.creator = serializeUserSecurely(attachment.creator);
+
     res.status(200).send({ attachment });
   });
 
   /**
    * return a list of Attachment
    */
-  router.get('/refs', async(req, res) => {
+  router.get('/refs', accessTokenParser, loginRequired, async(req, res) => {
     const user = req.user;
     const { prefix, pagePath } = req.query;
     const options = JSON.parse(req.query.options);
@@ -198,9 +206,16 @@ module.exports = (crowi) => {
     }
 
     const attachments = await query
-      .populate({ path: 'creator', select: User.USER_PUBLIC_FIELDS })
+      .populate('creator')
       .exec();
 
+    // serialize User data
+    attachments.forEach((doc) => {
+      if (doc.creator != null && doc.creator instanceof User) {
+        doc.creator = serializeUserSecurely(doc.creator);
+      }
+    });
+
     res.status(200).send({ attachments });
   });
 

+ 5 - 4
packages/plugin-lsx/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/plugin-lsx",
-  "version": "4.4.0-RC",
+  "version": "4.4.3-RC.0",
   "description": "GROWI plugin to list pages",
   "license": "MIT",
   "keywords": [
@@ -9,7 +9,9 @@
   ],
   "main": "dist/cjs/index.js",
   "module": "dist/esm/index.js",
-  "files": ["dist"],
+  "files": [
+    "dist"
+  ],
   "scripts": {
     "build": "run-p build:*",
     "build:cjs": "tsc -p tsconfig.build.cjs.json && tsc-alias -p tsconfig.build.cjs.json",
@@ -19,8 +21,7 @@
     "lint": "run-p lint:*",
     "test": ""
   },
-  "dependencies": {
-  },
+  "dependencies": {},
   "devDependencies": {
     "react": "^16.8.3",
     "react-dom": "^16.8.3"

+ 2 - 0
packages/plugin-lsx/src/client-entry.js

@@ -1,9 +1,11 @@
+import { LsxLogoutInterceptor } from './client/js/util/Interceptor/LsxLogoutInterceptor';
 import { LsxPreRenderInterceptor } from './client/js/util/Interceptor/LsxPreRenderInterceptor';
 import { LsxPostRenderInterceptor } from './client/js/util/Interceptor/LsxPostRenderInterceptor';
 
 export default (appContainer) => {
   // add interceptors
   appContainer.interceptorManager.addInterceptors([
+    new LsxLogoutInterceptor(),
     new LsxPreRenderInterceptor(),
     new LsxPostRenderInterceptor(appContainer),
   ]);

+ 7 - 2
packages/plugin-lsx/src/client/css/index.css

@@ -1,6 +1,7 @@
 .lsx .page-list-ul > li > a:not(:hover) {
   text-decoration: none;
 }
+
 .lsx .lsx-page-not-exist {
   opacity: 0.6;
 }
@@ -10,6 +11,10 @@
 }
 
 @keyframes lsx-fadeIn {
-  0% {opacity: .2}
-  100% {opacity: .9}
+  0% {
+    opacity: 0.2;
+  }
+  100% {
+    opacity: 0.9;
+  }
 }

+ 76 - 46
packages/plugin-lsx/src/client/js/components/Lsx.jsx

@@ -9,64 +9,86 @@ import { pathUtils } from 'growi-commons';
 import styles from '../../css/index.css';
 
 import { LsxContext } from '../util/LsxContext';
-import { LsxCacheHelper } from '../util/LsxCacheHelper';
+import { TagCacheManagerFactory } from '../util/TagCacheManagerFactory';
 import { PageNode } from './PageNode';
 import { LsxListView } from './LsxPageList/LsxListView';
 
-
 export class Lsx extends React.Component {
 
   constructor(props) {
     super(props);
 
     this.state = {
-      isLoading: true,
+      isLoading: false,
       isError: false,
+      isCacheExists: false,
       nodeTree: undefined,
       errorMessage: '',
     };
+
+    this.tagCacheManager = TagCacheManagerFactory.getInstance();
   }
 
-  componentWillMount() {
-    const lsxContext = this.props.lsxContext;
-    lsxContext.parse();
+  async componentWillMount() {
+    const { lsxContext, forceToFetchData } = this.props;
 
-    // check cache exists
-    if (this.props.lsxStateCache) {
+    // get state object cache
+    const stateCache = this.retrieveDataFromCache();
+
+    if (stateCache != null) {
       this.setState({
-        isLoading: false,
-        nodeTree: this.props.lsxStateCache.nodeTree,
-        isError: this.props.lsxStateCache.isError,
-        errorMessage: this.props.lsxStateCache.errorMessage,
+        isCacheExists: true,
+        nodeTree: stateCache.nodeTree,
+        isError: stateCache.isError,
+        errorMessage: stateCache.errorMessage,
       });
-      return; // go to render()
+
+      // switch behavior by forceToFetchData
+      if (!forceToFetchData) {
+        return; // go to render()
+      }
     }
 
+    lsxContext.parse();
+    this.setState({ isLoading: true });
+
     // add slash ensure not to forward match to another page
     // ex: '/Java/' not to match to '/JavaScript'
     const pagePath = pathUtils.addTrailingSlash(lsxContext.pagePath);
 
-    this.props.appContainer.apiGet('/plugins/lsx', { pagePath, options: lsxContext.options })
-      .then((res) => {
-        if (res.ok) {
-          const nodeTree = this.generatePageNodeTree(pagePath, res.pages);
-          this.setState({ nodeTree });
-        }
-        else {
-          return Promise.reject(res.error);
-        }
-      })
-      .catch((error) => {
-        this.setState({ isError: true, errorMessage: error.message });
-      })
-      // finally
-      .then(() => {
-        this.setState({ isLoading: false });
-
-        // store to sessionStorage
-        const cacheKey = LsxCacheHelper.generateCacheKeyFromContext(lsxContext);
-        LsxCacheHelper.cacheState(cacheKey, this.state);
+    try {
+      const res = await this.props.appContainer.apiGet('/plugins/lsx', { pagePath, options: lsxContext.options });
+
+      if (res.ok) {
+        const nodeTree = this.generatePageNodeTree(pagePath, res.pages);
+        this.setState({ nodeTree });
+      }
+    }
+    catch (error) {
+      this.setState({ isError: true, errorMessage: error.message });
+    }
+    finally {
+      this.setState({ isLoading: false });
+
+      // store to sessionStorage
+      this.tagCacheManager.cacheState(lsxContext, this.state);
+    }
+  }
+
+  retrieveDataFromCache() {
+    const { lsxContext } = this.props;
+
+    // get state object cache
+    const stateCache = this.tagCacheManager.getStateCache(lsxContext);
+
+    // instanciate PageNode
+    if (stateCache != null && stateCache.nodeTree != null) {
+      stateCache.nodeTree = stateCache.nodeTree.map((obj) => {
+        return PageNode.instanciateFrom(obj);
       });
+    }
+
+    return stateCache;
   }
 
   /**
@@ -170,16 +192,11 @@ export class Lsx extends React.Component {
 
   renderContents() {
     const lsxContext = this.props.lsxContext;
+    const {
+      isLoading, isError, isCacheExists, nodeTree,
+    } = this.state;
 
-    if (this.state.isLoading) {
-      return (
-        <div className="text-muted">
-          <i className="fa fa-spinner fa-pulse mr-1"></i>
-          <span className="lsx-blink">{lsxContext.tagExpression}</span>
-        </div>
-      );
-    }
-    if (this.state.isError) {
+    if (isError) {
       return (
         <div className="text-warning">
           <i className="fa fa-exclamation-triangle fa-fw"></i>
@@ -187,9 +204,22 @@ export class Lsx extends React.Component {
         </div>
       );
     }
-    // render tree
 
-    return <LsxListView nodeTree={this.state.nodeTree} lsxContext={this.props.lsxContext} />;
+
+    return (
+      <div className={isLoading ? 'lsx-blink' : ''}>
+        { isLoading && (
+          <div className="text-muted">
+            <i className="fa fa-spinner fa-pulse mr-1"></i>
+            {lsxContext.tagExpression}
+            { isCacheExists && <small>&nbsp;(Showing cache..)</small> }
+          </div>
+        ) }
+        { nodeTree && (
+          <LsxListView nodeTree={this.state.nodeTree} lsxContext={this.props.lsxContext} />
+        ) }
+      </div>
+    );
 
   }
 
@@ -201,7 +231,7 @@ export class Lsx extends React.Component {
 
 Lsx.propTypes = {
   appContainer: PropTypes.object.isRequired,
-
   lsxContext: PropTypes.instanceOf(LsxContext).isRequired,
-  lsxStateCache: PropTypes.object,
+
+  forceToFetchData: PropTypes.bool,
 };

+ 10 - 6
packages/plugin-lsx/src/client/js/components/LsxPageList/LsxListView.jsx

@@ -10,7 +10,9 @@ export class LsxListView extends React.Component {
   render() {
     const listView = this.props.nodeTree.map((pageNode) => {
       return (
-        <LsxPage key={pageNode.pagePath} depth={1}
+        <LsxPage
+          key={pageNode.pagePath}
+          depth={1}
           pageNode={pageNode}
           lsxContext={this.props.lsxContext}
         />
@@ -19,12 +21,14 @@ export class LsxListView extends React.Component {
 
     // no contents
     if (this.props.nodeTree.length === 0) {
-      return <div className="text-muted">
-        <small>
-          <i className="fa fa-fw fa-info-circle" aria-hidden="true"></i>
+      return (
+        <div className="text-muted">
+          <small>
+            <i className="fa fa-fw fa-info-circle" aria-hidden="true"></i>
             $lsx(<a href={this.props.lsxContext.pagePath}>{this.props.lsxContext.pagePath}</a>) has no contents
-        </small>
-      </div>;
+          </small>
+        </div>
+      );
     }
 
     return (

+ 3 - 1
packages/plugin-lsx/src/client/js/components/LsxPageList/LsxPage.jsx

@@ -57,7 +57,9 @@ export class LsxPage extends React.Component {
     if (this.state.hasChildren) {
       const pages = pageNode.children.map((pageNode) => {
         return (
-          <LsxPage key={pageNode.pagePath} depth={this.props.depth + 1}
+          <LsxPage
+            key={pageNode.pagePath}
+            depth={this.props.depth + 1}
             pageNode={pageNode}
             lsxContext={this.props.lsxContext}
           />

+ 1 - 0
packages/plugin-lsx/src/client/js/components/LsxPageList/PagePathWrapper.jsx

@@ -22,6 +22,7 @@ export class PagePathWrapper extends React.Component {
 PagePathWrapper.propTypes = {
   pagePath: PropTypes.string.isRequired,
   isExists: PropTypes.bool.isRequired,
+  excludePathString: PropTypes.string,
 };
 
 PagePathWrapper.defaultProps = {

+ 33 - 0
packages/plugin-lsx/src/client/js/util/Interceptor/LsxLogoutInterceptor.js

@@ -0,0 +1,33 @@
+import { BasicInterceptor } from 'growi-commons';
+
+import { TagCacheManagerFactory } from '../TagCacheManagerFactory';
+
+/**
+ * The interceptor for lsx
+ *
+ *  replace lsx tag to a React target element
+ */
+export class LsxLogoutInterceptor extends BasicInterceptor {
+
+  /**
+   * @inheritdoc
+   */
+  isInterceptWhen(contextName) {
+    return (
+      contextName === 'logout'
+    );
+  }
+
+  /**
+   * @inheritdoc
+   */
+  async process(contextName, ...args) {
+    const context = Object.assign(args[0]); // clone
+
+    TagCacheManagerFactory.getInstance().clearAllStateCaches();
+
+    // resolve
+    return context;
+  }
+
+}

+ 12 - 17
packages/plugin-lsx/src/client/js/util/Interceptor/LsxPostRenderInterceptor.js

@@ -3,8 +3,8 @@ import ReactDOM from 'react-dom';
 
 import { BasicInterceptor } from 'growi-commons';
 
+import { LsxContext } from '../LsxContext';
 import { Lsx } from '../../components/Lsx';
-import { LsxCacheHelper } from '../LsxCacheHelper';
 
 /**
  * The interceptor for lsx
@@ -31,35 +31,30 @@ export class LsxPostRenderInterceptor extends BasicInterceptor {
   /**
    * @inheritdoc
    */
-  process(contextName, ...args) {
+  async process(contextName, ...args) {
     const context = Object.assign(args[0]); // clone
 
-    if (context.lsxContextMap == null) {
-      return Promise.resolve();
-    }
+    const isPreview = (contextName === 'postRenderPreviewHtml');
 
     // forEach keys of lsxContextMap
-    Object.keys(context.lsxContextMap).forEach((renderId) => {
-      const elem = document.getElementById(renderId);
+    Object.keys(context.lsxContextMap).forEach((domId) => {
+      const elem = document.getElementById(domId);
 
       if (elem) {
-        // get LsxContext instance from context
-        const lsxContext = context.lsxContextMap[renderId];
+        // instanciate LsxContext from context
+        const lsxContext = new LsxContext(context.lsxContextMap[domId] || {});
+        lsxContext.fromPagePath = context.currentPagePath;
 
-        // check cache exists
-        const cacheKey = LsxCacheHelper.generateCacheKeyFromContext(lsxContext);
-        const lsxStateCache = LsxCacheHelper.getStateCache(cacheKey);
-
-        this.renderReactDOM(lsxContext, lsxStateCache, elem);
+        this.renderReactDOM(lsxContext, elem, isPreview);
       }
     });
 
-    return Promise.resolve();
+    return;
   }
 
-  renderReactDOM(lsxContext, lsxStateCache, elem) {
+  renderReactDOM(lsxContext, elem, isPreview) {
     ReactDOM.render(
-      <Lsx appContainer={this.appContainer} lsxContext={lsxContext} lsxStateCache={lsxStateCache} />,
+      <Lsx appContainer={this.appContainer} lsxContext={lsxContext} forceToFetchData={!isPreview} />,
       elem,
     );
   }

+ 33 - 62
packages/plugin-lsx/src/client/js/util/Interceptor/LsxPreRenderInterceptor.js

@@ -1,7 +1,5 @@
-import { BasicInterceptor } from 'growi-commons';
-
-import { LsxContext } from '../LsxContext';
-import { LsxCacheHelper } from '../LsxCacheHelper';
+import ReactDOM from 'react-dom';
+import { customTagUtils, BasicInterceptor } from 'growi-commons';
 
 /**
  * The interceptor for lsx
@@ -10,6 +8,12 @@ import { LsxCacheHelper } from '../LsxCacheHelper';
  */
 export class LsxPreRenderInterceptor extends BasicInterceptor {
 
+  constructor() {
+    super();
+
+    this.previousPreviewContext = null;
+  }
+
   /**
    * @inheritdoc
    */
@@ -23,75 +27,42 @@ export class LsxPreRenderInterceptor extends BasicInterceptor {
   /**
    * @inheritdoc
    */
-  process(contextName, ...args) {
+  isProcessableParallel() {
+    return false;
+  }
+
+  /**
+   * @inheritdoc
+   */
+  async process(contextName, ...args) {
     const context = Object.assign(args[0]); // clone
     const parsedHTML = context.parsedHTML;
-    const currentPagePath = context.currentPagePath;
-    this.initializeCache(contextName);
-
-    context.lsxContextMap = {};
-
-    // TODO retrieve from args for interceptor
-    const fromPagePath = currentPagePath;
 
-    // see: https://regex101.com/r/NQq3s9/7
-    const pattern = /\$lsx(\((.*?)\)(?=\s|<br>|\$lsx))|\$lsx(\((.*)\)(?!\s|<br>|\$lsx))/g;
-    context.parsedHTML = parsedHTML.replace(pattern, (all, group1, group2, group3, group4) => {
-      const tagExpression = all;
-      let lsxArgs = group2 || group4 || '';
-      lsxArgs = lsxArgs.trim();
+    const tagPattern = /ls|lsx/;
+    const result = customTagUtils.findTagAndReplace(tagPattern, parsedHTML);
 
-      // create contexts
-      const lsxContext = new LsxContext();
-      lsxContext.currentPagePath = currentPagePath;
-      lsxContext.tagExpression = tagExpression;
-      lsxContext.fromPagePath = fromPagePath;
-      lsxContext.lsxArgs = lsxArgs;
+    context.parsedHTML = result.html;
+    context.lsxContextMap = result.tagContextMap;
 
-      const renderId = `lsx-${this.createRandomStr(8)}`;
-
-      context.lsxContextMap[renderId] = lsxContext;
-
-      // return replace strings
-      return this.createReactTargetDom(renderId);
-    });
+    // unmount
+    if (contextName === 'preRenderPreviewHtml') {
+      this.unmountPreviousReactDOMs(context);
+    }
 
     // resolve
-    return Promise.resolve(context);
+    return context;
   }
 
-  createReactTargetDom(renderId) {
-    return `<div id="${renderId}"></div>`;
-  }
-
-  /**
-   * initialize cache
-   *  when contextName is 'preRenderHtml'         -> clear cache
-   *  when contextName is 'preRenderPreviewHtml'  -> doesn't clear cache
-   *
-   * @param {string} contextName
-   *
-   * @memberOf LsxPreRenderInterceptor
-   */
-  initializeCache(contextName) {
-    if (contextName === 'preRenderHtml') {
-      LsxCacheHelper.clearAllStateCaches();
+  unmountPreviousReactDOMs(newContext) {
+    if (this.previousPreviewContext != null) {
+      // forEach keys of lsxContextMap
+      Object.keys(this.previousPreviewContext.lsxContextMap).forEach((domId) => {
+        const elem = document.getElementById(domId);
+        ReactDOM.unmountComponentAtNode(elem);
+      });
     }
-  }
 
-  /**
-   * @see http://qiita.com/ryounagaoka/items/4736c225bdd86a74d59c
-   *
-   * @param {number} length
-   * @return random strings
-   */
-  createRandomStr(length) {
-    const bag = 'abcdefghijklmnopqrstuvwxyz0123456789';
-    let generated = '';
-    for (let i = 0; i < length; i++) {
-      generated += bag[Math.floor(Math.random() * bag.length)];
-    }
-    return generated;
+    this.previousPreviewContext = newContext;
   }
 
 }

+ 0 - 87
packages/plugin-lsx/src/client/js/util/LsxCacheHelper.js

@@ -1,87 +0,0 @@
-import { PageNode } from '../components/PageNode';
-
-export class LsxCacheHelper {
-
-  /**
-   * @private
-   */
-  static retrieveFromSessionStorage() {
-    return JSON.parse(sessionStorage.getItem('lsx-cache')) || {};
-  }
-
-  /**
-   * stringify and save obj
-   *
-   * @static
-   * @param {object} cacheObj
-   *
-   * @memberOf LsxCacheHelper
-   */
-  static saveToSessionStorage(cacheObj) {
-    sessionStorage.setItem('lsx-cache', JSON.stringify(cacheObj));
-  }
-
-  /**
-   * generate cache key for storing to storage
-   *
-   * @static
-   * @param {LsxContext} lsxContext
-   * @returns
-   *
-   * @memberOf LsxCacheHelper
-   */
-  static generateCacheKeyFromContext(lsxContext) {
-    return `${lsxContext.fromPagePath}__${lsxContext.lsxArgs}`;
-  }
-
-  /**
-   *
-   *
-   * @static
-   * @param {string} key
-   * @returns
-   *
-   * @memberOf LsxCacheHelper
-   */
-  static getStateCache(key) {
-    const cacheObj = LsxCacheHelper.retrieveFromSessionStorage();
-    const stateCache = cacheObj[key];
-
-    if (stateCache != null && stateCache.nodeTree != null) {
-      // instanciate PageNode
-      stateCache.nodeTree = stateCache.nodeTree.map((obj) => {
-        return PageNode.instanciateFrom(obj);
-      });
-    }
-
-    return stateCache;
-  }
-
-  /**
-   * store state object of React Component with specified key
-   *
-   * @static
-   * @param {string} key
-   * @param {object} lsxState state object of React Component
-   *
-   * @memberOf LsxCacheHelper
-   */
-  static cacheState(key, lsxState) {
-    const cacheObj = LsxCacheHelper.retrieveFromSessionStorage();
-    cacheObj[key] = lsxState;
-
-    LsxCacheHelper.saveToSessionStorage(cacheObj);
-  }
-
-  /**
-   * clear all state caches
-   *
-   * @static
-   *
-   * @memberOf LsxCacheHelper
-   */
-  static clearAllStateCaches() {
-    LsxCacheHelper.saveToSessionStorage({});
-  }
-
-}

+ 23 - 83
packages/plugin-lsx/src/client/js/util/LsxContext.js

@@ -1,19 +1,23 @@
 import * as url from 'url';
 
-import { pathUtils } from 'growi-commons';
+import { customTagUtils, pathUtils } from 'growi-commons';
 
-export class LsxContext {
+const { TagContext, ArgsParser, OptionParser } = customTagUtils;
+
+export class LsxContext extends TagContext {
+
+  /**
+   * @param {object|TagContext|LsxContext} initArgs
+   */
+  constructor(initArgs) {
+    super(initArgs);
 
-  constructor() {
-    this.currentPagePath = null;
-    this.tagExpression = null;
     this.fromPagePath = null;
-    this.lsxArgs = null;
 
     // initialized after parse()
     this.isParsed = null;
     this.pagePath = null;
-    this.options = null;
+    this.options = {};
   }
 
   parse() {
@@ -21,40 +25,17 @@ export class LsxContext {
       return;
     }
 
-    // initialize
-    let specifiedPath;
-    this.options = {};
-
-    if (this.lsxArgs.length > 0) {
-      const splittedArgs = this.lsxArgs.split(',');
-      let firstArgsKey; let
-        firstArgsValue;
-
-      splittedArgs.forEach((arg, index) => {
-        const trimedArg = arg.trim();
+    const parsedResult = ArgsParser.parse(this.args);
+    this.options = parsedResult.options;
 
-        // parse string like 'key1=value1, key2=value2, ...'
-        // see https://regex101.com/r/pYHcOM/1
-        const match = trimedArg.match(/([^=]+)=?(.+)?/);
-        const key = match[1];
-        const value = match[2] || true;
-        this.options[key] = value;
-
-        if (index === 0) {
-          firstArgsKey = key;
-          firstArgsValue = value;
-        }
-      });
-
-      // determine specifiedPath
-      // order:
-      //   1: lsx(prefix=..., ...)
-      //   2: lsx(firstArgs, ...)
-      //   3: fromPagePath
-      specifiedPath = this.options.prefix
-          || ((firstArgsValue === true) ? firstArgsKey : undefined)
-          || this.fromPagePath;
-    }
+    // determine specifiedPath
+    // order:
+    //   1: lsx(prefix=..., ...)
+    //   2: lsx(firstArgs, ...)
+    //   3: fromPagePath
+    const specifiedPath = this.options.prefix
+        || ((parsedResult.firstArgsValue === true) ? parsedResult.firstArgsKey : undefined)
+        || this.fromPagePath;
 
     // resolve pagePath
     //   when `fromPagePath`=/hoge and `specifiedPath`=./fuga,
@@ -71,51 +52,10 @@ export class LsxContext {
   }
 
   getOptDepth() {
-    // eslint-disable-next-line eqeqeq
-    if (this.options.depth == undefined) {
-      return undefined;
-    }
-    return this.parseNum(this.options.depth);
-  }
-
-  parseNum(str) {
-    // eslint-disable-next-line eqeqeq
-    if (str == undefined) {
-      return undefined;
-    }
-
-    // see: https://regex101.com/r/w4KCwC/3
-    const match = str.match(/^(-?[0-9]+)(([:+]{1})(-?[0-9]+)?)?$/);
-    if (!match) {
+    if (this.options.depth === undefined) {
       return undefined;
     }
-
-    // determine start
-    let start;
-    let end;
-
-    // has operator
-    // eslint-disable-next-line eqeqeq
-    if (match[3] != undefined) {
-      start = +match[1];
-      const operator = match[3];
-
-      // determine end
-      if (operator === ':') {
-        end = +match[4] || -1; // set last(-1) if undefined
-      }
-      else if (operator === '+') {
-        end = +match[4] || 0; // plus zero if undefined
-        end += start;
-      }
-    }
-    // don't have operator
-    else {
-      start = 1;
-      end = +match[1];
-    }
-
-    return { start, end };
+    return OptionParser.parseRange(this.options.depth);
   }
 
 }

+ 39 - 0
packages/plugin-lsx/src/client/js/util/TagCacheManagerFactory.js

@@ -0,0 +1,39 @@
+import { TagCacheManager } from 'growi-commons';
+
+const LSX_STATE_CACHE_NS = 'lsx-state-cache';
+
+
+// validate growi-commons version
+function validateGrowiCommonsVersion() {
+  // TagCacheManager was created on growi-commons@4.0.7
+  if (TagCacheManager == null) {
+    throw new Error(
+      'This version of \'growi-plugin-lsx\' requires \'growi-commons >= 4.0.7\'.\n'
+      + 'To resolve this, please process  either a) or b).\n'
+      + '\n'
+      + 'a) Use \'growi-plugin-lsx@3.0.0\'\n'
+      + 'b) Edit \'package.json\' of growi and upgrade \'growi-commons\' to v4.0.7 or above.',
+    );
+  }
+}
+
+
+let _instance;
+export class TagCacheManagerFactory {
+
+  static getInstance() {
+    validateGrowiCommonsVersion();
+
+    if (_instance == null) {
+      // create generateCacheKey implementation
+      const generateCacheKey = (lsxContext) => {
+        return `${lsxContext.fromPagePath}__${lsxContext.args}`;
+      };
+
+      _instance = new TagCacheManager(LSX_STATE_CACHE_NS, generateCacheKey);
+    }
+
+    return _instance;
+  }
+
+}

+ 11 - 1
packages/plugin-lsx/src/index.js

@@ -1 +1,11 @@
-module.exports = require('./meta');
+const isProd = process.env.NODE_ENV === 'production';
+
+module.exports = {
+  pluginSchemaVersion: 4,
+  serverEntries: [
+    isProd ? 'dist/cjs/server-entry.js' : 'src/server-entry.js',
+  ],
+  clientEntries: [
+    'src/client-entry.js',
+  ],
+};

+ 0 - 11
packages/plugin-lsx/src/meta.js

@@ -1,11 +0,0 @@
-const isProd = process.env.NODE_ENV === 'production';
-
-module.exports = {
-  pluginSchemaVersion: 4,
-  serverEntries: [
-    isProd ? 'dist/cjs/server-entry.js' : 'src/server-entry.js',
-  ],
-  clientEntries: [
-    'src/client-entry.js',
-  ],
-};

+ 10 - 8
packages/plugin-lsx/src/server/routes/index.js

@@ -1,11 +1,13 @@
+
+const loginRequiredFallback = (req, res) => {
+  return res.status(403).send('login required');
+};
+
 module.exports = (crowi, app) => {
   const lsx = require('./lsx')(crowi, app);
-  // const middleware = crowi.require('../util/middlewares');
-  // const debug = require('debug')('growi-plugin:lsx:routes');
-  // const loginRequired = middleware.loginRequired;
-  // const accessTokenParser = middleware.accessTokenParser(crowi, app);
-  // const csrf = middleware.csrfVerify(crowi, app);
-
-  // app.get('/_api/plugins/lsx', accessTokenParser , loginRequired(crowi, app) , lsx.renderHtml);
-  app.get('/_api/plugins/lsx', lsx.listPages);
+
+  const loginRequired = crowi.require('../middlewares/login-required')(crowi, true, loginRequiredFallback);
+  const accessTokenParser = crowi.require('../middlewares/access-token-parser')(crowi);
+
+  app.get('/_api/plugins/lsx', accessTokenParser, loginRequired, lsx.listPages);
 };

+ 81 - 15
packages/plugin-lsx/src/server/routes/lsx.js

@@ -2,6 +2,9 @@ const { customTagUtils } = require('growi-commons');
 
 const { OptionParser } = customTagUtils;
 
+
+const DEFAULT_PAGES_NUM = 50;
+
 class Lsx {
 
   /**
@@ -16,6 +19,11 @@ class Lsx {
    * @memberOf Lsx
    */
   static addDepthCondition(query, pagePath, optionsDepth) {
+    // when option strings is 'depth=', the option value is true
+    if (optionsDepth == null || optionsDepth === true) {
+      throw new Error('The value of depth option is invalid.');
+    }
+
     const range = OptionParser.parseRange(optionsDepth);
     const start = range.start;
     const end = range.end;
@@ -40,12 +48,21 @@ class Lsx {
    * @static
    * @param {any} query
    * @param {any} pagePath
-   * @param {any} optionsNum
+   * @param {number|string} optionsNum
    * @returns
    *
    * @memberOf Lsx
    */
   static addNumCondition(query, pagePath, optionsNum) {
+    // when option strings is 'num=', the option value is true
+    if (optionsNum == null || optionsNum === true) {
+      throw new Error('The value of num option is invalid.');
+    }
+
+    if (typeof optionsNum === 'number') {
+      return query.limit(optionsNum);
+    }
+
     const range = OptionParser.parseRange(optionsNum);
     const start = range.start;
     const end = range.end;
@@ -60,6 +77,47 @@ class Lsx {
     return query.skip(skip).limit(limit);
   }
 
+  /**
+   * add filter condition that filter fetched pages
+   *
+   * @static
+   * @param {any} query
+   * @param {any} pagePath
+   * @param {any} optionsFilter
+   * @param {boolean} isExceptFilter
+   * @returns
+   *
+   * @memberOf Lsx
+   */
+  static addFilterCondition(query, pagePath, optionsFilter, isExceptFilter = false) {
+    // when option strings is 'filter=', the option value is true
+    if (optionsFilter == null || optionsFilter === true) {
+      throw new Error('filter option require value in regular expression.');
+    }
+
+    let filterPath = '';
+    if (optionsFilter.charAt(0) === '^') {
+      // move '^' to the first of path
+      filterPath = new RegExp(`^${pagePath}${optionsFilter.slice(1, optionsFilter.length)}`);
+    }
+    else {
+      filterPath = new RegExp(`^${pagePath}.*${optionsFilter}`);
+    }
+
+    if (isExceptFilter) {
+      return query.and({
+        path: { $not: filterPath },
+      });
+    }
+    return query.and({
+      path: filterPath,
+    });
+  }
+
+  static addExceptCondition(query, pagePath, optionsFilter) {
+    return this.addFilterCondition(query, pagePath, optionsFilter, true);
+  }
+
   /**
    * add sort condition(sort key & sort order)
    *
@@ -75,7 +133,10 @@ class Lsx {
    *
    * @memberOf Lsx
    */
-  static addSortCondition(query, pagePath, optionsSort = 'path', optionsReverse) {
+  static addSortCondition(query, pagePath, optionsSortArg, optionsReverse) {
+    // init sort key
+    const optionsSort = optionsSortArg || 'path';
+
     // the default sort order
     let isReversed = false;
 
@@ -126,19 +187,18 @@ module.exports = (crowi, app) => {
     }
 
     const builder = new Page.PageQueryBuilder(baseQuery);
-    builder.addConditionToListWithDescendants(pagePath, {})
+    if (builder.addConditionToListOnlyDescendants == null) { // for Backward compatibility (<= GROWI v4.0.x)
+      builder.addConditionToListWithDescendants(pagePath);
+    }
+    else {
+      builder.addConditionToListOnlyDescendants(pagePath);
+    }
+
+    builder
       .addConditionToExcludeTrashed()
       .addConditionToExcludeRedirect();
 
-    let promisifiedBuilder = Promise.resolve(builder);
-
-    if (user != null) {
-      const UserGroupRelation = crowi.model('UserGroupRelation');
-      const userGroups = await UserGroupRelation.findAllUserGroupIdsRelatedToUser(user);
-      promisifiedBuilder = builder.addConditionToFilteringByViewer(user, userGroups);
-    }
-
-    return promisifiedBuilder;
+    return Page.addConditionToFilteringByViewerForList(builder, user);
   }
 
   actions.listPages = async(req, res) => {
@@ -154,10 +214,16 @@ module.exports = (crowi, app) => {
       if (options.depth != null) {
         query = Lsx.addDepthCondition(query, pagePath, options.depth);
       }
-      // num
-      if (options.num != null) {
-        query = Lsx.addNumCondition(query, pagePath, options.num);
+      // filter
+      if (options.filter != null) {
+        query = Lsx.addFilterCondition(query, pagePath, options.filter);
       }
+      if (options.except != null) {
+        query = Lsx.addExceptCondition(query, pagePath, options.except);
+      }
+      // num
+      const optionsNum = options.num || DEFAULT_PAGES_NUM;
+      query = Lsx.addNumCondition(query, pagePath, optionsNum);
       // sort
       query = Lsx.addSortCondition(query, pagePath, options.sort, options.reverse);
 

+ 4 - 2
packages/plugin-pukiwiki-like-linker/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/plugin-pukiwiki-like-linker",
-  "version": "4.4.0-RC",
+  "version": "4.4.3-RC.0",
   "description": "GROWI plugin to add PukiwikiLikeLinker",
   "license": "MIT",
   "keywords": [
@@ -9,7 +9,9 @@
   ],
   "main": "dist/cjs/index.js",
   "module": "dist/esm/index.js",
-  "files": ["dist"],
+  "files": [
+    "dist"
+  ],
   "scripts": {
     "build": "run-p build:*",
     "build:cjs": "tsc -p tsconfig.build.cjs.json && tsc-alias -p tsconfig.build.cjs.json",

+ 8 - 1
packages/plugin-pukiwiki-like-linker/src/index.js

@@ -1 +1,8 @@
-module.exports = require('./meta');
+module.exports = {
+  pluginSchemaVersion: 4,
+  serverEntries: [
+  ],
+  clientEntries: [
+    'src/client-entry.js',
+  ],
+};

+ 0 - 8
packages/plugin-pukiwiki-like-linker/src/meta.js

@@ -1,8 +0,0 @@
-module.exports = {
-  pluginSchemaVersion: 4,
-  serverEntries: [
-  ],
-  clientEntries: [
-    'src/client-entry.js',
-  ],
-};

+ 1 - 1
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slack",
-  "version": "4.4.0-RC",
+  "version": "4.4.3-RC.0",
   "license": "MIT",
   "main": "dist/index.js",
   "typings": "dist/index.d.ts",

+ 3 - 4
packages/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slackbot-proxy",
-  "version": "1.0.3",
+  "version": "4.4.3-RC.0",
   "license": "MIT",
   "scripts": {
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",
@@ -18,13 +18,12 @@
     "predev": "yarn cp:bootstrap:dev",
     "test": "cross-env NODE_ENV=test jest --passWithNoTests",
     "lint": "eslint src --ext .ts",
-    "lint:fix": "eslint src --ext .ts --fix",
-    "version": "node -p \"require('./package.json').version\""
+    "lint:fix": "eslint src --ext .ts --fix"
   },
   "// comments for dependencies": {},
   "dependencies": {
     "@godaddy/terminus": "^4.8.0",
-    "@growi/slack": "^4.4.0-RC",
+    "@growi/slack": "^4.4.3-RC.0",
     "@slack/oauth": "^2.0.1",
     "@slack/web-api": "^6.2.4",
     "@tsed/common": "^6.43.0",

+ 5 - 3
packages/slackbot-proxy/src/middlewares/slack-to-growi/join-to-conversation.ts

@@ -23,9 +23,11 @@ export class JoinToConversationMiddleware implements IMiddleware {
     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
     const client = generateWebClient(authorizeResult.botToken!);
 
-    const joinResult = await client.conversations.join({ channel: body.channel_id });
-    if (!joinResult.ok) {
-      logger.error(joinResult.error, joinResult);
+    try {
+      await client.conversations.join({ channel: body.channel_id });
+    }
+    catch (err) {
+      logger.error(err);
     }
   }
 

+ 4 - 2
packages/ui/package.json

@@ -1,13 +1,15 @@
 {
   "name": "@growi/ui",
-  "version": "4.4.0-RC",
+  "version": "4.4.3-RC.0",
   "description": "GROWI UI Libraries",
   "license": "MIT",
   "keywords": [
     "growi"
   ],
   "module": "dist/esm/index.js",
-  "files": ["dist"],
+  "files": [
+    "dist"
+  ],
   "scripts": {
     "lint:js": "eslint **/*.{js,jsx,ts,tsx}",
     "lint:styles": "stylelint src/styles/scss/**/*.scss",

Разница между файлами не показана из-за своего большого размера
+ 441 - 74
yarn.lock


Некоторые файлы не были показаны из-за большого количества измененных файлов