diff options
author | Hiroyuki Sato <sathiroyuki@gmail.com> | 2017-08-30 07:48:55 +0000 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2017-08-30 07:48:55 +0000 |
commit | 7187395ef13d8d84a145d1b5251882ebada3f7f2 (patch) | |
tree | 06188448a7059648d5ca99c159f525eaf3499cc3 /app/assets/javascripts/filtered_search | |
parent | df8ca5aaab21f47c328cc15f2c454b9cc97a3ed5 (diff) | |
download | gitlab-ce-7187395ef13d8d84a145d1b5251882ebada3f7f2.tar.gz |
Add filter by my reaction
Diffstat (limited to 'app/assets/javascripts/filtered_search')
7 files changed, 141 insertions, 4 deletions
diff --git a/app/assets/javascripts/filtered_search/dropdown_emoji.js b/app/assets/javascripts/filtered_search/dropdown_emoji.js new file mode 100644 index 00000000000..f9bbbf0cbc1 --- /dev/null +++ b/app/assets/javascripts/filtered_search/dropdown_emoji.js @@ -0,0 +1,82 @@ +/* global Flash */ + +import Ajax from '~/droplab/plugins/ajax'; +import Filter from '~/droplab/plugins/filter'; +import './filtered_search_dropdown'; + +class DropdownEmoji extends gl.FilteredSearchDropdown { + constructor(options = {}) { + super(options); + this.config = { + Ajax: { + endpoint: `${gon.relative_url_root || ''}/autocomplete/award_emojis`, + method: 'setData', + loadingTemplate: this.loadingTemplate, + onError() { + /* eslint-disable no-new */ + new Flash('An error occured fetching the dropdown data.'); + /* eslint-enable no-new */ + }, + }, + Filter: { + template: 'name', + }, + }; + + import(/* webpackChunkName: 'emoji' */ '~/emoji') + .then(({ glEmojiTag }) => { this.glEmojiTag = glEmojiTag; }) + .catch(() => { /* ignore error and leave emoji name in the search bar */ }); + + this.unbindEvents(); + this.bindEvents(); + } + + bindEvents() { + super.bindEvents(); + + this.listRenderedWrapper = this.listRendered.bind(this); + this.dropdown.addEventListener('render.dl', this.listRenderedWrapper); + } + + unbindEvents() { + this.dropdown.removeEventListener('render.dl', this.listRenderedWrapper); + super.unbindEvents(); + } + + listRendered() { + this.replaceEmojiElement(); + } + + itemClicked(e) { + super.itemClicked(e, (selected) => { + const name = selected.querySelector('.js-data-value').innerText.trim(); + return gl.DropdownUtils.getEscapedText(name); + }); + } + + renderContent(forceShowList = false) { + this.droplab.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config); + super.renderContent(forceShowList); + } + + replaceEmojiElement() { + if (!this.glEmojiTag) return; + + // Replace empty gl-emoji tag to real content + const dropdownItems = [...this.dropdown.querySelectorAll('.filter-dropdown-item')]; + dropdownItems.forEach((dropdownItem) => { + const name = dropdownItem.querySelector('.js-data-value').innerText; + const emojiTag = this.glEmojiTag(name); + const emojiElement = dropdownItem.querySelector('gl-emoji'); + emojiElement.outerHTML = emojiTag; + }); + } + + init() { + this.droplab + .addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init(); + } +} + +window.gl = window.gl || {}; +gl.DropdownEmoji = DropdownEmoji; diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js index a81389ab088..1c5ca1d3cf9 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js @@ -61,7 +61,7 @@ class DropdownHint extends gl.FilteredSearchDropdown { .map(tokenKey => ({ icon: `fa-${tokenKey.icon}`, hint: tokenKey.key, - tag: `<${tokenKey.symbol}${tokenKey.key}>`, + tag: `<${tokenKey.tag}>`, type: tokenKey.type, })); diff --git a/app/assets/javascripts/filtered_search/filtered_search_bundle.js b/app/assets/javascripts/filtered_search/filtered_search_bundle.js index 132b6fe698a..6d5dd747224 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_bundle.js +++ b/app/assets/javascripts/filtered_search/filtered_search_bundle.js @@ -1,3 +1,4 @@ +import './dropdown_emoji'; import './dropdown_hint'; import './dropdown_non_user'; import './dropdown_user'; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js index dd1c067df87..46c80dfd45e 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -58,6 +58,11 @@ class FilteredSearchDropdownManager { }, element: this.container.querySelector('#js-dropdown-label'), }, + 'my-reaction': { + reference: null, + gl: 'DropdownEmoji', + element: this.container.querySelector('#js-dropdown-my-reaction'), + }, hint: { reference: null, gl: 'DropdownHint', diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index a31be2b0bc7..038239bf466 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -439,8 +439,13 @@ class FilteredSearchManager { const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam); if (match) { - const indexOf = keyParam.indexOf('_'); - const sanitizedKey = indexOf !== -1 ? keyParam.slice(0, keyParam.indexOf('_')) : keyParam; + // Use lastIndexOf because the token key is allowed to contain underscore + // e.g. 'my_reaction' is the token key of 'my_reaction_emoji' + const lastIndexOf = keyParam.lastIndexOf('_'); + let sanitizedKey = lastIndexOf !== -1 ? keyParam.slice(0, lastIndexOf) : keyParam; + // Replace underscore with hyphen in the sanitizedkey. + // e.g. 'my_reaction' => 'my-reaction' + sanitizedKey = sanitizedKey.replace('_', '-'); const symbol = match.symbol; let quotationsToUse = ''; @@ -515,7 +520,10 @@ class FilteredSearchManager { const condition = this.filteredSearchTokenKeys .searchByConditionKeyValue(token.key, token.value.toLowerCase()); const { param } = this.filteredSearchTokenKeys.searchByKey(token.key) || {}; - const keyParam = param ? `${token.key}_${param}` : token.key; + // Replace hyphen with underscore to use as request parameter + // e.g. 'my-reaction' => 'my_reaction' + const underscoredKey = token.key.replace('-', '_'); + const keyParam = param ? `${underscoredKey}_${param}` : underscoredKey; let tokenPath = ''; if (condition) { diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js index 025d4d8795b..be595d7df1a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js @@ -4,26 +4,42 @@ const tokenKeys = [{ param: 'username', symbol: '@', icon: 'pencil', + tag: '@author', }, { key: 'assignee', type: 'string', param: 'username', symbol: '@', icon: 'user', + tag: '@assignee', }, { key: 'milestone', type: 'string', param: 'title', symbol: '%', icon: 'clock-o', + tag: '%milestone', }, { key: 'label', type: 'array', param: 'name[]', symbol: '~', icon: 'tag', + tag: '~label', }]; +if (gon.current_user_id) { + // Appending tokenkeys only logged-in + tokenKeys.push({ + key: 'my-reaction', + type: 'string', + param: 'emoji', + symbol: '', + icon: 'thumbs-up', + tag: 'emoji', + }); +} + const alternativeTokenKeys = [{ key: 'label', type: 'string', @@ -84,6 +100,10 @@ class FilteredSearchTokenKeys { return tokenKeysWithAlternative.find((tokenKey) => { let tokenKeyParam = tokenKey.key; + // Replace hyphen with underscore to compare keyParam with tokenKeyParam + // e.g. 'my-reaction' => 'my_reaction' + tokenKeyParam = tokenKeyParam.replace('-', '_'); + if (tokenKey.param) { tokenKeyParam += `_${tokenKey.param}`; } diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index 243ee4d723a..28e8240169d 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -132,6 +132,23 @@ class FilteredSearchVisualTokens { .catch(() => { }); } + static updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) { + const container = tokenValueContainer; + const element = tokenValueElement; + + return import(/* webpackChunkName: 'emoji' */ '../emoji') + .then((Emoji) => { + if (!Emoji.isEmojiNameValid(tokenValue)) { + return; + } + + container.dataset.originalValue = tokenValue; + element.innerHTML = Emoji.glEmojiTag(tokenValue); + }) + // ignore error and leave emoji name in the search bar + .catch(() => { }); + } + static renderVisualTokenValue(parentElement, tokenName, tokenValue) { const tokenValueContainer = parentElement.querySelector('.value-container'); const tokenValueElement = tokenValueContainer.querySelector('.value'); @@ -144,6 +161,10 @@ class FilteredSearchVisualTokens { FilteredSearchVisualTokens.updateUserTokenAppearance( tokenValueContainer, tokenValueElement, tokenValue, ); + } else if (tokenType === 'my-reaction') { + FilteredSearchVisualTokens.updateEmojiTokenAppearance( + tokenValueContainer, tokenValueElement, tokenValue, + ); } } |