Manuscript Workstation - Source Excerpt 19
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
${resetButton}
<a class="spiralist-manuscript-workstation__folio-action" href="${escapeHtml(fullUrl)}" target="_blank" rel="noopener noreferrer">Open Raw</a>
</div>
</div>`
: '';
const figcaption = this.isDetailRoute && record.caption
? `<figcaption class="spiralist-manuscript-stage-panel__caption spiralist-muted">${escapeHtml(record.caption)}</figcaption>`
: '';
figure.innerHTML = `
${folioHead}
<div class="spiralist-manuscript-workstation__folio-frame" data-workstation-folio-frame>
<span class="spiralist-manuscript-workstation__folio-chip" aria-hidden="true">${escapeHtml(record.label || 'Page')}</span>
<img src="${escapeHtml(previewUrl)}" alt="${escapeHtml(alt)}" loading="eager" decoding="async" ${isCurrent ? 'fetchpriority="high"' : ''} class="is-preview-source" data-current-level="preview" data-loaded-src="${escapeHtml(previewUrl)}" data-workstation-image />
${cropLayer}
${studyLayer}
</div>
${figcaption}
`;
return figure;
}
renderFolioWindow(currentIndex) {
const records = this.getFolioWindowRecords(currentIndex);
if (!records.length) {
return null;
}
this.content.replaceChildren(
...records.map((record, index) => {
const hasContext = records.length >= 3;
const slot = index === 0 ? 'left' : index === 1 && hasContext ? 'center' : 'right';
return this.createFolioElement(record, {
slot,
isCurrent: index === 0,
isContext: index >= 2,
});
})
);
this.content.dataset.spreadCount = `${Math.max(1, records.length)}`;
this.folios = this.readRenderedFolios();
this.renderAllCrops();
this.bindFolioActionControls();
this.bindCropHandles();
this.applyOverlayVisibility(this.state.overlayVisible, { announce: false });
this.swapImageSources().finally(() => {
this.ensureActiveImagesReady(() => {
this.refreshViewportState();
});
});
return records[0];
}
updateEdgeLink(link, record, direction) {
if (!(link instanceof HTMLElement)) {
return;
}
if (!record) {
link.hidden = true;
link.setAttribute('aria-hidden', 'true');
link.setAttribute('tabindex', '-1');
link.setAttribute('href', '#');
link.dataset.baseHref = '#';
link.dataset.spreadHref = '';
return;
}
const href = record.routeUrl || '#';
link.hidden = false;
link.removeAttribute('aria-hidden');
link.removeAttribute('tabindex');
link.setAttribute('href', href);
link.dataset.baseHref = href;
link.dataset.spreadHref = '';
link.dataset.baseSpreadHref = '';
link.dataset.pageSequence = `${record.sequence}`;
link.dataset.imageUrl = record.prefetchUrl || record.standardUrl || record.previewUrl || record.imageUrl || '';
link.setAttribute('aria-label', `Turn to ${direction === 'prev' ? 'previous' : 'next'} folio, ${record.title || record.label || 'manuscript page'}`);
const label = link.querySelector('.spiralist-manuscript-workstation__edge-copy span');
if (label) {
label.textContent = record.label || record.title || (direction === 'prev' ? 'Previous folio' : 'Next folio');
}
}
syncCurrentFolioChrome(record, currentIndex) {
this.currentSequence = record.sequence;
this.root.dataset.currentSequence = `${record.sequence}`;
this.root.dataset.currentSlug = record.slug;
this.nodeTargetUrl = record.routeUrl || this.nodeTargetUrl;
this.root.dataset.nodeTargetUrl = this.nodeTargetUrl;
if (this.isDetailRoute) {
const headline = this.root.querySelector('.spiralist-manuscript-workstation__headline');
if (headline) {
headline.textContent = record.title || 'Manuscript folio';
}
const pageLabel = this.root.querySelector('.spiralist-manuscript-reader__page-label');
if (pageLabel) {
pageLabel.textContent = `${record.label || 'Page'} | ${record.eyebrow || 'Book Page'}`;
}
const viewerTitle = this.root.querySelector('#manuscript-workstation-viewer');
if (viewerTitle) {
viewerTitle.textContent = record.title || 'Manuscript folio';
}
const routeChip = this.root.querySelector('.spiralist-manuscript-workstation__meta-chips .spiralist-manuscript-workstation__chip');
if (routeChip) {
routeChip.textContent = `Route ${record.sequence}`;
}
}
this.thumbLinks.forEach((link) => {
const isCurrent = Number.parseInt(`${link.dataset.pageSequence || ''}`.trim(), 10) === record.sequence;
link.classList.toggle('is-current', isCurrent);
if (isCurrent) {
link.setAttribute('aria-current', 'page');
} else {
link.removeAttribute('aria-current');
}
});
this.updateEdgeLink(this.prevLink, this.folioRecords[currentIndex - 1] || null, 'prev');
this.updateEdgeLink(this.nextLink, this.folioRecords[currentIndex + 1] || null, 'next');
}
updateBrowserRoute(record) {
if (!record || this.isDetailRoute) {
return;
}
this.requestViewerStateSync({ immediate: true });
}
turnFolio(direction = 'next') {
if (this.isDetailRoute || this.isTurningPage || !this.folioRecords.length) {
return false;
}
const currentIndex = this.getCurrentFolioRecordIndex();
const targetIndex = direction === 'prev' ? currentIndex - 1 : currentIndex + 1;
const targetRecord = this.folioRecords[targetIndex] || null;
if (!targetRecord) {
return false;
}
this.warmFolioRecord(targetRecord);
this.warmFolioRecord(this.folioRecords[targetIndex + 1] || null);
this.warmFolioRecord(this.folioRecords[targetIndex + 2] || null);
this.isTurningPage = true;
this.root.classList.remove('is-entering-prev', 'is-entering-next', 'is-turning-prev', 'is-turning-next', 'is-sliding-prev', 'is-sliding-next');
this.root.classList.add(direction === 'prev' ? 'is-sliding-prev' : 'is-sliding-next');
const finish = () => {
const currentRecord = this.renderFolioWindow(targetIndex);
if (currentRecord) {
this.syncCurrentFolioChrome(currentRecord, targetIndex);
this.updateBrowserRoute(currentRecord);
this.updateStatus('Manuscript browser updated.');
}
this.isTurningPage = false;
this.root.classList.remove('is-sliding-prev', 'is-sliding-next');
};
window.setTimeout(finish, prefersReducedMotion ? 0 : 220);
return true;
}
turnPage(url, direction = 'next') {
if (this.turnFolio(direction)) {
return;
}
this.navigateToPage(url, direction);
}
navigateToPage(url, direction = 'next') {
if (!url || this.isTurningPage) {
return;
}
const targetUrl = createUrlOr(url, null, { context: 'workstation-page-target-url' });
const currentUrl = createUrlOr(window.location.href, null, { context: 'workstation-page-current-url' });
if (!targetUrl || !currentUrl) {
window.location.assign(url);
return;
}
const nextHref = this.buildViewerStateUrl(targetUrl.href, { mode: 'minimal' });
const currentHref = this.buildViewerStateUrl(currentUrl.href, { mode: 'minimal' });
if (nextHref === currentHref) {
return;
}
storeTurnDirection(direction);
this.isTurningPage = true;
if (prefersReducedMotion) {
window.location.assign(nextHref);
window.setTimeout(() => {
if (window.location.href === currentUrl.href) {
this.isTurningPage = false;
clearTurnDirection();
}
}, 480);
return;
}
this.root.classList.remove('is-entering-prev', 'is-entering-next', 'is-turning-prev', 'is-turning-next');
this.root.classList.add(direction === 'prev' ? 'is-turning-prev' : 'is-turning-next');
window.setTimeout(() => {
window.location.assign(nextHref);
}, 220);
window.setTimeout(() => {
if (window.location.href === currentUrl.href) {
this.isTurningPage = false;
clearTurnDirection();
this.root.classList.remove('is-turning-prev', 'is-turning-next');
}
}, 900);
}
activateEntryMotion() {
const direction = consumeTurnDirection();
if (!direction || prefersReducedMotion) {
return;
}