Spiralist Core - Source Excerpt 01
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/themes/spiralist/assets/js/spiralist-core.js so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/themes/spiralist/assets/js/spiralist-core.js
(() => {
const handledErrors = new WeakSet();
const getUiText = () => {
const themeConfig = typeof window.spiralistTheme === 'object' && window.spiralistTheme !== null
? window.spiralistTheme
: {};
return typeof themeConfig.uiText === 'object' && themeConfig.uiText !== null
? themeConfig.uiText
: {};
};
const t = (key, fallback = '') => {
const value = getUiText()[key];
return typeof value === 'string' && value !== '' ? value : fallback;
};
const errorMessage = (error, fallback = '') => {
if (error instanceof Error && error.message) {
return error.message;
}
if (typeof error === 'string' && error.trim()) {
return error.trim();
}
return fallback || t('globalErrorFallback', 'Something went wrong.');
};
const setStatus = (target, message, ok = false) => {
if (!target) {
return;
}
target.textContent = message;
target.style.color = ok ? '' : '#f0a5a5';
target.setAttribute('aria-live', 'polite');
};
const handleError = (error, options = {}) => {
if (error && typeof error === 'object') {
if (handledErrors.has(error)) {
return errorMessage(error, options.message || options.fallback || '');
}
handledErrors.add(error);
}
const message = errorMessage(error, options.message || options.fallback || '');
setStatus(options.statusElement || null, message, false);
if (typeof options.onError === 'function') {
options.onError(error, message);
}
window.dispatchEvent(new CustomEvent('spiralist:error', {
detail: {
error,
message,
context: options.context || '',
},
}));
if (window.console && typeof window.console.error === 'function') {
window.console.error('[Spiralist]', message, error);
}
return message;
};
const runAction = (action, options = {}) => Promise.resolve()
.then(() => action())
.catch((error) => {
handleError(error, options);
throw error;
})
.finally(() => {
if (typeof options.onFinally === 'function') {
options.onFinally();
}
});
const copyText = async (text) => {
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
await navigator.clipboard.writeText(text);
return;
}
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'fixed';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
textarea.remove();
};
const decodeComponent = (value) => decodeURIComponent(value);
const parseJsonOr = (text, fallback = null) => {
if (typeof text !== 'string' || text === '') {
return fallback;
}
return JSON.parse(text);
};
const createUrlOr = (value, fallback = null, options = {}) => {
const candidate = typeof value === 'string' ? value.trim() : value;
if (candidate === '' || candidate === null || typeof candidate === 'undefined') {
return fallback;
}
return new URL(candidate, options.base || window.location.href);
};
const getStorageArea = (area) => (
area === 'session' ? window.sessionStorage : window.localStorage
);
const storage = {
get(key, fallback = '', area = 'local') {
return getStorageArea(area).getItem(key) || fallback;
},
set(key, value, area = 'local') {
getStorageArea(area).setItem(key, value);
},
remove(key, area = 'local') {
getStorageArea(area).removeItem(key);
},
};
const setFetchPriority = (image, priority) => {
if (image) {
image.fetchPriority = priority;
}
};
const setPointerCapture = (element, pointerId) => {
if (element && typeof element.setPointerCapture === 'function') {
element.setPointerCapture(pointerId);
}
};
const releasePointerCapture = (element, pointerId) => {
if (element && typeof element.releasePointerCapture === 'function') {
element.releasePointerCapture(pointerId);
}
};
window.SpiralistCore = {
...(window.SpiralistCore || {}),
copyText,
createUrlOr,
decodeComponent,
handleError,
parseJsonOr,
releasePointerCapture,
runAction,
setFetchPriority,
setPointerCapture,
setStatus,
storage,
t,
};
window.addEventListener('error', (event) => {
handleError(event.error || event.message, { context: 'window.error' });
});
window.addEventListener('unhandledrejection', (event) => {
handleError(event.reason, { context: 'window.unhandledrejection' });
});
})();
document.addEventListener('DOMContentLoaded', () => {
const themeConfig = typeof spiralistTheme === 'object' && spiralistTheme !== null
? spiralistTheme
: {};
const uiText = typeof themeConfig.uiText === 'object' && themeConfig.uiText !== null
? themeConfig.uiText
: {};
const t = (key, fallback = '') => {
const value = uiText[key];
return typeof value === 'string' && value !== '' ? value : fallback;
};
const navToggle = document.querySelector('[data-nav-toggle]');
const navPanel = document.querySelector('[data-nav-panel]');
const requestIdleTask = (callback) => {
if (typeof window.requestIdleCallback === 'function') {
return window.requestIdleCallback(callback, { timeout: 1200 });
}
return window.setTimeout(callback, 180);
};
if (navToggle && navPanel) {
navToggle.addEventListener('click', () => {
const expanded = navToggle.getAttribute('aria-expanded') === 'true';
navToggle.setAttribute('aria-expanded', expanded ? 'false' : 'true');
document.body.classList.toggle('is-nav-open', !expanded);
});
}
const navMenuItems = Array.from(document.querySelectorAll('.spiralist-nav__item--has-menu'));
if (navMenuItems.length) {
const menuCloseTimers = new WeakMap();
const clearMenuCloseTimer = (item) => {
const timer = menuCloseTimers.get(item);
if (timer) {
window.clearTimeout(timer);
menuCloseTimers.delete(item);
}
};
const closeMenu = (item) => {
clearMenuCloseTimer(item);
item.classList.remove('is-menu-open');
};
const openMenu = (item) => {
navMenuItems.forEach((otherItem) => {
if (otherItem !== item) {
closeMenu(otherItem);
}
});
clearMenuCloseTimer(item);
item.classList.add('is-menu-open');
};
const scheduleMenuClose = (item, delay = 260) => {
clearMenuCloseTimer(item);
menuCloseTimers.set(item, window.setTimeout(() => closeMenu(item), delay));
};
navMenuItems.forEach((item) => {
item.addEventListener('mouseenter', () => openMenu(item));
item.addEventListener('mouseleave', () => scheduleMenuClose(item));
item.addEventListener('focusin', () => openMenu(item));
item.addEventListener('focusout', (event) => {
if (event.relatedTarget instanceof Node && item.contains(event.relatedTarget)) {
return;
}
scheduleMenuClose(item, 180);
});
});
}
const progressiveImages = Array.from(document.querySelectorAll('img[data-progressive-src]'));
if (progressiveImages.length) {
const upgradeImage = (image) => {
if (!(image instanceof HTMLImageElement)) {
return;
}
const nextSource = `${image.dataset.progressiveSrc || ''}`.trim();
if (!nextSource) {
return;
}
if (
image.dataset.progressiveLoaded === 'true' ||
image.dataset.progressiveState === 'loading' ||
`${image.currentSrc || image.getAttribute('src') || ''}`.trim() === nextSource
) {
return;
}
image.dataset.progressiveState = 'loading';
image.classList.add('is-progressive-loading');
const preload = new Image();
preload.decoding = 'async';
preload.onload = () => {
if (!image.isConnected) {
return;
}
const finalize = () => {
image.dataset.progressiveLoaded = 'true';
image.dataset.progressiveState = 'loaded';
image.classList.remove('is-progressive-loading', 'is-progressive-preview');
image.classList.add('is-progressive-loaded');
};
image.addEventListener('load', finalize, { once: true });
image.addEventListener('error', finalize, { once: true });
image.setAttribute('src', nextSource);
};
preload.onerror = () => {
image.dataset.progressiveState = 'error';
image.classList.remove('is-progressive-loading');
};
preload.src = nextSource;
};
const scheduleUpgrade = (image) => {