forum: image attachments on threads + replies, AVIF picture render
New ImageAttachButton drops onto any composer textarea: file picker upload → /api/upload/image (the universal pipeline) → markdown  appended to the body. New thread + reply composers both gain the button with a 'Auto-optimized, never > 20 MB' caption. RichBody is the read-path companion: parses body markdown for image syntax and renders each as a <picture> element with the AVIF source first (derived from the .webp sibling on our bb-media bucket), then the WebP, then the original-format fallback img. Prose around images still runs through LinkifyPlainText so company auto-links still work. Forum users now have a way to include screenshots, gear photos, NAB booth shots, etc. in their posts — every image flows through the same optimization that gives <10% of original byte weight at visually identical quality. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import Link from 'next/link';
|
||||
import { useParams } from 'next/navigation';
|
||||
import Header from '@/components/Header';
|
||||
import Footer from '@/components/Footer';
|
||||
import ImageAttachButton from '@/components/forum/ImageAttachButton';
|
||||
import { createClient } from '@/lib/supabase/client';
|
||||
|
||||
interface ForumCategory {
|
||||
@@ -336,6 +337,10 @@ export default function ForumCategoryPage() {
|
||||
rows={5}
|
||||
className="w-full bg-[#111] border border-[#333] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#555] focus:outline-none focus:border-[#3b82f6] resize-none"
|
||||
/>
|
||||
<div className="flex items-center gap-3">
|
||||
<ImageAttachButton onInsert={(md) => setNewBody((b) => (b || "") + md)} />
|
||||
<span className="text-[10px] text-[#666]">Auto-optimized, never > 20 MB</span>
|
||||
</div>
|
||||
{newError && (
|
||||
<div className="bg-[#2a0a0a] border border-[#cc0000] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
|
||||
{newError}
|
||||
|
||||
@@ -9,6 +9,8 @@ import { pickAds, rotateAll } from '@/lib/ads';
|
||||
import { createClient } from '@/lib/supabase/client';
|
||||
import SidebarAdStack from "@/components/SidebarAdStack";
|
||||
import LinkifyPlainText from "@/components/LinkifyPlainText";
|
||||
import RichBody from "@/components/forum/RichBody";
|
||||
import ImageAttachButton from "@/components/forum/ImageAttachButton";
|
||||
import CompanyMentionsHover from "@/components/CompanyMentionsHover";
|
||||
|
||||
// Inline ad slot inserted between forum replies every N posts.
|
||||
@@ -371,7 +373,7 @@ export default function ForumThreadPage() {
|
||||
<span className="text-[#555] font-body text-xs">{thread.view_count} views</span>
|
||||
</div>
|
||||
<p className="text-[#d0d0d0] font-body text-sm leading-relaxed">
|
||||
<LinkifyPlainText body={thread.body} />
|
||||
<RichBody body={thread.body} />
|
||||
</p>
|
||||
<VoteButtons
|
||||
targetType="thread"
|
||||
@@ -413,7 +415,7 @@ export default function ForumThreadPage() {
|
||||
<span className="text-[#555] font-body text-xs">{timeAgo(reply.created_at)}</span>
|
||||
</div>
|
||||
<p className="text-[#d0d0d0] font-body text-sm leading-relaxed">
|
||||
<LinkifyPlainText body={reply.body} />
|
||||
<RichBody body={reply.body} />
|
||||
</p>
|
||||
<VoteButtons
|
||||
targetType="reply"
|
||||
@@ -495,6 +497,10 @@ export default function ForumThreadPage() {
|
||||
rows={5}
|
||||
className="w-full bg-[#111] border border-[#333] rounded-lg px-3 py-2 text-white font-body text-sm placeholder-[#555] focus:outline-none focus:border-[#3b82f6] resize-none"
|
||||
/>
|
||||
<div className="flex items-center gap-3">
|
||||
<ImageAttachButton onInsert={(md) => setReplyBody((b) => (b || "") + md)} />
|
||||
<span className="text-[10px] text-[#666]">Auto-optimized, never > 20 MB</span>
|
||||
</div>
|
||||
{replyError && (
|
||||
<div className="bg-[#2a0a0a] border border-[#cc0000] text-[#ff8a8a] font-body text-sm px-3 py-2 rounded">
|
||||
{replyError}
|
||||
|
||||
Reference in New Issue
Block a user