2
0
Эх сурвалжийг харах

Merge branch 'master' into fix/108868-PageAttachmentData-vrt

Shun Miyazawa 3 жил өмнө
parent
commit
d7cd158cb6
31 өөрчлөгдсөн 223 нэмэгдсэн , 208 устгасан
  1. 8 5
      packages/app/public/static/locales/en_US/admin.json
  2. 5 2
      packages/app/public/static/locales/en_US/commons.json
  3. 0 3
      packages/app/public/static/locales/en_US/translation.json
  4. 7 4
      packages/app/public/static/locales/ja_JP/admin.json
  5. 5 2
      packages/app/public/static/locales/ja_JP/commons.json
  6. 0 1
      packages/app/public/static/locales/ja_JP/translation.json
  7. 7 4
      packages/app/public/static/locales/zh_CN/admin.json
  8. 5 2
      packages/app/public/static/locales/zh_CN/commons.json
  9. 0 1
      packages/app/public/static/locales/zh_CN/translation.json
  10. 2 2
      packages/app/src/components/Admin/App/AppSetting.jsx
  11. 1 1
      packages/app/src/components/Admin/App/AppSettingsPageContents.tsx
  12. 6 6
      packages/app/src/components/Admin/App/SiteUrlSetting.tsx
  13. 1 1
      packages/app/src/components/Admin/Notification/NotificationSetting.jsx
  14. 1 1
      packages/app/src/components/Admin/Notification/UserTriggerNotification.jsx
  15. 1 1
      packages/app/src/components/Admin/Security/LocalSecuritySettingContents.jsx
  16. 13 11
      packages/app/src/components/Admin/Security/ShareLinkSetting.tsx
  17. 1 1
      packages/app/src/components/Admin/UserManagement.tsx
  18. 2 1
      packages/app/src/components/Admin/Users/GiveAdminButton.tsx
  19. 2 1
      packages/app/src/components/Admin/Users/RemoveAdminMenuItem.tsx
  20. 2 1
      packages/app/src/components/Admin/Users/StatusSuspendMenuItem.tsx
  21. 0 132
      packages/app/src/components/Admin/Users/UserMenu.jsx
  22. 5 0
      packages/app/src/components/Admin/Users/UserMenu.module.scss
  23. 114 0
      packages/app/src/components/Admin/Users/UserMenu.tsx
  24. 1 1
      packages/app/src/components/Common/ImageCropModal.tsx
  25. 1 1
      packages/app/src/components/PageEditor/HandsontableModal.tsx
  26. 6 4
      packages/app/src/pages/[[...path]].page.tsx
  27. 0 5
      packages/app/src/styles/_user.scss
  28. 5 2
      packages/app/test/cypress/integration/20-basic-features/access-to-page.spec.ts
  29. 1 1
      packages/app/test/cypress/integration/50-sidebar/access-to-side-bar.spec.ts
  30. 11 1
      packages/core/src/interfaces/user.ts
  31. 10 10
      packages/slackbot-proxy/src/services/SelectGrowiService.ts

+ 8 - 5
packages/app/public/static/locales/en_US/admin.json

@@ -329,9 +329,12 @@
     "site_name": "Site name",
     "site_name": "Site name",
     "sitename_change": "You can change site name which is used for header and HTML title.",
     "sitename_change": "You can change site name which is used for header and HTML title.",
     "header_content": "The contents entered here will be shown in the header etc.",
     "header_content": "The contents entered here will be shown in the header etc.",
-    "site_url_desc": "This is for the site URL setting.",
-    "site_url_warn": "Some features don't work because the site URL is not set.",
-    "siteurl_help": "Site full URL beginning from <code>http://</code> or <code>https://</code>.",
+    "site_url": {
+      "title": "Site URL settings",
+      "desc": "This is for the site URL setting.",
+      "warn": "Some features don't work because the site URL is not set.",
+      "help": "Site full URL beginning from <code>http://</code> or <code>https://</code>."
+    },
     "confidential_name": "Confidential name",
     "confidential_name": "Confidential name",
     "confidential_example": "ex): internal use only",
     "confidential_example": "ex): internal use only",
     "default_language": "Default language for new users",
     "default_language": "Default language for new users",
@@ -377,7 +380,7 @@
     "custom_endpoint_change": "Input the URL of the endpoint of an object storage service like MinIO that has a S3-compatible API.  Amazon S3 is used if empty.",
     "custom_endpoint_change": "Input the URL of the endpoint of an object storage service like MinIO that has a S3-compatible API.  Amazon S3 is used if empty.",
     "plugin_settings": "Plugin settings",
     "plugin_settings": "Plugin settings",
     "enable_plugin_loading": "Enable plugin loading",
     "enable_plugin_loading": "Enable plugin loading",
-    "load_plugins": "Load_plugins",
+    "load_plugins": "Load plugins",
     "enable": "Enable",
     "enable": "Enable",
     "disable": "Disable",
     "disable": "Disable",
     "use_env_var_if_empty": "If the value in the database is empty, the value of the environment variable <code>{{variable}}</code> is used.",
     "use_env_var_if_empty": "If the value in the database is empty, the value of the environment variable <code>{{variable}}</code> is used.",
@@ -577,7 +580,7 @@
     "export": "Export",
     "export": "Export",
     "cancel": "Cancel",
     "cancel": "Cancel",
     "file": "File",
     "file": "File",
-    "growi_version": "Growi Version",
+    "growi_version": "GROWI Version",
     "collections": "Collections",
     "collections": "Collections",
     "exported_at": "Exported At",
     "exported_at": "Exported At",
     "export_menu": "Export Menu",
     "export_menu": "Export Menu",

+ 5 - 2
packages/app/public/static/locales/en_US/commons.json

@@ -1,4 +1,9 @@
 {
 {
+  "Show": "Show",
+  "Hide": "Hide",
+  "Add": "Add",
+  "Reset": "Reset",
+
   "meta": {
   "meta": {
     "display_name": "English"
     "display_name": "English"
   },
   },
@@ -55,7 +60,6 @@
     "image_crop": "Image Crop",
     "image_crop": "Image Crop",
     "crop": "Crop",
     "crop": "Crop",
     "save": "Save",
     "save": "Save",
-    "reset": "Reset",
     "cancel": "Cancel"
     "cancel": "Cancel"
   },
   },
 
 
@@ -63,7 +67,6 @@
     "title": "Edit Table",
     "title": "Edit Table",
     "data_import": "Data Import",
     "data_import": "Data Import",
     "save": "Save",
     "save": "Save",
-    "reset": "Reset",
     "cancel": "Cancel",
     "cancel": "Cancel",
     "done": "Done",
     "done": "Done",
     "data_import_form": {
     "data_import_form": {

+ 0 - 3
packages/app/public/static/locales/en_US/translation.json

@@ -100,8 +100,6 @@
   "Updated": "Updated",
   "Updated": "Updated",
   "Upload new image": "Upload new image",
   "Upload new image": "Upload new image",
   "Connected": "Connected",
   "Connected": "Connected",
-  "Show": "Show",
-  "Hide": "Hide",
   "Loading": "Loading...",
   "Loading": "Loading...",
   "Disclose E-mail": "Disclose E-mail",
   "Disclose E-mail": "Disclose E-mail",
   "page exists": "this page already exists",
   "page exists": "this page already exists",
@@ -115,7 +113,6 @@
   "V5 Page Migration": "Convert To V5 Compatibility",
   "V5 Page Migration": "Convert To V5 Compatibility",
   "GROWI.5.0_new_schema": "GROWI.5.0 new schema",
   "GROWI.5.0_new_schema": "GROWI.5.0 new schema",
   "See_more_detail_on_new_schema": "See more detail on <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html#about-the-new-v5-compatible-format' target='_blank'>{{title}}</a> <i class='icon-share-alt'></i> ",
   "See_more_detail_on_new_schema": "See more detail on <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html#about-the-new-v5-compatible-format' target='_blank'>{{title}}</a> <i class='icon-share-alt'></i> ",
-  "Site URL settings": "Site URL settings",
   "external_account_management": "External Account Management",
   "external_account_management": "External Account Management",
   "UserGroup": "UserGroup",
   "UserGroup": "UserGroup",
   "Basic Settings": "Basic Settings",
   "Basic Settings": "Basic Settings",

+ 7 - 4
packages/app/public/static/locales/ja_JP/admin.json

@@ -355,9 +355,12 @@
     "site_name": "サイト名",
     "site_name": "サイト名",
     "sitename_change": "ヘッダーや HTML タイトルに使用されるサイト名を変更できます。",
     "sitename_change": "ヘッダーや HTML タイトルに使用されるサイト名を変更できます。",
     "header_content": "ここに入力した内容は、ヘッダー等に表示されます。",
     "header_content": "ここに入力した内容は、ヘッダー等に表示されます。",
-    "site_url_desc": "サイトURLを設定します。",
-    "site_url_warn": "サイトURLが設定されていないため、一部機能が動作しない状態になっています。",
-    "siteurl_help": "<code>http://</code> または <code>https://</code> から始まるサイトのURL",
+    "site_url": {
+      "title": "サイトURL設定",
+      "desc": "サイトURLを設定します。",
+      "warn": "サイトURLが設定されていないため、一部機能が動作しない状態になっています。",
+      "help": "<code>http://</code> または <code>https://</code> から始まるサイトのURL"
+    },
     "confidential_name": "コンフィデンシャル表示",
     "confidential_name": "コンフィデンシャル表示",
     "confidential_example": "例: 社外秘",
     "confidential_example": "例: 社外秘",
     "default_language": "新規ユーザーのデフォルト設定言語",
     "default_language": "新規ユーザーのデフォルト設定言語",
@@ -531,7 +534,7 @@
     "export": "エクスポート",
     "export": "エクスポート",
     "cancel": "キャンセル",
     "cancel": "キャンセル",
     "file": "ファイル名",
     "file": "ファイル名",
-    "growi_version": "Growi バージョン",
+    "growi_version": "GROWI バージョン",
     "collections": "コレクション",
     "collections": "コレクション",
     "exported_at": "エクスポートされた時間",
     "exported_at": "エクスポートされた時間",
     "export_menu": "エクスポートメニュー",
     "export_menu": "エクスポートメニュー",

+ 5 - 2
packages/app/public/static/locales/ja_JP/commons.json

@@ -1,4 +1,9 @@
 {
 {
+  "Show": "公開",
+  "Hide": "非公開",
+  "Add": "追加",
+  "Reset": "リセット",
+
   "meta": {
   "meta": {
     "display_name": "日本語"
     "display_name": "日本語"
   },
   },
@@ -55,7 +60,6 @@
     "image_crop": "画像の切り抜き",
     "image_crop": "画像の切り抜き",
     "crop": "トリミング",
     "crop": "トリミング",
     "save": "保存",
     "save": "保存",
-    "reset": "リセット",
     "cancel": "キャンセル"
     "cancel": "キャンセル"
   },
   },
 
 
@@ -63,7 +67,6 @@
     "title": "テーブル編集",
     "title": "テーブル編集",
     "data_import": "データインポート",
     "data_import": "データインポート",
     "save": "保存",
     "save": "保存",
-    "reset": "リセット",
     "cancel": "キャンセル",
     "cancel": "キャンセル",
     "done": "完了",
     "done": "完了",
     "data_import_form": {
     "data_import_form": {

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

@@ -110,7 +110,6 @@
   "V5 Page Migration": "V5 互換形式 への変換",
   "V5 Page Migration": "V5 互換形式 への変換",
   "GROWI.5.0_new_schema": "GROWI.5.0における新スキーマについて",
   "GROWI.5.0_new_schema": "GROWI.5.0における新スキーマについて",
   "See_more_detail_on_new_schema": "詳しくは<a href='https://docs.growi.org/ja/admin-guide/upgrading/50x.html#新しい-v5-互換形式について' target='_blank'>{{title}}</a><i class='icon-share-alt'></i>を参照ください。",
   "See_more_detail_on_new_schema": "詳しくは<a href='https://docs.growi.org/ja/admin-guide/upgrading/50x.html#新しい-v5-互換形式について' target='_blank'>{{title}}</a><i class='icon-share-alt'></i>を参照ください。",
-  "Site URL settings": "サイトURL設定",
   "external_account_management": "外部アカウント管理",
   "external_account_management": "外部アカウント管理",
   "UserGroup": "グループ",
   "UserGroup": "グループ",
   "Basic Settings": "基本設定",
   "Basic Settings": "基本設定",

+ 7 - 4
packages/app/public/static/locales/zh_CN/admin.json

@@ -360,9 +360,12 @@
     "site_name": "网站名称 ",
     "site_name": "网站名称 ",
     "sitename_change": "您可以更改用于标题和HTML标题的网站名称。",
     "sitename_change": "您可以更改用于标题和HTML标题的网站名称。",
     "header_content": "此处输入的内容将显示在标题等中。",
     "header_content": "此处输入的内容将显示在标题等中。",
-    "site_url_desc": "用于网站URL设置。",
-    "site_url_warn": "某些功能不起作用,因为未设置网站URL。",
-    "siteurl_help": "网站完整URL起始于 <code>http://</code> or <code>https://</code>.",
+    "site_url": {
+      "title": "主页URL设置",
+      "desc": "用于网站URL设置。",
+      "warn": "某些功能不起作用,因为未设置网站URL。",
+      "help": "网站完整URL起始于 <code>http://</code> or <code>https://</code>."
+    },
     "confidential_name": "内部名称",
     "confidential_name": "内部名称",
     "confidential_example": "ex):仅供内部使用",
     "confidential_example": "ex):仅供内部使用",
     "default_language": "新用户的默认语言",
     "default_language": "新用户的默认语言",
@@ -623,7 +626,7 @@
     "export": "导出",
     "export": "导出",
     "cancel": "取消",
     "cancel": "取消",
     "file": "文件",
     "file": "文件",
-    "growi_version": "Growi Version",
+    "growi_version": "GROWI Version",
     "collections": "Collections",
     "collections": "Collections",
     "exported_at": "Exported At",
     "exported_at": "Exported At",
     "export_menu": "导出菜单",
     "export_menu": "导出菜单",

+ 5 - 2
packages/app/public/static/locales/zh_CN/commons.json

@@ -1,4 +1,9 @@
 {
 {
+	"Show": "显示",
+	"Hide": "隐藏",
+  "Add": "添加",
+  "Reset": "重启",
+
   "meta": {
   "meta": {
     "display_name": "简体中文"
     "display_name": "简体中文"
   },
   },
@@ -55,7 +60,6 @@
     "image_crop": "图像裁剪",
     "image_crop": "图像裁剪",
     "crop": "修剪",
     "crop": "修剪",
     "save": "节省",
     "save": "节省",
-    "reset": "重启",
     "cancel": "取消"
     "cancel": "取消"
   },
   },
 
 
@@ -63,7 +67,6 @@
     "title": "编辑表格",
     "title": "编辑表格",
     "data_import": "数据导入",
     "data_import": "数据导入",
     "save": "节省",
     "save": "节省",
-    "reset": "重启",
     "cancel": "取消",
     "cancel": "取消",
     "done": "完毕",
     "done": "完毕",
     "data_import_form": {
     "data_import_form": {

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

@@ -117,7 +117,6 @@
   "V5 Page Migration": "转换为V5的兼容性",
   "V5 Page Migration": "转换为V5的兼容性",
   "GROWI.5.0_new_schema": "GROWI.5.0 new schema",
   "GROWI.5.0_new_schema": "GROWI.5.0 new schema",
   "See_more_detail_on_new_schema": "更多详情请见<a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html#about-the-new-v5-compatible-format' target='_blank'> {{title}}</a> <i class='icon-share-alt'></i> ",
   "See_more_detail_on_new_schema": "更多详情请见<a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html#about-the-new-v5-compatible-format' target='_blank'> {{title}}</a> <i class='icon-share-alt'></i> ",
-	"Site URL settings": "主页URL设置",
 	"Markdown Settings": "Markdown设置",
 	"Markdown Settings": "Markdown设置",
 	"Notification Settings": "通知设置",
 	"Notification Settings": "通知设置",
 	"external_account_management": "外部账户管理",
 	"external_account_management": "外部账户管理",

+ 2 - 2
packages/app/src/components/Admin/App/AppSetting.jsx

@@ -120,7 +120,7 @@ const AppSetting = (props) => {
               checked={adminAppContainer.state.isEmailPublishedForNewUser === true}
               checked={adminAppContainer.state.isEmailPublishedForNewUser === true}
               onChange={() => { adminAppContainer.changeIsEmailPublishedForNewUserShow(true) }}
               onChange={() => { adminAppContainer.changeIsEmailPublishedForNewUserShow(true) }}
             />
             />
-            <label className="custom-control-label" htmlFor="radio-email-show">{t('Show')}</label>
+            <label className="custom-control-label" htmlFor="radio-email-show">{t('commons:Show')}</label>
           </div>
           </div>
 
 
           <div className="custom-control custom-radio custom-control-inline">
           <div className="custom-control custom-radio custom-control-inline">
@@ -132,7 +132,7 @@ const AppSetting = (props) => {
               checked={adminAppContainer.state.isEmailPublishedForNewUser === false}
               checked={adminAppContainer.state.isEmailPublishedForNewUser === false}
               onChange={() => { adminAppContainer.changeIsEmailPublishedForNewUserShow(false) }}
               onChange={() => { adminAppContainer.changeIsEmailPublishedForNewUserShow(false) }}
             />
             />
-            <label className="custom-control-label" htmlFor="radio-email-hide">{t('Hide')}</label>
+            <label className="custom-control-label" htmlFor="radio-email-hide">{t('commons:Hide')}</label>
           </div>
           </div>
 
 
         </div>
         </div>

+ 1 - 1
packages/app/src/components/Admin/App/AppSettingsPageContents.tsx

@@ -89,7 +89,7 @@ const AppSettingsPageContents = (props: Props) => {
 
 
       <div className="row mt-5">
       <div className="row mt-5">
         <div className="col-lg-12">
         <div className="col-lg-12">
-          <h2 className="admin-setting-header">{t('Site URL settings')}</h2>
+          <h2 className="admin-setting-header">{t('app_setting.site_url.title')}</h2>
           <SiteUrlSetting />
           <SiteUrlSetting />
         </div>
         </div>
       </div>
       </div>

+ 6 - 6
packages/app/src/components/Admin/App/SiteUrlSetting.tsx

@@ -17,14 +17,14 @@ type Props = {
 }
 }
 
 
 const SiteUrlSetting = (props: Props) => {
 const SiteUrlSetting = (props: Props) => {
-  const { t } = useTranslation();
+  const { t } = useTranslation('admin', { keyPrefix: 'app_setting' });
   const { adminAppContainer } = props;
   const { adminAppContainer } = props;
 
 
 
 
   const submitHandler = useCallback(async() => {
   const submitHandler = useCallback(async() => {
     try {
     try {
       await adminAppContainer.updateSiteUrlSettingHandler();
       await adminAppContainer.updateSiteUrlSettingHandler();
-      toastSuccess(t('toaster.update_successed', { target: t('Site URL settings'), ns: 'commons' }));
+      toastSuccess(t('toaster.update_successed', { target: t('site_url.title') }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
@@ -34,9 +34,9 @@ const SiteUrlSetting = (props: Props) => {
 
 
   return (
   return (
     <React.Fragment>
     <React.Fragment>
-      <p className="card well">{t('admin:app_setting.site_url_desc')}</p>
+      <p className="card well">{t('site_url.desc')}</p>
       {!adminAppContainer.state.isSetSiteUrl
       {!adminAppContainer.state.isSetSiteUrl
-          && (<p className="alert alert-danger"><i className="icon-exclamation"></i> {t('admin:app_setting.site_url_warn')}</p>)}
+          && (<p className="alert alert-danger"><i className="icon-exclamation"></i> {t('site_url.warn')}</p>)}
 
 
       <div className="row form-group">
       <div className="row form-group">
         <div className="col-md-9 offset-md-3">
         <div className="col-md-9 offset-md-3">
@@ -64,14 +64,14 @@ const SiteUrlSetting = (props: Props) => {
                   />
                   />
                   <p className="form-text text-muted">
                   <p className="form-text text-muted">
                     {/* eslint-disable-next-line react/no-danger */}
                     {/* eslint-disable-next-line react/no-danger */}
-                    <span dangerouslySetInnerHTML={{ __html: t('admin:app_setting.siteurl_help') }} />
+                    <span dangerouslySetInnerHTML={{ __html: t('site_url.help') }} />
                   </p>
                   </p>
                 </td>
                 </td>
                 <td>
                 <td>
                   <input className="form-control" type="text" value={adminAppContainer.state.envSiteUrl || ''} readOnly />
                   <input className="form-control" type="text" value={adminAppContainer.state.envSiteUrl || ''} readOnly />
                   <p className="form-text text-muted">
                   <p className="form-text text-muted">
                     {/* eslint-disable-next-line react/no-danger */}
                     {/* eslint-disable-next-line react/no-danger */}
-                    <span dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'APP_SITE_URL' }) }} />
+                    <span dangerouslySetInnerHTML={{ __html: t('use_env_var_if_empty', { variable: 'APP_SITE_URL' }) }} />
                   </p>
                   </p>
                 </td>
                 </td>
               </tr>
               </tr>

+ 1 - 1
packages/app/src/components/Admin/Notification/NotificationSetting.jsx

@@ -74,7 +74,7 @@ const LegacySlackIntegrationListItem = ({ isEnabled }) => {
     <li className="list-group-item">
     <li className="list-group-item">
       <h4>
       <h4>
         <Badge isEnabled={isEnabled} />
         <Badge isEnabled={isEnabled} />
-        <a href="/admin/slack-integration-legacy" className="ml-2">{t('legacy_slack_integration')}</a>
+        <a href="/admin/slack-integration-legacy" className="ml-2">{t('slack_integration_legacy.slack_integration_legacy')}</a>
       </h4>
       </h4>
       { isEnabled && (
       { isEnabled && (
         <ul className="mt-2 pl-4">
         <ul className="mt-2 pl-4">

+ 1 - 1
packages/app/src/components/Admin/Notification/UserTriggerNotification.jsx

@@ -129,7 +129,7 @@ class UserTriggerNotification extends React.Component {
                 </p>
                 </p>
               </td>
               </td>
               <td>
               <td>
-                <button type="button" className="btn btn-primary" disabled={!this.validateForm()} onClick={this.onClickSubmit}>{t('add')}</button>
+                <button type="button" className="btn btn-primary" disabled={!this.validateForm()} onClick={this.onClickSubmit}>{t('commons:Add')}</button>
               </td>
               </td>
             </tr>
             </tr>
             {userNotifications.length > 0 && userNotifications.map((notification) => {
             {userNotifications.length > 0 && userNotifications.map((notification) => {

+ 1 - 1
packages/app/src/components/Admin/Security/LocalSecuritySettingContents.jsx

@@ -100,7 +100,7 @@ class LocalSecuritySettingContents extends React.Component {
 
 
             <div className="row">
             <div className="row">
               <div className="col-12 col-md-3 text-left text-md-right py-2">
               <div className="col-12 col-md-3 text-left text-md-right py-2">
-                <strong>{t('Register limitation')}</strong>
+                <strong>{t('security_settings.Register limitation')}</strong>
               </div>
               </div>
               <div className="col-12 col-md-6">
               <div className="col-12 col-md-6">
                 <div className="dropdown">
                 <div className="dropdown">

+ 13 - 11
packages/app/src/components/Admin/Security/ShareLinkSetting.tsx

@@ -134,19 +134,21 @@ const ShareLinkSetting = (props: ShareLinkSettingProps) => {
         </div>
         </div>
       </div>
       </div>
       <h4>{t('security_settings.all_share_links')}</h4>
       <h4>{t('security_settings.all_share_links')}</h4>
-      <Pager
-        activePage={shareLinksActivePage}
-        pagingHandler={getShareLinkList}
-        totalLinks={totalshareLinks}
-        limit={shareLinksPagingLimit}
-      />
 
 
       {(shareLinks.length !== 0) ? (
       {(shareLinks.length !== 0) ? (
-        <ShareLinkList
-          shareLinks={shareLinks}
-          onClickDeleteButton={deleteLinkById}
-          isAdmin
-        />
+        <>
+          <Pager
+            activePage={shareLinksActivePage}
+            pagingHandler={getShareLinkList}
+            totalLinks={totalshareLinks}
+            limit={shareLinksPagingLimit}
+          />
+          <ShareLinkList
+            shareLinks={shareLinks}
+            onClickDeleteButton={deleteLinkById}
+            isAdmin
+          />
+        </>
       )
       )
         : (<p className="text-center">{t('security_settings.No_share_links')}</p>
         : (<p className="text-center">{t('security_settings.No_share_links')}</p>
         )
         )

+ 1 - 1
packages/app/src/components/Admin/UserManagement.tsx

@@ -182,7 +182,7 @@ const UserManagement = (props: UserManagementProps) => {
               onClick={resetButtonClickHandler}
               onClick={resetButtonClickHandler}
             >
             >
               <span className="icon-refresh mr-1"></span>
               <span className="icon-refresh mr-1"></span>
-              Reset
+              {t('commons:Reset')}
             </button>
             </button>
           </div>
           </div>
         </div>
         </div>

+ 2 - 1
packages/app/src/components/Admin/Users/GiveAdminButton.tsx

@@ -39,6 +39,7 @@ const GiveAdminButton = (props: GiveAdminButtonProps): JSX.Element => {
 /**
 /**
  * Wrapper component for using unstated
  * Wrapper component for using unstated
  */
  */
-const GiveAdminButtonWrapper = withUnstatedContainers(GiveAdminButton, [AdminUsersContainer]);
+// eslint-disable-next-line max-len
+const GiveAdminButtonWrapper: React.ForwardRefExoticComponent<Pick<any, string | number | symbol> & React.RefAttributes<any>> = withUnstatedContainers(GiveAdminButton, [AdminUsersContainer]);
 
 
 export default GiveAdminButtonWrapper;
 export default GiveAdminButtonWrapper;

+ 2 - 1
packages/app/src/components/Admin/Users/RemoveAdminMenuItem.tsx

@@ -58,6 +58,7 @@ const RemoveAdminMenuItem = (props: Props): JSX.Element => {
 /**
 /**
 * Wrapper component for using unstated
 * Wrapper component for using unstated
 */
 */
-const RemoveAdminMenuItemWrapper = withUnstatedContainers(RemoveAdminMenuItem, [AdminUsersContainer]);
+// eslint-disable-next-line max-len
+const RemoveAdminMenuItemWrapper: React.ForwardRefExoticComponent<Pick<any, string | number | symbol> & React.RefAttributes<any>> = withUnstatedContainers(RemoveAdminMenuItem, [AdminUsersContainer]);
 
 
 export default RemoveAdminMenuItemWrapper;
 export default RemoveAdminMenuItemWrapper;

+ 2 - 1
packages/app/src/components/Admin/Users/StatusSuspendMenuItem.tsx

@@ -56,6 +56,7 @@ const StatusSuspendMenuItem = (props: Props): JSX.Element => {
 /**
 /**
  * Wrapper component for using unstated
  * Wrapper component for using unstated
  */
  */
-const StatusSuspendMenuItemWrapper = withUnstatedContainers(StatusSuspendMenuItem, [AdminUsersContainer]);
+// eslint-disable-next-line max-len
+const StatusSuspendMenuItemWrapper: React.ForwardRefExoticComponent<Pick<any, string | number | symbol> & React.RefAttributes<any>> = withUnstatedContainers(StatusSuspendMenuItem, [AdminUsersContainer]);
 
 
 export default StatusSuspendMenuItemWrapper;
 export default StatusSuspendMenuItemWrapper;

+ 0 - 132
packages/app/src/components/Admin/Users/UserMenu.jsx

@@ -1,132 +0,0 @@
-import React, { Fragment } from 'react';
-
-import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
-import {
-  UncontrolledDropdown, DropdownToggle, DropdownMenu,
-} from 'reactstrap';
-
-import AdminUsersContainer from '~/client/services/AdminUsersContainer';
-
-import { withUnstatedContainers } from '../../UnstatedUtils';
-
-import GiveAdminButton from './GiveAdminButton';
-import RemoveAdminMenuItem from './RemoveAdminMenuItem';
-import SendInvitationEmailButton from './SendInvitationEmailButton';
-import StatusActivateButton from './StatusActivateButton';
-import StatusSuspendedMenuItem from './StatusSuspendMenuItem';
-import UserRemoveButton from './UserRemoveButton';
-
-
-class UserMenu extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      isInvitationEmailSended: this.props.user.isInvitationEmailSended,
-    };
-
-    this.onPasswordResetClicked = this.onPasswordResetClicked.bind(this);
-    this.onSuccessfullySentInvitationEmail = this.onSuccessfullySentInvitationEmail.bind(this);
-  }
-
-  onPasswordResetClicked() {
-    this.props.adminUsersContainer.showPasswordResetModal(this.props.user);
-  }
-
-  onSuccessfullySentInvitationEmail() {
-    this.setState({ isInvitationEmailSended: true });
-  }
-
-  renderEditMenu() {
-    const { t } = this.props;
-
-    return (
-      <Fragment>
-        <li className="dropdown-divider"></li>
-        <li className="dropdown-header">{t('admin:user_management.user_table.edit_menu')}</li>
-        <li>
-          <button className="dropdown-item" type="button" onClick={this.onPasswordResetClicked}>
-            <i className="icon-fw icon-key"></i>{ t('admin:user_management.reset_password') }
-          </button>
-        </li>
-      </Fragment>
-    );
-  }
-
-  renderStatusMenu() {
-    const { t, user } = this.props;
-    const { isInvitationEmailSended } = this.state;
-
-    return (
-      <Fragment>
-        <li className="dropdown-divider"></li>
-        <li className="dropdown-header">{t('user_management.status')}</li>
-        <li>
-          {(user.status === 1 || user.status === 3) && <StatusActivateButton user={user} />}
-          {user.status === 2 && <StatusSuspendedMenuItem user={user} />}
-          {user.status === 5 && (
-            <SendInvitationEmailButton
-              user={user}
-              isInvitationEmailSended={isInvitationEmailSended}
-              onSuccessfullySentInvitationEmail={this.onSuccessfullySentInvitationEmail}
-            />
-          )}
-          {(user.status === 1 || user.status === 3 || user.status === 5) && <UserRemoveButton user={user} />}
-        </li>
-      </Fragment>
-    );
-  }
-
-  renderAdminMenu() {
-    const { t, user } = this.props;
-
-    return (
-      <Fragment>
-        <li className="dropdown-divider pl-0"></li>
-        <li className="dropdown-header">{t('admin:user_management.user_table.administrator_menu')}</li>
-        <li>
-          {user.admin === true && <RemoveAdminMenuItem user={user} />}
-          {user.admin === false && <GiveAdminButton user={user} />}
-        </li>
-      </Fragment>
-    );
-  }
-
-  render() {
-    const { user } = this.props;
-    const { isInvitationEmailSended } = this.state;
-
-    return (
-      <UncontrolledDropdown id="userMenu" size="sm">
-        <DropdownToggle caret color="secondary" outline>
-          <i className="icon-settings" />
-          {(user.status === 5 && !isInvitationEmailSended) && <i className="fa fa-circle text-danger grw-usermenu-notification-icon" />}
-        </DropdownToggle>
-        <DropdownMenu positionFixed>
-          {this.renderEditMenu()}
-          {user.status !== 4 && this.renderStatusMenu()}
-          {user.status === 2 && this.renderAdminMenu()}
-        </DropdownMenu>
-      </UncontrolledDropdown>
-    );
-  }
-
-}
-
-const UserMenuWrapperFC = (props) => {
-  const { t } = useTranslation('admin');
-  return <UserMenu t={t} {...props} />;
-};
-
-const UserMenuWrapper = withUnstatedContainers(UserMenuWrapperFC, [AdminUsersContainer]);
-
-UserMenu.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  adminUsersContainer: PropTypes.instanceOf(AdminUsersContainer).isRequired,
-
-  user: PropTypes.object.isRequired,
-};
-
-export default UserMenuWrapper;

+ 5 - 0
packages/app/src/components/Admin/Users/UserMenu.module.scss

@@ -0,0 +1,5 @@
+.grw-usermenu-notification-icon :global {
+  position: absolute;
+  top: -4px;
+  left: 30px;
+}

+ 114 - 0
packages/app/src/components/Admin/Users/UserMenu.tsx

@@ -0,0 +1,114 @@
+import React, { useState, useCallback } from 'react';
+
+import { IUserHasId, USER_STATUS } from '@growi/core';
+import { useTranslation } from 'next-i18next';
+import {
+  UncontrolledDropdown, DropdownToggle, DropdownMenu,
+} from 'reactstrap';
+
+import AdminUsersContainer from '~/client/services/AdminUsersContainer';
+
+import { withUnstatedContainers } from '../../UnstatedUtils';
+
+import GiveAdminButton from './GiveAdminButton';
+import RemoveAdminMenuItem from './RemoveAdminMenuItem';
+import SendInvitationEmailButton from './SendInvitationEmailButton';
+import StatusActivateButton from './StatusActivateButton';
+import StatusSuspendedMenuItem from './StatusSuspendMenuItem';
+import UserRemoveButton from './UserRemoveButton';
+
+import styles from './UserMenu.module.scss';
+
+type UserMenuProps = {
+  adminUsersContainer: AdminUsersContainer,
+  user: IUserHasId,
+}
+
+const UserMenu = (props: UserMenuProps) => {
+  const { t } = useTranslation('admin');
+
+  const { adminUsersContainer, user } = props;
+
+  const [isInvitationEmailSended, setIsInvitationEmailSended] = useState<boolean>(user.isInvitationEmailSended);
+
+  const onClickPasswordResetHandler = useCallback(async() => {
+    await adminUsersContainer.showPasswordResetModal(user);
+  }, [adminUsersContainer, user]);
+
+  const onSuccessfullySentInvitationEmailHandler = useCallback(() => {
+    setIsInvitationEmailSended(true);
+  }, []);
+
+  const renderEditMenu = useCallback(() => {
+    return (
+      <>
+        <li className="dropdown-divider"></li>
+        <li className="dropdown-header">{t('user_management.user_table.edit_menu')}</li>
+        <li>
+          <button className="dropdown-item" type="button" onClick={onClickPasswordResetHandler}>
+            <i className="icon-fw icon-key"></i>{ t('user_management.reset_password') }
+          </button>
+        </li>
+      </>
+    );
+  }, [onClickPasswordResetHandler, t]);
+
+  const renderStatusMenu = useCallback(() => {
+    return (
+      <>
+        <li className="dropdown-divider"></li>
+        <li className="dropdown-header">{t('user_management.status')}</li>
+        <li>
+          {(user.status === USER_STATUS.REGISTERED || user.status === USER_STATUS.SUSPENDED) && <StatusActivateButton user={user} />}
+          {user.status === USER_STATUS.ACTIVE && <StatusSuspendedMenuItem user={user} />}
+          {user.status === USER_STATUS.INVITED && (
+            <SendInvitationEmailButton
+              user={user}
+              isInvitationEmailSended={isInvitationEmailSended}
+              onSuccessfullySentInvitationEmail={onSuccessfullySentInvitationEmailHandler}
+            />
+          )}
+          {(user.status === USER_STATUS.REGISTERED || user.status === USER_STATUS.SUSPENDED || user.status === USER_STATUS.INVITED)
+          && <UserRemoveButton user={user} />}
+        </li>
+      </>
+    );
+  }, [isInvitationEmailSended, onSuccessfullySentInvitationEmailHandler, t, user]);
+
+  const renderAdminMenu = useCallback(() => {
+    return (
+      <>
+        <li className="dropdown-divider pl-0"></li>
+        <li className="dropdown-header">{t('user_management.user_table.administrator_menu')}</li>
+        <li>
+          {user.admin === true && <RemoveAdminMenuItem user={user} />}
+          {user.admin === false && <GiveAdminButton user={user} />}
+        </li>
+      </>
+    );
+  }, [t, user]);
+
+  return (
+    <UncontrolledDropdown id="userMenu" size="sm">
+      <DropdownToggle caret color="secondary" outline>
+        <i className="icon-settings" />
+        {(user.status === USER_STATUS.INVITED && !isInvitationEmailSended)
+        && <i className={`fa fa-circle text-danger grw-usermenu-notification-icon ${styles['grw-usermenu-notification-icon']}`} />}
+      </DropdownToggle>
+      <DropdownMenu positionFixed>
+        {renderEditMenu()}
+        {user.status !== USER_STATUS.DELETED && renderStatusMenu()}
+        {user.status === USER_STATUS.ACTIVE && renderAdminMenu()}
+      </DropdownMenu>
+    </UncontrolledDropdown>
+  );
+
+};
+
+/**
+* Wrapper component for using unstated
+*/
+// eslint-disable-next-line max-len
+const UserMenuWrapper: React.ForwardRefExoticComponent<Pick<any, string | number | symbol> & React.RefAttributes<any>> = withUnstatedContainers(UserMenu, [AdminUsersContainer]);
+
+export default UserMenuWrapper;

+ 1 - 1
packages/app/src/components/Common/ImageCropModal.tsx

@@ -158,7 +158,7 @@ const ImageCropModal: FC<Props> = (props: Props) => {
       </ModalBody>
       </ModalBody>
       <ModalFooter>
       <ModalFooter>
         <button type="button" className="btn btn-outline-danger rounded-pill mr-auto" disabled={!isCropImage} onClick={reset}>
         <button type="button" className="btn btn-outline-danger rounded-pill mr-auto" disabled={!isCropImage} onClick={reset}>
-          {t('crop_image_modal.reset')}
+          {t('commons:Reset')}
         </button>
         </button>
         { !showCropOption && (
         { !showCropOption && (
           <div className="mr-auto">
           <div className="mr-auto">

+ 1 - 1
packages/app/src/components/PageEditor/HandsontableModal.tsx

@@ -495,7 +495,7 @@ export const HandsontableModal = (): JSX.Element => {
         </div>
         </div>
       </ModalBody>
       </ModalBody>
       <ModalFooter className="grw-modal-footer">
       <ModalFooter className="grw-modal-footer">
-        <button type="button" className="btn btn-danger" onClick={reset}>{t('handsontable_modal.reset')}</button>
+        <button type="button" className="btn btn-danger" onClick={reset}>{t('commons:Reset')}</button>
         <div className="ml-auto">
         <div className="ml-auto">
           <button type="button" className="mr-2 btn btn-secondary" onClick={cancel}>{t('handsontable_modal.cancel')}</button>
           <button type="button" className="mr-2 btn btn-secondary" onClick={cancel}>{t('handsontable_modal.cancel')}</button>
           <button type="button" className="btn btn-primary" onClick={save}>{t('handsontable_modal.done')}</button>
           <button type="button" className="btn btn-primary" onClick={save}>{t('handsontable_modal.done')}</button>

+ 6 - 4
packages/app/src/pages/[[...path]].page.tsx

@@ -145,6 +145,7 @@ type Props = CommonProps & {
   // isAbleToDeleteCompletely: boolean,
   // isAbleToDeleteCompletely: boolean,
 
 
   templateTagData?: string[],
   templateTagData?: string[],
+  templateBodyData?: string,
 
 
   isSearchServiceConfigured: boolean,
   isSearchServiceConfigured: boolean,
   isSearchServiceReachable: boolean,
   isSearchServiceReachable: boolean,
@@ -250,7 +251,7 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   useCurrentRevisionId(props.currentRevisionId);
   useCurrentRevisionId(props.currentRevisionId);
 
 
   const { data: currentPage } = useSWRxCurrentPage(undefined, pageWithMeta?.data ?? null); // store initial data
   const { data: currentPage } = useSWRxCurrentPage(undefined, pageWithMeta?.data ?? null); // store initial data
-  useEditingMarkdown(pageWithMeta?.data.revision?.body ?? '');
+  useEditingMarkdown(pageWithMeta?.data.revision?.body ?? props.templateBodyData ?? '');
 
 
   const { data: grantData } = useSWRxIsGrantNormalized(pageId);
   const { data: grantData } = useSWRxIsGrantNormalized(pageId);
   const { mutate: mutateSelectedGrant } = useSelectedGrant();
   const { mutate: mutateSelectedGrant } = useSelectedGrant();
@@ -426,9 +427,10 @@ async function injectPageData(context: GetServerSidePropsContext, props: Props):
   }
   }
 
 
   if (page == null && user != null) {
   if (page == null && user != null) {
-    const template = await Page.findTemplate(props.currentPathname);
-    if (template != null) {
-      props.templateTagData = template.templateTags as string[];
+    const templateData = await Page.findTemplate(props.currentPathname);
+    if (templateData != null) {
+      props.templateTagData = templateData.templateTags as string[];
+      props.templateBodyData = templateData.templateBody as string;
     }
     }
   }
   }
 
 

+ 0 - 5
packages/app/src/styles/_user.scss

@@ -8,11 +8,6 @@ $easeInOutCubic: cubic-bezier(0.65, 0, 0.35, 1);
 /*
 /*
  * Styles
  * Styles
  */
  */
-.grw-usermenu-notification-icon {
-  position: absolute;
-  top: -4px;
-  left: 30px;
-}
 
 
 .draft-list-item {
 .draft-list-item {
   .icon-container {
   .icon-container {

+ 5 - 2
packages/app/test/cypress/integration/20-basic-features/access-to-page.spec.ts

@@ -42,7 +42,11 @@ context('Access to page', () => {
   it('/Sandbox with edit is successfully loaded', () => {
   it('/Sandbox with edit is successfully loaded', () => {
     cy.visit('/Sandbox');
     cy.visit('/Sandbox');
     cy.waitUntilSkeletonDisappear();
     cy.waitUntilSkeletonDisappear();
-    cy.get('#grw-subnav-container', { timeout: 30000 }).should('be.visible').within(()=>{
+
+    cy.get('#grw-subnav-container', { timeout: 30000 }).should('be.visible').within(() => {
+
+      // eslint-disable-next-line cypress/no-unnecessary-waiting
+      cy.wait(2000);
       cy.getByTestid('editor-button', { timeout: 30000 }).should('be.visible').click();
       cy.getByTestid('editor-button', { timeout: 30000 }).should('be.visible').click();
     })
     })
     cy.getByTestid('navbar-editor', { timeout: 30000 }).should('be.visible');
     cy.getByTestid('navbar-editor', { timeout: 30000 }).should('be.visible');
@@ -224,4 +228,3 @@ context('Access to /me/all-in-app-notifications', () => {
    });
    });
 
 
 })
 })
-

+ 1 - 1
packages/app/test/cypress/integration/50-sidebar/access-to-side-bar.spec.ts

@@ -165,7 +165,7 @@ context('Access to sidebar', () => {
   //   });
   //   });
   //   cy.screenshot(`${ssPrefix}access-to-drafts-page`);
   //   cy.screenshot(`${ssPrefix}access-to-drafts-page`);
   // });
   // });
-  it('Successfully access to Growi Docs page', () => {
+  it('Successfully access to GROWI Docs page', () => {
     cy.visit('/');
     cy.visit('/');
     cy.get('.grw-sidebar-nav-secondary-container').within(() => {
     cy.get('.grw-sidebar-nav-secondary-container').within(() => {
       cy.get('a[href*="https://docs.growi.org"]').then(($a) => {
       cy.get('a[href*="https://docs.growi.org"]').then(($a) => {

+ 11 - 1
packages/core/src/interfaces/user.ts

@@ -15,12 +15,13 @@ export type IUser = {
   admin: boolean,
   admin: boolean,
   apiToken?: string,
   apiToken?: string,
   isEmailPublished: boolean,
   isEmailPublished: boolean,
+  isInvitationEmailSended: boolean,
   lang: Lang,
   lang: Lang,
   slackMemberId?: string,
   slackMemberId?: string,
   createdAt: Date,
   createdAt: Date,
   lastLoginAt?: Date,
   lastLoginAt?: Date,
   introduction: string,
   introduction: string,
-  status: number,
+  status: IUserStatus,
 }
 }
 
 
 export type IUserGroupRelation = {
 export type IUserGroupRelation = {
@@ -36,6 +37,15 @@ export type IUserGroup = {
   parent: Ref<IUserGroupHasId> | null;
   parent: Ref<IUserGroupHasId> | null;
 }
 }
 
 
+export const USER_STATUS = {
+  REGISTERED: 1,
+  ACTIVE: 2,
+  SUSPENDED: 3,
+  DELETED: 4,
+  INVITED: 5,
+} as const;
+export type IUserStatus = typeof USER_STATUS[keyof typeof USER_STATUS]
+
 export type IUserHasId = IUser & HasObjectId;
 export type IUserHasId = IUser & HasObjectId;
 export type IUserGroupHasId = IUserGroup & HasObjectId;
 export type IUserGroupHasId = IUserGroup & HasObjectId;
 export type IUserGroupRelationHasId = IUserGroupRelation & HasObjectId;
 export type IUserGroupRelationHasId = IUserGroupRelation & HasObjectId;

+ 10 - 10
packages/slackbot-proxy/src/services/SelectGrowiService.ts

@@ -146,9 +146,9 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
 
 
     const selectGrowiValue = interactionPayloadAccessor.firstAction()?.value;
     const selectGrowiValue = interactionPayloadAccessor.firstAction()?.value;
     if (selectGrowiValue == null) {
     if (selectGrowiValue == null) {
-      logger.error('Growi command failed: The first action element must have the value parameter.');
+      logger.error('GROWI command failed: The first action element must have the value parameter.');
       await respond(responseUrl, {
       await respond(responseUrl, {
-        text: 'Growi command failed',
+        text: 'GROWI command failed',
         blocks: [
         blocks: [
           markdownSectionBlock('Error occurred while processing GROWI command.'),
           markdownSectionBlock('Error occurred while processing GROWI command.'),
         ],
         ],
@@ -159,9 +159,9 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
 
 
 
 
     if (growiCommand == null) {
     if (growiCommand == null) {
-      logger.error('Growi command failed: The first action value must have growiCommand parameter.');
+      logger.error('GROWI command failed: The first action value must have growiCommand parameter.');
       await respond(responseUrl, {
       await respond(responseUrl, {
-        text: 'Growi command failed',
+        text: 'GROWI command failed',
         blocks: [
         blocks: [
           markdownSectionBlock('Error occurred while processing GROWI command.'),
           markdownSectionBlock('Error occurred while processing GROWI command.'),
         ],
         ],
@@ -183,9 +183,9 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
       installation = await this.installationRepository.findByTeamIdOrEnterpriseId(installationId!);
       installation = await this.installationRepository.findByTeamIdOrEnterpriseId(installationId!);
     }
     }
     catch (err) {
     catch (err) {
-      logger.error('Growi command failed: No installation found.\n', err);
+      logger.error('GROWI command failed: No installation found.\n', err);
       await respond(responseUrl, {
       await respond(responseUrl, {
-        text: 'Growi command failed',
+        text: 'GROWI command failed',
         blocks: [
         blocks: [
           markdownSectionBlock('Error occurred while processing GROWI command.'),
           markdownSectionBlock('Error occurred while processing GROWI command.'),
         ],
         ],
@@ -200,9 +200,9 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
       .getOne();
       .getOne();
 
 
     if (relation == null) {
     if (relation == null) {
-      logger.error('Growi command failed: No installation found.');
+      logger.error('GROWI command failed: No installation found.');
       await respond(responseUrl, {
       await respond(responseUrl, {
-        text: 'Growi command failed',
+        text: 'GROWI command failed',
         blocks: [
         blocks: [
           markdownSectionBlock('Error occurred while processing GROWI command.'),
           markdownSectionBlock('Error occurred while processing GROWI command.'),
         ],
         ],
@@ -213,9 +213,9 @@ export class SelectGrowiService implements GrowiCommandProcessor<SelectGrowiComm
     // increment sendCommandBody
     // increment sendCommandBody
     const channel = interactionPayloadAccessor.getChannel();
     const channel = interactionPayloadAccessor.getChannel();
     if (channel == null) {
     if (channel == null) {
-      logger.error('Growi command failed: channel not found.');
+      logger.error('GROWI command failed: channel not found.');
       await respond(responseUrl, {
       await respond(responseUrl, {
-        text: 'Growi command failed',
+        text: 'GROWI command failed',
         blocks: [
         blocks: [
           markdownSectionBlock('Error occurred while processing GROWI command.'),
           markdownSectionBlock('Error occurred while processing GROWI command.'),
         ],
         ],