logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://anongit.hacktivis.me/git/pleroma-fe.git/
commit: fb8747729850a683995efc0c9823455b13e37e66
parent b27f696d77f772b9bf0c2e5376c27e315362c942
Author: tusooa <tusooa@kazv.moe>
Date:   Thu,  6 Apr 2023 14:15:57 -0400

Allow confirmation on closing reply form

Diffstat:

Asrc/components/draft_closer/draft_closer.js48++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/draft_closer/draft_closer.vue43+++++++++++++++++++++++++++++++++++++++++++
Msrc/components/post_status_form/post_status_form.js45+++++++++++++++++++++++++++++++++++----------
Msrc/components/post_status_form/post_status_form.vue5+++++
Msrc/components/settings_modal/tabs/general_tab.js5+++++
Msrc/components/settings_modal/tabs/general_tab.vue9+++++++++
Msrc/components/status/status.js7+++++++
Msrc/components/status/status.vue4+++-
Msrc/i18n/en.json11++++++++++-
Msrc/modules/config.js6++++--
Msrc/modules/instance.js1+
11 files changed, 170 insertions(+), 14 deletions(-)

diff --git a/src/components/draft_closer/draft_closer.js b/src/components/draft_closer/draft_closer.js @@ -0,0 +1,48 @@ +import DialogModal from 'src/components/dialog_modal/dialog_modal.vue' + +const DraftCloser = { + data () { + return { + showing: false + } + }, + components: { + DialogModal + }, + emits: [ + 'save', + 'discard' + ], + computed: { + action () { + return this.$store.getters.mergedConfig.unsavedPostAction + }, + shouldConfirm () { + return this.action === 'confirm' + } + }, + methods: { + requestClose () { + if (this.shouldConfirm) { + this.showing = true + } else if (this.action === 'save') { + this.save() + } else { + this.discard() + } + }, + save () { + this.$emit('save') + this.showing = false + }, + discard () { + this.$emit('discard') + this.showing = false + }, + cancel () { + this.showing = false + } + } +} + +export default DraftCloser diff --git a/src/components/draft_closer/draft_closer.vue b/src/components/draft_closer/draft_closer.vue @@ -0,0 +1,43 @@ +<template> + <teleport to="#modal"> + <dialog-modal + v-if="showing" + v-body-scroll-lock="true" + class="confirm-modal" + :on-cancel="cancel" + > + <template #header> + <span> + {{ $t('post_status.close_confirm_title') }} + </span> + </template> + + {{ $t('post_status.close_confirm') }} + + <template #footer> + <button + class="btn button-default" + @click.prevent="save" + > + {{ $t('post_status.close_confirm_save_button') }} + </button> + + <button + class="btn button-default" + @click.prevent="discard" + > + {{ $t('post_status.close_confirm_discard_button') }} + </button> + + <button + class="btn button-default" + @click.prevent="cancel" + > + {{ $t('post_status.close_confirm_continue_composing_button') }} + </button> + </template> + </dialog-modal> + </teleport> +</template> + +<script src="./draft_closer.js"></script> diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js @@ -16,6 +16,7 @@ import suggestor from '../emoji_input/suggestor.js' import { mapGetters, mapState } from 'vuex' import Checkbox from '../checkbox/checkbox.vue' import Select from '../select/select.vue' +import DraftCloser from 'src/components/draft_closer/draft_closer.vue' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -108,7 +109,8 @@ const PostStatusForm = { 'posted', 'resize', 'mediaplay', - 'mediapause' + 'mediapause', + 'can-close' ], components: { MediaUpload, @@ -119,7 +121,8 @@ const PostStatusForm = { Select, Attachment, StatusContent, - Gallery + Gallery, + DraftCloser }, mounted () { this.updateIdempotencyKey() @@ -205,7 +208,8 @@ const PostStatusForm = { previewLoading: false, emojiInputShown: false, idempotencyKey: '', - saveInhibited: true + saveInhibited: true, + savable: false } }, computed: { @@ -320,9 +324,9 @@ const PostStatusForm = { return false }, - debouncedSaveDraft () { - return debounce(this.saveDraft, 3000) - }, + // debouncedSaveDraft () { + // return debounce(this.saveDraft, 3000) + // }, pollFormVisible () { return this.newStatus.hasPoll }, @@ -340,13 +344,14 @@ const PostStatusForm = { } }, beforeUnmount () { - this.saveDraft() + // this.saveDraft() }, methods: { statusChanged () { this.autoPreview() this.updateIdempotencyKey() - this.debouncedSaveDraft() + // this.debouncedSaveDraft() + this.savable = true this.saveInhibited = false }, clearStatus () { @@ -375,6 +380,7 @@ const PostStatusForm = { el.style.height = undefined this.error = null if (this.preview) this.previewStatus() + this.savable = false }, async postStatus (event, newStatus, opts = {}) { if (this.posting && !this.optimisticPosting) { return } @@ -712,16 +718,18 @@ const PostStatusForm = { this.newStatus.files?.length || this.newStatus.hasPoll )) { - this.$store.dispatch('addOrSaveDraft', { draft: this.newStatus }) + return this.$store.dispatch('addOrSaveDraft', { draft: this.newStatus }) .then(id => { if (this.newStatus.id !== id) { this.newStatus.id = id + this.savable = false } }) } + return Promise.resolve() }, abandonDraft () { - this.$store.dispatch('abandonDraft', { id: this.newStatus.id }) + return this.$store.dispatch('abandonDraft', { id: this.newStatus.id }) }, getDraft (statusType, refId) { const maybeDraft = this.$store.state.drafts.drafts[this.draftId] @@ -735,6 +743,23 @@ const PostStatusForm = { } } // No draft available, fall back + }, + requestClose () { + if (!this.savable) { + this.$emit('can-close') + } else { + this.$refs.draftCloser.requestClose() + } + }, + saveAndCloseDraft () { + this.saveDraft().then(() => { + this.$emit('can-close') + }) + }, + discardAndCloseDraft () { + this.abandonDraft().then(() => { + this.$emit('can-close') + }) } } } diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue @@ -369,6 +369,11 @@ </Checkbox> </div> </form> + <DraftCloser + ref="draftCloser" + @save="saveAndCloseDraft" + @discard="discardAndCloseDraft" + /> </div> </template> diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js @@ -45,6 +45,11 @@ const GeneralTab = { value: mode, label: this.$t(`settings.user_popover_avatar_action_${mode}`) })), + unsavedPostActionOptions: ['save', 'discard', 'confirm'].map(mode => ({ + key: mode, + value: mode, + label: this.$t(`settings.unsaved_post_action_${mode}`) + })), loopSilentAvailable: // Firefox Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') || diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue @@ -453,6 +453,15 @@ {{ $t('settings.autocomplete_select_first') }} </BooleanSetting> </li> + <li> + <ChoiceSetting + id="unsavedPostAction" + path="unsavedPostAction" + :options="unsavedPostActionOptions" + > + {{ $t('settings.unsaved_post_action') }} + </ChoiceSetting> + </li> </ul> </div> </div> diff --git a/src/components/status/status.js b/src/components/status/status.js @@ -473,6 +473,13 @@ const Status = { }, toggleReplying () { this.$emit('interacted') + if (this.replying) { + this.$refs.postStatusForm.requestClose() + } else { + this.doToggleReplying() + } + }, + doToggleReplying () { controlledOrUncontrolledToggle(this, 'replying') }, gotoOriginal (id) { diff --git a/src/components/status/status.vue b/src/components/status/status.vue @@ -598,13 +598,15 @@ class="status-container reply-form" > <PostStatusForm + ref="postStatusForm" class="reply-body" :reply-to="status.id" :attentions="status.attentions" :replied-user="status.user" :copy-message-scope="status.visibility" :subject="replySubject" - @posted="toggleReplying" + @posted="doToggleReplying" + @can-close="doToggleReplying" /> </div> </template> diff --git a/src/i18n/en.json b/src/i18n/en.json @@ -308,7 +308,12 @@ "private": "Followers-only - post to followers only", "public": "Public - post to public timelines", "unlisted": "Unlisted - do not post to public timelines" - } + }, + "close_confirm_title": "Closing post form", + "close_confirm": "What do you want to do with your current writing?", + "close_confirm_save_button": "Save", + "close_confirm_discard_button": "Discard", + "close_confirm_continue_composing_button": "Continue composing" }, "registration": { "bio_optional": "Bio (optional)", @@ -505,6 +510,10 @@ "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.", "pad_emoji": "Pad emoji with spaces when adding from picker", "autocomplete_select_first": "Automatically select the first candidate when autocomplete results are available", + "unsaved_post_action": "When you try to close an unsaved posting form", + "unsaved_post_action_save": "Save it to drafts", + "unsaved_post_action_discard": "Discard it", + "unsaved_post_action_confirm": "Ask every time", "emoji_reactions_on_timeline": "Show emoji reactions on timeline", "emoji_reactions_scale": "Reactions scale factor", "export_theme": "Save preset", diff --git a/src/modules/config.js b/src/modules/config.js @@ -30,7 +30,8 @@ export const multiChoiceProperties = [ 'conversationDisplay', // tree | linear 'conversationOtherRepliesButton', // below | inside 'mentionLinkDisplay', // short | full_for_remote | full - 'userPopoverAvatarAction' // close | zoom | open + 'userPopoverAvatarAction', // close | zoom | open + 'unsavedPostAction' // save | discard | confirm ] export const defaultState = { @@ -180,7 +181,8 @@ export const defaultState = { autocompleteSelect: undefined, // instance default closingDrawerMarksAsSeen: undefined, // instance default unseenAtTop: undefined, // instance default - ignoreInactionableSeen: undefined // instance default + ignoreInactionableSeen: undefined, // instance default + unsavedPostAction: undefined // instance default } // caching the instance default properties diff --git a/src/modules/instance.js b/src/modules/instance.js @@ -119,6 +119,7 @@ const defaultState = { closingDrawerMarksAsSeen: true, unseenAtTop: false, ignoreInactionableSeen: false, + unsavedPostAction: 'confirm', // Nasty stuff customEmoji: [],