mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-03-11 17:48:44 -05:00
feat(editor): automatically save draft comments locally (#1868)
Resolves https://github.com/go-vikunja/vikunja/issues/1867
This commit is contained in:
@@ -183,6 +183,7 @@ import XButton from '@/components/input/Button.vue'
|
||||
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
|
||||
import inputPrompt from '@/helpers/inputPrompt'
|
||||
import {setLinkInEditor} from '@/components/input/editor/setLinkInEditor'
|
||||
import {saveEditorDraft, loadEditorDraft, clearEditorDraft} from '@/helpers/editorDraftStorage'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: string,
|
||||
@@ -195,6 +196,7 @@ const props = withDefaults(defineProps<{
|
||||
enableDiscardShortcut?: boolean,
|
||||
enableMentions?: boolean,
|
||||
mentionProjectId?: number,
|
||||
storageKey?: string,
|
||||
}>(), {
|
||||
uploadCallback: undefined,
|
||||
isEditEnabled: true,
|
||||
@@ -205,6 +207,7 @@ const props = withDefaults(defineProps<{
|
||||
enableDiscardShortcut: false,
|
||||
enableMentions: false,
|
||||
mentionProjectId: 0,
|
||||
storageKey: '',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'save'])
|
||||
@@ -571,12 +574,25 @@ function bubbleNow() {
|
||||
}
|
||||
|
||||
contentHasChanged.value = true
|
||||
emit('update:modelValue', editor.value?.getHTML())
|
||||
const newContent = editor.value?.getHTML()
|
||||
|
||||
// Save to localStorage if storageKey is provided
|
||||
if (props.storageKey) {
|
||||
saveEditorDraft(props.storageKey, newContent || '')
|
||||
}
|
||||
|
||||
emit('update:modelValue', newContent)
|
||||
}
|
||||
|
||||
function bubbleSave() {
|
||||
bubbleNow()
|
||||
lastSavedState = editor.value?.getHTML() ?? ''
|
||||
|
||||
// Clear draft from localStorage when saved
|
||||
if (props.storageKey) {
|
||||
clearEditorDraft(props.storageKey)
|
||||
}
|
||||
|
||||
emit('save', lastSavedState)
|
||||
if (isEditing.value) {
|
||||
internalMode.value = 'preview'
|
||||
@@ -585,6 +601,12 @@ function bubbleSave() {
|
||||
|
||||
function exitEditMode() {
|
||||
editor.value?.commands.setContent(lastSavedState, {emitUpdate: false})
|
||||
|
||||
// Clear draft from localStorage when discarding changes
|
||||
if (props.storageKey) {
|
||||
clearEditorDraft(props.storageKey)
|
||||
}
|
||||
|
||||
if (isEditing.value) {
|
||||
internalMode.value = 'preview'
|
||||
}
|
||||
@@ -680,6 +702,20 @@ onMounted(async () => {
|
||||
|
||||
await nextTick()
|
||||
|
||||
// Load draft from localStorage if available
|
||||
if (props.storageKey) {
|
||||
const draft = loadEditorDraft(props.storageKey)
|
||||
if (draft && isEditorContentEmpty(props.modelValue)) {
|
||||
// Only load draft if current content is empty
|
||||
// Set content and force edit mode for immediate editing
|
||||
editor.value?.commands.setContent(draft, {emitUpdate: false})
|
||||
internalMode.value = 'edit'
|
||||
// Emit the model update so parent sees the restored content
|
||||
emit('update:modelValue', draft)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
setModeAndValue(props.modelValue)
|
||||
})
|
||||
|
||||
|
||||
@@ -172,6 +172,7 @@
|
||||
:placeholder="$t('task.comment.placeholder')"
|
||||
:enable-mentions="true"
|
||||
:mention-project-id="projectId"
|
||||
:storage-key="commentStorageKey"
|
||||
@save="addComment()"
|
||||
/>
|
||||
</div>
|
||||
@@ -226,6 +227,7 @@ import type {ITask} from '@/modelTypes/ITask'
|
||||
import {uploadFile} from '@/helpers/attachments'
|
||||
import {success} from '@/message'
|
||||
import {formatDateLong, formatDisplayDate} from '@/helpers/time/formatDate'
|
||||
import {clearEditorDraft} from '@/helpers/editorDraftStorage'
|
||||
import {fetchAvatarBlobUrl, getDisplayName} from '@/models/user'
|
||||
import type {IUser} from '@/modelTypes/IUser'
|
||||
import {useConfigStore} from '@/stores/config'
|
||||
@@ -299,6 +301,7 @@ const actions = computed(() => {
|
||||
})
|
||||
|
||||
const frontendUrl = computed(() => configStore.frontendUrl)
|
||||
const commentStorageKey = computed(() => `task-comment-${props.taskId}`)
|
||||
|
||||
const currentPage = ref(1)
|
||||
|
||||
@@ -377,6 +380,10 @@ async function addComment() {
|
||||
const comment = await taskCommentService.create(newComment)
|
||||
comments.value.push(comment)
|
||||
newCommentText.value = ''
|
||||
|
||||
// Ensure draft is cleared from localStorage
|
||||
clearEditorDraft(commentStorageKey.value)
|
||||
|
||||
success({message: t('task.comment.addedSuccess')})
|
||||
} finally {
|
||||
creating.value = false
|
||||
|
||||
61
frontend/src/helpers/editorDraftStorage.ts
Normal file
61
frontend/src/helpers/editorDraftStorage.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import {isEditorContentEmpty} from '@/helpers/editorContentEmpty'
|
||||
|
||||
const STORAGE_KEY_PREFIX = 'editorDraft'
|
||||
|
||||
/**
|
||||
* Save editor content to local storage
|
||||
*/
|
||||
export function saveEditorDraft(storageKey: string, content: string) {
|
||||
if (!storageKey) {
|
||||
return
|
||||
}
|
||||
|
||||
const key = `${STORAGE_KEY_PREFIX}-${storageKey}`
|
||||
|
||||
try {
|
||||
if (!content || isEditorContentEmpty(content)) {
|
||||
// Remove empty drafts
|
||||
localStorage.removeItem(key)
|
||||
return
|
||||
}
|
||||
|
||||
localStorage.setItem(key, content)
|
||||
} catch (error) {
|
||||
console.warn('Failed to save editor draft:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load editor content from local storage
|
||||
*/
|
||||
export function loadEditorDraft(storageKey: string): string | null {
|
||||
if (!storageKey) {
|
||||
return null
|
||||
}
|
||||
|
||||
const key = `${STORAGE_KEY_PREFIX}-${storageKey}`
|
||||
|
||||
try {
|
||||
return localStorage.getItem(key)
|
||||
} catch (error) {
|
||||
console.warn('Failed to load editor draft:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear editor content from local storage
|
||||
*/
|
||||
export function clearEditorDraft(storageKey: string) {
|
||||
if (!storageKey) {
|
||||
return
|
||||
}
|
||||
|
||||
const key = `${STORAGE_KEY_PREFIX}-${storageKey}`
|
||||
|
||||
try {
|
||||
localStorage.removeItem(key)
|
||||
} catch (error) {
|
||||
console.warn('Failed to clear editor draft:', error)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user