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.
This commit is contained in:
Marcel Hellkamp 2024-06-06 11:22:05 +02:00
parent 4d77c6cf8a
commit 088f0fe094
4 changed files with 67 additions and 21 deletions

View File

@ -43,12 +43,15 @@ const onMediaLoad = inject('fixLayout', () => undefined)
<slot name="topleft"></slot>
</div>
<div class="card-body">
<div v-if="config.showMedia" class="wall-media mb-3" ref="mediaElement">
<img v-if="media?.type === 'image'" :src="media.url" :alt="media.alt" :title="media.alt" @load="onMediaLoad">
<video v-else-if="media?.type === 'video'" muted loop :autoplay="playVideo"
<div v-if="config.showMedia && media" class="wall-media mb-3" ref="mediaElement">
<img v-if="media.type === 'image'" :src="media.url" :alt="media.alt" :title="media.alt" @load="onMediaLoad">
<video v-else-if="media.type === 'video'" muted loop :autoplay="playVideo"
:poster="media.preview" :alt="media.alt" :title="media.alt" @loadedmetadata="onMediaLoad">
<source v-if="playVideo" :src="media.url">
</video>
<a v-else-if="media.type==='card'" :href="media.url" target="_blank">
<img :src="media.preview" :alt="media.alt" :title="media.alt" @load="onMediaLoad">
</a>
</div>
<p v-if="config.showText" class="card-text" v-dompurify-html="post.content"></p>
<p class="card-text text-end text-break"><a :href="post.url" target="_blank" :title="post.date.toLocaleString()"

View File

@ -1,7 +1,6 @@
import type { Config, MastodonAccount, MastodonStatus, Post, PostMedia } from "@/types";
import { regexEscape } from "@/utils";
import { notBlank, regexEscape } from "@/utils";
import { replaceInText } from '@/utils'
import type { faTags } from "@fortawesome/free-solid-svg-icons";
import DOMPurify from 'dompurify'
/**
@ -236,13 +235,42 @@ const filterStatus = (cfg: Config, status: MastodonStatus) => {
}
// 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,

View File

@ -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<MastodonMediaAttachment>;
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
}

View File

@ -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<T>(array: T[]) {
return array.filter((v, i, a) => a.indexOf(v) === i)
}