"use client"; import { diffWords, diffArrays } from "diff"; import { useTranslations } from "next-intl"; export type DiffSnapshot = { title: string; description?: string | null; ingredients: Array<{ rawName: string; quantity?: string | number | null; unit?: string | null; note?: string | null }>; steps: Array<{ instruction: string; timerSeconds?: number | null }>; }; function ingredientLine(i: DiffSnapshot["ingredients"][number]): string { return [i.quantity, i.unit, i.rawName, i.note && `(${i.note})`].filter(Boolean).join(" "); } function stepLine(s: DiffSnapshot["steps"][number]): string { return s.instruction; } function TextDiff({ before, after }: { before: string; after: string }) { const parts = diffWords(before, after); return (

{parts.map((part, i) => ( {part.value} ))}

); } function ListDiff({ before, after }: { before: string[]; after: string[] }) { const changes = diffArrays(before, after); const rows: React.ReactNode[] = []; let key = 0; for (let i = 0; i < changes.length; i++) { const change = changes[i]!; // A removed chunk immediately followed by an added chunk is usually an // edit, not a delete+insert — word-diff matching pairs so a one-word // tweak doesn't render as an unreadable full remove + full add. if (change.removed && changes[i + 1]?.added) { const removedItems = change.value; const addedItems = changes[i + 1]!.value; const pairCount = Math.min(removedItems.length, addedItems.length); for (let j = 0; j < pairCount; j++) { rows.push(
); } for (let j = pairCount; j < removedItems.length; j++) { rows.push(
− {removedItems[j]}
); } for (let j = pairCount; j < addedItems.length; j++) { rows.push(
+ {addedItems[j]}
); } i++; // consumed the paired "added" chunk too continue; } for (const item of change.value) { if (change.added) { rows.push(
+ {item}
); } else if (change.removed) { rows.push(
− {item}
); } else { rows.push(
{item}
); } } } return
{rows}
; } export function VersionDiffView({ before, after }: { before: DiffSnapshot; after: DiffSnapshot }) { const t = useTranslations("recipe"); return (

{t("diffTitleLabel")}

{(before.description || after.description) && (

{t("diffDescriptionLabel")}

)}

{t("diffIngredientsLabel")}

{t("diffStepsLabel")}

); }