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