ultimate-jekyll-manager 0.0.187 → 0.0.189
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.
- package/dist/assets/js/core/appearance.js +197 -0
- package/dist/assets/js/modules/vert.js +2 -2
- package/dist/assets/js/pages/test/libraries/appearance/index.js +214 -0
- package/dist/assets/js/ultimate-jekyll-manager.js +2 -0
- package/dist/defaults/dist/_includes/core/body.html +46 -10
- package/dist/defaults/dist/pages/test/libraries/appearance.html +271 -0
- package/package.json +1 -1
|
@@ -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
|
+
};
|
|
@@ -151,8 +151,8 @@ const createCustomAd = ($vertUnit, config) => {
|
|
|
151
151
|
const iframeId = `vert-${window.__ujVertIdCounter = (window.__ujVertIdCounter || 0) + 1}`;
|
|
152
152
|
|
|
153
153
|
// Build base URL for the ad content
|
|
154
|
-
// Use local server if debug=true OR if we're in development mode
|
|
155
|
-
const baseURL = (qsDebug || webManager.isDevelopment())
|
|
154
|
+
// Use local server if debug=true OR if we're in development mode AND on promo-server
|
|
155
|
+
const baseURL = (qsDebug || (webManager.isDevelopment() && webManager.config.brand.id === 'promo-server'))
|
|
156
156
|
? `${window.location.protocol}//${window.location.host}/verts/main`
|
|
157
157
|
: 'https://promo-server.itwcreativeworks.com/verts/main';
|
|
158
158
|
|
|
@@ -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 = [
|
|
@@ -38,18 +38,54 @@
|
|
|
38
38
|
<!-- Prerendered Icons -->
|
|
39
39
|
{%- assign icons = page.resolved.prerender_icons | default: empty -%}
|
|
40
40
|
{%- iftruthy icons -%}
|
|
41
|
-
<!-- Pre-rendered Icon Templates -->
|
|
42
|
-
<div id="prerendered-icons" class="d-none" aria-hidden="true">
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
</div>
|
|
41
|
+
<!-- Pre-rendered Icon Templates -->
|
|
42
|
+
<div id="prerendered-icons" class="d-none" aria-hidden="true">
|
|
43
|
+
{%- for icon in icons -%}
|
|
44
|
+
{%- assign icon_name = icon.name | default: icon -%}
|
|
45
|
+
{%- assign icon_class = icon.class | default: "fa-3xl" -%}
|
|
46
|
+
<div data-icon="{{ icon_name }}" data-class="{{ icon_class }}">
|
|
47
|
+
{% uj_icon icon_name, icon_class %}
|
|
48
|
+
</div>
|
|
49
|
+
{%- endfor -%}
|
|
50
|
+
</div>
|
|
51
51
|
{%- endiftruthy -%}
|
|
52
52
|
|
|
53
|
+
<!-- Script - Set preferred appearance -->
|
|
54
|
+
<!-- This runs immediately to prevent flash of wrong theme -->
|
|
55
|
+
<!-- Priority: 1) User saved preference, 2) Site preset, 3) System preference (if preset is 'system') -->
|
|
56
|
+
<script type="text/javascript">
|
|
57
|
+
(function() {
|
|
58
|
+
'use strict';
|
|
59
|
+
var $html = document.documentElement;
|
|
60
|
+
|
|
61
|
+
// Get the preset value from Jekyll (set at build time)
|
|
62
|
+
var preset = $html.getAttribute('data-bs-theme');
|
|
63
|
+
|
|
64
|
+
// Try to read user's saved preference from _manager storage
|
|
65
|
+
// (same format as webManager.storage() uses)
|
|
66
|
+
var saved = null;
|
|
67
|
+
try {
|
|
68
|
+
var managerData = localStorage.getItem('_manager');
|
|
69
|
+
var parsedData = managerData ? JSON.parse(managerData) : null;
|
|
70
|
+
saved = parsedData && parsedData.appearance && parsedData.appearance.preference;
|
|
71
|
+
} catch (e) {}
|
|
72
|
+
|
|
73
|
+
// Use saved preference if available, otherwise fall back to preset
|
|
74
|
+
var preference = saved || preset;
|
|
75
|
+
var theme = preference;
|
|
76
|
+
|
|
77
|
+
// If preference is 'system', resolve to actual light/dark based on OS setting
|
|
78
|
+
if (preference === 'system') {
|
|
79
|
+
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Only update if different from preset (avoid unnecessary DOM write)
|
|
83
|
+
if (theme && theme !== preset) {
|
|
84
|
+
$html.setAttribute('data-bs-theme', theme);
|
|
85
|
+
}
|
|
86
|
+
})();
|
|
87
|
+
</script>
|
|
88
|
+
|
|
53
89
|
<!-- Script to prevent clicks on disabled elements during page load -->
|
|
54
90
|
<script type="text/javascript">
|
|
55
91
|
(function() {
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
---
|
|
2
|
+
### ALL PAGES ###
|
|
3
|
+
layout: themes/[ site.theme.id ]/frontend/core/base
|
|
4
|
+
permalink: /test/libraries/appearance
|
|
5
|
+
|
|
6
|
+
### REGULAR PAGES ###
|
|
7
|
+
sitemap:
|
|
8
|
+
include: false
|
|
9
|
+
meta:
|
|
10
|
+
title: "Appearance Test Page"
|
|
11
|
+
description: "Testing the appearance switching system (dark, light, system modes)."
|
|
12
|
+
breadcrumb: "Appearance Test"
|
|
13
|
+
index: false
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<div class="container py-5">
|
|
18
|
+
<h1 class="mb-4">Appearance Test Page</h1>
|
|
19
|
+
<p class="text-muted mb-5">Testing the appearance switching system with detailed debugging information.</p>
|
|
20
|
+
|
|
21
|
+
<div class="row g-4">
|
|
22
|
+
|
|
23
|
+
<!-- Theme Switcher -->
|
|
24
|
+
<div class="col-lg-6">
|
|
25
|
+
<div class="card">
|
|
26
|
+
<div class="card-header">
|
|
27
|
+
<h5 class="mb-0">Theme Switcher</h5>
|
|
28
|
+
<small class="text-muted">Click to change appearance mode</small>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="card-body">
|
|
31
|
+
<!-- Dropdown Switcher -->
|
|
32
|
+
<div class="mb-4">
|
|
33
|
+
<label class="form-label">Dropdown Switcher</label>
|
|
34
|
+
<div class="dropdown">
|
|
35
|
+
<button class="btn btn-outline-adaptive dropdown-toggle w-100" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
36
|
+
<span data-appearance-icon="light" hidden>{% uj_icon "sun", "fa-md me-2" %}</span>
|
|
37
|
+
<span data-appearance-icon="dark" hidden>{% uj_icon "moon-stars", "fa-md me-2" %}</span>
|
|
38
|
+
<span data-appearance-icon="system" hidden>{% uj_icon "circle-half-stroke", "fa-md me-2" %}</span>
|
|
39
|
+
<span data-appearance-current></span>
|
|
40
|
+
</button>
|
|
41
|
+
<ul class="dropdown-menu w-100">
|
|
42
|
+
<li><a class="dropdown-item" href="#" data-appearance-set="light">{% uj_icon "sun", "fa-md me-2" %} Light</a></li>
|
|
43
|
+
<li><a class="dropdown-item" href="#" data-appearance-set="dark">{% uj_icon "moon-stars", "fa-md me-2" %} Dark</a></li>
|
|
44
|
+
<li><a class="dropdown-item" href="#" data-appearance-set="system">{% uj_icon "circle-half-stroke", "fa-md me-2" %} System</a></li>
|
|
45
|
+
</ul>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<!-- Button Group Switcher -->
|
|
50
|
+
<div class="mb-4">
|
|
51
|
+
<label class="form-label">Button Group Switcher</label>
|
|
52
|
+
<div class="btn-group w-100" role="group">
|
|
53
|
+
<button type="button" class="btn btn-outline-adaptive" data-appearance-set="light">
|
|
54
|
+
{% uj_icon "sun", "fa-md me-1" %} Light
|
|
55
|
+
</button>
|
|
56
|
+
<button type="button" class="btn btn-outline-adaptive" data-appearance-set="dark">
|
|
57
|
+
{% uj_icon "moon-stars", "fa-md me-1" %} Dark
|
|
58
|
+
</button>
|
|
59
|
+
<button type="button" class="btn btn-outline-adaptive" data-appearance-set="system">
|
|
60
|
+
{% uj_icon "circle-half-stroke", "fa-md me-1" %} System
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<!-- Programmatic Controls -->
|
|
66
|
+
<div class="mb-4">
|
|
67
|
+
<label class="form-label">Programmatic Controls</label>
|
|
68
|
+
<div class="d-flex gap-2 flex-wrap">
|
|
69
|
+
<button type="button" class="btn btn-primary btn-sm" id="btn-toggle">
|
|
70
|
+
{% uj_icon "repeat", "fa-md me-1" %} Toggle (dark/light)
|
|
71
|
+
</button>
|
|
72
|
+
<button type="button" class="btn btn-secondary btn-sm" id="btn-cycle">
|
|
73
|
+
{% uj_icon "arrows-spin", "fa-md me-1" %} Cycle (all 3)
|
|
74
|
+
</button>
|
|
75
|
+
<button type="button" class="btn btn-warning btn-sm" id="btn-clear">
|
|
76
|
+
{% uj_icon "trash", "fa-md me-1" %} Clear Preference
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<!-- Quick Set -->
|
|
82
|
+
<div>
|
|
83
|
+
<label class="form-label">Quick Set via API</label>
|
|
84
|
+
<div class="d-flex gap-2 flex-wrap">
|
|
85
|
+
<button type="button" class="btn btn-outline-warning btn-sm" id="btn-set-light">set('light')</button>
|
|
86
|
+
<button type="button" class="btn btn-outline-info btn-sm" id="btn-set-dark">set('dark')</button>
|
|
87
|
+
<button type="button" class="btn btn-outline-success btn-sm" id="btn-set-system">set('system')</button>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<!-- Debug Panel -->
|
|
95
|
+
<div class="col-lg-6">
|
|
96
|
+
<div class="card">
|
|
97
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
98
|
+
<div>
|
|
99
|
+
<h5 class="mb-0">Debug Panel</h5>
|
|
100
|
+
<small class="text-muted">Live values (updates automatically)</small>
|
|
101
|
+
</div>
|
|
102
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" id="btn-refresh">
|
|
103
|
+
{% uj_icon "arrows-rotate", "fa-md" %}
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
<div class="card-body">
|
|
107
|
+
<table class="table table-sm mb-0">
|
|
108
|
+
<tbody>
|
|
109
|
+
<tr>
|
|
110
|
+
<th scope="row" class="text-muted" style="width: 50%;">Saved Preference</th>
|
|
111
|
+
<td><code id="debug-saved">-</code></td>
|
|
112
|
+
</tr>
|
|
113
|
+
<tr>
|
|
114
|
+
<th scope="row" class="text-muted">Resolved Theme</th>
|
|
115
|
+
<td><code id="debug-resolved">-</code></td>
|
|
116
|
+
</tr>
|
|
117
|
+
<tr>
|
|
118
|
+
<th scope="row" class="text-muted">data-bs-theme attr</th>
|
|
119
|
+
<td><code id="debug-attr">-</code></td>
|
|
120
|
+
</tr>
|
|
121
|
+
<tr>
|
|
122
|
+
<th scope="row" class="text-muted">localStorage raw</th>
|
|
123
|
+
<td><code id="debug-localstorage">-</code></td>
|
|
124
|
+
</tr>
|
|
125
|
+
<tr>
|
|
126
|
+
<th scope="row" class="text-muted">System Preference</th>
|
|
127
|
+
<td><code id="debug-system">-</code></td>
|
|
128
|
+
</tr>
|
|
129
|
+
<tr>
|
|
130
|
+
<th scope="row" class="text-muted">Site Preset (Jekyll)</th>
|
|
131
|
+
<td><code id="debug-preset">{{ page.resolved.theme.appearance }}</code></td>
|
|
132
|
+
</tr>
|
|
133
|
+
</tbody>
|
|
134
|
+
</table>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<!-- Event Log -->
|
|
139
|
+
<div class="card mt-4">
|
|
140
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
141
|
+
<div>
|
|
142
|
+
<h5 class="mb-0">Event Log</h5>
|
|
143
|
+
<small class="text-muted">Theme change events</small>
|
|
144
|
+
</div>
|
|
145
|
+
<button type="button" class="btn btn-outline-secondary btn-sm" id="btn-clear-log">
|
|
146
|
+
{% uj_icon "trash", "fa-md" %}
|
|
147
|
+
</button>
|
|
148
|
+
</div>
|
|
149
|
+
<div class="card-body p-0">
|
|
150
|
+
<pre id="event-log" class="mb-0 p-3 small" style="max-height: 200px; overflow: auto; background: var(--bs-tertiary-bg);">Waiting for events...</pre>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<!-- Visual Test -->
|
|
156
|
+
<div class="col-12">
|
|
157
|
+
<div class="card">
|
|
158
|
+
<div class="card-header">
|
|
159
|
+
<h5 class="mb-0">Visual Theme Test</h5>
|
|
160
|
+
<small class="text-muted">These elements should adapt to the current theme</small>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="card-body">
|
|
163
|
+
<div class="row g-3">
|
|
164
|
+
<!-- Background colors -->
|
|
165
|
+
<div class="col-md-4">
|
|
166
|
+
<h6>Backgrounds</h6>
|
|
167
|
+
<div class="p-2 mb-2 bg-body border rounded">bg-body</div>
|
|
168
|
+
<div class="p-2 mb-2 bg-body-secondary border rounded">bg-body-secondary</div>
|
|
169
|
+
<div class="p-2 mb-2 bg-body-tertiary border rounded">bg-body-tertiary</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<!-- Text colors -->
|
|
173
|
+
<div class="col-md-4">
|
|
174
|
+
<h6>Text</h6>
|
|
175
|
+
<p class="text-body mb-2">text-body (primary text)</p>
|
|
176
|
+
<p class="text-body-secondary mb-2">text-body-secondary</p>
|
|
177
|
+
<p class="text-body-tertiary mb-2">text-body-tertiary</p>
|
|
178
|
+
<p class="text-muted mb-2">text-muted</p>
|
|
179
|
+
</div>
|
|
180
|
+
|
|
181
|
+
<!-- Buttons -->
|
|
182
|
+
<div class="col-md-4">
|
|
183
|
+
<h6>Adaptive Buttons</h6>
|
|
184
|
+
<div class="d-flex gap-2 flex-wrap">
|
|
185
|
+
<button class="btn btn-adaptive">btn-adaptive</button>
|
|
186
|
+
<button class="btn btn-outline-adaptive">btn-outline-adaptive</button>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<!-- Alerts -->
|
|
191
|
+
<div class="col-md-6">
|
|
192
|
+
<h6>Alerts</h6>
|
|
193
|
+
<div class="alert alert-primary py-2 mb-2">Primary alert</div>
|
|
194
|
+
<div class="alert alert-secondary py-2 mb-2">Secondary alert</div>
|
|
195
|
+
<div class="alert alert-success py-2 mb-0">Success alert</div>
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<!-- Form -->
|
|
199
|
+
<div class="col-md-6">
|
|
200
|
+
<h6>Form Elements</h6>
|
|
201
|
+
<input type="text" class="form-control mb-2" placeholder="Text input">
|
|
202
|
+
<select class="form-select mb-2">
|
|
203
|
+
<option>Select option</option>
|
|
204
|
+
</select>
|
|
205
|
+
<div class="form-check">
|
|
206
|
+
<input class="form-check-input" type="checkbox" id="test-check" checked>
|
|
207
|
+
<label class="form-check-label" for="test-check">Checkbox</label>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<!-- API Reference -->
|
|
216
|
+
<div class="col-12">
|
|
217
|
+
<div class="card">
|
|
218
|
+
<div class="card-header">
|
|
219
|
+
<h5 class="mb-0">API Reference</h5>
|
|
220
|
+
<small class="text-muted">webManager.uj().appearance methods</small>
|
|
221
|
+
</div>
|
|
222
|
+
<div class="card-body">
|
|
223
|
+
<div class="table-responsive">
|
|
224
|
+
<table class="table table-sm mb-0">
|
|
225
|
+
<thead>
|
|
226
|
+
<tr>
|
|
227
|
+
<th>Method</th>
|
|
228
|
+
<th>Returns</th>
|
|
229
|
+
<th>Description</th>
|
|
230
|
+
</tr>
|
|
231
|
+
</thead>
|
|
232
|
+
<tbody>
|
|
233
|
+
<tr>
|
|
234
|
+
<td><code>.get()</code></td>
|
|
235
|
+
<td><code>'dark' | 'light' | 'system' | null</code></td>
|
|
236
|
+
<td>Get saved preference (null if not set)</td>
|
|
237
|
+
</tr>
|
|
238
|
+
<tr>
|
|
239
|
+
<td><code>.getResolved()</code></td>
|
|
240
|
+
<td><code>'dark' | 'light'</code></td>
|
|
241
|
+
<td>Get actual displayed theme</td>
|
|
242
|
+
</tr>
|
|
243
|
+
<tr>
|
|
244
|
+
<td><code>.set(value)</code></td>
|
|
245
|
+
<td><code>void</code></td>
|
|
246
|
+
<td>Save and apply preference ('dark', 'light', or 'system')</td>
|
|
247
|
+
</tr>
|
|
248
|
+
<tr>
|
|
249
|
+
<td><code>.toggle()</code></td>
|
|
250
|
+
<td><code>void</code></td>
|
|
251
|
+
<td>Toggle between dark and light</td>
|
|
252
|
+
</tr>
|
|
253
|
+
<tr>
|
|
254
|
+
<td><code>.cycle()</code></td>
|
|
255
|
+
<td><code>void</code></td>
|
|
256
|
+
<td>Cycle: dark -> light -> system -> dark</td>
|
|
257
|
+
</tr>
|
|
258
|
+
<tr>
|
|
259
|
+
<td><code>.clear()</code></td>
|
|
260
|
+
<td><code>void</code></td>
|
|
261
|
+
<td>Clear saved preference (revert to site default)</td>
|
|
262
|
+
</tr>
|
|
263
|
+
</tbody>
|
|
264
|
+
</table>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|