Allow autocompleting scoped labels
The `:` key will no longer exit out of the autocomplete dialog, allowing auto-completion of labels with colons in them.
This commit is contained in:
parent
e3fa9d122b
commit
de7abc0651
|
@ -12,6 +12,7 @@ import 'core-js/es/promise/finally';
|
|||
import 'core-js/es/string/code-point-at';
|
||||
import 'core-js/es/string/from-code-point';
|
||||
import 'core-js/es/string/includes';
|
||||
import 'core-js/es/string/starts-with';
|
||||
import 'core-js/es/symbol';
|
||||
import 'core-js/es/map';
|
||||
import 'core-js/es/weak-map';
|
||||
|
|
|
@ -318,6 +318,7 @@ class GfmAutoComplete {
|
|||
}
|
||||
|
||||
setupLabels($input) {
|
||||
const instance = this;
|
||||
const fetchData = this.fetchData.bind(this);
|
||||
const LABEL_COMMAND = { LABEL: '/label', UNLABEL: '/unlabel', RELABEL: '/relabel' };
|
||||
let command = '';
|
||||
|
@ -348,7 +349,6 @@ class GfmAutoComplete {
|
|||
}));
|
||||
},
|
||||
matcher(flag, subtext) {
|
||||
const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers);
|
||||
const subtextNodes = subtext
|
||||
.split(/\n+/g)
|
||||
.pop()
|
||||
|
@ -366,6 +366,27 @@ class GfmAutoComplete {
|
|||
return null;
|
||||
});
|
||||
|
||||
// If any label matches the inserted text after the last `~`, suggest those labels,
|
||||
// even if any spaces or funky characters were typed.
|
||||
// This allows matching labels like "Accepting merge requests".
|
||||
const labels = instance.cachedData[flag];
|
||||
if (labels) {
|
||||
if (!subtext.includes(flag)) {
|
||||
// Do not match if there is no `~` before the cursor
|
||||
return null;
|
||||
}
|
||||
const lastCandidate = subtext.split(flag).pop();
|
||||
if (labels.find(label => label.title.startsWith(lastCandidate))) {
|
||||
return lastCandidate;
|
||||
}
|
||||
} else {
|
||||
// Load all labels into the autocompleter.
|
||||
// This needs to happen if e.g. editing a label in an existing comment, because normally
|
||||
// label data would only be loaded only once you type `~`.
|
||||
fetchData(this.$inputor, this.at);
|
||||
}
|
||||
|
||||
const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers);
|
||||
return match && match.length ? match[1] : null;
|
||||
},
|
||||
filter(query, data, searchKey) {
|
||||
|
@ -563,8 +584,9 @@ class GfmAutoComplete {
|
|||
const accentAChar = decodeURI('%C3%80');
|
||||
const accentYChar = decodeURI('%C3%BF');
|
||||
|
||||
// Holy regex, batman!
|
||||
const regexp = new RegExp(
|
||||
`^(?:\\B|[^a-zA-Z0-9_\`${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`,
|
||||
`^(?:\\B|[^a-zA-Z0-9_\`${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-:]|[^\\x00-\\x7a])*)$`,
|
||||
'gi',
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow auto-completing scoped labels
|
||||
merge_request: 29749
|
||||
author:
|
||||
type: added
|
|
@ -3,14 +3,14 @@ require 'rails_helper'
|
|||
describe 'GFM autocomplete', :js do
|
||||
let(:issue_xss_title) { 'This will execute alert<img src=x onerror=alert(2)<img src=x onerror=alert(1)>' }
|
||||
let(:user_xss_title) { 'eve <img src=x onerror=alert(2)<img src=x onerror=alert(1)>' }
|
||||
let(:label_xss_title) { 'alert label <img src=x onerror="alert(\'Hello xss\');" a'}
|
||||
let(:label_xss_title) { 'alert label <img src=x onerror="alert(\'Hello xss\');" a' }
|
||||
let(:milestone_xss_title) { 'alert milestone <img src=x onerror="alert(\'Hello xss\');" a' }
|
||||
|
||||
let(:user_xss) { create(:user, name: user_xss_title, username: 'xss.user') }
|
||||
let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
|
||||
let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
|
||||
let(:project) { create(:project) }
|
||||
let(:label) { create(:label, project: project, title: 'special+') }
|
||||
let(:issue) { create(:issue, project: project) }
|
||||
let(:issue) { create(:issue, project: project) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
@ -293,6 +293,70 @@ describe 'GFM autocomplete', :js do
|
|||
expect(find('.atwho-view-ul').text).to have_content('alert label')
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows colons when autocompleting scoped labels' do
|
||||
create(:label, project: project, title: 'scoped:label')
|
||||
|
||||
note = find('#note-body')
|
||||
type(note, '~scoped:')
|
||||
|
||||
wait_for_requests
|
||||
|
||||
page.within '.atwho-container #at-view-labels' do
|
||||
expect(find('.atwho-view-ul').text).to have_content('scoped:label')
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows colons when autocompleting scoped labels with double colons' do
|
||||
create(:label, project: project, title: 'scoped::label')
|
||||
|
||||
note = find('#note-body')
|
||||
type(note, '~scoped::')
|
||||
|
||||
wait_for_requests
|
||||
|
||||
page.within '.atwho-container #at-view-labels' do
|
||||
expect(find('.atwho-view-ul').text).to have_content('scoped::label')
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows spaces when autocompleting multi-word labels' do
|
||||
create(:label, project: project, title: 'Accepting merge requests')
|
||||
|
||||
note = find('#note-body')
|
||||
type(note, '~Accepting merge')
|
||||
|
||||
wait_for_requests
|
||||
|
||||
page.within '.atwho-container #at-view-labels' do
|
||||
expect(find('.atwho-view-ul').text).to have_content('Accepting merge requests')
|
||||
end
|
||||
end
|
||||
|
||||
it 'only autocompletes the latest label' do
|
||||
create(:label, project: project, title: 'Accepting merge requests')
|
||||
create(:label, project: project, title: 'Accepting job applicants')
|
||||
|
||||
note = find('#note-body')
|
||||
type(note, '~Accepting merge requests foo bar ~Accepting job')
|
||||
|
||||
wait_for_requests
|
||||
|
||||
page.within '.atwho-container #at-view-labels' do
|
||||
expect(find('.atwho-view-ul').text).to have_content('Accepting job applicants')
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not autocomplete labels if no tilde is typed' do
|
||||
create(:label, project: project, title: 'Accepting merge requests')
|
||||
|
||||
note = find('#note-body')
|
||||
type(note, 'Accepting merge')
|
||||
|
||||
wait_for_requests
|
||||
|
||||
expect(page).not_to have_css('.atwho-container #at-view-labels')
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'autocomplete suggestions' do
|
||||
|
|
Loading…
Reference in New Issue