feat(trips): owner publish/unpublish toggle on /trips listing
Add an owner-only publish switch to each /trips card. It POSTs to a new
entry-actions route that mutates trip.md `published` and invalidates the
page-tree cache so the listing, nav and home reflect the change on the
next load. Owner sees drafts (Draft badge); anon/non-owner unchanged.
- U1 EntryScopeGuard::resolveTripChild — resolve a slug to a direct child
of /trips (drafts included, for republish)
- U2 POST /api/v1/trip/{slug}/publish (setTripPublished) — owner-gated
write, strict is_bool body, header-mutation save, audit log
- U3 trip-publish-toggle partial + CSS (role=switch, Draft badge, visible
failure toast, ≥44px target)
- U4 owner-aware /trips listing + card restructure (toggle overlays cover
as a non-anchor sibling; works for coverless drafts)
- U5 home active-trip branch falls back when the active trip is unpublished
- U6 trip-publish.js (confirm/pending/optimistic/revert) + esbuild wiring
Cache note: an in-place frontmatter edit keeps the folder-check cache id,
and driver:auto uses APCu in web memory, so deleteAll()+invalidateCache()
is insufficient — the endpoint also calls apcu_clear_cache().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
This commit is contained in:
@@ -1289,6 +1289,143 @@ body::after {
|
||||
.trip-card-dates { font-size: var(--text-sm); color: var(--color-ink-2); }
|
||||
.trip-card-counts { font-size: var(--text-sm); color: var(--color-ink-muted); }
|
||||
|
||||
/* ── Owner publish/unpublish toggle (U3) ─────────────────────────────────────── */
|
||||
/* The card is wrapped in a position:relative container so this overlay can sit
|
||||
top-right over the cover as a sibling of the navigating <a> (KTD6). The wrapper
|
||||
also guarantees an anchor even for a coverless draft (a min-height header strip
|
||||
on the card itself), so the toggle never collapses to nothing. */
|
||||
.trip-card-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.trip-publish-overlay {
|
||||
position: absolute;
|
||||
top: var(--space-3);
|
||||
right: var(--space-3);
|
||||
z-index: 2; /* above the card <a> */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
/* Solid pill so both indicators stay legible over an arbitrary cover photo. */
|
||||
.trip-draft-badge {
|
||||
font-family: var(--font-ui);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.09em;
|
||||
text-transform: uppercase;
|
||||
color: #E0A458; /* warm amber — matches .journal-draft-badge */
|
||||
background: var(--color-canvas);
|
||||
border: 1px solid #E0A458;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.15em 0.5em;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.trip-draft-badge[hidden] { display: none; }
|
||||
|
||||
/* The switch: a solid chip backing keeps the track/knob readable on any cover.
|
||||
≥44px touch target via padding; the visible track is smaller and centred. */
|
||||
.trip-publish-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
padding: 0 var(--space-2);
|
||||
margin: 0;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-canvas);
|
||||
box-shadow: var(--shadow-sm);
|
||||
cursor: pointer;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.trip-publish-track {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 22px;
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-ink-muted); /* off = muted */
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
.trip-publish-knob {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-ink);
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
|
||||
/* on = teal track, knob slid right */
|
||||
.trip-publish-toggle[aria-checked="true"] .trip-publish-track {
|
||||
background: var(--color-accent);
|
||||
}
|
||||
.trip-publish-toggle[aria-checked="true"] .trip-publish-knob {
|
||||
transform: translateX(18px);
|
||||
background: var(--color-accent-on);
|
||||
}
|
||||
|
||||
/* Pending (R13): dimmed + wait cursor while a toggle is in flight. */
|
||||
.trip-publish-toggle[aria-busy="true"] {
|
||||
opacity: 0.55;
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
/* Keyboard focus ring that reads over a busy cover photo (white ring + dark halo). */
|
||||
.trip-publish-toggle:focus-visible {
|
||||
outline: 2px solid var(--color-accent-on);
|
||||
outline-offset: 2px;
|
||||
box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
/* Visible page-level failure toast (R15). Distinct from feed-actions.js's
|
||||
sr-only #feed-actions-live region: the trip card has no inline message slot,
|
||||
so a sighted owner needs a real, visible notice. trip-publish.js creates and
|
||||
populates the element; this only styles it. */
|
||||
.trip-publish-toast {
|
||||
position: fixed;
|
||||
top: var(--space-4);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
max-width: calc(100vw - var(--space-8));
|
||||
padding: var(--space-3) var(--space-4);
|
||||
font-family: var(--font-ui);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-ink);
|
||||
background: var(--color-canvas);
|
||||
border: 1px solid var(--color-error);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
.trip-publish-toast[hidden] { display: none; }
|
||||
.trip-publish-toast__close {
|
||||
flex-shrink: 0;
|
||||
min-width: 32px;
|
||||
min-height: 32px;
|
||||
padding: 0;
|
||||
font-size: var(--text-md);
|
||||
line-height: 1;
|
||||
color: var(--color-ink-muted);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
}
|
||||
.trip-publish-toast__close:hover { color: var(--color-ink); }
|
||||
|
||||
/* ── Trip page sidebar ───────────────────────────────────────────────────────── */
|
||||
|
||||
.trip-counts {
|
||||
|
||||
Reference in New Issue
Block a user