feat(client): compare two page history versions in history view

This commit is contained in:
Philipinho
2026-08-11 21:20:46 +01:00
parent 14d0ad5b01
commit 68f1b120b5
4 changed files with 76 additions and 9 deletions
@@ -1293,5 +1293,7 @@
"Compare": "Compare", "Compare": "Compare",
"Compare versions": "Compare versions", "Compare versions": "Compare versions",
"Select version from {{date}}": "Select version from {{date}}", "Select version from {{date}}": "Select version from {{date}}",
"Version actions for {{date}}": "Version actions for {{date}}" "Version actions for {{date}}": "Version actions for {{date}}",
"Comparing {{newer}} and {{older}}": "Comparing {{newer}} and {{older}}",
"Exit compare": "Exit compare"
} }
@@ -99,3 +99,8 @@
flex: 1; flex: 1;
padding: rem(16px) rem(40px); padding: rem(16px) rem(40px);
} }
.compareBanner {
border-bottom: rem(1px) solid
light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
}
@@ -1,5 +1,6 @@
import { import {
ActionIcon, ActionIcon,
CloseButton,
Group, Group,
Paper, Paper,
ScrollArea, ScrollArea,
@@ -12,17 +13,20 @@ import { useAtom, useAtomValue } from "jotai";
import { import {
activeHistoryIdAtom, activeHistoryIdAtom,
activeHistoryPrevIdAtom, activeHistoryPrevIdAtom,
comparePairAtom,
diffCountsAtom, diffCountsAtom,
highlightChangesAtom, highlightChangesAtom,
} from "@/features/page-history/atoms/history-atoms"; } from "@/features/page-history/atoms/history-atoms";
import HistoryView from "@/features/page-history/components/history-view"; import HistoryView from "@/features/page-history/components/history-view";
import { useRef } from "react"; import { useMemo, useRef } from "react";
import { IconChevronUp, IconChevronDown } from "@tabler/icons-react"; import { IconChevronUp, IconChevronDown } from "@tabler/icons-react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { import {
useDiffNavigation, useDiffNavigation,
useHistoryReset, useHistoryReset,
} from "@/features/page-history/hooks"; } from "@/features/page-history/hooks";
import { usePageHistoryListQuery } from "@/features/page-history/queries/page-history-query";
import { formattedDate } from "@/lib/time";
interface Props { interface Props {
pageId: string; pageId: string;
@@ -36,6 +40,28 @@ export default function HistoryModalBody({ pageId }: Props) {
const activeHistoryPrevId = useAtomValue(activeHistoryPrevIdAtom); const activeHistoryPrevId = useAtomValue(activeHistoryPrevIdAtom);
const [highlightChanges, setHighlightChanges] = useAtom(highlightChangesAtom); const [highlightChanges, setHighlightChanges] = useAtom(highlightChangesAtom);
const diffCounts = useAtomValue(diffCountsAtom); const diffCounts = useAtomValue(diffCountsAtom);
const [comparePair, setComparePair] = useAtom(comparePairAtom);
const { data: pageHistoryData } = usePageHistoryListQuery(pageId);
const historyItems = useMemo(
() => pageHistoryData?.pages.flatMap((page) => page.items) ?? [],
[pageHistoryData],
);
const compareLabel = useMemo(() => {
if (!comparePair) return null;
const newerItem = historyItems.find(
(item) => item.id === comparePair.newerId,
);
const olderItem = historyItems.find(
(item) => item.id === comparePair.olderId,
);
if (!newerItem || !olderItem) return null;
return t("Comparing {{newer}} and {{older}}", {
newer: formattedDate(new Date(newerItem.createdAt)),
older: formattedDate(new Date(olderItem.createdAt)),
});
}, [comparePair, historyItems, t]);
useHistoryReset(pageId); useHistoryReset(pageId);
const { currentChangeIndex, handlePrevChange, handleNextChange } = const { currentChangeIndex, handlePrevChange, handleNextChange } =
@@ -50,6 +76,25 @@ export default function HistoryModalBody({ pageId }: Props) {
</nav> </nav>
<div style={{ position: "relative", flex: 1 }}> <div style={{ position: "relative", flex: 1 }}>
{comparePair && compareLabel && (
<Group
justify="space-between"
wrap="nowrap"
px="md"
py={4}
className={classes.compareBanner}
>
<Text size="sm" fw={500} lineClamp={1}>
{compareLabel}
</Text>
<CloseButton
size="sm"
aria-label={t("Exit compare")}
onClick={() => setComparePair(null)}
/>
</Group>
)}
<ScrollArea <ScrollArea
h={650} h={650}
w="100%" w="100%"
@@ -57,11 +102,18 @@ export default function HistoryModalBody({ pageId }: Props) {
viewportRef={scrollViewportRef} viewportRef={scrollViewportRef}
> >
<div className={classes.sidebarRightSection}> <div className={classes.sidebarRightSection}>
{activeHistoryId && <HistoryView />} {comparePair ? (
<HistoryView
historyId={comparePair.newerId}
prevHistoryId={comparePair.olderId}
/>
) : (
activeHistoryId && <HistoryView />
)}
</div> </div>
</ScrollArea> </ScrollArea>
{activeHistoryId && activeHistoryPrevId && ( {(comparePair || (activeHistoryId && activeHistoryPrevId)) && (
<Paper <Paper
shadow="md" shadow="md"
radius="xl" radius="xl"
@@ -7,21 +7,29 @@ import {
activeHistoryPrevIdAtom, activeHistoryPrevIdAtom,
} from "@/features/page-history/atoms/history-atoms"; } from "@/features/page-history/atoms/history-atoms";
function HistoryView() { interface Props {
historyId?: string;
prevHistoryId?: string;
}
function HistoryView({ historyId, prevHistoryId }: Props) {
const { t } = useTranslation(); const { t } = useTranslation();
const historyId = useAtomValue(activeHistoryIdAtom); const activeId = useAtomValue(activeHistoryIdAtom);
const prevHistoryId = useAtomValue(activeHistoryPrevIdAtom); const activePrevId = useAtomValue(activeHistoryPrevIdAtom);
const resolvedId = historyId ?? activeId;
const resolvedPrevId = prevHistoryId ?? activePrevId;
const { const {
data, data,
isLoading: isLoadingCurrent, isLoading: isLoadingCurrent,
isError: isErrorCurrent, isError: isErrorCurrent,
} = usePageHistoryQuery(historyId); } = usePageHistoryQuery(resolvedId);
const { const {
data: prevData, data: prevData,
isLoading: isLoadingPrev, isLoading: isLoadingPrev,
isError: isErrorPrev, isError: isErrorPrev,
} = usePageHistoryQuery(prevHistoryId); } = usePageHistoryQuery(resolvedPrevId);
if (isLoadingCurrent || isLoadingPrev) { if (isLoadingCurrent || isLoadingPrev) {
return <></>; return <></>;