jekyll-theme-zer0 0.16.0 → 0.17.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.
@@ -0,0 +1,267 @@
1
+ /**
2
+ * ===================================================================
3
+ * SIDEBAR STATE - Persistence and State Management
4
+ * ===================================================================
5
+ *
6
+ * File: sidebar-state.js
7
+ * Path: assets/js/modules/navigation/sidebar-state.js
8
+ * Purpose: Manage and persist navigation state across page loads
9
+ *
10
+ * Features:
11
+ * - Persist expanded/collapsed state of tree nodes in localStorage
12
+ * - Emit custom events for state changes
13
+ * - Restore state on page load
14
+ * - Track sidebar open/close state
15
+ *
16
+ * Usage:
17
+ * import { SidebarState } from './sidebar-state.js';
18
+ * const state = new SidebarState();
19
+ * state.setExpanded('docs-section', true);
20
+ *
21
+ * ===================================================================
22
+ */
23
+
24
+ import { config } from './config.js';
25
+
26
+ export class SidebarState {
27
+ constructor() {
28
+ this._storagePrefix = config.state.storagePrefix;
29
+ this._expandedNodes = new Set();
30
+
31
+ this._init();
32
+ }
33
+
34
+ /**
35
+ * Initialize state management
36
+ * @private
37
+ */
38
+ _init() {
39
+ // Load persisted state
40
+ this._loadState();
41
+
42
+ // Listen for collapse events from Bootstrap
43
+ this._setupCollapseListeners();
44
+
45
+ // Restore expanded states on page load
46
+ this._restoreExpandedStates();
47
+
48
+ console.log('SidebarState: Initialized');
49
+ }
50
+
51
+ /**
52
+ * Load state from localStorage
53
+ * @private
54
+ */
55
+ _loadState() {
56
+ try {
57
+ const key = this._storagePrefix + config.state.keys.expandedNodes;
58
+ const stored = localStorage.getItem(key);
59
+ if (stored) {
60
+ const parsed = JSON.parse(stored);
61
+ this._expandedNodes = new Set(parsed);
62
+ }
63
+ } catch (error) {
64
+ console.warn('SidebarState: Could not load state from localStorage', error);
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Save state to localStorage
70
+ * @private
71
+ */
72
+ _saveState() {
73
+ try {
74
+ const key = this._storagePrefix + config.state.keys.expandedNodes;
75
+ const value = JSON.stringify([...this._expandedNodes]);
76
+ localStorage.setItem(key, value);
77
+ } catch (error) {
78
+ console.warn('SidebarState: Could not save state to localStorage', error);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Setup listeners for Bootstrap collapse events
84
+ * @private
85
+ */
86
+ _setupCollapseListeners() {
87
+ // Listen for collapse show events
88
+ document.addEventListener('show.bs.collapse', (event) => {
89
+ const nodeId = event.target.id;
90
+ if (nodeId && this._isNavNode(event.target)) {
91
+ this.setExpanded(nodeId, true);
92
+ }
93
+ });
94
+
95
+ // Listen for collapse hide events
96
+ document.addEventListener('hide.bs.collapse', (event) => {
97
+ const nodeId = event.target.id;
98
+ if (nodeId && this._isNavNode(event.target)) {
99
+ this.setExpanded(nodeId, false);
100
+ }
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Check if element is a navigation node
106
+ * @private
107
+ * @param {Element} element
108
+ * @returns {boolean}
109
+ */
110
+ _isNavNode(element) {
111
+ // Check if element is within sidebar or has nav-related classes
112
+ return element.closest('.bd-sidebar, .nav-tree, [data-nav-tree]') !== null;
113
+ }
114
+
115
+ /**
116
+ * Restore expanded states from persisted data
117
+ * @private
118
+ */
119
+ _restoreExpandedStates() {
120
+ // Wait for DOM to be ready
121
+ requestAnimationFrame(() => {
122
+ this._expandedNodes.forEach(nodeId => {
123
+ const element = document.getElementById(nodeId);
124
+ if (element && typeof bootstrap !== 'undefined') {
125
+ // Show the collapse without animation
126
+ element.classList.add('show');
127
+
128
+ // Update the toggle button state
129
+ const toggle = document.querySelector(`[data-bs-target="#${nodeId}"]`);
130
+ if (toggle) {
131
+ toggle.classList.remove('collapsed');
132
+ toggle.setAttribute('aria-expanded', 'true');
133
+ }
134
+ }
135
+ });
136
+ });
137
+ }
138
+
139
+ /**
140
+ * Set expanded state for a node
141
+ * @param {string} nodeId - The ID of the collapse element
142
+ * @param {boolean} expanded - Whether the node should be expanded
143
+ */
144
+ setExpanded(nodeId, expanded) {
145
+ if (expanded) {
146
+ this._expandedNodes.add(nodeId);
147
+ } else {
148
+ this._expandedNodes.delete(nodeId);
149
+ }
150
+
151
+ this._saveState();
152
+
153
+ // Dispatch custom event
154
+ document.dispatchEvent(new CustomEvent('navigation:toggle', {
155
+ detail: {
156
+ nodeId: nodeId,
157
+ expanded: expanded
158
+ }
159
+ }));
160
+ }
161
+
162
+ /**
163
+ * Check if a node is expanded
164
+ * @param {string} nodeId
165
+ * @returns {boolean}
166
+ */
167
+ isExpanded(nodeId) {
168
+ return this._expandedNodes.has(nodeId);
169
+ }
170
+
171
+ /**
172
+ * Expand all nodes
173
+ */
174
+ expandAll() {
175
+ const collapses = document.querySelectorAll('.bd-sidebar .collapse, .nav-tree .collapse');
176
+ collapses.forEach(collapse => {
177
+ if (collapse.id) {
178
+ this._expandedNodes.add(collapse.id);
179
+ if (typeof bootstrap !== 'undefined') {
180
+ const bsCollapse = bootstrap.Collapse.getOrCreateInstance(collapse, { toggle: false });
181
+ bsCollapse.show();
182
+ }
183
+ }
184
+ });
185
+ this._saveState();
186
+
187
+ document.dispatchEvent(new CustomEvent('navigation:expandAll'));
188
+ }
189
+
190
+ /**
191
+ * Collapse all nodes
192
+ */
193
+ collapseAll() {
194
+ const collapses = document.querySelectorAll('.bd-sidebar .collapse.show, .nav-tree .collapse.show');
195
+ collapses.forEach(collapse => {
196
+ if (collapse.id) {
197
+ this._expandedNodes.delete(collapse.id);
198
+ if (typeof bootstrap !== 'undefined') {
199
+ const bsCollapse = bootstrap.Collapse.getInstance(collapse);
200
+ if (bsCollapse) {
201
+ bsCollapse.hide();
202
+ }
203
+ }
204
+ }
205
+ });
206
+ this._saveState();
207
+
208
+ document.dispatchEvent(new CustomEvent('navigation:collapseAll'));
209
+ }
210
+
211
+ /**
212
+ * Expand path to a specific element (expand all parents)
213
+ * @param {string} targetId - The ID of the target element to reveal
214
+ */
215
+ expandPathTo(targetId) {
216
+ const target = document.getElementById(targetId);
217
+ if (!target) return;
218
+
219
+ // Find all parent collapses
220
+ let parent = target.closest('.collapse');
221
+ while (parent) {
222
+ if (parent.id) {
223
+ this.setExpanded(parent.id, true);
224
+ if (typeof bootstrap !== 'undefined') {
225
+ const bsCollapse = bootstrap.Collapse.getOrCreateInstance(parent, { toggle: false });
226
+ bsCollapse.show();
227
+ }
228
+ }
229
+ parent = parent.parentElement?.closest('.collapse');
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Get all expanded node IDs
235
+ * @returns {string[]}
236
+ */
237
+ getExpandedNodes() {
238
+ return [...this._expandedNodes];
239
+ }
240
+
241
+ /**
242
+ * Clear all persisted state
243
+ */
244
+ clearState() {
245
+ this._expandedNodes.clear();
246
+
247
+ try {
248
+ Object.values(config.state.keys).forEach(key => {
249
+ localStorage.removeItem(this._storagePrefix + key);
250
+ });
251
+ } catch (error) {
252
+ console.warn('SidebarState: Could not clear localStorage', error);
253
+ }
254
+
255
+ document.dispatchEvent(new CustomEvent('navigation:stateCleared'));
256
+ }
257
+
258
+ /**
259
+ * Cleanup
260
+ */
261
+ destroy() {
262
+ // State is persisted, nothing to clean up
263
+ console.log('SidebarState: Destroyed');
264
+ }
265
+ }
266
+
267
+ export default SidebarState;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * ===================================================================
3
+ * SMOOTH SCROLL - Enhanced Anchor Navigation
4
+ * ===================================================================
5
+ *
6
+ * File: smooth-scroll.js
7
+ * Path: assets/js/modules/navigation/smooth-scroll.js
8
+ * Purpose: Smooth scrolling to anchor links with header offset
9
+ *
10
+ * Features:
11
+ * - Smooth scroll with configurable offset for fixed headers
12
+ * - URL hash update without page jump
13
+ * - Closes mobile offcanvas after navigation
14
+ * - Accessibility-focused with proper focus management
15
+ *
16
+ * Usage:
17
+ * import { SmoothScroll } from './smooth-scroll.js';
18
+ * const smoothScroll = new SmoothScroll();
19
+ *
20
+ * ===================================================================
21
+ */
22
+
23
+ import { config, isBelowBreakpoint } from './config.js';
24
+
25
+ /**
26
+ * Get all elements safely
27
+ * @param {string} selector - CSS selector
28
+ * @returns {NodeList}
29
+ */
30
+ function getElements(selector) {
31
+ try {
32
+ return document.querySelectorAll(selector);
33
+ } catch (error) {
34
+ console.warn(`SmoothScroll: Elements not found - ${selector}`);
35
+ return [];
36
+ }
37
+ }
38
+
39
+ export class SmoothScroll {
40
+ constructor() {
41
+ this.tocLinks = getElements(config.selectors.tocLinks);
42
+ this._init();
43
+ }
44
+
45
+ /**
46
+ * Initialize click handlers
47
+ * @private
48
+ */
49
+ _init() {
50
+ this.tocLinks.forEach(link => {
51
+ link.addEventListener('click', e => this._handleClick(e));
52
+ });
53
+ console.log(`SmoothScroll: Initialized with ${this.tocLinks.length} links`);
54
+ }
55
+
56
+ /**
57
+ * Handle click on TOC link
58
+ * @private
59
+ * @param {Event} event
60
+ */
61
+ _handleClick(event) {
62
+ const href = event.currentTarget.getAttribute('href');
63
+
64
+ if (!href || !href.startsWith('#')) return;
65
+
66
+ event.preventDefault();
67
+
68
+ const targetId = href.substring(1);
69
+ const targetElement = document.getElementById(targetId);
70
+
71
+ if (!targetElement) {
72
+ console.warn(`SmoothScroll: Target element #${targetId} not found`);
73
+ return;
74
+ }
75
+
76
+ // Scroll to target
77
+ this.scrollToElement(targetElement);
78
+
79
+ // Update URL without jumping
80
+ if (history.pushState) {
81
+ history.pushState(null, null, href);
82
+ }
83
+
84
+ // Close mobile offcanvas if open
85
+ this._closeMobileOffcanvas();
86
+
87
+ // Dispatch custom event
88
+ document.dispatchEvent(new CustomEvent('navigation:scroll', {
89
+ detail: {
90
+ targetId: targetId,
91
+ targetElement: targetElement
92
+ }
93
+ }));
94
+ }
95
+
96
+ /**
97
+ * Scroll to an element with offset
98
+ * @param {Element} element - Target element
99
+ * @param {number} [offset] - Optional custom offset
100
+ */
101
+ scrollToElement(element, offset = config.smoothScroll.offset) {
102
+ const elementPosition = element.getBoundingClientRect().top;
103
+ const offsetPosition = elementPosition + window.pageYOffset - offset;
104
+
105
+ window.scrollTo({
106
+ top: offsetPosition,
107
+ behavior: config.smoothScroll.behavior
108
+ });
109
+
110
+ // Update focus for accessibility
111
+ element.setAttribute('tabindex', '-1');
112
+ element.focus({ preventScroll: true });
113
+ }
114
+
115
+ /**
116
+ * Scroll to element by ID
117
+ * @param {string} id - Element ID (without #)
118
+ * @param {number} [offset] - Optional custom offset
119
+ */
120
+ scrollToId(id, offset) {
121
+ const element = document.getElementById(id);
122
+ if (element) {
123
+ this.scrollToElement(element, offset);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Close mobile offcanvas if viewport is below lg breakpoint
129
+ * @private
130
+ */
131
+ _closeMobileOffcanvas() {
132
+ if (!isBelowBreakpoint('lg')) return;
133
+
134
+ const tocOffcanvas = document.getElementById('tocContents');
135
+ if (tocOffcanvas && typeof bootstrap !== 'undefined') {
136
+ const bsOffcanvas = bootstrap.Offcanvas.getInstance(tocOffcanvas);
137
+ if (bsOffcanvas) {
138
+ bsOffcanvas.hide();
139
+ }
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Cleanup event listeners
145
+ */
146
+ destroy() {
147
+ // Note: We'd need to store bound handlers to properly remove them
148
+ // For now, this is a no-op since the page will reload anyway
149
+ console.log('SmoothScroll: Destroyed');
150
+ }
151
+ }
152
+
153
+ export default SmoothScroll;
@@ -0,0 +1,146 @@
1
+ #!/bin/bash
2
+ # ===================================================================
3
+ # MIGRATE NAV MODES - Update Front Matter Navigation Values
4
+ # ===================================================================
5
+ #
6
+ # File: migrate-nav-modes.sh
7
+ # Path: scripts/migrate-nav-modes.sh
8
+ # Purpose: Update front matter nav values from old to new modes
9
+ #
10
+ # Migration Map:
11
+ # dynamic → auto
12
+ # searchCats → categories
13
+ # docs → tree
14
+ # about → tree
15
+ # quickstart → tree
16
+ # main → tree
17
+ #
18
+ # Usage:
19
+ # ./scripts/migrate-nav-modes.sh # Dry run (preview changes)
20
+ # ./scripts/migrate-nav-modes.sh --apply # Apply changes
21
+ #
22
+ # ===================================================================
23
+
24
+ set -eo pipefail
25
+
26
+ # Colors for output
27
+ RED='\033[0;31m'
28
+ GREEN='\033[0;32m'
29
+ YELLOW='\033[1;33m'
30
+ BLUE='\033[0;34m'
31
+ NC='\033[0m' # No Color
32
+
33
+ # Script configuration
34
+ PAGES_DIR="pages"
35
+ DRY_RUN=true
36
+
37
+ # Parse arguments
38
+ if [[ "${1:-}" == "--apply" ]]; then
39
+ DRY_RUN=false
40
+ fi
41
+
42
+ echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}"
43
+ echo -e "${BLUE} Navigation Mode Migration Script${NC}"
44
+ echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}"
45
+ echo ""
46
+
47
+ if $DRY_RUN; then
48
+ echo -e "${YELLOW}Running in DRY RUN mode. No files will be modified.${NC}"
49
+ echo -e "${YELLOW}Use --apply to make actual changes.${NC}"
50
+ else
51
+ echo -e "${RED}Running in APPLY mode. Files will be modified.${NC}"
52
+ fi
53
+ echo ""
54
+
55
+ # Migration patterns (old → new)
56
+ # Using simple arrays instead of associative arrays for compatibility
57
+ OLD_PATTERNS=("nav: dynamic" "nav: searchCats")
58
+ NEW_PATTERNS=("nav: auto" "nav: categories")
59
+
60
+ # Files that explicitly set nav values (not inherited from _config.yml)
61
+ EXPLICIT_NAV_PATTERNS=(
62
+ "nav: dynamic"
63
+ "nav: searchCats"
64
+ "nav: docs"
65
+ "nav: about"
66
+ "nav: quickstart"
67
+ "nav: main"
68
+ )
69
+
70
+ # Count changes
71
+ TOTAL_FILES=0
72
+ TOTAL_CHANGES=0
73
+
74
+ echo -e "${BLUE}Scanning for files with explicit nav values...${NC}"
75
+ echo ""
76
+
77
+ # Find all markdown files
78
+ while IFS= read -r -d '' file; do
79
+ file_changes=0
80
+
81
+ # Check if file has any nav patterns
82
+ for pattern in "${EXPLICIT_NAV_PATTERNS[@]}"; do
83
+ if grep -q "$pattern" "$file" 2>/dev/null; then
84
+ ((file_changes++)) || true
85
+ fi
86
+ done
87
+
88
+ if [[ $file_changes -gt 0 ]]; then
89
+ ((TOTAL_FILES++)) || true
90
+ echo -e "${GREEN}Found:${NC} $file"
91
+
92
+ # Show what would change
93
+ for i in "${!OLD_PATTERNS[@]}"; do
94
+ old_pattern="${OLD_PATTERNS[$i]}"
95
+ new_pattern="${NEW_PATTERNS[$i]}"
96
+ if grep -q "$old_pattern" "$file" 2>/dev/null; then
97
+ echo -e " ${YELLOW}Change:${NC} '$old_pattern' → '$new_pattern'"
98
+ ((TOTAL_CHANGES++)) || true
99
+
100
+ if ! $DRY_RUN; then
101
+ # Apply the change using sed
102
+ if [[ "$OSTYPE" == "darwin"* ]]; then
103
+ sed -i '' "s/$old_pattern/$new_pattern/g" "$file"
104
+ else
105
+ sed -i "s/$old_pattern/$new_pattern/g" "$file"
106
+ fi
107
+ fi
108
+ fi
109
+ done
110
+
111
+ # Check for named nav files that should use tree mode
112
+ # Note: These are trickier because we want to keep the YAML file reference
113
+ # but change how the template interprets them
114
+ for named_nav in "docs" "about" "quickstart" "main"; do
115
+ if grep -q "nav: $named_nav" "$file" 2>/dev/null; then
116
+ echo -e " ${BLUE}Note:${NC} 'nav: $named_nav' - YAML file will be used with tree mode"
117
+ # No automatic migration - sidebar-left.html handles this via fallthrough
118
+ fi
119
+ done
120
+
121
+ echo ""
122
+ fi
123
+ done < <(find "$PAGES_DIR" -name "*.md" -print0 2>/dev/null)
124
+
125
+ echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}"
126
+ echo -e "${BLUE} Summary${NC}"
127
+ echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}"
128
+ echo ""
129
+ echo -e "Files scanned: ${GREEN}$(find "$PAGES_DIR" -name "*.md" 2>/dev/null | wc -l | tr -d ' ')${NC}"
130
+ echo -e "Files affected: ${GREEN}$TOTAL_FILES${NC}"
131
+ echo -e "Changes made: ${GREEN}$TOTAL_CHANGES${NC}"
132
+ echo ""
133
+
134
+ if $DRY_RUN; then
135
+ echo -e "${YELLOW}This was a dry run. To apply changes, run:${NC}"
136
+ echo -e " ${GREEN}./scripts/migrate-nav-modes.sh --apply${NC}"
137
+ else
138
+ echo -e "${GREEN}Migration complete!${NC}"
139
+ fi
140
+
141
+ echo ""
142
+ echo -e "${BLUE}Navigation Mode Reference:${NC}"
143
+ echo " auto - Auto-generated from collection documents"
144
+ echo " tree - YAML-defined hierarchical navigation"
145
+ echo " categories - Category-based grouping"
146
+ echo ""
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-theme-zer0
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amr Abdel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-12-20 00:00:00.000000000 Z
11
+ date: 2025-12-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -80,9 +80,11 @@ files:
80
80
  - _data/README.md
81
81
  - _data/authors.yml
82
82
  - _data/content_statistics.yml
83
+ - _data/features.yml
83
84
  - _data/generate_statistics.rb
84
85
  - _data/generate_statistics.sh
85
86
  - _data/github-actions-example.yml
87
+ - _data/navigation/README.md
86
88
  - _data/navigation/about.yml
87
89
  - _data/navigation/docs.yml
88
90
  - _data/navigation/home.yml
@@ -127,6 +129,7 @@ files:
127
129
  - _includes/landing/landing-install-cards.html
128
130
  - _includes/landing/landing-quick-links.html
129
131
  - _includes/navigation/breadcrumbs.html
132
+ - _includes/navigation/nav-tree.html
130
133
  - _includes/navigation/nav_list.html
131
134
  - _includes/navigation/navbar.html
132
135
  - _includes/navigation/sidebar-categories.html
@@ -157,6 +160,7 @@ files:
157
160
  - _plugins/preview_image_generator.rb
158
161
  - _plugins/theme_version.rb
159
162
  - _sass/core/_docs.scss
163
+ - _sass/core/_nav-tree.scss
160
164
  - _sass/core/_syntax.scss
161
165
  - _sass/core/_theme.scss
162
166
  - _sass/core/_variables.scss
@@ -202,12 +206,19 @@ files:
202
206
  - assets/js/color-modes.js
203
207
  - assets/js/docs.min.js
204
208
  - assets/js/halfmoon.js
209
+ - assets/js/modules/navigation/config.js
210
+ - assets/js/modules/navigation/focus.js
211
+ - assets/js/modules/navigation/gestures.js
212
+ - assets/js/modules/navigation/index.js
213
+ - assets/js/modules/navigation/keyboard.js
214
+ - assets/js/modules/navigation/scroll-spy.js
215
+ - assets/js/modules/navigation/sidebar-state.js
216
+ - assets/js/modules/navigation/smooth-scroll.js
205
217
  - assets/js/myScript.js
206
218
  - assets/js/nanobar.min.js
207
219
  - assets/js/particles-source.js
208
220
  - assets/js/particles.js
209
221
  - assets/js/side-bar-folders.js
210
- - assets/js/sidebar.js
211
222
  - assets/particles.json
212
223
  - scripts/README.md
213
224
  - scripts/analyze-commits.sh
@@ -231,6 +242,7 @@ files:
231
242
  - scripts/lib/preview_generator.py
232
243
  - scripts/lib/validation.sh
233
244
  - scripts/lib/version.sh
245
+ - scripts/migrate-nav-modes.sh
234
246
  - scripts/release
235
247
  - scripts/setup.sh
236
248
  - scripts/test-auto-version.sh