| @@ -41,6 +41,7 @@ |
| 41 | 41 | <span class="h-4 w-4 text-zinc-500 i-hugeicons-image-01" aria-hidden="true"></span> |
| 42 | 42 | <input class="px-3 h-9 w-full text-neutral-950 text-sm bg-white border border-zinc-200 outline-none rounded-xl focus:border-blue-500 focus:ring-2 focus:ring-blue-100" type="url" inputmode="url" placeholder="Image URL (required for Instagram)" aria-label="Image URL for Instagram" data-compose-image> |
| 43 | 43 | </label> |
| 44 | + <input class="hidden mb-2 w-full font-bold leading-snug text-[22px] text-neutral-950 placeholder:text-zinc-400 bg-transparent border-0 outline-none" type="text" maxlength="200" placeholder="Post title" aria-label="Blog post title" data-compose-title> |
| 44 | 45 | <div class="grid overflow-y-auto content-start gap-4 pr-1 max-h-72" data-composer-posts> |
| 45 | 46 | <article class="grid gap-2 pb-3 last:pb-0 border-b border-zinc-100 last:border-b-0" data-composer-post data-active="true"> |
| 46 | 47 | <header class="flex gap-3 items-center justify-between"> |
| @@ -121,6 +122,8 @@ |
| 121 | 122 | const attachClear = root.querySelector('[data-attach-clear]') |
| 122 | 123 | |
| 123 | 124 | const attachedFile = () => attachInput?.files?.[0] || undefined |
| 125 | + const composeTitle = root.querySelector('[data-compose-title]') |
| 126 | + const composedTitle = () => composeTitle?.value.trim() || undefined |
| 124 | 127 | |
| 125 | 128 | const clearAttachment = () => { |
| 126 | 129 | if (attachInput) attachInput.value = '' |
| @@ -237,6 +240,10 @@ |
| 237 | 240 | const needsImage = selected.has('instagram') && connectedProviders().some(provider => provider.provider === 'instagram') |
| 238 | 241 | composeImageRow?.classList.toggle('hidden', !needsImage) |
| 239 | 242 | composeImageRow?.classList.toggle('flex', needsImage) |
| 243 | + // Long-form title field appears whenever the blog is a target. |
| 244 | + const wantsTitle = selected.has('blog') && connectedProviders().some(provider => provider.provider === 'blog') |
| 245 | + composeTitle?.classList.toggle('hidden', !wantsTitle) |
| 246 | + composeTitle?.classList.toggle('block', wantsTitle) |
| 240 | 247 | applyMaxLength() |
| 241 | 248 | updateCount() |
| 242 | 249 | } |
| @@ -526,6 +533,8 @@ |
| 526 | 533 | const form = new FormData() |
| 527 | 534 | form.set('text', text) |
| 528 | 535 | form.set('providers', targetProviders.join(',')) |
| 536 | + const queueTitle = composedTitle() |
| 537 | + if (queueTitle) form.set('title', queueTitle) |
| 529 | 538 | if (scheduleValue) form.set('scheduled_at', toUtcTimestamp(scheduleValue)) |
| 530 | 539 | |
| 531 | 540 | buttons.forEach(button => button.setAttribute('disabled', 'disabled')) |
| @@ -537,6 +546,7 @@ |
| 537 | 546 | const textarea = activeTextarea() |
| 538 | 547 | if (textarea) textarea.value = '' |
| 539 | 548 | if (scheduleInput) scheduleInput.value = '' |
| 549 | + if (composeTitle) composeTitle.value = '' |
| 540 | 550 | scheduleInput?.classList.add('hidden') |
| 541 | 551 | renderLinkPreview(activePost, null) |
| 542 | 552 | updateCount() |
| @@ -594,7 +604,7 @@ |
| 594 | 604 | : undefined |
| 595 | 605 | const imageUrl = composeImage?.value.trim() |
| 596 | 606 | const image = imageUrl ? { url: imageUrl } : undefined |
| 597 | | - return await social.publishCrosspost({ text, providers: targetProviders, external, image, imageFile: attachedFile() }) |
| 607 | + return await social.publishCrosspost({ text, title: composedTitle(), providers: targetProviders, external, image, imageFile: attachedFile() }) |
| 598 | 608 | } |
| 599 | 609 | |
| 600 | 610 | const publish = async () => { |
| @@ -657,6 +667,7 @@ |
| 657 | 667 | const thread = targets.map(post => textareaFor(post)?.value.trim() || '') |
| 658 | 668 | collect(await social.publishCrosspost({ |
| 659 | 669 | text: thread[0], |
| 670 | + title: composedTitle(), |
| 660 | 671 | thread, |
| 661 | 672 | providers: targetProviders, |
| 662 | 673 | external, |
| @@ -684,6 +695,7 @@ |
| 684 | 695 | updateCount() |
| 685 | 696 | saveDraft() |
| 686 | 697 | clearAttachment() |
| 698 | + if (composeTitle) composeTitle.value = '' |
| 687 | 699 | window.postlineToast?.dismiss?.('publish') |
| 688 | 700 | if (failures.length && successCount === 0) { |
| 689 | 701 | setStatus(`Publish failed — ${failures.join('; ')}`, 'error', { title: 'Publish failed' }) |