jekyll-theme-zer0 0.10.6 → 0.15.2

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +428 -0
  3. data/README.md +79 -31
  4. data/_data/README.md +419 -17
  5. data/_data/generate_statistics.rb +216 -9
  6. data/_data/generate_statistics.sh +106 -0
  7. data/_data/github-actions-example.yml +210 -0
  8. data/_data/navigation/about.yml +39 -11
  9. data/_data/navigation/docs.yml +53 -23
  10. data/_data/navigation/home.yml +27 -9
  11. data/_data/navigation/main.yml +27 -8
  12. data/_data/navigation/posts.yml +22 -6
  13. data/_data/navigation/quickstart.yml +19 -6
  14. data/_data/posts_organization.yml +153 -0
  15. data/_data/prerequisites.yml +112 -0
  16. data/_data/statistics_config.yml +203 -0
  17. data/_data/ui-text.yml +321 -0
  18. data/_data/update_statistics.sh +126 -0
  19. data/_includes/README.md +2 -0
  20. data/_includes/components/js-cdn.html +4 -1
  21. data/_includes/components/post-card.html +2 -11
  22. data/_includes/components/preview-image.html +32 -0
  23. data/_includes/content/intro.html +9 -10
  24. data/_includes/core/header.html +14 -0
  25. data/_includes/navigation/sidebar-categories.html +20 -9
  26. data/_includes/navigation/sidebar-folders.html +8 -7
  27. data/_includes/navigation/sidebar-right.html +16 -10
  28. data/_layouts/blog.html +15 -45
  29. data/_layouts/category.html +4 -24
  30. data/_layouts/collection.html +2 -12
  31. data/_layouts/default.html +1 -1
  32. data/_layouts/journals.html +2 -12
  33. data/_layouts/notebook.html +296 -0
  34. data/_sass/core/_docs.scss +1 -1
  35. data/_sass/custom.scss +54 -17
  36. data/_sass/notebooks.scss +458 -0
  37. data/assets/images/notebooks/test-notebook_files/test-notebook_4_0.png +0 -0
  38. data/assets/js/sidebar.js +511 -0
  39. data/scripts/README.md +131 -105
  40. data/scripts/analyze-commits.sh +9 -311
  41. data/scripts/bin/build +22 -22
  42. data/scripts/build +7 -111
  43. data/scripts/convert-notebooks.sh +415 -0
  44. data/scripts/features/validate_preview_urls.py +500 -0
  45. data/scripts/fix-markdown-format.sh +8 -262
  46. data/scripts/generate-preview-images.sh +7 -787
  47. data/scripts/install-preview-generator.sh +8 -528
  48. data/scripts/lib/README.md +5 -5
  49. data/scripts/lib/changelog.sh +89 -57
  50. data/scripts/lib/gem.sh +19 -7
  51. data/scripts/release +7 -236
  52. data/scripts/setup.sh +9 -153
  53. data/scripts/test/lib/run_tests.sh +1 -2
  54. data/scripts/test-auto-version.sh +7 -256
  55. data/scripts/test-mermaid.sh +7 -287
  56. data/scripts/test.sh +9 -154
  57. metadata +16 -10
  58. data/scripts/features/preview_generator.py +0 -646
  59. data/scripts/lib/test/run_tests.sh +0 -140
  60. data/scripts/lib/test/test_changelog.sh +0 -87
  61. data/scripts/lib/test/test_gem.sh +0 -68
  62. data/scripts/lib/test/test_git.sh +0 -82
  63. data/scripts/lib/test/test_validation.sh +0 -72
  64. data/scripts/lib/test/test_version.sh +0 -96
  65. data/scripts/version.sh +0 -178
@@ -0,0 +1,511 @@
1
+ /**
2
+ * ===================================================================
3
+ * SIDEBAR NAVIGATION - Enhanced JavaScript Functionality
4
+ * ===================================================================
5
+ *
6
+ * File: sidebar.js
7
+ * Path: assets/js/sidebar.js
8
+ * Purpose: Enhanced sidebar navigation with scroll spy, smooth scrolling,
9
+ * keyboard shortcuts, and mobile gesture support
10
+ *
11
+ * Features:
12
+ * - Intersection Observer-based scroll spy for better performance
13
+ * - Smooth scroll to TOC anchors with offset for fixed headers
14
+ * - Keyboard shortcuts for navigation ([ and ] for prev/next)
15
+ * - Swipe gesture support for mobile offcanvas
16
+ * - Active section highlighting in TOC
17
+ * - Focus management for accessibility
18
+ * - Error handling for missing elements
19
+ *
20
+ * Dependencies:
21
+ * - Bootstrap 5 (for offcanvas functionality)
22
+ * - Modern browser with Intersection Observer support
23
+ *
24
+ * Browser Support:
25
+ * - Chrome/Edge 58+
26
+ * - Firefox 55+
27
+ * - Safari 12.1+
28
+ *
29
+ * ===================================================================
30
+ */
31
+
32
+ (function() {
33
+ 'use strict';
34
+
35
+ // ===================================================================
36
+ // CONFIGURATION
37
+ // ===================================================================
38
+
39
+ const config = {
40
+ tocSelector: '#TableOfContents',
41
+ tocLinkSelector: '#TableOfContents a',
42
+ mainContentSelector: '.bd-content',
43
+ observerRootMargin: '-80px 0px -80px 0px', // Account for fixed headers
44
+ smoothScrollOffset: 80, // Offset for fixed headers
45
+ debounceDelay: 100,
46
+ enableKeyboardShortcuts: true,
47
+ enableSwipeGestures: true,
48
+ swipeThreshold: 50 // Minimum distance for swipe gesture
49
+ };
50
+
51
+ // ===================================================================
52
+ // UTILITY FUNCTIONS
53
+ // ===================================================================
54
+
55
+ /**
56
+ * Debounce function to limit function calls
57
+ */
58
+ function debounce(func, wait) {
59
+ let timeout;
60
+ return function executedFunction(...args) {
61
+ const later = () => {
62
+ clearTimeout(timeout);
63
+ func(...args);
64
+ };
65
+ clearTimeout(timeout);
66
+ timeout = setTimeout(later, wait);
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Get element safely with error handling
72
+ */
73
+ function getElement(selector) {
74
+ try {
75
+ return document.querySelector(selector);
76
+ } catch (error) {
77
+ console.warn(`Sidebar.js: Element not found - ${selector}`);
78
+ return null;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Get all elements safely with error handling
84
+ */
85
+ function getElements(selector) {
86
+ try {
87
+ return document.querySelectorAll(selector);
88
+ } catch (error) {
89
+ console.warn(`Sidebar.js: Elements not found - ${selector}`);
90
+ return [];
91
+ }
92
+ }
93
+
94
+ // ===================================================================
95
+ // SCROLL SPY - Intersection Observer Implementation
96
+ // ===================================================================
97
+
98
+ class ScrollSpy {
99
+ constructor() {
100
+ this.tocLinks = getElements(config.tocLinkSelector);
101
+ this.headings = this.getHeadings();
102
+ this.currentActive = null;
103
+
104
+ if (this.headings.length === 0 || this.tocLinks.length === 0) {
105
+ console.log('Sidebar.js: No TOC or headings found, skipping scroll spy');
106
+ return;
107
+ }
108
+
109
+ this.init();
110
+ }
111
+
112
+ /**
113
+ * Get all headings that have corresponding TOC links
114
+ */
115
+ getHeadings() {
116
+ const headings = [];
117
+ this.tocLinks.forEach(link => {
118
+ const href = link.getAttribute('href');
119
+ if (href && href.startsWith('#')) {
120
+ const id = href.substring(1);
121
+ const heading = document.getElementById(id);
122
+ if (heading) {
123
+ headings.push({
124
+ element: heading,
125
+ link: link,
126
+ id: id
127
+ });
128
+ }
129
+ }
130
+ });
131
+ return headings;
132
+ }
133
+
134
+ /**
135
+ * Initialize Intersection Observer
136
+ */
137
+ init() {
138
+ const observerOptions = {
139
+ root: null,
140
+ rootMargin: config.observerRootMargin,
141
+ threshold: [0, 0.25, 0.5, 0.75, 1]
142
+ };
143
+
144
+ this.observer = new IntersectionObserver(
145
+ entries => this.handleIntersection(entries),
146
+ observerOptions
147
+ );
148
+
149
+ // Observe all headings
150
+ this.headings.forEach(heading => {
151
+ this.observer.observe(heading.element);
152
+ });
153
+ }
154
+
155
+ /**
156
+ * Handle intersection events
157
+ */
158
+ handleIntersection(entries) {
159
+ // Find the most visible heading
160
+ let mostVisible = null;
161
+ let maxRatio = 0;
162
+
163
+ entries.forEach(entry => {
164
+ if (entry.isIntersecting && entry.intersectionRatio > maxRatio) {
165
+ maxRatio = entry.intersectionRatio;
166
+ mostVisible = this.headings.find(h => h.element === entry.target);
167
+ }
168
+ });
169
+
170
+ // If we found a visible heading, activate it
171
+ if (mostVisible) {
172
+ this.setActiveLink(mostVisible.link);
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Set active link with visual feedback
178
+ */
179
+ setActiveLink(link) {
180
+ if (this.currentActive === link) return;
181
+
182
+ // Remove previous active state
183
+ this.tocLinks.forEach(l => l.classList.remove('active'));
184
+
185
+ // Add active state
186
+ if (link) {
187
+ link.classList.add('active');
188
+ this.currentActive = link;
189
+
190
+ // Scroll TOC to show active link (if needed)
191
+ this.scrollTocToActiveLink(link);
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Scroll TOC container to show active link
197
+ */
198
+ scrollTocToActiveLink(link) {
199
+ const tocContainer = getElement('.bd-toc .offcanvas-body');
200
+ if (!tocContainer) return;
201
+
202
+ const linkRect = link.getBoundingClientRect();
203
+ const containerRect = tocContainer.getBoundingClientRect();
204
+
205
+ // Check if link is out of view
206
+ if (linkRect.top < containerRect.top || linkRect.bottom > containerRect.bottom) {
207
+ link.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Cleanup observer
213
+ */
214
+ destroy() {
215
+ if (this.observer) {
216
+ this.observer.disconnect();
217
+ }
218
+ }
219
+ }
220
+
221
+ // ===================================================================
222
+ // SMOOTH SCROLL - Enhanced anchor navigation
223
+ // ===================================================================
224
+
225
+ class SmoothScroll {
226
+ constructor() {
227
+ this.tocLinks = getElements(config.tocLinkSelector);
228
+ this.init();
229
+ }
230
+
231
+ init() {
232
+ this.tocLinks.forEach(link => {
233
+ link.addEventListener('click', e => this.handleClick(e));
234
+ });
235
+ }
236
+
237
+ handleClick(event) {
238
+ const href = event.currentTarget.getAttribute('href');
239
+
240
+ if (!href || !href.startsWith('#')) return;
241
+
242
+ event.preventDefault();
243
+
244
+ const targetId = href.substring(1);
245
+ const targetElement = document.getElementById(targetId);
246
+
247
+ if (!targetElement) return;
248
+
249
+ // Calculate scroll position with offset
250
+ const elementPosition = targetElement.getBoundingClientRect().top;
251
+ const offsetPosition = elementPosition + window.pageYOffset - config.smoothScrollOffset;
252
+
253
+ // Smooth scroll to target
254
+ window.scrollTo({
255
+ top: offsetPosition,
256
+ behavior: 'smooth'
257
+ });
258
+
259
+ // Update URL without jumping
260
+ if (history.pushState) {
261
+ history.pushState(null, null, href);
262
+ }
263
+
264
+ // Close mobile offcanvas if open
265
+ this.closeMobileOffcanvas();
266
+
267
+ // Update focus for accessibility
268
+ targetElement.setAttribute('tabindex', '-1');
269
+ targetElement.focus();
270
+ }
271
+
272
+ closeMobileOffcanvas() {
273
+ const tocOffcanvas = document.getElementById('tocContents');
274
+ if (tocOffcanvas && window.innerWidth < 992) {
275
+ const bsOffcanvas = bootstrap.Offcanvas.getInstance(tocOffcanvas);
276
+ if (bsOffcanvas) {
277
+ bsOffcanvas.hide();
278
+ }
279
+ }
280
+ }
281
+ }
282
+
283
+ // ===================================================================
284
+ // KEYBOARD SHORTCUTS - Navigation enhancements
285
+ // ===================================================================
286
+
287
+ class KeyboardShortcuts {
288
+ constructor() {
289
+ if (!config.enableKeyboardShortcuts) return;
290
+
291
+ this.tocLinks = Array.from(getElements(config.tocLinkSelector));
292
+ this.currentIndex = -1;
293
+ this.init();
294
+ }
295
+
296
+ init() {
297
+ document.addEventListener('keydown', e => this.handleKeydown(e));
298
+ }
299
+
300
+ handleKeydown(event) {
301
+ // Ignore if user is typing in an input
302
+ if (event.target.matches('input, textarea, select')) return;
303
+
304
+ switch(event.key) {
305
+ case '[':
306
+ event.preventDefault();
307
+ this.navigatePrevious();
308
+ break;
309
+ case ']':
310
+ event.preventDefault();
311
+ this.navigateNext();
312
+ break;
313
+ case '/':
314
+ event.preventDefault();
315
+ this.focusSearch();
316
+ break;
317
+ }
318
+ }
319
+
320
+ navigatePrevious() {
321
+ const activeLink = document.querySelector(`${config.tocLinkSelector}.active`);
322
+ if (activeLink) {
323
+ this.currentIndex = this.tocLinks.indexOf(activeLink);
324
+ }
325
+
326
+ if (this.currentIndex > 0) {
327
+ this.currentIndex--;
328
+ this.tocLinks[this.currentIndex].click();
329
+ }
330
+ }
331
+
332
+ navigateNext() {
333
+ const activeLink = document.querySelector(`${config.tocLinkSelector}.active`);
334
+ if (activeLink) {
335
+ this.currentIndex = this.tocLinks.indexOf(activeLink);
336
+ }
337
+
338
+ if (this.currentIndex < this.tocLinks.length - 1) {
339
+ this.currentIndex++;
340
+ this.tocLinks[this.currentIndex].click();
341
+ }
342
+ }
343
+
344
+ focusSearch() {
345
+ // Future: focus sidebar search if implemented
346
+ console.log('Sidebar.js: Search functionality not yet implemented');
347
+ }
348
+ }
349
+
350
+ // ===================================================================
351
+ // SWIPE GESTURES - Mobile offcanvas control
352
+ // ===================================================================
353
+
354
+ class SwipeGestures {
355
+ constructor() {
356
+ if (!config.enableSwipeGestures) return;
357
+
358
+ this.startX = 0;
359
+ this.startY = 0;
360
+ this.distX = 0;
361
+ this.distY = 0;
362
+ this.init();
363
+ }
364
+
365
+ init() {
366
+ document.addEventListener('touchstart', e => this.handleTouchStart(e), { passive: true });
367
+ document.addEventListener('touchmove', e => this.handleTouchMove(e), { passive: true });
368
+ document.addEventListener('touchend', e => this.handleTouchEnd(e));
369
+ }
370
+
371
+ handleTouchStart(event) {
372
+ const touch = event.touches[0];
373
+ this.startX = touch.clientX;
374
+ this.startY = touch.clientY;
375
+ }
376
+
377
+ handleTouchMove(event) {
378
+ if (!this.startX || !this.startY) return;
379
+
380
+ const touch = event.touches[0];
381
+ this.distX = touch.clientX - this.startX;
382
+ this.distY = touch.clientY - this.startY;
383
+ }
384
+
385
+ handleTouchEnd(event) {
386
+ if (Math.abs(this.distX) < config.swipeThreshold) {
387
+ this.reset();
388
+ return;
389
+ }
390
+
391
+ // Only handle horizontal swipes
392
+ if (Math.abs(this.distX) > Math.abs(this.distY)) {
393
+ if (this.distX > 0) {
394
+ this.handleSwipeRight();
395
+ } else {
396
+ this.handleSwipeLeft();
397
+ }
398
+ }
399
+
400
+ this.reset();
401
+ }
402
+
403
+ handleSwipeRight() {
404
+ // Open left sidebar
405
+ const leftSidebar = document.getElementById('bdSidebar');
406
+ if (leftSidebar && this.startX < 50) { // Only if started from left edge
407
+ const bsOffcanvas = bootstrap.Offcanvas.getOrCreateInstance(leftSidebar);
408
+ bsOffcanvas.show();
409
+ }
410
+ }
411
+
412
+ handleSwipeLeft() {
413
+ // Open right TOC sidebar
414
+ const rightSidebar = document.getElementById('tocContents');
415
+ const windowWidth = window.innerWidth;
416
+
417
+ if (rightSidebar && this.startX > windowWidth - 50) { // Only if started from right edge
418
+ const bsOffcanvas = bootstrap.Offcanvas.getOrCreateInstance(rightSidebar);
419
+ bsOffcanvas.show();
420
+ }
421
+ }
422
+
423
+ reset() {
424
+ this.startX = 0;
425
+ this.startY = 0;
426
+ this.distX = 0;
427
+ this.distY = 0;
428
+ }
429
+ }
430
+
431
+ // ===================================================================
432
+ // FOCUS MANAGEMENT - Accessibility enhancements
433
+ // ===================================================================
434
+
435
+ class FocusManager {
436
+ constructor() {
437
+ this.init();
438
+ }
439
+
440
+ init() {
441
+ // Handle focus return when offcanvas closes
442
+ const offcanvasElements = getElements('.offcanvas');
443
+
444
+ offcanvasElements.forEach(offcanvas => {
445
+ offcanvas.addEventListener('hidden.bs.offcanvas', () => {
446
+ this.returnFocus(offcanvas);
447
+ });
448
+ });
449
+ }
450
+
451
+ returnFocus(offcanvas) {
452
+ // Find the trigger button that opened this offcanvas
453
+ const triggerId = offcanvas.id;
454
+ const trigger = document.querySelector(`[data-bs-target="#${triggerId}"]`);
455
+
456
+ if (trigger) {
457
+ trigger.focus();
458
+ }
459
+ }
460
+ }
461
+
462
+ // ===================================================================
463
+ // INITIALIZATION
464
+ // ===================================================================
465
+
466
+ /**
467
+ * Initialize all sidebar enhancements when DOM is ready
468
+ */
469
+ function init() {
470
+ // Check if we're on a page that needs sidebar enhancements
471
+ const toc = getElement(config.tocSelector);
472
+ if (!toc) {
473
+ console.log('Sidebar.js: No table of contents found, skipping initialization');
474
+ return;
475
+ }
476
+
477
+ try {
478
+ // Initialize all modules
479
+ const scrollSpy = new ScrollSpy();
480
+ const smoothScroll = new SmoothScroll();
481
+ const keyboardShortcuts = new KeyboardShortcuts();
482
+ const swipeGestures = new SwipeGestures();
483
+ const focusManager = new FocusManager();
484
+
485
+ console.log('Sidebar.js: Successfully initialized all modules');
486
+
487
+ // Cleanup on page unload
488
+ window.addEventListener('beforeunload', () => {
489
+ if (scrollSpy && scrollSpy.destroy) {
490
+ scrollSpy.destroy();
491
+ }
492
+ });
493
+ } catch (error) {
494
+ console.error('Sidebar.js: Initialization error', error);
495
+ }
496
+ }
497
+
498
+ // Wait for DOM and Bootstrap to be ready
499
+ if (document.readyState === 'loading') {
500
+ document.addEventListener('DOMContentLoaded', init);
501
+ } else {
502
+ // DOM already loaded
503
+ if (typeof bootstrap !== 'undefined') {
504
+ init();
505
+ } else {
506
+ // Wait for Bootstrap to load
507
+ window.addEventListener('load', init);
508
+ }
509
+ }
510
+
511
+ })();