SEV-329: @switchev/web-shared package — formatters, markdown, waitForImage
Stands up the shared browser-helpers package (consumed by public/vendor/admin via the Gitea npm registry) to de-duplicate copy-pasted frontend code: - format.js: formatLabel/formatStatus/formatTimeline/formatBudget (single source of truth — these had drifted, see SEV-310). - markdown.js: renderMarkdown (marked + strict DOMPurify; both peer deps). - wait-image.js: waitForImage (was byte-identical in public + vendor). ESM source package (no build step — Vite consumers import directly). publishConfig points at the Gitea npm registry. node --test green (formatters). Next: publish v0.1.0, then migrate each site to consume it and delete the copies. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
// SEV-329: shared waitForImage (was byte-identical in public-site + vendor-site).
|
||||
//
|
||||
// SEV-306: uploads quarantine into a private incoming/ prefix and the
|
||||
// image-processor re-encodes them out to the public URL asynchronously (and
|
||||
// DELETES anything it can't decode). So the public URL 404s briefly after upload
|
||||
// and never appears for a rejected file. waitForImage polls (via Image() to
|
||||
// dodge CORS) until the sanitized object loads ('ready') or times out
|
||||
// ('rejected'). Callers gate display on this so users never see a broken <img>.
|
||||
export function waitForImage(url, { timeoutMs = 30000, intervalMs = 1500 } = {}) {
|
||||
return new Promise((resolve) => {
|
||||
const start = Date.now();
|
||||
const attempt = () => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve('ready');
|
||||
img.onerror = () => {
|
||||
if (Date.now() - start >= timeoutMs) resolve('rejected');
|
||||
else setTimeout(attempt, intervalMs);
|
||||
};
|
||||
img.src = url + (url.includes('?') ? '&' : '?') + '_cb=' + Date.now();
|
||||
};
|
||||
attempt();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user