also looking at this
fix: MVP-readiness fixes — restore lost UI feedback, register safety, analytics error state, mobile + a11y
#31
7 files
+123
-29
| @@ -64,10 +64,14 @@ | ||
| 64 | 64 | </div> |
| 65 | 65 | <strong class="text-[13px]" data-cal-title>—</strong> |
| 66 | 66 | </header> |
| 67 | <div class="grid grid-cols-7 font-bold text-[11px] text-zinc-500 border-b border-zinc-200"> | |
| 68 | <div class="p-2 text-center">Sun</div><div class="p-2 text-center">Mon</div><div class="p-2 text-center">Tue</div><div class="p-2 text-center">Wed</div><div class="p-2 text-center">Thu</div><div class="p-2 text-center">Fri</div><div class="p-2 text-center">Sat</div> | |
| 67 | <div class="overflow-x-auto"> | |
| 68 | <div class="min-w-[560px]"> | |
| 69 | <div class="grid grid-cols-7 font-bold text-[11px] text-zinc-500 border-b border-zinc-200"> | |
| 70 | <div class="p-2 text-center">Sun</div><div class="p-2 text-center">Mon</div><div class="p-2 text-center">Tue</div><div class="p-2 text-center">Wed</div><div class="p-2 text-center">Thu</div><div class="p-2 text-center">Fri</div><div class="p-2 text-center">Sat</div> | |
| 71 | </div> | |
| 72 | <div class="grid grid-cols-7" data-cal-grid></div> | |
| 73 | </div> | |
| 69 | 74 | </div> |
| 70 | <div class="grid grid-cols-7" data-cal-grid></div> | |
| 71 | 75 | </section> |
| 72 | 76 | </section> |
| 73 | 77 | |
| @@ -80,7 +84,10 @@ | ||
| 80 | 84 | const recentList = root.querySelector('[data-queue-recent]') |
| 81 | 85 | const meta = root.querySelector('[data-queue-meta]') |
| 82 | 86 | const recentMeta = root.querySelector('[data-queue-recent-meta]') |
| 83 | const toast = window.postlineToast?.show | |
| 87 | // Resolve the toast helper at call time — it's a deferred module that | |
| 88 | // loads after this inline script parses, so an eager capture would be | |
| 89 | // undefined on a full page load and drop every message to the console. | |
| 90 | const showToast = options => window.postlineToast?.show?.(options) | |
| 84 | 91 | |
| 85 | 92 | const PENDING = new Set(['draft', 'scheduled', 'publishing', 'failed']) |
| 86 | 93 | |
| @@ -113,6 +120,9 @@ | ||
| 113 | 120 | const liveUrlFor = (uri) => { |
| 114 | 121 | const value = String(uri || '') |
| 115 | 122 | if (value.startsWith('/')) return value |
| 123 | // A stored permalink (Mastodon, and any provider persisting a URL) is | |
| 124 | // already linkable as-is. | |
| 125 | if (/^https?:\/\//i.test(value)) return value | |
| 116 | 126 | const match = value.match(/^at:\/\/([^/]+)\/app\.bsky\.feed\.post\/(.+)$/) |
| 117 | 127 | return match ? `https://bsky.app/profile/${encodeURIComponent(match[1])}/post/${encodeURIComponent(match[2])}` : null |
| 118 | 128 | } |
| @@ -141,7 +151,7 @@ | ||
| 141 | 151 | </header> |
| 142 | 152 | <p class="m-0 leading-normal text-[13px] text-slate-700">${escapeHtml(item.body)}</p> |
| 143 | 153 | ${failure ? `<p class="m-0 text-red-700 text-xs">${escapeHtml(failure)}</p>` : ''} |
| 144 | <footer class="flex gap-2 items-center justify-end"> | |
| 154 | <footer class="flex flex-wrap gap-2 items-center justify-end"> | |
| 145 | 155 | ${liveUrl ? `<a class="inline-flex items-center justify-center px-2.5 h-7 font-bold text-neutral-950 text-xs bg-white hover:bg-zinc-50 border border-zinc-200 rounded-lg shadow-sm transition-colors" href="${escapeHtml(liveUrl)}" target="_blank" rel="noopener noreferrer">View</a>` : ''} |
| 146 | 156 | ${actionable ? `<a class="inline-flex items-center justify-center px-2.5 h-7 font-bold text-neutral-950 text-xs bg-white hover:bg-zinc-50 border border-zinc-200 rounded-lg shadow-sm transition-colors" href="/?edit=${item.id}" data-stx-link>Edit</a>` : ''} |
| 147 | 157 | ${actionable ? `<button class="inline-flex items-center justify-center px-2.5 h-7 font-bold text-white text-xs bg-neutral-950 hover:bg-neutral-800 rounded-lg transition-colors" type="button" data-queue-publish="${item.id}">Publish now</button>` : ''} |
| @@ -285,7 +295,7 @@ | ||
| 285 | 295 | const message = error instanceof Error ? error.message : String(error) |
| 286 | 296 | if (pendingList) pendingList.innerHTML = `<p class="m-0 p-3 text-[13px] text-zinc-500">${escapeHtml(message)}</p>` |
| 287 | 297 | if (meta) meta.textContent = 'Could not load' |
| 288 | if (toast) toast({ title: 'Queue', message, tone: 'error' }) | |
| 298 | showToast({ title: 'Queue', message, tone: 'error' }) | |
| 289 | 299 | } |
| 290 | 300 | } |
| 291 | 301 | |
| @@ -307,10 +317,10 @@ | ||
| 307 | 317 | publishButton.setAttribute('disabled', 'disabled') |
| 308 | 318 | try { |
| 309 | 319 | await post('/api/postline/queue/publish-now', publishButton.dataset.queuePublish) |
| 310 | if (toast) toast({ title: 'Published', message: 'Post published.', tone: 'success' }) | |
| 320 | showToast({ title: 'Published', message: 'Post published.', tone: 'success' }) | |
| 311 | 321 | } |
| 312 | 322 | catch (error) { |
| 313 | if (toast) toast({ title: 'Publish failed', message: error instanceof Error ? error.message : String(error), tone: 'error' }) | |
| 323 | showToast({ title: 'Publish failed', message: error instanceof Error ? error.message : String(error), tone: 'error' }) | |
| 314 | 324 | } |
| 315 | 325 | await refresh() |
| 316 | 326 | return |
| @@ -318,13 +328,14 @@ | ||
| 318 | 328 | |
| 319 | 329 | const deleteButton = target.closest('[data-queue-delete]') |
| 320 | 330 | if (deleteButton) { |
| 331 | if (!window.confirm('Delete this post? This cannot be undone.')) return | |
| 321 | 332 | deleteButton.setAttribute('disabled', 'disabled') |
| 322 | 333 | try { |
| 323 | 334 | await post('/api/postline/queue/delete', deleteButton.dataset.queueDelete) |
| 324 | if (toast) toast({ title: 'Removed', message: 'Post removed from the queue.', tone: 'success' }) | |
| 335 | showToast({ title: 'Removed', message: 'Post removed from the queue.', tone: 'success' }) | |
| 325 | 336 | } |
| 326 | 337 | catch (error) { |
| 327 | if (toast) toast({ title: 'Delete failed', message: error instanceof Error ? error.message : String(error), tone: 'error' }) | |
| 338 | showToast({ title: 'Delete failed', message: error instanceof Error ? error.message : String(error), tone: 'error' }) | |
| 328 | 339 | } |
| 329 | 340 | await refresh() |
| 330 | 341 | } |