diff options
Diffstat (limited to 'src/components/post_status_form')
| -rw-r--r-- | src/components/post_status_form/post_status_form.js | 150 | ||||
| -rw-r--r-- | src/components/post_status_form/post_status_form.vue | 165 |
2 files changed, 236 insertions, 79 deletions
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 9027566f..18f02eba 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -3,9 +3,11 @@ 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' -import { reject, map, uniqBy } from 'lodash' +import { reject, map, uniqBy, debounce } from 'lodash' import suggestor from '../emoji_input/suggestor.js' import { mapGetters } from 'vuex' import Checkbox from '../checkbox/checkbox.vue' @@ -38,7 +40,9 @@ const PostStatusForm = { EmojiInput, PollForm, ScopeSelector, - Checkbox + Checkbox, + Attachment, + StatusContent }, mounted () { this.resize(this.$refs.textarea) @@ -78,13 +82,16 @@ const PostStatusForm = { nsfw: false, files: [], poll: {}, + mediaDescriptions: {}, visibility: scope, contentType }, caret: 0, pollFormVisible: false, showDropIcon: 'hide', - dropStopTimeout: null + dropStopTimeout: null, + preview: null, + previewLoading: false } }, computed: { @@ -163,18 +170,29 @@ const PostStatusForm = { this.newStatus.poll && this.newStatus.poll.error }, + showPreview () { + return !!this.preview || this.previewLoading + }, + emptyStatus () { + return this.newStatus.status.trim() === '' && this.newStatus.files.length === 0 + }, ...mapGetters(['mergedConfig']) }, + watch: { + 'newStatus.contentType': function () { + this.autoPreview() + }, + 'newStatus.spoilerText': function () { + this.autoPreview() + } + }, methods: { - postStatus (newStatus) { + async postStatus (newStatus) { if (this.posting) { return } if (this.submitDisabled) { return } - - if (this.newStatus.status === '') { - if (this.newStatus.files.length === 0) { - this.error = 'Cannot post an empty status with no files' - return - } + if (this.emptyStatus) { + this.error = this.$t('post_status.empty_status_error') + return } const poll = this.pollFormVisible ? this.newStatus.poll : {} @@ -184,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, @@ -194,30 +221,84 @@ const PostStatusForm = { inReplyToStatusId: this.replyTo, contentType: newStatus.contentType, poll + }) + + 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() === '') { + this.preview = { error: this.$t('post_status.preview_empty') } + this.previewLoading = false + return + } + const newStatus = this.newStatus + this.previewLoading = true + statusPoster.postStatus({ + status: newStatus.status, + spoilerText: newStatus.spoilerText || null, + visibility: newStatus.visibility, + sensitive: newStatus.nsfw, + media: [], + store: this.$store, + inReplyToStatusId: this.replyTo, + contentType: newStatus.contentType, + poll: {}, + preview: true }).then((data) => { + // Don't apply preview if not loading, because it means + // user has closed the preview manually. + if (!this.previewLoading) return 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 + this.preview = data } else { - this.error = data.error + this.preview = { error: data.error } } - this.posting = false + }).catch((error) => { + this.preview = { error } + }).finally(() => { + this.previewLoading = false }) }, + debouncePreviewStatus: debounce(function () { this.previewStatus() }, 500), + autoPreview () { + if (!this.preview) return + this.previewLoading = true + this.debouncePreviewStatus() + }, + closePreview () { + this.preview = null + this.previewLoading = false + }, + togglePreview () { + if (this.showPreview) { + this.closePreview() + } else { + this.previewStatus() + } + }, addMediaFile (fileInfo) { this.newStatus.files.push(fileInfo) }, @@ -239,6 +320,7 @@ const PostStatusForm = { return fileTypeService.fileType(fileInfo.mimetype) }, paste (e) { + this.autoPreview() this.resize(e) if (e.clipboardData.files.length > 0) { // prevent pasting of file as text @@ -273,6 +355,7 @@ const PostStatusForm = { } }, onEmojiInputInput (e) { + this.autoPreview() this.$nextTick(() => { this.resize(this.$refs['textarea']) }) @@ -388,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 index e3d8d087..626584ed 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -69,6 +69,44 @@ <span v-if="safeDMEnabled">{{ $t('post_status.direct_warning_to_first_only') }}</span> <span v-else>{{ $t('post_status.direct_warning_to_all') }}</span> </p> + <div class="preview-heading faint"> + <a + class="preview-toggle faint" + @click.stop.prevent="togglePreview" + > + {{ $t('post_status.preview') }} + <i + class="icon-down-open" + :style="{ transform: showPreview ? 'rotate(0deg)' : 'rotate(-90deg)' }" + /> + </a> + <i + v-show="previewLoading" + class="icon-spin3 animate-spin" + /> + </div> + <div + v-if="showPreview" + class="preview-container" + > + <div + v-if="!preview" + class="preview-status" + > + {{ $t('general.loading') }} + </div> + <div + v-else-if="preview.error" + class="preview-status preview-error" + > + {{ preview.error }} + </div> + <StatusContent + v-else + :status="preview" + class="preview-status" + /> + </div> <EmojiInput v-if="newStatus.spoilerText || alwaysShowSubject" v-model="newStatus.spoilerText" @@ -77,7 +115,6 @@ class="form-control" > <input - v-model="newStatus.spoilerText" type="text" :placeholder="$t('post_status.content_warning')" @@ -245,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 @@ -303,14 +331,6 @@ } .post-status-form { - .visibility-tray { - display: flex; - justify-content: space-between; - padding-top: 5px; - } -} - -.post-status-form { .form-bottom { display: flex; justify-content: space-between; @@ -336,6 +356,48 @@ max-width: 10em; } + .preview-heading { + display: flex; + width: 100%; + + .icon-spin3 { + margin-left: auto; + } + } + + .preview-toggle { + display: flex; + cursor: pointer; + + &:hover { + text-decoration: underline; + } + } + + .icon-down-open { + transition: transform 0.1s; + } + + .preview-container { + margin-bottom: 1em; + } + + .preview-error { + font-style: italic; + color: $fallback--faint; + color: var(--faint, $fallback--faint); + } + + .preview-status { + border: 1px solid $fallback--border; + border: 1px solid var(--border, $fallback--border); + border-radius: $fallback--tooltipRadius; + border-radius: var(--tooltipRadius, $fallback--tooltipRadius); + padding: 0.5em; + margin: 0; + line-height: 1.4em; + } + .text-format { .only-format { color: $fallback--faint; @@ -343,6 +405,12 @@ } } + .visibility-tray { + display: flex; + justify-content: space-between; + padding-top: 5px; + } + .media-upload-icon, .poll-icon, .emoji-icon { font-size: 26px; flex: 1; @@ -381,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; @@ -399,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 { @@ -408,28 +488,13 @@ flex-direction: column; } - .attachments { + .media-upload-wrapper .attachments { padding: 0 0.5em; .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 { |
