Commit fb7b2330 authored by Coung Ngo's avatar Coung Ngo

Add emoji autocomplete to GfmAutocomplete

Add emojis to tribute autocompletion as part of migrating from
jQuery-based at.js.

https://gitlab.com/gitlab-org/gitlab/-/issues/230977
https://gitlab.com/groups/gitlab-org/-/epics/4002
parent 927f1801
......@@ -4,6 +4,7 @@ import {
GfmAutocompleteType,
tributeConfig,
} from 'ee_else_ce/vue_shared/components/gfm_autocomplete/utils';
import * as Emoji from '~/emoji';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
......@@ -76,6 +77,14 @@ export default {
return (inputText, processValues) => {
if (this.cache[type]) {
processValues(this.filterValues(type));
} else if (type === GfmAutocompleteType.Emojis) {
Emoji.initEmojiMap()
.then(() => {
const emojis = Emoji.getValidEmojiNames();
this.cache[type] = emojis;
processValues(emojis);
})
.catch(() => createFlash({ message: this.$options.errorMessage }));
} else if (this.dataSources[type]) {
axios
.get(this.dataSources[type])
......
import { escape, last } from 'lodash';
import * as Emoji from '~/emoji';
import { spriteIcon } from '~/lib/utils/common_utils';
const groupType = 'Group'; // eslint-disable-line @gitlab/require-i18n-strings
......@@ -6,6 +7,7 @@ const groupType = 'Group'; // eslint-disable-line @gitlab/require-i18n-strings
const nonWordOrInteger = /\W|^\d+$/;
export const GfmAutocompleteType = {
Emojis: 'emojis',
Issues: 'issues',
Labels: 'labels',
Members: 'members',
......@@ -21,6 +23,15 @@ function doesCurrentLineStartWith(searchString, fullText, selectionStart) {
}
export const tributeConfig = {
[GfmAutocompleteType.Emojis]: {
config: {
trigger: ':',
lookup: value => value,
menuItemTemplate: ({ original }) => `${original} ${Emoji.glEmojiTag(original)}`,
selectTemplate: ({ original }) => `:${original}:`,
},
},
[GfmAutocompleteType.Issues]: {
config: {
trigger: '#',
......
......@@ -169,7 +169,7 @@ export default {
return new GLForm(
$(this.$refs['gl-form']),
{
emojis: this.enableAutocomplete,
emojis: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete,
members: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete,
issues: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete,
mergeRequests: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete,
......
......@@ -426,36 +426,16 @@ RSpec.describe 'GFM autocomplete', :js do
visit project_issue_path(project, issue)
note = find('#note-body')
start_comment_with_emoji(note)
start_comment_with_emoji(note, '.atwho-view li')
start_and_cancel_discussion
note.fill_in(with: '')
start_comment_with_emoji(note)
start_comment_with_emoji(note, '.atwho-view li')
note.native.send_keys(:enter)
expect(note.value).to eql('Hello :100: ')
end
def start_comment_with_emoji(note)
note.native.send_keys('Hello :10')
wait_for_requests
find('.atwho-view li', text: '100')
end
def start_and_cancel_discussion
click_button('Reply...')
fill_in('note_note', with: 'Whoops!')
page.accept_alert 'Are you sure you want to cancel creating this comment?' do
click_button('Cancel')
end
wait_for_requests
end
end
shared_examples 'autocomplete suggestions' do
......@@ -599,6 +579,33 @@ RSpec.describe 'GFM autocomplete', :js do
expect(page).not_to have_selector('.tribute-container', visible: true)
end
it 'does not open autocomplete menu when ":" is prefixed by a number and letters' do
note = find('#note-body')
# Number.
page.within '.timeline-content-form' do
note.native.send_keys('7:')
end
expect(page).not_to have_selector('.tribute-container', visible: true)
# ASCII letter.
page.within '.timeline-content-form' do
note.set('')
note.native.send_keys('w:')
end
expect(page).not_to have_selector('.tribute-container', visible: true)
# Non-ASCII letter.
page.within '.timeline-content-form' do
note.set('')
note.native.send_keys('Ё:')
end
expect(page).not_to have_selector('.tribute-container', visible: true)
end
it 'selects the first item for assignee dropdowns' do
page.within '.timeline-content-form' do
find('#note-body').native.send_keys('@')
......@@ -624,6 +631,16 @@ RSpec.describe 'GFM autocomplete', :js do
expect(find('.tribute-container ul', visible: true)).to have_content(user.name)
end
it 'selects the first item for non-assignee dropdowns if a query is entered' do
page.within '.timeline-content-form' do
find('#note-body').native.send_keys(':1')
end
wait_for_requests
expect(find('.tribute-container ul', visible: true)).to have_selector('.highlight:first-of-type')
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
......@@ -687,6 +704,25 @@ RSpec.describe 'GFM autocomplete', :js do
expect_to_wrap(false, user_item, note, user.username)
end
it 'does not wrap for emoji values' do
note = find('#note-body')
page.within '.timeline-content-form' do
note.native.send_keys(":cartwheel_")
end
emoji_item = first('.tribute-container li', text: 'cartwheel_tone1', visible: true)
expect_to_wrap(false, emoji_item, note, 'cartwheel_tone1')
end
it 'does not open autocomplete if there is no space before' do
page.within '.timeline-content-form' do
find('#note-body').native.send_keys("hello:#{user.username[0..2]}")
end
expect(page).not_to have_selector('.tribute-container')
end
it 'triggers autocomplete after selecting a quick action' do
note = find('#note-body')
page.within '.timeline-content-form' do
......@@ -824,6 +860,26 @@ RSpec.describe 'GFM autocomplete', :js do
end
end
context 'when other notes are destroyed' do
let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }
# This is meant to protect against this issue https://gitlab.com/gitlab-org/gitlab/-/issues/228729
it 'keeps autocomplete key listeners' do
visit project_issue_path(project, issue)
note = find('#note-body')
start_comment_with_emoji(note, '.tribute-container li')
start_and_cancel_discussion
note.fill_in(with: '')
start_comment_with_emoji(note, '.tribute-container li')
note.native.send_keys(:enter)
expect(note.value).to eql('Hello :100: ')
end
end
shared_examples 'autocomplete suggestions' do
it 'suggests objects correctly' do
page.within '.timeline-content-form' do
......@@ -913,4 +969,24 @@ RSpec.describe 'GFM autocomplete', :js do
note.native.send_keys(text)
end
end
def start_comment_with_emoji(note, selector)
note.native.send_keys('Hello :10')
wait_for_requests
find(selector, text: '100')
end
def start_and_cancel_discussion
click_button('Reply...')
fill_in('note_note', with: 'Whoops!')
page.accept_alert 'Are you sure you want to cancel creating this comment?' do
click_button('Cancel')
end
wait_for_requests
end
end
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`gfm_autocomplete/utils emojis config shows the emoji name and icon in the menu item 1`] = `
"raised_hands
<gl-emoji
data-name=\\"raised_hands\\"></gl-emoji>
"
`;
exports[`gfm_autocomplete/utils issues config shows the iid and title in the menu item within a project context 1`] = `"<small>123456</small> Project context issue title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
exports[`gfm_autocomplete/utils issues config shows the reference and title in the menu item within a group context 1`] = `"<small>gitlab#987654</small> Group context issue title &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
......
......@@ -2,6 +2,27 @@ import { escape, last } from 'lodash';
import { GfmAutocompleteType, tributeConfig } from '~/vue_shared/components/gfm_autocomplete/utils';
describe('gfm_autocomplete/utils', () => {
describe('emojis config', () => {
const emojisConfig = tributeConfig[GfmAutocompleteType.Emojis].config;
const emoji = 'raised_hands';
it('uses : as the trigger', () => {
expect(emojisConfig.trigger).toBe(':');
});
it('searches using the emoji name', () => {
expect(emojisConfig.lookup(emoji)).toBe(emoji);
});
it('shows the emoji name and icon in the menu item', () => {
expect(emojisConfig.menuItemTemplate({ original: emoji })).toMatchSnapshot();
});
it('inserts the emoji name on autocomplete selection', () => {
expect(emojisConfig.selectTemplate({ original: emoji })).toBe(`:${emoji}:`);
});
});
describe('issues config', () => {
const issuesConfig = tributeConfig[GfmAutocompleteType.Issues].config;
const groupContextIssue = {
......
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