vyasa 0.3.6__py3-none-any.whl

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.
@@ -0,0 +1,1202 @@
1
+ import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
2
+
3
+ const mermaidStates = {};
4
+ const mermaidDebugEnabled = () => (
5
+ window.VYASA_DEBUG_MERMAID === true ||
6
+ localStorage.getItem('vyasaDebugMermaid') === '1'
7
+ );
8
+ const mermaidDebugLog = (...args) => {
9
+ if (mermaidDebugEnabled()) {
10
+ console.log('[vyasa][mermaid]', ...args);
11
+ }
12
+ };
13
+ const mermaidDebugSnapshot = (label) => {
14
+ if (!mermaidDebugEnabled()) {
15
+ return;
16
+ }
17
+ const wrappers = Array.from(document.querySelectorAll('.mermaid-wrapper'));
18
+ const withSvg = wrappers.filter(w => w.querySelector('svg'));
19
+ const interactive = wrappers.filter(w => w.dataset.mermaidInteractive === 'true');
20
+ const last = wrappers[wrappers.length - 1];
21
+ let lastRect = null;
22
+ if (last) {
23
+ const rect = last.getBoundingClientRect();
24
+ lastRect = {
25
+ id: last.id,
26
+ width: Math.round(rect.width),
27
+ height: Math.round(rect.height),
28
+ hasSvg: !!last.querySelector('svg'),
29
+ interactive: last.dataset.mermaidInteractive === 'true'
30
+ };
31
+ }
32
+ mermaidDebugLog(label, {
33
+ total: wrappers.length,
34
+ withSvg: withSvg.length,
35
+ interactive: interactive.length,
36
+ last: lastRect
37
+ });
38
+ };
39
+ const GANTT_WIDTH = 1200;
40
+
41
+ function handleCodeCopyClick(event) {
42
+ const button = event.target.closest('.code-copy-button, .hljs-copy-button');
43
+ if (!button) {
44
+ return;
45
+ }
46
+ event.preventDefault();
47
+ event.stopPropagation();
48
+ const container = button.closest('.code-block') || button.closest('pre') || button.parentElement;
49
+ const textarea = container ? container.querySelector('textarea[id$="-clipboard"]') : null;
50
+ let text = '';
51
+ if (textarea && textarea.value) {
52
+ text = textarea.value;
53
+ } else {
54
+ const codeEl = (container && container.querySelector('pre > code')) ||
55
+ (container && container.querySelector('code')) ||
56
+ button.closest('pre');
57
+ if (!codeEl) {
58
+ return;
59
+ }
60
+ text = codeEl.innerText || codeEl.textContent || '';
61
+ }
62
+ const showToast = () => {
63
+ let toast = document.getElementById('code-copy-toast');
64
+ if (!toast) {
65
+ toast = document.createElement('div');
66
+ toast.id = 'code-copy-toast';
67
+ toast.className = 'fixed top-6 right-6 z-[10000] text-xs bg-slate-900 text-white px-3 py-2 rounded shadow-lg opacity-0 transition-opacity duration-300';
68
+ toast.textContent = 'Copied';
69
+ document.body.appendChild(toast);
70
+ }
71
+ toast.classList.remove('opacity-0');
72
+ toast.classList.add('opacity-100');
73
+ setTimeout(() => {
74
+ toast.classList.remove('opacity-100');
75
+ toast.classList.add('opacity-0');
76
+ }, 1400);
77
+ };
78
+ if (navigator.clipboard && window.isSecureContext) {
79
+ navigator.clipboard.writeText(text).then(showToast).catch(() => {
80
+ const textarea = document.createElement('textarea');
81
+ textarea.value = text;
82
+ textarea.setAttribute('readonly', '');
83
+ textarea.style.position = 'absolute';
84
+ textarea.style.left = '-9999px';
85
+ document.body.appendChild(textarea);
86
+ textarea.select();
87
+ document.execCommand('copy');
88
+ document.body.removeChild(textarea);
89
+ showToast();
90
+ });
91
+ } else {
92
+ const textarea = document.createElement('textarea');
93
+ textarea.value = text;
94
+ textarea.setAttribute('readonly', '');
95
+ textarea.style.position = 'absolute';
96
+ textarea.style.left = '-9999px';
97
+ document.body.appendChild(textarea);
98
+ textarea.select();
99
+ document.execCommand('copy');
100
+ document.body.removeChild(textarea);
101
+ showToast();
102
+ }
103
+ }
104
+
105
+ document.addEventListener('click', handleCodeCopyClick, true);
106
+
107
+ function initMermaidInteraction() {
108
+ const wrappers = Array.from(document.querySelectorAll('.mermaid-wrapper'));
109
+ if (mermaidDebugEnabled()) {
110
+ const pending = wrappers.filter(w => !w.querySelector('svg'));
111
+ const last = wrappers[wrappers.length - 1];
112
+ mermaidDebugLog('initMermaidInteraction: total', wrappers.length, 'pending', pending.length);
113
+ if (last) {
114
+ mermaidDebugLog('initMermaidInteraction: last wrapper', last.id, 'hasSvg', !!last.querySelector('svg'));
115
+ }
116
+ }
117
+ wrappers.forEach((wrapper, idx) => {
118
+ const svg = wrapper.querySelector('svg');
119
+ const alreadyInteractive = wrapper.dataset.mermaidInteractive === 'true';
120
+ if (mermaidDebugEnabled()) {
121
+ mermaidDebugLog(
122
+ 'initMermaidInteraction: wrapper',
123
+ idx,
124
+ wrapper.id,
125
+ 'hasSvg',
126
+ !!svg,
127
+ 'interactive',
128
+ alreadyInteractive
129
+ );
130
+ }
131
+ const getSvg = () => wrapper.querySelector('svg');
132
+ const applySvgState = (currentSvg) => {
133
+ if (!currentSvg) {
134
+ return;
135
+ }
136
+ currentSvg.style.pointerEvents = 'none';
137
+ currentSvg.style.transform = `translate(${state.translateX}px, ${state.translateY}px) scale(${state.scale})`;
138
+ currentSvg.style.transformOrigin = 'center center';
139
+ };
140
+ if (svg) {
141
+ svg.style.pointerEvents = 'none';
142
+ }
143
+ if (!svg || alreadyInteractive) return;
144
+
145
+ // DEBUG: Log initial state
146
+ console.group(`🔍 initMermaidInteraction: ${wrapper.id}`);
147
+ console.log('Theme:', getCurrentTheme());
148
+ console.log('Wrapper computed style height:', window.getComputedStyle(wrapper).height);
149
+ console.log('Wrapper inline style:', wrapper.getAttribute('style'));
150
+
151
+ // Scale SVG to fit container (maintain aspect ratio, fit to width or height whichever is smaller)
152
+ const wrapperRect = wrapper.getBoundingClientRect();
153
+ const svgRect = svg.getBoundingClientRect();
154
+ console.log('Wrapper rect:', { width: wrapperRect.width, height: wrapperRect.height });
155
+ console.log('SVG rect:', { width: svgRect.width, height: svgRect.height });
156
+
157
+ const scaleX = (wrapperRect.width - 32) / svgRect.width; // 32 for p-4 padding (16px each side)
158
+ const scaleY = (wrapperRect.height - 32) / svgRect.height;
159
+ console.log('Scale factors:', { scaleX, scaleY });
160
+
161
+ // For very wide diagrams (like Gantt charts), prefer width scaling even if it exceeds height
162
+ const aspectRatio = svgRect.width / svgRect.height;
163
+ const maxUpscale = 1;
164
+ let initialScale;
165
+ if (aspectRatio > 3) {
166
+ // Wide diagram: scale to fit width, but do not upscale by default
167
+ initialScale = Math.min(scaleX, maxUpscale);
168
+ console.log('Wide diagram detected (aspect ratio > 3):', aspectRatio, 'Using scaleX:', initialScale);
169
+ } else {
170
+ // Normal diagram: fit to smaller dimension, but do not upscale by default
171
+ initialScale = Math.min(scaleX, scaleY, maxUpscale);
172
+ console.log('Normal diagram (aspect ratio <=3):', aspectRatio, 'Using min scale:', initialScale);
173
+ }
174
+
175
+ if (mermaidDebugEnabled()) {
176
+ mermaidDebugLog('initMermaidInteraction: sizing', {
177
+ id: wrapper.id,
178
+ wrapperWidth: wrapperRect.width,
179
+ wrapperHeight: wrapperRect.height,
180
+ svgWidth: svgRect.width,
181
+ svgHeight: svgRect.height,
182
+ initialScale
183
+ });
184
+ }
185
+
186
+ const state = {
187
+ scale: initialScale,
188
+ translateX: 0,
189
+ translateY: 0,
190
+ isPanning: false,
191
+ startX: 0,
192
+ startY: 0
193
+ };
194
+ mermaidStates[wrapper.id] = state;
195
+ wrapper.dataset.mermaidInteractive = 'true';
196
+ console.log('Final state:', state);
197
+ console.groupEnd();
198
+
199
+ if (mermaidDebugEnabled() && !wrapper.dataset.mermaidDebugBound) {
200
+ wrapper.dataset.mermaidDebugBound = 'true';
201
+ const logEvent = (name, event) => {
202
+ const target = event.target && event.target.tagName ? event.target.tagName : 'unknown';
203
+ mermaidDebugLog(`${name} on ${wrapper.id}`, { type: event.type, target });
204
+ };
205
+ wrapper.addEventListener('pointerdown', (e) => logEvent('pointerdown', e));
206
+ wrapper.addEventListener('pointermove', (e) => logEvent('pointermove', e));
207
+ wrapper.addEventListener('pointerup', (e) => logEvent('pointerup', e));
208
+ wrapper.addEventListener('wheel', (e) => logEvent('wheel', e));
209
+ }
210
+
211
+ function updateTransform() {
212
+ applySvgState(getSvg());
213
+ }
214
+
215
+ // Apply initial scale
216
+ updateTransform();
217
+
218
+ if (!wrapper.dataset.mermaidObserver) {
219
+ const observer = new MutationObserver(() => {
220
+ applySvgState(getSvg());
221
+ });
222
+ observer.observe(wrapper, { childList: true, subtree: true });
223
+ wrapper.dataset.mermaidObserver = 'true';
224
+ }
225
+
226
+ // Mouse wheel zoom (zooms towards cursor position)
227
+ wrapper.addEventListener('wheel', (e) => {
228
+ e.preventDefault();
229
+
230
+ const currentSvg = getSvg();
231
+ if (!currentSvg) {
232
+ return;
233
+ }
234
+ const rect = currentSvg.getBoundingClientRect();
235
+
236
+ // Mouse position relative to SVG's current position
237
+ const mouseX = e.clientX - rect.left - rect.width / 2;
238
+ const mouseY = e.clientY - rect.top - rect.height / 2;
239
+
240
+ const zoomIntensity = 0.01;
241
+ const delta = e.deltaY > 0 ? 1 - zoomIntensity : 1 + zoomIntensity; // Zoom out or in speed
242
+ const newScale = Math.min(Math.max(0.1, state.scale * delta), 55);
243
+
244
+ // Calculate how much to adjust translation to keep point under cursor fixed
245
+ // With center origin, we need to account for the scale change around center
246
+ const scaleFactor = newScale / state.scale - 1;
247
+ state.translateX -= mouseX * scaleFactor;
248
+ state.translateY -= mouseY * scaleFactor;
249
+ state.scale = newScale;
250
+
251
+ updateTransform();
252
+ }, { passive: false });
253
+
254
+ // Pan with pointer drag (mouse + touch)
255
+ wrapper.style.cursor = 'grab';
256
+ wrapper.style.touchAction = 'none';
257
+ wrapper.addEventListener('pointerdown', (e) => {
258
+ if (e.pointerType === 'mouse' && e.button !== 0) return;
259
+ state.isPanning = true;
260
+ state.startX = e.clientX - state.translateX;
261
+ state.startY = e.clientY - state.translateY;
262
+ wrapper.setPointerCapture(e.pointerId);
263
+ wrapper.style.cursor = 'grabbing';
264
+ e.preventDefault();
265
+ });
266
+
267
+ wrapper.addEventListener('pointermove', (e) => {
268
+ if (!state.isPanning) return;
269
+ state.translateX = e.clientX - state.startX;
270
+ state.translateY = e.clientY - state.startY;
271
+ updateTransform();
272
+ if (mermaidDebugEnabled()) {
273
+ mermaidDebugLog('pan update', wrapper.id, {
274
+ translateX: state.translateX,
275
+ translateY: state.translateY,
276
+ scale: state.scale,
277
+ svgTransform: (getSvg() && getSvg().style.transform) || ''
278
+ });
279
+ }
280
+ });
281
+
282
+ const stopPanning = (e) => {
283
+ if (!state.isPanning) return;
284
+ state.isPanning = false;
285
+ try {
286
+ wrapper.releasePointerCapture(e.pointerId);
287
+ } catch {
288
+ // Ignore if pointer capture is not active
289
+ }
290
+ wrapper.style.cursor = 'grab';
291
+ };
292
+
293
+ wrapper.addEventListener('pointerup', stopPanning);
294
+ wrapper.addEventListener('pointercancel', stopPanning);
295
+ });
296
+ }
297
+
298
+ function scheduleMermaidInteraction({ maxAttempts = 12, delayMs = 80, onReady } = {}) {
299
+ let attempt = 0;
300
+ const check = () => {
301
+ const wrappers = Array.from(document.querySelectorAll('.mermaid-wrapper'));
302
+ const pending = wrappers.filter(wrapper => !wrapper.querySelector('svg'));
303
+ if (mermaidDebugEnabled()) {
304
+ const last = wrappers[wrappers.length - 1];
305
+ mermaidDebugLog('scheduleMermaidInteraction attempt', attempt, 'pending', pending.length);
306
+ if (last) {
307
+ mermaidDebugLog('scheduleMermaidInteraction last wrapper', last.id, 'hasSvg', !!last.querySelector('svg'));
308
+ }
309
+ }
310
+ if (pending.length === 0 || attempt >= maxAttempts) {
311
+ initMermaidInteraction();
312
+ if (typeof onReady === 'function') {
313
+ onReady();
314
+ }
315
+ return;
316
+ }
317
+ attempt += 1;
318
+ setTimeout(check, delayMs);
319
+ };
320
+ check();
321
+ }
322
+
323
+ window.resetMermaidZoom = function(id) {
324
+ const state = mermaidStates[id];
325
+ if (state) {
326
+ state.scale = 1;
327
+ state.translateX = 0;
328
+ state.translateY = 0;
329
+ const svg = document.getElementById(id).querySelector('svg');
330
+ svg.style.transform = 'translate(0px, 0px) scale(1)';
331
+ }
332
+ };
333
+
334
+ window.zoomMermaidIn = function(id) {
335
+ const state = mermaidStates[id];
336
+ if (state) {
337
+ state.scale = Math.min(state.scale * 1.1, 10);
338
+ const svg = document.getElementById(id).querySelector('svg');
339
+ svg.style.transform = `translate(${state.translateX}px, ${state.translateY}px) scale(${state.scale})`;
340
+ }
341
+ };
342
+
343
+ window.zoomMermaidOut = function(id) {
344
+ const state = mermaidStates[id];
345
+ if (state) {
346
+ state.scale = Math.max(state.scale * 0.9, 0.1);
347
+ const svg = document.getElementById(id).querySelector('svg');
348
+ svg.style.transform = `translate(${state.translateX}px, ${state.translateY}px) scale(${state.scale})`;
349
+ }
350
+ };
351
+
352
+ window.openMermaidFullscreen = function(id) {
353
+ const wrapper = document.getElementById(id);
354
+ if (!wrapper) return;
355
+
356
+ const originalCode = wrapper.getAttribute('data-mermaid-code');
357
+ if (!originalCode) return;
358
+
359
+ // Decode HTML entities
360
+ const textarea = document.createElement('textarea');
361
+ textarea.innerHTML = originalCode;
362
+ const code = textarea.value;
363
+
364
+ // Create modal
365
+ const modal = document.createElement('div');
366
+ modal.id = 'mermaid-fullscreen-modal';
367
+ modal.className = 'fixed inset-0 z-[10000] bg-black/80 backdrop-blur-sm flex items-center justify-center p-4';
368
+ modal.style.animation = 'fadeIn 0.2s ease-in';
369
+
370
+ // Create modal content container
371
+ const modalContent = document.createElement('div');
372
+ modalContent.className = 'relative bg-white dark:bg-slate-900 rounded-lg shadow-2xl w-full h-full max-w-[95vw] max-h-[95vh] flex flex-col';
373
+
374
+ // Create header with close button
375
+ const header = document.createElement('div');
376
+ header.className = 'flex items-center justify-between p-4 border-b border-slate-200 dark:border-slate-700';
377
+
378
+ const title = document.createElement('h3');
379
+ title.className = 'text-lg font-semibold text-slate-800 dark:text-slate-200';
380
+ title.textContent = 'Diagram';
381
+
382
+ const closeBtn = document.createElement('button');
383
+ closeBtn.innerHTML = '✕';
384
+ closeBtn.className = 'px-3 py-1 text-xl text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800 rounded transition-colors';
385
+ closeBtn.title = 'Close (Esc)';
386
+ closeBtn.onclick = () => document.body.removeChild(modal);
387
+
388
+ header.appendChild(title);
389
+ header.appendChild(closeBtn);
390
+
391
+ // Create diagram container
392
+ const diagramContainer = document.createElement('div');
393
+ diagramContainer.className = 'flex-1 overflow-auto p-4 flex items-center justify-center';
394
+
395
+ const fullscreenId = `${id}-fullscreen`;
396
+ const fullscreenWrapper = document.createElement('div');
397
+ fullscreenWrapper.id = fullscreenId;
398
+ fullscreenWrapper.className = 'mermaid-wrapper w-full h-full flex items-center justify-center';
399
+ fullscreenWrapper.setAttribute('data-mermaid-code', originalCode);
400
+
401
+ const pre = document.createElement('pre');
402
+ pre.className = 'mermaid';
403
+ pre.textContent = code;
404
+ fullscreenWrapper.appendChild(pre);
405
+
406
+ diagramContainer.appendChild(fullscreenWrapper);
407
+
408
+ // Assemble modal
409
+ modalContent.appendChild(header);
410
+ modalContent.appendChild(diagramContainer);
411
+ modal.appendChild(modalContent);
412
+ document.body.appendChild(modal);
413
+
414
+ // Close on Esc key
415
+ const escHandler = (e) => {
416
+ if (e.key === 'Escape') {
417
+ document.body.removeChild(modal);
418
+ document.removeEventListener('keydown', escHandler);
419
+ }
420
+ };
421
+ document.addEventListener('keydown', escHandler);
422
+
423
+ // Close on background click
424
+ modal.addEventListener('click', (e) => {
425
+ if (e.target === modal) {
426
+ document.body.removeChild(modal);
427
+ document.removeEventListener('keydown', escHandler);
428
+ }
429
+ });
430
+
431
+ // Render mermaid in the fullscreen view
432
+ mermaid.run({ nodes: [pre] }).then(() => {
433
+ setTimeout(() => initMermaidInteraction(), 100);
434
+ });
435
+ };
436
+
437
+ function getCurrentTheme() {
438
+ return document.documentElement.classList.contains('dark') ? 'dark' : 'default';
439
+ }
440
+
441
+ function getDynamicGanttWidth() {
442
+ // Check if any mermaid wrapper has custom gantt width
443
+ const wrappers = document.querySelectorAll('.mermaid-wrapper[data-gantt-width]');
444
+ if (wrappers.length > 0) {
445
+ // Use the first custom width found, or max width if multiple
446
+ const widths = Array.from(wrappers).map(w => parseInt(w.getAttribute('data-gantt-width')) || GANTT_WIDTH);
447
+ return Math.max(...widths);
448
+ }
449
+ return GANTT_WIDTH;
450
+ }
451
+
452
+ function reinitializeMermaid() {
453
+ console.group('🔄 reinitializeMermaid called');
454
+ console.log('Switching to theme:', getCurrentTheme());
455
+ console.log('Is initial load?', isInitialLoad);
456
+
457
+ // Skip if this is the initial load (let it render naturally first)
458
+ if (isInitialLoad) {
459
+ console.log('Skipping reinitialize on initial load');
460
+ console.groupEnd();
461
+ return;
462
+ }
463
+
464
+ const dynamicWidth = getDynamicGanttWidth();
465
+ console.log('Using dynamic Gantt width:', dynamicWidth);
466
+
467
+ mermaid.initialize({
468
+ startOnLoad: false,
469
+ theme: getCurrentTheme(),
470
+ gantt: {
471
+ useWidth: dynamicWidth,
472
+ useMaxWidth: false
473
+ }
474
+ });
475
+
476
+ // Find all mermaid wrappers and re-render them
477
+ const shouldLockHeight = (wrapper) => {
478
+ const height = (wrapper.style.height || '').trim();
479
+ return height && height !== 'auto' && height !== 'initial' && height !== 'unset';
480
+ };
481
+
482
+ document.querySelectorAll('.mermaid-wrapper').forEach(wrapper => {
483
+ const originalCode = wrapper.getAttribute('data-mermaid-code');
484
+ if (originalCode) {
485
+ console.log(`Processing wrapper: ${wrapper.id}`);
486
+ console.log('BEFORE clear - wrapper height:', window.getComputedStyle(wrapper).height);
487
+ console.log('BEFORE clear - wrapper rect:', wrapper.getBoundingClientRect());
488
+
489
+ // Preserve the current computed height before clearing (height should already be set explicitly)
490
+ if (shouldLockHeight(wrapper)) {
491
+ const currentHeight = wrapper.getBoundingClientRect().height;
492
+ console.log('Preserving height:', currentHeight);
493
+ wrapper.style.height = currentHeight + 'px';
494
+ }
495
+
496
+ // Delete the old state so it can be recreated
497
+ delete mermaidStates[wrapper.id];
498
+ delete wrapper.dataset.mermaidInteractive;
499
+
500
+ // Decode HTML entities
501
+ const textarea = document.createElement('textarea');
502
+ textarea.innerHTML = originalCode;
503
+ const code = textarea.value;
504
+
505
+ // Clear the wrapper
506
+ wrapper.innerHTML = '';
507
+ console.log('AFTER clear - wrapper height:', window.getComputedStyle(wrapper).height);
508
+ console.log('AFTER clear - wrapper rect:', wrapper.getBoundingClientRect());
509
+
510
+ // Re-add the pre element with mermaid code
511
+ const newPre = document.createElement('pre');
512
+ newPre.className = 'mermaid';
513
+ newPre.textContent = code;
514
+ wrapper.appendChild(newPre);
515
+ }
516
+ });
517
+
518
+ // Re-run mermaid
519
+ mermaid.run().then(() => {
520
+ console.log('Mermaid re-render complete, scheduling initMermaidInteraction');
521
+ scheduleMermaidInteraction({
522
+ onReady: () => {
523
+ console.groupEnd();
524
+ }
525
+ });
526
+ });
527
+ }
528
+
529
+ console.log('🚀 Initial Mermaid setup - Theme:', getCurrentTheme());
530
+
531
+ const initialGanttWidth = getDynamicGanttWidth();
532
+ console.log('Using initial Gantt width:', initialGanttWidth);
533
+
534
+ mermaid.initialize({
535
+ startOnLoad: false,
536
+ theme: getCurrentTheme(),
537
+ gantt: {
538
+ useWidth: initialGanttWidth,
539
+ useMaxWidth: false
540
+ }
541
+ });
542
+
543
+ // Track if this is the initial load
544
+ let isInitialLoad = true;
545
+
546
+ // Initialize interaction after mermaid renders
547
+ document.addEventListener('DOMContentLoaded', () => {
548
+ mermaidDebugSnapshot('before mermaid.run (DOMContentLoaded)');
549
+ mermaid.run().then(() => {
550
+ mermaidDebugSnapshot('after mermaid.run (DOMContentLoaded)');
551
+ console.log('Initial mermaid render complete');
552
+ scheduleMermaidInteraction({
553
+ onReady: () => {
554
+ console.log('Calling initial initMermaidInteraction');
555
+
556
+ // After initial render, set explicit heights on all wrappers so theme switching works
557
+ const shouldLockHeight = (wrapper) => {
558
+ const height = (wrapper.style.height || '').trim();
559
+ return height && height !== 'auto' && height !== 'initial' && height !== 'unset';
560
+ };
561
+ document.querySelectorAll('.mermaid-wrapper').forEach(wrapper => {
562
+ if (!shouldLockHeight(wrapper)) {
563
+ return;
564
+ }
565
+ const currentHeight = wrapper.getBoundingClientRect().height;
566
+ console.log(`Setting initial height for ${wrapper.id}:`, currentHeight);
567
+ wrapper.style.height = currentHeight + 'px';
568
+ });
569
+ isInitialLoad = false;
570
+ }
571
+ });
572
+ });
573
+ });
574
+
575
+ // Reveal current file in sidebar
576
+ function revealInSidebar(rootElement = document) {
577
+ if (!window.location.pathname.startsWith('/posts/')) {
578
+ return;
579
+ }
580
+
581
+ // Decode the URL path to handle special characters and spaces
582
+ const currentPath = decodeURIComponent(window.location.pathname.replace(/^\/posts\//, ''));
583
+ const activeLink = rootElement.querySelector(`.post-link[data-path="${currentPath}"]`);
584
+
585
+ if (activeLink) {
586
+ // Expand all parent details elements within this sidebar
587
+ let parent = activeLink.closest('details');
588
+ while (parent && rootElement.contains(parent)) {
589
+ parent.open = true;
590
+ if (parent === rootElement) {
591
+ break;
592
+ }
593
+ parent = parent.parentElement.closest('details');
594
+ }
595
+
596
+ // Scroll to the active link
597
+ const scrollContainer = rootElement.querySelector('#sidebar-scroll-container');
598
+ if (scrollContainer) {
599
+ const linkRect = activeLink.getBoundingClientRect();
600
+ const containerRect = scrollContainer.getBoundingClientRect();
601
+ const scrollTop = scrollContainer.scrollTop;
602
+ const offset = linkRect.top - containerRect.top + scrollTop - (containerRect.height / 2) + (linkRect.height / 2);
603
+
604
+ scrollContainer.scrollTo({
605
+ top: offset,
606
+ behavior: 'smooth'
607
+ });
608
+ }
609
+
610
+ // Highlight the active link temporarily
611
+ activeLink.classList.remove('fade-out');
612
+ activeLink.classList.add('sidebar-highlight');
613
+ requestAnimationFrame(() => {
614
+ setTimeout(() => {
615
+ activeLink.classList.add('fade-out');
616
+ setTimeout(() => {
617
+ activeLink.classList.remove('sidebar-highlight', 'fade-out');
618
+ }, 10000);
619
+ }, 1000);
620
+ });
621
+ }
622
+ }
623
+
624
+ function initPostsSidebarAutoReveal() {
625
+ const postSidebars = document.querySelectorAll('details[data-sidebar="posts"]');
626
+
627
+ postSidebars.forEach((sidebar) => {
628
+ if (sidebar.dataset.revealBound === 'true') {
629
+ return;
630
+ }
631
+ sidebar.dataset.revealBound = 'true';
632
+
633
+ // Reveal immediately if sidebar is already open
634
+ if (sidebar.open) {
635
+ revealInSidebar(sidebar);
636
+ }
637
+
638
+ sidebar.addEventListener('toggle', () => {
639
+ if (!sidebar.open) {
640
+ return;
641
+ }
642
+ revealInSidebar(sidebar);
643
+ });
644
+ });
645
+ }
646
+
647
+ function initFolderChevronState(rootElement = document) {
648
+ rootElement.querySelectorAll('details[data-folder="true"]').forEach((details) => {
649
+ details.classList.toggle('is-open', details.open);
650
+ });
651
+ }
652
+
653
+ function initSearchPlaceholderCycle(rootElement = document) {
654
+ const inputs = rootElement.querySelectorAll('input[data-placeholder-cycle]');
655
+ inputs.forEach((input) => {
656
+ if (input.dataset.placeholderCycleBound === 'true') {
657
+ return;
658
+ }
659
+ input.dataset.placeholderCycleBound = 'true';
660
+ const primary = input.dataset.placeholderPrimary || input.getAttribute('placeholder') || '';
661
+ const alt = input.dataset.placeholderAlt || '';
662
+ if (!alt) {
663
+ return;
664
+ }
665
+ let showAlt = false;
666
+ setInterval(() => {
667
+ if (input.value) {
668
+ return;
669
+ }
670
+ showAlt = !showAlt;
671
+ input.setAttribute('placeholder', showAlt ? alt : primary);
672
+ }, 10000);
673
+ });
674
+ }
675
+
676
+ function initCodeBlockCopyButtons(rootElement = document) {
677
+ const buttons = rootElement.querySelectorAll('.code-copy-button');
678
+ buttons.forEach((button) => {
679
+ if (button.dataset.copyBound === 'true') {
680
+ return;
681
+ }
682
+ button.dataset.copyBound = 'true';
683
+ button.addEventListener('click', () => {
684
+ const container = button.closest('.code-block');
685
+ const codeEl = container ? container.querySelector('pre > code') : null;
686
+ if (!codeEl) {
687
+ return;
688
+ }
689
+ const text = codeEl.innerText || codeEl.textContent || '';
690
+ const done = () => {
691
+ button.classList.add('is-copied');
692
+ setTimeout(() => button.classList.remove('is-copied'), 1200);
693
+ };
694
+ if (navigator.clipboard && window.isSecureContext) {
695
+ navigator.clipboard.writeText(text).then(done).catch(() => {
696
+ const textarea = document.createElement('textarea');
697
+ textarea.value = text;
698
+ textarea.setAttribute('readonly', '');
699
+ textarea.style.position = 'absolute';
700
+ textarea.style.left = '-9999px';
701
+ document.body.appendChild(textarea);
702
+ textarea.select();
703
+ document.execCommand('copy');
704
+ document.body.removeChild(textarea);
705
+ done();
706
+ });
707
+ } else {
708
+ const textarea = document.createElement('textarea');
709
+ textarea.value = text;
710
+ textarea.setAttribute('readonly', '');
711
+ textarea.style.position = 'absolute';
712
+ textarea.style.left = '-9999px';
713
+ document.body.appendChild(textarea);
714
+ textarea.select();
715
+ document.execCommand('copy');
716
+ document.body.removeChild(textarea);
717
+ done();
718
+ }
719
+ });
720
+ });
721
+ }
722
+
723
+ function initPostsSearchPersistence(rootElement = document) {
724
+ const input = rootElement.querySelector('.posts-search-block input[type="search"][name="q"]');
725
+ const results = rootElement.querySelector('.posts-search-results');
726
+ if (!input || !results) {
727
+ return;
728
+ }
729
+ if (input.dataset.searchPersistenceBound === 'true') {
730
+ return;
731
+ }
732
+ input.dataset.searchPersistenceBound = 'true';
733
+ const termKey = 'vyasa:postsSearchTerm';
734
+ const resultsKey = 'vyasa:postsSearchResults';
735
+ const enhanceGatherLink = () => {
736
+ const gatherLink = results.querySelector('a[href^="/search/gather"]');
737
+ if (!gatherLink) {
738
+ return;
739
+ }
740
+ const href = gatherLink.getAttribute('href');
741
+ if (!href) {
742
+ return;
743
+ }
744
+ gatherLink.setAttribute('hx_get', href);
745
+ gatherLink.setAttribute('hx_target', '#main-content');
746
+ gatherLink.setAttribute('hx_push_url', 'true');
747
+ gatherLink.setAttribute('hx_swap', 'outerHTML show:window:top settle:0.1s');
748
+ };
749
+ let storedTerm = '';
750
+ let storedResults = null;
751
+ try {
752
+ storedTerm = localStorage.getItem(termKey) || '';
753
+ storedResults = localStorage.getItem(resultsKey);
754
+ } catch (err) {
755
+ storedTerm = '';
756
+ storedResults = null;
757
+ }
758
+ if (storedTerm && !input.value) {
759
+ input.value = storedTerm;
760
+ }
761
+ if (storedResults && input.value) {
762
+ try {
763
+ const payload = JSON.parse(storedResults);
764
+ if (payload && payload.term === input.value && payload.html) {
765
+ results.innerHTML = payload.html;
766
+ enhanceGatherLink();
767
+ }
768
+ } catch (err) {
769
+ // Ignore malformed cached payloads.
770
+ }
771
+ }
772
+ const persistTerm = () => {
773
+ try {
774
+ if (input.value) {
775
+ localStorage.setItem(termKey, input.value);
776
+ } else {
777
+ localStorage.removeItem(termKey);
778
+ localStorage.removeItem(resultsKey);
779
+ }
780
+ } catch (err) {
781
+ // Ignore storage failures.
782
+ }
783
+ };
784
+ input.addEventListener('input', persistTerm);
785
+ const fetchResults = (query) => {
786
+ return fetch(`/_sidebar/posts/search?q=${query}`)
787
+ .then((response) => response.text())
788
+ .then((html) => {
789
+ results.innerHTML = html;
790
+ enhanceGatherLink();
791
+ try {
792
+ localStorage.setItem(resultsKey, JSON.stringify({
793
+ term: input.value,
794
+ html: results.innerHTML
795
+ }));
796
+ } catch (err) {
797
+ // Ignore storage failures.
798
+ }
799
+ })
800
+ .catch(() => {});
801
+ };
802
+ document.body.addEventListener('htmx:afterSwap', (event) => {
803
+ if (event.target !== results) {
804
+ return;
805
+ }
806
+ enhanceGatherLink();
807
+ try {
808
+ localStorage.setItem(resultsKey, JSON.stringify({
809
+ term: input.value,
810
+ html: results.innerHTML
811
+ }));
812
+ } catch (err) {
813
+ // Ignore storage failures.
814
+ }
815
+ });
816
+ if (input.value) {
817
+ const query = encodeURIComponent(input.value);
818
+ if (window.htmx && typeof window.htmx.ajax === 'function') {
819
+ window.htmx.ajax('GET', `/_sidebar/posts/search?q=${query}`, { target: results, swap: 'innerHTML' });
820
+ } else {
821
+ fetchResults(query);
822
+ }
823
+ }
824
+ }
825
+
826
+ function initSearchClearButtons(rootElement = document) {
827
+ const blocks = rootElement.querySelectorAll('.posts-search-block');
828
+ blocks.forEach((block) => {
829
+ const input = block.querySelector('input[type="search"][name="q"]');
830
+ const button = block.querySelector('.posts-search-clear-button');
831
+ const results = block.querySelector('.posts-search-results');
832
+ if (!input || !button) {
833
+ return;
834
+ }
835
+ if (button.dataset.clearBound === 'true') {
836
+ return;
837
+ }
838
+ button.dataset.clearBound = 'true';
839
+ const updateVisibility = () => {
840
+ button.style.opacity = input.value ? '1' : '0';
841
+ button.style.pointerEvents = input.value ? 'auto' : 'none';
842
+ };
843
+ updateVisibility();
844
+ input.addEventListener('input', updateVisibility);
845
+ button.addEventListener('click', () => {
846
+ input.value = '';
847
+ input.dispatchEvent(new Event('input', { bubbles: true }));
848
+ if (results) {
849
+ results.innerHTML = '';
850
+ }
851
+ try {
852
+ localStorage.removeItem('vyasa:postsSearchTerm');
853
+ localStorage.removeItem('vyasa:postsSearchResults');
854
+ } catch (err) {
855
+ // Ignore storage failures.
856
+ }
857
+ });
858
+ });
859
+ }
860
+
861
+ document.addEventListener('toggle', (event) => {
862
+ const details = event.target;
863
+ if (!(details instanceof HTMLDetailsElement)) {
864
+ return;
865
+ }
866
+ if (!details.matches('details[data-folder="true"]')) {
867
+ return;
868
+ }
869
+ details.classList.toggle('is-open', details.open);
870
+ }, true);
871
+
872
+ // Update active post link in sidebar
873
+ function updateActivePostLink() {
874
+ const currentPath = window.location.pathname.replace(/^\/posts\//, '');
875
+ document.querySelectorAll('.post-link').forEach(link => {
876
+ const linkPath = link.getAttribute('data-path');
877
+ if (linkPath === currentPath) {
878
+ link.classList.add('bg-blue-50', 'dark:bg-blue-900/20', 'text-blue-600', 'dark:text-blue-400', 'font-medium');
879
+ link.classList.remove('text-slate-700', 'dark:text-slate-300', 'hover:text-blue-600');
880
+ } else {
881
+ link.classList.remove('bg-blue-50', 'dark:bg-blue-900/20', 'text-blue-600', 'dark:text-blue-400', 'font-medium');
882
+ link.classList.add('text-slate-700', 'dark:text-slate-300', 'hover:text-blue-600');
883
+ }
884
+ });
885
+ }
886
+
887
+ // Update active TOC link based on scroll position
888
+ let lastActiveTocAnchor = null;
889
+ function updateActiveTocLink() {
890
+ const headings = document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]');
891
+ const tocLinks = document.querySelectorAll('.toc-link');
892
+
893
+ let activeHeading = null;
894
+ let nearestBelow = null;
895
+ let nearestBelowTop = Infinity;
896
+ const offset = 140;
897
+ headings.forEach(heading => {
898
+ const rect = heading.getBoundingClientRect();
899
+ if (rect.top <= offset) {
900
+ activeHeading = heading;
901
+ } else if (rect.top < nearestBelowTop) {
902
+ nearestBelowTop = rect.top;
903
+ nearestBelow = heading;
904
+ }
905
+ });
906
+ if (!activeHeading && nearestBelow) {
907
+ activeHeading = nearestBelow;
908
+ }
909
+
910
+ tocLinks.forEach(link => {
911
+ const anchor = link.getAttribute('data-anchor');
912
+ if (activeHeading && anchor === activeHeading.id) {
913
+ link.classList.add('bg-blue-50', 'dark:bg-blue-900/20', 'text-blue-600', 'dark:text-blue-400', 'font-semibold');
914
+ } else {
915
+ link.classList.remove('bg-blue-50', 'dark:bg-blue-900/20', 'text-blue-600', 'dark:text-blue-400', 'font-semibold');
916
+ }
917
+ });
918
+
919
+ const activeId = activeHeading ? activeHeading.id : null;
920
+ if (activeId && activeId !== lastActiveTocAnchor) {
921
+ document.querySelectorAll(`.toc-link[data-anchor="${activeId}"]`).forEach(link => {
922
+ link.scrollIntoView({ block: 'nearest' });
923
+ });
924
+ lastActiveTocAnchor = activeId;
925
+ }
926
+ }
927
+
928
+ // Listen for scroll events to update active TOC link
929
+ let ticking = false;
930
+ window.addEventListener('scroll', () => {
931
+ if (!ticking) {
932
+ window.requestAnimationFrame(() => {
933
+ updateActiveTocLink();
934
+ ticking = false;
935
+ });
936
+ ticking = true;
937
+ }
938
+ });
939
+
940
+ // Sync TOC highlight on hash changes and TOC clicks
941
+ window.addEventListener('hashchange', () => {
942
+ requestAnimationFrame(updateActiveTocLink);
943
+ });
944
+
945
+ document.addEventListener('click', (event) => {
946
+ const link = event.target.closest('.toc-link');
947
+ if (!link) {
948
+ return;
949
+ }
950
+ const anchor = link.getAttribute('data-anchor');
951
+ if (!anchor) {
952
+ return;
953
+ }
954
+ requestAnimationFrame(() => {
955
+ document.querySelectorAll('.toc-link').forEach(item => {
956
+ item.classList.toggle(
957
+ 'bg-blue-50',
958
+ item.getAttribute('data-anchor') === anchor
959
+ );
960
+ item.classList.toggle(
961
+ 'dark:bg-blue-900/20',
962
+ item.getAttribute('data-anchor') === anchor
963
+ );
964
+ item.classList.toggle(
965
+ 'text-blue-600',
966
+ item.getAttribute('data-anchor') === anchor
967
+ );
968
+ item.classList.toggle(
969
+ 'dark:text-blue-400',
970
+ item.getAttribute('data-anchor') === anchor
971
+ );
972
+ item.classList.toggle(
973
+ 'font-semibold',
974
+ item.getAttribute('data-anchor') === anchor
975
+ );
976
+ });
977
+ lastActiveTocAnchor = anchor;
978
+ updateActiveTocLink();
979
+ });
980
+ });
981
+
982
+ // Re-run mermaid on HTMX content swaps
983
+ document.body.addEventListener('htmx:afterSwap', function(event) {
984
+ mermaidDebugSnapshot('before mermaid.run (htmx:afterSwap)');
985
+ document.querySelectorAll('.mermaid-wrapper').forEach(wrapper => {
986
+ if (!wrapper.id) {
987
+ return;
988
+ }
989
+ // HTMX swaps can trigger a mermaid re-run that replaces SVGs.
990
+ // Clear interaction state so we always re-bind after mermaid.run().
991
+ delete mermaidStates[wrapper.id];
992
+ delete wrapper.dataset.mermaidInteractive;
993
+ });
994
+ mermaid.run().then(() => {
995
+ mermaidDebugSnapshot('after mermaid.run (htmx:afterSwap)');
996
+ scheduleMermaidInteraction();
997
+ });
998
+ updateActivePostLink();
999
+ updateActiveTocLink();
1000
+ initMobileMenus(); // Reinitialize mobile menu handlers
1001
+ initPostsSidebarAutoReveal();
1002
+ initFolderChevronState();
1003
+ initSearchPlaceholderCycle(event.target || document);
1004
+ initCodeBlockCopyButtons(event.target || document);
1005
+ });
1006
+
1007
+ // Watch for theme changes and re-render mermaid diagrams
1008
+ const observer = new MutationObserver((mutations) => {
1009
+ mutations.forEach((mutation) => {
1010
+ if (mutation.attributeName === 'class') {
1011
+ reinitializeMermaid();
1012
+ }
1013
+ });
1014
+ });
1015
+
1016
+ observer.observe(document.documentElement, {
1017
+ attributes: true,
1018
+ attributeFilter: ['class']
1019
+ });
1020
+
1021
+ // Mobile menu toggle functionality
1022
+ function initMobileMenus() {
1023
+ const postsToggle = document.getElementById('mobile-posts-toggle');
1024
+ const tocToggle = document.getElementById('mobile-toc-toggle');
1025
+ const postsPanel = document.getElementById('mobile-posts-panel');
1026
+ const tocPanel = document.getElementById('mobile-toc-panel');
1027
+ const closePostsBtn = document.getElementById('close-mobile-posts');
1028
+ const closeTocBtn = document.getElementById('close-mobile-toc');
1029
+
1030
+ // Open posts panel
1031
+ if (postsToggle) {
1032
+ postsToggle.addEventListener('click', () => {
1033
+ if (postsPanel) {
1034
+ postsPanel.classList.remove('-translate-x-full');
1035
+ postsPanel.classList.add('translate-x-0');
1036
+ // Close TOC panel if open
1037
+ if (tocPanel) {
1038
+ tocPanel.classList.remove('translate-x-0');
1039
+ tocPanel.classList.add('translate-x-full');
1040
+ }
1041
+ }
1042
+ });
1043
+ }
1044
+
1045
+ // Open TOC panel
1046
+ if (tocToggle) {
1047
+ tocToggle.addEventListener('click', () => {
1048
+ if (tocPanel) {
1049
+ tocPanel.classList.remove('translate-x-full');
1050
+ tocPanel.classList.add('translate-x-0');
1051
+ // Close posts panel if open
1052
+ if (postsPanel) {
1053
+ postsPanel.classList.remove('translate-x-0');
1054
+ postsPanel.classList.add('-translate-x-full');
1055
+ }
1056
+ }
1057
+ });
1058
+ }
1059
+
1060
+ // Close posts panel
1061
+ if (closePostsBtn) {
1062
+ closePostsBtn.addEventListener('click', () => {
1063
+ if (postsPanel) {
1064
+ postsPanel.classList.remove('translate-x-0');
1065
+ postsPanel.classList.add('-translate-x-full');
1066
+ }
1067
+ });
1068
+ }
1069
+
1070
+ // Close TOC panel
1071
+ if (closeTocBtn) {
1072
+ closeTocBtn.addEventListener('click', () => {
1073
+ if (tocPanel) {
1074
+ tocPanel.classList.remove('translate-x-0');
1075
+ tocPanel.classList.add('translate-x-full');
1076
+ }
1077
+ });
1078
+ }
1079
+
1080
+ // Close panels on link click (for better mobile UX)
1081
+ if (postsPanel) {
1082
+ postsPanel.addEventListener('click', (e) => {
1083
+ if (e.target.tagName === 'A' || e.target.closest('a')) {
1084
+ setTimeout(() => {
1085
+ postsPanel.classList.remove('translate-x-0');
1086
+ postsPanel.classList.add('-translate-x-full');
1087
+ }, 100);
1088
+ }
1089
+ });
1090
+ }
1091
+
1092
+ if (tocPanel) {
1093
+ tocPanel.addEventListener('click', (e) => {
1094
+ if (e.target.tagName === 'A' || e.target.closest('a')) {
1095
+ setTimeout(() => {
1096
+ tocPanel.classList.remove('translate-x-0');
1097
+ tocPanel.classList.add('translate-x-full');
1098
+ }, 100);
1099
+ }
1100
+ });
1101
+ }
1102
+ }
1103
+
1104
+ // Keyboard shortcuts for toggling sidebars
1105
+ function initKeyboardShortcuts() {
1106
+ // Prewarm the selectors to avoid lazy compilation delays
1107
+ const postsSidebars = document.querySelectorAll('details[data-sidebar="posts"]');
1108
+ const tocSidebar = document.querySelector('#toc-sidebar details');
1109
+
1110
+ document.addEventListener('keydown', (e) => {
1111
+ // Skip if user is typing in an input field
1112
+ if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) {
1113
+ return;
1114
+ }
1115
+
1116
+ // Z: Toggle posts panel
1117
+ if (e.key === 'z' || e.key === 'Z') {
1118
+ e.preventDefault();
1119
+ const postsSidebars = document.querySelectorAll('details[data-sidebar="posts"]');
1120
+ postsSidebars.forEach(sidebar => {
1121
+ sidebar.open = !sidebar.open;
1122
+ });
1123
+ }
1124
+
1125
+ // X: Toggle TOC panel
1126
+ if (e.key === 'x' || e.key === 'X') {
1127
+ e.preventDefault();
1128
+ const tocSidebar = document.querySelector('#toc-sidebar details');
1129
+ if (tocSidebar) {
1130
+ tocSidebar.open = !tocSidebar.open;
1131
+ }
1132
+ }
1133
+ });
1134
+ }
1135
+
1136
+ function syncPdfFocusButtons(root = document) {
1137
+ const isFocused = document.body.classList.contains('pdf-focus');
1138
+ root.querySelectorAll('[data-pdf-focus-toggle]').forEach((button) => {
1139
+ const focusLabel = button.getAttribute('data-pdf-focus-label') || 'Focus PDF';
1140
+ const exitLabel = button.getAttribute('data-pdf-exit-label') || 'Exit focus';
1141
+ button.textContent = isFocused ? exitLabel : focusLabel;
1142
+ button.setAttribute('aria-pressed', isFocused ? 'true' : 'false');
1143
+ });
1144
+ }
1145
+
1146
+ function ensurePdfFocusState() {
1147
+ const hasPdfViewer = document.querySelector('.pdf-viewer') || document.querySelector('[data-pdf-focus-toggle]');
1148
+ if (!hasPdfViewer) {
1149
+ document.body.classList.remove('pdf-focus');
1150
+ }
1151
+ syncPdfFocusButtons(document);
1152
+ }
1153
+
1154
+ function initPdfFocusToggle() {
1155
+ document.addEventListener('click', (event) => {
1156
+ const button = event.target.closest('[data-pdf-focus-toggle]');
1157
+ if (!button) {
1158
+ return;
1159
+ }
1160
+ event.preventDefault();
1161
+ document.body.classList.toggle('pdf-focus');
1162
+ syncPdfFocusButtons(document);
1163
+ });
1164
+
1165
+ document.addEventListener('keydown', (event) => {
1166
+ if (event.key !== 'Escape') {
1167
+ return;
1168
+ }
1169
+ if (!document.body.classList.contains('pdf-focus')) {
1170
+ return;
1171
+ }
1172
+ document.body.classList.remove('pdf-focus');
1173
+ syncPdfFocusButtons(document);
1174
+ });
1175
+ }
1176
+
1177
+ // Initialize on page load
1178
+ document.addEventListener('DOMContentLoaded', () => {
1179
+ updateActivePostLink();
1180
+ updateActiveTocLink();
1181
+ initMobileMenus();
1182
+ initPostsSidebarAutoReveal();
1183
+ initFolderChevronState();
1184
+ initKeyboardShortcuts();
1185
+ initPdfFocusToggle();
1186
+ initSearchPlaceholderCycle(document);
1187
+ initPostsSearchPersistence(document);
1188
+ initCodeBlockCopyButtons(document);
1189
+ initSearchClearButtons(document);
1190
+ ensurePdfFocusState();
1191
+ });
1192
+
1193
+ document.body.addEventListener('htmx:afterSwap', (event) => {
1194
+ if (!event.target) {
1195
+ return;
1196
+ }
1197
+ initSearchPlaceholderCycle(event.target);
1198
+ initPostsSearchPersistence(event.target);
1199
+ initCodeBlockCopyButtons(event.target);
1200
+ initSearchClearButtons(event.target);
1201
+ ensurePdfFocusState();
1202
+ });