diff options
author | Robert Speicher <rspeicher@gmail.com> | 2021-01-20 22:34:23 +0300 |
---|---|---|
committer | Robert Speicher <rspeicher@gmail.com> | 2021-01-20 22:34:23 +0300 |
commit | 6438df3a1e0fb944485cebf07976160184697d72 (patch) | |
tree | 00b09bfd170e77ae9391b1a2f5a93ef6839f2597 /app/assets/javascripts/search_settings | |
parent | 42bcd54d971da7ef2854b896a7b34f4ef8601067 (diff) |
Add latest changes from gitlab-org/gitlab@13-8-stable-eev13.8.0-rc42
Diffstat (limited to 'app/assets/javascripts/search_settings')
3 files changed, 163 insertions, 0 deletions
diff --git a/app/assets/javascripts/search_settings/components/search_settings.vue b/app/assets/javascripts/search_settings/components/search_settings.vue new file mode 100644 index 00000000000..820055dc656 --- /dev/null +++ b/app/assets/javascripts/search_settings/components/search_settings.vue @@ -0,0 +1,129 @@ +<script> +import { GlSearchBoxByType } from '@gitlab/ui'; +import { uniq } from 'lodash'; +import { EXCLUDED_NODES, HIDE_CLASS, HIGHLIGHT_CLASS, TYPING_DELAY } from '../constants'; + +const findSettingsSection = (sectionSelector, node) => { + return node.parentElement.closest(sectionSelector); +}; + +const resetSections = ({ sectionSelector, expandSection, collapseSection }) => { + document.querySelectorAll(sectionSelector).forEach((section, index) => { + section.classList.remove(HIDE_CLASS); + + if (index === 0) { + expandSection(section); + } else { + collapseSection(section); + } + }); +}; + +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, + }, + }, + data() { + return { + searchTerm: '', + }; + }, + methods: { + search(value) { + const displayOptions = { + sectionSelector: this.sectionSelector, + expandSection: this.expandSection, + collapseSection: this.collapseSection, + }; + + this.searchTerm = value; + + clearResults(displayOptions); + + if (value.length) { + displayResults(displayOptions, search(this.searchRoot, value)); + } + }, + expandSection(section) { + this.$emit('expand', section); + }, + collapseSection(section) { + this.$emit('collapse', section); + }, + }, + TYPING_DELAY, +}; +</script> +<template> + <div class="gl-mt-5"> + <gl-search-box-by-type + :value="searchTerm" + :debounce="$options.TYPING_DELAY" + :placeholder="__('Search settings')" + @input="search" + /> + </div> +</template> diff --git a/app/assets/javascripts/search_settings/constants.js b/app/assets/javascripts/search_settings/constants.js new file mode 100644 index 00000000000..499e42854ed --- /dev/null +++ b/app/assets/javascripts/search_settings/constants.js @@ -0,0 +1,11 @@ +// We do not consider these nodes in the search index +export const EXCLUDED_NODES = ['OPTION']; + +// Used to hide the sections that do not match * the search term +export const HIDE_CLASS = 'gl-display-none'; + +// used to highlight the text that matches the * search term +export const HIGHLIGHT_CLASS = 'gl-bg-orange-50'; + +// How many seconds to wait until the user * stops typing +export const TYPING_DELAY = 400; diff --git a/app/assets/javascripts/search_settings/index.js b/app/assets/javascripts/search_settings/index.js new file mode 100644 index 00000000000..1fb1a378ffb --- /dev/null +++ b/app/assets/javascripts/search_settings/index.js @@ -0,0 +1,23 @@ +import Vue from 'vue'; +import $ from 'jquery'; +import { expandSection, closeSection } from '~/settings_panels'; +import SearchSettings from '~/search_settings/components/search_settings.vue'; + +const initSearch = ({ el }) => + new Vue({ + el, + render: (h) => + h(SearchSettings, { + ref: 'searchSettings', + props: { + searchRoot: document.querySelector('#content-body'), + sectionSelector: 'section.settings', + }, + on: { + collapse: (section) => closeSection($(section)), + expand: (section) => expandSection($(section)), + }, + }), + }); + +export default initSearch; |