diff --git a/apps/app/.eslintrc.js b/apps/app/.eslintrc.js index c5aac4ad847..7dbb8ac1b7d 100644 --- a/apps/app/.eslintrc.js +++ b/apps/app/.eslintrc.js @@ -55,6 +55,16 @@ module.exports = { 'src/client/components/*.jsx', 'src/client/components/*.ts', 'src/client/components/*.js', + 'src/client/components/Admin/*.ts', + 'src/client/components/Admin/*.tsx', + 'src/client/components/Admin/*.scss', + 'src/client/components/Admin/AdminHome/**', + 'src/client/components/Admin/Common/**', + 'src/client/components/Admin/ElasticsearchManagement/**', + 'src/client/components/Admin/ExportArchiveData/**', + 'src/client/components/Admin/ImportData/**', + 'src/client/components/Admin/LegacySlackIntegration/**', + 'src/client/components/Admin/MarkdownSetting/**', 'src/client/components/Admin/App/**', 'src/client/components/Admin/AuditLog/**', 'src/client/components/Admin/Customize/**', diff --git a/apps/app/src/client/components/Admin/AdminHome/AdminHome.jsx b/apps/app/src/client/components/Admin/AdminHome/AdminHome.jsx index fc43abd8c32..09f2be3e647 100644 --- a/apps/app/src/client/components/Admin/AdminHome/AdminHome.jsx +++ b/apps/app/src/client/components/Admin/AdminHome/AdminHome.jsx @@ -1,5 +1,4 @@ -import React, { useEffect, useCallback } from 'react'; - +import React, { useCallback, useEffect } from 'react'; import { useTranslation } from 'next-i18next'; import PropTypes from 'prop-types'; import { CopyToClipboard } from 'react-copy-to-clipboard'; @@ -10,14 +9,10 @@ import { toastError } from '~/client/util/toastr'; import { useSWRxV5MigrationStatus } from '~/stores/page-listing'; import loggerFactory from '~/utils/logger'; - import { withUnstatedContainers } from '../../UnstatedUtils'; - - import { EnvVarsTable } from './EnvVarsTable'; import SystemInfomationTable from './SystemInfomationTable'; - const logger = loggerFactory('growi:admin'); const AdminHome = (props) => { @@ -25,11 +20,10 @@ const AdminHome = (props) => { const { t } = useTranslation(); const { data: migrationStatus } = useSWRxV5MigrationStatus(); - const fetchAdminHomeData = useCallback(async() => { + const fetchAdminHomeData = useCallback(async () => { try { await adminHomeContainer.retrieveAdminHomeData(); - } - catch (err) { + } catch (err) { toastError(err); logger.error(err); } @@ -48,25 +42,36 @@ const AdminHome = (props) => {

{t('admin:maintenance_mode.maintenance_mode')}

-

- {t('admin:maintenance_mode.description')} -

+

{t('admin:maintenance_mode.description')}


- - {t('admin:maintenance_mode.end_maintenance_mode')} + + + {t('admin:maintenance_mode.end_maintenance_mode')} + ) } { // Alert message will be displayed in case that V5 migration has not been compleated - (migrationStatus != null && !migrationStatus.isV5Compatible) - && ( -
+ migrationStatus != null && !migrationStatus.isV5Compatible && ( +
{t('admin:v5_page_migration.migration_desc')} - + {t('admin:v5_page_migration.upgrade_to_v5')}
@@ -80,43 +85,65 @@ const AdminHome = (props) => {
-

{t('admin:admin_top.system_information')}

+

+ {t('admin:admin_top.system_information')} +

-

{t('admin:admin_top.list_of_env_vars')}

+

+ {t('admin:admin_top.list_of_env_vars')} +

{t('admin:admin_top.env_var_priority')}

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

+

-

{t('admin:admin_top.bug_report')}

+

+ {t('admin:admin_top.bug_report')} +

adminHomeContainer.onCopyPrefilledHostInformation()} > - {t('admin:admin_top:copy_prefilled_host_information:done')} - {/* eslint-disable-next-line react/no-danger */} - +
@@ -124,8 +151,9 @@ const AdminHome = (props) => { ); }; - -const AdminHomeWrapper = withUnstatedContainers(AdminHome, [AdminHomeContainer]); +const AdminHomeWrapper = withUnstatedContainers(AdminHome, [ + AdminHomeContainer, +]); AdminHome.propTypes = { adminHomeContainer: PropTypes.instanceOf(AdminHomeContainer).isRequired, diff --git a/apps/app/src/client/components/Admin/AdminHome/EnvVarsTable.tsx b/apps/app/src/client/components/Admin/AdminHome/EnvVarsTable.tsx index ccb5d46cbcb..334a969a369 100644 --- a/apps/app/src/client/components/Admin/AdminHome/EnvVarsTable.tsx +++ b/apps/app/src/client/components/Admin/AdminHome/EnvVarsTable.tsx @@ -1,12 +1,14 @@ -import React, { type JSX } from 'react'; - +import type React from 'react'; +import type { JSX } from 'react'; import { LoadingSpinner } from '@growi/ui/dist/components'; type EnvVarsTableProps = { - envVars?: Record, -} + envVars?: Record; +}; -export const EnvVarsTable: React.FC = (props: EnvVarsTableProps) => { +export const EnvVarsTable: React.FC = ( + props: EnvVarsTableProps, +) => { const { envVars } = props; if (envVars == null) { return ; @@ -27,9 +29,7 @@ export const EnvVarsTable: React.FC = (props: EnvVarsTablePro return ( - - {envVarRows} - + {envVarRows}
); }; diff --git a/apps/app/src/client/components/Admin/AdminHome/SystemInfomationTable.tsx b/apps/app/src/client/components/Admin/AdminHome/SystemInfomationTable.tsx index c1ce1241987..6011d7fc8e1 100644 --- a/apps/app/src/client/components/Admin/AdminHome/SystemInfomationTable.tsx +++ b/apps/app/src/client/components/Admin/AdminHome/SystemInfomationTable.tsx @@ -1,55 +1,62 @@ import React from 'react'; - import { LoadingSpinner } from '@growi/ui/dist/components'; import AdminHomeContainer from '~/client/services/AdminHomeContainer'; import { withUnstatedContainers } from '../../UnstatedUtils'; - type Props = { - adminHomeContainer: AdminHomeContainer, -} + adminHomeContainer: AdminHomeContainer; +}; const SystemInformationTable = (props: Props) => { const { adminHomeContainer } = props; - const { - growiVersion, nodeVersion, npmVersion, pnpmVersion, - } = adminHomeContainer.state; + const { growiVersion, nodeVersion, npmVersion, pnpmVersion } = + adminHomeContainer.state; - if (growiVersion == null || nodeVersion == null || npmVersion == null || pnpmVersion == null) { + if ( + growiVersion == null || + nodeVersion == null || + npmVersion == null || + pnpmVersion == null + ) { return ; } return ( - +
- + - + - + - +
GROWI{ growiVersion }{growiVersion}
node.js{ nodeVersion }{nodeVersion}
npm{ npmVersion }{npmVersion}
pnpm{ pnpmVersion }{pnpmVersion}
); - }; /** * Wrapper component for using unstated */ -const SystemInformationTableWrapper = withUnstatedContainers(SystemInformationTable, [AdminHomeContainer]); +const SystemInformationTableWrapper = withUnstatedContainers( + SystemInformationTable, + [AdminHomeContainer], +); export default SystemInformationTableWrapper; diff --git a/apps/app/src/client/components/Admin/AuditLogManagement.tsx b/apps/app/src/client/components/Admin/AuditLogManagement.tsx index 8a8704908db..7a4e1f4da0d 100644 --- a/apps/app/src/client/components/Admin/AuditLogManagement.tsx +++ b/apps/app/src/client/components/Admin/AuditLogManagement.tsx @@ -1,6 +1,6 @@ +import type React from 'react'; import type { FC } from 'react'; -import React, { useState, useCallback, useRef } from 'react'; - +import { useCallback, useRef, useState } from 'react'; import { LoadingSpinner } from '@growi/ui/dist/components'; import { format } from 'date-fns/format'; import { useAtomValue } from 'jotai'; @@ -9,11 +9,13 @@ import { useTranslation } from 'react-i18next'; import type { IClearable } from '~/client/interfaces/clearable'; import { toastError } from '~/client/util/toastr'; import type { SupportedActionType } from '~/interfaces/activity'; -import { auditLogEnabledAtom, auditLogAvailableActionsAtom } from '~/states/server-configurations'; +import { + auditLogAvailableActionsAtom, + auditLogEnabledAtom, +} from '~/states/server-configurations'; import { useSWRxActivity } from '~/stores/activity'; import PaginationWrapper from '../PaginationWrapper'; - import { ActivityTable } from './AuditLog/ActivityTable'; import { AuditLogDisableMode } from './AuditLog/AuditLogDisableMode'; import { AuditLogSettings } from './AuditLog/AuditLogSettings'; @@ -35,7 +37,9 @@ export const AuditLogManagement: FC = () => { const typeaheadRef = useRef(null); - const auditLogAvailableActionsData = useAtomValue(auditLogAvailableActionsAtom); + const auditLogAvailableActionsData = useAtomValue( + auditLogAvailableActionsAtom, + ); /* * State @@ -48,20 +52,39 @@ export const AuditLogManagement: FC = () => { const [endDate, setEndDate] = useState(null); const [selectedUsernames, setSelectedUsernames] = useState([]); const [actionMap, setActionMap] = useState( - new Map(auditLogAvailableActionsData != null ? auditLogAvailableActionsData.map(action => [action, true]) : []), + new Map( + auditLogAvailableActionsData != null + ? auditLogAvailableActionsData.map((action) => [action, true]) + : [], + ), ); /* * Fetch */ - const selectedDate = { startDate: formatDate(startDate), endDate: formatDate(endDate) }; - const selectedActionList = Array.from(actionMap.entries()).filter(v => v[1]).map(v => v[0]); - const searchFilter = { actions: selectedActionList, dates: selectedDate, usernames: selectedUsernames }; - - const { data: activityData, mutate: mutateActivity, error } = useSWRxActivity(PAGING_LIMIT, offset, searchFilter); + const selectedDate = { + startDate: formatDate(startDate), + endDate: formatDate(endDate), + }; + const selectedActionList = Array.from(actionMap.entries()) + .filter((v) => v[1]) + .map((v) => v[0]); + const searchFilter = { + actions: selectedActionList, + dates: selectedDate, + usernames: selectedUsernames, + }; + + const { + data: activityData, + mutate: mutateActivity, + error, + } = useSWRxActivity(PAGING_LIMIT, offset, searchFilter); const activityList = activityData?.docs != null ? activityData.docs : []; - const totalActivityNum = activityData?.totalDocs != null ? activityData.totalDocs : 0; - const totalPagingPages = activityData?.totalPages != null ? activityData.totalPages : 0; + const totalActivityNum = + activityData?.totalDocs != null ? activityData.totalDocs : 0; + const totalPagingPages = + activityData?.totalPages != null ? activityData.totalPages : 0; const isLoading = activityData === undefined && error == null; if (error != null) { @@ -83,17 +106,25 @@ export const AuditLogManagement: FC = () => { setEndDate(dateList[1]); }, []); - const actionCheckboxChangedHandler = useCallback((action: SupportedActionType) => { - setActivePageNumber(1); - actionMap.set(action, !actionMap.get(action)); - setActionMap(new Map(actionMap.entries())); - }, [actionMap, setActionMap]); + const actionCheckboxChangedHandler = useCallback( + (action: SupportedActionType) => { + setActivePageNumber(1); + actionMap.set(action, !actionMap.get(action)); + setActionMap(new Map(actionMap.entries())); + }, + [actionMap], + ); - const multipleActionCheckboxChangedHandler = useCallback((actions: SupportedActionType[], isChecked) => { - setActivePageNumber(1); - actions.forEach(action => actionMap.set(action, isChecked)); - setActionMap(new Map(actionMap.entries())); - }, [actionMap, setActionMap]); + const multipleActionCheckboxChangedHandler = useCallback( + (actions: SupportedActionType[], isChecked) => { + setActivePageNumber(1); + actions.forEach((action) => { + actionMap.set(action, isChecked); + }); + setActionMap(new Map(actionMap.entries())); + }, + [actionMap], + ); const setUsernamesHandler = useCallback((usernames: string[]) => { setActivePageNumber(1); @@ -108,41 +139,55 @@ export const AuditLogManagement: FC = () => { typeaheadRef.current?.clear(); if (auditLogAvailableActionsData != null) { - setActionMap(new Map(auditLogAvailableActionsData.map(action => [action, true]))); + setActionMap( + new Map( + auditLogAvailableActionsData.map((action) => [action, true]), + ), + ); } - }, [setActivePageNumber, setStartDate, setEndDate, setSelectedUsernames, setActionMap, auditLogAvailableActionsData]); + }, [auditLogAvailableActionsData]); const reloadButtonPushedHandler = useCallback(() => { setActivePageNumber(1); mutateActivity(); }, [mutateActivity]); - const jumpPageInputChangeHandler = useCallback((e: React.ChangeEvent) => { - const inputNumber = Number(e.target.value); - const isNan = Number.isNaN(inputNumber); - - if (!isNan) { - // eslint-disable-next-line no-nested-ternary - const jumpPageNumber = inputNumber > totalPagingPages ? totalPagingPages : inputNumber <= 0 ? activePageNumber : inputNumber; - setJumpPageNumber(jumpPageNumber); - } - else { - setJumpPageNumber(activePageNumber); - } - }, [totalPagingPages, activePageNumber, setJumpPageNumber]); + const jumpPageInputChangeHandler = useCallback( + (e: React.ChangeEvent) => { + const inputNumber = Number(e.target.value); + const isNan = Number.isNaN(inputNumber); + + if (!isNan) { + // eslint-disable-next-line no-nested-ternary + const jumpPageNumber = + inputNumber > totalPagingPages + ? totalPagingPages + : inputNumber <= 0 + ? activePageNumber + : inputNumber; + setJumpPageNumber(jumpPageNumber); + } else { + setJumpPageNumber(activePageNumber); + } + }, + [totalPagingPages, activePageNumber], + ); - const jumpPageInputKeyDownHandler = useCallback((e) => { - if (e.key === 'Enter') { - setActivePageNumber(jumpPageNumber); - } - }, [setActivePageNumber, jumpPageNumber]); + const jumpPageInputKeyDownHandler = useCallback( + (e) => { + if (e.key === 'Enter') { + setActivePageNumber(jumpPageNumber); + } + }, + [jumpPageNumber], + ); const jumpPageButtonPushedHandler = useCallback(() => { setActivePageNumber(jumpPageNumber); }, [jumpPageNumber]); - // eslint-disable-next-line max-len - const activityCounter = `${activityList.length === 0 ? 0 : offset + 1} - ${(PAGING_LIMIT * activePageNumber) - (PAGING_LIMIT - activityList.length)} of ${totalActivityNum}`; + const startIndex = activityList.length === 0 ? 0 : offset + 1; + const endIndex = activityList.length === 0 ? 0 : offset + activityList.length; if (!auditLogEnabled) { return ; @@ -150,20 +195,36 @@ export const AuditLogManagement: FC = () => { return (
-

- {isSettingPage ? t('audit_log_management.audit_log_settings') : t('audit_log_management.audit_log')} + {isSettingPage + ? t('audit_log_management.audit_log_settings') + : t('audit_log_management.audit_log')} - { !isSettingPage && ( - )} @@ -199,28 +260,28 @@ export const AuditLogManagement: FC = () => {

-
-

- - { isLoading - ? ( -

- -
- ) - : ( - - ) - } +

+ {startIndex} - {endIndex} of{' '} + {totalActivityNum} +

+ + {isLoading ? ( +
+ +
+ ) : ( + + )}
{ />
- + { onChange={jumpPageInputChangeHandler} onKeyDown={jumpPageInputKeyDownHandler} /> -
diff --git a/apps/app/src/client/components/Admin/Common/Accordion.jsx b/apps/app/src/client/components/Admin/Common/Accordion.jsx index 200e2bc2154..72d454e7473 100644 --- a/apps/app/src/client/components/Admin/Common/Accordion.jsx +++ b/apps/app/src/client/components/Admin/Common/Accordion.jsx @@ -1,9 +1,7 @@ import React, { useState } from 'react'; - import PropTypes from 'prop-types'; import { Collapse } from 'reactstrap'; - const Accordion = (props) => { const [isOpen, setIsOpen] = useState(props.isOpenDefault); return ( @@ -14,15 +12,13 @@ const Accordion = (props) => { type="button" data-bs-toggle="collapse" aria-expanded="true" - onClick={() => setIsOpen(prevState => !prevState)} + onClick={() => setIsOpen((prevState) => !prevState)} > {props.title}

-
- {props.children} -
+
{props.children}
); diff --git a/apps/app/src/client/components/Admin/Common/AdminInstallButtonRow.tsx b/apps/app/src/client/components/Admin/Common/AdminInstallButtonRow.tsx index 3a5a374d6c2..52e376dafc9 100644 --- a/apps/app/src/client/components/Admin/Common/AdminInstallButtonRow.tsx +++ b/apps/app/src/client/components/Admin/Common/AdminInstallButtonRow.tsx @@ -1,16 +1,22 @@ import React, { type JSX } from 'react'; type Props = { - onClick: () => void, - disabled: boolean, - -} + onClick: () => void; + disabled: boolean; +}; export const AdminInstallButtonRow = (props: Props): JSX.Element => { return (
- +
); diff --git a/apps/app/src/client/components/Admin/Common/AdminUpdateButtonRow.tsx b/apps/app/src/client/components/Admin/Common/AdminUpdateButtonRow.tsx index 98c08205671..90da2f5fae0 100644 --- a/apps/app/src/client/components/Admin/Common/AdminUpdateButtonRow.tsx +++ b/apps/app/src/client/components/Admin/Common/AdminUpdateButtonRow.tsx @@ -1,12 +1,11 @@ import React, { type JSX } from 'react'; - import { useTranslation } from 'next-i18next'; type Props = { - onClick?: () => void, - disabled?: boolean, - type?: 'button' | 'submit' | 'reset', -} + onClick?: () => void; + disabled?: boolean; + type?: 'button' | 'submit' | 'reset'; +}; const AdminUpdateButtonRow = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); @@ -22,7 +21,7 @@ const AdminUpdateButtonRow = (props: Props): JSX.Element => { onClick={props.onClick} disabled={props.disabled ?? false} > - { t('Update') } + {t('Update')} diff --git a/apps/app/src/client/components/Admin/Common/LabeledProgressBar.tsx b/apps/app/src/client/components/Admin/Common/LabeledProgressBar.tsx index f6b0ebe28ea..8c6a46e288d 100644 --- a/apps/app/src/client/components/Admin/Common/LabeledProgressBar.tsx +++ b/apps/app/src/client/components/Admin/Common/LabeledProgressBar.tsx @@ -1,18 +1,15 @@ import React, { type JSX } from 'react'; - import { Progress } from 'reactstrap'; type Props = { - header: string, - currentCount: number, - totalCount: number, - isInProgress?: boolean, -} + header: string; + currentCount: number; + totalCount: number; + isInProgress?: boolean; +}; const LabeledProgressBar = (props: Props): JSX.Element => { - const { - header, currentCount, totalCount, isInProgress, - } = props; + const { header, currentCount, totalCount, isInProgress } = props; const progressingColor = isInProgress ? 'info' : 'success'; @@ -20,14 +17,22 @@ const LabeledProgressBar = (props: Props): JSX.Element => { <>
{header} -
{currentCount} / {totalCount}
+
+ {currentCount} / {totalCount} +
- + ); - }; export default LabeledProgressBar; diff --git a/apps/app/src/client/components/Admin/ElasticsearchManagement/ElasticsearchManagement.tsx b/apps/app/src/client/components/Admin/ElasticsearchManagement/ElasticsearchManagement.tsx index 54e5fca456d..b9276b4c52b 100644 --- a/apps/app/src/client/components/Admin/ElasticsearchManagement/ElasticsearchManagement.tsx +++ b/apps/app/src/client/components/Admin/ElasticsearchManagement/ElasticsearchManagement.tsx @@ -1,10 +1,9 @@ -import React, { useEffect, useState, useCallback } from 'react'; - +import React, { useCallback, useEffect, useState } from 'react'; import { useAtomValue } from 'jotai'; import { useTranslation } from 'next-i18next'; import { apiv3Get, apiv3Post, apiv3Put } from '~/client/util/apiv3-client'; -import { toastSuccess, toastError } from '~/client/util/toastr'; +import { toastError, toastSuccess } from '~/client/util/toastr'; import { useAdminSocket } from '~/features/admin/states/socket-io'; import { SocketEventName } from '~/interfaces/websocket'; import { isSearchServiceReachableAtom } from '~/states/server-configurations'; @@ -24,7 +23,8 @@ const ElasticsearchManagement = (): JSX.Element => { const [isConnected, setIsConnected] = useState(false); const [isConfigured, setIsConfigured] = useState(false); - const [isReconnectingProcessing, setIsReconnectingProcessing] = useState(false); + const [isReconnectingProcessing, setIsReconnectingProcessing] = + useState(false); const [isRebuildingProcessing, setIsRebuildingProcessing] = useState(false); const [isRebuildingCompleted, setIsRebuildingCompleted] = useState(false); @@ -32,8 +32,7 @@ const ElasticsearchManagement = (): JSX.Element => { const [indicesData, setIndicesData] = useState(null); const [aliasesData, setAliasesData] = useState(null); - - const retrieveIndicesStatus = useCallback(async() => { + const retrieveIndicesStatus = useCallback(async () => { try { const { data } = await apiv3Get('/search/indices'); const { info } = data; @@ -46,8 +45,7 @@ const ElasticsearchManagement = (): JSX.Element => { setIsNormalized(info.isNormalized); return info.isNormalized; - } - catch (errors: unknown) { + } catch (errors: unknown) { setIsConnected(false); // evaluate whether configured or not @@ -58,14 +56,12 @@ const ElasticsearchManagement = (): JSX.Element => { } } toastError(errors as Error[]); - } - else { + } else { toastError(errors as Error); } return false; - } - finally { + } finally { setIsInitialized(true); } }, []); @@ -82,12 +78,12 @@ const ElasticsearchManagement = (): JSX.Element => { setIsRebuildingProcessing(true); }); - socket.on(SocketEventName.FinishAddPage, async(data) => { + socket.on(SocketEventName.FinishAddPage, async (data) => { let retryCount = 0; const maxRetries = 5; const retryDelay = 500; - const retrieveIndicesStatusWithRetry = async() => { + const retrieveIndicesStatusWithRetry = async () => { const isNormalizedResult = await retrieveIndicesStatus(); if (!isNormalizedResult && retryCount < maxRetries) { retryCount++; @@ -111,13 +107,12 @@ const ElasticsearchManagement = (): JSX.Element => { }; }, [retrieveIndicesStatus, socket]); - const reconnect = async() => { + const reconnect = async () => { setIsReconnectingProcessing(true); try { await apiv3Post('/search/connection'); - } - catch (e) { + } catch (e) { toastError(e); return; } @@ -126,12 +121,10 @@ const ElasticsearchManagement = (): JSX.Element => { window.location.reload(); }; - const normalizeIndices = async() => { - + const normalizeIndices = async () => { try { await apiv3Put('/search/indices', { operation: 'normalize' }); - } - catch (e) { + } catch (e) { toastError(e); } @@ -140,14 +133,13 @@ const ElasticsearchManagement = (): JSX.Element => { toastSuccess('Normalizing has succeeded'); }; - const rebuildIndices = async() => { + const rebuildIndices = async () => { setIsRebuildingProcessing(true); try { await apiv3Put('/search/indices', { operation: 'rebuild' }); toastSuccess('Rebuilding is requested'); - } - catch (e) { + } catch (e) { toastError(e); } @@ -156,7 +148,9 @@ const ElasticsearchManagement = (): JSX.Element => { const isErrorOccuredOnSearchService = !isSearchServiceReachable; - const isReconnectBtnEnabled = !isReconnectingProcessing && (!isInitialized || !isConnected || isErrorOccuredOnSearchService); + const isReconnectBtnEnabled = + !isReconnectingProcessing && + (!isInitialized || !isConnected || isErrorOccuredOnSearchService); return ( <> @@ -178,7 +172,9 @@ const ElasticsearchManagement = (): JSX.Element => { {/* Controls */}
- +
+ {t('full_text_search_management.reconnect')} +
{
- +
+ {t('full_text_search_management.normalize')} +
{
- +
+ {t('full_text_search_management.rebuild')} +
{ />
- ); - }; - -ElasticsearchManagement.propTypes = { - -}; +ElasticsearchManagement.propTypes = {}; export default ElasticsearchManagement; diff --git a/apps/app/src/client/components/Admin/ElasticsearchManagement/NormalizeIndicesControls.tsx b/apps/app/src/client/components/Admin/ElasticsearchManagement/NormalizeIndicesControls.tsx index 549b8f65553..22a20e381a4 100644 --- a/apps/app/src/client/components/Admin/ElasticsearchManagement/NormalizeIndicesControls.tsx +++ b/apps/app/src/client/components/Admin/ElasticsearchManagement/NormalizeIndicesControls.tsx @@ -1,32 +1,35 @@ import React, { type JSX } from 'react'; - import { useTranslation } from 'next-i18next'; type Props = { - isRebuildingProcessing: boolean, - onNormalizingRequested: () => void, - isNormalized?: boolean, -} + isRebuildingProcessing: boolean; + onNormalizingRequested: () => void; + isNormalized?: boolean; +}; const NormalizeIndicesControls = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); const { isNormalized, isRebuildingProcessing } = props; - const isEnabled = (isNormalized != null) && !isNormalized && !isRebuildingProcessing; + const isEnabled = + isNormalized != null && !isNormalized && !isRebuildingProcessing; return ( <>

- { t('full_text_search_management.normalize_description') }
+ {t('full_text_search_management.normalize_description')} +

); diff --git a/apps/app/src/client/components/Admin/ElasticsearchManagement/RebuildIndexControls.jsx b/apps/app/src/client/components/Admin/ElasticsearchManagement/RebuildIndexControls.jsx index ec6808d3d89..1aa97b54215 100644 --- a/apps/app/src/client/components/Admin/ElasticsearchManagement/RebuildIndexControls.jsx +++ b/apps/app/src/client/components/Admin/ElasticsearchManagement/RebuildIndexControls.jsx @@ -1,5 +1,4 @@ import React from 'react'; - import { useTranslation } from 'next-i18next'; import PropTypes from 'prop-types'; @@ -9,7 +8,6 @@ import { SocketEventName } from '~/interfaces/websocket'; import LabeledProgressBar from '../Common/LabeledProgressBar'; class RebuildIndexControls extends React.Component { - constructor(props) { super(props); @@ -44,12 +42,8 @@ class RebuildIndexControls extends React.Component { } renderProgressBar() { - const { - isRebuildingProcessing, isRebuildingCompleted, - } = this.props; - const { - total, current, - } = this.state; + const { isRebuildingProcessing, isRebuildingCompleted } = this.props; + const { total, current } = this.state; const showProgressBar = isRebuildingProcessing || isRebuildingCompleted; if (!showProgressBar) { @@ -76,25 +70,28 @@ class RebuildIndexControls extends React.Component { return ( <> - { this.renderProgressBar() } + {this.renderProgressBar()}

- { t('full_text_search_management.rebuild_description_1') }
- { t('full_text_search_management.rebuild_description_2') }
+ {t('full_text_search_management.rebuild_description_1')} +
+ {t('full_text_search_management.rebuild_description_2')} +

); } - } const RebuildIndexControlsFC = (props) => { @@ -103,7 +100,6 @@ const RebuildIndexControlsFC = (props) => { return ; }; - RebuildIndexControls.propTypes = { t: PropTypes.func.isRequired, // i18next diff --git a/apps/app/src/client/components/Admin/ElasticsearchManagement/ReconnectControls.tsx b/apps/app/src/client/components/Admin/ElasticsearchManagement/ReconnectControls.tsx index d7938fa1592..1503c42cb08 100644 --- a/apps/app/src/client/components/Admin/ElasticsearchManagement/ReconnectControls.tsx +++ b/apps/app/src/client/components/Admin/ElasticsearchManagement/ReconnectControls.tsx @@ -1,14 +1,12 @@ import React, { type JSX } from 'react'; - import { LoadingSpinner } from '@growi/ui/dist/components'; import { useTranslation } from 'next-i18next'; - type Props = { - isEnabled?: boolean, - isProcessing?: boolean, - onReconnectingRequested: () => void, -} + isEnabled?: boolean; + isProcessing?: boolean; + onReconnectingRequested: () => void; +}; const ReconnectControls = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); @@ -20,19 +18,21 @@ const ReconnectControls = (props: Props): JSX.Element => {

- { t('full_text_search_management.reconnect_description') }
+ {t('full_text_search_management.reconnect_description')} +

); - }; export default ReconnectControls; diff --git a/apps/app/src/client/components/Admin/ElasticsearchManagement/StatusTable.jsx b/apps/app/src/client/components/Admin/ElasticsearchManagement/StatusTable.jsx index 3160597af29..45754e62c42 100644 --- a/apps/app/src/client/components/Admin/ElasticsearchManagement/StatusTable.jsx +++ b/apps/app/src/client/components/Admin/ElasticsearchManagement/StatusTable.jsx @@ -1,43 +1,54 @@ import React from 'react'; - import { useTranslation } from 'next-i18next'; import PropTypes from 'prop-types'; class StatusTable extends React.PureComponent { - renderPreInitializedLabel() { return ――; } renderConnectionStatusLabels() { const { t } = this.props; - const { - isErrorOccuredOnSearchService, - isConnected, isConfigured, - } = this.props; + const { isErrorOccuredOnSearchService, isConnected, isConfigured } = + this.props; - const errorOccuredLabel = isErrorOccuredOnSearchService - ? { t('full_text_search_management.connection_status_label_erroroccured') } - : null; + const errorOccuredLabel = isErrorOccuredOnSearchService ? ( + + {t('full_text_search_management.connection_status_label_erroroccured')} + + ) : null; let connectionStatusLabel = null; if (!isConfigured) { connectionStatusLabel = ( - { t('full_text_search_management.connection_status_label_unconfigured') } + {t( + 'full_text_search_management.connection_status_label_unconfigured', + )} ); - } - else { - connectionStatusLabel = isConnected + } else { + connectionStatusLabel = isConnected ? ( // eslint-disable-next-line max-len - ? { t('full_text_search_management.connection_status_label_connected') } - : { t('full_text_search_management.connection_status_label_disconnected') }; + + {t('full_text_search_management.connection_status_label_connected')} + + ) : ( + + {t( + 'full_text_search_management.connection_status_label_disconnected', + )} + + ); } return ( <> - {connectionStatusLabel}{errorOccuredLabel} + {connectionStatusLabel} + {errorOccuredLabel} ); } @@ -45,9 +56,15 @@ class StatusTable extends React.PureComponent { renderIndicesStatusLabel() { const { t, isNormalized } = this.props; - return isNormalized - ? { t('full_text_search_management.indices_status_label_normalized') } - : { t('full_text_search_management.indices_status_label_unnormalized') }; + return isNormalized ? ( + + {t('full_text_search_management.indices_status_label_normalized')} + + ) : ( + + {t('full_text_search_management.indices_status_label_unnormalized')} + + ); } renderIndexInfoPanel(indexName, body = {}, aliases = []) { @@ -55,7 +72,10 @@ class StatusTable extends React.PureComponent { const aliasLabels = aliases.map((aliasName) => { return ( - + sell {aliasName} @@ -65,17 +85,22 @@ class StatusTable extends React.PureComponent { return (
- - - database {indexName} - + {aliasLabels}
-
-              {JSON.stringify(body, null, 2)}
-            
+
{JSON.stringify(body, null, 2)}
@@ -83,10 +108,7 @@ class StatusTable extends React.PureComponent { } renderIndexInfoPanels() { - const { - indicesData, - aliasesData, - } = this.props; + const { indicesData, aliasesData } = this.props; // data is null if (indicesData == null) { @@ -126,43 +148,60 @@ class StatusTable extends React.PureComponent { return (
- { Object.keys(indexNameToDataMap).map((indexName) => { + {Object.keys(indexNameToDataMap).map((indexName) => { return (
- { this.renderIndexInfoPanel(indexName, indexNameToDataMap[indexName], indexNameToAliasMap[indexName]) } + {this.renderIndexInfoPanel( + indexName, + indexNameToDataMap[indexName], + indexNameToAliasMap[indexName], + )}
); - }) } + })}
); } render() { const { t } = this.props; - const { - isInitialized, - } = this.props; + const { isInitialized } = this.props; return ( - - + + - - + + - - + +
{t('full_text_search_management.connection_status')}{ isInitialized ? this.renderConnectionStatusLabels() : this.renderPreInitializedLabel() } + {t('full_text_search_management.connection_status')} + + {isInitialized + ? this.renderConnectionStatusLabels() + : this.renderPreInitializedLabel()} +
{t('full_text_search_management.indices_status')}{ isInitialized ? this.renderIndicesStatusLabel() : this.renderPreInitializedLabel() } + {t('full_text_search_management.indices_status')} + + {isInitialized + ? this.renderIndicesStatusLabel() + : this.renderPreInitializedLabel()} +
{t('full_text_search_management.indices_summary')}{ isInitialized && this.renderIndexInfoPanels() } + {t('full_text_search_management.indices_summary')} + + {isInitialized && this.renderIndexInfoPanels()} +
); } - } const StatusTableWrapperFC = (props) => { diff --git a/apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTable.tsx b/apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTable.tsx index 600e963eeb5..99f281ae166 100644 --- a/apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTable.tsx +++ b/apps/app/src/client/components/Admin/ExportArchiveData/ArchiveFilesTable.tsx @@ -1,14 +1,13 @@ import React, { type JSX } from 'react'; - import { format } from 'date-fns/format'; import { useTranslation } from 'next-i18next'; import ArchiveFilesTableMenu from './ArchiveFilesTableMenu'; type ArchiveFilesTableProps = { - zipFileStats: any[], - onZipFileStatRemove: (fileName: string) => void, -} + zipFileStats: any[]; + onZipFileStatRemove: (fileName: string) => void; +}; const ArchiveFilesTable = (props: ArchiveFilesTableProps): JSX.Element => { const { t } = useTranslation(); @@ -30,8 +29,16 @@ const ArchiveFilesTable = (props: ArchiveFilesTableProps): JSX.Element => { {fileName} {meta.version} - {innerFileStats.map(fileStat => fileStat.collectionName).join(', ')} - {meta.exportedAt ? format(new Date(meta.exportedAt), 'yyyy/MM/dd HH:mm:ss') : ''} + + {innerFileStats + .map((fileStat) => fileStat.collectionName) + .join(', ')} + + + {meta.exportedAt + ? format(new Date(meta.exportedAt), 'yyyy/MM/dd HH:mm:ss') + : ''} + void, -} + fileName: string; + onZipFileStatRemove: (fileName: string) => void; +}; -const ArchiveFilesTableMenu = (props: ArchiveFilesTableMenuProps):JSX.Element => { +const ArchiveFilesTableMenu = ( + props: ArchiveFilesTableMenuProps, +): JSX.Element => { const { t } = useTranslation(); return (
-
    -
  • {t('admin:export_management.export_menu')}
  • - -
diff --git a/apps/app/src/client/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx b/apps/app/src/client/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx index a7340144c8d..4c6e21efa7c 100644 --- a/apps/app/src/client/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx +++ b/apps/app/src/client/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx @@ -1,47 +1,61 @@ -import React, { - useCallback, useState, useEffect, type JSX, -} from 'react'; - +import React, { type JSX, useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'next-i18next'; -import { - Modal, ModalHeader, ModalBody, ModalFooter, -} from 'reactstrap'; +import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { apiPost } from '~/client/util/apiv1-client'; import { toastError, toastSuccess } from '~/client/util/toastr'; - const GROUPS_PAGE = [ - 'pages', 'revisions', 'tags', 'pagetagrelations', 'pageredirects', 'comments', 'sharelinks', + 'pages', + 'revisions', + 'tags', + 'pagetagrelations', + 'pageredirects', + 'comments', + 'sharelinks', ]; const GROUPS_USER = [ - 'users', 'externalaccounts', 'usergroups', 'usergrouprelations', - 'externalusergroups', 'externalusergrouprelations', - 'useruisettings', 'editorsettings', 'bookmarks', 'bookmarkfolders', 'subscriptions', + 'users', + 'externalaccounts', + 'usergroups', + 'usergrouprelations', + 'externalusergroups', + 'externalusergrouprelations', + 'useruisettings', + 'editorsettings', + 'bookmarks', + 'bookmarkfolders', + 'subscriptions', 'inappnotificationsettings', ]; const GROUPS_CONFIG = [ - 'configs', 'migrations', 'updateposts', 'globalnotificationsettings', 'slackappintegrations', + 'configs', + 'migrations', + 'updateposts', + 'globalnotificationsettings', + 'slackappintegrations', 'growiplugins', ]; -const ALL_GROUPED_COLLECTIONS = GROUPS_PAGE.concat(GROUPS_USER).concat(GROUPS_CONFIG); +const ALL_GROUPED_COLLECTIONS = + GROUPS_PAGE.concat(GROUPS_USER).concat(GROUPS_CONFIG); type Props = { - isOpen: boolean, - onExportingRequested: () => void, - onClose: () => void, - collections: string[], - isAllChecked?: boolean, + isOpen: boolean; + onExportingRequested: () => void; + onClose: () => void; + collections: string[]; + isAllChecked?: boolean; }; const SelectCollectionsModal = (props: Props): JSX.Element => { const { t } = useTranslation(); - const { - isOpen, onExportingRequested, onClose, collections, isAllChecked, - } = props; + const { isOpen, onExportingRequested, onClose, collections, isAllChecked } = + props; - const [selectedCollections, setSelectedCollections] = useState>(new Set()); + const [selectedCollections, setSelectedCollections] = useState>( + new Set(), + ); const toggleCheckbox = useCallback((e) => { const { target } = e; @@ -51,8 +65,7 @@ const SelectCollectionsModal = (props: Props): JSX.Element => { const selectedCollections = new Set(prevState); if (checked) { selectedCollections.add(name); - } - else { + } else { selectedCollections.delete(name); } @@ -68,27 +81,31 @@ const SelectCollectionsModal = (props: Props): JSX.Element => { setSelectedCollections(new Set()); }, []); - const doExport = useCallback(async(e) => { - e.preventDefault(); + const doExport = useCallback( + async (e) => { + e.preventDefault(); - try { - // TODO: use apiv3Post - const result = await apiPost('/v3/export', { collections: Array.from(selectedCollections) }); + try { + // TODO: use apiv3Post + const result = await apiPost('/v3/export', { + collections: Array.from(selectedCollections), + }); - if (!result.ok) { - throw new Error('Error occured.'); - } + if (!result.ok) { + throw new Error('Error occured.'); + } - toastSuccess('Export process has requested.'); + toastSuccess('Export process has requested.'); - onExportingRequested(); - onClose(); - uncheckAll(); - } - catch (err) { - toastError(err); - } - }, [onClose, onExportingRequested, selectedCollections, uncheckAll]); + onExportingRequested(); + onClose(); + uncheckAll(); + } catch (err) { + toastError(err); + } + }, + [onClose, onExportingRequested, selectedCollections, uncheckAll], + ); const validateForm = useCallback(() => { return selectedCollections.size > 0; @@ -109,49 +126,58 @@ const SelectCollectionsModal = (props: Props): JSX.Element => { return (
- {/* eslint-disable-next-line react/no-danger */} + {/** biome-ignore lint/security/noDangerouslySetInnerHtml: ignore */}

); }, [selectedCollections, t]); - const renderCheckboxes = useCallback((collectionNames, color?) => { - const checkboxColor = color ? `form-check-${color}` : 'form-check-info'; + const renderCheckboxes = useCallback( + (collectionNames, color?) => { + const checkboxColor = color ? `form-check-${color}` : 'form-check-info'; - return ( -
-
- {collectionNames.map((collectionName) => { - return ( -
- - -
- ); - })} + return ( +
+
+ {collectionNames.map((collectionName) => { + return ( +
+ + +
+ ); + })} +
-
- ); - }, [selectedCollections, toggleCheckbox]); + ); + }, + [selectedCollections, toggleCheckbox], + ); - const renderGroups = useCallback((groupList, color?) => { - const collectionNames = groupList.filter((collectionName) => { - return collections.includes(collectionName); - }); + const renderGroups = useCallback( + (groupList, color?) => { + const collectionNames = groupList.filter((collectionName) => { + return collections.includes(collectionName); + }); - return renderCheckboxes(collectionNames, color); - }, [collections, renderCheckboxes]); + return renderCheckboxes(collectionNames, color); + }, + [collections, renderCheckboxes], + ); const renderOthers = useCallback(() => { const collectionNames = collections.filter((collectionName) => { @@ -175,11 +201,23 @@ const SelectCollectionsModal = (props: Props): JSX.Element => {
- -
@@ -198,21 +236,37 @@ const SelectCollectionsModal = (props: Props): JSX.Element => {
-

MongoDB Config Collections

+

+ MongoDB Config Collections +

{renderGroups(GROUPS_CONFIG)}
-

MongoDB Other Collections

+

+ MongoDB Other Collections +

{renderOthers()}
- - + + diff --git a/apps/app/src/client/components/Admin/ExportArchiveDataPage.tsx b/apps/app/src/client/components/Admin/ExportArchiveDataPage.tsx index 24968b6fff6..276e6e5a08e 100644 --- a/apps/app/src/client/components/Admin/ExportArchiveDataPage.tsx +++ b/apps/app/src/client/components/Admin/ExportArchiveDataPage.tsx @@ -1,10 +1,6 @@ -import React, { - useCallback, useEffect, useState, type JSX, -} from 'react'; - +import React, { type JSX, useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; - import { apiDelete } from '~/client/util/apiv1-client'; import { apiv3Get } from '~/client/util/apiv3-client'; import { toastError, toastSuccess } from '~/client/util/toastr'; @@ -14,9 +10,11 @@ import LabeledProgressBar from './Common/LabeledProgressBar'; import ArchiveFilesTable from './ExportArchiveData/ArchiveFilesTable'; import SelectCollectionsModal from './ExportArchiveData/SelectCollectionsModal'; - const IGNORED_COLLECTION_NAMES = [ - 'sessions', 'rlflx', 'yjs-writings', 'transferkeys', + 'sessions', + 'rlflx', + 'yjs-writings', + 'transferkeys', ]; const ExportArchiveDataPage = (): JSX.Element => { @@ -31,16 +29,26 @@ const ExportArchiveDataPage = (): JSX.Element => { const [isZipping, setZipping] = useState(false); const [isExported, setExported] = useState(false); - const fetchData = useCallback(async() => { - const [{ data: collectionsData }, { data: statusData }] = await Promise.all([ - apiv3Get<{collections: any[]}>('/mongo/collections', {}), - apiv3Get<{status: { zipFileStats: any[], isExporting: boolean, progressList: any[] }}>('/export/status', {}), - ]); + const fetchData = useCallback(async () => { + const [{ data: collectionsData }, { data: statusData }] = await Promise.all( + [ + apiv3Get<{ collections: any[] }>('/mongo/collections', {}), + apiv3Get<{ + status: { + zipFileStats: any[]; + isExporting: boolean; + progressList: any[]; + }; + }>('/export/status', {}), + ], + ); // filter only not ignored collection names - const filteredCollections = collectionsData.collections.filter((collectionName) => { - return !IGNORED_COLLECTION_NAMES.includes(collectionName); - }); + const filteredCollections = collectionsData.collections.filter( + (collectionName) => { + return !IGNORED_COLLECTION_NAMES.includes(collectionName); + }, + ); const { zipFileStats, isExporting, progressList } = statusData.status; setCollections(filteredCollections); @@ -67,7 +75,7 @@ const ExportArchiveDataPage = (): JSX.Element => { setExporting(false); setZipping(false); setExported(true); - setZipFileStats(prev => prev.concat([addedZipFileStat])); + setZipFileStats((prev) => prev.concat([addedZipFileStat])); toastSuccess(`New Archive Data '${addedZipFileStat.fileName}' is added`); }; @@ -83,18 +91,18 @@ const ExportArchiveDataPage = (): JSX.Element => { socket.off('admin:onStartZippingForExport', onStartZipping); socket.off('admin:onTerminateForExport', onTerminateForExport); }; - }, [socket]); - const onZipFileStatRemove = useCallback(async(fileName) => { + const onZipFileStatRemove = useCallback(async (fileName) => { try { await apiDelete(`/v3/export/${fileName}`, {}); - setZipFileStats(prev => prev.filter(stat => stat.fileName !== fileName)); + setZipFileStats((prev) => + prev.filter((stat) => stat.fileName !== fileName), + ); toastSuccess(`Deleted ${fileName}`); - } - catch (err) { + } catch (err) { toastError(err); } }, []); @@ -148,23 +156,28 @@ const ExportArchiveDataPage = (): JSX.Element => { }; }, [fetchData, setupWebsocketEventHandler]); - const showExportingData = (isExported || isExporting) && (progressList != null); + const showExportingData = (isExported || isExporting) && progressList != null; return (

{t('export_management.export_archive_data')}

- - { showExportingData && ( + {showExportingData && (

{t('export_management.exporting_collection_list')}

- { renderProgressBarsForCollections() } - { renderProgressBarForZipping() } + {renderProgressBarsForCollections()} + {renderProgressBarForZipping()}
- ) } + )}

{t('export_management.exported_data_list')}

diff --git a/apps/app/src/client/components/Admin/ForbiddenPage.tsx b/apps/app/src/client/components/Admin/ForbiddenPage.tsx index e9515c53bac..f4cba2583cd 100644 --- a/apps/app/src/client/components/Admin/ForbiddenPage.tsx +++ b/apps/app/src/client/components/Admin/ForbiddenPage.tsx @@ -1,9 +1,7 @@ import React, { type JSX } from 'react'; - import DefaultErrorPage from 'next/error'; import { useTranslation } from 'react-i18next'; - export const ForbiddenPage = (): JSX.Element => { const { t } = useTranslation('admin'); diff --git a/apps/app/src/client/components/Admin/FullTextSearchManagement.tsx b/apps/app/src/client/components/Admin/FullTextSearchManagement.tsx index fe8e376754f..903e79604dc 100644 --- a/apps/app/src/client/components/Admin/FullTextSearchManagement.tsx +++ b/apps/app/src/client/components/Admin/FullTextSearchManagement.tsx @@ -1,5 +1,4 @@ import React, { type JSX } from 'react'; - import { useTranslation } from 'next-i18next'; import ElasticsearchManagement from './ElasticsearchManagement/ElasticsearchManagement'; @@ -9,7 +8,10 @@ export const FullTextSearchManagement = (): JSX.Element => { return (
-

{ t('full_text_search_management.elasticsearch_management') }

+

+ {' '} + {t('full_text_search_management.elasticsearch_management')}{' '} +

); diff --git a/apps/app/src/client/components/Admin/G2GDataTransfer.tsx b/apps/app/src/client/components/Admin/G2GDataTransfer.tsx index c4444177155..1ba218d1f71 100644 --- a/apps/app/src/client/components/Admin/G2GDataTransfer.tsx +++ b/apps/app/src/client/components/Admin/G2GDataTransfer.tsx @@ -1,24 +1,27 @@ -import React, { - useCallback, useEffect, useState, type JSX, -} from 'react'; - +import React, { type JSX, useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'next-i18next'; import { useGenerateTransferKey } from '~/client/services/g2g-transfer'; import { apiv3Get, apiv3Post } from '~/client/util/apiv3-client'; import { toastError, toastSuccess } from '~/client/util/toastr'; import { useAdminSocket } from '~/features/admin/states/socket-io'; -import { G2G_PROGRESS_STATUS, type G2GProgress } from '~/interfaces/g2g-transfer'; +import { + G2G_PROGRESS_STATUS, + type G2GProgress, +} from '~/interfaces/g2g-transfer'; import { useGrowiDocumentationUrl } from '~/states/context'; import CustomCopyToClipBoard from '../Common/CustomCopyToClipBoard'; - // import { FileUploadSettingMolecule } from './App/FileUploadSetting'; import G2GDataTransferExportForm from './G2GDataTransferExportForm'; import G2GDataTransferStatusIcon from './G2GDataTransferStatusIcon'; const IGNORED_COLLECTION_NAMES = [ - 'sessions', 'rlflx', 'activities', 'attachmentFiles.files', 'attachmentFiles.chunks', + 'sessions', + 'rlflx', + 'activities', + 'attachmentFiles.files', + 'attachmentFiles.chunks', ]; const G2GDataTransfer = (): JSX.Element => { @@ -27,7 +30,9 @@ const G2GDataTransfer = (): JSX.Element => { const [startTransferKey, setStartTransferKey] = useState(''); const [collections, setCollections] = useState([]); - const [selectedCollections, setSelectedCollections] = useState>(new Set()); + const [selectedCollections, setSelectedCollections] = useState>( + new Set(), + ); const [optionsMap, setOptionsMap] = useState({}); const [isShowExportForm, setShowExportForm] = useState(false); const [isTransferring, setTransferring] = useState(false); @@ -61,13 +66,18 @@ const G2GDataTransfer = (): JSX.Element => { setStartTransferKey(e.target.value); }, []); - const setCollectionsAndSelectedCollections = useCallback(async() => { - const { data: collectionsData } = await apiv3Get<{collections: any[]}>('/mongo/collections', {}); + const setCollectionsAndSelectedCollections = useCallback(async () => { + const { data: collectionsData } = await apiv3Get<{ collections: any[] }>( + '/mongo/collections', + {}, + ); // filter only not ignored collection names - const filteredCollections = collectionsData.collections.filter((collectionName) => { - return !IGNORED_COLLECTION_NAMES.includes(collectionName); - }); + const filteredCollections = collectionsData.collections.filter( + (collectionName) => { + return !IGNORED_COLLECTION_NAMES.includes(collectionName); + }, + ); setCollections(filteredCollections); setSelectedCollections(new Set(filteredCollections)); @@ -78,7 +88,10 @@ const G2GDataTransfer = (): JSX.Element => { socket.on('admin:g2gProgress', (g2gProgress: G2GProgress) => { setG2GProgress(g2gProgress); - if (g2gProgress.mongo === G2G_PROGRESS_STATUS.COMPLETED && g2gProgress.attachments === G2G_PROGRESS_STATUS.COMPLETED) { + if ( + g2gProgress.mongo === G2G_PROGRESS_STATUS.COMPLETED && + g2gProgress.attachments === G2G_PROGRESS_STATUS.COMPLETED + ) { toastSuccess(t('admin:g2g:transfer_success')); } }); @@ -88,7 +101,7 @@ const G2GDataTransfer = (): JSX.Element => { toastError(t(key)); }); } - }, [socket, t, setTransferring, setG2GProgress]); + }, [socket, t]); const cleanUpWebsocketEventHandler = useCallback(() => { if (socket != null) { @@ -99,30 +112,31 @@ const G2GDataTransfer = (): JSX.Element => { const { transferKey, generateTransferKey } = useGenerateTransferKey(); - const onClickHandler = useCallback(async() => { + const onClickHandler = useCallback(async () => { try { await generateTransferKey(); - } - catch (errs) { + } catch (errs) { toastError(errs); } }, [generateTransferKey]); - const startTransfer = useCallback(async(e) => { - e.preventDefault(); - setTransferring(true); - - try { - await apiv3Post('/g2g-transfer/transfer', { - transferKey: startTransferKey, - collections: Array.from(selectedCollections), - optionsMap, - }); - } - catch (errs) { - toastError(errs); - } - }, [setTransferring, startTransferKey, selectedCollections, optionsMap]); + const startTransfer = useCallback( + async (e) => { + e.preventDefault(); + setTransferring(true); + + try { + await apiv3Post('/g2g-transfer/transfer', { + transferKey: startTransferKey, + collections: Array.from(selectedCollections), + optionsMap, + }); + } catch (errs) { + toastError(errs); + } + }, + [startTransferKey, selectedCollections, optionsMap], + ); const documentationUrl = useGrowiDocumentationUrl(); @@ -173,7 +187,6 @@ const G2GDataTransfer = (): JSX.Element => { // setGcsUploadNamespace(val); // }, []); - useEffect(() => { setCollectionsAndSelectedCollections(); setupWebsocketEventHandler(); @@ -181,13 +194,24 @@ const G2GDataTransfer = (): JSX.Element => { return () => { cleanUpWebsocketEventHandler(); }; - }, [setCollectionsAndSelectedCollections, setupWebsocketEventHandler, cleanUpWebsocketEventHandler]); + }, [ + setCollectionsAndSelectedCollections, + setupWebsocketEventHandler, + cleanUpWebsocketEventHandler, + ]); return (
-

{t('admin:g2g_data_transfer.transfer_data_to_another_growi')}

- - @@ -243,7 +267,9 @@ const G2GDataTransfer = (): JSX.Element => { />
- +
@@ -251,38 +277,66 @@ const G2GDataTransfer = (): JSX.Element => { {isTransferring && (
- MongoDB + {' '} + MongoDB
- Attachments + {' '} + Attachments
)} -

{t('commons:g2g_data_transfer.transfer_data_to_this_growi')}

+

+ {t('commons:g2g_data_transfer.transfer_data_to_this_growi')} +

-
- - + +
-

{t('commons:g2g_data_transfer.transfer_key_limit')}

-

{t('commons:g2g_data_transfer.once_transfer_key_used')}

+

+ {t('commons:g2g_data_transfer.transfer_key_limit')} +

+

+ {t('commons:g2g_data_transfer.once_transfer_key_used')} +

diff --git a/apps/app/src/client/components/Admin/G2GDataTransferExportForm.tsx b/apps/app/src/client/components/Admin/G2GDataTransferExportForm.tsx index d442ab81a64..1ff779e7705 100644 --- a/apps/app/src/client/components/Admin/G2GDataTransferExportForm.tsx +++ b/apps/app/src/client/components/Admin/G2GDataTransferExportForm.tsx @@ -1,7 +1,10 @@ import React, { - useState, useEffect, useCallback, useMemo, type JSX, + type JSX, + useCallback, + useEffect, + useMemo, + useState, } from 'react'; - import { useTranslation } from 'next-i18next'; import { GrowiArchiveImportOption } from '~/models/admin/growi-archive-import-option'; @@ -9,41 +12,210 @@ import { ImportOptionForPages } from '~/models/admin/import-option-for-pages'; import { ImportOptionForRevisions } from '~/models/admin/import-option-for-revisions'; import ImportCollectionConfigurationModal from './ImportData/GrowiArchive/ImportCollectionConfigurationModal'; -import ImportCollectionItem, { DEFAULT_MODE, MODE_RESTRICTED_COLLECTION } from './ImportData/GrowiArchive/ImportCollectionItem'; +import ImportCollectionItem, { + DEFAULT_MODE, + MODE_RESTRICTED_COLLECTION, +} from './ImportData/GrowiArchive/ImportCollectionItem'; -const GROUPS_PAGE = [ - 'pages', 'revisions', 'tags', 'pagetagrelations', -]; +const GROUPS_PAGE = ['pages', 'revisions', 'tags', 'pagetagrelations']; const GROUPS_USER = [ - 'users', 'externalaccounts', 'usergroups', 'usergrouprelations', -]; -const GROUPS_CONFIG = [ - 'configs', 'updateposts', 'globalnotificationsettings', + 'users', + 'externalaccounts', + 'usergroups', + 'usergrouprelations', ]; -const ALL_GROUPED_COLLECTIONS = GROUPS_PAGE.concat(GROUPS_USER).concat(GROUPS_CONFIG); - -const IMPORT_OPTION_CLASS_MAPPING: Record = { +const GROUPS_CONFIG = ['configs', 'updateposts', 'globalnotificationsettings']; +const ALL_GROUPED_COLLECTIONS = + GROUPS_PAGE.concat(GROUPS_USER).concat(GROUPS_CONFIG); + +const IMPORT_OPTION_CLASS_MAPPING: Record< + string, + typeof GrowiArchiveImportOption +> = { pages: ImportOptionForPages, revisions: ImportOptionForRevisions, }; type Props = { - allCollectionNames: string[], - selectedCollections: Set, - updateSelectedCollections: (newSelectedCollections: Set) => void, - optionsMap: any, - updateOptionsMap: (newOptionsMap: any) => void, + allCollectionNames: string[]; + selectedCollections: Set; + updateSelectedCollections: (newSelectedCollections: Set) => void; + optionsMap: any; + updateOptionsMap: (newOptionsMap: any) => void; +}; + +type ImportItemsProps = { + collectionNames: string[]; + selectedCollections: Set; + optionsMap: Record; + onToggleCollection: (collectionName: string, isChecked: boolean) => void; + onOptionChange: (collectionName: string, data: any) => void; + onConfigButtonClicked: (collectionName: string) => void; +}; + +const ImportItems = ({ + collectionNames, + selectedCollections, + optionsMap, + onToggleCollection, + onOptionChange, + onConfigButtonClicked, +}: ImportItemsProps): JSX.Element => { + return ( +
+ {collectionNames.map((collectionName) => { + const isConfigButtonAvailable = Object.keys( + IMPORT_OPTION_CLASS_MAPPING, + ).includes(collectionName); + + if (optionsMap[collectionName] == null) { + return null; + } + + return ( +
+ +
+ ); + })} +
+ ); +}; + +type WarnForGroupsProps = { + errors: Error[]; +}; + +const WarnForGroups = ({ errors }: WarnForGroupsProps): JSX.Element => { + if (errors.length === 0) { + return <>; + } + + return ( +
+
    + {errors.map((error, index) => { + return
  • {error.message}
  • ; + })} +
+
+ ); +}; + +type GroupImportItemsProps = { + groupList: string[]; + groupName: string; + errors: Error[]; + allCollectionNames: string[]; + selectedCollections: Set; + optionsMap: Record; + onToggleCollection: (collectionName: string, isChecked: boolean) => void; + onOptionChange: (collectionName: string, data: any) => void; + onConfigButtonClicked: (collectionName: string) => void; +}; + +const GroupImportItems = ({ + groupList, + groupName, + errors, + allCollectionNames, + selectedCollections, + optionsMap, + onToggleCollection, + onOptionChange, + onConfigButtonClicked, +}: GroupImportItemsProps): JSX.Element => { + const collectionNames = groupList.filter((groupCollectionName) => { + return allCollectionNames.includes(groupCollectionName); + }); + + if (collectionNames.length === 0) { + return <>; + } + + return ( +
+ {groupName} Collections + + +
+ ); +}; + +type OtherImportItemsProps = { + allCollectionNames: string[]; + selectedCollections: Set; + optionsMap: Record; + onToggleCollection: (collectionName: string, isChecked: boolean) => void; + onOptionChange: (collectionName: string, data: any) => void; + onConfigButtonClicked: (collectionName: string) => void; +}; + +const OtherImportItems = ({ + allCollectionNames, + selectedCollections, + optionsMap, + onToggleCollection, + onOptionChange, + onConfigButtonClicked, +}: OtherImportItemsProps): JSX.Element => { + const collectionNames = allCollectionNames.filter((collectionName) => { + return !ALL_GROUPED_COLLECTIONS.includes(collectionName); + }); + + // TODO: エラー対応 + return ( + + ); }; const G2GDataTransferExportForm = (props: Props): JSX.Element => { const { t } = useTranslation('admin'); const { - allCollectionNames, selectedCollections, updateSelectedCollections, optionsMap, updateOptionsMap, + allCollectionNames, + selectedCollections, + updateSelectedCollections, + optionsMap, + updateOptionsMap, } = props; const [isConfigurationModalOpen, setConfigurationModalOpen] = useState(false); - const [collectionNameForConfiguration, setCollectionNameForConfiguration] = useState(); + const [collectionNameForConfiguration, setCollectionNameForConfiguration] = + useState(); const checkAll = useCallback(() => { updateSelectedCollections(new Set(allCollectionNames)); @@ -53,26 +225,28 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => { updateSelectedCollections(new Set()); }, [updateSelectedCollections]); - const updateOption = useCallback((collectionName, data) => { - const options = optionsMap[collectionName]; + const updateOption = useCallback( + (collectionName, data) => { + const options = optionsMap[collectionName]; - // merge - Object.assign(options, data); + // merge + Object.assign(options, data); - const updatedOptionsMap = {}; - updatedOptionsMap[collectionName] = options; - updateOptionsMap((prev) => { - return { ...prev, updatedOptionsMap }; - }); - }, [optionsMap, updateOptionsMap]); + const updatedOptionsMap = {}; + updatedOptionsMap[collectionName] = options; + updateOptionsMap((prev) => { + return { ...prev, updatedOptionsMap }; + }); + }, + [optionsMap, updateOptionsMap], + ); - const ImportItems = ({ collectionNames }): JSX.Element => { - const toggleCheckbox = (collectionName, bool) => { + const toggleCheckbox = useCallback( + (collectionName, bool) => { const collections = new Set(selectedCollections); if (bool) { collections.add(collectionName); - } - else { + } else { collections.delete(collectionName); } @@ -80,90 +254,14 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => { // TODO: validation // this.validate(); - }; - - const openConfigurationModal = (collectionName) => { - setConfigurationModalOpen(true); - setCollectionNameForConfiguration(collectionName); - }; - - return ( -
- {collectionNames.map((collectionName) => { - const isConfigButtonAvailable = Object.keys(IMPORT_OPTION_CLASS_MAPPING).includes(collectionName); - - if (optionsMap[collectionName] == null) { - return null; - } - - return ( -
- -
- ); - })} -
- ); - }; - - const WarnForGroups = ({ errors }: { errors: Error[] }): JSX.Element => { - if (errors.length === 0) { - return <>; - } - - return ( -
-
    - {errors.map((error) => { - return
  • {error.message}
  • ; - })} -
-
- ); - }; - - const GroupImportItems = ({ groupList, groupName, errors }): JSX.Element => { - const collectionNames = groupList.filter((groupCollectionName) => { - return allCollectionNames.includes(groupCollectionName); - }); - - if (collectionNames.length === 0) { - return <>; - } - - return ( -
- {groupName} Collections - - -
- ); - }; - - const OtherImportItems = (): JSX.Element => { - const collectionNames = allCollectionNames.filter((collectionName) => { - return !ALL_GROUPED_COLLECTIONS.includes(collectionName); - }); + }, + [selectedCollections, updateSelectedCollections], + ); - // TODO: エラー対応 - return ; - }; + const openConfigurationModal = useCallback((collectionName) => { + setConfigurationModalOpen(true); + setCollectionNameForConfiguration(collectionName); + }, []); const configurationModal = useMemo(() => { if (collectionNameForConfiguration == null) { @@ -179,55 +277,130 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => { option={optionsMap[collectionNameForConfiguration]} /> ); - }, [collectionNameForConfiguration, isConfigurationModalOpen, optionsMap, updateOption]); + }, [ + collectionNameForConfiguration, + isConfigurationModalOpen, + optionsMap, + updateOption, + ]); const setInitialOptionsMap = useCallback(() => { const initialOptionsMap = {}; allCollectionNames.forEach((collectionName) => { - const initialMode = (MODE_RESTRICTED_COLLECTION[collectionName] != null) - ? MODE_RESTRICTED_COLLECTION[collectionName][0] - : DEFAULT_MODE; - const ImportOption = IMPORT_OPTION_CLASS_MAPPING[collectionName] || GrowiArchiveImportOption; - initialOptionsMap[collectionName] = new ImportOption(collectionName, initialMode); + const initialMode = + MODE_RESTRICTED_COLLECTION[collectionName] != null + ? MODE_RESTRICTED_COLLECTION[collectionName][0] + : DEFAULT_MODE; + const ImportOption = + IMPORT_OPTION_CLASS_MAPPING[collectionName] || GrowiArchiveImportOption; + initialOptionsMap[collectionName] = new ImportOption( + collectionName, + initialMode, + ); }); updateOptionsMap(initialOptionsMap); }, [allCollectionNames, updateOptionsMap]); useEffect(() => { setInitialOptionsMap(); - }, []); + }, [setInitialOptionsMap]); return ( <>
-
-
    -
  • {t('admin:importer_management.growi_settings.description_of_import_mode.about')}
  • +
  • + {t( + 'admin:importer_management.growi_settings.description_of_import_mode.about', + )} +
    • -
    • {t('admin:importer_management.growi_settings.description_of_import_mode.insert')}
    • -
    • {t('admin:importer_management.growi_settings.description_of_import_mode.upsert')}
    • -
    • {t('admin:importer_management.growi_settings.description_of_import_mode.flash_and_insert')}
    • +
    • + {t( + 'admin:importer_management.growi_settings.description_of_import_mode.insert', + )} +
    • +
    • + {t( + 'admin:importer_management.growi_settings.description_of_import_mode.upsert', + )} +
    • +
    • + {t( + 'admin:importer_management.growi_settings.description_of_import_mode.flash_and_insert', + )} +
{/* TODO: エラー追加 */} - - - - + + + + {configurationModal} diff --git a/apps/app/src/client/components/Admin/G2GDataTransferStatusIcon.tsx b/apps/app/src/client/components/Admin/G2GDataTransferStatusIcon.tsx index c4106dc6753..473312fede6 100644 --- a/apps/app/src/client/components/Admin/G2GDataTransferStatusIcon.tsx +++ b/apps/app/src/client/components/Admin/G2GDataTransferStatusIcon.tsx @@ -1,46 +1,85 @@ import React, { type ComponentPropsWithoutRef, type JSX } from 'react'; - import { LoadingSpinner } from '@growi/ui/dist/components'; -import { G2G_PROGRESS_STATUS, type G2GProgressStatus } from '~/interfaces/g2g-transfer'; - +import { + G2G_PROGRESS_STATUS, + type G2GProgressStatus, +} from '~/interfaces/g2g-transfer'; /** * Props for {@link G2GDataTransferStatusIcon} */ -interface Props extends ComponentPropsWithoutRef<'span'>{ +interface Props extends ComponentPropsWithoutRef<'span'> { status: G2GProgressStatus; } /** * Icon for G2G transfer status */ -const G2GDataTransferStatusIcon = ({ status, className, ...props }: Props): JSX.Element => { +const G2GDataTransferStatusIcon = ({ + status, + className, + ...props +}: Props): JSX.Element => { if (status === G2G_PROGRESS_STATUS.IN_PROGRESS) { return ( - + ); } if (status === G2G_PROGRESS_STATUS.COMPLETED) { return ( - check_circle + + check_circle + ); } if (status === G2G_PROGRESS_STATUS.ERROR) { return ( - error + + error + ); } if (status === G2G_PROGRESS_STATUS.SKIPPED) { return ( - block + + block + ); } - return circle; + return ( + + circle + + ); }; export default G2GDataTransferStatusIcon; diff --git a/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ErrorViewer.tsx b/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ErrorViewer.tsx index 705031b6418..94d7a0177f2 100644 --- a/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ErrorViewer.tsx +++ b/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ErrorViewer.tsx @@ -1,12 +1,11 @@ import React, { type JSX } from 'react'; - -import { Modal, ModalHeader, ModalBody } from 'reactstrap'; +import { Modal, ModalBody, ModalHeader } from 'reactstrap'; type ErrorViewerProps = { - isOpen: boolean, - errors: any[], - onClose: () => void, -} + isOpen: boolean; + errors: any[]; + onClose: () => void; +}; const ErrorViewer = (props: ErrorViewerProps): JSX.Element => { const { errors } = props; @@ -25,7 +24,13 @@ const ErrorViewer = (props: ErrorViewerProps): JSX.Element => { Errors - + ); diff --git a/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ImportCollectionConfigurationModal.jsx b/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ImportCollectionConfigurationModal.jsx index dc28c4c8d83..04ced1c80c8 100644 --- a/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ImportCollectionConfigurationModal.jsx +++ b/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ImportCollectionConfigurationModal.jsx @@ -1,23 +1,15 @@ /* eslint-disable react/no-danger */ import React from 'react'; - import { useTranslation } from 'next-i18next'; import PropTypes from 'prop-types'; -import { - Modal, - ModalHeader, - ModalBody, - ModalFooter, -} from 'reactstrap'; +import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'; import { GrowiArchiveImportOption } from '~/models/admin/growi-archive-import-option'; // import { toastSuccess, toastError } from '~/client/util/toastr'; - class ImportCollectionConfigurationModal extends React.Component { - constructor(props) { super(props); @@ -46,9 +38,7 @@ class ImportCollectionConfigurationModal extends React.Component { } updateOption() { - const { - collectionName, onOptionChange, onClose, - } = this.props; + const { collectionName, onOptionChange, onClose } = this.props; if (onOptionChange != null) { onOptionChange(collectionName, this.state.option); @@ -61,7 +51,8 @@ class ImportCollectionConfigurationModal extends React.Component { const { t } = this.props; const { option } = this.state; - const translationBase = 'admin:importer_management.growi_settings.configuration.pages'; + const translationBase = + 'admin:importer_management.growi_settings.configuration.pages'; /* eslint-disable react/no-unescaped-entities */ return ( @@ -72,11 +63,22 @@ class ImportCollectionConfigurationModal extends React.Component { type="checkbox" className="form-check-input" checked={option.isOverwriteAuthorWithCurrentUser || false} // add ' || false' to avoid uncontrolled input warning - onChange={() => this.changeHandler({ isOverwriteAuthorWithCurrentUser: !option.isOverwriteAuthorWithCurrentUser })} + onChange={() => + this.changeHandler({ + isOverwriteAuthorWithCurrentUser: + !option.isOverwriteAuthorWithCurrentUser, + }) + } />
@@ -85,13 +87,24 @@ class ImportCollectionConfigurationModal extends React.Component { type="checkbox" className="form-check-input" checked={option.makePublicForGrant2 || false} // add ' || false' to avoid uncontrolled input warning - onChange={() => this.changeHandler({ makePublicForGrant2: !option.makePublicForGrant2 })} + onChange={() => + this.changeHandler({ + makePublicForGrant2: !option.makePublicForGrant2, + }) + } />
@@ -101,13 +114,24 @@ class ImportCollectionConfigurationModal extends React.Component { type="checkbox" className="form-check-input" checked={option.makePublicForGrant4 || false} // add ' || false' to avoid uncontrolled input warning - onChange={() => this.changeHandler({ makePublicForGrant4: !option.makePublicForGrant4 })} + onChange={() => + this.changeHandler({ + makePublicForGrant4: !option.makePublicForGrant4, + }) + } />
@@ -117,13 +141,24 @@ class ImportCollectionConfigurationModal extends React.Component { type="checkbox" className="form-check-input" checked={option.makePublicForGrant5 || false} // add ' || false' to avoid uncontrolled input warning - onChange={() => this.changeHandler({ makePublicForGrant5: !option.makePublicForGrant5 })} + onChange={() => + this.changeHandler({ + makePublicForGrant5: !option.makePublicForGrant5, + }) + } />
@@ -133,11 +168,21 @@ class ImportCollectionConfigurationModal extends React.Component { type="checkbox" className="form-check-input" checked={option.initPageMetadatas || false} // add ' || false' to avoid uncontrolled input warning - onChange={() => this.changeHandler({ initPageMetadatas: !option.initPageMetadatas })} + onChange={() => + this.changeHandler({ + initPageMetadatas: !option.initPageMetadatas, + }) + } />
@@ -149,7 +194,8 @@ class ImportCollectionConfigurationModal extends React.Component { const { t } = this.props; const { option } = this.state; - const translationBase = 'admin:importer_management.growi_settings.configuration.revisions'; + const translationBase = + 'admin:importer_management.growi_settings.configuration.revisions'; /* eslint-disable react/no-unescaped-entities */ return ( @@ -160,11 +206,22 @@ class ImportCollectionConfigurationModal extends React.Component { type="checkbox" className="form-check-input" checked={option.isOverwriteAuthorWithCurrentUser || false} // add ' || false' to avoid uncontrolled input warning - onChange={() => this.changeHandler({ isOverwriteAuthorWithCurrentUser: !option.isOverwriteAuthorWithCurrentUser })} + onChange={() => + this.changeHandler({ + isOverwriteAuthorWithCurrentUser: + !option.isOverwriteAuthorWithCurrentUser, + }) + } />
@@ -189,23 +246,36 @@ class ImportCollectionConfigurationModal extends React.Component { } return ( - + {`'${collectionName}'`} Configuration - - {contents} - + {contents} - - + + ); } - } ImportCollectionConfigurationModal.propTypes = { diff --git a/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ImportCollectionItem.jsx b/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ImportCollectionItem.jsx index be5a48b3e36..27035f1a432 100644 --- a/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ImportCollectionItem.jsx +++ b/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ImportCollectionItem.jsx @@ -1,17 +1,23 @@ import React from 'react'; - import PropTypes from 'prop-types'; import { - Progress, UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem, + DropdownItem, + DropdownMenu, + DropdownToggle, + Progress, + UncontrolledDropdown, } from 'reactstrap'; import { GrowiArchiveImportOption } from '~/models/admin/growi-archive-import-option'; - const MODE_ATTR_MAP = { insert: { color: 'info', icon: 'add_circle', label: 'Insert' }, upsert: { color: 'success', icon: 'add_circle', label: 'Upsert' }, - flushAndInsert: { color: 'danger', icon: 'autorenew', label: 'Flush and Insert' }, + flushAndInsert: { + color: 'danger', + icon: 'autorenew', + label: 'Flush and Insert', + }, }; export const DEFAULT_MODE = 'insert'; @@ -23,13 +29,13 @@ export const MODE_RESTRICTED_COLLECTION = { }; export default class ImportCollectionItem extends React.Component { - constructor(props) { super(props); this.changeHandler = this.changeHandler.bind(this); this.modeSelectedHandler = this.modeSelectedHandler.bind(this); - this.configButtonClickedHandler = this.configButtonClickedHandler.bind(this); + this.configButtonClickedHandler = + this.configButtonClickedHandler.bind(this); this.errorLinkClickedHandler = this.errorLinkClickedHandler.bind(this); } @@ -76,13 +82,16 @@ export default class ImportCollectionItem extends React.Component { renderModeLabel(mode, isColorized = false) { const attrMap = MODE_ATTR_MAP[mode]; const className = isColorized ? `text-${attrMap.color}` : ''; - return {attrMap.icon} {attrMap.label}; + return ( + + {attrMap.icon}{' '} + {attrMap.label} + + ); } renderCheckbox() { - const { - collectionName, isSelected, isImporting, - } = this.props; + const { collectionName, isSelected, isImporting } = this.props; return (
@@ -96,7 +105,10 @@ export default class ImportCollectionItem extends React.Component { disabled={isImporting} onChange={this.changeHandler} /> -
@@ -104,22 +116,26 @@ export default class ImportCollectionItem extends React.Component { } renderModeSelector() { - const { - collectionName, option, isImporting, - } = this.props; + const { collectionName, option, isImporting } = this.props; const currentMode = option?.mode || 'insert'; const attrMap = MODE_ATTR_MAP[currentMode]; - const modes = MODE_RESTRICTED_COLLECTION[collectionName] || Object.keys(MODE_ATTR_MAP); + const modes = + MODE_RESTRICTED_COLLECTION[collectionName] || Object.keys(MODE_ATTR_MAP); return ( Mode:  - + {this.renderModeLabel(currentMode)} - {modes.map(mode => ( + {modes.map((mode) => ( this.modeSelectedHandler(mode)} @@ -141,7 +157,9 @@ export default class ImportCollectionItem extends React.Component { type="button" className="btn btn-outline-secondary btn-sm p-1 ms-2" disabled={isImporting || !isConfigButtonAvailable} - onClick={isConfigButtonAvailable ? this.configButtonClickedHandler : null} + onClick={ + isConfigButtonAvailable ? this.configButtonClickedHandler : null + } > settings @@ -149,17 +167,37 @@ export default class ImportCollectionItem extends React.Component { } renderProgressBar() { - const { - isImporting, insertedCount, modifiedCount, errorsCount, - } = this.props; + const { isImporting, insertedCount, modifiedCount, errorsCount } = + this.props; const total = insertedCount + modifiedCount + errorsCount; return ( - - - + + + ); } @@ -174,20 +212,35 @@ export default class ImportCollectionItem extends React.Component { const { insertedCount, modifiedCount, errorsCount } = this.props; return (
- {insertedCount} Inserted,  - {modifiedCount} Modified,  - { errorsCount > 0 - ? {errorsCount} Failed - : 0 Failed - } + + {insertedCount} Inserted + + ,  + + {modifiedCount} Modified + + ,  + {errorsCount > 0 ? ( + + ) : ( + + 0 Failed + + )}
); } render() { - const { - isSelected, isHideProgress, - } = this.props; + const { isSelected, isHideProgress } = this.props; return (
@@ -211,7 +264,6 @@ export default class ImportCollectionItem extends React.Component {
); } - } ImportCollectionItem.propTypes = { diff --git a/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ImportForm.jsx b/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ImportForm.jsx index db074fd678c..f88879e762b 100644 --- a/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ImportForm.jsx +++ b/apps/app/src/client/components/Admin/ImportData/GrowiArchive/ImportForm.jsx @@ -1,31 +1,31 @@ import React from 'react'; - import { useTranslation } from 'next-i18next'; import PropTypes from 'prop-types'; import { apiv3Post } from '~/client/util/apiv3-client'; -import { toastSuccess, toastError } from '~/client/util/toastr'; +import { toastError, toastSuccess } from '~/client/util/toastr'; import { useAdminSocket } from '~/features/admin/states/socket-io'; import { GrowiArchiveImportOption } from '~/models/admin/growi-archive-import-option'; import { ImportOptionForPages } from '~/models/admin/import-option-for-pages'; import { ImportOptionForRevisions } from '~/models/admin/import-option-for-revisions'; - import ErrorViewer from './ErrorViewer'; import ImportCollectionConfigurationModal from './ImportCollectionConfigurationModal'; -import ImportCollectionItem, { DEFAULT_MODE, MODE_RESTRICTED_COLLECTION } from './ImportCollectionItem'; +import ImportCollectionItem, { + DEFAULT_MODE, + MODE_RESTRICTED_COLLECTION, +} from './ImportCollectionItem'; - -const GROUPS_PAGE = [ - 'pages', 'revisions', 'tags', 'pagetagrelations', -]; +const GROUPS_PAGE = ['pages', 'revisions', 'tags', 'pagetagrelations']; const GROUPS_USER = [ - 'users', 'externalaccounts', 'usergroups', 'usergrouprelations', + 'users', + 'externalaccounts', + 'usergroups', + 'usergrouprelations', ]; -const GROUPS_CONFIG = [ - 'configs', 'updateposts', 'globalnotificationsettings', -]; -const ALL_GROUPED_COLLECTIONS = GROUPS_PAGE.concat(GROUPS_USER).concat(GROUPS_CONFIG); +const GROUPS_CONFIG = ['configs', 'updateposts', 'globalnotificationsettings']; +const ALL_GROUPED_COLLECTIONS = + GROUPS_PAGE.concat(GROUPS_USER).concat(GROUPS_CONFIG); /** @type Record */ const IMPORT_OPTION_CLASS_MAPPING = { @@ -34,7 +34,6 @@ const IMPORT_OPTION_CLASS_MAPPING = { }; class ImportForm extends React.Component { - constructor(props) { super(props); @@ -69,12 +68,17 @@ class ImportForm extends React.Component { this.initialState.collectionNameToFileNameMap[collectionName] = fileName; // determine initial mode - const initialMode = (MODE_RESTRICTED_COLLECTION[collectionName] != null) - ? MODE_RESTRICTED_COLLECTION[collectionName][0] - : DEFAULT_MODE; + const initialMode = + MODE_RESTRICTED_COLLECTION[collectionName] != null + ? MODE_RESTRICTED_COLLECTION[collectionName][0] + : DEFAULT_MODE; // create GrowiArchiveImportOption instance - const ImportOption = IMPORT_OPTION_CLASS_MAPPING[collectionName] || GrowiArchiveImportOption; - this.initialState.optionsMap[collectionName] = new ImportOption(collectionName, initialMode); + const ImportOption = + IMPORT_OPTION_CLASS_MAPPING[collectionName] || GrowiArchiveImportOption; + this.initialState.optionsMap[collectionName] = new ImportOption( + collectionName, + initialMode, + ); }); this.state = this.initialState; @@ -93,6 +97,7 @@ class ImportForm extends React.Component { return Object.keys(this.state.collectionNameToFileNameMap); } + // biome-ignore lint/correctness/noNestedComponentDefinitions: lifecycle method on a class component UNSAFE_componentWillMount() { this.setupWebsocketEventHandler(); } @@ -106,21 +111,24 @@ class ImportForm extends React.Component { // websocket event // eslint-disable-next-line object-curly-newline - socket.on('admin:onProgressForImport', ({ collectionName, collectionProgress, appendedErrors }) => { - const { progressMap, errorsMap } = this.state; - progressMap[collectionName] = collectionProgress; - - if (appendedErrors != null) { - const errors = errorsMap[collectionName] || []; - errorsMap[collectionName] = errors.concat(appendedErrors); - } - - this.setState({ - isImporting: true, - progressMap, - errorsMap, - }); - }); + socket.on( + 'admin:onProgressForImport', + ({ collectionName, collectionProgress, appendedErrors }) => { + const { progressMap, errorsMap } = this.state; + progressMap[collectionName] = collectionProgress; + + if (appendedErrors != null) { + const errors = errorsMap[collectionName] || []; + errorsMap[collectionName] = errors.concat(appendedErrors); + } + + this.setState({ + isImporting: true, + progressMap, + errorsMap, + }); + }, + ); // websocket event socket.on('admin:onTerminateForImport', () => { @@ -154,8 +162,7 @@ class ImportForm extends React.Component { const selectedCollections = new Set(this.state.selectedCollections); if (bool) { selectedCollections.add(collectionName); - } - else { + } else { selectedCollections.delete(collectionName); } @@ -165,7 +172,9 @@ class ImportForm extends React.Component { } async checkAll() { - await this.setState({ selectedCollections: new Set(this.allCollectionNames) }); + await this.setState({ + selectedCollections: new Set(this.allCollectionNames), + }); this.validate(); } @@ -186,11 +195,17 @@ class ImportForm extends React.Component { } openConfigurationModal(collectionName) { - this.setState({ isConfigurationModalOpen: true, collectionNameForConfiguration: collectionName }); + this.setState({ + isConfigurationModalOpen: true, + collectionNameForConfiguration: collectionName, + }); } showErrorsViewer(collectionName) { - this.setState({ isErrorsViewerOpen: true, collectionNameForErrorsViewer: collectionName }); + this.setState({ + isErrorsViewerOpen: true, + collectionNameForErrorsViewer: collectionName, + }); } async validate() { @@ -224,7 +239,9 @@ class ImportForm extends React.Component { const { warnForOtherGroups, selectedCollections } = this.state; if (selectedCollections.size === 0) { - warnForOtherGroups.push(t('admin:importer_management.growi_settings.errors.at_least_one')); + warnForOtherGroups.push( + t('admin:importer_management.growi_settings.errors.at_least_one'), + ); } this.setState({ warnForOtherGroups }); @@ -234,13 +251,20 @@ class ImportForm extends React.Component { const { t } = this.props; const { warnForPageGroups, selectedCollections } = this.state; - const pageRelatedCollectionsLength = ['pages', 'revisions'].filter((collectionName) => { - return selectedCollections.has(collectionName); - }).length; + const pageRelatedCollectionsLength = ['pages', 'revisions'].filter( + (collectionName) => { + return selectedCollections.has(collectionName); + }, + ).length; // MUST be included both or neither when importing - if (pageRelatedCollectionsLength !== 0 && pageRelatedCollectionsLength !== 2) { - warnForPageGroups.push(t('admin:importer_management.growi_settings.errors.page_and_revision')); + if ( + pageRelatedCollectionsLength !== 0 && + pageRelatedCollectionsLength !== 2 + ) { + warnForPageGroups.push( + t('admin:importer_management.growi_settings.errors.page_and_revision'), + ); } this.setState({ warnForPageGroups }); @@ -253,7 +277,12 @@ class ImportForm extends React.Component { // MUST include also 'users' if 'externalaccounts' is selected if (selectedCollections.has('externalaccounts')) { if (!selectedCollections.has('users')) { - warnForUserGroups.push(t('admin:importer_management.growi_settings.errors.depends', { target: 'Users', condition: 'Externalaccounts' })); + warnForUserGroups.push( + t('admin:importer_management.growi_settings.errors.depends', { + target: 'Users', + condition: 'Externalaccounts', + }), + ); } } @@ -267,7 +296,12 @@ class ImportForm extends React.Component { // MUST include also 'users' if 'usergroups' is selected if (selectedCollections.has('usergroups')) { if (!selectedCollections.has('users')) { - warnForUserGroups.push(t('admin:importer_management.growi_settings.errors.depends', { target: 'Users', condition: 'Usergroups' })); + warnForUserGroups.push( + t('admin:importer_management.growi_settings.errors.depends', { + target: 'Users', + condition: 'Usergroups', + }), + ); } } @@ -281,7 +315,12 @@ class ImportForm extends React.Component { // MUST include also 'usergroups' if 'usergrouprelations' is selected if (selectedCollections.has('usergrouprelations')) { if (!selectedCollections.has('usergroups')) { - warnForUserGroups.push(t('admin:importer_management.growi_settings.errors.depends', { target: 'Usergroups', condition: 'Usergrouprelations' })); + warnForUserGroups.push( + t('admin:importer_management.growi_settings.errors.depends', { + target: 'Usergroups', + condition: 'Usergrouprelations', + }), + ); } } @@ -289,9 +328,7 @@ class ImportForm extends React.Component { } async import() { - const { - fileName, onPostImport, t, - } = this.props; + const { fileName, onPostImport, t } = this.props; const { selectedCollections, optionsMap } = this.state; // init progress data @@ -314,8 +351,7 @@ class ImportForm extends React.Component { } toastSuccess(undefined, 'Import process has requested.'); - } - catch (err) { + } catch (err) { if (err.code === 'only_upsert_available') { toastError(t('admin:importer_management.error.only_upsert_available')); } @@ -331,9 +367,8 @@ class ImportForm extends React.Component { return (
    - {errors.map((error, index) => { - // eslint-disable-next-line react/no-array-index-key - return
  • {error}
  • ; + {errors.map((error) => { + return
  • {error}
  • ; })}
@@ -363,7 +398,11 @@ class ImportForm extends React.Component { return !ALL_GROUPED_COLLECTIONS.includes(collectionName); }); - return this.renderGroups(collectionNames, 'Other', this.state.warnForOtherGroups); + return this.renderGroups( + collectionNames, + 'Other', + this.state.warnForOtherGroups, + ); } renderImportItems(collectionNames) { @@ -382,15 +421,21 @@ class ImportForm extends React.Component { {collectionNames.map((collectionName) => { const collectionProgress = progressMap[collectionName]; const errorsCount = errorsMap[collectionName]?.length ?? 0; - const isConfigButtonAvailable = Object.keys(IMPORT_OPTION_CLASS_MAPPING).includes(collectionName); + const isConfigButtonAvailable = Object.keys( + IMPORT_OPTION_CLASS_MAPPING, + ).includes(collectionName); return (
-
-
    -
  • {t('admin:importer_management.growi_settings.description_of_import_mode.about')}
  • +
  • + {t( + 'admin:importer_management.growi_settings.description_of_import_mode.about', + )} +
    • -
    • {t('admin:importer_management.growi_settings.description_of_import_mode.insert')}
    • -
    • {t('admin:importer_management.growi_settings.description_of_import_mode.upsert')}
    • -
    • {t('admin:importer_management.growi_settings.description_of_import_mode.flash_and_insert')}
    • +
    • + {t( + 'admin:importer_management.growi_settings.description_of_import_mode.insert', + )} +
    • +
    • + {t( + 'admin:importer_management.growi_settings.description_of_import_mode.upsert', + )} +
    • +
    • + {t( + 'admin:importer_management.growi_settings.description_of_import_mode.flash_and_insert', + )} +
@@ -479,10 +560,19 @@ class ImportForm extends React.Component { {this.renderOthers()}
- -
@@ -492,7 +582,6 @@ class ImportForm extends React.Component { ); } - } ImportForm.propTypes = { @@ -516,5 +605,4 @@ const ImportFormWrapperFc = (props) => { return ; }; - export default ImportFormWrapperFc; diff --git a/apps/app/src/client/components/Admin/ImportData/GrowiArchive/UploadForm.jsx b/apps/app/src/client/components/Admin/ImportData/GrowiArchive/UploadForm.jsx index f511d931f5f..aa47f3a11ed 100644 --- a/apps/app/src/client/components/Admin/ImportData/GrowiArchive/UploadForm.jsx +++ b/apps/app/src/client/components/Admin/ImportData/GrowiArchive/UploadForm.jsx @@ -1,5 +1,4 @@ import React from 'react'; - import { useTranslation } from 'next-i18next'; import PropTypes from 'prop-types'; @@ -7,7 +6,6 @@ import { apiv3PostForm } from '~/client/util/apiv3-client'; import { toastError } from '~/client/util/toastr'; class UploadForm extends React.Component { - constructor(props) { super(props); @@ -33,14 +31,12 @@ class UploadForm extends React.Component { try { const { data } = await apiv3PostForm('/import/upload', formData); this.props.onUpload(data); - } - catch (err) { + } catch (err) { if (err[0].code === 'versions-are-not-met') { if (this.props.onVersionMismatch !== null) { this.props.onVersionMismatch(err[0].code); } - } - else { + } else { toastError(err); } } @@ -48,9 +44,9 @@ class UploadForm extends React.Component { validateForm() { return ( - this.inputRef.current // null check - && this.inputRef.current.files[0] // null check - && /\.zip$/.test(this.inputRef.current.files[0].name) // validate extension + this.inputRef.current && // null check + this.inputRef.current.files[0] && // null check + /\.zip$/.test(this.inputRef.current.files[0].name) // validate extension ); } @@ -61,7 +57,10 @@ class UploadForm extends React.Component {
-
- {adminSlackIntegrationLegacyContainer.state.selectSlackOption === 'Incoming Webhooks' ? ( + {adminSlackIntegrationLegacyContainer.state.selectSlackOption === + 'Incoming Webhooks' ? ( -

{t('notification_settings.slack_incoming_configuration')}

+

+ {t('notification_settings.slack_incoming_configuration')} +

- +
{ type="checkbox" className="form-check-input" id="cbPrioritizeIWH" - checked={adminSlackIntegrationLegacyContainer.state.isIncomingWebhookPrioritized || false} - onChange={() => { adminSlackIntegrationLegacyContainer.switchIsIncomingWebhookPrioritized() }} + checked={ + adminSlackIntegrationLegacyContainer.state + .isIncomingWebhookPrioritized || false + } + onChange={() => { + adminSlackIntegrationLegacyContainer.switchIsIncomingWebhookPrioritized(); + }} /> -
@@ -100,40 +145,61 @@ const SlackConfiguration = (props) => {
- ) - : ( - -

{t('notification_settings.slack_app_configuration')}

- - + ) : ( + +

+ {t('notification_settings.slack_app_configuration')} +

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

- {' '} - {t('notification_settings.how_to.header')} + {' '} + + {t('notification_settings.how_to.header')} +

-
    +
    1. {t('notification_settings.how_to.workspace')}
        {/* eslint-disable-next-line react/no-danger */} -
      1. +
      2. {t('notification_settings.how_to.workspace_desc2')}
      3. {t('notification_settings.how_to.workspace_desc3')}
      @@ -161,11 +239,15 @@ const SlackConfiguration = (props) => { {t('notification_settings.how_to.at_growi')}
        {/* eslint-disable-next-line react/no-danger */} -
      1. +
    - ); @@ -173,7 +255,9 @@ const SlackConfiguration = (props) => { SlackConfiguration.propTypes = { t: PropTypes.func.isRequired, // i18next - adminSlackIntegrationLegacyContainer: PropTypes.instanceOf(AdminSlackIntegrationLegacyContainer).isRequired, + adminSlackIntegrationLegacyContainer: PropTypes.instanceOf( + AdminSlackIntegrationLegacyContainer, + ).isRequired, }; const SlackConfigurationWrapperFc = (props) => { @@ -182,6 +266,9 @@ const SlackConfigurationWrapperFc = (props) => { return ; }; -const SlackConfigurationWrapper = withUnstatedContainers(SlackConfigurationWrapperFc, [AdminSlackIntegrationLegacyContainer]); +const SlackConfigurationWrapper = withUnstatedContainers( + SlackConfigurationWrapperFc, + [AdminSlackIntegrationLegacyContainer], +); export default SlackConfigurationWrapper; diff --git a/apps/app/src/client/components/Admin/ManageExternalAccount.tsx b/apps/app/src/client/components/Admin/ManageExternalAccount.tsx index 6cc0fa70e99..2ba15176c5f 100644 --- a/apps/app/src/client/components/Admin/ManageExternalAccount.tsx +++ b/apps/app/src/client/components/Admin/ManageExternalAccount.tsx @@ -1,34 +1,38 @@ -import React, { useCallback, useEffect, type JSX } from 'react'; - -import { useTranslation } from 'next-i18next'; +import React, { type JSX, useCallback, useEffect } from 'react'; import Link from 'next/link'; +import { useTranslation } from 'next-i18next'; import AdminExternalAccountsContainer from '~/client/services/AdminExternalAccountsContainer'; import { toastError } from '~/client/util/toastr'; import PaginationWrapper from '../PaginationWrapper'; import { withUnstatedContainers } from '../UnstatedUtils'; - import ExternalAccountTable from './Users/ExternalAccountTable'; type ManageExternalAccountProps = { - adminExternalAccountsContainer: AdminExternalAccountsContainer, -} - -const ManageExternalAccount = (props: ManageExternalAccountProps): JSX.Element => { + adminExternalAccountsContainer: AdminExternalAccountsContainer; +}; +const ManageExternalAccount = ( + props: ManageExternalAccountProps, +): JSX.Element => { const { t } = useTranslation(); const { adminExternalAccountsContainer } = props; - const { activePage, totalAccounts, pagingLimit } = adminExternalAccountsContainer.state; + const { activePage, totalAccounts, pagingLimit } = + adminExternalAccountsContainer.state; - const externalAccountPageHandler = useCallback(async(selectedPage) => { - try { - await adminExternalAccountsContainer.retrieveExternalAccountsByPagingNum(selectedPage); - } - catch (err) { - toastError(err); - } - }, [adminExternalAccountsContainer]); + const externalAccountPageHandler = useCallback( + async (selectedPage) => { + try { + await adminExternalAccountsContainer.retrieveExternalAccountsByPagingNum( + selectedPage, + ); + } catch (err) { + toastError(err); + } + }, + [adminExternalAccountsContainer], + ); // for Next routing useEffect(() => { @@ -54,28 +58,29 @@ const ManageExternalAccount = (props: ManageExternalAccountProps): JSX.Element = prefetch={false} className="btn btn-outline-secondary" > - + {t('admin:user_management.back_to_user_management')}

    {t('admin:user_management.external_account_list')}

    - {(totalAccounts !== 0) ? ( + {totalAccounts !== 0 ? ( <> {pager} {pager} - ) - : ( - <> - { t('admin:user_management.external_account_none') } - - ) - } + ) : ( + <>{t('admin:user_management.external_account_none')} + )} ); }; -const ManageExternalAccountWrapper = withUnstatedContainers(ManageExternalAccount, [AdminExternalAccountsContainer]); +const ManageExternalAccountWrapper = withUnstatedContainers( + ManageExternalAccount, + [AdminExternalAccountsContainer], +); export default ManageExternalAccountWrapper; diff --git a/apps/app/src/client/components/Admin/MarkdownSetting/IndentForm.tsx b/apps/app/src/client/components/Admin/MarkdownSetting/IndentForm.tsx index 9548f6d987a..ddb9c62640e 100644 --- a/apps/app/src/client/components/Admin/MarkdownSetting/IndentForm.tsx +++ b/apps/app/src/client/components/Admin/MarkdownSetting/IndentForm.tsx @@ -1,13 +1,15 @@ /* eslint-disable react/no-danger */ import React, { useCallback } from 'react'; - import { useTranslation } from 'next-i18next'; import { - UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem, + DropdownItem, + DropdownMenu, + DropdownToggle, + UncontrolledDropdown, } from 'reactstrap'; import AdminMarkDownContainer from '~/client/services/AdminMarkDownContainer'; -import { toastSuccess, toastError } from '~/client/util/toastr'; +import { toastError, toastSuccess } from '~/client/util/toastr'; import loggerFactory from '~/utils/logger'; import { withUnstatedContainers } from '../../UnstatedUtils'; @@ -15,24 +17,30 @@ import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow'; const logger = loggerFactory('growi:importer'); - type Props = { adminMarkDownContainer: AdminMarkDownContainer; -} +}; const IndentForm = (props: Props) => { const { t } = useTranslation('admin'); - const onClickSubmit = useCallback(async(props) => { - try { - await props.adminMarkDownContainer.updateIndentSetting(); - toastSuccess(t('toaster.update_successed', { target: t('markdown_settings.indent_header'), ns: 'commons' })); - } - catch (err) { - toastError(err); - logger.error(err); - } - }, [t]); + const onClickSubmit = useCallback( + async (props) => { + try { + await props.adminMarkDownContainer.updateIndentSetting(); + toastSuccess( + t('toaster.update_successed', { + target: t('markdown_settings.indent_header'), + ns: 'commons', + }), + ); + } catch (err) { + toastError(err); + logger.error(err); + } + }, + [t], + ); const renderIndentSizeOption = (props) => { const { adminMarkDownContainer } = props; @@ -41,9 +49,14 @@ const IndentForm = (props: Props) => { return (
    - + - + {adminPreferredIndentSize || 4} @@ -51,8 +64,14 @@ const IndentForm = (props: Props) => { {[2, 4].map((num) => { return ( - adminMarkDownContainer.setAdminPreferredIndentSize(num)}> - {num} + + adminMarkDownContainer.setAdminPreferredIndentSize(num) + } + > + {num} ); })} @@ -70,7 +89,9 @@ const IndentForm = (props: Props) => { const { adminMarkDownContainer } = props; const { isIndentSizeForced } = adminMarkDownContainer.state; - const helpIndentInComment = { __html: t('markdown_settings.indent_options.disallow_indent_change_desc') }; + const helpIndentInComment = { + __html: t('markdown_settings.indent_options.disallow_indent_change_desc'), + }; return (
    @@ -81,14 +102,23 @@ const IndentForm = (props: Props) => { id="isIndentSizeForced" checked={isIndentSizeForced || false} onChange={() => { - adminMarkDownContainer.setState({ isIndentSizeForced: !isIndentSizeForced }); + adminMarkDownContainer.setState({ + isIndentSizeForced: !isIndentSizeForced, + }); }} /> -
    -

    +

    ); }; @@ -101,7 +131,10 @@ const IndentForm = (props: Props) => { {renderIndentSizeOption(props)} {renderIndentForceOption(props)} - onClickSubmit(props)} disabled={adminMarkDownContainer.state.retrieveError != null} /> + onClickSubmit(props)} + disabled={adminMarkDownContainer.state.retrieveError != null} + /> ); }; @@ -109,6 +142,8 @@ const IndentForm = (props: Props) => { /** * Wrapper component for using unstated */ -const IndentFormWrapper = withUnstatedContainers(IndentForm, [AdminMarkDownContainer]); +const IndentFormWrapper = withUnstatedContainers(IndentForm, [ + AdminMarkDownContainer, +]); export default IndentFormWrapper; diff --git a/apps/app/src/client/components/Admin/MarkdownSetting/LineBreakForm.jsx b/apps/app/src/client/components/Admin/MarkdownSetting/LineBreakForm.jsx index 1d8ed94e0e3..b88efb56543 100644 --- a/apps/app/src/client/components/Admin/MarkdownSetting/LineBreakForm.jsx +++ b/apps/app/src/client/components/Admin/MarkdownSetting/LineBreakForm.jsx @@ -1,11 +1,10 @@ /* eslint-disable react/no-danger */ import React from 'react'; - import { useTranslation } from 'next-i18next'; import PropTypes from 'prop-types'; import AdminMarkDownContainer from '~/client/services/AdminMarkDownContainer'; -import { toastSuccess, toastError } from '~/client/util/toastr'; +import { toastError, toastSuccess } from '~/client/util/toastr'; import loggerFactory from '~/utils/logger'; import { withUnstatedContainers } from '../../UnstatedUtils'; @@ -14,22 +13,24 @@ import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow'; const logger = loggerFactory('growi:importer'); class LineBreakForm extends React.Component { - constructor(props) { super(props); this.onClickSubmit = this.onClickSubmit.bind(this); } - async onClickSubmit() { const { t } = this.props; try { await this.props.adminMarkDownContainer.updateLineBreakSetting(); - toastSuccess(t('toaster.update_successed', { target: t('markdown_settings.lineBreak_header'), ns: 'commons' })); - } - catch (err) { + toastSuccess( + t('toaster.update_successed', { + target: t('markdown_settings.lineBreak_header'), + ns: 'commons', + }), + ); + } catch (err) { toastError(err); logger.error(err); } @@ -39,7 +40,9 @@ class LineBreakForm extends React.Component { const { t, adminMarkDownContainer } = this.props; const { isEnabledLinebreaks } = adminMarkDownContainer.state; - const helpLineBreak = { __html: t('markdown_settings.lineBreak_options.enable_lineBreak_desc') }; + const helpLineBreak = { + __html: t('markdown_settings.lineBreak_options.enable_lineBreak_desc'), + }; return (
    @@ -49,13 +52,24 @@ class LineBreakForm extends React.Component { className="form-check-input" id="isEnabledLinebreaks" checked={isEnabledLinebreaks} - onChange={() => { adminMarkDownContainer.setState({ isEnabledLinebreaks: !isEnabledLinebreaks }) }} + onChange={() => { + adminMarkDownContainer.setState({ + isEnabledLinebreaks: !isEnabledLinebreaks, + }); + }} /> -
    -

    +

    ); } @@ -64,7 +78,11 @@ class LineBreakForm extends React.Component { const { t, adminMarkDownContainer } = this.props; const { isEnabledLinebreaksInComments } = adminMarkDownContainer.state; - const helpLineBreakInComment = { __html: t('markdown_settings.lineBreak_options.enable_lineBreak_for_comment_desc') }; + const helpLineBreakInComment = { + __html: t( + 'markdown_settings.lineBreak_options.enable_lineBreak_for_comment_desc', + ), + }; return (
    @@ -74,13 +92,26 @@ class LineBreakForm extends React.Component { className="form-check-input" id="isEnabledLinebreaksInComments" checked={isEnabledLinebreaksInComments} - onChange={() => { adminMarkDownContainer.setState({ isEnabledLinebreaksInComments: !isEnabledLinebreaksInComments }) }} + onChange={() => { + adminMarkDownContainer.setState({ + isEnabledLinebreaksInComments: !isEnabledLinebreaksInComments, + }); + }} /> -
    -

    +

    ); } @@ -94,11 +125,13 @@ class LineBreakForm extends React.Component { {this.renderLineBreakOption()} {this.renderLineBreakInCommentOption()} - + ); } - } const LineBreakFormFC = (props) => { @@ -109,11 +142,14 @@ const LineBreakFormFC = (props) => { /** * Wrapper component for using unstated */ -const LineBreakFormWrapper = withUnstatedContainers(LineBreakFormFC, [AdminMarkDownContainer]); +const LineBreakFormWrapper = withUnstatedContainers(LineBreakFormFC, [ + AdminMarkDownContainer, +]); LineBreakForm.propTypes = { t: PropTypes.func.isRequired, - adminMarkDownContainer: PropTypes.instanceOf(AdminMarkDownContainer).isRequired, + adminMarkDownContainer: PropTypes.instanceOf(AdminMarkDownContainer) + .isRequired, }; export default LineBreakFormWrapper; diff --git a/apps/app/src/client/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx b/apps/app/src/client/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx index 9a65dfca656..21757bf7f92 100644 --- a/apps/app/src/client/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx +++ b/apps/app/src/client/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx @@ -1,5 +1,4 @@ -import React, { useEffect, type JSX } from 'react'; - +import React, { type JSX, useEffect } from 'react'; import { useTranslation } from 'next-i18next'; import { Card, CardBody } from 'reactstrap'; @@ -9,30 +8,28 @@ import { toArrayIfNot } from '~/utils/array-utils'; import loggerFactory from '~/utils/logger'; import { withUnstatedContainers } from '../../UnstatedUtils'; - import IndentForm from './IndentForm'; import LineBreakForm from './LineBreakForm'; import XssForm from './XssForm'; const logger = loggerFactory('growi:MarkDown'); -type Props ={ - adminMarkDownContainer: AdminMarkDownContainer -} +type Props = { + adminMarkDownContainer: AdminMarkDownContainer; +}; const MarkDownSettingContents = React.memo((props: Props): JSX.Element => { const { t } = useTranslation('admin'); const { adminMarkDownContainer } = props; useEffect(() => { - const fetchMarkdownData = async() => { + const fetchMarkdownData = async () => { await adminMarkDownContainer.retrieveMarkdownData(); }; try { fetchMarkdownData(); - } - catch (err) { + } catch (err) { const errs = toArrayIfNot(err); toastError(errs); logger.error(errs); @@ -42,23 +39,35 @@ const MarkDownSettingContents = React.memo((props: Props): JSX.Element => { return (

    {/* Line Break Setting */} -

    {t('markdown_settings.lineBreak_header')}

    +

    + {t('markdown_settings.lineBreak_header')} +

    - { t('markdown_settings.lineBreak_desc') } + + {t('markdown_settings.lineBreak_desc')} + {/* Indent Setting */} -

    {t('markdown_settings.indent_header')}

    +

    + {t('markdown_settings.indent_header')} +

    - {t('markdown_settings.indent_desc') } + + {t('markdown_settings.indent_desc')} + {/* XSS Setting */} -

    { t('markdown_settings.xss_header') }

    +

    + {t('markdown_settings.xss_header')} +

    - { t('markdown_settings.xss_desc') } + + {t('markdown_settings.xss_desc')} +
    @@ -66,8 +75,9 @@ const MarkDownSettingContents = React.memo((props: Props): JSX.Element => { }); MarkDownSettingContents.displayName = 'MarkDownSettingContents'; - -const MarkdownSettingWithUnstatedContainer = withUnstatedContainers(MarkDownSettingContents, [AdminMarkDownContainer]); - +const MarkdownSettingWithUnstatedContainer = withUnstatedContainers( + MarkDownSettingContents, + [AdminMarkDownContainer], +); export default MarkdownSettingWithUnstatedContainer; diff --git a/apps/app/src/client/components/Admin/MarkdownSetting/WhitelistInput.tsx b/apps/app/src/client/components/Admin/MarkdownSetting/WhitelistInput.tsx index 33374b656bb..650891c3730 100644 --- a/apps/app/src/client/components/Admin/MarkdownSetting/WhitelistInput.tsx +++ b/apps/app/src/client/components/Admin/MarkdownSetting/WhitelistInput.tsx @@ -1,24 +1,25 @@ -import { useCallback, type JSX } from 'react'; - +import { type JSX, useCallback } from 'react'; import { useTranslation } from 'next-i18next'; import type { UseFormRegister, UseFormSetValue } from 'react-hook-form'; import type AdminMarkDownContainer from '~/client/services/AdminMarkDownContainer'; -import { tagNames as recommendedTagNames, attributes as recommendedAttributes } from '~/services/renderer/recommended-whitelist'; +import { + attributes as recommendedAttributes, + tagNames as recommendedTagNames, +} from '~/services/renderer/recommended-whitelist'; type FormValues = { - tagWhitelist: string, - attrWhitelist: string, -} + tagWhitelist: string; + attrWhitelist: string; +}; -type Props ={ - adminMarkDownContainer: AdminMarkDownContainer, - register: UseFormRegister, - setValue: UseFormSetValue, -} +type Props = { + adminMarkDownContainer: AdminMarkDownContainer; + register: UseFormRegister; + setValue: UseFormSetValue; +}; export const WhitelistInput = (props: Props): JSX.Element => { - const { t } = useTranslation('admin'); const { adminMarkDownContainer, register, setValue } = props; @@ -39,9 +40,16 @@ export const WhitelistInput = (props: Props): JSX.Element => {
    {t('markdown_settings.xss_options.tag_names')} -

    - {t('markdown_settings.xss_options.import_recommended', { target: 'Tags' })} -

    +