diff options
| -rw-r--r-- | src/components/post_status_form/post_status_form.js | 52 | ||||
| -rw-r--r-- | src/components/post_status_form/post_status_form.vue | 113 | ||||
| -rw-r--r-- | src/i18n/en.json | 5 | ||||
| -rw-r--r-- | src/services/api/api.service.js | 14 | ||||
| -rw-r--r-- | src/services/status_poster/status_poster.service.js | 19 |
5 files changed, 180 insertions, 23 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..3f7e36a6 100644 --- 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 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' @@ -38,7 +39,8 @@ const PostStatusForm = { EmojiInput, PollForm, ScopeSelector, - Checkbox + Checkbox, + StatusContent }, mounted () { this.resize(this.$refs.textarea) @@ -84,7 +86,9 @@ const PostStatusForm = { caret: 0, pollFormVisible: false, showDropIcon: 'hide', - dropStopTimeout: null + dropStopTimeout: null, + preview: null, + previewLoading: false } }, computed: { @@ -163,8 +167,20 @@ const PostStatusForm = { this.newStatus.poll && this.newStatus.poll.error }, + showPreview () { + return !!this.preview || this.previewLoading + }, ...mapGetters(['mergedConfig']) }, + watch: { + 'newStatus.contentType': function (newType) { + if (newType === 'text/plain') { + this.closePreview() + } else if (this.preview) { + this.previewStatus(this.newStatus) + } + } + }, methods: { postStatus (newStatus) { if (this.posting) { return } @@ -218,6 +234,38 @@ const PostStatusForm = { this.posting = false }) }, + previewStatus (newStatus) { + this.previewLoading = true + statusPoster.postStatus({ + status: newStatus.status, + spoilerText: newStatus.spoilerText || null, + visibility: newStatus.visibility, + sensitive: newStatus.nsfw, + media: newStatus.files, + 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.preview = data + } else { + this.preview = { error: data.error } + } + }).catch((error) => { + this.preview = { error } + }).finally(() => { + this.previewLoading = false + }) + }, + closePreview () { + this.preview = null + this.previewLoading = false + }, addMediaFile (fileInfo) { this.newStatus.files.push(fileInfo) }, diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index e3d8d087..9931f5ca 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -16,6 +16,58 @@ @drop.stop="fileDrop" /> <div class="form-group"> + <a + v-if="newStatus.contentType !== 'text/plain' && !showPreview" + class="preview-start faint" + type="button" + @click.stop.prevent="previewStatus(newStatus)" + > + {{ $t('status.preview') }} + </a> + <div + v-if="showPreview && newStatus.contentType !== 'text/plain'" + class="preview-container" + > + <span class="preview-heading"> + <span class="faint preview-title"> + {{ $t('status.status_preview') }} + </span> + <i + v-if="previewLoading" + class="icon-spin3 animate-spin" + /> + <a + v-else + class="faint preview-update" + @click.stop.prevent="previewStatus(newStatus)" + > + {{ $t('status.preview_update') }} + </a> + <a + class="preview-close" + @click.stop.prevent="closePreview" + > + <i class="icon-cancel" /> + </a> + </span> + <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> <i18n v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'" path="post_status.account_not_locked_warning" @@ -77,7 +129,6 @@ class="form-control" > <input - v-model="newStatus.spoilerText" type="text" :placeholder="$t('post_status.content_warning')" @@ -303,14 +354,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 +379,52 @@ max-width: 10em; } + .preview-start { + margin-left: auto; + cursor: pointer; + &:hover { + text-decoration: underline; + } + } + + .preview-container { + margin-bottom: 1em; + } + + .preview-heading { + display: flex; + width: 100%; + } + + .preview-title { + flex-grow: 1; + } + + .preview-close { + margin-left: 0.5em; + } + + .preview-update { + cursor: pointer; + &:hover { + text-decoration: underline; + } + } + + .preview-error { + color: $fallback--faint; + color: var(--faint, $fallback--faint); + font-style: italic; + } + + .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; + } + .text-format { .only-format { color: $fallback--faint; @@ -343,6 +432,12 @@ } } + .visibility-tray { + display: flex; + justify-content: space-between; + padding-top: 5px; + } + .media-upload-icon, .poll-icon, .emoji-icon { font-size: 26px; flex: 1; diff --git a/src/i18n/en.json b/src/i18n/en.json index 2840904f..7b884c7c 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -636,7 +636,10 @@ "status_unavailable": "Status unavailable", "copy_link": "Copy link to status", "thread_muted": "Thread muted", - "thread_muted_and_words": ", has words:" + "thread_muted_and_words": ", has words:", + "preview": "Preview", + "status_preview": "Status preview", + "preview_update": "Update" }, "user_card": { "approve": "Approve", diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index dfffc291..2f579ed2 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -617,7 +617,8 @@ const postStatus = ({ poll, mediaIds = [], inReplyToStatusId, - contentType + contentType, + preview }) => { const form = new FormData() const pollOptions = poll.options || [] @@ -647,6 +648,9 @@ const postStatus = ({ if (inReplyToStatusId) { form.append('in_reply_to_id', inReplyToStatusId) } + if (preview) { + form.append('preview', 'true') + } return fetch(MASTODON_POST_STATUS_URL, { body: form, @@ -654,13 +658,7 @@ const postStatus = ({ headers: authHeaders(credentials) }) .then((response) => { - if (response.ok) { - return response.json() - } else { - return { - error: response - } - } + return response.json() }) .then((data) => data.error ? data : parseStatus(data)) } diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index 9e904d3a..86fc5601 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -1,7 +1,18 @@ import { map } from 'lodash' import apiService from '../api/api.service.js' -const postStatus = ({ store, status, spoilerText, visibility, sensitive, poll, media = [], inReplyToStatusId = undefined, contentType = 'text/plain' }) => { +const postStatus = ({ + store, + status, + spoilerText, + visibility, + sensitive, + poll, + media = [], + inReplyToStatusId = undefined, + contentType = 'text/plain', + preview = false +}) => { const mediaIds = map(media, 'id') return apiService.postStatus({ @@ -13,9 +24,11 @@ const postStatus = ({ store, status, spoilerText, visibility, sensitive, poll, m mediaIds, inReplyToStatusId, contentType, - poll }) + poll, + preview + }) .then((data) => { - if (!data.error) { + if (!data.error && !preview) { store.dispatch('addNewStatuses', { statuses: [data], timeline: 'friends', |
