Commit 01f1788a authored by Coung Ngo's avatar Coung Ngo

Add new mentions component to Issues

As part of the refactor from at.js to tribute, the new
tribute component was added to the Issues description, note
and comment textareas
parent ecfc5247
......@@ -9,13 +9,15 @@ export default class GLForm {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
this.enableGFM = { ...defaultAutocompleteConfig, ...enableGFM };
// Disable autocomplete for keywords which do not have dataSources available
const dataSources = (gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources) || {};
Object.keys(this.enableGFM).forEach(item => {
if (item !== 'emojis') {
this.enableGFM[item] = Boolean(dataSources[item]);
if (item !== 'emojis' && !dataSources[item]) {
this.enableGFM[item] = false;
}
});
// Before we start, we should clean up any previous data for this form
this.destroy();
// Set up the form
......
......@@ -3,18 +3,19 @@ import { escape } from 'lodash';
import Tribute from 'tributejs';
import axios from '~/lib/utils/axios_utils';
import { spriteIcon } from '~/lib/utils/common_utils';
import SidebarMediator from '~/sidebar/sidebar_mediator';
/**
* Creates the HTML template for each row of the mentions dropdown.
*
* @param original An object from the array returned from the `autocomplete_sources/members` API
* @returns {string} An HTML template
* @param original - An object from the array returned from the `autocomplete_sources/members` API
* @returns {string} - An HTML template
*/
function menuItemTemplate({ original }) {
const rectAvatarClass = original.type === 'Group' ? 'rect-avatar' : '';
const avatarClasses = `avatar avatar-inline center s26 ${rectAvatarClass}
gl-display-inline-flex gl-align-items-center gl-justify-content-center`;
gl-display-inline-flex! gl-align-items-center gl-justify-content-center`;
const avatarTag = original.avatar_url
? `<img
......@@ -48,6 +49,7 @@ export default {
},
data() {
return {
assignees: undefined,
members: undefined,
};
},
......@@ -76,19 +78,37 @@ export default {
*/
getMembers(inputText, processValues) {
if (this.members) {
processValues(this.members);
processValues(this.getFilteredMembers());
} else if (this.dataSources.members) {
axios
.get(this.dataSources.members)
.then(response => {
this.members = response.data;
processValues(response.data);
processValues(this.getFilteredMembers());
})
.catch(() => {});
} else {
processValues([]);
}
},
getFilteredMembers() {
const fullText = this.$slots.default[0].elm.value;
if (!this.assignees) {
this.assignees =
SidebarMediator.singleton?.store?.assignees?.map(assignee => assignee.username) || [];
}
if (fullText.startsWith('/assign @')) {
return this.members.filter(member => !this.assignees.includes(member.username));
}
if (fullText.startsWith('/unassign @')) {
return this.members.filter(member => this.assignees.includes(member.username));
}
return this.members;
},
},
render(createElement) {
return createElement('div', this.$slots.default);
......
......@@ -4,21 +4,25 @@ import '~/behaviors/markdown/render_gfm';
import { unescape } from 'lodash';
import { __, sprintf } from '~/locale';
import { stripHtml } from '~/lib/utils/text_utility';
import Flash from '../../../flash';
import GLForm from '../../../gl_form';
import markdownHeader from './header.vue';
import markdownToolbar from './toolbar.vue';
import icon from '../icon.vue';
import Flash from '~/flash';
import GLForm from '~/gl_form';
import MarkdownHeader from './header.vue';
import MarkdownToolbar from './toolbar.vue';
import Icon from '../icon.vue';
import GlMentions from '~/vue_shared/components/gl_mentions.vue';
import Suggestions from '~/vue_shared/components/markdown/suggestions.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import axios from '~/lib/utils/axios_utils';
export default {
components: {
markdownHeader,
markdownToolbar,
icon,
GlMentions,
MarkdownHeader,
MarkdownToolbar,
Icon,
Suggestions,
},
mixins: [glFeatureFlagsMixin()],
props: {
isSubmitting: {
type: Boolean,
......@@ -159,12 +163,10 @@ export default {
},
},
mounted() {
/*
GLForm class handles all the toolbar buttons
*/
// GLForm class handles all the toolbar buttons
return new GLForm($(this.$refs['gl-form']), {
emojis: this.enableAutocomplete,
members: this.enableAutocomplete,
members: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete,
issues: this.enableAutocomplete,
mergeRequests: this.enableAutocomplete,
epics: this.enableAutocomplete,
......@@ -243,7 +245,10 @@ export default {
/>
<div v-show="!previewMarkdown" class="md-write-holder">
<div class="zen-backdrop">
<slot name="textarea"></slot>
<gl-mentions v-if="glFeatures.tributeAutocomplete">
<slot name="textarea"></slot>
</gl-mentions>
<slot v-else name="textarea"></slot>
<a
class="zen-control zen-control-leave js-zen-leave gl-text-gray-700"
href="#"
......
......@@ -46,6 +46,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:vue_issuable_sidebar, project.group)
push_frontend_feature_flag(:tribute_autocomplete, @project)
end
before_action only: :show do
......
......@@ -15,29 +15,63 @@ RSpec.describe 'GFM autocomplete EE', :js do
context 'assignees' do
let(:issue_assignee) { create(:issue, project: project) }
before do
issue_assignee.update(assignees: [user])
describe 'when tribute_autocomplete feature flag is off' do
before do
stub_feature_flags(tribute_autocomplete: false)
sign_in(user)
visit project_issue_path(project, issue_assignee)
issue_assignee.update(assignees: [user])
wait_for_requests
sign_in(user)
visit project_issue_path(project, issue_assignee)
wait_for_requests
end
it 'only lists users who are currently assigned to the issue when using /unassign' do
note = find('#note-body')
page.within '.timeline-content-form' do
note.native.send_keys('/una')
end
find('.atwho-view li', text: '/unassign')
note.native.send_keys(:tab)
wait_for_requests
users = find('#at-view-users .atwho-view-ul')
expect(users).to have_content(user.username)
expect(users).not_to have_content(another_user.username)
end
end
it 'only lists users who are currently assigned to the issue when using /unassign' do
note = find('#note-body')
page.within '.timeline-content-form' do
note.native.send_keys('/una')
describe 'when tribute_autocomplete feature flag is on' do
before do
stub_feature_flags(tribute_autocomplete: true)
issue_assignee.update(assignees: [user])
sign_in(user)
visit project_issue_path(project, issue_assignee)
wait_for_requests
end
find('.atwho-view li', text: '/unassign')
note.native.send_keys(:tab)
it 'only lists users who are currently assigned to the issue when using /unassign' do
note = find('#note-body')
page.within '.timeline-content-form' do
note.native.send_keys('/una')
end
find('.atwho-view li', text: '/unassign')
note.native.send_keys(:tab)
note.native.send_keys(:right)
wait_for_requests
wait_for_requests
users = find('#at-view-users .atwho-view-ul')
expect(users).to have_content(user.username)
expect(users).not_to have_content(another_user.username)
users = find('.tribute-container ul')
expect(users).to have_content(user.username)
expect(users).not_to have_content(another_user.username)
end
end
end
end
......@@ -36,12 +36,35 @@ RSpec.describe 'Member autocomplete', :js do
let(:noteable) { create(:issue, author: author, project: project) }
before do
stub_feature_flags(tribute_autocomplete: false)
visit project_issue_path(project, noteable)
end
include_examples "open suggestions when typing @", 'issue'
end
describe 'when tribute_autocomplete feature flag is on' do
context 'adding a new note on a Issue' do
let(:noteable) { create(:issue, author: author, project: project) }
before do
stub_feature_flags(tribute_autocomplete: true)
visit project_issue_path(project, noteable)
page.within('.new-note') do
find('#note-body').send_keys('@')
end
end
it 'suggests noteable author and note author' do
page.within('.tribute-container', visible: true) do
expect(page).to have_content(author.username)
expect(page).to have_content(note.author.username)
end
end
end
end
context 'adding a new note on a Merge Request' do
let(:project) { create(:project, :public, :repository) }
let(:noteable) do
......
......@@ -11507,10 +11507,10 @@ tr46@^2.0.2:
dependencies:
punycode "^2.1.1"
tributejs@4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-4.1.3.tgz#2e1be7d9a1e403ed4c394f91d859812267e4691c"
integrity sha512-+VUqyi8p7tCdaqCINCWHf95E2hJFMIML180BhplTpXNooz3E2r96AONXI9qO2Ru6Ugp7MsMPJjB+rnBq+hAmzA==
tributejs@5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-5.1.3.tgz#980600fc72865be5868893078b4bfde721129eae"
integrity sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ==
trim-newlines@^1.0.0:
version "1.0.0"
......
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