diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/post_status_form/post_status_form.js | 91 | ||||
| -rw-r--r-- | src/components/post_status_form/post_status_form.vue | 97 | ||||
| -rw-r--r-- | src/components/status/status.vue | 3 | ||||
| -rw-r--r-- | src/components/status_content/status_content.vue | 3 | ||||
| -rw-r--r-- | src/components/user_panel/user_panel.vue | 4 | ||||
| -rw-r--r-- | src/i18n/en.json | 2 | ||||
| -rw-r--r-- | src/services/api/api.service.js | 14 | ||||
| -rw-r--r-- | src/services/status_poster/status_poster.service.js | 19 |
8 files changed, 197 insertions, 36 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..1bf5dae3 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -3,9 +3,10 @@ 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' +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 +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,18 +167,33 @@ 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 (newType) { + if (newType === 'text/plain') { + this.closePreview() + } else if (this.preview) { + this.previewStatus(this.newStatus) + } + }, + 'newStatus.spoilerText': function () { + this.autoPreview() + } + }, methods: { 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 = 'Cannot post an empty status with no files' + return } const poll = this.pollFormVisible ? this.newStatus.poll : {} @@ -212,12 +231,64 @@ const PostStatusForm = { el.style.height = 'auto' el.style.height = undefined this.error = null + this.closePreview() } else { this.error = data.error } this.posting = false }) }, + previewStatus () { + if (this.emptyStatus && this.newStatus.spoilerText.trim() === '') { + this.preview = { error: this.$t('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.preview = data + } else { + this.preview = { error: data.error } + } + }).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 +310,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 +345,7 @@ const PostStatusForm = { } }, onEmojiInputInput (e) { + this.autoPreview() this.$nextTick(() => { this.resize(this.$refs['textarea']) }) diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index e3d8d087..0cebd36e 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -16,6 +16,44 @@ @drop.stop="fileDrop" /> <div class="form-group"> + <div class="preview-heading faint"> + <a + class="preview-toggle faint" + @click.stop.prevent="togglePreview" + > + {{ $t('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> <i18n v-if="!$store.state.users.currentUser.locked && newStatus.visibility == 'private'" path="post_status.account_not_locked_warning" @@ -77,7 +115,6 @@ class="form-control" > <input - v-model="newStatus.spoilerText" type="text" :placeholder="$t('post_status.content_warning')" @@ -303,14 +340,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 +365,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 +414,12 @@ } } + .visibility-tray { + display: flex; + justify-content: space-between; + padding-top: 5px; + } + .media-upload-icon, .poll-icon, .emoji-icon { font-size: 26px; flex: 1; @@ -408,7 +485,7 @@ flex-direction: column; } - .attachments { + .media-upload-wrapper .attachments { padding: 0 0.5em; .attachment { diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 8237be6c..f6b5dd6f 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -377,9 +377,6 @@ $status-margin: 0.75em; } .status-el { - overflow-wrap: break-word; - word-wrap: break-word; - word-break: break-word; border-left-width: 0px; min-width: 0; border-color: $fallback--border; diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue index 3460c2fa..40c28b5c 100644 --- a/src/components/status_content/status_content.vue +++ b/src/components/status_content/status_content.vue @@ -217,6 +217,9 @@ $status-margin: 0.75em; font-family: var(--postFont, sans-serif); line-height: 1.4em; white-space: pre-wrap; + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-word; blockquote { margin: 0.2em 0 0.2em 2em; diff --git a/src/components/user_panel/user_panel.vue b/src/components/user_panel/user_panel.vue index 1db4f648..5685916a 100644 --- a/src/components/user_panel/user_panel.vue +++ b/src/components/user_panel/user_panel.vue @@ -10,9 +10,7 @@ :hide-bio="true" rounded="top" /> - <div class="panel-footer"> - <PostStatusForm /> - </div> + <PostStatusForm /> </div> <auth-form v-else diff --git a/src/i18n/en.json b/src/i18n/en.json index 8f13dfe9..d7938405 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -644,6 +644,8 @@ "copy_link": "Copy link to status", "thread_muted": "Thread muted", "thread_muted_and_words": ", has words:", + "preview": "Preview", + "preview_empty": "Empty", "show_full_subject": "Show full subject", "hide_full_subject": "Hide full subject", "show_content": "Show content", diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index c9ec88b7..174add70 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -645,7 +645,8 @@ const postStatus = ({ poll, mediaIds = [], inReplyToStatusId, - contentType + contentType, + preview }) => { const form = new FormData() const pollOptions = poll.options || [] @@ -675,6 +676,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, @@ -682,13 +686,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', |
