feat: display embedded media player (#2417)

This commit is contained in:
Ayo Ayco 2023-11-07 10:57:44 +01:00 committed by GitHub
parent 0bd1209bee
commit 957f0d3b17
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 146 additions and 5 deletions

View file

@ -17,7 +17,7 @@ const emojisObject = useEmojisFallback(() => status.emojis)
const vnode = $computed(() => {
if (!status.content)
return null
const vnode = contentToVNode(status.content, {
return contentToVNode(status.content, {
emojis: emojisObject.value,
mentions: 'mentions' in status ? status.mentions : undefined,
markdown: true,
@ -25,7 +25,6 @@ const vnode = $computed(() => {
status: 'id' in status ? status : undefined,
inReplyToStatus: newer,
})
return vnode
})
</script>

View file

@ -26,9 +26,11 @@ const hasSpoilerOrSensitiveMedia = $computed(() => spoilerTextPresent || (status
const isSensitiveNonSpoiler = computed(() => status.sensitive && !status.spoilerText && !!status.mediaAttachments.length)
const hideAllMedia = computed(
() => {
return currentUser.value ? (getHideMediaByDefault(currentUser.value.account) && !!status.mediaAttachments.length) : false
return currentUser.value ? (getHideMediaByDefault(currentUser.value.account) && (!!status.mediaAttachments.length || !!status.card?.html)) : false
},
)
const embeddedMediaPreference = $(usePreferences('experimentalEmbeddedMedia'))
const allowEmbeddedMedia = $computed(() => status.card?.html && embeddedMediaPreference)
</script>
<template>
@ -56,10 +58,11 @@ const hideAllMedia = computed(
:is-preview="isPreview"
/>
<StatusPreviewCard
v-if="status.card"
v-if="status.card && !allowEmbeddedMedia"
:card="status.card"
:small-picture-only="status.mediaAttachments?.length > 0"
/>
<StatusEmbeddedMedia v-if="allowEmbeddedMedia" :status="status" />
<StatusCard
v-if="status.reblog"
:status="status.reblog" border="~ rounded"

View file

@ -0,0 +1,105 @@
<script setup lang="ts">
import type { mastodon } from 'masto'
const { status } = defineProps<{
status: mastodon.v1.Status
}>()
const vnode = $computed(() => {
if (!status.card?.html)
return null
const node = sanitizeEmbeddedIframe(status.card?.html)?.children[0]
return node ? nodeToVNode(node) : null
})
const overlayToggle = ref(true)
const card = ref(status.card)
</script>
<template>
<div v-if="card">
<div
v-if="overlayToggle"
h-80
cursor-pointer
relative
>
<div
p-3
absolute
w-full
h-full
z-100
rounded-lg
style="background: linear-gradient(black, rgba(0,0,0,0.5), transparent, transparent, rgba(0,0,0,0.20))"
>
<NuxtLink flex flex-col gap-1 hover:underline text-xs text-light font-light target="_blank" :href="card?.url">
<div flex gap-0.5>
<p flex-row line-clamp-1>
{{ card?.providerName }}<span v-if="card?.authorName"> {{ card?.authorName }}</span>
</p>
<span
flex-row
w-4 h-4
pointer-events-none
i-ri:arrow-right-up-line
/>
</div>
<p font-bold line-clamp-1 text-size-base>
{{ card?.title }}
</p>
<p line-clamp-1>
{{ $t('status.embedded_warning') }}
</p>
</NuxtLink>
<div
flex
h-50
mt-1
justify-center
flex-items-center
>
<button
absolute
bg-primary
opacity-85
rounded-full
hover:bg-primary-active
hover:opacity-95
transition-all
box-shadow-outline
@click.stop.prevent="() => overlayToggle = !overlayToggle"
>
<span
text-light
flex flex-col
gap-3
w-27 h-27
pointer-events-none
i-ri:play-circle-line
/>
</button>
</div>
</div>
<CommonBlurhash
v-if="card?.image"
:blurhash="card.blurhash"
:src="card.image"
w-full
h-full
object-cover
rounded-lg
/>
</div>
<div v-else>
<!-- this inserts the iframe -->
<component :is="vnode" v-if="vnode" rounded-lg h-80 />
</div>
</div>
</template>
<style>
iframe {
width: 100%;
height: 100%;
}
</style>

View file

@ -150,6 +150,25 @@ export function convertMastodonHTML(html: string, customEmojis: Record<string, m
return render(tree)
}
export function sanitizeEmbeddedIframe(html: string): Node {
const transforms: Transform[] = [
sanitize({
iframe: {
src: (src) => {
if (typeof src !== 'string')
return undefined
const url = new URL(src)
return url.protocol === 'https:' ? src : undefined
},
allowfullscreen: set('true'),
},
}),
]
return transformSync(parse(html), transforms)
}
export function htmlToText(html: string) {
try {
const tree = parse(html)

View file

@ -36,7 +36,7 @@ export function contentToVNode(
return h(Fragment, (tree.children as Node[] || []).map(n => treeToVNode(n)))
}
function nodeToVNode(node: Node): VNode | string | null {
export function nodeToVNode(node: Node): VNode | string | null {
if (node.type === TEXT_NODE)
return node.value

View file

@ -26,6 +26,7 @@ export interface PreferencesSettings {
experimentalVirtualScroller: boolean
experimentalGitHubCards: boolean
experimentalUserPicker: boolean
experimentalEmbeddedMedia: boolean
}
export interface UserSettings {
@ -78,6 +79,7 @@ export const DEFAULT__PREFERENCES_SETTINGS: PreferencesSettings = {
experimentalVirtualScroller: true,
experimentalGitHubCards: true,
experimentalUserPicker: true,
experimentalEmbeddedMedia: false,
}
export function getDefaultUserSettings(locales: string[]): UserSettings {

View file

@ -500,6 +500,8 @@
},
"notifications_settings": "Notifications",
"preferences": {
"embedded_media": "Embedded Media Player",
"embedded_media_description": "Display an embedded player instead of the normal preview card when expanding shared media streaming links.",
"enable_autoplay": "Enable Autoplay",
"enable_data_saving": "Enable data saving",
"enable_data_saving_description": "Save data by preventing attachments from automatically loading.",
@ -577,6 +579,7 @@
},
"boosted_by": "Boosted By",
"edited": "Edited {0}",
"embedded_warning": "Playing this may reveal your IP address to others.",
"favourited_by": "Favorited By",
"filter_hidden_phrase": "Filtered by",
"filter_show_anyway": "Show anyway",

View file

@ -117,6 +117,16 @@ const userSettings = useUserSettings()
<div i-ri-flask-line />
{{ $t('settings.preferences.title') }}
</h2>
<!-- Embedded Media -->
<SettingsToggleItem
:checked="getPreferences(userSettings, 'experimentalEmbeddedMedia')"
@click="togglePreferences('experimentalEmbeddedMedia')"
>
{{ $t('settings.preferences.embedded_media') }}
<template #description>
{{ $t('settings.preferences.embedded_media_description') }}
</template>
</SettingsToggleItem>
<SettingsToggleItem
:checked="getPreferences(userSettings, 'experimentalVirtualScroller')"
@click="togglePreferences('experimentalVirtualScroller')"