diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_edit_form.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_edit_form.vue index 3353374310fc8efd14c4ffb40b8789b787c9977a..36f1d018e3fd03b6c3bfa1820b286d491871092c 100644 --- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_edit_form.vue +++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_edit_form.vue @@ -1,10 +1,11 @@ <script> import { GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui'; - +import { __ } from '~/locale'; import Autosave from '~/autosave'; -import MarkdownField from '~/vue_shared/components/markdown/field.vue'; +import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; +import markdownEditorEventHub from '~/vue_shared/components/markdown/eventhub'; +import { CLEAR_AUTOSAVE_ENTRY_EVENT } from '~/vue_shared/constants'; import ZenMode from '~/zen_mode'; - import eventHub from '../event_hub'; export default { @@ -12,7 +13,7 @@ export default { GlForm, GlFormGroup, GlFormInput, - MarkdownField, + MarkdownEditor, }, props: { issuable: { @@ -48,8 +49,22 @@ export default { return { title: '', description: '', + formFieldProps: { + id: 'issuable-description', + name: 'issuable-description', + 'aria-label': __('Description'), + placeholder: __('Write a comment or drag your files here…'), + class: 'note-textarea js-gfm-input js-autosize markdown-area', + }, }; }, + computed: { + descriptionAutosaveKey() { + if (this.enableAutosave) + return [document.location.pathname, document.location.search, 'description'].join('/'); + return ''; + }, + }, watch: { issuable: { handler(value) { @@ -76,25 +91,20 @@ export default { }, methods: { initAutosave() { - const { titleInput, descriptionInput } = this.$refs; + const { titleInput } = this.$refs; - if (!titleInput || !descriptionInput) return; + if (!titleInput) return; this.autosaveTitle = new Autosave(titleInput.$el, [ document.location.pathname, document.location.search, 'title', ]); - - this.autosaveDescription = new Autosave(descriptionInput, [ - document.location.pathname, - document.location.search, - 'description', - ]); }, resetAutosave() { this.autosaveTitle.reset(); - this.autosaveDescription.reset(); + + markdownEditorEventHub.$emit(CLEAR_AUTOSAVE_ENTRY_EVENT, this.descriptionAutosaveKey); }, handleKeydown(e, inputType) { this.$emit(`keydown-${inputType}`, e, { @@ -132,26 +142,15 @@ export default { label-for="issuable-description" class="col-12 gl-px-0 common-note-form" > - <markdown-field - :markdown-preview-path="descriptionPreviewPath" + <markdown-editor + v-model="description" + :render-markdown-path="descriptionPreviewPath" :markdown-docs-path="descriptionHelpPath" :enable-autocomplete="enableAutocomplete" - :textarea-value="description" - > - <template #textarea> - <textarea - id="issuable-description" - ref="descriptionInput" - v-model="description" - :data-supports-quick-actions="enableAutocomplete" - :aria-label="__('Description')" - :placeholder="__('Write a comment or drag your files here…')" - class="note-textarea js-gfm-input js-autosize markdown-area" - dir="auto" - @keydown="handleKeydown($event, 'description')" - ></textarea> - </template> - </markdown-field> + :supports-quick-actions="enableAutocomplete" + :form-field-props="formFieldProps" + @keydown="handleKeydown($event, 'description')" + /> </gl-form-group> <div data-testid="actions" diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js index 4a52c2a8dade0ebe9cb74ed7519495e47afdfac7..304343d1a0668bb35e7ea5e482082a51f55c41b0 100644 --- a/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js +++ b/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js @@ -1,10 +1,14 @@ import { GlFormInput } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; - import { nextTick } from 'vue'; import IssuableEditForm from '~/vue_shared/issuable/show/components/issuable_edit_form.vue'; import IssuableEventHub from '~/vue_shared/issuable/show/event_hub'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; +import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue'; +import MarkdownToolbar from '~/vue_shared/components/markdown/toolbar.vue'; +import EditorModeSwitcher from '~/vue_shared/components/markdown/editor_mode_switcher.vue'; +import markdownEditorEventHub from '~/vue_shared/components/markdown/eventhub'; +import { CLEAR_AUTOSAVE_ENTRY_EVENT } from '~/vue_shared/constants'; import Autosave from '~/autosave'; import { mockIssuableShowProps, mockIssuable } from '../mock_data'; @@ -20,7 +24,10 @@ const createComponent = ({ propsData = issuableEditFormProps } = {}) => shallowMount(IssuableEditForm, { propsData, stubs: { + MarkdownEditor, MarkdownField, + MarkdownToolbar, + EditorModeSwitcher, }, slots: { 'edit-form-actions': ` @@ -109,17 +116,18 @@ describe('IssuableEditForm', () => { describe('methods', () => { describe('initAutosave', () => { it('initializes autosave', () => { - expect(Autosave.mock.calls).toEqual([ - [expect.any(Element), ['/', '', 'title']], - [expect.any(Element), ['/', '', 'description']], - ]); + expect(Autosave.mock.calls).toEqual([[expect.any(Element), ['/', '', 'title']]]); }); }); describe('resetAutosave', () => { - it('resets title and description on "update.issuable event"', () => { + it('resets title on "update.issuable event"', () => { + const clearDescriptionAutosaveSpy = jest.fn(); + markdownEditorEventHub.$on(CLEAR_AUTOSAVE_ENTRY_EVENT, clearDescriptionAutosaveSpy); + IssuableEventHub.$emit('update.issuable'); - expect(Autosave.prototype.reset.mock.calls).toEqual([[], []]); + expect(Autosave.prototype.reset).toHaveBeenCalled(); + expect(clearDescriptionAutosaveSpy).toHaveBeenCalled(); }); }); }); @@ -152,6 +160,12 @@ describe('IssuableEditForm', () => { }); }); + it('allows switching to rich text editor', () => { + const descriptionEl = wrapper.find('[data-testid="description"]'); + + expect(descriptionEl.text()).toContain('Switch to rich text editing'); + }); + it('renders form actions', () => { const actionsEl = wrapper.find('[data-testid="actions"]');