Spiralist - Source Excerpt 01
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/themes/spiralist/assets/js/spiralist.js so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/themes/spiralist/assets/js/spiralist.js
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 core = window.SpiralistCore || {};
const runAction = typeof core.runAction === 'function'
? core.runAction
: (action) => Promise.resolve().then(action);
const parseJsonOr = typeof core.parseJsonOr === 'function'
? core.parseJsonOr
: ((text, fallback = null) => (typeof text === 'string' && text !== '' ? JSON.parse(text) : fallback));
const decodeComponent = typeof core.decodeComponent === 'function'
? core.decodeComponent
: ((value) => decodeURIComponent(value));
const createUrlOr = typeof core.createUrlOr === 'function'
? core.createUrlOr
: ((value, fallback = null, options = {}) => new URL(value, options.base || window.location.href) || fallback);
const storage = core.storage || {
get: (key, fallback = '', area = 'local') => (area === 'session' ? window.sessionStorage : window.localStorage).getItem(key) || fallback,
set: (key, value, area = 'local') => (area === 'session' ? window.sessionStorage : window.localStorage).setItem(key, value),
remove: (key, area = 'local') => (area === 'session' ? window.sessionStorage : window.localStorage).removeItem(key),
};
const setFetchPriority = typeof core.setFetchPriority === 'function'
? core.setFetchPriority
: ((image, priority) => { image.fetchPriority = priority; });
const setPointerCapture = typeof core.setPointerCapture === 'function'
? core.setPointerCapture
: ((element, pointerId) => element.setPointerCapture(pointerId));
const releasePointerCapture = typeof core.releasePointerCapture === 'function'
? core.releasePointerCapture
: ((element, pointerId) => element.releasePointerCapture(pointerId));
const openDetailsForTarget = (target) => {
if (!target || !(target instanceof Element)) {
return;
}
let details = target.matches('details') ? target : target.closest('details');
while (details) {
details.open = true;
details = details.parentElement ? details.parentElement.closest('details') : null;
}
};
const decodeHashId = (hash) => decodeComponent(hash, hash, { context: 'location.hash' });
const openHashTargetDetails = () => {
const hash = window.location.hash ? window.location.hash.slice(1) : '';
if (!hash) {
return;
}
const target = document.getElementById(decodeHashId(hash));
openDetailsForTarget(target);
};
window.addEventListener('hashchange', openHashTargetDetails);
document.addEventListener('click', (event) => {
const clicked = event.target instanceof Element ? event.target : null;
const link = clicked ? clicked.closest('a[href^="#"]') : null;
if (!link) {
return;
}
const hash = link.getAttribute('href') || '';
if (hash.length <= 1) {
return;
}
const target = document.getElementById(decodeHashId(hash.slice(1)));
openDetailsForTarget(target);
});
openHashTargetDetails();
const openModal = (dialog) => {
if (!dialog) {
return;
}
if (typeof dialog.showModal === 'function' && !dialog.open) {
dialog.showModal();
} else {
dialog.setAttribute('open', '');
}
document.body.classList.add('is-modal-open');
const focusTarget = dialog.querySelector('[data-modal-focus]') ||
dialog.querySelector('input, select, textarea, button, a[href]');
if (focusTarget && typeof focusTarget.focus === 'function') {
focusTarget.focus({ preventScroll: true });
}
};
const closeModal = (dialog) => {
if (!dialog) {
return;
}
if (typeof dialog.close === 'function' && dialog.open) {
dialog.close();
} else {
dialog.removeAttribute('open');
if (!document.querySelector('[data-modal][open]')) {
document.body.classList.remove('is-modal-open');
}
}
};
const initModalLaunchers = () => {
const modals = Array.from(document.querySelectorAll('[data-modal]'));
document.querySelectorAll('[data-modal-open]').forEach((trigger) => {
trigger.addEventListener('click', () => {
openModal(document.getElementById(trigger.dataset.modalOpen || ''));
});
});
modals.forEach((dialog) => {
dialog.addEventListener('close', () => {
if (!document.querySelector('[data-modal][open]')) {
document.body.classList.remove('is-modal-open');
}
});
dialog.addEventListener('click', (event) => {
if (event.target === dialog) {
closeModal(dialog);
}
});
dialog.querySelectorAll('[data-modal-close]').forEach((button) => {
button.addEventListener('click', () => {
closeModal(dialog);
});
});
});
document.addEventListener('keydown', (event) => {
if (event.key !== 'Escape') {
return;
}
modals
.filter((dialog) => dialog.open)
.forEach((dialog) => closeModal(dialog));
});
};
const formatText = (key, fallback = '', replacements = {}) => {
let value = t(key, fallback);
Object.entries(replacements).forEach(([name, replacement]) => {
value = value.split(`{${name}}`).join(`${replacement}`);
});
return value;
};
const canTrackHumanActivity = Boolean(
themeConfig.isUserLoggedIn &&
themeConfig.interactionUrl &&
themeConfig.restNonce
);
const recordHumanInteraction = async (payload) => {
if (!canTrackHumanActivity) {
return;
}
await runAction(
() => fetch(themeConfig.interactionUrl, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-WP-Nonce': themeConfig.restNonce,
},
body: JSON.stringify(payload),
}),
{ context: 'recordHumanInteraction' }
);
};
initModalLaunchers();
const isPlainObject = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
const setStatusState = (target, message, state = 'idle') => {
if (!target) {
return;
}
target.textContent = message;
target.dataset.state = state;
};
const replaceQueryParam = (key, value) => {
const url = new URL(window.location.href);
const cleanValue = `${value ?? ''}`.trim();
if (key === 'symbol' && themeConfig.symbolsPageUrl) {
window.history.replaceState(window.history.state, '', buildChildRouteUrl(themeConfig.symbolsPageUrl, cleanValue ? [cleanValue] : [], window.location.hash));
return;
}
if (key === 'node' && themeConfig.manuscriptPageUrl) {
window.history.replaceState(window.history.state, '', buildChildRouteUrl(themeConfig.manuscriptPageUrl, cleanValue ? ['node', cleanValue] : [], window.location.hash));
return;
}
if (cleanValue === '') {
url.searchParams.delete(key);
} else {
url.searchParams.set(key, cleanValue);
}
window.history.replaceState(window.history.state, '', url);
};
const slugifyPathSegment = (value = '') => {
const normalized = `${value ?? ''}`
.trim()
.normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
return normalized || 'item';
};
const buildChildRouteUrl = (baseUrl, segments = [], hash = '') => {
const url = createUrlOr(baseUrl, null, { context: 'buildChildRouteUrl' });
if (!url) {
return baseUrl || window.location.href;
}
const basePath = url.pathname.replace(/\/+$/, '');
const childPath = segments
.map((segment) => slugifyPathSegment(segment))
.filter(Boolean)
.map(encodeURIComponent)
.join('/');
url.pathname = `${basePath || ''}/${childPath ? `${childPath}/` : ''}`.replace(/\/{2,}/g, '/');
url.search = '';
url.hash = hash || '';