From 088f0fe0945d3fc74ed413d05964b83fb0c180c8 Mon Sep 17 00:00:00 2001 From: Marcel Hellkamp Date: Thu, 6 Jun 2024 11:22:05 +0200 Subject: [PATCH] Fallback to card preview image on posts without media. Peertube posts have no media attachments but a preview card that shows an embedded video player in an iframe. Unfortunately this does not really support the small form-factor of a fediwall card and looks kinda ugly. Autoplay also won't work. So we just display the card preview image (if present) as a link you can click on to view the full video. --- src/components/Card.vue | 9 +++++--- src/sources.ts | 47 ++++++++++++++++++++++++++++------------- src/types.ts | 28 +++++++++++++++++++++--- src/utils.ts | 4 ++++ 4 files changed, 67 insertions(+), 21 deletions(-) diff --git a/src/components/Card.vue b/src/components/Card.vue index f426ed6..797ceed 100644 --- a/src/components/Card.vue +++ b/src/components/Card.vue @@ -43,12 +43,15 @@ const onMediaLoad = inject('fixLayout', () => undefined)
-
- -

{ } // Skip posts that would show up empty - if (!cfg.showText && !status.media_attachments?.length) return false; + if (!cfg.showText && findMedia(status).length == 0) return false; if (!cfg.showMedia && !status.content.trim()) return false; // Accept anything else return true; } +function findMedia(status: MastodonStatus) { + const media: PostMedia[] = [] + + status.media_attachments?.map((m): PostMedia | undefined => { + const url = m.url; + const alt = m.description ?? undefined + const preview = m.preview_url ?? undefined + switch (m.type) { + case "image": + return { type: "image", url, href:url, preview, alt } + case "video": + case "gifv": + return { type: "video", url, href:url, preview, alt } + case "audio": + case "unknown": + return + } + }).filter(m=>!!m).forEach(m=>media.push(m)) + + // Fall back to preview card images if no media is attached (e.g. for peertube posts) + if(media.length == 0 && status.card) { + const card = status.card + if(notBlank(card.image) && notBlank(card.url)) + media.push({type:"card", url: card.url, preview:card.image, alt: status.card.description}) + } + + return media +} + /** * Convert a mastodon status object to a Post. */ @@ -282,18 +310,7 @@ const statusToWallPost = (cfg: Config, status: MastodonStatus): Post => { const profile = status.account.acct const content = replaceEmojis(status.content, status.emojis) - const media = status.media_attachments?.map((m): PostMedia | undefined => { - switch (m.type) { - case "image": - return { type: "image", url: m.url, preview: m.preview_url, alt: m.description ?? undefined } - case "video": - case "gifv": - return { type: "video", url: m.url, preview: m.preview_url, alt: m.description ?? undefined } - case "audio": - case "unknown": - return - } - }).filter((m): m is PostMedia => m !== undefined) + const media = findMedia(status) return { id: status.uri, diff --git a/src/types.ts b/src/types.ts index a0d083a..89b76c7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -45,10 +45,11 @@ export type Post = { }; export type PostMedia = { - type: "image" | "video" - url: string, - preview: string + type: "image" | "video" | "card" + url: string + preview?: string alt?: string + size?: [number,number] } @@ -67,6 +68,7 @@ export type MastodonStatus = { in_reply_to_id?: string | null; language?: string | null; media_attachments: Array; + card?: MastodonCard, reblog?: MastodonStatus | null; sensitive: boolean; spoiler_text?: string | null; @@ -114,3 +116,23 @@ export type MastodonMediaAttachment = { preview_url: string; url: string; } + +export type MastodonCard = { + url: string; + title: string; + description: string; + language: string; + type: string; + author_name: string; + author_url: string; + provider_name: string; + provider_url: string; + html: string; + width: number; + height: number; + image: string; + image_description: string; + embed_url: string; + blurhash: string | null; + published_at?: any +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index ec5e24a..95f8900 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -9,6 +9,10 @@ export function isString(test: any) { return typeof test === 'string' || test instanceof String } +export function notBlank(test?: string) { + return test && test.trim().length > 0 +} + export function arrayUnique(array: T[]) { return array.filter((v, i, a) => a.indexOf(v) === i) }