commit: ec26ff04ce541c3ba5a7f89d57cd04f74e79cfcc
parent: bbb977a04475a445fc0588af86b3c5eb33ec0577
Author: Shpuld Shpludson <shp@cock.li>
Date: Tue, 7 Jul 2020 12:14:58 +0000
Merge branch 'xenofem/pleroma-fe-rebased-image-description-authoring' into 'develop'
media description authoring v3
See merge request pleroma/pleroma-fe!1161
Diffstat:
17 files changed, 186 insertions(+), 105 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
@@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Autocomplete domains from list of known instances
- 'Bot' settings option and badge
- Added profile meta data fields that can be set in profile settings
+- Descriptions can be set on uploaded files before posting
- Added status preview option to preview your statuses before posting
- When a post is a reply to an unavailable post, the 'Reply to'-text has a strike-through style
@@ -28,6 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Close the media modal on navigation events
- Add colons to the emoji alt text, to make them copyable
- Add better visual indication for drag-and-drop for files
+- When disabling attachments, the placeholder links now show an icon and the description instead of just IMAGE or VIDEO etc
### Fixed
- Custom Emoji will display in poll options now.
@@ -39,6 +41,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Subject field now appears disabled when posting
- Fix status ellipsis menu being cut off in notifications column
- Fixed autocomplete sometimes not returning the right user when there's already some results
+- Videos and audio and misc files show description as alt/title properly now
+- Clicking on non-image/video files no longer opens an empty modal
+- Audio files can now be played back in the frontend with hidden attachments
+- Videos are not cropped awkwardly in the uploads section anymore
- Reply filtering options in Settings -> Filtering now work again using filtering on server
- Don't show just blank-screen when cookies are disabled
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js
@@ -8,7 +8,6 @@ const Attachment = {
props: [
'attachment',
'nsfw',
- 'statusId',
'size',
'allowPlay',
'setMedia',
@@ -30,9 +29,21 @@ const Attachment = {
VideoAttachment
},
computed: {
- usePlaceHolder () {
+ usePlaceholder () {
return this.size === 'hide' || this.type === 'unknown'
},
+ placeholderName () {
+ if (this.attachment.description === '' || !this.attachment.description) {
+ return this.type.toUpperCase()
+ }
+ return this.attachment.description
+ },
+ placeholderIconClass () {
+ if (this.type === 'image') return 'icon-picture'
+ if (this.type === 'video') return 'icon-video'
+ if (this.type === 'audio') return 'icon-music'
+ return 'icon-doc'
+ },
referrerpolicy () {
return this.$store.state.instance.mediaProxyAvailable ? '' : 'no-referrer'
},
@@ -49,7 +60,15 @@ const Attachment = {
return this.size === 'small'
},
fullwidth () {
- return this.type === 'html' || this.type === 'audio'
+ if (this.size === 'hide') return false
+ return this.type === 'html' || this.type === 'audio' || this.type === 'unknown'
+ },
+ useModal () {
+ const modalTypes = this.size === 'hide' ? ['image', 'video', 'audio']
+ : this.mergedConfig.playVideosInModal
+ ? ['image', 'video']
+ : ['image']
+ return modalTypes.includes(this.type)
},
...mapGetters(['mergedConfig'])
},
@@ -60,12 +79,7 @@ const Attachment = {
}
},
openModal (event) {
- const modalTypes = this.mergedConfig.playVideosInModal
- ? ['image', 'video']
- : ['image']
- if (fileTypeService.fileMatchesSomeType(modalTypes, this.attachment) ||
- this.usePlaceHolder
- ) {
+ if (this.useModal) {
event.stopPropagation()
event.preventDefault()
this.setMedia()
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
@@ -1,6 +1,7 @@
<template>
<div
- v-if="usePlaceHolder"
+ v-if="usePlaceholder"
+ :class="{ 'fullwidth': fullwidth }"
@click="openModal"
>
<a
@@ -8,8 +9,11 @@
class="placeholder"
target="_blank"
:href="attachment.url"
+ :alt="attachment.description"
+ :title="attachment.description"
>
- [{{ nsfw ? "NSFW/" : "" }}{{ type.toUpperCase() }}]
+ <span :class="placeholderIconClass" />
+ <b>{{ nsfw ? "NSFW / " : "" }}</b>{{ placeholderName }}
</a>
</div>
<div
@@ -22,6 +26,8 @@
v-if="hidden"
class="image-attachment"
:href="attachment.url"
+ :alt="attachment.description"
+ :title="attachment.description"
@click.prevent="toggleHidden"
>
<img
@@ -51,7 +57,6 @@
:class="{'hidden': hidden && preloadImage }"
:href="attachment.url"
target="_blank"
- :title="attachment.description"
@click="openModal"
>
<StillImage
@@ -59,6 +64,7 @@
:mimetype="attachment.mimetype"
:src="attachment.large_thumb_url || attachment.url"
:image-load-handler="onImageLoad"
+ :alt="attachment.description"
/>
</a>
@@ -83,6 +89,8 @@
<audio
v-if="type === 'audio'"
:src="attachment.url"
+ :alt="attachment.description"
+ :title="attachment.description"
controls
/>
@@ -116,22 +124,19 @@
display: flex;
flex-wrap: wrap;
- .attachment.media-upload-container {
- flex: 0 0 auto;
- max-height: 200px;
+ .non-gallery {
max-width: 100%;
- display: flex;
- align-items: center;
- video {
- max-width: 100%;
- }
}
.placeholder {
- margin-right: 8px;
- margin-bottom: 4px;
+ display: inline-block;
+ padding: 0.3em 1em 0.3em 0;
color: $fallback--link;
color: var(--postLink, $fallback--link);
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ max-width: 100%;
}
.nsfw-placeholder {
diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue
@@ -50,9 +50,7 @@
align-content: stretch;
}
- // FIXME: specificity problem with this and .attachments.attachment
- // we shouldn't have the need for .image here
- .attachment.image {
+ .gallery-row-inner .attachment {
margin: 0 0.5em 0 0;
flex-grow: 1;
height: 100%;
diff --git a/src/components/media_modal/media_modal.vue b/src/components/media_modal/media_modal.vue
@@ -8,6 +8,8 @@
v-if="type === 'image'"
class="modal-image"
:src="currentMedia.url"
+ :alt="currentMedia.description"
+ :title="currentMedia.description"
@touchstart.stop="mediaTouchStart"
@touchmove.stop="mediaTouchMove"
@click="hide"
@@ -18,6 +20,14 @@
:attachment="currentMedia"
:controls="true"
/>
+ <audio
+ v-if="type === 'audio'"
+ class="modal-image"
+ :src="currentMedia.url"
+ :alt="currentMedia.description"
+ :title="currentMedia.description"
+ controls
+ />
<button
v-if="canNavigate"
:title="$t('media_modal.previous')"
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
@@ -3,6 +3,7 @@ import MediaUpload from '../media_upload/media_upload.vue'
import ScopeSelector from '../scope_selector/scope_selector.vue'
import EmojiInput from '../emoji_input/emoji_input.vue'
import PollForm from '../poll/poll_form.vue'
+import Attachment from '../attachment/attachment.vue'
import StatusContent from '../status_content/status_content.vue'
import fileTypeService from '../../services/file_type/file_type.service.js'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
@@ -40,6 +41,7 @@ const PostStatusForm = {
PollForm,
ScopeSelector,
Checkbox,
+ Attachment,
StatusContent
},
mounted () {
@@ -80,6 +82,7 @@ const PostStatusForm = {
nsfw: false,
files: [],
poll: {},
+ mediaDescriptions: {},
visibility: scope,
contentType
},
@@ -184,7 +187,7 @@ const PostStatusForm = {
}
},
methods: {
- postStatus (newStatus) {
+ async postStatus (newStatus) {
if (this.posting) { return }
if (this.submitDisabled) { return }
if (this.emptyStatus) {
@@ -199,7 +202,16 @@ const PostStatusForm = {
}
this.posting = true
- statusPoster.postStatus({
+
+ try {
+ await this.setAllMediaDescriptions()
+ } catch (e) {
+ this.error = this.$t('post_status.media_description_error')
+ this.posting = false
+ return
+ }
+
+ const data = await statusPoster.postStatus({
status: newStatus.status,
spoilerText: newStatus.spoilerText || null,
visibility: newStatus.visibility,
@@ -209,30 +221,32 @@ const PostStatusForm = {
inReplyToStatusId: this.replyTo,
contentType: newStatus.contentType,
poll
- }).then((data) => {
- if (!data.error) {
- this.newStatus = {
- status: '',
- spoilerText: '',
- files: [],
- visibility: newStatus.visibility,
- contentType: newStatus.contentType,
- poll: {}
- }
- this.pollFormVisible = false
- this.$refs.mediaUpload.clearFile()
- this.clearPollForm()
- this.$emit('posted')
- let el = this.$el.querySelector('textarea')
- el.style.height = 'auto'
- el.style.height = undefined
- this.error = null
- if (this.preview) this.previewStatus()
- } else {
- this.error = data.error
- }
- this.posting = false
})
+
+ if (!data.error) {
+ this.newStatus = {
+ status: '',
+ spoilerText: '',
+ files: [],
+ visibility: newStatus.visibility,
+ contentType: newStatus.contentType,
+ poll: {},
+ mediaDescriptions: {}
+ }
+ this.pollFormVisible = false
+ this.$refs.mediaUpload.clearFile()
+ this.clearPollForm()
+ this.$emit('posted')
+ let el = this.$el.querySelector('textarea')
+ el.style.height = 'auto'
+ el.style.height = undefined
+ this.error = null
+ if (this.preview) this.previewStatus()
+ } else {
+ this.error = data.error
+ }
+
+ this.posting = false
},
previewStatus () {
if (this.emptyStatus && this.newStatus.spoilerText.trim() === '') {
@@ -457,6 +471,15 @@ const PostStatusForm = {
},
dismissScopeNotice () {
this.$store.dispatch('setOption', { name: 'hideScopeNotice', value: true })
+ },
+ setMediaDescription (id) {
+ const description = this.newStatus.mediaDescriptions[id]
+ if (!description || description.trim() === '') return
+ return statusPoster.setMediaDescription({ store: this.$store, id, description })
+ },
+ setAllMediaDescriptions () {
+ const ids = this.newStatus.files.map(file => file.id)
+ return Promise.all(ids.map(id => this.setMediaDescription(id)))
}
}
}
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
@@ -282,27 +282,18 @@
class="fa button-icon icon-cancel"
@click="removeMediaFile(file)"
/>
- <div class="media-upload-container attachment">
- <img
- v-if="type(file) === 'image'"
- class="thumbnail media-upload"
- :src="file.url"
- >
- <video
- v-if="type(file) === 'video'"
- :src="file.url"
- controls
- />
- <audio
- v-if="type(file) === 'audio'"
- :src="file.url"
- controls
- />
- <a
- v-if="type(file) === 'unknown'"
- :href="file.url"
- >{{ file.url }}</a>
- </div>
+ <attachment
+ :attachment="file"
+ :set-media="() => $store.dispatch('setMedia', newStatus.files)"
+ size="small"
+ allow-play="false"
+ />
+ <input
+ v-model="newStatus.mediaDescriptions[file.id]"
+ type="text"
+ :placeholder="$t('post_status.media_description')"
+ @keydown.enter.prevent=""
+ >
</div>
</div>
<div
@@ -458,11 +449,9 @@
}
.media-upload-wrapper {
- flex: 0 0 auto;
- max-width: 100%;
- min-width: 50px;
margin-right: .2em;
margin-bottom: .5em;
+ width: 18em;
.icon-cancel {
display: inline-block;
@@ -476,6 +465,20 @@
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
+
+ img, video {
+ object-fit: contain;
+ max-height: 10em;
+ }
+
+ .video {
+ max-height: 10em;
+ }
+
+ input {
+ flex: 1;
+ width: 100%;
+ }
}
.status-input-wrapper {
@@ -490,23 +493,8 @@
.attachment {
margin: 0;
+ padding: 0;
position: relative;
- flex: 0 0 auto;
- border: 1px solid $fallback--border;
- border: 1px solid var(--border, $fallback--border);
- text-align: center;
-
- audio {
- min-width: 300px;
- flex: 1 0 auto;
- }
-
- a {
- display: block;
- text-align: left;
- line-height: 1.2;
- padding: .5em;
- }
}
i {
diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js
@@ -99,15 +99,8 @@ const StatusContent = {
file => !fileType.fileMatchesSomeType(this.galleryTypes, file)
)
},
- hasImageAttachments () {
- return this.status.attachments.some(
- file => fileType.fileType(file.mimetype) === 'image'
- )
- },
- hasVideoAttachments () {
- return this.status.attachments.some(
- file => fileType.fileType(file.mimetype) === 'video'
- )
+ attachmentTypes () {
+ return this.status.attachments.map(file => fileType.fileType(file.mimetype))
},
maxThumbnails () {
return this.mergedConfig.maxThumbnails
diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue
@@ -55,14 +55,22 @@
>
{{ $t("status.show_content") }}
<span
- v-if="hasImageAttachments"
+ v-if="attachmentTypes.includes('image')"
class="icon-picture"
/>
<span
- v-if="hasVideoAttachments"
+ v-if="attachmentTypes.includes('video')"
class="icon-video"
/>
<span
+ v-if="attachmentTypes.includes('audio')"
+ class="icon-music"
+ />
+ <span
+ v-if="attachmentTypes.includes('unknown')"
+ class="icon-doc"
+ />
+ <span
v-if="status.card"
class="icon-link"
/>
diff --git a/src/components/still-image/still-image.js b/src/components/still-image/still-image.js
@@ -4,7 +4,8 @@ const StillImage = {
'referrerpolicy',
'mimetype',
'imageLoadError',
- 'imageLoadHandler'
+ 'imageLoadHandler',
+ 'alt'
],
data () {
return {
diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue
@@ -11,6 +11,8 @@
<img
ref="src"
:key="src"
+ :alt="alt"
+ :title="alt"
:src="src"
:referrerpolicy="referrerpolicy"
@load="onLoad"
diff --git a/src/components/video_attachment/video_attachment.vue b/src/components/video_attachment/video_attachment.vue
@@ -4,6 +4,8 @@
:src="attachment.url"
:loop="loopVideo"
:controls="controls"
+ :alt="attachment.description"
+ :title="attachment.description"
playsinline
@loadeddata="onVideoDataLoad"
/>
diff --git a/src/i18n/en.json b/src/i18n/en.json
@@ -178,6 +178,7 @@
"account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
"account_not_locked_warning_link": "locked",
"attachments_sensitive": "Mark attachments as sensitive",
+ "media_description": "Media description",
"content_type": {
"text/plain": "Plain text",
"text/html": "HTML",
@@ -192,6 +193,7 @@
"preview": "Preview",
"preview_empty": "Empty",
"empty_status_error": "Can't post an empty status with no files",
+ "media_description_error": "Failed to update media, try again",
"scope_notice": {
"public": "This post will be visible to everyone",
"private": "This post will be visible to your followers only",
diff --git a/src/modules/media_viewer.js b/src/modules/media_viewer.js
@@ -22,7 +22,7 @@ const mediaViewer = {
setMedia ({ commit }, attachments) {
const media = attachments.filter(attachment => {
const type = fileTypeService.fileType(attachment.mimetype)
- return type === 'image' || type === 'video'
+ return type === 'image' || type === 'video' || type === 'audio'
})
commit('setMedia', media)
},
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
@@ -708,6 +708,17 @@ const uploadMedia = ({ formData, credentials }) => {
.then((data) => parseAttachment(data))
}
+const setMediaDescription = ({ id, description, credentials }) => {
+ return promisedRequest({
+ url: `${MASTODON_MEDIA_UPLOAD_URL}/${id}`,
+ method: 'PUT',
+ headers: authHeaders(credentials),
+ payload: {
+ description
+ }
+ }).then((data) => parseAttachment(data))
+}
+
const importBlocks = ({ file, credentials }) => {
const formData = new FormData()
formData.append('list', file)
@@ -1177,6 +1188,7 @@ const apiService = {
postStatus,
deleteStatus,
uploadMedia,
+ setMediaDescription,
fetchMutes,
muteUser,
unmuteUser,
diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js
@@ -47,13 +47,18 @@ const postStatus = ({
const uploadMedia = ({ store, formData }) => {
const credentials = store.state.users.currentUser.credentials
-
return apiService.uploadMedia({ credentials, formData })
}
+const setMediaDescription = ({ store, id, description }) => {
+ const credentials = store.state.users.currentUser.credentials
+ return apiService.setMediaDescription({ credentials, id, description })
+}
+
const statusPosterService = {
postStatus,
- uploadMedia
+ uploadMedia,
+ setMediaDescription
}
export default statusPosterService
diff --git a/static/fontello.json b/static/fontello.json
@@ -387,6 +387,18 @@
"css": "bookmark-empty",
"code": 61591,
"src": "fontawesome"
+ },
+ {
+ "uid": "9ea0a737ccc45d6c510dcbae56058849",
+ "css": "music",
+ "code": 59432,
+ "src": "fontawesome"
+ },
+ {
+ "uid": "1b5a5d7b7e3c71437f5a26befdd045ed",
+ "css": "doc",
+ "code": 59433,
+ "src": "fontawesome"
}
]
}
\ No newline at end of file