@gouvfr/dsfr-roller 1.0.70 → 1.0.72

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gouvfr/dsfr-roller",
3
- "version": "1.0.70",
3
+ "version": "1.0.72",
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,8 +56,8 @@
56
56
  ],
57
57
  "main": "./index.js",
58
58
  "dependencies": {
59
- "@gouvfr/dsfr-forge": "=1.0.70",
60
- "@gouvfr/dsfr-publisher": "npm:@gouvfr/dsfr@1.14.2",
59
+ "@gouvfr/dsfr-forge": "=1.0.72",
60
+ "@gouvfr/dsfr-publisher": "npm:@gouvfr/dsfr@1.14.3",
61
61
  "deepmerge": "^4.3.1",
62
62
  "ejs": "^3.1.10",
63
63
  "highlight.js": "^11.10.0",
@@ -26,7 +26,7 @@ class CodeNode extends Node {
26
26
  return `
27
27
  <div class="code-snippet fr-mb-6v">
28
28
  <pre><code${this.renderAttributes()}>${convertHTMLEntities(this.value)}</code></pre>
29
- <button type="button" class="code-snippet--copy fr-btn fr-btn--sm fr-btn--tertiary" data-label-copied="${this.data?.fragments?.button?.copied}">${this.data?.fragments?.button?.copy}</button>
29
+ <button type="button" class="code-snippet--copy fr-btn fr-btn--sm fr-btn--tertiary" data-label-copied="${this.data?.fragments?.button?.['text-copied']}">${this.data?.fragments?.button?.copy}</button>
30
30
  </div>
31
31
  `;
32
32
  }
@@ -0,0 +1,79 @@
1
+ import { Element } from '../core/element.js'
2
+
3
+ class CopyLink extends Element {
4
+ init() {
5
+ this._copiedLabel = this.button.getAttribute('data-label-copied');
6
+ this._isCopied = false;
7
+ this._heading = this.button.parentElement;
8
+ this.listenClick(this.button);
9
+ }
10
+
11
+ get button () {
12
+ return this.element;
13
+ }
14
+
15
+ get anchor () {
16
+ return this._heading && this._heading.id ? `#${this._heading.id}` : '';
17
+ }
18
+
19
+ get url () {
20
+ return window.location.origin + window.location.pathname + window.location.search;
21
+ }
22
+
23
+ get anchoredUrl () {
24
+ return this.url + this.anchor;
25
+ }
26
+
27
+ get isCopied () {
28
+ return this._isCopied;
29
+ }
30
+
31
+ set isCopied (value) {
32
+ this._isCopied = value;
33
+ if (value && this._tooltip) {
34
+ this._tooltip.innerText = this._copiedLabel;
35
+ this.button.setAttribute('aria-label', this._copiedLabel);
36
+ }
37
+ }
38
+
39
+ createTooltip() {
40
+ const tooltip = document.createElement("span");
41
+ tooltip.id = `${this.button.id}-tooltip`;
42
+ tooltip.className = 'fr-tooltip fr-placement';
43
+ tooltip.setAttribute('aria-hidden', 'true');
44
+ this.button.setAttribute('aria-describedby', tooltip.id);
45
+ this._heading.insertAdjacentElement('beforeend', tooltip);
46
+ return tooltip;
47
+ }
48
+
49
+ removeTooltip() {
50
+ if (this._tooltip) {
51
+ this._tooltip.remove();
52
+ this._tooltip = null;
53
+ }
54
+ }
55
+
56
+ handleClick () {
57
+ navigator.clipboard.writeText(this.anchoredUrl).then(() => {
58
+ if (this._tooltip) return;
59
+ this._tooltip = this.createTooltip();
60
+ this.isCopied = true;
61
+
62
+ setTimeout(() => {
63
+ if (typeof window.dsfr === 'function') {
64
+ window.dsfr(this._tooltip).tooltip.show();
65
+ window.dsfr(this._tooltip).tooltip.isEnabled = false;
66
+ setTimeout(this._handleTimeout.bind(this), 1500);
67
+ }
68
+ }, 10);
69
+ });
70
+ }
71
+
72
+ _handleTimeout () {
73
+ window.dsfr(this._tooltip).tooltip.hide();
74
+ this.isCopied = false;
75
+ this.removeTooltip();
76
+ }
77
+ }
78
+
79
+ export default CopyLink;
@@ -1,12 +1,14 @@
1
1
  import './minisearch/index.js';
2
2
  import { instantiateElements } from './core/element.js'
3
3
  import CopySnippet from './elements/copy-snippet.js';
4
+ import CopyLink from './elements/copy-link.js';
4
5
  import Storybook from './elements/storybook.js';
5
6
  import { SearchBar } from './elements/search-bar/index.js';
6
7
  import { ConsentManagementPlatform } from './cmp/index.js';
7
8
 
8
9
  window.onload = async () => {
9
10
  await instantiateElements('.code-snippet--copy', CopySnippet);
11
+ await instantiateElements('.dsfr-doc-anchor-heading__button', CopyLink);
10
12
  await instantiateElements('.dsfr-doc-storybook-leaf iframe', Storybook);
11
13
  await instantiateElements('#search', SearchBar);
12
14
  };
@@ -1,5 +1,3 @@
1
- .dsfr-doc-anchor-heading {
2
- &:not(:hover) {
3
- --underline-img: none;
4
- }
1
+ .dsfr-doc-anchor-heading__button {
2
+ margin-left: 0.25rem;
5
3
  }
@@ -5,18 +5,23 @@ const PERMALINK_CONTENT_REGEX = /<!--\s*NO_PERMALINK_START\s*-->\s(?<content>[\s
5
5
  const PREVENT_PERMALINKS_REGEX = /(?<permalinks><!--\s*NO_PERMALINK_START\s*-->\s(?:[\s\S]*)\s<!--\s*NO_PERMALINK_END\s*-->)/g;
6
6
  const HEADING_REGEX = /<h(?<level>[2-6])(?<attrs>[^>]*)>(?<content>.*?)<\/h\1>/g;
7
7
  const ATTRIBUTE_ID_REGEX = /\sid=["'](?<id>[^"']+)["']/;
8
- const PERMALINK_CLASS = 'dsfr-doc-anchor-heading';
8
+ const HEADING_BUTTON_CLASS = 'fr-btn--tooltip fr-btn fr-btn--sm fr-icon-link fr-btn--tertiary-no-outline dsfr-doc-anchor-heading__button';
9
9
 
10
- const injectPermaLink = (match, level, attrs, content) => {
10
+ const injectPermaLink = (match, level, attrs, content, fragments) => {
11
11
  const idMatch = attrs.match(ATTRIBUTE_ID_REGEX);
12
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}>`;
13
+ const copied = fragments?.button?.['link-copied'];
14
+ const copy = fragments?.button?.['copy-link'];
15
+ const buttonId = `${idMatch.groups.id}-anchor`;
16
+ const anchorButton = `<button type="button" id="${buttonId}" title="${copy}" data-label-copied="${copied}" class="${HEADING_BUTTON_CLASS}"></button>`;
17
+ return `<h${level}${attrs}>${content}${anchorButton}</h${level}>`;
18
+
15
19
  };
16
20
 
17
21
  class Template extends Renderable {
18
22
  constructor (data, hasPermaLinks = false) {
19
23
  super(data);
24
+ this._fragments = data.fragments;
20
25
  nodeFactory.populate(data.fragments);
21
26
  this._hasPermaLinks = hasPermaLinks;
22
27
  this._content = nodeFactory.create({
@@ -45,7 +50,10 @@ class Template extends Renderable {
45
50
  if (match && match.groups && match.groups.content) {
46
51
  return match.groups.content;
47
52
  }
48
- return part.replace(HEADING_REGEX, injectPermaLink);
53
+
54
+ return part.replace(HEADING_REGEX, (match, level, attrs, content) => {
55
+ return injectPermaLink(match, level, attrs, content, this._fragments);
56
+ });
49
57
  }).join('');
50
58
  }
51
59