@gouvfr/dsfr-roller 1.0.14 → 1.0.15
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/summary.js +13 -0
- package/src/node/generic/blockquote-node.js +1 -1
- package/src/node/generic/heading-node.js +10 -6
- package/src/node/node-root.js +3 -4
- package/src/node/node.js +12 -7
- package/src/page/body/toc.js +96 -0
- package/src/page/page.js +1 -0
- package/src/template/template.js +4 -0
- package/src/template/templates/editorial-template.js +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gouvfr/dsfr-roller",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
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",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
],
|
|
57
57
|
"main": "./index.js",
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@gouvfr/dsfr-forge": "=1.0.
|
|
59
|
+
"@gouvfr/dsfr-forge": "=1.0.15",
|
|
60
60
|
"@gouvfr/dsfr-publisher": "npm:@gouvfr/dsfr@1.13.1",
|
|
61
61
|
"deepmerge": "^4.3.1",
|
|
62
62
|
"ejs": "^3.1.10",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Component } from '../component.js';
|
|
2
|
+
|
|
3
|
+
class Summary extends Component {
|
|
4
|
+
constructor (data) {
|
|
5
|
+
super(data, 'summary');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
get ejsPath () {
|
|
9
|
+
return 'src/dsfr/component/summary/template/ejs/summary.ejs';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { Summary };
|
|
@@ -10,8 +10,8 @@ const LEVELS = new Map([
|
|
|
10
10
|
|
|
11
11
|
class BlockquoteNode extends Node {
|
|
12
12
|
constructor (data) {
|
|
13
|
-
if (data?.children?.[0]?.type === 'heading') data.children[0].attributes = { class: 'fr-callout__title' };
|
|
14
13
|
super(data, 'div');
|
|
14
|
+
if (this.children[0]?.type === 'heading') this.children[0].attributes.addClass('fr-callout__title');
|
|
15
15
|
this.attributes.addClass('fr-callout fr-mb-6v');
|
|
16
16
|
|
|
17
17
|
if (data?.level && LEVELS.has(data.level)) {
|
|
@@ -7,10 +7,14 @@ class HeadingNode extends Node {
|
|
|
7
7
|
this._depth = data.depth;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
get depth () {
|
|
11
|
+
return this._depth;
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
adjust (map) {
|
|
11
|
-
if (map.has(this.
|
|
12
|
-
|
|
13
|
-
this.tagName = `h${
|
|
15
|
+
if (map.has(this._depth)) {
|
|
16
|
+
const depth = map.get(this._depth);
|
|
17
|
+
this.tagName = `h${depth}`;
|
|
14
18
|
|
|
15
19
|
switch (true) {
|
|
16
20
|
case this._data?.data?.isSemantic:
|
|
@@ -18,7 +22,7 @@ class HeadingNode extends Node {
|
|
|
18
22
|
break;
|
|
19
23
|
|
|
20
24
|
default:
|
|
21
|
-
this.attributes.addClass(`fr-h${this.
|
|
25
|
+
this.attributes.addClass(`fr-h${this._depth}`);
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
28
|
}
|
|
@@ -28,7 +32,7 @@ HeadingNode.TYPE = 'heading';
|
|
|
28
32
|
|
|
29
33
|
const adjustHeading = (nodes) => {
|
|
30
34
|
let isPromoted = false;
|
|
31
|
-
const headings = nodes.map(node => node.
|
|
35
|
+
const headings = nodes.map(node => node.depth);
|
|
32
36
|
|
|
33
37
|
if (headings.indexOf(1) === -1) {
|
|
34
38
|
if (headings.filter(heading => heading === 2).length === 1) {
|
|
@@ -53,4 +57,4 @@ const adjustHeading = (nodes) => {
|
|
|
53
57
|
nodes.forEach(node => node.adjust(map));
|
|
54
58
|
};
|
|
55
59
|
|
|
56
|
-
export { HeadingNode, adjustHeading};
|
|
60
|
+
export { HeadingNode, adjustHeading };
|
package/src/node/node-root.js
CHANGED
|
@@ -11,12 +11,11 @@ class NodeRoot extends Node {
|
|
|
11
11
|
constructor (data) {
|
|
12
12
|
super(data);
|
|
13
13
|
this._nodes = gather(this);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async render () {
|
|
17
14
|
adjustHeading(this._nodes.filter(node => node.type === 'heading'));
|
|
15
|
+
}
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
get nodes () {
|
|
18
|
+
return this._nodes;
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
21
|
|
package/src/node/node.js
CHANGED
|
@@ -17,6 +17,11 @@ class Node extends Renderable {
|
|
|
17
17
|
if (typeof this.data?.classes === 'object' && Array.isArray(this.data.classes)) {
|
|
18
18
|
this.attributes.setClasses(this.data.classes);
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
if (this.hasNormalizedId) {
|
|
22
|
+
this._normalizedId = normalize(this.text);
|
|
23
|
+
this.attributes.setAttribute('id', this._normalizedId);
|
|
24
|
+
}
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
get children () {
|
|
@@ -43,6 +48,10 @@ class Node extends Renderable {
|
|
|
43
48
|
return this._hasNormalizedId;
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
get normalizedId () {
|
|
52
|
+
return this._normalizedId;
|
|
53
|
+
}
|
|
54
|
+
|
|
46
55
|
get attributes () {
|
|
47
56
|
if (!this._attributes) {
|
|
48
57
|
this._attributes = new TagAttributes();
|
|
@@ -76,15 +85,11 @@ class Node extends Renderable {
|
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
async render () {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
if (!this.tagName) return html;
|
|
88
|
+
const innerHtml = await this.renderChildren();
|
|
82
89
|
|
|
83
|
-
if (this.
|
|
84
|
-
this.attributes.setAttribute('id', normalize(this.text));
|
|
85
|
-
}
|
|
90
|
+
if (!this.tagName) return innerHtml;
|
|
86
91
|
|
|
87
|
-
return this.isSelfClosing ? `<${this.tagName}${this.renderAttributes()}/>` : `<${this.tagName}${this.renderAttributes()}>${
|
|
92
|
+
return this.isSelfClosing ? `<${this.tagName}${this.renderAttributes()}/>` : `<${this.tagName}${this.renderAttributes()}>${innerHtml}</${this.tagName}>`;
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
get text () {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Renderable } from '../../core/renderable.js';
|
|
2
|
+
import { Summary } from '../../component/components/summary.js';
|
|
3
|
+
|
|
4
|
+
const filterHeadings = (list, depths = [2]) => {
|
|
5
|
+
if (!list || list.length === 0) return undefined;
|
|
6
|
+
let heading = list[0];
|
|
7
|
+
if (!depths.includes(heading.depth)) return undefined;
|
|
8
|
+
for (heading of list) {
|
|
9
|
+
if (Array.isArray(heading.list)) {
|
|
10
|
+
heading.list = filterHeadings(heading.list, depths);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return list;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class TOC extends Renderable {
|
|
17
|
+
constructor (data, nodes) {
|
|
18
|
+
super(data);
|
|
19
|
+
this._hasToc = data.toc !== undefined;
|
|
20
|
+
this._nodes = nodes;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async render () {
|
|
24
|
+
if (!this._hasToc) {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const headings = this._nodes.filter(node => node.type === 'heading').map (heading => {
|
|
29
|
+
return {
|
|
30
|
+
depth: heading.depth,
|
|
31
|
+
href: `#${heading.normalizedId}`,
|
|
32
|
+
label: heading.text,
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
let depths = headings.map(heading => heading.depth).filter((depth, index, array) => array.indexOf(depth) === index).sort((a, b) => a - b);
|
|
37
|
+
|
|
38
|
+
let depth = depths[0];
|
|
39
|
+
const lists = [];
|
|
40
|
+
let mainList = [];
|
|
41
|
+
let list = mainList;
|
|
42
|
+
let previous;
|
|
43
|
+
|
|
44
|
+
for (const heading of headings) {
|
|
45
|
+
switch (true) {
|
|
46
|
+
case heading.depth === depth:
|
|
47
|
+
list.push(heading);
|
|
48
|
+
break;
|
|
49
|
+
|
|
50
|
+
case heading.depth < depth:
|
|
51
|
+
while (lists.length > 0 && list[0].depth > heading.depth) {
|
|
52
|
+
list = lists.pop();
|
|
53
|
+
}
|
|
54
|
+
if (list[0].depth === heading.depth) {
|
|
55
|
+
list.push(heading);
|
|
56
|
+
depth = heading.depth;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
list = [heading];
|
|
60
|
+
lists.push(list);
|
|
61
|
+
if (previous) previous.list = list;
|
|
62
|
+
depth = heading.depth;
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
|
|
66
|
+
case heading.depth > depth:
|
|
67
|
+
lists.push(list);
|
|
68
|
+
list = [heading];
|
|
69
|
+
if (previous) previous.list = list;
|
|
70
|
+
depth = heading.depth;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
previous = heading;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
while (mainList?.length === 1) mainList = mainList[0].list;
|
|
78
|
+
|
|
79
|
+
if (!mainList || mainList?.length === 0) return '';
|
|
80
|
+
|
|
81
|
+
switch (typeof this._data.toc) {
|
|
82
|
+
case 'string':
|
|
83
|
+
depths = this._data.toc.split(',').map(depth => parseInt(depth.trim(), 10)).filter(depth => !isNaN(depth));
|
|
84
|
+
mainList = filterHeadings(mainList, depths);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const summary = new Summary({ list: mainList });
|
|
89
|
+
|
|
90
|
+
return `<div class="fr-mb-6v" >
|
|
91
|
+
${await summary.render()}
|
|
92
|
+
</div>`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { TOC };
|
package/src/page/page.js
CHANGED
package/src/template/template.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Template } from '../template.js';
|
|
2
2
|
import { Sidemenu } from '../../component/components/sidemenu.js';
|
|
3
3
|
import { Breadcrumb } from '../../component/components/breadcrumb.js';
|
|
4
|
+
import { TOC } from '../../page/body/toc.js';
|
|
4
5
|
|
|
5
6
|
class EditorialTemplate extends Template {
|
|
6
7
|
constructor (data) {
|
|
@@ -11,6 +12,8 @@ class EditorialTemplate extends Template {
|
|
|
11
12
|
if (this._hasSidemenu) this._sidemenu = new Sidemenu(data.resource.navigation.sidemenu);
|
|
12
13
|
|
|
13
14
|
this._breadcrumb = new Breadcrumb(data.breadcrumb);
|
|
15
|
+
|
|
16
|
+
this._toc = new TOC(data, this.content.nodes);
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
async render () {
|
|
@@ -26,6 +29,7 @@ class EditorialTemplate extends Template {
|
|
|
26
29
|
cols.push(`
|
|
27
30
|
<div class="fr-col-12 fr-col-md-8">
|
|
28
31
|
${await this._breadcrumb.render()}
|
|
32
|
+
${await this._toc.render()}
|
|
29
33
|
${await super.render()}
|
|
30
34
|
</div>
|
|
31
35
|
`);
|