ryoji-s 3 лет назад
Родитель
Сommit
20ddf4afd3
37 измененных файлов с 267 добавлено и 177 удалено
  1. 53 1
      CHANGELOG.md
  2. 1 1
      lerna.json
  3. 1 1
      package.json
  4. 4 4
      packages/app/docker/README.md
  5. 11 11
      packages/app/package.json
  6. 1 0
      packages/app/resource/locales/en_US/translation.json
  7. 1 0
      packages/app/resource/locales/ja_JP/translation.json
  8. 2 1
      packages/app/resource/locales/zh_CN/translation.json
  9. 3 2
      packages/app/src/components/Common/Dropdown/PageItemControl.tsx
  10. 24 26
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  11. 3 7
      packages/app/src/components/Navbar/GrowiSubNavigation.tsx
  12. 8 8
      packages/app/src/components/Navbar/SubNavButtons.tsx
  13. 1 0
      packages/app/src/components/PageList/PageListItemL.tsx
  14. 26 32
      packages/app/src/components/SearchPage/SearchResultContent.tsx
  15. 21 14
      packages/app/src/components/Sidebar/Tag.tsx
  16. 13 24
      packages/app/src/components/TagCloudBox.tsx
  17. 2 2
      packages/app/src/components/TagList.tsx
  18. 6 1
      packages/app/src/server/routes/attachment.js
  19. 18 8
      packages/app/src/server/service/import.js
  20. 3 1
      packages/app/src/server/service/search-delegator/elasticsearch.ts
  21. 6 0
      packages/app/src/styles/_on-edit.scss
  22. 1 1
      packages/app/src/styles/_override-codemirror.scss
  23. 10 0
      packages/app/src/styles/_tag.scss
  24. 10 0
      packages/app/src/styles/theme/_apply-colors-dark.scss
  25. 10 0
      packages/app/src/styles/theme/_apply-colors-light.scss
  26. 1 1
      packages/codemirror-textlint/package.json
  27. 1 1
      packages/core/package.json
  28. 1 1
      packages/plugin-attachment-refs/package.json
  29. 1 1
      packages/plugin-lsx/package.json
  30. 5 4
      packages/plugin-lsx/src/client/js/components/Lsx.jsx
  31. 4 1
      packages/plugin-lsx/src/client/js/components/LsxPageList/LsxListView.jsx
  32. 4 3
      packages/plugin-lsx/src/client/js/components/LsxPageList/LsxPage.jsx
  33. 1 1
      packages/plugin-pukiwiki-like-linker/package.json
  34. 1 1
      packages/slack/package.json
  35. 2 2
      packages/slackbot-proxy/package.json
  36. 1 1
      packages/ui/package.json
  37. 6 15
      yarn.lock

+ 53 - 1
CHANGELOG.md

@@ -1,9 +1,47 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v5.0.7...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v5.0.8...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v5.0.8](https://github.com/weseek/growi/compare/v5.0.7...v5.0.8) - 2022-06-07
+
+### 🚀 Improvement
+
+- imprv: Fix subnavigation spacing (#5995) @yuki-takei
+- imprv: Set Content-Length header in response of attachment (#5972) @hiroki-hgs
+- imprv: Fix sidebar tag layout (#5984) @jam411
+- imprv: PageStatusAlert labels when data is outdated (#5961) @yuki-takei
+- imprv: Delete NotFoundAlert from not found page (#5919) @Shunm634-source
+
+### 🐛 Bug Fixes
+
+- fix: Too many footstamps icons are shown by lsx output 3 (#6000) @yuki-takei
+- fix: Adjust PageItemControl alignment (#5994) @yuki-takei
+- fix: CodeMirror placeholder color (#5993) @yuki-takei
+- fix: Chinese notation is broken on create new page modal (#5973) @jam411
+- fix: Document timestamps does not updated (#5979) @yuki-takei
+- fix: Slack channels are not automatically filled after setting up user trigger notification on v5.0.x (#5911) @kaoritokashiki
+- fix: Login required when viewing sharelink page (#5959) @yuki-takei
+- fix: Editor scroll sync by Preview scrolling does not work (#5949) @yuki-takei
+
+### 🧰 Maintenance
+
+- support: Enable garbage collection at runtime with expose-gc package (#5986) @yuki-takei
+- support: Upgrade aws-sdk to v3 (#5863) @mudana-grune
+
+## [v4.5.22](https://github.com/weseek/growi/compare/v4.5.21...v4.5.22) - 2022-06-07
+
+### 🐛 Bug Fixes
+
+- fix: Fixed the bug of auto-filling unintended values into the Email field of the User settings (#5885) @Shunm634-source
+- fix: google-oauth2 Automatically bind external accounts does not work (#5891) @kaoritokashiki
+- fix: Slack channels are not automatically filled after setting up user trigger notification (#5976) @kaoritokashiki
+
+### 🧰 Maintenance
+
+- support: Enable garbage collection at runtime with expose-gc package (#5998) @kaoritokashiki
+
 ## [v5.0.7](https://github.com/weseek/growi/compare/v5.0.6...v5.0.7) - 2022-05-30
 
 ### 💎 Features
@@ -43,6 +81,13 @@
 - fix: Can not toggle textlint function on v5.0.x (#5854) @kaoritokashiki
 - fix(google-oauth2): Automatically bind external accounts  does not work on v5.0.x (#5886) @kaoritokashiki
 
+## [v4.5.21](https://github.com/weseek/growi/compare/v4.5.20...v4.5.21) - 2022-05-23
+
+### 🐛 Bug Fixes
+
+- fix: Can not toggle textlint function on v4.5.x (https://github.com/weseek/growi/pull/5855) @kaoritokashiki
+- fix: Error on searching (https://github.com/weseek/growi/pull/5873) @miya
+
 ## [v5.0.5](https://github.com/weseek/growi/compare/v5.0.4...v5.0.5) - 2022-05-16
 
 ### 💎 Features
@@ -67,6 +112,13 @@
 
 - support: Typescriptize tag model (#5778) @kaoritokashiki
 
+
+## [v4.5.20](https://github.com/weseek/growi/compare/v4.5.19...v4.5.20) - 2022-05-12
+
+### 🐛 Bug Fixes
+
+- fix: Guest user cannot access share link pages (#5819) @kaoritokashiki
+
 ## [v5.0.4](https://github.com/weseek/growi/compare/v5.0.3...v5.0.4) - 2022-04-28
 
 ### 💎 Features

+ 1 - 1
lerna.json

@@ -1,7 +1,7 @@
 {
   "npmClient": "yarn",
   "useWorkspaces": true,
-  "version": "5.0.8-RC.0",
+  "version": "5.0.9-RC.0",
   "packages": [
     "packages/*"
   ]

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "5.0.8-RC.0",
+  "version": "5.0.9-RC.0",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",

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

@@ -10,10 +10,10 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 
-* [`5.0.7`, `5.0`, `5`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v5.0.7/docker/Dockerfile)
-* [`5.0.7-nocdn`, `5.0-nocdn`, `5-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v5.0.7/docker/Dockerfile)
-* [`4.5.15`, `4.5`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.15/docker/Dockerfile)
-* [`4.5.15-nocdn`, `4.5-nocdn`, `4-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.15/docker/Dockerfile)
+* [`5.0.8`, `5.0`, `5`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v5.0.8/docker/Dockerfile)
+* [`5.0.8-nocdn`, `5.0-nocdn`, `5-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v5.0.8/docker/Dockerfile)
+* [`4.5.22`, `4.5`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.22/docker/Dockerfile)
+* [`4.5.22-nocdn`, `4.5-nocdn`, `4-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.22/docker/Dockerfile)
 * [`4.4.13`, `4.4` (Dockerfile)](https://github.com/weseek/growi/blob/v4.4.13/docker/Dockerfile)
 * [`4.4.13-nocdn`, `4.4-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.4.13/docker/Dockerfile)
 

+ 11 - 11
packages/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "5.0.8-RC.0",
+  "version": "5.0.9-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -11,7 +11,7 @@
     "clean": "npx -y shx rm -rf dist transpiled",
     "prebuild": "yarn cross-env NODE_ENV=production run-p clean resources:*",
     "postbuild": "npx -y shx mv transpiled/src dist && npx -y shx cp -r src/server/views dist/server/ && npx -y shx rm -rf transpiled",
-    "server": "yarn cross-env NODE_ENV=production node -r dotenv-flow/config --expose_gc dist/server/app.js",
+    "server": "yarn cross-env NODE_ENV=production node -r dotenv-flow/config dist/server/app.js",
     "server:ci": "yarn server --ci",
     "preserver": "yarn cross-env NODE_ENV=production yarn migrate",
     "migrate": "node -r dotenv-flow/config node_modules/.bin/migrate-mongo up",
@@ -19,7 +19,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 ts-node-dev --inspect --expose-gc -r tsconfig-paths/register -r dotenv-flow/config --transpile-only src/server/app.ts",
+    "dev:server": "yarn cross-env NODE_ENV=development ts-node-dev --inspect -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 dev:migrate:up",
     "dev:migrate-mongo": "yarn cross-env NODE_ENV=development yarn ts-node node_modules/.bin/migrate-mongo",
@@ -64,11 +64,11 @@
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
-    "@growi/codemirror-textlint": "^5.0.8-RC.0",
-    "@growi/plugin-attachment-refs": "^5.0.8-RC.0",
-    "@growi/plugin-lsx": "^5.0.8-RC.0",
-    "@growi/plugin-pukiwiki-like-linker": "^5.0.8-RC.0",
-    "@growi/slack": "^5.0.8-RC.0",
+    "@growi/codemirror-textlint": "^5.0.9-RC.0",
+    "@growi/plugin-attachment-refs": "^5.0.9-RC.0",
+    "@growi/plugin-lsx": "^5.0.9-RC.0",
+    "@growi/plugin-pukiwiki-like-linker": "^5.0.9-RC.0",
+    "@growi/slack": "^5.0.9-RC.0",
     "@promster/express": "^7.0.2",
     "@promster/server": "^7.0.4",
     "@slack/events-api": "^3.0.0",
@@ -99,6 +99,7 @@
     "esa-node": "^0.2.2",
     "escape-string-regexp": "=4.0.0",
     "eslint-plugin-regex": "^1.8.0",
+    "expose-gc": "^1.0.0",
     "express": "^4.16.1",
     "express-bunyan-logger": "^1.3.3",
     "express-mongo-sanitize": "^2.1.0",
@@ -145,7 +146,6 @@
     "react-dnd-html5-backend": "^14.1.0",
     "react-image-crop": "^8.3.0",
     "react-multiline-clamp": "^2.0.0",
-    "react-tagcloud": "^2.1.1",
     "reconnecting-websocket": "^4.4.0",
     "redis": "^3.0.2",
     "rimraf": "^3.0.0",
@@ -169,7 +169,7 @@
   },
   "devDependencies": {
     "@alienfast/i18next-loader": "^1.1.4",
-    "@growi/ui": "^5.0.8-RC.0",
+    "@growi/ui": "^5.0.9-RC.0",
     "@handsontable/react": "=2.1.0",
     "@types/compression": "^1.7.0",
     "@types/express": "^4.17.11",
@@ -190,7 +190,6 @@
     "diff2html": "^3.1.2",
     "eazy-logger": "^3.1.0",
     "emoji-mart": "npm:panta82-emoji-mart@^3.0.1",
-    "markdown-it-emoji-mart": "^0.1.1",
     "eslint-plugin-cypress": "^2.12.1",
     "eslint-plugin-regex": "^1.8.0",
     "file-loader": "^5.0.2",
@@ -208,6 +207,7 @@
     "markdown-it-blockdiag": "^1.1.1",
     "markdown-it-drawio-viewer": "^1.3.1",
     "markdown-it-emoji": "^1.4.0",
+    "markdown-it-emoji-mart": "^0.1.1",
     "markdown-it-footnote": "^3.0.1",
     "markdown-it-mathjax": "^2.0.0",
     "markdown-it-named-headers": "^0.0.4",

+ 1 - 0
packages/app/resource/locales/en_US/translation.json

@@ -147,6 +147,7 @@
   "Shareable link": "Shareable link",
   "The whitelist of registration permission E-mail address": "The whitelist of registration permission E-mail address",
   "Add tags for this page": "Add tags for this page",
+  "tag_list": "Tag list",
   "popular_tags": "Popular tags",
   "Check All tags": "check all tags",
   "You have no tag, You can set tags on pages": "You have no tag, You can set tags on pages",

+ 1 - 0
packages/app/resource/locales/ja_JP/translation.json

@@ -146,6 +146,7 @@
   "Shareable link": "このページの共有用URL",
   "The whitelist of registration permission E-mail address": "登録許可メールアドレスの<br>ホワイトリスト",
   "Add tags for this page": "タグを付ける",
+  "tag_list": "タグ一覧",
   "popular_tags": "人気のタグ",
   "Check All tags": "全てのタグを見る",
   "You have no tag, You can set tags on pages": "使用中のタグがありません",

+ 2 - 1
packages/app/resource/locales/zh_CN/translation.json

@@ -155,6 +155,7 @@
 	"Shareable link": "可分享链接",
 	"The whitelist of registration permission E-mail address": "注册许可电子邮件地址的白名单",
 	"Add tags for this page": "添加标签",
+  "tag_list": "标签列表",
   "popular_tags": "流行标签",
   "Check All tags": "检查所有标签",
 	"You have no tag, You can set tags on pages": "你没有标签,可以在页面上设置标签",
@@ -524,7 +525,7 @@
 	"template": {
 		"modal_label": {
 			"Create/Edit Template Page": "创建/编辑模板页",
-			"Create template under": "在下面创建模板页:<br/><code><small>%s</small></code>"
+			"Create template under": "在下面创建模板页"
 		},
 		"option_label": {
 			"create/edit": "创建/编辑模板页。",

+ 3 - 2
packages/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -40,6 +40,7 @@ type CommonProps = {
 
   additionalMenuItemRenderer?: React.FunctionComponent<AdditionalMenuItemsRendererProps>,
   isInstantRename?: boolean,
+  alignRight?: boolean,
 }
 
 
@@ -55,7 +56,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
     pageId, isLoading,
     pageInfo, isEnableActions, forceHideMenuItems,
     onClickBookmarkMenuItem, onClickRenameMenuItem, onClickDuplicateMenuItem, onClickDeleteMenuItem, onClickRevertMenuItem,
-    additionalMenuItemRenderer: AdditionalMenuItems, isInstantRename,
+    additionalMenuItemRenderer: AdditionalMenuItems, isInstantRename, alignRight,
   } = props;
 
 
@@ -205,7 +206,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
   }
 
   return (
-    <DropdownMenu positionFixed modifiers={{ preventOverflow: { boundariesElement: undefined } }}>
+    <DropdownMenu positionFixed modifiers={{ preventOverflow: { boundariesElement: undefined } }} right={alignRight}>
       {contents}
     </DropdownMenu>
   );

+ 24 - 26
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -245,36 +245,34 @@ const GrowiContextualSubNavigation = (props) => {
       mutateEditorMode(viewType);
     }
 
-    const className = `d-flex flex-column align-items-end justify-content-center ${isViewMode ? ' h-50' : ''}`;
-
     return (
       <>
-        <div className={className}>
+        <div className="d-flex flex-column align-items-end justify-content-center py-md-2" style={{ gap: `${isCompactMode ? '5px' : '7px'}` }}>
           { pageId != null && isViewMode && (
-            <SubNavButtons
-              isCompactMode={isCompactMode}
-              pageId={pageId}
-              shareLinkId={shareLinkId}
-              revisionId={revisionId}
-              path={path}
-              disableSeenUserInfoPopover={isSharedUser}
-              showPageControlDropdown={isAbleToShowPageManagement}
-              additionalMenuItemRenderer={props => (
-                <AdditionalMenuItems
-                  {...props}
-                  pageId={pageId}
-                  revisionId={revisionId}
-                  isLinkSharingDisabled={isLinkSharingDisabled}
-                  onClickTemplateMenuItem={templateMenuItemClickHandler}
-                />
-              )}
-              onClickDuplicateMenuItem={duplicateItemClickedHandler}
-              onClickRenameMenuItem={renameItemClickedHandler}
-              onClickDeleteMenuItem={deleteItemClickedHandler}
-            />
+            <div className="h-50">
+              <SubNavButtons
+                isCompactMode={isCompactMode}
+                pageId={pageId}
+                shareLinkId={shareLinkId}
+                revisionId={revisionId}
+                path={path}
+                disableSeenUserInfoPopover={isSharedUser}
+                showPageControlDropdown={isAbleToShowPageManagement}
+                additionalMenuItemRenderer={props => (
+                  <AdditionalMenuItems
+                    {...props}
+                    pageId={pageId}
+                    revisionId={revisionId}
+                    isLinkSharingDisabled={isLinkSharingDisabled}
+                    onClickTemplateMenuItem={templateMenuItemClickHandler}
+                  />
+                )}
+                onClickDuplicateMenuItem={duplicateItemClickedHandler}
+                onClickRenameMenuItem={renameItemClickedHandler}
+                onClickDeleteMenuItem={deleteItemClickedHandler}
+              />
+            </div>
           ) }
-        </div>
-        <div className={`${className} ${isCompactMode ? '' : 'mt-2'}`}>
           {isAbleToShowPageEditorModeManager && (
             <PageEditorModeManager
               onPageEditorModeButtonClicked={onPageEditorModeButtonClicked}

+ 3 - 7
packages/app/src/components/Navbar/GrowiSubNavigation.tsx

@@ -1,19 +1,17 @@
 import React from 'react';
 
 import { IPageHasId } from '~/interfaces/page';
-
+import { IUser } from '~/interfaces/user';
 import {
   EditorMode, useEditorMode,
 } from '~/stores/ui';
 
 import TagLabels from '../Page/TagLabels';
+import PagePathNav from '../PagePathNav';
 
 import AuthorInfo from './AuthorInfo';
 import DrawerToggler from './DrawerToggler';
 
-import PagePathNav from '../PagePathNav';
-import { IUser } from '~/interfaces/user';
-
 
 type Props = {
   page: Partial<IPageHasId>,
@@ -85,9 +83,7 @@ export const GrowiSubNavigation = (props: Props): JSX.Element => {
       {/* Right side */}
       <div className="d-flex">
 
-        <div className="d-flex flex-column py-md-2" style={{ gap: `${isCompactMode ? '5px' : '0'}` }}>
-          { Controls && <Controls></Controls> }
-        </div>
+        { Controls && <Controls></Controls> }
 
         {/* Page Authors */}
         { (showPageAuthors && !isCompactMode) && (

+ 8 - 8
packages/app/src/components/Navbar/SubNavButtons.tsx

@@ -1,23 +1,22 @@
 import React, { useCallback } from 'react';
 
+import { toggleBookmark, toggleLike, toggleSubscribe } from '~/client/services/page-operation';
 import {
   IPageInfoAll, IPageToDeleteWithMeta, IPageToRenameWithMeta, isIPageInfoForEntity, isIPageInfoForOperation,
 } from '~/interfaces/page';
-
-import { useSWRxPageInfo } from '../../stores/page';
-import { useSWRBookmarkInfo } from '../../stores/bookmark';
-import { useSWRxUsersList } from '../../stores/user';
 import { useIsGuestUser } from '~/stores/context';
 import { IPageForPageDuplicateModal } from '~/stores/modal';
 
-import SubscribeButton from '../SubscribeButton';
-import LikeButtons from '../LikeButtons';
+import { useSWRBookmarkInfo } from '../../stores/bookmark';
+import { useSWRxPageInfo } from '../../stores/page';
+import { useSWRxUsersList } from '../../stores/user';
 import BookmarkButtons from '../BookmarkButtons';
-import SeenUserInfo from '../User/SeenUserInfo';
-import { toggleBookmark, toggleLike, toggleSubscribe } from '~/client/services/page-operation';
 import {
   AdditionalMenuItemsRendererProps, ForceHideMenuItems, MenuItemType, PageItemControl,
 } from '../Common/Dropdown/PageItemControl';
+import LikeButtons from '../LikeButtons';
+import SubscribeButton from '../SubscribeButton';
+import SeenUserInfo from '../User/SeenUserInfo';
 
 
 type CommonProps = {
@@ -184,6 +183,7 @@ const SubNavButtonsSubstance = (props: SubNavButtonsSubstanceProps): JSX.Element
       ) }
       { showPageControlDropdown && (
         <PageItemControl
+          alignRight
           pageId={pageId}
           pageInfo={pageInfo}
           isEnableActions={!isGuestUser}

+ 1 - 0
packages/app/src/components/PageList/PageListItemL.tsx

@@ -225,6 +225,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
               {/* doropdown icon includes page control buttons */}
               <div className="ml-auto">
                 <PageItemControl
+                  alignRight
                   pageId={pageData._id}
                   pageInfo={isIPageInfoForListing(pageMeta) ? pageMeta : undefined}
                   isEnableActions={isEnableActions}

+ 26 - 32
packages/app/src/components/SearchPage/SearchResultContent.tsx

@@ -1,33 +1,31 @@
 import React, {
   FC, useCallback, useEffect, useRef,
 } from 'react';
-import { useTranslation } from 'react-i18next';
 
+import { useTranslation } from 'react-i18next';
 import { DropdownItem } from 'reactstrap';
 
+import { exportAsMarkdown } from '~/client/services/page-operation';
+import { toastSuccess } from '~/client/util/apiNotification';
+import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import { IPageToDeleteWithMeta, IPageToRenameWithMeta, IPageWithMeta } from '~/interfaces/page';
 import { IPageSearchMeta } from '~/interfaces/search';
 import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
+import {
+  usePageDuplicateModal, usePageRenameModal, usePageDeleteModal,
+} from '~/stores/modal';
+import { useDescendantsPageListForCurrentPathTermManager } from '~/stores/page';
 import { usePageTreeTermManager } from '~/stores/page-listing';
 import { useFullTextSearchTermManager } from '~/stores/search';
-import { useDescendantsPageListForCurrentPathTermManager } from '~/stores/page';
 
-import { exportAsMarkdown } from '~/client/services/page-operation';
-import { toastSuccess } from '~/client/util/apiNotification';
-
-import PageContentFooter from '../PageContentFooter';
-import PageComment from '../PageComment';
 
-import RevisionLoader from '../Page/RevisionLoader';
 import AppContainer from '../../client/services/AppContainer';
-import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
+import { AdditionalMenuItemsRendererProps, ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
 import { GrowiSubNavigation } from '../Navbar/GrowiSubNavigation';
 import { SubNavButtons } from '../Navbar/SubNavButtons';
-import { AdditionalMenuItemsRendererProps, ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
-
-import {
-  usePageDuplicateModal, usePageRenameModal, usePageDeleteModal,
-} from '~/stores/modal';
+import RevisionLoader from '../Page/RevisionLoader';
+import PageComment from '../PageComment';
+import PageContentFooter from '../PageContentFooter';
 
 
 type AdditionalMenuItemsProps = AdditionalMenuItemsRendererProps & {
@@ -179,24 +177,20 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
       : page.revision._id;
 
     return (
-      <>
-        <div className="h-50 d-flex flex-column align-items-end justify-content-center">
-          <SubNavButtons
-            pageId={page._id}
-            revisionId={revisionId}
-            path={page.path}
-            showPageControlDropdown={showPageControlDropdown}
-            forceHideMenuItems={forceHideMenuItems}
-            additionalMenuItemRenderer={props => <AdditionalMenuItems {...props} pageId={page._id} revisionId={revisionId} />}
-            isCompactMode
-            onClickDuplicateMenuItem={duplicateItemClickedHandler}
-            onClickRenameMenuItem={renameItemClickedHandler}
-            onClickDeleteMenuItem={deleteItemClickedHandler}
-          />
-        </div>
-        <div className="h-50 d-flex flex-column align-items-end justify-content-center">
-        </div>
-      </>
+      <div className="d-flex flex-column align-items-end justify-content-center py-md-2">
+        <SubNavButtons
+          pageId={page._id}
+          revisionId={revisionId}
+          path={page.path}
+          showPageControlDropdown={showPageControlDropdown}
+          forceHideMenuItems={forceHideMenuItems}
+          additionalMenuItemRenderer={props => <AdditionalMenuItems {...props} pageId={page._id} revisionId={revisionId} />}
+          isCompactMode
+          onClickDuplicateMenuItem={duplicateItemClickedHandler}
+          onClickRenameMenuItem={renameItemClickedHandler}
+          onClickDeleteMenuItem={deleteItemClickedHandler}
+        />
+      </div>
     );
   }, [page, showPageControlDropdown, forceHideMenuItems, duplicateItemClickedHandler, renameItemClickedHandler, deleteItemClickedHandler]);
 

+ 21 - 14
packages/app/src/components/Sidebar/Tag.tsx

@@ -10,6 +10,7 @@ import TagList from '../TagList';
 
 
 const PAGING_LIMIT = 10;
+const TAG_CLOUD_LIMIT = 20;
 
 const Tag: FC = () => {
   const [activePage, setActivePage] = useState<number>(1);
@@ -20,6 +21,9 @@ const Tag: FC = () => {
   const totalCount: number = tagDataList?.totalCount || 0;
   const isLoading = tagDataList === undefined && error == null;
 
+  const { data: tagDataCloud } = useSWRxTagsList(TAG_CLOUD_LIMIT, 0);
+  const tagCloudData: IDataTagCount[] = tagDataCloud?.data || [];
+
   const { t } = useTranslation('');
 
   const setOffsetByPageNumber = useCallback((selectedPageNumber: number) => {
@@ -44,21 +48,8 @@ const Tag: FC = () => {
           <i className="icon icon-reload"></i>
         </button>
       </div>
-      <h2 className="my-3">{t('popular_tags')}</h2>
-
-      <div className="px-3 text-center">
-        <TagCloudBox tags={tagData} />
-      </div>
 
-      <div className="d-flex justify-content-center my-5">
-        <button
-          className="btn btn-primary rounded px-5"
-          type="button"
-          onClick={() => { window.location.href = '/tags' }}
-        >
-          {t('Check All tags')}
-        </button>
-      </div>
+      <h3 className="my-3">{t('tag_list')}</h3>
 
       { isLoading
         ? (
@@ -76,6 +67,22 @@ const Tag: FC = () => {
           />
         )
       }
+
+      <div className="d-flex justify-content-center my-5">
+        <button
+          className="btn btn-primary rounded px-4"
+          type="button"
+          onClick={() => { window.location.href = '/tags' }}
+        >
+          {t('Check All tags')}
+        </button>
+      </div>
+
+      <h3 className="my-3">{t('popular_tags')}</h3>
+
+      <div className="text-center">
+        <TagCloudBox tags={tagCloudData} />
+      </div>
     </div>
   );
 

+ 13 - 24
packages/app/src/components/TagCloudBox.tsx

@@ -1,7 +1,5 @@
 import React, { FC, memo } from 'react';
 
-import { TagCloud } from 'react-tagcloud';
-
 import { IDataTagCount } from '~/interfaces/tag';
 
 type Props = {
@@ -16,34 +14,25 @@ const defaultProps = {
   isDisableRandomColor: true,
 };
 
-const MIN_FONT_SIZE = 10;
-const MAX_FONT_SIZE = 24;
 const MAX_TAG_TEXT_LENGTH = 8;
 
 const TagCloudBox: FC<Props> = memo((props:(Props & typeof defaultProps)) => {
-  const {
-    tags, minSize, maxSize, isDisableRandomColor,
-  } = props;
+  const { tags } = props;
   const maxTagTextLength: number = props.maxTagTextLength ?? MAX_TAG_TEXT_LENGTH;
 
+  const tagElements = tags.map((tag:IDataTagCount) => {
+    const tagNameFormat = (tag.name).length > maxTagTextLength ? `${(tag.name).slice(0, maxTagTextLength)}...` : tag.name;
+    return (
+      <a key={tag.name} href={`/_search?q=tag:${tag.name}`} className="grw-tag-label badge badge-secondary mr-2">
+        {tagNameFormat}
+      </a>
+    );
+  });
+
   return (
-    <>
-      <TagCloud
-        minSize={minSize ?? MIN_FONT_SIZE}
-        maxSize={maxSize ?? MAX_FONT_SIZE}
-        tags={tags.map((tag:IDataTagCount) => {
-          return {
-            // text truncation
-            value: (tag.name).length > maxTagTextLength ? `${(tag.name).slice(0, maxTagTextLength)}...` : tag.name,
-            count: tag.count,
-          };
-        })}
-        disableRandomColor={isDisableRandomColor}
-        style={{ cursor: 'pointer' }}
-        className="simple-cloud text-secondary"
-        onClick={(target) => { window.location.href = `/_search?q=tag:${encodeURIComponent(target.value)}` }}
-      />
-    </>
+    <div className="grw-popular-tag-labels">
+      {tagElements}
+    </div>
   );
 
 });

+ 2 - 2
packages/app/src/components/TagList.tsx

@@ -38,7 +38,7 @@ const TagList: FC<TagListProps> = (props:(TagListProps & typeof defaultProps)) =
           href={`/_search?q=tag:${encodeURIComponent(tag.name)}`}
           className={tagListClasses}
         >
-          <div className="text-truncate">{tag.name}</div>
+          <div className="text-truncate list-tag-name">{tag.name}</div>
           <div className="ml-4 my-auto py-1 px-2 list-tag-count badge badge-secondary text-white">{tag.count}</div>
         </a>
       );
@@ -51,7 +51,7 @@ const TagList: FC<TagListProps> = (props:(TagListProps & typeof defaultProps)) =
 
   return (
     <>
-      <ul className="list-group text-left mb-4">
+      <ul className="list-group text-left mb-5">
         {generateTagList(tagData)}
       </ul>
       {isPaginationShown

+ 6 - 1
packages/app/src/server/routes/attachment.js

@@ -7,7 +7,6 @@ const logger = loggerFactory('growi:routes:attachment');
 
 const { serializePageSecurely } = require('../models/serializers/page-serializer');
 const { serializeRevisionSecurely } = require('../models/serializers/revision-serializer');
-
 const ApiResponse = require('../util/apiResponse');
 
 /**
@@ -231,6 +230,12 @@ module.exports = function(crowi, app) {
       'Last-Modified': attachment.createdAt.toUTCString(),
     });
 
+    if (!attachment.fileSize) {
+      res.set({
+        'Content-Length': attachment.fileSize,
+      });
+    }
+
     // download
     if (forceDownload) {
       res.set({

+ 18 - 8
packages/app/src/server/service/import.js

@@ -1,23 +1,24 @@
+import gc from 'expose-gc/function';
+
 import loggerFactory from '~/utils/logger';
 
-const logger = loggerFactory('growi:services:ImportService'); // eslint-disable-line no-unused-vars
 const fs = require('fs');
 const path = require('path');
-
-const isIsoDate = require('is-iso-date');
-const parseISO = require('date-fns/parseISO');
-
 const { Writable, Transform } = require('stream');
+
 const JSONStream = require('JSONStream');
+const parseISO = require('date-fns/parseISO');
+const isIsoDate = require('is-iso-date');
+const mongoose = require('mongoose');
 const streamToPromise = require('stream-to-promise');
 const unzipper = require('unzipper');
 
-const mongoose = require('mongoose');
+const CollectionProgressingStatus = require('../models/vo/collection-progressing-status');
+const { createBatchStream } = require('../util/batch-stream');
 
 const { ObjectId } = mongoose.Types;
 
-const { createBatchStream } = require('../util/batch-stream');
-const CollectionProgressingStatus = require('../models/vo/collection-progressing-status');
+const logger = loggerFactory('growi:services:ImportService'); // eslint-disable-line no-unused-vars
 
 
 const BULK_IMPORT_SIZE = 100;
@@ -286,6 +287,15 @@ class ImportService {
 
           emitProgressEvent(collectionProgress, errors);
 
+          try {
+            // First aid to prevent unexplained memory leaks
+            logger.info('global.gc() invoked.');
+            gc();
+          }
+          catch (err) {
+            logger.error('fail garbage collection: ', err);
+          }
+
           callback();
         },
         final(callback) {

+ 3 - 1
packages/app/src/server/service/search-delegator/elasticsearch.ts

@@ -3,6 +3,7 @@ import { URL } from 'url';
 
 import elasticsearch6 from '@elastic/elasticsearch6';
 import elasticsearch7 from '@elastic/elasticsearch7';
+import gc from 'expose-gc/function';
 import mongoose from 'mongoose';
 import streamToPromise from 'stream-to-promise';
 
@@ -593,7 +594,8 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
         if (invokeGarbageCollection) {
           try {
             // First aid to prevent unexplained memory leaks
-            global.gc();
+            logger.info('global.gc() invoked.');
+            gc();
           }
           catch (err) {
             logger.error('fail garbage collection: ', err);

+ 6 - 0
packages/app/src/styles/_on-edit.scss

@@ -181,6 +181,12 @@ body.on-edit {
     }
   }
 
+  .grw-copy-dropdown {
+    .btn-copy {
+      padding: 3px !important; // overwrite padding
+    }
+  }
+
   &.builtin-editor {
     /*****************
     * Editor styles

+ 1 - 1
packages/app/src/styles/_override-codemirror.scss

@@ -41,7 +41,7 @@
   }
 
   // overwrite .CodeMirror-placeholder
-  pre.CodeMirror-placeholder {
+  pre.CodeMirror-line-like.CodeMirror-placeholder {
     color: $text-muted;
   }
 }

+ 10 - 0
packages/app/src/styles/_tag.scss

@@ -12,6 +12,16 @@
   }
 }
 
+.grw-popular-tag-labels {
+  text-align: left;
+
+  .grw-tag-label {
+    font-size: 10px;
+    font-weight: normal;
+    border-radius: $border-radius;
+  }
+}
+
 #edit-tag-modal {
   .form-control {
     height: auto;

+ 10 - 0
packages/app/src/styles/theme/_apply-colors-dark.scss

@@ -467,6 +467,16 @@ ul.pagination {
   }
 }
 
+/*
+ * GROWI popular tags
+ */
+.grw-popular-tag-labels {
+  .grw-tag-label {
+    color: $color-tags;
+    background-color: $bgcolor-tags;
+  }
+}
+
 /*
  * admin settings
  */

+ 10 - 0
packages/app/src/styles/theme/_apply-colors-light.scss

@@ -356,6 +356,16 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
   }
 }
 
+/*
+ * GROWI popular tags
+ */
+.grw-popular-tag-labels {
+  .grw-tag-label {
+    color: $color-tags;
+    background-color: $bgcolor-tags;
+  }
+}
+
 /*
 * grw-side-contents
 */

+ 1 - 1
packages/codemirror-textlint/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/codemirror-textlint",
-  "version": "5.0.8-RC.0",
+  "version": "5.0.9-RC.0",
   "license": "MIT",
   "main": "dist/index.js",
   "scripts": {

+ 1 - 1
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/core",
-  "version": "5.0.8-RC.0",
+  "version": "5.0.9-RC.0",
   "description": "GROWI Core Libraries",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/plugin-attachment-refs/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/plugin-attachment-refs",
-  "version": "5.0.8-RC.0",
+  "version": "5.0.9-RC.0",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/plugin-lsx/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/plugin-lsx",
-  "version": "5.0.8-RC.0",
+  "version": "5.0.9-RC.0",
   "description": "GROWI plugin to list pages",
   "license": "MIT",
   "keywords": [

+ 5 - 4
packages/plugin-lsx/src/client/js/components/Lsx.jsx

@@ -25,13 +25,14 @@ export class Lsx extends React.Component {
       isError: false,
       isCacheExists: false,
       nodeTree: undefined,
+      basisViewersCount: undefined,
       errorMessage: '',
     };
 
     this.tagCacheManager = TagCacheManagerFactory.getInstance();
   }
 
-  async componentWillMount() {
+  async componentDidMount() {
     const { lsxContext, forceToFetchData } = this.props;
 
     // get state object cache
@@ -67,9 +68,9 @@ export class Lsx extends React.Component {
       });
 
       if (res.data.ok) {
-        lsxContext.toppageViewersCount = res.data.toppageViewersCount;
+        const basisViewersCount = res.data.toppageViewersCount;
         const nodeTree = this.generatePageNodeTree(pagePath, res.data.pages);
-        this.setState({ nodeTree });
+        this.setState({ nodeTree, basisViewersCount });
       }
     }
     catch (error) {
@@ -224,7 +225,7 @@ export class Lsx extends React.Component {
           </div>
         ) }
         { nodeTree && (
-          <LsxListView nodeTree={this.state.nodeTree} lsxContext={this.props.lsxContext} />
+          <LsxListView nodeTree={this.state.nodeTree} lsxContext={this.props.lsxContext} basisViewersCount={this.state.basisViewersCount} />
         ) }
       </div>
     );

+ 4 - 1
packages/plugin-lsx/src/client/js/components/LsxPageList/LsxListView.jsx

@@ -1,6 +1,7 @@
-import PropTypes from 'prop-types';
 import React from 'react';
 
+import PropTypes from 'prop-types';
+
 import { LsxContext } from '../../util/LsxContext';
 import { PageNode } from '../PageNode';
 
@@ -16,6 +17,7 @@ export class LsxListView extends React.Component {
           depth={1}
           pageNode={pageNode}
           lsxContext={this.props.lsxContext}
+          basisViewersCount={this.props.basisViewersCount}
         />
       );
     });
@@ -46,4 +48,5 @@ export class LsxListView extends React.Component {
 LsxListView.propTypes = {
   nodeTree: PropTypes.arrayOf(PropTypes.instanceOf(PageNode)).isRequired,
   lsxContext: PropTypes.instanceOf(LsxContext).isRequired,
+  basisViewersCount: PropTypes.number,
 };

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

@@ -61,6 +61,7 @@ export class LsxPage extends React.Component {
             depth={this.props.depth + 1}
             pageNode={pageNode}
             lsxContext={this.props.lsxContext}
+            basisViewersCount={this.props.basisViewersCount}
           />
         );
       });
@@ -78,8 +79,7 @@ export class LsxPage extends React.Component {
   }
 
   render() {
-    const pageNode = this.props.pageNode;
-    const { toppageViewersCount } = this.props.lsxContext;
+    const { pageNode, basisViewersCount } = this.props;
 
     // create PagePath element
     let pagePathNode = <PagePathWrapper pagePath={pageNode.pagePath} isExists={this.state.isExists} />;
@@ -88,7 +88,7 @@ export class LsxPage extends React.Component {
     }
 
     // create PageListMeta element
-    const pageListMeta = (this.state.isExists) ? <PageListMeta page={pageNode.page} basisViewersCount={toppageViewersCount} /> : '';
+    const pageListMeta = (this.state.isExists) ? <PageListMeta page={pageNode.page} basisViewersCount={basisViewersCount} /> : '';
 
     return (
       <li className="page-list-li">
@@ -105,4 +105,5 @@ LsxPage.propTypes = {
   pageNode: PropTypes.instanceOf(PageNode).isRequired,
   lsxContext: PropTypes.instanceOf(LsxContext).isRequired,
   depth: PropTypes.number,
+  basisViewersCount: PropTypes.number,
 };

+ 1 - 1
packages/plugin-pukiwiki-like-linker/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/plugin-pukiwiki-like-linker",
-  "version": "5.0.8-RC.0",
+  "version": "5.0.9-RC.0",
   "description": "GROWI plugin to add PukiwikiLikeLinker",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/slack/package.json

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

+ 2 - 2
packages/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slackbot-proxy",
-  "version": "5.0.8-slackbot-proxy.0",
+  "version": "5.0.9-slackbot-proxy.0",
   "license": "MIT",
   "scripts": {
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",
@@ -25,7 +25,7 @@
   },
   "dependencies": {
     "@godaddy/terminus": "^4.9.0",
-    "@growi/slack": "^5.0.8-RC.0",
+    "@growi/slack": "^5.0.9-RC.0",
     "@slack/oauth": "^2.0.1",
     "@slack/web-api": "^6.2.4",
     "@tsed/common": "^6.43.0",

+ 1 - 1
packages/ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/ui",
-  "version": "5.0.8-RC.0",
+  "version": "5.0.9-RC.0",
   "description": "GROWI UI Libraries",
   "license": "MIT",
   "keywords": [

+ 6 - 15
yarn.lock

@@ -9448,6 +9448,11 @@ expect@^27.0.6:
     jest-message-util "^27.0.6"
     jest-regex-util "^27.0.6"
 
+expose-gc@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/expose-gc/-/expose-gc-1.0.0.tgz#ba0e825b390cc3e7ab38fc5b945cd2b4018584b3"
+  integrity sha512-ecOHrdm+zyOCGIwX18/1RHkUWgxDqGGRiGhaNC+42jReTtudbm2ID/DMa/wpaHwqy5YQHPZvsDqRM2F2iZ0uVA==
+
 express-bunyan-logger@^1.3.3:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/express-bunyan-logger/-/express-bunyan-logger-1.3.3.tgz#e76d9b3d598ca83a69b692a9839c7453d01b5010"
@@ -17468,7 +17473,7 @@ randombytes@^2.1.0:
   dependencies:
     safe-buffer "^5.1.0"
 
-randomcolor@>=0.5.4, randomcolor@^0.5.4:
+randomcolor@>=0.5.4:
   version "0.5.4"
   resolved "https://registry.yarnpkg.com/randomcolor/-/randomcolor-0.5.4.tgz#df615b13f25b89ea58c5f8f72647f0a6f07adcc3"
   integrity sha512-nYd4nmTuuwMFzHL6W+UWR5fNERGZeVauho8mrJDUSXdNDbao4rbrUwhuLgKC/j8VCS5+34Ria8CsTDuBjrIrQA==
@@ -17750,15 +17755,6 @@ react-scrolllock@^1.0.9:
     create-react-class "^15.5.2"
     prop-types "^15.5.10"
 
-react-tagcloud@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/react-tagcloud/-/react-tagcloud-2.1.1.tgz#b8883634f76b5681c91a178689070efa0d442657"
-  integrity sha512-cM96jzUOKQqu2qlzwcO91r239MSDbFiAslFNk4Hja3MaZ4Y89goIzbTyXZwonkeJck1zY5wkNhJYeJ8YSdOwXg==
-  dependencies:
-    prop-types "^15.6.2"
-    randomcolor "^0.5.4"
-    shuffle-array "^1.0.1"
-
 react-transition-group@^2.2.1, react-transition-group@^2.3.1:
   version "2.9.0"
   resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
@@ -19241,11 +19237,6 @@ should@^13.2.1:
     should-type-adaptors "^1.0.1"
     should-util "^1.0.0"
 
-shuffle-array@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/shuffle-array/-/shuffle-array-1.0.1.tgz#c4ff3cfe74d16f93730592301b25e6577b12898b"
-  integrity sha1-xP88/nTRb5NzBZIwGyXmV3sSiYs=
-
 side-channel@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"