Manuscript Workstation - Source Excerpt 01
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
(() => {
const config = window.spiralistManuscriptWorkstation || {};
const DEFAULT_CROP = Object.freeze({
x: 0.08,
y: 0.06,
width: 0.84,
height: 0.88,
});
const MIN_CROP_SIZE = 0.05;
const warmCache = new Set();
const turnStateKey = 'spiralistManuscriptWorkstationTurn';
const runtimePathStateKey = 'spiralistManuscriptRuntimePath';
const viewerStateParams = Object.freeze({
viewMode: 'm_view',
fitMode: 'm_fit',
rawMode: 'm_raw',
guidesVisible: 'm_guides',
overlayVisible: 'm_overlay',
scale: 'm_zoom',
panX: 'm_x',
panY: 'm_y',
searchQuery: 'm_query',
currentSequence: 'm_page',
});
const viewerStateParamKeys = Object.freeze(Object.values(viewerStateParams));
const prefersReducedMotion =
typeof window.matchMedia === 'function' &&
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const core = window.SpiralistCore || {};
const handleCoreError = typeof core.handleError === 'function'
? core.handleError
: () => '';
const parseJsonOr = typeof core.parseJsonOr === 'function'
? core.parseJsonOr
: (text, fallback = null, options = {}) => {
if (typeof text !== 'string' || text === '') {
return fallback;
}
try {
return JSON.parse(text);
} catch (error) {
handleCoreError(error, { context: options.context || 'JSON.parse' });
return fallback;
}
};
const createUrlOr = typeof core.createUrlOr === 'function'
? core.createUrlOr
: (value, fallback = null, options = {}) => {
try {
return new URL(value, options.base || window.location.href);
} catch (error) {
handleCoreError(error, { context: options.context || 'URL' });
return fallback;
}
};
const storage = core.storage || {
get(key, fallback = '', area = 'local') {
try {
return (area === 'session' ? window.sessionStorage : window.localStorage).getItem(key) || fallback;
} catch (error) {
handleCoreError(error, { context: `${area}Storage.getItem` });
return fallback;
}
},
set(key, value, area = 'local') {
try {
(area === 'session' ? window.sessionStorage : window.localStorage).setItem(key, value);
return true;
} catch (error) {
handleCoreError(error, { context: `${area}Storage.setItem` });
return false;
}
},
remove(key, area = 'local') {
try {
(area === 'session' ? window.sessionStorage : window.localStorage).removeItem(key);
} catch (error) {
handleCoreError(error, { context: `${area}Storage.removeItem` });
}
},
};
const copyText = typeof core.copyText === 'function'
? core.copyText
: async (text) => {
if (window.navigator?.clipboard && typeof window.navigator.clipboard.writeText === 'function') {
await window.navigator.clipboard.writeText(text);
return;
}
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', 'readonly');
textarea.style.position = 'fixed';
textarea.style.top = '0';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
let copied = false;
try {
copied = Boolean(document.execCommand('copy'));
} finally {
textarea.remove();
}
if (!copied) {
throw new Error('Clipboard copy failed.');
}
};
const runWorkstationAction = (action, options = {}) => Promise.resolve()
.then(() => action())
.catch((error) => {
if (typeof options.shouldIgnoreError === 'function' && options.shouldIgnoreError(error)) {
return;
}
handleCoreError(error, options);
if (typeof options.onError === 'function') {
options.onError(error, error && error.message ? error.message : '');
}
throw error;
})
.finally(() => {
if (typeof options.onFinally === 'function') {
options.onFinally();
}
})
.catch(() => {});
const setPointerCapture = typeof core.setPointerCapture === 'function'
? core.setPointerCapture
: (element, pointerId) => {
if (!element || typeof element.setPointerCapture !== 'function') {
return;
}
try {
element.setPointerCapture(pointerId);
} catch (error) {
handleCoreError(error, { context: 'Element.setPointerCapture' });
}
};
const dispatchEventSafely = (eventName, detail = {}) => {
try {
window.dispatchEvent(new CustomEvent(eventName, { detail }));
} catch (error) {
handleCoreError(error, { context: eventName });
}
};
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
const parseBooleanParam = (value) => {
const normalized = `${value ?? ''}`.trim().toLowerCase();
if (!normalized) {
return null;
}
if (['1', 'true', 'yes', 'on'].includes(normalized)) {
return true;
}
if (['0', 'false', 'no', 'off'].includes(normalized)) {
return false;
}
return null;
};
const formatStateNumber = (value, precision = 4) => {
if (!Number.isFinite(value)) {
return '';
}
return Number(value).toFixed(precision).replace(/\.?0+$/, '');
};
const escapeHtml = (value) =>
`${value ?? ''}`
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
const escapeRegExp = (value) => `${value ?? ''}`.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const slugifyPathSegment = (value = '') => {
const normalized = `${value ?? ''}`
.trim()
.normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
return normalized || 'item';
};
const buildChildRouteUrl = (baseUrl, segments = [], hash = '') => {
const url = createUrlOr(baseUrl, null, { context: 'workstation-child-route' });
if (!url) {
return baseUrl || window.location.href;
}
const basePath = url.pathname.replace(/\/+$/, '');
const childPath = segments
.map((segment) => slugifyPathSegment(segment))
.filter(Boolean)
.map(encodeURIComponent)
.join('/');
url.pathname = `${basePath || ''}/${childPath ? `${childPath}/` : ''}`.replace(/\/{2,}/g, '/');
url.search = '';
url.hash = hash || '';
return url.toString();
};
const isEditableTarget = (target) => {
if (!(target instanceof HTMLElement)) {
return false;
}
const tagName = target.tagName.toUpperCase();
return target.isContentEditable || tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT';
};
const normalizeCropRect = (rect = {}) => {
const x = clamp(Number(rect.x) || DEFAULT_CROP.x, 0, 1);
const y = clamp(Number(rect.y) || DEFAULT_CROP.y, 0, 1);
let width = clamp(Number(rect.width) || DEFAULT_CROP.width, MIN_CROP_SIZE, 1);
let height = clamp(Number(rect.height) || DEFAULT_CROP.height, MIN_CROP_SIZE, 1);
let nextX = x;
let nextY = y;
if (nextX + width > 1) {
nextX = Math.max(0, 1 - width);
}
if (nextY + height > 1) {
nextY = Math.max(0, 1 - height);
}
width = clamp(width, MIN_CROP_SIZE, 1 - nextX);
height = clamp(height, MIN_CROP_SIZE, 1 - nextY);
return {
x: Number(nextX.toFixed(6)),
y: Number(nextY.toFixed(6)),
width: Number(width.toFixed(6)),
height: Number(height.toFixed(6)),
};
};
const parseCrop = (raw) => {
if (!raw) {
return { ...DEFAULT_CROP };
}
return normalizeCropRect(parseJsonOr(raw, DEFAULT_CROP, { context: 'workstation-crop' }));
};
const parseOverlayData = (raw) => {
if (!raw) {
return {};
}
const parsed = parseJsonOr(raw, {}, { context: 'workstation-overlay-data' });
return parsed && typeof parsed === 'object' ? parsed : {};
};
const getOverlayNodesUrl = (overlayData = {}) =>
`${overlayData.nodes_url || overlayData.nodesUrl || ''}`.trim();
const overlayIncludesGlobalNodes = (overlayData = {}) =>
Boolean(overlayData.include_global_nodes || overlayData.includeGlobalNodes);
const buildNodeUrl = (baseUrl, nodeId) => {
if (!baseUrl || !nodeId) {
return '#study-surface-tools';
}
return buildChildRouteUrl(config.manuscriptPageUrl || baseUrl, ['node', nodeId], '#study-surface-tools');
};
const addCacheBuster = (url) => {
if (!url) {
return '';
}
const nextUrl = createUrlOr(url, null, { context: 'workstation-cache-buster' });
if (!nextUrl) {
return url;
}