mirror of
https://github.com/HabiRabbu/Musicseerr
synced 2026-04-21 13:37:27 +00:00
Update whats new modal logic
This commit is contained in:
parent
3cef59f257
commit
2032f8385c
2 changed files with 72 additions and 45 deletions
|
|
@ -16,11 +16,28 @@
|
|||
const currentVersion = $derived(versionQuery.data?.version ?? null);
|
||||
const buildDate = $derived(versionQuery.data?.build_date ?? null);
|
||||
const isDev = $derived(currentVersion === 'dev' || currentVersion === 'hosting-local');
|
||||
const currentRelease = $derived(
|
||||
releaseHistoryQuery.data?.find((r) => r.tag_name === currentVersion) ??
|
||||
(isDev ? releaseHistoryQuery.data?.[0] : null) ??
|
||||
null
|
||||
);
|
||||
|
||||
function getMinorPrefix(tag: string): string | null {
|
||||
const m = tag.replace(/^v/, '').match(/^(\d+\.\d+)\./);
|
||||
return m ? m[1] : null;
|
||||
}
|
||||
|
||||
// Collect all releases sharing the same minor version (e.g. v1.3.0, v1.3.1, …)
|
||||
const minorReleases = $derived.by(() => {
|
||||
const releases = releaseHistoryQuery.data;
|
||||
if (!releases || releases.length === 0) return [];
|
||||
|
||||
const versionToMatch = isDev ? releases[0].tag_name : currentVersion;
|
||||
if (!versionToMatch) return [];
|
||||
|
||||
const prefix = getMinorPrefix(versionToMatch);
|
||||
if (!prefix) {
|
||||
const exact = releases.find((r) => r.tag_name === versionToMatch);
|
||||
return exact ? [exact] : [];
|
||||
}
|
||||
|
||||
return releases.filter((r) => getMinorPrefix(r.tag_name) === prefix);
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
updateAvailable = updateCheckQuery.data?.update_available ?? false;
|
||||
|
|
@ -31,10 +48,4 @@
|
|||
updateAvailable={updateCheckQuery.data?.update_available ?? false}
|
||||
latestVersion={updateCheckQuery.data?.latest_version ?? null}
|
||||
/>
|
||||
<WhatsNewModal
|
||||
{currentVersion}
|
||||
{buildDate}
|
||||
releaseTag={currentRelease?.tag_name ?? null}
|
||||
releaseBody={currentRelease?.body ?? null}
|
||||
releaseName={currentRelease?.name ?? null}
|
||||
/>
|
||||
<WhatsNewModal {currentVersion} {buildDate} releases={minorReleases} />
|
||||
|
|
|
|||
|
|
@ -3,43 +3,53 @@
|
|||
import { isWhatsNewDismissed, dismissWhatsNew } from '$lib/stores/version.svelte';
|
||||
import { renderMarkdown } from '$lib/utils/markdown';
|
||||
import { X, Sparkles, ExternalLink } from 'lucide-svelte';
|
||||
import type { GitHubRelease } from '$lib/queries/VersionQuery.svelte';
|
||||
|
||||
interface Props {
|
||||
currentVersion: string | null;
|
||||
buildDate: string | null;
|
||||
releaseTag: string | null;
|
||||
releaseBody: string | null;
|
||||
releaseName: string | null;
|
||||
releases: GitHubRelease[];
|
||||
}
|
||||
let { currentVersion, buildDate, releaseTag, releaseBody, releaseName }: Props = $props();
|
||||
let { currentVersion, buildDate, releases }: Props = $props();
|
||||
|
||||
let dialogEl: HTMLDialogElement | undefined = $state();
|
||||
let renderedBody = $state('');
|
||||
let renderedSections: { tag: string; name: string | null; html: string }[] = $state([]);
|
||||
|
||||
const isDev = $derived(currentVersion === 'dev' || currentVersion === 'hosting-local');
|
||||
const latestRelease = $derived(releases.length > 0 ? releases[0] : null);
|
||||
|
||||
// In dev: key dismissal to build_date so modal shows once per rebuild, not every refresh
|
||||
// In prod: key to release tag so modal shows once per new version
|
||||
const dismissKey = $derived(isDev ? (buildDate ?? 'dev') : (releaseTag ?? currentVersion));
|
||||
// In prod: key to latest release tag so modal re-shows when a new patch lands
|
||||
const dismissKey = $derived(
|
||||
isDev ? (buildDate ?? 'dev') : (latestRelease?.tag_name ?? currentVersion)
|
||||
);
|
||||
|
||||
const hasContent = $derived(releases.some((r) => r.body && r.body.trim().length > 0));
|
||||
|
||||
const shouldShow = $derived(
|
||||
currentVersion !== null &&
|
||||
dismissKey !== null &&
|
||||
releaseBody !== null &&
|
||||
releaseBody.trim().length > 0 &&
|
||||
!isWhatsNewDismissed(dismissKey)
|
||||
currentVersion !== null && dismissKey !== null && hasContent && !isWhatsNewDismissed(dismissKey)
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (releaseBody && releaseBody.trim()) {
|
||||
renderMarkdown(releaseBody)
|
||||
.then((html) => {
|
||||
renderedBody = html;
|
||||
})
|
||||
.catch(() => {
|
||||
renderedBody = '';
|
||||
});
|
||||
const withContent = releases.filter((r) => r.body && r.body.trim());
|
||||
if (withContent.length === 0) {
|
||||
renderedSections = [];
|
||||
return;
|
||||
}
|
||||
|
||||
Promise.all(
|
||||
withContent.map(async (r) => ({
|
||||
tag: r.tag_name,
|
||||
name: r.name,
|
||||
html: await renderMarkdown(r.body!)
|
||||
}))
|
||||
)
|
||||
.then((sections) => {
|
||||
renderedSections = sections;
|
||||
})
|
||||
.catch(() => {
|
||||
renderedSections = [];
|
||||
});
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
|
|
@ -102,22 +112,28 @@
|
|||
|
||||
<div class="divider my-0 opacity-10"></div>
|
||||
|
||||
{#if releaseName}
|
||||
<p class="text-base-content mt-4 mb-4 text-sm font-semibold border-l-2 border-accent/50 pl-3">
|
||||
{releaseName}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
{#if renderedBody}
|
||||
{#if renderedSections.length > 0}
|
||||
<div
|
||||
class="whats-new-content release-notes-prose prose prose-sm max-h-[55vh] max-w-none text-base-content/75 overflow-y-auto rounded-lg border border-base-content/5 bg-base-100/50 p-4 {releaseName
|
||||
? ''
|
||||
: 'mt-4'}"
|
||||
class="whats-new-content release-notes-prose max-h-[55vh] max-w-none overflow-y-auto rounded-lg border border-base-content/5 bg-base-100/50 p-4 mt-4"
|
||||
>
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -- sanitized via DOMPurify -->
|
||||
{@html renderedBody}
|
||||
{#each renderedSections as section, i}
|
||||
{#if i > 0}
|
||||
<div class="divider my-4 opacity-20"></div>
|
||||
{/if}
|
||||
<p
|
||||
class="text-base-content text-sm font-semibold border-l-2 border-accent/50 pl-3 {i > 0
|
||||
? ''
|
||||
: 'mt-0'} mb-3"
|
||||
>
|
||||
{section.name ?? section.tag}
|
||||
</p>
|
||||
<div class="prose prose-sm max-w-none text-base-content/75">
|
||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -- sanitized via DOMPurify -->
|
||||
{@html section.html}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
{:else if hasContent}
|
||||
<div class="flex justify-center py-12">
|
||||
<span class="loading loading-spinner loading-md text-accent/60"></span>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue