From 2c6ec37e6a796d2d7be7be58ac0e3c6c8bbd6a18 Mon Sep 17 00:00:00 2001 From: William Pitcock Date: Mon, 18 Feb 2019 05:03:26 +0000 Subject: media description support --- src/services/status_poster/status_poster.service.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/services/status_poster/status_poster.service.js') diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index 9e904d3a..090ff673 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -1,7 +1,7 @@ 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', mediaDescriptions = {} }) => { const mediaIds = map(media, 'id') return apiService.postStatus({ @@ -13,7 +13,8 @@ const postStatus = ({ store, status, spoilerText, visibility, sensitive, poll, m mediaIds, inReplyToStatusId, contentType, - poll }) + poll, + mediaDescriptions }) .then((data) => { if (!data.error) { store.dispatch('addNewStatuses', { -- cgit v1.2.3-70-g09d2 From 223fabfe90512122ee9d51c22deb01241ba931cd Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Sun, 28 Jun 2020 12:16:41 +0300 Subject: add rich text preview --- .../post_status_form/post_status_form.js | 52 +++++++++- .../post_status_form/post_status_form.vue | 113 +++++++++++++++++++-- src/i18n/en.json | 5 +- src/services/api/api.service.js | 14 ++- .../status_poster/status_poster.service.js | 19 +++- 5 files changed, 180 insertions(+), 23 deletions(-) (limited to 'src/services/status_poster/status_poster.service.js') 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" />
+ + {{ $t('status.preview') }} + +
+ + + {{ $t('status.status_preview') }} + + + + {{ $t('status.preview_update') }} + + + + + +
+ {{ $t('general.loading') }} +
+
+ {{ preview.error }} +
+ +
{ 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', -- cgit v1.2.3-70-g09d2 From b4709f93d46d1d88eb5cb0104b3ae07bd5cd823c Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Tue, 7 Jul 2020 09:07:20 +0300 Subject: add more ways to set description --- .../post_status_form/post_status_form.js | 73 ++++++++++++++-------- .../post_status_form/post_status_form.vue | 1 + src/services/api/api.service.js | 16 ++++- .../status_poster/status_poster.service.js | 15 +++-- 4 files changed, 71 insertions(+), 34 deletions(-) (limited to 'src/services/status_poster/status_poster.service.js') diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 6bbf6cf1..6c0947af 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -6,7 +6,7 @@ import PollForm from '../poll/poll_form.vue' import Attachment from '../attachment/attachment.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' @@ -169,7 +169,7 @@ const PostStatusForm = { ...mapGetters(['mergedConfig']) }, methods: { - postStatus (newStatus) { + async postStatus (newStatus) { if (this.posting) { return } if (this.submitDisabled) { return } @@ -187,41 +187,43 @@ const PostStatusForm = { } this.posting = true - statusPoster.postStatus({ + + await this.setAllMediaDescriptions() + + const data = await statusPoster.postStatus({ status: newStatus.status, spoilerText: newStatus.spoilerText || null, visibility: newStatus.visibility, sensitive: newStatus.nsfw, media: newStatus.files, - mediaDescriptions: newStatus.mediaDescriptions || {}, store: this.$store, inReplyToStatusId: this.replyTo, contentType: newStatus.contentType, poll - }).then((data) => { - if (!data.error) { - this.newStatus = { - status: '', - spoilerText: '', - files: [], - mediaDescriptions: {}, - 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 - } else { - this.error = data.error - } - this.posting = false }) + + 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 + } else { + this.error = data.error + } + + this.posting = false }, addMediaFile (fileInfo) { this.newStatus.files.push(fileInfo) @@ -393,6 +395,23 @@ const PostStatusForm = { }, dismissScopeNotice () { this.$store.dispatch('setOption', { name: 'hideScopeNotice', value: true }) + }, + debounceSendDescription: debounce(function (id, description) { + statusPoster.setMediaDescription({ store: this.$store, id, description }) + }, 500), + updateMediaDescription (fileId, e) { + const description = e.target.value + this.newStatus.mediaDescriptions[fileId] = description + this.debounceSendDescription(fileId, description) + }, + 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 442b9297..ea7b5b80 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -255,6 +255,7 @@ v-model="newStatus.mediaDescriptions[file.id]" type="text" :placeholder="$t('post_status.media_description')" + @blur="setMediaDescription(file.id)" @keydown.enter.prevent="" >
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index c4d7fb53..067acbe9 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -645,8 +645,7 @@ const postStatus = ({ poll, mediaIds = [], inReplyToStatusId, - contentType, - mediaDescriptions + contentType }) => { const form = new FormData() const pollOptions = poll.options || [] @@ -673,7 +672,6 @@ const postStatus = ({ form.append('poll[options][]', option) }) } - form.append('descriptions', JSON.stringify(mediaDescriptions)) if (inReplyToStatusId) { form.append('in_reply_to_id', inReplyToStatusId) } @@ -712,6 +710,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) @@ -1181,6 +1190,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 index 090ff673..7f5f501c 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -1,7 +1,7 @@ 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', mediaDescriptions = {} }) => { +const postStatus = ({ store, status, spoilerText, visibility, sensitive, poll, media = [], inReplyToStatusId = undefined, contentType = 'text/plain' }) => { const mediaIds = map(media, 'id') return apiService.postStatus({ @@ -13,8 +13,8 @@ const postStatus = ({ store, status, spoilerText, visibility, sensitive, poll, m mediaIds, inReplyToStatusId, contentType, - poll, - mediaDescriptions }) + poll + }) .then((data) => { if (!data.error) { store.dispatch('addNewStatuses', { @@ -39,9 +39,16 @@ const uploadMedia = ({ store, formData }) => { 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 -- cgit v1.2.3-70-g09d2 From 89a677f5e822456f0e8ec510ed6193a8e1783297 Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Wed, 15 Jul 2020 16:19:57 +0300 Subject: add basic idempotency support --- .../post_status_form/post_status_form.js | 59 +++++++++++++--------- src/services/api/api.service.js | 10 +++- .../status_poster/status_poster.service.js | 11 +++- 3 files changed, 52 insertions(+), 28 deletions(-) (limited to 'src/services/status_poster/status_poster.service.js') diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 1c0accac..29841261 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -61,6 +61,7 @@ const PostStatusForm = { StatusContent }, mounted () { + this.updateIdempotencyKey() this.resize(this.$refs.textarea) if (this.replyTo) { @@ -111,7 +112,8 @@ const PostStatusForm = { dropStopTimeout: null, preview: null, previewLoading: false, - emojiInputShown: false + emojiInputShown: false, + idempotencyKey: '' } }, computed: { @@ -214,6 +216,32 @@ const PostStatusForm = { } }, methods: { + clearStatus () { + const newStatus = this.newStatus + this.newStatus = { + status: '', + spoilerText: '', + files: [], + visibility: newStatus.visibility, + contentType: newStatus.contentType, + poll: {}, + mediaDescriptions: {} + } + this.pollFormVisible = false + this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile() + this.clearPollForm() + if (this.preserveFocus) { + this.$nextTick(() => { + this.$refs.textarea.focus() + }) + } + let el = this.$el.querySelector('textarea') + el.style.height = 'auto' + el.style.height = undefined + this.error = null + this.updateIdempotencyKey() + if (this.preview) this.previewStatus() + }, async postStatus (event, newStatus, opts = {}) { if (this.posting) { return } if (this.disableSubmit) { return } @@ -253,36 +281,16 @@ const PostStatusForm = { store: this.$store, inReplyToStatusId: this.replyTo, contentType: newStatus.contentType, - poll + poll, + idempotencyKey: this.idempotencyKey } const postHandler = this.postHandler ? this.postHandler : statusPoster.postStatus postHandler(postingOptions).then((data) => { if (!data.error) { - this.newStatus = { - status: '', - spoilerText: '', - files: [], - visibility: newStatus.visibility, - contentType: newStatus.contentType, - poll: {}, - mediaDescriptions: {} - } - this.pollFormVisible = false - this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile() - this.clearPollForm() + this.clearStatus() this.$emit('posted', data) - if (this.preserveFocus) { - this.$nextTick(() => { - this.$refs.textarea.focus() - }) - } - 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 } @@ -530,6 +538,9 @@ const PostStatusForm = { }, handleEmojiInputShow (value) { this.emojiInputShown = value + }, + updateIdempotencyKey () { + this.idempotencyKey = Date.now().toString() } } } diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 40ea5bd9..da519001 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -631,7 +631,8 @@ const postStatus = ({ mediaIds = [], inReplyToStatusId, contentType, - preview + preview, + idempotencyKey }) => { const form = new FormData() const pollOptions = poll.options || [] @@ -665,10 +666,15 @@ const postStatus = ({ form.append('preview', 'true') } + let postHeaders = authHeaders(credentials) + if (idempotencyKey) { + postHeaders['idempotency-key'] = idempotencyKey + } + return fetch(MASTODON_POST_STATUS_URL, { body: form, method: 'POST', - headers: authHeaders(credentials) + headers: postHeaders }) .then((response) => { return response.json() diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index ac469175..812f74d5 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -11,7 +11,8 @@ const postStatus = ({ media = [], inReplyToStatusId = undefined, contentType = 'text/plain', - preview = false + preview = false, + idempotencyKey = '' }) => { const mediaIds = map(media, 'id') @@ -25,9 +26,14 @@ const postStatus = ({ inReplyToStatusId, contentType, poll, - preview + preview, + idempotencyKey }) .then((data) => { + return { + error: 'test' + } + /* if (!data.error && !preview) { store.dispatch('addNewStatuses', { statuses: [data], @@ -37,6 +43,7 @@ const postStatus = ({ }) } return data + */ }) .catch((err) => { return { -- cgit v1.2.3-70-g09d2 From 0c7c24d3d1e4580d6ce03f71b9381aa3f6b689cb Mon Sep 17 00:00:00 2001 From: Shpuld Shpuldson Date: Thu, 16 Jul 2020 10:18:18 +0300 Subject: make idempotency watch entire status --- src/components/post_status_form/post_status_form.js | 16 +++++++++------- src/services/status_poster/status_poster.service.js | 5 ----- 2 files changed, 9 insertions(+), 12 deletions(-) (limited to 'src/services/status_poster/status_poster.service.js') diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index 29841261..c70c2232 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -208,14 +208,18 @@ const PostStatusForm = { }) }, watch: { - 'newStatus.contentType': function () { - this.autoPreview() - }, - 'newStatus.spoilerText': function () { - this.autoPreview() + 'newStatus': { + deep: true, + handler () { + this.statusChanged() + } } }, methods: { + statusChanged () { + this.autoPreview() + this.updateIdempotencyKey() + }, clearStatus () { const newStatus = this.newStatus this.newStatus = { @@ -239,7 +243,6 @@ const PostStatusForm = { el.style.height = 'auto' el.style.height = undefined this.error = null - this.updateIdempotencyKey() if (this.preview) this.previewStatus() }, async postStatus (event, newStatus, opts = {}) { @@ -407,7 +410,6 @@ const PostStatusForm = { } }, onEmojiInputInput (e) { - this.autoPreview() this.$nextTick(() => { this.resize(this.$refs['textarea']) }) diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index 812f74d5..f09196aa 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -30,10 +30,6 @@ const postStatus = ({ idempotencyKey }) .then((data) => { - return { - error: 'test' - } - /* if (!data.error && !preview) { store.dispatch('addNewStatuses', { statuses: [data], @@ -43,7 +39,6 @@ const postStatus = ({ }) } return data - */ }) .catch((err) => { return { -- cgit v1.2.3-70-g09d2