rails_accessibility_testing 1.5.3 → 1.5.5
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.
- checksums.yaml +4 -4
- data/ARCHITECTURE.md +376 -1
- data/CHANGELOG.md +63 -1
- data/GUIDES/getting_started.md +40 -5
- data/GUIDES/system_specs_for_accessibility.md +12 -4
- data/README.md +52 -8
- data/docs_site/Gemfile.lock +89 -0
- data/docs_site/_config.yml +9 -0
- data/docs_site/_includes/header.html +1 -0
- data/docs_site/_layouts/default.html +754 -15
- data/docs_site/architecture.md +533 -0
- data/docs_site/index.md +2 -1
- data/exe/a11y_live_scanner +10 -39
- data/exe/a11y_static_scanner +333 -0
- data/lib/generators/rails_a11y/install/install_generator.rb +19 -30
- data/lib/generators/rails_a11y/install/templates/accessibility.yml.erb +39 -0
- data/lib/generators/rails_a11y/install/templates/all_pages_accessibility_spec.rb.erb +132 -45
- data/lib/rails_accessibility_testing/accessibility_helper.rb +131 -126
- data/lib/rails_accessibility_testing/checks/base_check.rb +14 -5
- data/lib/rails_accessibility_testing/checks/form_errors_check.rb +1 -1
- data/lib/rails_accessibility_testing/checks/form_labels_check.rb +6 -4
- data/lib/rails_accessibility_testing/checks/heading_check.rb +7 -15
- data/lib/rails_accessibility_testing/checks/image_alt_text_check.rb +1 -1
- data/lib/rails_accessibility_testing/checks/interactive_elements_check.rb +12 -8
- data/lib/rails_accessibility_testing/config/yaml_loader.rb +20 -0
- data/lib/rails_accessibility_testing/erb_extractor.rb +141 -0
- data/lib/rails_accessibility_testing/error_message_builder.rb +11 -6
- data/lib/rails_accessibility_testing/file_change_tracker.rb +95 -0
- data/lib/rails_accessibility_testing/line_number_finder.rb +61 -0
- data/lib/rails_accessibility_testing/rspec_integration.rb +74 -33
- data/lib/rails_accessibility_testing/shared_examples.rb +2 -0
- data/lib/rails_accessibility_testing/static_file_scanner.rb +80 -0
- data/lib/rails_accessibility_testing/static_page_adapter.rb +116 -0
- data/lib/rails_accessibility_testing/static_scanning.rb +61 -0
- data/lib/rails_accessibility_testing/version.rb +3 -1
- data/lib/rails_accessibility_testing/violation_converter.rb +80 -0
- data/lib/rails_accessibility_testing.rb +9 -1
- metadata +26 -2
|
@@ -1,32 +1,53 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
|
+
|
|
3
4
|
<head>
|
|
4
5
|
<meta charset="UTF-8">
|
|
5
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
7
|
<title>{% if page.title %}{{ page.title }} - {% endif %}{{ site.title }}</title>
|
|
7
8
|
<meta name="description" content="{{ site.description }}">
|
|
8
9
|
<style>
|
|
9
|
-
* {
|
|
10
|
+
* {
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
body {
|
|
11
17
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
12
18
|
line-height: 1.6;
|
|
13
19
|
color: #333;
|
|
14
20
|
background: #fff;
|
|
15
21
|
}
|
|
22
|
+
|
|
16
23
|
header {
|
|
17
24
|
background: #2c3e50;
|
|
18
25
|
color: white;
|
|
19
26
|
padding: 2rem 0;
|
|
20
27
|
text-align: center;
|
|
21
28
|
}
|
|
22
|
-
|
|
23
|
-
header h1
|
|
24
|
-
|
|
29
|
+
|
|
30
|
+
header h1 {
|
|
31
|
+
font-size: 2.5rem;
|
|
32
|
+
margin-bottom: 0.5rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
header h1 a {
|
|
36
|
+
color: white;
|
|
37
|
+
text-decoration: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
header p {
|
|
41
|
+
font-size: 1.2rem;
|
|
42
|
+
opacity: 0.9;
|
|
43
|
+
}
|
|
44
|
+
|
|
25
45
|
nav {
|
|
26
46
|
background: #34495e;
|
|
27
47
|
padding: 1rem 0;
|
|
28
48
|
text-align: center;
|
|
29
49
|
}
|
|
50
|
+
|
|
30
51
|
nav a {
|
|
31
52
|
color: white;
|
|
32
53
|
text-decoration: none;
|
|
@@ -34,22 +55,42 @@
|
|
|
34
55
|
padding: 0.5rem 1rem;
|
|
35
56
|
display: inline-block;
|
|
36
57
|
}
|
|
37
|
-
|
|
58
|
+
|
|
59
|
+
nav a:hover {
|
|
60
|
+
background: #2c3e50;
|
|
61
|
+
}
|
|
62
|
+
|
|
38
63
|
main {
|
|
39
64
|
max-width: 1200px;
|
|
40
65
|
margin: 2rem auto;
|
|
41
66
|
padding: 0 2rem;
|
|
42
67
|
}
|
|
68
|
+
|
|
43
69
|
section {
|
|
44
70
|
margin: 3rem 0;
|
|
45
71
|
}
|
|
46
|
-
|
|
72
|
+
|
|
73
|
+
h1,
|
|
74
|
+
h2 {
|
|
47
75
|
color: #2c3e50;
|
|
48
76
|
margin-bottom: 1rem;
|
|
49
77
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
78
|
+
|
|
79
|
+
h1 {
|
|
80
|
+
font-size: 2.5rem;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
h2 {
|
|
84
|
+
font-size: 2rem;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
h3 {
|
|
88
|
+
font-size: 1.5rem;
|
|
89
|
+
color: #34495e;
|
|
90
|
+
margin-top: 2rem;
|
|
91
|
+
margin-bottom: 1rem;
|
|
92
|
+
}
|
|
93
|
+
|
|
53
94
|
code {
|
|
54
95
|
background: #f4f4f4;
|
|
55
96
|
padding: 0.2rem 0.4rem;
|
|
@@ -57,6 +98,7 @@
|
|
|
57
98
|
font-family: 'Courier New', monospace;
|
|
58
99
|
font-size: 0.9em;
|
|
59
100
|
}
|
|
101
|
+
|
|
60
102
|
pre {
|
|
61
103
|
background: #f4f4f4;
|
|
62
104
|
padding: 1rem;
|
|
@@ -64,10 +106,12 @@
|
|
|
64
106
|
overflow-x: auto;
|
|
65
107
|
margin: 1rem 0;
|
|
66
108
|
}
|
|
109
|
+
|
|
67
110
|
pre code {
|
|
68
111
|
background: none;
|
|
69
112
|
padding: 0;
|
|
70
113
|
}
|
|
114
|
+
|
|
71
115
|
footer {
|
|
72
116
|
background: #2c3e50;
|
|
73
117
|
color: white;
|
|
@@ -75,6 +119,7 @@
|
|
|
75
119
|
padding: 2rem 0;
|
|
76
120
|
margin-top: 4rem;
|
|
77
121
|
}
|
|
122
|
+
|
|
78
123
|
.cta {
|
|
79
124
|
background: #3498db;
|
|
80
125
|
color: white;
|
|
@@ -84,47 +129,741 @@
|
|
|
84
129
|
display: inline-block;
|
|
85
130
|
margin: 1rem 0;
|
|
86
131
|
}
|
|
87
|
-
|
|
88
|
-
|
|
132
|
+
|
|
133
|
+
.cta:hover {
|
|
134
|
+
background: #2980b9;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
ul,
|
|
138
|
+
ol {
|
|
89
139
|
margin-left: 2rem;
|
|
90
140
|
margin-bottom: 1rem;
|
|
91
141
|
}
|
|
142
|
+
|
|
92
143
|
li {
|
|
93
144
|
margin-bottom: 0.5rem;
|
|
94
145
|
}
|
|
146
|
+
|
|
95
147
|
blockquote {
|
|
96
148
|
border-left: 4px solid #3498db;
|
|
97
149
|
padding-left: 1rem;
|
|
98
150
|
margin: 1rem 0;
|
|
99
151
|
color: #666;
|
|
100
152
|
}
|
|
153
|
+
|
|
101
154
|
table {
|
|
102
155
|
width: 100%;
|
|
103
156
|
border-collapse: collapse;
|
|
104
157
|
margin: 1rem 0;
|
|
105
158
|
}
|
|
106
|
-
|
|
159
|
+
|
|
160
|
+
th,
|
|
161
|
+
td {
|
|
107
162
|
border: 1px solid #ddd;
|
|
108
163
|
padding: 0.5rem;
|
|
109
164
|
text-align: left;
|
|
110
165
|
}
|
|
166
|
+
|
|
111
167
|
th {
|
|
112
168
|
background: #f4f4f4;
|
|
113
169
|
font-weight: 600;
|
|
114
170
|
}
|
|
171
|
+
|
|
172
|
+
/* Mermaid diagram container styles */
|
|
173
|
+
.mermaid-container {
|
|
174
|
+
position: relative;
|
|
175
|
+
margin: 2rem 0;
|
|
176
|
+
border: 1px solid #ddd;
|
|
177
|
+
border-radius: 8px;
|
|
178
|
+
padding: 1.5rem;
|
|
179
|
+
background: #fafafa;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.mermaid-container .mermaid {
|
|
183
|
+
margin: 0;
|
|
184
|
+
text-align: center;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Fullscreen button */
|
|
188
|
+
.fullscreen-btn {
|
|
189
|
+
position: absolute;
|
|
190
|
+
top: 10px;
|
|
191
|
+
right: 10px;
|
|
192
|
+
background: #3498db;
|
|
193
|
+
color: white;
|
|
194
|
+
border: none;
|
|
195
|
+
padding: 8px 12px;
|
|
196
|
+
border-radius: 5px;
|
|
197
|
+
cursor: pointer;
|
|
198
|
+
font-size: 14px;
|
|
199
|
+
display: flex;
|
|
200
|
+
align-items: center;
|
|
201
|
+
gap: 5px;
|
|
202
|
+
transition: background 0.3s, transform 0.2s;
|
|
203
|
+
z-index: 10;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.fullscreen-btn:hover {
|
|
207
|
+
background: #2980b9;
|
|
208
|
+
transform: scale(1.05);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.fullscreen-btn:focus {
|
|
212
|
+
outline: 2px solid #2c3e50;
|
|
213
|
+
outline-offset: 2px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.fullscreen-btn svg {
|
|
217
|
+
width: 16px;
|
|
218
|
+
height: 16px;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/* Fullscreen overlay */
|
|
222
|
+
.diagram-fullscreen-overlay {
|
|
223
|
+
display: none;
|
|
224
|
+
position: fixed;
|
|
225
|
+
top: 0;
|
|
226
|
+
left: 0;
|
|
227
|
+
width: 100%;
|
|
228
|
+
height: 100%;
|
|
229
|
+
background: rgba(0, 0, 0, 0.95);
|
|
230
|
+
z-index: 9999;
|
|
231
|
+
overflow: auto;
|
|
232
|
+
animation: fadeIn 0.3s ease;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.diagram-fullscreen-overlay.active {
|
|
236
|
+
display: flex;
|
|
237
|
+
align-items: center;
|
|
238
|
+
justify-content: center;
|
|
239
|
+
padding: 2rem;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.diagram-fullscreen-content {
|
|
243
|
+
background: white;
|
|
244
|
+
border-radius: 8px;
|
|
245
|
+
padding: 2rem;
|
|
246
|
+
width: 98vw;
|
|
247
|
+
height: 98vh;
|
|
248
|
+
overflow: auto;
|
|
249
|
+
position: relative;
|
|
250
|
+
animation: slideIn 0.3s ease;
|
|
251
|
+
display: flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
justify-content: center;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#diagram-zoom-wrapper {
|
|
257
|
+
display: flex;
|
|
258
|
+
align-items: center;
|
|
259
|
+
justify-content: center;
|
|
260
|
+
min-width: 100%;
|
|
261
|
+
min-height: 100%;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.diagram-fullscreen-content .mermaid {
|
|
265
|
+
margin: 0;
|
|
266
|
+
display: block;
|
|
267
|
+
width: auto;
|
|
268
|
+
height: auto;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.diagram-fullscreen-content .mermaid svg {
|
|
272
|
+
max-width: none !important;
|
|
273
|
+
max-height: none !important;
|
|
274
|
+
width: auto !important;
|
|
275
|
+
height: auto !important;
|
|
276
|
+
display: block;
|
|
277
|
+
margin: 0 auto;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* Close button */
|
|
281
|
+
.close-fullscreen-btn {
|
|
282
|
+
position: fixed;
|
|
283
|
+
top: 20px;
|
|
284
|
+
right: 20px;
|
|
285
|
+
background: #e74c3c;
|
|
286
|
+
color: white;
|
|
287
|
+
border: none;
|
|
288
|
+
padding: 10px 20px;
|
|
289
|
+
border-radius: 5px;
|
|
290
|
+
cursor: pointer;
|
|
291
|
+
font-size: 16px;
|
|
292
|
+
font-weight: bold;
|
|
293
|
+
z-index: 10000;
|
|
294
|
+
transition: background 0.3s, transform 0.2s;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.close-fullscreen-btn:hover {
|
|
298
|
+
background: #c0392b;
|
|
299
|
+
transform: scale(1.05);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.close-fullscreen-btn:focus {
|
|
303
|
+
outline: 2px solid white;
|
|
304
|
+
outline-offset: 2px;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* Zoom controls */
|
|
308
|
+
.zoom-controls {
|
|
309
|
+
position: fixed;
|
|
310
|
+
top: 20px;
|
|
311
|
+
left: 20px;
|
|
312
|
+
display: flex;
|
|
313
|
+
gap: 10px;
|
|
314
|
+
z-index: 10000;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.zoom-btn {
|
|
318
|
+
background: #3498db;
|
|
319
|
+
color: white;
|
|
320
|
+
border: none;
|
|
321
|
+
padding: 10px 15px;
|
|
322
|
+
border-radius: 5px;
|
|
323
|
+
cursor: pointer;
|
|
324
|
+
font-size: 16px;
|
|
325
|
+
font-weight: bold;
|
|
326
|
+
transition: background 0.3s, transform 0.2s;
|
|
327
|
+
display: flex;
|
|
328
|
+
align-items: center;
|
|
329
|
+
gap: 5px;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.zoom-btn:hover {
|
|
333
|
+
background: #2980b9;
|
|
334
|
+
transform: scale(1.05);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.zoom-btn:focus {
|
|
338
|
+
outline: 2px solid white;
|
|
339
|
+
outline-offset: 2px;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.zoom-btn:disabled {
|
|
343
|
+
background: #95a5a6;
|
|
344
|
+
cursor: not-allowed;
|
|
345
|
+
transform: none;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.zoom-level {
|
|
349
|
+
background: rgba(255, 255, 255, 0.9);
|
|
350
|
+
color: #2c3e50;
|
|
351
|
+
padding: 10px 15px;
|
|
352
|
+
border-radius: 5px;
|
|
353
|
+
font-size: 14px;
|
|
354
|
+
font-weight: bold;
|
|
355
|
+
display: flex;
|
|
356
|
+
align-items: center;
|
|
357
|
+
min-width: 80px;
|
|
358
|
+
justify-content: center;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* Animations */
|
|
362
|
+
@keyframes fadeIn {
|
|
363
|
+
from {
|
|
364
|
+
opacity: 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
to {
|
|
368
|
+
opacity: 1;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
@keyframes slideIn {
|
|
373
|
+
from {
|
|
374
|
+
opacity: 0;
|
|
375
|
+
transform: scale(0.9) translateY(-20px);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
to {
|
|
379
|
+
opacity: 1;
|
|
380
|
+
transform: scale(1) translateY(0);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/* Responsive adjustments */
|
|
385
|
+
@media (max-width: 768px) {
|
|
386
|
+
.diagram-fullscreen-content {
|
|
387
|
+
padding: 1rem;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.fullscreen-btn {
|
|
391
|
+
font-size: 12px;
|
|
392
|
+
padding: 6px 10px;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
115
395
|
</style>
|
|
116
396
|
</head>
|
|
397
|
+
|
|
117
398
|
<body>
|
|
118
399
|
{% include header.html %}
|
|
119
|
-
|
|
400
|
+
|
|
120
401
|
<main>
|
|
121
402
|
{{ content }}
|
|
122
403
|
</main>
|
|
123
|
-
|
|
404
|
+
|
|
124
405
|
<footer>
|
|
125
406
|
<p>© {{ 'now' | date: "%Y" }} Rails Accessibility Testing. MIT License.</p>
|
|
126
407
|
<p><a href="https://github.com/rayraycodes/rails-accessibility-testing" style="color: white;">GitHub</a></p>
|
|
127
408
|
</footer>
|
|
409
|
+
|
|
410
|
+
<!-- Mermaid.js for diagram rendering - Load at end of body for better reliability -->
|
|
411
|
+
<script type="module">
|
|
412
|
+
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
|
413
|
+
|
|
414
|
+
// Initialize Mermaid
|
|
415
|
+
mermaid.initialize({
|
|
416
|
+
startOnLoad: true,
|
|
417
|
+
theme: 'default',
|
|
418
|
+
securityLevel: 'loose',
|
|
419
|
+
flowchart: { useMaxWidth: true },
|
|
420
|
+
sequence: { useMaxWidth: true },
|
|
421
|
+
gantt: { useMaxWidth: true }
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Function to create fullscreen overlay
|
|
425
|
+
function createFullscreenOverlay() {
|
|
426
|
+
if (document.getElementById('diagram-fullscreen-overlay')) {
|
|
427
|
+
return; // Already exists
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const overlay = document.createElement('div');
|
|
431
|
+
overlay.id = 'diagram-fullscreen-overlay';
|
|
432
|
+
overlay.className = 'diagram-fullscreen-overlay';
|
|
433
|
+
overlay.setAttribute('role', 'dialog');
|
|
434
|
+
overlay.setAttribute('aria-modal', 'true');
|
|
435
|
+
overlay.setAttribute('aria-label', 'Fullscreen diagram viewer');
|
|
436
|
+
|
|
437
|
+
// Zoom controls
|
|
438
|
+
const zoomControls = document.createElement('div');
|
|
439
|
+
zoomControls.className = 'zoom-controls';
|
|
440
|
+
|
|
441
|
+
const zoomOutBtn = document.createElement('button');
|
|
442
|
+
zoomOutBtn.className = 'zoom-btn';
|
|
443
|
+
zoomOutBtn.textContent = '−';
|
|
444
|
+
zoomOutBtn.setAttribute('aria-label', 'Zoom out');
|
|
445
|
+
zoomOutBtn.onclick = zoomOut;
|
|
446
|
+
|
|
447
|
+
const zoomLevel = document.createElement('div');
|
|
448
|
+
zoomLevel.className = 'zoom-level';
|
|
449
|
+
zoomLevel.id = 'zoom-level-display';
|
|
450
|
+
zoomLevel.textContent = '100%';
|
|
451
|
+
|
|
452
|
+
const zoomInBtn = document.createElement('button');
|
|
453
|
+
zoomInBtn.className = 'zoom-btn';
|
|
454
|
+
zoomInBtn.textContent = '+';
|
|
455
|
+
zoomInBtn.setAttribute('aria-label', 'Zoom in');
|
|
456
|
+
zoomInBtn.onclick = zoomIn;
|
|
457
|
+
|
|
458
|
+
const resetBtn = document.createElement('button');
|
|
459
|
+
resetBtn.className = 'zoom-btn';
|
|
460
|
+
resetBtn.textContent = 'Reset';
|
|
461
|
+
resetBtn.setAttribute('aria-label', 'Reset zoom to 100%');
|
|
462
|
+
resetBtn.onclick = resetZoom;
|
|
463
|
+
|
|
464
|
+
zoomControls.appendChild(zoomOutBtn);
|
|
465
|
+
zoomControls.appendChild(zoomLevel);
|
|
466
|
+
zoomControls.appendChild(zoomInBtn);
|
|
467
|
+
zoomControls.appendChild(resetBtn);
|
|
468
|
+
|
|
469
|
+
const closeBtn = document.createElement('button');
|
|
470
|
+
closeBtn.className = 'close-fullscreen-btn';
|
|
471
|
+
closeBtn.textContent = '✕ Close';
|
|
472
|
+
closeBtn.setAttribute('aria-label', 'Close fullscreen diagram');
|
|
473
|
+
closeBtn.onclick = closeFullscreen;
|
|
474
|
+
|
|
475
|
+
const content = document.createElement('div');
|
|
476
|
+
content.className = 'diagram-fullscreen-content';
|
|
477
|
+
|
|
478
|
+
overlay.appendChild(zoomControls);
|
|
479
|
+
overlay.appendChild(closeBtn);
|
|
480
|
+
overlay.appendChild(content);
|
|
481
|
+
document.body.appendChild(overlay);
|
|
482
|
+
|
|
483
|
+
// Close on overlay click (but not content click)
|
|
484
|
+
overlay.addEventListener('click', (e) => {
|
|
485
|
+
if (e.target === overlay) {
|
|
486
|
+
closeFullscreen();
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
// Close on Escape key
|
|
491
|
+
document.addEventListener('keydown', (e) => {
|
|
492
|
+
if (e.key === 'Escape' && overlay.classList.contains('active')) {
|
|
493
|
+
closeFullscreen();
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
// Keyboard shortcuts for zoom
|
|
498
|
+
document.addEventListener('keydown', (e) => {
|
|
499
|
+
if (overlay.classList.contains('active')) {
|
|
500
|
+
if (e.key === '+' || e.key === '=') {
|
|
501
|
+
e.preventDefault();
|
|
502
|
+
zoomIn();
|
|
503
|
+
} else if (e.key === '-') {
|
|
504
|
+
e.preventDefault();
|
|
505
|
+
zoomOut();
|
|
506
|
+
} else if (e.key === '0') {
|
|
507
|
+
e.preventDefault();
|
|
508
|
+
resetZoom();
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Drag/pan functionality
|
|
515
|
+
let isDragging = false;
|
|
516
|
+
let startX = 0;
|
|
517
|
+
let startY = 0;
|
|
518
|
+
let currentX = 0;
|
|
519
|
+
let currentY = 0;
|
|
520
|
+
|
|
521
|
+
function initDrag(wrapper) {
|
|
522
|
+
wrapper.style.cursor = 'grab';
|
|
523
|
+
wrapper.style.userSelect = 'none';
|
|
524
|
+
|
|
525
|
+
wrapper.addEventListener('mousedown', (e) => {
|
|
526
|
+
if (e.button === 0) { // Left mouse button only
|
|
527
|
+
isDragging = true;
|
|
528
|
+
startX = e.clientX - currentX;
|
|
529
|
+
startY = e.clientY - currentY;
|
|
530
|
+
wrapper.style.cursor = 'grabbing';
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
document.addEventListener('mousemove', (e) => {
|
|
535
|
+
if (isDragging) {
|
|
536
|
+
e.preventDefault();
|
|
537
|
+
currentX = e.clientX - startX;
|
|
538
|
+
currentY = e.clientY - startY;
|
|
539
|
+
wrapper.style.transform = `scale(${currentZoom / 100}) translate(${currentX}px, ${currentY}px)`;
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
document.addEventListener('mouseup', () => {
|
|
544
|
+
if (isDragging) {
|
|
545
|
+
isDragging = false;
|
|
546
|
+
wrapper.style.cursor = 'grab';
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// Touch support for mobile
|
|
551
|
+
let touchStartX = 0;
|
|
552
|
+
let touchStartY = 0;
|
|
553
|
+
|
|
554
|
+
wrapper.addEventListener('touchstart', (e) => {
|
|
555
|
+
if (e.touches.length === 1) {
|
|
556
|
+
isDragging = true;
|
|
557
|
+
touchStartX = e.touches[0].clientX - currentX;
|
|
558
|
+
touchStartY = e.touches[0].clientY - currentY;
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
document.addEventListener('touchmove', (e) => {
|
|
563
|
+
if (isDragging && e.touches.length === 1) {
|
|
564
|
+
e.preventDefault();
|
|
565
|
+
currentX = e.touches[0].clientX - touchStartX;
|
|
566
|
+
currentY = e.touches[0].clientY - touchStartY;
|
|
567
|
+
wrapper.style.transform = `scale(${currentZoom / 100}) translate(${currentX}px, ${currentY}px)`;
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
document.addEventListener('touchend', () => {
|
|
572
|
+
isDragging = false;
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Function to open diagram in fullscreen
|
|
577
|
+
function openFullscreen(diagramElement) {
|
|
578
|
+
const overlay = document.getElementById('diagram-fullscreen-overlay');
|
|
579
|
+
const content = overlay.querySelector('.diagram-fullscreen-content');
|
|
580
|
+
|
|
581
|
+
// Get the original Mermaid code from the source
|
|
582
|
+
let mermaidCode = null;
|
|
583
|
+
const originalPre = diagramElement.closest('pre');
|
|
584
|
+
if (originalPre) {
|
|
585
|
+
const codeBlock = originalPre.querySelector('code.language-mermaid');
|
|
586
|
+
if (codeBlock) {
|
|
587
|
+
mermaidCode = codeBlock.textContent || codeBlock.innerText;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// If we can't find the code, try to find it from all code blocks
|
|
592
|
+
if (!mermaidCode) {
|
|
593
|
+
const allCodeBlocks = document.querySelectorAll('code.language-mermaid');
|
|
594
|
+
for (const codeBlock of allCodeBlocks) {
|
|
595
|
+
const parentPre = codeBlock.closest('pre');
|
|
596
|
+
if (parentPre && parentPre.querySelector('.mermaid') === diagramElement) {
|
|
597
|
+
mermaidCode = codeBlock.textContent || codeBlock.innerText;
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
content.innerHTML = '';
|
|
604
|
+
|
|
605
|
+
// Create wrapper for zoom and drag functionality
|
|
606
|
+
const wrapper = document.createElement('div');
|
|
607
|
+
wrapper.id = 'diagram-zoom-wrapper';
|
|
608
|
+
wrapper.style.display = 'flex';
|
|
609
|
+
wrapper.style.alignItems = 'center';
|
|
610
|
+
wrapper.style.justifyContent = 'center';
|
|
611
|
+
wrapper.style.position = 'relative';
|
|
612
|
+
wrapper.style.width = 'fit-content';
|
|
613
|
+
wrapper.style.height = 'fit-content';
|
|
614
|
+
wrapper.style.minWidth = '100%';
|
|
615
|
+
wrapper.style.minHeight = '100%';
|
|
616
|
+
|
|
617
|
+
// If we have the Mermaid code, create a new div and let Mermaid render it
|
|
618
|
+
if (mermaidCode) {
|
|
619
|
+
const mermaidDiv = document.createElement('div');
|
|
620
|
+
mermaidDiv.className = 'mermaid';
|
|
621
|
+
mermaidDiv.textContent = mermaidCode;
|
|
622
|
+
mermaidDiv.style.display = 'block';
|
|
623
|
+
mermaidDiv.style.visibility = 'visible';
|
|
624
|
+
mermaidDiv.style.opacity = '1';
|
|
625
|
+
wrapper.appendChild(mermaidDiv);
|
|
626
|
+
|
|
627
|
+
// Re-render with Mermaid
|
|
628
|
+
mermaid.run({ nodes: [mermaidDiv] }).then(() => {
|
|
629
|
+
const svg = mermaidDiv.querySelector('svg');
|
|
630
|
+
if (svg) {
|
|
631
|
+
svg.style.display = 'block';
|
|
632
|
+
svg.style.visibility = 'visible';
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
} else {
|
|
636
|
+
// Fallback: clone the existing diagram
|
|
637
|
+
const clone = diagramElement.cloneNode(true);
|
|
638
|
+
clone.style.display = 'block';
|
|
639
|
+
clone.style.visibility = 'visible';
|
|
640
|
+
clone.style.opacity = '1';
|
|
641
|
+
wrapper.appendChild(clone);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
content.appendChild(wrapper);
|
|
645
|
+
|
|
646
|
+
// Ensure content can scroll to show entire diagram
|
|
647
|
+
content.style.overflow = 'auto';
|
|
648
|
+
content.style.display = 'flex';
|
|
649
|
+
content.style.alignItems = 'center';
|
|
650
|
+
content.style.justifyContent = 'center';
|
|
651
|
+
|
|
652
|
+
// Reset zoom and position
|
|
653
|
+
currentZoom = 100;
|
|
654
|
+
currentX = 0;
|
|
655
|
+
currentY = 0;
|
|
656
|
+
wrapper.style.transform = 'scale(1) translate(0px, 0px)';
|
|
657
|
+
wrapper.style.transformOrigin = 'center center';
|
|
658
|
+
updateZoomLevel(100);
|
|
659
|
+
|
|
660
|
+
// Initialize drag functionality
|
|
661
|
+
initDrag(wrapper);
|
|
662
|
+
|
|
663
|
+
// Show overlay
|
|
664
|
+
overlay.classList.add('active');
|
|
665
|
+
document.body.style.overflow = 'hidden';
|
|
666
|
+
|
|
667
|
+
// Ensure visibility and center after rendering
|
|
668
|
+
setTimeout(() => {
|
|
669
|
+
wrapper.style.display = 'flex';
|
|
670
|
+
wrapper.style.visibility = 'visible';
|
|
671
|
+
wrapper.style.opacity = '1';
|
|
672
|
+
|
|
673
|
+
const mermaidEl = wrapper.querySelector('.mermaid');
|
|
674
|
+
if (mermaidEl) {
|
|
675
|
+
mermaidEl.style.display = 'block';
|
|
676
|
+
mermaidEl.style.visibility = 'visible';
|
|
677
|
+
mermaidEl.style.opacity = '1';
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const svg = wrapper.querySelector('svg');
|
|
681
|
+
if (svg) {
|
|
682
|
+
svg.style.display = 'block';
|
|
683
|
+
svg.style.visibility = 'visible';
|
|
684
|
+
svg.style.opacity = '1';
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Center scroll position
|
|
688
|
+
setTimeout(() => {
|
|
689
|
+
if (content.scrollHeight > content.clientHeight) {
|
|
690
|
+
content.scrollTop = (content.scrollHeight - content.clientHeight) / 2;
|
|
691
|
+
}
|
|
692
|
+
if (content.scrollWidth > content.clientWidth) {
|
|
693
|
+
content.scrollLeft = (content.scrollWidth - content.clientWidth) / 2;
|
|
694
|
+
}
|
|
695
|
+
}, 200);
|
|
696
|
+
}, 300);
|
|
697
|
+
|
|
698
|
+
// Focus on close button for accessibility
|
|
699
|
+
const closeBtn = overlay.querySelector('.close-fullscreen-btn');
|
|
700
|
+
closeBtn.focus();
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Zoom functionality
|
|
704
|
+
let currentZoom = 100;
|
|
705
|
+
const minZoom = 25;
|
|
706
|
+
const maxZoom = 500;
|
|
707
|
+
const zoomStep = 25;
|
|
708
|
+
|
|
709
|
+
function zoomIn() {
|
|
710
|
+
if (currentZoom < maxZoom) {
|
|
711
|
+
currentZoom = Math.min(currentZoom + zoomStep, maxZoom);
|
|
712
|
+
applyZoom();
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function zoomOut() {
|
|
717
|
+
if (currentZoom > minZoom) {
|
|
718
|
+
currentZoom = Math.max(currentZoom - zoomStep, minZoom);
|
|
719
|
+
applyZoom();
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function resetZoom() {
|
|
724
|
+
currentZoom = 100;
|
|
725
|
+
currentX = 0;
|
|
726
|
+
currentY = 0;
|
|
727
|
+
const wrapper = document.getElementById('diagram-zoom-wrapper');
|
|
728
|
+
if (wrapper) {
|
|
729
|
+
wrapper.style.transform = 'scale(1) translate(0px, 0px)';
|
|
730
|
+
updateZoomLevel(100);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function applyZoom() {
|
|
735
|
+
const wrapper = document.getElementById('diagram-zoom-wrapper');
|
|
736
|
+
if (wrapper) {
|
|
737
|
+
// Preserve current position when zooming
|
|
738
|
+
wrapper.style.transform = `scale(${currentZoom / 100}) translate(${currentX}px, ${currentY}px)`;
|
|
739
|
+
updateZoomLevel(currentZoom);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function updateZoomLevel(level) {
|
|
744
|
+
const zoomLevelEl = document.getElementById('zoom-level-display');
|
|
745
|
+
if (zoomLevelEl) {
|
|
746
|
+
zoomLevelEl.textContent = `${level}%`;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Function to close fullscreen
|
|
751
|
+
function closeFullscreen() {
|
|
752
|
+
const overlay = document.getElementById('diagram-fullscreen-overlay');
|
|
753
|
+
overlay.classList.remove('active');
|
|
754
|
+
document.body.style.overflow = '';
|
|
755
|
+
// Reset zoom for next time
|
|
756
|
+
currentZoom = 100;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Make functions global for button onclick
|
|
760
|
+
window.closeFullscreen = closeFullscreen;
|
|
761
|
+
window.zoomIn = zoomIn;
|
|
762
|
+
window.zoomOut = zoomOut;
|
|
763
|
+
window.resetZoom = resetZoom;
|
|
764
|
+
|
|
765
|
+
// Function to wrap diagrams in containers with fullscreen buttons
|
|
766
|
+
function wrapDiagramsWithButtons() {
|
|
767
|
+
const diagrams = document.querySelectorAll('.mermaid');
|
|
768
|
+
|
|
769
|
+
diagrams.forEach((diagram, index) => {
|
|
770
|
+
// Skip if already wrapped
|
|
771
|
+
if (diagram.parentElement.classList.contains('mermaid-container')) {
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Create container
|
|
776
|
+
const container = document.createElement('div');
|
|
777
|
+
container.className = 'mermaid-container';
|
|
778
|
+
|
|
779
|
+
// Create fullscreen button
|
|
780
|
+
const btn = document.createElement('button');
|
|
781
|
+
btn.className = 'fullscreen-btn';
|
|
782
|
+
btn.setAttribute('aria-label', `View diagram ${index + 1} in fullscreen`);
|
|
783
|
+
btn.innerHTML = `
|
|
784
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
785
|
+
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path>
|
|
786
|
+
</svg>
|
|
787
|
+
<span>Fullscreen</span>
|
|
788
|
+
`;
|
|
789
|
+
|
|
790
|
+
btn.onclick = () => openFullscreen(diagram);
|
|
791
|
+
|
|
792
|
+
// Wrap diagram
|
|
793
|
+
diagram.parentNode.insertBefore(container, diagram);
|
|
794
|
+
container.appendChild(diagram);
|
|
795
|
+
container.appendChild(btn);
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Function to process Mermaid diagrams
|
|
800
|
+
function processMermaidDiagrams() {
|
|
801
|
+
// Find all code blocks with mermaid language
|
|
802
|
+
const mermaidBlocks = document.querySelectorAll('code.language-mermaid, pre code.language-mermaid');
|
|
803
|
+
|
|
804
|
+
if (mermaidBlocks.length > 0) {
|
|
805
|
+
console.log('Found', mermaidBlocks.length, 'Mermaid diagram blocks');
|
|
806
|
+
|
|
807
|
+
// Process each block
|
|
808
|
+
mermaidBlocks.forEach((block, index) => {
|
|
809
|
+
// Get the parent pre element if it exists
|
|
810
|
+
const preElement = block.parentElement;
|
|
811
|
+
if (preElement && preElement.tagName === 'PRE') {
|
|
812
|
+
// Get the mermaid code
|
|
813
|
+
const mermaidCode = block.textContent || block.innerText;
|
|
814
|
+
|
|
815
|
+
// Create a div for Mermaid to render into
|
|
816
|
+
const mermaidDiv = document.createElement('div');
|
|
817
|
+
mermaidDiv.className = 'mermaid';
|
|
818
|
+
mermaidDiv.textContent = mermaidCode;
|
|
819
|
+
|
|
820
|
+
// Replace the pre element with the mermaid div
|
|
821
|
+
preElement.parentNode.replaceChild(mermaidDiv, preElement);
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
// Run Mermaid to render all diagrams
|
|
826
|
+
mermaid.run().then(() => {
|
|
827
|
+
// After rendering, wrap diagrams with fullscreen buttons
|
|
828
|
+
createFullscreenOverlay();
|
|
829
|
+
wrapDiagramsWithButtons();
|
|
830
|
+
});
|
|
831
|
+
} else {
|
|
832
|
+
// Fallback: look for pre > code blocks and check if they contain mermaid syntax
|
|
833
|
+
const allCodeBlocks = document.querySelectorAll('pre code');
|
|
834
|
+
allCodeBlocks.forEach((codeBlock) => {
|
|
835
|
+
const code = codeBlock.textContent || codeBlock.innerText;
|
|
836
|
+
// Check if it looks like mermaid code (starts with graph, sequenceDiagram, etc.)
|
|
837
|
+
if (code.trim().match(/^(graph|sequenceDiagram|flowchart|classDiagram|stateDiagram|erDiagram|gantt|pie|gitgraph|journey)/)) {
|
|
838
|
+
const preElement = codeBlock.parentElement;
|
|
839
|
+
if (preElement && preElement.tagName === 'PRE') {
|
|
840
|
+
const mermaidDiv = document.createElement('div');
|
|
841
|
+
mermaidDiv.className = 'mermaid';
|
|
842
|
+
mermaidDiv.textContent = code;
|
|
843
|
+
preElement.parentNode.replaceChild(mermaidDiv, preElement);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
// Run Mermaid after processing
|
|
849
|
+
if (document.querySelectorAll('.mermaid').length > 0) {
|
|
850
|
+
mermaid.run().then(() => {
|
|
851
|
+
// After rendering, wrap diagrams with fullscreen buttons
|
|
852
|
+
createFullscreenOverlay();
|
|
853
|
+
wrapDiagramsWithButtons();
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Process diagrams when DOM is ready
|
|
860
|
+
if (document.readyState === 'loading') {
|
|
861
|
+
document.addEventListener('DOMContentLoaded', processMermaidDiagrams);
|
|
862
|
+
} else {
|
|
863
|
+
// DOM already loaded, process immediately
|
|
864
|
+
processMermaidDiagrams();
|
|
865
|
+
}
|
|
866
|
+
</script>
|
|
128
867
|
</body>
|
|
129
|
-
</html>
|
|
130
868
|
|
|
869
|
+
</html>
|