debian-mirror-gitlab/app/assets/javascripts/search_settings/components/search_settings.vue

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

216 lines
5.6 KiB
Vue
Raw Normal View History

2021-03-08 18:12:59 +05:30
<script>
2022-11-25 23:54:43 +05:30
import { GlEmptyState, GlSearchBoxByType } from '@gitlab/ui';
2022-06-21 17:19:12 +05:30
import { escapeRegExp } from 'lodash';
2021-11-18 22:05:49 +05:30
import {
EXCLUDED_NODES,
HIDE_CLASS,
HIGHLIGHT_CLASS,
NONE_PADDING_CLASS,
TYPING_DELAY,
} from '../constants';
2021-03-08 18:12:59 +05:30
2021-03-11 19:13:27 +05:30
const origExpansions = new Map();
2021-03-08 18:12:59 +05:30
const findSettingsSection = (sectionSelector, node) => {
return node.parentElement.closest(sectionSelector);
};
2021-03-11 19:13:27 +05:30
const restoreExpansionState = ({ expandSection, collapseSection }) => {
origExpansions.forEach((isExpanded, section) => {
if (isExpanded) {
2021-03-08 18:12:59 +05:30
expandSection(section);
} else {
collapseSection(section);
}
});
2021-03-11 19:13:27 +05:30
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-03-08 18:12:59 +05:30
};
const clearHighlights = () => {
2021-11-18 22:05:49 +05:30
document.querySelectorAll(`.${HIGHLIGHT_CLASS}`).forEach((element) => {
const { parentNode } = element;
const textNode = document.createTextNode(element.textContent);
parentNode.replaceChild(textNode, element);
parentNode.normalize();
});
2021-03-08 18:12:59 +05:30
};
const hideSectionsExcept = (sectionSelector, visibleSections) => {
Array.from(document.querySelectorAll(sectionSelector))
.filter((section) => !visibleSections.includes(section))
.forEach((section) => {
section.classList.add(HIDE_CLASS);
});
};
2022-06-21 17:19:12 +05:30
const highlightTextNode = (textNode, searchTerm) => {
2021-11-18 22:05:49 +05:30
const escapedSearchTerm = new RegExp(`(${escapeRegExp(searchTerm)})`, 'gi');
2022-06-21 17:19:12 +05:30
const textList = textNode.data.split(escapedSearchTerm);
return textList.reduce((documentFragment, text) => {
let addElement;
2021-11-18 22:05:49 +05:30
if (escapedSearchTerm.test(text)) {
addElement = document.createElement('mark');
addElement.className = `${HIGHLIGHT_CLASS} ${NONE_PADDING_CLASS}`;
addElement.textContent = text;
escapedSearchTerm.lastIndex = 0;
2022-06-21 17:19:12 +05:30
} else {
addElement = document.createTextNode(text);
2021-11-18 22:05:49 +05:30
}
2022-06-21 17:19:12 +05:30
documentFragment.appendChild(addElement);
return documentFragment;
}, document.createDocumentFragment());
2021-11-18 22:05:49 +05:30
};
2022-06-21 17:19:12 +05:30
const highlightText = (textNodes = [], searchTerm) => {
textNodes.forEach((textNode) => {
const fragmentWithHighlights = highlightTextNode(textNode, searchTerm);
textNode.parentElement.replaceChild(fragmentWithHighlights, textNode);
2021-11-18 22:05:49 +05:30
});
2021-03-08 18:12:59 +05:30
};
2022-06-21 17:19:12 +05:30
const displayResults = ({ sectionSelector, expandSection, searchTerm }, matchingTextNodes) => {
const sections = Array.from(
new Set(matchingTextNodes.map((node) => findSettingsSection(sectionSelector, node))),
);
2021-03-08 18:12:59 +05:30
hideSectionsExcept(sectionSelector, sections);
sections.forEach(expandSection);
2022-06-21 17:19:12 +05:30
highlightText(matchingTextNodes, searchTerm);
2022-11-25 23:54:43 +05:30
return sections.length > 0;
2021-03-08 18:12:59 +05:30
};
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;
},
});
2022-06-21 17:19:12 +05:30
const textNodes = [];
2021-03-08 18:12:59 +05:30
for (let currentNode = iterator.nextNode(); currentNode; currentNode = iterator.nextNode()) {
2022-06-21 17:19:12 +05:30
textNodes.push(currentNode);
2021-03-08 18:12:59 +05:30
}
2022-06-21 17:19:12 +05:30
return textNodes;
2021-03-08 18:12:59 +05:30
};
export default {
components: {
2022-11-25 23:54:43 +05:30
GlEmptyState,
2021-03-08 18:12:59 +05:30
GlSearchBoxByType,
},
props: {
searchRoot: {
type: Element,
required: true,
},
sectionSelector: {
type: String,
required: true,
},
2022-11-25 23:54:43 +05:30
hideWhenEmptySelector: {
type: String,
required: true,
default: null,
},
2021-03-11 19:13:27 +05:30
isExpandedFn: {
type: Function,
required: false,
// default to a function that returns false
default: () => () => false,
},
2021-03-08 18:12:59 +05:30
},
data() {
return {
searchTerm: '',
2022-11-25 23:54:43 +05:30
hasMatches: true,
2021-03-08 18:12:59 +05:30
};
},
2022-11-25 23:54:43 +05:30
watch: {
hasMatches(newHasMatches) {
document.querySelectorAll(this.hideWhenEmptySelector).forEach((section) => {
section.classList.toggle(HIDE_CLASS, !newHasMatches);
});
},
},
2021-03-08 18:12:59 +05:30
methods: {
search(value) {
2021-11-18 22:05:49 +05:30
this.searchTerm = value;
2021-03-08 18:12:59 +05:30
const displayOptions = {
sectionSelector: this.sectionSelector,
expandSection: this.expandSection,
collapseSection: this.collapseSection,
2021-03-11 19:13:27 +05:30
isExpanded: this.isExpandedFn,
2021-11-18 22:05:49 +05:30
searchTerm: this.searchTerm,
2021-03-08 18:12:59 +05:30
};
clearResults(displayOptions);
2022-11-25 23:54:43 +05:30
this.hasMatches = true;
2021-03-08 18:12:59 +05:30
if (value.length) {
2021-03-11 19:13:27 +05:30
saveExpansionState(document.querySelectorAll(this.sectionSelector), displayOptions);
2022-11-25 23:54:43 +05:30
this.hasMatches = displayResults(displayOptions, search(this.searchRoot, this.searchTerm));
2021-03-11 19:13:27 +05:30
} else {
restoreExpansionState(displayOptions);
2021-03-08 18:12:59 +05:30
}
},
expandSection(section) {
this.$emit('expand', section);
},
collapseSection(section) {
this.$emit('collapse', section);
},
},
TYPING_DELAY,
};
</script>
<template>
2022-11-25 23:54:43 +05:30
<div>
<gl-search-box-by-type
:value="searchTerm"
:debounce="$options.TYPING_DELAY"
:placeholder="__('Search page')"
@input="search"
/>
<gl-empty-state
v-if="!hasMatches"
:title="__('No results found')"
:description="__('Edit your search and try again')"
/>
</div>
2021-03-08 18:12:59 +05:30
</template>