@gouvfr/dsfr-roller 1.0.7 → 1.0.9
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 +2 -2
- package/src/component/components/button.js +2 -2
- package/src/component/components/header.js +1 -1
- package/src/node/directive/{doc → components/accordion}/accordion-container-directive.js +4 -4
- package/src/node/directive/{doc → components/accordion}/accordions-group-container-directive.js +1 -1
- package/src/node/directive/{doc → components/button}/button-leaf-directive.js +3 -3
- package/src/node/directive/components/card/card-container-directive.js +114 -0
- package/src/node/directive/{doc → components/table}/table-container-directive.js +7 -7
- package/src/node/directive/{doc → components/tabs}/tab-container-directive.js +1 -1
- package/src/node/directive/{doc → components/tabs}/tabs-container-directive.js +1 -1
- package/src/node/directive/components/tile/tile-container-directive.js +135 -0
- package/src/node/directive/core/grid-container-directive.js +15 -0
- package/src/node/directive/doc/{anatomy-container-directive.js → guidance/anatomy-container-directive.js} +2 -2
- package/src/node/directive/doc/{guideline-container-directive.js → guidance/guideline-container-directive.js} +3 -3
- package/src/node/directive/doc/{guidelines-container-directive.js → guidance/guidelines-container-directive.js} +1 -1
- package/src/node/directive/doc/{pin-leaf-directive.js → guidance/pin-leaf-directive.js} +1 -1
- package/src/node/directive/doc/page-item-card-container-directive.js +53 -0
- package/src/node/directive/doc/page-item-list-leaf-directive.js +33 -0
- package/src/node/directive/doc/tab-navigation-container-directive.js +1 -1
- package/src/node/directive/doc/video-leaf-directive.js +44 -0
- package/src/node/directive/home/hp-analytics-container-directive.js +67 -0
- package/src/node/directive/home/hp-community-container-directive.js +78 -0
- package/src/node/directive/home/hp-community-tile-container-directive.js +57 -0
- package/src/node/directive/home/hp-discover-container-directive.js +41 -0
- package/src/node/directive/home/hp-discover-tile-container-directive.js +81 -0
- package/src/node/directive/home/hp-faq-container-directive.js +60 -0
- package/src/node/directive/home/hp-goals-container-directive.js +37 -0
- package/src/node/directive/home/hp-hero-container-directive.js +54 -0
- package/src/node/directive/home/hp-news-container-directive.js +53 -0
- package/src/node/directive/home/hp-showcase-card-container-directive.js +81 -0
- package/src/node/directive/home/hp-showcase-container-directive.js +55 -0
- package/src/node/directive/home/hp-slice-video-container-directive.js +246 -0
- package/src/node/generic/link-node.js +1 -1
- package/src/node/node-factory.js +45 -11
- package/src/page/head/head.js +3 -0
- package/src/page/head/resource.js +17 -0
- package/src/page/head/share.js +23 -5
- package/src/page/head/stylesheets.js +2 -1
- package/src/page/page.js +3 -1
- package/src/page/scripts/scripts.js +2 -1
- package/src/script/home/index.js +10 -0
- package/src/script/home/inject-svg.js +28 -0
- package/src/script/home/show-on-scroll.js +24 -0
- package/src/script/home/stop-video-on-close.js +20 -0
- package/src/script/main/cmp/index.js +72 -0
- package/src/script/main/cmp/tarteaucitron/config.js +34 -0
- package/src/script/main/cmp/tarteaucitron/lang.js +14 -0
- package/src/script/main/cmp/tarteaucitron/services.js +9142 -0
- package/src/script/main/cmp/tarteaucitron/tarteaucitron.js +3301 -0
- package/src/script/main/core/element.js +7 -3
- package/src/script/main/core/get-current-repo.js +6 -0
- package/src/script/main/core/get-query.js +12 -0
- package/src/script/main/core/replace-fragments.js +4 -0
- package/src/script/main/elements/pagination/index.js +23 -0
- package/src/script/main/elements/pagination/pagination-item.js +94 -0
- package/src/script/main/elements/pagination/pagination-list.js +131 -0
- package/src/script/main/elements/search-bar/index.js +64 -0
- package/src/script/main/elements/search-bar/results/result-item.js +13 -0
- package/src/script/main/elements/search-bar/results/results-button.js +20 -0
- package/src/script/main/elements/search-bar/results/results-dropdown.js +48 -0
- package/src/script/main/elements/search-bar/results/results-empty.js +21 -0
- package/src/script/main/elements/search-bar/results/results-list.js +19 -0
- package/src/script/main/index.js +11 -5
- package/src/script/main/minisearch/index.js +46 -0
- package/src/script/search/elements/result-card.js +46 -0
- package/src/script/search/elements/results-count.js +21 -0
- package/src/script/search/elements/results-empty.js +21 -0
- package/src/script/search/elements/results-list.js +25 -0
- package/src/script/search/elements/search-page.js +44 -0
- package/src/script/search/index.js +9 -0
- package/src/style/home/_analytics.scss +105 -0
- package/src/style/home/_community.scss +222 -0
- package/src/style/home/_discover.scss +208 -0
- package/src/style/home/_faq.scss +24 -0
- package/src/style/home/_goals.scss +53 -0
- package/src/style/home/_hero.scss +135 -0
- package/src/style/home/_news.scss +26 -0
- package/src/style/home/_showcase.scss +129 -0
- package/src/style/home/_slice-video.scss +168 -0
- package/src/style/home/index.scss +13 -0
- package/src/style/main/components/_dsfr-doc-pagination.scss +4 -0
- package/src/style/main/components/_dsfr-doc-searchbar.scss +54 -0
- package/src/style/main/components/_index.scss +3 -1
- package/src/style/search/_search-page.scss +0 -0
- package/src/style/search/index.scss +1 -0
- package/src/template/templates/home-template.js +4 -3
- package/src/template/templates/search-template.js +15 -8
- package/static/img/placeholder.16x9.png +0 -0
- package/src/script/main/elements/searchbar.js +0 -18
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
class Element {
|
|
2
2
|
constructor (element) {
|
|
3
3
|
this._element = element;
|
|
4
|
-
this.init();
|
|
5
4
|
}
|
|
6
5
|
|
|
7
6
|
get element () {
|
|
8
7
|
return this._element;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
init () {}
|
|
10
|
+
async init () {}
|
|
12
11
|
|
|
13
12
|
observeResize (target) {
|
|
14
13
|
this._observer = new ResizeObserver(this.resize.bind(this));
|
|
@@ -24,6 +23,11 @@ class Element {
|
|
|
24
23
|
handleClick () {}
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
const instantiateElements = (selector, ElementClass) =>
|
|
26
|
+
const instantiateElements = async (selector, ElementClass) => {
|
|
27
|
+
const elements = [...document.querySelectorAll(selector)].map(element => new ElementClass(element))
|
|
28
|
+
const promises = elements.map(element => element.init());
|
|
29
|
+
await Promise.all(promises);
|
|
30
|
+
return elements;
|
|
31
|
+
}
|
|
28
32
|
|
|
29
33
|
export { Element, instantiateElements };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const paramsString = window.location.search;
|
|
2
|
+
const searchParams = new URLSearchParams(paramsString);
|
|
3
|
+
|
|
4
|
+
const getQuery = () => {
|
|
5
|
+
return searchParams.get('query');
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const getCurrentPagination = () => {
|
|
9
|
+
return parseInt(searchParams.get('page'));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { getQuery, getCurrentPagination };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Element } from '../../core/element.js';
|
|
2
|
+
import { PaginationList } from './pagination-list.js';
|
|
3
|
+
|
|
4
|
+
class Pagination extends Element {
|
|
5
|
+
constructor(data, resultsPerPage = 10) {
|
|
6
|
+
const element = document.createElement('section');
|
|
7
|
+
element.classList.add('dsfr-doc--pagination', 'fr-my-8v');
|
|
8
|
+
super(element, 'pagination');
|
|
9
|
+
|
|
10
|
+
this._totalPages = Math.ceil(data.length / resultsPerPage);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
init() {
|
|
14
|
+
const list = new PaginationList(this._totalPages);
|
|
15
|
+
const content = list.render();
|
|
16
|
+
|
|
17
|
+
this.element.innerHTML = `<nav role="navigation" class="fr-pagination" aria-label="Pagination">
|
|
18
|
+
${content}
|
|
19
|
+
</nav>`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { Pagination };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const PaginationItemTypes = {
|
|
2
|
+
FIRST: {
|
|
3
|
+
type: 'first',
|
|
4
|
+
disabledAtMin: true,
|
|
5
|
+
},
|
|
6
|
+
PREV: {
|
|
7
|
+
type: 'prev',
|
|
8
|
+
disabledAtMin: true,
|
|
9
|
+
hasLGLabel: true,
|
|
10
|
+
},
|
|
11
|
+
NEXT: {
|
|
12
|
+
type: 'next',
|
|
13
|
+
disabledAtMax: true,
|
|
14
|
+
hasLGLabel: true,
|
|
15
|
+
},
|
|
16
|
+
LAST: {
|
|
17
|
+
type: 'last',
|
|
18
|
+
disabledAtMax: true,
|
|
19
|
+
},
|
|
20
|
+
PAGE: {
|
|
21
|
+
type: 'page',
|
|
22
|
+
},
|
|
23
|
+
ELLIPSIS: {
|
|
24
|
+
type: 'ellipsis',
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const PaginationItemPositions = {
|
|
29
|
+
FIRST: 'first',
|
|
30
|
+
LAST: 'last',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
class PaginationItem {
|
|
34
|
+
constructor(config, index, isCurrentPage = false, position = null) {
|
|
35
|
+
this._typeConfig = config;
|
|
36
|
+
this._index = index;
|
|
37
|
+
this._type = config.type;
|
|
38
|
+
this._isPage = this._type === 'page';
|
|
39
|
+
this._isCurrentPage = isCurrentPage;
|
|
40
|
+
this._position = position;
|
|
41
|
+
this._href = this.setPageHref();
|
|
42
|
+
this._title = `${window.resource?.pagination?.[this._type]} ${this._isPage ? this._index : ''}`;
|
|
43
|
+
this._label = this._isPage ? this._index : this._title;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setPageHref() {
|
|
47
|
+
const searchUrl = new URL(window.location.href);
|
|
48
|
+
searchUrl.searchParams.set('page', this._index);
|
|
49
|
+
return searchUrl.href;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getAttributes() {
|
|
53
|
+
const isDisabled =
|
|
54
|
+
(this._position === PaginationItemPositions.FIRST &&
|
|
55
|
+
this._typeConfig.disabledAtMin) ||
|
|
56
|
+
(this._position === PaginationItemPositions.LAST &&
|
|
57
|
+
this._typeConfig.disabledAtMax);
|
|
58
|
+
const classes = ['fr-pagination__link'];
|
|
59
|
+
if (!this._isPage) classes.push(`fr-pagination__link--${this._type}`);
|
|
60
|
+
if (this._typeConfig.hasLGLabel)
|
|
61
|
+
classes.push('fr-pagination__link--lg-label');
|
|
62
|
+
|
|
63
|
+
const attributes = {
|
|
64
|
+
href: !this._isCurrentPage ? this._href : null,
|
|
65
|
+
'aria-current': this._isCurrentPage && this._isPage ? 'page' : '',
|
|
66
|
+
class: classes.join(' '),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (isDisabled) {
|
|
70
|
+
attributes['aria-disabled'] = 'true';
|
|
71
|
+
attributes.role = 'link';
|
|
72
|
+
attributes.href = null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return attributes;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
render() {
|
|
79
|
+
const id = `pagination-${this._type}${this._isPage ? '-' + this._index : ''}`;
|
|
80
|
+
const tag = this._type === PaginationItemTypes.ELLIPSIS.type ? 'span' : 'a';
|
|
81
|
+
const attributes = Object.entries(this.getAttributes())
|
|
82
|
+
.filter(([key, value]) => value !== null && value !== '')
|
|
83
|
+
.map(([key, value]) => `${key}="${value}"`)
|
|
84
|
+
.join(' ');
|
|
85
|
+
|
|
86
|
+
return `<li>
|
|
87
|
+
<${tag} id="${id}" title="${this._title}" ${attributes}>
|
|
88
|
+
${this._label}
|
|
89
|
+
</${tag}>
|
|
90
|
+
</li>`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { PaginationItem, PaginationItemTypes, PaginationItemPositions };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PaginationItem,
|
|
3
|
+
PaginationItemTypes,
|
|
4
|
+
PaginationItemPositions,
|
|
5
|
+
} from './pagination-item.js';
|
|
6
|
+
import { getCurrentPagination } from '../../core/get-query.js';
|
|
7
|
+
|
|
8
|
+
class PaginationList {
|
|
9
|
+
constructor(pagesNumber) {
|
|
10
|
+
this._currentPage = getCurrentPagination() || 1;
|
|
11
|
+
this._totalPages = pagesNumber;
|
|
12
|
+
this._items = [];
|
|
13
|
+
this.init();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getPosition() {
|
|
17
|
+
switch (true) {
|
|
18
|
+
case this._currentPage === 1:
|
|
19
|
+
return PaginationItemPositions.FIRST;
|
|
20
|
+
case this._currentPage === this._totalPages:
|
|
21
|
+
return PaginationItemPositions.LAST;
|
|
22
|
+
default:
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
init() {
|
|
28
|
+
const position = this.getPosition();
|
|
29
|
+
this._items.push(
|
|
30
|
+
new PaginationItem(
|
|
31
|
+
PaginationItemTypes.FIRST,
|
|
32
|
+
1,
|
|
33
|
+
this._currentPage === 1,
|
|
34
|
+
position
|
|
35
|
+
)
|
|
36
|
+
);
|
|
37
|
+
this._items.push(
|
|
38
|
+
new PaginationItem(
|
|
39
|
+
PaginationItemTypes.PREV,
|
|
40
|
+
Math.max(this._currentPage - 1, 1),
|
|
41
|
+
this._currentPage === 1,
|
|
42
|
+
position
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const pages =
|
|
47
|
+
this._totalPages <= 5 ? this.getAllPages() : this.getDynamicPages();
|
|
48
|
+
|
|
49
|
+
for (const page of pages) {
|
|
50
|
+
this._items.push(
|
|
51
|
+
new PaginationItem(
|
|
52
|
+
page.type,
|
|
53
|
+
page.index,
|
|
54
|
+
this._currentPage === page.index
|
|
55
|
+
)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this._items.push(
|
|
60
|
+
new PaginationItem(
|
|
61
|
+
PaginationItemTypes.NEXT,
|
|
62
|
+
Math.min(this._currentPage + 1, this._totalPages),
|
|
63
|
+
this._currentPage === this._totalPages,
|
|
64
|
+
position
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
this._items.push(
|
|
68
|
+
new PaginationItem(
|
|
69
|
+
PaginationItemTypes.LAST,
|
|
70
|
+
this._totalPages,
|
|
71
|
+
this._currentPage === this._totalPages,
|
|
72
|
+
position
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getAllPages() {
|
|
78
|
+
return [
|
|
79
|
+
...Array.from({ length: this._totalPages }, (_, i) => ({
|
|
80
|
+
type: PaginationItemTypes.PAGE,
|
|
81
|
+
index: i + 1,
|
|
82
|
+
})),
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getDynamicPages() {
|
|
87
|
+
const ellipsis = { type: PaginationItemTypes.ELLIPSIS };
|
|
88
|
+
if (this._currentPage <= 3) {
|
|
89
|
+
return [
|
|
90
|
+
...Array.from({ length: 3 }, (_, i) => ({
|
|
91
|
+
type: PaginationItemTypes.PAGE,
|
|
92
|
+
index: i + 1,
|
|
93
|
+
})),
|
|
94
|
+
ellipsis,
|
|
95
|
+
{ type: PaginationItemTypes.PAGE, index: this._totalPages },
|
|
96
|
+
];
|
|
97
|
+
} else if (this._currentPage >= this._totalPages - 2) {
|
|
98
|
+
return [
|
|
99
|
+
{ type: PaginationItemTypes.PAGE, index: 1 },
|
|
100
|
+
ellipsis,
|
|
101
|
+
{ type: PaginationItemTypes.PAGE, index: this._totalPages - 2 },
|
|
102
|
+
{ type: PaginationItemTypes.PAGE, index: this._totalPages - 1 },
|
|
103
|
+
{ type: PaginationItemTypes.PAGE, index: this._totalPages },
|
|
104
|
+
];
|
|
105
|
+
} else {
|
|
106
|
+
return [
|
|
107
|
+
{ type: PaginationItemTypes.PAGE, index: 1 },
|
|
108
|
+
ellipsis,
|
|
109
|
+
{
|
|
110
|
+
type: PaginationItemTypes.PAGE,
|
|
111
|
+
index: this._currentPage - 1,
|
|
112
|
+
},
|
|
113
|
+
{ type: PaginationItemTypes.PAGE, index: this._currentPage },
|
|
114
|
+
{
|
|
115
|
+
type: PaginationItemTypes.PAGE,
|
|
116
|
+
index: this._currentPage + 1,
|
|
117
|
+
},
|
|
118
|
+
ellipsis,
|
|
119
|
+
{ type: PaginationItemTypes.PAGE, index: this._totalPages },
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
render() {
|
|
125
|
+
return `<ul class="fr-pagination__list">
|
|
126
|
+
${this._items.map((item) => item.render()).join('')}
|
|
127
|
+
</ul>`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export { PaginationList };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Element } from '../../core/element.js';
|
|
2
|
+
import { ResultsDropdown } from './results/results-dropdown.js';
|
|
3
|
+
|
|
4
|
+
class SearchBar extends Element {
|
|
5
|
+
constructor (element) {
|
|
6
|
+
super(element, 'searchBar');
|
|
7
|
+
this._searchInput = this.element.querySelector('#search-input');
|
|
8
|
+
this._searchButton = this.element.querySelector('#search-button');
|
|
9
|
+
this._query = this._searchInput.value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async init () {
|
|
13
|
+
this._url = this._searchButton.getAttribute('data-href');
|
|
14
|
+
|
|
15
|
+
this._resultsDropdown = new ResultsDropdown(this._url);
|
|
16
|
+
this.element.appendChild(this._resultsDropdown.element);
|
|
17
|
+
await this._resultsDropdown.init();
|
|
18
|
+
|
|
19
|
+
document.addEventListener('click', this.handleDocumentClick.bind(this));
|
|
20
|
+
|
|
21
|
+
this._searchInput.addEventListener(
|
|
22
|
+
'focus',
|
|
23
|
+
this.handleInputFocus.bind(this)
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
this._searchInput.addEventListener(
|
|
27
|
+
'keyup',
|
|
28
|
+
this.handleKeyup.bind(this)
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// Handle search input clear
|
|
32
|
+
this._searchInput.addEventListener(
|
|
33
|
+
'search',
|
|
34
|
+
this.handleKeyup.bind(this)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
update (query) {
|
|
39
|
+
this._query = query;
|
|
40
|
+
this._searchButton.setAttribute('href', `${this._url}?query=${query}`);
|
|
41
|
+
this._resultsDropdown.update(query);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
handleDocumentClick (event) {
|
|
45
|
+
const outsideClick = !this.element.contains(event.target);
|
|
46
|
+
if (outsideClick) {
|
|
47
|
+
this._resultsDropdown.reset();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
handleKeyup (event) {
|
|
52
|
+
if (event.key === 'Enter') {
|
|
53
|
+
document.location.href = this._searchButton.getAttribute('href');
|
|
54
|
+
} else {
|
|
55
|
+
this.update(event.target.value);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
handleInputFocus () {
|
|
60
|
+
this.update(this._query);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { SearchBar };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class ResultsButton {
|
|
2
|
+
constructor (url) {
|
|
3
|
+
this._url = url;
|
|
4
|
+
this._query = '';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
set query (query) {
|
|
8
|
+
this._query = query;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get query () {
|
|
12
|
+
return this._query;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
render () {
|
|
16
|
+
return `<a class="fr-btn fr-btn--secondary fr-mt-2v" href="${this._url}?query=${this._query}" id="search-button">${window.resource?.search?.results?.button}</a>`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { ResultsButton };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Element } from '../../../core/element.js';
|
|
2
|
+
import { ResultsEmpty } from './results-empty.js';
|
|
3
|
+
import { ResultsButton } from './results-button.js';
|
|
4
|
+
import { ResultsList } from './results-list.js';
|
|
5
|
+
|
|
6
|
+
class ResultsDropdown extends Element {
|
|
7
|
+
constructor(url) {
|
|
8
|
+
const element = document.createElement('div');
|
|
9
|
+
element.classList.add('dsfr-doc-search-results--dropdown');
|
|
10
|
+
super(element);
|
|
11
|
+
|
|
12
|
+
this._button = new ResultsButton(url);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async init() {
|
|
16
|
+
await window.searchEngine.init('searchBar');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
reset() {
|
|
20
|
+
this.element.innerHTML = '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
update(query) {
|
|
24
|
+
if (query.length <= 2) {
|
|
25
|
+
this.reset();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const results = window.searchEngine.search(query);
|
|
30
|
+
|
|
31
|
+
switch (true) {
|
|
32
|
+
case !results:
|
|
33
|
+
case results.length === 0:
|
|
34
|
+
const empty = new ResultsEmpty(query);
|
|
35
|
+
this._content = empty.render();
|
|
36
|
+
break;
|
|
37
|
+
|
|
38
|
+
default:
|
|
39
|
+
const list = new ResultsList(results);
|
|
40
|
+
this._button.query = query;
|
|
41
|
+
this._content = list.render() + this._button.render();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.element.innerHTML = `<div class="fr-px-md-4v fr-py-6v">${this._content}</div>`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { ResultsDropdown };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { replaceFragment } from '../../../core/replace-fragments.js';
|
|
2
|
+
|
|
3
|
+
class ResultsEmpty {
|
|
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-search-results--list--no-results">
|
|
15
|
+
<li>${noResults?.suggestions1}</li>
|
|
16
|
+
<li>${noResults?.suggestions2}</li>
|
|
17
|
+
</ul>`;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { ResultsEmpty };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ResultItem } from './result-item.js';
|
|
2
|
+
|
|
3
|
+
const MAX_RESULTS = 6;
|
|
4
|
+
|
|
5
|
+
class ResultsList {
|
|
6
|
+
constructor (data) {
|
|
7
|
+
this._items = data.slice(0, MAX_RESULTS).map(itemData => new ResultItem(itemData));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
render() {
|
|
11
|
+
return `<ul class="dsfr-doc-search-results--list">
|
|
12
|
+
${this._items.map(item => item.render()).join('')}
|
|
13
|
+
</ul>`;
|
|
14
|
+
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { ResultsList };
|
package/src/script/main/index.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import './minisearch/index.js';
|
|
1
2
|
import { instantiateElements } from './core/element.js'
|
|
2
3
|
import CopySnippet from './elements/copy-snippet.js';
|
|
3
4
|
import Storybook from './elements/storybook.js';
|
|
4
|
-
import {
|
|
5
|
+
import { SearchBar } from './elements/search-bar/index.js';
|
|
6
|
+
import { ConsentManagementPlatform } from './cmp/index.js';
|
|
5
7
|
|
|
6
|
-
window.onload = () => {
|
|
7
|
-
instantiateElements('.code-snippet--copy', CopySnippet);
|
|
8
|
-
instantiateElements('.storybook-leaf iframe', Storybook);
|
|
9
|
-
instantiateElements('#search',
|
|
8
|
+
window.onload = async () => {
|
|
9
|
+
await instantiateElements('.code-snippet--copy', CopySnippet);
|
|
10
|
+
await instantiateElements('.storybook-leaf iframe', Storybook);
|
|
11
|
+
await instantiateElements('#search', SearchBar);
|
|
10
12
|
};
|
|
13
|
+
|
|
14
|
+
const consentResource = window.resource?.consent || {};
|
|
15
|
+
const cmp = new ConsentManagementPlatform(consentResource);
|
|
16
|
+
cmp.init();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import MiniSearch from 'minisearch';
|
|
2
|
+
|
|
3
|
+
const LOAD_OPTIONS = {
|
|
4
|
+
fields: ['title', 'keywords', 'summary', 'excerpt', 'text'],
|
|
5
|
+
storeFields: ['title', 'url', 'boost', 'summary', 'excerpt', 'cover', 'section'],
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const SEARCH_OPTIONS = {
|
|
9
|
+
prefix: true,
|
|
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
|
+
switch (type) {
|
|
17
|
+
case 'search':
|
|
18
|
+
return SEARCH_OPTIONS;
|
|
19
|
+
default:
|
|
20
|
+
return LOAD_OPTIONS;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ROOT_REGEX = /^(?<root>\/(?<version>v\d+\.\d+|[\w-]+)\/(?<locale>\w+)\/)/;
|
|
25
|
+
const CURRENT_VERSION_REGEX = /^(?<root>\/(?<version>v\d+\.\d+|[\w-]+)\/(?<locale>\w+)\/(?<search>v\d+\.\d+|[\w-]+))/
|
|
26
|
+
|
|
27
|
+
const getSearchIndex = (type) => {
|
|
28
|
+
const searchIndexRegex = type === 'searchPage' ? CURRENT_VERSION_REGEX : ROOT_REGEX;
|
|
29
|
+
const { groups: { root, version, locale } } = window.location.pathname.match(searchIndexRegex);
|
|
30
|
+
return `${root}/index.json`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class SearchEngine {
|
|
34
|
+
async init (type) {
|
|
35
|
+
const response = await fetch(getSearchIndex(type));
|
|
36
|
+
const json = JSON.stringify(await response.json());
|
|
37
|
+
const options = getOptions('load');
|
|
38
|
+
this._miniSearch = MiniSearch.loadJSON(json, options);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
search (query) {
|
|
42
|
+
return this._miniSearch.search(query, getOptions('search'));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
window.searchEngine = new SearchEngine();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
class ResultCard {
|
|
2
|
+
constructor(data) {
|
|
3
|
+
this._title = data.title;
|
|
4
|
+
this._url = data.url;
|
|
5
|
+
this._desc = data.excerpt ?? data.summary;
|
|
6
|
+
this._cover = data.cover || '/static/img/placeholder.16x9.png';
|
|
7
|
+
this._section = data.section;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
getTags() {
|
|
11
|
+
if (!this._section) return '';
|
|
12
|
+
return `<div class="fr-card__start">
|
|
13
|
+
<ul class="fr-tags-group">
|
|
14
|
+
<li>
|
|
15
|
+
<p class="fr-tag">${this._section}</p>
|
|
16
|
+
</li>
|
|
17
|
+
</ul>
|
|
18
|
+
</div>`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getCover() {
|
|
22
|
+
if (!this._cover) return '';
|
|
23
|
+
return `<div class="fr-card__header">
|
|
24
|
+
<div class="fr-card__img">
|
|
25
|
+
<img src="${this._cover}" alt="" class="fr-responsive-img">
|
|
26
|
+
</div>
|
|
27
|
+
</div>`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
render() {
|
|
31
|
+
return `<div class="fr-card fr-enlarge-link fr-card--horizontal fr-mb-8v">
|
|
32
|
+
<div class="fr-card__body">
|
|
33
|
+
<div class="fr-card__content">
|
|
34
|
+
<h3 class="fr-card__title">
|
|
35
|
+
<a href="${this._url}">${this._title}</a>
|
|
36
|
+
</h3>
|
|
37
|
+
${this._desc ? `<p class="fr-card__desc">${this._desc}</p>` : ''}
|
|
38
|
+
${this.getTags()}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
${this.getCover()}
|
|
42
|
+
</div>`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { ResultCard };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { replaceFragment } from '../../main/core/replace-fragments.js';
|
|
2
|
+
import { getQuery } from '../../main/core/get-query.js';
|
|
3
|
+
|
|
4
|
+
class ResultsCount {
|
|
5
|
+
constructor(count) {
|
|
6
|
+
this._resourceSearch = window.resource?.search;
|
|
7
|
+
this._query = getQuery();
|
|
8
|
+
this._resultsCount = count
|
|
9
|
+
? `${count} ${count > 1 ? this._resourceSearch?.results?.count?.start?.plural : this._resourceSearch?.results?.count?.start?.single} ${replaceFragment(this._resourceSearch?.results?.count?.end, this._query)}`
|
|
10
|
+
: replaceFragment(
|
|
11
|
+
this._resourceSearch?.noresults?.title,
|
|
12
|
+
this._query
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
render() {
|
|
17
|
+
return this._resultsCount;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { ResultsCount };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { replaceFragment } from '../../../core/replace-fragments.js';
|
|
2
|
+
|
|
3
|
+
class ResultsEmpty {
|
|
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-search-results--list--no-results">
|
|
15
|
+
<li>${noResults?.suggestions1}</li>
|
|
16
|
+
<li>${noResults?.suggestions2}</li>
|
|
17
|
+
</ul>`;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { ResultsEmpty };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Element } from '../../main/core/element.js';
|
|
2
|
+
import { ResultCard } from './result-card.js';
|
|
3
|
+
import { getCurrentPagination } from '../../main/core/get-query.js';
|
|
4
|
+
|
|
5
|
+
class ResultsList extends Element {
|
|
6
|
+
constructor(data, resultsPerPage = 10) {
|
|
7
|
+
const element = document.createElement('section');
|
|
8
|
+
element.classList.add('fr-mt-8v', 'fr-mb-14v');
|
|
9
|
+
super(element, 'resultsList');
|
|
10
|
+
|
|
11
|
+
const page = getCurrentPagination() || 1;
|
|
12
|
+
const results = data.slice(
|
|
13
|
+
(page - 1) * resultsPerPage,
|
|
14
|
+
page * resultsPerPage
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
this._cards = results.map((cardData) => new ResultCard(cardData));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
init() {
|
|
21
|
+
this.element.innerHTML = this._cards.map((item) => item.render()).join('');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { ResultsList };
|