182 lines
6.0 KiB
JavaScript
182 lines
6.0 KiB
JavaScript
import FilteredSearchContainer from './container';
|
|
|
|
(() => {
|
|
class DropdownUtils {
|
|
static getEscapedText(text) {
|
|
let escapedText = text;
|
|
const hasSpace = text.indexOf(' ') !== -1;
|
|
const hasDoubleQuote = text.indexOf('"') !== -1;
|
|
|
|
// Encapsulate value with quotes if it has spaces
|
|
// Known side effect: values's with both single and double quotes
|
|
// won't escape properly
|
|
if (hasSpace) {
|
|
if (hasDoubleQuote) {
|
|
escapedText = `'${text}'`;
|
|
} else {
|
|
// Encapsulate singleQuotes or if it hasSpace
|
|
escapedText = `"${text}"`;
|
|
}
|
|
}
|
|
|
|
return escapedText;
|
|
}
|
|
|
|
static filterWithSymbol(filterSymbol, input, item) {
|
|
const updatedItem = item;
|
|
const searchInput = gl.DropdownUtils.getSearchInput(input);
|
|
|
|
const title = updatedItem.title.toLowerCase();
|
|
let value = searchInput.toLowerCase();
|
|
let symbol = '';
|
|
|
|
// Remove the symbol for filter
|
|
if (value[0] === filterSymbol) {
|
|
symbol = value[0];
|
|
value = value.slice(1);
|
|
}
|
|
|
|
// Removes the first character if it is a quotation so that we can search
|
|
// with multiple words
|
|
if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
|
|
value = value.slice(1);
|
|
}
|
|
|
|
// Eg. filterSymbol = ~ for labels
|
|
const matchWithoutSymbol = symbol === filterSymbol && title.indexOf(value) !== -1;
|
|
const match = title.indexOf(`${symbol}${value}`) !== -1;
|
|
|
|
updatedItem.droplab_hidden = !match && !matchWithoutSymbol;
|
|
|
|
return updatedItem;
|
|
}
|
|
|
|
static filterHint(input, item) {
|
|
const updatedItem = item;
|
|
const searchInput = gl.DropdownUtils.getSearchQuery(input);
|
|
const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput);
|
|
const lastKey = lastToken.key || lastToken || '';
|
|
const allowMultiple = item.type === 'array';
|
|
const itemInExistingTokens = tokens.some(t => t.key === item.hint);
|
|
|
|
if (!allowMultiple && itemInExistingTokens) {
|
|
updatedItem.droplab_hidden = true;
|
|
} else if (!lastKey || searchInput.split('').last() === ' ') {
|
|
updatedItem.droplab_hidden = false;
|
|
} else if (lastKey) {
|
|
const split = lastKey.split(':');
|
|
const tokenName = split[0].split(' ').last();
|
|
|
|
const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1;
|
|
updatedItem.droplab_hidden = tokenName ? match : false;
|
|
}
|
|
|
|
return updatedItem;
|
|
}
|
|
|
|
static setDataValueIfSelected(filter, selected) {
|
|
const dataValue = selected.getAttribute('data-value');
|
|
|
|
if (dataValue) {
|
|
gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue, true);
|
|
}
|
|
|
|
// Return boolean based on whether it was set
|
|
return dataValue !== null;
|
|
}
|
|
|
|
// Determines the full search query (visual tokens + input)
|
|
static getSearchQuery(untilInput = false) {
|
|
const container = FilteredSearchContainer.container;
|
|
const tokens = [].slice.call(container.querySelectorAll('.tokens-container li'));
|
|
const values = [];
|
|
|
|
if (untilInput) {
|
|
const inputIndex = _.findIndex(tokens, t => t.classList.contains('input-token'));
|
|
// Add one to include input-token to the tokens array
|
|
tokens.splice(inputIndex + 1);
|
|
}
|
|
|
|
tokens.forEach((token) => {
|
|
if (token.classList.contains('js-visual-token')) {
|
|
const name = token.querySelector('.name');
|
|
const value = token.querySelector('.value');
|
|
const symbol = value && value.dataset.symbol ? value.dataset.symbol : '';
|
|
let valueText = '';
|
|
|
|
if (value && value.innerText) {
|
|
valueText = value.innerText;
|
|
}
|
|
|
|
if (token.className.indexOf('filtered-search-token') !== -1) {
|
|
values.push(`${name.innerText.toLowerCase()}:${symbol}${valueText}`);
|
|
} else {
|
|
values.push(name.innerText);
|
|
}
|
|
} else if (token.classList.contains('input-token')) {
|
|
const { isLastVisualTokenValid } =
|
|
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
|
|
|
|
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
|
|
const inputValue = input && input.value;
|
|
|
|
if (isLastVisualTokenValid) {
|
|
values.push(inputValue);
|
|
} else {
|
|
const previous = values.pop();
|
|
values.push(`${previous}${inputValue}`);
|
|
}
|
|
}
|
|
});
|
|
|
|
return values.join(' ');
|
|
}
|
|
|
|
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
|
|
// (will continue to match entire string until an end quote is found if any)
|
|
// This helps with matching the beginning & end of a token:key
|
|
inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/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 || {};
|
|
gl.DropdownUtils = DropdownUtils;
|
|
})();
|