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

Merge branch 'support/apply-nextjs-2' into imprv/1000059-show-customize-page

kaori 3 лет назад
Родитель
Сommit
a773222c9d
100 измененных файлов с 1163 добавлено и 741 удалено
  1. 5 0
      packages/app/next.config.js
  2. 4 0
      packages/app/package.json
  3. 167 0
      packages/app/public/static/locales/en_US/admin/admin.json
  4. 167 0
      packages/app/public/static/locales/ja_JP/admin/admin.json
  5. 167 0
      packages/app/public/static/locales/zh_CN/admin/admin.json
  6. 1 1
      packages/app/resource/locales/en_US/welcome.md
  7. 1 1
      packages/app/resource/locales/ja_JP/welcome.md
  8. 1 1
      packages/app/resource/locales/zh_CN/welcome.md
  9. 0 5
      packages/app/src/client/base.jsx
  10. 0 4
      packages/app/src/client/boot.js
  11. 1 2
      packages/app/src/client/services/page-operation.ts
  12. 0 73
      packages/app/src/client/util/color-scheme.js
  13. 1 1
      packages/app/src/components/Admin/AuditLog/ActivityTable.tsx
  14. 1 1
      packages/app/src/components/Admin/AuditLog/AuditLogSettings.tsx
  15. 2 2
      packages/app/src/components/Admin/AuditLog/SelectActionDropdown.tsx
  16. 5 6
      packages/app/src/components/Admin/Customize/CustomizeLayoutSetting.tsx
  17. 4 5
      packages/app/src/components/Admin/Customize/CustomizeSidebarSetting.tsx
  18. 17 16
      packages/app/src/components/Admin/Customize/CustomizeThemeOptions.jsx
  19. 0 1
      packages/app/src/components/Admin/ManageExternalAccount.jsx
  20. 23 24
      packages/app/src/components/Admin/UserGroup/UserGroupDeleteModal.tsx
  21. 1 1
      packages/app/src/components/Admin/UserGroup/UserGroupModal.tsx
  22. 1 1
      packages/app/src/components/Admin/UserGroup/UserGroupPage.tsx
  23. 7 11
      packages/app/src/components/Admin/UserGroup/UserGroupTable.tsx
  24. 2 1
      packages/app/src/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx
  25. 2 1
      packages/app/src/components/Admin/UserGroupDetail/UserGroupUserTable.jsx
  26. 0 49
      packages/app/src/components/BasicLayout.tsx
  27. 30 7
      packages/app/src/components/Drawio.tsx
  28. 1 1
      packages/app/src/components/InAppNotification/InAppNotificationElm.tsx
  29. 2 1
      packages/app/src/components/InAppNotification/InAppNotificationList.tsx
  30. 1 1
      packages/app/src/components/InAppNotification/PageNotification/PageModelNotification.tsx
  31. 23 23
      packages/app/src/components/InstallerForm.jsx
  32. 5 1
      packages/app/src/components/Layout/AdminLayout.tsx
  33. 56 0
      packages/app/src/components/Layout/BasicLayout.tsx
  34. 48 0
      packages/app/src/components/Layout/RawLayout.tsx
  35. 12 28
      packages/app/src/components/Navbar/AppearanceModeDropdown.tsx
  36. 4 6
      packages/app/src/components/Navbar/GlobalSearch.tsx
  37. 1 2
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  38. 7 13
      packages/app/src/components/Navbar/GrowiNavbar.tsx
  39. 1 2
      packages/app/src/components/Page/DisplaySwitcher.tsx
  40. 1 1
      packages/app/src/components/Page/RevisionRenderer.tsx
  41. 5 3
      packages/app/src/components/PageAlert/PageStaleAlert.tsx
  42. 1 1
      packages/app/src/components/PageAlert/TrashPageAlert.tsx
  43. 24 22
      packages/app/src/components/PageComment/CommentEditor.tsx
  44. 2 1
      packages/app/src/components/PageContentFooter.tsx
  45. 4 18
      packages/app/src/components/PageCreateModal.jsx
  46. 1 2
      packages/app/src/components/PageDeleteModal.tsx
  47. 4 3
      packages/app/src/components/PageEditor/EmojiPicker.tsx
  48. 15 12
      packages/app/src/components/PageEditor/Preview.tsx
  49. 4 4
      packages/app/src/components/PageList/PageList.tsx
  50. 3 3
      packages/app/src/components/PageList/PageListItemL.tsx
  51. 0 30
      packages/app/src/components/RawLayout.tsx
  52. 18 0
      packages/app/src/components/ReactMarkdownComponents/Header.module.scss
  53. 4 1
      packages/app/src/components/ReactMarkdownComponents/Header.tsx
  54. 1 2
      packages/app/src/components/SavePageControls/GrantSelector.tsx
  55. 2 3
      packages/app/src/components/SearchForm.tsx
  56. 2 2
      packages/app/src/components/SearchPage/SearchResultContent.tsx
  57. 4 4
      packages/app/src/components/SearchPage/SearchResultList.tsx
  58. 3 4
      packages/app/src/components/SearchPage2/SearchPageBase.tsx
  59. 5 6
      packages/app/src/components/SearchTypeahead.tsx
  60. 10 10
      packages/app/src/components/Sidebar.module.scss
  61. 50 48
      packages/app/src/components/Sidebar.tsx
  62. 1 2
      packages/app/src/components/Sidebar/PageTree/Item.tsx
  63. 1 1
      packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx
  64. 1 4
      packages/app/src/components/Sidebar/RecentChanges.tsx
  65. 1 2
      packages/app/src/components/SubscribeButton.tsx
  66. 12 4
      packages/app/src/components/SystemVersion.tsx
  67. 7 7
      packages/app/src/components/Theme/ThemeAntarctic.module.scss
  68. 8 0
      packages/app/src/components/Theme/ThemeAntarctic.tsx
  69. 7 7
      packages/app/src/components/Theme/ThemeBlackboard.module.scss
  70. 8 0
      packages/app/src/components/Theme/ThemeBlackboard.tsx
  71. 7 7
      packages/app/src/components/Theme/ThemeChristmas.module.scss
  72. 8 0
      packages/app/src/components/Theme/ThemeChristmas.tsx
  73. 9 9
      packages/app/src/components/Theme/ThemeDefault.module.scss
  74. 8 0
      packages/app/src/components/Theme/ThemeDefault.tsx
  75. 0 0
      packages/app/src/components/Theme/ThemeFireRed.module.scss
  76. 0 0
      packages/app/src/components/Theme/ThemeFuture.module.scss
  77. 0 0
      packages/app/src/components/Theme/ThemeHalloween.module.scss
  78. 0 0
      packages/app/src/components/Theme/ThemeHufflepuff.module.scss
  79. 0 0
      packages/app/src/components/Theme/ThemeIsland.module.scss
  80. 0 0
      packages/app/src/components/Theme/ThemeJadeGreen.module.scss
  81. 0 0
      packages/app/src/components/Theme/ThemeKibela.module.scss
  82. 0 0
      packages/app/src/components/Theme/ThemeMonoBlue.module.scss
  83. 0 0
      packages/app/src/components/Theme/ThemeNature.module.scss
  84. 0 0
      packages/app/src/components/Theme/ThemeSpring.module.scss
  85. 0 0
      packages/app/src/components/Theme/ThemeWood.module.scss
  86. 12 0
      packages/app/src/components/Theme/utils/ThemeInjector.tsx
  87. 31 0
      packages/app/src/components/Theme/utils/ThemeProvider.tsx
  88. 66 27
      packages/app/src/interfaces/activity.ts
  89. 1 11
      packages/app/src/interfaces/attachment.ts
  90. 2 2
      packages/app/src/interfaces/comment.ts
  91. 0 19
      packages/app/src/interfaces/common.ts
  92. 2 1
      packages/app/src/interfaces/external-account.ts
  93. 1 6
      packages/app/src/interfaces/global.ts
  94. 8 0
      packages/app/src/interfaces/graph-viewer.ts
  95. 1 1
      packages/app/src/interfaces/page-grant.ts
  96. 10 116
      packages/app/src/interfaces/page.ts
  97. 3 25
      packages/app/src/interfaces/revision.ts
  98. 10 6
      packages/app/src/interfaces/search.ts
  99. 1 6
      packages/app/src/interfaces/subscription.ts
  100. 3 6
      packages/app/src/interfaces/tag.ts

+ 5 - 0
packages/app/next.config.js

@@ -22,8 +22,13 @@ const setupWithTM = () => {
     'unified',
     'unified',
     'comma-separated-tokens',
     'comma-separated-tokens',
     'decode-named-character-reference',
     'decode-named-character-reference',
+    'html-void-elements',
+    'property-information',
     'space-separated-tokens',
     'space-separated-tokens',
     'trim-lines',
     'trim-lines',
+    'web-namespaces',
+    'vfile',
+    'zwitch',
     'emoticon',
     'emoticon',
     ...listPrefixedPackages(['remark-', 'rehype-', 'hast-', 'mdast-', 'micromark-', 'micromark-', 'unist-']),
     ...listPrefixedPackages(['remark-', 'rehype-', 'hast-', 'mdast-', 'micromark-', 'micromark-', 'unist-']),
   ];
   ];

+ 4 - 0
packages/app/package.json

@@ -126,6 +126,7 @@
     "multer-autoreap": "^1.0.3",
     "multer-autoreap": "^1.0.3",
     "next": "^12.1.6",
     "next": "^12.1.6",
     "next-i18next": "^11.0.0",
     "next-i18next": "^11.0.0",
+    "next-themes": "^0.2.0",
     "next-transpile-modules": "^9.0.0",
     "next-transpile-modules": "^9.0.0",
     "nocache": "^3.0.1",
     "nocache": "^3.0.1",
     "nodemailer": "^6.6.2",
     "nodemailer": "^6.6.2",
@@ -153,6 +154,8 @@
     "react-multiline-clamp": "^2.0.0",
     "react-multiline-clamp": "^2.0.0",
     "reconnecting-websocket": "^4.4.0",
     "reconnecting-websocket": "^4.4.0",
     "redis": "^3.0.2",
     "redis": "^3.0.2",
+    "rehype-raw": "^6.1.1",
+    "rehype-sanitize": "^5.0.1",
     "rehype-slug": "^5.0.1",
     "rehype-slug": "^5.0.1",
     "rehype-toc": "^3.0.2",
     "rehype-toc": "^3.0.2",
     "remark-breaks": "^3.0.2",
     "remark-breaks": "^3.0.2",
@@ -200,6 +203,7 @@
     "emoji-mart": "npm:panta82-emoji-mart@^3.0.1",
     "emoji-mart": "npm:panta82-emoji-mart@^3.0.1",
     "eslint-plugin-cypress": "^2.12.1",
     "eslint-plugin-cypress": "^2.12.1",
     "eslint-plugin-regex": "^1.8.0",
     "eslint-plugin-regex": "^1.8.0",
+    "font-awesome": "^4.7.0",
     "handsontable": "=6.2.2",
     "handsontable": "=6.2.2",
     "i18next-hmr": "^1.7.7",
     "i18next-hmr": "^1.7.7",
     "jquery-slimscroll": "^1.3.8",
     "jquery-slimscroll": "^1.3.8",

+ 167 - 0
packages/app/public/static/locales/en_US/admin/admin.json

@@ -537,5 +537,172 @@
     "available_action_list_explain": "List of actions that can be search / view in the Audit Log",
     "available_action_list_explain": "List of actions that can be search / view in the Audit Log",
     "action_list": "Action List",
     "action_list": "Action List",
     "disable_mode_explain": "Audit log is currently disabled. To enable it, set the environment variable <code>AUDIT_LOG_ENABLED</code> to true."
     "disable_mode_explain": "Audit log is currently disabled. To enable it, set the environment variable <code>AUDIT_LOG_ENABLED</code> to true."
+  },
+  "audit_log_action_category": {
+    "Page": "Page",
+    "Comment": "Comment",
+    "Tag": "Tag",
+    "Attachment": "Attachment",
+    "ShareLink": "ShareLink",
+    "Search": "Search",
+    "User": "User",
+    "Admin": "Admin"
+  },
+  "audit_log_action": {
+    "USER_REGISTRATION_SUCCESS": "User Creation",
+    "USER_LOGIN_WITH_LOCAL": "Login with ID/Password",
+    "USER_LOGIN_WITH_LDAP": "Login with LDAP",
+    "USER_LOGIN_WITH_GOOGLE": "Login with Google",
+    "USER_LOGIN_WITH_GITHUB": "Login with GitHub",
+    "USER_LOGIN_WITH_TWITTER": "Login with Twitter",
+    "USER_LOGIN_WITH_OIDC": "Login with OIDC",
+    "USER_LOGIN_WITH_SAML": "Login with SAML",
+    "USER_LOGIN_WITH_BASIC": "Login with BASIC",
+    "USER_LOGIN_FAILURE": "Login failure",
+    "USER_LOGOUT": "Logout",
+    "USER_FOGOT_PASSWORD": "Request password reset",
+    "USER_RESET_PASSWORD": "Reset password",
+    "USER_PERSONAL_SETTINGS_UPDATE": "User personal settings update",
+    "USER_IMAGE_TYPE_UPDATE": "User image type update",
+    "USER_LDAP_ACCOUNT_ASSOCIATE": "LDAP account associate",
+    "USER_LDAP_ACCOUNT_DISCONNECT": "LDAP account disconnect",
+    "USER_PASSWORD_UPDATE": "Password update",
+    "USER_API_TOKEN_UPDATE": "API Token update",
+    "USER_EDITOR_SETTINGS_UPDATE": "Editor settings update",
+    "USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE": "In-App Notification settings update",
+    "PAGE_VIEW": "Page view",
+    "PAGE_USER_HOME_VIEW": "Page view (User home)",
+    "PAGE_FORBIDDEN": "Page view (Fobidden page)",
+    "PAGE_NOT_FOUND": "Page view (Not found page)",
+    "PAGE_NOT_CREATABLE": "Page view(Not Creatable page)",
+    "PAGE_LIKE": "Like",
+    "PAGE_UNLIKE": "Unlike",
+    "PAGE_BOOKMARK": "Bookmark",
+    "PAGE_UNBOOKMARK": "Unbookmark",
+    "PAGE_CREATE": "Create page",
+    "PAGE_UPDATE": "Update page",
+    "PAGE_RENAME": "Rename page",
+    "PAGE_DUPLICATE": "Duplicate page",
+    "PAGE_DELETE": "Delete page",
+    "PAGE_DELETE_COMPLETELY": "Delete completely page",
+    "PAGE_REVERT": "Revert page",
+    "PAGE_EMPTY_TRASH": "Empty trash",
+    "PAGE_SUBSCRIBE": "Subscribe page",
+    "PAGE_UNSUBSCRIBE": "Unsubscribe page",
+    "PAGE_EXPORT": "Export page",
+    "TAG_UPDATE": "Update tags",
+    "IN_APP_NOTIFICATION_ALL_STATUSES_OPEN": "Read all In-App notifications",
+    "COMMENT_CREATE": "Create comment",
+    "COMMENT_UPDATE": "Update comment",
+    "COMMENT_REMOVE": "Delete comment",
+    "SHARE_LINK_CREATE": "Create Share link",
+    "SHARE_LINK_DELETE": "Delete Share link",
+    "SHARE_LINK_DELETE_BY_PAGE": "Remove all shared links on the page",
+    "SHARE_LINK_ALL_DELETE": "Delete all share link",
+    "SHARE_LINK_PAGE_VIEW": "Page view(Share link)",
+    "SHARE_LINK_EXPIRED_PAGE_VIEW": "Page view(Expired share link)",
+    "SHARE_LINK_NOT_FOUND": "Page view (Not found share link)",
+    "ATTACHMENT_ADD": "Add Attachment",
+    "ATTACHMENT_REMOVE": "Delete Attachment",
+    "ACTION_ATTACHMENT_DOWNLOAD": "Download Attachment",
+    "SEARCH_PAGE": "Page Search",
+    "SEARCH_PAGE_VIEW": "Page view(Search results page)",
+    "ADMIN_APP_SETTING_UPDATE": "Update App Settings",
+    "ADMIN_SITE_URL_UPDATE": "Update Site URL Settings",
+    "ADMIN_MAIL_SMTP_UPDATE": "Update E-mail(SMTP) Settings",
+    "ADMIN_MAIL_SES_UPDATE": "Update E-mail(SES) Settings",
+    "ADMIN_MAIL_TEST_SUBMIT" : "Send test mail",
+    "ADMIN_FILE_UPLOAD_CONFIG_UPDATE": "Update File Upload Settings",
+    "ADMIN_PLUGIN_UPDATE": "Update Plugin Settings",
+    "ADMIN_MAINTENANCEMODE_ENABLED": "Enable Maintenance Mode",
+    "ADMIN_MAINTENANCEMODE_DISABLED": "Disabled Maintenance Mode",
+    "ADMIN_SECURITY_SETTINGS_UPDATE": "Update Security Settings",
+    "ADMIN_PERMIT_SHARE_LINK": "Enable Share Link",
+    "ADMIN_REJECT_SHARE_LINK": "Disable Share Link",
+    "ADMIN_AUTH_ID_PASS_ENABLED": "Enable ID/Password auth",
+    "ADMIN_AUTH_ID_PASS_DISABLED": "Disable ID/Password auth",
+    "ADMIN_AUTH_ID_PASS_UPDATE": "Update ID/Password auth settings",
+    "ADMIN_AUTH_LDAP_ENABLED": "Enable LDAP auth",
+    "ADMIN_AUTH_LDAP_DISABLED": "Disable LDAP auth",
+    "ADMIN_AUTH_LDAP_UPDATE": "Update LDAP auth settings",
+    "ADMIN_AUTH_SAML_ENABLED": "Enable SAML auth",
+    "ADMIN_AUTH_SAML_DISABLED": "Disable SAML auth",
+    "ADMIN_AUTH_SAML_UPDATE": "Update SAML auth settings",
+    "ADMIN_AUTH_OIDC_ENABLED": "Enable OIDC auth",
+    "ADMIN_AUTH_OIDC_DISABLED": "Disable OIDC auth",
+    "ADMIN_AUTH_OIDC_UPDATE": "Update OIDC settings",
+    "ADMIN_AUTH_BASIC_ENABLED": "Enable BASIC auth",
+    "ADMIN_AUTH_BASIC_DISABLED": "Disable BASIC auth",
+    "ADMIN_AUTH_BASIC_UPDATE": "Update BASIC auth settings",
+    "ADMIN_AUTH_GOOGLE_ENABLED": "Enable Google auth",
+    "ADMIN_AUTH_GOOGLE_DISABLED": "Disable Google auth",
+    "ADMIN_AUTH_GOOGLE_UPDATE": "Update Google auth settings",
+    "ADMIN_AUTH_GITHUB_ENABLED": "Enable GitHub auth",
+    "ADMIN_AUTH_GITHUB_DISABLED": "Disable GitHub auth",
+    "ADMIN_AUTH_GITHUB_UPDATE": "Update GitHub auth settings",
+    "ADMIN_AUTH_TWITTER_ENABLED": "Enable Twitter auth",
+    "ADMIN_AUTH_TWITTER_DISABLED": "Disable Twitter auth",
+    "ADMIN_AUTH_TWITTER_UPDATE": "Update Twitter auth settings",
+    "ADMIN_MARKDOWN_LINE_BREAK_UPDATE": "Update Link Break settings",
+    "ADMIN_MARKDOWN_INDENT_UPDATE": "Update Indent settings",
+    "ADMIN_MARKDOWN_PRESENTATION_UPDATE": "Update Presentation setting",
+    "ADMIN_MARKDOWN_XSS_UPDATE": "Update prevent XSS settings",
+    "ADMIN_LAYOUT_UPDATE": "Update Layout",
+    "ADMIN_THEME_UPDATE": "Update Theme",
+    "ADMIN_SIDEBAR_UPDATE": "Update Default Sidebar mode",
+    "ADMIN_FUNCTION_UPDATE": "Update Function",
+    "ADMIN_CODE_HIGHLIGHT_UPDATE": "Update Code Highlight",
+    "ADMIN_CUSTOM_TITLE_UPDATE": "Update Custom Title",
+    "ADMIN_CUSTOM_HTML_HEADER_UPDATE": "Update Custom HTML header",
+    "ADMIN_CUSTOM_CSS_UPDATE": "Update Custom CSS",
+    "ADMIN_CUSTOM_SCRIPT_UPDATE": "Update Custom script",
+    "ADMIN_ARCHIVE_DATA_UPLOAD": "Upload Archived Data",
+    "ADMIN_GROWI_DATA_IMPORTED": "Import Archived Data",
+    "ADMIN_UPLOADED_GROWI_DATA_DISCARDED": "Discard Archived Data",
+    "ADMIN_ESA_DATA_IMPORTED": "Import from esa.io",
+    "ADMIN_ESA_DATA_UPDATED": "Update esa.io import settings",
+    "ADMIN_CONNECTION_TEST_OF_ESA_DATA": "Test connection to esa",
+    "ADMIN_QIITA_DATA_IMPORTED": "Import from Qiita:Team",
+    "ADMIN_QIITA_DATA_UPDATED": "Update Qiita:Team import settings",
+    "ADMIN_CONNECTION_TEST_OF_QIITA_DATA": "Test connection to Qiita:Team",
+    "ADMIN_ARCHIVE_DATA_CREATE": "Create Archived Data",
+    "ADMIN_ARCHIVE_DATA_DOWNLOAD": "Download Archive Data",
+    "ADMIN_ARCHIVE_DATA_DELETE": "Delete Archive Data",
+    "ADMIN_USER_NOTIFICATION_SETTINGS_ADD": "Add User trigger notification notification settings",
+    "ADMIN_USER_NOTIFICATION_SETTINGS_DELETE": "Delete User trigger notification notification settings",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD": "Add Grobal notification settings",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_UPDATE": "Update Grobal notification settings",
+    "ADMIN_NOTIFICATION_GRANT_SETTINGS_UPDATE": "Update Grobal notification permissions",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ENABLED": "Enable Grobal notification settings",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_DISABLED": "Disable Grobal notification settings",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_DELETE": "Delete Grobal notification settings",
+    "ADMIN_SLACK_WORKSPACE_CREATE": "Add Slack Workspace",
+    "ADMIN_SLACK_WORKSPACE_DELETE": "Delete Slack Workspace",
+    "ADMIN_SLACK_BOT_TYPE_UPDATE": "Change Slack bot type",
+    "ADMIN_SLACK_BOT_TYPE_DELETE": "Delete Slack bot type",
+    "ADMIN_SLACK_ACCESS_TOKEN_REGENERATE": "Regenerate Slack access token",
+    "ADMIN_SLACK_MAKE_APP_PRIMARY": "Make the Slack bot primary",
+    "ADMIN_SLACK_PERMISSION_UPDATE": "Update Slack bot permissions",
+    "ADMIN_SLACK_PROXY_URI_UPDATE": "Update Proxy URL for Custom bot with proxy",
+    "ADMIN_SLACK_RELATION_TEST": "Test connection to slack bot",
+    "ADMIN_SLACK_WITHOUT_PROXY_SETTINGS_UPDATE": "Update Slack bot without proxy settings",
+    "ADMIN_SLACK_WITHOUT_PROXY_PERMISSION_UPDATE": "Update Slack bot without proxy permissions",
+    "ADMIN_SLACK_WITHOUT_PROXY_TEST": "Test connection to Slack bot without proxy",
+    "ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE": "Update Slack Incoming Webhooks configuration",
+    "ADMIN_USERS_INVITE": "User Invitation",
+    "ADMIN_USERS_PASSWORD_RESET": "Reset user password",
+    "ADMIN_USERS_ACTIVATE": "Activate user",
+    "ADMIN_USERS_DEACTIVATE": "Deactivate user",
+    "ADMIN_USERS_GIVE_ADMIN": "Give admin access",
+    "ADMIN_USERS_REMOVE_ADMIN": "Remove admin access",
+    "ADMIN_USERS_SEND_INVITATION_EMAIL": "Resend invitation email",
+    "ADMIN_USERS_REMOVE": "Remove user",
+    "ADMIN_USER_GROUP_CREATE": "Create User Group",
+    "ADMIN_USER_GROUP_UPDATE": "Update User Group",
+    "ADMIN_USER_GROUP_DELETE": "Delete User Group",
+    "ADMIN_USER_GROUP_ADD_USER": "Add User to User Group",
+    "ADMIN_SEARCH_CONNECTION": "Attempting to reconnect to Elasticsearch",
+    "ADMIN_SEARCH_INDICES_NORMALIZE": "Normalize of Elasticsearch indexes",
+    "ADMIN_SEARCH_INDICES_REBUILD": "Rebuild Elasticsearch indexes"
   }
   }
 }
 }

+ 167 - 0
packages/app/public/static/locales/ja_JP/admin/admin.json

@@ -536,5 +536,172 @@
     "available_action_list_explain": "監査ログで 検索 / 表示 可能なアクション一覧です",
     "available_action_list_explain": "監査ログで 検索 / 表示 可能なアクション一覧です",
     "action_list": "アクション一覧",
     "action_list": "アクション一覧",
     "disable_mode_explain": "現在、監査ログは無効になっています。有効にする場合は環境変数 <code>AUDIT_LOG_ENABLED</code> を true に設定してください。"
     "disable_mode_explain": "現在、監査ログは無効になっています。有効にする場合は環境変数 <code>AUDIT_LOG_ENABLED</code> を true に設定してください。"
+  },
+  "audit_log_action_category": {
+    "Page": "ページ",
+    "Comment": "コメント",
+    "Tag": "タグ",
+    "Attachment": "添付ファイル",
+    "ShareLink": "シェアリンク",
+    "Search": "検索",
+    "User": "ユーザー",
+    "Admin": "管理者ユーザー"
+  },
+  "audit_log_action": {
+    "USER_REGISTRATION_SUCCESS": "ユーザー作成",
+    "USER_LOGIN_WITH_LOCAL": "ID/Password 認証でログイン",
+    "USER_LOGIN_WITH_LDAP": "LDAP 認証でログイン",
+    "USER_LOGIN_WITH_GOOGLE": "Google 認証でログイン",
+    "USER_LOGIN_WITH_GITHUB": "GitHub 認証でログイン",
+    "USER_LOGIN_WITH_TWITTER": "Twitter 認証でログイン",
+    "USER_LOGIN_WITH_OIDC": "OIDC 認証でログイン",
+    "USER_LOGIN_WITH_SAML": "SAML 認証でログイン",
+    "USER_LOGIN_WITH_BASIC": "BASIC 認証でログイン",
+    "USER_LOGIN_FAILURE": "ログイン失敗",
+    "USER_LOGOUT": "ログアウト",
+    "USER_FOGOT_PASSWORD": "パスワードリセットのリクエスト",
+    "USER_RESET_PASSWORD": "パスワードのリセット",
+    "USER_PERSONAL_SETTINGS_UPDATE": "ユーザーの基本情報の更新",
+    "USER_IMAGE_TYPE_UPDATE": "プロフィール画像の変更",
+    "USER_LDAP_ACCOUNT_ASSOCIATE": "LDAP アカウントの追加",
+    "USER_LDAP_ACCOUNT_DISCONNECT": "LDAP アカウントの切断",
+    "USER_PASSWORD_UPDATE": "パスワードの変更",
+    "USER_API_TOKEN_UPDATE": "API トークンの更新",
+    "USER_EDITOR_SETTINGS_UPDATE": "エディター設定の更新",
+    "USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE": "アプリ内通知設定の更新",
+    "PAGE_VIEW": "ページ閲覧",
+    "PAGE_USER_HOME_VIEW": "ページ閲覧(ユーザーホーム)",
+    "PAGE_FORBIDDEN": "ページ閲覧(fobiddenページ)",
+    "PAGE_NOT_FOUND": "ページ閲覧(Not Found ページ)",
+    "PAGE_NOT_CREATABLE": "ページ閲覧(Not Creatable ページ)",
+    "PAGE_LIKE": "いいね",
+    "PAGE_UNLIKE": "いいねの解除",
+    "PAGE_BOOKMARK": "ブックマーク",
+    "PAGE_UNBOOKMARK": "ブックマークの解除",
+    "PAGE_CREATE": "ページの作成",
+    "PAGE_UPDATE": "ページの更新",
+    "PAGE_RENAME": "ページのリネーム",
+    "PAGE_DUPLICATE": "ページの複製",
+    "PAGE_DELETE": "ページの削除",
+    "PAGE_DELETE_COMPLETELY": "ページの完全削除",
+    "PAGE_REVERT": "ページを元に戻す",
+    "PAGE_EMPTY_TRASH": "ゴミ箱を空にする",
+    "PAGE_SUBSCRIBE": "ページをサブスクライブ",
+    "PAGE_UNSUBSCRIBE": "ページをアンサブスクライブ",
+    "PAGE_EXPORT": "マークダウン形式でページをエクスポート",
+    "TAG_UPDATE": "タグの更新",
+    "IN_APP_NOTIFICATION_ALL_STATUSES_OPEN": "アプリ内通知を全て既読",
+    "COMMENT_CREATE": "コメントの作成",
+    "COMMENT_UPDATE": "コメントの更新",
+    "COMMENT_REMOVE": "コメントの削除",
+    "SHARE_LINK_CREATE": "共有リンクの作成",
+    "SHARE_LINK_DELETE": "共有リンクの削除",
+    "SHARE_LINK_DELETE_BY_PAGE": "ページ内の共有リンクを全て削除",
+    "SHARE_LINK_ALL_DELETE": "共有リンクを全て削除",
+    "SHARE_LINK_PAGE_VIEW": "ページ閲覧(共有リンク)",
+    "SHARE_LINK_EXPIRED_PAGE_VIEW": "ページ閲覧(期限切れの共有リンク)",
+    "SHARE_LINK_NOT_FOUND": "ページ閲覧(存在しない共有リンク)",
+    "ATTACHMENT_ADD": "添付データの追加",
+    "ATTACHMENT_REMOVE": "添付データの削除",
+    "ACTION_ATTACHMENT_DOWNLOAD": "添付データのダウンロード",
+    "SEARCH_PAGE": "ページの検索",
+    "SEARCH_PAGE_VIEW": "ページ閲覧(検索結果ページ)",
+    "ADMIN_APP_SETTING_UPDATE": "アプリ設定の更新",
+    "ADMIN_SITE_URL_UPDATE": "サイトURL設定の更新",
+    "ADMIN_MAIL_SMTP_UPDATE": "メール設定(SMTP)の更新",
+    "ADMIN_MAIL_SES_UPDATE": "メール設定(SES)の更新",
+    "ADMIN_MAIL_TEST_SUBMIT" : "テストメールの送信",
+    "ADMIN_FILE_UPLOAD_CONFIG_UPDATE": "ファイルアップロード設定の更新",
+    "ADMIN_PLUGIN_UPDATE": "プラグイン設定の更新",
+    "ADMIN_MAINTENANCEMODE_ENABLED": "メンテナンスモードの開始",
+    "ADMIN_MAINTENANCEMODE_DISABLED": "メンテナンスモードの終了",
+    "ADMIN_SECURITY_SETTINGS_UPDATE": "セキュリティ設定の更新",
+    "ADMIN_PERMIT_SHARE_LINK": "共有リンクの有効化",
+    "ADMIN_REJECT_SHARE_LINK": "共有リンクの無効化",
+    "ADMIN_AUTH_ID_PASS_ENABLED": "ID/Password 認証を有効",
+    "ADMIN_AUTH_ID_PASS_DISABLED": "ID/Password 認証を無効",
+    "ADMIN_AUTH_ID_PASS_UPDATE": "ID/Password 認証設定を更新",
+    "ADMIN_AUTH_LDAP_ENABLED": "LDAP 認証を有効",
+    "ADMIN_AUTH_LDAP_DISABLED": "LDAP 認証を無効",
+    "ADMIN_AUTH_LDAP_UPDATE": "LDAP 認証設定を更新",
+    "ADMIN_AUTH_SAML_ENABLED": "SAML 認証を有効",
+    "ADMIN_AUTH_SAML_DISABLED": "SAML 認証を無効",
+    "ADMIN_AUTH_SAML_UPDATE": "SAML 認証設定を更新",
+    "ADMIN_AUTH_OIDC_ENABLED": "OIDC 認証を有効",
+    "ADMIN_AUTH_OIDC_DISABLED": "OIDC 認証を無効",
+    "ADMIN_AUTH_OIDC_UPDATE": "OIDC 認証設定の更新",
+    "ADMIN_AUTH_BASIC_ENABLED": "BASIC 認証の有効",
+    "ADMIN_AUTH_BASIC_DISABLED": "BASIC 認証の無効",
+    "ADMIN_AUTH_BASIC_UPDATE": "BASIC 認証設定の更新",
+    "ADMIN_AUTH_GOOGLE_ENABLED": "Google 認証の有効",
+    "ADMIN_AUTH_GOOGLE_DISABLED": "Google 認証の無効",
+    "ADMIN_AUTH_GOOGLE_UPDATE": "Google 認証設定の更新",
+    "ADMIN_AUTH_GITHUB_ENABLED": "GitHub 認証の有効",
+    "ADMIN_AUTH_GITHUB_DISABLED": "GitHub 認証の無効",
+    "ADMIN_AUTH_GITHUB_UPDATE": "GitHub 認証設定の更新",
+    "ADMIN_AUTH_TWITTER_ENABLED": "Twitter 認証の有効",
+    "ADMIN_AUTH_TWITTER_DISABLED": "Twitter 認証の無効",
+    "ADMIN_AUTH_TWITTER_UPDATE": "Twitter 認証設定の更新",
+    "ADMIN_MARKDOWN_LINE_BREAK_UPDATE": "Line Break 設定の更新",
+    "ADMIN_MARKDOWN_INDENT_UPDATE": "インデント設定の更新",
+    "ADMIN_MARKDOWN_PRESENTATION_UPDATE": "プレゼンテーション設定の更新",
+    "ADMIN_MARKDOWN_XSS_UPDATE": "XSS 対策設定の更新",
+    "ADMIN_LAYOUT_UPDATE": "レイアウト設定の更新",
+    "ADMIN_THEME_UPDATE": "テーマ設定の更新",
+    "ADMIN_SIDEBAR_UPDATE": "デフォルトのサイドバーモードの設定の更新",
+    "ADMIN_FUNCTION_UPDATE": "機能設定の更新",
+    "ADMIN_CODE_HIGHLIGHT_UPDATE": "コードハイライト設定の更新",
+    "ADMIN_CUSTOM_TITLE_UPDATE": "カスタムタイトル設定の更新",
+    "ADMIN_CUSTOM_HTML_HEADER_UPDATE": "カスタム HTML Header 設定の更新",
+    "ADMIN_CUSTOM_CSS_UPDATE": "カスタム CSS 設定の更新",
+    "ADMIN_CUSTOM_SCRIPT_UPDATE": "カスタムスクリプト設定の更新",
+    "ADMIN_ARCHIVE_DATA_UPLOAD": "アーカイブデータのアップロード",
+    "ADMIN_GROWI_DATA_IMPORTED": "アーカイブデータのインポート",
+    "ADMIN_UPLOADED_GROWI_DATA_DISCARDED": "アーカイブデータの破棄",
+    "ADMIN_ESA_DATA_IMPORTED": "esa.io からインポート",
+    "ADMIN_ESA_DATA_UPDATED": "esa.io のインポート設定の更新",
+    "ADMIN_CONNECTION_TEST_OF_ESA_DATA": "esa.io の接続テスト",
+    "ADMIN_QIITA_DATA_IMPORTED": "Qiita:Team からのインポート",
+    "ADMIN_QIITA_DATA_UPDATED": "Qiita:Team のインポート設定の更新",
+    "ADMIN_CONNECTION_TEST_OF_QIITA_DATA": "Qiita:Team の接続テスト",
+    "ADMIN_ARCHIVE_DATA_CREATE": "アーカイブデータの作成",
+    "ADMIN_ARCHIVE_DATA_DOWNLOAD": "アーカイブデータのダウンロード",
+    "ADMIN_ARCHIVE_DATA_DELETE": "アーカイブデータの削除",
+    "ADMIN_USER_NOTIFICATION_SETTINGS_ADD": "User trigger notification の通知設定の追加",
+    "ADMIN_USER_NOTIFICATION_SETTINGS_DELETE": "User trigger notification の通知設定の削除",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD": "Grobal notification の設定の追加",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_UPDATE": "Grobal notification の設定の更新",
+    "ADMIN_NOTIFICATION_GRANT_SETTINGS_UPDATE": "Grobal notification の権限の更新",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ENABLED": "Grobal notification の設定の有効",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_DISABLED": "Grobal notification の設定の無効",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_DELETE": "Grobal notification の設定の削除",
+    "ADMIN_SLACK_WORKSPACE_CREATE": "Slack ワークスペースの追加",
+    "ADMIN_SLACK_WORKSPACE_DELETE": "Slack ワークスペースの削除",
+    "ADMIN_SLACK_BOT_TYPE_UPDATE": "Slack bot タイプの変更",
+    "ADMIN_SLACK_BOT_TYPE_DELETE": "Slack bot タイプの削除",
+    "ADMIN_SLACK_ACCESS_TOKEN_REGENERATE": "Slack アクセストークンの再発行",
+    "ADMIN_SLACK_MAKE_APP_PRIMARY": "Slack bot をプライマリーにする",
+    "ADMIN_SLACK_PERMISSION_UPDATE": "Slack bot の権限の更新",
+    "ADMIN_SLACK_PROXY_URI_UPDATE": "Custom bot with proxy の Proxy URL の更新",
+    "ADMIN_SLACK_RELATION_TEST": "Slack bot の接続テスト",
+    "ADMIN_SLACK_WITHOUT_PROXY_SETTINGS_UPDATE": "Slack bot without proxy の設定の更新",
+    "ADMIN_SLACK_WITHOUT_PROXY_PERMISSION_UPDATE": "Slack bot without proxy の権限の更新",
+    "ADMIN_SLACK_WITHOUT_PROXY_TEST": "Slack bot without proxy の接続テスト",
+    "ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE": "Slack Incoming Webhooks の設定の更新",
+    "ADMIN_USERS_INVITE": "ユーザーの招待",
+    "ADMIN_USERS_PASSWORD_RESET": "ユーザーのパスワードをリセット",
+    "ADMIN_USERS_ACTIVATE": "ユーザーを承認する",
+    "ADMIN_USERS_DEACTIVATE": "ユーザーを停止する",
+    "ADMIN_USERS_GIVE_ADMIN": "管理者にする",
+    "ADMIN_USERS_REMOVE_ADMIN": "管理者から外す",
+    "ADMIN_USERS_SEND_INVITATION_EMAIL": "招待メールの再送信",
+    "ADMIN_USERS_REMOVE": "ユーザーの削除",
+    "ADMIN_USER_GROUP_CREATE": "ユーザーグループの作成",
+    "ADMIN_USER_GROUP_UPDATE": "ユーザーグループの更新",
+    "ADMIN_USER_GROUP_DELETE": "ユーザーグループの削除",
+    "ADMIN_USER_GROUP_ADD_USER": "ユーザーグループにユーザーを追加",
+    "ADMIN_SEARCH_CONNECTION": "Elasticsearch の再接続の試行",
+    "ADMIN_SEARCH_INDICES_NORMALIZE": "Elasticsearch のインデックスの正規化",
+    "ADMIN_SEARCH_INDICES_REBUILD": "Elasticsearch のインデックスのリビルド"
   }
   }
 }
 }

+ 167 - 0
packages/app/public/static/locales/zh_CN/admin/admin.json

@@ -546,5 +546,172 @@
     "available_action_list_explain": "可以在审计日志中 搜索/查看 的行动列表",
     "available_action_list_explain": "可以在审计日志中 搜索/查看 的行动列表",
     "action_list": "行动清单",
     "action_list": "行动清单",
     "disable_mode_explain": "审计日志当前已禁用。 要启用它,请将环境变量 <code>AUDIT_LOG_ENABLED</code> 设置为 true。"
     "disable_mode_explain": "审计日志当前已禁用。 要启用它,请将环境变量 <code>AUDIT_LOG_ENABLED</code> 设置为 true。"
+  },
+  "audit_log_action_category": {
+    "Page": "页面",
+    "Comment": "评论",
+    "Tag": "标签",
+    "Attachment": "附件",
+    "ShareLink": "分享链接",
+    "Search": "搜索",
+    "User": "用户",
+    "Admin": "管理"
+  },
+  "audit_log_action": {
+    "USER_REGISTRATION_SUCCESS": "用户创建",
+    "USER_LOGIN_WITH_LOCAL": "用ID/密码登录",
+    "USER_LOGIN_WITH_LDAP": "使用 LDAP 登录",
+    "USER_LOGIN_WITH_GOOGLE": "用谷歌登录",
+    "USER_LOGIN_WITH_GITHUB": "使用 GitHub 登录",
+    "USER_LOGIN_WITH_TWITTER": "使用 Twitter 登录",
+    "USER_LOGIN_WITH_OIDC": "使用 OIDC 登录",
+    "USER_LOGIN_WITH_SAML": "使用 SAML 登录",
+    "USER_LOGIN_WITH_BASIC": "使用 BASIC 登录",
+    "USER_LOGIN_FAILURE": "登录失败",
+    "USER_LOGOUT": "注销",
+    "USER_FOGOT_PASSWORD": "要求重置密码",
+    "USER_RESET_PASSWORD": "重置密码",
+    "USER_PERSONAL_SETTINGS_UPDATE": "用户个人设置更新",
+    "USER_IMAGE_TYPE_UPDATE": "用户图片类型更新",
+    "USER_LDAP_ACCOUNT_ASSOCIATE": "LDAP 帐户关联",
+    "USER_LDAP_ACCOUNT_DISCONNECT": "LDAP 账户断开连接",
+    "USER_PASSWORD_UPDATE": "密码更新",
+    "USER_API_TOKEN_UPDATE": "API 令牌更新",
+    "USER_EDITOR_SETTINGS_UPDATE": "编辑器设置更新",
+    "USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE": "应用内通知设置更新",
+    "PAGE_VIEW": "页面浏览量",
+    "PAGE_USER_HOME_VIEW": "页面浏览量(用户主页)",
+    "PAGE_FORBIDDEN": "页面浏览量(禁止页面)",
+    "PAGE_NOT_FOUND": "页面查看(未找到页面)",
+    "PAGE_NOT_CREATABLE": "页面浏览量(不可创建页面)",
+    "PAGE_LIKE": "喜欢",
+    "PAGE_UNLIKE": "不喜欢",
+    "PAGE_BOOKMARK": "书签",
+    "PAGE_UNBOOKMARK": "取消书签",
+    "PAGE_CREATE": "创建页面",
+    "PAGE_UPDATE": "更新页面",
+    "PAGE_RENAME": "重命名页面",
+    "PAGE_DUPLICATE": "重复页面",
+    "PAGE_DELETE": "删除页面",
+    "PAGE_DELETE_COMPLETELY": "彻底删除页面",
+    "PAGE_REVERT": "还原页面",
+    "PAGE_EMPTY_TRASH": "清空垃圾箱",
+    "PAGE_SUBSCRIBE": "订阅页面",
+    "PAGE_UNSUBSCRIBE": "退订页面",
+    "PAGE_EXPORT": "导出页面",
+    "TAG_UPDATE": "更新标签",
+    "IN_APP_NOTIFICATION_ALL_STATUSES_OPEN": "读取所有应用内通知",
+    "COMMENT_CREATE": "创建评论",
+    "COMMENT_UPDATE": "更新评论",
+    "COMMENT_REMOVE": "删除评论",
+    "SHARE_LINK_CREATE": "创建分享链接",
+    "SHARE_LINK_DELETE": "删除分享链接",
+    "SHARE_LINK_DELETE_BY_PAGE": "删除页面上的所有共享链接",
+    "SHARE_LINK_ALL_DELETE": "删除所有分享链接",
+    "SHARE_LINK_PAGE_VIEW": "页面浏览量(分享链接)",
+    "SHARE_LINK_EXPIRED_PAGE_VIEW": "页面浏览量(已过期的分享链接)",
+    "SHARE_LINK_NOT_FOUND": "页面浏览量(未找到分享链接)",
+    "ATTACHMENT_ADD": "添加附件",
+    "ATTACHMENT_REMOVE": "删除附件",
+    "ACTION_ATTACHMENT_DOWNLOAD": "下载附件",
+    "SEARCH_PAGE": "页面搜索",
+    "SEARCH_PAGE_VIEW": "页面浏览量(搜索结果页面)",
+    "ADMIN_APP_SETTING_UPDATE": "更新应用设置",
+    "ADMIN_SITE_URL_UPDATE": "更新站点 URL 设置",
+    "ADMIN_MAIL_SMTP_UPDATE": "更新电子邮件(SMTP)设置",
+    "ADMIN_MAIL_SES_UPDATE": "更新电子邮件(SES)设置",
+    "ADMIN_MAIL_TEST_SUBMIT" : "发送测试邮件",
+    "ADMIN_FILE_UPLOAD_CONFIG_UPDATE": "更新文件上传设置",
+    "ADMIN_PLUGIN_UPDATE": "更新插件设置",
+    "ADMIN_MAINTENANCEMODE_ENABLED": "启用维护模式",
+    "ADMIN_MAINTENANCEMODE_DISABLED": "禁用维护模式",
+    "ADMIN_SECURITY_SETTINGS_UPDATE": "更新安全设置",
+    "ADMIN_PERMIT_SHARE_LINK": "启用分享链接",
+    "ADMIN_REJECT_SHARE_LINK": "禁用分享链接",
+    "ADMIN_AUTH_ID_PASS_ENABLED": "启用 ID/密码验证",
+    "ADMIN_AUTH_ID_PASS_DISABLED": "禁用 ID/密码验证",
+    "ADMIN_AUTH_ID_PASS_UPDATE": "更新 ID/密码验证设置",
+    "ADMIN_AUTH_LDAP_ENABLED": "启用 LDAP 身份验证",
+    "ADMIN_AUTH_LDAP_DISABLED": "禁用 LDAP 身份验证",
+    "ADMIN_AUTH_LDAP_UPDATE": "更新 LDAP 身份验证设置",
+    "ADMIN_AUTH_SAML_ENABLED": "启用 SAML 身份验证",
+    "ADMIN_AUTH_SAML_DISABLED": "禁用 SAML 身份验证",
+    "ADMIN_AUTH_SAML_UPDATE": "更新 SAML 身份验证设置",
+    "ADMIN_AUTH_OIDC_ENABLED": "启用 OIDC 身份验证",
+    "ADMIN_AUTH_OIDC_DISABLED": "禁用 OIDC 身份验证",
+    "ADMIN_AUTH_OIDC_UPDATE": "更新 OIDC 设置",
+    "ADMIN_AUTH_BASIC_ENABLED": "启用基本身份验证",
+    "ADMIN_AUTH_BASIC_DISABLED": "禁用基本身份验证",
+    "ADMIN_AUTH_BASIC_UPDATE": "更新基本认证设置",
+    "ADMIN_AUTH_GOOGLE_ENABLED": "启用谷歌身份验证",
+    "ADMIN_AUTH_GOOGLE_DISABLED": "禁用谷歌身份验证",
+    "ADMIN_AUTH_GOOGLE_UPDATE": "更新谷歌授权设置",
+    "ADMIN_AUTH_GITHUB_ENABLED": "启用 GitHub 身份验证",
+    "ADMIN_AUTH_GITHUB_DISABLED": "禁用 GitHub 身份验证",
+    "ADMIN_AUTH_GITHUB_UPDATE": "更新 GitHub 授权设置",
+    "ADMIN_AUTH_TWITTER_ENABLED": "启用 Twitter 身份验证",
+    "ADMIN_AUTH_TWITTER_DISABLED": "禁用 Twitter 身份验证",
+    "ADMIN_AUTH_TWITTER_UPDATE": "更新 Twitter 授权设置",
+    "ADMIN_MARKDOWN_LINE_BREAK_UPDATE": "更新链接中断设置",
+    "ADMIN_MARKDOWN_INDENT_UPDATE": "更新缩进设置",
+    "ADMIN_MARKDOWN_PRESENTATION_UPDATE": "更新演示设置",
+    "ADMIN_MARKDOWN_XSS_UPDATE": "更新阻止 XSS 设置",
+    "ADMIN_LAYOUT_UPDATE": "更新布局",
+    "ADMIN_THEME_UPDATE": "更新主题",
+    "ADMIN_SIDEBAR_UPDATE": "更新默认的侧边栏模式",
+    "ADMIN_FUNCTION_UPDATE": "更新函数",
+    "ADMIN_CODE_HIGHLIGHT_UPDATE": "更新代码高亮",
+    "ADMIN_CUSTOM_TITLE_UPDATE": "更新自定义标题",
+    "ADMIN_CUSTOM_HTML_HEADER_UPDATE": "更新自定义 HTML 标头",
+    "ADMIN_CUSTOM_CSS_UPDATE": "更新自定义 CSS",
+    "ADMIN_CUSTOM_SCRIPT_UPDATE": "更新自定义脚本",
+    "ADMIN_ARCHIVE_DATA_UPLOAD": "上传存档数据",
+    "ADMIN_GROWI_DATA_IMPORTED": "导入存档数据",
+    "ADMIN_UPLOADED_GROWI_DATA_DISCARDED": "丢弃存档数据",
+    "ADMIN_ESA_DATA_IMPORTED": "从 esa.io 导入",
+    "ADMIN_ESA_DATA_UPDATED": "更新 esa.io 导入设置",
+    "ADMIN_CONNECTION_TEST_OF_ESA_DATA": "测试与 esa 的连接",
+    "ADMIN_QIITA_DATA_IMPORTED": "从 Qiita:Team 导入",
+    "ADMIN_QIITA_DATA_UPDATED": "更新 Qiita:团队导入设置",
+    "ADMIN_CONNECTION_TEST_OF_QIITA_DATA": "测试与 Qiita:Team 的连接",
+    "ADMIN_ARCHIVE_DATA_CREATE": "创建归档数据",
+    "ADMIN_ARCHIVE_DATA_DOWNLOAD": "下载存档数据",
+    "ADMIN_ARCHIVE_DATA_DELETE": "删除存档数据",
+    "ADMIN_USER_NOTIFICATION_SETTINGS_ADD": "添加用户触发通知通知设置",
+    "ADMIN_USER_NOTIFICATION_SETTINGS_DELETE": "删除用户触发通知通知设置",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD": "添加全局通知设置",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_UPDATE": "更新 Grobal 通知设置",
+    "ADMIN_NOTIFICATION_GRANT_SETTINGS_UPDATE": "更新 Grobal 通知权限",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ENABLED": "启用 Grobal 通知设置",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_DISABLED": "禁用 Grobal 通知设置",
+    "ADMIN_GLOBAL_NOTIFICATION_SETTINGS_DELETE": "删除 Grobal 通知设置",
+    "ADMIN_SLACK_WORKSPACE_CREATE": "添加松弛工作区",
+    "ADMIN_SLACK_WORKSPACE_DELETE": "删除 Slack 工作区",
+    "ADMIN_SLACK_BOT_TYPE_UPDATE": "更改 Slack 机器人类型",
+    "ADMIN_SLACK_BOT_TYPE_DELETE": "删除 Slack 机器人类型",
+    "ADMIN_SLACK_ACCESS_TOKEN_REGENERATE": "重新生成 Slack 访问令牌",
+    "ADMIN_SLACK_MAKE_APP_PRIMARY": "将 Slack 机器人设为主要",
+    "ADMIN_SLACK_PERMISSION_UPDATE": "更新 Slack 机器人权限",
+    "ADMIN_SLACK_PROXY_URI_UPDATE": "使用代理更新自定义机器人的代理 URL",
+    "ADMIN_SLACK_RELATION_TEST": "测试与 slack 机器人的连接",
+    "ADMIN_SLACK_WITHOUT_PROXY_SETTINGS_UPDATE": "在没有代理设置的情况下更新 Slack 机器人",
+    "ADMIN_SLACK_WITHOUT_PROXY_PERMISSION_UPDATE": "更新没有代理权限的 Slack 机器人",
+    "ADMIN_SLACK_WITHOUT_PROXY_TEST": "在没有代理的情况下测试与 Slack 机器人的连接",
+    "ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE": "更新 Slack Incoming Webhooks 配置",
+    "ADMIN_USERS_INVITE": "用户邀请",
+    "ADMIN_USERS_PASSWORD_RESET": "重置用户密码",
+    "ADMIN_USERS_ACTIVATE": "激活用户",
+    "ADMIN_USERS_DEACTIVATE": "停用用户",
+    "ADMIN_USERS_GIVE_ADMIN": "授予管理员访问权限",
+    "ADMIN_USERS_REMOVE_ADMIN": "删除管理员访问权限",
+    "ADMIN_USERS_SEND_INVITATION_EMAIL": "重发邀请函",
+    "ADMIN_USERS_REMOVE": "删除用户",
+    "ADMIN_USER_GROUP_CREATE": "创建用户组",
+    "ADMIN_USER_GROUP_UPDATE": "更新用户组",
+    "ADMIN_USER_GROUP_DELETE": "删除用户组",
+    "ADMIN_USER_GROUP_ADD_USER": "添加用户到用户组",
+    "ADMIN_SEARCH_CONNECTION": "重试Elasticsearch连接",
+    "ADMIN_SEARCH_INDICES_NORMALIZE": "试图重新连接Elasticsearch",
+    "ADMIN_SEARCH_INDICES_REBUILD": "重建 Elasticsearch 索引"
   }
   }
 }
 }

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

@@ -1,7 +1,7 @@
 # :tada: Welcome to GROWI
 # :tada: Welcome to GROWI
 
 
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
+[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
 
 
 GROWI is a Wiki for Individuals and Corporations | A knowledge base tool.
 GROWI is a Wiki for Individuals and Corporations | A knowledge base tool.
 Knowledge in companies, university laboratories, and clubs can be easily shared and anyone can edit the page.
 Knowledge in companies, university laboratories, and clubs can be easily shared and anyone can edit the page.

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

@@ -1,6 +1,6 @@
 # :tada: GROWI へようこそ
 # :tada: GROWI へようこそ
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
+[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
 
 
 GROWI は個人・法人向けの Wiki | ナレッジベースツールです。  
 GROWI は個人・法人向けの Wiki | ナレッジベースツールです。  
 会社や大学の研究室、サークルでのナレッジ情報を簡単に共有でき、作られたページは誰でも編集が可能です。
 会社や大学の研究室、サークルでのナレッジ情報を簡単に共有でき、作られたページは誰でも編集が可能です。

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

@@ -1,7 +1,7 @@
 # :tada: 欢迎来到GROWI
 # :tada: 欢迎来到GROWI
 
 
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
+[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
 
 
 GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
 GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
 公司、大学实验室和俱乐部的知识可以轻松共享,任何人都可以编辑页面。
 公司、大学实验室和俱乐部的知识可以轻松共享,任何人都可以编辑页面。

+ 0 - 5
packages/app/src/client/base.jsx

@@ -8,7 +8,6 @@ import PutbackPageModal from '~/components/PutbackPageModal';
 import ShortcutsModal from '~/components/ShortcutsModal';
 import ShortcutsModal from '~/components/ShortcutsModal';
 import SystemVersion from '~/components/SystemVersion';
 import SystemVersion from '~/components/SystemVersion';
 import InterceptorManager from '~/services/interceptor-manager';
 import InterceptorManager from '~/services/interceptor-manager';
-import Xss from '~/services/xss';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
 import EmptyTrashModal from '../components/EmptyTrashModal';
 import EmptyTrashModal from '../components/EmptyTrashModal';
@@ -30,10 +29,6 @@ if (!window) {
   window = {};
   window = {};
 }
 }
 
 
-// setup xss library
-const xss = new Xss();
-window.xss = xss;
-
 window.globalEmitter = new EventEmitter();
 window.globalEmitter = new EventEmitter();
 window.interceptorManager = new InterceptorManager();
 window.interceptorManager = new InterceptorManager();
 
 

+ 0 - 4
packages/app/src/client/boot.js

@@ -1,9 +1,5 @@
-import {
-  applyColorScheme,
-} from './util/color-scheme';
 import {
 import {
   applyOldIos,
   applyOldIos,
 } from './util/old-ios';
 } from './util/old-ios';
 
 
-applyColorScheme();
 applyOldIos();
 applyOldIos();

+ 1 - 2
packages/app/src/client/services/page-operation.ts

@@ -1,7 +1,6 @@
+import { SubscriptionStatusType } from '@growi/core';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
-import { SubscriptionStatusType } from '~/interfaces/subscription';
-
 import { toastError } from '../util/apiNotification';
 import { toastError } from '../util/apiNotification';
 import { apiv3Post, apiv3Put } from '../util/apiv3-client';
 import { apiv3Post, apiv3Put } from '../util/apiv3-client';
 
 

+ 0 - 73
packages/app/src/client/util/color-scheme.js

@@ -1,73 +0,0 @@
-const mediaQueryListForDarkMode = window.matchMedia('(prefers-color-scheme: dark)');
-
-function isUserPreferenceExists() {
-  return localStorage.preferDarkModeByUser != null;
-}
-
-function isPreferedDarkModeByUser() {
-  return localStorage.preferDarkModeByUser === 'true';
-}
-
-function isDarkMode() {
-  if (isUserPreferenceExists()) {
-    return isPreferedDarkModeByUser();
-  }
-  return mediaQueryListForDarkMode.matches;
-}
-
-/**
- * Apply color scheme as 'dark' attribute of <html></html>
- */
-function applyColorScheme() {
-  let isDarkMode = mediaQueryListForDarkMode.matches;
-  if (isUserPreferenceExists()) {
-    isDarkMode = isPreferedDarkModeByUser();
-  }
-
-  // switch to dark mode
-  if (isDarkMode) {
-    document.documentElement.removeAttribute('light');
-    document.documentElement.setAttribute('dark', 'true');
-  }
-  // switch to light mode
-  else {
-    document.documentElement.setAttribute('light', 'true');
-    document.documentElement.removeAttribute('dark');
-  }
-}
-
-/**
- * Remove color scheme preference
- */
-function removeUserPreference() {
-  if (isUserPreferenceExists()) {
-    delete localStorage.removeItem('preferDarkModeByUser');
-  }
-}
-
-/**
- * Set color scheme preference
- * @param {boolean} isDarkMode
- */
-function updateUserPreference(isDarkMode) {
-  // store settings to localStorage
-  localStorage.preferDarkModeByUser = isDarkMode;
-}
-
-/**
- * Set color scheme preference with OS settings
- */
-function updateUserPreferenceWithOsSettings() {
-  localStorage.preferDarkModeByUser = mediaQueryListForDarkMode.matches;
-}
-
-export {
-  mediaQueryListForDarkMode,
-  isUserPreferenceExists,
-  isPreferedDarkModeByUser,
-  isDarkMode,
-  applyColorScheme,
-  removeUserPreference,
-  updateUserPreference,
-  updateUserPreferenceWithOsSettings,
-};

+ 1 - 1
packages/app/src/components/Admin/AuditLog/ActivityTable.tsx

@@ -34,7 +34,7 @@ export const ActivityTable : FC<Props> = (props: Props) => {
               <tr data-testid="activity-table" key={activity._id}>
               <tr data-testid="activity-table" key={activity._id}>
                 <td>{activity.snapshot?.username}</td>
                 <td>{activity.snapshot?.username}</td>
                 <td>{formatDate(activity.createdAt)}</td>
                 <td>{formatDate(activity.createdAt)}</td>
-                <td>{activity.action}</td>
+                <td>{t(`admin:audit_log_action.${activity.action}`)}</td>
                 <td>{activity.ip}</td>
                 <td>{activity.ip}</td>
                 <td>{activity.endpoint}</td>
                 <td>{activity.endpoint}</td>
               </tr>
               </tr>

+ 1 - 1
packages/app/src/components/Admin/AuditLog/AuditLogSettings.tsx

@@ -45,7 +45,7 @@ export const AuditLogSettings: FC = () => {
       <Collapse isOpen={isExpandActionList}>
       <Collapse isOpen={isExpandActionList}>
         <ul className="list-group">
         <ul className="list-group">
           { availableActions.map(action => (
           { availableActions.map(action => (
-            <li key={action} className="list-group-item">{ action }</li>
+            <li key={action} className="list-group-item">{t(`admin:audit_log_action.${action}`)}</li>
           )) }
           )) }
         </ul>
         </ul>
       </Collapse>
       </Collapse>

+ 2 - 2
packages/app/src/components/Admin/AuditLog/SelectActionDropdown.tsx

@@ -91,7 +91,7 @@ export const SelectActionDropdown: FC<Props> = (props: Props) => {
                   defaultChecked
                   defaultChecked
                   onChange={(e) => { multipleActionCheckboxChangedHandler(item.actions, e.target.checked) }}
                   onChange={(e) => { multipleActionCheckboxChangedHandler(item.actions, e.target.checked) }}
                 />
                 />
-                <label className="form-check-label">{item.actionCategory}</label>
+                <label className="form-check-label">{t(`admin:audit_log_action_category.${item.actionCategory}`)}</label>
               </div>
               </div>
             </div>
             </div>
             {
             {
@@ -109,7 +109,7 @@ export const SelectActionDropdown: FC<Props> = (props: Props) => {
                       className="form-check-label"
                       className="form-check-label"
                       htmlFor={`checkbox${action}`}
                       htmlFor={`checkbox${action}`}
                     >
                     >
-                      {action}
+                      {t(`admin:audit_log_action.${action}`)}
                     </label>
                     </label>
                   </div>
                   </div>
                 </div>
                 </div>

+ 5 - 6
packages/app/src/components/Admin/Customize/CustomizeLayoutSetting.tsx

@@ -4,14 +4,13 @@ import { useTranslation } from 'next-i18next';
 
 
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
 import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
-import { isDarkMode as isDarkModeByUtil } from '~/client/util/color-scheme';
-
-const isDarkMode = isDarkModeByUtil();
-const colorText = isDarkMode ? 'dark' : 'light';
+import { useNextThemes } from '~/stores/use-next-themes';
 
 
 const CustomizeLayoutSetting = (): JSX.Element => {
 const CustomizeLayoutSetting = (): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
+  const { resolvedTheme } = useNextThemes();
+
   const [isContainerFluid, setIsContainerFluid] = useState(false);
   const [isContainerFluid, setIsContainerFluid] = useState(false);
   const [retrieveError, setRetrieveError] = useState();
   const [retrieveError, setRetrieveError] = useState();
 
 
@@ -54,7 +53,7 @@ const CustomizeLayoutSetting = (): JSX.Element => {
                 onClick={() => setIsContainerFluid(false)}
                 onClick={() => setIsContainerFluid(false)}
                 role="button"
                 role="button"
               >
               >
-                <img src={`/images/customize-settings/default-${colorText}.svg`} />
+                <img src={`/images/customize-settings/default-${resolvedTheme}.svg`} />
                 <div className="card-body text-center">
                 <div className="card-body text-center">
                   {t('admin:customize_setting.layout_options.default')}
                   {t('admin:customize_setting.layout_options.default')}
                 </div>
                 </div>
@@ -64,7 +63,7 @@ const CustomizeLayoutSetting = (): JSX.Element => {
                 onClick={() => setIsContainerFluid(true)}
                 onClick={() => setIsContainerFluid(true)}
                 role="button"
                 role="button"
               >
               >
-                <img src={`/images/customize-settings/fluid-${colorText}.svg`} />
+                <img src={`/images/customize-settings/fluid-${resolvedTheme}.svg`} />
                 <div className="card-body  text-center">
                 <div className="card-body  text-center">
                   {t('admin:customize_setting.layout_options.expanded')}
                   {t('admin:customize_setting.layout_options.expanded')}
                 </div>
                 </div>

+ 4 - 5
packages/app/src/components/Admin/Customize/CustomizeSidebarSetting.tsx

@@ -4,8 +4,8 @@ import { useTranslation } from 'next-i18next';
 import { Card, CardBody } from 'reactstrap';
 import { Card, CardBody } from 'reactstrap';
 
 
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
-import { isDarkMode as isDarkModeByUtil } from '~/client/util/color-scheme';
 import { useSWRxSidebarConfig } from '~/stores/ui';
 import { useSWRxSidebarConfig } from '~/stores/ui';
+import { useNextThemes } from '~/stores/use-next-themes';
 
 
 const CustomizeSidebarsetting = (): JSX.Element => {
 const CustomizeSidebarsetting = (): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
@@ -13,10 +13,9 @@ const CustomizeSidebarsetting = (): JSX.Element => {
     update, isSidebarDrawerMode, isSidebarClosedAtDockMode, setIsSidebarDrawerMode, setIsSidebarClosedAtDockMode,
     update, isSidebarDrawerMode, isSidebarClosedAtDockMode, setIsSidebarDrawerMode, setIsSidebarClosedAtDockMode,
   } = useSWRxSidebarConfig();
   } = useSWRxSidebarConfig();
 
 
-  const isDarkMode = isDarkModeByUtil();
-  const colorText = isDarkMode ? 'dark' : 'light';
-  const drawerIconFileName = `/images/customize-settings/drawer-${colorText}.svg`;
-  const dockIconFileName = `/images/customize-settings/dock-${colorText}.svg`;
+  const { resolvedTheme } = useNextThemes();
+  const drawerIconFileName = `/images/customize-settings/drawer-${resolvedTheme}.svg`;
+  const dockIconFileName = `/images/customize-settings/dock-${resolvedTheme}.svg`;
 
 
   const onClickSubmit = useCallback(async() => {
   const onClickSubmit = useCallback(async() => {
     try {
     try {

+ 17 - 16
packages/app/src/components/Admin/Customize/CustomizeThemeOptions.jsx

@@ -1,10 +1,11 @@
 import React from 'react';
 import React from 'react';
 
 
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 
 
 
 
 import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
 import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
+import { GrowiThemes } from '~/interfaces/theme';
 
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
 
@@ -12,37 +13,37 @@ import ThemeColorBox from './ThemeColorBox';
 
 
 /* eslint-disable no-multi-spaces */
 /* eslint-disable no-multi-spaces */
 const lightNDarkTheme = [{
 const lightNDarkTheme = [{
-  name: 'default',    bg: '#ffffff', topbar: '#2a2929', sidebar: '#122c55', theme: '#209fd8',
+  name: GrowiThemes.DEFAULT,      bg: '#ffffff', topbar: '#2a2929', sidebar: '#122c55', theme: '#209fd8',
 }, {
 }, {
-  name: 'mono-blue',  bg: '#F7FBFD', topbar: '#2a2929', sidebar: '#00587A', theme: '#00587A',
+  name: GrowiThemes.MONO_BLUE,    bg: '#F7FBFD', topbar: '#2a2929', sidebar: '#00587A', theme: '#00587A',
 }, {
 }, {
-  name: 'hufflepuff',  bg: '#EFE2CF', topbar: '#2a2929', sidebar: '#EAAB20', theme: '#993439',
+  name: GrowiThemes.HUFFLEPUFF,   bg: '#EFE2CF', topbar: '#2a2929', sidebar: '#EAAB20', theme: '#993439',
 }, {
 }, {
-  name: 'fire-red',  bg: '#FDFDFD', topbar: '#2c2c2c', sidebar: '#BFBFBF', theme: '#EA5532',
+  name: GrowiThemes.FIRE_RED,     bg: '#FDFDFD', topbar: '#2c2c2c', sidebar: '#BFBFBF', theme: '#EA5532',
 }, {
 }, {
-  name: 'jade-green',  bg: '#FDFDFD', topbar: '#2c2c2c', sidebar: '#BFBFBF', theme: '#38B48B',
+  name: GrowiThemes.JADE_GREEN,   bg: '#FDFDFD', topbar: '#2c2c2c', sidebar: '#BFBFBF', theme: '#38B48B',
 }];
 }];
 
 
 const uniqueTheme = [{
 const uniqueTheme = [{
-  name: 'nature',     bg: '#f9fff3', topbar: '#234136', sidebar: '#118050', theme: '#460039',
+  name: GrowiThemes.NATURE,       bg: '#f9fff3', topbar: '#234136', sidebar: '#118050', theme: '#460039',
 }, {
 }, {
-  name: 'wood',       bg: '#fffefb', topbar: '#2a2929', sidebar: '#aaa45f', theme: '#aaa45f',
+  name: GrowiThemes.WOOD,         bg: '#fffefb', topbar: '#2a2929', sidebar: '#aaa45f', theme: '#aaa45f',
 }, {
 }, {
-  name: 'island',     bg: '#cef2ef', topbar: '#2a2929', sidebar: '#0c2a44', theme: 'rgba(183, 226, 219, 1)',
+  name: GrowiThemes.ISLAND,       bg: '#cef2ef', topbar: '#2a2929', sidebar: '#0c2a44', theme: 'rgba(183, 226, 219, 1)',
 }, {
 }, {
-  name: 'christmas',  bg: '#fffefb', topbar: '#b3000c', sidebar: '#30882c', theme: '#d3c665',
+  name: GrowiThemes.CHRISTMAS,    bg: '#fffefb', topbar: '#b3000c', sidebar: '#30882c', theme: '#d3c665',
 }, {
 }, {
-  name: 'antarctic',  bg: '#ffffff', topbar: '#2a2929', sidebar: '#000080', theme: '#fa9913',
+  name: GrowiThemes.ANTARCTIC,    bg: '#ffffff', topbar: '#2a2929', sidebar: '#000080', theme: '#fa9913',
 }, {
 }, {
-  name: 'spring',     bg: '#ffffff', topbar: '#d3687c', sidebar: '#ffb8c6', theme: '#67a856',
+  name: GrowiThemes.SPRING,       bg: '#ffffff', topbar: '#d3687c', sidebar: '#ffb8c6', theme: '#67a856',
 }, {
 }, {
-  name: 'future',     bg: '#16282d', topbar: '#2a2929', sidebar: '#00b5b7', theme: '#00b5b7',
+  name: GrowiThemes.FUTURE,       bg: '#16282d', topbar: '#2a2929', sidebar: '#00b5b7', theme: '#00b5b7',
 }, {
 }, {
-  name: 'halloween',  bg: '#030003', topbar: '#aa4a04', sidebar: '#162b33', theme: '#e9af2b',
+  name: GrowiThemes.HALLOWEEN,    bg: '#030003', topbar: '#aa4a04', sidebar: '#162b33', theme: '#e9af2b',
 }, {
 }, {
-  name: 'kibela',  bg: '#f4f5f6', topbar: '#1256a3', sidebar: '#5882fa', theme: '#b5cbf79c',
+  name: GrowiThemes.KIBELA,       bg: '#f4f5f6', topbar: '#1256a3', sidebar: '#5882fa', theme: '#b5cbf79c',
 }, {
 }, {
-  name: 'blackboard',  bg: '#223729', topbar: '#563E23', sidebar: '#7B5932', theme: '#DA8506',
+  name: GrowiThemes.BLACKBOARD,   bg: '#223729', topbar: '#563E23', sidebar: '#7B5932', theme: '#DA8506',
 }];
 }];
 
 
 
 

+ 0 - 1
packages/app/src/components/Admin/ManageExternalAccount.jsx

@@ -17,7 +17,6 @@ class ManageExternalAccount extends React.Component {
 
 
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
-    this.xss = window.xss;
     this.handleExternalAccountPage = this.handleExternalAccountPage.bind(this);
     this.handleExternalAccountPage = this.handleExternalAccountPage.bind(this);
   }
   }
 
 

+ 23 - 24
packages/app/src/components/Admin/UserGroup/UserGroupDeleteModal.tsx

@@ -9,7 +9,7 @@ import {
 } from 'reactstrap';
 } from 'reactstrap';
 
 
 import { IUserGroupHasId } from '~/interfaces/user';
 import { IUserGroupHasId } from '~/interfaces/user';
-import { useXss } from '~/stores/xss';
+
 /**
 /**
  * Delete User Group Select component
  * Delete User Group Select component
  *
  *
@@ -41,10 +41,13 @@ const actionForPages = {
 };
 };
 
 
 const UserGroupDeleteModal: FC<Props> = (props: Props) => {
 const UserGroupDeleteModal: FC<Props> = (props: Props) => {
-  const { data: xss } = useXss();
 
 
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
+  const {
+    onHide, onDelete, userGroups, deleteUserGroup,
+  } = props;
+
   const availableOptions = useMemo<AvailableOption[]>(() => {
   const availableOptions = useMemo<AvailableOption[]>(() => {
     return [
     return [
       {
       {
@@ -69,7 +72,7 @@ const UserGroupDeleteModal: FC<Props> = (props: Props) => {
         label: t('admin:user_group_management.delete_modal.transfer_pages'),
         label: t('admin:user_group_management.delete_modal.transfer_pages'),
       },
       },
     ];
     ];
-  }, []);
+  }, [t]);
 
 
   /*
   /*
    * State
    * State
@@ -85,14 +88,14 @@ const UserGroupDeleteModal: FC<Props> = (props: Props) => {
     setTransferToUserGroupId('');
     setTransferToUserGroupId('');
   }, []);
   }, []);
 
 
-  const onHide = useCallback(() => {
-    if (props.onHide == null) {
+  const toggleHandler = useCallback(() => {
+    if (onHide == null) {
       return;
       return;
     }
     }
 
 
     resetStates();
     resetStates();
-    props.onHide();
-  }, [props.onHide]);
+    onHide();
+  }, [onHide, resetStates]);
 
 
   const handleActionChange = useCallback((e) => {
   const handleActionChange = useCallback((e) => {
     const actionName = e.target.value;
     const actionName = e.target.value;
@@ -105,23 +108,22 @@ const UserGroupDeleteModal: FC<Props> = (props: Props) => {
   }, []);
   }, []);
 
 
   const handleSubmit = useCallback((e) => {
   const handleSubmit = useCallback((e) => {
-    if (props.onDelete == null || props.deleteUserGroup == null) {
+    if (onDelete == null || deleteUserGroup == null) {
       return;
       return;
     }
     }
 
 
     e.preventDefault();
     e.preventDefault();
 
 
-    props.onDelete(
-      props.deleteUserGroup._id,
+    onDelete(
+      deleteUserGroup._id,
       actionName,
       actionName,
       transferToUserGroupId,
       transferToUserGroupId,
     );
     );
-  }, [props.onDelete, props.deleteUserGroup, actionName, transferToUserGroupId]);
+  }, [onDelete, deleteUserGroup, actionName, transferToUserGroupId]);
 
 
   const renderPageActionSelector = useCallback(() => {
   const renderPageActionSelector = useCallback(() => {
     const options = availableOptions.map((opt) => {
     const options = availableOptions.map((opt) => {
-      const dataContent = `<i class="icon icon-fw ${opt.iconClass} ${opt.styleClass}"></i> <span class="action-name ${opt.styleClass}">${opt.label}</span>`;
-      return <option key={opt.id} value={opt.actionForPages} data-content={dataContent}>{opt.label}</option>;
+      return <option key={opt.id} value={opt.actionForPages}>{opt.label}</option>;
     });
     });
 
 
     return (
     return (
@@ -133,25 +135,22 @@ const UserGroupDeleteModal: FC<Props> = (props: Props) => {
         onChange={handleActionChange}
         onChange={handleActionChange}
       >
       >
         <option value="" disabled>{t('admin:user_group_management.delete_modal.dropdown_desc')}</option>
         <option value="" disabled>{t('admin:user_group_management.delete_modal.dropdown_desc')}</option>
-        {options}
+        {...options}
       </select>
       </select>
     );
     );
-  }, [handleActionChange, actionName, availableOptions]);
+  }, [availableOptions, actionName, handleActionChange, t]);
 
 
   const renderGroupSelector = useCallback(() => {
   const renderGroupSelector = useCallback(() => {
-    const { deleteUserGroup } = props;
-
     if (deleteUserGroup == null) {
     if (deleteUserGroup == null) {
       return;
       return;
     }
     }
 
 
-    const groups = props.userGroups.filter((group) => {
+    const groups = userGroups.filter((group) => {
       return group._id !== deleteUserGroup._id;
       return group._id !== deleteUserGroup._id;
     });
     });
 
 
     const options = groups.map((group) => {
     const options = groups.map((group) => {
-      const dataContent = `<i class="icon icon-fw icon-organization"></i> ${xss.process(group.name)}`;
-      return <option key={group._id} value={group._id} data-content={dataContent}>{xss.process(group.name)}</option>;
+      return <option key={group._id} value={group._id}>{group.name}</option>;
     });
     });
 
 
     const defaultOptionText = groups.length === 0 ? t('admin:user_group_management.delete_modal.no_groups')
     const defaultOptionText = groups.length === 0 ? t('admin:user_group_management.delete_modal.no_groups')
@@ -165,10 +164,10 @@ const UserGroupDeleteModal: FC<Props> = (props: Props) => {
         onChange={handleGroupChange}
         onChange={handleGroupChange}
       >
       >
         <option value="" disabled>{defaultOptionText}</option>
         <option value="" disabled>{defaultOptionText}</option>
-        {options}
+        {...options}
       </select>
       </select>
     );
     );
-  }, [actionName, transferToUserGroupId, props.userGroups, props.deleteUserGroup]);
+  }, [deleteUserGroup, userGroups, t, actionName, transferToUserGroupId, handleGroupChange]);
 
 
   const validateForm = useCallback(() => {
   const validateForm = useCallback(() => {
     let isValid = true;
     let isValid = true;
@@ -184,8 +183,8 @@ const UserGroupDeleteModal: FC<Props> = (props: Props) => {
   }, [actionName, transferToUserGroupId]);
   }, [actionName, transferToUserGroupId]);
 
 
   return (
   return (
-    <Modal className="modal-md" isOpen={props.isShow} toggle={onHide}>
-      <ModalHeader tag="h4" toggle={onHide} className="bg-danger text-light">
+    <Modal className="modal-md" isOpen={props.isShow} toggle={toggleHandler}>
+      <ModalHeader tag="h4" toggle={toggleHandler} className="bg-danger text-light">
         <i className="icon icon-fire"></i> {t('admin:user_group_management.delete_modal.header')}
         <i className="icon icon-fire"></i> {t('admin:user_group_management.delete_modal.header')}
       </ModalHeader>
       </ModalHeader>
       <ModalBody>
       <ModalBody>

+ 1 - 1
packages/app/src/components/Admin/UserGroup/UserGroupModal.tsx

@@ -2,13 +2,13 @@ import React, {
   FC, useState, useEffect, useCallback,
   FC, useState, useEffect, useCallback,
 } from 'react';
 } from 'react';
 
 
+import { Ref } from '@growi/core';
 import { TFunctionResult } from 'i18next';
 import { TFunctionResult } from 'i18next';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import {
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
-import { Ref } from '~/interfaces/common';
 import { IUserGroup, IUserGroupHasId } from '~/interfaces/user';
 import { IUserGroup, IUserGroupHasId } from '~/interfaces/user';
 
 
 type Props = {
 type Props = {

+ 1 - 1
packages/app/src/components/Admin/UserGroup/UserGroupPage.tsx

@@ -1,6 +1,6 @@
 import React, { FC, useState, useCallback } from 'react';
 import React, { FC, useState, useCallback } from 'react';
 
 
-import { useTranslation } from 'next-i18next';
+import { useTranslation } from 'react-i18next';
 
 
 
 
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';

+ 7 - 11
packages/app/src/components/Admin/UserGroup/UserGroupTable.tsx

@@ -1,15 +1,12 @@
 import React, {
 import React, {
-  FC, useState, useCallback, useEffect,
+  FC, useState, useEffect,
 } from 'react';
 } from 'react';
 
 
 import dateFnsFormat from 'date-fns/format';
 import dateFnsFormat from 'date-fns/format';
 import { TFunctionResult } from 'i18next';
 import { TFunctionResult } from 'i18next';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
-import { CustomWindow } from '~/interfaces/global';
 import { IUserGroupHasId, IUserGroupRelation, IUserHasId } from '~/interfaces/user';
 import { IUserGroupHasId, IUserGroupRelation, IUserHasId } from '~/interfaces/user';
-import Xss from '~/services/xss';
-import { useXss } from '~/stores/xss';
 
 
 type Props = {
 type Props = {
   headerLabel?: TFunctionResult,
   headerLabel?: TFunctionResult,
@@ -57,7 +54,6 @@ const generateGroupIdToChildGroupsMap = (childUserGroups: IUserGroupHasId[]): Re
 
 
 
 
 const UserGroupTable: FC<Props> = (props: Props) => {
 const UserGroupTable: FC<Props> = (props: Props) => {
-  const { data: xss } = useXss();
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
   /*
   /*
@@ -152,17 +148,17 @@ const UserGroupTable: FC<Props> = (props: Props) => {
               <tr key={group._id}>
               <tr key={group._id}>
                 {props.isAclEnabled
                 {props.isAclEnabled
                   ? (
                   ? (
-                    <td><a href={`/admin/user-group-detail/${group._id}`}>{xss?.process(group.name)}</a></td>
+                    <td><a href={`/admin/user-group-detail/${group._id}`}>{group.name}</a></td>
                   )
                   )
                   : (
                   : (
-                    <td>{xss?.process(group.name)}</td>
+                    <td>{group.name}</td>
                   )
                   )
                 }
                 }
-                <td>{xss?.process(group.description)}</td>
+                <td>{group.description}</td>
                 <td>
                 <td>
                   <ul className="list-inline">
                   <ul className="list-inline">
                     {users != null && users.map((user) => {
                     {users != null && users.map((user) => {
-                      return <li key={user._id} className="list-inline-item badge badge-pill badge-warning">{xss?.process(user.username)}</li>;
+                      return <li key={user._id} className="list-inline-item badge badge-pill badge-warning">{user.username}</li>;
                     })}
                     })}
                   </ul>
                   </ul>
                 </td>
                 </td>
@@ -173,10 +169,10 @@ const UserGroupTable: FC<Props> = (props: Props) => {
                         <li key={group._id} className="list-inline-item badge badge-success">
                         <li key={group._id} className="list-inline-item badge badge-success">
                           {props.isAclEnabled
                           {props.isAclEnabled
                             ? (
                             ? (
-                              <a href={`/admin/user-group-detail/${group._id}`}>{xss?.process(group.name)}</a>
+                              <a href={`/admin/user-group-detail/${group._id}`}>{group.name}</a>
                             )
                             )
                             : (
                             : (
-                              <p>{xss?.process(group.name)}</p>
+                              <p>{group.name}</p>
                             )
                             )
                           }
                           }
                         </li>
                         </li>

+ 2 - 1
packages/app/src/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx

@@ -9,6 +9,7 @@ import { debounce } from 'throttle-debounce';
 import AdminUserGroupDetailContainer from '~/client/services/AdminUserGroupDetailContainer';
 import AdminUserGroupDetailContainer from '~/client/services/AdminUserGroupDetailContainer';
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
+import Xss from '~/services/xss';
 
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
 
@@ -25,7 +26,7 @@ class UserGroupUserFormByInput extends React.Component {
       searchError: null,
       searchError: null,
     };
     };
 
 
-    this.xss = window.xss;
+    this.xss = new Xss();
 
 
     this.addUserBySubmit = this.addUserBySubmit.bind(this);
     this.addUserBySubmit = this.addUserBySubmit.bind(this);
     this.validateForm = this.validateForm.bind(this);
     this.validateForm = this.validateForm.bind(this);

+ 2 - 1
packages/app/src/components/Admin/UserGroupDetail/UserGroupUserTable.jsx

@@ -8,6 +8,7 @@ import { useTranslation } from 'next-i18next';
 import AdminUserGroupDetailContainer from '~/client/services/AdminUserGroupDetailContainer';
 import AdminUserGroupDetailContainer from '~/client/services/AdminUserGroupDetailContainer';
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
+import Xss from '~/services/xss';
 
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
 
@@ -16,7 +17,7 @@ class UserGroupUserTable extends React.Component {
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
 
 
-    this.xss = window.xss;
+    this.xss = new Xss();
 
 
     this.removeUser = this.removeUser.bind(this);
     this.removeUser = this.removeUser.bind(this);
   }
   }

+ 0 - 49
packages/app/src/components/BasicLayout.tsx

@@ -1,49 +0,0 @@
-import React, { ReactNode } from 'react';
-
-import dynamic from 'next/dynamic';
-
-import { GrowiNavbar } from './Navbar/GrowiNavbar';
-import { RawLayout } from './RawLayout';
-import Sidebar from './Sidebar';
-
-
-type Props = {
-  title: string
-  className?: string,
-  children?: ReactNode
-}
-
-export const BasicLayout = ({ children, title, className }: Props): JSX.Element => {
-
-  // const HotkeysManager = dynamic(() => import('../client/js/components/Hotkeys/HotkeysManager'), { ssr: false });
-  // const PageCreateModal = dynamic(() => import('../client/js/components/PageCreateModal'), { ssr: false });
-  const GrowiNavbarBottom = dynamic(() => import('./Navbar/GrowiNavbarBottom').then(mod => mod.GrowiNavbarBottom), { ssr: false });
-  const ShortcutsModal = dynamic(() => import('./ShortcutsModal'), { ssr: false });
-  const SystemVersion = dynamic(() => import('./SystemVersion'), { ssr: false });
-
-  return (
-    <>
-      <RawLayout title={title} className={className}>
-        <GrowiNavbar />
-
-        <div className="page-wrapper d-flex d-print-block">
-          <div className="grw-sidebar-wrapper">
-            <Sidebar />
-          </div>
-
-          <div className="flex-fill mw-0">
-            {children}
-          </div>
-        </div>
-
-        <GrowiNavbarBottom />
-      </RawLayout>
-
-      {/* <PageCreateModal /> */}
-      {/* <HotkeysManager /> */}
-
-      <ShortcutsModal />
-      <SystemVersion />
-    </>
-  );
-};

+ 30 - 7
packages/app/src/components/Drawio.tsx

@@ -1,5 +1,5 @@
 import React, {
 import React, {
-  useCallback, useEffect, useMemo, useRef,
+  useCallback, useEffect, useMemo, useRef, useState,
 } from 'react';
 } from 'react';
 
 
 import EventEmitter from 'events';
 import EventEmitter from 'events';
@@ -8,35 +8,55 @@ import { useTranslation } from 'next-i18next';
 import { debounce } from 'throttle-debounce';
 import { debounce } from 'throttle-debounce';
 
 
 import { CustomWindow } from '~/interfaces/global';
 import { CustomWindow } from '~/interfaces/global';
-import { IGraphViewer } from '~/interfaces/graph-viewer';
+import { IGraphViewer, isGraphViewer } from '~/interfaces/graph-viewer';
 
 
 import NotAvailableForGuest from './NotAvailableForGuest';
 import NotAvailableForGuest from './NotAvailableForGuest';
 
 
 type Props = {
 type Props = {
+  GraphViewer: IGraphViewer,
   drawioContent: string,
   drawioContent: string,
   rangeLineNumberOfMarkdown: { beginLineNumber: number, endLineNumber: number },
   rangeLineNumberOfMarkdown: { beginLineNumber: number, endLineNumber: number },
   isPreview?: boolean,
   isPreview?: boolean,
 }
 }
 
 
+// It calls callback when GraphViewer is not null.
+// eslint-disable-next-line @typescript-eslint/ban-types
+const waitForGraphViewer = async(callback: Function) => {
+  const MAX_WAIT_COUNT = 10; // no reason for 10
+
+  for (let i = 0; i < MAX_WAIT_COUNT; i++) {
+    if (isGraphViewer((window as CustomWindow).GraphViewer)) {
+      callback((window as CustomWindow).GraphViewer);
+      break;
+    }
+    // Sleep 500 ms
+    // eslint-disable-next-line no-await-in-loop
+    await new Promise<void>(r => setTimeout(() => r(), 500));
+  }
+};
+
 const Drawio = (props: Props): JSX.Element => {
 const Drawio = (props: Props): JSX.Element => {
 
 
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
+  // Wrap with a function since GraphViewer is a function.
+  // This applies when call setGraphViewer as well.
+  const [GraphViewer, setGraphViewer] = useState<IGraphViewer | undefined>(() => (window as CustomWindow).GraphViewer);
+
   const { drawioContent, rangeLineNumberOfMarkdown, isPreview } = props;
   const { drawioContent, rangeLineNumberOfMarkdown, isPreview } = props;
 
 
   // const { open: openDrawioModal } = useDrawioModalForPage();
   // const { open: openDrawioModal } = useDrawioModalForPage();
 
 
   const drawioContainerRef = useRef<HTMLDivElement>(null);
   const drawioContainerRef = useRef<HTMLDivElement>(null);
 
 
-  const globalEmitter: EventEmitter = useMemo(() => (window as CustomWindow).globalEmitter, []);
-  const GraphViewer: IGraphViewer = useMemo(() => (window as CustomWindow).GraphViewer, []);
+  const globalEmitter: EventEmitter = (window as CustomWindow).globalEmitter;
 
 
   const editButtonClickHandler = useCallback(() => {
   const editButtonClickHandler = useCallback(() => {
     const { beginLineNumber, endLineNumber } = rangeLineNumberOfMarkdown;
     const { beginLineNumber, endLineNumber } = rangeLineNumberOfMarkdown;
     globalEmitter.emit('launchDrawioModal', beginLineNumber, endLineNumber);
     globalEmitter.emit('launchDrawioModal', beginLineNumber, endLineNumber);
   }, [rangeLineNumberOfMarkdown, globalEmitter]);
   }, [rangeLineNumberOfMarkdown, globalEmitter]);
 
 
-  const renderDrawio = useCallback(() => {
+  const renderDrawio = useCallback((GraphViewer: IGraphViewer) => {
     if (drawioContainerRef.current == null) {
     if (drawioContainerRef.current == null) {
       return;
       return;
     }
     }
@@ -51,16 +71,19 @@ const Drawio = (props: Props): JSX.Element => {
         GraphViewer.createViewerForElement(div);
         GraphViewer.createViewerForElement(div);
       }
       }
     }
     }
-  }, [GraphViewer]);
+  }, [drawioContainerRef]);
 
 
   const renderDrawioWithDebounce = useMemo(() => debounce(200, renderDrawio), [renderDrawio]);
   const renderDrawioWithDebounce = useMemo(() => debounce(200, renderDrawio), [renderDrawio]);
 
 
   useEffect(() => {
   useEffect(() => {
     if (GraphViewer == null) {
     if (GraphViewer == null) {
+      waitForGraphViewer((gv: IGraphViewer) => {
+        setGraphViewer(() => gv);
+      });
       return;
       return;
     }
     }
 
 
-    renderDrawioWithDebounce();
+    renderDrawioWithDebounce(GraphViewer);
   }, [renderDrawioWithDebounce, GraphViewer]);
   }, [renderDrawioWithDebounce, GraphViewer]);
 
 
   return (
   return (

+ 1 - 1
packages/app/src/components/InAppNotification/InAppNotificationElm.tsx

@@ -2,12 +2,12 @@ import React, {
   FC, useRef,
   FC, useRef,
 } from 'react';
 } from 'react';
 
 
+import { HasObjectId } from '@growi/core';
 import { UserPicture } from '@growi/ui';
 import { UserPicture } from '@growi/ui';
 import { DropdownItem } from 'reactstrap';
 import { DropdownItem } from 'reactstrap';
 
 
 import { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';
 import { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { apiv3Post } from '~/client/util/apiv3-client';
-import { HasObjectId } from '~/interfaces/has-object-id';
 import { IInAppNotification, InAppNotificationStatuses } from '~/interfaces/in-app-notification';
 import { IInAppNotification, InAppNotificationStatuses } from '~/interfaces/in-app-notification';
 
 
 // Change the display for each targetmodel
 // Change the display for each targetmodel

+ 2 - 1
packages/app/src/components/InAppNotification/InAppNotificationList.tsx

@@ -1,7 +1,8 @@
 import React, { FC } from 'react';
 import React, { FC } from 'react';
 
 
+import { HasObjectId } from '@growi/core';
+
 import { IInAppNotification, PaginateResult } from '~/interfaces/in-app-notification';
 import { IInAppNotification, PaginateResult } from '~/interfaces/in-app-notification';
-import { HasObjectId } from '~/interfaces/has-object-id';
 
 
 import InAppNotificationElm from './InAppNotificationElm';
 import InAppNotificationElm from './InAppNotificationElm';
 
 

+ 1 - 1
packages/app/src/components/InAppNotification/PageNotification/PageModelNotification.tsx

@@ -2,10 +2,10 @@ import React, {
   forwardRef, ForwardRefRenderFunction, useImperativeHandle,
   forwardRef, ForwardRefRenderFunction, useImperativeHandle,
 } from 'react';
 } from 'react';
 
 
+import { HasObjectId } from '@growi/core';
 import { PagePathLabel } from '@growi/ui';
 import { PagePathLabel } from '@growi/ui';
 
 
 import { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';
 import { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';
-import { HasObjectId } from '~/interfaces/has-object-id';
 import { IInAppNotification } from '~/interfaces/in-app-notification';
 import { IInAppNotification } from '~/interfaces/in-app-notification';
 
 
 import { parseSnapshot } from '../../../models/serializers/in-app-notification-snapshot/page';
 import { parseSnapshot } from '../../../models/serializers/in-app-notification-snapshot/page';

+ 23 - 23
packages/app/src/components/InstallerForm.jsx

@@ -1,10 +1,10 @@
 import React from 'react';
 import React from 'react';
 
 
 import i18next from 'i18next';
 import i18next from 'i18next';
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 
 
-import { localeMetadatas } from '~/client/util/i18n';
+// import { localeMetadatas } from '~/client/util/i18n';
 import { useCsrfToken } from '~/stores/context';
 import { useCsrfToken } from '~/stores/context';
 
 
 class InstallerForm extends React.Component {
 class InstallerForm extends React.Component {
@@ -17,31 +17,31 @@ class InstallerForm extends React.Component {
       isSubmittingDisabled: false,
       isSubmittingDisabled: false,
       selectedLang: {},
       selectedLang: {},
     };
     };
-    // this.checkUserName = this.checkUserName.bind(this);
+    this.checkUserName = this.checkUserName.bind(this);
 
 
     this.submitHandler = this.submitHandler.bind(this);
     this.submitHandler = this.submitHandler.bind(this);
   }
   }
 
 
-  UNSAFE_componentWillMount() {
-    const meta = localeMetadatas.find(v => v.id === i18next.language);
-    if (meta == null) {
-      return this.setState({ selectedLang: localeMetadatas[0] });
-    }
-    this.setState({ selectedLang: meta });
-  }
-
-  // checkUserName(event) {
-  //   const axios = require('axios').create({
-  //     headers: {
-  //       'Content-Type': 'application/json',
-  //       'X-Requested-With': 'XMLHttpRequest',
-  //     },
-  //     responseType: 'json',
-  //   });
-  //   axios.get('/_api/v3/check-username', { params: { username: event.target.value } })
-  //     .then((res) => { return this.setState({ isValidUserName: res.data.valid }) });
+  // UNSAFE_componentWillMount() {
+  //   const meta = localeMetadatas.find(v => v.id === i18next.language);
+  //   if (meta == null) {
+  //     return this.setState({ selectedLang: localeMetadatas[0] });
+  //   }
+  //   this.setState({ selectedLang: meta });
   // }
   // }
 
 
+  checkUserName(event) {
+    const axios = require('axios').create({
+      headers: {
+        'Content-Type': 'application/json',
+        'X-Requested-With': 'XMLHttpRequest',
+      },
+      responseType: 'json',
+    });
+    axios.get('/_api/v3/check-username', { params: { username: event.target.value } })
+      .then((res) => { return this.setState({ isValidUserName: res.data.valid }) });
+  }
+
   changeLanguage(meta) {
   changeLanguage(meta) {
     i18next.changeLanguage(meta.id);
     i18next.changeLanguage(meta.id);
     this.setState({ selectedLang: meta });
     this.setState({ selectedLang: meta });
@@ -97,7 +97,7 @@ class InstallerForm extends React.Component {
                   value={this.state.selectedLang.id}
                   value={this.state.selectedLang.id}
                   name="registerForm[app:globalLang]"
                   name="registerForm[app:globalLang]"
                 />
                 />
-                <div className="dropdown-menu" aria-labelledby="dropdownLanguage">
+                {/* <div className="dropdown-menu" aria-labelledby="dropdownLanguage">
                   {
                   {
                     localeMetadatas.map(meta => (
                     localeMetadatas.map(meta => (
                       <button
                       <button
@@ -111,7 +111,7 @@ class InstallerForm extends React.Component {
                       </button>
                       </button>
                     ))
                     ))
                   }
                   }
-                </div>
+                </div> */}
               </div>
               </div>
             </div>
             </div>
 
 

+ 5 - 1
packages/app/src/components/AdminLayout.tsx → packages/app/src/components/Layout/AdminLayout.tsx

@@ -3,7 +3,8 @@ import React, { ReactNode } from 'react';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 import { Provider } from 'unstated';
 import { Provider } from 'unstated';
 
 
-import { GrowiNavbar } from './Navbar/GrowiNavbar';
+import { GrowiNavbar } from '../Navbar/GrowiNavbar';
+
 import { RawLayout } from './RawLayout';
 import { RawLayout } from './RawLayout';
 
 
 // import { injectableContainers } from '~/client/admin';
 // import { injectableContainers } from '~/client/admin';
@@ -25,6 +26,7 @@ const AdminLayout = ({
 }: Props): JSX.Element => {
 }: Props): JSX.Element => {
 
 
   const AdminNavigation = dynamic(() => import('~/components/Admin/Common/AdminNavigation'), { ssr: false });
   const AdminNavigation = dynamic(() => import('~/components/Admin/Common/AdminNavigation'), { ssr: false });
+  const SystemVersion = dynamic(() => import('../SystemVersion'), { ssr: false });
 
 
   return (
   return (
     <RawLayout title={title}>
     <RawLayout title={title}>
@@ -48,6 +50,8 @@ const AdminLayout = ({
           </div>
           </div>
         </div>
         </div>
       </div>
       </div>
+
+      <SystemVersion />
     </RawLayout>
     </RawLayout>
   );
   );
 };
 };

+ 56 - 0
packages/app/src/components/Layout/BasicLayout.tsx

@@ -0,0 +1,56 @@
+import React, { ReactNode } from 'react';
+
+import dynamic from 'next/dynamic';
+
+import { GrowiNavbar } from '../Navbar/GrowiNavbar';
+import Sidebar from '../Sidebar';
+
+import { RawLayout } from './RawLayout';
+
+
+type Props = {
+  title: string
+  className?: string,
+  children?: ReactNode
+}
+
+export const BasicLayout = ({ children, title, className }: Props): JSX.Element => {
+
+  // const HotkeysManager = dynamic(() => import('../client/js/components/Hotkeys/HotkeysManager'), { ssr: false });
+  // const PageCreateModal = dynamic(() => import('../client/js/components/PageCreateModal'), { ssr: false });
+  const GrowiNavbarBottom = dynamic(() => import('../Navbar/GrowiNavbarBottom').then(mod => mod.GrowiNavbarBottom), { ssr: false });
+  const ShortcutsModal = dynamic(() => import('../ShortcutsModal'), { ssr: false });
+  const SystemVersion = dynamic(() => import('../SystemVersion'), { ssr: false });
+  // Page modals
+  const PageCreateModal = dynamic(() => import('../PageCreateModal'), { ssr: false });
+  const PageDuplicateModal = dynamic(() => import('../PageDuplicateModal'), { ssr: false });
+  const PageDeleteModal = dynamic(() => import('../PageDeleteModal'), { ssr: false });
+  const PageRenameModal = dynamic(() => import('../PageRenameModal'), { ssr: false });
+
+  return (
+    <RawLayout title={title} className={className}>
+      <GrowiNavbar />
+
+      <div className="page-wrapper d-flex d-print-block">
+        <div className="grw-sidebar-wrapper">
+          <Sidebar />
+        </div>
+
+        <div className="flex-fill mw-0">
+          {children}
+        </div>
+      </div>
+
+      <GrowiNavbarBottom />
+
+      <PageCreateModal />
+      <PageDuplicateModal />
+      <PageDeleteModal />
+      <PageRenameModal />
+      {/* <HotkeysManager /> */}
+
+      <ShortcutsModal />
+      <SystemVersion showShortcutsButton />
+    </RawLayout>
+  );
+};

+ 48 - 0
packages/app/src/components/Layout/RawLayout.tsx

@@ -0,0 +1,48 @@
+import React, { ReactNode, useEffect, useState } from 'react';
+
+import Head from 'next/head';
+
+import { useGrowiTheme } from '~/stores/context';
+import { Themes, useNextThemes } from '~/stores/use-next-themes';
+
+import { ThemeProvider } from '../Theme/utils/ThemeProvider';
+
+type Props = {
+  title: string,
+  className?: string,
+  children?: ReactNode,
+}
+
+export const RawLayout = ({ children, title, className }: Props): JSX.Element => {
+
+  const classNames: string[] = ['wrapper'];
+  if (className != null) {
+    classNames.push(className);
+  }
+  const { data: growiTheme } = useGrowiTheme();
+
+  // get color scheme from next-themes
+  const { resolvedTheme } = useNextThemes();
+
+  const [colorScheme, setColorScheme] = useState<Themes|undefined>(undefined);
+
+  // set colorScheme in CSR
+  useEffect(() => {
+    setColorScheme(resolvedTheme as Themes);
+  }, [resolvedTheme]);
+
+  return (
+    <>
+      <Head>
+        <title>{title}</title>
+        <meta charSet="utf-8" />
+        <meta name="viewport" content="initial-scale=1.0, width=device-width" />
+      </Head>
+      <ThemeProvider theme={growiTheme}>
+        <div className={classNames.join(' ')} data-color-scheme={colorScheme}>
+          {children}
+        </div>
+      </ThemeProvider>
+    </>
+  );
+};

+ 12 - 28
packages/app/src/components/Navbar/AppearanceModeDropdown.tsx

@@ -1,5 +1,5 @@
 import React, {
 import React, {
-  FC, useState, useCallback, useRef,
+  FC, useCallback, useRef,
 } from 'react';
 } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
@@ -7,15 +7,8 @@ import { useRipple } from 'react-use-ripple';
 import { UncontrolledTooltip } from 'reactstrap';
 import { UncontrolledTooltip } from 'reactstrap';
 
 
 import { useUserUISettings } from '~/client/services/user-ui-settings';
 import { useUserUISettings } from '~/client/services/user-ui-settings';
-import {
-  isUserPreferenceExists,
-  isDarkMode as isDarkModeByUtil,
-  applyColorScheme,
-  removeUserPreference,
-  updateUserPreference,
-  updateUserPreferenceWithOsSettings,
-} from '~/client/util/color-scheme';
 import { usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser } from '~/stores/ui';
 import { usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser } from '~/stores/ui';
+import { Themes, useNextThemes } from '~/stores/use-next-themes';
 
 
 import MoonIcon from '../Icons/MoonIcon';
 import MoonIcon from '../Icons/MoonIcon';
 import SidebarDockIcon from '../Icons/SidebarDockIcon';
 import SidebarDockIcon from '../Icons/SidebarDockIcon';
@@ -31,9 +24,9 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
 
 
   const { isAuthenticated } = props;
   const { isAuthenticated } = props;
 
 
-  const [useOsSettings, setOsSettings] = useState(!isUserPreferenceExists());
-  const [isDarkMode, setIsDarkMode] = useState(isDarkModeByUtil());
-
+  const {
+    setTheme, resolvedTheme, useOsSettings, isDarkMode,
+  } = useNextThemes();
   const { data: isPreferDrawerMode, update: updatePreferDrawerMode } = usePreferDrawerModeByUser();
   const { data: isPreferDrawerMode, update: updatePreferDrawerMode } = usePreferDrawerModeByUser();
   const { data: isPreferDrawerModeOnEdit, mutate: mutatePreferDrawerModeOnEdit } = usePreferDrawerModeOnEditByUser();
   const { data: isPreferDrawerModeOnEdit, mutate: mutatePreferDrawerModeOnEdit } = usePreferDrawerModeOnEditByUser();
   const { scheduleToPut } = useUserUISettings();
   const { scheduleToPut } = useUserUISettings();
@@ -52,27 +45,18 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
     }
     }
   }, [updatePreferDrawerMode, mutatePreferDrawerModeOnEdit, scheduleToPut]);
   }, [updatePreferDrawerMode, mutatePreferDrawerModeOnEdit, scheduleToPut]);
 
 
-  const followOsCheckboxModifiedHandler = useCallback((useOsSettings: boolean) => {
-    if (useOsSettings) {
-      removeUserPreference();
+  const followOsCheckboxModifiedHandler = useCallback((isChecked: boolean) => {
+    if (isChecked) {
+      setTheme(Themes.system);
     }
     }
     else {
     else {
-      updateUserPreferenceWithOsSettings();
+      setTheme(resolvedTheme ?? Themes.light);
     }
     }
-    applyColorScheme();
-
-    // update states
-    setOsSettings(useOsSettings);
-    setIsDarkMode(isDarkModeByUtil());
-  }, []);
+  }, [resolvedTheme, setTheme]);
 
 
   const userPreferenceSwitchModifiedHandler = useCallback((isDarkMode: boolean) => {
   const userPreferenceSwitchModifiedHandler = useCallback((isDarkMode: boolean) => {
-    updateUserPreference(isDarkMode);
-    applyColorScheme();
-
-    // update state
-    setIsDarkMode(isDarkModeByUtil());
-  }, []);
+    setTheme(isDarkMode ? 'dark' : 'light');
+  }, [setTheme]);
 
 
   /* eslint-disable react/prop-types */
   /* eslint-disable react/prop-types */
   const IconWithTooltip = ({
   const IconWithTooltip = ({

+ 4 - 6
packages/app/src/components/Navbar/GlobalSearch.tsx

@@ -5,8 +5,7 @@ import assert from 'assert';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
 import { IFocusable } from '~/client/interfaces/focusable';
 import { IFocusable } from '~/client/interfaces/focusable';
-import { IPageWithMeta } from '~/interfaces/page';
-import { IPageSearchMeta } from '~/interfaces/search';
+import { IPageWithSearchMeta } from '~/interfaces/search';
 import {
 import {
   useCurrentPagePath, useIsSearchScopeChildrenAsDefault, useIsSearchServiceReachable,
   useCurrentPagePath, useIsSearchScopeChildrenAsDefault, useIsSearchServiceReachable,
 } from '~/stores/context';
 } from '~/stores/context';
@@ -14,15 +13,14 @@ import { useGlobalSearchFormRef } from '~/stores/ui';
 
 
 import SearchForm from '../SearchForm';
 import SearchForm from '../SearchForm';
 
 
-
 import styles from './GlobalSearch.module.scss';
 import styles from './GlobalSearch.module.scss';
 
 
 
 
-type Props = {
+export type GlobalSearchProps = {
   dropup?: boolean,
   dropup?: boolean,
 }
 }
 
 
-export const GlobalSearch = (props: Props): JSX.Element => {
+export const GlobalSearch = (props: GlobalSearchProps): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
   const { dropup } = props;
   const { dropup } = props;
@@ -40,7 +38,7 @@ export const GlobalSearch = (props: Props): JSX.Element => {
   const [isFocused, setFocused] = useState<boolean>(false);
   const [isFocused, setFocused] = useState<boolean>(false);
 
 
 
 
-  const gotoPage = useCallback((data: IPageWithMeta<IPageSearchMeta>[]) => {
+  const gotoPage = useCallback((data: IPageWithSearchMeta[]) => {
     assert(data.length > 0);
     assert(data.length > 0);
 
 
     const page = data[0].data; // should be single page selected
     const page = data[0].data; // should be single page selected

+ 1 - 2
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -1,13 +1,12 @@
 import React, { useState, useEffect, useCallback } from 'react';
 import React, { useState, useEffect, useCallback } from 'react';
 
 
+import { isPopulated } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
 import { DropdownItem } from 'reactstrap';
 import { DropdownItem } from 'reactstrap';
 
 
 import { exportAsMarkdown } from '~/client/services/page-operation';
 import { exportAsMarkdown } from '~/client/services/page-operation';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiPost } from '~/client/util/apiv1-client';
 import { apiPost } from '~/client/util/apiv1-client';
-import { isPopulated } from '~/interfaces/common';
 import {
 import {
   IPageToRenameWithMeta, IPageWithMeta, IPageInfoForEntity,
   IPageToRenameWithMeta, IPageWithMeta, IPageInfoForEntity,
 } from '~/interfaces/page';
 } from '~/interfaces/page';

+ 7 - 13
packages/app/src/components/Navbar/GrowiNavbar.tsx

@@ -9,27 +9,21 @@ import Link from 'next/link';
 import { useRipple } from 'react-use-ripple';
 import { useRipple } from 'react-use-ripple';
 import { UncontrolledTooltip } from 'reactstrap';
 import { UncontrolledTooltip } from 'reactstrap';
 
 
-import { HasChildren } from '~/interfaces/common';
 import {
 import {
   useIsSearchPage, useCurrentPagePath, useIsGuestUser, useIsSearchServiceConfigured, useAppTitle, useConfidential,
   useIsSearchPage, useCurrentPagePath, useIsGuestUser, useIsSearchServiceConfigured, useAppTitle, useConfidential,
 } from '~/stores/context';
 } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
 import { usePageCreateModal } from '~/stores/modal';
 import { useIsDeviceSmallerThanMd } from '~/stores/ui';
 import { useIsDeviceSmallerThanMd } from '~/stores/ui';
 
 
+import { HasChildren } from '../../interfaces/common';
 import GrowiLogo from '../Icons/GrowiLogo';
 import GrowiLogo from '../Icons/GrowiLogo';
 
 
+import { GlobalSearchProps } from './GlobalSearch';
 import PersonalDropdown from './PersonalDropdown';
 import PersonalDropdown from './PersonalDropdown';
 
 
 import styles from './GrowiNavbar.module.scss';
 import styles from './GrowiNavbar.module.scss';
 
 
 
 
-const ShowSkeltonInSSR = memo(({ children }: HasChildren): JSX.Element => {
-  return isServer()
-    ? <></>
-    : <>{children}</>;
-});
-ShowSkeltonInSSR.displayName = 'ShowSkeltonInSSR';
-
 const NavbarRight = memo((): JSX.Element => {
 const NavbarRight = memo((): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
@@ -52,7 +46,7 @@ const NavbarRight = memo((): JSX.Element => {
     return (
     return (
       <>
       <>
         <li className="nav-item">
         <li className="nav-item">
-          <ShowSkeltonInSSR><InAppNotificationDropdown /></ShowSkeltonInSSR>
+          <InAppNotificationDropdown />
         </li>
         </li>
 
 
         <li className="nav-item d-none d-md-block">
         <li className="nav-item d-none d-md-block">
@@ -69,11 +63,11 @@ const NavbarRight = memo((): JSX.Element => {
         </li>
         </li>
 
 
         <li className="grw-apperance-mode-dropdown nav-item dropdown">
         <li className="grw-apperance-mode-dropdown nav-item dropdown">
-          <ShowSkeltonInSSR><AppearanceModeDropdown isAuthenticated={isAuthenticated} /></ShowSkeltonInSSR>
+          <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
         </li>
         </li>
 
 
         <li className="grw-personal-dropdown nav-item dropdown dropdown-toggle dropdown-toggle-no-caret" data-testid="grw-personal-dropdown">
         <li className="grw-personal-dropdown nav-item dropdown dropdown-toggle dropdown-toggle-no-caret" data-testid="grw-personal-dropdown">
-          <ShowSkeltonInSSR><PersonalDropdown /></ShowSkeltonInSSR>
+          <PersonalDropdown />
         </li>
         </li>
       </>
       </>
     );
     );
@@ -83,7 +77,7 @@ const NavbarRight = memo((): JSX.Element => {
     return (
     return (
       <>
       <>
         <li className="grw-apperance-mode-dropdown nav-item dropdown">
         <li className="grw-apperance-mode-dropdown nav-item dropdown">
-          <ShowSkeltonInSSR><AppearanceModeDropdown isAuthenticated={isAuthenticated} /></ShowSkeltonInSSR>
+          <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
         </li>
         </li>
 
 
         <li id="login-user" className="nav-item"><a className="nav-link" href="/login">Login</a></li>;
         <li id="login-user" className="nav-item"><a className="nav-link" href="/login">Login</a></li>;
@@ -130,7 +124,7 @@ Confidential.displayName = 'Confidential';
 
 
 export const GrowiNavbar = (): JSX.Element => {
 export const GrowiNavbar = (): JSX.Element => {
 
 
-  const GlobalSearch = dynamic(() => import('./GlobalSearch').then(mod => mod.GlobalSearch), { ssr: false });
+  const GlobalSearch = dynamic<GlobalSearchProps>(() => import('./GlobalSearch').then(mod => mod.GlobalSearch), { ssr: false });
 
 
   const { data: appTitle } = useAppTitle();
   const { data: appTitle } = useAppTitle();
   const { data: confidential } = useConfidential();
   const { data: confidential } = useConfidential();

+ 1 - 2
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -1,4 +1,4 @@
-import React, { useMemo } from 'react';
+import React from 'react';
 
 
 import { pagePathUtils } from '@growi/core';
 import { pagePathUtils } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
@@ -6,7 +6,6 @@ import dynamic from 'next/dynamic';
 import { TabContent, TabPane } from 'reactstrap';
 import { TabContent, TabPane } from 'reactstrap';
 
 
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
-import { isPopulated } from '~/interfaces/common';
 import {
 import {
   useCurrentPagePath, useIsSharedUser, useIsEditable, useIsUserPage, usePageUser, useShareLinkId, useIsNotFound, useIsNotCreatable,
   useCurrentPagePath, useIsSharedUser, useIsEditable, useIsUserPage, usePageUser, useShareLinkId, useIsNotFound, useIsNotCreatable,
 } from '~/stores/context';
 } from '~/stores/context';

+ 1 - 1
packages/app/src/components/Page/RevisionRenderer.tsx

@@ -47,7 +47,7 @@ const logger = loggerFactory('components:Page:RevisionRenderer');
 
 
 //   // for non-chrome browsers compatibility
 //   // for non-chrome browsers compatibility
 //   try {
 //   try {
-//     // eslint-disable-next-line regex/invalid
+// eslint-disable-next-line regex/invalid, max-len
 //     keywordRegexp2 = new RegExp(`(?<!<)${normalizedKeywords}(?!(.*?("|>)))`, 'ig'); // inferior (this doesn't work well when html tags exist a lot) https://regex101.com/r/Dfi61F/1
 //     keywordRegexp2 = new RegExp(`(?<!<)${normalizedKeywords}(?!(.*?("|>)))`, 'ig'); // inferior (this doesn't work well when html tags exist a lot) https://regex101.com/r/Dfi61F/1
 //   }
 //   }
 //   catch (err) {
 //   catch (err) {

+ 5 - 3
packages/app/src/components/PageAlert/PageStaleAlert.tsx

@@ -1,4 +1,6 @@
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
+
+import { isIPageInfoForEntity } from '~/interfaces/page';
 
 
 import { useIsEnabledStaleNotification } from '../../stores/context';
 import { useIsEnabledStaleNotification } from '../../stores/context';
 import { useSWRxCurrentPage, useSWRxPageInfo } from '../../stores/page';
 import { useSWRxCurrentPage, useSWRxPageInfo } from '../../stores/page';
@@ -11,7 +13,7 @@ export const PageStaleAlert = ():JSX.Element => {
   const { data: pageData } = useSWRxCurrentPage();
   const { data: pageData } = useSWRxCurrentPage();
   const { data: pageInfo } = useSWRxPageInfo(isEnabledStaleNotification ? pageData?._id : null);
   const { data: pageInfo } = useSWRxPageInfo(isEnabledStaleNotification ? pageData?._id : null);
 
 
-  const contentAge = pageInfo?.contentAge;
+  const contentAge = isIPageInfoForEntity(pageInfo) ? pageInfo.contentAge : null;
 
 
   if (!isEnabledStaleNotification) {
   if (!isEnabledStaleNotification) {
     return <></>;
     return <></>;
@@ -36,7 +38,7 @@ export const PageStaleAlert = ():JSX.Element => {
   return (
   return (
     <div className={`alert ${alertClass}`}>
     <div className={`alert ${alertClass}`}>
       <i className="icon-fw icon-hourglass"></i>
       <i className="icon-fw icon-hourglass"></i>
-      <strong>{ t('page_page.notice.stale', { count: pageInfo.contentAge }) }</strong>
+      <strong>{ t('page_page.notice.stale', { count: contentAge }) }</strong>
     </div>
     </div>
   );
   );
 };
 };

+ 1 - 1
packages/app/src/components/PageAlert/TrashPageAlert.tsx

@@ -36,7 +36,7 @@ export const TrashPageAlert = (): JSX.Element => {
 
 
   const lastUpdateUserName = pageData?.lastUpdateUser.name;
   const lastUpdateUserName = pageData?.lastUpdateUser.name;
   const deletedAt = pageData?.deletedAt ? format(new Date(pageData?.deletedAt), 'yyyy/MM/dd HH:mm') : '';
   const deletedAt = pageData?.deletedAt ? format(new Date(pageData?.deletedAt), 'yyyy/MM/dd HH:mm') : '';
-  const revisionId = pageData?.revision._id;
+  const revisionId = pageData?.revision?._id;
 
 
   if (!isTrashPage) {
   if (!isTrashPage) {
     return <></>;
     return <></>;

+ 24 - 22
packages/app/src/components/PageComment/CommentEditor.tsx

@@ -100,28 +100,30 @@ const CommentEditor = (props: PropsType): JSX.Element => {
       parsedHTML: '',
       parsedHTML: '',
     };
     };
 
 
-    const interceptorManager: IInterceptorManager = (window as CustomWindow).interceptorManager;
-    interceptorManager.process('preRenderCommnetPreview', context)
-      .then(() => { return interceptorManager.process('prePreProcess', context) })
-      .then(() => {
-        context.markdown = rendererOptions.preProcess(context.markdown, context);
-      })
-      .then(() => { return interceptorManager.process('postPreProcess', context) })
-      .then(() => {
-        const parsedHTML = rendererOptions.process(context.markdown, context);
-        context.parsedHTML = parsedHTML;
-      })
-      .then(() => { return interceptorManager.process('prePostProcess', context) })
-      .then(() => {
-        context.parsedHTML = rendererOptions.postProcess(context.parsedHTML, context);
-      })
-      .then(() => { return interceptorManager.process('postPostProcess', context) })
-      .then(() => { return interceptorManager.process('preRenderCommentPreviewHtml', context) })
-      .then(() => {
-        setHtml(context.parsedHTML);
-      })
-      // process interceptors for post rendering
-      .then(() => { return interceptorManager.process('postRenderCommentPreviewHtml', context) });
+    // TODO: use ReactMarkdown
+
+    // const interceptorManager: IInterceptorManager = (window as CustomWindow).interceptorManager;
+    // interceptorManager.process('preRenderCommnetPreview', context)
+    //   .then(() => { return interceptorManager.process('prePreProcess', context) })
+    //   .then(() => {
+    //     context.markdown = rendererOptions.preProcess(context.markdown, context);
+    //   })
+    //   .then(() => { return interceptorManager.process('postPreProcess', context) })
+    //   .then(() => {
+    //     const parsedHTML = rendererOptions.process(context.markdown, context);
+    //     context.parsedHTML = parsedHTML;
+    //   })
+    //   .then(() => { return interceptorManager.process('prePostProcess', context) })
+    //   .then(() => {
+    //     context.parsedHTML = rendererOptions.postProcess(context.parsedHTML, context);
+    //   })
+    //   .then(() => { return interceptorManager.process('postPostProcess', context) })
+    //   .then(() => { return interceptorManager.process('preRenderCommentPreviewHtml', context) })
+    //   .then(() => {
+    //     setHtml(context.parsedHTML);
+    //   })
+    //   // process interceptors for post rendering
+    //   .then(() => { return interceptorManager.process('postRenderCommentPreviewHtml', context) });
   }, [rendererOptions]);
   }, [rendererOptions]);
 
 
   const handleSelect = useCallback((activeTab: string) => {
   const handleSelect = useCallback((activeTab: string) => {

+ 2 - 1
packages/app/src/components/PageContentFooter.tsx

@@ -1,6 +1,7 @@
 import React, { FC, memo } from 'react';
 import React, { FC, memo } from 'react';
 
 
-import { Ref } from '../interfaces/common';
+import { Ref } from '@growi/core';
+
 import { IUser } from '../interfaces/user';
 import { IUser } from '../interfaces/user';
 
 
 import AuthorInfo from './Navbar/AuthorInfo';
 import AuthorInfo from './Navbar/AuthorInfo';

+ 4 - 18
packages/app/src/components/PageCreateModal.jsx

@@ -1,23 +1,19 @@
 import React, {
 import React, {
-  useEffect, useState, useMemo, useCallback,
+  useEffect, useState, useMemo,
 } from 'react';
 } from 'react';
 
 
 import { pagePathUtils, pathUtils } from '@growi/core';
 import { pagePathUtils, pathUtils } from '@growi/core';
 import { format } from 'date-fns';
 import { format } from 'date-fns';
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { Modal, ModalHeader, ModalBody } from 'reactstrap';
 import { Modal, ModalHeader, ModalBody } from 'reactstrap';
 import { debounce } from 'throttle-debounce';
 import { debounce } from 'throttle-debounce';
 
 
 
 
-import AppContainer from '~/client/services/AppContainer';
 import { toastError } from '~/client/util/apiNotification';
 import { toastError } from '~/client/util/apiNotification';
-import { useCurrentUser } from '~/stores/context';
+import { useCurrentUser, useIsSearchServiceReachable } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
 import { usePageCreateModal } from '~/stores/modal';
 
 
 import PagePathAutoComplete from './PagePathAutoComplete';
 import PagePathAutoComplete from './PagePathAutoComplete';
-import { withUnstatedContainers } from './UnstatedUtils';
-
 
 
 const {
 const {
   userPageRoot, isCreatablePage, generateEditorPath, isUsersHomePage,
   userPageRoot, isCreatablePage, generateEditorPath, isUsersHomePage,
@@ -25,15 +21,13 @@ const {
 
 
 const PageCreateModal = (props) => {
 const PageCreateModal = (props) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
-  const { appContainer } = props;
 
 
   const { data: currentUser } = useCurrentUser();
   const { data: currentUser } = useCurrentUser();
 
 
   const { data: pageCreateModalData, close: closeCreateModal } = usePageCreateModal();
   const { data: pageCreateModalData, close: closeCreateModal } = usePageCreateModal();
   const { isOpened, path } = pageCreateModalData;
   const { isOpened, path } = pageCreateModalData;
 
 
-  const config = appContainer.getConfig();
-  const isReachable = config.isSearchServiceReachable;
+  const { data: isReachable } = useIsSearchServiceReachable();
   const pathname = path || '';
   const pathname = path || '';
   const userPageRootPath = userPageRoot(currentUser);
   const userPageRootPath = userPageRoot(currentUser);
   const isCreatable = isCreatablePage(pathname) || isUsersHomePage(pathname);
   const isCreatable = isCreatablePage(pathname) || isUsersHomePage(pathname);
@@ -311,13 +305,5 @@ const PageCreateModal = (props) => {
   );
   );
 };
 };
 
 
-PageCreateModal.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-};
-
-/**
- * Wrapper component for using unstated
- */
-const PageCreateModalWrapper = withUnstatedContainers(PageCreateModal, [AppContainer]);
 
 
-export default PageCreateModalWrapper;
+export default PageCreateModal;

+ 1 - 2
packages/app/src/components/PageDeleteModal.tsx

@@ -2,7 +2,7 @@ import React, {
   useState, FC, useMemo, useEffect,
   useState, FC, useMemo, useEffect,
 } from 'react';
 } from 'react';
 
 
-import { pagePathUtils } from '@growi/core';
+import { HasObjectId, pagePathUtils } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import {
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
   Modal, ModalHeader, ModalBody, ModalFooter,
@@ -10,7 +10,6 @@ import {
 
 
 import { apiPost } from '~/client/util/apiv1-client';
 import { apiPost } from '~/client/util/apiv1-client';
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { apiv3Post } from '~/client/util/apiv3-client';
-import { HasObjectId } from '~/interfaces/has-object-id';
 import {
 import {
   IDeleteSinglePageApiv1Result, IDeleteManyPageApiv3Result, IPageToDeleteWithMeta, IDataWithMeta, isIPageInfoForEntity, IPageInfoForEntity,
   IDeleteSinglePageApiv1Result, IDeleteManyPageApiv3Result, IPageToDeleteWithMeta, IDataWithMeta, isIPageInfoForEntity, IPageInfoForEntity,
 } from '~/interfaces/page';
 } from '~/interfaces/page';

+ 4 - 3
packages/app/src/components/PageEditor/EmojiPicker.tsx

@@ -3,7 +3,7 @@ import React, { FC } from 'react';
 import { Picker } from 'emoji-mart';
 import { Picker } from 'emoji-mart';
 import { Modal } from 'reactstrap';
 import { Modal } from 'reactstrap';
 
 
-import { isDarkMode } from '~/client/util/color-scheme';
+import { useNextThemes } from '~/stores/use-next-themes';
 
 
 import EmojiPickerHelper, { getEmojiTranslation } from './EmojiPickerHelper';
 import EmojiPickerHelper, { getEmojiTranslation } from './EmojiPickerHelper';
 
 
@@ -20,6 +20,8 @@ const EmojiPicker: FC<Props> = (props: Props) => {
     onClose, emojiSearchText, emojiPickerHelper, isOpen,
     onClose, emojiSearchText, emojiPickerHelper, isOpen,
   } = props;
   } = props;
 
 
+  const { resolvedTheme } = useNextThemes();
+
   // Set search emoji input and trigger search
   // Set search emoji input and trigger search
   const searchEmoji = () => {
   const searchEmoji = () => {
     const input = window.document.querySelector('[id^="emoji-mart-search"]') as HTMLInputElement;
     const input = window.document.querySelector('[id^="emoji-mart-search"]') as HTMLInputElement;
@@ -42,7 +44,6 @@ const EmojiPicker: FC<Props> = (props: Props) => {
 
 
 
 
   const translation = getEmojiTranslation();
   const translation = getEmojiTranslation();
-  const theme = isDarkMode() ? 'dark' : 'light';
 
 
   return (
   return (
     <Modal isOpen={isOpen} toggle={onClose} onOpened={searchEmoji} backdropClassName="emoji-picker-modal" fade={false}>
     <Modal isOpen={isOpen} toggle={onClose} onOpened={searchEmoji} backdropClassName="emoji-picker-modal" fade={false}>
@@ -52,7 +53,7 @@ const EmojiPicker: FC<Props> = (props: Props) => {
         title={translation.title}
         title={translation.title}
         emojiTooltip
         emojiTooltip
         style={emojiPickerHelper.setStyle()}
         style={emojiPickerHelper.setStyle()}
-        theme={theme}
+        theme={resolvedTheme}
       />
       />
     </Modal>
     </Modal>
   );
   );

+ 15 - 12
packages/app/src/components/PageEditor/Preview.tsx

@@ -47,19 +47,22 @@ const Preview = React.forwardRef((props: UnstatedProps, ref: RefObject<HTMLDivEl
   }, [markdown, pagePath, editorSettings?.renderDrawioInRealtime]);
   }, [markdown, pagePath, editorSettings?.renderDrawioInRealtime]);
 
 
   const renderPreview = useCallback(async() => {
   const renderPreview = useCallback(async() => {
-    if (interceptorManager != null) {
-      await interceptorManager.process('preRenderPreview', context);
-      await interceptorManager.process('prePreProcess', context);
-      context.markdown = rendererOptions.preProcess(context.markdown, context);
-      await interceptorManager.process('postPreProcess', context);
-      context.parsedHTML = rendererOptions.process(context.markdown, context);
-      await interceptorManager.process('prePostProcess', context);
-      context.parsedHTML = rendererOptions.postProcess(context.parsedHTML, context);
-      await interceptorManager.process('postPostProcess', context);
-      await interceptorManager.process('preRenderPreviewHtml', context);
-    }
 
 
-    setHtml(context.parsedHTML ?? '');
+    // TODO: use ReactMarkdown
+
+    // if (interceptorManager != null) {
+    //   await interceptorManager.process('preRenderPreview', context);
+    //   await interceptorManager.process('prePreProcess', context);
+    //   context.markdown = rendererOptions.preProcess(context.markdown, context);
+    //   await interceptorManager.process('postPreProcess', context);
+    //   context.parsedHTML = rendererOptions.process(context.markdown, context);
+    //   await interceptorManager.process('prePostProcess', context);
+    //   context.parsedHTML = rendererOptions.postProcess(context.parsedHTML, context);
+    //   await interceptorManager.process('postPostProcess', context);
+    //   await interceptorManager.process('preRenderPreviewHtml', context);
+    // }
+
+    // setHtml(context.parsedHTML ?? '');
   }, [context, rendererOptions]);
   }, [context, rendererOptions]);
 
 
   useEffect(() => {
   useEffect(() => {

+ 4 - 4
packages/app/src/components/PageList/PageList.tsx

@@ -2,7 +2,7 @@ import React from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
-import { IPageWithMeta } from '~/interfaces/page';
+import { IPageInfoForEntity, IPageWithMeta } from '~/interfaces/page';
 import { OnDeletedFunction, OnPutBackedFunction } from '~/interfaces/ui';
 import { OnDeletedFunction, OnPutBackedFunction } from '~/interfaces/ui';
 
 
 import { ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
 import { ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
@@ -10,15 +10,15 @@ import { ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
 import { PageListItemL } from './PageListItemL';
 import { PageListItemL } from './PageListItemL';
 
 
 
 
-type Props = {
-  pages: IPageWithMeta[],
+type Props<M extends IPageInfoForEntity> = {
+  pages: IPageWithMeta<M>[],
   isEnableActions?: boolean,
   isEnableActions?: boolean,
   forceHideMenuItems?: ForceHideMenuItems,
   forceHideMenuItems?: ForceHideMenuItems,
   onPagesDeleted?: OnDeletedFunction,
   onPagesDeleted?: OnDeletedFunction,
   onPagePutBacked?: OnPutBackedFunction,
   onPagePutBacked?: OnPutBackedFunction,
 }
 }
 
 
-const PageList = (props: Props): JSX.Element => {
+const PageList = (props: Props<IPageInfoForEntity>): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
   const {
   const {
     pages, isEnableActions, forceHideMenuItems, onPagesDeleted, onPagePutBacked,
     pages, isEnableActions, forceHideMenuItems, onPagesDeleted, onPagePutBacked,

+ 3 - 3
packages/app/src/components/PageList/PageListItemL.tsx

@@ -16,9 +16,9 @@ import urljoin from 'url-join';
 import { ISelectable } from '~/client/interfaces/selectable-all';
 import { ISelectable } from '~/client/interfaces/selectable-all';
 import { bookmark, unbookmark } from '~/client/services/page-operation';
 import { bookmark, unbookmark } from '~/client/services/page-operation';
 import {
 import {
-  IPageInfoAll, IPageInfoForEntity, IPageInfoForListing, IPageWithMeta, isIPageInfoForListing, isIPageInfoForEntity,
+  IPageInfoAll, isIPageInfoForListing, isIPageInfoForEntity, IPageWithMeta, IPageInfoForListing,
 } from '~/interfaces/page';
 } from '~/interfaces/page';
-import { IPageSearchMeta, isIPageSearchMeta } from '~/interfaces/search';
+import { IPageSearchMeta, IPageWithSearchMeta, isIPageSearchMeta } from '~/interfaces/search';
 import {
 import {
   OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction, OnPutBackedFunction,
   OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction, OnPutBackedFunction,
 } from '~/interfaces/ui';
 } from '~/interfaces/ui';
@@ -33,7 +33,7 @@ import { ForceHideMenuItems, PageItemControl } from '../Common/Dropdown/PageItem
 import PagePathHierarchicalLink from '../PagePathHierarchicalLink';
 import PagePathHierarchicalLink from '../PagePathHierarchicalLink';
 
 
 type Props = {
 type Props = {
-  page: IPageWithMeta<IPageInfoForEntity> | IPageWithMeta<IPageSearchMeta> | IPageWithMeta<IPageInfoForListing & IPageSearchMeta>,
+  page: IPageWithSearchMeta | IPageWithMeta<IPageInfoForListing & IPageSearchMeta>,
   isSelected?: boolean, // is item selected(focused)
   isSelected?: boolean, // is item selected(focused)
   isEnableActions?: boolean,
   isEnableActions?: boolean,
   forceHideMenuItems?: ForceHideMenuItems,
   forceHideMenuItems?: ForceHideMenuItems,

+ 0 - 30
packages/app/src/components/RawLayout.tsx

@@ -1,30 +0,0 @@
-import React, { ReactNode } from 'react';
-
-import Head from 'next/head';
-
-type Props = {
-  title: string,
-  className?: string,
-  children?: ReactNode,
-}
-
-export const RawLayout = ({ children, title, className }: Props): JSX.Element => {
-
-  const classNames: string[] = ['wrapper'];
-  if (className != null) {
-    classNames.push(className);
-  }
-
-  return (
-    <>
-      <Head>
-        <title>{title}</title>
-        <meta charSet="utf-8" />
-        <meta name="viewport" content="initial-scale=1.0, width=device-width" />
-      </Head>
-      <div className={classNames.join(' ')}>
-        {children}
-      </div>
-    </>
-  );
-};

+ 18 - 0
packages/app/src/components/ReactMarkdownComponents/Header.module.scss

@@ -0,0 +1,18 @@
+.revision-head :global {
+  a {
+    text-decoration: none;
+  }
+
+  .revision-head-link,
+  .revision-head-edit-button {
+    margin-left: 0.5em;
+    font-size: 0.6em;
+    opacity: 0;
+  }
+}
+
+.revision-head:hover :global {
+  .revision-head-link, .revision-head-edit-button {
+    opacity: 1 !important;
+  }
+}

+ 4 - 1
packages/app/src/components/ReactMarkdownComponents/Header.tsx

@@ -3,6 +3,9 @@ import { Element } from 'react-markdown/lib/rehype-filter';
 import { NextLink } from './NextLink';
 import { NextLink } from './NextLink';
 
 
 
 
+import styles from './Header.module.scss';
+
+
 type EditLinkProps = {
 type EditLinkProps = {
   line?: number,
   line?: number,
 }
 }
@@ -38,7 +41,7 @@ export const Header = (props: HeaderProps): JSX.Element => {
   const CustomTag = `h${level}` as keyof JSX.IntrinsicElements;
   const CustomTag = `h${level}` as keyof JSX.IntrinsicElements;
 
 
   return (
   return (
-    <CustomTag id={id} className="revision-head">
+    <CustomTag id={id} className={`revision-head ${styles['revision-head']} ${styles.hoge}`}>
       {children}
       {children}
       <NextLink href={`#${id}`} className="revision-head-link">
       <NextLink href={`#${id}`} className="revision-head-link">
         <span className="icon-link"></span>
         <span className="icon-link"></span>

+ 1 - 2
packages/app/src/components/SavePageControls/GrantSelector.tsx

@@ -1,5 +1,6 @@
 import React, { useCallback, useState } from 'react';
 import React, { useCallback, useState } from 'react';
 
 
+import { isPopulated } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import {
 import {
   UncontrolledDropdown,
   UncontrolledDropdown,
@@ -8,8 +9,6 @@ import {
   Modal, ModalHeader, ModalBody,
   Modal, ModalHeader, ModalBody,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
-
-import { isPopulated } from '~/interfaces/common';
 import { IUserGroupHasId } from '~/interfaces/user';
 import { IUserGroupHasId } from '~/interfaces/user';
 import { useCurrentUser } from '~/stores/context';
 import { useCurrentUser } from '~/stores/context';
 import { useSWRxMyUserGroupRelations } from '~/stores/user-group';
 import { useSWRxMyUserGroupRelations } from '~/stores/user-group';

+ 2 - 3
packages/app/src/components/SearchForm.tsx

@@ -7,8 +7,7 @@ import { useTranslation } from 'next-i18next';
 
 
 import { IFocusable } from '~/client/interfaces/focusable';
 import { IFocusable } from '~/client/interfaces/focusable';
 import { TypeaheadProps } from '~/client/interfaces/react-bootstrap-typeahead';
 import { TypeaheadProps } from '~/client/interfaces/react-bootstrap-typeahead';
-import { IPageWithMeta } from '~/interfaces/page';
-import { IPageSearchMeta } from '~/interfaces/search';
+import { IPageWithSearchMeta } from '~/interfaces/search';
 
 
 import SearchTypeahead from './SearchTypeahead';
 import SearchTypeahead from './SearchTypeahead';
 
 
@@ -84,7 +83,7 @@ type Props = TypeaheadProps & {
 
 
   keywordOnInit?: string,
   keywordOnInit?: string,
   disableIncrementalSearch?: boolean,
   disableIncrementalSearch?: boolean,
-  onChange?: (data: IPageWithMeta<IPageSearchMeta>[]) => void,
+  onChange?: (data: IPageWithSearchMeta[]) => void,
   onSubmit?: (input: string) => void,
   onSubmit?: (input: string) => void,
 };
 };
 
 

+ 2 - 2
packages/app/src/components/SearchPage/SearchResultContent.tsx

@@ -9,7 +9,7 @@ import { exportAsMarkdown } from '~/client/services/page-operation';
 import { toastSuccess } from '~/client/util/apiNotification';
 import { toastSuccess } from '~/client/util/apiNotification';
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import { IPageToDeleteWithMeta, IPageToRenameWithMeta, IPageWithMeta } from '~/interfaces/page';
 import { IPageToDeleteWithMeta, IPageToRenameWithMeta, IPageWithMeta } from '~/interfaces/page';
-import { IPageSearchMeta } from '~/interfaces/search';
+import { IPageWithSearchMeta } from '~/interfaces/search';
 import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import {
 import {
   usePageDuplicateModal, usePageRenameModal, usePageDeleteModal,
   usePageDuplicateModal, usePageRenameModal, usePageDeleteModal,
@@ -55,7 +55,7 @@ const MUTATION_OBSERVER_CONFIG = { childList: true, subtree: true };
 
 
 type Props ={
 type Props ={
   appContainer: AppContainer,
   appContainer: AppContainer,
-  pageWithMeta : IPageWithMeta<IPageSearchMeta>,
+  pageWithMeta : IPageWithSearchMeta,
   highlightKeywords?: string[],
   highlightKeywords?: string[],
   showPageControlDropdown?: boolean,
   showPageControlDropdown?: boolean,
   forceHideMenuItems?: ForceHideMenuItems,
   forceHideMenuItems?: ForceHideMenuItems,

+ 4 - 4
packages/app/src/components/SearchPage/SearchResultList.tsx

@@ -10,7 +10,7 @@ import { toastSuccess } from '~/client/util/apiNotification';
 import {
 import {
   IPageInfoForListing, IPageWithMeta, isIPageInfoForListing,
   IPageInfoForListing, IPageWithMeta, isIPageInfoForListing,
 } from '~/interfaces/page';
 } from '~/interfaces/page';
-import { IPageSearchMeta } from '~/interfaces/search';
+import { IPageSearchMeta, IPageWithSearchMeta } from '~/interfaces/search';
 import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import { useIsGuestUser } from '~/stores/context';
 import { useIsGuestUser } from '~/stores/context';
 import { useSWRxPageInfoForList, usePageTreeTermManager } from '~/stores/page-listing';
 import { useSWRxPageInfoForList, usePageTreeTermManager } from '~/stores/page-listing';
@@ -21,10 +21,10 @@ import { PageListItemL } from '../PageList/PageListItemL';
 
 
 
 
 type Props = {
 type Props = {
-  pages: IPageWithMeta<IPageSearchMeta>[],
+  pages: IPageWithSearchMeta[],
   selectedPageId?: string,
   selectedPageId?: string,
   forceHideMenuItems?: ForceHideMenuItems,
   forceHideMenuItems?: ForceHideMenuItems,
-  onPageSelected?: (page?: IPageWithMeta<IPageSearchMeta>) => void,
+  onPageSelected?: (page?: IPageWithSearchMeta) => void,
   onCheckboxChanged?: (isChecked: boolean, pageId: string) => void,
   onCheckboxChanged?: (isChecked: boolean, pageId: string) => void,
 }
 }
 
 
@@ -73,7 +73,7 @@ const SearchResultListSubstance: ForwardRefRenderFunction<ISelectableAll, Props>
     }
     }
   }, [onPageSelected, pages]);
   }, [onPageSelected, pages]);
 
 
-  let injectedPages: (IPageWithMeta<IPageSearchMeta> | IPageWithMeta<IPageInfoForListing & IPageSearchMeta>)[] | undefined;
+  let injectedPages: (IPageWithSearchMeta | IPageWithMeta<IPageInfoForListing & IPageSearchMeta>)[] | undefined;
   // inject data to list
   // inject data to list
   if (idToPageInfo != null) {
   if (idToPageInfo != null) {
     injectedPages = pages.map((page) => {
     injectedPages = pages.map((page) => {

+ 3 - 4
packages/app/src/components/SearchPage2/SearchPageBase.tsx

@@ -7,8 +7,7 @@ import { useTranslation } from 'next-i18next';
 import { ISelectableAll } from '~/client/interfaces/selectable-all';
 import { ISelectableAll } from '~/client/interfaces/selectable-all';
 import AppContainer from '~/client/services/AppContainer';
 import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess } from '~/client/util/apiNotification';
 import { toastSuccess } from '~/client/util/apiNotification';
-import { IPageWithMeta } from '~/interfaces/page';
-import { IFormattedSearchResult, IPageSearchMeta } from '~/interfaces/search';
+import { IFormattedSearchResult, IPageWithSearchMeta } from '~/interfaces/search';
 import { OnDeletedFunction } from '~/interfaces/ui';
 import { OnDeletedFunction } from '~/interfaces/ui';
 import { useIsGuestUser, useIsSearchServiceConfigured, useIsSearchServiceReachable } from '~/stores/context';
 import { useIsGuestUser, useIsSearchServiceConfigured, useIsSearchServiceReachable } from '~/stores/context';
 import { usePageDeleteModal } from '~/stores/modal';
 import { usePageDeleteModal } from '~/stores/modal';
@@ -31,7 +30,7 @@ export interface IReturnSelectedPageIds {
 type Props = {
 type Props = {
   appContainer: AppContainer,
   appContainer: AppContainer,
 
 
-  pages?: IPageWithMeta<IPageSearchMeta>[],
+  pages?: IPageWithSearchMeta[],
   searchingKeyword?: string,
   searchingKeyword?: string,
 
 
   forceHideMenuItems?: ForceHideMenuItems,
   forceHideMenuItems?: ForceHideMenuItems,
@@ -61,7 +60,7 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll & IReturn
 
 
   const [selectedPageIdsByCheckboxes] = useState<Set<string>>(new Set());
   const [selectedPageIdsByCheckboxes] = useState<Set<string>>(new Set());
   // const [allPageIds] = useState<Set<string>>(new Set());
   // const [allPageIds] = useState<Set<string>>(new Set());
-  const [selectedPageWithMeta, setSelectedPageWithMeta] = useState<IPageWithMeta<IPageSearchMeta> | undefined>();
+  const [selectedPageWithMeta, setSelectedPageWithMeta] = useState<IPageWithSearchMeta | undefined>();
 
 
   // publish selectAll()
   // publish selectAll()
   useImperativeHandle(ref, () => ({
   useImperativeHandle(ref, () => ({

+ 5 - 6
packages/app/src/components/SearchTypeahead.tsx

@@ -8,8 +8,7 @@ import { AsyncTypeahead, Menu, MenuItem } from 'react-bootstrap-typeahead';
 
 
 import { IFocusable } from '~/client/interfaces/focusable';
 import { IFocusable } from '~/client/interfaces/focusable';
 import { TypeaheadProps } from '~/client/interfaces/react-bootstrap-typeahead';
 import { TypeaheadProps } from '~/client/interfaces/react-bootstrap-typeahead';
-import { IPageWithMeta } from '~/interfaces/page';
-import { IPageSearchMeta } from '~/interfaces/search';
+import { IPageWithSearchMeta } from '~/interfaces/search';
 import { useSWRxSearch } from '~/stores/search';
 import { useSWRxSearch } from '~/stores/search';
 
 
 
 
@@ -49,7 +48,7 @@ type TypeaheadInstance = {
   clear: () => void,
   clear: () => void,
   focus: () => void,
   focus: () => void,
   toggleMenu: () => void,
   toggleMenu: () => void,
-  state: { selected: IPageWithMeta<IPageSearchMeta>[] }
+  state: { selected: IPageWithSearchMeta[] }
 }
 }
 
 
 const SearchTypeahead: ForwardRefRenderFunction<IFocusable, Props> = (props: Props, ref) => {
 const SearchTypeahead: ForwardRefRenderFunction<IFocusable, Props> = (props: Props, ref) => {
@@ -132,7 +131,7 @@ const SearchTypeahead: ForwardRefRenderFunction<IFocusable, Props> = (props: Pro
   const DELAY_FOR_SUBMISSION = 100;
   const DELAY_FOR_SUBMISSION = 100;
   const timeoutIdRef = useRef<NodeJS.Timeout>();
   const timeoutIdRef = useRef<NodeJS.Timeout>();
 
 
-  const changeHandler = useCallback((selectedItems: IPageWithMeta<IPageSearchMeta>[]) => {
+  const changeHandler = useCallback((selectedItems: IPageWithSearchMeta[]) => {
     // cancel schedule to submit
     // cancel schedule to submit
     if (timeoutIdRef.current != null) {
     if (timeoutIdRef.current != null) {
       clearTimeout(timeoutIdRef.current);
       clearTimeout(timeoutIdRef.current);
@@ -165,11 +164,11 @@ const SearchTypeahead: ForwardRefRenderFunction<IFocusable, Props> = (props: Pro
     }
     }
   }, [onSearchError, searchError]);
   }, [onSearchError, searchError]);
 
 
-  const labelKey = useCallback((option?: IPageWithMeta<IPageSearchMeta>) => {
+  const labelKey = useCallback((option?: IPageWithSearchMeta) => {
     return option?.data.path ?? '';
     return option?.data.path ?? '';
   }, []);
   }, []);
 
 
-  const renderMenu = useCallback((options: IPageWithMeta<IPageSearchMeta>[], menuProps) => {
+  const renderMenu = useCallback((options: IPageWithSearchMeta[], menuProps) => {
     if (!isForcused) {
     if (!isForcused) {
       return <></>;
       return <></>;
     }
     }

+ 10 - 10
packages/app/src/components/Sidebar.module.scss

@@ -240,18 +240,18 @@
   }
   }
 }
 }
 
 
-// '&' could not be set after :global
-// workaround from https://github.com/css-modules/css-modules/issues/295#issuecomment-404873976
-.grw-sidebar :global {
-  .grw-sidebar-drawer {
-    @include drawer();
-  }
-  .grw-sidebar-dock {
-    @include bs.media-breakpoint-down(sm) {
+.grw-sidebar {
+  &:global {
+    &.grw-sidebar-drawer {
       @include drawer();
       @include drawer();
     }
     }
-    @include bs.media-breakpoint-up(md) {
-      @include dock();
+    &.grw-sidebar-dock {
+      @include bs.media-breakpoint-down(sm) {
+        @include drawer();
+      }
+      @include bs.media-breakpoint-up(md) {
+        @include dock();
+      }
     }
     }
   }
   }
 }
 }

+ 50 - 48
packages/app/src/components/Sidebar.tsx

@@ -88,8 +88,7 @@ const SidebarContentsWrapper = () => {
 
 
 const Sidebar = (): JSX.Element => {
 const Sidebar = (): JSX.Element => {
 
 
-  // const { data: isDrawerMode } = useDrawerMode(); Todo Universalize
-  const isDrawerMode = false; // dummy
+  const { data: isDrawerMode } = useDrawerMode();
   const { data: isDrawerOpened, mutate: mutateDrawerOpened } = useDrawerOpened();
   const { data: isDrawerOpened, mutate: mutateDrawerOpened } = useDrawerOpened();
   const { data: currentProductNavWidth, mutate: mutateProductNavWidth } = useCurrentProductNavWidth();
   const { data: currentProductNavWidth, mutate: mutateProductNavWidth } = useCurrentProductNavWidth();
   const { data: isCollapsed, mutate: mutateSidebarCollapsed } = useSidebarCollapsed();
   const { data: isCollapsed, mutate: mutateSidebarCollapsed } = useSidebarCollapsed();
@@ -287,58 +286,61 @@ const Sidebar = (): JSX.Element => {
 
 
   const showContents = isDrawerMode || isHover || !isCollapsed;
   const showContents = isDrawerMode || isHover || !isCollapsed;
 
 
+
+  // css styles
+  const grwSidebarClass = `grw-sidebar ${styles['grw-sidebar']}`;
+  const sidebarModeClass = `${isDrawerMode ? 'grw-sidebar-drawer' : 'grw-sidebar-dock'}`;
+  const isOpenClass = `${isDrawerOpened ? 'open' : ''}`;
   return (
   return (
     <>
     <>
-      <div className={`grw-sidebar ${styles['grw-sidebar']}`}>
-        <div className={`d-print-none ${isDrawerMode ? 'grw-sidebar-drawer' : 'grw-sidebar-dock'} ${isDrawerOpened ? 'open' : ''}`}>
-          <div className="data-layout-container">
-            <div
-              className='navigation transition-enabled'
-              onMouseEnter={hoverOnHandler}
-              onMouseLeave={hoverOutHandler}
-            >
-              <div className="grw-navigation-wrap">
-                <div className="grw-global-navigation">
-                  <GlobalNavigation></GlobalNavigation>
-                </div>
-                <div
-                  ref={resizableContainer}
-                  className="grw-contextual-navigation"
-                  onMouseEnter={hoverOnResizableContainerHandler}
-                  onMouseLeave={hoverOutResizableContainerHandler}
-                  style={{ width: isCollapsed ? sidebarMinimizeWidth : currentProductNavWidth }}
-                >
-                  <div className="grw-contextual-navigation-child">
-                    <div role="group" data-testid="grw-contextual-navigation-sub" className={`grw-contextual-navigation-sub ${showContents ? '' : 'd-none'}`}>
-                      <SidebarContentsWrapper></SidebarContentsWrapper>
-                    </div>
+      <div className={`${grwSidebarClass} ${sidebarModeClass} ${isOpenClass} d-print-none`}>
+        <div className="data-layout-container">
+          <div
+            className='navigation transition-enabled'
+            onMouseEnter={hoverOnHandler}
+            onMouseLeave={hoverOutHandler}
+          >
+            <div className="grw-navigation-wrap">
+              <div className="grw-global-navigation">
+                <GlobalNavigation></GlobalNavigation>
+              </div>
+              <div
+                ref={resizableContainer}
+                className="grw-contextual-navigation"
+                onMouseEnter={hoverOnResizableContainerHandler}
+                onMouseLeave={hoverOutResizableContainerHandler}
+                style={{ width: isCollapsed ? sidebarMinimizeWidth : currentProductNavWidth }}
+              >
+                <div className="grw-contextual-navigation-child">
+                  <div role="group" data-testid="grw-contextual-navigation-sub" className={`grw-contextual-navigation-sub ${showContents ? '' : 'd-none'}`}>
+                    <SidebarContentsWrapper></SidebarContentsWrapper>
                   </div>
                   </div>
                 </div>
                 </div>
               </div>
               </div>
-              <div className="grw-navigation-draggable">
-                { isResizableByDrag && (
-                  <div
-                    className="grw-navigation-draggable-hitarea"
-                    onMouseDown={dragableAreaMouseDownHandler}
-                  >
-                    <div className="grw-navigation-draggable-hitarea-child"></div>
-                  </div>
-                ) }
-                <button
-                  data-testid="grw-navigation-resize-button"
-                  className={`grw-navigation-resize-button ${!isDrawerMode ? 'resizable' : ''} ${isCollapsed ? 'collapsed' : ''} `}
-                  type="button"
-                  aria-expanded="true"
-                  aria-label="Toggle navigation"
-                  disabled={isDrawerMode}
-                  onClick={toggleNavigationBtnClickHandler}
+            </div>
+            <div className="grw-navigation-draggable">
+              { isResizableByDrag && (
+                <div
+                  className="grw-navigation-draggable-hitarea"
+                  onMouseDown={dragableAreaMouseDownHandler}
                 >
                 >
-                  <span className="hexagon-container" role="presentation">
-                    <NavigationResizeHexagon />
-                  </span>
-                  <span className="hitarea" role="presentation"></span>
-                </button>
-              </div>
+                  <div className="grw-navigation-draggable-hitarea-child"></div>
+                </div>
+              ) }
+              <button
+                data-testid="grw-navigation-resize-button"
+                className={`grw-navigation-resize-button ${!isDrawerMode ? 'resizable' : ''} ${isCollapsed ? 'collapsed' : ''} `}
+                type="button"
+                aria-expanded="true"
+                aria-label="Toggle navigation"
+                disabled={isDrawerMode}
+                onClick={toggleNavigationBtnClickHandler}
+              >
+                <span className="hexagon-container" role="presentation">
+                  <NavigationResizeHexagon />
+                </span>
+                <span className="hitarea" role="presentation"></span>
+              </button>
             </div>
             </div>
           </div>
           </div>
         </div>
         </div>

+ 1 - 2
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -4,7 +4,7 @@ import React, {
 
 
 import nodePath from 'path';
 import nodePath from 'path';
 
 
-import { pathUtils, pagePathUtils } from '@growi/core';
+import { pathUtils, pagePathUtils, Nullable } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 import Link from 'next/link';
 import { useDrag, useDrop } from 'react-dnd';
 import { useDrag, useDrop } from 'react-dnd';
@@ -14,7 +14,6 @@ import { bookmark, unbookmark, resumeRenameOperation } from '~/client/services/p
 import { toastWarning, toastError, toastSuccess } from '~/client/util/apiNotification';
 import { toastWarning, toastError, toastSuccess } from '~/client/util/apiNotification';
 import { apiv3Put, apiv3Post } from '~/client/util/apiv3-client';
 import { apiv3Put, apiv3Post } from '~/client/util/apiv3-client';
 import TriangleIcon from '~/components/Icons/TriangleIcon';
 import TriangleIcon from '~/components/Icons/TriangleIcon';
-import { Nullable } from '~/interfaces/common';
 import {
 import {
   IPageHasId, IPageInfoAll, IPageToDeleteWithMeta,
   IPageHasId, IPageInfoAll, IPageToDeleteWithMeta,
 } from '~/interfaces/page';
 } from '~/interfaces/page';

+ 1 - 1
packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx

@@ -2,11 +2,11 @@ import React, {
   useEffect, useRef, useState, useMemo, useCallback,
   useEffect, useRef, useState, useMemo, useCallback,
 } from 'react';
 } from 'react';
 
 
+import { Nullable } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { debounce } from 'throttle-debounce';
 import { debounce } from 'throttle-debounce';
 
 
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
-import { Nullable } from '~/interfaces/common';
 import { IPageHasId, IPageToDeleteWithMeta } from '~/interfaces/page';
 import { IPageHasId, IPageToDeleteWithMeta } from '~/interfaces/page';
 import { AncestorsChildrenResult, RootPageResult, TargetAndAncestors } from '~/interfaces/page-listing-results';
 import { AncestorsChildrenResult, RootPageResult, TargetAndAncestors } from '~/interfaces/page-listing-results';
 import { OnDuplicatedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import { OnDuplicatedFunction, OnDeletedFunction } from '~/interfaces/ui';

+ 1 - 4
packages/app/src/components/Sidebar/RecentChanges.tsx

@@ -2,15 +2,12 @@ import React, {
   memo, useCallback, useEffect, useState,
   memo, useCallback, useEffect, useState,
 } from 'react';
 } from 'react';
 
 
-import { DevidedPagePath } from '@growi/core';
+import { DevidedPagePath, isPopulated } from '@growi/core';
 import { UserPicture, FootstampIcon } from '@growi/ui';
 import { UserPicture, FootstampIcon } from '@growi/ui';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 import Link from 'next/link';
-import PropTypes from 'prop-types';
-
 
 
 import PagePathHierarchicalLink from '~/components/PagePathHierarchicalLink';
 import PagePathHierarchicalLink from '~/components/PagePathHierarchicalLink';
-import { isPopulated } from '~/interfaces/common';
 import { IPageHasId } from '~/interfaces/page';
 import { IPageHasId } from '~/interfaces/page';
 import LinkedPagePath from '~/models/linked-page-path';
 import LinkedPagePath from '~/models/linked-page-path';
 import { useSWRInifinitexRecentlyUpdated } from '~/stores/page-listing';
 import { useSWRInifinitexRecentlyUpdated } from '~/stores/page-listing';

+ 1 - 2
packages/app/src/components/SubscribeButton.tsx

@@ -1,10 +1,9 @@
 import React, { FC, useCallback } from 'react';
 import React, { FC, useCallback } from 'react';
 
 
+import { SubscriptionStatusType } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import { UncontrolledTooltip } from 'reactstrap';
 import { UncontrolledTooltip } from 'reactstrap';
 
 
-import { SubscriptionStatusType } from '~/interfaces/subscription';
-
 import styles from './SubscribeButton.module.scss';
 import styles from './SubscribeButton.module.scss';
 
 
 
 

+ 12 - 4
packages/app/src/components/SystemVersion.tsx

@@ -5,7 +5,13 @@ import { useShortcutsModal } from '~/stores/modal';
 
 
 import styles from './SystemVersion.module.scss';
 import styles from './SystemVersion.module.scss';
 
 
-const SystemVersion = (): JSX.Element => {
+
+type Props = {
+  showShortcutsButton?: boolean,
+}
+
+const SystemVersion = (props: Props): JSX.Element => {
+  const { showShortcutsButton } = props;
 
 
   const { open: openShortcutsModal } = useShortcutsModal();
   const { open: openShortcutsModal } = useShortcutsModal();
 
 
@@ -21,9 +27,11 @@ const SystemVersion = (): JSX.Element => {
         <span>
         <span>
           <a href="https://growi.org">GROWI</a> {growiVersion}
           <a href="https://growi.org">GROWI</a> {growiVersion}
         </span>
         </span>
-        <button type="button" className="btn btn-link ml-2 p-0" onClick={() => openShortcutsModal()}>
-          <i className="fa fa-keyboard-o"></i>&nbsp;<span className={`cmd-key ${os}`}></span>-/
-        </button>
+        { showShortcutsButton && (
+          <button type="button" className="btn btn-link ml-2 p-0" onClick={() => openShortcutsModal()}>
+            <i className="fa fa-keyboard-o"></i>&nbsp;<span className={`cmd-key ${os}`}></span>-/
+          </button>
+        ) }
       </div>
       </div>
 
 
     </>
     </>

+ 7 - 7
packages/app/src/styles/theme/antarctic.scss → packages/app/src/components/Theme/ThemeAntarctic.module.scss

@@ -1,5 +1,6 @@
-@import '../variables';
-@import '../override-bootstrap-variables';
+@use '../../styles/variables' as *;
+@use '../../styles/bootstrap/variables' as *;
+@use '../../styles/theme/mixins/page-editor-mode-manager';
 
 
 // == Define Bootstrap theme colors
 // == Define Bootstrap theme colors
 //
 //
@@ -43,8 +44,7 @@ $accentcolor: #ffd700;
 
 
 //== Light Mode
 //== Light Mode
 //
 //
-html[light],
-html[dark] {
+.theme :global {
   $primary: $themecolor;
   $primary: $themecolor;
 
 
   // Background colors
   // Background colors
@@ -110,13 +110,13 @@ html[dark] {
   // admin theme box
   // admin theme box
   $color-theme-color-box: lighten($themecolor, 20%);
   $color-theme-color-box: lighten($themecolor, 20%);
 
 
-  @import 'apply-colors';
-  @import 'apply-colors-light';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-light';
 
 
   //Button
   //Button
   .btn-group.grw-page-editor-mode-manager {
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
     .btn.btn-outline-primary {
-      @include btn-page-editor-mode-manager(darken($primary, 10%), lighten($primary, 55%), lighten($primary, 60%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(darken($primary, 10%), lighten($primary, 55%), lighten($primary, 60%));
     }
     }
   }
   }
 
 

+ 8 - 0
packages/app/src/components/Theme/ThemeAntarctic.tsx

@@ -0,0 +1,8 @@
+import { ThemeInjector } from './utils/ThemeInjector';
+
+import styles from './ThemeAntarctic.module.scss';
+
+const ThemeAntarctic = ({ children }: { children: JSX.Element }): JSX.Element => {
+  return <ThemeInjector className={styles.theme}>{children}</ThemeInjector>;
+};
+export default ThemeAntarctic;

+ 7 - 7
packages/app/src/styles/theme/blackboard.scss → packages/app/src/components/Theme/ThemeBlackboard.module.scss

@@ -1,8 +1,8 @@
-@import '../variables';
-@import '../override-bootstrap-variables';
+@use '../../styles/variables' as *;
+@use '../../styles/bootstrap/variables' as *;
+@use '../../styles/theme/mixins/page-editor-mode-manager';
 
 
-html[light],
-html[dark] {
+.theme :global {
   // Theme colors
   // Theme colors
   $themecolor: #da8506;
   $themecolor: #da8506;
   $themelight: #223729;
   $themelight: #223729;
@@ -79,8 +79,8 @@ html[dark] {
   // admin theme box
   // admin theme box
   $color-theme-color-box: $primary;
   $color-theme-color-box: $primary;
 
 
-  @import 'apply-colors';
-  @import 'apply-colors-dark';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-dark';
 
 
   // Navs
   // Navs
   .nav-tabs {
   .nav-tabs {
@@ -108,7 +108,7 @@ html[dark] {
   // Button
   // Button
   .btn-group.grw-page-editor-mode-manager {
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
     .btn.btn-outline-primary {
-      @include btn-page-editor-mode-manager(#ffffff, $primary, $primary, darken($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, $primary, $primary, darken($primary, 20%));
     }
     }
   }
   }
 }
 }

+ 8 - 0
packages/app/src/components/Theme/ThemeBlackboard.tsx

@@ -0,0 +1,8 @@
+import { ThemeInjector } from './utils/ThemeInjector';
+
+import styles from './ThemeBlackboard.module.scss';
+
+const ThemeBlackboard = ({ children }: { children: JSX.Element }): JSX.Element => {
+  return <ThemeInjector className={styles.theme}>{children}</ThemeInjector>;
+};
+export default ThemeBlackboard;

+ 7 - 7
packages/app/src/styles/theme/christmas.scss → packages/app/src/components/Theme/ThemeChristmas.module.scss

@@ -1,5 +1,6 @@
-@import '../variables';
-@import '../override-bootstrap-variables';
+@use '../../styles/variables' as *;
+@use '../../styles/bootstrap/variables' as *;
+@use '../../styles/theme/mixins/page-editor-mode-manager';
 
 
 // == Define Bootstrap theme colors
 // == Define Bootstrap theme colors
 //
 //
@@ -37,8 +38,7 @@ $color-link-wiki-hover: lighten($color-link-wiki, 15%);
 
 
 //== Light Mode
 //== Light Mode
 //
 //
-html[light],
-html[dark] {
+.theme :global {
   $primary: #d3c665;
   $primary: #d3c665;
   // Background colors
   // Background colors
   $bgcolor-card: $gray-50;
   $bgcolor-card: $gray-50;
@@ -102,8 +102,8 @@ html[dark] {
   // admin theme box
   // admin theme box
   $color-theme-color-box: lighten($themecolor, 20%);
   $color-theme-color-box: lighten($themecolor, 20%);
 
 
-  @import 'apply-colors';
-  @import 'apply-colors-light';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-light';
 
 
   // change color of highlighted header in wiki (default: orange)
   // change color of highlighted header in wiki (default: orange)
 
 
@@ -176,7 +176,7 @@ html[dark] {
   // Button
   // Button
   .grw-page-editor-mode-manager {
   .grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
     .btn.btn-outline-primary {
-      @include btn-page-editor-mode-manager(darken($subthemecolor, 15%), lighten($subthemecolor, 35%), lighten($subthemecolor, 45%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(darken($subthemecolor, 15%), lighten($subthemecolor, 35%), lighten($subthemecolor, 45%));
     }
     }
   }
   }
 }
 }

+ 8 - 0
packages/app/src/components/Theme/ThemeChristmas.tsx

@@ -0,0 +1,8 @@
+import { ThemeInjector } from './utils/ThemeInjector';
+
+import styles from './ThemeChristmas.module.scss';
+
+const ThemeChristmas = ({ children }: { children: JSX.Element }): JSX.Element => {
+  return <ThemeInjector className={styles.theme}>{children}</ThemeInjector>;
+};
+export default ThemeChristmas;

+ 9 - 9
packages/app/src/styles/theme/default.scss → packages/app/src/components/Theme/ThemeDefault.module.scss

@@ -1,6 +1,6 @@
-@use '../variables' as *;
-@use '../bootstrap/variables' as *;
-@use './mixins/page-editor-mode-manager';
+@use '../../styles/variables' as *;
+@use '../../styles/bootstrap/variables' as *;
+@use '../../styles/theme/mixins/page-editor-mode-manager';
 
 
 // == Define Bootstrap theme colors
 // == Define Bootstrap theme colors
 //
 //
@@ -16,7 +16,7 @@
 
 
 //== Light Mode
 //== Light Mode
 //
 //
-html[light] {
+.theme[data-color-scheme='light'] :global {
   $primary: #122c55;
   $primary: #122c55;
   $accent: #209fd8;
   $accent: #209fd8;
 
 
@@ -103,8 +103,8 @@ html[light] {
   // admin theme box
   // admin theme box
   $color-theme-color-box: lighten($primary, 20%);
   $color-theme-color-box: lighten($primary, 20%);
 
 
-  @import 'apply-colors';
-  @import 'apply-colors-light';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-light';
 
 
   // Button
   // Button
   .btn-group.grw-page-editor-mode-manager {
   .btn-group.grw-page-editor-mode-manager {
@@ -116,7 +116,7 @@ html[light] {
 
 
 //== Dark Mode
 //== Dark Mode
 //
 //
-html[dark] {
+.theme[data-color-scheme='dark'] :global {
   $primary: #115cd3;
   $primary: #115cd3;
   $accent: #db00c2;
   $accent: #db00c2;
 
 
@@ -200,8 +200,8 @@ html[dark] {
   // admin theme box
   // admin theme box
   $color-theme-color-box: $primary;
   $color-theme-color-box: $primary;
 
 
-  @import 'apply-colors';
-  @import 'apply-colors-dark';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-dark';
 
 
   //Button
   //Button
   .btn-group.grw-page-editor-mode-manager {
   .btn-group.grw-page-editor-mode-manager {

+ 8 - 0
packages/app/src/components/Theme/ThemeDefault.tsx

@@ -0,0 +1,8 @@
+import { ThemeInjector } from './utils/ThemeInjector';
+
+import styles from './ThemeDefault.module.scss';
+
+const ThemeDefault = ({ children }: { children: JSX.Element }): JSX.Element => {
+  return <ThemeInjector className={styles.theme}>{children}</ThemeInjector>;
+};
+export default ThemeDefault;

+ 0 - 0
packages/app/src/styles/theme/fire-red.scss → packages/app/src/components/Theme/ThemeFireRed.module.scss


+ 0 - 0
packages/app/src/styles/theme/future.scss → packages/app/src/components/Theme/ThemeFuture.module.scss


+ 0 - 0
packages/app/src/styles/theme/halloween.scss → packages/app/src/components/Theme/ThemeHalloween.module.scss


+ 0 - 0
packages/app/src/styles/theme/hufflepuff.scss → packages/app/src/components/Theme/ThemeHufflepuff.module.scss


+ 0 - 0
packages/app/src/styles/theme/island.scss → packages/app/src/components/Theme/ThemeIsland.module.scss


+ 0 - 0
packages/app/src/styles/theme/jade-green.scss → packages/app/src/components/Theme/ThemeJadeGreen.module.scss


+ 0 - 0
packages/app/src/styles/theme/kibela.scss → packages/app/src/components/Theme/ThemeKibela.module.scss


+ 0 - 0
packages/app/src/styles/theme/mono-blue.scss → packages/app/src/components/Theme/ThemeMonoBlue.module.scss


+ 0 - 0
packages/app/src/styles/theme/nature.scss → packages/app/src/components/Theme/ThemeNature.module.scss


+ 0 - 0
packages/app/src/styles/theme/spring.scss → packages/app/src/components/Theme/ThemeSpring.module.scss


+ 0 - 0
packages/app/src/styles/theme/wood.scss → packages/app/src/components/Theme/ThemeWood.module.scss


+ 12 - 0
packages/app/src/components/Theme/utils/ThemeInjector.tsx

@@ -0,0 +1,12 @@
+
+import React from 'react';
+
+type Props = {
+  children: JSX.Element,
+  className: string,
+}
+
+export const ThemeInjector = ({ children, className: themeClassName }: Props): JSX.Element => {
+  const className = `${children.props.className ?? ''} ${themeClassName}`;
+  return React.cloneElement(children, { className });
+};

+ 31 - 0
packages/app/src/components/Theme/utils/ThemeProvider.tsx

@@ -0,0 +1,31 @@
+
+import React from 'react';
+
+import dynamic from 'next/dynamic';
+
+import { GrowiThemes } from '~/interfaces/theme';
+
+
+const ThemeAntarctic = dynamic(() => import('../ThemeAntarctic'));
+const ThemeBlackboard = dynamic(() => import('../ThemeBlackboard'));
+const ThemeChristmas = dynamic(() => import('../ThemeChristmas'));
+const ThemeDefault = dynamic(() => import('../ThemeDefault'));
+
+
+type Props = {
+  children: JSX.Element,
+  theme?: GrowiThemes,
+}
+
+export const ThemeProvider = ({ theme, children }: Props): JSX.Element => {
+  switch (theme) {
+    case GrowiThemes.ANTARCTIC:
+      return <ThemeAntarctic>{children}</ThemeAntarctic>;
+    case GrowiThemes.BLACKBOARD:
+      return <ThemeBlackboard>{children}</ThemeBlackboard>;
+    case GrowiThemes.CHRISTMAS:
+      return <ThemeChristmas>{children}</ThemeChristmas>;
+    default:
+      return <ThemeDefault>{children}</ThemeDefault>;
+  }
+};

+ 66 - 27
packages/app/src/interfaces/activity.ts

@@ -1,5 +1,5 @@
-import { Ref } from './common';
-import { HasObjectId } from './has-object-id';
+import { Ref, HasObjectId } from '@growi/core';
+
 import { IUser } from './user';
 import { IUser } from './user';
 
 
 // Model
 // Model
@@ -19,6 +19,8 @@ const ACTION_USER_LOGIN_WITH_SAML = 'USER_LOGIN_WITH_SAML';
 const ACTION_USER_LOGIN_WITH_BASIC = 'USER_LOGIN_WITH_BASIC';
 const ACTION_USER_LOGIN_WITH_BASIC = 'USER_LOGIN_WITH_BASIC';
 const ACTION_USER_LOGIN_FAILURE = 'USER_LOGIN_FAILURE';
 const ACTION_USER_LOGIN_FAILURE = 'USER_LOGIN_FAILURE';
 const ACTION_USER_LOGOUT = 'USER_LOGOUT';
 const ACTION_USER_LOGOUT = 'USER_LOGOUT';
+const ACTION_USER_FOGOT_PASSWORD = 'USER_FOGOT_PASSWORD';
+const ACTION_USER_RESET_PASSWORD = 'USER_RESET_PASSWORD';
 const ACTION_USER_PERSONAL_SETTINGS_UPDATE = 'USER_PERSONAL_SETTINGS_UPDATE';
 const ACTION_USER_PERSONAL_SETTINGS_UPDATE = 'USER_PERSONAL_SETTINGS_UPDATE';
 const ACTION_USER_IMAGE_TYPE_UPDATE = 'USER_IMAGE_TYPE_UPDATE';
 const ACTION_USER_IMAGE_TYPE_UPDATE = 'USER_IMAGE_TYPE_UPDATE';
 const ACTION_USER_LDAP_ACCOUNT_ASSOCIATE = 'USER_LDAP_ACCOUNT_ASSOCIATE';
 const ACTION_USER_LDAP_ACCOUNT_ASSOCIATE = 'USER_LDAP_ACCOUNT_ASSOCIATE';
@@ -68,7 +70,7 @@ const ACTION_ADMIN_APP_SETTINGS_UPDATE = 'ADMIN_APP_SETTING_UPDATE';
 const ACTION_ADMIN_SITE_URL_UPDATE = 'ADMIN_SITE_URL_UPDATE';
 const ACTION_ADMIN_SITE_URL_UPDATE = 'ADMIN_SITE_URL_UPDATE';
 const ACTION_ADMIN_MAIL_SMTP_UPDATE = 'ADMIN_MAIL_SMTP_UPDATE';
 const ACTION_ADMIN_MAIL_SMTP_UPDATE = 'ADMIN_MAIL_SMTP_UPDATE';
 const ACTION_ADMIN_MAIL_SES_UPDATE = 'ADMIN_MAIL_SES_UPDATE';
 const ACTION_ADMIN_MAIL_SES_UPDATE = 'ADMIN_MAIL_SES_UPDATE';
-const ACTION_ADMIN_MAIL_TEST_SUBMIT = 'ADMIN_MAIL_TEST_SUBMIT ';
+const ACTION_ADMIN_MAIL_TEST_SUBMIT = 'ADMIN_MAIL_TEST_SUBMIT';
 const ACTION_ADMIN_FILE_UPLOAD_CONFIG_UPDATE = 'ADMIN_FILE_UPLOAD_CONFIG_UPDATE';
 const ACTION_ADMIN_FILE_UPLOAD_CONFIG_UPDATE = 'ADMIN_FILE_UPLOAD_CONFIG_UPDATE';
 const ACTION_ADMIN_PLUGIN_UPDATE = 'ADMIN_PLUGIN_UPDATE';
 const ACTION_ADMIN_PLUGIN_UPDATE = 'ADMIN_PLUGIN_UPDATE';
 const ACTION_ADMIN_MAINTENANCEMODE_ENABLED = 'ADMIN_MAINTENANCEMODE_ENABLED';
 const ACTION_ADMIN_MAINTENANCEMODE_ENABLED = 'ADMIN_MAINTENANCEMODE_ENABLED';
@@ -106,6 +108,7 @@ const ACTION_ADMIN_MARKDOWN_PRESENTATION_UPDATE = 'ADMIN_MARKDOWN_PRESENTATION_U
 const ACTION_ADMIN_MARKDOWN_XSS_UPDATE = 'ADMIN_MARKDOWN_XSS_UPDATE';
 const ACTION_ADMIN_MARKDOWN_XSS_UPDATE = 'ADMIN_MARKDOWN_XSS_UPDATE';
 const ACTION_ADMIN_LAYOUT_UPDATE = 'ADMIN_LAYOUT_UPDATE';
 const ACTION_ADMIN_LAYOUT_UPDATE = 'ADMIN_LAYOUT_UPDATE';
 const ACTION_ADMIN_THEME_UPDATE = 'ADMIN_THEME_UPDATE';
 const ACTION_ADMIN_THEME_UPDATE = 'ADMIN_THEME_UPDATE';
+const ACTION_ADMIN_SIDEBAR_UPDATE = 'ADMIN_SIDEBAR_UPDATE';
 const ACTION_ADMIN_FUNCTION_UPDATE = 'ADMIN_FUNCTION_UPDATE';
 const ACTION_ADMIN_FUNCTION_UPDATE = 'ADMIN_FUNCTION_UPDATE';
 const ACTION_ADMIN_CODE_HIGHLIGHT_UPDATE = 'ADMIN_CODE_HIGHLIGHT_UPDATE';
 const ACTION_ADMIN_CODE_HIGHLIGHT_UPDATE = 'ADMIN_CODE_HIGHLIGHT_UPDATE';
 const ACTION_ADMIN_CUSTOM_TITLE_UPDATE = 'ADMIN_CUSTOM_TITLE_UPDATE';
 const ACTION_ADMIN_CUSTOM_TITLE_UPDATE = 'ADMIN_CUSTOM_TITLE_UPDATE';
@@ -113,7 +116,17 @@ const ACTION_ADMIN_CUSTOM_HTML_HEADER_UPDATE = 'ADMIN_CUSTOM_HTML_HEADER_UPDATE'
 const ACTION_ADMIN_CUSTOM_CSS_UPDATE = 'ADMIN_CUSTOM_CSS_UPDATE';
 const ACTION_ADMIN_CUSTOM_CSS_UPDATE = 'ADMIN_CUSTOM_CSS_UPDATE';
 const ACTION_ADMIN_CUSTOM_SCRIPT_UPDATE = 'ADMIN_CUSTOM_SCRIPT_UPDATE';
 const ACTION_ADMIN_CUSTOM_SCRIPT_UPDATE = 'ADMIN_CUSTOM_SCRIPT_UPDATE';
 const ACTION_ADMIN_ARCHIVE_DATA_UPLOAD = 'ADMIN_ARCHIVE_DATA_UPLOAD';
 const ACTION_ADMIN_ARCHIVE_DATA_UPLOAD = 'ADMIN_ARCHIVE_DATA_UPLOAD';
+const ACTION_ADMIN_GROWI_DATA_IMPORTED = 'ADMIN_GROWI_DATA_IMPORTED';
+const ACTION_ADMIN_UPLOADED_GROWI_DATA_DISCARDED = 'ADMIN_UPLOADED_GROWI_DATA_DISCARDED';
+const ACTION_ADMIN_ESA_DATA_IMPORTED = 'ADMIN_ESA_DATA_IMPORTED';
+const ACTION_ADMIN_ESA_DATA_UPDATED = 'ADMIN_ESA_DATA_UPDATED';
+const ACTION_ADMIN_CONNECTION_TEST_OF_ESA_DATA = 'ADMIN_CONNECTION_TEST_OF_ESA_DATA';
+const ACTION_ADMIN_QIITA_DATA_IMPORTED = 'ADMIN_QIITA_DATA_IMPORTED';
+const ACTION_ADMIN_QIITA_DATA_UPDATED = 'ADMIN_QIITA_DATA_UPDATED';
+const ACTION_ADMIN_CONNECTION_TEST_OF_QIITA_DATA = 'ADMIN_CONNECTION_TEST_OF_QIITA_DATA';
 const ACTION_ADMIN_ARCHIVE_DATA_CREATE = 'ADMIN_ARCHIVE_DATA_CREATE';
 const ACTION_ADMIN_ARCHIVE_DATA_CREATE = 'ADMIN_ARCHIVE_DATA_CREATE';
+const ACTION_ADMIN_ARCHIVE_DATA_DOWNLOAD = 'ADMIN_ARCHIVE_DATA_DOWNLOAD';
+const ACTION_ADMIN_ARCHIVE_DATA_DELETE = 'ADMIN_ARCHIVE_DATA_DELETE';
 const ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_ADD = 'ADMIN_USER_NOTIFICATION_SETTINGS_ADD';
 const ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_ADD = 'ADMIN_USER_NOTIFICATION_SETTINGS_ADD';
 const ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_DELETE = 'ADMIN_USER_NOTIFICATION_SETTINGS_DELETE';
 const ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_DELETE = 'ADMIN_USER_NOTIFICATION_SETTINGS_DELETE';
 const ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD = 'ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD';
 const ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD = 'ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD';
@@ -136,20 +149,20 @@ const ACTION_ADMIN_SLACK_WITHOUT_PROXY_PERMISSION_UPDATE = 'ADMIN_SLACK_WITHOUT_
 const ACTION_ADMIN_SLACK_WITHOUT_PROXY_TEST = 'ADMIN_SLACK_WITHOUT_PROXY_TEST';
 const ACTION_ADMIN_SLACK_WITHOUT_PROXY_TEST = 'ADMIN_SLACK_WITHOUT_PROXY_TEST';
 const ACTION_ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE = 'ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE';
 const ACTION_ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE = 'ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE';
 const ACTION_ADMIN_USERS_INVITE = 'ADMIN_USERS_INVITE';
 const ACTION_ADMIN_USERS_INVITE = 'ADMIN_USERS_INVITE';
+const ACTION_ADMIN_USERS_PASSWORD_RESET = 'ADMIN_USERS_PASSWORD_RESET';
+const ACTION_ADMIN_USERS_ACTIVATE = 'ADMIN_USERS_ACTIVATE';
+const ACTION_ADMIN_USERS_GIVE_ADMIN = 'ADMIN_USERS_GIVE_ADMIN';
+const ACTION_ADMIN_USERS_REMOVE_ADMIN = 'ADMIN_USERS_REMOVE_ADMIN';
+const ACTION_ADMIN_USERS_DEACTIVATE = 'ADMIN_USERS_DEACTIVATE';
+const ACTION_ADMIN_USERS_SEND_INVITATION_EMAIL = 'ADMIN_USERS_SEND_INVITATION_EMAIL';
+const ACTION_ADMIN_USERS_REMOVE = 'ADMIN_USERS_REMOVE';
 const ACTION_ADMIN_USER_GROUP_CREATE = 'ADMIN_USER_GROUP_CREATE';
 const ACTION_ADMIN_USER_GROUP_CREATE = 'ADMIN_USER_GROUP_CREATE';
 const ACTION_ADMIN_USER_GROUP_UPDATE = 'ADMIN_USER_GROUP_UPDATE';
 const ACTION_ADMIN_USER_GROUP_UPDATE = 'ADMIN_USER_GROUP_UPDATE';
 const ACTION_ADMIN_USER_GROUP_DELETE = 'ADMIN_USER_GROUP_DELETE';
 const ACTION_ADMIN_USER_GROUP_DELETE = 'ADMIN_USER_GROUP_DELETE';
 const ACTION_ADMIN_USER_GROUP_ADD_USER = 'ADMIN_USER_GROUP_ADD_USER';
 const ACTION_ADMIN_USER_GROUP_ADD_USER = 'ADMIN_USER_GROUP_ADD_USER';
+const ACTION_ADMIN_SEARCH_CONNECTION = 'ADMIN_SEARCH_CONNECTION';
 const ACTION_ADMIN_SEARCH_INDICES_NORMALIZE = 'ADMIN_SEARCH_INDICES_NORMALIZE';
 const ACTION_ADMIN_SEARCH_INDICES_NORMALIZE = 'ADMIN_SEARCH_INDICES_NORMALIZE';
 const ACTION_ADMIN_SEARCH_INDICES_REBUILD = 'ADMIN_SEARCH_INDICES_REBUILD';
 const ACTION_ADMIN_SEARCH_INDICES_REBUILD = 'ADMIN_SEARCH_INDICES_REBUILD';
-const ACTION_ADMIN_GROWI_DATA_IMPORTED = 'ADMIN_GROWI_DATA_IMPORTED';
-const ACTION_ADMIN_ESA_DATA_IMPORTED = 'ADMIN_ESA_DATA_IMPORTED';
-const ACTION_ADMIN_QIITA_DATA_IMPORTED = 'ADMIN_QIITA_DATA_IMPORTED';
-const ACTION_ADMIN_UPLOADED_GROWI_DATA_DISCARDED = 'ADMIN_UPLOADED_GROWI_DATA_DISCARDED';
-const ACTION_ADMIN_ESA_DATA_UPDATED = 'ADMIN_ESA_DATA_UPDATED';
-const ACTION_ADMIN_CONNECTION_TEST_OF_ESA_DATA = 'ADMIN_CONNECTION_TEST_OF_ESA_DATA';
-const ACTION_ADMIN_QIITA_DATA_UPDATED = 'ADMIN_QIITA_DATA_UPDATED';
-const ACTION_ADMIN_CONNECTION_TEST_OF_QIITA_DATA = 'ADMIN_CONNECTION_TEST_OF_QIITA_DATA';
 
 
 
 
 export const SupportedTargetModel = {
 export const SupportedTargetModel = {
@@ -185,6 +198,8 @@ export const SupportedAction = {
   ACTION_USER_LOGIN_WITH_BASIC,
   ACTION_USER_LOGIN_WITH_BASIC,
   ACTION_USER_LOGIN_FAILURE,
   ACTION_USER_LOGIN_FAILURE,
   ACTION_USER_LOGOUT,
   ACTION_USER_LOGOUT,
+  ACTION_USER_FOGOT_PASSWORD,
+  ACTION_USER_RESET_PASSWORD,
   ACTION_USER_PERSONAL_SETTINGS_UPDATE,
   ACTION_USER_PERSONAL_SETTINGS_UPDATE,
   ACTION_USER_IMAGE_TYPE_UPDATE,
   ACTION_USER_IMAGE_TYPE_UPDATE,
   ACTION_USER_LDAP_ACCOUNT_ASSOCIATE,
   ACTION_USER_LDAP_ACCOUNT_ASSOCIATE,
@@ -272,6 +287,7 @@ export const SupportedAction = {
   ACTION_ADMIN_MARKDOWN_XSS_UPDATE,
   ACTION_ADMIN_MARKDOWN_XSS_UPDATE,
   ACTION_ADMIN_LAYOUT_UPDATE,
   ACTION_ADMIN_LAYOUT_UPDATE,
   ACTION_ADMIN_THEME_UPDATE,
   ACTION_ADMIN_THEME_UPDATE,
+  ACTION_ADMIN_SIDEBAR_UPDATE,
   ACTION_ADMIN_FUNCTION_UPDATE,
   ACTION_ADMIN_FUNCTION_UPDATE,
   ACTION_ADMIN_CODE_HIGHLIGHT_UPDATE,
   ACTION_ADMIN_CODE_HIGHLIGHT_UPDATE,
   ACTION_ADMIN_CUSTOM_TITLE_UPDATE,
   ACTION_ADMIN_CUSTOM_TITLE_UPDATE,
@@ -279,7 +295,17 @@ export const SupportedAction = {
   ACTION_ADMIN_CUSTOM_CSS_UPDATE,
   ACTION_ADMIN_CUSTOM_CSS_UPDATE,
   ACTION_ADMIN_CUSTOM_SCRIPT_UPDATE,
   ACTION_ADMIN_CUSTOM_SCRIPT_UPDATE,
   ACTION_ADMIN_ARCHIVE_DATA_UPLOAD,
   ACTION_ADMIN_ARCHIVE_DATA_UPLOAD,
+  ACTION_ADMIN_GROWI_DATA_IMPORTED,
+  ACTION_ADMIN_ESA_DATA_IMPORTED,
+  ACTION_ADMIN_QIITA_DATA_IMPORTED,
+  ACTION_ADMIN_UPLOADED_GROWI_DATA_DISCARDED,
+  ACTION_ADMIN_ESA_DATA_UPDATED,
+  ACTION_ADMIN_CONNECTION_TEST_OF_ESA_DATA,
+  ACTION_ADMIN_QIITA_DATA_UPDATED,
+  ACTION_ADMIN_CONNECTION_TEST_OF_QIITA_DATA,
   ACTION_ADMIN_ARCHIVE_DATA_CREATE,
   ACTION_ADMIN_ARCHIVE_DATA_CREATE,
+  ACTION_ADMIN_ARCHIVE_DATA_DOWNLOAD,
+  ACTION_ADMIN_ARCHIVE_DATA_DELETE,
   ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_ADD,
   ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_ADD,
   ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_DELETE,
   ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_DELETE,
   ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD,
   ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD,
@@ -302,20 +328,20 @@ export const SupportedAction = {
   ACTION_ADMIN_SLACK_WITHOUT_PROXY_TEST,
   ACTION_ADMIN_SLACK_WITHOUT_PROXY_TEST,
   ACTION_ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE,
   ACTION_ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE,
   ACTION_ADMIN_USERS_INVITE,
   ACTION_ADMIN_USERS_INVITE,
+  ACTION_ADMIN_USERS_PASSWORD_RESET,
+  ACTION_ADMIN_USERS_ACTIVATE,
+  ACTION_ADMIN_USERS_DEACTIVATE,
+  ACTION_ADMIN_USERS_GIVE_ADMIN,
+  ACTION_ADMIN_USERS_REMOVE_ADMIN,
+  ACTION_ADMIN_USERS_SEND_INVITATION_EMAIL,
+  ACTION_ADMIN_USERS_REMOVE,
   ACTION_ADMIN_USER_GROUP_CREATE,
   ACTION_ADMIN_USER_GROUP_CREATE,
   ACTION_ADMIN_USER_GROUP_UPDATE,
   ACTION_ADMIN_USER_GROUP_UPDATE,
   ACTION_ADMIN_USER_GROUP_DELETE,
   ACTION_ADMIN_USER_GROUP_DELETE,
   ACTION_ADMIN_USER_GROUP_ADD_USER,
   ACTION_ADMIN_USER_GROUP_ADD_USER,
+  ACTION_ADMIN_SEARCH_CONNECTION,
   ACTION_ADMIN_SEARCH_INDICES_NORMALIZE,
   ACTION_ADMIN_SEARCH_INDICES_NORMALIZE,
   ACTION_ADMIN_SEARCH_INDICES_REBUILD,
   ACTION_ADMIN_SEARCH_INDICES_REBUILD,
-  ACTION_ADMIN_GROWI_DATA_IMPORTED,
-  ACTION_ADMIN_ESA_DATA_IMPORTED,
-  ACTION_ADMIN_QIITA_DATA_IMPORTED,
-  ACTION_ADMIN_UPLOADED_GROWI_DATA_DISCARDED,
-  ACTION_ADMIN_ESA_DATA_UPDATED,
-  ACTION_ADMIN_CONNECTION_TEST_OF_ESA_DATA,
-  ACTION_ADMIN_QIITA_DATA_UPDATED,
-  ACTION_ADMIN_CONNECTION_TEST_OF_QIITA_DATA,
 } as const;
 } as const;
 
 
 // Action required for notification
 // Action required for notification
@@ -356,6 +382,8 @@ export const SmallActionGroup = {
 export const MediumActionGroup = {
 export const MediumActionGroup = {
   ...SmallActionGroup,
   ...SmallActionGroup,
   ACTION_USER_REGISTRATION_SUCCESS,
   ACTION_USER_REGISTRATION_SUCCESS,
+  ACTION_USER_FOGOT_PASSWORD,
+  ACTION_USER_RESET_PASSWORD,
   ACTION_USER_PERSONAL_SETTINGS_UPDATE,
   ACTION_USER_PERSONAL_SETTINGS_UPDATE,
   ACTION_USER_IMAGE_TYPE_UPDATE,
   ACTION_USER_IMAGE_TYPE_UPDATE,
   ACTION_USER_LDAP_ACCOUNT_ASSOCIATE,
   ACTION_USER_LDAP_ACCOUNT_ASSOCIATE,
@@ -440,6 +468,7 @@ export const LargeActionGroup = {
   ACTION_ADMIN_MARKDOWN_XSS_UPDATE,
   ACTION_ADMIN_MARKDOWN_XSS_UPDATE,
   ACTION_ADMIN_LAYOUT_UPDATE,
   ACTION_ADMIN_LAYOUT_UPDATE,
   ACTION_ADMIN_THEME_UPDATE,
   ACTION_ADMIN_THEME_UPDATE,
+  ACTION_ADMIN_SIDEBAR_UPDATE,
   ACTION_ADMIN_FUNCTION_UPDATE,
   ACTION_ADMIN_FUNCTION_UPDATE,
   ACTION_ADMIN_CODE_HIGHLIGHT_UPDATE,
   ACTION_ADMIN_CODE_HIGHLIGHT_UPDATE,
   ACTION_ADMIN_CUSTOM_TITLE_UPDATE,
   ACTION_ADMIN_CUSTOM_TITLE_UPDATE,
@@ -447,7 +476,17 @@ export const LargeActionGroup = {
   ACTION_ADMIN_CUSTOM_CSS_UPDATE,
   ACTION_ADMIN_CUSTOM_CSS_UPDATE,
   ACTION_ADMIN_CUSTOM_SCRIPT_UPDATE,
   ACTION_ADMIN_CUSTOM_SCRIPT_UPDATE,
   ACTION_ADMIN_ARCHIVE_DATA_UPLOAD,
   ACTION_ADMIN_ARCHIVE_DATA_UPLOAD,
+  ACTION_ADMIN_GROWI_DATA_IMPORTED,
+  ACTION_ADMIN_ESA_DATA_IMPORTED,
+  ACTION_ADMIN_QIITA_DATA_IMPORTED,
+  ACTION_ADMIN_UPLOADED_GROWI_DATA_DISCARDED,
+  ACTION_ADMIN_ESA_DATA_UPDATED,
+  ACTION_ADMIN_CONNECTION_TEST_OF_ESA_DATA,
+  ACTION_ADMIN_QIITA_DATA_UPDATED,
+  ACTION_ADMIN_CONNECTION_TEST_OF_QIITA_DATA,
   ACTION_ADMIN_ARCHIVE_DATA_CREATE,
   ACTION_ADMIN_ARCHIVE_DATA_CREATE,
+  ACTION_ADMIN_ARCHIVE_DATA_DOWNLOAD,
+  ACTION_ADMIN_ARCHIVE_DATA_DELETE,
   ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_ADD,
   ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_ADD,
   ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_DELETE,
   ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_DELETE,
   ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD,
   ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD,
@@ -470,20 +509,20 @@ export const LargeActionGroup = {
   ACTION_ADMIN_SLACK_WITHOUT_PROXY_TEST,
   ACTION_ADMIN_SLACK_WITHOUT_PROXY_TEST,
   ACTION_ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE,
   ACTION_ADMIN_SLACK_CONFIGURATION_SETTING_UPDATE,
   ACTION_ADMIN_USERS_INVITE,
   ACTION_ADMIN_USERS_INVITE,
+  ACTION_ADMIN_USERS_PASSWORD_RESET,
+  ACTION_ADMIN_USERS_ACTIVATE,
+  ACTION_ADMIN_USERS_DEACTIVATE,
+  ACTION_ADMIN_USERS_GIVE_ADMIN,
+  ACTION_ADMIN_USERS_REMOVE_ADMIN,
+  ACTION_ADMIN_USERS_SEND_INVITATION_EMAIL,
+  ACTION_ADMIN_USERS_REMOVE,
   ACTION_ADMIN_USER_GROUP_CREATE,
   ACTION_ADMIN_USER_GROUP_CREATE,
   ACTION_ADMIN_USER_GROUP_UPDATE,
   ACTION_ADMIN_USER_GROUP_UPDATE,
   ACTION_ADMIN_USER_GROUP_DELETE,
   ACTION_ADMIN_USER_GROUP_DELETE,
   ACTION_ADMIN_USER_GROUP_ADD_USER,
   ACTION_ADMIN_USER_GROUP_ADD_USER,
+  ACTION_ADMIN_SEARCH_CONNECTION,
   ACTION_ADMIN_SEARCH_INDICES_NORMALIZE,
   ACTION_ADMIN_SEARCH_INDICES_NORMALIZE,
   ACTION_ADMIN_SEARCH_INDICES_REBUILD,
   ACTION_ADMIN_SEARCH_INDICES_REBUILD,
-  ACTION_ADMIN_GROWI_DATA_IMPORTED,
-  ACTION_ADMIN_ESA_DATA_IMPORTED,
-  ACTION_ADMIN_QIITA_DATA_IMPORTED,
-  ACTION_ADMIN_UPLOADED_GROWI_DATA_DISCARDED,
-  ACTION_ADMIN_ESA_DATA_UPDATED,
-  ACTION_ADMIN_CONNECTION_TEST_OF_ESA_DATA,
-  ACTION_ADMIN_QIITA_DATA_UPDATED,
-  ACTION_ADMIN_CONNECTION_TEST_OF_QIITA_DATA,
 } as const;
 } as const;
 
 
 
 

+ 1 - 11
packages/app/src/interfaces/attachment.ts

@@ -1,11 +1 @@
-import { Ref } from './common';
-import { IPage } from './page';
-import { IUser } from './user';
-
-export type IAttachment = {
-  page?: Ref<IPage>,
-  creator?: Ref<IUser>,
-
-  // virtual property
-  filePathProxied: string,
-};
+export type { IAttachment } from '@growi/core';

+ 2 - 2
packages/app/src/interfaces/comment.ts

@@ -1,5 +1,5 @@
-import { Nullable, Ref } from './common';
-import { HasObjectId } from './has-object-id';
+import { Nullable, Ref, HasObjectId } from '@growi/core';
+
 import { IPage } from './page';
 import { IPage } from './page';
 import { IRevision } from './revision';
 import { IRevision } from './revision';
 import { IUser } from './user';
 import { IUser } from './user';

+ 0 - 19
packages/app/src/interfaces/common.ts

@@ -4,25 +4,6 @@
 
 
 import { ReactNode } from 'react';
 import { ReactNode } from 'react';
 
 
-import { HasObjectId } from './has-object-id';
-
-
-// Foreign key field
-export type Ref<T> = string | T & HasObjectId;
-
-export type Nullable<T> = T | null | undefined;
-
-export const isPopulated = <T>(ref: Ref<T>): ref is T & HasObjectId => {
-  return !(typeof ref === 'string');
-};
-
-export const getIdForRef = <T>(ref: Ref<T>): string => {
-  return isPopulated(ref)
-    ? ref._id
-    : ref;
-};
-
-
 export type HasChildren<T = ReactNode> = {
 export type HasChildren<T = ReactNode> = {
   children?: T
   children?: T
 }
 }

+ 2 - 1
packages/app/src/interfaces/external-account.ts

@@ -1,4 +1,5 @@
-import { Ref } from '~/interfaces/common';
+import { Ref } from '@growi/core';
+
 import { IUser } from '~/interfaces/user';
 import { IUser } from '~/interfaces/user';
 
 
 
 

+ 1 - 6
packages/app/src/interfaces/global.ts

@@ -1,13 +1,8 @@
 import EventEmitter from 'events';
 import EventEmitter from 'events';
 
 
-import GrowiRenderer from '~/services/renderer/growi-renderer';
-import Xss from '~/services/xss';
-
 import { IGraphViewer } from './graph-viewer';
 import { IGraphViewer } from './graph-viewer';
 
 
 export type CustomWindow = Window
 export type CustomWindow = Window
                          & typeof globalThis
                          & typeof globalThis
                          & { globalEmitter: EventEmitter }
                          & { globalEmitter: EventEmitter }
-                         & { GraphViewer: IGraphViewer }
-                         & { growiRenderer: GrowiRenderer }
-                         & { previewRenderer: GrowiRenderer }; // TODO: Remove this code when reveal.js is omitted. see: https://github.com/weseek/growi/pull/6223
+                         & { GraphViewer: IGraphViewer };

+ 8 - 0
packages/app/src/interfaces/graph-viewer.ts

@@ -1,3 +1,11 @@
 export interface IGraphViewer {
 export interface IGraphViewer {
   createViewerForElement: (Element) => void,
   createViewerForElement: (Element) => void,
 }
 }
+
+export const isGraphViewer = (val: any): val is IGraphViewer => {
+  if (typeof val === 'function' && typeof val.createViewerForElement === 'function') {
+    return true;
+  }
+
+  return false;
+};

+ 1 - 1
packages/app/src/interfaces/page-grant.ts

@@ -5,7 +5,7 @@ export type IDataApplicableGroup = {
 }
 }
 
 
 export type IDataApplicableGrant = null | IDataApplicableGroup;
 export type IDataApplicableGrant = null | IDataApplicableGroup;
-export type IRecordApplicableGrant = Record<PageGrant, IDataApplicableGrant>
+export type IRecordApplicableGrant = Partial<Record<PageGrant, IDataApplicableGrant>>
 export type IResApplicableGrant = {
 export type IResApplicableGrant = {
   data?: IRecordApplicableGrant
   data?: IRecordApplicableGrant
 }
 }

+ 10 - 116
packages/app/src/interfaces/page.ts

@@ -1,125 +1,19 @@
-import { Ref, Nullable } from './common';
-import { HasObjectId } from './has-object-id';
-import { IPageOperationProcessData } from './page-operation';
-import { IRevision, HasRevisionShortbody } from './revision';
-import { SubscriptionStatusType } from './subscription';
-import { ITag } from './tag';
-import { IUser } from './user';
-
+import { IPageHasId, Nullable } from '@growi/core';
 
 
-export interface IPage {
-  path: string,
-  status: string,
-  revision: Ref<IRevision>,
-  tags: Ref<ITag>[],
-  creator: any,
-  createdAt: Date,
-  updatedAt: Date,
-  seenUsers: Ref<IUser>[],
-  parent: Ref<IPage> | null,
-  descendantCount: number,
-  isEmpty: boolean,
-  grant: PageGrant,
-  grantedUsers: Ref<IUser>[],
-  grantedGroup: Ref<any>,
-  lastUpdateUser: Ref<IUser>,
-  liker: Ref<IUser>[],
-  commentCount: number
-  slackChannels: string,
-  pageIdOnHackmd: string,
-  revisionHackmdSynced: Ref<IRevision>,
-  hasDraftOnHackmd: boolean,
-  deleteUser: Ref<IUser>,
-  deletedAt: Date,
-  latestRevision?: Ref<IRevision>,
-}
+import { IPageOperationProcessData } from './page-operation';
 
 
-export const PageGrant = {
-  GRANT_PUBLIC: 1,
-  GRANT_RESTRICTED: 2,
-  GRANT_SPECIFIED: 3, // DEPRECATED
-  GRANT_OWNER: 4,
-  GRANT_USER_GROUP: 5,
-};
-export type PageGrant = typeof PageGrant[keyof typeof PageGrant];
+export { PageGrant } from '@growi/core';
+export type {
+  IPage, IPageHasId, IPageInfo, IPageInfoForEntity, IPageInfoForOperation, IPageInfoForListing, IPageInfoAll,
+  IDataWithMeta, IPageWithMeta, IPageToDeleteWithMeta, IPageToRenameWithMeta,
+} from '@growi/core';
 
 
-export type IPageHasId = IPage & HasObjectId;
+export {
+  isIPageInfoForEntity, isIPageInfoForOperation, isIPageInfoForListing,
+} from '@growi/core';
 
 
 export type IPageForItem = Partial<IPageHasId & {isTarget?: boolean, processData?: IPageOperationProcessData}>;
 export type IPageForItem = Partial<IPageHasId & {isTarget?: boolean, processData?: IPageOperationProcessData}>;
 
 
-export type IPageInfo = {
-  isV5Compatible: boolean,
-  isEmpty: boolean,
-  isMovable: boolean,
-  isDeletable: boolean,
-  isAbleToDeleteCompletely: boolean,
-  isRevertible: boolean,
-  contentAge?: number,
-}
-
-export type IPageInfoForEntity = IPageInfo & {
-  bookmarkCount?: number,
-  sumOfLikers?: number,
-  likerIds?: string[],
-  sumOfSeenUsers?: number,
-  seenUserIds?: string[],
-}
-
-export type IPageInfoForOperation = IPageInfoForEntity & {
-  isBookmarked?: boolean,
-  isLiked?: boolean,
-  subscriptionStatus?: SubscriptionStatusType,
-}
-
-export type IPageInfoForListing = IPageInfoForEntity & HasRevisionShortbody;
-
-export type IPageInfoAll = IPageInfo | IPageInfoForEntity | IPageInfoForOperation | IPageInfoForListing;
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export const isIPageInfoForEntity = (pageInfo: any | undefined): pageInfo is IPageInfoForEntity => {
-  return pageInfo != null;
-};
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export const isIPageInfoForOperation = (pageInfo: any | undefined): pageInfo is IPageInfoForOperation => {
-  return pageInfo != null
-    && isIPageInfoForEntity(pageInfo)
-    && ('isBookmarked' in pageInfo || 'isLiked' in pageInfo || 'subscriptionStatus' in pageInfo);
-};
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export const isIPageInfoForListing = (pageInfo: any | undefined): pageInfo is IPageInfoForListing => {
-  return pageInfo != null
-    && isIPageInfoForEntity(pageInfo)
-    && 'revisionShortBody' in pageInfo;
-};
-
-// export type IPageInfoTypeResolver<T extends IPageInfo> =
-//   T extends HasRevisionShortbody ? IPageInfoForListing :
-//   T extends { isBookmarked?: boolean } | { isLiked?: boolean } | { subscriptionStatus?: SubscriptionStatusType } ? IPageInfoForOperation :
-//   T extends { bookmarkCount: number } ? IPageInfoForEntity :
-//   T extends { isEmpty: number } ? IPageInfo :
-//   T;
-
-/**
- * Union Distribution
- * @param pageInfo
- * @returns
- */
-// export const resolvePageInfo = <T extends IPageInfo>(pageInfo: T | undefined): IPageInfoTypeResolver<T> => {
-//   return <IPageInfoTypeResolver<T>>pageInfo;
-// };
-
-export type IDataWithMeta<D = unknown, M = unknown> = {
-  data: D,
-  meta?: M,
-}
-
-export type IPageWithMeta<M = IPageInfoAll> = IDataWithMeta<IPageHasId, M>;
-
-export type IPageToDeleteWithMeta<T = IPageInfoForEntity | unknown> = IDataWithMeta<HasObjectId & (IPage | { path: string, revision: string | null}), T>;
-export type IPageToRenameWithMeta<T = IPageInfoForEntity | unknown> = IPageToDeleteWithMeta<T>;
-
 export type IPageGrantData = {
 export type IPageGrantData = {
   grant: number,
   grant: number,
   grantedGroup?: {
   grantedGroup?: {

+ 3 - 25
packages/app/src/interfaces/revision.ts

@@ -1,25 +1,3 @@
-import { IUser } from './user';
-
-export type IRevision = {
-  body: string,
-  author: IUser,
-  hasDiffToPrev: boolean;
-  createdAt: Date,
-  updatedAt: Date,
-}
-
-export type IRevisionsForPagination = {
-  revisions: IRevision[], // revisions in one pagination
-  totalCounts: number // total counts
-}
-
-export type IRevisionOnConflict = {
-  revisionId: string,
-  revisionBody: string,
-  createdAt: Date,
-  user: IUser
-}
-
-export type HasRevisionShortbody = {
-  revisionShortBody?: string,
-}
+export type {
+  IRevision, IRevisionsForPagination, IRevisionOnConflict, HasRevisionShortbody,
+} from '@growi/core';

+ 10 - 6
packages/app/src/interfaces/search.ts

@@ -1,4 +1,4 @@
-import { IPageWithMeta } from './page';
+import { IDataWithMeta, IPageHasId } from './page';
 
 
 export type IPageSearchMeta = {
 export type IPageSearchMeta = {
   bookmarkCount?: number,
   bookmarkCount?: number,
@@ -14,10 +14,6 @@ export const isIPageSearchMeta = (meta: any): meta is IPageSearchMeta => {
   return meta != null && 'elasticSearchResult' in meta;
   return meta != null && 'elasticSearchResult' in meta;
 };
 };
 
 
-export type ISearchResult<T > = ISearchResultMeta & {
-  data: T[],
-}
-
 export type ISearchResultMeta = {
 export type ISearchResultMeta = {
   meta: {
   meta: {
     took?: number
     took?: number
@@ -26,7 +22,15 @@ export type ISearchResultMeta = {
   },
   },
 }
 }
 
 
-export type IFormattedSearchResult = ISearchResult<IPageWithMeta<IPageSearchMeta>>;
+export type ISearchResult<T> = ISearchResultMeta & {
+  data: T[],
+}
+
+export type IPageWithSearchMeta = IDataWithMeta<IPageHasId, IPageSearchMeta>;
+
+export type IFormattedSearchResult = ISearchResultMeta & {
+  data: IPageWithSearchMeta[],
+}
 
 
 export const SORT_AXIS = {
 export const SORT_AXIS = {
   RELATION_SCORE: 'relationScore',
   RELATION_SCORE: 'relationScore',

+ 1 - 6
packages/app/src/interfaces/subscription.ts

@@ -1,6 +1 @@
-export const SubscriptionStatusType = {
-  SUBSCRIBE: 'SUBSCRIBE',
-  UNSUBSCRIBE: 'UNSUBSCRIBE',
-} as const;
-export const AllSubscriptionStatusType = Object.values(SubscriptionStatusType);
-export type SubscriptionStatusType = typeof SubscriptionStatusType[keyof typeof SubscriptionStatusType];
+export { SubscriptionStatusType, AllSubscriptionStatusType } from '@growi/core';

+ 3 - 6
packages/app/src/interfaces/tag.ts

@@ -1,20 +1,17 @@
+import { ITag } from '@growi/core';
+
 import { IPageHasId } from './page';
 import { IPageHasId } from './page';
 
 
-export type ITag<ID = string> = {
-  _id: ID
-  name: string,
-}
+export type { ITag } from '@growi/core';
 
 
 export type IDataTagCount = ITag & {count: number}
 export type IDataTagCount = ITag & {count: number}
 
 
-
 export type IPageTagsInfo = {
 export type IPageTagsInfo = {
   tags : string[],
   tags : string[],
 }
 }
 
 
 export type IListTagNamesByPage = string[];
 export type IListTagNamesByPage = string[];
 
 
-
 export type IResTagsUpdateApiv1 = {
 export type IResTagsUpdateApiv1 = {
   ok: boolean,
   ok: boolean,
   savedPage: IPageHasId,
   savedPage: IPageHasId,

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