ultimate-jekyll-manager 0.0.188 → 0.0.190

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 (28) hide show
  1. package/CLAUDE.md +64 -0
  2. package/README.md +29 -0
  3. package/TODO.md +2 -0
  4. package/dist/assets/js/core/appearance.js +197 -0
  5. package/dist/assets/js/pages/test/libraries/appearance/index.js +214 -0
  6. package/dist/assets/js/ultimate-jekyll-manager.js +2 -0
  7. package/dist/assets/themes/bootstrap/_theme.scss +9 -230
  8. package/dist/assets/themes/bootstrap/overrides/_accordion.scss +21 -0
  9. package/dist/assets/themes/{classy/css/components → bootstrap/overrides}/_avatars.scss +18 -3
  10. package/dist/assets/themes/bootstrap/overrides/_blog-images.scss +45 -0
  11. package/dist/assets/themes/bootstrap/overrides/_buttons-adaptive.scss +122 -0
  12. package/dist/assets/themes/bootstrap/overrides/_cursor-utilities.scss +13 -0
  13. package/dist/assets/themes/bootstrap/overrides/_index.scss +29 -0
  14. package/dist/assets/themes/bootstrap/overrides/_links.scss +31 -0
  15. package/dist/assets/themes/{classy/css/base → bootstrap/overrides}/_soft-colors.scss +11 -4
  16. package/dist/assets/themes/bootstrap/overrides/_spacing-extended.scss +74 -0
  17. package/dist/assets/themes/{classy/css/components → bootstrap/overrides}/_spinners.scss +6 -3
  18. package/dist/assets/themes/bootstrap/overrides/_typography-utilities.scss +40 -0
  19. package/dist/assets/themes/classy/_theme.js +2 -1
  20. package/dist/assets/themes/classy/_theme.scss +4 -5
  21. package/dist/assets/themes/classy/css/base/_typography.scss +0 -31
  22. package/dist/assets/themes/classy/css/base/_utilities.scss +1 -14
  23. package/dist/assets/themes/classy/css/components/_buttons.scss +0 -133
  24. package/dist/defaults/dist/_includes/core/body.html +46 -10
  25. package/dist/defaults/dist/pages/test/libraries/appearance.html +271 -0
  26. package/package.json +1 -1
  27. package/dist/assets/themes/classy/css/base/_spacing.scss +0 -68
  28. package/dist/assets/themes/classy/css/components/_links.scss +0 -19
package/CLAUDE.md CHANGED
@@ -558,6 +558,70 @@ data-lazy="@type value"
558
558
 
559
559
  **Implementation:** `src/assets/js/core/lazy-loading.js`
560
560
 
561
+ ## Appearance Switching System
562
+
563
+ Ultimate Jekyll supports dark/light/system theme switching with user preference persistence.
564
+
565
+ ### Supported Modes
566
+ - `dark` - Force dark mode
567
+ - `light` - Force light mode
568
+ - `system` - Auto-detect from OS preference (`prefers-color-scheme`)
569
+
570
+ ### JavaScript API
571
+
572
+ ```javascript
573
+ // Get/set preference
574
+ webManager.uj().appearance.get(); // Returns 'dark', 'light', 'system', or null
575
+ webManager.uj().appearance.set('dark'); // Save and apply preference
576
+ webManager.uj().appearance.getResolved(); // Returns actual theme: 'dark' or 'light'
577
+
578
+ // Utilities
579
+ webManager.uj().appearance.toggle(); // Toggle between dark/light
580
+ webManager.uj().appearance.cycle(); // Cycle: dark → light → system → dark
581
+ webManager.uj().appearance.clear(); // Clear saved preference
582
+ ```
583
+
584
+ ### HTML Data Attributes
585
+
586
+ ```html
587
+ <!-- Buttons to set appearance (auto-gets 'active' class) -->
588
+ <button data-appearance-set="light">Light</button>
589
+ <button data-appearance-set="dark">Dark</button>
590
+ <button data-appearance-set="system">System</button>
591
+
592
+ <!-- Display current mode as text -->
593
+ <span data-appearance-current></span>
594
+
595
+ <!-- Show/hide icons based on current mode -->
596
+ <span data-appearance-icon="light" hidden>☀️</span>
597
+ <span data-appearance-icon="dark" hidden>🌙</span>
598
+ <span data-appearance-icon="system" hidden>💻</span>
599
+ ```
600
+
601
+ ### Dropdown Example
602
+
603
+ ```html
604
+ <div class="dropdown">
605
+ <button class="btn dropdown-toggle" data-bs-toggle="dropdown">
606
+ <span data-appearance-icon="light" hidden>{% uj_icon "sun", "fa-md me-2" %}</span>
607
+ <span data-appearance-icon="dark" hidden>{% uj_icon "moon-stars", "fa-md me-2" %}</span>
608
+ <span data-appearance-icon="system" hidden>{% uj_icon "circle-half-stroke", "fa-md me-2" %}</span>
609
+ <span data-appearance-current></span>
610
+ </button>
611
+ <ul class="dropdown-menu">
612
+ <li><a class="dropdown-item" href="#" data-appearance-set="light">Light</a></li>
613
+ <li><a class="dropdown-item" href="#" data-appearance-set="dark">Dark</a></li>
614
+ <li><a class="dropdown-item" href="#" data-appearance-set="system">System</a></li>
615
+ </ul>
616
+ </div>
617
+ ```
618
+
619
+ ### Implementation
620
+ - **Inline script:** `src/defaults/dist/_includes/core/body.html` - Runs immediately to prevent flash
621
+ - **Module:** `src/assets/js/core/appearance.js` - API and UI handling
622
+ - **Storage:** Saved under `_manager.appearance.preference` in localStorage
623
+ - **Test page:** `/test/libraries/appearance`
624
+
561
625
  ## JavaScript Libraries
562
626
 
563
627
  ### WebManager
package/README.md CHANGED
@@ -256,6 +256,35 @@ The `<html>` element has data attributes for JavaScript/CSS targeting:
256
256
  | `data-runtime` | `web`, `extension`, `electron`, `node` |
257
257
  | `aria-busy` | `true` (loading), `false` (ready) |
258
258
 
259
+ ### Appearance Switching
260
+
261
+ Ultimate Jekyll supports dark/light/system theme switching with user preference persistence.
262
+
263
+ **JavaScript API:**
264
+ ```javascript
265
+ webManager.uj().appearance.get(); // Returns 'dark', 'light', 'system', or null
266
+ webManager.uj().appearance.set('dark'); // Save and apply preference
267
+ webManager.uj().appearance.toggle(); // Toggle dark/light
268
+ webManager.uj().appearance.cycle(); // Cycle: dark → light → system
269
+ ```
270
+
271
+ **HTML Dropdown Example:**
272
+ ```html
273
+ <div class="dropdown">
274
+ <button class="btn dropdown-toggle" data-bs-toggle="dropdown">
275
+ <span data-appearance-icon="light" hidden>{% uj_icon "sun" %}</span>
276
+ <span data-appearance-icon="dark" hidden>{% uj_icon "moon-stars" %}</span>
277
+ <span data-appearance-icon="system" hidden>{% uj_icon "circle-half-stroke" %}</span>
278
+ <span data-appearance-current></span>
279
+ </button>
280
+ <ul class="dropdown-menu">
281
+ <li><a href="#" data-appearance-set="light">Light</a></li>
282
+ <li><a href="#" data-appearance-set="dark">Dark</a></li>
283
+ <li><a href="#" data-appearance-set="system">System</a></li>
284
+ </ul>
285
+ </div>
286
+ ```
287
+
259
288
  ### Page Loading Protection System
260
289
 
261
290
  Ultimate Jekyll includes an automatic protection system that prevents users from clicking buttons before JavaScript is fully loaded, eliminating race conditions and errors.
package/TODO.md CHANGED
@@ -8,6 +8,8 @@ NEW TODO
8
8
  - form manager should NOT submit if the button that was clicked is disabled (class or attribute)
9
9
  - GLOBAL FIXES FOR ALL UJM PROJECTS: nav.json should ahve class only not color
10
10
 
11
+ /authentication-required
12
+ * ithink this is leftover from Electron Manager auth system??? but lots on proxifly. need to make a version of it for UJM2.0
11
13
 
12
14
  Make an admin dashboard backend
13
15
 
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Appearance Module
3
+ * Handles theme appearance switching (dark, light, system)
4
+ */
5
+
6
+ // Constants
7
+ const STORAGE_KEY = 'appearance.preference';
8
+ const VALID_VALUES = ['dark', 'light', 'system'];
9
+
10
+ // Module state
11
+ let webManager = null;
12
+ let mediaQuery = null;
13
+
14
+ // Module
15
+ export default (Manager) => {
16
+ // Shortcuts
17
+ webManager = Manager.webManager;
18
+
19
+ // Create appearance API
20
+ const appearanceAPI = {
21
+ /**
22
+ * Get the current saved preference
23
+ * @returns {string|null} 'dark', 'light', 'system', or null if not set
24
+ */
25
+ get: () => webManager.storage().get(STORAGE_KEY) || null,
26
+
27
+ /**
28
+ * Get the resolved (actual) theme being displayed
29
+ * @returns {string} 'dark' or 'light'
30
+ */
31
+ getResolved: () => document.documentElement.getAttribute('data-bs-theme') || 'dark',
32
+
33
+ /**
34
+ * Set the appearance preference
35
+ * @param {string} value - 'dark', 'light', or 'system'
36
+ */
37
+ set: (value) => {
38
+ // Validate
39
+ if (!VALID_VALUES.includes(value)) {
40
+ console.warn(`Invalid appearance value: ${value}. Must be one of: ${VALID_VALUES.join(', ')}`);
41
+ return;
42
+ }
43
+
44
+ // Save preference
45
+ webManager.storage().set(STORAGE_KEY, value);
46
+
47
+ // Apply theme
48
+ applyTheme(value);
49
+
50
+ // Update UI elements
51
+ updateUI(value);
52
+
53
+ // Setup or teardown system preference listener
54
+ setupSystemListener(value === 'system');
55
+ },
56
+
57
+ /**
58
+ * Toggle between dark and light (skips system)
59
+ */
60
+ toggle() {
61
+ const current = this.getResolved();
62
+ const next = current === 'dark' ? 'light' : 'dark';
63
+ this.set(next);
64
+ },
65
+
66
+ /**
67
+ * Cycle through all three modes: dark → light → system → dark
68
+ */
69
+ cycle() {
70
+ const current = this.get() || this.getResolved();
71
+ const order = ['dark', 'light', 'system'];
72
+ const currentIndex = order.indexOf(current);
73
+ const nextIndex = (currentIndex + 1) % order.length;
74
+ this.set(order[nextIndex]);
75
+ },
76
+
77
+ /**
78
+ * Clear saved preference (revert to site default)
79
+ */
80
+ clear: () => {
81
+ webManager.storage().remove(STORAGE_KEY);
82
+ updateUI(null);
83
+ setupSystemListener(false);
84
+ }
85
+ };
86
+
87
+ // Register on UJ library
88
+ webManager._ujLibrary.appearance = appearanceAPI;
89
+
90
+ // Initialize UI event listeners
91
+ initializeUI();
92
+
93
+ // Setup system listener if current preference is 'system'
94
+ const currentPreference = appearanceAPI.get();
95
+ if (currentPreference === 'system') {
96
+ setupSystemListener(true);
97
+ }
98
+
99
+ // Update UI with current state
100
+ updateUI(currentPreference);
101
+
102
+ console.log('Appearance module loaded');
103
+ };
104
+
105
+ /**
106
+ * Apply theme to the document
107
+ * @param {string} preference - 'dark', 'light', or 'system'
108
+ */
109
+ const applyTheme = (preference) => {
110
+ let theme = preference;
111
+
112
+ // Resolve system preference
113
+ if (preference === 'system') {
114
+ theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
115
+ }
116
+
117
+ // Apply to document
118
+ document.documentElement.setAttribute('data-bs-theme', theme);
119
+ };
120
+
121
+ /**
122
+ * Setup or teardown the system preference change listener
123
+ * @param {boolean} enable - Whether to enable the listener
124
+ */
125
+ const setupSystemListener = (enable) => {
126
+ // Clean up existing listener
127
+ if (mediaQuery) {
128
+ mediaQuery.removeEventListener('change', handleSystemChange);
129
+ mediaQuery = null;
130
+ }
131
+
132
+ // Setup new listener if needed
133
+ if (enable) {
134
+ mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
135
+ mediaQuery.addEventListener('change', handleSystemChange);
136
+ }
137
+ };
138
+
139
+ /**
140
+ * Handle system preference change
141
+ * @param {MediaQueryListEvent} event
142
+ */
143
+ const handleSystemChange = (event) => {
144
+ const theme = event.matches ? 'dark' : 'light';
145
+ document.documentElement.setAttribute('data-bs-theme', theme);
146
+ updateUI('system');
147
+ };
148
+
149
+ /**
150
+ * Initialize UI event listeners
151
+ */
152
+ const initializeUI = () => {
153
+ // Use event delegation for appearance controls
154
+ document.addEventListener('click', (event) => {
155
+ const $target = event.target.closest('[data-appearance-set]');
156
+
157
+ if (!$target) {
158
+ return;
159
+ }
160
+
161
+ event.preventDefault();
162
+
163
+ const value = $target.getAttribute('data-appearance-set');
164
+ webManager.uj().appearance.set(value);
165
+ });
166
+ };
167
+
168
+ /**
169
+ * Update UI elements to reflect current state
170
+ * @param {string|null} preference - Current preference
171
+ */
172
+ const updateUI = (preference) => {
173
+ const resolved = document.documentElement.getAttribute('data-bs-theme');
174
+ const displayValue = preference || resolved;
175
+
176
+ // Update [data-appearance-current] elements with the preference
177
+ document.querySelectorAll('[data-appearance-current]').forEach(($el) => {
178
+ const format = $el.getAttribute('data-appearance-current') || 'preference';
179
+ $el.textContent = format === 'resolved' ? resolved : displayValue;
180
+ });
181
+
182
+ // Update [data-appearance-icon] elements - show/hide based on current mode
183
+ // Icons should have data-appearance-icon="light|dark|system" attribute
184
+ document.querySelectorAll('[data-appearance-icon]').forEach(($el) => {
185
+ const iconMode = $el.getAttribute('data-appearance-icon');
186
+ $el.hidden = iconMode !== displayValue;
187
+ });
188
+
189
+ // Update active state on [data-appearance-set] elements
190
+ document.querySelectorAll('[data-appearance-set]').forEach(($el) => {
191
+ const value = $el.getAttribute('data-appearance-set');
192
+ const isActive = value === preference || (!preference && value === resolved);
193
+
194
+ $el.classList.toggle('active', isActive);
195
+ $el.setAttribute('aria-pressed', isActive ? 'true' : 'false');
196
+ });
197
+ };
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Appearance Test Page JavaScript
3
+ */
4
+
5
+ // Libraries
6
+ let webManager = null;
7
+
8
+ // Module
9
+ export default (Manager) => {
10
+ return new Promise(async function (resolve) {
11
+ // Shortcuts
12
+ webManager = Manager.webManager;
13
+
14
+ // Initialize when DOM is ready
15
+ await webManager.dom().ready();
16
+
17
+ // Initialize debug panel
18
+ initDebugPanel();
19
+
20
+ // Initialize programmatic controls
21
+ initControls();
22
+
23
+ // Initialize event logging
24
+ initEventLog();
25
+
26
+ // Resolve after initialization
27
+ return resolve();
28
+ });
29
+ };
30
+
31
+ /**
32
+ * Initialize debug panel with live values
33
+ */
34
+ function initDebugPanel() {
35
+ const $debugSaved = document.getElementById('debug-saved');
36
+ const $debugResolved = document.getElementById('debug-resolved');
37
+ const $debugAttr = document.getElementById('debug-attr');
38
+ const $debugLocalStorage = document.getElementById('debug-localstorage');
39
+ const $debugSystem = document.getElementById('debug-system');
40
+ const $btnRefresh = document.getElementById('btn-refresh');
41
+
42
+ // Update function
43
+ function updateDebug() {
44
+ const appearance = webManager.uj().appearance;
45
+
46
+ // Saved preference via API
47
+ const saved = appearance.get();
48
+ $debugSaved.textContent = saved !== null ? `"${saved}"` : 'null (not set)';
49
+
50
+ // Resolved theme via API
51
+ const resolved = appearance.getResolved();
52
+ $debugResolved.textContent = `"${resolved}"`;
53
+
54
+ // Raw HTML attribute
55
+ const attr = document.documentElement.getAttribute('data-bs-theme');
56
+ $debugAttr.textContent = `"${attr}"`;
57
+
58
+ // Raw localStorage value (stored under _manager.appearance.preference)
59
+ let rawStorage = null;
60
+ try {
61
+ const managerData = localStorage.getItem('_manager');
62
+ const parsedData = managerData ? JSON.parse(managerData) : null;
63
+ rawStorage = parsedData?.appearance?.preference || null;
64
+ } catch (e) {
65
+ rawStorage = '(error reading)';
66
+ }
67
+ $debugLocalStorage.textContent = rawStorage !== null ? `"${rawStorage}"` : 'null';
68
+
69
+ // System preference
70
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
71
+ const systemPref = prefersDark ? 'dark' : 'light';
72
+ $debugSystem.textContent = `"${systemPref}" (prefers-color-scheme: ${prefersDark ? 'dark' : 'light'})`;
73
+
74
+ console.log('[Appearance Test] Debug updated:', {
75
+ saved,
76
+ resolved,
77
+ attr,
78
+ rawStorage,
79
+ systemPref,
80
+ });
81
+ }
82
+
83
+ // Initial update
84
+ updateDebug();
85
+
86
+ // Refresh button
87
+ $btnRefresh.addEventListener('click', updateDebug);
88
+
89
+ // Auto-update when theme changes (observe attribute changes)
90
+ const observer = new MutationObserver((mutations) => {
91
+ mutations.forEach((mutation) => {
92
+ if (mutation.attributeName === 'data-bs-theme') {
93
+ updateDebug();
94
+ }
95
+ });
96
+ });
97
+ observer.observe(document.documentElement, { attributes: true });
98
+
99
+ // Also listen for system preference changes
100
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
101
+ mediaQuery.addEventListener('change', updateDebug);
102
+
103
+ // Export for console access
104
+ window.updateAppearanceDebug = updateDebug;
105
+ }
106
+
107
+ /**
108
+ * Initialize programmatic control buttons
109
+ */
110
+ function initControls() {
111
+ const appearance = webManager.uj().appearance;
112
+
113
+ // Toggle button
114
+ document.getElementById('btn-toggle').addEventListener('click', () => {
115
+ console.log('[Appearance Test] Toggle clicked');
116
+ appearance.toggle();
117
+ });
118
+
119
+ // Cycle button
120
+ document.getElementById('btn-cycle').addEventListener('click', () => {
121
+ console.log('[Appearance Test] Cycle clicked');
122
+ appearance.cycle();
123
+ });
124
+
125
+ // Clear button
126
+ document.getElementById('btn-clear').addEventListener('click', () => {
127
+ console.log('[Appearance Test] Clear clicked');
128
+ appearance.clear();
129
+ // Trigger debug update
130
+ window.updateAppearanceDebug?.();
131
+ });
132
+
133
+ // Quick set buttons
134
+ document.getElementById('btn-set-light').addEventListener('click', () => {
135
+ console.log('[Appearance Test] set("light") clicked');
136
+ appearance.set('light');
137
+ });
138
+
139
+ document.getElementById('btn-set-dark').addEventListener('click', () => {
140
+ console.log('[Appearance Test] set("dark") clicked');
141
+ appearance.set('dark');
142
+ });
143
+
144
+ document.getElementById('btn-set-system').addEventListener('click', () => {
145
+ console.log('[Appearance Test] set("system") clicked');
146
+ appearance.set('system');
147
+ });
148
+ }
149
+
150
+ /**
151
+ * Initialize event logging
152
+ */
153
+ function initEventLog() {
154
+ const $log = document.getElementById('event-log');
155
+ const $clearBtn = document.getElementById('btn-clear-log');
156
+ let logLines = [];
157
+
158
+ function addLogEntry(message) {
159
+ const timestamp = new Date().toLocaleTimeString();
160
+ const entry = `[${timestamp}] ${message}`;
161
+ logLines.push(entry);
162
+
163
+ // Keep last 50 entries
164
+ if (logLines.length > 50) {
165
+ logLines = logLines.slice(-50);
166
+ }
167
+
168
+ $log.textContent = logLines.join('\n');
169
+ $log.scrollTop = $log.scrollHeight;
170
+ }
171
+
172
+ // Clear log button
173
+ $clearBtn.addEventListener('click', () => {
174
+ logLines = [];
175
+ $log.textContent = 'Log cleared...';
176
+ });
177
+
178
+ // Observe data-bs-theme changes
179
+ const observer = new MutationObserver((mutations) => {
180
+ mutations.forEach((mutation) => {
181
+ if (mutation.attributeName === 'data-bs-theme') {
182
+ const oldValue = mutation.oldValue;
183
+ const newValue = document.documentElement.getAttribute('data-bs-theme');
184
+ addLogEntry(`Theme changed: "${oldValue}" -> "${newValue}"`);
185
+ }
186
+ });
187
+ });
188
+ observer.observe(document.documentElement, {
189
+ attributes: true,
190
+ attributeOldValue: true,
191
+ attributeFilter: ['data-bs-theme'],
192
+ });
193
+
194
+ // Log system preference changes
195
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
196
+ mediaQuery.addEventListener('change', (event) => {
197
+ const systemPref = event.matches ? 'dark' : 'light';
198
+ addLogEntry(`System preference changed: "${systemPref}"`);
199
+ });
200
+
201
+ // Log click events on appearance controls
202
+ document.addEventListener('click', (event) => {
203
+ const $target = event.target.closest('[data-appearance-set]');
204
+ if ($target) {
205
+ const value = $target.getAttribute('data-appearance-set');
206
+ addLogEntry(`User clicked: set("${value}")`);
207
+ }
208
+ });
209
+
210
+ // Initial log entry
211
+ addLogEntry('Appearance test page loaded');
212
+ addLogEntry(`Initial theme: "${document.documentElement.getAttribute('data-bs-theme')}"`);
213
+ addLogEntry(`Saved preference: ${webManager.uj().appearance.get() || '(none)'}`);
214
+ }
@@ -4,6 +4,7 @@ import authModule from '__main_assets__/js/core/auth.js';
4
4
  import lazyLoadingModule from '__main_assets__/js/core/lazy-loading.js';
5
5
  import queryStringsModule from '__main_assets__/js/core/query-strings.js';
6
6
  import serviceWorkerModule from '__main_assets__/js/core/service-worker.js';
7
+ import appearanceModule from '__main_assets__/js/core/appearance.js';
7
8
  import completeModule from '__main_assets__/js/core/complete.js';
8
9
 
9
10
  // Ultimate Jekyll Manager Module
@@ -32,6 +33,7 @@ export default async function (Manager, options) {
32
33
  lazyLoadingModule(Manager, options);
33
34
  queryStringsModule(Manager, options);
34
35
  serviceWorkerModule(Manager, options);
36
+ appearanceModule(Manager, options);
35
37
 
36
38
  // Conditionally loaded modules based on config (keep as dynamic imports)
37
39
  const conditionalModules = [