import React, { type JSX, useEffect, useRef, useState } from 'react'; import type { IRevisionHasId } from '@growi/core'; import { useTranslation } from 'next-i18next'; import { useSWRxInfinitePageRevisions } from '~/stores/page'; import { RevisionComparer } from '../RevisionComparer/RevisionComparer'; import { Revision } from './Revision'; import styles from './PageRevisionTable.module.scss'; type PageRevisionTableProps = { sourceRevisionId?: string; targetRevisionId?: string; onClose: () => void; currentPageId: string; currentPagePath: string; }; export const PageRevisionTable = ( props: PageRevisionTableProps, ): JSX.Element => { const { t } = useTranslation(); const REVISIONS_PER_PAGE = 10; const { sourceRevisionId, targetRevisionId, onClose, currentPageId, currentPagePath, } = props; // Load all data if source revision id and target revision id not null const revisionPerPage = sourceRevisionId != null && targetRevisionId != null ? 0 : REVISIONS_PER_PAGE; const swrInifiniteResponse = useSWRxInfinitePageRevisions( currentPageId, revisionPerPage, ); const { data, size, error, setSize, isValidating } = swrInifiniteResponse; const revisions = data && data[0].revisions; const oldestRevision = revisions && revisions[revisions.length - 1]; // First load const isLoadingInitialData = !data && !error; const isLoadingMore = isLoadingInitialData || (isValidating && data != null && typeof data[size - 1] === 'undefined'); const isReachingEnd = revisionPerPage === 0 || !!( data != null && data[data.length - 1]?.revisions.length < REVISIONS_PER_PAGE ); const [sourceRevision, setSourceRevision] = useState(); const [targetRevision, setTargetRevision] = useState(); const tbodyRef = useRef(null); useEffect(() => { if (revisions != null) { // when both source and target are specified if (sourceRevisionId != null && targetRevisionId != null) { const sourceRevision = revisions.filter( (revision) => revision._id === sourceRevisionId, )[0]; const targetRevision = revisions.filter( (revision) => revision._id === targetRevisionId, )[0]; setSourceRevision(sourceRevision); setTargetRevision(targetRevision); } else { const latestRevision = revisions != null ? revisions[0] : undefined; const previousRevision = revisions.length >= 2 ? revisions[1] : latestRevision; setTargetRevision(latestRevision); setSourceRevision(previousRevision); } } }, [revisions, sourceRevisionId, targetRevisionId]); useEffect(() => { // Apply ref to tbody const tbody = tbodyRef.current; const handleScroll = () => { const offset = 30; // Threshold before scroll actually reaching the end if (tbody) { // Scroll end const isEnd = tbody.scrollTop + tbody.clientHeight + offset >= tbody.scrollHeight; if (isEnd && !isLoadingMore && !isReachingEnd) { setSize(size + 1); } } }; if (tbody) { tbody.addEventListener('scroll', handleScroll); } return () => { if (tbody) { tbody.removeEventListener('scroll', handleScroll); } }; }, [isLoadingMore, isReachingEnd, setSize, size]); const renderRow = ( revision: IRevisionHasId, previousRevision: IRevisionHasId, latestRevision: IRevisionHasId, isOldestRevision: boolean, hasDiff: boolean, ) => { const revisionId = revision._id; const handleCompareLatestRevisionButton = () => { setSourceRevision(revision); setTargetRevision(latestRevision); }; const handleComparePreviousRevisionButton = () => { setSourceRevision(previousRevision); setTargetRevision(revision); }; return (
{hasDiff && (
)}
{(hasDiff || revisionId === sourceRevision?._id) && (
setSourceRevision(revision)} />
)} {(hasDiff || revisionId === targetRevision?._id) && (
setTargetRevision(revision)} />
)} ); }; return ( <> {revisions != null && data != null && data .flatMap((apiResult) => apiResult.revisions) .map((revision, idx) => { const previousRevision = idx + 1 < revisions?.length ? revisions[idx + 1] : revision; const isOldestRevision = revision === oldestRevision; const latestRevision = revisions[0]; // set 'true' if undefined for backward compatibility const hasDiff = revision.hasDiffToPrev !== false; return renderRow( revision, previousRevision, latestRevision, isOldestRevision, hasDiff, ); })}
{t('page_history.revision')} {t('page_history.comparing_source')} {t('page_history.comparing_target')}
{sourceRevision != null && targetRevision != null && (
)} ); };