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

Merge branch 'master' into imprv/128163-export-md-with-page-name

soumaeda 2 лет назад
Родитель
Сommit
381cb92502
47 измененных файлов с 418 добавлено и 187 удалено
  1. 1 1
      .github/ISSUE_TEMPLATE/config.yml
  2. 66 1
      CHANGELOG.md
  3. 2 2
      README.md
  4. 2 2
      README_JP.md
  5. 1 1
      SECURITY.md
  6. 1 1
      apps/app/package.json
  7. 5 1
      apps/app/public/static/locales/en_US/admin.json
  8. 1 0
      apps/app/public/static/locales/en_US/translation.json
  9. 7 2
      apps/app/public/static/locales/ja_JP/admin.json
  10. 1 0
      apps/app/public/static/locales/ja_JP/translation.json
  11. 5 1
      apps/app/public/static/locales/zh_CN/admin.json
  12. 1 0
      apps/app/public/static/locales/zh_CN/translation.json
  13. 1 1
      apps/app/resource/locales/en_US/welcome.md
  14. 1 1
      apps/app/resource/locales/ja_JP/welcome.md
  15. 1 1
      apps/app/resource/locales/zh_CN/welcome.md
  16. 21 2
      apps/app/src/client/services/AdminCustomizeContainer.js
  17. 58 0
      apps/app/src/client/services/search-operation.ts
  18. 4 0
      apps/app/src/components/Admin/Customize/Customize.jsx
  19. 0 28
      apps/app/src/components/Admin/Customize/CustomizeFunctionSetting.tsx
  20. 70 0
      apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx
  21. 4 5
      apps/app/src/components/Navbar/GlobalSearch.tsx
  22. 10 1
      apps/app/src/components/Page/RenderTagLabels.tsx
  23. 3 1
      apps/app/src/components/ReactMarkdownComponents/LightBox.tsx
  24. 15 40
      apps/app/src/components/SearchPage.tsx
  25. 32 20
      apps/app/src/components/SearchPage/SearchControl.tsx
  26. 4 1
      apps/app/src/components/Sidebar/CustomSidebar.tsx
  27. 10 7
      apps/app/src/components/Sidebar/RecentChanges.tsx
  28. 7 9
      apps/app/src/components/TagCloudBox.tsx
  29. 12 15
      apps/app/src/components/TagList.tsx
  30. 24 4
      apps/app/src/server/routes/apiv3/customize-setting.js
  31. 13 4
      apps/app/src/stores/tag.tsx
  32. 2 1
      apps/app/test/cypress/e2e/20-basic-features/20-basic-features--sticky-features.cy.ts
  33. 1 1
      apps/slackbot-proxy/package.json
  34. 1 1
      apps/slackbot-proxy/src/views/top.ejs
  35. 1 1
      package.json
  36. 1 1
      packages/core/package.json
  37. 1 1
      packages/hackmd/package.json
  38. 6 6
      packages/presentation/package.json
  39. 1 1
      packages/preset-templates/package.json
  40. 1 1
      packages/preset-themes/package.json
  41. 3 3
      packages/remark-attachment-refs/package.json
  42. 2 3
      packages/remark-drawio/package.json
  43. 1 1
      packages/remark-growi-directive/package.json
  44. 1 1
      packages/remark-lsx/package.json
  45. 1 1
      packages/slack/package.json
  46. 1 1
      packages/ui/package.json
  47. 11 11
      yarn.lock

+ 1 - 1
.github/ISSUE_TEMPLATE/config.yml

@@ -4,5 +4,5 @@ contact_links:
     url: https://github.com/weseek/growi/discussions
     about: If you have feature requests or suggestions, you can create a new discussion and consider it with the community.
   - name: Questions
-    url: https://growi-slackin.weseek.co.jp/
+    url: https://communityinviter.com/apps/wsgrowi/invite/
     about: If you have questions, you can join our Slack team and talk about anything, anytime.

+ 66 - 1
CHANGELOG.md

@@ -1,9 +1,74 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v6.1.12...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v6.2.1...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v6.1.15](https://github.com/weseek/growi/compare/v6.1.14...v6.1.15) - 2023-09-11
+
+### 🚀 Improvement
+
+- imprv: Add CSP style-src for Safari and Content-Disposition of attachment (for v6.1.x) (#8057) @yuki-takei
+
+## [v6.1.14](https://github.com/weseek/growi/compare/v6.1.13...v6.1.14) - 2023-08-22
+
+### 🐛 Bug Fixes
+
+- fix: Add option to lightbox (6.1.x) (#8003) @yuki-takei
+
+## [v6.1.13](https://github.com/weseek/growi/compare/v6.1.12...v6.1.13) - 2023-08-17
+
+### 🐛 Bug Fixes
+
+- fix: Do not work img tag if use style property (#7988) @jam411
+- fix: "Searching..." label appearing unnecessarily (#7990) @yuki-takei
+
+## [v6.2.1](https://github.com/weseek/growi/compare/v6.2.0...v6.2.1) - 2023-09-20
+
+### 🚀 Improvement
+
+- imprv: i18n "Create /Sidebar page" label (#8085) @yuki-takei
+- imprv: Admin customize presentation form (#8083) @meiri-k
+
+### 🐛 Bug Fixes
+
+- fix: Do not work img tag if use style property 62x (#8092) @jam411
+
+## [v6.2.0](https://github.com/weseek/growi/compare/v6.1.12...v6.2.0) - 2023-09-14
+
+### 💎 Features
+
+- feat: Presentation preview and support Marp  (#8029) @reiji-h
+
+### 🚀 Improvement
+
+- imprv: Able to customize users homepage deletion (#7921) @yuki-takei
+- imprv: Search behavior (#8069) @yuki-takei
+- imprv: Add CSP style-src for Safari and Content-Disposition of attachment (#8049) @ykanematsu
+- imprv: Correct update message (#8040) @reiji-h
+- imprv: Add installed date to questionnaire answer (#7971) @TatsuyaIse
+- imprv: Show modal when you delete plugin (#7875) @soumaeda
+- imprv: i18n resetting password mail body (#8058) @meiri-k
+- imprv: Create Japanese ejs files (#7957) @meiri-k
+- imprv: Clean up old toastr (#7949) @jam411
+- imprv: Persist the installed date in the Config collection (#7936) @TatsuyaIse
+
+### 🐛 Bug Fixes
+
+- fix: Pages can be created under a non-existent user page (#7974) @miya
+- fix: Pages can be created under a non-existent user page (During attachment upload) (#8001) @miya
+- fix: Type safe implementation for objects imported from ElasticsearchClient (#7862) @miya
+- fix: Consider an empty page when renaming and duplicating (#7979) @yuki-takei
+- fix: Remove redundant toastSuccess for pasted attachments (#8044) @jam411
+- fix: Fixing swagger for tag update api (#8010) @miya
+- fix: Modification of links in the docs (#8004) @miya
+
+### 🧰 Maintenance
+
+- support: Omit core-js v2 (#7944) @yuki-takei
+- support: Improve build settings (#7919) @yuki-takei
+- support: Url to join to the slack team (#8073) @WNomunomu
+
 ## [v6.1.12](https://github.com/weseek/growi/compare/v6.1.11...v6.1.12) - 2023-08-14
 
 ### 🐛 Bug Fixes

+ 2 - 2
README.md

@@ -7,7 +7,7 @@
 </p>
 <p align="center">
   <a href="https://github.com/weseek/growi/releases/latest"><img src="https://img.shields.io/github/release/weseek/growi.svg"></a>
-  <a href="https://growi-slackin.weseek.co.jp/"><img src="https://growi-slackin.weseek.co.jp/badge.svg"></a>
+  <a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
 </p>
 
 <p align="center">
@@ -132,7 +132,7 @@ You can write issues and PRs in English or Japanese.
 
 ## Discussion
 
-If you have questions or suggestions, you can [join our Slack team](https://growi-slackin.weseek.co.jp/) and talk about anything, anytime.
+If you have questions or suggestions, you can [join our Slack team](https://communityinviter.com/apps/wsgrowi/invite/) and talk about anything, anytime.
 
 # License
 

+ 2 - 2
README_JP.md

@@ -6,7 +6,7 @@
   </p>
   <p align="center">
     <a href="https://github.com/weseek/growi/releases/latest"><img src="https://img.shields.io/github/release/weseek/growi.svg"></a>
-    <a href="https://growi-slackin.weseek.co.jp/"><img src="https://growi-slackin.weseek.co.jp/badge.svg"></a>
+    <a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
   </p>
 
 <p align="center">
@@ -129,7 +129,7 @@ Issue と Pull requests の作成は英語・日本語どちらでも受け付
 
 ## GROWI について話し合いましょう!
 
-質問や提案があれば、私たちの [Slack team](https://growi-slackin.weseek.co.jp/) にぜひご参加ください。
+質問や提案があれば、私たちの [Slack team](https://communityinviter.com/apps/wsgrowi/invite/) にぜひご参加ください。
 いつでも、どこでも GROWI について議論しましょう!
 
 # ライセンス

+ 1 - 1
SECURITY.md

@@ -13,7 +13,7 @@
 
 If you believe you have found a security vulnerability in any GROWI related repository, please report it to us using one of the methods described below.
 
-  * [Join our Slack team](https://growi-slackin.weseek.co.jp/) and send DM to `@yuki` who is the lead developer
+  * [Join our Slack team](https://communityinviter.com/apps/wsgrowi/invite/) and send DM to `@yuki` who is the lead developer
   * Report to JPCERT/CC[^jpcertcc]
     * [[PDF] JPCERT/CC Vulnerability Coordination and Disclosure Policy](https://www.jpcert.or.jp/english/vh/vul-coordination-disclosure-policy_2019.pdf)
 

+ 1 - 1
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "6.2.0-RC.0",
+  "version": "6.2.2-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",

+ 5 - 1
apps/app/public/static/locales/en_US/admin.json

@@ -480,7 +480,10 @@
       "show_all_reply_comments": "Show all reply comments",
       "show_all_reply_comments_desc": "When the setting value is off, comments other than the latest two are omitted.",
       "select_search_scope_children_as_default": "Select 'Only children of this tree' as default value of search range",
-      "select_search_scope_children_as_default_desc": "When the setting value is off, 'All pages' is used as default value of search range.",
+      "select_search_scope_children_as_default_desc": "When the setting value is off, 'All pages' is used as default value of search range."
+    },
+      "presentation": "Presentation",
+    "presentation_options": {
       "enable_marp": "Enable Marp ",
       "enable_marp_desc": "Marp can be used in presentation preview. This option may make you vulnerable to XSS.",
       "marp_official_site": "The Marp Official Site",
@@ -499,6 +502,7 @@
     "write_css": "You can write CSS that is applied to whole system.",
     "ctrl_space": "Ctrl+Space to autocomplete",
     "custom_script": "Custom script",
+    "custom_presentation": "Custom presentation",
     "write_java": "You can write Javascript that is applied to whole system.",
     "reflect_change": "You need to reload the page to reflect the change.",
     "custom_logo" : "Custom Logo",

+ 1 - 0
apps/app/public/static/locales/en_US/translation.json

@@ -66,6 +66,7 @@
   "No users have liked this yet.": "No users have liked this yet.",
   "No users have bookmarked yet": "No users have bookmarked yet",
   "Create Archive Page": "Create Archive Page",
+  "Create Sidebar Page": "Create <strong>/Sidebar</strong> Page",
   "File type": "File type",
   "Target page": "Target page",
   "Include Attachment File": "Include Attachment File",

+ 7 - 2
apps/app/public/static/locales/ja_JP/admin.json

@@ -488,7 +488,11 @@
       "show_all_reply_comments": "返信コメントを全て表示する",
       "show_all_reply_comments_desc": "OFFの場合、最新2件のコメント以外が省略されます。",
       "select_search_scope_children_as_default": "検索範囲のデフォルト設定を「この階層下の子ページ」にする",
-      "select_search_scope_children_as_default_desc": "OFFの場合、検索範囲のデフォルト設定は「全てのページ」になります。",
+      "select_search_scope_children_as_default_desc": "OFFの場合、検索範囲のデフォルト設定は「全てのページ」になります。"
+
+    },
+    "presentation":"プレゼンテーション",
+    "presentation_options":{
       "enable_marp": "Marp を有効化する",
       "enable_marp_desc": "プレゼンテーション表示に Marp を利用できるようになります。ただし、XSS に対して脆弱になる恐れがあります。",
       "marp_official_site": "参考:Marp 公式サイト",
@@ -507,6 +511,7 @@
     "write_css": " システム全体に適用されるCSSを記述できます。",
     "ctrl_space": "Ctrl+Space でコード補完",
     "custom_script": "カスタムスクリプト",
+    "custom_presentation":"プレゼンテーション",
     "write_java": "システム全体に適用されるJavaScriptを記述できます。",
     "reflect_change": "変更の反映はページの更新が必要です。",
     "custom_logo": "カスタムロゴ",
@@ -515,7 +520,7 @@
     "current_logo": "現在のロゴ",
     "upload_new_logo": "新しいロゴをアップロードする",
     "delete_logo": "ロゴを削除"
-  },
+   },
   "importer_management": {
     "import_data": "データインポート",
     "article": "記事",

+ 1 - 0
apps/app/public/static/locales/ja_JP/translation.json

@@ -62,6 +62,7 @@
   "No users have liked this yet": "いいねをしているユーザーはいません",
   "No users have bookmarked yet": "ブックマークしているユーザーはいません",
   "Create Archive Page": "アーカイブページの作成",
+  "Create Sidebar Page": "<strong>/Sidebar</strong> ページを作成する",
   "Target page": "対象ページ",
   "File type": "ファイル形式",
   "Include Attachment File": "添付ファイルも含める",

+ 5 - 1
apps/app/public/static/locales/zh_CN/admin.json

@@ -488,7 +488,10 @@
       "show_all_reply_comments": "显示所有回复评论",
       "show_all_reply_comments_desc": "当设置值为“关”时,将忽略最近两个之外的注释。",
       "select_search_scope_children_as_default": "选择“当前分支以下内容”, 作为搜索范围的默认值",
-      "select_search_scope_children_as_default_desc": "当设置值为“关”时,“所有页面”被作为搜索范围的默认值。",
+      "select_search_scope_children_as_default_desc": "当设置值为“关”时,“所有页面”被作为搜索范围的默认值。"
+    },
+      "presentation": "表达",
+      "presentation_options": {
       "enable_marp": "启用 Marp",
       "enable_marp_desc": "Marp 可在演示视图中使用。该选项可能会使您受到 XSS 的攻击。",
       "marp_official_site": "参考资料:Marp 官方网站",
@@ -507,6 +510,7 @@
     "write_css": "您可以编写应用于整个系统的CSS。",
     "ctrl_space": "Ctrl+Space 自动完成",
     "custom_script": "定制纸条",
+    "custom_presentation":"表达",
     "write_java": "您可以编写应用于整个系统的Javascript。",
     "reflect_change": "您需要重新加载页面以反映更改。",
     "custom_logo": "自定义徽标",

+ 1 - 0
apps/app/public/static/locales/zh_CN/translation.json

@@ -62,6 +62,7 @@
   "No users have liked this yet": "还没有用户喜欢这个",
   "No users have bookmarked yet": "还没有用户加入书签",
   "Create Archive Page": "创建归档页",
+  "Create Sidebar Page": "创建 <strong>/Sidebar</strong> 页面",
   "File type": "文件类型",
   "Target page": "目标页面",
   "Include Attachment File": "包含附件",

+ 1 - 1
apps/app/resource/locales/en_US/welcome.md

@@ -58,7 +58,7 @@ We can display the content list using a table and `$lsx`.
 
 # Slack
 
-<a href="https://growi-slackin.weseek.co.jp/"><img src="https://growi-slackin.weseek.co.jp/badge.svg"></a>
+<a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
 
 We welcome newcomers joining our slack channel to help improve GROWI.
 In addition to discussing development, we are also happy to answer your questions when you join.

+ 1 - 1
apps/app/resource/locales/ja_JP/welcome.md

@@ -54,7 +54,7 @@ GROWI は個人・法人向けの Wiki | ナレッジベースツールです。
 
 # Slack
 
-<a href="https://growi-slackin.weseek.co.jp/"><img src="https://growi-slackin.weseek.co.jp/badge.svg"></a>
+<a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
 
 GROWI をより良いものにするために、是非 Slack に参加してください。  
 開発に関する議論を行っている他、導入時の質問等も受け付けています。

+ 1 - 1
apps/app/resource/locales/zh_CN/welcome.md

@@ -58,7 +58,7 @@ GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
 
 # Slack
 
-<a href="https://growi-slackin.weseek.co.jp/"><img src="https://growi-slackin.weseek.co.jp/badge.svg"></a>
+<a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
 
 我们欢迎新人加入我们的slack频道,帮助改善Growi。
 除了讨论发展问题,我们也很乐意在你加入时回答你的问题。

+ 21 - 2
apps/app/src/client/services/AdminCustomizeContainer.js

@@ -1,3 +1,4 @@
+/* eslint-disable lines-between-class-members */
 import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
@@ -154,7 +155,7 @@ export default class AdminCustomizeContainer extends Container {
   /**
    * Switch isEnabledMarp
    */
-  switchIsEnabledMarp() {
+  switchIsEnabledMarp(inputValue) {
     this.setState({ isEnabledMarp: !this.state.isEnabledMarp });
   }
 
@@ -203,7 +204,6 @@ export default class AdminCustomizeContainer extends Container {
         isEnabledStaleNotification: this.state.isEnabledStaleNotification,
         isAllReplyShown: this.state.isAllReplyShown,
         isSearchScopeChildrenAsDefault: this.state.isSearchScopeChildrenAsDefault,
-        isEnabledMarp: this.state.isEnabledMarp,
       });
       const { customizedParams } = response.data;
       this.setState({
@@ -216,6 +216,25 @@ export default class AdminCustomizeContainer extends Container {
         isEnabledStaleNotification: customizedParams.isEnabledStaleNotification,
         isAllReplyShown: customizedParams.isAllReplyShown,
         isSearchScopeChildrenAsDefault: customizedParams.isSearchScopeChildrenAsDefault,
+      });
+    }
+    catch (err) {
+      logger.error(err);
+      throw new Error('Failed to update data');
+    }
+  }
+  /**
+   * Update presentation
+   * @memberOf AdminCustomizeContainer
+   */
+  async updateCustomizePresentation() {
+    try {
+      const response = await apiv3Put('/customize-setting/presentation', {
+        isEnabledMarp: this.state.isEnabledMarp,
+      });
+
+      const { customizedParams } = response.data;
+      this.setState({
         isEnabledMarp: customizedParams.isEnabledMarp,
       });
     }

+ 58 - 0
apps/app/src/client/services/search-operation.ts

@@ -0,0 +1,58 @@
+import { useCallback, useEffect, useRef } from 'react';
+
+import { type SWRResponseWithUtils, withUtils } from '@growi/core/dist/swr';
+import { useRouter } from 'next/router';
+import useSWRImmutable from 'swr/immutable';
+
+
+type UseKeywordManagerUtils = {
+  pushState: (newKeyword: string) => void,
+}
+
+export const useKeywordManager = (): SWRResponseWithUtils<UseKeywordManagerUtils, string> => {
+  // routerRef solve the problem of infinite redrawing that occurs with routers
+  const router = useRouter();
+  const routerRef = useRef(router);
+
+  // parse URL Query
+  const queries = router.query.q;
+  const initialKeyword = (Array.isArray(queries) ? queries.join(' ') : queries) ?? '';
+
+  const swrResponse = useSWRImmutable<string>('searchKeyword', null, {
+    fallbackData: initialKeyword,
+  });
+
+  const { mutate } = swrResponse;
+  const pushState = useCallback((newKeyword: string) => {
+    mutate((prevKeyword) => {
+      if (prevKeyword !== newKeyword) {
+        const newUrl = new URL('/_search', 'http://example.com');
+        newUrl.searchParams.append('q', newKeyword);
+        routerRef.current.push(`${newUrl.pathname}${newUrl.search}`, '');
+      }
+
+      return newKeyword;
+    });
+  }, [mutate]);
+
+  // detect search keyword from the query of URL
+  useEffect(() => {
+    mutate(initialKeyword);
+  }, [mutate, initialKeyword]);
+
+  // browser back and forward
+  useEffect(() => {
+    routerRef.current.beforePopState(({ url }) => {
+      const newUrl = new URL(url, 'https://exmple.com');
+      const newKeyword = newUrl.searchParams.get('q');
+      if (newKeyword != null) {
+        mutate(newKeyword);
+      }
+      return true;
+    });
+  }, [mutate]);
+
+  return withUtils(swrResponse, {
+    pushState,
+  });
+};

+ 4 - 0
apps/app/src/components/Admin/Customize/Customize.jsx

@@ -15,6 +15,7 @@ import CustomizeFunctionSetting from './CustomizeFunctionSetting';
 import CustomizeLayoutSetting from './CustomizeLayoutSetting';
 import CustomizeLogoSetting from './CustomizeLogoSetting';
 import CustomizeNoscriptSetting from './CustomizeNoscriptSetting';
+import CustomizePresentationSetting from './CustomizePresentationSetting';
 import CustomizeScriptSetting from './CustomizeScriptSetting';
 import CustomizeSidebarSetting from './CustomizeSidebarSetting';
 import CustomizeThemeSetting from './CustomizeThemeSetting';
@@ -58,6 +59,9 @@ function Customize(props) {
       <div className="mb-5">
         <CustomizeFunctionSetting />
       </div>
+      <div className="mb-5">
+        <CustomizePresentationSetting />
+      </div>
       <div className="mb-5">
         <CustomizeTitle />
       </div>

+ 0 - 28
apps/app/src/components/Admin/Customize/CustomizeFunctionSetting.tsx

@@ -133,34 +133,6 @@ const CustomizeFunctionSetting = (props: Props): JSX.Element => {
             </div>
           </div>
 
-          <div className="form-group row">
-            <div className="offset-md-3 col-md-6 text-left">
-              <CustomizeFunctionOption
-                optionId="isEnabledMarp"
-                label={t('admin:customize_settings.function_options.enable_marp')}
-                isChecked={adminCustomizeContainer.state.isEnabledMarp || false}
-                onChecked={() => { adminCustomizeContainer.switchIsEnabledMarp() }}
-              >
-                <p className="form-text text-muted">
-                  {t('admin:customize_settings.function_options.enable_marp_desc')}
-                  <br></br>
-                  <a
-                    href={`${t('admin:customize_settings.function_options.marp_official_site_link')}`}
-                    target="_blank"
-                    rel="noopener noreferrer"
-                  >{`${t('admin:customize_settings.function_options.marp_official_site')}`}
-                  </a>
-                  <br></br>
-                  <a
-                    href={`${t('admin:customize_settings.function_options.marp_in_growi_link')}`}
-                    target="_blank"
-                    rel="noopener noreferrer"
-                  >{`${t('admin:customize_settings.function_options.marp_in_growi')}`}
-                  </a>
-                </p>
-              </CustomizeFunctionOption>
-            </div>
-          </div>
 
           <AdminUpdateButtonRow onClick={onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
         </div>

+ 70 - 0
apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx

@@ -0,0 +1,70 @@
+import React, { useCallback } from 'react';
+
+import { useTranslation } from 'next-i18next';
+
+import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
+import { toastSuccess, toastError } from '~/client/util/toastr';
+
+import { withUnstatedContainers } from '../../UnstatedUtils';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
+
+import CustomizePresentationOption from './CustomizeFunctionOption';
+
+type Props = {
+  adminCustomizeContainer: AdminCustomizeContainer
+}
+
+const CustomizePresentationSetting = (props: Props): JSX.Element => {
+  const { adminCustomizeContainer } = props;
+
+  console.log(adminCustomizeContainer);
+
+  const { t } = useTranslation();
+  const onClickSubmit = useCallback(async() => {
+    try {
+      await adminCustomizeContainer.updateCustomizePresentation();
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.presentation'), ns: 'commons' }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }, [adminCustomizeContainer]);
+  return (
+    <React.Fragment>
+      <h2 className="admin-setting-header">{t('admin:customize_settings.custom_presentation')}</h2>
+      <div className="form-group row">
+        <div className="offset-md-3 col-md-6 text-left">
+          <CustomizePresentationOption
+            optionId="isEnabledMarp"
+            label={t('admin:customize_settings.presentation_options.enable_marp')}
+            isChecked={adminCustomizeContainer?.state.isEnabledMarp || false}
+            onChecked={() => { adminCustomizeContainer.switchIsEnabledMarp() }}
+          >
+            <p className="form-text text-muted">
+              {t('admin:customize_settings.presentation_options.enable_marp_desc')}
+              <br></br>
+              <a
+                href={`${t('admin:customize_settings.presentation_options.marp_official_site_link')}`}
+                target="_blank"
+                rel="noopener noreferrer"
+              >{`${t('admin:customize_settings.presentation_options.marp_official_site')}`}
+              </a>
+              <br></br>
+              <a
+                href={`${t('admin:customize_settings.presentation_options.marp_in_gorwi_link')}`}
+                target="_blank"
+                rel="noopener noreferrer"
+              >{`${t('admin:customize_settings.presenattion_options.marp_in_growi')}`}
+              </a>
+            </p>
+          </CustomizePresentationOption>
+        </div>
+      </div>
+
+      <AdminUpdateButtonRow onClick={onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
+    </React.Fragment>
+  );
+};
+const CustomizePresentationSettingWrapper = withUnstatedContainers(CustomizePresentationSetting, [AdminCustomizeContainer]);
+
+export default CustomizePresentationSettingWrapper;

+ 4 - 5
apps/app/src/components/Navbar/GlobalSearch.tsx

@@ -9,6 +9,7 @@ import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
 
 import { IFocusable } from '~/client/interfaces/focusable';
+import { useKeywordManager } from '~/client/services/search-operation';
 import { IPageWithSearchMeta } from '~/interfaces/search';
 import {
   useIsSearchScopeChildrenAsDefault, useIsSearchServiceReachable,
@@ -46,6 +47,8 @@ export const GlobalSearch = (props: GlobalSearchProps): JSX.Element => {
   const [isScopeChildren, setScopeChildren] = useState<boolean|undefined>(isSearchScopeChildrenAsDefault ?? false);
   const [isFocused, setFocused] = useState<boolean>(false);
 
+  const { pushState } = useKeywordManager();
+
   useEffect(() => {
     setScopeChildren(isSearchScopeChildrenAsDefault);
   }, [isSearchScopeChildrenAsDefault]);
@@ -63,17 +66,13 @@ export const GlobalSearch = (props: GlobalSearchProps): JSX.Element => {
   }, [returnPathForURL, router]);
 
   const search = useCallback(() => {
-    const url = new URL(window.location.href);
-    url.pathname = '/_search';
-
     // construct search query
     let q = text;
     if (isScopeChildren) {
       q += ` prefix:${currentPagePath ?? window.location.pathname}`;
     }
-    url.searchParams.append('q', q);
 
-    router.push(url.href);
+    pushState(q);
   }, [currentPagePath, isScopeChildren, router, text]);
 
   const scopeLabel = isScopeChildren

+ 10 - 1
apps/app/src/components/Page/RenderTagLabels.tsx

@@ -2,6 +2,8 @@ import React from 'react';
 
 import { useTranslation } from 'next-i18next';
 
+import { useKeywordManager } from '~/client/services/search-operation';
+
 import { NotAvailableForGuest } from '../NotAvailableForGuest';
 import { NotAvailableForReadOnlyUser } from '../NotAvailableForReadOnlyUser';
 
@@ -15,6 +17,8 @@ const RenderTagLabels = React.memo((props: RenderTagLabelsProps) => {
   const { tags, isTagLabelsDisabled, openEditorModal } = props;
   const { t } = useTranslation();
 
+  const { pushState } = useKeywordManager();
+
   function openEditorHandler() {
     if (openEditorModal == null) {
       return;
@@ -28,7 +32,12 @@ const RenderTagLabels = React.memo((props: RenderTagLabelsProps) => {
     <>
       {tags.map((tag) => {
         return (
-          <a key={tag} href={`/_search?q=tag:${tag}`} className="grw-tag-label badge badge-secondary mr-2">
+          <a
+            key={tag}
+            type="button"
+            className="grw-tag-label badge badge-secondary mr-2"
+            onClick={() => pushState(`tag:${tag}`)}
+          >
             {tag}
           </a>
         );

+ 3 - 1
apps/app/src/components/ReactMarkdownComponents/LightBox.tsx

@@ -4,9 +4,11 @@ import FsLightbox from 'fslightbox-react';
 
 export const LightBox = (props) => {
   const [toggler, setToggler] = useState(false);
+  const { node, ...rest } = props;
+
   return (
     <>
-      <img {...props.node.properties} onClick={() => setToggler(!toggler)} />
+      <img {...rest} onClick={() => setToggler(!toggler)} />
       <FsLightbox
         toggler={toggler}
         sources={[props.src]}

+ 15 - 40
apps/app/src/components/SearchPage.tsx

@@ -1,23 +1,21 @@
 import React, {
-  useCallback, useEffect, useMemo, useRef, useState,
+  useCallback, useMemo, useRef, useState,
 } from 'react';
 
-
 import { useTranslation } from 'next-i18next';
-import { useRouter } from 'next/router';
-
 
-import { ISelectableAll, ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
-import { IFormattedSearchResult } from '~/interfaces/search';
+import type { ISelectableAll, ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
+import { useKeywordManager } from '~/client/services/search-operation';
+import type { IFormattedSearchResult } from '~/interfaces/search';
 import { useIsSearchServiceReachable, useShowPageLimitationL } from '~/stores/context';
-import { ISearchConditions, ISearchConfigurations, useSWRxSearch } from '~/stores/search';
+import { type ISearchConditions, type ISearchConfigurations, useSWRxSearch } from '~/stores/search';
 
 import { NotAvailableForGuest } from './NotAvailableForGuest';
 import { NotAvailableForReadOnlyUser } from './NotAvailableForReadOnlyUser';
 import PaginationWrapper from './PaginationWrapper';
 import { OperateAllControl } from './SearchPage/OperateAllControl';
 import SearchControl from './SearchPage/SearchControl';
-import { IReturnSelectedPageIds, SearchPageBase, usePageDeleteModalForBulkDeletion } from './SearchPage/SearchPageBase';
+import { type IReturnSelectedPageIds, SearchPageBase, usePageDeleteModalForBulkDeletion } from './SearchPage/SearchPageBase';
 
 
 // TODO: replace with "customize:showPageLimitationS"
@@ -93,15 +91,8 @@ export const SearchPage = (): JSX.Element => {
   const { t } = useTranslation();
   const { data: showPageLimitationL } = useShowPageLimitationL();
 
-  // routerRef solve the problem of infinite redrawing that occurs with routers
-  const router = useRouter();
-  const routerRef = useRef(router);
+  const { data: keyword, pushState } = useKeywordManager();
 
-  // parse URL Query
-  const queries = router.query.q;
-  const initQ = (Array.isArray(queries) ? queries.join(' ') : queries) ?? '';
-
-  const [keyword, setKeyword] = useState<string>(initQ);
   const [offset, setOffset] = useState<number>(0);
   const [limit, setLimit] = useState<number>(showPageLimitationL ?? INITIAL_PAGIONG_SIZE);
   const [configurationsByControl, setConfigurationsByControl] = useState<Partial<ISearchConfigurations>>({});
@@ -110,17 +101,20 @@ export const SearchPage = (): JSX.Element => {
 
   const { data: isSearchServiceReachable } = useIsSearchServiceReachable();
 
-  const { data, conditions, mutate } = useSWRxSearch(keyword, null, {
+  const { data, conditions, mutate } = useSWRxSearch(keyword ?? '', null, {
     ...configurationsByControl,
     offset,
     limit,
   });
 
-  const searchInvokedHandler = useCallback((_keyword: string, newConfigurations: Partial<ISearchConfigurations>) => {
-    setKeyword(_keyword);
+  const searchInvokedHandler = useCallback((newKeyword: string, newConfigurations: Partial<ISearchConfigurations>) => {
     setOffset(0);
     setConfigurationsByControl(newConfigurations);
-  }, []);
+
+    pushState(newKeyword);
+
+    mutate();
+  }, [keyword, mutate, pushState]);
 
   const selectAllCheckboxChangedHandler = useCallback((isChecked: boolean) => {
     const instance = searchPageBaseRef.current;
@@ -176,25 +170,6 @@ export const SearchPage = (): JSX.Element => {
   // for bulk deletion
   const deleteAllButtonClickedHandler = usePageDeleteModalForBulkDeletion(data, searchPageBaseRef, () => mutate());
 
-  // push state
-  useEffect(() => {
-    const newUrl = new URL('/_search', 'http://example.com');
-    newUrl.searchParams.append('q', keyword);
-    routerRef.current.push(`${newUrl.pathname}${newUrl.search}`, '', { shallow: true });
-  }, [keyword, routerRef]);
-
-  // browser back and forward
-  useEffect(() => {
-    routerRef.current.beforePopState(({ url }) => {
-      const newUrl = new URL(url, 'https://exmple.com');
-      const newKeyword = newUrl.searchParams.get('q');
-      if (newKeyword != null) {
-        setKeyword(newKeyword);
-      }
-      return true;
-    });
-  }, [setKeyword, routerRef]);
-
   const hitsCount = data?.meta.hitsCount;
 
   const allControl = useMemo(() => {
@@ -246,7 +221,7 @@ export const SearchPage = (): JSX.Element => {
     return (
       <SearchResultListHead
         searchResult={data}
-        searchingKeyword={keyword}
+        searchingKeyword={keyword ?? ''}
         offset={offset}
         pagingSize={limit}
         onPagingSizeChanged={pagingSizeChangedHandler}

+ 32 - 20
apps/app/src/components/SearchPage/SearchControl.tsx

@@ -18,7 +18,7 @@ type Props = {
   isEnableFilter: boolean,
   initialSearchConditions: Partial<ISearchConditions>,
 
-  onSearchInvoked: (keyword: string, configurations: Partial<ISearchConfigurations>) => void,
+  onSearchInvoked?: (keyword: string, configurations: Partial<ISearchConfigurations>) => void,
 
   allControl: React.ReactNode,
 }
@@ -34,7 +34,9 @@ const SearchControl = React.memo((props: Props): JSX.Element => {
     allControl,
   } = props;
 
-  const [keyword, setKeyword] = useState(initialSearchConditions.keyword ?? '');
+  const keywordOnInit = initialSearchConditions.keyword ?? '';
+
+  const [keyword, setKeyword] = useState(keywordOnInit);
   const [sort, setSort] = useState<SORT_AXIS>(initialSearchConditions.sort ?? SORT_AXIS.RELATION_SCORE);
   const [order, setOrder] = useState<SORT_ORDER>(initialSearchConditions.order ?? SORT_ORDER.DESC);
   const [includeUserPages, setIncludeUserPages] = useState(initialSearchConditions.includeUserPages ?? false);
@@ -43,32 +45,42 @@ const SearchControl = React.memo((props: Props): JSX.Element => {
 
   const { t } = useTranslation('');
 
-  const invokeSearch = useCallback(() => {
-    if (onSearchInvoked == null) {
-      return;
-    }
+  const searchFormSubmittedHandler = useCallback((input: string) => {
+    setKeyword(input);
 
-    onSearchInvoked(keyword, {
+    onSearchInvoked?.(input, {
       sort, order, includeUserPages, includeTrashPages,
     });
-  }, [keyword, sort, order, includeTrashPages, includeUserPages, onSearchInvoked]);
-
-  const searchFormSubmittedHandler = useCallback((input: string) => {
-    setKeyword(input);
-  }, []);
+  }, [includeTrashPages, includeUserPages, onSearchInvoked, order, sort]);
 
   const changeSortHandler = useCallback((nextSort: SORT_AXIS, nextOrder: SORT_ORDER) => {
     setSort(nextSort);
     setOrder(nextOrder);
-  }, []);
 
-  useEffect(() => {
-    invokeSearch();
-  }, [invokeSearch]);
+    onSearchInvoked?.(keyword, {
+      sort: nextSort, order: nextOrder, includeUserPages, includeTrashPages,
+    });
+  }, [includeTrashPages, includeUserPages, keyword, onSearchInvoked]);
+
+  const changeIncludeUserPagesHandler = useCallback((include: boolean) => {
+    setIncludeUserPages(include);
+
+    onSearchInvoked?.(keyword, {
+      sort, order, includeUserPages: include, includeTrashPages,
+    });
+  }, [includeTrashPages, keyword, onSearchInvoked, order, sort]);
+
+  const changeIncludeTrashPagesHandler = useCallback((include: boolean) => {
+    setIncludeTrashPages(include);
+
+    onSearchInvoked?.(keyword, {
+      sort, order, includeUserPages, includeTrashPages: include,
+    });
+  }, [includeUserPages, keyword, onSearchInvoked, order, sort]);
 
   useEffect(() => {
-    setKeyword(initialSearchConditions.keyword ?? '');
-  }, [initialSearchConditions.keyword]);
+    setKeyword(keywordOnInit);
+  }, [keywordOnInit]);
 
   return (
     <div className="position-sticky sticky-top shadow-sm">
@@ -128,7 +140,7 @@ const SearchControl = React.memo((props: Props): JSX.Element => {
                     type="checkbox"
                     id="flexCheckDefault"
                     defaultChecked={includeUserPages}
-                    onChange={e => setIncludeUserPages(e.target.checked)}
+                    onChange={e => changeIncludeUserPagesHandler(e.target.checked)}
                   />
                   <label className="custom-control-label mb-0 d-flex align-items-center text-secondary with-no-font-weight" htmlFor="flexCheckDefault">
                     {t('Include Subordinated Target Page', { target: '/user' })}
@@ -142,7 +154,7 @@ const SearchControl = React.memo((props: Props): JSX.Element => {
                     type="checkbox"
                     id="flexCheckChecked"
                     checked={includeTrashPages}
-                    onChange={e => setIncludeTrashPages(e.target.checked)}
+                    onChange={e => changeIncludeTrashPagesHandler(e.target.checked)}
                   />
                   <label
                     className="custom-control-label

+ 4 - 1
apps/app/src/components/Sidebar/CustomSidebar.tsx

@@ -20,10 +20,13 @@ const logger = loggerFactory('growi:cli:CustomSidebar');
 
 
 const SidebarNotFound = () => {
+  const { t } = useTranslation();
+
   return (
     <div className="grw-sidebar-content-header h5 text-center py-3">
       <Link href="/Sidebar#edit">
-        <i className="icon-magic-wand"></i>Create<strong>/Sidebar</strong>page
+        <i className="icon-fw icon-magic-wand"></i>
+        <span dangerouslySetInnerHTML={{ __html: t('Create Sidebar Page') }}></span>
       </Link>
     </div>
   );

+ 10 - 7
apps/app/src/components/Sidebar/RecentChanges.tsx

@@ -6,8 +6,8 @@ import { isPopulated, type IPageHasId } from '@growi/core';
 import { DevidedPagePath } from '@growi/core/dist/models';
 import { UserPicture, FootstampIcon } from '@growi/ui/dist/components';
 import { useTranslation } from 'next-i18next';
-import Link from 'next/link';
 
+import { useKeywordManager } from '~/client/services/search-operation';
 import PagePathHierarchicalLink from '~/components/PagePathHierarchicalLink';
 import LinkedPagePath from '~/models/linked-page-path';
 import { useSWRINFxRecentlyUpdated } from '~/stores/page-listing';
@@ -30,6 +30,7 @@ type PageItemLowerProps = {
 
 type PageItemProps = PageItemLowerProps & {
   isSmall: boolean
+  onClickTag?: (tagName: string) => void,
 }
 
 const PageItemLower = memo(({ page }: PageItemLowerProps): JSX.Element => {
@@ -49,7 +50,7 @@ const PageItemLower = memo(({ page }: PageItemLowerProps): JSX.Element => {
 });
 PageItemLower.displayName = 'PageItemLower';
 
-const PageItem = memo(({ page, isSmall }: PageItemProps): JSX.Element => {
+const PageItem = memo(({ page, isSmall, onClickTag }: PageItemProps): JSX.Element => {
   const dPagePath = new DevidedPagePath(page.path, false, true);
   const linkedPagePathFormer = new LinkedPagePath(dPagePath.former);
   const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter);
@@ -70,14 +71,14 @@ const PageItem = memo(({ page, isSmall }: PageItemProps): JSX.Element => {
       return <></>;
     }
     return (
-      <Link
+      <a
         key={tag.name}
-        href={`/_search?q=tag:${tag.name}`}
+        type="button"
         className="grw-tag-label badge badge-secondary mr-2 small"
-        prefetch={false}
+        onClick={() => onClickTag?.(tag.name)}
       >
         {tag.name}
-      </Link>
+      </a>
     );
   });
 
@@ -113,6 +114,8 @@ const RecentChanges = (): JSX.Element => {
     data, mutate, isLoading,
   } = swrInifinitexRecentlyUpdated;
 
+  const { pushState } = useKeywordManager();
+
   const [isRecentChangesSidebarSmall, setIsRecentChangesSidebarSmall] = useState(false);
   const isEmpty = data?.[0]?.pages.length === 0;
   const isReachingEnd = isEmpty || (data != null && data[data.length - 1]?.pages.length < PER_PAGE);
@@ -162,7 +165,7 @@ const RecentChanges = (): JSX.Element => {
               >
                 { data != null && data.map(apiResult => apiResult.pages).flat()
                   .map(page => (
-                    <PageItem key={page._id} page={page} isSmall={isRecentChangesSidebarSmall} />
+                    <PageItem key={page._id} page={page} isSmall={isRecentChangesSidebarSmall} onClickTag={tagName => pushState(`tag:${tagName}`)} />
                   ))
                 }
               </InfiniteScroll>

+ 7 - 9
apps/app/src/components/TagCloudBox.tsx

@@ -1,7 +1,6 @@
 import React, { FC, memo } from 'react';
 
-import Link from 'next/link';
-
+import { useKeywordManager } from '~/client/services/search-operation';
 import { IDataTagCount } from '~/interfaces/tag';
 
 
@@ -23,21 +22,20 @@ const TagCloudBox: FC<Props> = memo((props:(Props & typeof defaultProps)) => {
   const { tags } = props;
   const maxTagTextLength: number = props.maxTagTextLength ?? MAX_TAG_TEXT_LENGTH;
 
+  const { pushState } = useKeywordManager();
+
   const tagElements = tags.map((tag:IDataTagCount) => {
     const tagNameFormat = (tag.name).length > maxTagTextLength ? `${(tag.name).slice(0, maxTagTextLength)}...` : tag.name;
 
-    const url = new URL('/_search', 'https://example.com');
-    url.searchParams.append('q', `tag:${tag.name}`);
-
     return (
-      <Link
+      <a
         key={tag.name}
-        href={`${url.pathname}${url.search}`}
+        type="button"
         className="grw-tag-label badge badge-secondary mr-2"
-        prefetch={false}
+        onClick={() => pushState(`tag:${tag.name}`)}
       >
         {tagNameFormat}
-      </Link>
+      </a>
     );
   });
 

+ 12 - 15
apps/app/src/components/TagList.tsx

@@ -3,8 +3,8 @@ import React, {
 } from 'react';
 
 import { useTranslation } from 'next-i18next';
-import Link from 'next/link';
 
+import { useKeywordManager } from '~/client/services/search-operation';
 import { IDataTagCount } from '~/interfaces/tag';
 
 import PaginationWrapper from './PaginationWrapper';
@@ -29,26 +29,23 @@ const TagList: FC<TagListProps> = (props:(TagListProps & typeof defaultProps)) =
   const isTagExist: boolean = tagData.length > 0;
   const { t } = useTranslation('');
 
-  const generateTagList = useCallback((tagData) => {
-    return tagData.map((tag:IDataTagCount, index:number) => {
-      const tagListClasses: string = index === 0 ? 'list-group-item d-flex' : 'list-group-item d-flex border-top-0';
-
-      const url = new URL('/_search', 'https://example.com');
-      url.searchParams.append('q', `tag:${tag.name}`);
+  const { pushState } = useKeywordManager();
 
+  const generateTagList = useCallback((tagData) => {
+    return tagData.map((tag:IDataTagCount) => {
       return (
-        <Link
+        <button
           key={tag._id}
-          href={`${url.pathname}${url.search}`}
-          className={tagListClasses}
-          prefetch={false}
+          type="button"
+          className="list-group-item list-group-item-action d-flex"
+          onClick={() => pushState(`tag:${tag.name}`)}
         >
           <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>
-        </Link>
+        </button>
       );
     });
-  }, []);
+  }, [pushState]);
 
   if (!isTagExist) {
     return <h3>{ t('You have no tag, You can set tags on pages') }</h3>;
@@ -56,9 +53,9 @@ const TagList: FC<TagListProps> = (props:(TagListProps & typeof defaultProps)) =
 
   return (
     <>
-      <ul className="list-group text-left mb-5">
+      <div className="list-group text-left mb-5">
         {generateTagList(tagData)}
-      </ul>
+      </div>
       {isPaginationShown
       && (
         <PaginationWrapper

+ 24 - 4
apps/app/src/server/routes/apiv3/customize-setting.js

@@ -61,8 +61,6 @@ const router = express.Router();
  *            type: boolean
  *          isSearchScopeChildrenAsDefault:
  *            type: boolean
- *          isEnabledMarp:
- *            type: boolean
  *      CustomizeHighlight:
  *        description: CustomizeHighlight
  *        type: object
@@ -127,6 +125,8 @@ module.exports = (crowi) => {
       body('isEnabledStaleNotification').isBoolean(),
       body('isAllReplyShown').isBoolean(),
       body('isSearchScopeChildrenAsDefault').isBoolean(),
+    ],
+    CustomizePresentation: [
       body('isEnabledMarp').isBoolean(),
     ],
     customizeTitle: [
@@ -411,7 +411,6 @@ module.exports = (crowi) => {
       'customize:isEnabledStaleNotification': req.body.isEnabledStaleNotification,
       'customize:isAllReplyShown': req.body.isAllReplyShown,
       'customize:isSearchScopeChildrenAsDefault': req.body.isSearchScopeChildrenAsDefault,
-      'customize:isEnabledMarp': req.body.isEnabledMarp,
     };
 
     try {
@@ -426,7 +425,6 @@ module.exports = (crowi) => {
         isEnabledStaleNotification: await crowi.configManager.getConfig('crowi', 'customize:isEnabledStaleNotification'),
         isAllReplyShown: await crowi.configManager.getConfig('crowi', 'customize:isAllReplyShown'),
         isSearchScopeChildrenAsDefault: await crowi.configManager.getConfig('crowi', 'customize:isSearchScopeChildrenAsDefault'),
-        isEnabledMarp: await crowi.configManager.getConfig('crowi', 'customize:isEnabledMarp'),
       };
       const parameters = { action: SupportedAction.ACTION_ADMIN_FUNCTION_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
@@ -439,6 +437,28 @@ module.exports = (crowi) => {
     }
   });
 
+
+  router.put('/presentation', loginRequiredStrictly, adminRequired, addActivity, validator.CustomizePresentation, apiV3FormValidator, async(req, res) => {
+    const requestParams = {
+      'customize:isEnabledMarp': req.body.isEnabledMarp,
+    };
+
+    try {
+      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      const customizedParams = {
+        isEnabledMarp: await crowi.configManager.getConfig('crowi', 'customize:isEnabledMarp'),
+      };
+      const parameters = { action: SupportedAction.ACTION_ADMIN_FUNCTION_UPDATE };
+      activityEvent.emit('update', res.locals.activity._id, parameters);
+      return res.apiv3({ customizedParams });
+    }
+    catch (err) {
+      const msg = 'Error occurred in updating presentaion';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'update-presentation-failed'));
+    }
+  });
+
   /**
    * @swagger
    *

+ 13 - 4
apps/app/src/stores/tag.tsx

@@ -1,19 +1,28 @@
-import { SWRResponse } from 'swr';
-import useSWRImmutable from 'swr/immutable';
+import useSWR, { SWRResponse } from 'swr';
 
 import { apiGet } from '~/client/util/apiv1-client';
 import { IResTagsListApiv1, IResTagsSearchApiv1 } from '~/interfaces/tag';
 
 export const useSWRxTagsList = (limit?: number, offset?: number): SWRResponse<IResTagsListApiv1, Error> => {
-  return useSWRImmutable(
+  return useSWR(
     ['/tags.list', limit, offset],
     ([endpoint, limit, offset]) => apiGet(endpoint, { limit, offset }).then((result: IResTagsListApiv1) => result),
+    {
+      keepPreviousData: true,
+      revalidateOnFocus: false,
+      revalidateOnReconnect: false,
+    },
   );
 };
 
 export const useSWRxTagsSearch = (query: string): SWRResponse<IResTagsSearchApiv1, Error> => {
-  return useSWRImmutable(
+  return useSWR(
     ['/tags.search', query],
     ([endpoint, query]) => apiGet(endpoint, { q: query }).then((result: IResTagsSearchApiv1) => result),
+    {
+      keepPreviousData: true,
+      revalidateOnFocus: false,
+      revalidateOnReconnect: false,
+    },
   );
 };

+ 2 - 1
apps/app/test/cypress/e2e/20-basic-features/20-basic-features--sticky-features.cy.ts

@@ -111,7 +111,8 @@ context('Access to any page', () => {
     });
     cy.waitUntil(() => {
       cy.getByTestid('grw-subnav-switcher').within(() => {
-        cy.getByTestid('editor-button').should('be.visible').click();
+        cy.getByTestid('editor-button').as('editorButton').should('be.visible');
+        cy.get('@editorButton').click();
       });
       return cy.get('.layout-root').then($elem => $elem.hasClass('editing'));
     });

+ 1 - 1
apps/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slackbot-proxy",
-  "version": "6.2.0-slackbot-proxy.0",
+  "version": "6.2.2-slackbot-proxy.0",
   "license": "MIT",
   "scripts": {
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",

+ 1 - 1
apps/slackbot-proxy/src/views/top.ejs

@@ -38,7 +38,7 @@
           </ul>
         <div class="mt-3">
           GROWI is open-source software developed by WESEEK, Inc and we are looking for contributors who can work with us.<br>
-          Please <a href="https://growi-slackin.weseek.co.jp/">join</a> Slack and feel free to talk to WESEEK members!
+          Please <a href="https://communityinviter.com/apps/wsgrowi/invite/">join</a> Slack and feel free to talk to WESEEK members!
         </div>
       </div>
     </div>

+ 1 - 1
package.json

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

+ 1 - 1
packages/core/package.json

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

+ 1 - 1
packages/hackmd/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/hackmd",
-  "version": "6.2.0-RC.0",
+  "version": "6.2.2-RC.0",
   "description": "GROWI js and css files to use hackmd",
   "license": "MIT",
   "type": "module",

+ 6 - 6
packages/presentation/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/presentation",
-  "version": "6.2.0-RC.0",
+  "version": "6.2.2-RC.0",
   "description": "GROWI plugin for presentation",
   "license": "MIT",
   "keywords": [
@@ -31,23 +31,23 @@
     "@marp-team/marp-core": "^3.6.0",
     "@types/reveal.js": "^4.4.1",
     "eslint-plugin-regex": "^1.8.0",
-    "reveal.js": "^4.4.0",
+    "hast-util-sanitize": "^4.1.0",
+    "hast-util-select": "^5.0.5",
     "mdast-util-frontmatter": "^1.0.0",
     "mdast-util-gfm": "^2.0.1",
     "mdast-util-to-markdown": "^1.3.0",
-    "hast-util-sanitize": "^4.1.0",
-    "hast-util-select": "^5.0.5",
     "react-markdown": "^8.0.7",
     "remark-frontmatter": "^4.0.1",
     "remark-stringify": "^10.0.0",
+    "reveal.js": "^4.4.0",
     "unified": "^10.1.2",
     "unist-util-find-after": "^4.0.0",
     "unist-util-visit": "^4.0.0"
   },
   "peerDependencies": {
+    "@marp-team/marpit": "*",
     "next": "^13",
     "react": "^18.2.0",
-    "react-dom": "^18.2.0",
-    "@marp-team/marpit": "*"
+    "react-dom": "^18.2.0"
   }
 }

+ 1 - 1
packages/preset-templates/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/preset-templates",
-  "version": "6.2.0-RC.0",
+  "version": "6.2.2-RC.0",
   "scripts": {
     "test": "vitest run",
     "version": "yarn version --no-git-tag-version --preid=RC"

+ 1 - 1
packages/preset-themes/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@growi/preset-themes",
   "description": "GROWI preset themes",
-  "version": "6.2.0-RC.0",
+  "version": "6.2.2-RC.0",
   "license": "MIT",
   "main": "dist/libs/preset-themes.umd.js",
   "module": "dist/libs/preset-themes.mjs",

+ 3 - 3
packages/remark-attachment-refs/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-attachment-refs",
-  "version": "6.2.0-RC.0",
+  "version": "6.2.2-RC.0",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "license": "MIT",
   "keywords": [
@@ -48,8 +48,8 @@
     "axios": "^0.24.0",
     "bunyan": "^1.8.15",
     "hast-util-select": "^5.0.5",
-    "universal-bunyan": "^0.9.2",
-    "swr": "^2.0.3"
+    "swr": "^2.0.3",
+    "universal-bunyan": "^0.9.2"
   },
   "devDependencies": {
     "csstype": "^3.1.2",

+ 2 - 3
packages/remark-drawio/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-drawio",
-  "version": "6.2.0-RC.0",
+  "version": "6.2.2-RC.0",
   "description": "remark plugin to draw diagrams with draw.io (diagrams.net)",
   "license": "MIT",
   "keywords": [
@@ -30,8 +30,7 @@
     "lint": "run-p lint:*",
     "version": "yarn version --no-git-tag-version --preid=RC"
   },
-  "dependencies": {
-  },
+  "dependencies": {},
   "devDependencies": {
     "eslint-plugin-regex": "^1.8.0",
     "hast-util-sanitize": "^4.1.0",

+ 1 - 1
packages/remark-growi-directive/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-growi-directive",
-  "version": "6.2.0-RC.0",
+  "version": "6.2.2-RC.0",
   "description": "remark plugin to support GROWI plugin (forked from remark-directive@2.0.1)",
   "license": "MIT",
   "keywords": [

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

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

+ 1 - 1
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slack",
-  "version": "6.2.0-RC.0",
+  "version": "6.2.2-RC.0",
   "license": "MIT",
   "type": "module",
   "main": "dist/index.cjs",

+ 1 - 1
packages/ui/package.json

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

+ 11 - 11
yarn.lock

@@ -2500,13 +2500,13 @@
     xdg-basedir "^4.0.0"
 
 "@growi/core@link:packages/core":
-  version "6.2.0-RC.0"
+  version "6.2.2-RC.0"
   dependencies:
     bson-objectid "^2.0.4"
     escape-string-regexp "^4.0.0"
 
 "@growi/hackmd@link:packages/hackmd":
-  version "6.2.0-RC.0"
+  version "6.2.2-RC.0"
 
 "@growi/pluginkit@link:packages/pluginkit":
   version "0.1.0"
@@ -2515,18 +2515,18 @@
     extensible-custom-error "^0.0.7"
 
 "@growi/presentation@link:packages/presentation":
-  version "6.2.0-RC.0"
+  version "6.2.2-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"
 
 "@growi/preset-templates@link:packages/preset-templates":
-  version "6.2.0-RC.0"
+  version "6.2.2-RC.0"
 
 "@growi/preset-themes@link:packages/preset-themes":
-  version "6.2.0-RC.0"
+  version "6.2.2-RC.0"
 
 "@growi/remark-attachment-refs@link:packages/remark-attachment-refs":
-  version "6.2.0-RC.0"
+  version "6.2.2-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"
     "@growi/remark-growi-directive" "link:packages/remark-growi-directive"
@@ -2538,10 +2538,10 @@
     universal-bunyan "^0.9.2"
 
 "@growi/remark-drawio@link:packages/remark-drawio":
-  version "6.2.0-RC.0"
+  version "6.2.2-RC.0"
 
 "@growi/remark-growi-directive@link:packages/remark-growi-directive":
-  version "6.2.0-RC.0"
+  version "6.2.2-RC.0"
   dependencies:
     "@types/mdast" "^3.0.0"
     "@types/unist" "^2.0.0"
@@ -2558,7 +2558,7 @@
     uvu "^0.5.0"
 
 "@growi/remark-lsx@link:packages/remark-lsx":
-  version "6.2.0-RC.0"
+  version "6.2.2-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"
     "@growi/remark-growi-directive" "link:packages/remark-growi-directive"
@@ -2570,7 +2570,7 @@
     swr "^2.0.3"
 
 "@growi/slack@link:packages/slack":
-  version "6.2.0-RC.0"
+  version "6.2.2-RC.0"
   dependencies:
     "@slack/oauth" "^2.0.1"
     "@slack/web-api" "^6.2.4"
@@ -2586,7 +2586,7 @@
     url-join "^4.0.0"
 
 "@growi/ui@link:packages/ui":
-  version "6.2.0-RC.0"
+  version "6.2.2-RC.0"
   dependencies:
     "@growi/core" "link:packages/core"