test: Ensure tests work after latest changes
All checks were successful
Queue Release Build / prepare (push) Successful in 17s
Deploy Web Apps / deploy (push) Successful in 7m20s
Queue Release Build / build-windows (push) Successful in 25m4s
Queue Release Build / build-linux (push) Successful in 33m59s
Queue Release Build / finalize (push) Successful in 41s
All checks were successful
Queue Release Build / prepare (push) Successful in 17s
Deploy Web Apps / deploy (push) Successful in 7m20s
Queue Release Build / build-windows (push) Successful in 25m4s
Queue Release Build / build-linux (push) Successful in 33m59s
Queue Release Build / finalize (push) Successful in 41s
This commit is contained in:
@@ -1107,7 +1107,7 @@ function parsePluginEntry(sourceUrl: string, sourceTitle: string, value: unknown
|
||||
githubUrl: resolveOptionalUrl(sourceUrl, readGithubUrl(value)),
|
||||
homepageUrl: resolveOptionalUrl(sourceUrl, readString(value, 'homepage', 'homepageUrl', 'website')),
|
||||
id,
|
||||
imageUrl: resolveOptionalUrl(sourceUrl, readString(value, 'image', 'imageUrl', 'icon', 'iconUrl', 'banner')),
|
||||
imageUrl: normalizeImageUrl(resolveOptionalUrl(sourceUrl, readString(value, 'image', 'imageUrl', 'icon', 'iconUrl', 'banner'))),
|
||||
installUrl: resolveOptionalUrl(sourceUrl, readString(value, 'install', 'installUrl', 'manifest', 'manifestUrl')),
|
||||
readmeUrl: resolveOptionalUrl(sourceUrl, readString(value, 'readme', 'readmeUrl')),
|
||||
scope: readPluginInstallScope(value),
|
||||
@@ -1300,6 +1300,44 @@ function normalizeOptionalSourceUrl(rawUrl: string): string | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites human-friendly GitHub URLs so the browser can load the underlying
|
||||
* binary asset. Users typically paste links copied from the GitHub web UI which
|
||||
* point at the rendered HTML preview (`github.com/<owner>/<repo>/blob/...`) or
|
||||
* the raw redirector (`github.com/<owner>/<repo>/raw/...`). Both forms must be
|
||||
* mapped to `raw.githubusercontent.com` for `<img>` tags to work.
|
||||
*/
|
||||
function normalizeImageUrl(rawUrl: string | undefined): string | undefined {
|
||||
if (!rawUrl) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let url: URL;
|
||||
|
||||
try {
|
||||
url = new URL(rawUrl);
|
||||
} catch {
|
||||
return rawUrl;
|
||||
}
|
||||
|
||||
if (url.hostname !== 'github.com' && url.hostname !== 'www.github.com') {
|
||||
return rawUrl;
|
||||
}
|
||||
|
||||
const segments = url.pathname.split('/').filter(Boolean);
|
||||
const kindIndex = segments.findIndex((segment) => segment === 'blob' || segment === 'raw');
|
||||
|
||||
if (kindIndex < 2 || kindIndex >= segments.length - 1) {
|
||||
return rawUrl;
|
||||
}
|
||||
|
||||
const owner = segments[0];
|
||||
const repo = segments[1];
|
||||
const ref = segments.slice(kindIndex + 1).join('/');
|
||||
|
||||
return `https://raw.githubusercontent.com/${owner}/${repo}/${ref}${url.search}`;
|
||||
}
|
||||
|
||||
function resolveOptionalUrl(sourceUrl: string, rawUrl?: string): string | undefined {
|
||||
if (!rawUrl) {
|
||||
return undefined;
|
||||
|
||||
@@ -257,11 +257,13 @@
|
||||
@for (plugin of filteredPlugins(); track trackPlugin($index, plugin)) {
|
||||
<article class="grid min-w-0 overflow-hidden rounded-lg border border-border bg-background sm:grid-cols-[5.5rem_minmax(0,1fr)]">
|
||||
<div class="grid min-h-24 place-items-center bg-secondary text-muted-foreground sm:min-h-full">
|
||||
@if (plugin.imageUrl) {
|
||||
@if (plugin.imageUrl && !hasBrokenImage(plugin)) {
|
||||
<img
|
||||
[src]="plugin.imageUrl"
|
||||
[alt]="plugin.title"
|
||||
(error)="hideBrokenImage($event)"
|
||||
(error)="hideBrokenImage($event, plugin)"
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
} @else {
|
||||
|
||||
@@ -155,6 +155,7 @@ export class PluginStoreComponent implements OnInit {
|
||||
readonly serverInstallOptional = signal(false);
|
||||
readonly serverInstallError = signal<string | null>(null);
|
||||
readonly serverInstallBusy = signal(false);
|
||||
readonly brokenImageKeys = signal<Set<string>>(new Set());
|
||||
|
||||
private destroyed = false;
|
||||
private readonly destroyRef = inject(DestroyRef);
|
||||
@@ -530,12 +531,26 @@ export class PluginStoreComponent implements OnInit {
|
||||
return `${plugin.sourceUrl}:${plugin.id}`;
|
||||
}
|
||||
|
||||
hideBrokenImage(event: Event): void {
|
||||
hideBrokenImage(event: Event, plugin: PluginStoreEntry): void {
|
||||
const image = event.target as HTMLImageElement | null;
|
||||
|
||||
if (image) {
|
||||
image.hidden = true;
|
||||
}
|
||||
|
||||
const key = this.imageKey(plugin);
|
||||
const next = new Set(this.brokenImageKeys());
|
||||
|
||||
next.add(key);
|
||||
this.brokenImageKeys.set(next);
|
||||
}
|
||||
|
||||
hasBrokenImage(plugin: PluginStoreEntry): boolean {
|
||||
return this.brokenImageKeys().has(this.imageKey(plugin));
|
||||
}
|
||||
|
||||
private imageKey(plugin: PluginStoreEntry): string {
|
||||
return `${plugin.sourceUrl}:${plugin.id}:${plugin.imageUrl ?? ''}`;
|
||||
}
|
||||
|
||||
trackServer(index: number, server: Room): string {
|
||||
|
||||
Reference in New Issue
Block a user