@knowcode/doc-builder 1.9.29 → 1.9.31

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.
Files changed (197) hide show
  1. package/assets/js/main.js +7 -0
  2. package/lib/core-builder.js +18 -1
  3. package/package.json +12 -2
  4. package/.claude/settings.local.json +0 -56
  5. package/CACHE-BUSTING-GUIDE.md +0 -82
  6. package/CLAUDE.md +0 -86
  7. package/CONTRIBUTING.md +0 -148
  8. package/GITHUB_SETUP.md +0 -203
  9. package/RELEASE-NOTES-1.7.5.md +0 -64
  10. package/Screenshot 2025-07-22 at 19.51.21.png +0 -0
  11. package/Screenshot 2025-07-26 at 17.06.49.png +0 -0
  12. package/add-user-clive.sql +0 -35
  13. package/add-user-lindsay-fixed.sql +0 -85
  14. package/add-user-lindsay.sql +0 -68
  15. package/add-user-pmorgan.sql +0 -35
  16. package/add-user-robbie.sql +0 -35
  17. package/add-wru-users.sql +0 -105
  18. package/debug-login.sql +0 -30
  19. package/doc-builder.config.js +0 -126
  20. package/doc-builder.config.js.backup.1753793768283 +0 -47
  21. package/doc-builder.config.js.backup.1753803964423 +0 -114
  22. package/doc-builder.config.js.backup.1753945707032 +0 -115
  23. package/doc-builder.config.js.backup.1754059241330 +0 -115
  24. package/doc-builder.config.js.backup.1754119567787 +0 -123
  25. package/doc-builder.config.js.backup.1754120048862 +0 -124
  26. package/doc-builder.config.js.backup.1754120529913 +0 -124
  27. package/doc-builder.config.js.backup.1754218469785 +0 -124
  28. package/doc-builder.config.js.backup.1754384764054 +0 -124
  29. package/doc-builder.config.js.backup.1754567425847 +0 -124
  30. package/doc-builder.config.js.backup.1754568137859 +0 -126
  31. package/doc-builder.config.js.backup.1754569388252 +0 -126
  32. package/doc-builder.config.js.backup.1754576694123 +0 -126
  33. package/doc-builder.config.js.backup.1755031374829 +0 -126
  34. package/doc-builder.config.js.backup.1755034500990 +0 -126
  35. package/grant-access.sql +0 -15
  36. package/html/11.png +0 -0
  37. package/html/404.html +0 -115
  38. package/html/README.html +0 -522
  39. package/html/Screenshot 2025-08-12 at 21.35.07.png +0 -0
  40. package/html/about-doc-builder.html +0 -491
  41. package/html/auth.js +0 -157
  42. package/html/claude-workflow-guide.html +0 -525
  43. package/html/css/notion-style.css +0 -2502
  44. package/html/documentation-index.html +0 -471
  45. package/html/guides/authentication-default-change.html +0 -370
  46. package/html/guides/authentication-guide.html +0 -509
  47. package/html/guides/cache-control-anti-pattern.html +0 -361
  48. package/html/guides/claude-workflow-guide.html +0 -1074
  49. package/html/guides/configuration-guide.html +0 -472
  50. package/html/guides/document-standards.html +0 -518
  51. package/html/guides/documentation-standards.html +0 -694
  52. package/html/guides/html-embedding-guide.html +0 -461
  53. package/html/guides/image-modal-guide.html +0 -515
  54. package/html/guides/next-steps-walkthrough.html +0 -638
  55. package/html/guides/phosphor-icons-guide.html +0 -584
  56. package/html/guides/private-directory-authentication-troubleshooting.html +0 -555
  57. package/html/guides/private-directory-authentication.html +0 -541
  58. package/html/guides/public-site-deployment.html +0 -431
  59. package/html/guides/search-engine-verification-guide.html +0 -542
  60. package/html/guides/seo-guide.html +0 -661
  61. package/html/guides/seo-optimization-guide.html +0 -887
  62. package/html/guides/supabase-auth-implementation-plan.html +0 -543
  63. package/html/guides/supabase-auth-integration-plan.html +0 -671
  64. package/html/guides/supabase-auth-setup-guide.html +0 -498
  65. package/html/guides/supabase-authentication-complete-guide.html +0 -866
  66. package/html/guides/troubleshooting-guide.html +0 -633
  67. package/html/guides/vercel-deployment-auth-setup.html +0 -337
  68. package/html/guides/windows-setup-guide.html +0 -859
  69. package/html/image-modal-test.html +0 -318
  70. package/html/index.html +0 -522
  71. package/html/js/auth.js +0 -157
  72. package/html/js/main.js +0 -1747
  73. package/html/launch/README.html +0 -297
  74. package/html/launch/bubble-plugin-specification.html +0 -933
  75. package/html/launch/go-to-market-strategy.html +0 -663
  76. package/html/launch/launch-announcements.html +0 -593
  77. package/html/login.html +0 -102
  78. package/html/logout.html +0 -18
  79. package/html/private/cache-control-anti-pattern.html +0 -429
  80. package/html/private/launch/README.html +0 -371
  81. package/html/private/launch/auth-cleanup-summary.html +0 -361
  82. package/html/private/launch/bubble-plugin-specification.html +0 -1007
  83. package/html/private/launch/go-to-market-strategy.html +0 -737
  84. package/html/private/launch/launch-announcements.html +0 -667
  85. package/html/private/launch/vercel-deployment-auth-setup.html +0 -417
  86. package/html/private/next-steps-walkthrough.html +0 -679
  87. package/html/private/supabase-auth-implementation-completed.html +0 -454
  88. package/html/private/supabase-auth-implementation-plan.html +0 -594
  89. package/html/private/supabase-auth-integration-plan.html +0 -704
  90. package/html/private/supabase-auth-setup-guide.html +0 -555
  91. package/html/private/test-private-doc.html +0 -302
  92. package/html/private/user-management-tooling.html +0 -601
  93. package/html/prompts/Screenshot 2025-08-02 at 08.49.55.png +0 -0
  94. package/html/prompts/beautiful-documentation-design.html +0 -784
  95. package/html/prompts/markdown-document-standards.html +0 -422
  96. package/html/prompts/project-rename-strategy-sasha-publish.html +0 -530
  97. package/html/robots.txt +0 -9
  98. package/html/sitemap.xml +0 -357
  99. package/html/test-questions/how-does-it-work%3F.html +0 -294
  100. package/html/test-questions/step-1%3A%20getting-started.html +0 -289
  101. package/html/test-questions/what-is-the-purpose.html +0 -293
  102. package/html/test-status.html +0 -281
  103. package/html/vercel-cli-setup-guide.html +0 -495
  104. package/html/vercel-first-time-setup-guide.html +0 -454
  105. package/html/vercel.json +0 -29
  106. package/html-static/11.png +0 -0
  107. package/html-static/404.html +0 -115
  108. package/html-static/README.html +0 -599
  109. package/html-static/Screenshot 2025-08-12 at 21.35.07.png +0 -0
  110. package/html-static/about-doc-builder.html +0 -568
  111. package/html-static/css/notion-style.css +0 -2502
  112. package/html-static/documentation-index.html +0 -548
  113. package/html-static/guides/authentication-default-change.html +0 -447
  114. package/html-static/guides/authentication-guide.html +0 -586
  115. package/html-static/guides/claude-workflow-guide.html +0 -1151
  116. package/html-static/guides/configuration-guide.html +0 -549
  117. package/html-static/guides/documentation-standards.html +0 -771
  118. package/html-static/guides/html-embedding-guide.html +0 -538
  119. package/html-static/guides/image-modal-guide.html +0 -592
  120. package/html-static/guides/phosphor-icons-guide.html +0 -661
  121. package/html-static/guides/private-directory-authentication-troubleshooting.html +0 -632
  122. package/html-static/guides/private-directory-authentication.html +0 -618
  123. package/html-static/guides/public-site-deployment.html +0 -508
  124. package/html-static/guides/search-engine-verification-guide.html +0 -619
  125. package/html-static/guides/seo-guide.html +0 -738
  126. package/html-static/guides/seo-optimization-guide.html +0 -964
  127. package/html-static/guides/supabase-authentication-complete-guide.html +0 -943
  128. package/html-static/guides/troubleshooting-guide.html +0 -710
  129. package/html-static/guides/windows-setup-guide.html +0 -936
  130. package/html-static/image-modal-test.html +0 -395
  131. package/html-static/index.html +0 -599
  132. package/html-static/js/main.js +0 -1747
  133. package/html-static/prompts/Screenshot 2025-08-02 at 08.49.55.png +0 -0
  134. package/html-static/prompts/beautiful-documentation-design.html +0 -861
  135. package/html-static/prompts/markdown-document-standards.html +0 -499
  136. package/html-static/prompts/project-rename-strategy-sasha-publish.html +0 -607
  137. package/html-static/robots.txt +0 -5
  138. package/html-static/sitemap.xml +0 -195
  139. package/html-static/test-questions/how-does-it-work%3F.html +0 -371
  140. package/html-static/test-questions/step-1%3A%20getting-started.html +0 -366
  141. package/html-static/test-questions/what-is-the-purpose.html +0 -370
  142. package/html-static/vercel-cli-setup-guide.html +0 -572
  143. package/html-static/vercel-first-time-setup-guide.html +0 -531
  144. package/manage-users.sql +0 -191
  145. package/migrate-to-domain-auth.sql +0 -47
  146. package/package/CACHE-BUSTING-GUIDE.md +0 -82
  147. package/package/CHANGELOG.md +0 -902
  148. package/package/README.md +0 -248
  149. package/package/assets/css/notion-style.css +0 -2211
  150. package/package/assets/js/auth.js +0 -67
  151. package/package/assets/js/main.js +0 -1565
  152. package/package/cli.js +0 -764
  153. package/package/index.js +0 -38
  154. package/package/knowcode-doc-builder-1.3.15.tgz +0 -0
  155. package/package/lib/builder.js +0 -32
  156. package/package/lib/config.js +0 -278
  157. package/package/lib/core-builder.js +0 -957
  158. package/package/lib/deploy.js +0 -497
  159. package/package/lib/dev-server.js +0 -96
  160. package/package/package.json +0 -34
  161. package/package/scripts/npx-runner.js +0 -27
  162. package/package/scripts/setup.js +0 -56
  163. package/package/test-cache-bust.sh +0 -43
  164. package/public-config.js +0 -22
  165. package/public-html/404.html +0 -115
  166. package/public-html/README.html +0 -149
  167. package/public-html/css/notion-style.css +0 -2036
  168. package/public-html/index.html +0 -149
  169. package/public-html/js/auth.js +0 -67
  170. package/public-html/js/main.js +0 -1485
  171. package/quick-test-commands.md +0 -40
  172. package/recordings/Screenshot 2025-07-24 at 18.22.01.png +0 -0
  173. package/recordings/mh-ls-22jul.txt +0 -2305
  174. package/screenshot.png +0 -0
  175. package/scripts/Screenshot 2025-07-23 at 15.39.41.png +0 -0
  176. package/setup-database-v2.sql +0 -53
  177. package/setup-database.sql +0 -41
  178. package/test-auth-config.js +0 -17
  179. package/test-cache-bust.sh +0 -43
  180. package/test-docs/README.md +0 -39
  181. package/test-html/404.html +0 -115
  182. package/test-html/README.html +0 -172
  183. package/test-html/auth.js +0 -97
  184. package/test-html/css/notion-style.css +0 -2036
  185. package/test-html/index.html +0 -172
  186. package/test-html/js/auth.js +0 -97
  187. package/test-html/js/main.js +0 -1485
  188. package/test-html/login.html +0 -102
  189. package/test-html/logout.html +0 -18
  190. package/update-domain.sql +0 -9
  191. package/user-access-view.sql +0 -49
  192. package/user-management/README.md +0 -301
  193. package/user-management/add-users.sh +0 -776
  194. package/user-management/create-user.js +0 -65
  195. package/user-management/users.txt +0 -15
  196. package/view-all-users.sql +0 -40
  197. package/wru-auth-config.js +0 -17
package/html/js/main.js DELETED
@@ -1,1747 +0,0 @@
1
- // Documentation Builder - Main JavaScript
2
-
3
- // Preview Banner Management
4
- // Set up banner state immediately to prevent flash
5
- const bannerDismissed = localStorage.getItem('banner-dismissed') === 'true';
6
-
7
- // Apply styles immediately if banner should be visible
8
- if (!bannerDismissed) {
9
- document.documentElement.style.setProperty('--banner-offset', '3.5rem');
10
- } else {
11
- document.documentElement.style.setProperty('--banner-offset', '0rem');
12
- }
13
-
14
- document.addEventListener('DOMContentLoaded', function() {
15
- const banner = document.getElementById('preview-banner');
16
- const dismissButton = document.getElementById('dismiss-banner');
17
- const mainWrapper = document.querySelector('.main-wrapper');
18
- const sidebar = document.querySelector('.sidebar');
19
- const breadcrumbs = document.querySelector('.breadcrumbs');
20
-
21
- if (banner) {
22
- if (bannerDismissed) {
23
- banner.classList.add('hidden');
24
- } else {
25
- // Show banner and adjust layout
26
- banner.classList.add('visible');
27
- mainWrapper?.classList.add('banner-visible');
28
- sidebar?.classList.add('banner-visible');
29
- breadcrumbs?.classList.add('banner-visible');
30
- }
31
- }
32
-
33
- // Handle banner dismissal
34
- if (dismissButton) {
35
- dismissButton.addEventListener('click', function() {
36
- banner.classList.remove('visible');
37
- banner.classList.add('hidden');
38
- mainWrapper.classList.remove('banner-visible');
39
- sidebar.classList.remove('banner-visible');
40
- breadcrumbs?.classList.remove('banner-visible');
41
- document.documentElement.style.setProperty('--banner-offset', '0rem');
42
-
43
- // Remember that the banner was dismissed
44
- localStorage.setItem('banner-dismissed', 'true');
45
- });
46
- }
47
-
48
- // Handle Escape key to dismiss banner
49
- document.addEventListener('keydown', function(e) {
50
- if (e.key === 'Escape' && banner.classList.contains('visible')) {
51
- dismissButton.click();
52
- }
53
- });
54
-
55
- // Initialize Mermaid Full Screen Functionality
56
- initializeMermaidFullScreen();
57
- });
58
-
59
- // Mermaid Theme Configuration
60
- function configureMermaidTheme() {
61
- // Check if enhanced styling is enabled (passed from config)
62
- const enhancedStyling = window.docBuilderConfig?.features?.mermaidEnhanced !== false;
63
-
64
- // Set data attribute for CSS styling
65
- document.documentElement.setAttribute('data-mermaid-enhanced', enhancedStyling.toString());
66
-
67
- // Get current theme (light/dark mode)
68
- const isDarkMode = document.documentElement.getAttribute('data-theme') === 'dark';
69
-
70
- // Notion-inspired color palette
71
- const lightTheme = {
72
- primaryColor: '#F7F6F3', // Light background for shapes
73
- primaryTextColor: '#37352F', // Dark text
74
- primaryBorderColor: '#E3E2E0', // Subtle borders
75
- lineColor: '#787774', // Connection lines
76
- secondaryColor: '#EDEBE9', // Secondary backgrounds
77
- tertiaryColor: '#E9E9E7', // Tertiary elements
78
- background: '#FFFFFF', // Diagram background
79
- fontSize: '16px', // Text size
80
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
81
- };
82
-
83
- const darkTheme = {
84
- primaryColor: '#2F2F2F', // Dark background for shapes
85
- primaryTextColor: '#E3E2E0', // Light text
86
- primaryBorderColor: '#454545', // Darker borders
87
- lineColor: '#9B9A97', // Lighter connection lines
88
- secondaryColor: '#404040', // Secondary backgrounds
89
- tertiaryColor: '#4A4A4A', // Tertiary elements
90
- background: '#1A1A1A', // Dark diagram background
91
- fontSize: '16px', // Text size
92
- fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
93
- };
94
-
95
- const currentTheme = isDarkMode ? darkTheme : lightTheme;
96
-
97
- // Initialize Mermaid with custom theme (only if enhanced styling is enabled)
98
- const mermaidConfig = {
99
- startOnLoad: false, // We'll manually start it after configuration
100
- theme: enhancedStyling ? 'base' : 'default',
101
- ...(enhancedStyling && { themeVariables: currentTheme }),
102
- flowchart: {
103
- curve: 'basis', // Smoother curves
104
- padding: 20, // More padding around elements
105
- nodeSpacing: 50, // Space between nodes
106
- rankSpacing: 50, // Space between ranks
107
- marginX: 20, // Horizontal margins
108
- marginY: 20 // Vertical margins
109
- },
110
- sequence: {
111
- actorMargin: 50,
112
- width: 150,
113
- height: 65,
114
- boxMargin: 10,
115
- boxTextMargin: 5,
116
- noteMargin: 10,
117
- messageMargin: 35
118
- },
119
- ...(enhancedStyling && {
120
- gantt: {
121
- leftPadding: 75,
122
- gridLineStartPadding: 35,
123
- fontSize: 12,
124
- sectionFontSize: 24
125
- }
126
- })
127
- };
128
-
129
- mermaid.initialize(mermaidConfig);
130
-
131
- // Manually render all mermaid diagrams after configuration
132
- setTimeout(() => {
133
- mermaid.run();
134
- }, 100);
135
- }
136
-
137
- // Mermaid Full Screen Viewer
138
- function initializeMermaidFullScreen() {
139
- // Wait for Mermaid to initialize
140
- if (typeof mermaid === 'undefined') {
141
- setTimeout(initializeMermaidFullScreen, 100);
142
- return;
143
- }
144
-
145
- // Configure Mermaid with enhanced styling
146
- configureMermaidTheme();
147
-
148
- // Find all Mermaid diagrams and wrap them with full-screen controls
149
- const mermaidDivs = document.querySelectorAll('.mermaid');
150
-
151
- mermaidDivs.forEach((mermaidDiv, index) => {
152
- // Skip if already processed
153
- if (mermaidDiv.closest('.mermaid-container')) {
154
- return;
155
- }
156
-
157
- // Create container
158
- const container = document.createElement('div');
159
- container.className = 'mermaid-container';
160
-
161
- // Create toolbar
162
- const toolbar = document.createElement('div');
163
- toolbar.className = 'mermaid-toolbar';
164
-
165
- const actions = document.createElement('div');
166
- actions.className = 'mermaid-actions';
167
-
168
- // Full screen button
169
- const fullScreenBtn = document.createElement('button');
170
- fullScreenBtn.className = 'mermaid-btn';
171
- fullScreenBtn.innerHTML = '<i class="fas fa-expand"></i>';
172
- fullScreenBtn.title = 'Full Screen';
173
- fullScreenBtn.addEventListener('click', () => openMermaidFullScreen(mermaidDiv, index));
174
-
175
- actions.appendChild(fullScreenBtn);
176
-
177
- toolbar.appendChild(actions);
178
-
179
- // Create wrapper for the diagram
180
- const wrapper = document.createElement('div');
181
- wrapper.className = 'mermaid-wrapper';
182
-
183
- // Insert container before mermaid div
184
- mermaidDiv.parentNode.insertBefore(container, mermaidDiv);
185
-
186
- // Move mermaid div into wrapper
187
- wrapper.appendChild(mermaidDiv);
188
-
189
- // Assemble container
190
- container.appendChild(toolbar);
191
- container.appendChild(wrapper);
192
- });
193
-
194
- // Create fullscreen modal (only once)
195
- if (!document.getElementById('mermaid-fullscreen-modal')) {
196
- createMermaidFullScreenModal();
197
- }
198
- }
199
-
200
- function createMermaidFullScreenModal() {
201
- const modal = document.createElement('div');
202
- modal.id = 'mermaid-fullscreen-modal';
203
- modal.className = 'mermaid-fullscreen';
204
-
205
- modal.innerHTML = `
206
- <div class="mermaid-fullscreen-toolbar">
207
- <div class="mermaid-fullscreen-title">Mermaid Diagram - Full Screen View</div>
208
- <div class="mermaid-fullscreen-controls">
209
- <div class="mermaid-zoom-controls">
210
- <button class="mermaid-zoom-btn" id="zoom-out">
211
- <i class="fas fa-minus"></i>
212
- </button>
213
- <div class="mermaid-zoom-level" id="zoom-level">100%</div>
214
- <button class="mermaid-zoom-btn" id="zoom-in">
215
- <i class="fas fa-plus"></i>
216
- </button>
217
- <button class="mermaid-zoom-btn" id="zoom-reset">
218
- <i class="fas fa-expand-arrows-alt"></i>
219
- </button>
220
- </div>
221
- <button class="mermaid-close-btn" id="close-fullscreen">
222
- <i class="fas fa-times"></i> Close
223
- </button>
224
- </div>
225
- </div>
226
- <div class="mermaid-fullscreen-content">
227
- <div class="mermaid-fullscreen-wrapper" id="fullscreen-wrapper">
228
- <div class="mermaid-fullscreen-diagram" id="fullscreen-diagram">
229
- <!-- Diagram will be inserted here -->
230
- </div>
231
- </div>
232
- </div>
233
- `;
234
-
235
- document.body.appendChild(modal);
236
-
237
- // Set up event listeners - store zoom in modal element
238
- modal.currentZoom = 1;
239
- const wrapper = document.getElementById('fullscreen-wrapper');
240
- const zoomLevel = document.getElementById('zoom-level');
241
-
242
- function updateZoom() {
243
- const currentZoom = modal.currentZoom || 1;
244
- wrapper.style.transform = `scale(${currentZoom})`;
245
- zoomLevel.textContent = `${Math.round(currentZoom * 100)}%`;
246
-
247
- if (currentZoom > 1) {
248
- wrapper.classList.add('zoomed');
249
- } else {
250
- wrapper.classList.remove('zoomed');
251
- }
252
- }
253
-
254
- // Zoom controls
255
- document.getElementById('zoom-in').addEventListener('click', () => {
256
- modal.currentZoom = Math.min((modal.currentZoom || 1) + 0.25, 3);
257
- updateZoom();
258
- });
259
-
260
- document.getElementById('zoom-out').addEventListener('click', () => {
261
- modal.currentZoom = Math.max((modal.currentZoom || 1) - 0.25, 0.25);
262
- updateZoom();
263
- });
264
-
265
- document.getElementById('zoom-reset').addEventListener('click', () => {
266
- modal.currentZoom = 1;
267
- updateZoom();
268
- });
269
-
270
- // Close functionality
271
- document.getElementById('close-fullscreen').addEventListener('click', closeMermaidFullScreen);
272
-
273
- // Close on backdrop click
274
- modal.addEventListener('click', (e) => {
275
- if (e.target === modal) {
276
- closeMermaidFullScreen();
277
- }
278
- });
279
-
280
- // Close on Escape key
281
- document.addEventListener('keydown', (e) => {
282
- if (e.key === 'Escape' && modal.classList.contains('active')) {
283
- closeMermaidFullScreen();
284
- }
285
- });
286
- }
287
-
288
- function openMermaidFullScreen(mermaidDiv, index) {
289
- const modal = document.getElementById('mermaid-fullscreen-modal');
290
- const diagramContainer = document.getElementById('fullscreen-diagram');
291
- const wrapper = document.getElementById('fullscreen-wrapper');
292
- const zoomLevel = document.getElementById('zoom-level');
293
-
294
- // Reset zoom to 100% when opening new diagram
295
- modal.currentZoom = 1;
296
- wrapper.style.transform = `scale(${modal.currentZoom})`;
297
- zoomLevel.textContent = `${Math.round(modal.currentZoom * 100)}%`;
298
- wrapper.classList.remove('zoomed');
299
-
300
- // Clone the mermaid diagram
301
- const clonedDiagram = mermaidDiv.cloneNode(true);
302
-
303
- // Reset all styles that might interfere
304
- clonedDiagram.style.cssText = '';
305
-
306
- // Find the SVG and make it scale properly
307
- const svg = clonedDiagram.querySelector('svg');
308
- if (svg) {
309
- // Store original dimensions for reference
310
- const originalWidth = svg.getAttribute('width');
311
- const originalHeight = svg.getAttribute('height');
312
- const originalViewBox = svg.getAttribute('viewBox');
313
-
314
- // Reset SVG styles
315
- svg.style.cssText = '';
316
-
317
- // Ensure we have a proper viewBox for scaling
318
- if (!originalViewBox && originalWidth && originalHeight) {
319
- svg.setAttribute('viewBox', `0 0 ${originalWidth} ${originalHeight}`);
320
- }
321
-
322
- // Remove fixed dimensions to enable responsive scaling
323
- svg.removeAttribute('width');
324
- svg.removeAttribute('height');
325
-
326
- // Set responsive attributes
327
- svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
328
-
329
- // Apply CSS for proper scaling
330
- svg.style.width = '100%';
331
- svg.style.height = '100%';
332
- svg.style.maxWidth = '100%';
333
- svg.style.maxHeight = '100%';
334
- svg.style.display = 'block';
335
- }
336
-
337
- // Apply proper styles to the cloned diagram
338
- clonedDiagram.style.width = '100%';
339
- clonedDiagram.style.height = '100%';
340
- clonedDiagram.style.display = 'flex';
341
- clonedDiagram.style.justifyContent = 'center';
342
- clonedDiagram.style.alignItems = 'center';
343
-
344
- // Clear previous content and add new diagram
345
- diagramContainer.innerHTML = '';
346
- diagramContainer.appendChild(clonedDiagram);
347
-
348
- // Show modal
349
- modal.classList.add('active');
350
- document.body.style.overflow = 'hidden';
351
-
352
- // Update title
353
- const title = document.querySelector('.mermaid-fullscreen-title');
354
- const container = mermaidDiv.closest('.mermaid-container');
355
- const originalTitle = container ? container.querySelector('.mermaid-toolbar div').textContent : 'Mermaid Diagram';
356
- title.textContent = `${originalTitle} - Full Screen View`;
357
-
358
- // Debug logging
359
- console.log('Fullscreen opened with diagram:', clonedDiagram);
360
- console.log('SVG found:', svg);
361
- if (svg) {
362
- console.log('SVG viewBox:', svg.getAttribute('viewBox'));
363
- console.log('SVG dimensions:', svg.getBoundingClientRect());
364
- }
365
- }
366
-
367
- function closeMermaidFullScreen() {
368
- const modal = document.getElementById('mermaid-fullscreen-modal');
369
- modal.classList.remove('active');
370
- document.body.style.overflow = '';
371
-
372
- // Clear diagram content
373
- setTimeout(() => {
374
- const diagramContainer = document.getElementById('fullscreen-diagram');
375
- diagramContainer.innerHTML = '';
376
- }, 300);
377
- }
378
-
379
- function copyMermaidSVG(mermaidDiv) {
380
- try {
381
- const svg = mermaidDiv.querySelector('svg');
382
- if (svg) {
383
- const svgString = new XMLSerializer().serializeToString(svg);
384
-
385
- // Try to use the modern clipboard API
386
- if (navigator.clipboard && navigator.clipboard.writeText) {
387
- navigator.clipboard.writeText(svgString).then(() => {
388
- showCopySuccess('SVG copied to clipboard!');
389
- }).catch(() => {
390
- fallbackCopy(svgString, 'SVG');
391
- });
392
- } else {
393
- fallbackCopy(svgString, 'SVG');
394
- }
395
- }
396
- } catch (error) {
397
- console.error('Error copying SVG:', error);
398
- showCopyError();
399
- }
400
- }
401
-
402
- function copyMermaidSource(mermaidDiv) {
403
- try {
404
- // Find the original Mermaid source code
405
- let mermaidSource = '';
406
-
407
- // Try to get from data attribute first
408
- if (mermaidDiv.dataset.mermaidSource) {
409
- mermaidSource = mermaidDiv.dataset.mermaidSource;
410
- } else {
411
- // Try to find in the page content - look for the nearest pre code block
412
- const container = mermaidDiv.closest('.content');
413
- if (container) {
414
- const codeBlocks = container.querySelectorAll('pre code');
415
- for (const block of codeBlocks) {
416
- if (block.textContent.includes('flowchart') || block.textContent.includes('graph')) {
417
- mermaidSource = block.textContent;
418
- break;
419
- }
420
- }
421
- }
422
-
423
- // Fallback: extract from the SVG if available
424
- if (!mermaidSource) {
425
- const svg = mermaidDiv.querySelector('svg');
426
- if (svg) {
427
- // Try to reconstruct basic Mermaid from SVG elements
428
- mermaidSource = reconstructMermaidFromSVG(svg);
429
- }
430
- }
431
- }
432
-
433
- if (mermaidSource) {
434
- // Try to use the modern clipboard API
435
- if (navigator.clipboard && navigator.clipboard.writeText) {
436
- navigator.clipboard.writeText(mermaidSource).then(() => {
437
- showCopySuccess('Mermaid source copied to clipboard!');
438
- }).catch(() => {
439
- fallbackCopy(mermaidSource, 'Mermaid');
440
- });
441
- } else {
442
- fallbackCopy(mermaidSource, 'Mermaid');
443
- }
444
- } else {
445
- showCopyError('Could not find Mermaid source');
446
- }
447
- } catch (error) {
448
- console.error('Error copying Mermaid source:', error);
449
- showCopyError();
450
- }
451
- }
452
-
453
- function reconstructMermaidFromSVG(svg) {
454
- // Basic reconstruction - this is a fallback method
455
- let mermaidCode = 'flowchart TD\n';
456
-
457
- // Try to extract node information from SVG
458
- const nodes = svg.querySelectorAll('g.node');
459
- const edges = svg.querySelectorAll('g.edgePath');
460
-
461
- // Add nodes
462
- nodes.forEach((node, index) => {
463
- const label = node.querySelector('span, text, foreignObject');
464
- if (label) {
465
- const nodeText = label.textContent.trim();
466
- const nodeId = `N${index + 1}`;
467
-
468
- if (nodeText.includes('?')) {
469
- mermaidCode += ` ${nodeId}{{"${nodeText}"}}\n`;
470
- } else {
471
- mermaidCode += ` ${nodeId}["${nodeText}"]\n`;
472
- }
473
- }
474
- });
475
-
476
- mermaidCode += '\n %% Note: This is a reconstructed version - original source may differ\n';
477
-
478
- return mermaidCode;
479
- }
480
-
481
- function fallbackCopy(text, type = 'content') {
482
- // Fallback for older browsers
483
- const textArea = document.createElement('textarea');
484
- textArea.value = text;
485
- textArea.style.position = 'fixed';
486
- textArea.style.opacity = '0';
487
- document.body.appendChild(textArea);
488
- textArea.select();
489
-
490
- try {
491
- document.execCommand('copy');
492
- showCopySuccess(`${type} copied to clipboard!`);
493
- } catch (error) {
494
- showCopyError(`Failed to copy ${type.toLowerCase()}`);
495
- }
496
-
497
- document.body.removeChild(textArea);
498
- }
499
-
500
- function showCopySuccess(message = 'Content copied to clipboard!') {
501
- // Create temporary success message
502
- const messageDiv = document.createElement('div');
503
- messageDiv.textContent = message;
504
- messageDiv.style.cssText = `
505
- position: fixed;
506
- top: 50%;
507
- left: 50%;
508
- transform: translate(-50%, -50%);
509
- background: var(--success);
510
- color: white;
511
- padding: 1rem 2rem;
512
- border-radius: 0.5rem;
513
- z-index: 10001;
514
- font-size: 0.875rem;
515
- box-shadow: var(--shadow-lg);
516
- max-width: 300px;
517
- text-align: center;
518
- `;
519
-
520
- document.body.appendChild(messageDiv);
521
-
522
- setTimeout(() => {
523
- messageDiv.remove();
524
- }, 2000);
525
- }
526
-
527
- function showCopyError(message = 'Failed to copy content') {
528
- // Create temporary error message
529
- const messageDiv = document.createElement('div');
530
- messageDiv.textContent = message;
531
- messageDiv.style.cssText = `
532
- position: fixed;
533
- top: 50%;
534
- left: 50%;
535
- transform: translate(-50%, -50%);
536
- background: var(--danger);
537
- color: white;
538
- padding: 1rem 2rem;
539
- border-radius: 0.5rem;
540
- z-index: 10001;
541
- font-size: 0.875rem;
542
- box-shadow: var(--shadow-lg);
543
- max-width: 300px;
544
- text-align: center;
545
- `;
546
-
547
- document.body.appendChild(messageDiv);
548
-
549
- setTimeout(() => {
550
- messageDiv.remove();
551
- }, 2000);
552
- }
553
-
554
- // Theme Management
555
- const themeToggle = document.getElementById('theme-toggle');
556
- const html = document.documentElement;
557
-
558
- // Check for saved theme preference or default to 'light'
559
- const currentTheme = localStorage.getItem('theme') || 'light';
560
- html.setAttribute('data-theme', currentTheme);
561
- updateThemeIcon(currentTheme);
562
-
563
- themeToggle.addEventListener('click', () => {
564
- const newTheme = html.getAttribute('data-theme') === 'light' ? 'dark' : 'light';
565
- html.setAttribute('data-theme', newTheme);
566
- localStorage.setItem('theme', newTheme);
567
- updateThemeIcon(newTheme);
568
- });
569
-
570
- function updateThemeIcon(theme) {
571
- const icon = themeToggle.querySelector('i');
572
- if (icon) {
573
- icon.className = theme === 'light' ? 'fas fa-moon' : 'fas fa-sun';
574
- }
575
- }
576
-
577
- // Mobile Menu Toggle
578
- const menuToggle = document.getElementById('menu-toggle');
579
- const sidebar = document.querySelector('.sidebar');
580
-
581
- // Set initial menu state based on configuration
582
- const menuDefaultOpen = window.docBuilderConfig?.features?.menuDefaultOpen !== false;
583
- if (sidebar && window.innerWidth > 768) {
584
- if (!menuDefaultOpen) {
585
- sidebar.classList.add('closed');
586
- // Add class to body to show menu toggle on desktop when menu starts closed
587
- document.body.classList.add('menu-starts-closed');
588
- }
589
- }
590
-
591
- // Create overlay element for mobile
592
- let overlay = document.querySelector('.sidebar-overlay');
593
- if (!overlay && window.innerWidth <= 768) {
594
- overlay = document.createElement('div');
595
- overlay.className = 'sidebar-overlay';
596
- document.body.appendChild(overlay);
597
- }
598
-
599
- if (menuToggle) {
600
- menuToggle.addEventListener('click', () => {
601
- if (window.innerWidth <= 768) {
602
- // Mobile: toggle 'open' class
603
- sidebar.classList.toggle('open');
604
- } else {
605
- // Desktop: toggle 'closed' class
606
- sidebar.classList.toggle('closed');
607
- // Update visibility of menu toggle based on sidebar state
608
- updateMenuToggleVisibility();
609
- }
610
- if (overlay) {
611
- overlay.classList.toggle('active');
612
- }
613
- });
614
- }
615
-
616
- // Function to update menu toggle visibility
617
- function updateMenuToggleVisibility() {
618
- if (window.innerWidth > 768) {
619
- if (!menuDefaultOpen || sidebar.classList.contains('closed')) {
620
- document.body.classList.add('show-menu-toggle');
621
- } else {
622
- document.body.classList.remove('show-menu-toggle');
623
- }
624
- }
625
- }
626
-
627
- // Initial check
628
- updateMenuToggleVisibility();
629
-
630
- // Update on window resize
631
- window.addEventListener('resize', updateMenuToggleVisibility);
632
-
633
- // Close menu when clicking overlay
634
- if (overlay) {
635
- overlay.addEventListener('click', () => {
636
- sidebar.classList.remove('open');
637
- overlay.classList.remove('active');
638
- });
639
- }
640
-
641
- // Floating Menu Button for Mobile
642
- function initFloatingMenuButton() {
643
- // Only initialize on mobile
644
- if (window.innerWidth > 768) return;
645
-
646
- // Check if button already exists
647
- if (document.getElementById('floating-menu-toggle')) return;
648
-
649
- // Create floating button
650
- const floatingButton = document.createElement('button');
651
- floatingButton.id = 'floating-menu-toggle';
652
- floatingButton.className = 'floating-menu-toggle';
653
- floatingButton.setAttribute('aria-label', 'Toggle menu');
654
- floatingButton.innerHTML = '<i class="fas fa-bars"></i>';
655
- floatingButton.style.display = 'flex'; // Always visible on mobile
656
- floatingButton.classList.add('visible'); // Start visible
657
-
658
- // Add to body
659
- document.body.appendChild(floatingButton);
660
-
661
- // Toggle sidebar on click
662
- floatingButton.addEventListener('click', () => {
663
- sidebar.classList.toggle('open');
664
-
665
- // Handle overlay
666
- let overlay = document.querySelector('.sidebar-overlay');
667
- if (!overlay) {
668
- overlay = document.createElement('div');
669
- overlay.className = 'sidebar-overlay';
670
- document.body.appendChild(overlay);
671
-
672
- // Add overlay click handler
673
- overlay.addEventListener('click', () => {
674
- sidebar.classList.remove('open');
675
- overlay.classList.remove('active');
676
- floatingButton.querySelector('i').className = 'fas fa-bars';
677
- });
678
- }
679
-
680
- if (overlay) {
681
- overlay.classList.toggle('active');
682
- }
683
-
684
- // Update icon based on state
685
- const icon = floatingButton.querySelector('i');
686
- if (sidebar.classList.contains('open')) {
687
- icon.className = 'fas fa-times';
688
- } else {
689
- icon.className = 'fas fa-bars';
690
- }
691
- });
692
-
693
- // Remove scroll-based visibility - button is always visible on mobile
694
-
695
- // Update icon when sidebar state changes from other sources
696
- const observer = new MutationObserver(() => {
697
- const icon = floatingButton.querySelector('i');
698
- if (sidebar.classList.contains('open')) {
699
- icon.className = 'fas fa-times';
700
- } else {
701
- icon.className = 'fas fa-bars';
702
- }
703
- });
704
-
705
- observer.observe(sidebar, {
706
- attributes: true,
707
- attributeFilter: ['class']
708
- });
709
- }
710
-
711
- // Initialize floating button on load and resize
712
- document.addEventListener('DOMContentLoaded', initFloatingMenuButton);
713
- window.addEventListener('resize', () => {
714
- const existingButton = document.getElementById('floating-menu-toggle');
715
- if (window.innerWidth > 768 && existingButton) {
716
- existingButton.remove();
717
- } else if (window.innerWidth <= 768 && !existingButton) {
718
- initFloatingMenuButton();
719
- }
720
- });
721
-
722
- // Prevent sidebar from closing when clicking nav items
723
- // Only close when clicking outside the sidebar or the close button
724
- document.addEventListener('click', (e) => {
725
- // Check if we're on mobile
726
- if (window.innerWidth <= 768) {
727
- const isClickInsideSidebar = sidebar && sidebar.contains(e.target);
728
- const isMenuToggle = e.target.closest('#menu-toggle');
729
- const isFloatingButton = e.target.closest('#floating-menu-toggle');
730
- const isNavItem = e.target.closest('.nav-item, .nav-title');
731
- const overlay = document.querySelector('.sidebar-overlay');
732
-
733
- // Close sidebar only if clicking outside AND not on menu toggle AND not on nav items
734
- if (!isClickInsideSidebar && !isMenuToggle && !isFloatingButton && !isNavItem && sidebar?.classList.contains('open')) {
735
- sidebar.classList.remove('open');
736
- if (overlay) {
737
- overlay.classList.remove('active');
738
- }
739
- // Update floating button icon if it exists
740
- const floatingBtn = document.getElementById('floating-menu-toggle');
741
- if (floatingBtn) {
742
- floatingBtn.querySelector('i').className = 'fas fa-bars';
743
- }
744
- }
745
- }
746
- });
747
-
748
- // Smooth Scrolling for Anchor Links
749
- document.querySelectorAll('a[href^="#"]').forEach(anchor => {
750
- anchor.addEventListener('click', function (e) {
751
- e.preventDefault();
752
- const href = this.getAttribute('href');
753
- // Skip if href is just '#' (prevents querySelector error)
754
- if (href && href !== '#') {
755
- const target = document.querySelector(href);
756
- if (target) {
757
- target.scrollIntoView({
758
- behavior: 'smooth',
759
- block: 'start'
760
- });
761
- }
762
- }
763
- });
764
- });
765
-
766
- // Active Navigation Highlighting
767
- const sections = document.querySelectorAll('section[id]');
768
- const navItems = document.querySelectorAll('.nav-item');
769
-
770
- function highlightNavigation() {
771
- const scrollY = window.pageYOffset;
772
-
773
- sections.forEach(section => {
774
- const sectionHeight = section.offsetHeight;
775
- const sectionTop = section.offsetTop - 100;
776
- const sectionId = section.getAttribute('id');
777
-
778
- if (scrollY > sectionTop && scrollY <= sectionTop + sectionHeight) {
779
- navItems.forEach(item => {
780
- item.classList.remove('active');
781
- if (item.getAttribute('href') === `#${sectionId}`) {
782
- item.classList.add('active');
783
- }
784
- });
785
- }
786
- });
787
- }
788
-
789
- window.addEventListener('scroll', highlightNavigation);
790
-
791
- // Search Functionality (Basic Implementation)
792
- const searchInput = document.getElementById('search-input');
793
- const searchResults = document.getElementById('search-results');
794
-
795
- if (searchInput) {
796
- searchInput.addEventListener('input', (e) => {
797
- const query = e.target.value.toLowerCase();
798
-
799
- if (query.length < 2) {
800
- searchResults.style.display = 'none';
801
- return;
802
- }
803
-
804
- // This would be replaced with actual search logic
805
- performSearch(query);
806
- });
807
-
808
- // Close search results when clicking outside
809
- document.addEventListener('click', (e) => {
810
- if (!e.target.closest('.search-box')) {
811
- searchResults.style.display = 'none';
812
- }
813
- });
814
- }
815
-
816
- function performSearch(query) {
817
- // Placeholder for search functionality
818
- // In a real implementation, this would search through all content
819
- searchResults.innerHTML = `
820
- <div class="search-result-item">
821
- <strong>Search results for "${query}"</strong>
822
- <p>Search functionality will be implemented here...</p>
823
- </div>
824
- `;
825
- searchResults.style.display = 'block';
826
- }
827
-
828
- // Copy Code Blocks
829
- document.querySelectorAll('pre').forEach(block => {
830
- const wrapper = document.createElement('div');
831
- wrapper.className = 'code-block-wrapper';
832
- block.parentNode.insertBefore(wrapper, block);
833
- wrapper.appendChild(block);
834
-
835
- const button = document.createElement('button');
836
- button.className = 'copy-button';
837
- button.textContent = 'Copy';
838
- wrapper.appendChild(button);
839
-
840
- button.addEventListener('click', () => {
841
- const code = block.textContent;
842
- navigator.clipboard.writeText(code).then(() => {
843
- button.textContent = 'Copied!';
844
- setTimeout(() => {
845
- button.textContent = 'Copy';
846
- }, 2000);
847
- });
848
- });
849
- });
850
-
851
- // Table of Contents Generation
852
- function generateTableOfContents() {
853
- const toc = document.getElementById('table-of-contents');
854
- if (!toc) return;
855
-
856
- const headings = document.querySelectorAll('.content h2, .content h3');
857
- const tocList = document.createElement('ul');
858
- tocList.className = 'toc-list';
859
-
860
- headings.forEach(heading => {
861
- const li = document.createElement('li');
862
- const a = document.createElement('a');
863
- a.href = `#${heading.id}`;
864
- a.textContent = heading.textContent;
865
- a.className = heading.tagName.toLowerCase() === 'h3' ? 'toc-h3' : 'toc-h2';
866
- li.appendChild(a);
867
- tocList.appendChild(li);
868
- });
869
-
870
- toc.appendChild(tocList);
871
- }
872
-
873
- // Keyboard Shortcuts
874
- document.addEventListener('keydown', (e) => {
875
- // Cmd/Ctrl + K for search
876
- if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
877
- e.preventDefault();
878
- searchInput?.focus();
879
- }
880
-
881
- // Escape to close mobile menu
882
- if (e.key === 'Escape') {
883
- sidebar?.classList.remove('open');
884
- }
885
- });
886
-
887
- // Fade-in Animation on Scroll
888
- const observerOptions = {
889
- threshold: 0.1,
890
- rootMargin: '0px 0px -100px 0px'
891
- };
892
-
893
- const observer = new IntersectionObserver((entries) => {
894
- entries.forEach(entry => {
895
- if (entry.isIntersecting) {
896
- entry.target.classList.add('fade-in');
897
- observer.unobserve(entry.target);
898
- }
899
- });
900
- }, observerOptions);
901
-
902
- document.querySelectorAll('.feature-card, .timeline-item, .metric-card').forEach(el => {
903
- observer.observe(el);
904
- });
905
-
906
- // Sidebar Resizing
907
- function initSidebarResize() {
908
- const sidebar = document.querySelector('.sidebar');
909
- const resizeHandle = document.querySelector('.resize-handle');
910
- const content = document.querySelector('.content');
911
-
912
- if (!sidebar || !resizeHandle || !content) return;
913
-
914
- let isResizing = false;
915
- let startX = 0;
916
- let startWidth = 0;
917
-
918
- // Restore saved width on load
919
- const savedWidth = localStorage.getItem('sidebarWidth');
920
- if (savedWidth && savedWidth >= 200 && savedWidth <= 500) {
921
- sidebar.style.width = `${savedWidth}px`;
922
- // Don't set margin-left - flexbox handles the layout
923
- }
924
-
925
- // Mouse down on resize handle
926
- resizeHandle.addEventListener('mousedown', (e) => {
927
- isResizing = true;
928
- startX = e.clientX;
929
- startWidth = parseInt(document.defaultView.getComputedStyle(sidebar).width, 10);
930
-
931
- // Add global event listeners
932
- document.addEventListener('mousemove', handleMouseMove);
933
- document.addEventListener('mouseup', handleMouseUp);
934
-
935
- // Prevent text selection during resize
936
- document.body.style.userSelect = 'none';
937
- document.body.style.cursor = 'col-resize';
938
-
939
- e.preventDefault();
940
- });
941
-
942
- function handleMouseMove(e) {
943
- if (!isResizing) return;
944
-
945
- const width = startWidth + e.clientX - startX;
946
-
947
- // Constrain width between 200px and 500px
948
- const constrainedWidth = Math.max(200, Math.min(500, width));
949
-
950
- sidebar.style.width = `${constrainedWidth}px`;
951
- // Don't set margin-left - flexbox handles the layout
952
-
953
- e.preventDefault();
954
- }
955
-
956
- function handleMouseUp() {
957
- if (!isResizing) return;
958
-
959
- isResizing = false;
960
-
961
- // Remove global event listeners
962
- document.removeEventListener('mousemove', handleMouseMove);
963
- document.removeEventListener('mouseup', handleMouseUp);
964
-
965
- // Restore normal cursor and text selection
966
- document.body.style.userSelect = '';
967
- document.body.style.cursor = '';
968
-
969
- // Save the current width
970
- const currentWidth = parseInt(document.defaultView.getComputedStyle(sidebar).width, 10);
971
- localStorage.setItem('sidebarWidth', currentWidth);
972
- }
973
-
974
- // Touch events for mobile support
975
- resizeHandle.addEventListener('touchstart', (e) => {
976
- isResizing = true;
977
- startX = e.touches[0].clientX;
978
- startWidth = parseInt(document.defaultView.getComputedStyle(sidebar).width, 10);
979
-
980
- document.addEventListener('touchmove', handleTouchMove);
981
- document.addEventListener('touchend', handleTouchEnd);
982
-
983
- e.preventDefault();
984
- });
985
-
986
- function handleTouchMove(e) {
987
- if (!isResizing) return;
988
-
989
- const width = startWidth + e.touches[0].clientX - startX;
990
- const constrainedWidth = Math.max(200, Math.min(500, width));
991
-
992
- sidebar.style.width = `${constrainedWidth}px`;
993
- // Don't set margin-left - flexbox handles the layout
994
-
995
- e.preventDefault();
996
- }
997
-
998
- function handleTouchEnd() {
999
- if (!isResizing) return;
1000
-
1001
- isResizing = false;
1002
-
1003
- document.removeEventListener('touchmove', handleTouchMove);
1004
- document.removeEventListener('touchend', handleTouchEnd);
1005
-
1006
- // Save the current width
1007
- const currentWidth = parseInt(document.defaultView.getComputedStyle(sidebar).width, 10);
1008
- localStorage.setItem('sidebarWidth', currentWidth);
1009
- }
1010
- }
1011
-
1012
- // Collapsible Navigation
1013
- function initCollapsibleNavigation() {
1014
- // Debug: Log initial state
1015
- console.log('[Navigation] Initializing collapsible navigation');
1016
- const allNavSections = document.querySelectorAll('.nav-section');
1017
- console.log(`[Navigation] Found ${allNavSections.length} nav sections`);
1018
-
1019
- // First, ensure sections with active items are expanded on page load
1020
- expandActiveNavSections();
1021
-
1022
- // Also run it again after a short delay to handle any timing issues
1023
- setTimeout(() => {
1024
- console.log('[Navigation] Running delayed expandActiveNavSections');
1025
- expandActiveNavSections();
1026
- }, 100);
1027
-
1028
- // Additional fallback: if no active items found, try to expand based on current URL
1029
- setTimeout(() => {
1030
- const activeItems = document.querySelectorAll('.nav-item.active');
1031
- if (activeItems.length === 0) {
1032
- console.log('[Navigation] No active items found, trying URL-based expansion');
1033
- expandSectionByCurrentURL();
1034
- }
1035
- }, 200);
1036
-
1037
- // Handle toggle-all button for root navigation
1038
- const toggleAllButton = document.getElementById('nav-toggle-all');
1039
- if (toggleAllButton) {
1040
- toggleAllButton.addEventListener('click', (e) => {
1041
- e.preventDefault();
1042
-
1043
- const icon = document.getElementById('toggle-all-icon');
1044
- const isExpanded = toggleAllButton.classList.contains('expanded');
1045
-
1046
- // Get all collapsible sections (excluding the root)
1047
- const allSections = document.querySelectorAll('.nav-section[data-level]:not([data-level="0"]) .nav-title.collapsible');
1048
- const allContents = document.querySelectorAll('.nav-section[data-level]:not([data-level="0"]) .nav-content.collapsed, .nav-section[data-level]:not([data-level="0"]) .nav-content:not(.collapsed)');
1049
-
1050
- if (isExpanded) {
1051
- // Collapse all sections
1052
- toggleAllButton.classList.remove('expanded');
1053
- icon.className = 'ph ph-caret-right';
1054
-
1055
- allSections.forEach(section => {
1056
- section.classList.remove('expanded');
1057
- });
1058
-
1059
- allContents.forEach(content => {
1060
- if (content.id) { // Only collapse if it has an id (is collapsible)
1061
- content.classList.add('collapsed');
1062
- }
1063
- });
1064
- } else {
1065
- // Expand all sections
1066
- toggleAllButton.classList.add('expanded');
1067
- icon.className = 'ph ph-caret-down';
1068
-
1069
- allSections.forEach(section => {
1070
- section.classList.add('expanded');
1071
- });
1072
-
1073
- allContents.forEach(content => {
1074
- content.classList.remove('collapsed');
1075
- });
1076
- }
1077
- });
1078
- }
1079
-
1080
- const collapsibleTitles = document.querySelectorAll('.nav-title.collapsible');
1081
-
1082
- collapsibleTitles.forEach(title => {
1083
- title.addEventListener('click', (e) => {
1084
- // Prevent default link behavior for collapsible titles
1085
- e.preventDefault();
1086
-
1087
- // Get the target content to toggle
1088
- const targetId = title.getAttribute('data-target');
1089
- const content = document.getElementById(targetId);
1090
-
1091
- if (content) {
1092
- const isExpanded = title.classList.contains('expanded');
1093
-
1094
- if (isExpanded) {
1095
- // Collapse this section
1096
- title.classList.remove('expanded');
1097
- content.classList.add('collapsed');
1098
-
1099
- // Also collapse all child sections within this content
1100
- const childSections = content.querySelectorAll('.nav-title.collapsible');
1101
- childSections.forEach(childTitle => {
1102
- const childTargetId = childTitle.getAttribute('data-target');
1103
- const childContent = document.getElementById(childTargetId);
1104
- if (childContent) {
1105
- childTitle.classList.remove('expanded');
1106
- childContent.classList.add('collapsed');
1107
- }
1108
- });
1109
- } else {
1110
- // Expand this section
1111
- title.classList.add('expanded');
1112
- content.classList.remove('collapsed');
1113
- }
1114
- }
1115
- });
1116
- });
1117
-
1118
- // Prevent nav items from triggering collapse and maintain parent expansion
1119
- const navItems = document.querySelectorAll('.nav-item');
1120
- navItems.forEach(item => {
1121
- item.addEventListener('click', (e) => {
1122
- // Only stop propagation to prevent collapse, but allow normal link navigation
1123
- e.stopPropagation(); // Prevent event from bubbling up to the nav-title
1124
-
1125
- // Ensure ALL parent sections stay expanded when navigating within them
1126
- let currentElement = item;
1127
- while (currentElement) {
1128
- const parentContent = currentElement.closest('.nav-content');
1129
- if (!parentContent) break;
1130
-
1131
- const parentTitle = parentContent.parentElement?.querySelector('.nav-title.collapsible');
1132
-
1133
- if (parentTitle && parentContent) {
1134
- parentTitle.classList.add('expanded');
1135
- parentContent.classList.remove('collapsed');
1136
- }
1137
-
1138
- // Move up to check for nested sections
1139
- currentElement = parentContent.parentElement;
1140
- }
1141
-
1142
- // Allow normal link navigation - no preventDefault or manual navigation needed
1143
- });
1144
- });
1145
- }
1146
-
1147
- // Expand navigation section based on current URL
1148
- function expandSectionByCurrentURL() {
1149
- try {
1150
- const currentPath = window.location.pathname;
1151
- console.log('[Navigation] Current path:', currentPath);
1152
-
1153
- // Find all nav items and check if any match the current URL
1154
- const navItems = document.querySelectorAll('.nav-item');
1155
- let foundMatch = false;
1156
-
1157
- navItems.forEach(item => {
1158
- const href = item.getAttribute('href');
1159
- if (href) {
1160
- // Normalize paths for comparison
1161
- const itemPath = new URL(href, window.location.href).pathname;
1162
- if (itemPath === currentPath) {
1163
- console.log('[Navigation] Found matching nav item by URL:', item.textContent.trim());
1164
- item.classList.add('active');
1165
- foundMatch = true;
1166
-
1167
- // Expand parent sections
1168
- let currentElement = item;
1169
- while (currentElement && currentElement !== document.body) {
1170
- if (currentElement.classList && currentElement.classList.contains('nav-content')) {
1171
- const parentSection = currentElement.closest('.nav-section');
1172
- if (parentSection) {
1173
- const parentTitle = parentSection.querySelector('.nav-title.collapsible');
1174
- if (parentTitle && currentElement.classList.contains('collapsed')) {
1175
- parentTitle.classList.add('expanded');
1176
- currentElement.classList.remove('collapsed');
1177
- console.log('[Navigation] Expanded section by URL:', parentTitle.textContent.trim());
1178
- }
1179
- }
1180
- }
1181
- currentElement = currentElement.parentElement;
1182
- }
1183
- }
1184
- }
1185
- });
1186
-
1187
- if (!foundMatch) {
1188
- console.warn('[Navigation] No matching nav item found for current URL');
1189
- }
1190
- } catch (error) {
1191
- console.error('[Navigation] Error in expandSectionByCurrentURL:', error);
1192
- }
1193
- }
1194
-
1195
- // Ensure sections containing active nav items stay expanded
1196
- function expandActiveNavSections() {
1197
- try {
1198
- const activeNavItems = document.querySelectorAll('.nav-item.active');
1199
-
1200
- console.log(`[Navigation] Found ${activeNavItems.length} active nav items`);
1201
-
1202
- if (activeNavItems.length === 0) {
1203
- console.warn('[Navigation] No active navigation items found!');
1204
- return;
1205
- }
1206
-
1207
- activeNavItems.forEach(activeItem => {
1208
- console.log(`[Navigation] Expanding sections for: ${activeItem.textContent.trim()}`);
1209
-
1210
- // Start from the active item and work up the DOM tree
1211
- let currentElement = activeItem;
1212
- let sectionsExpanded = 0;
1213
-
1214
- while (currentElement && currentElement !== document.body) {
1215
- // Check if we're inside a nav-content element
1216
- if (currentElement.classList && currentElement.classList.contains('nav-content')) {
1217
- console.log('[Navigation] Found nav-content element with id:', currentElement.id);
1218
-
1219
- // Find the corresponding nav-title in the parent nav-section
1220
- const parentSection = currentElement.closest('.nav-section');
1221
- if (parentSection) {
1222
- const parentTitle = parentSection.querySelector('.nav-title.collapsible');
1223
-
1224
- if (parentTitle && currentElement.classList.contains('collapsed')) {
1225
- // Expand this section
1226
- parentTitle.classList.add('expanded');
1227
- currentElement.classList.remove('collapsed');
1228
- sectionsExpanded++;
1229
- console.log(`[Navigation] Expanded section: ${parentTitle.textContent.trim()}`);
1230
- } else if (parentTitle && !currentElement.classList.contains('collapsed')) {
1231
- console.log(`[Navigation] Section already expanded: ${parentTitle.textContent.trim()}`);
1232
- }
1233
- }
1234
- }
1235
-
1236
- // Move up to the parent element
1237
- currentElement = currentElement.parentElement;
1238
- }
1239
-
1240
- if (sectionsExpanded === 0) {
1241
- console.warn('[Navigation] No sections were expanded for active item:', activeItem.textContent.trim());
1242
- } else {
1243
- console.log(`[Navigation] Successfully expanded ${sectionsExpanded} sections`);
1244
- }
1245
- });
1246
- } catch (error) {
1247
- console.error('[Navigation] Error in expandActiveNavSections:', error);
1248
- }
1249
- }
1250
-
1251
- // Navigation Filter
1252
- function initNavigationFilter() {
1253
- const filterInput = document.getElementById('nav-filter');
1254
- if (!filterInput) return;
1255
-
1256
- filterInput.addEventListener('input', (e) => {
1257
- const query = e.target.value.toLowerCase().trim();
1258
- const navItems = document.querySelectorAll('.nav-item');
1259
- const navSections = document.querySelectorAll('.nav-section');
1260
-
1261
- if (query === '') {
1262
- // Show all items and restore original state
1263
- navItems.forEach(item => {
1264
- item.style.display = 'flex';
1265
- });
1266
- navSections.forEach(section => {
1267
- section.style.display = 'block';
1268
- });
1269
- } else {
1270
- // Filter items
1271
- navItems.forEach(item => {
1272
- const text = item.textContent.toLowerCase();
1273
- const shouldShow = text.includes(query);
1274
- item.style.display = shouldShow ? 'flex' : 'none';
1275
- });
1276
-
1277
- // Show/hide sections based on whether they have visible items
1278
- navSections.forEach(section => {
1279
- const visibleItems = section.querySelectorAll('.nav-item[style*="flex"]');
1280
- const hasVisibleItems = Array.from(section.querySelectorAll('.nav-item')).some(item =>
1281
- item.style.display !== 'none'
1282
- );
1283
- section.style.display = hasVisibleItems ? 'block' : 'none';
1284
-
1285
- // Expand sections with matches
1286
- if (hasVisibleItems && query !== '') {
1287
- const navContent = section.querySelector('.nav-content');
1288
- const navTitle = section.querySelector('.nav-title.collapsible');
1289
- if (navContent && navTitle) {
1290
- navContent.classList.remove('collapsed');
1291
- navTitle.classList.add('expanded');
1292
- }
1293
- }
1294
- });
1295
- }
1296
- });
1297
- }
1298
-
1299
- // PDF Export functionality
1300
- function exportToPDF() {
1301
- // Hide UI elements for printing
1302
- const elementsToHide = [
1303
- '.sidebar',
1304
- '.header',
1305
- '.preview-banner',
1306
- '.resize-handle',
1307
- '.copy-button'
1308
- ];
1309
-
1310
- elementsToHide.forEach(selector => {
1311
- const elements = document.querySelectorAll(selector);
1312
- elements.forEach(el => el.style.display = 'none');
1313
- });
1314
-
1315
- // Adjust content for printing
1316
- const content = document.querySelector('.content');
1317
- const mainWrapper = document.querySelector('.main-wrapper');
1318
-
1319
- if (content) {
1320
- content.style.padding = '20px';
1321
- content.style.maxWidth = 'none';
1322
- }
1323
-
1324
- if (mainWrapper) {
1325
- mainWrapper.style.paddingTop = '0';
1326
- }
1327
-
1328
- // Add print-specific styles
1329
- const printStyles = document.createElement('style');
1330
- printStyles.id = 'print-styles';
1331
- printStyles.textContent = `
1332
- @media print {
1333
- body {
1334
- font-size: 12pt;
1335
- line-height: 1.4;
1336
- color: black !important;
1337
- }
1338
- .content {
1339
- margin: 0 !important;
1340
- padding: 0 !important;
1341
- max-width: none !important;
1342
- }
1343
- .main-wrapper {
1344
- padding-top: 0 !important;
1345
- }
1346
- h1, h2, h3, h4, h5, h6 {
1347
- color: black !important;
1348
- page-break-after: avoid;
1349
- }
1350
- .hero {
1351
- background: none !important;
1352
- color: black !important;
1353
- padding: 20px 0 !important;
1354
- margin: 0 !important;
1355
- }
1356
- .hero h1 {
1357
- color: black !important;
1358
- text-shadow: none !important;
1359
- font-size: 24pt !important;
1360
- }
1361
- .hero-subtitle {
1362
- color: black !important;
1363
- text-shadow: none !important;
1364
- }
1365
- .feature-grid, .metrics-grid {
1366
- display: block !important;
1367
- }
1368
- .feature-card, .metric-card {
1369
- break-inside: avoid;
1370
- margin-bottom: 10px;
1371
- border: 1px solid #ccc;
1372
- padding: 10px;
1373
- }
1374
- .mermaid {
1375
- break-inside: avoid;
1376
- background: white !important;
1377
- border: 1px solid #ccc;
1378
- }
1379
- pre, code {
1380
- background: #f5f5f5 !important;
1381
- border: 1px solid #ddd;
1382
- font-size: 10pt;
1383
- }
1384
- table {
1385
- break-inside: avoid;
1386
- }
1387
- .timeline {
1388
- display: block !important;
1389
- }
1390
- .timeline-item {
1391
- break-inside: avoid;
1392
- margin-bottom: 15px;
1393
- padding-left: 0 !important;
1394
- }
1395
- .timeline::before {
1396
- display: none;
1397
- }
1398
- .timeline-item::before {
1399
- display: none;
1400
- }
1401
- a {
1402
- color: black !important;
1403
- text-decoration: underline;
1404
- }
1405
- .gradient-text {
1406
- color: black !important;
1407
- background: none !important;
1408
- -webkit-text-fill-color: black !important;
1409
- }
1410
- }
1411
- `;
1412
- document.head.appendChild(printStyles);
1413
-
1414
- // Trigger print dialog
1415
- setTimeout(() => {
1416
- window.print();
1417
-
1418
- // Restore UI after print dialog
1419
- setTimeout(() => {
1420
- // Remove print styles
1421
- const printStylesEl = document.getElementById('print-styles');
1422
- if (printStylesEl) {
1423
- printStylesEl.remove();
1424
- }
1425
-
1426
- // Restore hidden elements
1427
- elementsToHide.forEach(selector => {
1428
- const elements = document.querySelectorAll(selector);
1429
- elements.forEach(el => el.style.display = '');
1430
- });
1431
-
1432
- // Restore content styles
1433
- if (content) {
1434
- content.style.padding = '';
1435
- content.style.maxWidth = '';
1436
- }
1437
-
1438
- if (mainWrapper) {
1439
- mainWrapper.style.paddingTop = '';
1440
- }
1441
- }, 500);
1442
- }, 100);
1443
- }
1444
-
1445
- // Add PDF export button functionality
1446
- function addPDFExportButton() {
1447
- // Check configuration - default to true if not set
1448
- const showPdfDownload = window.docBuilderConfig?.features?.showPdfDownload !== false;
1449
- if (!showPdfDownload) return;
1450
-
1451
- const headerActions = document.querySelector('.header-actions');
1452
- if (headerActions) {
1453
- const pdfButton = document.createElement('button');
1454
- pdfButton.innerHTML = '<i class="fas fa-file-pdf"></i>';
1455
- pdfButton.className = 'theme-toggle';
1456
- pdfButton.title = 'Export to PDF';
1457
- pdfButton.setAttribute('aria-label', 'Export to PDF');
1458
- pdfButton.addEventListener('click', exportToPDF);
1459
-
1460
- // Insert before theme toggle
1461
- const themeToggle = document.getElementById('theme-toggle');
1462
- headerActions.insertBefore(pdfButton, themeToggle);
1463
- }
1464
- }
1465
-
1466
- // Breadcrumb Generation
1467
- function generateBreadcrumbs() {
1468
- const breadcrumbContainer = document.getElementById('breadcrumbs');
1469
- if (!breadcrumbContainer) return;
1470
-
1471
- // Check if this is a static build (has data attributes)
1472
- const isStaticBuild = breadcrumbContainer.classList.contains('breadcrumbs-static');
1473
- const buildDate = breadcrumbContainer.getAttribute('data-build-date');
1474
- const docBuilderVersion = breadcrumbContainer.getAttribute('data-doc-builder-version');
1475
-
1476
- // Decode the URL to handle special characters and spaces
1477
- const currentPath = decodeURIComponent(window.location.pathname);
1478
- let pathSegments = currentPath.split('/').filter(segment => segment !== '');
1479
-
1480
- // Find the index of 'html' directory and slice from there
1481
- const htmlIndex = pathSegments.findIndex(segment => segment === 'html');
1482
- if (htmlIndex !== -1) {
1483
- // Remove everything before and including 'html'
1484
- pathSegments = pathSegments.slice(htmlIndex + 1);
1485
- }
1486
-
1487
- // Remove .html extension from the last segment
1488
- if (pathSegments.length > 0) {
1489
- const lastSegment = pathSegments[pathSegments.length - 1];
1490
- if (lastSegment.endsWith('.html')) {
1491
- pathSegments[pathSegments.length - 1] = lastSegment.slice(0, -5);
1492
- }
1493
- }
1494
-
1495
- const breadcrumbs = [];
1496
-
1497
- // Calculate relative path to root for proper navigation
1498
- const depth = pathSegments.length;
1499
- const relativeRoot = depth > 0 ? '../'.repeat(depth) : './';
1500
-
1501
- // Always start with Home (relative to current page)
1502
- breadcrumbs.push({
1503
- text: 'Home',
1504
- href: relativeRoot + 'index.html',
1505
- icon: 'fas fa-home'
1506
- });
1507
-
1508
- // Build breadcrumb path
1509
- let currentUrl = '';
1510
- pathSegments.forEach((segment, index) => {
1511
- currentUrl += '/' + segment;
1512
-
1513
- // Calculate relative path for this breadcrumb level
1514
- const remainingDepth = pathSegments.length - index - 1;
1515
- const relativePath = remainingDepth > 0 ? '../'.repeat(remainingDepth) : './';
1516
-
1517
- // For the last segment, don't add .html back if it's not index
1518
- const href = index === pathSegments.length - 1 && segment !== 'index'
1519
- ? '#' // Current page, no navigation needed
1520
- : relativePath + segment + '.html';
1521
-
1522
- // Prettify segment names
1523
- const text = segment
1524
- .replace(/[-_]/g, ' ')
1525
- .split(' ')
1526
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
1527
- .join(' ')
1528
- .replace(/\b(Api|Html|Css|Js|Pdf|Qa|Ai)\b/g, (match) => match.toUpperCase())
1529
- .replace(/\bReadme\b/g, 'Overview');
1530
-
1531
- // Get appropriate icon based on segment
1532
- let icon = 'fas fa-folder';
1533
- if (segment.includes('bubble')) icon = 'fas fa-circle';
1534
- else if (segment.includes('system')) icon = 'fas fa-sitemap';
1535
- else if (segment.includes('middleware')) icon = 'fas fa-layer-group';
1536
- else if (segment.includes('quickbase')) icon = 'fas fa-database';
1537
- else if (segment.includes('product-roadmap')) icon = 'fas fa-road';
1538
- else if (segment.includes('team')) icon = 'fas fa-users';
1539
- else if (segment.includes('testing')) icon = 'fas fa-vial';
1540
- else if (segment.includes('paths')) icon = 'fas fa-route';
1541
- else if (segment.includes('diagrams')) icon = 'fas fa-project-diagram';
1542
- else if (segment.includes('technical')) icon = 'fas fa-cogs';
1543
- else if (segment.includes('application')) icon = 'fas fa-desktop';
1544
- else if (index === pathSegments.length - 1) icon = 'fas fa-file-alt';
1545
-
1546
- breadcrumbs.push({
1547
- text,
1548
- href,
1549
- icon,
1550
- isLast: index === pathSegments.length - 1
1551
- });
1552
- });
1553
-
1554
- // Generate breadcrumb HTML
1555
- const breadcrumbHTML = breadcrumbs.map((crumb, index) => {
1556
- if (crumb.isLast) {
1557
- return `<span class="breadcrumb-item current">
1558
- <i class="${crumb.icon}"></i>
1559
- <span>${crumb.text}</span>
1560
- </span>`;
1561
- } else {
1562
- return `<a href="${crumb.href}" class="breadcrumb-item">
1563
- <i class="${crumb.icon}"></i>
1564
- <span>${crumb.text}</span>
1565
- </a>`;
1566
- }
1567
- }).join('<i class="fas fa-chevron-right breadcrumb-separator"></i>');
1568
-
1569
- // For static builds, add timestamp and PDF button to breadcrumbs
1570
- if (isStaticBuild) {
1571
- // Use buildDate if available, otherwise use a fallback
1572
- const displayDate = buildDate || 'Documentation';
1573
-
1574
- // Check if we're on the homepage (only "Home" breadcrumb)
1575
- const isHomePage = pathSegments.length === 0 ||
1576
- (pathSegments.length === 1 && (pathSegments[0] === 'index' || pathSegments[0] === 'README'));
1577
-
1578
- if (isHomePage) {
1579
- // On homepage, just show the timestamp and PDF button in a compact bar
1580
- const wrapperHTML = `
1581
- <div class="breadcrumbs-content breadcrumbs-homepage">
1582
- <div class="breadcrumbs-right">
1583
- <span class="breadcrumb-date" title="Built with doc-builder v${docBuilderVersion || ''}">Last updated: ${displayDate}</span>
1584
- <button class="breadcrumb-pdf-btn" onclick="exportToPDF()" title="Export to PDF" aria-label="Export to PDF">
1585
- <i class="fas fa-file-pdf"></i>
1586
- </button>
1587
- </div>
1588
- </div>
1589
- `;
1590
- breadcrumbContainer.innerHTML = wrapperHTML;
1591
- breadcrumbContainer.classList.add('breadcrumbs-minimal');
1592
- } else {
1593
- // Create a wrapper div for layout with breadcrumbs
1594
- const wrapperHTML = `
1595
- <div class="breadcrumbs-content">
1596
- <div class="breadcrumbs-left">
1597
- ${breadcrumbHTML}
1598
- </div>
1599
- <div class="breadcrumbs-right">
1600
- <span class="breadcrumb-date" title="Built with doc-builder v${docBuilderVersion || ''}">Last updated: ${displayDate}</span>
1601
- <button class="breadcrumb-pdf-btn" onclick="exportToPDF()" title="Export to PDF" aria-label="Export to PDF">
1602
- <i class="fas fa-file-pdf"></i>
1603
- </button>
1604
- </div>
1605
- </div>
1606
- `;
1607
- breadcrumbContainer.innerHTML = wrapperHTML;
1608
- }
1609
-
1610
- // Debug logging
1611
- console.log('Static build breadcrumbs generated:', {
1612
- isStaticBuild,
1613
- buildDate,
1614
- isHomePage,
1615
- pathSegments
1616
- });
1617
- } else {
1618
- breadcrumbContainer.innerHTML = breadcrumbHTML;
1619
- }
1620
- }
1621
-
1622
- // Initialize tooltip positioning for navigation items
1623
- function initTooltips() {
1624
- const tooltipElements = document.querySelectorAll('[data-tooltip]');
1625
-
1626
- tooltipElements.forEach(element => {
1627
- element.addEventListener('mouseenter', function(e) {
1628
- const rect = element.getBoundingClientRect();
1629
- const tooltip = window.getComputedStyle(element, '::after');
1630
-
1631
- // Position the tooltip using CSS variables
1632
- element.style.setProperty('--tooltip-left', `${rect.right + 10}px`);
1633
- element.style.setProperty('--tooltip-top', `${rect.top + rect.height / 2}px`);
1634
- });
1635
- });
1636
- }
1637
-
1638
- // Handle .md link redirects
1639
- function initMarkdownLinkRedirects() {
1640
- // Check if current URL ends with .md and redirect
1641
- if (window.location.pathname.endsWith('.md')) {
1642
- const htmlPath = window.location.pathname.replace(/\.md$/, '.html');
1643
- console.log(`Redirecting from .md to .html: ${htmlPath}`);
1644
- window.location.replace(htmlPath);
1645
- return; // Stop execution as we're redirecting
1646
- }
1647
-
1648
- // Intercept clicks on .md links
1649
- document.addEventListener('click', function(e) {
1650
- const link = e.target.closest('a');
1651
- if (link && link.href && link.href.endsWith('.md')) {
1652
- e.preventDefault();
1653
- const htmlUrl = link.href.replace(/\.md$/, '.html');
1654
- console.log(`Converting .md link to .html: ${htmlUrl}`);
1655
- window.location.href = htmlUrl;
1656
- }
1657
- });
1658
- }
1659
-
1660
- // Image Modal System
1661
- function initImageModal() {
1662
- // Create modal HTML structure
1663
- const modalHTML = `
1664
- <div class="image-modal" id="imageModal">
1665
- <div class="image-modal-content">
1666
- <div class="image-modal-close" id="imageModalClose">&times;</div>
1667
- <img class="image-modal-img" id="imageModalImg" src="" alt="">
1668
- <div class="image-modal-caption" id="imageModalCaption"></div>
1669
- </div>
1670
- </div>
1671
- `;
1672
-
1673
- // Add modal to document body
1674
- document.body.insertAdjacentHTML('beforeend', modalHTML);
1675
-
1676
- const modal = document.getElementById('imageModal');
1677
- const modalImg = document.getElementById('imageModalImg');
1678
- const modalCaption = document.getElementById('imageModalCaption');
1679
- const closeBtn = document.getElementById('imageModalClose');
1680
-
1681
- // Add click handlers to all content images
1682
- const contentImages = document.querySelectorAll('.content img');
1683
- contentImages.forEach(img => {
1684
- img.addEventListener('click', function() {
1685
- modal.classList.add('active');
1686
- modalImg.src = this.src;
1687
- modalImg.alt = this.alt;
1688
- modalCaption.textContent = this.alt;
1689
-
1690
- // Hide caption if no alt text
1691
- if (!this.alt) {
1692
- modalCaption.style.display = 'none';
1693
- } else {
1694
- modalCaption.style.display = 'block';
1695
- }
1696
-
1697
- // Prevent body scrolling
1698
- document.body.style.overflow = 'hidden';
1699
- });
1700
- });
1701
-
1702
- // Close modal functions
1703
- function closeModal() {
1704
- modal.classList.remove('active');
1705
- document.body.style.overflow = '';
1706
- }
1707
-
1708
- // Close on X button click
1709
- closeBtn.addEventListener('click', closeModal);
1710
-
1711
- // Close on overlay click (but not on image click)
1712
- modal.addEventListener('click', function(e) {
1713
- if (e.target === modal) {
1714
- closeModal();
1715
- }
1716
- });
1717
-
1718
- // Close on Escape key
1719
- document.addEventListener('keydown', function(e) {
1720
- if (e.key === 'Escape' && modal.classList.contains('active')) {
1721
- closeModal();
1722
- }
1723
- });
1724
-
1725
- // Prevent modal content from closing modal when clicked
1726
- modalImg.addEventListener('click', function(e) {
1727
- e.stopPropagation();
1728
- });
1729
-
1730
- document.querySelector('.image-modal-content').addEventListener('click', function(e) {
1731
- e.stopPropagation();
1732
- });
1733
- }
1734
-
1735
- // Initialize on DOM Load
1736
- document.addEventListener('DOMContentLoaded', () => {
1737
- initMarkdownLinkRedirects();
1738
- highlightNavigation();
1739
- generateTableOfContents();
1740
- initSidebarResize();
1741
- initCollapsibleNavigation();
1742
- initNavigationFilter();
1743
- addPDFExportButton();
1744
- generateBreadcrumbs();
1745
- initImageModal();
1746
- initTooltips();
1747
- });