Manuscript Workstation - Source Excerpt 09
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
});
this.toggleButtons.forEach((button) => {
const toggle = `${button.dataset.workstationToggle || ''}`.trim();
const active =
(toggle === 'raw' && this.state.rawMode) ||
(toggle === 'guides' && this.state.guidesVisible) ||
(toggle === 'overlay' && this.state.overlayVisible);
button.classList.toggle('is-active', active);
if (toggle === 'overlay') {
button.disabled = !overlayAvailable;
button.classList.toggle('is-disabled', !overlayAvailable);
button.setAttribute('aria-disabled', overlayAvailable ? 'false' : 'true');
}
button.setAttribute('aria-pressed', active ? 'true' : 'false');
});
this.requestViewerStateSync();
}
updateDirtyState() {
const dirtyCount = this.dirtyFolios.size;
this.root.dataset.dirtyCount = `${dirtyCount}`;
this.saveButtons.forEach((button) => {
const pending = dirtyCount > 0;
button.classList.toggle('is-pending', pending);
if (this.canEdit) {
button.disabled = this.saveInFlight || !pending;
}
const copy = button.querySelector('[data-workstation-save-copy]');
if (copy) {
copy.textContent = !this.canEdit
? 'Editor access required'
: this.saveInFlight
? 'Saving crop data'
: pending
? `Persist ${dirtyCount} unsaved crop${dirtyCount === 1 ? '' : 's'}`
: 'All visible crops saved';
}
});
}
setViewMode(nextMode, options = {}) {
const announce = options.announce !== false;
const userInitiated = options.userInitiated !== false;
this.state.viewMode = nextMode === 'single' ? 'single' : 'spread';
if (userInitiated) {
this.userSelectedViewMode = true;
}
this.syncButtons();
this.swapImageSources().finally(() => {
this.ensureActiveImagesReady(() => {
this.refreshViewportState();
});
});
if (announce) {
this.updateStatus(
this.state.viewMode === 'single'
? 'Single-page view active. The current folio stays centered for close reading.'
: 'Facing-page view active. Adjacent folios stay visible together for book-style reading.'
);
}
}
toggleGuides() {
this.state.guidesVisible = !this.state.guidesVisible;
this.syncButtons();
this.updateStatus(this.state.guidesVisible ? 'Crop guides shown.' : 'Crop guides hidden for clean inspection.');
}
toggleRawMode() {
this.state.rawMode = !this.state.rawMode;
this.syncButtons();
this.swapImageSources().finally(() => {
this.ensureActiveImagesReady(() => {
this.refreshViewportState();
});
});
this.updateStatus(
this.state.rawMode
? 'Raw inspection mode active. Full-resolution sources are prioritized for crisp zooming.'
: 'Reading mode active. Standard viewer sources restored.'
);
}
async toggleOverlay() {
const hasStudyLayer = this.folios.some((folio) => folio.studyLayer);
if (!hasStudyLayer) {
this.updateStatus('This folio route does not expose the study overlay.');
return;
}
if (!this.state.overlayVisible) {
await this.ensureStudyOverlay();
}
this.applyOverlayVisibility(!this.state.overlayVisible, { announce: true });
}
openAnchoredStudyTools() {
if (this.studyTools && window.location.hash === '#study-surface-tools') {
this.studyTools.open = true;
}
}
bindControls() {
this.fitButtons.forEach((button) => {
button.addEventListener('click', () => {
const mode = `${button.dataset.workstationFit || ''}`.trim() || 'width';
this.fitTo(mode);
});
});
this.viewModeButtons.forEach((button) => {
button.addEventListener('click', () => {
const mode = `${button.dataset.workstationViewMode || ''}`.trim() || 'spread';
this.setViewMode(mode, { announce: true, userInitiated: true });
});
});
this.toggleButtons.forEach((button) => {
button.addEventListener('click', async () => {
const toggle = `${button.dataset.workstationToggle || ''}`.trim();
if (toggle === 'guides') {
this.toggleGuides();
}
if (toggle === 'raw') {
this.toggleRawMode();
}
if (toggle === 'overlay') {
await this.toggleOverlay();
}
});
});
this.toolButtons.forEach((button) => {
button.addEventListener('click', async () => {
const action = `${button.dataset.workstationAction || ''}`.trim();
if (action === 'copy-link') {
await this.copyDeepLink();
}
if (action === 'reset-view') {
this.resetViewerState();
}
if (action === 'calibrate') {
await this.calibrateVisibleFolios();
}
if (action === 'auto-crop') {
await this.autoCropVisibleFolios();
}
if (action === 'refresh') {
await this.refreshVisibleFolios();
}
if (action === 'save') {
await this.saveDirtyFolios();
}
if (action === 'apply') {
this.focusCurrentCrop();
}
});
});
this.bindFolioActionControls();
const handleViewportResize = () => {
this.syncViewportLayoutMetrics();
const responsiveViewMode = this.getResponsiveViewMode(`${this.root.dataset.initialViewMode || 'spread'}`.trim() || 'spread');
if (!this.userSelectedViewMode && responsiveViewMode !== this.state.viewMode) {
this.setViewMode(responsiveViewMode, { announce: false, userInitiated: false });
return;
}
if (this.state.fitMode === 'actual' || this.state.fitMode === 'custom') {
this.renderSurface();
return;
}
this.fitTo(this.state.fitMode, false);
};
window.addEventListener('resize', handleViewportResize);
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', handleViewportResize);
}
}
getFullscreenElement() {
return document.fullscreenElement || document.webkitFullscreenElement || null;
}
isViewportFullscreen() {
return this.getFullscreenElement() === this.viewport;
}
isFullscreenSupported() {
return Boolean(
this.viewport &&
(
document.fullscreenEnabled ||
document.webkitFullscreenEnabled ||
this.viewport.requestFullscreen ||
this.viewport.webkitRequestFullscreen
)
);
}
syncFullscreenButton(options = {}) {
const { announce = false } = options;
if (!this.fullscreenButton) {
return;
}
if (!this.isFullscreenSupported()) {
this.fullscreenButton.hidden = true;
return;
}
const active = this.isViewportFullscreen();
const label = active ? 'Exit fullscreen' : 'Enter fullscreen';
this.fullscreenButton.hidden = false;
this.fullscreenButton.classList.toggle('is-active', active);
this.fullscreenButton.setAttribute('aria-pressed', active ? 'true' : 'false');
this.fullscreenButton.setAttribute('aria-label', label);
this.fullscreenButton.setAttribute('title', label);
this.root.dataset.fullscreenActive = active ? 'true' : 'false';
if (!announce) {
return;
}
this.updateStatus(active ? 'Fullscreen viewer active.' : 'Viewer returned to normal size.');
}
async requestViewportFullscreen() {
if (!this.viewport) {
return false;
}
if (typeof this.viewport.requestFullscreen === 'function') {
await this.viewport.requestFullscreen();
return true;
}
if (typeof this.viewport.webkitRequestFullscreen === 'function') {
this.viewport.webkitRequestFullscreen();
return true;
}
return false;
}
async exitViewportFullscreen() {
if (typeof document.exitFullscreen === 'function') {
await document.exitFullscreen();
return true;
}
if (typeof document.webkitExitFullscreen === 'function') {
document.webkitExitFullscreen();
return true;
}
return false;
}
bindFullscreenControl() {
if (!this.fullscreenButton) {
return;
}
if (!this.isFullscreenSupported()) {