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:
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: [],