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 (
+
+
+
+ );
+ } else {
+ return (
+
+
+
+ );
+ }
+ }
+
+ }
+ }
+ )
+ );
return (
-
+
{showTimestamp && (
hasTimestamp ? (