Manuscript Workstation - Source Excerpt 06
Back to Manuscript Workstation
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/ns12-manuscript/assets/js/manuscript-workstation.js so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/plugins/ns12-manuscript/assets/js/manuscript-workstation.js
viewerStateParamKeys.forEach((key) => {
url.searchParams.delete(key);
});
Object.entries(this.serializeViewerState(mode)).forEach(([key, value]) => {
if (`${value || ''}`.trim()) {
url.searchParams.set(key, value);
}
});
return url.toString();
}
syncNavigationStateUrls() {
const mode = 'minimal';
this.pageLinks.forEach((link) => {
const baseHref = this.getLinkBaseHref(link);
if (baseHref) {
link.setAttribute('href', this.buildViewerStateUrl(baseHref, { mode }));
}
const baseSpreadHref = this.getLinkBaseSpreadHref(link);
if (baseSpreadHref) {
link.dataset.spreadHref = this.buildViewerStateUrl(baseSpreadHref, { mode });
}
});
if (this.jumpSelect) {
Array.from(this.jumpSelect.options || []).forEach((option) => {
const baseValue = this.getOptionBaseValue(option);
if (baseValue) {
option.value = this.buildViewerStateUrl(baseValue, { mode });
}
});
}
}
requestViewerStateSync(options = {}) {
if (!this.viewerStateReady || this.isApplyingViewerState) {
return;
}
const { immediate = false } = options;
const commit = () => {
this.syncNavigationStateUrls();
if (!window.history || typeof window.history.replaceState !== 'function') {
return;
}
const nextUrl = this.buildViewerStateUrl(window.location.href, { mode: 'minimal' });
if (nextUrl && nextUrl !== window.location.href) {
window.history.replaceState(window.history.state, '', nextUrl);
}
};
window.clearTimeout(this.viewerStateSyncTimer);
if (immediate) {
commit();
return;
}
this.viewerStateSyncTimer = window.setTimeout(commit, 140);
}
refreshViewportState() {
if (this.state.fitMode === 'actual' || this.state.fitMode === 'custom') {
this.renderSurface();
return;
}
this.fitTo(this.state.fitMode, false);
}
getScaleBounds() {
const widthScale = this.getFitScale('width');
const heightScale = this.getFitScale('height');
const minScale = Math.min(widthScale || 1, heightScale || 1, 1) * 0.75;
return {
min: Math.max(0.15, minScale || 0.15),
max: this.state.rawMode ? 10 : 6,
};
}
ensurePannableScale() {
const { maxX, maxY } = this.getPanExtents();
if (maxX > 0 || maxY > 0) {
return false;
}
const viewportWidth = Math.max(1, this.viewport.clientWidth - 24);
const viewportHeight = Math.max(1, this.viewport.clientHeight - 24);
const contentWidth = Math.max(1, this.content.offsetWidth);
const contentHeight = Math.max(1, this.content.offsetHeight);
const bounds = this.getScaleBounds();
const targetScale = Math.max(
this.state.scale,
(viewportWidth + 120) / contentWidth,
(viewportHeight + 120) / contentHeight
);
const clampedScale = clamp(targetScale, bounds.min, bounds.max);
if (clampedScale <= this.state.scale + 0.001) {
return false;
}
this.state.fitMode = 'custom';
this.state.scale = clampedScale;
this.syncButtons();
return true;
}
canUseSwipeTurnGesture() {
if (!this.isBrowserRoute || this.isTurningPage) {
return false;
}
const { maxX, maxY } = this.getPanExtents();
return maxX <= 18 && maxY <= 18;
}
isHorizontalTurnGesture(deltaX = 0, deltaY = 0) {
const absX = Math.abs(deltaX);
const absY = Math.abs(deltaY);
return absX > 12 && absX >= absY * 1.15;
}
hasTurnTarget(direction = 'next') {
const link = direction === 'prev' ? this.prevLink : this.nextLink;
return link instanceof HTMLElement && !link.hidden && Boolean(this.getNavigationHref(link));
}
getDragTurnThreshold() {
if (!this.viewport) {
return 108;
}
return Math.min(180, Math.max(96, this.viewport.clientWidth * 0.14));
}
setDragTurnOffset(offsetX = 0) {
if (!this.content) {
return;
}
const maxOffset = this.viewport
? Math.min(220, Math.max(72, this.viewport.clientWidth * 0.24))
: 180;
const clampedOffset = clamp(offsetX, -maxOffset, maxOffset);
this.content.style.setProperty('--spiralist-workstation-drag-shift', `${clampedOffset}px`);
}
resetDragTurnOffset() {
if (!this.content) {
return;
}
this.content.style.setProperty('--spiralist-workstation-drag-shift', '0px');
}
updateStatus(message) {
if (this.statusTarget) {
this.statusTarget.textContent = message;
}
}
updateRuntimeStatus(message) {
if (this.runtimeStatus) {
this.runtimeStatus.textContent = message;
}
}
getRuntimeHeaders(includeJson = false) {
const headers = {
Accept: 'application/json',
};
if (includeJson) {
headers['Content-Type'] = 'application/json';
}
if (config.restNonce) {
headers['X-WP-Nonce'] = config.restNonce;
}
return headers;
}
normalizeRuntimeNode(entry = {}) {
if (typeof entry === 'string') {
return {
id: entry,
label: entry,
inputState: '',
outputState: '',
};
}
if (!entry || typeof entry !== 'object') {
return null;
}
const id = `${entry.node_id || entry.nodeId || entry.id || ''}`.trim();
if (!id) {
return null;
}
return {
id,
label: `${entry.label || entry.title || id}`.trim(),
inputState: `${entry.input_state || entry.inputState || entry.input || ''}`.trim(),
outputState: `${entry.output_state || entry.outputState || entry.output || ''}`.trim(),
};
}
normalizeRuntimePath(payload = {}) {
const nextNodes = Array.isArray(payload.next_nodes)
? payload.next_nodes
: (Array.isArray(payload.nextNodes) ? payload.nextNodes : []);
const activeSymbols = Array.isArray(payload.active_symbols)
? payload.active_symbols
: (Array.isArray(payload.activeSymbols) ? payload.activeSymbols : []);
return {
currentState: `${payload.current_state || payload.currentState || ''}`.trim(),
currentNode: `${payload.current_node || payload.currentNode || ''}`.trim(),
activeSymbols: activeSymbols.map((symbol) => `${symbol || ''}`.trim()).filter(Boolean),
history: Array.isArray(payload.history) ? payload.history.filter((event) => event && typeof event === 'object') : [],
nextNodes: nextNodes.map((entry) => this.normalizeRuntimeNode(entry)).filter(Boolean),
storage: `${payload.storage || ''}`.trim(),
};
}
getStoredRuntimePath() {
if (config.isUserLoggedIn) {
return null;
}
const stored = readStoredRuntimePath();
if (!stored) {
return null;
}
const path = this.normalizeRuntimePath({
...stored,
storage: 'browser',
});
return path.currentState || path.currentNode || path.history.length ? path : null;
}
persistRuntimePath() {
if (config.isUserLoggedIn || !this.runtimePath.currentState) {
return;
}
writeStoredRuntimePath({
...this.runtimePath,
storage: 'browser',
storedAt: new Date().toISOString(),
});
}
clearRuntimePathStorage() {
if (!config.isUserLoggedIn) {
clearStoredRuntimePath();
}
}
announceRuntimePath(source = 'workstation') {
emitRuntimePathUpdate(this.runtimePath, source);
}
setRuntimeText(target, value, fallback = 'Not available') {
if (target) {
target.textContent = value || fallback;
}
}
renderRuntimePath() {
if (!this.runtimeDrawer) {
return;
}
const path = this.runtimePath;
const currentState = path.currentState || 'No state';
const currentNode = path.currentNode || 'No current node';
const nextCount = path.nextNodes.length;
const historyCount = path.history.length;
this.setRuntimeText(this.runtimeCurrentStateChip, currentState, 'State pending');
this.setRuntimeText(this.runtimeNextCountChip, `${nextCount} next node${nextCount === 1 ? '' : 's'}`, 'No next nodes');
this.setRuntimeText(this.runtimeHistoryCountChip, `${historyCount} path event${historyCount === 1 ? '' : 's'}`, 'No path history');