diff options
author | David O'Regan <doregan@gitlab.com> | 2023-02-18 09:20:53 +0300 |
---|---|---|
committer | David O'Regan <doregan@gitlab.com> | 2023-02-18 09:20:53 +0300 |
commit | 0f33faabd5810059187b1996fa3a92ba0179c41d (patch) | |
tree | c03b0ca7a6183497352d79283a7a8be005d0d38a | |
parent | 8593a8ca83f96738661baec8c48e4e3838adfbf8 (diff) | |
parent | f15693455391c9d5403bc91437a055246d0447bf (diff) |
Merge branch '1311-gps-filters' into 'main'
Add search filtering options
Closes #1311
See merge request https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/3554
Merged-by: David O'Regan <doregan@gitlab.com>
Approved-by: Suzanne Selhorn <sselhorn@gitlab.com>
Approved-by: David O'Regan <doregan@gitlab.com>
Co-authored-by: Sarah German <sgerman@gitlab.com>
-rw-r--r-- | content/_data/navigation.yaml | 7 | ||||
-rw-r--r-- | content/assets/stylesheets/_utilities.scss | 11 | ||||
-rw-r--r-- | content/assets/stylesheets/stylesheet.scss | 20 | ||||
-rw-r--r-- | content/frontend/search/components/google_results.vue | 144 | ||||
-rw-r--r-- | content/frontend/search/components/search_filters.vue | 37 | ||||
-rw-r--r-- | package.json | 1 |
6 files changed, 166 insertions, 54 deletions
diff --git a/content/_data/navigation.yaml b/content/_data/navigation.yaml index b04ee9e3..943aa3ce 100644 --- a/content/_data/navigation.yaml +++ b/content/_data/navigation.yaml @@ -17,6 +17,13 @@ # Options: # # - Use "external_url: true" for external URLs in docs and categories +# +# Section titles: +# +# Section titles are also used as options for filtering search results. +# If you add, edit, or remove a section_title, you will need to also update +# the filters in /content/frontend/search/components/search_filters.vue. +# You may want to get a frontend engineer to assist with this. sections: # Documentation from https://gitlab.com/gitlab-org/gitlab diff --git a/content/assets/stylesheets/_utilities.scss b/content/assets/stylesheets/_utilities.scss index 5afd4303..4e982393 100644 --- a/content/assets/stylesheets/_utilities.scss +++ b/content/assets/stylesheets/_utilities.scss @@ -65,3 +65,14 @@ .gl-fill-blue-400 { fill: $blue-400; } + +.lg-w-20p { + @media (min-width: $bp-lg) { + width: 20%; + } +} +.lg-w-70p { + @media (min-width: $bp-lg) { + width: 70%; + } +} diff --git a/content/assets/stylesheets/stylesheet.scss b/content/assets/stylesheets/stylesheet.scss index a13e0248..b5da36e2 100644 --- a/content/assets/stylesheets/stylesheet.scss +++ b/content/assets/stylesheets/stylesheet.scss @@ -596,3 +596,23 @@ select[name='removal_milestone'], select[name='breaking'] { background-image: $gl-icon-select-chevron-down; } + +// GitLab UI does not export vendor prefixes in the build we use, so we need to add them here. +// Otherwise we don't get checkmarks in Chrome and Safari. +// @see https://caniuse.com/css-masks +$gl-icon-check: 'data:image/svg+xml,%3Csvg width="8" height="7" viewBox="0 0 8 7" fill="none" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath d="M1 3.05299L2.99123 5L7 1" stroke="white" stroke-width="2"/%3E%3C/svg%3E%0A'; + +.custom-control-input[type='checkbox']:checked ~ .custom-control-label, +.custom-control-input[type='checkbox']:indeterminate ~ .custom-control-label, +.custom-control-input[type='radio']:checked ~ .custom-control-label { + &::after { + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-position: center center; + mask-position: center center; + } +} +.custom-control-input[type='checkbox']:checked ~ .custom-control-label::after { + -webkit-mask-image: url('#{$gl-icon-check}'); + mask-image: url('#{$gl-icon-check}'); +} diff --git a/content/frontend/search/components/google_results.vue b/content/frontend/search/components/google_results.vue index 31c1ffaf..cc078674 100644 --- a/content/frontend/search/components/google_results.vue +++ b/content/frontend/search/components/google_results.vue @@ -7,11 +7,14 @@ import { GlSafeHtmlDirective as SafeHtml, GlPagination, } from '@gitlab/ui'; +import isEqual from 'lodash.isequal'; import { getSearchQueryFromURL, updateURLParams } from '../search_helpers'; import { GPS_ENDPOINT, GPS_ID } from '../../services/google_search_api'; +import SearchFilters from './search_filters.vue'; export default { components: { + SearchFilters, GlSearchBoxByClick, GlLink, GlLoadingIcon, @@ -30,6 +33,7 @@ export default { pageNumber: 1, response: {}, results: [], + activeFilters: [], }; }, computed: { @@ -39,11 +43,14 @@ export default { return `Showing ${startIndex}-${end} of ${this.response.searchInformation.formattedTotalResults} results`; }, noResults() { - return this.submitted && !this.loading && !this.results.length && !this.error; + return this.query && !this.loading && !this.results.length && !this.error; }, showPager() { return ( - this.submitted && this.response.searchInformation.totalResults > this.MAX_RESULTS_PER_PAGE + this.submitted && + this.results.length && + this.response.searchInformation.totalResults > this.MAX_RESULTS_PER_PAGE && + !this.loading ); }, pagerMaxItems() { @@ -57,22 +64,28 @@ export default { }, mounted() { if (this.query) { - this.search(this.query); + this.search(this.query, this.activeFilters); } }, methods: { cleanTitle(title) { return title.replace(' | GitLab', ''); }, - async fetchGoogleResults() { + async fetchGoogleResults(filters) { let data = {}; + + // Construct the query string for additional filters if needed. + const filterQuery = filters.length + ? `+more:pagemap:metatags-gitlab-docs-section:${filters.join(',')}` + : ''; + try { const response = await fetch( GPS_ENDPOINT + new URLSearchParams({ key: GOOGLE_SEARCH_KEY, cx: GPS_ID, - q: this.query, + q: `${this.query}${filterQuery}`.replaceAll(' ', '*'), start: (this.pageNumber - 1) * this.MAX_RESULTS_PER_PAGE + 1, }), ); @@ -85,29 +98,36 @@ export default { }, handleError(error) { this.error = true; + this.loading = false; throw new Error(`Error code ${error.code}: ${error.message}`); }, - onSubmit() { - if (this.query) { - this.search(this.query); - } - }, - async search(query) { - this.query = query; + async search(query, filters = []) { this.results = []; + if (!query) return; - this.loading = true; - this.response = await this.fetchGoogleResults(); - this.results = this.response.items ? this.response.items : []; - this.loading = false; - this.submitted = true; - updateURLParams(this.query); + // If the query or filters changed, return to page 1 of results. + if (query !== this.query || !isEqual(filters, this.activeFilters)) this.pageNumber = 1; + + this.query = query; + this.activeFilters = filters; - // Add a relative link to each result object. - this.results = this.results.map((obj) => ({ - ...obj, - relativeLink: obj.link.replace('https://docs.gitlab.com/', '/'), - })); + try { + this.loading = true; + this.response = await this.fetchGoogleResults(filters); + this.results = this.response.items ? this.response.items : []; + } catch (error) { + this.handleError(error); + } finally { + this.loading = false; + this.submitted = true; + updateURLParams(this.query); + + // Add a relative link to each result object. + this.results = this.results.map((obj) => ({ + ...obj, + relativeLink: obj.link.replace('https://docs.gitlab.com/', '/'), + })); + } }, }, }; @@ -116,46 +136,62 @@ export default { <template> <div class="google-search gl-mb-9"> <h1>Search</h1> - <gl-search-box-by-click v-model="query" :value="query" @submit="onSubmit" /> - <div v-if="results.length" class="gl-font-sm gl-mb-5"> - {{ resultSummary }} + <div class="gl-h-11 gl-mb-5"> + <gl-search-box-by-click v-model="query" :value="query" @submit="search(query)" /> + <div v-if="results.length" class="gl-font-sm gl-mb-5 gl-ml-1"> + {{ resultSummary }} + </div> </div> - <gl-loading-icon v-if="loading" size="lg" class="gl-mt-5" /> + <div class="results-container gl-lg-display-flex"> + <div v-if="submitted" class="results-sidebar gl-mb-5 lg-w-20p"> + <h2 class="gl-mt-0! gl-mb-5!">Filter results</h2> + <search-filters @filteredSearch="(filters) => search(query, filters)" /> + </div> - <ul - v-if="results.length" - class="gl-list-style-none gl-pl-2 gl-max-w-80" - data-testid="search-results" - > - <li v-for="result in results" :key="result.cacheId" class="gl-mb-5!"> - <gl-link - :href="`${result.relativeLink}`" - class="gl-font-lg gl-border-bottom-0! gl-hover-text-decoration-underline:hover gl-mb-2" - >{{ cleanTitle(result.title) }}</gl-link - > - <p v-safe-html="result.htmlSnippet" class="result-snippet"></p> - </li> - </ul> + <div class="lg-w-70p"> + <gl-loading-icon v-if="loading" size="lg" class="gl-mt-5 gl-text-center" /> - <gl-pagination - v-if="showPager" - v-model="pageNumber" - :per-page="MAX_RESULTS_PER_PAGE" - :total-items="pagerMaxItems" - class="gl-mt-9" - @input="search(query)" - /> + <ul v-if="results.length" class="gl-list-style-none gl-pl-2" data-testid="search-results"> + <li v-for="result in results" :key="result.cacheId" class="gl-mb-5!"> + <gl-link + :href="`${result.relativeLink}`" + class="gl-font-lg gl-border-bottom-0! gl-hover-text-decoration-underline:hover gl-mb-2" + >{{ cleanTitle(result.title) }} + </gl-link> + <p v-safe-html="result.htmlSnippet" class="result-snippet"></p> + </li> + </ul> - <p v-if="noResults" class="gl-py-5">No results found.</p> - <p v-if="error" class="gl-py-5" data-testid="search-error"> - Error fetching results. Please try again later. - </p> + <gl-pagination + v-if="showPager" + v-model="pageNumber" + :per-page="MAX_RESULTS_PER_PAGE" + :total-items="pagerMaxItems" + class="gl-mt-9" + @input="search(query, activeFilters)" + /> + + <p v-if="noResults" class="gl-py-5"> + No results found. Try adjusting your search terms, or searching the + <gl-link class="gl-font-base" href="https://forum.gitlab.com/">community forum</gl-link>. + </p> + <p v-if="error" class="gl-py-5" data-testid="search-error"> + Error fetching results. Please try again later. + </p> + </div> + </div> </div> </template> -<style scoped> +<style> +html { + overflow-y: scroll; +} .result-snippet { font-size: 0.875rem; } +.results-sidebar h2 { + font-size: 1.5rem; +} </style> diff --git a/content/frontend/search/components/search_filters.vue b/content/frontend/search/components/search_filters.vue new file mode 100644 index 00000000..6bfec780 --- /dev/null +++ b/content/frontend/search/components/search_filters.vue @@ -0,0 +1,37 @@ +<script> +import { GlFormCheckboxGroup } from '@gitlab/ui'; + +export default { + name: 'SearchFilters', + components: { + GlFormCheckboxGroup, + }, + data() { + return { + selected: [], + }; + }, + created() { + this.filters = [ + { + title: 'Sections', + options: ['Tutorials', 'Install', 'Administer', 'Use GitLab', 'Develop', 'Contribute'], + }, + ]; + }, +}; +</script> + +<template> + <div> + <div v-for="filter in filters" :key="filter.title"> + <h3 class="gl-font-lg! gl-mt-5! gl-mb-3!">{{ filter.title }}</h3> + <gl-form-checkbox-group + v-model="selected" + :label="filter.title" + :options="filter.options" + @input="$emit('filteredSearch', selected)" + /> + </div> + </div> +</template> diff --git a/package.json b/package.json index 615ac8fa..fa8af755 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "instantsearch.js": "^4.50.3", "jquery": "^3.6.3", "js-yaml": "^4.1.0", + "lodash.isequal": "^4.5.0", "lunr": "^2.3.9", "mermaid": "^9.3.0", "pikaday": "^1.8.2", |