diff options
author | Kushal Pandya <kushalspandya@gmail.com> | 2019-06-25 10:19:30 +0000 |
---|---|---|
committer | Kushal Pandya <kushalspandya@gmail.com> | 2019-06-25 10:19:30 +0000 |
commit | b1038a3a8db9960f39812d7c6e3c888e35c4d679 (patch) | |
tree | 9527aca4e2d130fe30948f137791f12b9ed32343 | |
parent | e3fa9d122b0a593b0c6441a35cbad8ab6c8e70b9 (diff) | |
parent | de7abc06511fef0342af256075987903f68567bd (diff) | |
download | gitlab-ce-b1038a3a8db9960f39812d7c6e3c888e35c4d679.tar.gz |
Merge branch 'mh/colon-autocomplete' into 'master'
Allow autocompleting labels using colons and spaces
Closes #63343 and #56510
See merge request gitlab-org/gitlab-ce!29749
-rw-r--r-- | app/assets/javascripts/commons/polyfills.js | 1 | ||||
-rw-r--r-- | app/assets/javascripts/gfm_auto_complete.js | 26 | ||||
-rw-r--r-- | changelogs/unreleased/mh-colon-autocomplete.yml | 5 | ||||
-rw-r--r-- | spec/features/issues/gfm_autocomplete_spec.rb | 70 |
4 files changed, 97 insertions, 5 deletions
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js index d0cc4897aeb..a4394ab7e92 100644 --- a/app/assets/javascripts/commons/polyfills.js +++ b/app/assets/javascripts/commons/polyfills.js @@ -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'; diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 3b73dd83c9f..b308cd9c236 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -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', ); diff --git a/changelogs/unreleased/mh-colon-autocomplete.yml b/changelogs/unreleased/mh-colon-autocomplete.yml new file mode 100644 index 00000000000..8b169c22588 --- /dev/null +++ b/changelogs/unreleased/mh-colon-autocomplete.yml @@ -0,0 +1,5 @@ +--- +title: Allow auto-completing scoped labels +merge_request: 29749 +author: +type: added diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 8eb413bdd8d..40845ec48f9 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -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 |