Fall back to an inline link when a link preview fails

This commit is contained in:
Coracle-Bot 2026-09-08 22:36:23 +00:00
parent 78fe1e11f4
commit 21aa5b165e
3 changed files with 49 additions and 47 deletions

View file

@ -940,8 +940,7 @@ Acceptance:
- A standalone url whose preview resolves shows a card with title, description, - A standalone url whose preview resolves shows a card with title, description,
and image after a brief loading state. and image after a brief loading state.
- A standalone url with nothing usable shows a card explaining the preview - A standalone url with nothing usable falls back to the compact inline link.
couldn't be loaded, naming the url.
- The same url embedded in a sentence renders as a compact inline link with no - The same url embedded in a sentence renders as a compact inline link with no
card. card.

View file

@ -231,7 +231,7 @@ test("US-063 preview a shared link", async ({seed, as}) => {
const previewImage = 'img[src="https://images.test/preview.png"]' const previewImage = 'img[src="https://images.test/preview.png"]'
await expect(card.locator(".spinner")).toBeVisible() await expect(card.locator(".spinner")).toBeVisible()
await expect(failed.getByText(`Unable to load a preview for ${unreadable}`)).toBeVisible() await expect(failed.getByRole("link", {name: "example.test/unreadable"})).toBeVisible()
servePreview() servePreview()

View file

@ -52,6 +52,11 @@
Boolean(url.match(/\.(mp3|m4a|wav|ogg|oga|opus|flac)$/)) || Boolean(url.match(/\.(mp3|m4a|wav|ogg|oga|opus|flac)$/)) ||
AUDIO_CONTENT_TYPES.includes(fileType) AUDIO_CONTENT_TYPES.includes(fileType)
const isVideo = Boolean(url.match(/\.(mov|webm|mp4)$/)) || VIDEO_CONTENT_TYPES.includes(fileType)
const isImage =
Boolean(url.match(/\.(jpe?g|png|gif|webp)$/)) || IMAGE_CONTENT_TYPES.includes(fileType)
const getVideoPoster = (videoUrl: string): string | undefined => { const getVideoPoster = (videoUrl: string): string | undefined => {
if (Capacitor.getPlatform() === "android" && THUMBNAIL_URL) { if (Capacitor.getPlatform() === "android" && THUMBNAIL_URL) {
return `${THUMBNAIL_URL}/thumbnail?url=${encodeURIComponent(videoUrl)}` return `${THUMBNAIL_URL}/thumbnail?url=${encodeURIComponent(videoUrl)}`
@ -71,9 +76,8 @@
<ContentLinkUrl {url} class="link-content whitespace-nowrap" /> <ContentLinkUrl {url} class="link-content whitespace-nowrap" />
{:else if isAudio} {:else if isAudio}
<audio controls src={url} preload="metadata" class="my-2 w-full max-w-xl"></audio> <audio controls src={url} preload="metadata" class="my-2 w-full max-w-xl"></audio>
{:else} {:else if isVideo}
<Link {external} {href} class="my-2 block"> <Link {external} {href} class="my-2 block">
{#if url.match(/\.(mov|webm|mp4)$/) || VIDEO_CONTENT_TYPES.includes(fileType)}
<video <video
controls controls
src={url} src={url}
@ -82,16 +86,20 @@
class="max-h-96 rounded-2xl object-contain object-center"> class="max-h-96 rounded-2xl object-contain object-center">
<track kind="captions" /> <track kind="captions" />
</video> </video>
{:else if url.match(/\.(jpe?g|png|gif|webp)$/) || IMAGE_CONTENT_TYPES.includes(fileType)} </Link>
{:else if isImage}
<Link {external} {href} class="my-2 block">
<button type="button" onclick={stopPropagation(preventDefault(expand))}> <button type="button" onclick={stopPropagation(preventDefault(expand))}>
<ContentLinkBlockImage {value} {event} class="m-auto max-h-96 rounded-2xl" /> <ContentLinkBlockImage {value} {event} class="m-auto max-h-96 rounded-2xl" />
</button> </button>
</Link>
{:else} {:else}
{#await loadPreview(url)} {#await loadPreview(url)}
<div class="flex justify-center items-center my-12 w-full"> <div class="flex justify-center items-center my-12 w-full">
<Spinner /> <Spinner />
</div> </div>
{:then preview} {:then preview}
<Link {external} {href} class="my-2 block">
<div <div
class="border border-solid flex max-w-xl flex-col overflow-hidden leading-normal rounded-2xl" class="border border-solid flex max-w-xl flex-col overflow-hidden leading-normal rounded-2xl"
style="border-color: var(--line)"> style="border-color: var(--line)">
@ -108,13 +116,8 @@
<p>{ellipsize(preview.description, 140)}</p> <p>{ellipsize(preview.description, 140)}</p>
</div> </div>
</div> </div>
</Link>
{:catch} {:catch}
<p <ContentLinkUrl {url} class="link-content whitespace-nowrap" />
class="border border-solid p-12 text-center leading-normal rounded-2xl"
style="border-color: var(--line)">
Unable to load a preview for {url}
</p>
{/await} {/await}
{/if} {/if}
</Link>
{/if}