fix(trips): remaining publish-toggle review findings
- resolveTripChild now asserts the resolved page uses the trip template, so a non-trip direct child of /trips could never be toggled through this endpoint (P3 adversarial). - apiSend gains an optional timeoutMs (AbortController); trip-publish passes 10s so a hung toggle can't leave the switch stuck aria-busy. post-form omits it, keeping media uploads unbounded (P2 reliability). - trips.html.twig reuses trip.html.twig's one-line active-trip slug match instead of a bespoke 3-branch OR (P2 maintainability). - Draft-badge amber is now a --color-draft-accent token shared by the trip and journal badges instead of a twice-hardcoded #E0A458 literal (P3). Rebuilt js/trip-publish.js and js/post/post-form.js (shared api-utils change). PHP lint clean; trip-publish suite 10/10; post suite unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
This commit is contained in:
@@ -12,12 +12,25 @@
|
||||
// generic failure. `okStatuses` lists extra codes to accept as success (e.g.
|
||||
// 204 no-content, or 404 already-gone for an idempotent DELETE). Cookies
|
||||
// auto-included.
|
||||
export function apiSend(url, opts, okStatuses) {
|
||||
return fetch(url, Object.assign({ credentials: 'include' }, opts)).then(function (r) {
|
||||
//
|
||||
// `timeoutMs` is OPTIONAL: when set, a hung request is aborted after that many
|
||||
// ms so the caller's pending state can't stick forever (the abort rejects like
|
||||
// any network error → generic error copy). Omit it (post-form's media uploads)
|
||||
// to keep the old unbounded behaviour — a large upload must not be timed out.
|
||||
export function apiSend(url, opts, okStatuses, timeoutMs) {
|
||||
var controller = timeoutMs ? new AbortController() : null;
|
||||
var timer = controller ? setTimeout(function () { controller.abort(); }, timeoutMs) : null;
|
||||
var fetchOpts = Object.assign({ credentials: 'include' }, opts);
|
||||
if (controller) fetchOpts.signal = controller.signal;
|
||||
return fetch(url, fetchOpts).then(function (r) {
|
||||
if (timer) clearTimeout(timer);
|
||||
if (r.ok || (okStatuses && okStatuses.indexOf(r.status) !== -1)) return r;
|
||||
var e = new Error('HTTP ' + r.status);
|
||||
e.status = r.status;
|
||||
throw e;
|
||||
}, function (err) {
|
||||
if (timer) clearTimeout(timer);
|
||||
throw err; // abort (no .status) or network error → generic fallback copy
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import { apiSend, apiErrorMsg } from './api-utils.js';
|
||||
|
||||
var TOAST_TIMEOUT_MS = 5000;
|
||||
var PUBLISH_TIMEOUT_MS = 10000; // abort a hung toggle so the switch never sticks (R13)
|
||||
var toastTimer = null;
|
||||
|
||||
// One visible, page-level polite toast (R15). Modelled on the feed-actions.js
|
||||
@@ -103,7 +104,7 @@ function onToggle(btn) {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
body: JSON.stringify({ published: next })
|
||||
}).then(function () {
|
||||
}, null, PUBLISH_TIMEOUT_MS).then(function () {
|
||||
// Success: flip the switch + Draft badge in place, no reload (R14).
|
||||
setPublishedUI(btn, next);
|
||||
setPending(btn, false);
|
||||
|
||||
Reference in New Issue
Block a user