diff --git a/apps/app/package.json b/apps/app/package.json index 61530a31b02..62ec1da8722 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -158,7 +158,7 @@ "multer": "~1.4.0", "multer-autoreap": "^1.0.3", "mustache": "^4.2.0", - "next": "^14.2.13", + "next": "^14.2.15", "next-dynamic-loading-props": "^0.1.1", "next-i18next": "^15.3.1", "next-superjson": "^1.0.7", @@ -198,9 +198,11 @@ "reconnecting-websocket": "^4.4.0", "redis": "^3.0.2", "rehype-katex": "^7.0.1", + "rehype-meta": "^4.0.1", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "rehype-slug": "^6.0.0", + "rehype-stringify": "^10.0.1", "rehype-toc": "^3.0.2", "remark-breaks": "^4.0.0", "remark-directive": "^3.0.0", diff --git a/apps/app/public/static/locales/en_US/admin.json b/apps/app/public/static/locales/en_US/admin.json index b0c5430e59a..920129084fa 100644 --- a/apps/app/public/static/locales/en_US/admin.json +++ b/apps/app/public/static/locales/en_US/admin.json @@ -10,6 +10,7 @@ "only_me": "Only me", "only_inside_the_group": "Only inside the group", "optional": "Optional", + "days": "days", "security_settings": { "security_settings": "Security Settings", "scope_of_page_disclosure": "Scope of page disclosure", @@ -362,9 +363,11 @@ "file_uploading": "File uploading", "enable_files_except_image": "Enabling this option will allow upload of any file type. Without this option, only image file upload is supported.", "attach_enable": "You can attach files other than image files if you enable this option.", - "enable_page_bulk_export": "Enable bulk exporting a page and it's child pages", - "page_bulk_export_explanation": "When enabled, users will be able to bulk export from the page menu.", - "page_bulk_export_warning": "The download period for exported results is 24 hours, and they will be deleted after that. During this period, running multiple large exports consecutively can fill up the file system.", + "page_bulk_export_settings": "Page Bulk Export Settings", + "enable_page_bulk_export": "Enable bulk export", + "page_bulk_export_explanation": "Enables a feature that allows all users to export a page and all it's child pages at once from the menu. Exported data will be automatically deleted after the storage period has passed.", + "page_bulk_export_warning": "The bulk page export feature is available to all users. In order to maintain system resources, we ask for your cooperation in using the minimum amount necessary. If you are an administrator, please inform all users of this.", + "page_bulk_export_storage_period": "Storage period", "update": "Update", "mail_settings": "E-mail Settings", "mailer_is_not_set_up": "E-mail setting is not set up.", @@ -1066,7 +1069,8 @@ "ADMIN_USER_GROUP_ADD_USER": "Add User to User Group", "ADMIN_SEARCH_CONNECTION": "Attempting to reconnect to Elasticsearch", "ADMIN_SEARCH_INDICES_NORMALIZE": "Normalize of Elasticsearch indexes", - "ADMIN_SEARCH_INDICES_REBUILD": "Rebuild Elasticsearch indexes" + "ADMIN_SEARCH_INDICES_REBUILD": "Rebuild Elasticsearch indexes", + "ADMIN_PAGE_BULK_EXPORT_SETTINGS_UPDATE": "Update Page Bulk Export Settings" }, "g2g": { "transfer_success": "Completed GROWI to GROWI transfer successfully", diff --git a/apps/app/public/static/locales/en_US/translation.json b/apps/app/public/static/locales/en_US/translation.json index 6f15105fc78..4734d2d42c1 100644 --- a/apps/app/public/static/locales/en_US/translation.json +++ b/apps/app/public/static/locales/en_US/translation.json @@ -117,7 +117,7 @@ "Create under": "Create page under below:", "V5 Page Migration": "Convert To V5 Compatibility", "GROWI.5.0_new_schema": "GROWI.5.0 new schema", - "See_more_detail_on_new_schema": "See more detail on {{title}} external_link ", + "See_more_detail_on_new_schema": "See more detail on {{title}} external_link ", "external_account_management": "External Account Management", "UserGroup": "UserGroup", "Basic Settings": "Basic Settings", @@ -157,7 +157,7 @@ "duplicated_path": "Duplicated path", "Link sharing is disabled": "Link sharing is disabled", "successfully_saved_the_page": "Successfully saved the page", - "you_can_not_create_page_with_this_name": "You can not create page with this name", + "you_can_not_create_page_with_this_name_or_hierarchy": "You can not create page with this name or page hierarchy", "not_allowed_to_see_this_page": "You cannot see this page", "Confirm": "Confirm", "Successfully requested": "Successfully requested.", @@ -615,7 +615,7 @@ "alert_desc1": "On this page, you can select pages with the checkbox and batch convert to the new v5 compatible format from the \"Bulk operation\" button at the top of the screen.", "nopages_title": "Congratulations. Ready to use GROWI v5!", "nopages_desc1": "Now all the pages you can manage seem to be in v5 compatible format.", - "detail_info": "See the detail information from Upgrading GROWI to v5.0.x external_link.", + "detail_info": "See the detail information from Upgrading GROWI to v5.0.x external_link.", "modal": { "title": "Convert to new v5 compatible format", "converting_pages": "Converting pages", @@ -660,7 +660,9 @@ "export_page_markdown": "Export page as Markdown", "export_page_pdf": "Export page as PDF", "bulk_export": "Export page and all child pages", - "bulk_export_notice": "Once a download link is ready, a notification will be sent. If the number of pages is large, it may take a while for preparation.", + "bulk_export_download_explanation": "A notification will be sent when the export is complete. To download the exported file, click the notification.", + "bulk_export_exec_time_warning": "If the number of pages is large, it may take a while to export", + "large_bulk_export_warning": "To conserve system resources, please refrain from exporting a large number of pages consecutively", "markdown": "Markdown", "choose_export_format": "Select export format", "bulk_export_started": "Please wait a moment...", diff --git a/apps/app/public/static/locales/fr_FR/admin.json b/apps/app/public/static/locales/fr_FR/admin.json index b9a90105174..3d560185990 100644 --- a/apps/app/public/static/locales/fr_FR/admin.json +++ b/apps/app/public/static/locales/fr_FR/admin.json @@ -10,6 +10,7 @@ "only_me": "Seulement moi", "only_inside_the_group": "Utilisateurs du groupe", "optional": "Optionnel", + "days": "jours", "security_settings": { "security_settings": "Paramètres de sécurité", "scope_of_page_disclosure": "Confidentialité de la page", @@ -362,9 +363,11 @@ "file_uploading": "Téléversement de fichiers", "enable_files_except_image": "Autorise le téléversement de fichiers de n'importe quel type. Lorsque désactivé, seul les fichiers de type image sont autorisés.", "attach_enable": "Autorise le téléversement de fichiers de n'importe quel type", - "enable_page_bulk_export": "Autoriser l'exportation groupée de pages et de leurs pages subordonnées", - "page_bulk_export_explanation": "Si autorisé, l'exportation groupée sera possible à partir du menu de la page.", - "page_bulk_export_warning": "La période de téléchargement des résultats exportés est de 24 heures, après quoi ils seront supprimés. Pendant cette période, l’exécution consécutive de plusieurs exportations volumineuses peut remplir le système de fichiers.", + "page_bulk_export_settings": "Paramètres d'exportation de pages par lots", + "enable_page_bulk_export": "Activer l'exportation groupée", + "page_bulk_export_explanation": "Active une fonctionnalité qui permet à tous les utilisateurs d'exporter simultanément toutes les pages sélectionnées dans le menu des pages et leurs pages subordonnées. Les données exportées seront automatiquement supprimées une fois la période de conservation écoulée.", + "page_bulk_export_warning": "La fonctionnalité d’exportation de pages en masse est disponible pour tous les utilisateurs. Afin de maintenir les ressources du système, nous demandons votre coopération pour utiliser le montant minimum nécessaire. Si vous êtes administrateur, veuillez en informer tous les utilisateurs.", + "page_bulk_export_storage_period": "Date limite de téléchargement", "update": "Sauvegarder", "mail_settings": "Configuration e-mail", "mailer_is_not_set_up": "Paramètres e-mail non configurés.", @@ -1065,7 +1068,8 @@ "ADMIN_USER_GROUP_ADD_USER": "Ajouter l'utilisateur au groupe", "ADMIN_SEARCH_CONNECTION": "Essai de reconnexion Elasticsearch", "ADMIN_SEARCH_INDICES_NORMALIZE": "Nomarliser l'index Elasticsearch", - "ADMIN_SEARCH_INDICES_REBUILD": "Reconstruire l'index Elasticsearch" + "ADMIN_SEARCH_INDICES_REBUILD": "Reconstruire l'index Elasticsearch", + "ADMIN_PAGE_BULK_EXPORT_SETTINGS_UPDATE": "Mettre à jour les paramètres d'exportation groupée de la page" }, "g2g": { "transfer_success": "Transfert de GROWI vers GROWI complété!", diff --git a/apps/app/public/static/locales/fr_FR/translation.json b/apps/app/public/static/locales/fr_FR/translation.json index 914a989153c..923f45b24bf 100644 --- a/apps/app/public/static/locales/fr_FR/translation.json +++ b/apps/app/public/static/locales/fr_FR/translation.json @@ -117,7 +117,7 @@ "Create under": "Créer la page sous:", "V5 Page Migration": "Convertir vers la V5", "GROWI.5.0_new_schema": "Nouveau schéma GROWI.5.0", - "See_more_detail_on_new_schema": "Plus de détails sur {{title}} ", + "See_more_detail_on_new_schema": "Plus de détails sur {{title}}external_link ", "external_account_management": "Gestion des comptes externes", "UserGroup": "Groupe utilisateur", "Basic Settings": "Paramètres de base", @@ -157,7 +157,7 @@ "duplicated_path": "Chemin dupliqué", "Link sharing is disabled": "Le partage est désactivé", "successfully_saved_the_page": "Page sauvegardée", - "you_can_not_create_page_with_this_name": "Vous ne pouvez pas créer cette page", + "you_can_not_create_page_with_this_name_or_hierarchy": "Vous ne pouvez pas créer de page avec ce nom ou cette hiérarchie de pages", "not_allowed_to_see_this_page": "Vous ne pouvez pas voir cette page", "Confirm": "Confirmer", "Successfully requested": "Demande envoyée.", @@ -608,7 +608,7 @@ "alert_desc1": "Sélectionner les pages à convertir vers le format V5 avec le bouton \"Opération de masse\".", "nopages_title": "GROWI V5 est maintenant utilisable!", "nopages_desc1": "Toutes les pages ont été converties au format V5.", - "detail_info": "Pour plus de détails, voir Convertir vers GROWI v5.0.x external_link.", + "detail_info": "Pour plus de détails, voir Convertir vers GROWI v5.0.x external_link.", "modal": { "title": "Convertir au format V5", "converting_pages": "Conversion des pages", @@ -653,7 +653,9 @@ "export_page_markdown": "Exporter la page en Markdown", "export_page_pdf": "Exporter la page en PDF", "bulk_export": "Exporter la page et toutes les pages enfants", - "bulk_export_notice": "Une fois qu'un lien de téléchargement est prêt, une notification sera envoyée. Si le nombre de pages est important, la préparation peut prendre un certain temps.", + "bulk_export_download_explanation": "Une notification sera envoyée lorsque l’exportation sera terminée. Pour télécharger le fichier exporté, cliquez sur la notification.", + "bulk_export_exec_time_warning": "Si le nombre de pages est important, l'exportation peut prendre un certain temps.", + "large_bulk_export_warning": "Pour préserver les ressources du système, veuillez éviter d'exporter un grand nombre de pages consécutivement", "markdown": "Markdown", "choose_export_format": "Sélectionnez le format d'exportation", "bulk_export_started": "Patientez s'il-vous-plait...", diff --git a/apps/app/public/static/locales/ja_JP/admin.json b/apps/app/public/static/locales/ja_JP/admin.json index d350895d528..30f5fa1bee7 100644 --- a/apps/app/public/static/locales/ja_JP/admin.json +++ b/apps/app/public/static/locales/ja_JP/admin.json @@ -19,6 +19,7 @@ "only_me": "自分のみ", "only_inside_the_group": "特定グループのみ", "optional": "オプション", + "days": "日", "security_settings": { "security_settings": "セキュリティ設定", "scope_of_page_disclosure": "ページの公開範囲", @@ -371,9 +372,11 @@ "file_uploading": "ファイルアップロード", "enable_files_except_image": "画像以外のファイルアップロードを許可", "attach_enable": "許可をしている場合、画像以外のファイルをページに添付可能になります。", - "enable_page_bulk_export": "ページとその配下のページの一括エクスポートを許可", - "page_bulk_export_explanation": "許可している場合、個別ページのメニューから一括エクスポートが可能になります。", - "page_bulk_export_warning": "エクスポート結果のダウンロード期限は24時間で、それを過ぎるとファイルシステムから削除されます。この間にページ数の多いエクスポートを連続で実行すると、ファイルシステムを圧迫する可能性があります。", + "page_bulk_export_settings": "ページ一括エクスポート設定", + "enable_page_bulk_export": "一括エクスポートを有効にする", + "page_bulk_export_explanation": "すべてのユーザーが、ページメニューから選択したページとその配下ページをまとめてエクスポートできる機能を有効化します。エクスポートされたデータは保存期間経過後に自動的に削除されます。", + "page_bulk_export_warning": "ページ一括エクスポート機能は全ユーザーが利用可能です。システムリソースの維持のため、必要最小限の利用にご協力をお願いいたします。管理者の方は、この旨をユーザーの皆様にご周知ください。", + "page_bulk_export_storage_period": "保存期間", "update": "更新", "mail_settings": "メールの設定", "mailer_is_not_set_up": "メール設定がセットアップされていません。", @@ -1076,7 +1079,8 @@ "ADMIN_USER_GROUP_ADD_USER": "ユーザーグループにユーザーを追加", "ADMIN_SEARCH_CONNECTION": "Elasticsearch の再接続の試行", "ADMIN_SEARCH_INDICES_NORMALIZE": "Elasticsearch のインデックスの正規化", - "ADMIN_SEARCH_INDICES_REBUILD": "Elasticsearch のインデックスのリビルド" + "ADMIN_SEARCH_INDICES_REBUILD": "Elasticsearch のインデックスのリビルド", + "ADMIN_PAGE_BULK_EXPORT_SETTINGS_UPDATE": "ページ一括エクスポート設定の更新" }, "g2g": { "transfer_success": "G2G移行が完了しました", diff --git a/apps/app/public/static/locales/ja_JP/translation.json b/apps/app/public/static/locales/ja_JP/translation.json index 73ff28aedb3..648fd319288 100644 --- a/apps/app/public/static/locales/ja_JP/translation.json +++ b/apps/app/public/static/locales/ja_JP/translation.json @@ -116,7 +116,7 @@ "Create under": "ページを以下に作成", "V5 Page Migration": "V5 互換形式 への変換", "GROWI.5.0_new_schema": "GROWI.5.0における新スキーマについて", - "See_more_detail_on_new_schema": "詳しくは{{title}}external_linkを参照ください。", + "See_more_detail_on_new_schema": "詳しくは{{title}}external_linkを参照ください。", "external_account_management": "外部アカウント管理", "UserGroup": "グループ", "Basic Settings": "基本設定", @@ -158,7 +158,7 @@ "duplicated_path": "重複したパス", "Link sharing is disabled": "リンクのシェアは無効化されています", "successfully_saved_the_page": "ページが正常に保存されました", - "you_can_not_create_page_with_this_name": "この名前でページを作成することはできません", + "you_can_not_create_page_with_this_name_or_hierarchy": "この名前、または階層でページを作成することはできません", "not_allowed_to_see_this_page": "このページは閲覧できません", "Confirm": "確認", "Successfully requested": "正常に処理を受け付けました", @@ -647,7 +647,7 @@ "alert_desc1": "このページでは、チェックボックスでページを選択し、画面上部の「一括操作」ボタンから新しい v5 互換形式に一括変換できます。", "nopages_title": "おめでとうございます。GROWI v5 を使う準備が完了しました!", "nopages_desc1": "今あなたが管理可能なページはすべて v5 互換形式になっているようです。", - "detail_info": "詳しくは GROWI v5.0.x へのアップグレード external_link を参照ください。", + "detail_info": "詳しくは GROWI v5.0.x へのアップグレード external_link を参照ください。", "modal": { "title": "新しい v5 互換形式への変換", "converting_pages": "以下のページを変換します", @@ -692,7 +692,9 @@ "export_page_markdown": "マークダウン形式でページをエクスポート", "export_page_pdf": "PDF形式でページをエクスポート", "bulk_export": "ページとその配下のページを全てエクスポート", - "bulk_export_notice": "ダウンロードの準備が完了すると、通知が届きます。ページ数が多いと、準備に時間がかかる場合があります。", + "bulk_export_download_explanation": "エクスポート完了後に通知が届きます。通知をクリックし、ファイルをダウンロードしてください。", + "bulk_export_exec_time_warning": "ページ数が多いと、エクスポートに時間がかかる場合があります", + "large_bulk_export_warning": "システムリソースの維持のため、ページ数の多いエクスポートを連続して実行することはご遠慮ください", "markdown": "マークダウン", "choose_export_format": "エクスポート形式を選択してください", "bulk_export_started": "ただいま準備中です...", diff --git a/apps/app/public/static/locales/zh_CN/admin.json b/apps/app/public/static/locales/zh_CN/admin.json index 25f3afdde56..4e0d660a7d7 100644 --- a/apps/app/public/static/locales/zh_CN/admin.json +++ b/apps/app/public/static/locales/zh_CN/admin.json @@ -19,6 +19,7 @@ "only_me": "只有我", "only_inside_the_group": "仅组内", "optional": "可选的", + "days": "天", "security_settings": { "security_settings": "安全设置", "scope_of_page_disclosure": "页面公开范围", @@ -371,9 +372,11 @@ "file_uploading": "文件上传", "enable_files_except_image": "启用此选项将允许上传任何文件类型。如果没有此选项,则仅支持图像文件上载。", "attach_enable": "如果启用此选项,则可以附加图像文件以外的文件。", - "enable_page_bulk_export": "允许批量导出页面及其子页面", - "page_bulk_export_explanation": "如果允许,可以从各个页面的菜单中批量导出。", - "page_bulk_export_warning": "导出结果的下载期限为24小时,下载期限过后将被删除。在此期间,连续运行多个大导出可能会填满文件系统。", + "page_bulk_export_settings": "页面批量导出设置", + "enable_page_bulk_export": "启用批量导出", + "page_bulk_export_explanation": "启用一项功能,允许所有用户一次性导出从页面菜单中选择的所有页面及其下级页面。保留期限过后,导出的数据将自动删除。", + "page_bulk_export_warning": "批量页面导出功能可供所有用户使用。为了维护系统资源,请您配合使用最低限度的资源。如果您是管理员,请将此事实告知所有用户。", + "page_bulk_export_storage_period": "储存期限", "update": "更新", "mail_settings": "邮件设置", "mailer_is_not_set_up": "邮件设置尚未完成。", @@ -1075,7 +1078,8 @@ "ADMIN_USER_GROUP_ADD_USER": "添加用户到用户组", "ADMIN_SEARCH_CONNECTION": "重试Elasticsearch连接", "ADMIN_SEARCH_INDICES_NORMALIZE": "试图重新连接Elasticsearch", - "ADMIN_SEARCH_INDICES_REBUILD": "重建 Elasticsearch 索引" + "ADMIN_SEARCH_INDICES_REBUILD": "重建 Elasticsearch 索引", + "ADMIN_PAGE_BULK_EXPORT_SETTINGS_UPDATE": "更新页面批量导出设置" }, "g2g": { "transfer_success": "Completed GROWI to GROWI transfer successfully", diff --git a/apps/app/public/static/locales/zh_CN/translation.json b/apps/app/public/static/locales/zh_CN/translation.json index 38fc6bbe03d..0b5ae956dea 100644 --- a/apps/app/public/static/locales/zh_CN/translation.json +++ b/apps/app/public/static/locales/zh_CN/translation.json @@ -122,7 +122,7 @@ "Create under": "Create page under below:", "V5 Page Migration": "转换为V5的兼容性", "GROWI.5.0_new_schema": "GROWI.5.0 new schema", - "See_more_detail_on_new_schema": "更多详情请见 {{title}} external_link ", + "See_more_detail_on_new_schema": "更多详情请见 {{title}} external_link ", "Markdown Settings": "Markdown设置", "external_account_management": "外部账户管理", "UserGroup": "用户组", @@ -163,7 +163,7 @@ "duplicated_path": "Duplicated path", "Link sharing is disabled": "你不允许分享该链接", "successfully_saved_the_page": "成功地保存了该页面", - "you_can_not_create_page_with_this_name": "您无法使用此名称创建页面", + "you_can_not_create_page_with_this_name_or_hierarchy": "您無法使用此名稱或頁面層級建立頁面", "not_allowed_to_see_this_page": "你不能看到这个页面", "Confirm": "确定", "Successfully requested": "进程成功接受", @@ -617,7 +617,7 @@ "alert_desc1": "在这一页,你可以用复选框选择页面,并通过屏幕上方的批量操作按钮批量转换为新的v5兼容格式。", "nopages_title": "恭喜你。准备使用GROWI v5!", "nopages_desc1": "现在你能管理的所有页面似乎都是v5兼容的格式。", - "detail_info": "请参见 升级GROWI到v5.0.x external_link.的详细内容。", + "detail_info": "请参见 升级GROWI到v5.0.x external_link.的详细内容。", "modal": { "title": "转换为新的v5兼容格式", "converting_pages": "转换页面", @@ -662,7 +662,9 @@ "export_page_markdown": "以Markdown格式导出页面", "export_page_pdf": "以PDF格式导出页面", "bulk_export": "导出页面及其下的所有页面", - "bulk_export_notice": "下载链接准备好后,将发送通知。如果页数较多,则可能需要一段时间准备。", + "bulk_export_download_explanation": "导出完成后将发送通知。要下载导出的文件,请单击通知。", + "bulk_export_exec_time_warning": "如果页数较多,导出可能需要一段时间", + "large_bulk_export_warning": "为了节省系统资源,请避免连续导出大量页面", "markdown": "Markdown", "choose_export_format": "选择导出格式", "bulk_export_started": "目前我们正在准备...", diff --git a/apps/app/src/client/components/Admin/App/AppSetting.jsx b/apps/app/src/client/components/Admin/App/AppSetting.jsx index 0f66f660849..f6da00f70b7 100644 --- a/apps/app/src/client/components/Admin/App/AppSetting.jsx +++ b/apps/app/src/client/components/Admin/App/AppSetting.jsx @@ -170,54 +170,6 @@ const AppSetting = (props) => { -
- -
-
- { - adminAppContainer.changeIsPageBulkExportEnabled(e.target.checked); - }} - /> - -
- -

- {t('admin:app_setting.page_bulk_export_explanation')} -

- -

- {t('admin:app_setting.page_bulk_export_warning')} -

- - {adminAppContainer.state.isFixedIsBulkExportPagesEnabled && ( -

- help - FIXED
- {/* eslint-disable-next-line react/no-danger */} - -

- )} -
-
- ); diff --git a/apps/app/src/client/components/Admin/App/AppSettingsPageContents.tsx b/apps/app/src/client/components/Admin/App/AppSettingsPageContents.tsx index 13729252d23..68b9713a2d2 100644 --- a/apps/app/src/client/components/Admin/App/AppSettingsPageContents.tsx +++ b/apps/app/src/client/components/Admin/App/AppSettingsPageContents.tsx @@ -14,6 +14,7 @@ import AppSetting from './AppSetting'; import FileUploadSetting from './FileUploadSetting'; import MailSetting from './MailSetting'; import { MaintenanceMode } from './MaintenanceMode'; +import PageBulkExportSettings from './PageBulkExportSettings'; import QuestionnaireSettings from './QuestionnaireSettings'; import SiteUrlSetting from './SiteUrlSetting'; import V5PageMigration from './V5PageMigration'; @@ -108,6 +109,13 @@ const AppSettingsPageContents = (props: Props) => { +
+
+

{t('admin:app_setting.page_bulk_export_settings')}

+ +
+
+

{t('admin:app_setting.questionnaire_settings')}

diff --git a/apps/app/src/client/components/Admin/App/PageBulkExportSettings.tsx b/apps/app/src/client/components/Admin/App/PageBulkExportSettings.tsx new file mode 100644 index 00000000000..e4e815da9ae --- /dev/null +++ b/apps/app/src/client/components/Admin/App/PageBulkExportSettings.tsx @@ -0,0 +1,136 @@ +import { + useState, useCallback, useEffect, +} from 'react'; + +import { LoadingSpinner } from '@growi/ui/dist/components'; +import { useTranslation } from 'next-i18next'; + +import { apiv3Put } from '~/client/util/apiv3-client'; +import { toastSuccess, toastError } from '~/client/util/toastr'; +import { useSWRxAppSettings } from '~/stores/admin/app-settings'; + +import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow'; + +const PageBulkExportSettings = (): JSX.Element => { + const { t } = useTranslation(['admin', 'commons']); + + const { data, error, mutate } = useSWRxAppSettings(); + + const [isBulkExportPagesEnabled, setIsBulkExportPagesEnabled] = useState(data?.isBulkExportPagesEnabled); + const [bulkExportDownloadExpirationSeconds, setBulkExportDownloadExpirationSeconds] = useState(data?.bulkExportDownloadExpirationSeconds); + + const changeBulkExportDownloadExpirationSeconds = (bulkExportDownloadExpirationDays: number) => { + const bulkExportDownloadExpirationSeconds = bulkExportDownloadExpirationDays * 24 * 60 * 60; + setBulkExportDownloadExpirationSeconds(bulkExportDownloadExpirationSeconds); + }; + + const onSubmitHandler = useCallback(async() => { + try { + await apiv3Put('/app-settings/page-bulk-export-settings', { + isBulkExportPagesEnabled, + bulkExportDownloadExpirationSeconds, + }); + toastSuccess(t('commons:toaster.update_successed', { target: t('app_setting.questionnaire_settings') })); + } + catch (err) { + toastError(err); + } + mutate(); + }, [isBulkExportPagesEnabled, bulkExportDownloadExpirationSeconds, mutate, t]); + + useEffect(() => { + if (data?.useOnlyEnvVarForFileUploadType) { + setIsBulkExportPagesEnabled(data?.envIsBulkExportPagesEnabled); + } + else { + setIsBulkExportPagesEnabled(data?.isBulkExportPagesEnabled); + } + setBulkExportDownloadExpirationSeconds(data?.bulkExportDownloadExpirationSeconds); + }, [data]); + + const isLoading = data === undefined && error === undefined; + + return ( + <> + {isLoading && ( +
+ +
+ )} + + {!isLoading && ( + <> +

+ {t('admin:app_setting.page_bulk_export_explanation')}
+ + {t('admin:app_setting.page_bulk_export_warning')} + +

+ +
+ + +
+
+ setIsBulkExportPagesEnabled(e.target.checked)} + /> + +
+ {data?.useOnlyEnvVarsForIsBulkExportPagesEnabled && ( +

+ {/* eslint-disable-next-line react/no-danger */} + +

+ )} +
+
+ +
+
+ + +
+ +
+
+
+ + + + )} + + ); +}; + +export default PageBulkExportSettings; diff --git a/apps/app/src/client/components/Admin/App/QuestionnaireSettings.tsx b/apps/app/src/client/components/Admin/App/QuestionnaireSettings.tsx index e3162f75d1d..fc94985b5f6 100644 --- a/apps/app/src/client/components/Admin/App/QuestionnaireSettings.tsx +++ b/apps/app/src/client/components/Admin/App/QuestionnaireSettings.tsx @@ -72,37 +72,51 @@ const QuestionnaireSettings = (): JSX.Element => { {!isLoading && ( <> -
-
- - +
+ + +
+
+ + +
-
-
- - -

- {t('app_setting.url_anonymization_explanation')} -

+
+ + +
+
+ + +

+ {t('app_setting.url_anonymization_explanation')} +

+
diff --git a/apps/app/src/client/components/TreeItem/NewPageInput/use-new-page-input.tsx b/apps/app/src/client/components/TreeItem/NewPageInput/use-new-page-input.tsx index cd86eaf517b..e3053fcf19b 100644 --- a/apps/app/src/client/components/TreeItem/NewPageInput/use-new-page-input.tsx +++ b/apps/app/src/client/components/TreeItem/NewPageInput/use-new-page-input.tsx @@ -99,7 +99,7 @@ export const useNewPageInput = (): UseNewPageInput => { const isCreatable = pagePathUtils.isCreatablePage(newPagePath); if (!isCreatable) { - toastWarning(t('you_can_not_create_page_with_this_name')); + toastWarning(t('you_can_not_create_page_with_this_name_or_hierarchy')); return; } diff --git a/apps/app/src/client/services/AdminAppContainer.js b/apps/app/src/client/services/AdminAppContainer.js index 05f78993620..6edddffe915 100644 --- a/apps/app/src/client/services/AdminAppContainer.js +++ b/apps/app/src/client/services/AdminAppContainer.js @@ -23,8 +23,6 @@ export default class AdminAppContainer extends Container { globalLang: '', isEmailPublishedForNewUser: true, fileUpload: '', - isBulkExportPagesEnabled: false, - isFixedIsBulkExportPagesEnabled: false, isV5Compatible: null, siteUrl: '', @@ -102,7 +100,6 @@ export default class AdminAppContainer extends Container { globalLang: appSettingsParams.globalLang, isEmailPublishedForNewUser: appSettingsParams.isEmailPublishedForNewUser, fileUpload: appSettingsParams.fileUpload, - isBulkExportPagesEnabled: appSettingsParams.isBulkExportPagesEnabled, isV5Compatible: appSettingsParams.isV5Compatible, siteUrl: appSettingsParams.siteUrl, siteUrlUseOnlyEnvVars: appSettingsParams.siteUrlUseOnlyEnvVars, @@ -160,12 +157,6 @@ export default class AdminAppContainer extends Container { this.setState({ fileUploadType: appSettingsParams.envFileUploadType }); this.setState({ isFixedFileUploadByEnvVar: true }); } - - if (appSettingsParams.useOnlyEnvVarsForIsBulkExportPagesEnabled) { - this.setState({ isBulkExportPagesEnabled: appSettingsParams.envIsBulkExportPagesEnabled }); - this.setState({ isFixedIsBulkExportPagesEnabled: true }); - } - } /** @@ -203,13 +194,6 @@ export default class AdminAppContainer extends Container { this.setState({ fileUpload }); } - /** - * Change isBulkExportPagesEnabled - */ - changeIsPageBulkExportEnabled(isBulkExportPagesEnabled) { - this.setState({ isBulkExportPagesEnabled }); - } - /** * Change site url */ @@ -412,7 +396,6 @@ export default class AdminAppContainer extends Container { globalLang: this.state.globalLang, isEmailPublishedForNewUser: this.state.isEmailPublishedForNewUser, fileUpload: this.state.fileUpload, - isBulkExportPagesEnabled: this.state.isBulkExportPagesEnabled, }); const { appSettingParams } = response.data; return appSettingParams; diff --git a/apps/app/src/components/Common/PagePathNav/PagePathNav.module.scss b/apps/app/src/components/Common/PagePathNav/PagePathNav.module.scss index c11a6ea9e82..34a506a19d8 100644 --- a/apps/app/src/components/Common/PagePathNav/PagePathNav.module.scss +++ b/apps/app/src/components/Common/PagePathNav/PagePathNav.module.scss @@ -4,7 +4,7 @@ .grw-page-path-nav-layout :global { .grw-page-path-nav-copydropdown { - display: none; + visibility: hidden; @include bs.media-breakpoint-down(md) { display: block; } @@ -15,7 +15,7 @@ &:global { &:hover { .grw-page-path-nav-copydropdown { - display: block; + visibility: visible; } } } diff --git a/apps/app/src/features/openai/server/services/openai.ts b/apps/app/src/features/openai/server/services/openai.ts index 608f6576278..bf9c4dcc765 100644 --- a/apps/app/src/features/openai/server/services/openai.ts +++ b/apps/app/src/features/openai/server/services/openai.ts @@ -2,6 +2,7 @@ import assert from 'node:assert'; import { Readable, Transform } from 'stream'; import { pipeline } from 'stream/promises'; +import type { IPagePopulatedToShowRevision } from '@growi/core'; import { PageGrant, isPopulated } from '@growi/core'; import type { HydratedDocument, Types } from 'mongoose'; import mongoose from 'mongoose'; @@ -20,7 +21,7 @@ import { createBatchStream } from '~/server/util/batch-stream'; import loggerFactory from '~/utils/logger'; import { OpenaiServiceTypes } from '../../interfaces/ai'; -import { sanitizeMarkdown } from '../utils/sanitize-markdown'; +import { convertMarkdownToHtml } from '../utils/convert-markdown-to-html'; import { getClient } from './client-delegator'; // import { splitMarkdownIntoChunks } from './markdown-splitter/markdown-token-splitter'; @@ -157,9 +158,9 @@ class OpenaiService implements IOpenaiService { // } // } - private async uploadFile(pageId: Types.ObjectId, body: string): Promise { - const sanitizedMarkdown = await sanitizeMarkdown(body); - const file = await toFile(Readable.from(sanitizedMarkdown), `${pageId}.md`); + private async uploadFile(pageId: Types.ObjectId, pagePath: string, revisionBody: string): Promise { + const convertedHtml = await convertMarkdownToHtml({ pagePath, revisionBody }); + const file = await toFile(Readable.from(convertedHtml), `${pageId}.html`); const uploadedFile = await this.client.uploadFile(file); return uploadedFile; } @@ -183,17 +184,17 @@ class OpenaiService implements IOpenaiService { async createVectorStoreFile(pages: Array>): Promise { const vectorStore = await this.getOrCreateVectorStoreForPublicScope(); const vectorStoreFileRelationsMap: VectorStoreFileRelationsMap = new Map(); - const processUploadFile = async(page: PageDocument) => { + const processUploadFile = async(page: HydratedDocument) => { if (page._id != null && page.grant === PageGrant.GRANT_PUBLIC && page.revision != null) { if (isPopulated(page.revision) && page.revision.body.length > 0) { - const uploadedFile = await this.uploadFile(page._id, page.revision.body); + const uploadedFile = await this.uploadFile(page._id, page.path, page.revision.body); prepareVectorStoreFileRelations(vectorStore._id, page._id, uploadedFile.id, vectorStoreFileRelationsMap); return; } const pagePopulatedToShowRevision = await page.populateDataToShowRevision(); if (pagePopulatedToShowRevision.revision != null && pagePopulatedToShowRevision.revision.body.length > 0) { - const uploadedFile = await this.uploadFile(page._id, pagePopulatedToShowRevision.revision.body); + const uploadedFile = await this.uploadFile(page._id, page.path, pagePopulatedToShowRevision.revision.body); prepareVectorStoreFileRelations(vectorStore._id, page._id, uploadedFile.id, vectorStoreFileRelationsMap); } } diff --git a/apps/app/src/features/openai/server/utils/convert-markdown-to-html.ts b/apps/app/src/features/openai/server/utils/convert-markdown-to-html.ts new file mode 100644 index 00000000000..fa0dcf4dbe4 --- /dev/null +++ b/apps/app/src/features/openai/server/utils/convert-markdown-to-html.ts @@ -0,0 +1,89 @@ +import { dynamicImport } from '@cspell/dynamic-import'; +import type { Root, Code } from 'mdast'; +import type * as RehypeMeta from 'rehype-meta'; +import type * as RehypeStringify from 'rehype-stringify'; +import type * as RemarkParse from 'remark-parse'; +import type * as RemarkRehype from 'remark-rehype'; +import type * as Unified from 'unified'; +import type * as UnistUtilVisit from 'unist-util-visit'; + +interface ModuleCache { + unified?: typeof Unified.unified; + visit?: typeof UnistUtilVisit.visit; + remarkParse?: typeof RemarkParse.default; + remarkRehype?: typeof RemarkRehype.default; + rehypeMeta?: typeof RehypeMeta.default; + rehypeStringify?: typeof RehypeStringify.default; +} + +let moduleCache: ModuleCache = {}; + +const initializeModules = async(): Promise => { + if (moduleCache.unified != null + && moduleCache.visit != null + && moduleCache.remarkParse != null + && moduleCache.remarkRehype != null + && moduleCache.rehypeMeta != null + && moduleCache.rehypeStringify != null + ) { + return; + } + + const [ + { unified }, + { visit }, + { default: remarkParse }, + { default: remarkRehype }, + { default: rehypeMeta }, + { default: rehypeStringify }, + ] = await Promise.all([ + dynamicImport('unified', __dirname), + dynamicImport('unist-util-visit', __dirname), + dynamicImport('remark-parse', __dirname), + dynamicImport('remark-rehype', __dirname), + dynamicImport('rehype-meta', __dirname), + dynamicImport('rehype-stringify', __dirname), + ]); + + moduleCache = { + unified, + visit, + remarkParse, + remarkRehype, + rehypeMeta, + rehypeStringify, + }; +}; + +export const convertMarkdownToHtml = async({ pagePath, revisionBody }: { pagePath: string, revisionBody: string }): Promise => { + await initializeModules(); + + const { + unified, visit, remarkParse, remarkRehype, rehypeMeta, rehypeStringify, + } = moduleCache; + + if (unified == null || visit == null || remarkParse == null || remarkRehype == null || rehypeMeta == null || rehypeStringify == null) { + throw new Error('Failed to initialize required modules'); + } + + const sanitizeMarkdown = () => { + return (tree: Root) => { + visit(tree, 'code', (node: Code) => { + if (node.lang === 'drawio') { + node.value = ''; + } + }); + }; + }; + + const processor = unified() + .use(remarkParse) + .use(sanitizeMarkdown) + .use(remarkRehype) + .use(rehypeMeta, { + title: pagePath, + }) + .use(rehypeStringify); + + return processor.processSync(revisionBody).toString(); +}; diff --git a/apps/app/src/features/openai/server/utils/sanitize-markdown.ts b/apps/app/src/features/openai/server/utils/sanitize-markdown.ts deleted file mode 100644 index f604be5e058..00000000000 --- a/apps/app/src/features/openai/server/utils/sanitize-markdown.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { dynamicImport } from '@cspell/dynamic-import'; -import type { Root, Code } from 'mdast'; -import type * as RemarkParse from 'remark-parse'; -import type * as RemarkStringify from 'remark-stringify'; -import type * as Unified from 'unified'; -import type * as UnistUtilVisit from 'unist-util-visit'; - -interface ModuleCache { - remarkParse?: typeof RemarkParse.default; - remarkStringify?: typeof RemarkStringify.default; - unified?: typeof Unified.unified; - visit?: typeof UnistUtilVisit.visit; -} - -let moduleCache: ModuleCache = {}; - -const initializeModules = async(): Promise => { - if (moduleCache.remarkParse != null && moduleCache.remarkStringify != null && moduleCache.unified != null && moduleCache.visit != null) { - return; - } - - const [{ default: remarkParse }, { default: remarkStringify }, { unified }, { visit }] = await Promise.all([ - dynamicImport('remark-parse', __dirname), - dynamicImport('remark-stringify', __dirname), - dynamicImport('unified', __dirname), - dynamicImport('unist-util-visit', __dirname), - ]); - - moduleCache = { - remarkParse, - remarkStringify, - unified, - visit, - }; -}; - -export const sanitizeMarkdown = async(markdown: string): Promise => { - await initializeModules(); - - const { - remarkParse, remarkStringify, unified, visit, - } = moduleCache; - - - if (remarkParse == null || remarkStringify == null || unified == null || visit == null) { - throw new Error('Failed to initialize required modules'); - } - - const sanitize = () => { - return (tree: Root) => { - visit(tree, 'code', (node: Code) => { - if (node.lang === 'drawio') { - node.value = ''; - } - }); - }; - }; - - const processor = unified() - .use(remarkParse) - .use(sanitize) - .use(remarkStringify); - - return processor.processSync(markdown).toString(); -}; diff --git a/apps/app/src/features/page-bulk-export/client/components/PageBulkExportSelectModal.tsx b/apps/app/src/features/page-bulk-export/client/components/PageBulkExportSelectModal.tsx index 2bf735a9480..0196c3df6a4 100644 --- a/apps/app/src/features/page-bulk-export/client/components/PageBulkExportSelectModal.tsx +++ b/apps/app/src/features/page-bulk-export/client/components/PageBulkExportSelectModal.tsx @@ -54,17 +54,20 @@ const PageBulkExportSelectModal = (): JSX.Element => { return ( <> {status != null && ( - + {t('page_export.bulk_export')} - {t('page_export.choose_export_format')} -
- - {t('page_export.bulk_export_notice')} - -
+

+ {t('page_export.bulk_export_download_explanation')} + warning{t('Warning')} +

    +
  • {t('page_export.bulk_export_exec_time_warning')}
  • +
  • {t('page_export.large_bulk_export_warning')}
  • +
+

+ {t('page_export.choose_export_format')}: