diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/attachment/attachment.js | 3 | ||||
| -rw-r--r-- | src/components/conversation/conversation.js | 16 | ||||
| -rw-r--r-- | src/components/edit_status_modal/edit_status_modal.js | 75 | ||||
| -rw-r--r-- | src/components/edit_status_modal/edit_status_modal.vue | 48 | ||||
| -rw-r--r-- | src/components/extra_buttons/extra_buttons.js | 31 | ||||
| -rw-r--r-- | src/components/extra_buttons/extra_buttons.vue | 22 | ||||
| -rw-r--r-- | src/components/post_status_form/post_status_form.js | 48 | ||||
| -rw-r--r-- | src/components/post_status_form/post_status_form.vue | 18 | ||||
| -rw-r--r-- | src/components/status/status.js | 6 | ||||
| -rw-r--r-- | src/components/status/status.scss | 3 | ||||
| -rw-r--r-- | src/components/status/status.vue | 24 | ||||
| -rw-r--r-- | src/components/status_history_modal/status_history_modal.js | 60 | ||||
| -rw-r--r-- | src/components/status_history_modal/status_history_modal.vue | 46 |
13 files changed, 385 insertions, 15 deletions
diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index d62a4adc..5dc50475 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -129,6 +129,9 @@ const Attachment = { ...mapGetters(['mergedConfig']) }, watch: { + 'attachment.description' (newVal) { + this.localDescription = newVal + }, localDescription (newVal) { this.onEdit(newVal) } diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 3b540cac..47509e86 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -1,6 +1,8 @@ import { reduce, filter, findIndex, clone, get } from 'lodash' import Status from '../status/status.vue' import ThreadTree from '../thread_tree/thread_tree.vue' +import { WSConnectionStatus } from '../../services/api/api.service.js' +import { mapGetters, mapState } from 'vuex' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -77,6 +79,9 @@ const conversation = { const maxDepth = this.$store.getters.mergedConfig.maxDepthInThread - 2 return maxDepth >= 1 ? maxDepth : 1 }, + streamingEnabled () { + return this.mergedConfig.useStreamingApi && this.mastoUserSocketStatus === WSConnectionStatus.JOINED + }, displayStyle () { return this.$store.getters.mergedConfig.conversationDisplay }, @@ -339,7 +344,11 @@ const conversation = { }, maybeHighlight () { return this.isExpanded ? this.highlight : null - } + }, + ...mapGetters(['mergedConfig']), + ...mapState({ + mastoUserSocketStatus: state => state.api.mastoUserSocketStatus + }) }, components: { Status, @@ -395,6 +404,11 @@ const conversation = { setHighlight (id) { if (!id) return this.highlight = id + + if (!this.streamingEnabled) { + this.$store.dispatch('fetchStatus', id) + } + this.$store.dispatch('fetchFavsAndRepeats', id) this.$store.dispatch('fetchEmojiReactionsBy', id) }, diff --git a/src/components/edit_status_modal/edit_status_modal.js b/src/components/edit_status_modal/edit_status_modal.js new file mode 100644 index 00000000..75adfea7 --- /dev/null +++ b/src/components/edit_status_modal/edit_status_modal.js @@ -0,0 +1,75 @@ +import PostStatusForm from '../post_status_form/post_status_form.vue' +import Modal from '../modal/modal.vue' +import statusPosterService from '../../services/status_poster/status_poster.service.js' +import get from 'lodash/get' + +const EditStatusModal = { + components: { + PostStatusForm, + Modal + }, + data () { + return { + resettingForm: false + } + }, + computed: { + isLoggedIn () { + return !!this.$store.state.users.currentUser + }, + modalActivated () { + return this.$store.state.editStatus.modalActivated + }, + isFormVisible () { + return this.isLoggedIn && !this.resettingForm && this.modalActivated + }, + params () { + return this.$store.state.editStatus.params || {} + } + }, + watch: { + params (newVal, oldVal) { + if (get(newVal, 'statusId') !== get(oldVal, 'statusId')) { + this.resettingForm = true + this.$nextTick(() => { + this.resettingForm = false + }) + } + }, + isFormVisible (val) { + if (val) { + this.$nextTick(() => this.$el && this.$el.querySelector('textarea').focus()) + } + } + }, + methods: { + doEditStatus ({ status, spoilerText, sensitive, media, contentType, poll }) { + const params = { + store: this.$store, + statusId: this.$store.state.editStatus.params.statusId, + status, + spoilerText, + sensitive, + poll, + media, + contentType + } + + return statusPosterService.editStatus(params) + .then((data) => { + return data + }) + .catch((err) => { + console.error('Error editing status', err) + return { + error: err.message + } + }) + }, + closeModal () { + this.$store.dispatch('closeEditStatusModal') + } + } +} + +export default EditStatusModal diff --git a/src/components/edit_status_modal/edit_status_modal.vue b/src/components/edit_status_modal/edit_status_modal.vue new file mode 100644 index 00000000..00dde7de --- /dev/null +++ b/src/components/edit_status_modal/edit_status_modal.vue @@ -0,0 +1,48 @@ +<template> + <Modal + v-if="isFormVisible" + class="edit-form-modal-view" + @backdropClicked="closeModal" + > + <div class="edit-form-modal-panel panel"> + <div class="panel-heading"> + {{ $t('post_status.edit_status') }} + </div> + <PostStatusForm + class="panel-body" + v-bind="params" + @posted="closeModal" + :disablePolls="true" + :disableVisibilitySelector="true" + :post-handler="doEditStatus" + /> + </div> + </Modal> +</template> + +<script src="./edit_status_modal.js"></script> + +<style lang="scss"> +.modal-view.edit-form-modal-view { + align-items: flex-start; +} +.edit-form-modal-panel { + flex-shrink: 0; + margin-top: 25%; + margin-bottom: 2em; + width: 100%; + max-width: 700px; + + @media (orientation: landscape) { + margin-top: 8%; + } + + .form-bottom-left { + max-width: 6.5em; + + .emoji-icon { + justify-content: right; + } + } +} +</style> diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js index 22ffb65a..8c6aca9b 100644 --- a/src/components/extra_buttons/extra_buttons.js +++ b/src/components/extra_buttons/extra_buttons.js @@ -6,7 +6,8 @@ import { faEyeSlash, faThumbtack, faShareAlt, - faExternalLinkAlt + faExternalLinkAlt, + faHistory } from '@fortawesome/free-solid-svg-icons' import { faBookmark as faBookmarkReg, @@ -21,7 +22,8 @@ library.add( faThumbtack, faShareAlt, faExternalLinkAlt, - faFlag + faFlag, + faHistory ) const ExtraButtons = { @@ -71,6 +73,25 @@ const ExtraButtons = { }, reportStatus () { this.$store.dispatch('openUserReportingModal', { userId: this.status.user.id, statusIds: [this.status.id] }) + }, + editStatus () { + this.$store.dispatch('fetchStatusSource', { id: this.status.id }) + .then(data => this.$store.dispatch('openEditStatusModal', { + statusId: this.status.id, + subject: data.spoiler_text, + statusText: data.text, + statusIsSensitive: this.status.nsfw, + statusPoll: this.status.poll, + statusFiles: [...this.status.attachments], + visibility: this.status.visibility, + statusContentType: data.content_type + })) + }, + showStatusHistory () { + const originalStatus = { ...this.status } + const stripFieldsList = ['attachments', 'created_at', 'emojis', 'text', 'raw_html', 'nsfw', 'poll', 'summary', 'summary_raw_html'] + stripFieldsList.forEach(p => delete originalStatus[p]) + this.$store.dispatch('openStatusHistoryModal', originalStatus) } }, computed: { @@ -94,7 +115,11 @@ const ExtraButtons = { }, statusLink () { return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}` - } + }, + isEdited () { + return this.status.edited_at !== null + }, + editingAvailable () { return this.$store.state.instance.editingAvailable } } } diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue index 2c893bf3..b3e37dff 100644 --- a/src/components/extra_buttons/extra_buttons.vue +++ b/src/components/extra_buttons/extra_buttons.vue @@ -76,6 +76,28 @@ </button> </template> <button + v-if="ownStatus && editingAvailable" + class="button-default dropdown-item dropdown-item-icon" + @click.prevent="editStatus" + @click="close" + > + <FAIcon + fixed-width + icon="pen" + /><span>{{ $t("status.edit") }}</span> + </button> + <button + v-if="isEdited && editingAvailable" + class="button-default dropdown-item dropdown-item-icon" + @click.prevent="showStatusHistory" + @click="close" + > + <FAIcon + fixed-width + icon="history" + /><span>{{ $t("status.status_history") }}</span> + </button> + <button v-if="canDelete" class="button-default dropdown-item dropdown-item-icon" @click.prevent="deleteStatus" diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js index c0d80b20..77f73d04 100644 --- a/src/components/post_status_form/post_status_form.js +++ b/src/components/post_status_form/post_status_form.js @@ -55,6 +55,14 @@ const pxStringToNumber = (str) => { const PostStatusForm = { props: [ + 'statusId', + 'statusText', + 'statusIsSensitive', + 'statusPoll', + 'statusFiles', + 'statusMediaDescriptions', + 'statusScope', + 'statusContentType', 'replyTo', 'repliedUser', 'attentions', @@ -62,6 +70,7 @@ const PostStatusForm = { 'subject', 'disableSubject', 'disableScopeSelector', + 'disableVisibilitySelector', 'disableNotice', 'disableLockWarning', 'disablePolls', @@ -125,22 +134,38 @@ const PostStatusForm = { const { postContentType: contentType, sensitiveByDefault } = this.$store.getters.mergedConfig + let statusParams = { + spoilerText: this.subject || '', + status: statusText, + nsfw: !!sensitiveByDefault, + files: [], + poll: {}, + mediaDescriptions: {}, + visibility: scope, + contentType + } + + if (this.statusId) { + const statusContentType = this.statusContentType || contentType + statusParams = { + spoilerText: this.subject || '', + status: this.statusText || '', + nsfw: this.statusIsSensitive || !!sensitiveByDefault, + files: this.statusFiles || [], + poll: this.statusPoll || {}, + mediaDescriptions: this.statusMediaDescriptions || {}, + visibility: this.statusScope || scope, + contentType: statusContentType + } + } + return { dropFiles: [], uploadingFiles: false, error: null, posting: false, highlighted: 0, - newStatus: { - spoilerText: this.subject || '', - status: statusText, - nsfw: !!sensitiveByDefault, - files: [], - poll: {}, - mediaDescriptions: {}, - visibility: scope, - contentType - }, + newStatus: statusParams, caret: 0, pollFormVisible: false, showDropIcon: 'hide', @@ -236,6 +261,9 @@ const PostStatusForm = { uploadFileLimitReached () { return this.newStatus.files.length >= this.fileLimit }, + isEdit () { + return typeof this.statusId !== 'undefined' && this.statusId.trim() !== '' + }, ...mapGetters(['mergedConfig']), ...mapState({ mobileLayout: state => state.interface.mobileLayout diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue index 62613bd1..f65058f4 100644 --- a/src/components/post_status_form/post_status_form.vue +++ b/src/components/post_status_form/post_status_form.vue @@ -67,6 +67,13 @@ <span v-else>{{ $t('post_status.direct_warning_to_all') }}</span> </p> <div + v-if="isEdit" + class="visibility-notice edit-warning" + > + <p>{{ $t('post_status.edit_remote_warning') }}</p> + <p>{{ $t('post_status.edit_unsupported_warning') }}</p> + </div> + <div v-if="!disablePreview" class="preview-heading faint" > @@ -170,6 +177,7 @@ class="visibility-tray" > <scope-selector + v-if="!disableVisibilitySelector" :show-all="showAllScopes" :user-default="userDefaultScope" :original-scope="copyMessageScope" @@ -410,6 +418,16 @@ align-items: baseline; } + .visibility-notice.edit-warning { + > :first-child { + margin-top: 0; + } + + > :last-child { + margin-bottom: 0; + } + } + .media-upload-icon, .poll-icon, .emoji-icon { font-size: 1.85em; line-height: 1.1; diff --git a/src/components/status/status.js b/src/components/status/status.js index 384063a7..99865204 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -393,6 +393,12 @@ const Status = { }, visibilityLocalized () { return this.$i18n.t('general.scope_in_timeline.' + this.status.visibility) + }, + isEdited () { + return this.status.edited_at !== null + }, + editingAvailable () { + return this.$store.state.instance.editingAvailable } }, methods: { diff --git a/src/components/status/status.scss b/src/components/status/status.scss index b3ad3818..ada9841e 100644 --- a/src/components/status/status.scss +++ b/src/components/status/status.scss @@ -156,7 +156,8 @@ margin-right: 0.2em; } - & .heading-reply-row { + & .heading-reply-row, + & .heading-edited-row { position: relative; align-content: baseline; font-size: 0.85em; diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 967a966c..a13e5ab0 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -327,6 +327,30 @@ class="mentions-line" /> </div> + <div + v-if="isEdited && editingAvailable && !isPreview" + class="heading-edited-row" + > + <i18n-t + keypath="status.edited_at" + tag="span" + > + <template #time> + <i18n-t + keypath="time.in_past" + tag="span" + > + <template> + <Timeago + :time="status.edited_at" + :auto-update="60" + :long-format="true" + /> + </template> + </i18n-t> + </template> + </i18n-t> + </div> </div> <StatusContent diff --git a/src/components/status_history_modal/status_history_modal.js b/src/components/status_history_modal/status_history_modal.js new file mode 100644 index 00000000..3941a56f --- /dev/null +++ b/src/components/status_history_modal/status_history_modal.js @@ -0,0 +1,60 @@ +import { get } from 'lodash' +import Modal from '../modal/modal.vue' +import Status from '../status/status.vue' + +const StatusHistoryModal = { + components: { + Modal, + Status + }, + data () { + return { + statuses: [] + } + }, + computed: { + modalActivated () { + return this.$store.state.statusHistory.modalActivated + }, + params () { + return this.$store.state.statusHistory.params + }, + statusId () { + return this.params.id + }, + historyCount () { + return this.statuses.length + }, + history () { + return this.statuses + } + }, + watch: { + params (newVal, oldVal) { + const newStatusId = get(newVal, 'id') !== get(oldVal, 'id') + if (newStatusId) { + this.resetHistory() + } + + if (newStatusId || get(newVal, 'edited_at') !== get(oldVal, 'edited_at')) { + this.fetchStatusHistory() + } + } + }, + methods: { + resetHistory () { + this.statuses = [] + }, + fetchStatusHistory () { + this.$store.dispatch('fetchStatusHistory', this.params) + .then(data => { + this.statuses = data + }) + }, + closeModal () { + this.$store.dispatch('closeStatusHistoryModal') + } + } +} + +export default StatusHistoryModal diff --git a/src/components/status_history_modal/status_history_modal.vue b/src/components/status_history_modal/status_history_modal.vue new file mode 100644 index 00000000..d6680df2 --- /dev/null +++ b/src/components/status_history_modal/status_history_modal.vue @@ -0,0 +1,46 @@ +<template> + <Modal + v-if="modalActivated" + class="status-history-modal-view" + @backdropClicked="closeModal" + > + <div class="status-history-modal-panel panel"> + <div class="panel-heading"> + {{ $t('status.status_history') }} ({{ historyCount }}) + </div> + <div class="panel-body"> + <div + v-if="historyCount > 0" + class="history-body" + > + <status + v-for="status in history" + :key="status.id" + :statusoid="status" + :isPreview="true" + class="conversation-status status-fadein panel-body" + /> + </div> + </div> + </div> + </Modal> +</template> + +<script src="./status_history_modal.js"></script> + +<style lang="scss"> +.modal-view.status-history-modal-view { + align-items: flex-start; +} +.status-history-modal-panel { + flex-shrink: 0; + margin-top: 25%; + margin-bottom: 2em; + width: 100%; + max-width: 700px; + + @media (orientation: landscape) { + margin-top: 8%; + } +} +</style> |
