Commit 0a761e5f authored by Mike Greiling's avatar Mike Greiling

Replace comment type dropdown with Vue component

The snippets page and commit discussions still utilize an older
dropdown implementation based on DropLab. This updates them to
use the same dropdown component as issuable discussion components.
parent f43f24e1
import DropLab from './droplab/drop_lab';
import ISetter from './droplab/plugins/input_setter';
// Todo: Remove this when fixing issue in input_setter plugin
const InputSetter = { ...ISetter };
class CommentTypeToggle {
constructor(opts = {}) {
this.dropdownTrigger = opts.dropdownTrigger;
this.dropdownList = opts.dropdownList;
this.noteTypeInput = opts.noteTypeInput;
this.submitButton = opts.submitButton;
}
initDroplab() {
this.droplab = new DropLab();
const config = this.setConfig();
this.droplab.init(this.dropdownTrigger, this.dropdownList, [InputSetter], config);
}
setConfig() {
const config = {
InputSetter: [
{
// when option is clicked, sets the `value` attribute on
// `#note_type` to whatever the `data-value` attribute was
// on the clicked option
input: this.noteTypeInput,
valueAttribute: 'data-value',
},
{
// when option is clicked, sets the `value` attribute on
// `.js-comment-type-dropdown .js-comment-submit-button` to
// whatever the `data-value` attribute was on the clicked
// option
input: this.submitButton,
valueAttribute: 'data-submit-text',
},
],
};
return config;
}
}
export default CommentTypeToggle;
......@@ -19,9 +19,10 @@ import Vue from 'vue';
import '~/lib/utils/jquery_at_who';
import AjaxCache from '~/lib/utils/ajax_cache';
import syntaxHighlight from '~/syntax_highlight';
import CommentTypeDropdown from '~/notes/components/comment_type_dropdown.vue';
import * as constants from '~/notes/constants';
import Autosave from './autosave';
import loadAwardsHandler from './awards_handler';
import CommentTypeToggle from './comment_type_toggle';
import createFlash from './flash';
import { defaultAutocompleteConfig } from './gfm_auto_complete';
import GLForm from './gl_form';
......@@ -201,19 +202,39 @@ export default class Notes {
}
static initCommentTypeToggle(form) {
const dropdownTrigger = form.querySelector('.js-comment-type-dropdown .dropdown-toggle');
const dropdownList = form.querySelector('.js-comment-type-dropdown .dropdown-menu');
const el = form.querySelector('.js-comment-type-dropdown');
const { noteableName } = el.dataset;
const noteTypeInput = form.querySelector('#note_type');
const submitButton = form.querySelector('.js-comment-type-dropdown .js-comment-submit-button');
const formHasContent = form.querySelector('.js-note-text').value.trim().length > 0;
const commentTypeToggle = new CommentTypeToggle({
dropdownTrigger,
dropdownList,
noteTypeInput,
submitButton,
form.commentTypeComponent = new Vue({
el,
data() {
return {
noteType: constants.COMMENT,
disabled: !formHasContent,
};
},
render(createElement) {
return createElement(CommentTypeDropdown, {
props: {
noteType: this.noteType,
noteableDisplayName: noteableName,
disabled: this.disabled,
},
on: {
change: (arg) => {
this.noteType = arg;
if (this.noteType === constants.DISCUSSION) {
noteTypeInput.value = constants.DISCUSSION_NOTE;
} else {
noteTypeInput.value = '';
}
},
},
});
},
});
commentTypeToggle.initDroplab();
}
keydownNoteText(e) {
......@@ -1103,6 +1124,7 @@ export default class Notes {
const form = textarea.parents('form');
const reopenbtn = form.find('.js-note-target-reopen');
const closebtn = form.find('.js-note-target-close');
const commentTypeComponent = form.get(0)?.commentTypeComponent;
if (textarea.val().trim().length > 0) {
reopentext = reopenbtn.attr('data-alternative-text');
......@@ -1119,6 +1141,9 @@ export default class Notes {
if (closebtn.is(':not(.btn-comment-and-close)')) {
closebtn.addClass('btn-comment-and-close');
}
if (commentTypeComponent) {
commentTypeComponent.disabled = false;
}
} else {
reopentext = reopenbtn.data('originalText');
closetext = closebtn.data('originalText');
......@@ -1134,6 +1159,9 @@ export default class Notes {
if (closebtn.is('.btn-comment-and-close')) {
closebtn.removeClass('btn-comment-and-close');
}
if (commentTypeComponent) {
commentTypeComponent.disabled = true;
}
}
}
......@@ -1304,9 +1332,6 @@ export default class Notes {
}
cleanForm($form) {
// Remove JS classes that are not needed here
$form.find('.js-comment-type-dropdown').removeClass('btn-group');
// Remove dropdown
$form.find('.dropdown-menu').remove();
......@@ -1501,6 +1526,8 @@ export default class Notes {
const $submitBtn = $(e.target);
$submitBtn.prop('disabled', true);
let $form = $submitBtn.parents('form');
const commentTypeComponent = $form.get(0)?.commentTypeComponent;
if (commentTypeComponent) commentTypeComponent.disabled = true;
const $closeBtn = $form.find('.js-note-target-close');
const isDiscussionNote =
$submitBtn.parent().find('li.droplab-item-selected').attr('id') === 'discussion';
......@@ -1580,6 +1607,8 @@ export default class Notes {
const note = res.data;
$submitBtn.prop('disabled', false);
if (commentTypeComponent) commentTypeComponent.disabled = false;
// Submission successful! remove placeholder
$notesContainer.find(`#${noteUniqueId}`).remove();
......@@ -1658,6 +1687,8 @@ export default class Notes {
// Submission failed, remove placeholder note and show Flash error message
$notesContainer.find(`#${noteUniqueId}`).remove();
$submitBtn.prop('disabled', false);
if (commentTypeComponent) commentTypeComponent.disabled = false;
const blurEvent = new CustomEvent('blur.imageDiff', {
detail: e,
});
......
......@@ -96,7 +96,11 @@ export default {
data-track-action="click_button"
@click="$emit('click')"
>
<gl-dropdown-item is-check-item :is-checked="isNoteTypeComment" @click="setNoteTypeToComment">
<gl-dropdown-item
is-check-item
:is-checked="isNoteTypeComment"
@click.stop.prevent="setNoteTypeToComment"
>
<strong>{{ $options.i18n.submitButton.comment }}</strong>
<p class="gl-m-0">{{ commentDescription }}</p>
</gl-dropdown-item>
......@@ -105,7 +109,7 @@ export default {
is-check-item
:is-checked="isNoteTypeDiscussion"
data-qa-selector="discussion_menu_item"
@click="setNoteTypeToDiscussion"
@click.stop.prevent="setNoteTypeToDiscussion"
>
<strong>{{ $options.i18n.submitButton.startThread }}</strong>
<p class="gl-m-0">{{ startDiscussionDescription }}</p>
......
- noteable_name = @note.noteable.human_class_name
.float-left.btn-group.gl-sm-mr-3.droplab-dropdown.comment-type-dropdown.js-comment-type-dropdown
.js-comment-type-dropdown.float-left.gl-sm-mr-3{ data: { noteable_name: noteable_name } }
%input.btn.gl-button.btn-confirm.js-comment-button.js-comment-submit-button{ type: 'submit', value: _('Comment'), data: { qa_selector: 'comment_button' } }
- if @note.can_be_discussion_note?
= button_tag type: 'button', class: 'gl-button btn dropdown-toggle btn-confirm js-note-new-discussion js-disable-on-submit', data: { 'dropdown-trigger' => '#resolvable-comment-menu' }, 'aria-label' => _('Open comment type dropdown') do
= sprite_icon('chevron-down')
%ul#resolvable-comment-menu.dropdown-menu.dropdown-open-top{ data: { dropdown: true } }
%li#comment.droplab-item-selected{ data: { value: '', 'submit-text' => _('Comment'), 'close-text' => _("Comment & close %{noteable_name}") % { noteable_name: noteable_name }, 'reopen-text' => _("Comment & reopen %{noteable_name}") % { noteable_name: noteable_name } } }
%button.btn.gl-button.btn-default-tertiary
= sprite_icon('check', css_class: 'icon')
.description
%strong= _("Comment")
%p
= _("Add a general comment to this %{noteable_name}.") % { noteable_name: noteable_name }
%li.divider.droplab-item-ignore
%li#discussion{ data: { value: 'DiscussionNote', 'submit-text' => _('Start thread'), 'close-text' => _("Start thread & close %{noteable_name}") % { noteable_name: noteable_name }, 'reopen-text' => _("Start thread & reopen %{noteable_name}") % { noteable_name: noteable_name } } }
%button.btn.gl-button.btn-default-tertiary
= sprite_icon('check', css_class: 'icon')
.description
%strong= _("Start thread")
%p
= succeed '.' do
- if @note.noteable.supports_resolvable_notes?
= _('Discuss a specific suggestion or question that needs to be resolved')
- else
= _('Discuss a specific suggestion or question')
import CommentTypeToggle from '~/comment_type_toggle';
import DropLab from '~/droplab/drop_lab';
import InputSetter from '~/droplab/plugins/input_setter';
describe('CommentTypeToggle', () => {
const testContext = {};
describe('class constructor', () => {
beforeEach(() => {
testContext.dropdownTrigger = {};
testContext.dropdownList = {};
testContext.noteTypeInput = {};
testContext.submitButton = {};
testContext.commentTypeToggle = new CommentTypeToggle({
dropdownTrigger: testContext.dropdownTrigger,
dropdownList: testContext.dropdownList,
noteTypeInput: testContext.noteTypeInput,
submitButton: testContext.submitButton,
});
});
it('should set .dropdownTrigger', () => {
expect(testContext.commentTypeToggle.dropdownTrigger).toBe(testContext.dropdownTrigger);
});
it('should set .dropdownList', () => {
expect(testContext.commentTypeToggle.dropdownList).toBe(testContext.dropdownList);
});
it('should set .noteTypeInput', () => {
expect(testContext.commentTypeToggle.noteTypeInput).toBe(testContext.noteTypeInput);
});
it('should set .submitButton', () => {
expect(testContext.commentTypeToggle.submitButton).toBe(testContext.submitButton);
});
});
describe('initDroplab', () => {
beforeEach(() => {
testContext.commentTypeToggle = {
dropdownTrigger: {},
dropdownList: {},
noteTypeInput: {},
submitButton: {},
closeButton: {},
setConfig: () => {},
};
testContext.config = {};
jest.spyOn(DropLab.prototype, 'init').mockImplementation();
jest.spyOn(DropLab.prototype, 'constructor').mockImplementation();
jest.spyOn(testContext.commentTypeToggle, 'setConfig').mockReturnValue(testContext.config);
CommentTypeToggle.prototype.initDroplab.call(testContext.commentTypeToggle);
});
it('should instantiate a DropLab instance and set .droplab', () => {
expect(testContext.commentTypeToggle.droplab instanceof DropLab).toBe(true);
});
it('should call .setConfig', () => {
expect(testContext.commentTypeToggle.setConfig).toHaveBeenCalled();
});
it('should call DropLab.prototype.init', () => {
expect(DropLab.prototype.init).toHaveBeenCalledWith(
testContext.commentTypeToggle.dropdownTrigger,
testContext.commentTypeToggle.dropdownList,
[InputSetter],
testContext.config,
);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment