2021-01-07 09:10:22 +00:00
|
|
|
<script>
|
|
|
|
import { GlSearchBoxByType } from '@gitlab/ui';
|
|
|
|
import { uniq } from 'lodash';
|
|
|
|
import { EXCLUDED_NODES, HIDE_CLASS, HIGHLIGHT_CLASS, TYPING_DELAY } from '../constants';
|
|
|
|
|
2021-02-10 21:09:24 +00:00
|
|
|
const origExpansions = new Map();
|
|
|
|
|
2021-01-07 09:10:22 +00:00
|
|
|
const findSettingsSection = (sectionSelector, node) => {
|
|
|
|
return node.parentElement.closest(sectionSelector);
|
|
|
|
};
|
|
|
|
|
2021-02-10 21:09:24 +00:00
|
|
|
const restoreExpansionState = ({ expandSection, collapseSection }) => {
|
|
|
|
origExpansions.forEach((isExpanded, section) => {
|
|
|
|
if (isExpanded) {
|
2021-01-07 09:10:22 +00:00
|
|
|
expandSection(section);
|
|
|
|
} else {
|
|
|
|
collapseSection(section);
|
|
|
|
}
|
|
|
|
});
|
2021-02-10 21:09:24 +00:00
|
|
|
|
|
|
|
origExpansions.clear();
|
|
|
|
};
|
|
|
|
|
|
|
|
const saveExpansionState = (sections, { isExpanded }) => {
|
|
|
|
// If we've saved expansions before, don't override it.
|
|
|
|
if (origExpansions.size > 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
sections.forEach((section) => origExpansions.set(section, isExpanded(section)));
|
|
|
|
};
|
|
|
|
|
|
|
|
const resetSections = ({ sectionSelector }) => {
|
|
|
|
document.querySelectorAll(sectionSelector).forEach((section) => {
|
|
|
|
section.classList.remove(HIDE_CLASS);
|
|
|
|
});
|
2021-01-07 09:10:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const clearHighlights = () => {
|
|
|
|
document
|
|
|
|
.querySelectorAll(`.${HIGHLIGHT_CLASS}`)
|
|
|
|
.forEach((element) => element.classList.remove(HIGHLIGHT_CLASS));
|
|
|
|
};
|
|
|
|
|
|
|
|
const hideSectionsExcept = (sectionSelector, visibleSections) => {
|
|
|
|
Array.from(document.querySelectorAll(sectionSelector))
|
|
|
|
.filter((section) => !visibleSections.includes(section))
|
|
|
|
.forEach((section) => {
|
|
|
|
section.classList.add(HIDE_CLASS);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const highlightElements = (elements = []) => {
|
|
|
|
elements.forEach((element) => element.classList.add(HIGHLIGHT_CLASS));
|
|
|
|
};
|
|
|
|
|
|
|
|
const displayResults = ({ sectionSelector, expandSection }, matches) => {
|
|
|
|
const elements = matches.map((match) => match.parentElement);
|
|
|
|
const sections = uniq(elements.map((element) => findSettingsSection(sectionSelector, element)));
|
|
|
|
|
|
|
|
hideSectionsExcept(sectionSelector, sections);
|
|
|
|
sections.forEach(expandSection);
|
|
|
|
highlightElements(elements);
|
|
|
|
};
|
|
|
|
|
|
|
|
const clearResults = (params) => {
|
|
|
|
resetSections(params);
|
|
|
|
clearHighlights();
|
|
|
|
};
|
|
|
|
|
|
|
|
const includeNode = (node, lowerSearchTerm) =>
|
|
|
|
node.textContent.toLowerCase().includes(lowerSearchTerm) &&
|
|
|
|
EXCLUDED_NODES.every((excluded) => !node.parentElement.closest(excluded));
|
|
|
|
|
|
|
|
const search = (root, searchTerm) => {
|
|
|
|
const iterator = document.createNodeIterator(root, NodeFilter.SHOW_TEXT, {
|
|
|
|
acceptNode(node) {
|
|
|
|
return includeNode(node, searchTerm.toLowerCase())
|
|
|
|
? NodeFilter.FILTER_ACCEPT
|
|
|
|
: NodeFilter.FILTER_REJECT;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const results = [];
|
|
|
|
|
|
|
|
for (let currentNode = iterator.nextNode(); currentNode; currentNode = iterator.nextNode()) {
|
|
|
|
results.push(currentNode);
|
|
|
|
}
|
|
|
|
|
|
|
|
return results;
|
|
|
|
};
|
|
|
|
|
|
|
|
export default {
|
|
|
|
components: {
|
|
|
|
GlSearchBoxByType,
|
|
|
|
},
|
|
|
|
props: {
|
|
|
|
searchRoot: {
|
|
|
|
type: Element,
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
sectionSelector: {
|
|
|
|
type: String,
|
|
|
|
required: true,
|
|
|
|
},
|
2021-02-10 21:09:24 +00:00
|
|
|
isExpandedFn: {
|
|
|
|
type: Function,
|
|
|
|
required: false,
|
|
|
|
// default to a function that returns false
|
|
|
|
default: () => () => false,
|
|
|
|
},
|
2021-01-07 09:10:22 +00:00
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
searchTerm: '',
|
|
|
|
};
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
search(value) {
|
|
|
|
const displayOptions = {
|
|
|
|
sectionSelector: this.sectionSelector,
|
|
|
|
expandSection: this.expandSection,
|
|
|
|
collapseSection: this.collapseSection,
|
2021-02-10 21:09:24 +00:00
|
|
|
isExpanded: this.isExpandedFn,
|
2021-01-07 09:10:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
this.searchTerm = value;
|
|
|
|
|
|
|
|
clearResults(displayOptions);
|
|
|
|
|
|
|
|
if (value.length) {
|
2021-02-10 21:09:24 +00:00
|
|
|
saveExpansionState(document.querySelectorAll(this.sectionSelector), displayOptions);
|
|
|
|
|
2021-01-07 09:10:22 +00:00
|
|
|
displayResults(displayOptions, search(this.searchRoot, value));
|
2021-02-10 21:09:24 +00:00
|
|
|
} else {
|
|
|
|
restoreExpansionState(displayOptions);
|
2021-01-07 09:10:22 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
expandSection(section) {
|
|
|
|
this.$emit('expand', section);
|
|
|
|
},
|
|
|
|
collapseSection(section) {
|
|
|
|
this.$emit('collapse', section);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
TYPING_DELAY,
|
|
|
|
};
|
|
|
|
</script>
|
|
|
|
<template>
|
2021-01-29 12:09:29 +00:00
|
|
|
<gl-search-box-by-type
|
|
|
|
:value="searchTerm"
|
|
|
|
:debounce="$options.TYPING_DELAY"
|
|
|
|
:placeholder="__('Search settings')"
|
|
|
|
@input="search"
|
|
|
|
/>
|
2021-01-07 09:10:22 +00:00
|
|
|
</template>
|