Explorar o código

Merge branch 'support/apply-nextjs-2' into feat/show-sidebar-drawer-mode

yohei0125 %!s(int64=3) %!d(string=hai) anos
pai
achega
ee75e074e9
Modificáronse 100 ficheiros con 990 adicións e 829 borrados
  1. 156 0
      packages/app/public/static/locales/en_US/admin/admin.json
  2. 156 0
      packages/app/public/static/locales/ja_JP/admin/admin.json
  3. 156 0
      packages/app/public/static/locales/zh_CN/admin/admin.json
  4. 2 2
      packages/app/src/client/app.jsx
  5. 2 10
      packages/app/src/client/base.jsx
  6. 0 2
      packages/app/src/client/legacy/crowi.js
  7. 1 24
      packages/app/src/client/services/AdminSocketIoContainer.js
  8. 10 11
      packages/app/src/client/services/AppContainer.js
  9. 6 10
      packages/app/src/client/services/ContextExtractor.tsx
  10. 1 2
      packages/app/src/client/services/page-operation.ts
  11. 1 1
      packages/app/src/components/Admin/AuditLog/ActivityTable.tsx
  12. 1 1
      packages/app/src/components/Admin/AuditLog/AuditLogSettings.tsx
  13. 2 2
      packages/app/src/components/Admin/AuditLog/SelectActionDropdown.tsx
  14. 0 1
      packages/app/src/components/Admin/ManageExternalAccount.jsx
  15. 23 24
      packages/app/src/components/Admin/UserGroup/UserGroupDeleteModal.tsx
  16. 1 1
      packages/app/src/components/Admin/UserGroup/UserGroupModal.tsx
  17. 1 1
      packages/app/src/components/Admin/UserGroup/UserGroupPage.tsx
  18. 7 11
      packages/app/src/components/Admin/UserGroup/UserGroupTable.tsx
  19. 2 1
      packages/app/src/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx
  20. 2 1
      packages/app/src/components/Admin/UserGroupDetail/UserGroupUserTable.jsx
  21. 1 1
      packages/app/src/components/InAppNotification/InAppNotificationElm.tsx
  22. 2 1
      packages/app/src/components/InAppNotification/InAppNotificationList.tsx
  23. 1 1
      packages/app/src/components/InAppNotification/PageNotification/PageModelNotification.tsx
  24. 0 223
      packages/app/src/components/MyDraftList/Draft.jsx
  25. 144 0
      packages/app/src/components/MyDraftList/Draft.tsx
  26. 1 1
      packages/app/src/components/MyDraftList/MyDraftList.jsx
  27. 2 2
      packages/app/src/components/Navbar/GlobalSearch.tsx
  28. 1 2
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  29. 3 2
      packages/app/src/components/Navbar/GrowiNavbar.tsx
  30. 1 2
      packages/app/src/components/Page/DisplaySwitcher.tsx
  31. 5 11
      packages/app/src/components/Page/RevisionLoader.jsx
  32. 2 3
      packages/app/src/components/Page/RevisionRenderer.tsx
  33. 12 10
      packages/app/src/components/PageAlert/PageGrantAlert.tsx
  34. 12 11
      packages/app/src/components/PageAlert/PageStaleAlert.tsx
  35. 1 1
      packages/app/src/components/PageAlert/TrashPageAlert.tsx
  36. 6 6
      packages/app/src/components/PageComment.tsx
  37. 7 6
      packages/app/src/components/PageComment/Comment.jsx
  38. 28 26
      packages/app/src/components/PageComment/CommentEditor.tsx
  39. 4 4
      packages/app/src/components/PageComment/CommentEditorLazyRenderer.tsx
  40. 4 2
      packages/app/src/components/PageComment/ReplayComments.jsx
  41. 2 1
      packages/app/src/components/PageContentFooter.tsx
  42. 1 2
      packages/app/src/components/PageDeleteModal.tsx
  43. 5 5
      packages/app/src/components/PageEditor.tsx
  44. 19 16
      packages/app/src/components/PageEditor/Preview.tsx
  45. 9 9
      packages/app/src/components/PageTimeline.jsx
  46. 1 2
      packages/app/src/components/SavePageControls/GrantSelector.tsx
  47. 4 4
      packages/app/src/components/SearchPage/SearchResultContent.tsx
  48. 5 12
      packages/app/src/components/Sidebar/CustomSidebar.tsx
  49. 1 2
      packages/app/src/components/Sidebar/PageTree/Item.tsx
  50. 1 1
      packages/app/src/components/Sidebar/PageTree/ItemsTree.tsx
  51. 1 4
      packages/app/src/components/Sidebar/RecentChanges.tsx
  52. 1 2
      packages/app/src/components/SubscribeButton.tsx
  53. 25 19
      packages/app/src/interfaces/activity.ts
  54. 1 11
      packages/app/src/interfaces/attachment.ts
  55. 2 2
      packages/app/src/interfaces/comment.ts
  56. 0 19
      packages/app/src/interfaces/common.ts
  57. 2 1
      packages/app/src/interfaces/external-account.ts
  58. 1 6
      packages/app/src/interfaces/global.ts
  59. 1 1
      packages/app/src/interfaces/page-grant.ts
  60. 10 116
      packages/app/src/interfaces/page.ts
  61. 3 25
      packages/app/src/interfaces/revision.ts
  62. 6 8
      packages/app/src/interfaces/services/renderer.ts
  63. 1 6
      packages/app/src/interfaces/subscription.ts
  64. 3 6
      packages/app/src/interfaces/tag.ts
  65. 1 1
      packages/app/src/interfaces/ui.ts
  66. 2 2
      packages/app/src/interfaces/user-ui-settings.ts
  67. 3 37
      packages/app/src/interfaces/user.ts
  68. 2 1
      packages/app/src/migrations/20180927102719-init-serverurl.js
  69. 2 1
      packages/app/src/migrations/20190618055300-abolish-crowi-classic-auth.js
  70. 2 1
      packages/app/src/migrations/20190618104011-add-config-app-installed.js
  71. 2 1
      packages/app/src/migrations/20200420160390-remove-crowi-layout.js
  72. 2 1
      packages/app/src/migrations/20200512005851-remove-behavior-type.js
  73. 2 1
      packages/app/src/migrations/20200514001356-update-theme-color-for-dark.js
  74. 2 1
      packages/app/src/migrations/20200620203632-normalize-locale-id.js
  75. 2 1
      packages/app/src/migrations/20200827045151-remove-layout-setting.js
  76. 2 1
      packages/app/src/migrations/20200828024025-copy-aws-setting.js
  77. 2 1
      packages/app/src/migrations/20200901034313-update-mail-transmission.js
  78. 3 1
      packages/app/src/migrations/20200903080025-remove-timeline-type.js.js
  79. 3 1
      packages/app/src/migrations/20200915035234-rename-s3-config.js
  80. 2 1
      packages/app/src/migrations/20210830074539-update-configs-for-slackbot.js
  81. 3 2
      packages/app/src/migrations/20211005131430-config-without-proxy-command-permission-for-renaming.js
  82. 3 2
      packages/app/src/migrations/20220131001218-convert-redirect-to-pages-to-page-redirect-documents.js
  83. 1 3
      packages/app/src/next-i18next.config.ts
  84. 26 26
      packages/app/src/pages/[[...path]].page.tsx
  85. 1 2
      packages/app/src/pages/commons.ts
  86. 0 1
      packages/app/src/server/crowi/index.js
  87. 3 2
      packages/app/src/server/models/page.ts
  88. 6 5
      packages/app/src/server/models/password-reset-order.ts
  89. 1 2
      packages/app/src/server/models/subscription.ts
  90. 1 1
      packages/app/src/server/models/user-registration-order.ts
  91. 10 0
      packages/app/src/server/routes/admin.js
  92. 3 1
      packages/app/src/server/routes/apiv3/export.js
  93. 1 2
      packages/app/src/server/routes/apiv3/page.js
  94. 3 3
      packages/app/src/server/service/activity.ts
  95. 1 2
      packages/app/src/server/service/in-app-notification.ts
  96. 1 2
      packages/app/src/server/service/installer.ts
  97. 3 3
      packages/app/src/server/service/page.ts
  98. 7 7
      packages/app/src/server/service/search.ts
  99. 3 3
      packages/app/src/services/renderer/PreProcessor/XssFilter.ts
  100. 7 7
      packages/app/src/services/renderer/markdown-it/blockdiag.ts

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

@@ -537,5 +537,161 @@
     "available_action_list_explain": "List of actions that can be search / view in the Audit Log",
     "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."
+  },
+  "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_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_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_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_INDICES_NORMALIZE": "Normalize of Elasticsearch indexes",
+    "ADMIN_SEARCH_INDICES_REBUILD": "Rebuild Elasticsearch indexes"
   }
 }

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

@@ -536,5 +536,161 @@
     "available_action_list_explain": "監査ログで 検索 / 表示 可能なアクション一覧です",
     "action_list": "アクション一覧",
     "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_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_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_USER_GROUP_CREATE": "ユーザーグループの作成",
+    "ADMIN_USER_GROUP_UPDATE": "ユーザーグループの更新",
+    "ADMIN_USER_GROUP_DELETE": "ユーザーグループの削除",
+    "ADMIN_USER_GROUP_ADD_USER": "ユーザーグループにユーザーを追加",
+    "ADMIN_SEARCH_INDICES_NORMALIZE": "Elasticsearch のインデックスの正規化",
+    "ADMIN_SEARCH_INDICES_REBUILD": "Elasticsearch のインデックスのリビルド"
   }
 }

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

@@ -546,5 +546,161 @@
     "available_action_list_explain": "可以在审计日志中 搜索/查看 的行动列表",
     "action_list": "行动清单",
     "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_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_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_USER_GROUP_CREATE": "创建用户组",
+    "ADMIN_USER_GROUP_UPDATE": "更新用户组",
+    "ADMIN_USER_GROUP_DELETE": "删除用户组",
+    "ADMIN_USER_GROUP_ADD_USER": "添加用户到用户组",
+    "ADMIN_SEARCH_INDICES_NORMALIZE": "Elasticsearch 索引的规范化",
+    "ADMIN_SEARCH_INDICES_REBUILD": "重建 Elasticsearch 索引"
   }
 }

+ 2 - 2
packages/app/src/client/app.jsx

@@ -10,7 +10,7 @@ import { Provider } from 'unstated';
 import ContextExtractor from '~/client/services/ContextExtractor';
 import EditorContainer from '~/client/services/EditorContainer';
 import PageContainer from '~/client/services/PageContainer';
-import IdenticalPathPage from '~/components/IdenticalPathPage';
+import { IdenticalPathPage } from '~/components/IdenticalPathPage';
 import PrivateLegacyPages from '~/components/PrivateLegacyPages';
 import loggerFactory from '~/utils/logger';
 import { swrGlobalConfiguration } from '~/utils/swr-utils';
@@ -26,7 +26,7 @@ import MyDraftList from '../components/MyDraftList/MyDraftList';
 import GrowiContextualSubNavigation from '../components/Navbar/GrowiContextualSubNavigation';
 import GrowiSubNavigationSwitcher from '../components/Navbar/GrowiSubNavigationSwitcher';
 import NotFoundPage from '../components/NotFoundPage';
-import Page from '../components/Page';
+import { Page } from '../components/Page';
 import DisplaySwitcher from '../components/Page/DisplaySwitcher';
 import RedirectedAlert from '../components/Page/RedirectedAlert';
 import ShareLinkAlert from '../components/Page/ShareLinkAlert';

+ 2 - 10
packages/app/src/client/base.jsx

@@ -3,19 +3,17 @@ import React from 'react';
 import EventEmitter from 'events';
 
 import AppContainer from '~/client/services/AppContainer';
-import SocketIoContainer from '~/client/services/SocketIoContainer';
 import { DescendantsPageListModal } from '~/components/DescendantsPageListModal';
 import PutbackPageModal from '~/components/PutbackPageModal';
 import ShortcutsModal from '~/components/ShortcutsModal';
 import SystemVersion from '~/components/SystemVersion';
 import InterceptorManager from '~/services/interceptor-manager';
-import Xss from '~/services/xss';
 import loggerFactory from '~/utils/logger';
 
 import EmptyTrashModal from '../components/EmptyTrashModal';
 import HotkeysManager from '../components/Hotkeys/HotkeysManager';
-import GrowiNavbar from '../components/Navbar/GrowiNavbar';
-import GrowiNavbarBottom from '../components/Navbar/GrowiNavbarBottom';
+import { GrowiNavbar } from '../components/Navbar/GrowiNavbar';
+import { GrowiNavbarBottom } from '../components/Navbar/GrowiNavbarBottom';
 import PageAccessoriesModal from '../components/PageAccessoriesModal';
 import PageCreateModal from '../components/PageCreateModal';
 import PageDeleteModal from '../components/PageDeleteModal';
@@ -31,17 +29,11 @@ if (!window) {
   window = {};
 }
 
-// setup xss library
-const xss = new Xss();
-window.xss = xss;
-
 window.globalEmitter = new EventEmitter();
 window.interceptorManager = new InterceptorManager();
 
 // create unstated container instance
 const appContainer = new AppContainer();
-// eslint-disable-next-line no-unused-vars
-const socketIoContainer = new SocketIoContainer(appContainer);
 
 appContainer.initApp();
 

+ 0 - 2
packages/app/src/client/legacy/crowi.js

@@ -1,8 +1,6 @@
 /* eslint-disable react/jsx-filename-extension */
 require('jquery.cookie');
 
-require('./thirdparty-js/waves');
-
 const Crowi = {};
 
 if (!window) {

+ 1 - 24
packages/app/src/client/services/AdminSocketIoContainer.js

@@ -1,25 +1,2 @@
-import SocketIoContainer from './SocketIoContainer';
-import { toastError } from '../util/apiNotification';
 
-/**
- * A subclass of SocketIoContainer for /admin namespace
- */
-export default class AdminSocketIoContainer extends SocketIoContainer {
-
-  constructor(appContainer) {
-    super(appContainer, '/admin');
-
-    // show toastr
-    this.socket.on('error', (error) => {
-      toastError(new Error(error));
-    });
-  }
-
-  /**
-   * Workaround for the mangling in production build to break constructor.name
-   */
-  static getClassName() {
-    return 'AdminSocketIoContainer';
-  }
-
-}
+export default class AdminSocketIoContainer {}

+ 10 - 11
packages/app/src/client/services/AppContainer.js

@@ -1,8 +1,5 @@
 import { Container } from 'unstated';
 
-
-import GrowiRenderer, { generatePreviewRenderer } from '~/services/renderer/growi-renderer';
-
 import { i18nFactory } from '../util/i18n';
 
 /**
@@ -59,17 +56,19 @@ export default class AppContainer extends Container {
   }
 
   injectToWindow() {
-    window.appContainer = this;
+    // for fix lint error
+
+    // window.appContainer = this;
 
-    const growiRenderer = new GrowiRenderer(this.getConfig());
-    growiRenderer.init();
+    // const growiRenderer = new GrowiRenderer(this.getConfig());
+    // growiRenderer.init();
 
-    window.growiRenderer = growiRenderer;
+    // window.growiRenderer = growiRenderer;
 
-    // backward compatibility
-    window.crowi = this;
-    window.crowiRenderer = window.growiRenderer;
-    window.crowiPlugin = window.growiPlugin;
+    // // backward compatibility
+    // window.crowi = this;
+    // window.crowiRenderer = window.growiRenderer;
+    // window.crowiPlugin = window.growiPlugin;
   }
 
   getConfig() {

+ 6 - 10
packages/app/src/client/services/ContextExtractor.tsx

@@ -6,7 +6,6 @@ import { pagePathUtils } from '@growi/core';
 import { CustomWindow } from '~/interfaces/global';
 import { IUserUISettings } from '~/interfaces/user-ui-settings';
 // import { generatePreviewRenderer } from '~/services/renderer/growi-renderer';
-import { useRendererSettings } from '~/stores/renderer';
 import {
   useIsDeviceSmallerThanMd, useIsDeviceSmallerThanLg,
   usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
@@ -23,7 +22,7 @@ import {
   useIsSearchPage, useIsForbidden, useIsIdenticalPath, useHasParent,
   useIsAclEnabled, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsEnabledAttachTitleHeader,
   useDefaultIndentSize, useIsIndentSizeForced, useCsrfToken, useGrowiVersion, useAuditLogEnabled,
-  useActivityExpirationSeconds, useAuditLogAvailableActions, useGrowiRendererConfig,
+  useActivityExpirationSeconds, useAuditLogAvailableActions, useRendererConfig,
 } from '../../stores/context';
 
 const { isTrashPage: _isTrashPage } = pagePathUtils;
@@ -120,22 +119,19 @@ const ContextExtractorOnce: FC = () => {
   useActivityExpirationSeconds(configByContextHydrate.activityExpirationSeconds);
   useAuditLogAvailableActions(configByContextHydrate.auditLogAvailableActions);
   useGrowiVersion(configByContextHydrate.crowi.version);
-  useRendererSettings({
+  useRendererConfig({
     isEnabledLinebreaks: configByContextHydrate.isEnabledLinebreaks,
     isEnabledLinebreaksInComments: configByContextHydrate.isEnabledLinebreaksInComments,
     adminPreferredIndentSize: configByContextHydrate.adminPreferredIndentSize,
     isIndentSizeForced: configByContextHydrate.isIndentSizeForced,
-  });
-  useGrowiRendererConfig({
+
     isEnabledXssPrevention: configByContextHydrate.isEnabledXssPrevention,
     attrWhiteList: configByContextHydrate.attrWhiteList,
     tagWhiteList: configByContextHydrate.tagWhiteList,
     highlightJsStyleBorder: configByContextHydrate.highlightJsStyleBorder,
-    env: {
-      MATHJAX: configByContextHydrate.env.MATHJAX,
-      PLANTUML_URI: configByContextHydrate.env.PLANTUML_URI,
-      BLOCKDIAG_URI: configByContextHydrate.env.BLOCKDIAG_URI,
-    },
+
+    plantumlUri: configByContextHydrate.env.PLANTUML_URI,
+    blockdiagUri: configByContextHydrate.env.BLOCKDIAG_URI,
   });
 
   // Page

+ 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 { SubscriptionStatusType } from '~/interfaces/subscription';
-
 import { toastError } from '../util/apiNotification';
 import { apiv3Post, apiv3Put } from '../util/apiv3-client';
 

+ 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}>
                 <td>{activity.snapshot?.username}</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.endpoint}</td>
               </tr>

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

@@ -45,7 +45,7 @@ export const AuditLogSettings: FC = () => {
       <Collapse isOpen={isExpandActionList}>
         <ul className="list-group">
           { 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>
       </Collapse>

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

@@ -91,7 +91,7 @@ export const SelectActionDropdown: FC<Props> = (props: Props) => {
                   defaultChecked
                   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>
             {
@@ -109,7 +109,7 @@ export const SelectActionDropdown: FC<Props> = (props: Props) => {
                       className="form-check-label"
                       htmlFor={`checkbox${action}`}
                     >
-                      {action}
+                      {t(`admin:audit_log_action.${action}`)}
                     </label>
                   </div>
                 </div>

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

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

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

@@ -9,7 +9,7 @@ import {
 } from 'reactstrap';
 
 import { IUserGroupHasId } from '~/interfaces/user';
-import { useXss } from '~/stores/xss';
+
 /**
  * Delete User Group Select component
  *
@@ -41,10 +41,13 @@ const actionForPages = {
 };
 
 const UserGroupDeleteModal: FC<Props> = (props: Props) => {
-  const { data: xss } = useXss();
 
   const { t } = useTranslation();
 
+  const {
+    onHide, onDelete, userGroups, deleteUserGroup,
+  } = props;
+
   const availableOptions = useMemo<AvailableOption[]>(() => {
     return [
       {
@@ -69,7 +72,7 @@ const UserGroupDeleteModal: FC<Props> = (props: Props) => {
         label: t('admin:user_group_management.delete_modal.transfer_pages'),
       },
     ];
-  }, []);
+  }, [t]);
 
   /*
    * State
@@ -85,14 +88,14 @@ const UserGroupDeleteModal: FC<Props> = (props: Props) => {
     setTransferToUserGroupId('');
   }, []);
 
-  const onHide = useCallback(() => {
-    if (props.onHide == null) {
+  const toggleHandler = useCallback(() => {
+    if (onHide == null) {
       return;
     }
 
     resetStates();
-    props.onHide();
-  }, [props.onHide]);
+    onHide();
+  }, [onHide, resetStates]);
 
   const handleActionChange = useCallback((e) => {
     const actionName = e.target.value;
@@ -105,23 +108,22 @@ const UserGroupDeleteModal: FC<Props> = (props: Props) => {
   }, []);
 
   const handleSubmit = useCallback((e) => {
-    if (props.onDelete == null || props.deleteUserGroup == null) {
+    if (onDelete == null || deleteUserGroup == null) {
       return;
     }
 
     e.preventDefault();
 
-    props.onDelete(
-      props.deleteUserGroup._id,
+    onDelete(
+      deleteUserGroup._id,
       actionName,
       transferToUserGroupId,
     );
-  }, [props.onDelete, props.deleteUserGroup, actionName, transferToUserGroupId]);
+  }, [onDelete, deleteUserGroup, actionName, transferToUserGroupId]);
 
   const renderPageActionSelector = useCallback(() => {
     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 (
@@ -133,25 +135,22 @@ const UserGroupDeleteModal: FC<Props> = (props: Props) => {
         onChange={handleActionChange}
       >
         <option value="" disabled>{t('admin:user_group_management.delete_modal.dropdown_desc')}</option>
-        {options}
+        {...options}
       </select>
     );
-  }, [handleActionChange, actionName, availableOptions]);
+  }, [availableOptions, actionName, handleActionChange, t]);
 
   const renderGroupSelector = useCallback(() => {
-    const { deleteUserGroup } = props;
-
     if (deleteUserGroup == null) {
       return;
     }
 
-    const groups = props.userGroups.filter((group) => {
+    const groups = userGroups.filter((group) => {
       return group._id !== deleteUserGroup._id;
     });
 
     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')
@@ -165,10 +164,10 @@ const UserGroupDeleteModal: FC<Props> = (props: Props) => {
         onChange={handleGroupChange}
       >
         <option value="" disabled>{defaultOptionText}</option>
-        {options}
+        {...options}
       </select>
     );
-  }, [actionName, transferToUserGroupId, props.userGroups, props.deleteUserGroup]);
+  }, [deleteUserGroup, userGroups, t, actionName, transferToUserGroupId, handleGroupChange]);
 
   const validateForm = useCallback(() => {
     let isValid = true;
@@ -184,8 +183,8 @@ const UserGroupDeleteModal: FC<Props> = (props: Props) => {
   }, [actionName, transferToUserGroupId]);
 
   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')}
       </ModalHeader>
       <ModalBody>

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

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

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

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

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

@@ -1,15 +1,12 @@
 import React, {
-  FC, useState, useCallback, useEffect,
+  FC, useState, useEffect,
 } from 'react';
 
 import dateFnsFormat from 'date-fns/format';
 import { TFunctionResult } from 'i18next';
 import { useTranslation } from 'next-i18next';
 
-import { CustomWindow } from '~/interfaces/global';
 import { IUserGroupHasId, IUserGroupRelation, IUserHasId } from '~/interfaces/user';
-import Xss from '~/services/xss';
-import { useXss } from '~/stores/xss';
 
 type Props = {
   headerLabel?: TFunctionResult,
@@ -57,7 +54,6 @@ const generateGroupIdToChildGroupsMap = (childUserGroups: IUserGroupHasId[]): Re
 
 
 const UserGroupTable: FC<Props> = (props: Props) => {
-  const { data: xss } = useXss();
   const { t } = useTranslation();
 
   /*
@@ -152,17 +148,17 @@ const UserGroupTable: FC<Props> = (props: Props) => {
               <tr key={group._id}>
                 {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>
                   <ul className="list-inline">
                     {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>
                 </td>
@@ -173,10 +169,10 @@ const UserGroupTable: FC<Props> = (props: Props) => {
                         <li key={group._id} className="list-inline-item badge badge-success">
                           {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>

+ 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 AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
+import Xss from '~/services/xss';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
@@ -25,7 +26,7 @@ class UserGroupUserFormByInput extends React.Component {
       searchError: null,
     };
 
-    this.xss = window.xss;
+    this.xss = new Xss();
 
     this.addUserBySubmit = this.addUserBySubmit.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 AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
+import Xss from '~/services/xss';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
@@ -16,7 +17,7 @@ class UserGroupUserTable extends React.Component {
   constructor(props) {
     super(props);
 
-    this.xss = window.xss;
+    this.xss = new Xss();
 
     this.removeUser = this.removeUser.bind(this);
   }

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

@@ -2,12 +2,12 @@ import React, {
   FC, useRef,
 } from 'react';
 
+import { HasObjectId } from '@growi/core';
 import { UserPicture } from '@growi/ui';
 import { DropdownItem } from 'reactstrap';
 
 import { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';
 import { apiv3Post } from '~/client/util/apiv3-client';
-import { HasObjectId } from '~/interfaces/has-object-id';
 import { IInAppNotification, InAppNotificationStatuses } from '~/interfaces/in-app-notification';
 
 // 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 { HasObjectId } from '@growi/core';
+
 import { IInAppNotification, PaginateResult } from '~/interfaces/in-app-notification';
-import { HasObjectId } from '~/interfaces/has-object-id';
 
 import InAppNotificationElm from './InAppNotificationElm';
 

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

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

+ 0 - 223
packages/app/src/components/MyDraftList/Draft.jsx

@@ -1,223 +0,0 @@
-import React from 'react';
-
-import PropTypes from 'prop-types';
-import { CopyToClipboard } from 'react-copy-to-clipboard';
-import { useTranslation } from 'next-i18next';
-import {
-  Collapse,
-  UncontrolledTooltip,
-} from 'reactstrap';
-
-import AppContainer from '~/client/services/AppContainer';
-import GrowiRenderer from '~/services/renderer/growi-renderer';
-import { useDraftRenderer } from '~/stores/renderer';
-
-import RevisionBody from '../Page/RevisionBody';
-import { withUnstatedContainers } from '../UnstatedUtils';
-
-class Draft extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      html: '',
-      isRendered: false,
-      isPanelExpanded: false,
-      showCopiedMessage: false,
-    };
-
-    this.growiRenderer = this.props.growiRenderer;
-
-    this.changeToolTipLabel = this.changeToolTipLabel.bind(this);
-    this.expandPanelHandler = this.expandPanelHandler.bind(this);
-    this.collapsePanelHandler = this.collapsePanelHandler.bind(this);
-    this.renderHtml = this.renderHtml.bind(this);
-    this.renderAccordionTitle = this.renderAccordionTitle.bind(this);
-  }
-
-  changeToolTipLabel() {
-    this.setState({ showCopiedMessage: true });
-    setTimeout(() => {
-      this.setState({ showCopiedMessage: false });
-    }, 1000);
-  }
-
-  expandPanelHandler() {
-    this.setState({ isPanelExpanded: true });
-
-    if (!this.state.isRendered) {
-      this.renderHtml();
-    }
-  }
-
-  collapsePanelHandler() {
-    this.setState({ isPanelExpanded: false });
-  }
-
-  async renderHtml() {
-    const context = {
-      markdown: this.props.markdown,
-    };
-
-    const growiRenderer = this.growiRenderer;
-    const { interceptorManager } = window;
-    await interceptorManager.process('prePreProcess', context)
-      .then(() => {
-        context.markdown = growiRenderer.preProcess(context.markdown, context);
-      })
-      .then(() => { return interceptorManager.process('postPreProcess', context) })
-      .then(() => {
-        const parsedHTML = growiRenderer.process(context.markdown, context);
-        context.parsedHTML = parsedHTML;
-      })
-      .then(() => { return interceptorManager.process('prePostProcess', context) })
-      .then(() => {
-        context.parsedHTML = growiRenderer.postProcess(context.parsedHTML, context);
-      })
-      .then(() => { return interceptorManager.process('postPostProcess', context) })
-      .then(() => {
-        this.setState({ html: context.parsedHTML, isRendered: true });
-      });
-  }
-
-  renderAccordionTitle(isExist) {
-    const { isPanelExpanded } = this.state;
-    const { t } = this.props;
-    const iconClass = isPanelExpanded ? 'fa-rotate-90' : '';
-
-    return (
-      <span>
-
-        <span className="mr-2 draft-path" onClick={() => this.setState({ isPanelExpanded: !isPanelExpanded })}>
-          <i className={`fa fa-fw fa-angle-right mr-2 ${iconClass}`}></i>
-          {this.props.path}
-        </span>
-        { isExist && (
-          <span className="badge badge-warning">{t('page exists')}</span>
-        ) }
-        { !isExist && (
-          <span className="badge badge-info">draft</span>
-        ) }
-
-        <a className="ml-2" href={this.props.path}><i className="icon icon-login"></i></a>
-      </span>
-    );
-  }
-
-  renderControls() {
-    const { t, path, index } = this.props;
-
-    const tooltipTargetId = `draft-copied-tooltip_${index}`;
-
-    return (
-      <div className="icon-container">
-        {this.props.isExist
-          ? null
-          : (
-            <a
-              href={`${path}#edit`}
-              target="_blank"
-              rel="noopener noreferrer"
-              data-toggle="tooltip"
-              title={this.props.t('Edit')}
-            >
-              <i className="mx-2 icon-note" />
-            </a>
-          )
-        }
-        <span id={tooltipTargetId}>
-          <CopyToClipboard text={this.props.markdown} onCopy={this.changeToolTipLabel}>
-            <a
-              className="text-center draft-copy"
-            >
-              <i className="mx-2 ti-clipboard" />
-            </a>
-          </CopyToClipboard>
-        </span>
-        <UncontrolledTooltip placement="top" target={tooltipTargetId} fade={false} trigger="hover">
-          { this.state.showCopiedMessage && (
-            <strong>copied!</strong>
-          ) }
-          { !this.state.showCopiedMessage && (
-            <span>{this.props.t('Copy')}</span>
-          ) }
-        </UncontrolledTooltip>
-        <a
-          className="text-danger text-center"
-          data-toggle="tooltip"
-          data-placement="top"
-          title={t('Delete')}
-          onClick={() => { return this.props.clearDraft(this.props.path) }}
-        >
-          <i className="mx-2 icon-trash" />
-        </a>
-      </div>
-    );
-  }
-
-  render() {
-    const { isPanelExpanded } = this.state;
-
-    return (
-      <div className="accordion draft-list-item" role="tablist">
-        <div className="card">
-
-          <div className="card-header d-flex" role="tab">
-            {this.renderAccordionTitle(this.props.isExist)}
-
-            <div className="flex-grow-1"></div>
-
-            {this.renderControls()}
-          </div>
-
-          <Collapse isOpen={isPanelExpanded} onEntering={this.expandPanelHandler} onExiting={this.collapsePanelHandler}>
-            <div className="card-body">
-              {/* loading spinner */}
-              { this.state.isPanelExpanded && !this.state.isRendered && (
-                <div className="text-center">
-                  <i className="fa fa-lg fa-spinner fa-pulse mx-auto text-muted"></i>
-                </div>
-              ) }
-              {/* contents */}
-              { this.state.isPanelExpanded && this.state.isRendered && (
-                <RevisionBody html={this.state.html} />
-              ) }
-            </div>
-          </Collapse>
-
-        </div>
-      </div>
-    );
-  }
-
-}
-
-Draft.propTypes = {
-  t: PropTypes.func.isRequired,
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  growiRenderer: PropTypes.instanceOf(GrowiRenderer).isRequired,
-
-  index: PropTypes.number.isRequired,
-  path: PropTypes.string.isRequired,
-  markdown: PropTypes.string.isRequired,
-  isExist: PropTypes.bool.isRequired,
-  clearDraft: PropTypes.func.isRequired,
-};
-
-const DraftWrapperFC = (props) => {
-  const { t } = useTranslation();
-  const { data: growiRenderer } = useDraftRenderer();
-  if (growiRenderer == null) {
-    return <></>;
-  }
-
-  return <Draft t={t} growiRenderer={growiRenderer} {...props} />;
-};
-
-/**
- * Wrapper component for using unstated
- */
-const DraftWrapper = withUnstatedContainers(DraftWrapperFC, [AppContainer]);
-
-export default DraftWrapper;

+ 144 - 0
packages/app/src/components/MyDraftList/Draft.tsx

@@ -0,0 +1,144 @@
+import React, { useState } from 'react';
+
+import { useTranslation } from 'next-i18next';
+import { CopyToClipboard } from 'react-copy-to-clipboard';
+import ReactMarkdown from 'react-markdown';
+import {
+  Collapse,
+  UncontrolledTooltip,
+} from 'reactstrap';
+
+import { useDraftOptions } from '~/stores/renderer';
+
+type DraftProps = {
+  path: string,
+  isExist: boolean,
+  index: number,
+  markdown: string,
+  clearDraft: (path: string) => void,
+}
+
+export const Draft = (props: DraftProps): JSX.Element => {
+
+  const {
+    path, isExist, index, markdown, clearDraft,
+  } = props;
+  const { t } = useTranslation();
+  const { data: rendererOptions } = useDraftOptions();
+  const [isPanelExpanded, setIsPanelExpanded] = useState(false);
+  const [showCopiedMessage, setShowCopiedMessage] = useState(false);
+
+  const changeToolTipLabel = () => {
+    setShowCopiedMessage(true);
+    setTimeout(() => {
+      setShowCopiedMessage(false);
+    }, 1000);
+  };
+
+  const collapsePanelHandler = () => {
+    setIsPanelExpanded(false);
+  };
+
+  const expandPanelHandler = () => {
+    setIsPanelExpanded(true);
+  };
+
+  const Controls = () => {
+
+    const tooltipTargetId = `draft-copied-tooltip_${index}`;
+
+    return (
+      <div className="icon-container">
+        {isExist
+          ? null
+          : (
+            <a
+              href={`${path}#edit`}
+              target="_blank"
+              rel="noopener noreferrer"
+              data-toggle="tooltip"
+              title={t('Edit')}
+            >
+              <i className="mx-2 icon-note" />
+            </a>
+          )
+        }
+        <span id={tooltipTargetId}>
+          <CopyToClipboard text={markdown} onCopy={changeToolTipLabel}>
+            <a
+              className="text-center draft-copy"
+            >
+              <i className="mx-2 ti-clipboard" />
+            </a>
+          </CopyToClipboard>
+        </span>
+        <UncontrolledTooltip placement="top" target={tooltipTargetId} fade={false} trigger="hover">
+          { showCopiedMessage && (
+            <strong>copied!</strong>
+          ) }
+          { !showCopiedMessage && (
+            <span>{t('Copy')}</span>
+          ) }
+        </UncontrolledTooltip>
+        <a
+          className="text-danger text-center"
+          data-toggle="tooltip"
+          data-placement="top"
+          title={t('Delete')}
+          onClick={() => { return clearDraft(path) }}
+        >
+          <i className="mx-2 icon-trash" />
+        </a>
+      </div>
+    );
+  };
+
+  const AccordionTitle = () => {
+    const iconClass = isPanelExpanded ? 'fa-rotate-90' : '';
+
+    return (
+      <span>
+
+        <span className="mr-2 draft-path" onClick={() => setIsPanelExpanded(!isPanelExpanded)}>
+          <i className={`fa fa-fw fa-angle-right mr-2 ${iconClass}`}></i>
+          {path}
+        </span>
+        { isExist && (
+          <span className="badge badge-warning">{t('page exists')}</span>
+        ) }
+        { !isExist && (
+          <span className="badge badge-info">draft</span>
+        ) }
+
+        <a className="ml-2" href={path}><i className="icon icon-login"></i></a>
+      </span>
+    );
+  };
+
+
+  return (
+    <div className="accordion draft-list-item" role="tablist">
+      <div className="card">
+
+        <div className="card-header d-flex" role="tab">
+          <AccordionTitle/>
+
+          <div className="flex-grow-1"></div>
+
+          <Controls/>
+        </div>
+
+        <Collapse isOpen={isPanelExpanded} onEntering={expandPanelHandler} onExiting={collapsePanelHandler}>
+          <div className="card-body">
+            { isPanelExpanded && (
+              <ReactMarkdown {...rendererOptions} className='wiki'>
+                {markdown}
+              </ReactMarkdown>
+            ) }
+          </div>
+        </Collapse>
+
+      </div>
+    </div>
+  );
+};

+ 1 - 1
packages/app/src/components/MyDraftList/MyDraftList.jsx

@@ -10,7 +10,7 @@ import { apiGet } from '~/client/util/apiv1-client';
 import PaginationWrapper from '../PaginationWrapper';
 import { withUnstatedContainers } from '../UnstatedUtils';
 
-import Draft from './Draft';
+import { Draft } from './Draft';
 
 class MyDraftList extends React.Component {
 

+ 2 - 2
packages/app/src/components/Navbar/GlobalSearch.tsx

@@ -18,11 +18,11 @@ import SearchForm from '../SearchForm';
 import styles from './GlobalSearch.module.scss';
 
 
-type Props = {
+export type GlobalSearchProps = {
   dropup?: boolean,
 }
 
-export const GlobalSearch = (props: Props): JSX.Element => {
+export const GlobalSearch = (props: GlobalSearchProps): JSX.Element => {
   const { t } = useTranslation();
 
   const { dropup } = props;

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

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

+ 3 - 2
packages/app/src/components/Navbar/GrowiNavbar.tsx

@@ -9,18 +9,19 @@ import Link from 'next/link';
 import { useRipple } from 'react-use-ripple';
 import { UncontrolledTooltip } from 'reactstrap';
 
-import { HasChildren } from '~/interfaces/common';
 import {
   useIsSearchPage, useCurrentPagePath, useIsGuestUser, useIsSearchServiceConfigured, useAppTitle, useConfidential,
 } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
 import { useIsDeviceSmallerThanMd } from '~/stores/ui';
 
+import { HasChildren } from '../../interfaces/common';
 import GrowiLogo from '../Icons/GrowiLogo';
 
 import PersonalDropdown from './PersonalDropdown';
 
 import styles from './GrowiNavbar.module.scss';
+import { GlobalSearchProps } from './GlobalSearch';
 
 
 const ShowSkeltonInSSR = memo(({ children }: HasChildren): JSX.Element => {
@@ -130,7 +131,7 @@ Confidential.displayName = 'Confidential';
 
 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: 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 { useTranslation } from 'next-i18next';
@@ -6,7 +6,6 @@ import dynamic from 'next/dynamic';
 import { TabContent, TabPane } from 'reactstrap';
 
 import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
-import { isPopulated } from '~/interfaces/common';
 import {
   useCurrentPagePath, useIsSharedUser, useIsEditable, useIsUserPage, usePageUser, useShareLinkId, useIsNotFound, useIsNotCreatable,
 } from '~/stores/context';

+ 5 - 11
packages/app/src/components/Page/RevisionLoader.jsx

@@ -1,12 +1,11 @@
 import React from 'react';
 
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 import { Waypoint } from 'react-waypoint';
 
 import { apiv3Get } from '~/client/util/apiv3-client';
-import GrowiRenderer from '~/services/renderer/growi-renderer';
-import { useViewRenderer } from '~/stores/renderer';
+import { RendererOptions } from '~/services/renderer/renderer';
 import loggerFactory from '~/utils/logger';
 
 import RevisionRenderer from './RevisionRenderer';
@@ -110,7 +109,7 @@ class RevisionLoader extends React.Component {
 
     return (
       <RevisionRenderer
-        growiRenderer={this.props.growiRenderer}
+        rendererOptions={this.props.rendererOptions}
         markdown={markdown}
         pagePath={this.props.pagePath}
         highlightKeywords={this.props.highlightKeywords}
@@ -124,7 +123,7 @@ class RevisionLoader extends React.Component {
 RevisionLoader.propTypes = {
   t: PropTypes.func.isRequired,
 
-  growiRenderer: PropTypes.instanceOf(GrowiRenderer).isRequired,
+  rendererOptions: PropTypes.instanceOf(RendererOptions).isRequired,
   pageId: PropTypes.string.isRequired,
   pagePath: PropTypes.string.isRequired,
   revisionId: PropTypes.string.isRequired,
@@ -135,12 +134,7 @@ RevisionLoader.propTypes = {
 
 const RevisionLoaderWrapperFC = (props) => {
   const { t } = useTranslation();
-  const { data: growiRenderer } = useViewRenderer();
-  if (growiRenderer == null) {
-    return <></>;
-  }
-
-  return <RevisionLoader t={t} growiRenderer={growiRenderer} {...props} />;
+  return <RevisionLoader t={t} {...props} />;
 };
 
 export default RevisionLoaderWrapperFC;

+ 2 - 3
packages/app/src/components/Page/RevisionRenderer.tsx

@@ -5,8 +5,7 @@ import ReactMarkdown from 'react-markdown';
 import { blinkElem } from '~/client/util/blink-section-header';
 import { addSmoothScrollEvent } from '~/client/util/smooth-scroll';
 import { CustomWindow } from '~/interfaces/global';
-// import GrowiRenderer from '~/services/renderer/growi-renderer';
-import { RendererOptions } from '~/services/renderer/growi-renderer';
+import { RendererOptions } from '~/services/renderer/renderer';
 import { useCurrentPathname, useInterceptorManager } from '~/stores/context';
 import { useEditorSettings } from '~/stores/editor';
 import { useViewOptions } from '~/stores/renderer';
@@ -48,7 +47,7 @@ const logger = loggerFactory('components:Page:RevisionRenderer');
 
 //   // for non-chrome browsers compatibility
 //   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
 //   }
 //   catch (err) {

+ 12 - 10
packages/app/src/components/PageAlert/PageGrantAlert.tsx

@@ -1,7 +1,9 @@
 import React from 'react';
+
+import { useTranslation } from 'react-i18next';
+
 import { useSWRxCurrentPage } from '~/stores/page';
 import { useXss } from '~/stores/xss';
-import { useTranslation } from 'react-i18next';
 
 
 export const PageGrantAlert = (): JSX.Element => {
@@ -9,32 +11,32 @@ export const PageGrantAlert = (): JSX.Element => {
   const { data: pageData } = useSWRxCurrentPage();
   const { data: xss } = useXss();
 
-  if ( pageData == null || pageData.grant == null || pageData.grant == 1 || xss == null) {
-    return <></>
+  if (pageData == null || pageData.grant == null || pageData.grant === 1 || xss == null) {
+    return <></>;
   }
 
   const renderAlertContent = () => {
     const getGrantLabel = () => {
-      if (pageData.grant == 2) {
+      if (pageData.grant === 2) {
         return (
           <>
             <i className="icon-fw icon-link"></i><strong>{t('Anyone with the link')} only</strong>
           </>
-        )
+        );
       }
-      if (pageData.grant == 4) {
+      if (pageData.grant === 4) {
         return (
           <>
             <i className="icon-fw icon-lock"></i><strong>{t('Only me')} only</strong>
           </>
-        )
+        );
       }
-      if (pageData.grant == 5) {
+      if (pageData.grant === 5) {
         return (
           <>
             <i className="icon-fw icon-organization"></i><strong>{xss.process(pageData.grantedGroup.name)} only</strong>
           </>
-        )
+        );
       }
     };
     return (
@@ -50,4 +52,4 @@ export const PageGrantAlert = (): JSX.Element => {
       {renderAlertContent()}
     </p>
   );
-}
+};

+ 12 - 11
packages/app/src/components/PageAlert/PageStaleAlert.tsx

@@ -1,9 +1,10 @@
-import { useIsEnabledStaleNotification } from '../../stores/context'
-import { useSWRxCurrentPage, useSWRxPageInfo } from '../../stores/page'
 import { useTranslation } from 'react-i18next';
 
+import { useIsEnabledStaleNotification } from '../../stores/context';
+import { useSWRxCurrentPage, useSWRxPageInfo } from '../../stores/page';
+
 export const PageStaleAlert = ():JSX.Element => {
-  const { t } = useTranslation()
+  const { t } = useTranslation();
   const { data: isEnabledStaleNotification } = useIsEnabledStaleNotification();
 
   // Todo: determine if it should fetch or not like useSWRxPageInfo below after https://redmine.weseek.co.jp/issues/96788
@@ -13,23 +14,23 @@ export const PageStaleAlert = ():JSX.Element => {
   const contentAge = pageInfo?.contentAge;
 
   if (!isEnabledStaleNotification) {
-    return <></>
+    return <></>;
   }
 
-  if( pageInfo == null || contentAge == null || contentAge === 0) {
-    return <></>
+  if (pageInfo == null || contentAge == null || contentAge === 0) {
+    return <></>;
   }
 
   let alertClass;
   switch (contentAge) {
     case 1:
-      alertClass = "alert-info";
+      alertClass = 'alert-info';
       break;
     case 2:
-      alertClass = "alert-warning";
+      alertClass = 'alert-warning';
       break;
     default:
-      alertClass = "alert-danger";
+      alertClass = 'alert-danger';
   }
 
   return (
@@ -37,5 +38,5 @@ export const PageStaleAlert = ():JSX.Element => {
       <i className="icon-fw icon-hourglass"></i>
       <strong>{ t('page_page.notice.stale', { count: pageInfo.contentAge }) }</strong>
     </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 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) {
     return <></>;

+ 6 - 6
packages/app/src/components/PageComment.tsx

@@ -8,7 +8,7 @@ import { Button } from 'reactstrap';
 import AppContainer from '~/client/services/AppContainer';
 import { toastError } from '~/client/util/apiNotification';
 import { apiPost } from '~/client/util/apiv1-client';
-import { useCommentPreviewRenderer } from '~/stores/renderer';
+import { useCommentPreviewOptions } from '~/stores/renderer';
 
 import { ICommentHasId, ICommentHasIdList } from '../interfaces/comment';
 import { useSWRxPageComment } from '../stores/comment';
@@ -36,7 +36,7 @@ const PageComment:FC<Props> = memo((props:Props):JSX.Element => {
   } = props;
 
   const { data: comments, mutate } = useSWRxPageComment(pageId);
-  const { data: growiRenderer } = useCommentPreviewRenderer();
+  const { data: rendererOptions } = useCommentPreviewOptions();
 
   const [commentToBeDeleted, setCommentToBeDeleted] = useState<ICommentHasId | null>(null);
   const [isDeleteConfirmModalShown, setIsDeleteConfirmModalShown] = useState<boolean>(false);
@@ -112,7 +112,7 @@ const PageComment:FC<Props> = memo((props:Props):JSX.Element => {
 
   const generateCommentInnerElement = (comment: ICommentHasId) => (
     <Comment
-      growiRenderer={growiRenderer}
+      rendererOptions={rendererOptions}
       deleteBtnClicked={onClickDeleteButton}
       comment={comment}
       onComment={mutate}
@@ -124,7 +124,7 @@ const PageComment:FC<Props> = memo((props:Props):JSX.Element => {
     <ReplayComments
       replyList={replyComments}
       deleteBtnClicked={onClickDeleteButton}
-      growiRenderer={growiRenderer}
+      rendererOptions={rendererOptions}
       isReadOnly={isReadOnly}
     />
   );
@@ -144,7 +144,7 @@ const PageComment:FC<Props> = memo((props:Props):JSX.Element => {
     return <></>;
   }
 
-  if (growiRenderer == null) {
+  if (rendererOptions == null) {
     return <></>;
   }
 
@@ -191,7 +191,7 @@ const PageComment:FC<Props> = memo((props:Props):JSX.Element => {
                     {/* display reply editor */}
                     {(!isReadOnly && showEditorIds.has(comment._id)) && (
                       <CommentEditor
-                        growiRenderer={growiRenderer}
+                        rendererOptions={rendererOptions}
                         replyTo={comment._id}
                         onCancelButtonClicked={() => {
                           removeShowEditorId(comment._id);

+ 7 - 6
packages/app/src/components/PageComment/Comment.jsx

@@ -19,6 +19,7 @@ import Username from '../User/Username';
 import CommentControl from './CommentControl';
 import CommentEditor from './CommentEditor';
 
+import { RendererOptions } from '~/services/renderer/renderer';
 
 /**
  *
@@ -132,17 +133,17 @@ class Comment extends React.PureComponent {
 
   async renderHtml() {
 
-    const { growiRenderer, appContainer } = this.props;
+    const { rendererOptions, appContainer } = this.props;
     const { interceptorManager } = window;
     const context = this.currentRenderingContext;
 
     await interceptorManager.process('preRenderComment', context);
     await interceptorManager.process('prePreProcess', context);
-    context.markdown = await growiRenderer.preProcess(context.markdown, context);
+    context.markdown = await rendererOptions.preProcess(context.markdown, context);
     await interceptorManager.process('postPreProcess', context);
-    context.parsedHTML = await growiRenderer.process(context.markdown, context);
+    context.parsedHTML = await rendererOptions.process(context.markdown, context);
     await interceptorManager.process('prePostProcess', context);
-    context.parsedHTML = await growiRenderer.postProcess(context.parsedHTML, context);
+    context.parsedHTML = await rendererOptions.postProcess(context.parsedHTML, context);
     await interceptorManager.process('postPostProcess', context);
     await interceptorManager.process('preRenderCommentHtml', context);
     this.setState({ html: context.parsedHTML });
@@ -173,7 +174,7 @@ class Comment extends React.PureComponent {
       <React.Fragment>
         {(this.state.isReEdit && !isReadOnly) ? (
           <CommentEditor
-            growiRenderer={this.props.growiRenderer}
+            rendererOptions={this.props.rendererOptions}
             currentCommentId={commentId}
             commentBody={comment.comment}
             replyTo={undefined}
@@ -236,7 +237,7 @@ Comment.propTypes = {
 
   comment: PropTypes.object.isRequired,
   isReadOnly: PropTypes.bool.isRequired,
-  growiRenderer: PropTypes.object.isRequired,
+  rendererOptions: PropTypes.instanceOf(RendererOptions).isRequired,
   deleteBtnClicked: PropTypes.func.isRequired,
   currentUser: PropTypes.object,
   onComment: PropTypes.func,

+ 28 - 26
packages/app/src/components/PageComment/CommentEditor.tsx

@@ -15,7 +15,7 @@ import PageContainer from '~/client/services/PageContainer';
 import { apiPostForm } from '~/client/util/apiv1-client';
 import { CustomWindow } from '~/interfaces/global';
 import { IInterceptorManager } from '~/interfaces/interceptor-manager';
-import GrowiRenderer from '~/services/renderer/growi-renderer';
+import { RendererOptions } from '~/services/renderer/renderer';
 import { useSWRxPageComment } from '~/stores/comment';
 import {
   useCurrentPagePath, useCurrentPageId, useCurrentUser, useRevisionId,
@@ -49,7 +49,7 @@ const navTabMapping = {
 type PropsType = {
   appContainer: AppContainer,
 
-  growiRenderer: GrowiRenderer,
+  rendererOptions: RendererOptions,
   isForNewComment?: boolean,
   replyTo?: string,
   currentCommentId?: string,
@@ -68,7 +68,7 @@ type EditorRef = {
 const CommentEditor = (props: PropsType): JSX.Element => {
 
   const {
-    appContainer, growiRenderer, isForNewComment, replyTo,
+    appContainer, rendererOptions, isForNewComment, replyTo,
     currentCommentId, commentBody, commentCreator, onCancelButtonClicked, onCommentButtonClicked,
   } = props;
   const { data: currentUser } = useCurrentUser();
@@ -100,29 +100,31 @@ const CommentEditor = (props: PropsType): JSX.Element => {
       parsedHTML: '',
     };
 
-    const interceptorManager: IInterceptorManager = (window as CustomWindow).interceptorManager;
-    interceptorManager.process('preRenderCommnetPreview', context)
-      .then(() => { return interceptorManager.process('prePreProcess', context) })
-      .then(() => {
-        context.markdown = growiRenderer.preProcess(context.markdown, context);
-      })
-      .then(() => { return interceptorManager.process('postPreProcess', context) })
-      .then(() => {
-        const parsedHTML = growiRenderer.process(context.markdown, context);
-        context.parsedHTML = parsedHTML;
-      })
-      .then(() => { return interceptorManager.process('prePostProcess', context) })
-      .then(() => {
-        context.parsedHTML = growiRenderer.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) });
-  }, [growiRenderer]);
+    // 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]);
 
   const handleSelect = useCallback((activeTab: string) => {
     setActiveTab(activeTab);

+ 4 - 4
packages/app/src/components/PageComment/CommentEditorLazyRenderer.tsx

@@ -5,7 +5,7 @@ import { useSWRxPageComment } from '../../stores/comment';
 import AppContainer from '~/client/services/AppContainer';
 
 import CommentEditor from './CommentEditor';
-import { useCommentPreviewRenderer } from '~/stores/renderer';
+import { useCommentPreviewOptions } from '~/stores/renderer';
 
 type Props = {
   appContainer: AppContainer,
@@ -16,9 +16,9 @@ const CommentEditorLazyRenderer:FC<Props> = (props:Props):JSX.Element => {
 
   const { pageId } = props;
   const { mutate } = useSWRxPageComment(pageId);
-  const { data: growiRenderer } = useCommentPreviewRenderer();
+  const { data: rendererOptions } = useCommentPreviewOptions();
 
-  if (growiRenderer == null) {
+  if (rendererOptions == null) {
     return <></>;
   }
 
@@ -27,7 +27,7 @@ const CommentEditorLazyRenderer:FC<Props> = (props:Props):JSX.Element => {
   return (
     <CommentEditor
       appContainer={appContainer}
-      growiRenderer={growiRenderer}
+      rendererOptions={rendererOptions}
       replyTo={undefined}
       onCommentButtonClicked={mutate}
       isForNewComment

+ 4 - 2
packages/app/src/components/PageComment/ReplayComments.jsx

@@ -10,6 +10,8 @@ import Comment from './Comment';
 
 import { withUnstatedContainers } from '../UnstatedUtils';
 
+import { RendererOptions } from '~/services/renderer/renderer';
+
 class ReplayComments extends React.PureComponent {
 
   constructor() {
@@ -32,7 +34,7 @@ class ReplayComments extends React.PureComponent {
         <Comment
           comment={reply}
           deleteBtnClicked={this.props.deleteBtnClicked}
-          growiRenderer={this.props.growiRenderer}
+          rendererOptions={this.props.rendererOptions}
           isReadOnly={this.props.isReadOnly}
         />
       </div>
@@ -107,7 +109,7 @@ ReplayComments.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
 
-  growiRenderer: PropTypes.object.isRequired,
+  rendererOptions: PropTypes.instanceOf(RendererOptions).isRequired,
   deleteBtnClicked: PropTypes.func.isRequired,
   isReadOnly: PropTypes.bool.isRequired,
   replyList: PropTypes.array,

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

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

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

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

+ 5 - 5
packages/app/src/components/PageEditor.tsx

@@ -20,7 +20,7 @@ import {
   useCurrentIndentSize, useSWRxSlackChannels, useIsSlackEnabled, useIsTextlintEnabled, usePageTagsForEditors,
   useIsEnabledUnsavedWarning,
 } from '~/stores/editor';
-import { usePreviewRenderer } from '~/stores/renderer';
+import { usePreviewOptions } from '~/stores/renderer';
 import {
   EditorMode,
   useEditorMode, useIsMobile, useSelectedGrant, useSelectedGrantGroupId, useSelectedGrantGroupName,
@@ -100,7 +100,7 @@ const PageEditor = (props: Props): JSX.Element => {
   const { data: indentSize, mutate: mutateCurrentIndentSize } = useCurrentIndentSize();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
 
-  const { data: growiRenderer } = usePreviewRenderer();
+  const { data: rendererOptions } = usePreviewOptions();
 
   // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
   const [markdown, setMarkdown] = useState<string>(pageContainer.state.markdown!);
@@ -392,7 +392,7 @@ const PageEditor = (props: Props): JSX.Element => {
     return <></>;
   }
 
-  if (growiRenderer == null) {
+  if (rendererOptions == null) {
     return <></>;
   }
 
@@ -432,9 +432,9 @@ const PageEditor = (props: Props): JSX.Element => {
       <div className="d-none d-lg-block page-editor-preview-container flex-grow-1 flex-basis-0 mw-0">
         <Preview
           markdown={markdown}
-          growiRenderer={growiRenderer}
+          rendererOptions={rendererOptions}
           ref={previewRef}
-          isMathJaxEnabled={isMathJaxEnabled}
+          // isMathJaxEnabled={isMathJaxEnabled}
           renderMathJaxOnInit={false}
           onScroll={offset => scrollEditorByPreviewScrollWithThrottle(offset)}
         />

+ 19 - 16
packages/app/src/components/PageEditor/Preview.tsx

@@ -5,7 +5,7 @@ import React, {
 
 import AppContainer from '~/client/services/AppContainer';
 import InterceptorManager from '~/services/interceptor-manager';
-import GrowiRenderer from '~/services/renderer/growi-renderer';
+import { RendererOptions } from '~/services/renderer/renderer';
 import { useEditorSettings } from '~/stores/editor';
 
 import RevisionBody from '../Page/RevisionBody';
@@ -16,7 +16,7 @@ declare const interceptorManager: InterceptorManager;
 
 
 type Props = {
-  growiRenderer: GrowiRenderer,
+  rendererOptions: RendererOptions,
   markdown?: string,
   pagePath?: string,
   renderMathJaxOnInit?: boolean,
@@ -28,7 +28,7 @@ type UnstatedProps = Props & { appContainer: AppContainer };
 const Preview = React.forwardRef((props: UnstatedProps, ref: RefObject<HTMLDivElement>): JSX.Element => {
 
   const {
-    growiRenderer,
+    rendererOptions,
     markdown, pagePath,
   } = props;
 
@@ -47,20 +47,23 @@ const Preview = React.forwardRef((props: UnstatedProps, ref: RefObject<HTMLDivEl
   }, [markdown, pagePath, editorSettings?.renderDrawioInRealtime]);
 
   const renderPreview = useCallback(async() => {
-    if (interceptorManager != null) {
-      await interceptorManager.process('preRenderPreview', context);
-      await interceptorManager.process('prePreProcess', context);
-      context.markdown = growiRenderer.preProcess(context.markdown, context);
-      await interceptorManager.process('postPreProcess', context);
-      context.parsedHTML = growiRenderer.process(context.markdown, context);
-      await interceptorManager.process('prePostProcess', context);
-      context.parsedHTML = growiRenderer.postProcess(context.parsedHTML, context);
-      await interceptorManager.process('postPostProcess', context);
-      await interceptorManager.process('preRenderPreviewHtml', context);
-    }
 
-    setHtml(context.parsedHTML ?? '');
-  }, [context, growiRenderer]);
+    // 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]);
 
   useEffect(() => {
     if (markdown == null) {

+ 9 - 9
packages/app/src/components/PageTimeline.jsx

@@ -6,8 +6,8 @@ import { useTranslation } from 'next-i18next';
 import AppContainer from '~/client/services/AppContainer';
 import PageContainer from '~/client/services/PageContainer';
 import { apiv3Get } from '~/client/util/apiv3-client';
-import GrowiRenderer from '~/services/renderer/growi-renderer';
-import { useTimelineRenderer } from '~/stores/renderer';
+import { RendererOptions } from '~/services/renderer/renderer';
+import { useTimelineOptions } from '~/stores/renderer';
 
 import RevisionLoader from './Page/RevisionLoader';
 import PaginationWrapper from './PaginationWrapper';
@@ -50,9 +50,9 @@ class PageTimeline extends React.Component {
   }
 
   UNSAFE_componentWillMount() {
-    const { growiRenderer } = this.props;
+    const { rendererOptions } = this.props;
     // initialize GrowiRenderer
-    this.growiRenderer = growiRenderer;
+    this.rendererOptions = rendererOptions;
   }
 
   async componentDidMount() {
@@ -85,7 +85,7 @@ class PageTimeline extends React.Component {
                 <div className="card-body">
                   <RevisionLoader
                     lazy
-                    growiRenderer={this.growiRenderer}
+                    rendererOptions={this.rendererOptions}
                     pageId={page._id}
                     pagePath={page.path}
                     revisionId={page.revision}
@@ -112,20 +112,20 @@ class PageTimeline extends React.Component {
 PageTimeline.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  growiRenderer: PropTypes.instanceOf(GrowiRenderer).isRequired,
+  rendererOptions: PropTypes.instanceOf(RendererOptions).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
   pages: PropTypes.arrayOf(PropTypes.object),
 };
 
 const PageTimelineWrapperFC = (props) => {
   const { t } = useTranslation();
-  const { data: growiRenderer } = useTimelineRenderer();
+  const { data: rendererOptions } = useTimelineOptions();
 
-  if (growiRenderer == null) {
+  if (rendererOptions == null) {
     return <></>;
   }
 
-  return <PageTimeline t={t} growiRenderer={growiRenderer} {...props} />;
+  return <PageTimeline t={t} rendererOptions={rendererOptions} {...props} />;
 };
 
 /**

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

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

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

@@ -15,7 +15,7 @@ import {
   usePageDuplicateModal, usePageRenameModal, usePageDeleteModal,
 } from '~/stores/modal';
 import { useDescendantsPageListForCurrentPathTermManager, usePageTreeTermManager } from '~/stores/page-listing';
-import { useSearchResultRenderer } from '~/stores/renderer';
+import { useSearchResultOptions } from '~/stores/renderer';
 import { useFullTextSearchTermManager } from '~/stores/search';
 
 
@@ -120,7 +120,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
   const { open: openRenameModal } = usePageRenameModal();
   const { open: openDeleteModal } = usePageDeleteModal();
 
-  const { data: growiRenderer } = useSearchResultRenderer();
+  const { data: rendererOptions } = useSearchResultOptions();
 
   const duplicateItemClickedHandler = useCallback(async(pageToDuplicate) => {
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -194,7 +194,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
   }, [page, showPageControlDropdown, forceHideMenuItems, duplicateItemClickedHandler, renameItemClickedHandler, deleteItemClickedHandler]);
 
   // return if page or growiRenderer is null
-  if (page == null || growiRenderer == null) return <></>;
+  if (page == null || rendererOptions == null) return <></>;
 
   return (
     <div key={page._id} data-testid="search-result-content" className="search-result-content grw-page-path-text-muted-container d-flex flex-column">
@@ -208,7 +208,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
       </div>
       <div className="search-result-content-body-container" ref={scrollElementRef}>
         <RevisionLoader
-          growiRenderer={growiRenderer}
+          rendererOptions={rendererOptions}
           pageId={page._id}
           pagePath={page.path}
           revisionId={page.revision}

+ 5 - 12
packages/app/src/components/Sidebar/CustomSidebar.tsx

@@ -7,7 +7,7 @@ import { useSWRxPageByPath } from '~/stores/page';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import RevisionRenderer from '../Page/RevisionRenderer';
 import { IRevision } from '~/interfaces/revision';
-import { useCustomSidebarRenderer } from '~/stores/renderer';
+import { useCustomSidebarOptions } from '~/stores/renderer';
 
 const logger = loggerFactory('growi:cli:CustomSidebar');
 
@@ -28,13 +28,11 @@ type Props = {
 
 const CustomSidebar: FC<Props> = (props: Props) => {
 
-  const { appContainer } = props;
-
-  const { data: renderer } = useCustomSidebarRenderer();
+  const { data: rendererOptions } = useCustomSidebarOptions();
 
   const { data: page, error, mutate } = useSWRxPageByPath('/Sidebar');
 
-  if (renderer == null) {
+  if (rendererOptions == null) {
     return <></>;
   }
 
@@ -65,7 +63,7 @@ const CustomSidebar: FC<Props> = (props: Props) => {
         (!isLoading && markdown != null) && (
           <div className="p-3">
             <RevisionRenderer
-              growiRenderer={renderer}
+              rendererOptions={rendererOptions}
               markdown={markdown}
               pagePath="/Sidebar"
               additionalClassName="grw-custom-sidebar-content"
@@ -83,9 +81,4 @@ const CustomSidebar: FC<Props> = (props: Props) => {
   );
 };
 
-/**
- * Wrapper component for using unstated
- */
-const CustomSidebarWrapper = withUnstatedContainers(CustomSidebar, [AppContainer]);
-
-export default CustomSidebarWrapper;
+export default CustomSidebar;

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

@@ -4,7 +4,7 @@ import React, {
 
 import nodePath from 'path';
 
-import { pathUtils, pagePathUtils } from '@growi/core';
+import { pathUtils, pagePathUtils, Nullable } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 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 { apiv3Put, apiv3Post } from '~/client/util/apiv3-client';
 import TriangleIcon from '~/components/Icons/TriangleIcon';
-import { Nullable } from '~/interfaces/common';
 import {
   IPageHasId, IPageInfoAll, IPageToDeleteWithMeta,
 } from '~/interfaces/page';

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

@@ -2,11 +2,11 @@ import React, {
   useEffect, useRef, useState, useMemo, useCallback,
 } from 'react';
 
+import { Nullable } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { debounce } from 'throttle-debounce';
 
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
-import { Nullable } from '~/interfaces/common';
 import { IPageHasId, IPageToDeleteWithMeta } from '~/interfaces/page';
 import { AncestorsChildrenResult, RootPageResult, TargetAndAncestors } from '~/interfaces/page-listing-results';
 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,
 } from 'react';
 
-import { DevidedPagePath } from '@growi/core';
+import { DevidedPagePath, isPopulated } from '@growi/core';
 import { UserPicture, FootstampIcon } from '@growi/ui';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
-import PropTypes from 'prop-types';
-
 
 import PagePathHierarchicalLink from '~/components/PagePathHierarchicalLink';
-import { isPopulated } from '~/interfaces/common';
 import { IPageHasId } from '~/interfaces/page';
 import LinkedPagePath from '~/models/linked-page-path';
 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 { SubscriptionStatusType } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { UncontrolledTooltip } from 'reactstrap';
 
-import { SubscriptionStatusType } from '~/interfaces/subscription';
-
 import styles from './SubscribeButton.module.scss';
 
 

+ 25 - 19
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';
 
 // Model
@@ -68,7 +68,7 @@ const ACTION_ADMIN_APP_SETTINGS_UPDATE = 'ADMIN_APP_SETTING_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_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_PLUGIN_UPDATE = 'ADMIN_PLUGIN_UPDATE';
 const ACTION_ADMIN_MAINTENANCEMODE_ENABLED = 'ADMIN_MAINTENANCEMODE_ENABLED';
@@ -113,7 +113,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_SCRIPT_UPDATE = 'ADMIN_CUSTOM_SCRIPT_UPDATE';
 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_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_DELETE = 'ADMIN_USER_NOTIFICATION_SETTINGS_DELETE';
 const ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD = 'ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD';
@@ -142,14 +152,6 @@ 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_SEARCH_INDICES_NORMALIZE = 'ADMIN_SEARCH_INDICES_NORMALIZE';
 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 = {
@@ -279,7 +281,17 @@ export const SupportedAction = {
   ACTION_ADMIN_CUSTOM_CSS_UPDATE,
   ACTION_ADMIN_CUSTOM_SCRIPT_UPDATE,
   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_DOWNLOAD,
+  ACTION_ADMIN_ARCHIVE_DATA_DELETE,
   ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_ADD,
   ACTION_ADMIN_USER_NOTIFICATION_SETTINGS_DELETE,
   ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD,
@@ -308,14 +320,6 @@ export const SupportedAction = {
   ACTION_ADMIN_USER_GROUP_ADD_USER,
   ACTION_ADMIN_SEARCH_INDICES_NORMALIZE,
   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;
 
 // Action required for notification
@@ -448,6 +452,8 @@ export const LargeActionGroup = {
   ACTION_ADMIN_CUSTOM_SCRIPT_UPDATE,
   ACTION_ADMIN_ARCHIVE_DATA_UPLOAD,
   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_DELETE,
   ACTION_ADMIN_GLOBAL_NOTIFICATION_SETTINGS_ADD,

+ 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 { IRevision } from './revision';
 import { IUser } from './user';

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

@@ -4,25 +4,6 @@
 
 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> = {
   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';
 
 

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

@@ -1,13 +1,8 @@
 import EventEmitter from 'events';
 
-import GrowiRenderer from '~/services/renderer/growi-renderer';
-import Xss from '~/services/xss';
-
 import { IGraphViewer } from './graph-viewer';
 
 export type CustomWindow = Window
                          & typeof globalThis
                          & { 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 };

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

@@ -5,7 +5,7 @@ export type IDataApplicableGroup = {
 }
 
 export type IDataApplicableGrant = null | IDataApplicableGroup;
-export type IRecordApplicableGrant = Record<PageGrant, IDataApplicableGrant>
+export type IRecordApplicableGrant = Partial<Record<PageGrant, IDataApplicableGrant>>
 export type IResApplicableGrant = {
   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 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 = {
   grant: number,
   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';

+ 6 - 8
packages/app/src/interfaces/services/renderer.ts

@@ -1,12 +1,5 @@
 import { XssOptionConfig } from '~/services/xss/xssOption';
 
-export type RendererSettings = {
-  isEnabledLinebreaks: boolean,
-  isEnabledLinebreaksInComments: boolean,
-  adminPreferredIndentSize: number,
-  isIndentSizeForced: boolean,
-};
-
 // export type GrowiHydratedEnv = {
 //   DRAWIO_URI: string | null,
 //   HACKMD_URI: string | null,
@@ -15,8 +8,13 @@ export type RendererSettings = {
 //   GROWI_APP_ID_FOR_GROWI_CLOUD: string | null,
 // }
 
-export type GrowiRendererConfig = {
+export type RendererConfig = {
+  isEnabledLinebreaks: boolean,
+  isEnabledLinebreaksInComments: boolean,
+  adminPreferredIndentSize: number,
+  isIndentSizeForced: boolean,
   highlightJsStyleBorder: boolean
+
   plantumlUri: string | null,
   blockdiagUri: string | null,
 } & XssOptionConfig;

+ 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';
 
-export type ITag<ID = string> = {
-  _id: ID
-  name: string,
-}
+export type { ITag } from '@growi/core';
 
 export type IDataTagCount = ITag & {count: number}
 
-
 export type IPageTagsInfo = {
   tags : string[],
 }
 
 export type IListTagNamesByPage = string[];
 
-
 export type IResTagsUpdateApiv1 = {
   ok: boolean,
   savedPage: IPageHasId,

+ 1 - 1
packages/app/src/interfaces/ui.ts

@@ -1,4 +1,4 @@
-import { Nullable } from './common';
+import { Nullable } from '@growi/core';
 
 export const SidebarContentsType = {
   CUSTOM: 'custom',

+ 2 - 2
packages/app/src/interfaces/user-ui-settings.ts

@@ -1,7 +1,7 @@
-import { IUser } from './user';
+import { Ref } from '@growi/core';
 
 import { SidebarContentsType } from './ui';
-import { Ref } from './common';
+import { IUser } from './user';
 
 export interface IUserUISettings {
   user: Ref<IUser> | null;

+ 3 - 37
packages/app/src/interfaces/user.ts

@@ -1,37 +1,3 @@
-import { IAttachment } from './attachment';
-import { Ref } from './common';
-import { HasObjectId } from './has-object-id';
-import { Lang } from './lang';
-
-export type IUser = {
-  name: string,
-  username: string,
-  email: string,
-  password: string,
-  image?: string, // for backward conpatibility
-  imageAttachment?: Ref<IAttachment>,
-  imageUrlCached: string,
-  isGravatarEnabled: boolean,
-  admin: boolean,
-  apiToken?: string,
-  isEmailPublished: boolean,
-  lang: Lang,
-  slackMemberId?: string,
-}
-
-export type IUserGroupRelation = {
-  relatedGroup: Ref<IUserGroup>,
-  relatedUser: Ref<IUser>,
-  createdAt: Date,
-}
-
-export type IUserGroup = {
-  name: string;
-  createdAt: Date;
-  description: string;
-  parent: Ref<IUserGroupHasId> | null;
-}
-
-export type IUserHasId = IUser & HasObjectId;
-export type IUserGroupHasId = IUserGroup & HasObjectId;
-export type IUserGroupRelationHasId = IUserGroupRelation & HasObjectId;
+export type {
+  IUser, IUserGroupRelation, IUserGroup, IUserHasId, IUserGroupHasId, IUserGroupRelationHasId,
+} from '@growi/core';

+ 2 - 1
packages/app/src/migrations/20180927102719-init-serverurl.js

@@ -1,6 +1,7 @@
+import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+// eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import loggerFactory from '~/utils/logger';
 

+ 2 - 1
packages/app/src/migrations/20190618055300-abolish-crowi-classic-auth.js

@@ -1,6 +1,7 @@
+import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+// eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import loggerFactory from '~/utils/logger';
 

+ 2 - 1
packages/app/src/migrations/20190618104011-add-config-app-installed.js

@@ -1,6 +1,7 @@
+import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
+// eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import loggerFactory from '~/utils/logger';
 

+ 2 - 1
packages/app/src/migrations/20200420160390-remove-crowi-layout.js

@@ -1,6 +1,7 @@
+import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+// eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import loggerFactory from '~/utils/logger';
 

+ 2 - 1
packages/app/src/migrations/20200512005851-remove-behavior-type.js

@@ -1,6 +1,7 @@
+import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+// eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import loggerFactory from '~/utils/logger';
 

+ 2 - 1
packages/app/src/migrations/20200514001356-update-theme-color-for-dark.js

@@ -1,6 +1,7 @@
+import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+// eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import loggerFactory from '~/utils/logger';
 

+ 2 - 1
packages/app/src/migrations/20200620203632-normalize-locale-id.js

@@ -1,6 +1,7 @@
+import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
+// eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import loggerFactory from '~/utils/logger';
 

+ 2 - 1
packages/app/src/migrations/20200827045151-remove-layout-setting.js

@@ -1,6 +1,7 @@
+import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+// eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import loggerFactory from '~/utils/logger';
 

+ 2 - 1
packages/app/src/migrations/20200828024025-copy-aws-setting.js

@@ -1,6 +1,7 @@
+import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+// eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import loggerFactory from '~/utils/logger';
 

+ 2 - 1
packages/app/src/migrations/20200901034313-update-mail-transmission.js

@@ -1,6 +1,7 @@
+import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+// eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import loggerFactory from '~/utils/logger';
 

+ 3 - 1
packages/app/src/migrations/20200903080025-remove-timeline-type.js.js

@@ -1,7 +1,9 @@
 import { getMongoUri, mongoOptions } from '@growi/core';
-import loggerFactory from '~/utils/logger';
 
+// eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
+import loggerFactory from '~/utils/logger';
+
 
 const logger = loggerFactory('growi:migrate:remove-timeline-type');
 

+ 3 - 1
packages/app/src/migrations/20200915035234-rename-s3-config.js

@@ -1,7 +1,9 @@
 import { getMongoUri, mongoOptions } from '@growi/core';
-import loggerFactory from '~/utils/logger';
 
+// eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
+import loggerFactory from '~/utils/logger';
+
 
 const logger = loggerFactory('growi:migrate:remove-timeline-type');
 

+ 2 - 1
packages/app/src/migrations/20210830074539-update-configs-for-slackbot.js

@@ -1,6 +1,7 @@
+import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+// eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
 import loggerFactory from '~/utils/logger';
 

+ 3 - 2
packages/app/src/migrations/20211005131430-config-without-proxy-command-permission-for-renaming.js

@@ -1,8 +1,9 @@
+import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
-import loggerFactory from '~/utils/logger';
+// eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
+import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:migrate:slack-app-integration-rename-keys');

+ 3 - 2
packages/app/src/migrations/20220131001218-convert-redirect-to-pages-to-page-redirect-documents.js

@@ -1,9 +1,10 @@
-import mongoose from 'mongoose';
 import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
+import mongoose from 'mongoose';
 
+// eslint-disable-next-line import/no-named-as-default
 import PageRedirectModel from '~/server/models/page-redirect';
-import loggerFactory from '~/utils/logger';
 import { createBatchStream } from '~/server/util/batch-stream';
+import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:convert-redirect-to-pages-to-page-redirect-documents');
 

+ 1 - 3
packages/app/src/next-i18next.config.ts

@@ -1,12 +1,10 @@
 import path from 'path';
 
-import { isServer } from '@growi/core';
+import { isServer, AllLang, Lang } from '@growi/core';
 import I18nextChainedBackend from 'i18next-chained-backend';
 import I18NextHttpBackend from 'i18next-http-backend';
 import I18NextLocalStorageBackend from 'i18next-localstorage-backend';
 
-import { AllLang, Lang } from './interfaces/lang';
-
 const isDev = process.env.NODE_ENV === 'development';
 
 export const i18n = {

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

@@ -2,7 +2,9 @@ import React, { useEffect } from 'react';
 
 import EventEmitter from 'events';
 
-import { isClient, pagePathUtils, pathUtils } from '@growi/core';
+import {
+  IDataWithMeta, IPageInfoForEntity, IPagePopulatedToShowRevision, isClient, pagePathUtils, pathUtils,
+} from '@growi/core';
 import ExtensibleCustomError from 'extensible-custom-error';
 import {
   NextPage, GetServerSideProps, GetServerSidePropsContext,
@@ -21,14 +23,12 @@ import { CrowiRequest } from '~/interfaces/crowi-request';
 // import { useRendererSettings } from '~/stores/renderer';
 // import { EditorMode, useEditorMode, useIsMobile } from '~/stores/ui';
 import { CustomWindow } from '~/interfaces/global';
-import { IPageWithMeta } from '~/interfaces/page';
-import { GrowiRendererConfig, RendererSettings } from '~/interfaces/services/renderer';
+import { RendererConfig } from '~/interfaces/services/renderer';
 import { ISidebarConfig } from '~/interfaces/sidebar-config';
 import { PageModel, PageDocument } from '~/server/models/page';
 import UserUISettings, { UserUISettingsDocument } from '~/server/models/user-ui-settings';
 import Xss from '~/services/xss';
 import { useSWRxCurrentPage, useSWRxPageInfo, useSWRxPage } from '~/stores/page';
-import { useRendererSettings } from '~/stores/renderer';
 import {
   usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
 } from '~/stores/ui';
@@ -49,13 +49,14 @@ import DisplaySwitcher from '../components/Page/DisplaySwitcher';
 
 import {
   useCurrentUser, useCurrentPagePath,
-  useOwnerOfCurrentPage, useIsLatestRevision,
-  useIsForbidden, useIsNotFound, useIsTrashPage, useShared, useShareLinkId, useIsSharedUser, useIsAbleToDeleteCompletely,
+  useIsLatestRevision,
+  useIsForbidden, useIsNotFound, useIsTrashPage, useIsSharedUser,
   useAppTitle, useSiteUrl, useConfidential, useIsEnabledStaleNotification, useIsIdenticalPath,
-  useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsMailerSetup, useDisableLinkSharing,
-  useAclEnabled, useIsAclEnabled, useHasSlackConfig, useDrawioUri, useHackmdUri, useIsUserPage, useIsNotCreatable,
-  useNoCdn, useEditorConfig, useCsrfToken, useIsSearchScopeChildrenAsDefault, useCurrentPageId, useCurrentPathname,
-  useIsSlackConfigured, useGrowiRendererConfig, useIsBlinkedHeaderAtBoot,
+  useIsSearchServiceConfigured, useIsSearchServiceReachable, useDisableLinkSharing,
+  useHackmdUri,
+  useIsAclEnabled, useIsUserPage, useIsNotCreatable,
+  useCsrfToken, useIsSearchScopeChildrenAsDefault, useCurrentPageId, useCurrentPathname,
+  useIsSlackConfigured, useIsBlinkedHeaderAtBoot, useRendererConfig,
 } from '../stores/context';
 import { useXss } from '../stores/xss';
 
@@ -77,6 +78,7 @@ const IdenticalPathPage = (): JSX.Element => {
   return <IdenticalPathPage />;
 };
 
+type IPageToShowRevisionWithMeta = IDataWithMeta<IPagePopulatedToShowRevision, IPageInfoForEntity>;
 
 type Props = CommonProps & {
   currentUser: string,
@@ -118,8 +120,7 @@ type Props = CommonProps & {
   // isIndentSizeForced: boolean,
   disableLinkSharing: boolean,
 
-  rendererSettings: RendererSettings,
-  growiRendererConfig: GrowiRendererConfig,
+  rendererConfig: RendererConfig,
 
   // UI
   userUISettings: UserUISettingsDocument | null
@@ -165,10 +166,9 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   // useIsTrashPage(_isTrashPage(props.currentPagePath));
   // useShared();
   // useShareLinkId(props.shareLinkId);
-  useIsSharedUser(props.currentUser == null); // '/shared' is not routed this page
+  useIsSharedUser(false); // this page cann't be routed for '/share'
   useIsIdenticalPath(false); // TODO: need to initialize from props
   // useIsAbleToDeleteCompletely(props.isAbleToDeleteCompletely);
-  useIsSharedUser(false); // this page cann't be routed for '/share'
   useIsEnabledStaleNotification(props.isEnabledStaleNotification);
   useIsBlinkedHeaderAtBoot(false);
 
@@ -187,17 +187,16 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   // useIndentSize(props.adminPreferredIndentSize);
   useDisableLinkSharing(props.disableLinkSharing);
 
-  useRendererSettings(props.rendererSettings);
-  useGrowiRendererConfig(props.growiRendererConfig);
+  useRendererConfig(props.rendererConfig);
   // useRendererSettings(props.rendererSettingsStr != null ? JSON.parse(props.rendererSettingsStr) : undefined);
   // useGrowiRendererConfig(props.growiRendererConfigStr != null ? JSON.parse(props.growiRendererConfigStr) : undefined);
 
 
   // const { data: editorMode } = useEditorMode();
 
-  let pageWithMeta: IPageWithMeta | undefined;
+  let pageWithMeta: IPageToShowRevisionWithMeta | undefined;
   if (props.pageWithMetaStr != null) {
-    pageWithMeta = JSON.parse(props.pageWithMetaStr) as IPageWithMeta;
+    pageWithMeta = JSON.parse(props.pageWithMetaStr) as IPageToShowRevisionWithMeta;
   }
   useCurrentPageId(pageWithMeta?.data._id);
   useSWRxCurrentPage(undefined, pageWithMeta?.data); // store initial data
@@ -314,7 +313,7 @@ class MultiplePagesHitsError extends ExtensibleCustomError {
 
 }
 
-async function getPageData(context: GetServerSidePropsContext, props: Props): Promise<IPageWithMeta|null> {
+async function getPageData(context: GetServerSidePropsContext, props: Props): Promise<IPageToShowRevisionWithMeta|null> {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const { revisionId } = req.query;
@@ -337,7 +336,7 @@ async function getPageData(context: GetServerSidePropsContext, props: Props): Pr
     }
   }
 
-  const result: IPageWithMeta = await pageService.findPageAndMetaDataByViewer(pageId, currentPathname, user, true); // includeEmpty = true, isSharedPage = false
+  const result: IPageToShowRevisionWithMeta = await pageService.findPageAndMetaDataByViewer(pageId, currentPathname, user, true); // includeEmpty = true, isSharedPage = false
   const page = result?.data as unknown as PageDocument;
 
   // populate & check if the revision is latest
@@ -350,7 +349,7 @@ async function getPageData(context: GetServerSidePropsContext, props: Props): Pr
   return result;
 }
 
-async function injectRoutingInformation(context: GetServerSidePropsContext, props: Props, pageWithMeta: IPageWithMeta|null): Promise<void> {
+async function injectRoutingInformation(context: GetServerSidePropsContext, props: Props, pageWithMeta: IPageToShowRevisionWithMeta|null): Promise<void> {
   const req: CrowiRequest = context.req as CrowiRequest;
   const { crowi } = req;
   const Page = crowi.model('Page') as PageModel;
@@ -438,19 +437,20 @@ async function injectServerConfigurations(context: GetServerSidePropsContext, pr
   // props.adminPreferredIndentSize = configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize');
   // props.isIndentSizeForced = configManager.getConfig('markdown', 'markdown:isIndentSizeForced');
 
-  props.rendererSettings = {
+  props.rendererConfig = {
     isEnabledLinebreaks: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
     isEnabledLinebreaksInComments: configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
     adminPreferredIndentSize: configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize'),
     isIndentSizeForced: configManager.getConfig('markdown', 'markdown:isIndentSizeForced'),
-  };
-  props.growiRendererConfig = {
+
+    plantumlUri: process.env.PLANTUML_URI ?? null,
+    blockdiagUri: process.env.BLOCKDIAG_URI ?? null,
+
+    // XSS Options
     isEnabledXssPrevention: configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention'),
     attrWhiteList: crowi.xssService.getAttrWhiteList(),
     tagWhiteList: crowi.xssService.getTagWhiteList(),
     highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
-    plantumlUri: process.env.PLANTUML_URI ?? null,
-    blockdiagUri: process.env.BLOCKDIAG_URI ?? null,
   };
 
   props.sidebarConfig = {

+ 1 - 2
packages/app/src/pages/commons.ts

@@ -1,9 +1,8 @@
-import { DevidedPagePath } from '@growi/core';
+import { DevidedPagePath, Lang } from '@growi/core';
 import { GetServerSideProps, GetServerSidePropsContext } from 'next';
 import { SSRConfig, UserConfig } from 'next-i18next';
 
 import { CrowiRequest } from '~/interfaces/crowi-request';
-import { Lang } from '~/interfaces/lang';
 
 import * as nextI18NextConfig from '../next-i18next.config';
 

+ 0 - 1
packages/app/src/server/crowi/index.js

@@ -15,7 +15,6 @@ import loggerFactory from '~/utils/logger';
 import { projectRoot } from '~/utils/project-dir-utils';
 
 import Activity from '../models/activity';
-import PageOperation, { PageActionType } from '../models/page-operation';
 import PageRedirect from '../models/page-redirect';
 import Tag from '../models/tag';
 import UserGroup from '../models/user-group';

+ 3 - 2
packages/app/src/server/models/page.ts

@@ -15,7 +15,6 @@ import { IUserHasId } from '~/interfaces/user';
 import { ObjectIdLike } from '~/server/interfaces/mongoose-utils';
 
 import loggerFactory from '../../utils/logger';
-import Crowi from '../crowi';
 
 import { getPageSchema, extractToAncestorsPaths, populateDataToShowRevision } from './obsolete-page';
 
@@ -957,7 +956,9 @@ export type PageCreateOptions = {
 /*
  * Merge obsolete page model methods and define new methods which depend on crowi instance
  */
-export default (crowi: Crowi): any => {
+// remove type for crowi to prevent 'import/no-cycle'
+// eslint-disable-next-line import/no-anonymous-default-export
+export default (crowi): any => {
   let pageEvent;
   if (crowi != null) {
     pageEvent = crowi.event('page');

+ 6 - 5
packages/app/src/server/models/password-reset-order.ts

@@ -1,11 +1,12 @@
+import crypto from 'crypto';
+
+import { getOrCreateModel } from '@growi/core';
+import { addMinutes } from 'date-fns';
 import mongoose, {
   Schema, Model, Document,
 } from 'mongoose';
-
-import { addMinutes } from 'date-fns';
 import uniqueValidator from 'mongoose-unique-validator';
-import crypto from 'crypto';
-import { getOrCreateModel } from '@growi/core';
+
 
 const ObjectId = mongoose.Schema.Types.ObjectId;
 
@@ -20,7 +21,7 @@ export interface IPasswordResetOrder {
 }
 
 export interface PasswordResetOrderDocument extends IPasswordResetOrder, Document {
-  isExpired(): Promise<boolean>
+  isExpired(): boolean
   revokeOneTimeToken(): Promise<void>
 }
 

+ 1 - 2
packages/app/src/server/models/subscription.ts

@@ -1,10 +1,9 @@
-import { getOrCreateModel } from '@growi/core';
+import { getOrCreateModel, SubscriptionStatusType, AllSubscriptionStatusType } from '@growi/core';
 import {
   Types, Document, Model, Schema,
 } from 'mongoose';
 
 import { AllSupportedTargetModels } from '~/interfaces/activity';
-import { SubscriptionStatusType, AllSubscriptionStatusType } from '~/interfaces/subscription';
 
 
 export interface ISubscription {

+ 1 - 1
packages/app/src/server/models/user-registration-order.ts

@@ -17,7 +17,7 @@ export interface IUserRegistrationOrder {
 }
 
 export interface UserRegistrationOrderDocument extends IUserRegistrationOrder, Document {
-  isExpired(): Promise<boolean>
+  isExpired(): boolean
   revokeOneTimeToken(): Promise<void>
 }
 

+ 10 - 0
packages/app/src/server/routes/admin.js

@@ -356,6 +356,16 @@ module.exports = function(crowi, app) {
 
     try {
       const zipFile = exportService.getFile(fileName);
+      const parameters = {
+        ip:  req.ip,
+        endpoint: req.originalUrl,
+        action: SupportedAction.ACTION_ADMIN_ARCHIVE_DATA_DOWNLOAD,
+        user: req.user?._id,
+        snapshot: {
+          username: req.user?.username,
+        },
+      };
+      crowi.activityService.createActivity(parameters);
       return res.download(zipFile);
     }
     catch (err) {

+ 3 - 1
packages/app/src/server/routes/apiv3/export.js

@@ -168,13 +168,15 @@ module.exports = (crowi) => {
    *              schema:
    *                type: object
    */
-  router.delete('/:fileName', accessTokenParser, loginRequired, adminRequired, validator.deleteFile, apiV3FormValidator, async(req, res) => {
+  router.delete('/:fileName', accessTokenParser, loginRequired, adminRequired, validator.deleteFile, apiV3FormValidator, addActivity, async(req, res) => {
     // TODO: add express validator
     const { fileName } = req.params;
 
     try {
       const zipFile = exportService.getFile(fileName);
       fs.unlinkSync(zipFile);
+      const parameters = { action: SupportedAction.ACTION_ADMIN_ARCHIVE_DATA_DELETE };
+      activityEvent.emit('update', res.locals.activity._id, parameters);
 
       // TODO: use res.apiv3
       return res.status(200).send({ ok: true });

+ 1 - 2
packages/app/src/server/routes/apiv3/page.js

@@ -1,7 +1,6 @@
-import { pagePathUtils } from '@growi/core';
+import { pagePathUtils, AllSubscriptionStatusType, SubscriptionStatusType } from '@growi/core';
 
 import { SupportedAction, SupportedTargetModel } from '~/interfaces/activity';
-import { AllSubscriptionStatusType, SubscriptionStatusType } from '~/interfaces/subscription';
 import { generateAddActivityMiddleware } from '~/server/middlewares/add-activity';
 import Subscription from '~/server/models/subscription';
 import UserGroup from '~/server/models/user-group';

+ 3 - 3
packages/app/src/server/service/activity.ts

@@ -1,7 +1,7 @@
 import mongoose from 'mongoose';
 
 import {
-  IActivity, SupportedAction, SupportedActionType, AllSupportedActions, ActionGroupSize,
+  IActivity, SupportedActionType, AllSupportedActions, ActionGroupSize,
   AllEssentialActions, AllSmallGroupActions, AllMediumGroupActions, AllLargeGroupActions,
 } from '~/interfaces/activity';
 import { IPage } from '~/interfaces/page';
@@ -96,11 +96,11 @@ class ActivityService {
     }
 
     return Array.from(availableActionsSet);
-  }
+  };
 
   shoudUpdateActivity = function(action: SupportedActionType): boolean {
     return this.getAvailableActions().includes(action);
-  }
+  };
 
   // for GET request
   createActivity = async function(parameters): Promise<void> {

+ 1 - 2
packages/app/src/server/service/in-app-notification.ts

@@ -1,11 +1,10 @@
+import { HasObjectId, SubscriptionStatusType } from '@growi/core';
 import { subDays } from 'date-fns';
 import { Types } from 'mongoose';
 
 import { AllEssentialActions, SupportedAction } from '~/interfaces/activity';
-import { HasObjectId } from '~/interfaces/has-object-id';
 import { InAppNotificationStatuses, PaginateResult } from '~/interfaces/in-app-notification';
 import { IPage } from '~/interfaces/page';
-import { SubscriptionStatusType } from '~/interfaces/subscription';
 import { IUser } from '~/interfaces/user';
 import { stringifySnapshot } from '~/models/serializers/in-app-notification-snapshot/page';
 import { ActivityDocument } from '~/server/models/activity';

+ 1 - 2
packages/app/src/server/service/installer.ts

@@ -1,11 +1,10 @@
 import path from 'path';
 
+import { Lang } from '@growi/core';
 import ExtensibleCustomError from 'extensible-custom-error';
 import fs from 'graceful-fs';
 import mongoose from 'mongoose';
 
-
-import { Lang } from '~/interfaces/lang';
 import { IPage } from '~/interfaces/page';
 import { IUser } from '~/interfaces/user';
 import loggerFactory from '~/utils/logger';

+ 3 - 3
packages/app/src/server/service/page.ts

@@ -1,14 +1,14 @@
 import pathlib from 'path';
 import { Readable, Writable } from 'stream';
 
-import { pagePathUtils, pathUtils } from '@growi/core';
+import {
+  pagePathUtils, pathUtils, Ref, HasObjectId,
+} from '@growi/core';
 import escapeStringRegexp from 'escape-string-regexp';
 import mongoose, { ObjectId, QueryCursor } from 'mongoose';
 import streamToPromise from 'stream-to-promise';
 
-import { Ref } from '~/interfaces/common';
 import { V5ConversionErrCode } from '~/interfaces/errors/v5-conversion-error';
-import { HasObjectId } from '~/interfaces/has-object-id';
 import {
   IPage, IPageInfo, IPageInfoForEntity, IPageWithMeta,
 } from '~/interfaces/page';

+ 7 - 7
packages/app/src/server/service/search.ts

@@ -1,24 +1,24 @@
-import xss from 'xss';
 import mongoose from 'mongoose';
+import xss from 'xss';
 
 import { SearchDelegatorName } from '~/interfaces/named-query';
 import { IPageWithMeta } from '~/interfaces/page';
 import { IFormattedSearchResult, IPageSearchMeta, ISearchResult } from '~/interfaces/search';
 import loggerFactory from '~/utils/logger';
 
-import NamedQuery from '../models/named-query';
+import { ObjectIdLike } from '../interfaces/mongoose-utils';
 import {
   SearchDelegator, SearchQueryParser, SearchResolver, ParsedQuery, SearchableData, QueryTerms,
 } from '../interfaces/search';
-import ElasticsearchDelegator from './search-delegator/elasticsearch';
-import PrivateLegacyPagesDelegator from './search-delegator/private-legacy-pages';
-
+import NamedQuery from '../models/named-query';
 import { PageModel } from '../models/page';
 import { serializeUserSecurely } from '../models/serializers/user-serializer';
-
-import { ObjectIdLike } from '../interfaces/mongoose-utils';
 import { SearchError } from '../models/vo/search-error';
 
+import ElasticsearchDelegator from './search-delegator/elasticsearch';
+import PrivateLegacyPagesDelegator from './search-delegator/private-legacy-pages';
+
+
 // eslint-disable-next-line no-unused-vars
 const logger = loggerFactory('growi:service:search');
 

+ 3 - 3
packages/app/src/services/renderer/PreProcessor/XssFilter.ts

@@ -3,11 +3,11 @@ import XssOption, { XssOptionConfig } from '~/services/xss/xssOption';
 
 export default class XssFilter {
 
-  xssOption: XssOption
+  xssOption: XssOption;
 
-  xss
+  xss;
 
-  config: XssOptionConfig
+  config: XssOptionConfig;
 
   constructor(config: XssOptionConfig) {
     this.config = config;

+ 7 - 7
packages/app/src/services/renderer/markdown-it/blockdiag.ts

@@ -1,18 +1,18 @@
-import { GrowiRendererConfig } from '~/interfaces/services/renderer';
+import { RendererConfig } from '~/interfaces/services/renderer';
 
 export default class BlockdiagConfigurer {
 
   generateSourceUrl: string;
 
-  constructor(growiConfig: GrowiRendererConfig) {
-    this.generateSourceUrl = growiConfig.blockdiagUri || 'https://blockdiag-api.com/';
+  constructor(config: RendererConfig) {
+    this.generateSourceUrl = config.blockdiagUri || 'https://blockdiag-api.com/';
   }
 
   configure(md) {
-    md.use(require('markdown-it-blockdiag'), {
-      generateSourceUrl: this.generateSourceUrl,
-      marker: ':::',
-    });
+    // md.use(require('markdown-it-blockdiag'), {
+    //   generateSourceUrl: this.generateSourceUrl,
+    //   marker: ':::',
+    // });
   }
 
 }

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio