Allow confirmation on closing reply form
This commit is contained in:
parent
aa59adc81f
commit
f2bdc1c563
|
@ -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
|
|
@ -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>
|
|
@ -15,6 +15,7 @@ import suggestor from '../emoji_input/suggestor.js'
|
||||||
import { mapGetters, mapState } from 'vuex'
|
import { mapGetters, mapState } from 'vuex'
|
||||||
import Checkbox from '../checkbox/checkbox.vue'
|
import Checkbox from '../checkbox/checkbox.vue'
|
||||||
import Select from '../select/select.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 { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
|
@ -104,7 +105,8 @@ const PostStatusForm = {
|
||||||
'posted',
|
'posted',
|
||||||
'resize',
|
'resize',
|
||||||
'mediaplay',
|
'mediaplay',
|
||||||
'mediapause'
|
'mediapause',
|
||||||
|
'can-close'
|
||||||
],
|
],
|
||||||
components: {
|
components: {
|
||||||
MediaUpload,
|
MediaUpload,
|
||||||
|
@ -115,7 +117,8 @@ const PostStatusForm = {
|
||||||
Select,
|
Select,
|
||||||
Attachment,
|
Attachment,
|
||||||
StatusContent,
|
StatusContent,
|
||||||
Gallery
|
Gallery,
|
||||||
|
DraftCloser
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.updateIdempotencyKey()
|
this.updateIdempotencyKey()
|
||||||
|
@ -199,7 +202,8 @@ const PostStatusForm = {
|
||||||
previewLoading: false,
|
previewLoading: false,
|
||||||
emojiInputShown: false,
|
emojiInputShown: false,
|
||||||
idempotencyKey: '',
|
idempotencyKey: '',
|
||||||
saveInhibited: true
|
saveInhibited: true,
|
||||||
|
savable: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -290,9 +294,9 @@ const PostStatusForm = {
|
||||||
isEdit () {
|
isEdit () {
|
||||||
return typeof this.statusId !== 'undefined' && this.statusId.trim() !== ''
|
return typeof this.statusId !== 'undefined' && this.statusId.trim() !== ''
|
||||||
},
|
},
|
||||||
debouncedSaveDraft () {
|
// debouncedSaveDraft () {
|
||||||
return debounce(this.saveDraft, 3000)
|
// return debounce(this.saveDraft, 3000)
|
||||||
},
|
// },
|
||||||
pollFormVisible () {
|
pollFormVisible () {
|
||||||
return this.newStatus.hasPoll
|
return this.newStatus.hasPoll
|
||||||
},
|
},
|
||||||
|
@ -310,13 +314,14 @@ const PostStatusForm = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeUnmount () {
|
beforeUnmount () {
|
||||||
this.saveDraft()
|
// this.saveDraft()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
statusChanged () {
|
statusChanged () {
|
||||||
this.autoPreview()
|
this.autoPreview()
|
||||||
this.updateIdempotencyKey()
|
this.updateIdempotencyKey()
|
||||||
this.debouncedSaveDraft()
|
// this.debouncedSaveDraft()
|
||||||
|
this.savable = true
|
||||||
this.saveInhibited = false
|
this.saveInhibited = false
|
||||||
},
|
},
|
||||||
clearStatus () {
|
clearStatus () {
|
||||||
|
@ -344,6 +349,7 @@ const PostStatusForm = {
|
||||||
el.style.height = undefined
|
el.style.height = undefined
|
||||||
this.error = null
|
this.error = null
|
||||||
if (this.preview) this.previewStatus()
|
if (this.preview) this.previewStatus()
|
||||||
|
this.savable = false
|
||||||
},
|
},
|
||||||
async postStatus (event, newStatus, opts = {}) {
|
async postStatus (event, newStatus, opts = {}) {
|
||||||
if (this.posting && !this.optimisticPosting) { return }
|
if (this.posting && !this.optimisticPosting) { return }
|
||||||
|
@ -678,16 +684,18 @@ const PostStatusForm = {
|
||||||
this.newStatus.files?.length ||
|
this.newStatus.files?.length ||
|
||||||
this.newStatus.hasPoll
|
this.newStatus.hasPoll
|
||||||
)) {
|
)) {
|
||||||
this.$store.dispatch('addOrSaveDraft', { draft: this.newStatus })
|
return this.$store.dispatch('addOrSaveDraft', { draft: this.newStatus })
|
||||||
.then(id => {
|
.then(id => {
|
||||||
if (this.newStatus.id !== id) {
|
if (this.newStatus.id !== id) {
|
||||||
this.newStatus.id = id
|
this.newStatus.id = id
|
||||||
|
this.savable = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
return Promise.resolve()
|
||||||
},
|
},
|
||||||
abandonDraft () {
|
abandonDraft () {
|
||||||
this.$store.dispatch('abandonDraft', { id: this.newStatus.id })
|
return this.$store.dispatch('abandonDraft', { id: this.newStatus.id })
|
||||||
},
|
},
|
||||||
getDraft (statusType, refId) {
|
getDraft (statusType, refId) {
|
||||||
const maybeDraft = this.$store.state.drafts.drafts[this.draftId]
|
const maybeDraft = this.$store.state.drafts.drafts[this.draftId]
|
||||||
|
@ -701,6 +709,23 @@ const PostStatusForm = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// No draft available, fall back
|
// 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')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -339,6 +339,11 @@
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<DraftCloser
|
||||||
|
ref="draftCloser"
|
||||||
|
@save="saveAndCloseDraft"
|
||||||
|
@discard="discardAndCloseDraft"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,11 @@ const GeneralTab = {
|
||||||
value: mode,
|
value: mode,
|
||||||
label: this.$t(`settings.user_popover_avatar_action_${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:
|
loopSilentAvailable:
|
||||||
// Firefox
|
// Firefox
|
||||||
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
|
Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') ||
|
||||||
|
|
|
@ -518,6 +518,15 @@
|
||||||
{{ $t('settings.autocomplete_select_first') }}
|
{{ $t('settings.autocomplete_select_first') }}
|
||||||
</BooleanSetting>
|
</BooleanSetting>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<ChoiceSetting
|
||||||
|
id="unsavedPostAction"
|
||||||
|
path="unsavedPostAction"
|
||||||
|
:options="unsavedPostActionOptions"
|
||||||
|
>
|
||||||
|
{{ $t('settings.unsaved_post_action') }}
|
||||||
|
</ChoiceSetting>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -423,6 +423,13 @@ const Status = {
|
||||||
this.error = undefined
|
this.error = undefined
|
||||||
},
|
},
|
||||||
toggleReplying () {
|
toggleReplying () {
|
||||||
|
if (this.replying) {
|
||||||
|
this.$refs.postStatusForm.requestClose()
|
||||||
|
} else {
|
||||||
|
this.doToggleReplying()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doToggleReplying () {
|
||||||
controlledOrUncontrolledToggle(this, 'replying')
|
controlledOrUncontrolledToggle(this, 'replying')
|
||||||
},
|
},
|
||||||
gotoOriginal (id) {
|
gotoOriginal (id) {
|
||||||
|
|
|
@ -496,13 +496,15 @@
|
||||||
class="status-container reply-form"
|
class="status-container reply-form"
|
||||||
>
|
>
|
||||||
<PostStatusForm
|
<PostStatusForm
|
||||||
|
ref="postStatusForm"
|
||||||
class="reply-body"
|
class="reply-body"
|
||||||
:reply-to="status.id"
|
:reply-to="status.id"
|
||||||
:attentions="status.attentions"
|
:attentions="status.attentions"
|
||||||
:replied-user="status.user"
|
:replied-user="status.user"
|
||||||
:copy-message-scope="status.visibility"
|
:copy-message-scope="status.visibility"
|
||||||
:subject="replySubject"
|
:subject="replySubject"
|
||||||
@posted="toggleReplying"
|
@posted="doToggleReplying"
|
||||||
|
@can-close="doToggleReplying"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -296,7 +296,12 @@
|
||||||
"private": "Followers-only - post to followers only",
|
"private": "Followers-only - post to followers only",
|
||||||
"public": "Public - post to public timelines",
|
"public": "Public - post to public timelines",
|
||||||
"unlisted": "Unlisted - do not 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": {
|
"registration": {
|
||||||
"bio_optional": "Bio (optional)",
|
"bio_optional": "Bio (optional)",
|
||||||
|
@ -467,6 +472,10 @@
|
||||||
"avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
|
"avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
|
||||||
"pad_emoji": "Pad emoji with spaces when adding from picker",
|
"pad_emoji": "Pad emoji with spaces when adding from picker",
|
||||||
"autocomplete_select_first": "Automatically select the first candidate when autocomplete results are available",
|
"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_on_timeline": "Show emoji reactions on timeline",
|
||||||
"emoji_reactions_scale": "Reactions scale factor",
|
"emoji_reactions_scale": "Reactions scale factor",
|
||||||
"export_theme": "Save preset",
|
"export_theme": "Save preset",
|
||||||
|
|
|
@ -18,7 +18,8 @@ export const multiChoiceProperties = [
|
||||||
'conversationDisplay', // tree | linear
|
'conversationDisplay', // tree | linear
|
||||||
'conversationOtherRepliesButton', // below | inside
|
'conversationOtherRepliesButton', // below | inside
|
||||||
'mentionLinkDisplay', // short | full_for_remote | full
|
'mentionLinkDisplay', // short | full_for_remote | full
|
||||||
'userPopoverAvatarAction' // close | zoom | open
|
'userPopoverAvatarAction', // close | zoom | open
|
||||||
|
'unsavedPostAction' // save | discard | confirm
|
||||||
]
|
]
|
||||||
|
|
||||||
export const defaultState = {
|
export const defaultState = {
|
||||||
|
@ -117,7 +118,8 @@ export const defaultState = {
|
||||||
conversationOtherRepliesButton: undefined, // instance default
|
conversationOtherRepliesButton: undefined, // instance default
|
||||||
conversationTreeFadeAncestors: undefined, // instance default
|
conversationTreeFadeAncestors: undefined, // instance default
|
||||||
maxDepthInThread: undefined, // instance default
|
maxDepthInThread: undefined, // instance default
|
||||||
autocompleteSelect: undefined // instance default
|
autocompleteSelect: undefined, // instance default
|
||||||
|
unsavedPostAction: undefined // instance default
|
||||||
}
|
}
|
||||||
|
|
||||||
// caching the instance default properties
|
// caching the instance default properties
|
||||||
|
|
|
@ -105,6 +105,7 @@ const defaultState = {
|
||||||
conversationTreeFadeAncestors: false,
|
conversationTreeFadeAncestors: false,
|
||||||
maxDepthInThread: 6,
|
maxDepthInThread: 6,
|
||||||
autocompleteSelect: false,
|
autocompleteSelect: false,
|
||||||
|
unsavedPostAction: 'confirm',
|
||||||
|
|
||||||
// Nasty stuff
|
// Nasty stuff
|
||||||
customEmoji: [],
|
customEmoji: [],
|
||||||
|
|
Loading…
Reference in New Issue