BigW Consortium Gitlab
Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
G
gitlab-ce
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Forest Godfrey
gitlab-ce
Commits
bddd09c4
Commit
bddd09c4
authored
Jan 21, 2017
by
Jacob Schatz
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'issue-search-token-position' into 'master'
Filtered search input click back at token See merge request !8617
parents
e8552366
e75c20fc
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
165 additions
and
41 deletions
+165
-41
dropdown_hint.js.es6
app/assets/javascripts/filtered_search/dropdown_hint.js.es6
+1
-1
dropdown_non_user.js.es6
...sets/javascripts/filtered_search/dropdown_non_user.js.es6
+1
-1
dropdown_user.js.es6
app/assets/javascripts/filtered_search/dropdown_user.js.es6
+1
-1
dropdown_utils.js.es6
app/assets/javascripts/filtered_search/dropdown_utils.js.es6
+47
-6
filtered_search_dropdown_manager.js.es6
...s/filtered_search/filtered_search_dropdown_manager.js.es6
+21
-23
filtered_search_manager.js.es6
...avascripts/filtered_search/filtered_search_manager.js.es6
+13
-0
filter_issues_spec.rb
spec/features/issues/filtered_search/filter_issues_spec.rb
+47
-2
dropdown_utils_spec.js.es6
spec/javascripts/filtered_search/dropdown_utils_spec.js.es6
+34
-7
No files found.
app/assets/javascripts/filtered_search/dropdown_hint.js.es6
View file @
bddd09c4
...
...
@@ -9,7 +9,7 @@
this.config = {
droplabFilter: {
template: 'hint',
filterFunction: gl.DropdownUtils.filterHint,
filterFunction: gl.DropdownUtils.filterHint
.bind(null, input)
,
},
};
}
...
...
app/assets/javascripts/filtered_search/dropdown_non_user.js.es6
View file @
bddd09c4
...
...
@@ -15,7 +15,7 @@
loadingTemplate: this.loadingTemplate,
},
droplabFilter: {
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol),
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol
, input
),
},
};
}
...
...
app/assets/javascripts/filtered_search/dropdown_user.js.es6
View file @
bddd09c4
...
...
@@ -37,7 +37,7 @@
}
getSearchInput() {
const query =
this.input.value.trim(
);
const query =
gl.DropdownUtils.getSearchInput(this.input
);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
return lastToken.value || '';
...
...
app/assets/javascripts/filtered_search/dropdown_utils.js.es6
View file @
bddd09c4
...
...
@@ -20,17 +20,15 @@
return escapedText;
}
static filterWithSymbol(filterSymbol, i
tem, query
) {
static filterWithSymbol(filterSymbol, i
nput, item
) {
const updatedItem = item;
const query = gl.DropdownUtils.getSearchInput(input);
const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(query);
if (lastToken !== searchToken) {
const title = updatedItem.title.toLowerCase();
let value = lastToken.value.toLowerCase();
if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
value = value.slice(1);
}
value = value.replace(/"(.*?)"/g, str => str.slice(1).slice(0, -1));
// Eg. filterSymbol = ~ for labels
const matchWithoutSymbol = lastToken.symbol === filterSymbol && title.indexOf(value) !== -1;
...
...
@@ -44,8 +42,9 @@
return updatedItem;
}
static filterHint(i
tem, query
) {
static filterHint(i
nput, item
) {
const updatedItem = item;
const query = gl.DropdownUtils.getSearchInput(input);
let { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
lastToken = lastToken.key || lastToken || '';
...
...
@@ -72,6 +71,48 @@
// Return boolean based on whether it was set
return dataValue !== null;
}
static getSearchInput(filteredSearchInput) {
const inputValue = filteredSearchInput.value;
const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput);
return inputValue.slice(0, right);
}
static getInputSelectionPosition(input) {
const selectionStart = input.selectionStart;
let inputValue = input.value;
// Replace all spaces inside quote marks with underscores
// This helps with matching the beginning & end of a token:key
inputValue = inputValue.replace(/"(.*?)"/g, str => str.replace(/\s/g, '_'));
// Get the right position for the word selected
// Regex matches first space
let right = inputValue.slice(selectionStart).search(/\s/);
if (right >= 0) {
right += selectionStart;
} else if (right < 0) {
right = inputValue.length;
}
// Get the left position for the word selected
// Regex matches last non-whitespace character
let left = inputValue.slice(0, right).search(/\S+$/);
if (selectionStart === 0) {
left = 0;
} else if (selectionStart === inputValue.length && left < 0) {
left = inputValue.length;
} else if (left < 0) {
left = selectionStart;
}
return {
left,
right,
};
}
}
window.gl = window.gl || {};
...
...
app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
View file @
bddd09c4
...
...
@@ -57,28 +57,25 @@
static addWordToInput(tokenName, tokenValue = '') {
const input = document.querySelector('.filtered-search');
const inputValue = input.value;
const word = `${tokenName}:${tokenValue}`;
const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(input.value);
const lastSearchToken = searchToken.split(' ').last();
const lastInputCharacter = input.value[input.value.length - 1];
const lastInputTrimmedCharacter = input.value.trim()[input.value.trim().length - 1];
// Remove the typed tokenName
if (word.indexOf(lastSearchToken) === 0 && searchToken !== '') {
// Remove spaces after the colon
if (lastInputCharacter === ' ' && lastInputTrimmedCharacter === ':') {
input.value = input.value.trim();
}
input.value = input.value.slice(0, -1 * lastSearchToken.length);
} else if (lastInputCharacter !== ' ' || (lastToken && lastToken.value[lastToken.value.length - 1] === ' ')) {
// Remove the existing tokenValue
const lastTokenString = `${lastToken.key}:${lastToken.symbol}${lastToken.value}`;
input.value = input.value.slice(0, -1 * lastTokenString.length);
}
// Get the string to replace
const selectionStart = input.selectionStart;
const { left, right } = gl.DropdownUtils.getInputSelectionPosition(input);
input.value = `${inputValue.substr(0, left)}${word}${inputValue.substr(right)}`;
gl.FilteredSearchDropdownManager.updateInputCaretPosition(selectionStart, input);
}
static updateInputCaretPosition(selectionStart, input) {
// Reset the position
// Sometimes can end up at end of input
input.setSelectionRange(selectionStart, selectionStart);
const { right } = gl.DropdownUtils.getInputSelectionPosition(input);
input.
value += word
;
input.
setSelectionRange(right, right)
;
}
updateCurrentDropdownOffset() {
...
...
@@ -90,9 +87,10 @@
this.font = window.getComputedStyle(this.filteredSearchInput).font;
}
const input = this.filteredSearchInput;
const inputText = input.value.slice(0, input.selectionStart);
const filterIconPadding = 27;
const offset = gl.text
.getTextWidth(this.filteredSearchInput.value, this.font) + filterIconPadding;
const offset = gl.text.getTextWidth(inputText, this.font) + filterIconPadding;
this.mapping[key].reference.setOffset(offset);
}
...
...
@@ -148,9 +146,9 @@
setDropdown() {
const { lastToken, searchToken } = this.tokenizer
.processTokens(
this.filteredSearchInput.value
);
.processTokens(
gl.DropdownUtils.getSearchInput(this.filteredSearchInput)
);
if (this.
filteredSearchInput.value.split('').last() === ' '
) {
if (this.
currentDropdown
) {
this.updateCurrentDropdownOffset();
}
...
...
app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
View file @
bddd09c4
...
...
@@ -30,11 +30,14 @@
this.checkForEnterWrapper = this.checkForEnter.bind(this);
this.clearSearchWrapper = this.clearSearch.bind(this);
this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
this.tokenChange = this.tokenChange.bind(this);
this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper);
this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper);
this.filteredSearchInput.addEventListener('click', this.tokenChange);
this.filteredSearchInput.addEventListener('keyup', this.tokenChange);
this.clearSearchButton.addEventListener('click', this.clearSearchWrapper);
}
...
...
@@ -43,6 +46,8 @@
this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper);
this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper);
this.filteredSearchInput.removeEventListener('click', this.tokenChange);
this.filteredSearchInput.removeEventListener('keyup', this.tokenChange);
this.clearSearchButton.removeEventListener('click', this.clearSearchWrapper);
}
...
...
@@ -188,6 +193,14 @@
}
return usernamesById;
}
tokenChange() {
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
const currentDropdownRef = dropdown.reference;
this.setDropdownWrapper();
currentDropdownRef.dispatchInputEvent();
}
}
window.gl = window.gl || {};
...
...
spec/features/issues/filtered_search/filter_issues_spec.rb
View file @
bddd09c4
...
...
@@ -19,9 +19,12 @@ describe 'Filter issues', js: true, feature: true do
let!
(
:closed_issue
)
{
create
(
:issue
,
title:
'bug that is closed'
,
project:
project
,
state: :closed
)
}
let
(
:filtered_search
)
{
find
(
'.filtered-search'
)
}
def
input_filtered_search
(
search_term
)
def
input_filtered_search
(
search_term
,
submit:
true
)
filtered_search
.
set
(
search_term
)
filtered_search
.
send_keys
(
:enter
)
if
submit
filtered_search
.
send_keys
(
:enter
)
end
end
def
expect_filtered_search_input
(
input
)
...
...
@@ -43,6 +46,10 @@ describe 'Filter issues', js: true, feature: true do
end
end
def
select_search_at_index
(
pos
)
evaluate_script
(
"el = document.querySelector('.filtered-search'); el.focus(); el.setSelectionRange(
#{
pos
}
,
#{
pos
}
);"
)
end
before
do
project
.
team
<<
[
user
,
:master
]
project
.
team
<<
[
user2
,
:master
]
...
...
@@ -522,6 +529,44 @@ describe 'Filter issues', js: true, feature: true do
end
end
describe
'overwrites selected filter'
do
it
'changes author'
do
input_filtered_search
(
"author:@
#{
user
.
username
}
"
,
submit:
false
)
select_search_at_index
(
3
)
page
.
within
'#js-dropdown-author'
do
click_button
user2
.
username
end
expect
(
filtered_search
.
value
).
to
eq
(
"author:@
#{
user2
.
username
}
"
)
end
it
'changes label'
do
input_filtered_search
(
"author:@
#{
user
.
username
}
label:~
#{
bug_label
.
title
}
"
,
submit:
false
)
select_search_at_index
(
27
)
page
.
within
'#js-dropdown-label'
do
click_button
label
.
name
end
expect
(
filtered_search
.
value
).
to
eq
(
"author:@
#{
user
.
username
}
label:~
#{
label
.
name
}
"
)
end
it
'changes label correctly space is in previous label'
do
input_filtered_search
(
"label:~
\"
#{
multiple_words_label
.
title
}
\"
"
,
submit:
false
)
select_search_at_index
(
0
)
page
.
within
'#js-dropdown-label'
do
click_button
label
.
name
end
expect
(
filtered_search
.
value
).
to
eq
(
"label:~
#{
label
.
name
}
"
)
end
end
describe
'filter issues by text'
do
context
'only text'
do
it
'filters issues by searched text'
do
...
...
spec/javascripts/filtered_search/dropdown_utils_spec.js.es6
View file @
bddd09c4
...
...
@@ -31,41 +31,68 @@
});
describe('filterWithSymbol', () => {
let input;
const item = {
title: '@root',
};
beforeEach(() => {
setFixtures(`
<input type="text" id="test" />
`);
input = document.getElementById('test');
});
it('should filter without symbol', () => {
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':roo');
input.value = ':roo';
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with symbol', () => {
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':@roo');
input.value = '@roo';
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item);
expect(updatedItem.droplab_hidden).toBe(false);
});
it('should filter with colon', () => {
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':');
input.value = 'roo';
const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item);
expect(updatedItem.droplab_hidden).toBe(false);
});
});
describe('filterHint', () => {
let input;
beforeEach(() => {
setFixtures(`
<input type="text" id="test" />
`);
input = document.getElementById('test');
});
it('should filter', () => {
let updatedItem = gl.DropdownUtils.filterHint({
input.value = 'l';
let updatedItem = gl.DropdownUtils.filterHint(input, {
hint: 'label',
}
, 'l'
);
});
expect(updatedItem.droplab_hidden).toBe(false);
updatedItem = gl.DropdownUtils.filterHint({
input.value = 'o';
updatedItem = gl.DropdownUtils.filterHint(input, {
hint: 'label',
}, 'o');
expect(updatedItem.droplab_hidden).toBe(true);
});
it('should return droplab_hidden false when item has no hint', () => {
const updatedItem = gl.DropdownUtils.filterHint({}, '');
const updatedItem = gl.DropdownUtils.filterHint(
input,
{}, '');
expect(updatedItem.droplab_hidden).toBe(false);
});
});
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment