forked from extern/fediwall
Add support for video media attachments.
Videos are only downloaded if autoplay is enabled and the video is visible on screen. Disabling autoplay shows preview images instead.
This commit is contained in:
parent
2c6d99e8ec
commit
d7fde80d06
@ -197,11 +197,13 @@ const privacyLink = computed(() => {
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div v-if="filteredPosts.length === 0 && updateInProgress">Loading first posts ...</div>
|
||||
<div v-if="config === undefined">Initialiting ...</div>
|
||||
<div v-else-if="filteredPosts.length === 0 && updateInProgress">Loading first posts ...</div>
|
||||
<div v-else-if="filteredPosts.length === 0">Nothing there yet ...</div>
|
||||
<div v-else v-masonry transition-duration="1s" item-selector=".wall-item" percent-position="true" id="wall">
|
||||
<Card v-masonry-tile class="wall-item secret-hover" v-for="post in filteredPosts" :key="post.id"
|
||||
:post="post">
|
||||
:post="post" :config="config">
|
||||
|
||||
<template v-slot:topleft>
|
||||
<div class="dropdown secret">
|
||||
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="dropdown"
|
||||
@ -216,6 +218,7 @@ const privacyLink = computed(() => {
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
|
@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { useIntervalFn } from '@vueuse/core'
|
||||
import { ref } from 'vue';
|
||||
import { useElementVisibility, useIntervalFn } from '@vueuse/core'
|
||||
import { computed, ref } from 'vue';
|
||||
import moment from 'moment'
|
||||
import { type Post } from '@/types';
|
||||
import { type Config, type Post } from '@/types';
|
||||
|
||||
const props = defineProps<{
|
||||
post: Post
|
||||
config: Config,
|
||||
post: Post,
|
||||
}>()
|
||||
|
||||
const timeAgo = ref(moment(props.post.date).fromNow())
|
||||
@ -14,6 +15,16 @@ useIntervalFn(() => {
|
||||
timeAgo.value = moment(props.post.date).fromNow()
|
||||
}, 1000)
|
||||
|
||||
const media = computed(() => {
|
||||
return props.post.media[0]
|
||||
})
|
||||
|
||||
const videoElement = ref(null)
|
||||
const videoIsVisible = useElementVisibility(videoElement)
|
||||
const playVideo = computed(() => {
|
||||
return media.value?.type === "video" && props.config.playVideos && videoIsVisible.value
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -29,8 +40,14 @@ useIntervalFn(() => {
|
||||
<slot name="topleft"></slot>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<img v-if="post.media" :src="post.media" class="wall-media mb-3">
|
||||
<p class="card-text" v-dompurify-html="post.content"></p>
|
||||
<div v-if="config.showMedia" class="wall-media mb-3">
|
||||
<img v-if="media?.type === 'image'" :src="media.url" :alt="media.alt">
|
||||
<video v-else-if="media?.type === 'video'" ref="videoElement"
|
||||
muted loop :autoplay="playVideo" :poster="media.preview" :alt="media.alt" >
|
||||
<source v-if="playVideo" :src="media.url">
|
||||
</video>
|
||||
</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"
|
||||
alt="${post.date}" class="text-decoration-none text-muted"><small>{{ timeAgo }}</small></a></p>
|
||||
</div>
|
||||
@ -56,11 +73,17 @@ useIntervalFn(() => {
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
.wall-item .wall-media {
|
||||
.wall-media {
|
||||
}
|
||||
|
||||
.wall-media img, .wall-media video {
|
||||
width: 100%;
|
||||
max-height: 1wh;
|
||||
object-fit: cover;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
||||
.wall-item .invisible {
|
||||
font-size: 0 !important;
|
||||
line-height: 0 !important;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { Config, MastodonAccount, MastodonStatus, Post } from "@/types";
|
||||
import type { Config, MastodonAccount, MastodonStatus, Post, PostMedia } from "@/types";
|
||||
import { regexEscape } from "@/utils";
|
||||
|
||||
|
||||
@ -211,10 +211,18 @@ const statusToWallPost = (status: MastodonStatus): Post => {
|
||||
if (status.reblog)
|
||||
status = status.reblog
|
||||
|
||||
let media;
|
||||
const image = status.media_attachments?.find((m: any) => m.type == "image")
|
||||
if (image)
|
||||
media = image.url
|
||||
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)
|
||||
|
||||
return {
|
||||
id: status.uri,
|
||||
|
12
src/types.ts
12
src/types.ts
@ -38,10 +38,18 @@ export type Post = {
|
||||
url?: string;
|
||||
};
|
||||
|
||||
media?: string;
|
||||
media: Array<PostMedia>;
|
||||
|
||||
pinned?: boolean;
|
||||
};
|
||||
|
||||
export type PostMedia = {
|
||||
type: "image" | "video"
|
||||
url: string,
|
||||
preview: string
|
||||
alt?: string
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mastodon types. We only model what is important for us
|
||||
@ -98,7 +106,7 @@ export type MastodonTag = {
|
||||
|
||||
export type MastodonMediaAttachment = {
|
||||
id: string;
|
||||
type: 'audio' | 'video' | 'gifv' | 'unknown';
|
||||
type: 'image' | 'audio' | 'video' | 'gifv' | 'unknown';
|
||||
blurhash?: string | null;
|
||||
description?: string | null;
|
||||
preview_url: string;
|
||||
|
Loading…
Reference in New Issue
Block a user