Book Shell - Source Excerpt 01
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/ns12-manuscript/assets/js/book-shell.js so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/plugins/ns12-manuscript/assets/js/book-shell.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 handleCoreError = typeof core.handleError === 'function'
? core.handleError
: () => '';
const parseJsonOr = typeof core.parseJsonOr === 'function'
? core.parseJsonOr
: (text, fallback = null, options = {}) => {
if (typeof text !== 'string' || text === '') {
return fallback;
}
try {
return JSON.parse(text);
} catch (error) {
handleCoreError(error, { context: options.context || 'JSON.parse' });
return fallback;
}
};
const createUrlOr = typeof core.createUrlOr === 'function'
? core.createUrlOr
: (value, fallback = null, options = {}) => {
try {
return new URL(value, options.base || window.location.href);
} catch (error) {
handleCoreError(error, { context: options.context || 'URL' });
return fallback;
}
};
const decodeComponent = typeof core.decodeComponent === 'function'
? core.decodeComponent
: (value, fallback = value) => {
try {
return decodeURIComponent(value);
} catch (error) {
handleCoreError(error, { context: 'decodeURIComponent' });
return fallback;
}
};
const storage = core.storage || {
get(key, fallback = '', area = 'local') {
try {
return (area === 'session' ? window.sessionStorage : window.localStorage).getItem(key) || fallback;
} catch (error) {
handleCoreError(error, { context: `${area}Storage.getItem` });
return fallback;
}
},
set(key, value, area = 'local') {
try {
(area === 'session' ? window.sessionStorage : window.localStorage).setItem(key, value);
} catch (error) {
handleCoreError(error, { context: `${area}Storage.setItem` });
}
},
remove(key, area = 'local') {
try {
(area === 'session' ? window.sessionStorage : window.localStorage).removeItem(key);
} catch (error) {
handleCoreError(error, { context: `${area}Storage.removeItem` });
}
},
};
const copyText = typeof core.copyText === 'function'
? core.copyText
: async (text) => {
await navigator.clipboard.writeText(text);
};
const runBackgroundAction = (action, options = {}) => {
const runner = typeof core.runAction === 'function'
? core.runAction
: (nextAction, nextOptions = {}) => Promise.resolve()
.then(() => nextAction())
.catch((error) => {
handleCoreError(error, nextOptions);
if (typeof nextOptions.onError === 'function') {
nextOptions.onError(error, error && error.message ? error.message : '');
}
throw error;
})
.finally(() => {
if (typeof nextOptions.onFinally === 'function') {
nextOptions.onFinally();
}
});
return runner(action, options).catch(() => {});
};
const setFetchPriority = typeof core.setFetchPriority === 'function'
? core.setFetchPriority
: (image, priority) => {
try {
image.fetchPriority = priority;
} catch (error) {
handleCoreError(error, { context: 'HTMLImageElement.fetchPriority' });
}
};
const setPointerCapture = typeof core.setPointerCapture === 'function'
? core.setPointerCapture
: (element, pointerId) => {
if (!element || typeof element.setPointerCapture !== 'function') {
return;
}
try {
element.setPointerCapture(pointerId);
} catch (error) {
handleCoreError(error, { context: 'Element.setPointerCapture' });
}
};
const releasePointerCapture = typeof core.releasePointerCapture === 'function'
? core.releasePointerCapture
: (element, pointerId) => {
if (!element || typeof element.releasePointerCapture !== 'function') {
return;
}
try {
element.releasePointerCapture(pointerId);
} catch (error) {
handleCoreError(error, { context: 'Element.releasePointerCapture' });
}
};
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 = (payload) => {
if (!canTrackHumanActivity) {
return;
}
runBackgroundAction(async () => {
await fetch(themeConfig.interactionUrl, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-WP-Nonce': themeConfig.restNonce,
},
body: JSON.stringify(payload),
});
}, { context: 'book-shell-human-interaction' });
};
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 = createUrlOr(window.location.href, null, { context: 'book-shell-query-param' });
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 (!url) {
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: 'book-shell-child-route' });
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 || '';