@gouvfr/dsfr-roller 1.0.7 → 1.0.8

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.
Files changed (89) hide show
  1. package/package.json +2 -2
  2. package/src/component/components/button.js +2 -2
  3. package/src/component/components/header.js +1 -1
  4. package/src/node/directive/{doc → components/accordion}/accordion-container-directive.js +4 -4
  5. package/src/node/directive/{doc → components/accordion}/accordions-group-container-directive.js +1 -1
  6. package/src/node/directive/{doc → components/button}/button-leaf-directive.js +3 -3
  7. package/src/node/directive/components/card/card-container-directive.js +114 -0
  8. package/src/node/directive/{doc → components/table}/table-container-directive.js +7 -7
  9. package/src/node/directive/{doc → components/tabs}/tab-container-directive.js +1 -1
  10. package/src/node/directive/{doc → components/tabs}/tabs-container-directive.js +1 -1
  11. package/src/node/directive/components/tile/tile-container-directive.js +135 -0
  12. package/src/node/directive/core/grid-container-directive.js +15 -0
  13. package/src/node/directive/doc/{anatomy-container-directive.js → guidance/anatomy-container-directive.js} +2 -2
  14. package/src/node/directive/doc/{guideline-container-directive.js → guidance/guideline-container-directive.js} +3 -3
  15. package/src/node/directive/doc/{guidelines-container-directive.js → guidance/guidelines-container-directive.js} +1 -1
  16. package/src/node/directive/doc/{pin-leaf-directive.js → guidance/pin-leaf-directive.js} +1 -1
  17. package/src/node/directive/doc/page-item-card-container-directive.js +53 -0
  18. package/src/node/directive/doc/page-item-list-leaf-directive.js +33 -0
  19. package/src/node/directive/doc/tab-navigation-container-directive.js +1 -1
  20. package/src/node/directive/doc/video-leaf-directive.js +44 -0
  21. package/src/node/directive/home/hp-analytics-container-directive.js +67 -0
  22. package/src/node/directive/home/hp-community-container-directive.js +78 -0
  23. package/src/node/directive/home/hp-community-tile-container-directive.js +57 -0
  24. package/src/node/directive/home/hp-discover-container-directive.js +41 -0
  25. package/src/node/directive/home/hp-discover-tile-container-directive.js +81 -0
  26. package/src/node/directive/home/hp-faq-container-directive.js +60 -0
  27. package/src/node/directive/home/hp-goals-container-directive.js +37 -0
  28. package/src/node/directive/home/hp-hero-container-directive.js +54 -0
  29. package/src/node/directive/home/hp-news-container-directive.js +53 -0
  30. package/src/node/directive/home/hp-showcase-card-container-directive.js +81 -0
  31. package/src/node/directive/home/hp-showcase-container-directive.js +55 -0
  32. package/src/node/directive/home/hp-slice-video-container-directive.js +246 -0
  33. package/src/node/generic/link-node.js +1 -1
  34. package/src/node/node-factory.js +45 -11
  35. package/src/page/head/head.js +3 -0
  36. package/src/page/head/resource.js +17 -0
  37. package/src/page/head/share.js +23 -5
  38. package/src/page/head/stylesheets.js +2 -1
  39. package/src/page/page.js +3 -1
  40. package/src/page/scripts/scripts.js +2 -1
  41. package/src/script/home/index.js +10 -0
  42. package/src/script/home/inject-svg.js +28 -0
  43. package/src/script/home/show-on-scroll.js +24 -0
  44. package/src/script/home/stop-video-on-close.js +20 -0
  45. package/src/script/main/cmp/index.js +72 -0
  46. package/src/script/main/cmp/tarteaucitron/config.js +34 -0
  47. package/src/script/main/cmp/tarteaucitron/lang.js +14 -0
  48. package/src/script/main/cmp/tarteaucitron/services.js +9142 -0
  49. package/src/script/main/cmp/tarteaucitron/tarteaucitron.js +3301 -0
  50. package/src/script/main/core/element.js +7 -3
  51. package/src/script/main/core/get-current-repo.js +6 -0
  52. package/src/script/main/core/get-query.js +12 -0
  53. package/src/script/main/core/replace-fragments.js +4 -0
  54. package/src/script/main/elements/pagination/index.js +23 -0
  55. package/src/script/main/elements/pagination/pagination-item.js +94 -0
  56. package/src/script/main/elements/pagination/pagination-list.js +131 -0
  57. package/src/script/main/elements/search-bar/index.js +64 -0
  58. package/src/script/main/elements/search-bar/results/result-item.js +13 -0
  59. package/src/script/main/elements/search-bar/results/results-button.js +20 -0
  60. package/src/script/main/elements/search-bar/results/results-dropdown.js +48 -0
  61. package/src/script/main/elements/search-bar/results/results-empty.js +21 -0
  62. package/src/script/main/elements/search-bar/results/results-list.js +19 -0
  63. package/src/script/main/index.js +11 -5
  64. package/src/script/main/minisearch/index.js +46 -0
  65. package/src/script/search/elements/result-card.js +46 -0
  66. package/src/script/search/elements/results-count.js +21 -0
  67. package/src/script/search/elements/results-empty.js +21 -0
  68. package/src/script/search/elements/results-list.js +25 -0
  69. package/src/script/search/elements/search-page.js +44 -0
  70. package/src/script/search/index.js +9 -0
  71. package/src/style/home/_analytics.scss +105 -0
  72. package/src/style/home/_community.scss +222 -0
  73. package/src/style/home/_discover.scss +208 -0
  74. package/src/style/home/_faq.scss +24 -0
  75. package/src/style/home/_goals.scss +53 -0
  76. package/src/style/home/_hero.scss +135 -0
  77. package/src/style/home/_news.scss +26 -0
  78. package/src/style/home/_showcase.scss +129 -0
  79. package/src/style/home/_slice-video.scss +168 -0
  80. package/src/style/home/index.scss +13 -0
  81. package/src/style/main/components/_dsfr-doc-pagination.scss +4 -0
  82. package/src/style/main/components/_dsfr-doc-searchbar.scss +54 -0
  83. package/src/style/main/components/_index.scss +3 -1
  84. package/src/style/search/_search-page.scss +0 -0
  85. package/src/style/search/index.scss +1 -0
  86. package/src/template/templates/home-template.js +4 -3
  87. package/src/template/templates/search-template.js +15 -8
  88. package/static/img/placeholder.16x9.png +0 -0
  89. 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) => [...document.querySelectorAll(selector)].map(element => new ElementClass(element));
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,6 @@
1
+ const getCurrentRepo = () => {
2
+ const pathArray = window.location.pathname.split('/');
3
+ return `/${pathArray.splice(1, 2).join('/')}`;
4
+ };
5
+
6
+ export { getCurrentRepo };
@@ -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,4 @@
1
+ export const replaceFragment = (fragment, query) => {
2
+ if (!fragment) return undefined;
3
+ return fragment.replace(/\[(\w+)\]\(%s\)/g, query);
4
+ };
@@ -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,13 @@
1
+ class ResultItem {
2
+ constructor (data) {
3
+ this._title = data.title;
4
+ this._url = data.url;
5
+
6
+ }
7
+
8
+ render () {
9
+ return `<li><a class="fr-link" href="${this._url}" >${this._title}</a></li>`;
10
+ }
11
+ }
12
+
13
+ export { ResultItem };
@@ -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 };
@@ -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 { Searchbar } from './elements/searchbar.js'
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', Searchbar);
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 };