Commit e84a0fd5 authored by Coung Ngo's avatar Coung Ngo Committed by Mark Florian

Improve @mentions UX for tribute

The @mentions autocomplete menu becomes very wide when groups with
very long display names or paths are in the list. This change
reduces the width by only showing the leaf group names.
parent 304bdcec
<script>
import { escape } from 'lodash';
import { escape, last } from 'lodash';
import Tribute from 'tributejs';
import axios from '~/lib/utils/axios_utils';
import { spriteIcon } from '~/lib/utils/common_utils';
......@@ -12,6 +12,8 @@ const AutoComplete = {
MergeRequests: 'mergeRequests',
};
const groupType = 'Group'; // eslint-disable-line @gitlab/require-i18n-strings
function doesCurrentLineStartWith(searchString, fullText, selectionStart) {
const currentLineNumber = fullText.slice(0, selectionStart).split('\n').length;
const currentLine = fullText.split('\n')[currentLineNumber - 1];
......@@ -79,30 +81,40 @@ const autoCompleteMap = {
return this.members;
},
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`;
const avatarTag = original.avatar_url
? `<img
src="${original.avatar_url}"
alt="${original.username}'s avatar"
class="${avatarClasses}"/>`
: `<div class="${avatarClasses}">${original.username.charAt(0).toUpperCase()}</div>`;
const name = escape(original.name);
const commonClasses = 'gl-avatar gl-avatar-s24 gl-flex-shrink-0';
const noAvatarClasses = `${commonClasses} gl-rounded-small
gl-display-flex gl-align-items-center gl-justify-content-center`;
const avatar = original.avatar_url
? `<img class="${commonClasses} gl-avatar-circle" src="${original.avatar_url}" alt="" />`
: `<div class="${noAvatarClasses}" aria-hidden="true">
${original.username.charAt(0).toUpperCase()}</div>`;
let displayName = original.name;
let parentGroupOrUsername = `@${original.username}`;
if (original.type === groupType) {
const splitName = original.name.split(' / ');
displayName = splitName.pop();
parentGroupOrUsername = splitName.pop();
}
const count = original.count && !original.mentionsDisabled ? ` (${original.count})` : '';
const icon = original.mentionsDisabled
? spriteIcon('notifications-off', 's16 gl-vertical-align-middle gl-ml-3')
const disabledMentionsIcon = original.mentionsDisabled
? spriteIcon('notifications-off', 's16 gl-ml-3')
: '';
return `${avatarTag}
${original.username}
<small class="gl-text-small gl-font-weight-normal gl-reset-color">${name}${count}</small>
${icon}`;
return `
<div class="gl-display-flex gl-align-items-center">
${avatar}
<div class="gl-font-sm gl-line-height-normal gl-ml-3">
<div>${escape(displayName)}${count}</div>
<div class="gl-text-gray-700">${escape(parentGroupOrUsername)}</div>
</div>
${disabledMentionsIcon}
</div>
`;
},
},
[AutoComplete.MergeRequests]: {
......@@ -139,7 +151,8 @@ export default {
{
trigger: '@',
fillAttr: 'username',
lookup: value => value.name + value.username,
lookup: value =>
value.type === groupType ? last(value.name.split(' / ')) : value.name + value.username,
menuItemTemplate: autoCompleteMap[AutoComplete.Members].menuItemTemplate,
values: this.getValues(AutoComplete.Members),
},
......
......@@ -6,7 +6,9 @@ RSpec.describe 'GFM autocomplete', :js do
let_it_be(:user_xss_title) { 'eve <img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;' }
let_it_be(:user_xss) { create(:user, name: user_xss_title, username: 'xss.user') }
let_it_be(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
let_it_be(:project) { create(:project) }
let_it_be(:group) { create(:group, name: 'Ancestor') }
let_it_be(:child_group) { create(:group, parent: group, name: 'My group') }
let_it_be(:project) { create(:project, group: child_group) }
let_it_be(:label) { create(:label, project: project, title: 'special+') }
let(:issue) { create(:issue, project: project) }
......@@ -535,7 +537,7 @@ RSpec.describe 'GFM autocomplete', :js do
expect(page).to have_selector('.tribute-container', visible: true)
expect(find('.tribute-container ul', visible: true).text).to have_content(user_xss.username)
expect(find('.tribute-container ul', visible: true)).to have_text(user_xss.username)
end
it 'selects the first item for assignee dropdowns' do
......@@ -563,6 +565,24 @@ RSpec.describe 'GFM autocomplete', :js do
expect(find('.tribute-container ul', visible: true)).to have_content(user.name)
end
context 'when autocompleting for groups' do
it 'shows the group when searching for the name of the group' do
page.within '.timeline-content-form' do
find('#note-body').native.send_keys('@mygroup')
end
expect(find('.tribute-container ul', visible: true)).to have_text('My group')
end
it 'does not show the group when searching for the name of the parent of the group' do
page.within '.timeline-content-form' do
find('#note-body').native.send_keys('@ancestor')
end
expect(find('.tribute-container ul', visible: true)).not_to have_text('My group')
end
end
context 'if a selected value has special characters' do
it 'wraps the result in double quotes' do
note = find('#note-body')
......
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