From 2cd85878e23ea642ac78203a8fb7df31922d3401 Mon Sep 17 00:00:00 2001 From: thepoy Date: Tue, 20 Oct 2026 11:11:42 +0800 Subject: [PATCH] feat(settings): show the release notes in a dialog, and link to the changelog The notes are the release body the updater already fetches. Rendered inside the version card they pushed the install button off screen and showed raw Markdown, so they now open in a dialog: a small renderer turns headings, bullets and inline code into elements and never touches an HTML sink. The About card also gains a Changelog row for what is already installed. It opens the release list rather than one tag, because nightly builds are published under their own release. The ui/dialog and ui/alert-dialog families ride along, together with the Button and ButtonGroupText fixes they need: this change imports them, so they cannot be committed apart from it. --- src/components/settings/AboutSection.tsx | 12 ++- src/components/settings/ReleaseNotes.tsx | 73 ++++++++++++++++ src/components/settings/UpdateCheck.tsx | 38 ++++++++- .../alert-dialog/AlertDialog/AlertDialog.tsx | 19 +++++ .../AlertDialog/AlertDialog.types.ts | 7 ++ .../ui/alert-dialog/AlertDialog/index.ts | 2 + .../AlertDialogAction/AlertDialogAction.tsx | 18 ++++ .../AlertDialogAction.types.ts | 3 + .../alert-dialog/AlertDialogAction/index.ts | 1 + .../AlertDialogCancel/AlertDialogCancel.tsx | 24 ++++++ .../AlertDialogCancel.types.ts | 3 + .../alert-dialog/AlertDialogCancel/index.ts | 1 + .../AlertDialogContent.styles.ts | 4 + .../AlertDialogContent/AlertDialogContent.tsx | 23 +++++ .../AlertDialogContent.types.ts | 12 +++ .../alert-dialog/AlertDialogContent/index.ts | 2 + .../AlertDialogDescription.styles.ts | 4 + .../AlertDialogDescription.tsx | 27 ++++++ .../AlertDialogDescription.types.ts | 7 ++ .../AlertDialogDescription/index.ts | 1 + .../AlertDialogFooter.styles.ts | 4 + .../AlertDialogFooter/AlertDialogFooter.tsx | 17 ++++ .../AlertDialogFooter.types.ts | 3 + .../alert-dialog/AlertDialogFooter/index.ts | 1 + .../AlertDialogHeader.styles.ts | 4 + .../AlertDialogHeader/AlertDialogHeader.tsx | 17 ++++ .../AlertDialogHeader.types.ts | 3 + .../alert-dialog/AlertDialogHeader/index.ts | 1 + .../AlertDialogMedia.styles.ts | 4 + .../AlertDialogMedia/AlertDialogMedia.tsx | 17 ++++ .../AlertDialogMedia.types.ts | 3 + .../ui/alert-dialog/AlertDialogMedia/index.ts | 1 + .../AlertDialogOverlay/AlertDialogOverlay.tsx | 6 ++ .../AlertDialogOverlay.types.ts | 3 + .../alert-dialog/AlertDialogOverlay/index.ts | 1 + .../AlertDialogPortal/AlertDialogPortal.tsx | 6 ++ .../AlertDialogPortal.types.ts | 3 + .../alert-dialog/AlertDialogPortal/index.ts | 1 + .../AlertDialogTitle.styles.ts | 4 + .../AlertDialogTitle/AlertDialogTitle.tsx | 27 ++++++ .../AlertDialogTitle.types.ts | 3 + .../ui/alert-dialog/AlertDialogTitle/index.ts | 1 + .../AlertDialogTrigger/AlertDialogTrigger.tsx | 31 +++++++ .../AlertDialogTrigger.types.ts | 7 ++ .../alert-dialog/AlertDialogTrigger/index.ts | 2 + src/components/ui/alert-dialog/index.ts | 12 +++ .../button-group/ButtonGroup/ButtonGroup.tsx | 1 + .../ButtonGroupText/ButtonGroupText.tsx | 10 ++- src/components/ui/button/Button.tsx | 1 + .../ui/dialog/Dialog/Dialog.context.ts | 24 ++++++ .../ui/dialog/Dialog/Dialog.styles.ts | 0 src/components/ui/dialog/Dialog/Dialog.tsx | 83 +++++++++++++++++++ .../ui/dialog/Dialog/Dialog.types.ts | 29 +++++++ src/components/ui/dialog/Dialog/index.ts | 3 + .../dialog/DialogClose/DialogClose.styles.ts | 1 + .../ui/dialog/DialogClose/DialogClose.tsx | 43 ++++++++++ .../dialog/DialogClose/DialogClose.types.ts | 3 + src/components/ui/dialog/DialogClose/index.ts | 1 + .../DialogContent/DialogContent.context.ts | 16 ++++ .../DialogContent/DialogContent.styles.ts | 2 + .../ui/dialog/DialogContent/DialogContent.tsx | 32 +++++++ .../DialogContent/DialogContent.types.ts | 9 ++ .../ui/dialog/DialogContent/index.ts | 2 + .../DialogDescription.styles.ts | 2 + .../DialogDescription/DialogDescription.tsx | 27 ++++++ .../DialogDescription.types.ts | 3 + .../ui/dialog/DialogDescription/index.ts | 1 + .../DialogFooter/DialogFooter.styles.ts | 2 + .../ui/dialog/DialogFooter/DialogFooter.tsx | 17 ++++ .../dialog/DialogFooter/DialogFooter.types.ts | 3 + .../ui/dialog/DialogFooter/index.ts | 1 + .../DialogHeader/DialogHeader.styles.ts | 1 + .../ui/dialog/DialogHeader/DialogHeader.tsx | 17 ++++ .../dialog/DialogHeader/DialogHeader.types.ts | 3 + .../ui/dialog/DialogHeader/index.ts | 1 + .../DialogOverlay/DialogOverlay.styles.ts | 2 + .../ui/dialog/DialogOverlay/DialogOverlay.tsx | 46 ++++++++++ .../DialogOverlay/DialogOverlay.types.ts | 10 +++ .../ui/dialog/DialogOverlay/index.ts | 1 + .../ui/dialog/DialogPortal/DialogPortal.tsx | 10 +++ .../dialog/DialogPortal/DialogPortal.types.ts | 3 + .../ui/dialog/DialogPortal/index.ts | 1 + .../ui/dialog/DialogSurface/DialogSurface.tsx | 82 ++++++++++++++++++ .../DialogSurface/DialogSurface.types.ts | 20 +++++ .../ui/dialog/DialogSurface/index.ts | 2 + .../dialog/DialogTitle/DialogTitle.styles.ts | 1 + .../ui/dialog/DialogTitle/DialogTitle.tsx | 27 ++++++ .../dialog/DialogTitle/DialogTitle.types.ts | 3 + src/components/ui/dialog/DialogTitle/index.ts | 1 + .../ui/dialog/DialogTrigger/DialogTrigger.tsx | 29 +++++++ .../DialogTrigger/DialogTrigger.types.ts | 4 + .../ui/dialog/DialogTrigger/index.ts | 4 + .../dialog/DialogTrigger/useDialogTrigger.ts | 38 +++++++++ src/components/ui/dialog/index.ts | 11 +++ src/i18n/en-US.ts | 4 + src/i18n/ja-JP.ts | 4 + src/i18n/ko-KR.ts | 4 + src/i18n/zh-CN.ts | 4 + src/i18n/zh-HK.ts | 4 + src/i18n/zh-TW.ts | 4 + src/lib/release-notes.test.ts | 82 ++++++++++++++++++ src/lib/release-notes.ts | 79 ++++++++++++++++++ src/store/update.store.test.ts | 42 +++++++++- src/store/update.store.ts | 10 ++- src/types/props.types.ts | 2 +- 105 files changed, 1326 insertions(+), 9 deletions(-) create mode 100644 src/components/settings/ReleaseNotes.tsx create mode 100644 src/components/ui/alert-dialog/AlertDialog/AlertDialog.tsx create mode 100644 src/components/ui/alert-dialog/AlertDialog/AlertDialog.types.ts create mode 100644 src/components/ui/alert-dialog/AlertDialog/index.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogAction/AlertDialogAction.tsx create mode 100644 src/components/ui/alert-dialog/AlertDialogAction/AlertDialogAction.types.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogAction/index.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogCancel/AlertDialogCancel.tsx create mode 100644 src/components/ui/alert-dialog/AlertDialogCancel/AlertDialogCancel.types.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogCancel/index.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogContent/AlertDialogContent.styles.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogContent/AlertDialogContent.tsx create mode 100644 src/components/ui/alert-dialog/AlertDialogContent/AlertDialogContent.types.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogContent/index.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogDescription/AlertDialogDescription.styles.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogDescription/AlertDialogDescription.tsx create mode 100644 src/components/ui/alert-dialog/AlertDialogDescription/AlertDialogDescription.types.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogDescription/index.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogFooter/AlertDialogFooter.styles.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogFooter/AlertDialogFooter.tsx create mode 100644 src/components/ui/alert-dialog/AlertDialogFooter/AlertDialogFooter.types.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogFooter/index.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogHeader/AlertDialogHeader.styles.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogHeader/AlertDialogHeader.tsx create mode 100644 src/components/ui/alert-dialog/AlertDialogHeader/AlertDialogHeader.types.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogHeader/index.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogMedia/AlertDialogMedia.styles.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogMedia/AlertDialogMedia.tsx create mode 100644 src/components/ui/alert-dialog/AlertDialogMedia/AlertDialogMedia.types.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogMedia/index.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogOverlay/AlertDialogOverlay.tsx create mode 100644 src/components/ui/alert-dialog/AlertDialogOverlay/AlertDialogOverlay.types.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogOverlay/index.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogPortal/AlertDialogPortal.tsx create mode 100644 src/components/ui/alert-dialog/AlertDialogPortal/AlertDialogPortal.types.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogPortal/index.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogTitle/AlertDialogTitle.styles.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogTitle/AlertDialogTitle.tsx create mode 100644 src/components/ui/alert-dialog/AlertDialogTitle/AlertDialogTitle.types.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogTitle/index.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogTrigger/AlertDialogTrigger.tsx create mode 100644 src/components/ui/alert-dialog/AlertDialogTrigger/AlertDialogTrigger.types.ts create mode 100644 src/components/ui/alert-dialog/AlertDialogTrigger/index.ts create mode 100644 src/components/ui/alert-dialog/index.ts create mode 100644 src/components/ui/dialog/Dialog/Dialog.context.ts create mode 100644 src/components/ui/dialog/Dialog/Dialog.styles.ts create mode 100644 src/components/ui/dialog/Dialog/Dialog.tsx create mode 100644 src/components/ui/dialog/Dialog/Dialog.types.ts create mode 100644 src/components/ui/dialog/Dialog/index.ts create mode 100644 src/components/ui/dialog/DialogClose/DialogClose.styles.ts create mode 100644 src/components/ui/dialog/DialogClose/DialogClose.tsx create mode 100644 src/components/ui/dialog/DialogClose/DialogClose.types.ts create mode 100644 src/components/ui/dialog/DialogClose/index.ts create mode 100644 src/components/ui/dialog/DialogContent/DialogContent.context.ts create mode 100644 src/components/ui/dialog/DialogContent/DialogContent.styles.ts create mode 100644 src/components/ui/dialog/DialogContent/DialogContent.tsx create mode 100644 src/components/ui/dialog/DialogContent/DialogContent.types.ts create mode 100644 src/components/ui/dialog/DialogContent/index.ts create mode 100644 src/components/ui/dialog/DialogDescription/DialogDescription.styles.ts create mode 100644 src/components/ui/dialog/DialogDescription/DialogDescription.tsx create mode 100644 src/components/ui/dialog/DialogDescription/DialogDescription.types.ts create mode 100644 src/components/ui/dialog/DialogDescription/index.ts create mode 100644 src/components/ui/dialog/DialogFooter/DialogFooter.styles.ts create mode 100644 src/components/ui/dialog/DialogFooter/DialogFooter.tsx create mode 100644 src/components/ui/dialog/DialogFooter/DialogFooter.types.ts create mode 100644 src/components/ui/dialog/DialogFooter/index.ts create mode 100644 src/components/ui/dialog/DialogHeader/DialogHeader.styles.ts create mode 100644 src/components/ui/dialog/DialogHeader/DialogHeader.tsx create mode 100644 src/components/ui/dialog/DialogHeader/DialogHeader.types.ts create mode 100644 src/components/ui/dialog/DialogHeader/index.ts create mode 100644 src/components/ui/dialog/DialogOverlay/DialogOverlay.styles.ts create mode 100644 src/components/ui/dialog/DialogOverlay/DialogOverlay.tsx create mode 100644 src/components/ui/dialog/DialogOverlay/DialogOverlay.types.ts create mode 100644 src/components/ui/dialog/DialogOverlay/index.ts create mode 100644 src/components/ui/dialog/DialogPortal/DialogPortal.tsx create mode 100644 src/components/ui/dialog/DialogPortal/DialogPortal.types.ts create mode 100644 src/components/ui/dialog/DialogPortal/index.ts create mode 100644 src/components/ui/dialog/DialogSurface/DialogSurface.tsx create mode 100644 src/components/ui/dialog/DialogSurface/DialogSurface.types.ts create mode 100644 src/components/ui/dialog/DialogSurface/index.ts create mode 100644 src/components/ui/dialog/DialogTitle/DialogTitle.styles.ts create mode 100644 src/components/ui/dialog/DialogTitle/DialogTitle.tsx create mode 100644 src/components/ui/dialog/DialogTitle/DialogTitle.types.ts create mode 100644 src/components/ui/dialog/DialogTitle/index.ts create mode 100644 src/components/ui/dialog/DialogTrigger/DialogTrigger.tsx create mode 100644 src/components/ui/dialog/DialogTrigger/DialogTrigger.types.ts create mode 100644 src/components/ui/dialog/DialogTrigger/index.ts create mode 100644 src/components/ui/dialog/DialogTrigger/useDialogTrigger.ts create mode 100644 src/components/ui/dialog/index.ts create mode 100644 src/lib/release-notes.test.ts create mode 100644 src/lib/release-notes.ts diff --git a/src/components/settings/AboutSection.tsx b/src/components/settings/AboutSection.tsx index 3bf499d9..ca06d3c9 100644 --- a/src/components/settings/AboutSection.tsx +++ b/src/components/settings/AboutSection.tsx @@ -1,5 +1,5 @@ // Responsibility: About group — asymmetric layout (one large version block + a column of narrow link rows), breaking the default three-equal-card look. -import { ChevronRight, Code, FileText } from "lucide-solid"; +import { ChevronRight, Code, FileText, ScrollText } from "lucide-solid"; import { createResource, type JSXElement } from "solid-js"; import { Item, @@ -16,6 +16,8 @@ import { SettingsGroup } from "./SettingsGroup"; import { UpdateCheck } from "./UpdateCheck"; const SOURCE_URL = "https://github.com/dwall-rs/dwall"; +/** The release list, not one tag: nightly builds are published under their own release. */ +const CHANGELOG_URL = `${SOURCE_URL}/releases`; export function AboutSection() { const [info] = createResource(getAppInfo); @@ -29,7 +31,7 @@ export function AboutSection() { delay={200} >
-
+
{t("settings.about.logoAlt")}
+ } + title={t("settings.about.changelog")} + sub={t("settings.about.changelogSub")} + onClick={() => void openUrl(CHANGELOG_URL)} + /> } title={t("settings.about.sourceCode")} diff --git a/src/components/settings/ReleaseNotes.tsx b/src/components/settings/ReleaseNotes.tsx new file mode 100644 index 00000000..dd33743e --- /dev/null +++ b/src/components/settings/ReleaseNotes.tsx @@ -0,0 +1,73 @@ +// Responsibility: renders the parsed release notes as elements — headings, lists, paragraphs and +// inline code — with no HTML sink anywhere. +import { For, Match, Switch, type JSX } from "solid-js"; +import { + parseReleaseNoteSpans, + parseReleaseNotes, + type ReleaseNoteBlock, +} from "@/lib/release-notes"; + +const asHeading = (block: ReleaseNoteBlock) => + block.kind === "heading" ? block : undefined; + +const asList = (block: ReleaseNoteBlock) => + block.kind === "list" ? block : undefined; + +const asParagraph = (block: ReleaseNoteBlock) => + block.kind === "paragraph" ? block : undefined; + +function Spans(props: { text: string }): JSX.Element { + return ( + + {(span) => + span.code ? ( + + {span.text} + + ) : ( + span.text + ) + } + + ); +} + +export function ReleaseNotes(props: { markdown: string }) { + return ( +
+ + {(block) => ( + + + {(heading) => ( +

+ {heading().text} +

+ )} +
+ + {(list) => ( +
    + + {(item) => ( +
  • + +
  • + )} +
    +
+ )} +
+ + {(paragraph) => ( +

+ +

+ )} +
+
+ )} +
+
+ ); +} diff --git a/src/components/settings/UpdateCheck.tsx b/src/components/settings/UpdateCheck.tsx index f299f442..80408667 100644 --- a/src/components/settings/UpdateCheck.tsx +++ b/src/components/settings/UpdateCheck.tsx @@ -1,8 +1,15 @@ -// Responsibility: Update check widget — idle / checking / up to date / update available / downloading / awaiting restart / failed. -import { Match, Switch } from "solid-js"; +// Responsibility: Update check widget — idle / checking / up to date / update available (with its notes in a dialog) / downloading / awaiting restart / failed. +import { Match, Show, Switch } from "solid-js"; import { t } from "@/i18n"; import { check, install, restart, updateStore } from "@/store/update.store"; import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; import { Check, Download, @@ -10,6 +17,7 @@ import { RefreshCw, TriangleAlert, } from "lucide-solid"; +import { ReleaseNotes } from "./ReleaseNotes"; export function UpdateCheck() { const percent = () => @@ -29,6 +37,32 @@ export function UpdateCheck() { version: updateStore.version ?? "", })} + {/* The notes are the release body the updater already fetched. They open in a + dialog rather than inside this card: a changelog is longer than the card and + would push the install button off the screen. */} + + + + {t("settings.about.whatsNew")} + + + + + {t("settings.about.whatsNewFor", { + version: updateStore.version ?? "", + })} + + +
+ +
+
+
+