Spiralist - Source Excerpt 03
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
renderPromptReferences(target, payload.items || [], emptyMessage);
}, {
context: 'loadPromptReferences',
onError: () => {
target.textContent = t('symbolsPromptUnavailable', 'Prompt references unavailable.');
},
});
};
const fieldModeRank = {
minimal: 0,
expanded: 1,
expert: 2,
};
const formatNodeType = (type = '') => `${type}`
.replace(/-/g, ' ')
.replace(/\b\w/g, (match) => match.toUpperCase());
const getNodeMode = (node = {}) => (
Object.prototype.hasOwnProperty.call(fieldModeRank, node.mode) ? node.mode : 'minimal'
);
const isVisibleForMode = (item, mode) => {
const itemMode = getNodeMode(item);
return fieldModeRank[itemMode] <= fieldModeRank[mode];
};
const getNodeCenter = (node) => {
const x = Number(node.x) || 0;
const y = Number(node.y) || 0;
if (node.type === 'structure') {
return {
x: x + ((Number(node.w) || 0) / 2),
y: y + ((Number(node.h) || 0) / 2),
};
}
return { x, y };
};
const createFieldNodeButton = (node) => {
const button = document.createElement('button');
button.type = 'button';
button.className = `spiralist-field-node spiralist-field-node--${node.type || 'symbol'}`;
button.dataset.nodeId = node.id || '';
button.setAttribute('aria-pressed', 'false');
button.setAttribute('aria-label', `${formatNodeType(node.type)}: ${node.label || node.id}`);
if (node.type === 'structure') {
button.style.left = `${(Number(node.x) || 0) * 100}%`;
button.style.top = `${(Number(node.y) || 0) * 100}%`;
button.style.width = `${(Number(node.w) || 0.16) * 100}%`;
button.style.height = `${(Number(node.h) || 0.08) * 100}%`;
const label = document.createElement('span');
label.className = 'spiralist-field-node__label';
label.textContent = node.label || t('manuscriptFieldSectionFallback', 'Section');
button.appendChild(label);
} else {
button.style.left = `${(Number(node.x) || 0) * 100}%`;
button.style.top = `${(Number(node.y) || 0) * 100}%`;
const mark = document.createElement('span');
mark.className = 'spiralist-field-node__mark';
mark.textContent = node.type === 'symbol' ? (node.glyph || '') : '';
button.appendChild(mark);
}
return button;
};
const createRelationLine = (relation, fromNode, toNode) => {
const from = getNodeCenter(fromNode);
const to = getNodeCenter(toNode);
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', `${from.x * 100}`);
line.setAttribute('y1', `${from.y * 100}`);
line.setAttribute('x2', `${to.x * 100}`);
line.setAttribute('y2', `${to.y * 100}`);
line.classList.add('spiralist-field-line', `spiralist-field-line--${relation.type || 'relation'}`);
line.dataset.relationId = relation.id || '';
return line;
};
const initFieldInterface = async (field) => {
const nodesTarget = field.querySelector('[data-field-nodes]');
const relationsTarget = field.querySelector('[data-field-relations]');
const listTarget = field.querySelector('[data-field-list]');
const statusTarget = field.querySelector('[data-field-status]');
const modeButtons = field.querySelectorAll('[data-field-mode]');
const title = field.querySelector('[data-node-title]');
const type = field.querySelector('[data-node-type]');
const glyph = field.querySelector('[data-node-glyph]');
const summary = field.querySelector('[data-node-summary]');
const id = field.querySelector('[data-node-id]');
const layer = field.querySelector('[data-node-layer]');
const meaning = field.querySelector('[data-node-meaning]');
const definition = field.querySelector('[data-node-definition]');
const transformation = field.querySelector('[data-node-transformation]');
const derivation = field.querySelector('[data-node-derivation]');
const sourceAxiom = field.querySelector('[data-node-source-axiom]');
const related = field.querySelector('[data-node-related]');
const io = field.querySelector('[data-node-io]');
const prompts = field.querySelector('[data-node-prompts]');
const json = field.querySelector('[data-node-json]');
const crosswalk = field.querySelector('[data-node-crosswalk]');
const prev = field.querySelector('[data-node-prev]');
const next = field.querySelector('[data-node-next]');
const nodesUrl = field.dataset.nodesUrl || themeConfig.manuscriptNodesUrl;
const requestedNode = new URL(window.location.href).searchParams.get('node') || getRouteSegmentAfter('node') || '';
const executeUrl = themeConfig.engineExecuteUrl || '';
const instanceUrl = themeConfig.engineInstanceUrl || '';
const runtimeStorageKey = 'spiralistManuscriptRuntimePath';
const runtimeHeaders = {
Accept: 'application/json',
};
if (themeConfig.restNonce) {
runtimeHeaders['X-WP-Nonce'] = themeConfig.restNonce;
}
if (!nodesTarget || !relationsTarget || !listTarget || !nodesUrl) {
return;
}
relationsTarget.setAttribute('viewBox', '0 0 100 100');
relationsTarget.setAttribute('preserveAspectRatio', 'none');
const payload = await runAction(async () => {
const response = await fetch(nodesUrl, { headers: { Accept: 'application/json' } });
if (!response.ok) {
throw new Error(`Node dataset failed with ${response.status}`);
}
return response.json();
}, {
context: 'initFieldInterface.nodes',
onError: () => {
setStatusState(statusTarget, t('manuscriptFieldDatasetError', 'The symbolic field dataset could not be loaded.'), 'error');
},
});
const nodes = Array.isArray(payload.nodes) ? payload.nodes : [];
const relations = Array.isArray(payload.relations) ? payload.relations : [];
const nodesById = new Map(nodes.map((node) => [node.id, node]));
const nodeButtons = new Map();
let currentMode = 'minimal';
let selectedNodeId = '';
let hoverNodeId = '';
let runtimeState = {
currentState: '',
currentNode: '',
history: [],
nextNodeIds: [],
activeSymbols: [],
};
if (!nodes.length) {
setStatusState(statusTarget, t('manuscriptFieldNodesEmpty', 'No manuscript nodes are currently available for inspection.'), 'empty');
return;
}
const getVisibleNodes = () => nodes.filter((node) => isVisibleForMode(node, currentMode));
const getVisibleRelations = () => relations.filter((relation) => {
const fromNode = nodesById.get(relation.from);
const toNode = nodesById.get(relation.to);
return fromNode &&
toNode &&
isVisibleForMode(relation, currentMode) &&
isVisibleForMode(fromNode, currentMode) &&
isVisibleForMode(toNode, currentMode);
});
const resolveNodeByIdentifier = (identifier = '') => nodes.find((node) => (
node.id === identifier ||
node.canonicalId === identifier ||
slugifyPathSegment(node.id) === slugifyPathSegment(identifier) ||
slugifyPathSegment(node.canonicalId) === slugifyPathSegment(identifier) ||
(Array.isArray(node.aliases) && (
node.aliases.includes(identifier) ||
node.aliases.some((alias) => slugifyPathSegment(alias) === slugifyPathSegment(identifier))
))
));
const readRuntimeStorage = () => {
const raw = storage.get(runtimeStorageKey, '');
if (!raw) {
return null;
}
const parsed = parseJsonOr(raw, null, { context: 'manuscript runtime storage' });
return parsed && typeof parsed === 'object' ? parsed : null;
};
const writeRuntimeStorage = () => {
const nextNodes = runtimeState.nextNodeIds.map((nodeId) => {
const node = nodesById.get(nodeId);
return node
? {
id: node.id || nodeId,
label: node.label || node.id || nodeId,
inputState: node.inputState || node.input || '',
outputState: node.outputState || node.output || '',
}
: { id: nodeId };
});
const runtimePayload = {
currentState: runtimeState.currentState || '',
currentNode: runtimeState.currentNode || '',
history: runtimeState.history,
activeSymbols: runtimeState.activeSymbols,
nextNodes,
storage: 'browser',
};