diff --git a/frontend/app/components/videos/ChatMessage.module.css b/frontend/app/components/videos/ChatMessage.module.css index 0f8c785a..44aecaf3 100644 --- a/frontend/app/components/videos/ChatMessage.module.css +++ b/frontend/app/components/videos/ChatMessage.module.css @@ -8,6 +8,9 @@ grid-template-columns: 52px minmax(0, 1fr); column-gap: 8px; align-items: baseline; + border-left: 2px solid transparent; + border-radius: 4px; + padding: 2px 4px 2px 2px; } .chatMessageNoTimestamp { display: block; @@ -43,6 +46,90 @@ line-height: 20px; font-size: 14px; } +.eventMessage { + background: color-mix(in srgb, var(--mantine-color-violet-6) 12%, transparent); + border-left-color: var(--mantine-color-violet-5); +} +.highlightedMessage { + background: color-mix(in srgb, var(--mantine-color-yellow-6) 14%, transparent); + border-left-color: var(--mantine-color-yellow-5); +} +.actionMessage .message { + font-style: italic; +} +.replyPreview { + align-items: center; + color: var(--mantine-color-dimmed); + display: flex; + font-size: 12px; + gap: 4px; + line-height: 16px; + margin-bottom: 1px; + min-width: 0; +} +.replyAuthor { + color: var(--mantine-color-gray-5); + flex: 0 1 auto; + font-size: 12px; + font-weight: 700; + line-height: 16px; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.replyBody { + color: var(--mantine-color-dimmed); + flex: 1 1 auto; + font-size: 12px; + line-height: 16px; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.eventLabel, +.bitsLabel { + align-items: center; + border-radius: 4px; + display: inline-flex; + font-size: 11px; + font-weight: 700; + gap: 2px; + line-height: 16px; + margin-right: 4px; + padding: 0 4px; + vertical-align: middle; +} +.eventLabel { + background: color-mix(in srgb, var(--mantine-color-violet-5) 20%, transparent); + color: var(--mantine-color-violet-2); +} +.bitsLabel { + background: color-mix(in srgb, var(--mantine-color-yellow-5) 22%, transparent); + color: var(--mantine-color-yellow-2); +} +.eventBody { + display: block; + margin-top: 2px; +} +.eventSystemMessage { + background: color-mix(in srgb, var(--mantine-color-violet-5) 14%, transparent); + border-radius: 4px; + color: var(--mantine-color-violet-1); + display: inline-block; + font-size: 13px; + font-weight: 700; + line-height: 18px; + margin-right: 4px; + padding: 1px 5px; +} +.eventUserMessage { + color: var(--mantine-color-text); + display: inline; + font-size: 14px; + line-height: 20px; +} .emoteImage { padding-right: 2px; vertical-align: middle; diff --git a/frontend/app/components/videos/ChatMessage.tsx b/frontend/app/components/videos/ChatMessage.tsx index b5902105..b8f73925 100644 --- a/frontend/app/components/videos/ChatMessage.tsx +++ b/frontend/app/components/videos/ChatMessage.tsx @@ -1,9 +1,10 @@ /* eslint-disable @next/next/no-img-element */ -import { Comment, GanymedeFormattedBadge, GanymedeFormattedMessageFragment, GanymedeFormattedMessageType } from "@/app/hooks/useChat"; +import { Comment, GanymedeChatMessageKind, GanymedeFormattedBadge, GanymedeFormattedMessageFragment, GanymedeFormattedMessageType } from "@/app/hooks/useChat"; import { durationToTime } from "@/app/util/util"; import classes from "./ChatMessage.module.css" import { Text, Tooltip } from "@mantine/core" import { useTranslations } from "next-intl"; +import { IconBolt, IconMessageCircle, IconStar } from "@tabler/icons-react"; interface Params { comment: Comment; @@ -19,9 +20,70 @@ const ChatMessage = ({ comment, showTimestamp, timestampSeconds, onTimestampClic const timestampActionLabel = hasTimestamp ? t("chatJumpToTimestamp", { timestamp: timestampLabel }) : ""; + const messageKind = comment.ganymede_chat_message_kind ?? GanymedeChatMessageKind.Normal; + const isEvent = messageKind === GanymedeChatMessageKind.UserNotice; + const isHighlighted = messageKind === GanymedeChatMessageKind.Highlighted; + const isAction = messageKind === GanymedeChatMessageKind.Action; + const bitsSpent = comment.message.bits_spent ?? 0; + const eventSystemMessage = isEvent ? comment.message.user_notice_params?.system_msg?.trim() : ""; + const eventUserMessageFromParams = isEvent ? comment.message.user_notice_params?.params?.["user-message"]?.trim() : ""; + const eventUserMessageFromBody = isEvent && eventSystemMessage && comment.message.body.startsWith(eventSystemMessage) + ? comment.message.body.slice(eventSystemMessage.length).trim() + : ""; + const eventUserMessage = eventUserMessageFromParams || eventUserMessageFromBody; + const rowClassName = [ + classes.chatMessage, + !showTimestamp ? classes.chatMessageNoTimestamp : "", + isEvent ? classes.eventMessage : "", + isHighlighted ? classes.highlightedMessage : "", + isAction ? classes.actionMessage : "", + ].filter(Boolean).join(" "); + + const renderFormattedMessage = () => ( + comment.ganymede_formatted_message && comment.ganymede_formatted_message.map( + (fragment: GanymedeFormattedMessageFragment, index: number) => { + switch (fragment.type) { + case GanymedeFormattedMessageType.Text: + return ( + + {fragment.text} + + ) + case GanymedeFormattedMessageType.Emote: { + const emoteName = fragment.emote?.name || fragment.text; + // some emotes include a height, use the provided height or hardcode a standard height if not included + if ((fragment.emote?.height ?? 0) !== 0 && (fragment.emote?.width ?? 0) !== 0) { + return ( + + {emoteName} + + ); + } else { + return ( + + {emoteName} + + ); + } + } + + } + } + ) + ); return ( -
+
{showTimestamp && ( hasTimestamp ? (