feat(themes): persist publisher id in theme manifests

Publishing now stamps the signed-in EZPPFarm user ID into theme.json and saves it before packaging, so installed theme cards can resolve avatars by stable user ID. Older manifests keep the author-name fallback.
This commit is contained in:
HorizonCode committed 2026-11-03 23:45:00 +01:00
1 parent 5eea428802
commit 11aefee7ed
4 files changed
+35 -6

No files matched your search

@@ -3343,7 +3343,8 @@
async function openPublishDialog() { async function openPublishDialog() {
if (exporting || saving || publishing) return; if (exporting || saving || publishing) return;
if (!$currentUser) { const user = $currentUser;
if (!user) {
sileo.error({ sileo.error({
title: 'Authentication Required', title: 'Authentication Required',
description: description:
@@ -3361,8 +3362,12 @@
return; return;
} }
if (dirty) { const authorIdChanged = definition.authorId !== user.id;
await saveTheme({ notify: false }); if (authorIdChanged) definition.authorId = user.id;
if (dirty || authorIdChanged) {
const saved = await saveTheme({ notify: false });
if (!saved) return;
} }
publishThemeId = definition.workshopId ?? ''; publishThemeId = definition.workshopId ?? '';
+8
View File
@@ -646,6 +646,8 @@ export type ThemeV2Definition = {
version: string; version: string;
apiVersion: '2.0'; apiVersion: '2.0';
author: string; author: string;
/** EZPPFarm user ID of the publisher, written when the theme is published. */
authorId?: number;
description?: string; description?: string;
/** Workshop discovery tags for this theme. */ /** Workshop discovery tags for this theme. */
tags?: string[]; tags?: string[];
@@ -755,6 +757,11 @@ const normalizeWorkshopId = (raw: unknown): string | undefined => {
return WORKSHOP_ID_PATTERN.test(id) ? id : undefined; return WORKSHOP_ID_PATTERN.test(id) ? id : undefined;
}; };
const normalizeAuthorId = (raw: unknown): number | undefined => {
if (typeof raw !== 'number' || !Number.isSafeInteger(raw) || raw <= 0) return undefined;
return raw;
};
const clampInteger = (value: unknown, fallback: number, min: number, max: number) => const clampInteger = (value: unknown, fallback: number, min: number, max: number) =>
Math.round(clamp(value, fallback, min, max)); Math.round(clamp(value, fallback, min, max));
@@ -1913,6 +1920,7 @@ export const normalizeThemeV2 = (raw: Partial<ThemeV2Definition>): ThemeV2Defini
version: text(raw.version, '1.0.0'), version: text(raw.version, '1.0.0'),
apiVersion: '2.0', apiVersion: '2.0',
author: text(raw.author, 'Unknown'), author: text(raw.author, 'Unknown'),
authorId: normalizeAuthorId(raw.authorId),
description: text(raw.description), description: text(raw.description),
tags: normalizeThemeTags(raw.tags), tags: normalizeThemeTags(raw.tags),
workshopId: normalizeWorkshopId(raw.workshopId), workshopId: normalizeWorkshopId(raw.workshopId),
+10
View File
@@ -16,6 +16,8 @@ export type Theme = {
version: string; version: string;
apiVersion: string; apiVersion: string;
author: string; author: string;
/** EZPPFarm user ID stored in the manifest when the theme is published. */
authorId?: number;
description: string; description: string;
nsfw: boolean; nsfw: boolean;
scriptUrl: string; scriptUrl: string;
@@ -44,6 +46,7 @@ export type ThemeInfo = {
apiVersion?: string; apiVersion?: string;
workshopId?: string; workshopId?: string;
author?: string; author?: string;
authorId?: number;
description?: string; description?: string;
nsfw?: boolean; nsfw?: boolean;
entry?: string; entry?: string;
@@ -173,6 +176,12 @@ const themeFromFolder = async (
version: parsed.version ?? '1.0.0', version: parsed.version ?? '1.0.0',
apiVersion, apiVersion,
author: parsed.author ?? 'Unknown', author: parsed.author ?? 'Unknown',
authorId:
typeof parsed.authorId === 'number' &&
Number.isSafeInteger(parsed.authorId) &&
parsed.authorId > 0
? parsed.authorId
: undefined,
description: parsed.description ?? '', description: parsed.description ?? '',
nsfw: parsed.nsfw === true, nsfw: parsed.nsfw === true,
scriptUrl, scriptUrl,
@@ -523,6 +532,7 @@ export const reloadThemes = async () => {
preview: theme.preview, preview: theme.preview,
version: theme.version, version: theme.version,
author: theme.author, author: theme.author,
authorId: theme.authorId,
apiVersion: theme.apiVersion, apiVersion: theme.apiVersion,
description: theme.description, description: theme.description,
nsfw: theme.nsfw, nsfw: theme.nsfw,
+9 -3
View File
@@ -422,6 +422,14 @@
return ownedWorkshopThemeNames.includes(theme.name.trim().toLowerCase()); return ownedWorkshopThemeNames.includes(theme.name.trim().toLowerCase());
}; };
const themeAuthorAvatarUrl = (theme: Theme) => {
if (theme.authorId && theme.authorId > 0) {
return `https://a.ez-pp.farm/${theme.authorId}`;
}
const author = theme.author.trim();
return author ? `https://a.ez-pp.farm/${encodeURIComponent(author)}` : undefined;
};
onMount(() => { onMount(() => {
let loadedUserId: number | undefined; let loadedUserId: number | undefined;
return currentUser.subscribe((user) => { return currentUser.subscribe((user) => {
@@ -4073,9 +4081,7 @@
class="size-5 shrink-0 border border-white/20 bg-theme-800" class="size-5 shrink-0 border border-white/20 bg-theme-800"
> >
<Avatar.Image <Avatar.Image
src={theme.author.trim() src={themeAuthorAvatarUrl(theme)}
? `https://a.ez-pp.farm/${encodeURIComponent(theme.author.trim())}`
: undefined}
alt={theme.author || 'Unknown author'} alt={theme.author || 'Unknown author'}
class="object-cover" class="object-cover"
/> />