feat(post-form): require 1–6 photos per entry
Raise the FilePond limit from 4 to 6 and enforce a minimum of one photo. The photo field is first in the form, so initValidation checks it first: an empty picker blocks submit, reveals the (possibly collapsed) photo section, and shows "Add at least one photo." under its header. Labels updated to "Photos (1–6)". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,7 @@ form:
|
|||||||
# bar so the thumbnails don't crowd the writing area.
|
# bar so the thumbnails don't crowd the writing area.
|
||||||
-
|
-
|
||||||
name: photos
|
name: photos
|
||||||
label: Photos (max 4)
|
label: Photos (1–6)
|
||||||
# Grav-managed filepond field. post-form.js hooks its beforeAddFile
|
# Grav-managed filepond field. post-form.js hooks its beforeAddFile
|
||||||
# to convert HEIC->JPEG in the browser, then re-adds the JPEG via the
|
# to convert HEIC->JPEG in the browser, then re-adds the JPEG via the
|
||||||
# public pond.addFile() API — FilePond keeps ownership of the upload
|
# public pond.addFile() API — FilePond keeps ownership of the upload
|
||||||
@@ -32,7 +32,9 @@ form:
|
|||||||
type: filepond
|
type: filepond
|
||||||
multiple: true
|
multiple: true
|
||||||
destination: '@self'
|
destination: '@self'
|
||||||
limit: 4
|
# Max 6 photos per entry; at least 1 is required (enforced
|
||||||
|
# client-side in post-form.js initValidation).
|
||||||
|
limit: 6
|
||||||
accept:
|
accept:
|
||||||
- 'image/*'
|
- 'image/*'
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -120,7 +120,7 @@ function buildPhotoCollapse() {
|
|||||||
details.open = true;
|
details.open = true;
|
||||||
var summary = document.createElement('summary');
|
var summary = document.createElement('summary');
|
||||||
summary.className = 'photos-collapse__summary';
|
summary.className = 'photos-collapse__summary';
|
||||||
summary.textContent = 'Photos (max 4)';
|
summary.textContent = 'Photos (1–6)';
|
||||||
details.appendChild(summary);
|
details.appendChild(summary);
|
||||||
|
|
||||||
// Relocate the existing control (+ label/status) into the details body.
|
// Relocate the existing control (+ label/status) into the details body.
|
||||||
@@ -188,7 +188,7 @@ function initPhotoConversion() {
|
|||||||
collapse.summary.textContent = '✓ ' + total + ' photo' + (total > 1 ? 's' : '') + ' ready — tap to review';
|
collapse.summary.textContent = '✓ ' + total + ' photo' + (total > 1 ? 's' : '') + ' ready — tap to review';
|
||||||
collapse.details.open = false; // auto-collapse when settled
|
collapse.details.open = false; // auto-collapse when settled
|
||||||
} else {
|
} else {
|
||||||
collapse.summary.textContent = 'Photos (max 4)';
|
collapse.summary.textContent = 'Photos (1–6)';
|
||||||
collapse.details.open = true;
|
collapse.details.open = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -469,11 +469,40 @@ function initValidation() {
|
|||||||
el.parentNode.insertBefore(err, el.nextSibling);
|
el.parentNode.insertBefore(err, el.nextSibling);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// At least one photo is required. The picker is first in the form, so if it
|
||||||
|
// is empty this is the first invalid field — reveal the (possibly collapsed)
|
||||||
|
// section, show the message under its header, and return an element to
|
||||||
|
// scroll to. Uses the same .field-error class so clearErrors() cleans it up.
|
||||||
|
function showPhotoError(msg) {
|
||||||
|
var collapse = form.querySelector('.photos-collapse');
|
||||||
|
var host, anchor;
|
||||||
|
if (collapse) {
|
||||||
|
collapse.open = true; // reveal the drop zone + message
|
||||||
|
anchor = collapse.querySelector('.photos-collapse__summary');
|
||||||
|
host = collapse;
|
||||||
|
} else {
|
||||||
|
host = document.querySelector('.filepond-root, .form-input-file');
|
||||||
|
anchor = host;
|
||||||
|
}
|
||||||
|
if (!anchor) return host || null;
|
||||||
|
var err = document.createElement('span');
|
||||||
|
err.className = 'field-error';
|
||||||
|
err.textContent = msg;
|
||||||
|
anchor.insertAdjacentElement('afterend', err);
|
||||||
|
return host || anchor;
|
||||||
|
}
|
||||||
|
|
||||||
// Bubble phase: runs after initEditor's capture-phase codemirror.save(), so
|
// Bubble phase: runs after initEditor's capture-phase codemirror.save(), so
|
||||||
// the content textarea already holds the live value.
|
// the content textarea already holds the live value.
|
||||||
form.addEventListener('submit', function (e) {
|
form.addEventListener('submit', function (e) {
|
||||||
clearErrors();
|
clearErrors();
|
||||||
var firstInvalid = null;
|
var firstInvalid = null;
|
||||||
|
|
||||||
|
// ≥1 photo (photos are the first field). Count any present FilePond item.
|
||||||
|
if (document.querySelectorAll('.filepond--item').length < 1) {
|
||||||
|
firstInvalid = showPhotoError('Add at least one photo.');
|
||||||
|
}
|
||||||
|
|
||||||
Object.keys(REQUIRED).forEach(function (name) {
|
Object.keys(REQUIRED).forEach(function (name) {
|
||||||
var el = field(name);
|
var el = field(name);
|
||||||
if (el && !String(el.value).trim()) {
|
if (el && !String(el.value).trim()) {
|
||||||
@@ -483,7 +512,7 @@ function initValidation() {
|
|||||||
});
|
});
|
||||||
if (firstInvalid) {
|
if (firstInvalid) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
firstInvalid.focus();
|
if (typeof firstInvalid.focus === 'function') firstInvalid.focus();
|
||||||
firstInvalid.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
firstInvalid.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user