diff --git a/frontend/src/ts/components/common/AnimatedModal.tsx b/frontend/src/ts/components/common/AnimatedModal.tsx index 36445e20a123..1fadcdd9d82e 100644 --- a/frontend/src/ts/components/common/AnimatedModal.tsx +++ b/frontend/src/ts/components/common/AnimatedModal.tsx @@ -42,6 +42,7 @@ type AnimatedModalProps = ParentProps<{ afterHide?: () => void | Promise; onEscape?: (e: KeyboardEvent) => void; onBackdropClick?: (e: MouseEvent) => void; + onScrollEnd?: (e: Event) => void; title?: string; modalClass?: string; @@ -280,6 +281,8 @@ export function AnimatedModal(props: AnimatedModalProps): JSXElement { props.modalClass, )} ref={modalRef} + // oxlint-disable-next-line react/no-unknown-property + onScrollEnd={(e) => props.onScrollEnd?.(e)} >
{props.title}
diff --git a/frontend/src/ts/components/common/Button.tsx b/frontend/src/ts/components/common/Button.tsx index acd421c94a66..31e5713a040f 100644 --- a/frontend/src/ts/components/common/Button.tsx +++ b/frontend/src/ts/components/common/Button.tsx @@ -15,11 +15,13 @@ type ButtonProps = BaseProps & { onClick: () => void; href?: never; sameTarget?: true; + disabled?: boolean; }; type AnchorProps = BaseProps & { href: string; onClick?: never; + disabled?: never; }; export function Button(props: ButtonProps | AnchorProps): JSXElement { @@ -61,6 +63,7 @@ export function Button(props: ButtonProps | AnchorProps): JSXElement { type="button" classList={getClassList()} onClick={() => props.onClick?.()} + disabled={props.disabled ?? false} > {content} diff --git a/frontend/src/ts/components/modals/VersionHistoryModal.tsx b/frontend/src/ts/components/modals/VersionHistoryModal.tsx index 85556adceb27..03eed6227ef1 100644 --- a/frontend/src/ts/components/modals/VersionHistoryModal.tsx +++ b/frontend/src/ts/components/modals/VersionHistoryModal.tsx @@ -1,58 +1,67 @@ -import { format } from "date-fns/format"; -import { JSXElement, createResource, For } from "solid-js"; +import { useInfiniteQuery } from "@tanstack/solid-query"; +import { For, JSXElement } from "solid-js"; +import { getVersionHistoryQueryOptions } from "../../queries/public"; import { isModalOpen } from "../../stores/modals"; -import { getReleasesFromGitHub } from "../../utils/json-data"; import { AnimatedModal } from "../common/AnimatedModal"; import AsyncContent from "../common/AsyncContent"; +import { Button } from "../common/Button"; export function VersionHistoryModal(): JSXElement { const isOpen = (): boolean => isModalOpen("VersionHistory"); - const [releases] = createResource(isOpen, async (open) => { - if (!open) return null; - const releases = await getReleasesFromGitHub(); - const data = []; - for (const release of releases) { - if (release.draft || release.prerelease) continue; - let body = release.body; + const releases = useInfiniteQuery(() => ({ + ...getVersionHistoryQueryOptions(), + enabled: isOpen(), + })); - body = body.replace(/\r\n/g, "
"); - //replace ### title with h3 title h3 - body = body.replace( - /### (.*?)
/g, - '

$1

', - ); - body = body.replace(/<\/h3>
/gi, ""); - //remove - at the start of a line - body = body.replace(/^- /gm, ""); - //replace **bold** with bold - body = body.replace(/\*\*(.*?)\*\*/g, "$1"); - //replace links with a tags - body = body.replace( - /\[(.*?)\]\((.*?)\)/g, - '$1', - ); + const scrollToLoadMore = (e: Event): void => { + const element = e.target as HTMLElement; - data.push({ - name: release.name, - publishedAt: format(new Date(release.published_at), "dd MMM yyyy"), - bodyHTML: body, - }); + if ( + element.scrollHeight - element.scrollTop - element.clientHeight < 10 && + releases.hasNextPage && + !releases.isLoading + ) { + void releases.fetchNextPage(); } - return data; - }); + }; return ( - + {(data) => ( -
- {(release) => } -
+ <> +
+ it.releases)}> + {(release) => } + +
+ +