@gouvfr/dsfr-roller 1.0.81 → 1.0.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/component/components/search.js +24 -0
- package/src/node/directive/components/search/search-leaf-directive.js +18 -0
- package/src/node/directive/doc/filter-leaf-directive.js +82 -0
- package/src/node/node-factory.js +5 -2
- package/src/page/head/resource.js +2 -1
- package/src/script/main/core/get-query.js +13 -3
- package/src/script/main/elements/copy-snippet.js +11 -1
- package/src/script/main/elements/filter-bar/index.js +95 -0
- package/src/script/main/elements/filter-bar/results/cards/result-color-card.js +80 -0
- package/src/script/main/elements/filter-bar/results/cards/result-icon-card.js +69 -0
- package/src/script/main/elements/filter-bar/results/cards/result-pictogram-card.js +65 -0
- package/src/script/main/elements/filter-bar/results/result-filter-item.js +27 -0
- package/src/script/main/elements/filter-bar/results/results-filter-empty.js +21 -0
- package/src/script/main/elements/filter-bar/results/results-filter-list.js +129 -0
- package/src/script/main/index.js +2 -0
- package/src/script/main/minisearch/filter-engine.js +53 -0
- package/src/script/main/minisearch/index.js +3 -51
- package/src/script/main/minisearch/search-engine.js +53 -0
- package/src/style/main/components/_dsfr-doc-filter.scss +53 -0
- package/src/style/main/components/_index.scss +1 -0
- package/src/style/search/_search-page.scss +0 -0
- package/src/style/search/index.scss +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gouvfr/dsfr-roller",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.83",
|
|
4
4
|
"description": "Le module `dsfr-roller` permet de publier le site de documentation du Système de Design de l’État - DSFR",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Système de Design de l'État",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
],
|
|
61
61
|
"main": "./index.js",
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@gouvfr/dsfr-forge": "=1.0.
|
|
64
|
-
"@gouvfr/dsfr-kit": "=1.0.
|
|
63
|
+
"@gouvfr/dsfr-forge": "=1.0.83",
|
|
64
|
+
"@gouvfr/dsfr-kit": "=1.0.83",
|
|
65
65
|
"@gouvfr/dsfr-publisher": "npm:@gouvfr/dsfr@1.14.4",
|
|
66
66
|
"deepmerge": "^4.3.1",
|
|
67
67
|
"ejs": "^3.1.10",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Component } from '../component.js';
|
|
2
|
+
|
|
3
|
+
class Search extends Component {
|
|
4
|
+
constructor (data) {
|
|
5
|
+
super(data, 'search');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get ejsPath () {
|
|
9
|
+
return 'src/dsfr/component/search/template/ejs/search.ejs';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async format () {
|
|
13
|
+
const title = this.data.title || this.data.label;
|
|
14
|
+
const search = {
|
|
15
|
+
id: this.data.id,
|
|
16
|
+
input: { id: this.data.inputId, placeholder: this.data.label, classes: this.data.inputClasses, label: this.data.label },
|
|
17
|
+
button: { id: this.data.buttonId, label: this.data.label, classes: this.data.buttonClasses, title: title, markup: 'button' }
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return search;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { Search };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Node } from '../../../node.js';
|
|
2
|
+
import { Search } from '../../../../component/components/search.js';
|
|
3
|
+
|
|
4
|
+
class SearchLeafDirective extends Node {
|
|
5
|
+
constructor (data) {
|
|
6
|
+
super(data);
|
|
7
|
+
this.search = new Search({...this.data.properties, ...this.data.attributes});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async render () {
|
|
11
|
+
const data = { label: await this.renderChildren() };
|
|
12
|
+
return this.search.render(data);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
SearchLeafDirective.NAME = 'fr-search';
|
|
17
|
+
|
|
18
|
+
export { SearchLeafDirective };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Node } from '../../node.js'
|
|
2
|
+
|
|
3
|
+
class FilterLeafDirective extends Node {
|
|
4
|
+
constructor (data) {
|
|
5
|
+
super(data);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
structure (data) {
|
|
9
|
+
return {
|
|
10
|
+
type: 'htmlContainer',
|
|
11
|
+
tagName: 'div',
|
|
12
|
+
classes: ['dsfr-doc-filter'],
|
|
13
|
+
attributes: {
|
|
14
|
+
'data-fr-filter': data.properties.id,
|
|
15
|
+
'data-fr-filter-subitem': data.properties.subItem || '',
|
|
16
|
+
},
|
|
17
|
+
children: [
|
|
18
|
+
{
|
|
19
|
+
type: 'leafDirective',
|
|
20
|
+
name: 'fr-search',
|
|
21
|
+
properties: {
|
|
22
|
+
id: `filter-${data.properties.id}`,
|
|
23
|
+
inputId: `filter-${data.properties.id}-input`,
|
|
24
|
+
inputClasses: ['dsfr-doc-filter-input'],
|
|
25
|
+
buttonId: `filter-${data.properties.id}-button`,
|
|
26
|
+
buttonClasses: ['dsfr-doc-filter-button'],
|
|
27
|
+
label: data.properties.label,
|
|
28
|
+
title: data.properties.title,
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
...(data.properties.select ? [
|
|
32
|
+
{
|
|
33
|
+
type: 'htmlContainer',
|
|
34
|
+
tagName: 'div',
|
|
35
|
+
classes: ['dsfr-doc-filter-select', 'fr-select-group'],
|
|
36
|
+
children: [
|
|
37
|
+
{
|
|
38
|
+
type: 'htmlContainer',
|
|
39
|
+
tagName: 'label',
|
|
40
|
+
classes: ['fr-label'],
|
|
41
|
+
attributes: {
|
|
42
|
+
for: `filter-${data.properties.select}-select`
|
|
43
|
+
},
|
|
44
|
+
children: [
|
|
45
|
+
{
|
|
46
|
+
type: 'text',
|
|
47
|
+
value: data.properties.selectLabel
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'htmlContainer',
|
|
53
|
+
tagName: 'select',
|
|
54
|
+
classes: ['fr-select'],
|
|
55
|
+
attributes: {
|
|
56
|
+
id: `filter-${data.properties.select}-select`,
|
|
57
|
+
name: `filter-${data.properties.select}-select`,
|
|
58
|
+
'data-fr-filter-select': data.properties.select
|
|
59
|
+
},
|
|
60
|
+
children: data.properties.selectOptions.map(option => ({
|
|
61
|
+
type: 'htmlContainer',
|
|
62
|
+
tagName: 'option',
|
|
63
|
+
attributes: {
|
|
64
|
+
value: option.value
|
|
65
|
+
},
|
|
66
|
+
children: [{
|
|
67
|
+
type: 'text',
|
|
68
|
+
value: option.label
|
|
69
|
+
}]
|
|
70
|
+
}))
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
] : [])
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
FilterLeafDirective.NAME = 'dsfr-doc-filter';
|
|
81
|
+
|
|
82
|
+
export { FilterLeafDirective };
|
package/src/node/node-factory.js
CHANGED
|
@@ -61,7 +61,8 @@ import { VideoLeafDirective } from './directive/doc/video-leaf-directive.js';
|
|
|
61
61
|
import { ImageTextDirective } from './directive/doc/image-text-directive.js'
|
|
62
62
|
import { ChangelogLeafDirective } from './directive/doc/changelog-leaf-directive.js'
|
|
63
63
|
import { PreventPermalinksContainerDirective } from './directive/doc/prevent-permalinks-container-directive.js';
|
|
64
|
-
|
|
64
|
+
import { FilterLeafDirective } from './directive/doc/filter-leaf-directive.js';
|
|
65
|
+
import { SearchLeafDirective } from './directive/components/search/search-leaf-directive.js';
|
|
65
66
|
|
|
66
67
|
const NODES = [
|
|
67
68
|
NodeRoot,
|
|
@@ -131,7 +132,9 @@ const DIRECTIVE_LEAFS = [
|
|
|
131
132
|
ChangelogLeafDirective,
|
|
132
133
|
PinLeafDirective,
|
|
133
134
|
VideoLeafDirective,
|
|
134
|
-
PageItemListLeafDirective
|
|
135
|
+
PageItemListLeafDirective,
|
|
136
|
+
SearchLeafDirective,
|
|
137
|
+
FilterLeafDirective
|
|
135
138
|
];
|
|
136
139
|
const DIRECTIVE_TEXTS = [
|
|
137
140
|
ImageTextDirective
|
|
@@ -7,7 +7,8 @@ class Resource extends Renderable {
|
|
|
7
7
|
search: this._data.resource.search,
|
|
8
8
|
pagination: this._data.resource.pagination,
|
|
9
9
|
consent: this._data.resource.consent,
|
|
10
|
-
badge: this._data.resource.badge
|
|
10
|
+
badge: this._data.resource.badge,
|
|
11
|
+
filter: this._data.resource.filter
|
|
11
12
|
};
|
|
12
13
|
return `<script>
|
|
13
14
|
window.resource = ${JSON.stringify(resource)};
|
|
@@ -2,12 +2,22 @@ const paramsString = window.location.search;
|
|
|
2
2
|
const searchParams = new URLSearchParams(paramsString);
|
|
3
3
|
import { normalizeTerm } from '@gouvfr/dsfr-kit';
|
|
4
4
|
|
|
5
|
-
const getQuery = () => {
|
|
6
|
-
return normalizeTerm(searchParams.get(
|
|
5
|
+
const getQuery = (query = 'query') => {
|
|
6
|
+
return searchParams.get(query) ? normalizeTerm(searchParams.get(query)) : '';
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const setQuery = (query, value) => {
|
|
10
|
+
if (value) {
|
|
11
|
+
searchParams.set(query, value);
|
|
12
|
+
} else {
|
|
13
|
+
searchParams.delete(query);
|
|
14
|
+
}
|
|
15
|
+
const queryString = searchParams.toString();
|
|
16
|
+
window.history.pushState({}, '', queryString ? `?${queryString}` : window.location.pathname);
|
|
7
17
|
};
|
|
8
18
|
|
|
9
19
|
const getCurrentPagination = () => {
|
|
10
20
|
return parseInt(searchParams.get('page'));
|
|
11
21
|
};
|
|
12
22
|
|
|
13
|
-
export { getQuery, getCurrentPagination };
|
|
23
|
+
export { getQuery, setQuery, getCurrentPagination };
|
|
@@ -4,13 +4,22 @@ class CopySnippet extends Element {
|
|
|
4
4
|
get button () {
|
|
5
5
|
return this.element;
|
|
6
6
|
}
|
|
7
|
+
|
|
7
8
|
init() {
|
|
8
9
|
this._copyLabel = this.button.innerText;
|
|
9
10
|
this._copiedLabel = this.button.getAttribute('data-label-copied');
|
|
10
|
-
this._code = this.
|
|
11
|
+
this._code = this.getCodeToCopy();
|
|
11
12
|
this.listenClick(this.button);
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
getCodeToCopy () {
|
|
16
|
+
const card = this.button.closest('.fr-card');
|
|
17
|
+
const pictogramPreview = card?.querySelector('.pictogram-preview');
|
|
18
|
+
if (pictogramPreview) return pictogramPreview.innerHTML.trim();
|
|
19
|
+
|
|
20
|
+
return this.button.getAttribute('data-copy-value') || this.button.previousElementSibling?.innerText || '';
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
get isCopied () {
|
|
15
24
|
return this._isCopied;
|
|
16
25
|
}
|
|
@@ -22,6 +31,7 @@ class CopySnippet extends Element {
|
|
|
22
31
|
}
|
|
23
32
|
|
|
24
33
|
handleClick () {
|
|
34
|
+
if (!this._code) return;
|
|
25
35
|
navigator.clipboard.writeText(this._code);
|
|
26
36
|
this.isCopied = true;
|
|
27
37
|
setTimeout(this._handleTimeout.bind(this), 1500);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Element } from '../../core/element.js';
|
|
2
|
+
import { ResultsFilterList } from './results/results-filter-list.js';
|
|
3
|
+
import { getQuery, setQuery } from '../../core/get-query.js';
|
|
4
|
+
import { normalizeTerm } from '@gouvfr/dsfr-kit';
|
|
5
|
+
|
|
6
|
+
class FilterBar extends Element {
|
|
7
|
+
constructor (element) {
|
|
8
|
+
super(element, 'filterBar');
|
|
9
|
+
this._filterType = this.element.getAttribute('data-fr-filter');
|
|
10
|
+
this._filterInput = this.element.querySelector('.dsfr-doc-filter-input');
|
|
11
|
+
this._filterButton = this.element.querySelector('.dsfr-doc-filter-button');
|
|
12
|
+
this._select = this.element.querySelector('select');
|
|
13
|
+
this._query = normalizeTerm(this._filterInput.value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async init () {
|
|
17
|
+
this._resultsList = new ResultsFilterList(this._filterType, this._select && this._select.value !== '' ? { [this._select.getAttribute('data-fr-filter-select')]: this._select.value } : {});
|
|
18
|
+
this.element.after(this._resultsList.element);
|
|
19
|
+
await this._resultsList.init();
|
|
20
|
+
|
|
21
|
+
const queryFromUrl = getQuery();
|
|
22
|
+
const filterFromUrl = getQuery('filter');
|
|
23
|
+
|
|
24
|
+
if (this._select && filterFromUrl) {
|
|
25
|
+
this._select.value = filterFromUrl;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const hasQueryFromUrl = !!queryFromUrl;
|
|
29
|
+
const hasFilterFromUrl = this._select && !!filterFromUrl && this._select.value === filterFromUrl;
|
|
30
|
+
|
|
31
|
+
if (hasQueryFromUrl) {
|
|
32
|
+
this._filterInput.value = queryFromUrl;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (hasQueryFromUrl || hasFilterFromUrl) {
|
|
36
|
+
this.update(hasQueryFromUrl ? queryFromUrl : this._query, this._select && this._select.value !== '' ? { [this._select.getAttribute('data-fr-filter-select')]: this._select.value } : {});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (this._select) {
|
|
40
|
+
this._select.addEventListener('change', this.handleChange.bind(this));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this._filterInput.addEventListener(
|
|
44
|
+
'keyup',
|
|
45
|
+
this.handleKeyup.bind(this)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Handle filter input clear
|
|
49
|
+
this._filterInput.addEventListener(
|
|
50
|
+
'search', () => {
|
|
51
|
+
setQuery('query', '');
|
|
52
|
+
this.update('');
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
this._filterButton.addEventListener(
|
|
57
|
+
'click',
|
|
58
|
+
this.handleFilterSubmit.bind(this)
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
update (query, filters = {}) {
|
|
63
|
+
this._query = query;
|
|
64
|
+
this._resultsList.update(query, filters);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
handleFilterSubmit () {
|
|
68
|
+
setQuery('query', normalizeTerm(this._filterInput.value));
|
|
69
|
+
if (this._select && this._select.value !== '') {
|
|
70
|
+
setQuery('filter', normalizeTerm(this._select.value));
|
|
71
|
+
} else {
|
|
72
|
+
setQuery('filter', null);
|
|
73
|
+
}
|
|
74
|
+
this.update(normalizeTerm(this._filterInput.value), this._select && this._select.value !== '' ? { [this._select.getAttribute('data-fr-filter-select')]: this._select.value } : {});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
handleKeyup (event) {
|
|
78
|
+
if (event.key === 'Enter') {
|
|
79
|
+
this.handleFilterSubmit(event);
|
|
80
|
+
} else {
|
|
81
|
+
if (this._select && this._select.value !== '') {
|
|
82
|
+
this.update(normalizeTerm(event.target.value), { [this._select.getAttribute('data-fr-filter-select')]: this._select.value });
|
|
83
|
+
} else {
|
|
84
|
+
this.update(normalizeTerm(event.target.value));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
handleChange (event) {
|
|
90
|
+
const value = event.target.value;
|
|
91
|
+
this._resultsList.update(this._query, value !== '' ? { [this._select.getAttribute('data-fr-filter-select')]: value } : {});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export { FilterBar };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
class ResultColorCard {
|
|
2
|
+
constructor (data) {
|
|
3
|
+
this._colorClass = data.colorClass;
|
|
4
|
+
this._tint = data.tint;
|
|
5
|
+
this._usage = data.usage;
|
|
6
|
+
this._context = data.context;
|
|
7
|
+
this._family = data.family;
|
|
8
|
+
this._hover = data.hover;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
getHeader () {
|
|
12
|
+
if (!this._colorClass) return '';
|
|
13
|
+
|
|
14
|
+
let content = '';
|
|
15
|
+
switch (this._context) {
|
|
16
|
+
case 'text':
|
|
17
|
+
content = `<div class="color-preview" ${this._usage === 'inverted' ? 'style="background-color: var(--background-flat-' + this._tint + ')"' : ''}><p class="fr-h1 ${this._colorClass}">Aa</p></div>`;
|
|
18
|
+
break;
|
|
19
|
+
|
|
20
|
+
case 'artwork':
|
|
21
|
+
content = `<div class="color-preview ${this._colorClass}" style="background-color: var(--artwork-${this._usage}-${this._tint})"></div>`;
|
|
22
|
+
break;
|
|
23
|
+
|
|
24
|
+
default:
|
|
25
|
+
content = `<div class="color-preview ${this._colorClass} ${this._context === 'border' ? 'fr-border-width-1v' : ''}"></div>`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return `<div class="fr-card__header">
|
|
29
|
+
<div class="fr-card__img">
|
|
30
|
+
${content}
|
|
31
|
+
</div>
|
|
32
|
+
</div>`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getFooter () {
|
|
36
|
+
return `<div class="fr-card__footer">
|
|
37
|
+
<ul class="fr-btns-group fr-btns-group--sm">
|
|
38
|
+
<li>
|
|
39
|
+
<button data-fr-analytics-action="reduce" data-copy-value="${this._colorClass}" data-label-copied="${window.resource?.filter?.copy?.classCopied}" class="fr-btn fr-btn--secondary dsfr-doc-copy-snippet">${window.resource?.filter?.copy?.class}</button>
|
|
40
|
+
</li>
|
|
41
|
+
</ul>
|
|
42
|
+
</div>`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getTags () {
|
|
46
|
+
if (!this._context) return '';
|
|
47
|
+
return `<ul class="fr-tags-group fr-tags-group--sm">
|
|
48
|
+
<li>
|
|
49
|
+
<p class="fr-tag">${this._context}</p>
|
|
50
|
+
</li>
|
|
51
|
+
<li>
|
|
52
|
+
<p class="fr-tag">${this._usage}</p>
|
|
53
|
+
</li>
|
|
54
|
+
</ul>`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getBody () {
|
|
58
|
+
return `<div class="fr-card__body">
|
|
59
|
+
<div class="fr-card__content">
|
|
60
|
+
<h3 class="fr-card__title">${this._tint}</h3>
|
|
61
|
+
<div class="fr-card__start">
|
|
62
|
+
${this.getTags()}
|
|
63
|
+
</div>
|
|
64
|
+
<div class="fr-card__end">
|
|
65
|
+
<p class="fr-card__detail">${this._colorClass}</p>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
${this.getFooter()}
|
|
69
|
+
</div>`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
render () {
|
|
73
|
+
return `<div class="fr-card fr-card--shadow">
|
|
74
|
+
${this.getBody()}
|
|
75
|
+
${this.getHeader()}
|
|
76
|
+
</div>`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { ResultColorCard };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
class ResultIconCard {
|
|
2
|
+
constructor (data) {
|
|
3
|
+
this._data = data;
|
|
4
|
+
this._iconName = data.name;
|
|
5
|
+
this._category = data.category;
|
|
6
|
+
this._family = data.family;
|
|
7
|
+
this._path = data.path;
|
|
8
|
+
this._class = 'fr-icon-' + this._iconName;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
getHeader () {
|
|
12
|
+
if (!this._iconName) return '';
|
|
13
|
+
return `<div class="fr-card__header">
|
|
14
|
+
<div class="fr-card__img">
|
|
15
|
+
<div class="icon-preview">
|
|
16
|
+
<span class="fr-icon fr-icon--lg ${this._class}" aria-hidden="true"></span>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getFooter () {
|
|
23
|
+
return `<div class="fr-card__footer">
|
|
24
|
+
<ul class="fr-btns-group fr-btns-group--sm">
|
|
25
|
+
<li>
|
|
26
|
+
<button data-fr-analytics-action="reduce" data-copy-value="${this._class}" data-label-copied="${window.resource?.filter?.copy?.classCopied}" class="fr-btn fr-btn--secondary dsfr-doc-copy-snippet">${window.resource?.filter?.copy?.class}</button>
|
|
27
|
+
</li>
|
|
28
|
+
</ul>
|
|
29
|
+
</div>`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getTags () {
|
|
33
|
+
if (!this._category || !this._family) return '';
|
|
34
|
+
return `<div class="fr-card__start">
|
|
35
|
+
<ul class="fr-tags-group fr-tags-group--sm">
|
|
36
|
+
<li>
|
|
37
|
+
<p class="fr-tag">${this._category}</p>
|
|
38
|
+
</li>
|
|
39
|
+
<li>
|
|
40
|
+
<p class="fr-tag">${this._family}</p>
|
|
41
|
+
</li>
|
|
42
|
+
</ul>
|
|
43
|
+
</div>`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getBody () {
|
|
47
|
+
return `<div class="fr-card__body">
|
|
48
|
+
<div class="fr-card__content">
|
|
49
|
+
<h3 class="fr-card__title">${this._iconName}</h3>
|
|
50
|
+
<div class="fr-card__start">
|
|
51
|
+
${this.getTags()}
|
|
52
|
+
</div>
|
|
53
|
+
<div class="fr-card__end">
|
|
54
|
+
<p class="fr-card__detail">${this._class}</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
${this.getFooter()}
|
|
58
|
+
</div>`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
render () {
|
|
62
|
+
return `<div class="fr-card fr-card--shadow">
|
|
63
|
+
${this.getBody()}
|
|
64
|
+
${this.getHeader()}
|
|
65
|
+
</div>`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { ResultIconCard };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
class ResultPictogramCard {
|
|
2
|
+
constructor (data) {
|
|
3
|
+
this._data = data;
|
|
4
|
+
this._name = data.name;
|
|
5
|
+
this._category = data.category;
|
|
6
|
+
this._path = data.path;
|
|
7
|
+
this._code = `<svg aria-hidden="true" class="fr-artwork" viewBox="0 0 80 80" width="80px" height="80px">
|
|
8
|
+
<use class="fr-artwork-decorative" href="/dist/${this._path}#artwork-decorative"></use>
|
|
9
|
+
<use class="fr-artwork-minor" href="/dist/${this._path}#artwork-minor"></use>
|
|
10
|
+
<use class="fr-artwork-major" href="/dist/${this._path}#artwork-major"></use>
|
|
11
|
+
</svg>`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getHeader () {
|
|
15
|
+
return `<div class="fr-card__header">
|
|
16
|
+
<div class="fr-card__img">
|
|
17
|
+
<div class="pictogram-preview">
|
|
18
|
+
${this._code}
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getFooter () {
|
|
25
|
+
return `<div class="fr-card__footer">
|
|
26
|
+
<ul class="fr-btns-group fr-btns-group--sm">
|
|
27
|
+
<li>
|
|
28
|
+
<button data-fr-analytics-action="reduce" data-label-copied="${window.resource?.filter?.copy?.codeCopied}" class="fr-btn fr-btn--secondary dsfr-doc-copy-snippet">${window.resource?.filter?.copy?.code}</button>
|
|
29
|
+
</li>
|
|
30
|
+
</ul>
|
|
31
|
+
</div>`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getTags () {
|
|
35
|
+
if (!this._category) return '';
|
|
36
|
+
return `<div class="fr-card__start">
|
|
37
|
+
<ul class="fr-tags-group fr-tags-group--sm">
|
|
38
|
+
<li>
|
|
39
|
+
<p class="fr-tag">${this._category}</p>
|
|
40
|
+
</li>
|
|
41
|
+
</ul>
|
|
42
|
+
</div>`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getBody () {
|
|
46
|
+
return `<div class="fr-card__body">
|
|
47
|
+
<div class="fr-card__content">
|
|
48
|
+
<h3 class="fr-card__title">${this._name}</h3>
|
|
49
|
+
<div class="fr-card__start">
|
|
50
|
+
${this.getTags()}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
${this.getFooter()}
|
|
54
|
+
</div>`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
render () {
|
|
58
|
+
return `<div class="fr-card fr-card--shadow">
|
|
59
|
+
${this.getBody()}
|
|
60
|
+
${this.getHeader()}
|
|
61
|
+
</div>`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { ResultPictogramCard };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ResultIconCard } from './cards/result-icon-card.js';
|
|
2
|
+
import { ResultColorCard } from './cards/result-color-card.js';
|
|
3
|
+
import { ResultPictogramCard } from './cards/result-pictogram-card.js';
|
|
4
|
+
|
|
5
|
+
class ResultFilterItem {
|
|
6
|
+
constructor (type, data) {
|
|
7
|
+
this._data = data;
|
|
8
|
+
this._type = type;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
render () {
|
|
12
|
+
switch (this._type) {
|
|
13
|
+
case 'icon':
|
|
14
|
+
return new ResultIconCard(this._data).render();
|
|
15
|
+
|
|
16
|
+
case 'colors':
|
|
17
|
+
// console.log(this._data);
|
|
18
|
+
// if (this._data.context === 'artwork' && this._data.usage !== 'minor') return;
|
|
19
|
+
return new ResultColorCard(this._data).render();
|
|
20
|
+
|
|
21
|
+
case 'pictogram':
|
|
22
|
+
return new ResultPictogramCard(this._data).render();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { ResultFilterItem };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { replaceFragment } from '../../../core/replace-fragments.js';
|
|
2
|
+
|
|
3
|
+
class ResultsFilterEmpty {
|
|
4
|
+
constructor (query) {
|
|
5
|
+
this._query = query;
|
|
6
|
+
}
|
|
7
|
+
render () {
|
|
8
|
+
const noResults = window.resource?.search?.noresults;
|
|
9
|
+
return `
|
|
10
|
+
<p class="fr-mb-2v">
|
|
11
|
+
<strong>${replaceFragment(noResults?.title, this._query)}</strong>
|
|
12
|
+
</p>
|
|
13
|
+
<p class="fr-mb-2v">${noResults?.subtitle}</p>
|
|
14
|
+
<ul class="dsfr-doc-filter-results-list--no-results">
|
|
15
|
+
<li>${noResults?.suggestions1}</li>
|
|
16
|
+
<li>${noResults?.suggestions2}</li>
|
|
17
|
+
</ul>`;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { ResultsFilterEmpty };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { ResultFilterItem } from './result-filter-item.js';
|
|
2
|
+
import { ResultsFilterEmpty } from './results-filter-empty.js';
|
|
3
|
+
import { Element } from '../../../core/element.js';
|
|
4
|
+
import { capitalize } from '@gouvfr/dsfr-kit';
|
|
5
|
+
import CopySnippet from '../../copy-snippet.js';
|
|
6
|
+
|
|
7
|
+
class ResultsFilterList extends Element {
|
|
8
|
+
constructor (type, filters = {}) {
|
|
9
|
+
const element = document.createElement('div');
|
|
10
|
+
element.classList.add('dsfr-doc-filter-results');
|
|
11
|
+
element.setAttribute('aria-live', 'polite');
|
|
12
|
+
super(element);
|
|
13
|
+
this._type = type;
|
|
14
|
+
this._filters = filters;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async init() {
|
|
18
|
+
await window.filterEngine.init(this._type);
|
|
19
|
+
this.update('');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
search (query, filters = {}) {
|
|
23
|
+
this._results = window.filterEngine.search(query, { ...this._filters, ...filters });
|
|
24
|
+
this._resultsArray = Array.isArray(this._results) ? this._results : Object.values(this._results || {});
|
|
25
|
+
return this._resultsArray;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
update (query, filters = {}) {
|
|
29
|
+
this._results = this.search(query, filters);
|
|
30
|
+
|
|
31
|
+
switch (true) {
|
|
32
|
+
case !this._results:
|
|
33
|
+
case this._results.length === 0:
|
|
34
|
+
const empty = new ResultsFilterEmpty(query);
|
|
35
|
+
this._content = empty.render();
|
|
36
|
+
break;
|
|
37
|
+
|
|
38
|
+
default:
|
|
39
|
+
this._items = this._results.map(result => new ResultFilterItem(this._type, result));
|
|
40
|
+
this._content = this.render();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.element.innerHTML = this._content;
|
|
44
|
+
this.initCopyButtons();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
initCopyButtons () {
|
|
48
|
+
const buttons = this.element.querySelectorAll('.dsfr-doc-copy-snippet');
|
|
49
|
+
buttons.forEach(button => {
|
|
50
|
+
const copySnippet = new CopySnippet(button);
|
|
51
|
+
copySnippet.init();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
groupByCategory(categoryField = 'category', subcategoryField = null) {
|
|
56
|
+
const grouped = {};
|
|
57
|
+
this._items.forEach(item => {
|
|
58
|
+
const category = item._data[categoryField] || 'Non catégorisé';
|
|
59
|
+
|
|
60
|
+
if (!grouped[category]) {
|
|
61
|
+
grouped[category] = subcategoryField ? {} : [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (subcategoryField) {
|
|
65
|
+
const subcategory = item._data[subcategoryField] || 'Autre';
|
|
66
|
+
if (!grouped[category][subcategory]) {
|
|
67
|
+
grouped[category][subcategory] = [];
|
|
68
|
+
}
|
|
69
|
+
grouped[category][subcategory].push(item);
|
|
70
|
+
} else {
|
|
71
|
+
grouped[category].push(item);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return grouped;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
sortByCategory(grouped) {
|
|
78
|
+
const sorted = {};
|
|
79
|
+
Object.keys(grouped).sort().forEach(category => {
|
|
80
|
+
sorted[category] = grouped[category];
|
|
81
|
+
});
|
|
82
|
+
return sorted;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
render() {
|
|
86
|
+
switch (this._type) {
|
|
87
|
+
case 'colors':
|
|
88
|
+
this._grouped = this.groupByCategory('context', 'usage');
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
this._grouped = this.sortByCategory(this.groupByCategory());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return Object.entries(this._grouped).map(([category, itemsOrSubcategories]) => {
|
|
95
|
+
// Si c'est un tableau, on a une structure simple sans sous-catégories
|
|
96
|
+
if (Array.isArray(itemsOrSubcategories)) {
|
|
97
|
+
return `<div class="fr-grid-row fr-grid-row--gutters fr-mb-6v">
|
|
98
|
+
<div class="fr-col-12">
|
|
99
|
+
<h3 class="fr-mb-0">${capitalize(category)}</h3>
|
|
100
|
+
</div>
|
|
101
|
+
${itemsOrSubcategories.map(item => `<div class="fr-col-12 fr-col-sm-6 fr-col-lg-4">
|
|
102
|
+
${item.render()}
|
|
103
|
+
</div>`).join('')}
|
|
104
|
+
</div>`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Si c'est un objet, on a des sous-catégories
|
|
108
|
+
return `<div class="fr-mb-8v">
|
|
109
|
+
<div class="fr-col-12">
|
|
110
|
+
<h3>${capitalize(category)}</h3>
|
|
111
|
+
${category === 'artwork' ? `<p class="fr-text--sm fr-mt-1v">${window.resource?.filter?.artworkDescription}</p>` : ''}
|
|
112
|
+
</div>
|
|
113
|
+
${Object.entries(itemsOrSubcategories).map(([subcategory, items]) => {
|
|
114
|
+
return `<div class="fr-grid-row fr-grid-row--gutters fr-mb-6v">
|
|
115
|
+
<div class="fr-col-12">
|
|
116
|
+
<h4 class="fr-mb-0">${subcategory}</h4>
|
|
117
|
+
</div>
|
|
118
|
+
${items.map(item => `<div class="fr-col-12 fr-col-sm-6 fr-col-lg-4">
|
|
119
|
+
${item.render()}
|
|
120
|
+
</div>`).join('')}
|
|
121
|
+
</div>`;
|
|
122
|
+
}).join('')}
|
|
123
|
+
</div>`;
|
|
124
|
+
}).join('');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export { ResultsFilterList };
|
package/src/script/main/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import CopyLink from './elements/copy-link.js';
|
|
|
5
5
|
import ConsentCGU from './elements/consent-cgu.js';
|
|
6
6
|
import Storybook from './elements/storybook.js';
|
|
7
7
|
import { SearchBar } from './elements/search-bar/index.js';
|
|
8
|
+
import { FilterBar } from './elements/filter-bar/index.js';
|
|
8
9
|
import { ConsentManagementPlatform } from './cmp/index.js';
|
|
9
10
|
|
|
10
11
|
window.onload = async () => {
|
|
@@ -13,6 +14,7 @@ window.onload = async () => {
|
|
|
13
14
|
await instantiateElements('.dsfr-doc-anchor-heading__button', CopyLink);
|
|
14
15
|
await instantiateElements('.dsfr-doc-storybook-leaf iframe', Storybook);
|
|
15
16
|
await instantiateElements('#search', SearchBar);
|
|
17
|
+
await instantiateElements('.dsfr-doc-filter', FilterBar);
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
const consentResource = window.resource?.consent || {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import MiniSearch from 'minisearch';
|
|
2
|
+
import { normalizeTerm } from '@gouvfr/dsfr-kit';
|
|
3
|
+
|
|
4
|
+
const ROOT_REGEX = /^(?<root>\/(?<version>v\d+\.\d+|[\w-]+)\/(?<locale>[a-zA-Z-]+))/;
|
|
5
|
+
|
|
6
|
+
class FilterEngine {
|
|
7
|
+
async init (type) {
|
|
8
|
+
const response = await fetch(this.getFilterIndex(type));
|
|
9
|
+
this._documentJSON = await response.json();
|
|
10
|
+
this._document = JSON.stringify(this._documentJSON);
|
|
11
|
+
this._miniSearch = MiniSearch.loadJSON(this._document, {fields: this.getFields(), storeFields: this.getFields()});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getFilterIndex = (type) => {
|
|
15
|
+
const { groups: { root, version, locale } } = window.location.pathname.match(ROOT_REGEX);
|
|
16
|
+
return `${root}/${type}.json`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getFields = () => {
|
|
20
|
+
const fields = Object.keys(this._documentJSON.fieldIds || {});
|
|
21
|
+
return fields;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getOptions = (filter = null) => {
|
|
25
|
+
const options = {
|
|
26
|
+
prefix: true,
|
|
27
|
+
fuzzy: 0.1,
|
|
28
|
+
processTerm: (term) => normalizeTerm(term.toLowerCase()),
|
|
29
|
+
tokenize: (string) => string.split(/\s+/)
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (filter) {
|
|
33
|
+
options.filter = (result) => {
|
|
34
|
+
return Object.entries(filter).every(([key, value]) => result[key] === value);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return options;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
search (query, filter) {
|
|
42
|
+
if (query === '' || query == null || query.trim().length === 0) {
|
|
43
|
+
return Object.values(this._documentJSON.storedFields).filter(result => {
|
|
44
|
+
return Object.entries(filter).every(([key, value]) => result[key] === value);
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
const results = this._miniSearch.search(query, this.getOptions(filter));
|
|
48
|
+
return results;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { FilterEngine };
|
|
@@ -1,53 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const LOAD_OPTIONS = {
|
|
5
|
-
fields: ['title', 'keywords', 'summary', 'excerpt', 'text'],
|
|
6
|
-
storeFields: ['title', 'url', 'boost', 'summary', 'excerpt', 'cover', 'section'],
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
const SEARCH_OPTIONS = {
|
|
10
|
-
boost: { title: 2.5, keywords: 2, summary: 1.5, text: 1 },
|
|
11
|
-
boostDocument: (documentId, query, storedFields) =>
|
|
12
|
-
storedFields?.boost ?? 1,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const getOptions = (type) => {
|
|
16
|
-
const options = {
|
|
17
|
-
prefix: true,
|
|
18
|
-
fuzzy: 0.2,
|
|
19
|
-
processTerm: (term) => normalizeTerm(term.toLowerCase())
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
switch (type) {
|
|
23
|
-
case 'search':
|
|
24
|
-
return { ...options, ...SEARCH_OPTIONS };
|
|
25
|
-
|
|
26
|
-
default:
|
|
27
|
-
return { ...options, ...LOAD_OPTIONS };
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const ROOT_REGEX = /^(?<root>\/(?<version>v\d+\.\d+|[\w-]+)\/(?<locale>[a-zA-Z-]+))/;
|
|
32
|
-
const CURRENT_VERSION_REGEX = /^(?<root>\/(?<version>v\d+\.\d+|[\w-]+)\/(?<locale>\w+)\/(?<search>v\d+\.\d+|[\w-]+))/
|
|
33
|
-
|
|
34
|
-
const getSearchIndex = (type) => {
|
|
35
|
-
const searchIndexRegex = type === 'searchPage' ? CURRENT_VERSION_REGEX : ROOT_REGEX;
|
|
36
|
-
const { groups: { root, version, locale } } = window.location.pathname.match(searchIndexRegex);
|
|
37
|
-
return `${root}/index.json`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
class SearchEngine {
|
|
41
|
-
async init (type) {
|
|
42
|
-
const response = await fetch(getSearchIndex(type));
|
|
43
|
-
const json = JSON.stringify(await response.json());
|
|
44
|
-
const options = getOptions('load');
|
|
45
|
-
this._miniSearch = MiniSearch.loadJSON(json, options);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
search (query) {
|
|
49
|
-
return this._miniSearch.search(query, getOptions('search'));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
1
|
+
import { SearchEngine } from './search-engine.js';
|
|
2
|
+
import { FilterEngine } from './filter-engine.js';
|
|
52
3
|
|
|
53
4
|
window.searchEngine = new SearchEngine();
|
|
5
|
+
window.filterEngine = new FilterEngine();
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import MiniSearch from 'minisearch';
|
|
2
|
+
import { normalizeTerm } from '@gouvfr/dsfr-kit';
|
|
3
|
+
|
|
4
|
+
const LOAD_OPTIONS = {
|
|
5
|
+
fields: ['title', 'keywords', 'summary', 'excerpt', 'text'],
|
|
6
|
+
storeFields: ['title', 'url', 'boost', 'summary', 'excerpt', 'cover', 'section'],
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const SEARCH_OPTIONS = {
|
|
10
|
+
boost: { title: 2.5, keywords: 2, summary: 1.5, text: 1 },
|
|
11
|
+
boostDocument: (documentId, query, storedFields) =>
|
|
12
|
+
storedFields?.boost ?? 1,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ROOT_REGEX = /^(?<root>\/(?<version>v\d+\.\d+|[\w-]+)\/(?<locale>[a-zA-Z-]+))/;
|
|
16
|
+
const CURRENT_VERSION_REGEX = /^(?<root>\/(?<version>v\d+\.\d+|[\w-]+)\/(?<locale>\w+)\/(?<search>v\d+\.\d+|[\w-]+))/
|
|
17
|
+
|
|
18
|
+
class SearchEngine {
|
|
19
|
+
async init (type) {
|
|
20
|
+
const response = await fetch(this.getIndex(type));
|
|
21
|
+
const json = JSON.stringify(await response.json());
|
|
22
|
+
const options = this.getOptions('load');
|
|
23
|
+
this._miniSearch = MiniSearch.loadJSON(json, options);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getOptions = (type) => {
|
|
27
|
+
const options = {
|
|
28
|
+
prefix: true,
|
|
29
|
+
fuzzy: 0.2,
|
|
30
|
+
processTerm: (term) => normalizeTerm(term.toLowerCase())
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
switch (type) {
|
|
34
|
+
case 'search':
|
|
35
|
+
return { ...options, ...SEARCH_OPTIONS };
|
|
36
|
+
|
|
37
|
+
default:
|
|
38
|
+
return { ...options, ...LOAD_OPTIONS };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getIndex = (type) => {
|
|
43
|
+
const searchIndexRegex = type === 'searchPage' ? CURRENT_VERSION_REGEX : ROOT_REGEX;
|
|
44
|
+
const { groups: { root, version, locale } } = window.location.pathname.match(searchIndexRegex);
|
|
45
|
+
return `${root}/index.json`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
search (query) {
|
|
49
|
+
return this._miniSearch.search(query, getOptions('search'));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { SearchEngine };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
.dsfr-doc-filter-results {
|
|
2
|
+
margin-top: 2rem;
|
|
3
|
+
margin-bottom: 2rem;
|
|
4
|
+
|
|
5
|
+
p + ul.dsfr-doc-filter-results-list--no-results {
|
|
6
|
+
margin-top: 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.fr-card__content {
|
|
10
|
+
padding: 1rem;
|
|
11
|
+
|
|
12
|
+
.fr-card__end {
|
|
13
|
+
margin-top: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.fr-card__start {
|
|
17
|
+
.fr-tags-group>li {
|
|
18
|
+
line-height: 1.25rem;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.fr-card__footer {
|
|
24
|
+
padding: 0 1rem 1rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.fr-card__img {
|
|
28
|
+
.icon-preview, .pictogram-preview, .color-preview {
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
width: 100%;
|
|
33
|
+
height: 120px;
|
|
34
|
+
border-bottom: 1px solid var(--background-alt-grey);
|
|
35
|
+
|
|
36
|
+
& > * {
|
|
37
|
+
margin: 0;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.icon-preview {
|
|
42
|
+
height: 80px;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.dsfr-doc-filter-select {
|
|
48
|
+
margin-top: 1rem;
|
|
49
|
+
|
|
50
|
+
.fr-select {
|
|
51
|
+
max-width: 200px;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@use 'search-page';
|