forked from Mirrors/elk
feat: display embedded media player (#2417)
This commit is contained in:
parent
0bd1209bee
commit
957f0d3b17
8 changed files with 146 additions and 5 deletions
|
@ -17,7 +17,7 @@ const emojisObject = useEmojisFallback(() => status.emojis)
|
||||||
const vnode = $computed(() => {
|
const vnode = $computed(() => {
|
||||||
if (!status.content)
|
if (!status.content)
|
||||||
return null
|
return null
|
||||||
const vnode = contentToVNode(status.content, {
|
return contentToVNode(status.content, {
|
||||||
emojis: emojisObject.value,
|
emojis: emojisObject.value,
|
||||||
mentions: 'mentions' in status ? status.mentions : undefined,
|
mentions: 'mentions' in status ? status.mentions : undefined,
|
||||||
markdown: true,
|
markdown: true,
|
||||||
|
@ -25,7 +25,6 @@ const vnode = $computed(() => {
|
||||||
status: 'id' in status ? status : undefined,
|
status: 'id' in status ? status : undefined,
|
||||||
inReplyToStatus: newer,
|
inReplyToStatus: newer,
|
||||||
})
|
})
|
||||||
return vnode
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -26,9 +26,11 @@ const hasSpoilerOrSensitiveMedia = $computed(() => spoilerTextPresent || (status
|
||||||
const isSensitiveNonSpoiler = computed(() => status.sensitive && !status.spoilerText && !!status.mediaAttachments.length)
|
const isSensitiveNonSpoiler = computed(() => status.sensitive && !status.spoilerText && !!status.mediaAttachments.length)
|
||||||
const hideAllMedia = computed(
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -56,10 +58,11 @@ const hideAllMedia = computed(
|
||||||
:is-preview="isPreview"
|
:is-preview="isPreview"
|
||||||
/>
|
/>
|
||||||
<StatusPreviewCard
|
<StatusPreviewCard
|
||||||
v-if="status.card"
|
v-if="status.card && !allowEmbeddedMedia"
|
||||||
:card="status.card"
|
:card="status.card"
|
||||||
:small-picture-only="status.mediaAttachments?.length > 0"
|
:small-picture-only="status.mediaAttachments?.length > 0"
|
||||||
/>
|
/>
|
||||||
|
<StatusEmbeddedMedia v-if="allowEmbeddedMedia" :status="status" />
|
||||||
<StatusCard
|
<StatusCard
|
||||||
v-if="status.reblog"
|
v-if="status.reblog"
|
||||||
:status="status.reblog" border="~ rounded"
|
:status="status.reblog" border="~ rounded"
|
||||||
|
|
105
components/status/StatusEmbeddedMedia.vue
Normal file
105
components/status/StatusEmbeddedMedia.vue
Normal 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>
|
|
@ -150,6 +150,25 @@ export function convertMastodonHTML(html: string, customEmojis: Record<string, m
|
||||||
return render(tree)
|
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) {
|
export function htmlToText(html: string) {
|
||||||
try {
|
try {
|
||||||
const tree = parse(html)
|
const tree = parse(html)
|
||||||
|
|
|
@ -36,7 +36,7 @@ export function contentToVNode(
|
||||||
return h(Fragment, (tree.children as Node[] || []).map(n => treeToVNode(n)))
|
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)
|
if (node.type === TEXT_NODE)
|
||||||
return node.value
|
return node.value
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ export interface PreferencesSettings {
|
||||||
experimentalVirtualScroller: boolean
|
experimentalVirtualScroller: boolean
|
||||||
experimentalGitHubCards: boolean
|
experimentalGitHubCards: boolean
|
||||||
experimentalUserPicker: boolean
|
experimentalUserPicker: boolean
|
||||||
|
experimentalEmbeddedMedia: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserSettings {
|
export interface UserSettings {
|
||||||
|
@ -78,6 +79,7 @@ export const DEFAULT__PREFERENCES_SETTINGS: PreferencesSettings = {
|
||||||
experimentalVirtualScroller: true,
|
experimentalVirtualScroller: true,
|
||||||
experimentalGitHubCards: true,
|
experimentalGitHubCards: true,
|
||||||
experimentalUserPicker: true,
|
experimentalUserPicker: true,
|
||||||
|
experimentalEmbeddedMedia: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultUserSettings(locales: string[]): UserSettings {
|
export function getDefaultUserSettings(locales: string[]): UserSettings {
|
||||||
|
|
|
@ -500,6 +500,8 @@
|
||||||
},
|
},
|
||||||
"notifications_settings": "Notifications",
|
"notifications_settings": "Notifications",
|
||||||
"preferences": {
|
"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_autoplay": "Enable Autoplay",
|
||||||
"enable_data_saving": "Enable data saving",
|
"enable_data_saving": "Enable data saving",
|
||||||
"enable_data_saving_description": "Save data by preventing attachments from automatically loading.",
|
"enable_data_saving_description": "Save data by preventing attachments from automatically loading.",
|
||||||
|
@ -577,6 +579,7 @@
|
||||||
},
|
},
|
||||||
"boosted_by": "Boosted By",
|
"boosted_by": "Boosted By",
|
||||||
"edited": "Edited {0}",
|
"edited": "Edited {0}",
|
||||||
|
"embedded_warning": "Playing this may reveal your IP address to others.",
|
||||||
"favourited_by": "Favorited By",
|
"favourited_by": "Favorited By",
|
||||||
"filter_hidden_phrase": "Filtered by",
|
"filter_hidden_phrase": "Filtered by",
|
||||||
"filter_show_anyway": "Show anyway",
|
"filter_show_anyway": "Show anyway",
|
||||||
|
|
|
@ -117,6 +117,16 @@ const userSettings = useUserSettings()
|
||||||
<div i-ri-flask-line />
|
<div i-ri-flask-line />
|
||||||
{{ $t('settings.preferences.title') }}
|
{{ $t('settings.preferences.title') }}
|
||||||
</h2>
|
</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
|
<SettingsToggleItem
|
||||||
:checked="getPreferences(userSettings, 'experimentalVirtualScroller')"
|
:checked="getPreferences(userSettings, 'experimentalVirtualScroller')"
|
||||||
@click="togglePreferences('experimentalVirtualScroller')"
|
@click="togglePreferences('experimentalVirtualScroller')"
|
||||||
|
|
Loading…
Reference in a new issue