@gouvfr/dsfr-roller 1.0.49 → 1.0.51

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/SECURITY.md CHANGED
@@ -1,4 +1,4 @@
1
- ## Sécurité du Système de Design de l'État
1
+ ## Sécurité du Système de Design de l’État
2
2
 
3
3
  L’équipe derrière le Système de Design de l’État prend les risques liés à la sécurité très au sérieux.
4
4
  C’est pour cette raison qu’un audit de sécurité à été réalisé sur l’ensemble des composants et des librairies avant la sortie ainsi que la mise en place de bonnes pratiques (double authentification, signature des paquets, etc.).
package/legal/cgu.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ## CONDITIONS GÉNÉRALES D’UTILISATION DU SYSTÈME DE DESIGN DE L’ÉTAT
2
2
 
3
- Le Service d’Information du Gouvernement (« SIG ») vous remercie de bien vouloir lire attentivement l’intégralité des présentes Conditions Générales d’Utilisation (« CGU ») avant d’utiliser la plateforme du Système de Design de l'État (« Plateforme »). Tout accès, toute utilisation et/ou toute Contribution à la Plateforme est subordonné au respect des présentes CGU. Tout Utilisateur de la Plateforme reconnaît souscrire sans réserve aux présentes CGU et s’engage à les respecter.
3
+ Le Service d’Information du Gouvernement (« SIG ») vous remercie de bien vouloir lire attentivement l’intégralité des présentes Conditions Générales d’Utilisation (« CGU ») avant d’utiliser la plateforme du Système de Design de l’État (« Plateforme »). Tout accès, toute utilisation et/ou toute Contribution à la Plateforme est subordonné au respect des présentes CGU. Tout Utilisateur de la Plateforme reconnaît souscrire sans réserve aux présentes CGU et s’engage à les respecter.
4
4
 
5
5
 
6
6
 
@@ -9,7 +9,7 @@ Le Service d’Information du Gouvernement (« SIG ») vous remercie de bien vou
9
9
  Le SIG est le pilote de la transformation numérique de la communication de l’État . À ce titre, le SIG a pour mission de placer toutes les actions du Gouvernement sous la marque de l’État afin de garantir l’homogénéité et la reconnaissance de l’ensemble des initiatives ministérielles ou interministérielles.
10
10
  Dans ce cadre, le SIG a mis en place la nouvelle stratégie de la marque de l’État pour rendre l’action de l’État plus concrète, lisible et visible grâce à un signe commun pour les citoyens.
11
11
 
12
- Dans la continuité de la mise en œuvre de la marque de l’État, le SIG a structuré dans le cadre d’une plateforme, le Système de Design de l'État visant à mettre à disposition des librairies de Ressources permettant notamment de standardiser et d’harmoniser l’expérience numérique de l’État et des citoyens grâce à une interface commune des sites Internet et applications de l’État. Le Système de Design de l’État permet ainsi de générer des économies, en rationalisant les développements numériques, de garantir la qualité et l’efficacité de l’expérience utilisateur, la cohérence et une meilleure reconnaissance des services de l’État.
12
+ Dans la continuité de la mise en œuvre de la marque de l’État, le SIG a structuré dans le cadre d’une plateforme, le Système de Design de l’État visant à mettre à disposition des librairies de Ressources permettant notamment de standardiser et d’harmoniser l’expérience numérique de l’État et des citoyens grâce à une interface commune des sites Internet et applications de l’État. Le Système de Design de l’État permet ainsi de générer des économies, en rationalisant les développements numériques, de garantir la qualité et l’efficacité de l’expérience utilisateur, la cohérence et une meilleure reconnaissance des services de l’État.
13
13
 
14
14
  Les présentes Conditions Générales définissent les conditions selon lesquelles l’Utilisateur de l’État ou les Autres Utilisateurs peuvent naviguer sur la Plateforme accessible sur l’URL https://www.systeme-de-design.gouv.fr, accéder et exploiter les Ressources et Contribuer à leur amélioration ou leur enrichissement.
15
15
 
@@ -19,13 +19,13 @@ Les présentes Conditions Générales définissent les conditions selon lesquell
19
19
 
20
20
  Les termes débutant avec une majuscule dans les présentes CGU, qu’ils soient utilisés au singulier ou au pluriel auront la signification qui leur est attribuée ci-après :
21
21
 
22
- * **Communauté** : désigne la communauté en ligne des Utilisateurs et des membres de l’équipe du Système de Design de l'État.
22
+ * **Communauté** : désigne la communauté en ligne des Utilisateurs et des membres de l’équipe du Système de Design de l’État.
23
23
 
24
24
  * **Composants** : désigne les librairies d’éléments destinés aux designers et aux développeurs intégrant des Éléments Graphiques (Fondamentaux Techniques et les Éléments d’Identité), des Éléments d’Interface, des Paquets de codes sources.
25
25
 
26
26
  * **Contribution** : désigne tout élément fourni par l’Utilisateur dans le cadre de l’utilisation de la Plateforme, qu’il s’agisse de la modification d’un Composant ou du développement d’un nouveau Composant.
27
27
 
28
- * **Système de Design de l'État (DSFR)** : désigne l’ensemble de Composants réutilisables, guidé par des standards et une gouvernance, devant être assemblés pour construire les sites internet et applications mobiles des Entités Autorisées et objet des présentes Conditions Générales.
28
+ * **Système de Design de l’État (DSFR)** : désigne l’ensemble de Composants réutilisables, guidé par des standards et une gouvernance, devant être assemblés pour construire les sites internet et applications mobiles des Entités Autorisées et objet des présentes Conditions Générales.
29
29
 
30
30
  * **Documentation** : désigne les principes d’utilisation des Composants auxquels les Utilisateurs doivent se conformer.
31
31
 
@@ -41,11 +41,11 @@ Les termes débutant avec une majuscule dans les présentes CGU, qu’ils soient
41
41
 
42
42
  * **Marque de l’Etat** : telle que définie par la circulaire du Premier Ministre en date du 17 février 2020, laquelle précise la stratégie de marque de l’Etat.
43
43
 
44
- * **Opérateurs de l’État** : désigne les entités définies chaque année dans l’annexe « Opérateurs de l'État » du projet de loi de finances de l’année en cours.
44
+ * **Opérateurs de l’État** : désigne les entités définies chaque année dans l’annexe « Opérateurs de l’État » du projet de loi de finances de l’année en cours.
45
45
 
46
46
  * **Paquets de codes sources** : désigne les fichiers de codes sources accessibles sur la plateforme de Git (Github).
47
47
 
48
- * **Plateforme** : désigne le site Internet du Système de Design de l'État, permettant aux Utilisateurs de naviguer, utiliser et contribuer au Système de Design de l'État.
48
+ * **Plateforme** : désigne le site Internet du Système de Design de l’État, permettant aux Utilisateurs de naviguer, utiliser et contribuer au Système de Design de l’État.
49
49
 
50
50
  * **Ressources** : désigne les Composants, la Documentation, le support et les outils mis à disposition de l’Utilisateur sur ou au travers de la Plateforme.
51
51
 
@@ -146,7 +146,7 @@ Cette Plateforme est en constante évolution et enrichissement et le SIG ne saur
146
146
 
147
147
  ### ARTICLE 5 – PROCÉDURE D’AGRÉMENT
148
148
 
149
- Conformément à la circulaire n°6120/SG du 14 octobre 2019 relative à l’organisation et la coordination de la communication gouvernementale, toutes les refontes ou créations de site Internet et applications mobiles de communication de l'État (que le nom de domaine soit en <.gouv.fr>, en <.fr> ou autre) sont obligatoirement soumises à l’agrément du SIG.
149
+ Conformément à la circulaire n°6120/SG du 14 octobre 2019 relative à l’organisation et la coordination de la communication gouvernementale, toutes les refontes ou créations de site Internet et applications mobiles de communication de l’État (que le nom de domaine soit en <.gouv.fr>, en <.fr> ou autre) sont obligatoirement soumises à l’agrément du SIG.
150
150
 
151
151
  Les Entités Autorisées doivent solliciter un agrément de principe du SIG, préalablement au lancement des prestations de refonte ou de création d’un site Internet et applications mobiles de communication de l’État pour lequel ils entendent utiliser la Plateforme. Cette demande s’effectue à l’aide du formulaire disponible au lien suivant : https://www.demarches-simplifiees.fr/commencer/agrement-principe-site-internet
152
152
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gouvfr/dsfr-roller",
3
- "version": "1.0.49",
3
+ "version": "1.0.51",
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",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "type": "module",
44
44
  "engines": {
45
- "node": ">=20.19.3"
45
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
46
46
  },
47
47
  "packageManager": "yarn@4.5.2+sha224.c2e2e9ed3cdadd6ec250589b3393f71ae56d5ec297af11cec1eba3b4",
48
48
  "files": [
@@ -56,8 +56,8 @@
56
56
  ],
57
57
  "main": "./index.js",
58
58
  "dependencies": {
59
- "@gouvfr/dsfr-forge": "=1.0.49",
60
- "@gouvfr/dsfr-publisher": "npm:@gouvfr/dsfr@1.14.0",
59
+ "@gouvfr/dsfr-forge": "=1.0.51",
60
+ "@gouvfr/dsfr-publisher": "npm:@gouvfr/dsfr@1.14.1-rc.1",
61
61
  "deepmerge": "^4.3.1",
62
62
  "ejs": "^3.1.10",
63
63
  "highlight.js": "^11.10.0",
@@ -22,6 +22,7 @@ class TableContainerDirective extends Node {
22
22
  const table = data.children?.find(child => child.type === 'table');
23
23
  if (table ) {
24
24
  table.caption = directiveLabel.children?.[0]?.value;
25
+ table.multiline = data.properties?.multiline === true;
25
26
  }
26
27
  return data;
27
28
  }
@@ -25,6 +25,8 @@ class TileContainerDirective extends Node {
25
25
  structureTile(data) {
26
26
  const pictogramUrl = data.properties.pictogram;
27
27
  const image = Node.getImageChild(data);
28
+ const imgDarkUrl = data.imgDarkUrl;
29
+ const hasImage = image || imgDarkUrl;
28
30
  const contentChildren = data.children.filter(child => !Node.getImageChild(child));
29
31
  const title = contentChildren.find(child => child.type === 'heading');
30
32
  const description = contentChildren[1];
@@ -71,7 +73,7 @@ class TileContainerDirective extends Node {
71
73
  },
72
74
  ]
73
75
  },
74
- pictogramUrl || image ? {
76
+ pictogramUrl || hasImage ? {
75
77
  type: 'htmlContainer',
76
78
  tagName: 'div',
77
79
  classes: ['fr-tile__header'],
@@ -120,17 +122,28 @@ class TileContainerDirective extends Node {
120
122
  }
121
123
  ]
122
124
  } : {},
123
- image ? {
125
+ hasImage ? {
124
126
  type: 'htmlContainer',
125
127
  tagName: 'div',
126
128
  classes: ['fr-tile__img'],
127
129
  children: [
128
130
  {
129
- classe: ['fr-responsive-img'],
130
- ...image
131
- }
131
+ classes: ['fr-responsive-img', imgDarkUrl ? 'fr-tile__img--light' : ''],
132
+ attributes: {
133
+ 'aria-hidden': 'true',
134
+ },
135
+ ...image,
136
+ },
137
+ imgDarkUrl ? {
138
+ classes: ['fr-responsive-img', 'fr-tile__img--dark'],
139
+ type: 'image',
140
+ attributes: {
141
+ src: imgDarkUrl,
142
+ 'aria-hidden': 'true'
143
+ }
144
+ } : {}
132
145
  ]
133
- } : {},
146
+ } : {}
134
147
  ]
135
148
  } : {},
136
149
  ]
@@ -37,10 +37,7 @@ class GuidelineContainerDirective extends Node {
37
37
  {
38
38
  type: 'image',
39
39
  classes: ['dsfr-doc-guideline__img'],
40
- attributes: {
41
- src: image.url,
42
- alt: image.alt,
43
- }
40
+ ...image
44
41
  },
45
42
  {
46
43
  type: 'htmlContainer',
@@ -0,0 +1,12 @@
1
+ import { Node } from '../../node.js'
2
+
3
+ class PreventPermalinksContainerDirective extends Node {
4
+ async render() {
5
+ const content = await super.render();
6
+ return `<!-- NO_PERMALINK_START -->\n${content}\n<!-- NO_PERMALINK_END -->`;
7
+ }
8
+ }
9
+
10
+ PreventPermalinksContainerDirective.NAME = 'dsfr-doc-prevent-permalinks';
11
+
12
+ export { PreventPermalinksContainerDirective };
@@ -5,7 +5,6 @@ class TableHeaderNode extends TableCellNode {
5
5
  super(data, 'th');
6
6
  if (this.data.scope) {
7
7
  this.attributes.setAttribute('scope', this.data.scope);
8
- if (this.data.scope === 'row') this.attributes.addClass('fr-cell--fixed');
9
8
  }
10
9
  if (this.data.isColumnHeader) this.attributes.setAttribute('role', 'columnheader');
11
10
  }
@@ -5,6 +5,7 @@ class TableNode extends Node {
5
5
  constructor (data) {
6
6
  super(data);
7
7
  this._caption = data.caption;
8
+ this._multiline = data.multiline;
8
9
  this.attributes.addClass('fr-table');
9
10
  }
10
11
 
@@ -70,7 +71,7 @@ class TableNode extends Node {
70
71
  <div class="fr-table__wrapper">
71
72
  <div class="fr-table__container">
72
73
  <div class="fr-table__content">
73
- <table>
74
+ <table ${this._multiline ? 'class="fr-cell--multiline"' : ''}>
74
75
  ${caption}
75
76
  ${await this.renderChildren()}
76
77
  </table>
@@ -59,6 +59,7 @@ import { PageItemCardContainerDirective } from './directive/doc/page-item-card-c
59
59
  import { VideoLeafDirective } from './directive/doc/video-leaf-directive.js';
60
60
  import { ImageTextDirective } from './directive/doc/image-text-directive.js'
61
61
  import { ChangelogLeafDirective } from './directive/doc/changelog-leaf-directive.js'
62
+ import { PreventPermalinksContainerDirective } from './directive/doc/prevent-permalinks-container-directive.js';
62
63
 
63
64
 
64
65
  const NODES = [
@@ -118,7 +119,8 @@ const DIRECTIVE_CONTAINERS = [
118
119
  TabsContainerDirective,
119
120
  AnatomyContainerDirective,
120
121
  GridContainerDirective,
121
- ColContainerDirective
122
+ ColContainerDirective,
123
+ PreventPermalinksContainerDirective
122
124
  ];
123
125
  const DIRECTIVE_LEAFS = [
124
126
  ButtonLeafDirective,
package/src/node/node.js CHANGED
@@ -31,7 +31,7 @@ class Node extends Renderable {
31
31
  }
32
32
 
33
33
  if (this.hasNormalizedId) {
34
- this._normalizedId = normalize(this.text);
34
+ this._normalizedId = normalize(this.text);
35
35
  this.attributes.setAttribute('id', this._normalizedId);
36
36
  }
37
37
  }
@@ -6,9 +6,8 @@ class Edit {
6
6
  classes: ['dsfr-doc-edit', 'fr-background-alt--blue-france', `fr-icon-${data.icon ?? 'edit-fill'}`],
7
7
  children: [
8
8
  {
9
- type: 'heading',
9
+ type: 'paragraph',
10
10
  classes: ['dsfr-doc-edit__title', 'fr-h5'],
11
- depth: data.depth ?? 5,
12
11
  children: [
13
12
  {
14
13
  type: 'text',
@@ -0,0 +1,55 @@
1
+ const MAX_MESH_ITEMS = 3;
2
+
3
+ class Mesh {
4
+ constructor(data) {
5
+ const children = [];
6
+ for (const [i, item] of data.items.entries()) {
7
+ if (i >= MAX_MESH_ITEMS) break;
8
+ children.push({
9
+ ...item,
10
+ type: 'containerDirective',
11
+ name: 'dsfr-doc-page-item-card',
12
+ });
13
+ }
14
+ children.sort((a, b) => {
15
+ const textA = a.text.toLowerCase();
16
+ const textB = b.text.toLowerCase();
17
+ return textA.localeCompare(textB);
18
+ });
19
+
20
+ this._node ={
21
+ type: 'htmlContainer',
22
+ tagName: 'div',
23
+ classes: ['dsfr-doc-mesh'],
24
+ children: [
25
+ {
26
+ type: 'heading',
27
+ classes: ['dsfr-doc-mesh__title', 'fr-h3'],
28
+ depth: data.depth ?? 5,
29
+ children: [
30
+ {
31
+ type: 'text',
32
+ value: data.title
33
+ }
34
+ ]
35
+ },
36
+ {
37
+ type: 'htmlContainer',
38
+ tagName: 'div',
39
+ classes: [
40
+ 'fr-grid-row',
41
+ 'fr-grid-row--gutters',
42
+ 'fr-mb-12v',
43
+ ],
44
+ children: children,
45
+ }
46
+ ]
47
+ }
48
+ }
49
+
50
+ get node () {
51
+ return this._node;
52
+ }
53
+ }
54
+
55
+ export { Mesh };
@@ -13,7 +13,8 @@ const DEFAULT_LIB_FILES = [
13
13
 
14
14
  const SCRIPTS_MAP = new Map([
15
15
  ['search', 'dsfr-doc-search'],
16
- ['home', 'dsfr-doc-home']
16
+ ['home', 'dsfr-doc-home'],
17
+ ['pattern', 'dsfr-doc-pattern']
17
18
  ]);
18
19
 
19
20
  class Scripts extends Renderable {
@@ -0,0 +1,3 @@
1
+ import './name/add-firstname.js';
2
+ import './name/remove-firstname.js';
3
+ import './name/toggle-disabled.js';
@@ -0,0 +1,38 @@
1
+ const getFieldsetElement = (node) => {
2
+ const parent = node.parentNode;
3
+ if (parent.className.indexOf('fr-fieldset__element') > -1) return parent;
4
+ return getFieldsetElement(parent);
5
+ };
6
+
7
+ window.addFirstname = (button, id) => {
8
+ button.firstnamesCount = (button.firstnamesCount + 1) || 1;
9
+
10
+ const firstname = getFieldsetElement(document.getElementById(id));
11
+ const reference = getFieldsetElement(button);
12
+
13
+ const copy = firstname.cloneNode(true);
14
+ let html = copy.innerHTML;
15
+ const regexp = new RegExp(id, 'g');
16
+ html = html.replace(regexp, `${id}-added-${button.firstnamesCount}`);
17
+ html = html.replace('name=&quot;given-name&quot;', `name=&quot;additional-name-${button.firstnamesCount}&quot;`);
18
+ html = html.replace('autocomplete=&quot;given-name&quot;', 'autocomplete=&quot;additional-name&quot;');
19
+ copy.innerHTML = html;
20
+
21
+ const wrapper = document.createElement('div');
22
+ wrapper.classList.add('fr-input-wrap', 'fr-input-wrap--action');
23
+
24
+ const removeButton = document.createElement('button');
25
+ const labelRemoveButton = '<%= getText("label.remove-firstname", "name") %>';
26
+ removeButton.classList.add('fr-btn', 'fr-btn--secondary', 'fr-icon-delete-line');
27
+ removeButton.innerHTML = labelRemoveButton;
28
+ removeButton.title = labelRemoveButton;
29
+
30
+ removeButton.setAttribute('onclick', 'removeFirstname(this.copy)');
31
+ removeButton.copy = copy;
32
+
33
+ wrapper.appendChild(copy.getElementsByTagName('input')[0]);
34
+ wrapper.appendChild(removeButton);
35
+ copy.getElementsByTagName('label')[0].after(wrapper);
36
+ reference.parentNode.insertBefore(copy, reference);
37
+ document.getElementById(`${id}-added-${button.firstnamesCount}`).focus();
38
+ };
@@ -0,0 +1,4 @@
1
+ window.removeFirstname = (copy) => {
2
+ copy.nextElementSibling.querySelector('input,button').focus();
3
+ copy.remove();
4
+ };
@@ -0,0 +1,8 @@
1
+ window.toggleDisabled = (checkbox, id) => {
2
+ const fieldset = document.getElementById(id);
3
+ if (checkbox.checked) fieldset.setAttribute('disabled', '');
4
+ else {
5
+ fieldset.removeAttribute('disabled');
6
+ fieldset.querySelector('input, button').focus();
7
+ }
8
+ };
@@ -12,6 +12,7 @@
12
12
  &__content {
13
13
  position: relative;
14
14
  padding-left: 2rem;
15
+ list-style-type: none;
15
16
  }
16
17
 
17
18
  &__pin {
@@ -0,0 +1,17 @@
1
+ .fr-tile {
2
+ &__img--dark {
3
+ display: none;
4
+ }
5
+ }
6
+
7
+ [data-fr-theme='dark'] {
8
+ .fr-tile {
9
+ &__img--dark {
10
+ display: block;
11
+ }
12
+
13
+ &__img--light {
14
+ display: none;
15
+ }
16
+ }
17
+ }
@@ -8,3 +8,4 @@
8
8
  @use 'dsfr-doc-pagination';
9
9
  @use 'dsfr-doc-edit';
10
10
  @use 'dsfr-doc-storybook-leaf';
11
+ @use 'fr-tile';
@@ -0,0 +1,5 @@
1
+ .dsfr-doc-anchor-heading {
2
+ &:not(:hover) {
3
+ --underline-img: none;
4
+ }
5
+ }
@@ -1,4 +1,5 @@
1
1
  @use 'global';
2
2
  @use 'font';
3
3
  @use 'dsfr-doc-code';
4
+ @use 'dsfr-doc-anchor-heading';
4
5
  @use 'icon';
@@ -1,10 +1,24 @@
1
1
  import { Renderable } from '../core/renderable.js';
2
2
  import { nodeFactory } from '../node/node-factory.js';
3
3
 
4
+ const PERMALINK_CONTENT_REGEX = /<!--\s*NO_PERMALINK_START\s*-->\s(?<content>[\s\S]*)\s<!--\s*NO_PERMALINK_END\s*-->/g;
5
+ const PREVENT_PERMALINKS_REGEX = /(?<permalinks><!--\s*NO_PERMALINK_START\s*-->\s(?:[\s\S]*)\s<!--\s*NO_PERMALINK_END\s*-->)/g;
6
+ const HEADING_REGEX = /<h(?<level>[2-6])(?<attrs>[^>]*)>(?<content>.*?)<\/h\1>/g;
7
+ const ATTRIBUTE_ID_REGEX = /\sid=["'](?<id>[^"']+)["']/;
8
+ const PERMALINK_CLASS = 'dsfr-doc-anchor-heading';
9
+
10
+ const injectPermaLink = (match, level, attrs, content) => {
11
+ const idMatch = attrs.match(ATTRIBUTE_ID_REGEX);
12
+ if (!idMatch || !idMatch.groups?.id) return match;
13
+ const link = `<a href="#${idMatch.groups.id}" class="${PERMALINK_CLASS}">${content}</a>`;
14
+ return `<h${level}${attrs}>${link}</h${level}>`;
15
+ };
16
+
4
17
  class Template extends Renderable {
5
- constructor (data) {
18
+ constructor (data, hasPermaLinks = false) {
6
19
  super(data);
7
20
  nodeFactory.populate(data.fragments);
21
+ this._hasPermaLinks = hasPermaLinks;
8
22
  this._content = nodeFactory.create({
9
23
  type: 'root',
10
24
  children: data.nodes
@@ -20,7 +34,19 @@ class Template extends Renderable {
20
34
  }
21
35
 
22
36
  async render () {
23
- return await this._content.render();
37
+ const html = await this._content.render();
38
+ if (!this._hasPermaLinks) return html;
39
+ return this.addPermaLinks(html);
40
+ }
41
+
42
+ addPermaLinks (html) {
43
+ return html.split(PREVENT_PERMALINKS_REGEX).map((part) => {
44
+ const match = PERMALINK_CONTENT_REGEX.exec(part);
45
+ if (match && match.groups && match.groups.content) {
46
+ return match.groups.content;
47
+ }
48
+ return part.replace(HEADING_REGEX, injectPermaLink);
49
+ }).join('');
24
50
  }
25
51
 
26
52
  get text () {
@@ -3,10 +3,11 @@ import { Sidemenu } from '../../component/components/sidemenu.js';
3
3
  import { Breadcrumb } from '../../component/components/breadcrumb.js';
4
4
  import { TOC } from '../../page/body/toc.js';
5
5
  import { Edit } from '../../page/body/edit.js';
6
+ import { Mesh } from '../../page/body/mesh.js';
6
7
 
7
8
  class EditorialTemplate extends Template {
8
9
  constructor (data) {
9
- super(data);
10
+ super(data, true);
10
11
 
11
12
  this._hasSidemenu = data?.resource?.navigation?.sidemenu !== undefined;
12
13
  if (this._hasSidemenu) this._sidemenu = new Sidemenu(data.resource.navigation.sidemenu, data.resource.badge);
@@ -18,11 +19,18 @@ class EditorialTemplate extends Template {
18
19
 
19
20
  structure (data) {
20
21
  this._hasEditUrl = data?.editUrl != null;
22
+ this._hasMesh = data?.mesh?.items?.length;
23
+
21
24
  if (this._hasEditUrl) {
22
25
  const edit = new Edit({...data.resource.edit, editUrl: data.editUrl, blankLabel: data.fragments.blank});
23
26
  data.nodes.push(edit.node);
24
27
  }
25
28
 
29
+ if (this._hasMesh) {
30
+ const mesh = new Mesh({...data.resource.mesh, ...data.mesh});
31
+ data.nodes.push(mesh.node);
32
+ }
33
+
26
34
  return super.structure(data);
27
35
  }
28
36