Skip to content
代码片段 群组 项目
提交 dfbec8b4 编辑于 作者: Chad Lavimoniere's avatar Chad Lavimoniere
浏览文件

When localStorage draft exists for comment reply, show it on load

When a user has a comment draft saved in localstorage, we do not
indicate that on page load at all; the user can only see their comment
draft by clicking reply and seeing the draft populated into the markdown
editor.

This MR changes the noteable_discussion component's behavior such that
it exposes the markdown editor with the user's comment draft on page
load if there is a saved draft for the comment in localstorage.

Changelog: fixed
上级 c669b921
No related branches found
No related tags found
无相关合并请求
...@@ -63,3 +63,6 @@ export const updateDraft = (autosaveKey, text, lockVersion) => { ...@@ -63,3 +63,6 @@ export const updateDraft = (autosaveKey, text, lockVersion) => {
export const getDiscussionReplyKey = (noteableType, discussionId) => export const getDiscussionReplyKey = (noteableType, discussionId) =>
/* eslint-disable-next-line @gitlab/require-i18n-strings */ /* eslint-disable-next-line @gitlab/require-i18n-strings */
['Note', capitalizeFirstCharacter(noteableType), discussionId, 'Reply'].join('/'); ['Note', capitalizeFirstCharacter(noteableType), discussionId, 'Reply'].join('/');
export const getAutoSaveKeyFromDiscussion = (discussion) =>
getDiscussionReplyKey(discussion.notes.slice(0, 1)[0].noteable_type, discussion.id);
...@@ -4,7 +4,7 @@ import { GlTooltipDirective, GlIcon } from '@gitlab/ui'; ...@@ -4,7 +4,7 @@ import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import DraftNote from '~/batch_comments/components/draft_note.vue'; import DraftNote from '~/batch_comments/components/draft_note.vue';
import { createAlert } from '~/alert'; import { createAlert } from '~/alert';
import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave'; import { clearDraft, getDraft, getAutoSaveKeyFromDiscussion } from '~/lib/utils/autosave';
import { isLoggedIn } from '~/lib/utils/common_utils'; import { isLoggedIn } from '~/lib/utils/common_utils';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import { ignoreWhilePending } from '~/lib/utils/ignore_while_pending'; import { ignoreWhilePending } from '~/lib/utils/ignore_while_pending';
...@@ -119,14 +119,11 @@ export default { ...@@ -119,14 +119,11 @@ export default {
return this.discussion.internal ? __('internal note') : __('comment'); return this.discussion.internal ? __('internal note') : __('comment');
}, },
autosaveKey() { autosaveKey() {
return getDiscussionReplyKey(this.firstNote.noteable_type, this.discussion.id); return getAutoSaveKeyFromDiscussion(this.discussion);
}, },
newNotePath() { newNotePath() {
return this.getNoteableData.create_note_path; return this.getNoteableData.create_note_path;
}, },
firstNote() {
return this.discussion.notes.slice(0, 1)[0];
},
saveButtonTitle() { saveButtonTitle() {
return this.discussion.internal ? __('Reply internally') : __('Reply'); return this.discussion.internal ? __('Reply internally') : __('Reply');
}, },
...@@ -187,9 +184,15 @@ export default { ...@@ -187,9 +184,15 @@ export default {
'gl-pt-0!': !this.discussion.diff_discussion && this.isReplying, 'gl-pt-0!': !this.discussion.diff_discussion && this.isReplying,
}; };
}, },
hasDraft() {
return Boolean(getDraft(this.autosaveKey));
},
}, },
created() { created() {
eventHub.$on('startReplying', this.onStartReplying); eventHub.$on('startReplying', this.onStartReplying);
if (this.hasDraft) {
this.showReplyForm();
}
}, },
beforeDestroy() { beforeDestroy() {
eventHub.$off('startReplying', this.onStartReplying); eventHub.$off('startReplying', this.onStartReplying);
...@@ -360,6 +363,7 @@ export default { ...@@ -360,6 +363,7 @@ export default {
:diff-file="diffFile" :diff-file="diffFile"
:line="diffLine" :line="diffLine"
:save-button-title="saveButtonTitle" :save-button-title="saveButtonTitle"
:autofocus="!hasDraft"
:autosave-key="autosaveKey" :autosave-key="autosaveKey"
@handleFormUpdateAddToReview="addReplyToReview" @handleFormUpdateAddToReview="addReplyToReview"
@handleFormUpdate="saveReply" @handleFormUpdate="saveReply"
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { InternalEvents } from '~/tracking'; import { InternalEvents } from '~/tracking';
import { getDraft, getAutoSaveKeyFromDiscussion } from '~/lib/utils/autosave';
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user'; import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import OrderedLayout from '~/vue_shared/components/ordered_layout.vue'; import OrderedLayout from '~/vue_shared/components/ordered_layout.vue';
...@@ -197,7 +198,11 @@ export default { ...@@ -197,7 +198,11 @@ export default {
'fetchNotes', 'fetchNotes',
]), ]),
discussionIsIndividualNoteAndNotConverted(discussion) { discussionIsIndividualNoteAndNotConverted(discussion) {
return discussion.individual_note && !this.convertedDisscussionIds.includes(discussion.id); return (
discussion.individual_note &&
!this.convertedDisscussionIds.includes(discussion.id) &&
!this.hasDraft(discussion)
);
}, },
handleHashChanged() { handleHashChanged() {
const noteId = this.checkLocationHash(); const noteId = this.checkLocationHash();
...@@ -238,6 +243,10 @@ export default { ...@@ -238,6 +243,10 @@ export default {
this.trackEvent(types[event.name]); this.trackEvent(types[event.name]);
} }
}, },
hasDraft(discussion) {
const autoSaveKey = getAutoSaveKeyFromDiscussion(discussion);
return Boolean(getDraft(autoSaveKey));
},
}, },
systemNote: constants.SYSTEM_NOTE, systemNote: constants.SYSTEM_NOTE,
}; };
......
...@@ -27,6 +27,7 @@ import { ...@@ -27,6 +27,7 @@ import {
loggedOutnoteableData, loggedOutnoteableData,
userDataMock, userDataMock,
} from '../mock_data'; } from '../mock_data';
import { useLocalStorageSpy } from '../../__helpers__/local_storage_helper';
Vue.use(Vuex); Vue.use(Vuex);
...@@ -98,6 +99,34 @@ describe('noteable_discussion component', () => { ...@@ -98,6 +99,34 @@ describe('noteable_discussion component', () => {
expect(wrapper.vm.canShowReplyActions).toBe(false); expect(wrapper.vm.canShowReplyActions).toBe(false);
}); });
describe('drafts', () => {
useLocalStorageSpy();
afterEach(() => {
localStorage.clear();
});
it.each`
show | exists | hasDraft
${'show'} | ${'exists'} | ${true}
${'not show'} | ${'does not exist'} | ${false}
`(
'should $show markdown editor on create if reply draft $exists in localStorage',
({ hasDraft }) => {
if (hasDraft) {
localStorage.setItem(`autosave/Note/Issue/${discussionMock.id}/Reply`, 'draft');
}
window.gon.current_user_id = userDataMock.id;
store.dispatch('setUserData', userDataMock);
wrapper = mount(NoteableDiscussion, {
store,
propsData: { discussion: discussionMock },
});
expect(wrapper.find('.note-edit-form').exists()).toBe(hasDraft);
},
);
});
describe('actions', () => { describe('actions', () => {
it('should toggle reply form', async () => { it('should toggle reply form', async () => {
await nextTick(); await nextTick();
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册