Skip to content

Commit dc4bb14

Browse files
authored
Merge pull request #2158 from strictdoc-project/stanislaw/stimulus_umd
Move to UMD version of Stimulus.js
2 parents daf53e5 + f37e3ef commit dc4bb14

32 files changed

+1026
-1032
lines changed

docs/strictdoc_25_design.sdoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@ The JavaScript framework used by StrictDoc is minimized to Turbo.js/Stimulus.js
152152
The Hotwire approach helps to reduce the differences between the static HTML produced by the StrictDoc command-line application and the StrictDoc web application. In both cases, the core content of StrictDoc is a statically generated website with documents. The web application extends the static HTML content with Turbo/Stimulus to turn it into a dynamic website.
153153

154154
Currently, the web server renders the HTML documents using the same generators that are used by the static HTML export, so the static HTML documentation and the web application interface look identical. The web interface adds the action buttons and other additional UI elements for editing the content.
155+
156+
To ensure that certain Stimulus-based interactive features work not only in the web application but also in the static HTML output, StrictDoc uses the UMD build of Stimulus instead of the default ES module version. This allows the browser to execute Stimulus controllers in environments where module loading is not available, preserving interactivity in static documentation pages.
155157
<<<
156158

157159
[/SECTION]
Lines changed: 124 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,127 @@
1-
const anchorIconSVG = `
2-
<svg class="svg_icon" width="16px" height="16px" viewBox="0 0 16 16">
3-
<g class="svg_icon_not_hover_visible" id="anchorIconSVG">
4-
<circle cx="8" cy="3" r="1"></circle>
5-
<line x1="8" y1="4" x2="8" y2="14.5"></line>
6-
<path d="M5.5,6 C5.5,6 7,6 10.5,6 C10.5,6 5.5,6 5.5,6 Z"/>
7-
<path d="M2,11 C3,10 3.5,9.5 3.5,9.5 C3.5,12 5,13 8,13 C11,13 12.5,12 12.5,9.5 C12.5,9.5 13,10 14,11"/>
8-
</g>
9-
<g class="svg_icon_hover_visible" id="copyIconSVG">
10-
<path d="M8,2 L12,2 C13,2 14,3 14,4 L14,8 C14,9 13,10 12,10 L8,10 C7,10 6,9 6,8 L6,4 C6,3 7,2 8,2 Z"/>
11-
<path d="M10,12 C10,13 9,14 8,14 L4,14 C3,14 2,13 2,12 L2,8 C2,7 3,6 4,6"/>
12-
</g>
1+
(() => {
2+
3+
const anchorIconSVG = `
4+
<svg class="svg_icon" width="16px" height="16px" viewBox="0 0 16 16">
5+
<g class="svg_icon_not_hover_visible" id="anchorIconSVG">
6+
<circle cx="8" cy="3" r="1"></circle>
7+
<line x1="8" y1="4" x2="8" y2="14.5"></line>
8+
<path d="M5.5,6 C5.5,6 7,6 10.5,6 C10.5,6 5.5,6 5.5,6 Z"/>
9+
<path d="M2,11 C3,10 3.5,9.5 3.5,9.5 C3.5,12 5,13 8,13 C11,13 12.5,12 12.5,9.5 C12.5,9.5 13,10 14,11"/>
10+
</g>
11+
<g class="svg_icon_hover_visible" id="copyIconSVG">
12+
<path d="M8,2 L12,2 C13,2 14,3 14,4 L14,8 C14,9 13,10 12,10 L8,10 C7,10 6,9 6,8 L6,4 C6,3 7,2 8,2 Z"/>
13+
<path d="M10,12 C10,13 9,14 8,14 L4,14 C3,14 2,13 2,12 L2,8 C2,7 3,6 4,6"/>
14+
</g>
15+
</svg>`;
16+
17+
const checkIconSVG = `
18+
<svg class="svg_icon" width="16px" height="16px" viewBox="0 0 16 16">
19+
<path d="M2.5,8.5 C2.5,8.5 3.5,9.5 6,12 C11,7 13.5,4.5 13.5,4.5"/>
1320
</svg>`;
1421

15-
const checkIconSVG = `
16-
<svg class="svg_icon" width="16px" height="16px" viewBox="0 0 16 16">
17-
<path d="M2.5,8.5 C2.5,8.5 3.5,9.5 6,12 C11,7 13.5,4.5 13.5,4.5"/>
18-
</svg>`;
19-
20-
21-
22-
Stimulus.register("anchor_controller", class extends Controller {
23-
initialize() {
24-
// this.element is the DOM element to which the controller is connected to.
25-
26-
// The content boundary is needed for correct positioning of anchors,
27-
// not depending on the display type of the parent elements, i.e. "left".
28-
// If triggered on a node,
29-
// the result is expected to be the same as on the content flow.
30-
const contentLeft = this.element.getBoundingClientRect().x;
31-
32-
// ** 1) if a section does not have [data-uid] parameter,
33-
// ** we don't want to show anchors;
34-
// ** 2) show anchors only for sections: has [node-role="section"]:
35-
const anchors = [...this.element.querySelectorAll('sdoc-anchor[data-uid][node-role="section"]')];
36-
37-
anchors.forEach(anchor => {
38-
39-
// We add data-controller="anchor_controller"
40-
// on both the entire content element and a node.
41-
// When editing a node, only the single node is reloaded,
42-
// so to avoid duplicate anchor processing
43-
// on nodes not affected by the edit,
44-
// we do the check:
45-
if (anchor.hasAttribute("visible")) { return };
46-
47-
// In the other case, start processing the anchor.
48-
49-
// This attribute triggers CSS:
50-
anchor.setAttribute("visible", "");
51-
52-
const anchorText = anchor.dataset.anchor || anchor.dataset.uid;
53-
54-
// Create anchor content block:
55-
const anchorBlock = document.createElement('div');
56-
anchorBlock.classList.add('anchor_block');
57-
anchorBlock.setAttribute('data-testid', 'anchor_hover_button');
58-
59-
// Create the button:
60-
const anchorButton = document.createElement('div');
61-
anchorButton.classList.add('anchor_button');
62-
anchorButton.title = "Click to copy";
63-
const anchorIcon = createIcon(anchorIconSVG);
64-
const checkIcon = createIcon(checkIconSVG);
65-
checkIcon.style.display = 'none';
66-
anchorButton.append(anchorIcon, checkIcon,);
67-
68-
// Append button:
69-
anchorBlock.append(anchorButton);
70-
71-
// Add anchor back links IF EXIST:
72-
let template = anchor.querySelector("template");
73-
if (template) {
74-
// Create anchor back links block:
75-
const anchorBackLinks = document.createElement('div');
76-
anchorBackLinks.classList.add('anchor_back_links');
77-
anchorBackLinks.append(template.content.cloneNode(true));
78-
79-
// Calculate back links:
80-
let linksNumber = anchorBackLinks.querySelectorAll('a').length;
81-
82-
const anchorBackLinksNumber = document.createElement('div');
83-
anchorBackLinksNumber.classList.add('anchor_back_links_number');
84-
anchorBackLinksNumber.setAttribute('data-testid', 'anchor_links_number');
85-
anchorBackLinksNumber.innerText = linksNumber;
22+
class AnchorController extends Stimulus.Controller {
23+
initialize() {
24+
// this.element is the DOM element to which the controller is connected to.
25+
26+
// The content boundary is needed for correct positioning of anchors,
27+
// not depending on the display type of the parent elements, i.e. "left".
28+
// If triggered on a node,
29+
// the result is expected to be the same as on the content flow.
30+
const contentLeft = this.element.getBoundingClientRect().x;
31+
32+
// ** 1) if a section does not have [data-uid] parameter,
33+
// ** we don't want to show anchors;
34+
// ** 2) show anchors only for sections: has [node-role="section"]:
35+
const anchors = [...this.element.querySelectorAll('sdoc-anchor[data-uid][node-role="section"]')];
36+
37+
anchors.forEach(anchor => {
38+
39+
// We add data-controller="anchor_controller"
40+
// on both the entire content element and a node.
41+
// When editing a node, only the single node is reloaded,
42+
// so to avoid duplicate anchor processing
43+
// on nodes not affected by the edit,
44+
// we do the check:
45+
if (anchor.hasAttribute("visible")) { return };
46+
47+
// In the other case, start processing the anchor.
48+
49+
// This attribute triggers CSS:
50+
anchor.setAttribute("visible", "");
51+
52+
const anchorText = anchor.dataset.anchor || anchor.dataset.uid;
53+
54+
// Create anchor content block:
55+
const anchorBlock = document.createElement('div');
56+
anchorBlock.classList.add('anchor_block');
57+
anchorBlock.setAttribute('data-testid', 'anchor_hover_button');
58+
59+
// Create the button:
60+
const anchorButton = document.createElement('div');
61+
anchorButton.classList.add('anchor_button');
62+
anchorButton.title = "Click to copy";
63+
const anchorIcon = createIcon(anchorIconSVG);
64+
const checkIcon = createIcon(checkIconSVG);
65+
checkIcon.style.display = 'none';
66+
anchorButton.append(anchorIcon, checkIcon,);
67+
68+
// Append button:
69+
anchorBlock.append(anchorButton);
70+
71+
// Add anchor back links IF EXIST:
72+
let template = anchor.querySelector("template");
73+
if (template) {
74+
// Create anchor back links block:
75+
const anchorBackLinks = document.createElement('div');
76+
anchorBackLinks.classList.add('anchor_back_links');
77+
anchorBackLinks.append(template.content.cloneNode(true));
78+
79+
// Calculate back links:
80+
let linksNumber = anchorBackLinks.querySelectorAll('a').length;
81+
82+
const anchorBackLinksNumber = document.createElement('div');
83+
anchorBackLinksNumber.classList.add('anchor_back_links_number');
84+
anchorBackLinksNumber.setAttribute('data-testid', 'anchor_links_number');
85+
anchorBackLinksNumber.innerText = linksNumber;
86+
87+
anchor.classList.add('anchor_has_links');
88+
89+
// Append links block:
90+
anchorBlock.append(anchorBackLinks, anchorBackLinksNumber);
91+
}
8692

87-
anchor.classList.add('anchor_has_links');
93+
// Append anchor content block:
94+
anchor.append(anchorBlock);
8895

89-
// Append links block:
90-
anchorBlock.append(anchorBackLinks, anchorBackLinksNumber);
91-
}
96+
// Now position the anchor, considering the added button:
97+
const translate = anchor.getBoundingClientRect().right - contentLeft;
98+
anchor.style.transform = `translate(-${translate}px,0)`;
9299

93-
// Append anchor content block:
94-
anchor.append(anchorBlock);
100+
// Add button content block
101+
const anchorButtonText = document.createElement('span');
102+
anchorButtonText.classList.add('anchor_button_text');
103+
anchorButtonText.innerHTML = anchorText;
104+
anchorButton.append(anchorButtonText);
105+
anchorButton.setAttribute('data-testid', 'section-anchor-button');
95106

96-
// Now position the anchor, considering the added button:
97-
const translate = anchor.getBoundingClientRect().right - contentLeft;
98-
anchor.style.transform = `translate(-${translate}px,0)`;
107+
// Add event listener
108+
anchorButton.addEventListener("click", function (event) {
109+
event.preventDefault();
110+
updateClipboard(anchorText, confirmMessage(anchorButton, anchorIcon, checkIcon))
111+
});
99112

100-
// Add button content block
101-
const anchorButtonText = document.createElement('span');
102-
anchorButtonText.classList.add('anchor_button_text');
103-
anchorButtonText.innerHTML = anchorText;
104-
anchorButton.append(anchorButtonText);
105-
anchorButton.setAttribute('data-testid', 'section-anchor-button');
106-
107-
// Add event listener
108-
anchorButton.addEventListener("click", function(event){
109-
event.preventDefault();
110-
updateClipboard(anchorText, confirmMessage(anchorButton, anchorIcon, checkIcon))
111-
});
113+
})
114+
}
115+
}
112116

113-
})
117+
Stimulus.application.register("anchor_controller", AnchorController);
114118

119+
function createIcon(iconSVG) {
120+
const icon = document.createElement('span');
121+
icon.style.lineHeight = .1;
122+
icon.innerHTML = iconSVG;
123+
return icon
115124
}
116-
});
117-
118-
function createIcon(iconSVG) {
119-
const icon = document.createElement('span');
120-
icon.style.lineHeight = .1;
121-
icon.innerHTML = iconSVG;
122-
return icon
123-
}
124125

125126
function updateClipboard(newClip, callback) {
126127
navigator.clipboard.writeText(newClip).then(() => {
@@ -157,17 +158,18 @@ function createIcon(iconSVG) {
157158
anchorIcon.style.display = 'none';
158159
checkIcon.style.display = 'inline';
159160
const fadeTimer = setInterval(() => {
160-
if (op <= 0.1){
161-
clearInterval(fadeTimer);
162-
element.remove();
163-
anchorIcon.style.display = 'inline';
164-
checkIcon.style.display = 'none';
165-
anchorButton.style.opacity = '';
166-
}
167-
element.style.opacity = op;
168-
op -= op * 0.1;
161+
if (op <= 0.1) {
162+
clearInterval(fadeTimer);
163+
element.remove();
164+
anchorIcon.style.display = 'inline';
165+
checkIcon.style.display = 'none';
166+
anchorButton.style.opacity = '';
167+
}
168+
element.style.opacity = op;
169+
op -= op * 0.1;
169170
}, 30);
170171

171172
// return element;
172173
anchorButton.append(element);
173174
}
175+
})();

0 commit comments

Comments
 (0)