@knowcode/doc-builder 1.9.30 → 1.10.0

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 (198) hide show
  1. package/lib/core-builder.js +164 -174
  2. package/lib/emoji-mapper.js +27 -12
  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/doc-builder.config.js.backup.1755034809236 +0 -126
  36. package/grant-access.sql +0 -15
  37. package/html/11.png +0 -0
  38. package/html/404.html +0 -115
  39. package/html/README.html +0 -522
  40. package/html/Screenshot 2025-08-12 at 21.35.07.png +0 -0
  41. package/html/about-doc-builder.html +0 -491
  42. package/html/auth.js +0 -157
  43. package/html/claude-workflow-guide.html +0 -525
  44. package/html/css/notion-style.css +0 -2502
  45. package/html/documentation-index.html +0 -471
  46. package/html/guides/authentication-default-change.html +0 -370
  47. package/html/guides/authentication-guide.html +0 -509
  48. package/html/guides/cache-control-anti-pattern.html +0 -361
  49. package/html/guides/claude-workflow-guide.html +0 -1074
  50. package/html/guides/configuration-guide.html +0 -472
  51. package/html/guides/document-standards.html +0 -518
  52. package/html/guides/documentation-standards.html +0 -694
  53. package/html/guides/html-embedding-guide.html +0 -461
  54. package/html/guides/image-modal-guide.html +0 -515
  55. package/html/guides/next-steps-walkthrough.html +0 -638
  56. package/html/guides/phosphor-icons-guide.html +0 -584
  57. package/html/guides/private-directory-authentication-troubleshooting.html +0 -555
  58. package/html/guides/private-directory-authentication.html +0 -541
  59. package/html/guides/public-site-deployment.html +0 -431
  60. package/html/guides/search-engine-verification-guide.html +0 -542
  61. package/html/guides/seo-guide.html +0 -661
  62. package/html/guides/seo-optimization-guide.html +0 -887
  63. package/html/guides/supabase-auth-implementation-plan.html +0 -543
  64. package/html/guides/supabase-auth-integration-plan.html +0 -671
  65. package/html/guides/supabase-auth-setup-guide.html +0 -498
  66. package/html/guides/supabase-authentication-complete-guide.html +0 -866
  67. package/html/guides/troubleshooting-guide.html +0 -633
  68. package/html/guides/vercel-deployment-auth-setup.html +0 -337
  69. package/html/guides/windows-setup-guide.html +0 -859
  70. package/html/image-modal-test.html +0 -318
  71. package/html/index.html +0 -522
  72. package/html/js/auth.js +0 -157
  73. package/html/js/main.js +0 -1754
  74. package/html/launch/README.html +0 -297
  75. package/html/launch/bubble-plugin-specification.html +0 -933
  76. package/html/launch/go-to-market-strategy.html +0 -663
  77. package/html/launch/launch-announcements.html +0 -593
  78. package/html/login.html +0 -102
  79. package/html/logout.html +0 -18
  80. package/html/private/cache-control-anti-pattern.html +0 -429
  81. package/html/private/launch/README.html +0 -371
  82. package/html/private/launch/auth-cleanup-summary.html +0 -361
  83. package/html/private/launch/bubble-plugin-specification.html +0 -1007
  84. package/html/private/launch/go-to-market-strategy.html +0 -737
  85. package/html/private/launch/launch-announcements.html +0 -667
  86. package/html/private/launch/vercel-deployment-auth-setup.html +0 -417
  87. package/html/private/next-steps-walkthrough.html +0 -679
  88. package/html/private/supabase-auth-implementation-completed.html +0 -454
  89. package/html/private/supabase-auth-implementation-plan.html +0 -594
  90. package/html/private/supabase-auth-integration-plan.html +0 -704
  91. package/html/private/supabase-auth-setup-guide.html +0 -555
  92. package/html/private/test-private-doc.html +0 -302
  93. package/html/private/user-management-tooling.html +0 -601
  94. package/html/prompts/Screenshot 2025-08-02 at 08.49.55.png +0 -0
  95. package/html/prompts/beautiful-documentation-design.html +0 -784
  96. package/html/prompts/markdown-document-standards.html +0 -422
  97. package/html/prompts/project-rename-strategy-sasha-publish.html +0 -530
  98. package/html/robots.txt +0 -9
  99. package/html/sitemap.xml +0 -357
  100. package/html/test-questions/how-does-it-work%3F.html +0 -294
  101. package/html/test-questions/step-1%3A%20getting-started.html +0 -289
  102. package/html/test-questions/what-is-the-purpose.html +0 -293
  103. package/html/test-status.html +0 -281
  104. package/html/vercel-cli-setup-guide.html +0 -495
  105. package/html/vercel-first-time-setup-guide.html +0 -454
  106. package/html/vercel.json +0 -29
  107. package/html-static/11.png +0 -0
  108. package/html-static/404.html +0 -115
  109. package/html-static/README.html +0 -609
  110. package/html-static/Screenshot 2025-08-12 at 21.35.07.png +0 -0
  111. package/html-static/about-doc-builder.html +0 -578
  112. package/html-static/css/notion-style.css +0 -2502
  113. package/html-static/documentation-index.html +0 -558
  114. package/html-static/guides/authentication-default-change.html +0 -457
  115. package/html-static/guides/authentication-guide.html +0 -596
  116. package/html-static/guides/claude-workflow-guide.html +0 -1161
  117. package/html-static/guides/configuration-guide.html +0 -559
  118. package/html-static/guides/documentation-standards.html +0 -781
  119. package/html-static/guides/html-embedding-guide.html +0 -548
  120. package/html-static/guides/image-modal-guide.html +0 -602
  121. package/html-static/guides/phosphor-icons-guide.html +0 -671
  122. package/html-static/guides/private-directory-authentication-troubleshooting.html +0 -642
  123. package/html-static/guides/private-directory-authentication.html +0 -628
  124. package/html-static/guides/public-site-deployment.html +0 -518
  125. package/html-static/guides/search-engine-verification-guide.html +0 -629
  126. package/html-static/guides/seo-guide.html +0 -748
  127. package/html-static/guides/seo-optimization-guide.html +0 -974
  128. package/html-static/guides/supabase-authentication-complete-guide.html +0 -953
  129. package/html-static/guides/troubleshooting-guide.html +0 -720
  130. package/html-static/guides/windows-setup-guide.html +0 -946
  131. package/html-static/image-modal-test.html +0 -405
  132. package/html-static/index.html +0 -609
  133. package/html-static/js/main.js +0 -1754
  134. package/html-static/prompts/Screenshot 2025-08-02 at 08.49.55.png +0 -0
  135. package/html-static/prompts/beautiful-documentation-design.html +0 -871
  136. package/html-static/prompts/markdown-document-standards.html +0 -509
  137. package/html-static/prompts/project-rename-strategy-sasha-publish.html +0 -617
  138. package/html-static/robots.txt +0 -5
  139. package/html-static/sitemap.xml +0 -195
  140. package/html-static/test-questions/how-does-it-work%3F.html +0 -381
  141. package/html-static/test-questions/step-1%3A%20getting-started.html +0 -376
  142. package/html-static/test-questions/what-is-the-purpose.html +0 -380
  143. package/html-static/vercel-cli-setup-guide.html +0 -582
  144. package/html-static/vercel-first-time-setup-guide.html +0 -541
  145. package/manage-users.sql +0 -191
  146. package/migrate-to-domain-auth.sql +0 -47
  147. package/package/CACHE-BUSTING-GUIDE.md +0 -82
  148. package/package/CHANGELOG.md +0 -902
  149. package/package/README.md +0 -248
  150. package/package/assets/css/notion-style.css +0 -2211
  151. package/package/assets/js/auth.js +0 -67
  152. package/package/assets/js/main.js +0 -1565
  153. package/package/cli.js +0 -764
  154. package/package/index.js +0 -38
  155. package/package/knowcode-doc-builder-1.3.15.tgz +0 -0
  156. package/package/lib/builder.js +0 -32
  157. package/package/lib/config.js +0 -278
  158. package/package/lib/core-builder.js +0 -957
  159. package/package/lib/deploy.js +0 -497
  160. package/package/lib/dev-server.js +0 -96
  161. package/package/package.json +0 -34
  162. package/package/scripts/npx-runner.js +0 -27
  163. package/package/scripts/setup.js +0 -56
  164. package/package/test-cache-bust.sh +0 -43
  165. package/public-config.js +0 -22
  166. package/public-html/404.html +0 -115
  167. package/public-html/README.html +0 -149
  168. package/public-html/css/notion-style.css +0 -2036
  169. package/public-html/index.html +0 -149
  170. package/public-html/js/auth.js +0 -67
  171. package/public-html/js/main.js +0 -1485
  172. package/quick-test-commands.md +0 -40
  173. package/recordings/Screenshot 2025-07-24 at 18.22.01.png +0 -0
  174. package/recordings/mh-ls-22jul.txt +0 -2305
  175. package/screenshot.png +0 -0
  176. package/scripts/Screenshot 2025-07-23 at 15.39.41.png +0 -0
  177. package/setup-database-v2.sql +0 -53
  178. package/setup-database.sql +0 -41
  179. package/test-auth-config.js +0 -17
  180. package/test-cache-bust.sh +0 -43
  181. package/test-docs/README.md +0 -39
  182. package/test-html/404.html +0 -115
  183. package/test-html/README.html +0 -172
  184. package/test-html/auth.js +0 -97
  185. package/test-html/css/notion-style.css +0 -2036
  186. package/test-html/index.html +0 -172
  187. package/test-html/js/auth.js +0 -97
  188. package/test-html/js/main.js +0 -1485
  189. package/test-html/login.html +0 -102
  190. package/test-html/logout.html +0 -18
  191. package/update-domain.sql +0 -9
  192. package/user-access-view.sql +0 -49
  193. package/user-management/README.md +0 -301
  194. package/user-management/add-users.sh +0 -776
  195. package/user-management/create-user.js +0 -65
  196. package/user-management/users.txt +0 -15
  197. package/view-all-users.sql +0 -40
  198. package/wru-auth-config.js +0 -17
@@ -1,1754 +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
- // For static builds, check if content is already pre-rendered
1477
- if (isStaticBuild && breadcrumbContainer.querySelector('.breadcrumbs-content')) {
1478
- // Content is pre-rendered, just ensure exportToPDF is available globally
1479
- window.exportToPDF = exportToPDF;
1480
- return; // Don't override pre-rendered content
1481
- }
1482
-
1483
- // Decode the URL to handle special characters and spaces
1484
- const currentPath = decodeURIComponent(window.location.pathname);
1485
- let pathSegments = currentPath.split('/').filter(segment => segment !== '');
1486
-
1487
- // Find the index of 'html' directory and slice from there
1488
- const htmlIndex = pathSegments.findIndex(segment => segment === 'html');
1489
- if (htmlIndex !== -1) {
1490
- // Remove everything before and including 'html'
1491
- pathSegments = pathSegments.slice(htmlIndex + 1);
1492
- }
1493
-
1494
- // Remove .html extension from the last segment
1495
- if (pathSegments.length > 0) {
1496
- const lastSegment = pathSegments[pathSegments.length - 1];
1497
- if (lastSegment.endsWith('.html')) {
1498
- pathSegments[pathSegments.length - 1] = lastSegment.slice(0, -5);
1499
- }
1500
- }
1501
-
1502
- const breadcrumbs = [];
1503
-
1504
- // Calculate relative path to root for proper navigation
1505
- const depth = pathSegments.length;
1506
- const relativeRoot = depth > 0 ? '../'.repeat(depth) : './';
1507
-
1508
- // Always start with Home (relative to current page)
1509
- breadcrumbs.push({
1510
- text: 'Home',
1511
- href: relativeRoot + 'index.html',
1512
- icon: 'fas fa-home'
1513
- });
1514
-
1515
- // Build breadcrumb path
1516
- let currentUrl = '';
1517
- pathSegments.forEach((segment, index) => {
1518
- currentUrl += '/' + segment;
1519
-
1520
- // Calculate relative path for this breadcrumb level
1521
- const remainingDepth = pathSegments.length - index - 1;
1522
- const relativePath = remainingDepth > 0 ? '../'.repeat(remainingDepth) : './';
1523
-
1524
- // For the last segment, don't add .html back if it's not index
1525
- const href = index === pathSegments.length - 1 && segment !== 'index'
1526
- ? '#' // Current page, no navigation needed
1527
- : relativePath + segment + '.html';
1528
-
1529
- // Prettify segment names
1530
- const text = segment
1531
- .replace(/[-_]/g, ' ')
1532
- .split(' ')
1533
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
1534
- .join(' ')
1535
- .replace(/\b(Api|Html|Css|Js|Pdf|Qa|Ai)\b/g, (match) => match.toUpperCase())
1536
- .replace(/\bReadme\b/g, 'Overview');
1537
-
1538
- // Get appropriate icon based on segment
1539
- let icon = 'fas fa-folder';
1540
- if (segment.includes('bubble')) icon = 'fas fa-circle';
1541
- else if (segment.includes('system')) icon = 'fas fa-sitemap';
1542
- else if (segment.includes('middleware')) icon = 'fas fa-layer-group';
1543
- else if (segment.includes('quickbase')) icon = 'fas fa-database';
1544
- else if (segment.includes('product-roadmap')) icon = 'fas fa-road';
1545
- else if (segment.includes('team')) icon = 'fas fa-users';
1546
- else if (segment.includes('testing')) icon = 'fas fa-vial';
1547
- else if (segment.includes('paths')) icon = 'fas fa-route';
1548
- else if (segment.includes('diagrams')) icon = 'fas fa-project-diagram';
1549
- else if (segment.includes('technical')) icon = 'fas fa-cogs';
1550
- else if (segment.includes('application')) icon = 'fas fa-desktop';
1551
- else if (index === pathSegments.length - 1) icon = 'fas fa-file-alt';
1552
-
1553
- breadcrumbs.push({
1554
- text,
1555
- href,
1556
- icon,
1557
- isLast: index === pathSegments.length - 1
1558
- });
1559
- });
1560
-
1561
- // Generate breadcrumb HTML
1562
- const breadcrumbHTML = breadcrumbs.map((crumb, index) => {
1563
- if (crumb.isLast) {
1564
- return `<span class="breadcrumb-item current">
1565
- <i class="${crumb.icon}"></i>
1566
- <span>${crumb.text}</span>
1567
- </span>`;
1568
- } else {
1569
- return `<a href="${crumb.href}" class="breadcrumb-item">
1570
- <i class="${crumb.icon}"></i>
1571
- <span>${crumb.text}</span>
1572
- </a>`;
1573
- }
1574
- }).join('<i class="fas fa-chevron-right breadcrumb-separator"></i>');
1575
-
1576
- // For static builds, add timestamp and PDF button to breadcrumbs
1577
- if (isStaticBuild) {
1578
- // Use buildDate if available, otherwise use a fallback
1579
- const displayDate = buildDate || 'Documentation';
1580
-
1581
- // Check if we're on the homepage (only "Home" breadcrumb)
1582
- const isHomePage = pathSegments.length === 0 ||
1583
- (pathSegments.length === 1 && (pathSegments[0] === 'index' || pathSegments[0] === 'README'));
1584
-
1585
- if (isHomePage) {
1586
- // On homepage, just show the timestamp and PDF button in a compact bar
1587
- const wrapperHTML = `
1588
- <div class="breadcrumbs-content breadcrumbs-homepage">
1589
- <div class="breadcrumbs-right">
1590
- <span class="breadcrumb-date" title="Built with doc-builder v${docBuilderVersion || ''}">Last updated: ${displayDate}</span>
1591
- <button class="breadcrumb-pdf-btn" onclick="exportToPDF()" title="Export to PDF" aria-label="Export to PDF">
1592
- <i class="fas fa-file-pdf"></i>
1593
- </button>
1594
- </div>
1595
- </div>
1596
- `;
1597
- breadcrumbContainer.innerHTML = wrapperHTML;
1598
- breadcrumbContainer.classList.add('breadcrumbs-minimal');
1599
- } else {
1600
- // Create a wrapper div for layout with breadcrumbs
1601
- const wrapperHTML = `
1602
- <div class="breadcrumbs-content">
1603
- <div class="breadcrumbs-left">
1604
- ${breadcrumbHTML}
1605
- </div>
1606
- <div class="breadcrumbs-right">
1607
- <span class="breadcrumb-date" title="Built with doc-builder v${docBuilderVersion || ''}">Last updated: ${displayDate}</span>
1608
- <button class="breadcrumb-pdf-btn" onclick="exportToPDF()" title="Export to PDF" aria-label="Export to PDF">
1609
- <i class="fas fa-file-pdf"></i>
1610
- </button>
1611
- </div>
1612
- </div>
1613
- `;
1614
- breadcrumbContainer.innerHTML = wrapperHTML;
1615
- }
1616
-
1617
- // Debug logging
1618
- console.log('Static build breadcrumbs generated:', {
1619
- isStaticBuild,
1620
- buildDate,
1621
- isHomePage,
1622
- pathSegments
1623
- });
1624
- } else {
1625
- breadcrumbContainer.innerHTML = breadcrumbHTML;
1626
- }
1627
- }
1628
-
1629
- // Initialize tooltip positioning for navigation items
1630
- function initTooltips() {
1631
- const tooltipElements = document.querySelectorAll('[data-tooltip]');
1632
-
1633
- tooltipElements.forEach(element => {
1634
- element.addEventListener('mouseenter', function(e) {
1635
- const rect = element.getBoundingClientRect();
1636
- const tooltip = window.getComputedStyle(element, '::after');
1637
-
1638
- // Position the tooltip using CSS variables
1639
- element.style.setProperty('--tooltip-left', `${rect.right + 10}px`);
1640
- element.style.setProperty('--tooltip-top', `${rect.top + rect.height / 2}px`);
1641
- });
1642
- });
1643
- }
1644
-
1645
- // Handle .md link redirects
1646
- function initMarkdownLinkRedirects() {
1647
- // Check if current URL ends with .md and redirect
1648
- if (window.location.pathname.endsWith('.md')) {
1649
- const htmlPath = window.location.pathname.replace(/\.md$/, '.html');
1650
- console.log(`Redirecting from .md to .html: ${htmlPath}`);
1651
- window.location.replace(htmlPath);
1652
- return; // Stop execution as we're redirecting
1653
- }
1654
-
1655
- // Intercept clicks on .md links
1656
- document.addEventListener('click', function(e) {
1657
- const link = e.target.closest('a');
1658
- if (link && link.href && link.href.endsWith('.md')) {
1659
- e.preventDefault();
1660
- const htmlUrl = link.href.replace(/\.md$/, '.html');
1661
- console.log(`Converting .md link to .html: ${htmlUrl}`);
1662
- window.location.href = htmlUrl;
1663
- }
1664
- });
1665
- }
1666
-
1667
- // Image Modal System
1668
- function initImageModal() {
1669
- // Create modal HTML structure
1670
- const modalHTML = `
1671
- <div class="image-modal" id="imageModal">
1672
- <div class="image-modal-content">
1673
- <div class="image-modal-close" id="imageModalClose">&times;</div>
1674
- <img class="image-modal-img" id="imageModalImg" src="" alt="">
1675
- <div class="image-modal-caption" id="imageModalCaption"></div>
1676
- </div>
1677
- </div>
1678
- `;
1679
-
1680
- // Add modal to document body
1681
- document.body.insertAdjacentHTML('beforeend', modalHTML);
1682
-
1683
- const modal = document.getElementById('imageModal');
1684
- const modalImg = document.getElementById('imageModalImg');
1685
- const modalCaption = document.getElementById('imageModalCaption');
1686
- const closeBtn = document.getElementById('imageModalClose');
1687
-
1688
- // Add click handlers to all content images
1689
- const contentImages = document.querySelectorAll('.content img');
1690
- contentImages.forEach(img => {
1691
- img.addEventListener('click', function() {
1692
- modal.classList.add('active');
1693
- modalImg.src = this.src;
1694
- modalImg.alt = this.alt;
1695
- modalCaption.textContent = this.alt;
1696
-
1697
- // Hide caption if no alt text
1698
- if (!this.alt) {
1699
- modalCaption.style.display = 'none';
1700
- } else {
1701
- modalCaption.style.display = 'block';
1702
- }
1703
-
1704
- // Prevent body scrolling
1705
- document.body.style.overflow = 'hidden';
1706
- });
1707
- });
1708
-
1709
- // Close modal functions
1710
- function closeModal() {
1711
- modal.classList.remove('active');
1712
- document.body.style.overflow = '';
1713
- }
1714
-
1715
- // Close on X button click
1716
- closeBtn.addEventListener('click', closeModal);
1717
-
1718
- // Close on overlay click (but not on image click)
1719
- modal.addEventListener('click', function(e) {
1720
- if (e.target === modal) {
1721
- closeModal();
1722
- }
1723
- });
1724
-
1725
- // Close on Escape key
1726
- document.addEventListener('keydown', function(e) {
1727
- if (e.key === 'Escape' && modal.classList.contains('active')) {
1728
- closeModal();
1729
- }
1730
- });
1731
-
1732
- // Prevent modal content from closing modal when clicked
1733
- modalImg.addEventListener('click', function(e) {
1734
- e.stopPropagation();
1735
- });
1736
-
1737
- document.querySelector('.image-modal-content').addEventListener('click', function(e) {
1738
- e.stopPropagation();
1739
- });
1740
- }
1741
-
1742
- // Initialize on DOM Load
1743
- document.addEventListener('DOMContentLoaded', () => {
1744
- initMarkdownLinkRedirects();
1745
- highlightNavigation();
1746
- generateTableOfContents();
1747
- initSidebarResize();
1748
- initCollapsibleNavigation();
1749
- initNavigationFilter();
1750
- addPDFExportButton();
1751
- generateBreadcrumbs();
1752
- initImageModal();
1753
- initTooltips();
1754
- });