diff --git a/src/ui/locales/en.json b/src/ui/locales/en.json index 7a0388277..b3b644f07 100644 --- a/src/ui/locales/en.json +++ b/src/ui/locales/en.json @@ -2108,8 +2108,9 @@ "releaseNotes": "Release notes", "noReleaseNotes": "No notes for this version.", "viewRelease": "View on GitHub", - "showAllReleases": "Show all {{count}}", - "showFewerReleases": "Show fewer", + "showOlderReleases": "Older releases ({{count}})", + "hideOlderReleases": "Hide older releases", + "showMoreChanges": "Show {{count}} more", "videoTitle": "{{name}} video", "changeTypes": { "added": "Added", diff --git a/src/ui/plugins/PluginReleaseNotes.tsx b/src/ui/plugins/PluginReleaseNotes.tsx index 393e313d9..621285462 100644 --- a/src/ui/plugins/PluginReleaseNotes.tsx +++ b/src/ui/plugins/PluginReleaseNotes.tsx @@ -12,7 +12,8 @@ import { type ReleaseRow, } from "./plugin-model"; -const SHOWN = 3; +/** A long release shows this many changes until it is opened up. */ +const MAX_CHANGES = 8; function formatDate(value: string | null): string { if (!value) return ""; @@ -48,7 +49,17 @@ function Release({ installedVersion: string | null; }) { const { t } = useTranslation(); - const groups = groupChanges(row.changes); + const [full, setFull] = useState(false); + const all = groupChanges(row.changes); + const hidden = full ? 0 : Math.max(0, row.changes.length - MAX_CHANGES); + let budget = full ? Infinity : MAX_CHANGES; + const groups = all + .map((group) => { + const items = group.items.slice(0, Math.max(0, budget)); + budget -= items.length; + return { ...group, items }; + }) + .filter((group) => group.items.length > 0); return (
@@ -69,7 +80,9 @@ function Release({
{row.summary && ( -

+

)} @@ -87,6 +100,15 @@ function Release({ ))} + {hidden > 0 && ( + + )} {groups.length === 0 && !row.summary && ( {t("plugins.manager.noReleaseNotes")} @@ -107,13 +129,14 @@ function Release({ } /** - * Every version's notes, from the registry and the installed CHANGELOG.md. + * The newest version's notes, from the registry and the installed + * CHANGELOG.md, with older versions behind a toggle in a capped scroll area. * Renders nothing when there are no versions to list. */ export function PluginReleaseNotes({ plugin }: { plugin: PluginEntry }) { const { t } = useTranslation(); const [local, setLocal] = useState([]); - const [expanded, setExpanded] = useState(false); + const [showOlder, setShowOlder] = useState(false); useEffect(() => { let cancelled = false; @@ -134,35 +157,42 @@ export function PluginReleaseNotes({ plugin }: { plugin: PluginEntry }) { [plugin.versions, local], ); if (rows.length === 0) return null; - const shown = expanded ? rows : rows.slice(0, SHOWN); + const [latest, ...older] = rows; return ( SHOWN ? ( + older.length > 0 ? ( ) : undefined } > -
- {shown.map((row) => ( - - ))} +
+ + {showOlder && older.length > 0 && ( +
+ {older.map((row) => ( + + ))} +
+ )}
); diff --git a/src/ui/tests/plugins/PluginReleaseNotes.test.tsx b/src/ui/tests/plugins/PluginReleaseNotes.test.tsx index 8c028c290..4ab6d6cb5 100644 --- a/src/ui/tests/plugins/PluginReleaseNotes.test.tsx +++ b/src/ui/tests/plugins/PluginReleaseNotes.test.tsx @@ -1,5 +1,11 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { cleanup, render, screen, waitFor } from "@testing-library/react"; +import { + cleanup, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/react"; import i18next from "i18next"; import { I18nextProvider, initReactI18next } from "react-i18next"; import type { PluginEntry } from "@/plugins/plugin-model"; @@ -27,6 +33,9 @@ async function i18n() { releaseNotes: "Release notes", installedBadge: "Installed", changeTypes: { added: "Added", fixed: "Fixed" }, + showOlderReleases: "Older releases ({{count}})", + hideOlderReleases: "Hide older releases", + showMoreChanges: "Show {{count}} more", videoTitle: "{{name}} video", }, }, @@ -71,6 +80,43 @@ describe("PluginReleaseNotes", () => { expect(screen.getByText("Installed")).toBeTruthy(); }); + it("shows only the newest release until older ones are opened", async () => { + getPluginChangelog.mockResolvedValue([ + { version: "1.1.0", changes: [{ type: "fixed", text: "Newest" }] }, + { version: "1.0.0", changes: [{ type: "added", text: "Oldest" }] }, + ]); + render( + + + , + ); + await waitFor(() => expect(screen.getByText("Newest")).toBeTruthy()); + expect(screen.queryByText("Oldest")).toBeNull(); + fireEvent.click(screen.getByText("Older releases (1)")); + expect(screen.getByText("Oldest")).toBeTruthy(); + }); + + it("cuts a long release down until it is opened up", async () => { + getPluginChangelog.mockResolvedValue([ + { + version: "1.0.0", + changes: Array.from({ length: 12 }, (_, i) => ({ + type: "added", + text: `Change ${i}`, + })), + }, + ]); + render( + + + , + ); + await waitFor(() => expect(screen.getByText("Change 7")).toBeTruthy()); + expect(screen.queryByText("Change 8")).toBeNull(); + fireEvent.click(screen.getByText("Show 4 more")); + expect(screen.getByText("Change 11")).toBeTruthy(); + }); + it("renders nothing for a plugin with no versions", async () => { const { container } = render(