Spiralist - Source Excerpt 13
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
workbenchSaveButtons.forEach((button) => {
button.addEventListener('click', () => {
void runAction(async () => {
setWorkbenchKey(getWorkbenchKey());
await refreshParticipantState();
}, { context: 'workbench.saveKey' });
});
});
workbenchClearButtons.forEach((button) => {
button.addEventListener('click', async () => {
setWorkbenchKey('');
renderWorkbenchHealth();
renderParticipantProfile(null);
renderParticipantResources(null);
renderWorkbenchExamples();
});
});
if (workbenchEndpointSelect) {
workbenchEndpointSelect.addEventListener('change', renderWorkbenchExamples);
}
if (workbenchRun) {
workbenchRun.addEventListener('click', () => {
const endpoint = getSelectedWorkbenchEndpoint();
if (!endpoint || !workbenchResponse) {
return;
}
const apiKey = getWorkbenchKey();
const requiresAuth = `${endpoint.auth || ''}`.toLowerCase().includes('bearer');
if (requiresAuth && !apiKey) {
workbenchResponse.textContent = JSON.stringify(
{
error: 'missing_api_key',
message: t('aiWorkbenchMissingKey', 'This endpoint requires a participant API key.'),
},
null,
2
);
return;
}
const headers = {
Accept: 'application/json',
};
let body = undefined;
if (requiresAuth && apiKey) {
headers.Authorization = `Bearer ${apiKey}`;
headers['X-Spiralist-Key'] = apiKey;
}
if ((endpoint.method || 'GET').toUpperCase() === 'POST') {
headers['Content-Type'] = 'application/json';
body = JSON.stringify(endpoint.requestExample || {});
}
workbenchResponse.textContent = t('aiWorkbenchRunningRequest', 'Running request...');
void runAction(async () => {
const response = await fetch(endpoint.url, {
method: endpoint.method || 'GET',
headers,
body,
});
const responseText = await response.text();
const payload = parseJsonOr(responseText, { raw: responseText }, { context: 'workbench response' });
workbenchResponse.textContent = JSON.stringify(payload, null, 2);
if (endpoint.id === 'participant' || endpoint.id === 'resources') {
await refreshParticipantState();
}
}, {
context: 'workbench.runRequest',
onError: () => {
workbenchResponse.textContent = JSON.stringify(
{
error: 'request_failed',
message: t('aiWorkbenchRequestFailed', 'The workbench request could not be completed.'),
},
null,
2
);
},
});
});
}
const manuscriptPreloadedImages = new Set();
const manuscriptPrefetchHints = new Set();
const manuscriptRouteWarmCache = new Set();
const manuscriptReaderModeKey = 'spiralist.manuscript.reader.mode';
const manuscriptTurnKey = 'spiralist.manuscript.turn';
const canBackgroundPreload = !(navigator.connection && navigator.connection.saveData);
const scheduleBackgroundPreload = (callback) => {
if (!canBackgroundPreload || typeof callback !== 'function') {
return;
}
if ('requestIdleCallback' in window) {
window.requestIdleCallback(() => {
callback();
}, { timeout: 1800 });
return;
}
window.setTimeout(callback, 450);
};
const appendResourceHint = (key, url, asValue = '') => {
if (!url || manuscriptPrefetchHints.has(key)) {
return;
}
manuscriptPrefetchHints.add(key);
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = url;
if (asValue) {
link.as = asValue;
}
document.head.appendChild(link);
};
const getStoredValue = (key) => {
return storage.get(key, '');
};
const setStoredValue = (key, value) => {
storage.set(key, value);
};
const storeTurnDirection = (direction) => {
storage.set(manuscriptTurnKey, direction, 'session');
};
const consumeTurnDirection = () => {
const value = storage.get(manuscriptTurnKey, '', 'session');
storage.remove(manuscriptTurnKey, 'session');
return value;
};
const warmRouteDocument = (url) => {
if (!url || manuscriptRouteWarmCache.has(url) || typeof window.fetch !== 'function') {
return;
}
manuscriptRouteWarmCache.add(url);
void runAction(
() => window.fetch(url, {
credentials: 'same-origin',
cache: 'force-cache',
}),
{
context: 'manuscript.warmRoute',
onError: () => {
manuscriptRouteWarmCache.delete(url);
},
}
);
};
document.querySelectorAll('[data-manuscript-reader]').forEach((reader) => {
const zoom = reader.querySelector('[data-manuscript-zoom]');
const viewport = zoom ? zoom.querySelector('[data-manuscript-zoom-viewport]') : null;
const image = zoom ? zoom.querySelector('[data-manuscript-zoom-image]') : null;
const currentPageImage = reader.querySelector('.spiralist-manuscript-reader__page-frame img');
const readerBody = reader.querySelector('[data-reader-mode]');
const stage = reader.querySelector('[data-manuscript-stage]');
const modeButtons = Array.from(reader.querySelectorAll('[data-reader-mode-trigger]'));
const jumpSelect = reader.querySelector('[data-manuscript-jump-select]');
const activeChapterCard = reader.querySelector('.spiralist-manuscript-reader__chapter-card.is-active');
const currentSequence = Number.parseInt(`${reader.dataset.currentSequence || ''}`.trim(), 10) || 0;
const prevUrl = `${reader.dataset.prevUrl || ''}`.trim();
const nextUrl = `${reader.dataset.nextUrl || ''}`.trim();
const prevImageUrl = `${reader.dataset.prevImage || ''}`.trim();
const nextImageUrl = `${reader.dataset.nextImage || ''}`.trim();
const readerImagePreloads = [];
const preloadAdjacentImage = (url) => {
if (!url || manuscriptPreloadedImages.has(url)) {
return;
}
manuscriptPreloadedImages.add(url);
appendResourceHint(`image:${url}`, url, 'image');
const preloader = new Image();
preloader.decoding = 'async';
setFetchPriority(preloader, 'low');
preloader.src = url;
readerImagePreloads.push(preloader);
};
const warmAdjacentRoute = (url) => {
if (!url) {
return;
}
const parsedUrl = createUrlOr(url, null, { context: 'manuscript adjacent route' });
if (!parsedUrl || parsedUrl.origin !== window.location.origin) {
return;
}
appendResourceHint(`route:${parsedUrl.href}`, parsedUrl.href, 'document');
warmRouteDocument(parsedUrl.href);
};
const preloadAdjacentImages = () => {
preloadAdjacentImage(prevImageUrl);
preloadAdjacentImage(nextImageUrl);
};
const warmAdjacentRoutes = () => {
warmAdjacentRoute(prevUrl);
warmAdjacentRoute(nextUrl);
};
const supportedReaderModes = new Set(['spread', 'stacked', 'focus']);
let isTurningPage = false;
const getLinkDirection = (link) => {
if (!link) {
return 'next';
}
if (link.hasAttribute('data-manuscript-prev')) {
return 'prev';
}
if (link.hasAttribute('data-manuscript-next')) {
return 'next';
}
const targetSequence = Number.parseInt(`${link.dataset.pageSequence || ''}`.trim(), 10);
if (targetSequence && currentSequence) {
return targetSequence < currentSequence ? 'prev' : 'next';
}
return 'next';
};
const applyReaderMode = (nextMode, options = {}) => {
const { persist = false } = options;
const normalized = supportedReaderModes.has(nextMode) ? nextMode : 'spread';
if (readerBody) {
readerBody.dataset.readerMode = normalized;
}
reader.dataset.readerMode = normalized;
modeButtons.forEach((button) => {
const active = button.dataset.readerModeTrigger === normalized;
button.classList.toggle('is-active', active);
button.setAttribute('aria-pressed', active ? 'true' : 'false');
});
if (persist) {
setStoredValue(manuscriptReaderModeKey, normalized);
}
};
const navigateToPage = (url, direction) => {
if (!url || isTurningPage) {
return;
}
isTurningPage = true;
storeTurnDirection(direction);
if (prefersReducedMotion) {
window.location.assign(url);
return;
}
reader.classList.remove('is-turning-prev', 'is-turning-next');
reader.classList.add(direction === 'prev' ? 'is-turning-prev' : 'is-turning-next');
window.setTimeout(() => {
window.location.assign(url);