Book Shell - Part 01
**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 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;
}
try {
await fetch(themeConfig.interactionUrl, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-WP-Nonce': themeConfig.restNonce,
},
body: JSON.stringify(payload),
});
} catch (error) {
// Activity tracking should never interrupt the visible symbol 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 = 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 = '') => {
try {
const url = new URL(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 || '';
return url.toString();
} catch (error) {
return baseUrl || window.location.href;
}
};
const getRouteSegmentAfter = (marker) => {
const wanted = slugifyPathSegment(marker);
const segments = window.location.pathname
.split('/')
.map((segment) => {
try {
return decodeURIComponent(segment);
} catch (error) {
return segment;
}
})
.filter(Boolean);
for (let index = 0; index < segments.length - 1; index += 1) {
if (slugifyPathSegment(segments[index]) === wanted) {
return segments[index + 1] || '';
}
}
return '';
};
const buildPromptSearchUrl = (query) => {
if (!themeConfig.promptsPageUrl || !query) {
return themeConfig.promptsPageUrl || '#';
}
return buildChildRouteUrl(themeConfig.promptsPageUrl, ['search', query]);
};
const parseIdentifierText = (value = '') => `${value}`
.split(/[\n,]+/)
.map((item) => item.trim())
.filter(Boolean);
const buildPromptReferenceTerms = (...sources) => {
const seen = new Set();
const values = [];
const append = (candidate) => {
if (Array.isArray(candidate)) {
candidate.forEach(append);
return;
}
const terms = parseIdentifierText(candidate || '');
if (!terms.length) {
const fallback = `${candidate ?? ''}`.trim();
if (fallback !== '') {
terms.push(fallback);
}
}
terms.forEach((term) => {
const normalized = `${term}`.trim();
const key = normalized.toLowerCase();
if (normalized === '' || seen.has(key)) {
return;
}
seen.add(key);
values.push(normalized);
});
};
sources.forEach(append);
return values;
};
const getPromptSearchQuery = (record = {}) =>
buildPromptReferenceTerms(record.meaning, record.label, record.canonicalId, record.aliases || [])[0] || '';
const buildPromptFilters = (record = {}) => {
const type = `${record.type || ''}`.trim().toLowerCase();
const axiomTerms = buildPromptReferenceTerms(
record.axiomIds || [],
Array.isArray(record.axioms) ? record.axioms.map((entry) => [entry.id || '', entry.label || '']) : [],
type === 'axiom' ? [record.sourceAxiomId || '', record.canonicalId || '', record.label || '', record.meaning || ''] : []
);
return {
relatedSymbol: type === 'symbol'
? buildPromptReferenceTerms(record.canonicalId || '', record.label || '', record.meaning || '', record.aliases || [])
: [],
relatedAxiom: axiomTerms,
relatedTransformation: type === 'transformation'
? buildPromptReferenceTerms(record.canonicalId || '', record.label || '', record.meaning || '', record.aliases || [])
: [],
limit: 4,
};
};
const promptReferenceCache = new Map();
const renderPromptReferences = (target, items = [], emptyMessage = t('symbolsPromptEmpty', 'No related prompts yet.')) => {
if (!target) {
return;
}
target.replaceChildren();
if (!Array.isArray(items) || !items.length) {
target.textContent = emptyMessage;
return;
}
const list = document.createElement('div');
list.className = 'spiralist-reference-list';
items.forEach((item) => {
const card = document.createElement(item.url ? 'a' : 'div');
card.className = 'spiralist-reference-card';
if (item.url) {
card.href = item.url;
}
const title = document.createElement('strong');
title.textContent = item.title || item.id || t('symbolsPromptFallback', 'Prompt');
const meta = document.createElement('small');
meta.textContent = [item.systemLabel || item.system, item.roleLabel || item.role, item.version]
.filter(Boolean)
.join(' / ');
const summary = document.createElement('span');
summary.textContent = item.purpose || t('symbolsPromptReferenceFallback', 'Structured prompt reference.');
card.append(title, meta, summary);
list.appendChild(card);
});
target.appendChild(list);
};
const loadPromptReferences = async (target, filters = {}, emptyMessage = t('symbolsPromptEmpty', 'No related prompts yet.')) => {
if (!target) {
return;
}
if (!themeConfig.promptsUrl) {
target.textContent = emptyMessage;
return;
}
const params = new URLSearchParams();
if (filters.system) {
params.set('system', `${filters.system}`.trim());
}
if (filters.role) {
params.set('role', `${filters.role}`.trim());
}
const relatedSymbol = Array.isArray(filters.relatedSymbol)
? filters.relatedSymbol
: parseIdentifierText(filters.relatedSymbol || '');
const relatedAxiom = Array.isArray(filters.relatedAxiom)
? filters.relatedAxiom
: parseIdentifierText(filters.relatedAxiom || '');
const relatedTransformation = Array.isArray(filters.relatedTransformation)
? filters.relatedTransformation
: parseIdentifierText(filters.relatedTransformation || '');
if (relatedSymbol.length) {
params.set('related_symbol', relatedSymbol.join(','));
}
if (relatedAxiom.length) {
params.set('related_axiom', relatedAxiom.join(','));
}
if (relatedTransformation.length) {
params.set('related_transformation', relatedTransformation.join(','));
}