@ttt-productions/chat-react 0.12.7 → 0.12.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"MessageItemDefault.js","sourceRoot":"","sources":["../../src/ui/MessageItemDefault.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport type { ChatMessageV1, ChatAttachment, ModerationHandlers } from \"@ttt-productions/chat-core\";\nimport { MessageText } from \"../mentions/MessageText.js\";\nimport { cn } from \"@ttt-productions/ui-core\";\nimport { Button } from \"@ttt-productions/ui-core/react\";\nimport { MediaViewer } from \"@ttt-productions/media-viewer/react\";\nimport { FileText, Loader2, AlertTriangle } from \"lucide-react\";\nimport { MessageActions } from \"./menus.js\";\nimport { useResolvedSenderName } from \"../context/ChatNameResolverContext.js\";\nimport { useChatAttachmentUrlResolver } from \"../context/ChatAttachmentUrlContext.js\";\nimport { useChatAttachmentMediaComponent } from \"../context/ChatAttachmentMediaContext.js\";\n\n// ============================================\n// Attachment rendering\n// ============================================\n\nfunction getAttachmentLabel(type: ChatAttachment[\"type\"]): string {\n switch (type) {\n case \"image\": return \"Image attachment\";\n case \"video\": return \"Video attachment\";\n case \"audio\": return \"Audio attachment\";\n default: return \"File attachment\";\n }\n}\n\nfunction AttachmentView({ att }: { att: ChatAttachment }) {\n // Display URL is built at render time by the app-injected resolver from\n // att.mediaAssetId — attachments never store URLs (protected-gateway model).\n const resolveAttachmentUrl = useChatAttachmentUrlResolver();\n // App-injected media component (optional) — lets chat attachments ride the\n // app's ONE display path (recovery adapter, diagnostics, telemetry). Falls\n // back to the package's own MediaViewer when no provider is present.\n const Media = useChatAttachmentMediaComponent() ?? MediaViewer;\n // In-flight placeholder — bytes uploaded, processing/moderation pending. No\n // `mediaAssetId` yet. Rendered only to the sender (other participants can't\n // read a pending doc — Firestore rules scope it until `status === 'ready'`).\n if (att.status === \"pending\") {\n return (\n <div className=\"chat-attachment-pending\">\n <Loader2 className=\"h-4 w-4 shrink-0 animate-spin\" />\n <span className=\"truncate\">{getAttachmentLabel(att.type)}</span>\n <span className=\"chat-attachment-status-label\">Sending…</span>\n </div>\n );\n }\n // Processing/moderation rejected the attachment — sender-only.\n if (att.status === \"failed\") {\n return (\n <div className=\"chat-attachment-rejected\">\n <AlertTriangle className=\"h-4 w-4 shrink-0\" />\n <span className=\"truncate\">{getAttachmentLabel(att.type)}</span>\n <span className=\"chat-attachment-status-label\">\n {att.failureReason ?? \"Couldn't send this attachment\"}\n </span>\n </div>\n );\n }\n // ready (or legacy/absent status) — resolve the display URL from the asset ref.\n const url = resolveAttachmentUrl(att);\n if (!url) return null;\n if (att.type === \"image\") {\n return <Media type=\"image\" url={url} alt=\"Image attachment\" className=\"chat-attachment-media\" />;\n }\n if (att.type === \"video\") {\n return <Media type=\"video\" url={url} controls className=\"chat-attachment-media\" />;\n }\n if (att.type === \"audio\") {\n return <Media type=\"audio\" url={url} controls className=\"chat-attachment-media\" />;\n }\n // text/markdown — download link\n return (\n <a\n href={url}\n download={att.name}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"chat-attachment-text-link\"\n >\n <FileText className=\"h-4 w-4 shrink-0\" />\n <span className=\"truncate\">Download attachment</span>\n </a>\n );\n}\n\n// ============================================\n// Reply-to quote\n// ============================================\n\nfunction ReplyQuote({ replyTo }: { replyTo: NonNullable<ChatMessageV1[\"replyTo\"]> }) {\n const replyName = useResolvedSenderName(replyTo.senderId);\n return (\n <div className=\"chat-reply-quote\">\n <span className=\"chat-reply-quote-sender\">{replyName}</span>\n <span className=\"chat-reply-quote-preview\"><MessageText text={replyTo.messagePreview} /></span>\n </div>\n );\n}\n\n// ============================================\n// System message\n// ============================================\n\nfunction SystemMessage({ m }: { m: ChatMessageV1 }) {\n return (\n <div className=\"chat-system-message\">\n {m.text && <span>{m.text}</span>}\n </div>\n );\n}\n\n// ============================================\n// Default message item\n// ============================================\n\nexport type MessageItemDefaultProps = {\n m: ChatMessageV1;\n currentUserId: string;\n isAdmin: boolean;\n handlers?: ModerationHandlers;\n\n // Grouping\n isContinuation?: boolean;\n\n // Sender interaction\n onSenderClick?: (senderId: string, displayName: string) => void;\n\n /** Retry a failed realtime send by its original clientMessageId (wired by\n * ChatShell on the realtime transport; absent on firestore). Enables the\n * retry affordance on a `meta.sendFailed` bubble. */\n onRetrySend?: (clientMessageId: string) => void;\n\n};\n\nexport function MessageItemDefault(props: MessageItemDefaultProps) {\n const { m, currentUserId, isAdmin, handlers, isContinuation, onSenderClick, onRetrySend } = props;\n const senderName = useResolvedSenderName(m.senderId);\n\n // System messages render differently\n if (m.isSystemMessage) {\n return <SystemMessage m={m} />;\n }\n\n const mine = m.senderId === currentUserId;\n\n // Realtime optimistic-send lifecycle (meta is stamped by the channel client):\n // an un-acked echo renders subtly pending; one past the retry/age cap renders a\n // visibly failed bubble with a retry affordance (same clientMessageId — the DO\n // dedups, so a retry can never double-send).\n const sendFailed = m.meta?.sendFailed === true;\n const sendPending = m.meta?.optimistic === true && !sendFailed;\n const clientMessageId =\n typeof m.meta?.clientMessageId === \"string\" ? m.meta.clientMessageId : null;\n\n return (\n <div\n className={cn(\n \"flex flex-col w-fit max-w-[85%]\",\n mine ? \"ml-auto items-end\" : \"mr-auto items-start\",\n isContinuation ? \"chat-continuation-gap\" : \"chat-group-gap\"\n )}\n >\n <div\n className={cn(\n \"chat-bubble\",\n mine ? \"chat-bubble--mine\" : \"chat-bubble--theirs\",\n sendPending && \"chat-bubble--pending\",\n sendFailed && \"chat-bubble--failed\"\n )}\n >\n {!isContinuation && (\n <div className=\"flex items-center gap-2 text-xs opacity-80 mb-1\">\n <span\n className={cn(\"font-medium\", onSenderClick && \"cursor-pointer hover:underline\")}\n onClick={onSenderClick ? () => onSenderClick(m.senderId, senderName) : undefined}\n role={onSenderClick ? \"button\" : undefined}\n tabIndex={onSenderClick ? 0 : undefined}\n onKeyDown={onSenderClick ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") onSenderClick(m.senderId, senderName);\n } : undefined}\n >\n {senderName}\n </span>\n <span>·</span>\n <span>{new Date(m.createdAt).toLocaleTimeString([], { hour: \"numeric\", minute: \"2-digit\" })}</span>\n </div>\n )}\n\n {m.replyTo && <ReplyQuote replyTo={m.replyTo} />}\n\n {m.text && (\n <p className=\"text-sm whitespace-pre-wrap\">\n <MessageText text={m.text} />\n </p>\n )}\n\n {m.attachment && (\n <div className=\"mt-2\">\n <AttachmentView att={m.attachment} />\n </div>\n )}\n </div>\n\n {/* Send-state status rows render regardless of grouping — a pending/failed\n send must never be indistinguishable from a delivered one. */}\n {sendPending && (\n <div className=\"chat-send-status mt-0.5\" role=\"status\" aria-live=\"polite\">\n <Loader2 className=\"h-3 w-3 shrink-0 animate-spin\" />\n <span>Sending…</span>\n </div>\n )}\n {sendFailed && (\n <div className=\"chat-send-status chat-send-status--failed mt-0.5\" role=\"alert\">\n <AlertTriangle className=\"h-3 w-3 shrink-0\" />\n <span>Couldn&apos;t send</span>\n {onRetrySend && clientMessageId && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-5 px-1.5 text-[0.6875rem]\"\n onClick={() => onRetrySend(clientMessageId)}\n >\n Retry\n </Button>\n )}\n </div>\n )}\n\n {!isContinuation && (\n <div className=\"mt-1\">\n <MessageActions messageId={m.messageId} isAdmin={isAdmin} handlers={handlers} />\n </div>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"file":"MessageItemDefault.js","sourceRoot":"","sources":["../../src/ui/MessageItemDefault.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\nimport type { ChatMessageV1, ChatAttachment, ModerationHandlers } from \"@ttt-productions/chat-core\";\nimport { MessageText } from \"../mentions/MessageText.js\";\nimport { cn } from \"@ttt-productions/ui-core\";\nimport { Button } from \"@ttt-productions/ui-core/react\";\nimport { MediaViewer } from \"@ttt-productions/media-viewer/react\";\nimport { FileText, Loader2, AlertTriangle } from \"lucide-react\";\nimport { MessageActions } from \"./menus.js\";\nimport { useResolvedSenderName } from \"../context/ChatNameResolverContext.js\";\nimport { useChatAttachmentUrlResolver } from \"../context/ChatAttachmentUrlContext.js\";\nimport { useChatAttachmentMediaComponent } from \"../context/ChatAttachmentMediaContext.js\";\n\n// ============================================\n// Attachment rendering\n// ============================================\n\nfunction getAttachmentLabel(type: ChatAttachment[\"type\"]): string {\n switch (type) {\n case \"image\": return \"Image attachment\";\n case \"video\": return \"Video attachment\";\n case \"audio\": return \"Audio attachment\";\n default: return \"File attachment\";\n }\n}\n\nfunction AttachmentView({ att }: { att: ChatAttachment }) {\n // Display URL is built at render time by the app-injected resolver from\n // att.mediaAssetId — attachments never store URLs (protected-gateway model).\n const resolveAttachmentUrl = useChatAttachmentUrlResolver();\n // App-injected media component (optional) — lets chat attachments ride the\n // app's ONE display path (recovery adapter, diagnostics, telemetry). Falls\n // back to the package's own MediaViewer when no provider is present.\n const Media = useChatAttachmentMediaComponent() ?? MediaViewer;\n // In-flight placeholder — bytes uploaded, processing/moderation pending. No\n // `mediaAssetId` yet. Rendered only to the sender (other participants can't\n // read a pending doc — Firestore rules scope it until `status === 'ready'`).\n if (att.status === \"pending\") {\n return (\n <div className=\"chat-attachment-pending\">\n <Loader2 className=\"h-4 w-4 shrink-0 animate-spin\" />\n <span className=\"truncate\">{getAttachmentLabel(att.type)}</span>\n <span className=\"chat-attachment-status-label\">Sending…</span>\n </div>\n );\n }\n // Processing/moderation rejected the attachment — sender-only.\n if (att.status === \"failed\") {\n return (\n <div className=\"chat-attachment-rejected\">\n <AlertTriangle className=\"h-4 w-4 shrink-0\" />\n <span className=\"truncate\">{getAttachmentLabel(att.type)}</span>\n <span className=\"chat-attachment-status-label\">\n {att.failureReason ?? \"Couldn't send this attachment\"}\n </span>\n </div>\n );\n }\n // ready (or legacy/absent status) — resolve the display URL from the asset ref.\n const url = resolveAttachmentUrl(att);\n if (!url) {\n // Terminal-ready row, but the authorized URL is still SETTLING (the grant/URL\n // resolver has nothing yet). Returning null left only the surrounding message\n // metadata → an observed empty bubble; a broken/grant-less <img> would flash a\n // 403. Render a neutral loading placeholder in the existing loading visual\n // language (spinner + kind-appropriate generic label + \"Loading…\", never\n // \"Sending…\", NO filename) and do NOT mount the media/download renderer until a\n // non-empty authorized URL exists. Sender-only pending/processing/failed states\n // above are unchanged — this begins only after a terminal-ready row is received.\n return (\n <div className=\"chat-attachment-pending\">\n <Loader2 className=\"h-4 w-4 shrink-0 animate-spin\" />\n <span className=\"truncate\">{getAttachmentLabel(att.type)}</span>\n <span className=\"chat-attachment-status-label\">Loading…</span>\n </div>\n );\n }\n if (att.type === \"image\") {\n return <Media type=\"image\" url={url} alt=\"Image attachment\" className=\"chat-attachment-media\" />;\n }\n if (att.type === \"video\") {\n return <Media type=\"video\" url={url} controls className=\"chat-attachment-media\" />;\n }\n if (att.type === \"audio\") {\n return <Media type=\"audio\" url={url} controls className=\"chat-attachment-media\" />;\n }\n // text/markdown — download link\n return (\n <a\n href={url}\n download={att.name}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"chat-attachment-text-link\"\n >\n <FileText className=\"h-4 w-4 shrink-0\" />\n <span className=\"truncate\">Download attachment</span>\n </a>\n );\n}\n\n// ============================================\n// Reply-to quote\n// ============================================\n\nfunction ReplyQuote({ replyTo }: { replyTo: NonNullable<ChatMessageV1[\"replyTo\"]> }) {\n const replyName = useResolvedSenderName(replyTo.senderId);\n return (\n <div className=\"chat-reply-quote\">\n <span className=\"chat-reply-quote-sender\">{replyName}</span>\n <span className=\"chat-reply-quote-preview\"><MessageText text={replyTo.messagePreview} /></span>\n </div>\n );\n}\n\n// ============================================\n// System message\n// ============================================\n\nfunction SystemMessage({ m }: { m: ChatMessageV1 }) {\n return (\n <div className=\"chat-system-message\">\n {m.text && <span>{m.text}</span>}\n </div>\n );\n}\n\n// ============================================\n// Default message item\n// ============================================\n\nexport type MessageItemDefaultProps = {\n m: ChatMessageV1;\n currentUserId: string;\n isAdmin: boolean;\n handlers?: ModerationHandlers;\n\n // Grouping\n isContinuation?: boolean;\n\n // Sender interaction\n onSenderClick?: (senderId: string, displayName: string) => void;\n\n /** Retry a failed realtime send by its original clientMessageId (wired by\n * ChatShell on the realtime transport; absent on firestore). Enables the\n * retry affordance on a `meta.sendFailed` bubble. */\n onRetrySend?: (clientMessageId: string) => void;\n\n};\n\nexport function MessageItemDefault(props: MessageItemDefaultProps) {\n const { m, currentUserId, isAdmin, handlers, isContinuation, onSenderClick, onRetrySend } = props;\n const senderName = useResolvedSenderName(m.senderId);\n\n // System messages render differently\n if (m.isSystemMessage) {\n return <SystemMessage m={m} />;\n }\n\n const mine = m.senderId === currentUserId;\n\n // Realtime optimistic-send lifecycle (meta is stamped by the channel client):\n // an un-acked echo renders subtly pending; one past the retry/age cap renders a\n // visibly failed bubble with a retry affordance (same clientMessageId — the DO\n // dedups, so a retry can never double-send).\n const sendFailed = m.meta?.sendFailed === true;\n const sendPending = m.meta?.optimistic === true && !sendFailed;\n const clientMessageId =\n typeof m.meta?.clientMessageId === \"string\" ? m.meta.clientMessageId : null;\n\n return (\n <div\n className={cn(\n \"flex flex-col w-fit max-w-[85%]\",\n mine ? \"ml-auto items-end\" : \"mr-auto items-start\",\n isContinuation ? \"chat-continuation-gap\" : \"chat-group-gap\"\n )}\n >\n <div\n className={cn(\n \"chat-bubble\",\n mine ? \"chat-bubble--mine\" : \"chat-bubble--theirs\",\n sendPending && \"chat-bubble--pending\",\n sendFailed && \"chat-bubble--failed\"\n )}\n >\n {!isContinuation && (\n <div className=\"flex items-center gap-2 text-xs opacity-80 mb-1\">\n <span\n className={cn(\"font-medium\", onSenderClick && \"cursor-pointer hover:underline\")}\n onClick={onSenderClick ? () => onSenderClick(m.senderId, senderName) : undefined}\n role={onSenderClick ? \"button\" : undefined}\n tabIndex={onSenderClick ? 0 : undefined}\n onKeyDown={onSenderClick ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") onSenderClick(m.senderId, senderName);\n } : undefined}\n >\n {senderName}\n </span>\n <span>·</span>\n <span>{new Date(m.createdAt).toLocaleTimeString([], { hour: \"numeric\", minute: \"2-digit\" })}</span>\n </div>\n )}\n\n {m.replyTo && <ReplyQuote replyTo={m.replyTo} />}\n\n {m.text && (\n <p className=\"text-sm whitespace-pre-wrap\">\n <MessageText text={m.text} />\n </p>\n )}\n\n {m.attachment && (\n <div className=\"mt-2\">\n <AttachmentView att={m.attachment} />\n </div>\n )}\n </div>\n\n {/* Send-state status rows render regardless of grouping — a pending/failed\n send must never be indistinguishable from a delivered one. */}\n {sendPending && (\n <div className=\"chat-send-status mt-0.5\" role=\"status\" aria-live=\"polite\">\n <Loader2 className=\"h-3 w-3 shrink-0 animate-spin\" />\n <span>Sending…</span>\n </div>\n )}\n {sendFailed && (\n <div className=\"chat-send-status chat-send-status--failed mt-0.5\" role=\"alert\">\n <AlertTriangle className=\"h-3 w-3 shrink-0\" />\n <span>Couldn&apos;t send</span>\n {onRetrySend && clientMessageId && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-5 px-1.5 text-[0.6875rem]\"\n onClick={() => onRetrySend(clientMessageId)}\n >\n Retry\n </Button>\n )}\n </div>\n )}\n\n {!isContinuation && (\n <div className=\"mt-1\">\n <MessageActions messageId={m.messageId} isAdmin={isAdmin} handlers={handlers} />\n </div>\n )}\n </div>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttt-productions/chat-react",
3
- "version": "0.12.7",
3
+ "version": "0.12.9",
4
4
  "description": "Chat React UI, hooks, and Firebase-client adapter for TTT Productions apps (realtime newest window + infinite older pagination)",
5
5
  "repository": {
6
6
  "type": "git",