refactor(theme): share apiSend/apiErrorMsg between post-form and trip-publish

Address the P1 maintainability finding that trip-publish.js reimplemented
post-form.js's apiSend + login-expired error copy verbatim. Extract both into
js/src/api-utils.js and import from both entry points; esbuild inlines the
module into each bundle so there is no runtime coupling. Also drops the stale
data-trip-route reference from trip-publish.js's markup-contract comment.

Rebuilt js/trip-publish.js and js/post/post-form.js.

Verified: trip-publish suite 8/8; post suite unchanged (34 pass, same 6
owner-gate environmental fails as baseline — photo-editor specs that exercise
post-form's apiSend/apiErrorMsg all green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
This commit is contained in:
2026-07-08 17:14:00 +02:00
co-authored by Claude Opus 4.8
parent 5b4e31678e
commit 25cee53718
5 changed files with 95 additions and 102 deletions
+4 -25
View File
@@ -11,6 +11,7 @@ import EasyMDE from 'easymde';
import Sortable from 'sortablejs';
import 'easymde/dist/easymde.min.css';
import './post-form.css';
import { apiSend, apiErrorMsg } from './api-utils.js';
/* ── Markdown editor (EasyMDE) ───────────────────────────── */
function initEditor() {
@@ -882,29 +883,7 @@ function initPhotoEditor(route) {
if (sortable) sortable.option('disabled', b);
}
// Mutation fetch that RESOLVES on success and REJECTS with a status-bearing
// Error otherwise, so callers can tell an expired login (401/403) apart from a
// 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. (Superseded the old boolean apiOk, which swallowed the code.)
function apiSend(url, opts, okStatuses) {
return fetch(url, Object.assign({ credentials: 'include' }, opts)).then(function (r) {
if (r.ok || (okStatuses && okStatuses.indexOf(r.status) !== -1)) return r;
var e = new Error('HTTP ' + r.status);
e.status = r.status;
throw e;
});
}
// Turn a failed apiSend/fetch into owner-facing copy. A 401/403 almost always
// means the login session lapsed mid-edit — say so, because a plain "try
// again" wouldn't help until they sign back in.
function editErrorMsg(err, fallback) {
var s = err && err.status;
return (s === 401 || s === 403)
? 'Your login session expired — sign in again, then retry.'
: fallback;
}
// apiSend / apiErrorMsg now live in ./api-utils.js (shared with trip-publish.js).
function mediaList() {
return fetch('/api/v1/pages' + route + '/media', { credentials: 'include', headers: { Accept: 'application/json' } })
@@ -990,7 +969,7 @@ function initPhotoEditor(route) {
function () { setStatus(''); render(next); } // saved; DOM already shows it
);
}, function (err) {
setStatus(editErrorMsg(err, 'Couldnt save the new order — reverted. Try again.'), true);
setStatus(apiErrorMsg(err, 'Couldnt save the new order — reverted. Try again.'), true);
render(lastGood); // revert the SortableJS move to last-known-good
}).then(function () { setBusy(false); });
}
@@ -1038,7 +1017,7 @@ function initPhotoEditor(route) {
);
}, function (err) {
// The DELETE request itself failed — nothing changed on disk.
setStatus(editErrorMsg(err, 'Couldnt delete that photo. Try again.'), true);
setStatus(apiErrorMsg(err, 'Couldnt delete that photo. Try again.'), true);
render(lastGood);
})
.then(function () { setBusy(false); });