Manuscript Workstation - Source Excerpt 02
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
nextUrl.searchParams.set('refresh', `${Date.now()}`);
return nextUrl.toString();
};
const imageLoadCache = new Map();
const preloadImageAsset = (url) => {
if (!url) {
return Promise.resolve('');
}
if (imageLoadCache.has(url)) {
return imageLoadCache.get(url);
}
const promise = new Promise((resolve) => {
const image = new Image();
const finalize = () => resolve(url);
image.decoding = 'async';
image.addEventListener(
'load',
() => {
if (typeof image.decode === 'function') {
image.decode().catch(() => {}).finally(finalize);
return;
}
finalize();
},
{ once: true }
);
image.addEventListener('error', () => resolve(''), { once: true });
image.src = url;
}).finally(() => {
imageLoadCache.delete(url);
});
imageLoadCache.set(url, promise);
return promise;
};
const storeTurnDirection = (direction) => {
storage.set(turnStateKey, direction, 'session');
};
const consumeTurnDirection = () => {
const value = `${storage.get(turnStateKey, '', 'session') || ''}`.trim();
storage.remove(turnStateKey, 'session');
return value;
};
const clearTurnDirection = () => {
storage.remove(turnStateKey, 'session');
};
const readStoredRuntimePath = () => {
const raw = `${storage.get(runtimePathStateKey, '') || ''}`.trim();
if (!raw) {
return null;
}
const parsed = parseJsonOr(raw, null, { context: 'workstation-runtime-path-storage' });
return parsed && typeof parsed === 'object' ? parsed : null;
};
const writeStoredRuntimePath = (path) => {
return storage.set(runtimePathStateKey, JSON.stringify(path));
};
const clearStoredRuntimePath = () => {
storage.remove(runtimePathStateKey);
};
const emitRuntimePathUpdate = (path, source = 'workstation') => {
dispatchEventSafely('spiralist:runtime-path-updated', {
...(path && typeof path === 'object' ? path : {}),
source,
});
};
class ManuscriptWorkstationController {
constructor(root) {
this.root = root;
this.page = root.closest('.spiralist-page--manuscript');
this.viewport = root.querySelector('[data-workstation-viewport]');
this.viewer = root.querySelector('.spiralist-manuscript-workstation__viewer');
this.surface = root.querySelector('[data-workstation-surface]');
this.content = root.querySelector('[data-workstation-content]');
this.statusTarget = root.querySelector('[data-workstation-status]');
this.runtimeDrawer = root.querySelector('[data-workstation-runtime-drawer]');
this.runtimeStatus = root.querySelector('[data-runtime-status]');
this.runtimeCurrentStateChip = root.querySelector('[data-runtime-current-state]');
this.runtimeNextCountChip = root.querySelector('[data-runtime-next-count]');
this.runtimeHistoryCountChip = root.querySelector('[data-runtime-history-count]');
this.runtimeStateValue = root.querySelector('[data-runtime-state-value]');
this.runtimeNodeValue = root.querySelector('[data-runtime-node-value]');
this.runtimeStorageValue = root.querySelector('[data-runtime-storage-value]');
this.runtimeActiveSymbols = root.querySelector('[data-runtime-active-symbols]');
this.runtimeNextNodes = root.querySelector('[data-runtime-next-nodes]');
this.runtimeHistory = root.querySelector('[data-runtime-history]');
this.runtimeRefresh = root.querySelector('[data-runtime-refresh]');
this.runtimeReset = root.querySelector('[data-runtime-reset]');
this.studyTools = root.querySelector('#study-surface-tools');
this.contextDrawer = root.querySelector('[data-workstation-context-drawer]');
this.dossierDrawer = root.querySelector('[data-workstation-dossier-drawer]');
this.fullscreenButton = root.querySelector('[data-workstation-fullscreen]');
this.searchModal = root.querySelector('[data-workstation-search-modal]');
this.searchDialog = root.querySelector('[data-workstation-search-dialog]');
this.searchScroll = root.querySelector('[data-workstation-search-scroll]');
this.searchOpeners = Array.from(root.querySelectorAll('[data-workstation-open-modal="search"]'));
this.searchClosers = Array.from(root.querySelectorAll('[data-workstation-close-modal="search"]'));
this.tocModal = root.querySelector('[data-workstation-toc-modal]');
this.tocDialog = root.querySelector('[data-workstation-toc-dialog]');
this.tocScroll = root.querySelector('[data-workstation-toc-scroll]');
this.tocOpeners = Array.from(root.querySelectorAll('[data-workstation-open-modal="toc"]'));
this.tocClosers = Array.from(root.querySelectorAll('[data-workstation-close-modal="toc"]'));
this.tocSections = Array.from(root.querySelectorAll('[data-workstation-toc-section]'));
this.prevLink = this.viewport ? this.viewport.querySelector('[data-workstation-prev]') : null;
this.nextLink = this.viewport ? this.viewport.querySelector('[data-workstation-next]') : null;
this.jumpSelect = root.querySelector('[data-workstation-jump]');
this.fitButtons = Array.from(root.querySelectorAll('[data-workstation-fit]'));
this.viewModeButtons = Array.from(root.querySelectorAll('[data-workstation-view-mode]'));
this.toggleButtons = Array.from(root.querySelectorAll('[data-workstation-toggle]'));
this.toolButtons = Array.from(root.querySelectorAll('[data-workstation-action]'));
this.drawerButtons = Array.from(root.querySelectorAll('[data-workstation-open-drawer]'));
this.pageLinks = Array.from(root.querySelectorAll('[data-workstation-page-link]'));
this.thumbLinks = Array.from(root.querySelectorAll('.spiralist-manuscript-workstation__thumb[data-workstation-page-link]'));
this.searchInput = root.querySelector('[data-workstation-search]');
this.searchClear = root.querySelector('[data-workstation-search-clear]');
this.searchStatus = root.querySelector('[data-workstation-search-status]');
this.searchResults = root.querySelector('[data-workstation-search-results]');
this.searchResultsScroll = root.querySelector('[data-workstation-search-results-scroll]');
this.runSearch = null;
this.saveButtons = this.toolButtons.filter(
(button) => `${button.dataset.workstationAction || ''}`.trim() === 'save'
);
this.copyButtons = this.toolButtons.filter(
(button) => `${button.dataset.workstationAction || ''}`.trim() === 'copy-link'
);
this.canEdit = root.dataset.canEdit === 'true' && Boolean(config.canEdit);
this.routeMode = `${root.dataset.routeMode || 'overview'}`.trim() || 'overview';
this.isDetailRoute = this.routeMode === 'folio';
this.isBrowserRoute = !this.isDetailRoute;
this.nodeTargetUrl = `${root.dataset.nodeTargetUrl || window.location.href}`.trim();
this.enginePathUrl = `${config.enginePathUrl || ''}`.trim();
this.engineExecuteUrl = `${config.engineExecuteUrl || ''}`.trim();
this.currentSequence = Number.parseInt(`${root.dataset.currentSequence || ''}`.trim(), 10) || 0;
this.initialSequence = this.currentSequence;
this.folios = this.readRenderedFolios();
this.folioRecords = this.readFolioRecords();
this.initialViewMode = this.getResponsiveViewMode(`${root.dataset.initialViewMode || 'spread'}`.trim() || 'spread');
this.userSelectedViewMode = false;
this.state = {
fitMode: 'width',
viewMode: this.initialViewMode,
rawMode: false,
guidesVisible: true,
overlayVisible: false,
scale: 1,
panX: 0,
panY: 0,
edgeHint: 'none',
};
this.viewportDrag = null;
this.cropDrag = null;
this.cropGlobalBound = false;
this.studyPayload = null;
this.studyLoading = false;
this.runtimePath = {
currentState: '',
currentNode: '',
activeSymbols: [],
history: [],
nextNodes: [],
storage: '',
};
this.runtimeLoading = false;
this.calibrationProfiles = new Map();
this.autoDetected = new Set();
this.dirtyFolios = new Set();
this.saveInFlight = false;
this.isTurningPage = false;
this.suppressViewportClick = false;
this.previousModalFocus = null;
this.searchTimer = 0;
this.searchAbortController = null;
this.lastSearchQuery = '';
this.copyFeedbackTimer = 0;
this.viewerStateSyncTimer = 0;
this.viewerStateReady = false;
this.isApplyingViewerState = false;
this.baseFitMode = 'width';
this.initialViewerState = this.readViewerStateFromUrl();