feat: add support for audio attachments (#1293)
This is a new thing in Mastodon v2.9. I kept the "camera" icon because I like it better than the paperclip, and I think it covers the 99% use case.
This commit is contained in:
parent
ea220c32d3
commit
d31f2ce010
|
@ -1,5 +1,9 @@
|
||||||
<li class="compose-media compose-media-realm-{realm}">
|
<li class="compose-media compose-media-realm-{realm}">
|
||||||
<img src={mediaItem.data.preview_url} {alt} />
|
<img
|
||||||
|
class="{type === 'audio' ? 'audio-preview' : ''}"
|
||||||
|
src={previewSrc}
|
||||||
|
{alt}
|
||||||
|
/>
|
||||||
<div class="compose-media-delete">
|
<div class="compose-media-delete">
|
||||||
<button class="compose-media-delete-button"
|
<button class="compose-media-delete-button"
|
||||||
aria-label="Delete {shortName}"
|
aria-label="Delete {shortName}"
|
||||||
|
@ -10,13 +14,11 @@
|
||||||
<div class="compose-media-alt">
|
<div class="compose-media-alt">
|
||||||
<textarea id="compose-media-input-{uuid}"
|
<textarea id="compose-media-input-{uuid}"
|
||||||
class="compose-media-alt-input"
|
class="compose-media-alt-input"
|
||||||
placeholder="Describe for the visually impaired"
|
placeholder="Description"
|
||||||
ref:textarea
|
ref:textarea
|
||||||
bind:value=rawText
|
bind:value=rawText
|
||||||
></textarea>
|
></textarea>
|
||||||
<label for="compose-media-input-{uuid}" class="sr-only">
|
<label for="compose-media-input-{uuid}" class="sr-only">{label}</label>
|
||||||
Describe {shortName} for the visually impaired
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<style>
|
<style>
|
||||||
|
@ -85,6 +87,10 @@
|
||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.audio-preview {
|
||||||
|
background: var(--audio-bg);
|
||||||
|
}
|
||||||
|
|
||||||
.compose-media-realm-dialog {
|
.compose-media-realm-dialog {
|
||||||
max-height: 20vh;
|
max-height: 20vh;
|
||||||
}
|
}
|
||||||
|
@ -106,6 +112,7 @@
|
||||||
import { observe } from 'svelte-extras'
|
import { observe } from 'svelte-extras'
|
||||||
import SvgIcon from '../SvgIcon.html'
|
import SvgIcon from '../SvgIcon.html'
|
||||||
import { autosize } from '../../_thirdparty/autosize/autosize'
|
import { autosize } from '../../_thirdparty/autosize/autosize'
|
||||||
|
import { ONE_TRANSPARENT_PIXEL } from '../../_static/media'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
oncreate () {
|
oncreate () {
|
||||||
|
@ -126,7 +133,14 @@
|
||||||
// so fall back to the description if it was provided
|
// so fall back to the description if it was provided
|
||||||
filename || mediaItem.description || ''
|
filename || mediaItem.description || ''
|
||||||
),
|
),
|
||||||
|
type: ({ mediaItem }) => mediaItem.data.type,
|
||||||
shortName: ({ filename }) => filename || 'media',
|
shortName: ({ filename }) => filename || 'media',
|
||||||
|
previewSrc: ({ mediaItem, type }) => (
|
||||||
|
type === 'audio' ? ONE_TRANSPARENT_PIXEL : mediaItem.data.preview_url
|
||||||
|
),
|
||||||
|
label: ({ shortName }) => (
|
||||||
|
`Describe ${shortName} for the visually impaired (image, video) or auditorily impaired (audio, video)`
|
||||||
|
),
|
||||||
uuid: ({ realm, mediaItem }) => `${realm}-${mediaItem.data.id}`
|
uuid: ({ realm, mediaItem }) => `${realm}-${mediaItem.data.id}`
|
||||||
},
|
},
|
||||||
store: () => store,
|
store: () => store,
|
||||||
|
|
|
@ -217,7 +217,7 @@
|
||||||
computed: {
|
computed: {
|
||||||
length: ({ mediaItems }) => mediaItems.length,
|
length: ({ mediaItems }) => mediaItems.length,
|
||||||
dots: ({ length }) => times(length, i => ({ i })),
|
dots: ({ length }) => times(length, i => ({ i })),
|
||||||
canPinchZoom: ({ mediaItems }) => !mediaItems.some(media => media.type === 'video')
|
canPinchZoom: ({ mediaItems }) => !mediaItems.some(media => ['video', 'audio'].includes(media.type))
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
|
|
|
@ -6,8 +6,18 @@
|
||||||
{poster}
|
{poster}
|
||||||
controls
|
controls
|
||||||
{intrinsicsize}
|
{intrinsicsize}
|
||||||
ref:video
|
ref:player
|
||||||
/>
|
/>
|
||||||
|
{:elseif type === 'audio'}
|
||||||
|
<div class="audio-player-container">
|
||||||
|
<audio
|
||||||
|
class="audio-player"
|
||||||
|
aria-label={description}
|
||||||
|
src={url}
|
||||||
|
controls
|
||||||
|
ref:player
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{:elseif type === 'gifv'}
|
{:elseif type === 'gifv'}
|
||||||
<video
|
<video
|
||||||
class="media-fit"
|
class="media-fit"
|
||||||
|
@ -36,6 +46,27 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
.audio-player-container {
|
||||||
|
min-height: 400px;
|
||||||
|
min-width: 400px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-end;
|
||||||
|
background: var(--audio-bg);
|
||||||
|
}
|
||||||
|
.audio-player {
|
||||||
|
padding: 30px 10px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.audio-player-container {
|
||||||
|
min-height: 200px;
|
||||||
|
min-width: calc(100vw - 40px);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
@ -54,8 +85,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ondestroy () {
|
ondestroy () {
|
||||||
if (this.refs.video && !this.refs.video.paused) {
|
let player = this.refs.player
|
||||||
this.refs.video.pause()
|
if (player && !player.paused) {
|
||||||
|
player.pause()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,22 @@
|
||||||
{#if type === 'video'}
|
{#if type === 'video' || type === 'audio'}
|
||||||
<button id={elementId}
|
<button id={elementId}
|
||||||
type="button"
|
type="button"
|
||||||
class="play-video-button {$largeInlineMedia ? '' : 'fixed-size'}"
|
class="play-video-button {$largeInlineMedia ? '' : 'fixed-size'} {type === 'audio' ? 'play-audio-button' : ''}"
|
||||||
aria-label="Play video: {description}"
|
aria-label="Play video: {description}"
|
||||||
style="width: {inlineWidth}px; height: {inlineHeight}px;">
|
style="width: {inlineWidth}px; height: {inlineHeight}px;">
|
||||||
<PlayVideoIcon />
|
<PlayVideoIcon />
|
||||||
<LazyImage
|
{#if type === 'video'}
|
||||||
alt={description}
|
<LazyImage
|
||||||
title={description}
|
alt={description}
|
||||||
src={previewUrl}
|
title={description}
|
||||||
fallback={oneTransparentPixel}
|
src={previewUrl}
|
||||||
width={inlineWidth}
|
fallback={oneTransparentPixel}
|
||||||
height={inlineHeight}
|
width={inlineWidth}
|
||||||
background="var(--loading-bg)"
|
height={inlineHeight}
|
||||||
{focus}
|
background="var(--loading-bg)"
|
||||||
/>
|
{focus}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button id={elementId}
|
<button id={elementId}
|
||||||
|
@ -72,6 +74,9 @@
|
||||||
background: none;
|
background: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
.play-audio-button {
|
||||||
|
background: var(--audio-bg);
|
||||||
|
}
|
||||||
|
|
||||||
/* the actual focus outline is not very visible, so use an ::after pseudo-element */
|
/* the actual focus outline is not very visible, so use an ::after pseudo-element */
|
||||||
.play-video-button:focus, .show-image-button:focus {
|
.play-video-button:focus, .show-image-button:focus {
|
||||||
|
|
|
@ -118,4 +118,6 @@
|
||||||
--floating-button-bg-active: #{darken(rgba($main-bg-color, 0.9), 10%)};
|
--floating-button-bg-active: #{darken(rgba($main-bg-color, 0.9), 10%)};
|
||||||
|
|
||||||
--length-indicator-color: #{$main-theme-color};
|
--length-indicator-color: #{$main-theme-color};
|
||||||
|
|
||||||
|
--audio-bg: #{rgba(30, 30, 30, 0.8)};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue