Skip to content

Commit

Permalink
Fallback to card preview image on posts without media.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
defnull committed Jun 6, 2024
1 parent 4d77c6c commit 088f0fe
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 21 deletions.
9 changes: 6 additions & 3 deletions src/components/Card.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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()"
Expand Down
47 changes: 32 additions & 15 deletions src/sources.ts
Original file line number Diff line number Diff line change
@@ -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'

/**
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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,
Expand Down
28 changes: 25 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}


Expand All @@ -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;
Expand Down Expand Up @@ -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
}
4 changes: 4 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down

0 comments on commit 088f0fe

Please sign in to comment.