Book Shell - Source Excerpt 06
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
const instancePayload = await response.json();
syncRuntimeState(instancePayload);
}, { context: 'field-runtime-instance' });
};
const selectNode = (nodeId, shouldScroll = false) => {
const node = nodesById.get(nodeId);
if (!node) {
return;
}
selectedNodeId = nodeId;
setInspectorNode(node);
markNodeState(hoverNodeId || selectedNodeId);
renderRelations(hoverNodeId || selectedNodeId);
if (shouldScroll) {
const button = nodeButtons.get(nodeId);
if (button) {
button.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
button.classList.add('is-pulsing');
window.setTimeout(() => button.classList.remove('is-pulsing'), 900);
}
}
replaceQueryParam('node', node.id || node.canonicalId || '');
recordHumanInteraction({
type: 'manuscript-field-node-view',
symbolId: node.canonicalId || node.id || '',
symbolLabel: node.label || '',
page: window.location.pathname,
});
};
const renderNodeList = () => {
listTarget.replaceChildren();
getVisibleNodes().forEach((node) => {
const button = document.createElement('button');
button.type = 'button';
button.className = `spiralist-node-pill spiralist-node-pill--${node.type || 'symbol'}`;
button.dataset.fieldListNode = node.id || '';
button.setAttribute('aria-pressed', node.id === selectedNodeId ? 'true' : 'false');
const label = document.createElement('span');
label.textContent = node.label || node.id || t('manuscriptFieldNodeFallback', 'Node');
button.appendChild(label);
const meta = document.createElement('small');
meta.textContent = formatNodeType(node.type);
button.appendChild(meta);
button.addEventListener('click', () => {
selectNode(node.id, true);
executeSelectedNode(node.id);
});
listTarget.appendChild(button);
});
};
const renderNodes = () => {
nodesTarget.replaceChildren();
nodeButtons.clear();
getVisibleNodes().forEach((node) => {
const button = createFieldNodeButton(node);
button.addEventListener('click', () => {
selectNode(node.id);
executeSelectedNode(node.id);
});
button.addEventListener('mouseenter', () => {
hoverNodeId = node.id;
markNodeState(hoverNodeId);
renderRelations(hoverNodeId);
});
button.addEventListener('mouseleave', () => {
hoverNodeId = '';
markNodeState(selectedNodeId);
renderRelations(selectedNodeId);
});
nodesTarget.appendChild(button);
nodeButtons.set(node.id, button);
});
renderNodeList();
updateFieldStatus('ready');
const selectedNode = nodesById.get(selectedNodeId);
if (!selectedNodeId || !selectedNode || !isVisibleForMode(selectedNode, currentMode)) {
const firstNode = getVisibleNodes()[0];
if (firstNode) {
selectedNodeId = firstNode.id;
}
}
const inspectedNode = nodesById.get(selectedNodeId);
if (inspectedNode) {
setInspectorNode(inspectedNode);
}
markNodeState(selectedNodeId);
renderRelations(selectedNodeId);
};
function setMode(nextMode, updateSelection = true) {
if (!Object.prototype.hasOwnProperty.call(fieldModeRank, nextMode)) {
return;
}
currentMode = nextMode;
modeButtons.forEach((button) => {
const isActive = button.dataset.fieldMode === currentMode;
button.classList.toggle('is-active', isActive);
button.setAttribute('aria-pressed', isActive ? 'true' : 'false');
});
if (updateSelection && selectedNodeId) {
const selectedNode = nodesById.get(selectedNodeId);
if (selectedNode && !isVisibleForMode(selectedNode, currentMode)) {
const firstVisible = getVisibleNodes()[0];
selectedNodeId = firstVisible ? firstVisible.id : '';
}
}
renderNodes();
}
modeButtons.forEach((button) => {
button.addEventListener('click', () => setMode(button.dataset.fieldMode || 'minimal'));
});
const selectSiblingNode = (direction) => {
const visibleNodes = getVisibleNodes();
if (!visibleNodes.length) {
return;
}
const currentIndex = Math.max(0, visibleNodes.findIndex((node) => node.id === selectedNodeId));
const nextIndex = (currentIndex + direction + visibleNodes.length) % visibleNodes.length;
selectNode(visibleNodes[nextIndex].id, true);
};
if (prev) {
prev.addEventListener('click', () => selectSiblingNode(-1));
}
if (next) {
next.addEventListener('click', () => selectSiblingNode(1));
}
if (requestedNode) {
const initialNode = resolveNodeByIdentifier(requestedNode);
if (initialNode) {
if (!isVisibleForMode(initialNode, currentMode)) {
currentMode = getNodeMode(initialNode);
}
selectedNodeId = initialNode.id || '';
}
}
modeButtons.forEach((button) => {
const isActive = button.dataset.fieldMode === currentMode;
button.classList.toggle('is-active', isActive);
button.setAttribute('aria-pressed', isActive ? 'true' : 'false');
});
await initializeRuntime();
renderNodes();
};
document.querySelectorAll('[data-field-interface]').forEach((field) => {
const statusTarget = field.querySelector('[data-field-status]');
runBackgroundAction(() => initFieldInterface(field), {
context: 'field-interface',
onError: () => {
setStatusState(statusTarget, t('manuscriptFieldDatasetError', 'The symbolic field dataset could not be loaded.'), 'error');
},
});
});
document.querySelectorAll('[data-symbol-browser]').forEach((browser) => {
const title = browser.querySelector('[data-symbol-title]');
const glyph = browser.querySelector('[data-symbol-glyph]');
const id = browser.querySelector('[data-symbol-id]');
const type = browser.querySelector('[data-symbol-type]');
const layer = browser.querySelector('[data-symbol-layer]');
const category = browser.querySelector('[data-symbol-category]');
const meaning = browser.querySelector('[data-symbol-meaning]');
const definition = browser.querySelector('[data-symbol-definition]');
const axiom = browser.querySelector('[data-symbol-axiom]');
const derivation = browser.querySelector('[data-symbol-derivation]');
const provenance = browser.querySelector('[data-symbol-provenance]');
const related = browser.querySelector('[data-symbol-related]');
const transformation = browser.querySelector('[data-symbol-transformation]');
const prompts = browser.querySelector('[data-symbol-prompts]');
const manuscriptAnchor = browser.querySelector('[data-symbol-manuscript-anchor]');
const axioms = browser.querySelector('[data-symbol-axioms]');
const transformationList = browser.querySelector('[data-symbol-transformations]');
const crosswalk = browser.querySelector('[data-symbol-crosswalk]');
const searchInput = browser.querySelector('[data-symbol-search]');
const layerFilter = browser.querySelector('[data-symbol-layer-filter]');
const typeFilter = browser.querySelector('[data-symbol-type-filter]');
const categoryFilter = browser.querySelector('[data-symbol-category-filter]');
const results = browser.querySelector('[data-symbol-results]');
const empty = browser.querySelector('[data-symbol-empty]');
const cards = Array.from(browser.querySelectorAll('[data-symbol]'));
const currentUrl = createUrlOr(window.location.href, null, { context: 'symbol-browser-url' });
const requestedSymbol = (currentUrl ? currentUrl.searchParams.get('symbol') : '') || getRouteSegmentAfter('symbols') || '';
const cardPayloadCache = new WeakMap();
let firstPayload = null;
const parseCardPayload = (card) => {
if (cardPayloadCache.has(card)) {
return cardPayloadCache.get(card);
}
const payload = parseJsonOr(card.dataset.symbol || '', null, { context: 'symbol-card-payload' });
cardPayloadCache.set(card, payload);