feat: add discard and reload confirmation modal (#2154)
This commit is contained in:
parent
8144560dd7
commit
bf8138ec3c
|
|
@ -33,6 +33,7 @@
|
|||
:enable-discard-shortcut="true"
|
||||
:enable-mentions="true"
|
||||
:mention-project-id="modelValue.projectId"
|
||||
:storage-key="descriptionStorageKey"
|
||||
@update:modelValue="saveWithDelay"
|
||||
@save="save"
|
||||
/>
|
||||
|
|
@ -40,14 +41,15 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, watchEffect, onMounted, onBeforeUnmount} from 'vue'
|
||||
import {ref, computed, watchEffect, onBeforeUnmount} from 'vue'
|
||||
import {onBeforeRouteLeave} from 'vue-router'
|
||||
|
||||
import CustomTransition from '@/components/misc/CustomTransition.vue'
|
||||
import Editor from '@/components/input/AsyncEditor'
|
||||
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import {useTaskStore} from '@/stores/tasks'
|
||||
import { clearEditorDraft } from '@/helpers/editorDraftStorage'
|
||||
import type { ITask } from '@/modelTypes/ITask'
|
||||
import { useTaskStore } from '@/stores/tasks'
|
||||
|
||||
export type AttachmentUploadFunction = (file: File, onSuccess: (attachmentUrl: string) => void) => Promise<string>
|
||||
|
||||
|
|
@ -78,9 +80,7 @@ const loading = computed(() => taskStore.isLoading)
|
|||
|
||||
const changeTimeout = ref<ReturnType<typeof setTimeout> | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('beforeunload', save)
|
||||
})
|
||||
const descriptionStorageKey = computed(() => `task-description-${props.modelValue.id}`)
|
||||
|
||||
async function saveWithDelay() {
|
||||
if (description.value === props.modelValue.description) {
|
||||
|
|
@ -106,7 +106,6 @@ onBeforeUnmount(async () => {
|
|||
if (changeTimeout.value !== null) {
|
||||
clearTimeout(changeTimeout.value)
|
||||
}
|
||||
window.removeEventListener('beforeunload', save)
|
||||
})
|
||||
|
||||
onBeforeRouteLeave(() => save())
|
||||
|
|
@ -130,6 +129,9 @@ async function save() {
|
|||
})
|
||||
emit('update:modelValue', updated)
|
||||
|
||||
// Clear draft from localStorage when saved successfully
|
||||
clearEditorDraft(descriptionStorageKey.value)
|
||||
|
||||
saved.value = true
|
||||
setTimeout(() => {
|
||||
saved.value = false
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
:class="{'disabled': !canWrite}"
|
||||
:contenteditable="canWrite ? true : undefined"
|
||||
:spellcheck="false"
|
||||
@input="handleTitleInput"
|
||||
@blur="save(($event.target as HTMLInputElement).textContent as string)"
|
||||
@keydown.enter.prevent.stop="!$event.isComposing && ($event.target as HTMLInputElement).blur()"
|
||||
@keydown.esc.prevent.stop="!$event.isComposing && cancel($event.target as HTMLInputElement)"
|
||||
|
|
@ -64,7 +65,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed} from 'vue'
|
||||
import {ref, computed, onMounted, onBeforeUnmount, watch} from 'vue'
|
||||
import {useRouter} from 'vue-router'
|
||||
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
|
@ -109,6 +110,36 @@ const saving = ref(false)
|
|||
|
||||
const showSavedMessage = ref(false)
|
||||
|
||||
// Track if title has unsaved changes
|
||||
const titleHasChanges = ref(false)
|
||||
|
||||
function handleBeforeUnload(e: BeforeUnloadEvent) {
|
||||
if (titleHasChanges.value) {
|
||||
e.preventDefault()
|
||||
// Modern browsers ignore custom messages but this is still required
|
||||
e.returnValue = ''
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
})
|
||||
|
||||
// Reset titleHasChanges when the task changes
|
||||
watch(() => props.task.id, () => {
|
||||
titleHasChanges.value = false
|
||||
})
|
||||
|
||||
function handleTitleInput(event: Event) {
|
||||
const target = event.target as HTMLInputElement
|
||||
titleHasChanges.value = target.textContent !== props.task.title
|
||||
}
|
||||
|
||||
async function save(title: string) {
|
||||
// We only want to save if the title was actually changed.
|
||||
// so we only continue if the task title changed.
|
||||
|
|
@ -123,6 +154,7 @@ async function save(title: string) {
|
|||
title,
|
||||
})
|
||||
emit('update:task', newTask)
|
||||
titleHasChanges.value = false
|
||||
showSavedMessage.value = true
|
||||
setTimeout(() => {
|
||||
showSavedMessage.value = false
|
||||
|
|
@ -134,6 +166,7 @@ async function save(title: string) {
|
|||
|
||||
async function cancel(element: HTMLInputElement) {
|
||||
element.textContent = props.task.title
|
||||
titleHasChanges.value = false
|
||||
element.blur()
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue