feat(post-form): photos-first order, auto-collapsing photo summary, success-only view
- Reorder blueprint to Photos → Title → Content; drop title autofocus so the
picker leads (it anchors what you write).
- After upload the photo section auto-collapses to a live summary bar
("✓ N photos ready — tap to review") and re-expands on tap; the field's
own .form-label is hidden so the <summary> is the sole header. Trailing
refreshes settle the summary past FilePond's event/DOM timing gap.
- Replace FilePond's murky completed-thumbnail overlay with a clean green ✓
badge (remove action left intact).
- On a successful post, hide the reset form + location/weather controls so
only the "✓ Saved" confirmation + View-journal CTA remain.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -195,6 +195,70 @@
|
||||
/* Hide FilePond's "Powered by PQINA" credit. */
|
||||
.filepond--credits { display: none !important; }
|
||||
|
||||
/* Issue #2: FilePond's completed thumbnail carries a murky gradient tint + the
|
||||
filename overlay, which reads as "something's wrong". Strip those overlays
|
||||
and show a single clean green ✓ badge so a finished upload is unambiguous.
|
||||
The remove (×) action button is left untouched so photos stay removable. */
|
||||
.filepond--image-preview-overlay { display: none !important; }
|
||||
.filepond--item[data-filepond-item-state="processing-complete"] .filepond--file-info,
|
||||
.filepond--item[data-filepond-item-state="processing-complete"] .filepond--file-status {
|
||||
opacity: 0;
|
||||
}
|
||||
.filepond--item[data-filepond-item-state="processing-complete"]::after {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 5;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
background: var(--color-accent);
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Issue #3: photo section auto-collapses to a summary bar after upload — same
|
||||
<details> affordance as "More options" above. */
|
||||
.photos-collapse {
|
||||
margin-bottom: var(--space-5);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-canvas);
|
||||
}
|
||||
.photos-collapse__summary {
|
||||
cursor: pointer;
|
||||
padding: 0.875rem 1rem;
|
||||
min-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: var(--font-ui);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 600;
|
||||
color: var(--color-ink);
|
||||
list-style: none;
|
||||
user-select: none;
|
||||
}
|
||||
.photos-collapse__summary::-webkit-details-marker { display: none; }
|
||||
.photos-collapse__summary::before {
|
||||
content: '▸';
|
||||
margin-right: var(--space-2);
|
||||
color: var(--color-ink-muted);
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
.photos-collapse[open] .photos-collapse__summary::before { transform: rotate(90deg); }
|
||||
.photos-collapse[open] .photos-collapse__summary { border-bottom: 1px solid var(--color-border); }
|
||||
.photos-collapse > :not(summary) { padding-left: 1rem; padding-right: 1rem; }
|
||||
.photos-collapse[open] .filepond--root,
|
||||
.photos-collapse[open] .filepond-root { margin: var(--space-4) 0; }
|
||||
.photos-collapse[open] > .photo-convert-status:last-child,
|
||||
.photos-collapse[open] > .photo-reauth-hint:last-child { padding-bottom: var(--space-4); }
|
||||
|
||||
/* ── EasyMDE — Field Notes dark theme (U5) ─────────────────── */
|
||||
.EasyMDEContainer .CodeMirror {
|
||||
background: var(--color-canvas);
|
||||
|
||||
@@ -101,11 +101,42 @@ function photoStatusEl() {
|
||||
return el;
|
||||
}
|
||||
|
||||
// Wrap the photo picker in a native <details> so it can auto-collapse to a
|
||||
// live summary bar once uploads settle (issue #3: thumbnails otherwise crowd
|
||||
// the writing area). The field's own <label> is hidden — the <summary> becomes
|
||||
// the section header. Returns { details, summary } or null when no picker.
|
||||
function buildPhotoCollapse() {
|
||||
var root = document.querySelector('.filepond-root, .form-input-file');
|
||||
if (!root) return null;
|
||||
var wrapper = root.closest('.form-field');
|
||||
if (!wrapper) return null;
|
||||
|
||||
// Grav wraps the field label in a .form-label div (not a bare <label>);
|
||||
// hide it so the <summary> is the section's only header.
|
||||
var fieldLabel = wrapper.querySelector('.form-label');
|
||||
|
||||
var details = document.createElement('details');
|
||||
details.className = 'photos-collapse';
|
||||
details.open = true;
|
||||
var summary = document.createElement('summary');
|
||||
summary.className = 'photos-collapse__summary';
|
||||
summary.textContent = 'Photos (max 4)';
|
||||
details.appendChild(summary);
|
||||
|
||||
// Relocate the existing control (+ label/status) into the details body.
|
||||
while (wrapper.firstChild) details.appendChild(wrapper.firstChild);
|
||||
wrapper.appendChild(details);
|
||||
if (fieldLabel) fieldLabel.style.display = 'none';
|
||||
|
||||
return { details: details, summary: summary };
|
||||
}
|
||||
|
||||
function initPhotoConversion() {
|
||||
var form = document.querySelector('form[name="new-entry"]');
|
||||
if (!form) return;
|
||||
|
||||
var converting = 0; // HEIC conversions in flight (before FilePond) — gates Submit
|
||||
var collapse = buildPhotoCollapse();
|
||||
|
||||
function setStatus(msg, kind) {
|
||||
var el = photoStatusEl();
|
||||
@@ -114,6 +145,42 @@ function initPhotoConversion() {
|
||||
el.className = 'photo-convert-status form-status' + (kind ? ' form-status--' + kind : '');
|
||||
}
|
||||
|
||||
// Live summary + auto-collapse for the photo section. Reads FilePond's DOM
|
||||
// item states (robust across FilePond versions) plus the pre-FilePond HEIC
|
||||
// `converting` counter, so the section stays open while anything is in
|
||||
// flight and collapses to "✓ N ready" only once everything has settled.
|
||||
function refreshCollapse() {
|
||||
if (!collapse) return;
|
||||
var items = document.querySelectorAll('.filepond--item[data-filepond-item-state]');
|
||||
var total = items.length;
|
||||
var done = 0;
|
||||
Array.prototype.forEach.call(items, function (el) {
|
||||
if (el.getAttribute('data-filepond-item-state') === 'processing-complete') done++;
|
||||
});
|
||||
if (converting > 0 || (total > 0 && done < total)) {
|
||||
collapse.summary.textContent = converting > 0
|
||||
? 'Converting ' + converting + ' photo' + (converting > 1 ? 's' : '') + '…'
|
||||
: 'Uploading ' + total + ' photo' + (total > 1 ? 's' : '') + '…';
|
||||
collapse.details.open = true; // keep visible while working
|
||||
} else if (total > 0) {
|
||||
collapse.summary.textContent = '✓ ' + total + ' photo' + (total > 1 ? 's' : '') + ' ready — tap to review';
|
||||
collapse.details.open = false; // auto-collapse when settled
|
||||
} else {
|
||||
collapse.summary.textContent = 'Photos (max 4)';
|
||||
collapse.details.open = true;
|
||||
}
|
||||
}
|
||||
|
||||
// FilePond fires processfile/processfiles a tick before it flips the item's
|
||||
// data-filepond-item-state to "processing-complete", so a refresh bound
|
||||
// straight to the event reads a stale state. Re-check on trailing timeouts
|
||||
// so the summary settles on the final DOM state.
|
||||
function scheduleRefresh() {
|
||||
refreshCollapse();
|
||||
setTimeout(refreshCollapse, 150);
|
||||
setTimeout(refreshCollapse, 500);
|
||||
}
|
||||
|
||||
function updateGate() {
|
||||
var btn = form.querySelector('button[type="submit"], input[type="submit"]');
|
||||
if (btn) btn.disabled = converting > 0;
|
||||
@@ -123,6 +190,7 @@ function initPhotoConversion() {
|
||||
var el = photoStatusEl();
|
||||
if (el && /Converting/.test(el.textContent)) setStatus('');
|
||||
}
|
||||
refreshCollapse();
|
||||
}
|
||||
|
||||
// Guard the submit while a HEIC is still converting (it hasn't entered
|
||||
@@ -177,6 +245,11 @@ function initPhotoConversion() {
|
||||
if (pond && !pond._heicHooked) {
|
||||
pond._heicHooked = true;
|
||||
pond.setOptions({ beforeAddFile: makeBeforeAddFile(pond) });
|
||||
// Update the collapse summary as files are added/uploaded/removed.
|
||||
['addfile', 'processfile', 'processfiles', 'removefile', 'error'].forEach(function (ev) {
|
||||
try { pond.on(ev, scheduleRefresh); } catch (e) { /* older FilePond API */ }
|
||||
});
|
||||
refreshCollapse();
|
||||
}
|
||||
});
|
||||
})();
|
||||
@@ -496,6 +569,16 @@ function initSuccessState() {
|
||||
panel.appendChild(actions);
|
||||
notice.parentNode.insertBefore(panel, notice.nextSibling);
|
||||
|
||||
// Issue #1: after a successful post, show the confirmation *only* — hide the
|
||||
// (now-reset) form and the location/weather controls so a stray empty form
|
||||
// doesn't render below the success panel. The .notices message and the
|
||||
// injected panel live in .form-wrapper *before* the <form>, so hiding the
|
||||
// form leaves them visible.
|
||||
['form', '.form-action-row', '#location-status', '#weather-status'].forEach(function (sel) {
|
||||
var el = wrap.querySelector(sel);
|
||||
if (el) el.style.display = 'none';
|
||||
});
|
||||
|
||||
notice.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user