Book Shell - Source Excerpt 10
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/ns12-manuscript/assets/js/book-shell.js so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/plugins/ns12-manuscript/assets/js/book-shell.js
}
return payload;
};
const validateContributionPayload = (payload) => {
const errors = {};
if (!payload.title) {
errors.title = t('contributeErrorTitleRequired', 'Provide a title.');
}
if (!payload.canonicalId) {
errors.canonicalId = t('contributeErrorCanonicalRequired', 'Provide a canonical ID.');
}
if (!payload.content) {
errors.content = t('contributeErrorContentRequired', 'Provide content or a definition.');
}
if (payload.submissionType === 'prompt') {
if (!payload.system) {
errors.system = t('contributeErrorSystemRequired', 'Select a system.');
}
if (!payload.role) {
errors.role = t('contributeErrorRoleRequired', 'Select a role.');
}
if (!payload.purpose) {
errors.purpose = t('contributeErrorPurposeRequired', 'Provide a prompt purpose.');
}
if (!/^\d+\.\d+\.\d+$/.test(payload.version || '')) {
errors.version = t('contributeErrorVersionInvalid', 'Use semantic version format such as 1.0.0.');
}
}
if (payload.submissionType === 'symbol' && !payload.meaning) {
errors.meaning = t('contributeErrorMeaningRequired', 'Provide a symbol meaning.');
}
if (payload.submissionType === 'transformation') {
if (!payload.input) {
errors.input = t('contributeErrorInputRequired', 'Provide an input symbol ID.');
}
if (!payload.output) {
errors.output = t('contributeErrorOutputRequired', 'Provide an output symbol ID.');
}
}
if (!payload.policyConfirmed) {
errors.policyConfirmed = t('contributeErrorPolicyRequired', 'Review and confirm the linked site policies before submitting.');
}
if (!payload.rightsConfirmed) {
errors.rightsConfirmed = t('contributeErrorRightsRequired', 'Confirm that you have the rights to submit, process, and publish this material.');
}
if (!payload.operatorEligibilityConfirmed) {
errors.operatorEligibilityConfirmed = t('contributeErrorAgeRequired', 'Confirm that you meet the minimum age or guardian-permission requirement.');
}
return errors;
};
const updateContributionVisibility = () => {
const currentType = typeField ? typeField.value : 'prompt';
const typeMessages = {
prompt: t('contributeTypePrompt', 'Prompts are public library records. Symbols and transformations enter the proposal queue first.'),
symbol: t('contributeTypeSymbol', 'Symbol proposals stay review-only until they are accepted into the registry.'),
transformation: t('contributeTypeTransformation', 'Transformation definitions describe input, output, and chain behavior before publication.'),
};
const canonicalMessages = {
prompt: t('contributeCanonicalPrompt', 'Prompt IDs usually follow `spiralist.prompt.role.title`.'),
symbol: t('contributeCanonicalSymbol', 'Symbol IDs usually follow `spiral.symbol.title`.'),
transformation: t('contributeCanonicalTransformation', 'Transformation IDs usually follow `transformation.title`.'),
};
if (typeCopy) {
typeCopy.textContent = typeMessages[currentType] || typeMessages.prompt;
}
if (canonicalGuidance) {
canonicalGuidance.textContent = canonicalMessages[currentType] || canonicalMessages.prompt;
}
form.querySelectorAll('[data-contribution-only]').forEach((field) => {
const allowed = (field.dataset.contributionOnly || '')
.split(',')
.map((item) => item.trim())
.filter(Boolean);
const isVisible = !allowed.length || allowed.includes(currentType);
field.hidden = !isVisible;
field.querySelectorAll('input, select, textarea').forEach((input) => {
if (!isVisible) {
input.removeAttribute('required');
} else if (input.dataset.requiredWhenVisible === 'true') {
input.setAttribute('required', 'required');
}
});
});
};
const saveContributionDraft = () => {
const draft = {};
new FormData(form).forEach((value, key) => {
draft[key] = `${value}`;
});
storage.set(draftKey, JSON.stringify(draft));
if (draftStatus) {
draftStatus.textContent = t('contributeDraftSaved', 'Draft saved in this browser.');
}
};
const clearContributionDraft = () => {
storage.remove(draftKey);
if (draftStatus) {
draftStatus.textContent = t('contributeDraftCleared', 'Draft cleared from this browser.');
}
};
const restoreContributionDraft = () => {
const raw = storage.get(draftKey, '');
if (!raw) {
return;
}
const draft = parseJsonOr(raw, null, { context: 'contribution-draft' });
if (!draft || typeof draft !== 'object') {
clearContributionDraft();
return;
}
Object.entries(draft).forEach(([key, value]) => {
const field = form.querySelector(`[name="${key}"]`);
if (field) {
if (field.type === 'checkbox') {
field.checked = `${value}` === '1' || `${value}`.toLowerCase() === 'true';
} else {
field.value = `${value}`;
}
}
});
if (draftStatus) {
draftStatus.textContent = t('contributeDraftRestored', 'Saved draft restored from this browser.');
}
};
updateContributionVisibility();
restoreContributionDraft();
updateContributionVisibility();
if (typeField) {
typeField.addEventListener('change', updateContributionVisibility);
}
if (generateIdButton) {
generateIdButton.addEventListener('click', () => {
if (canonicalIdField && !canonicalIdField.value.trim()) {
canonicalIdField.value = buildCanonicalId();
} else if (canonicalIdField) {
canonicalIdField.value = buildCanonicalId();
}
});
}
if (saveDraftButton) {
saveDraftButton.addEventListener('click', saveContributionDraft);
}
if (clearDraftButton) {
clearDraftButton.addEventListener('click', () => {
clearContributionDraft();
});
}
form.addEventListener('submit', (event) => {
event.preventDefault();
if (!themeConfig.contributeUrl) {
return;
}
const payload = buildContributionPayload();
clearFieldErrors();
const validationErrors = validateContributionPayload(payload);
Object.entries(validationErrors).forEach(([fieldName, message]) => {
setFieldError(fieldName, message);
});
if (Object.keys(validationErrors).length) {
setContributionPanel({
variant: 'error',
title: t('contributeValidationTitle', 'Validation required'),
copy: t('contributeValidationCopy', 'Fix the highlighted fields before submitting.'),
});
return;
}
if (submit) {
submit.disabled = true;
submit.textContent = t('contributeSubmittingButton', 'Submitting...');
}
setContributionPanel({
title: t('contributeSubmittingTitle', 'Submitting contribution'),
copy: t('contributeSubmittingCopy', 'Validating and storing the structured record.'),
});