@knowcode/doc-builder 1.9.20 → 1.9.21

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 (86) hide show
  1. package/cli.js +15 -1
  2. package/doc-builder.config.js +4 -2
  3. package/doc-builder.config.js.backup.1754567425847 +124 -0
  4. package/html/README.html +3 -3
  5. package/html/about-doc-builder.html +3 -3
  6. package/html/documentation-index.html +3 -3
  7. package/html/guides/authentication-default-change.html +3 -3
  8. package/html/guides/authentication-guide.html +3 -3
  9. package/html/guides/claude-workflow-guide.html +3 -3
  10. package/html/guides/configuration-guide.html +3 -3
  11. package/html/guides/documentation-standards.html +3 -3
  12. package/html/guides/html-embedding-guide.html +3 -3
  13. package/html/guides/image-modal-guide.html +3 -3
  14. package/html/guides/phosphor-icons-guide.html +3 -3
  15. package/html/guides/private-directory-authentication-troubleshooting.html +3 -3
  16. package/html/guides/private-directory-authentication.html +3 -3
  17. package/html/guides/public-site-deployment.html +3 -3
  18. package/html/guides/search-engine-verification-guide.html +3 -3
  19. package/html/guides/seo-guide.html +3 -3
  20. package/html/guides/seo-optimization-guide.html +3 -3
  21. package/html/guides/supabase-authentication-complete-guide.html +3 -3
  22. package/html/guides/troubleshooting-guide.html +3 -3
  23. package/html/guides/windows-setup-guide.html +3 -3
  24. package/html/image-modal-test.html +3 -3
  25. package/html/index.html +3 -3
  26. package/html/private/cache-control-anti-pattern.html +3 -3
  27. package/html/private/launch/README.html +3 -3
  28. package/html/private/launch/auth-cleanup-summary.html +3 -3
  29. package/html/private/launch/bubble-plugin-specification.html +3 -3
  30. package/html/private/launch/go-to-market-strategy.html +3 -3
  31. package/html/private/launch/launch-announcements.html +3 -3
  32. package/html/private/launch/vercel-deployment-auth-setup.html +3 -3
  33. package/html/private/next-steps-walkthrough.html +3 -3
  34. package/html/private/supabase-auth-implementation-completed.html +3 -3
  35. package/html/private/supabase-auth-implementation-plan.html +3 -3
  36. package/html/private/supabase-auth-integration-plan.html +3 -3
  37. package/html/private/supabase-auth-setup-guide.html +3 -3
  38. package/html/private/test-private-doc.html +3 -3
  39. package/html/private/user-management-tooling.html +3 -3
  40. package/html/prompts/beautiful-documentation-design.html +3 -3
  41. package/html/prompts/markdown-document-standards.html +3 -3
  42. package/html/prompts/project-rename-strategy-sasha-publish.html +3 -3
  43. package/html/sitemap.xml +59 -59
  44. package/html/test-questions/how-does-it-work%3F.html +3 -3
  45. package/html/test-questions/step-1%3A%20getting-started.html +3 -3
  46. package/html/test-questions/what-is-the-purpose.html +3 -3
  47. package/html/vercel-cli-setup-guide.html +3 -3
  48. package/html/vercel-first-time-setup-guide.html +3 -3
  49. package/html-static/404.html +115 -0
  50. package/html-static/README.html +456 -0
  51. package/html-static/about-doc-builder.html +425 -0
  52. package/html-static/css/notion-style.css +2426 -0
  53. package/html-static/documentation-index.html +405 -0
  54. package/html-static/guides/authentication-default-change.html +304 -0
  55. package/html-static/guides/authentication-guide.html +443 -0
  56. package/html-static/guides/claude-workflow-guide.html +1008 -0
  57. package/html-static/guides/configuration-guide.html +406 -0
  58. package/html-static/guides/documentation-standards.html +628 -0
  59. package/html-static/guides/html-embedding-guide.html +395 -0
  60. package/html-static/guides/image-modal-guide.html +449 -0
  61. package/html-static/guides/phosphor-icons-guide.html +518 -0
  62. package/html-static/guides/private-directory-authentication-troubleshooting.html +489 -0
  63. package/html-static/guides/private-directory-authentication.html +475 -0
  64. package/html-static/guides/public-site-deployment.html +365 -0
  65. package/html-static/guides/search-engine-verification-guide.html +476 -0
  66. package/html-static/guides/seo-guide.html +595 -0
  67. package/html-static/guides/seo-optimization-guide.html +821 -0
  68. package/html-static/guides/supabase-authentication-complete-guide.html +800 -0
  69. package/html-static/guides/troubleshooting-guide.html +567 -0
  70. package/html-static/guides/windows-setup-guide.html +793 -0
  71. package/html-static/image-modal-test.html +252 -0
  72. package/html-static/index.html +456 -0
  73. package/html-static/js/main.js +1692 -0
  74. package/html-static/prompts/beautiful-documentation-design.html +718 -0
  75. package/html-static/prompts/markdown-document-standards.html +356 -0
  76. package/html-static/prompts/project-rename-strategy-sasha-publish.html +464 -0
  77. package/html-static/robots.txt +5 -0
  78. package/html-static/sitemap.xml +189 -0
  79. package/html-static/test-questions/how-does-it-work%3F.html +228 -0
  80. package/html-static/test-questions/step-1%3A%20getting-started.html +223 -0
  81. package/html-static/test-questions/what-is-the-purpose.html +227 -0
  82. package/html-static/vercel-cli-setup-guide.html +429 -0
  83. package/html-static/vercel-first-time-setup-guide.html +388 -0
  84. package/lib/config.js +9 -2
  85. package/lib/core-builder.js +148 -4
  86. package/package.json +1 -1
@@ -0,0 +1,1692 @@
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
+ // Decode the URL to handle special characters and spaces
1472
+ const currentPath = decodeURIComponent(window.location.pathname);
1473
+ let pathSegments = currentPath.split('/').filter(segment => segment !== '');
1474
+
1475
+ // Find the index of 'html' directory and slice from there
1476
+ const htmlIndex = pathSegments.findIndex(segment => segment === 'html');
1477
+ if (htmlIndex !== -1) {
1478
+ // Remove everything before and including 'html'
1479
+ pathSegments = pathSegments.slice(htmlIndex + 1);
1480
+ }
1481
+
1482
+ // Remove .html extension from the last segment
1483
+ if (pathSegments.length > 0) {
1484
+ const lastSegment = pathSegments[pathSegments.length - 1];
1485
+ if (lastSegment.endsWith('.html')) {
1486
+ pathSegments[pathSegments.length - 1] = lastSegment.slice(0, -5);
1487
+ }
1488
+ }
1489
+
1490
+ const breadcrumbs = [];
1491
+
1492
+ // Calculate relative path to root for proper navigation
1493
+ const depth = pathSegments.length;
1494
+ const relativeRoot = depth > 0 ? '../'.repeat(depth) : './';
1495
+
1496
+ // Always start with Home (relative to current page)
1497
+ breadcrumbs.push({
1498
+ text: 'Home',
1499
+ href: relativeRoot + 'index.html',
1500
+ icon: 'fas fa-home'
1501
+ });
1502
+
1503
+ // Build breadcrumb path
1504
+ let currentUrl = '';
1505
+ pathSegments.forEach((segment, index) => {
1506
+ currentUrl += '/' + segment;
1507
+
1508
+ // Calculate relative path for this breadcrumb level
1509
+ const remainingDepth = pathSegments.length - index - 1;
1510
+ const relativePath = remainingDepth > 0 ? '../'.repeat(remainingDepth) : './';
1511
+
1512
+ // For the last segment, don't add .html back if it's not index
1513
+ const href = index === pathSegments.length - 1 && segment !== 'index'
1514
+ ? '#' // Current page, no navigation needed
1515
+ : relativePath + segment + '.html';
1516
+
1517
+ // Prettify segment names
1518
+ const text = segment
1519
+ .replace(/[-_]/g, ' ')
1520
+ .split(' ')
1521
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
1522
+ .join(' ')
1523
+ .replace(/\b(Api|Html|Css|Js|Pdf|Qa|Ai)\b/g, (match) => match.toUpperCase())
1524
+ .replace(/\bReadme\b/g, 'Overview');
1525
+
1526
+ // Get appropriate icon based on segment
1527
+ let icon = 'fas fa-folder';
1528
+ if (segment.includes('bubble')) icon = 'fas fa-circle';
1529
+ else if (segment.includes('system')) icon = 'fas fa-sitemap';
1530
+ else if (segment.includes('middleware')) icon = 'fas fa-layer-group';
1531
+ else if (segment.includes('quickbase')) icon = 'fas fa-database';
1532
+ else if (segment.includes('product-roadmap')) icon = 'fas fa-road';
1533
+ else if (segment.includes('team')) icon = 'fas fa-users';
1534
+ else if (segment.includes('testing')) icon = 'fas fa-vial';
1535
+ else if (segment.includes('paths')) icon = 'fas fa-route';
1536
+ else if (segment.includes('diagrams')) icon = 'fas fa-project-diagram';
1537
+ else if (segment.includes('technical')) icon = 'fas fa-cogs';
1538
+ else if (segment.includes('application')) icon = 'fas fa-desktop';
1539
+ else if (index === pathSegments.length - 1) icon = 'fas fa-file-alt';
1540
+
1541
+ breadcrumbs.push({
1542
+ text,
1543
+ href,
1544
+ icon,
1545
+ isLast: index === pathSegments.length - 1
1546
+ });
1547
+ });
1548
+
1549
+ // Generate breadcrumb HTML
1550
+ const breadcrumbHTML = breadcrumbs.map((crumb, index) => {
1551
+ if (crumb.isLast) {
1552
+ return `<span class="breadcrumb-item current">
1553
+ <i class="${crumb.icon}"></i>
1554
+ <span>${crumb.text}</span>
1555
+ </span>`;
1556
+ } else {
1557
+ return `<a href="${crumb.href}" class="breadcrumb-item">
1558
+ <i class="${crumb.icon}"></i>
1559
+ <span>${crumb.text}</span>
1560
+ </a>`;
1561
+ }
1562
+ }).join('<i class="fas fa-chevron-right breadcrumb-separator"></i>');
1563
+
1564
+ breadcrumbContainer.innerHTML = breadcrumbHTML;
1565
+ }
1566
+
1567
+ // Initialize tooltip positioning for navigation items
1568
+ function initTooltips() {
1569
+ const tooltipElements = document.querySelectorAll('[data-tooltip]');
1570
+
1571
+ tooltipElements.forEach(element => {
1572
+ element.addEventListener('mouseenter', function(e) {
1573
+ const rect = element.getBoundingClientRect();
1574
+ const tooltip = window.getComputedStyle(element, '::after');
1575
+
1576
+ // Position the tooltip using CSS variables
1577
+ element.style.setProperty('--tooltip-left', `${rect.right + 10}px`);
1578
+ element.style.setProperty('--tooltip-top', `${rect.top + rect.height / 2}px`);
1579
+ });
1580
+ });
1581
+ }
1582
+
1583
+ // Handle .md link redirects
1584
+ function initMarkdownLinkRedirects() {
1585
+ // Check if current URL ends with .md and redirect
1586
+ if (window.location.pathname.endsWith('.md')) {
1587
+ const htmlPath = window.location.pathname.replace(/\.md$/, '.html');
1588
+ console.log(`Redirecting from .md to .html: ${htmlPath}`);
1589
+ window.location.replace(htmlPath);
1590
+ return; // Stop execution as we're redirecting
1591
+ }
1592
+
1593
+ // Intercept clicks on .md links
1594
+ document.addEventListener('click', function(e) {
1595
+ const link = e.target.closest('a');
1596
+ if (link && link.href && link.href.endsWith('.md')) {
1597
+ e.preventDefault();
1598
+ const htmlUrl = link.href.replace(/\.md$/, '.html');
1599
+ console.log(`Converting .md link to .html: ${htmlUrl}`);
1600
+ window.location.href = htmlUrl;
1601
+ }
1602
+ });
1603
+ }
1604
+
1605
+ // Image Modal System
1606
+ function initImageModal() {
1607
+ // Create modal HTML structure
1608
+ const modalHTML = `
1609
+ <div class="image-modal" id="imageModal">
1610
+ <div class="image-modal-content">
1611
+ <div class="image-modal-close" id="imageModalClose">&times;</div>
1612
+ <img class="image-modal-img" id="imageModalImg" src="" alt="">
1613
+ <div class="image-modal-caption" id="imageModalCaption"></div>
1614
+ </div>
1615
+ </div>
1616
+ `;
1617
+
1618
+ // Add modal to document body
1619
+ document.body.insertAdjacentHTML('beforeend', modalHTML);
1620
+
1621
+ const modal = document.getElementById('imageModal');
1622
+ const modalImg = document.getElementById('imageModalImg');
1623
+ const modalCaption = document.getElementById('imageModalCaption');
1624
+ const closeBtn = document.getElementById('imageModalClose');
1625
+
1626
+ // Add click handlers to all content images
1627
+ const contentImages = document.querySelectorAll('.content img');
1628
+ contentImages.forEach(img => {
1629
+ img.addEventListener('click', function() {
1630
+ modal.classList.add('active');
1631
+ modalImg.src = this.src;
1632
+ modalImg.alt = this.alt;
1633
+ modalCaption.textContent = this.alt;
1634
+
1635
+ // Hide caption if no alt text
1636
+ if (!this.alt) {
1637
+ modalCaption.style.display = 'none';
1638
+ } else {
1639
+ modalCaption.style.display = 'block';
1640
+ }
1641
+
1642
+ // Prevent body scrolling
1643
+ document.body.style.overflow = 'hidden';
1644
+ });
1645
+ });
1646
+
1647
+ // Close modal functions
1648
+ function closeModal() {
1649
+ modal.classList.remove('active');
1650
+ document.body.style.overflow = '';
1651
+ }
1652
+
1653
+ // Close on X button click
1654
+ closeBtn.addEventListener('click', closeModal);
1655
+
1656
+ // Close on overlay click (but not on image click)
1657
+ modal.addEventListener('click', function(e) {
1658
+ if (e.target === modal) {
1659
+ closeModal();
1660
+ }
1661
+ });
1662
+
1663
+ // Close on Escape key
1664
+ document.addEventListener('keydown', function(e) {
1665
+ if (e.key === 'Escape' && modal.classList.contains('active')) {
1666
+ closeModal();
1667
+ }
1668
+ });
1669
+
1670
+ // Prevent modal content from closing modal when clicked
1671
+ modalImg.addEventListener('click', function(e) {
1672
+ e.stopPropagation();
1673
+ });
1674
+
1675
+ document.querySelector('.image-modal-content').addEventListener('click', function(e) {
1676
+ e.stopPropagation();
1677
+ });
1678
+ }
1679
+
1680
+ // Initialize on DOM Load
1681
+ document.addEventListener('DOMContentLoaded', () => {
1682
+ initMarkdownLinkRedirects();
1683
+ highlightNavigation();
1684
+ generateTableOfContents();
1685
+ initSidebarResize();
1686
+ initCollapsibleNavigation();
1687
+ initNavigationFilter();
1688
+ addPDFExportButton();
1689
+ generateBreadcrumbs();
1690
+ initImageModal();
1691
+ initTooltips();
1692
+ });