transcriptify 1.0.0

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.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -0
  3. package/dist/src/generateHtml.d.ts +3 -0
  4. package/dist/src/generateHtml.js +303 -0
  5. package/dist/src/index.d.ts +5 -0
  6. package/dist/src/index.js +252 -0
  7. package/dist/src/transcript.d.ts +12 -0
  8. package/dist/src/transcript.js +179 -0
  9. package/dist/src/types/entities.d.ts +30 -0
  10. package/dist/src/types/entities.js +2 -0
  11. package/dist/src/utils/assetManager.d.ts +12 -0
  12. package/dist/src/utils/assetManager.js +295 -0
  13. package/dist/src/utils/authors.d.ts +4 -0
  14. package/dist/src/utils/authors.js +115 -0
  15. package/dist/src/utils/cache.d.ts +16 -0
  16. package/dist/src/utils/cache.js +29 -0
  17. package/dist/src/utils/extractors.d.ts +112 -0
  18. package/dist/src/utils/extractors.js +223 -0
  19. package/dist/src/utils/polls.d.ts +2 -0
  20. package/dist/src/utils/polls.js +91 -0
  21. package/dist/src/utils/transformer.d.ts +6 -0
  22. package/dist/src/utils/transformer.js +78 -0
  23. package/dist/src/utils/user.d.ts +4 -0
  24. package/dist/src/utils/user.js +56 -0
  25. package/dist/src/web/client.d.ts +20 -0
  26. package/dist/src/web/client.js +21 -0
  27. package/dist/src/web/discord-components/AudioPlayer.d.ts +5 -0
  28. package/dist/src/web/discord-components/AudioPlayer.js +231 -0
  29. package/dist/src/web/discord-components/Button.d.ts +3 -0
  30. package/dist/src/web/discord-components/Button.js +27 -0
  31. package/dist/src/web/discord-components/ChannelPinnedMessage.d.ts +7 -0
  32. package/dist/src/web/discord-components/ChannelPinnedMessage.js +11 -0
  33. package/dist/src/web/discord-components/DateSeperator.d.ts +3 -0
  34. package/dist/src/web/discord-components/DateSeperator.js +19 -0
  35. package/dist/src/web/discord-components/Embed.d.ts +2 -0
  36. package/dist/src/web/discord-components/Embed.js +78 -0
  37. package/dist/src/web/discord-components/ForwardedMessage.d.ts +2 -0
  38. package/dist/src/web/discord-components/ForwardedMessage.js +44 -0
  39. package/dist/src/web/discord-components/Message.d.ts +2 -0
  40. package/dist/src/web/discord-components/Message.js +543 -0
  41. package/dist/src/web/discord-components/PinnedMessagesModal.d.ts +6 -0
  42. package/dist/src/web/discord-components/PinnedMessagesModal.js +119 -0
  43. package/dist/src/web/discord-components/PinnedMessagesOverview.d.ts +5 -0
  44. package/dist/src/web/discord-components/PinnedMessagesOverview.js +22 -0
  45. package/dist/src/web/discord-components/Reply.d.ts +2 -0
  46. package/dist/src/web/discord-components/Reply.js +42 -0
  47. package/dist/src/web/discord-components/StickerPreview.d.ts +6 -0
  48. package/dist/src/web/discord-components/StickerPreview.js +40 -0
  49. package/dist/src/web/discord-components/ThemeSwitcher.d.ts +2 -0
  50. package/dist/src/web/discord-components/ThemeSwitcher.js +54 -0
  51. package/dist/src/web/discord-components/Transcript.d.ts +2 -0
  52. package/dist/src/web/discord-components/Transcript.js +174 -0
  53. package/dist/src/web/discord-components/UserJoinMessage.d.ts +3 -0
  54. package/dist/src/web/discord-components/UserJoinMessage.js +33 -0
  55. package/dist/src/web/discord-components/VideoPlayer.d.ts +6 -0
  56. package/dist/src/web/discord-components/VideoPlayer.js +222 -0
  57. package/dist/src/web/discord-components/icons/ChevronDownIcon.d.ts +1 -0
  58. package/dist/src/web/discord-components/icons/ChevronDownIcon.js +7 -0
  59. package/dist/src/web/discord-components/icons/CloseIcon.d.ts +1 -0
  60. package/dist/src/web/discord-components/icons/CloseIcon.js +7 -0
  61. package/dist/src/web/discord-components/icons/ExternalLinkIcon.d.ts +1 -0
  62. package/dist/src/web/discord-components/icons/ExternalLinkIcon.js +7 -0
  63. package/dist/src/web/discord-components/icons/FileAudioIcon.d.ts +1 -0
  64. package/dist/src/web/discord-components/icons/FileAudioIcon.js +7 -0
  65. package/dist/src/web/discord-components/icons/FileCodeIcon.d.ts +1 -0
  66. package/dist/src/web/discord-components/icons/FileCodeIcon.js +7 -0
  67. package/dist/src/web/discord-components/icons/FileDocumentIcon.d.ts +1 -0
  68. package/dist/src/web/discord-components/icons/FileDocumentIcon.js +7 -0
  69. package/dist/src/web/discord-components/icons/PinIcon.d.ts +1 -0
  70. package/dist/src/web/discord-components/icons/PinIcon.js +7 -0
  71. package/dist/src/web/discord-components/icons/VerifiedIcon.d.ts +1 -0
  72. package/dist/src/web/discord-components/icons/VerifiedIcon.js +7 -0
  73. package/dist/src/web/discord-components/index.d.ts +11 -0
  74. package/dist/src/web/discord-components/index.js +24 -0
  75. package/dist/src/web/discord-components/messageHelpers.d.ts +8 -0
  76. package/dist/src/web/discord-components/messageHelpers.js +72 -0
  77. package/dist/src/web/discord-components/themeColors.d.ts +9 -0
  78. package/dist/src/web/discord-components/themeColors.js +320 -0
  79. package/dist/src/web/discord-components/transcriptHelpers.d.ts +19 -0
  80. package/dist/src/web/discord-components/transcriptHelpers.js +120 -0
  81. package/dist/src/web/discord-components/types.d.ts +1 -0
  82. package/dist/src/web/discord-components/types.js +2 -0
  83. package/dist/src/web/discord-components/utils/date.d.ts +3 -0
  84. package/dist/src/web/discord-components/utils/date.js +50 -0
  85. package/dist/src/web/discord-components/utils/markdown.d.ts +11 -0
  86. package/dist/src/web/discord-components/utils/markdown.js +538 -0
  87. package/dist/src/web/discord-components/utils/markdownUtils.d.ts +12 -0
  88. package/dist/src/web/discord-components/utils/markdownUtils.js +140 -0
  89. package/dist/src/web/helpers/avatarHelpers.d.ts +2 -0
  90. package/dist/src/web/helpers/avatarHelpers.js +15 -0
  91. package/dist/src/web/helpers/cdnHelpers.d.ts +5 -0
  92. package/dist/src/web/helpers/cdnHelpers.js +48 -0
  93. package/dist/src/web/helpers/contentHelpers.d.ts +9 -0
  94. package/dist/src/web/helpers/contentHelpers.js +41 -0
  95. package/dist/src/web/helpers/renderContent.d.ts +2 -0
  96. package/dist/src/web/helpers/renderContent.js +15 -0
  97. package/dist/src/web/helpers/scrollHelpers.d.ts +2 -0
  98. package/dist/src/web/helpers/scrollHelpers.js +31 -0
  99. package/dist/src/web/helpers/timestampHelpers.d.ts +6 -0
  100. package/dist/src/web/helpers/timestampHelpers.js +66 -0
  101. package/dist/src/web/hooks/useMessageContent.d.ts +5 -0
  102. package/dist/src/web/hooks/useMessageContent.js +37 -0
  103. package/dist/src/web/index.d.ts +1 -0
  104. package/dist/src/web/index.js +17 -0
  105. package/dist/src/web/types/attachment.d.ts +6 -0
  106. package/dist/src/web/types/attachment.js +2 -0
  107. package/dist/src/web/types/author.d.ts +14 -0
  108. package/dist/src/web/types/author.js +2 -0
  109. package/dist/src/web/types/channel.d.ts +8 -0
  110. package/dist/src/web/types/channel.js +2 -0
  111. package/dist/src/web/types/embed.d.ts +52 -0
  112. package/dist/src/web/types/embed.js +2 -0
  113. package/dist/src/web/types/interaction.d.ts +8 -0
  114. package/dist/src/web/types/interaction.js +2 -0
  115. package/dist/src/web/types/markdown.d.ts +5 -0
  116. package/dist/src/web/types/markdown.js +2 -0
  117. package/dist/src/web/types/message.d.ts +73 -0
  118. package/dist/src/web/types/message.js +2 -0
  119. package/dist/src/web/types/poll.d.ts +11 -0
  120. package/dist/src/web/types/poll.js +2 -0
  121. package/dist/src/web/types/props.d.ts +155 -0
  122. package/dist/src/web/types/props.js +2 -0
  123. package/dist/src/web/types/reaction.d.ts +6 -0
  124. package/dist/src/web/types/reaction.js +2 -0
  125. package/dist/src/web/types/theme.d.ts +14 -0
  126. package/dist/src/web/types/theme.js +2 -0
  127. package/dist/src/web/types/ui.d.ts +10 -0
  128. package/dist/src/web/types/ui.js +2 -0
  129. package/dist/types/download.d.ts +12 -0
  130. package/dist/types/download.js +2 -0
  131. package/dist/types/exportableTranscript.d.ts +169 -0
  132. package/dist/types/exportableTranscript.js +2 -0
  133. package/dist/types/general.d.ts +90 -0
  134. package/dist/types/general.js +2 -0
  135. package/package.json +46 -0
@@ -0,0 +1,543 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.default = Message;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const react_1 = __importDefault(require("react"));
9
+ const AudioPlayer_1 = __importDefault(require("./AudioPlayer"));
10
+ const VideoPlayer_1 = __importDefault(require("./VideoPlayer"));
11
+ const ChannelPinnedMessage_1 = __importDefault(require("./ChannelPinnedMessage"));
12
+ const Embed_1 = __importDefault(require("./Embed"));
13
+ const ForwardedMessage_1 = __importDefault(require("./ForwardedMessage"));
14
+ const ChevronDownIcon_1 = __importDefault(require("./icons/ChevronDownIcon"));
15
+ const ExternalLinkIcon_1 = __importDefault(require("./icons/ExternalLinkIcon"));
16
+ const VerifiedIcon_1 = __importDefault(require("./icons/VerifiedIcon"));
17
+ const FileAudioIcon_1 = __importDefault(require("./icons/FileAudioIcon"));
18
+ const FileCodeIcon_1 = __importDefault(require("./icons/FileCodeIcon"));
19
+ const FileDocumentIcon_1 = __importDefault(require("./icons/FileDocumentIcon"));
20
+ const Reply_1 = __importDefault(require("./Reply"));
21
+ const StickerPreview_1 = __importDefault(require("./StickerPreview"));
22
+ const UserJoinMessage_1 = __importDefault(require("./UserJoinMessage"));
23
+ const date_1 = require("./utils/date");
24
+ const markdown_1 = require("./utils/markdown");
25
+ const avatarHelpers_1 = require("../helpers/avatarHelpers");
26
+ const cdnHelpers_1 = require("../helpers/cdnHelpers");
27
+ const messageHelpers_1 = require("./messageHelpers");
28
+ function renderCommandLine(interaction, author, resolvedUsers, defaultAvatar) {
29
+ if (!interaction?.name || !interaction?.user?.username)
30
+ return null;
31
+ const username = interaction.user.username;
32
+ const userId = interaction.user.id;
33
+ const displayName = userId && resolvedUsers?.[userId]?.displayName;
34
+ const commandName = interaction.name;
35
+ let userAvatar = defaultAvatar;
36
+ if (userId && resolvedUsers?.[userId]?.avatar) {
37
+ const avatarHash = resolvedUsers[userId].avatar;
38
+ userAvatar = /^https?:\/\//i.test(avatarHash) ? avatarHash : (0, avatarHelpers_1.buildAvatarUrl)(String(userId), avatarHash);
39
+ }
40
+ return ((0, jsx_runtime_1.jsx)("div", { className: "flex items-center text-sm mb-1 relative", style: { marginLeft: "-56px", paddingLeft: "56px" }, children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5", children: [userAvatar && (0, jsx_runtime_1.jsx)("img", { src: userAvatar, alt: "", className: "w-4 h-4 rounded-full" }), (0, jsx_runtime_1.jsx)("span", { className: "text-[#b5bac1] font-medium", children: username }), displayName && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 24 24", className: "text-[#b5bac1]", children: (0, jsx_runtime_1.jsx)("path", { fill: "currentColor", d: "M2.06 7.61c-.25.95.31 1.92 1.26 2.18l4.3 1.15c.94.25 1.91-.31 2.17-1.26l1.15-4.3c.25-.94-.31-1.91-1.26-2.17l-4.3-1.15c-.94-.25-1.91.31-2.17 1.26l-1.15 4.3ZM12.98 7.87a2 2 0 0 0 1.75 2.95H20a2 2 0 0 0 1.76-2.95l-2.63-4.83a2 2 0 0 0-3.51 0l-2.63 4.83ZM5.86 13.27a.89.89 0 0 1 1.28 0l.75.77a.9.9 0 0 0 .54.26l1.06.12c.5.06.85.52.8 1.02l-.13 1.08c-.02.2.03.42.14.6l.56.92c.27.43.14 1-.28 1.26l-.9.58a.92.92 0 0 0-.37.48l-.36 1.02a.9.9 0 0 1-1.15.57l-1-.36a.89.89 0 0 0-.6 0l-1 .36a.9.9 0 0 1-1.15-.57l-.36-1.02a.92.92 0 0 0-.37-.48l-.9-.58a.93.93 0 0 1-.28-1.26l.56-.93c.11-.17.16-.38.14-.59l-.12-1.08c-.06-.5.3-.96.8-1.02l1.05-.12a.9.9 0 0 0 .54-.26l.75-.77ZM18.52 13.71a1.1 1.1 0 0 0-2.04 0l-.46 1.24c-.19.5-.57.88-1.07 1.07l-1.24.46a1.1 1.1 0 0 0 0 2.04l1.24.46c.5.19.88.57 1.07 1.07l.46 1.24c.35.95 1.7.95 2.04 0l.46-1.24c.19-.5.57-.88 1.07-1.07l1.24-.46a1.1 1.1 0 0 0 0-2.04l-1.24-.46a1.8 1.8 0 0 1-1.07-1.07l-.46-1.24Z" }) }), (0, jsx_runtime_1.jsx)("span", { className: "text-white font-semibold", children: displayName })] })), (0, jsx_runtime_1.jsx)("span", { className: "text-[#b5bac1]", children: "used" }), (0, jsx_runtime_1.jsxs)("span", { className: "bg-[#141437] text-[#6896f1] font-medium px-1.5 py-0.5 rounded inline-flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)("svg", { width: "12", height: "12", viewBox: "0 0 24 24", className: "text-[#6896f1]", children: (0, jsx_runtime_1.jsx)("path", { fill: "currentColor", d: "M2.06 7.61c-.25.95.31 1.92 1.26 2.18l4.3 1.15c.94.25 1.91-.31 2.17-1.26l1.15-4.3c.25-.94-.31-1.91-1.26-2.17l-4.3-1.15c-.94-.25-1.91.31-2.17 1.26l-1.15 4.3ZM12.98 7.87a2 2 0 0 0 1.75 2.95H20a2 2 0 0 0 1.76-2.95l-2.63-4.83a2 2 0 0 0-3.51 0l-2.63 4.83ZM5.86 13.27a.89.89 0 0 1 1.28 0l.75.77a.9.9 0 0 0 .54.26l1.06.12c.5.06.85.52.8 1.02l-.13 1.08c-.02.2.03.42.14.6l.56.92c.27.43.14 1-.28 1.26l-.9.58a.92.92 0 0 0-.37.48l-.36 1.02a.9.9 0 0 1-1.15.57l-1-.36a.89.89 0 0 0-.6 0l-1 .36a.9.9 0 0 1-1.15-.57l-.36-1.02a.92.92 0 0 0-.37-.48l-.9-.58a.93.93 0 0 1-.28-1.26l.56-.93c.11-.17.16-.38.14-.59l-.12-1.08c-.06-.5.3-.96.8-1.02l1.05-.12a.9.9 0 0 0 .54-.26l.75-.77ZM18.52 13.71a1.1 1.1 0 0 0-2.04 0l-.46 1.24c-.19.5-.57.88-1.07 1.07l-1.24.46a1.1 1.1 0 0 0 0 2.04l1.24.46c.5.19.88.57 1.07 1.07l.46 1.24c.35.95 1.7.95 2.04 0l.46-1.24c.19-.5.57-.88 1.07-1.07l1.24-.46a1.1 1.1 0 0 0 0-2.04l-1.24-.46a1.8 1.8 0 0 1-1.07-1.07l-.46-1.24Z" }) }), commandName] })] }) }));
41
+ }
42
+ function Message(props) {
43
+ const { id, author, timestamp, editedAt, content, attachments, embeds, selects, replyTo, poll, forwardedFrom, forwardedMessage, reactions, compact = false, resolvedUsers, resolvedRoles, resolvedChannels, channelGuildId, emojiUrls, stickers, interaction, messageType, pinned, onPinIconClick, referencedMessage } = props;
44
+ const [brokenImages, setBrokenImages] = react_1.default.useState(new Set());
45
+ const handleImageError = (url) => {
46
+ setBrokenImages((prev) => new Set([...prev, url]));
47
+ };
48
+ const handleReplyClick = (ev) => {
49
+ ev.preventDefault();
50
+ let targetId = replyTo?.id || replyTo?.messageId || replyTo?.messageIdString || replyTo?.reference;
51
+ if (!targetId)
52
+ return;
53
+ targetId = String(targetId);
54
+ let el = document.getElementById(targetId);
55
+ if (!el)
56
+ el = document.querySelector(`[data-message-id="${targetId}"]`);
57
+ if (el) {
58
+ document.querySelectorAll(".message-highlight").forEach((e) => e.classList.remove("message-highlight"));
59
+ el.classList.add("message-highlight");
60
+ el.scrollIntoView({ behavior: "smooth", block: "center" });
61
+ setTimeout(() => {
62
+ el?.classList.remove("message-highlight");
63
+ }, 2000);
64
+ }
65
+ };
66
+ const handleForwardClick = (ev) => {
67
+ ev.preventDefault();
68
+ let targetId = forwardedMessage?.id ||
69
+ forwardedMessage?.messageId ||
70
+ forwardedMessage?.originalId ||
71
+ forwardedMessage?.reference;
72
+ if (!targetId)
73
+ return;
74
+ targetId = String(targetId);
75
+ let el = document.getElementById(targetId);
76
+ if (!el)
77
+ el = document.querySelector(`[data-message-id="${targetId}"]`);
78
+ if (el)
79
+ el.scrollIntoView({ behavior: "smooth", block: "center" });
80
+ };
81
+ const dateObj = timestamp ? new Date(timestamp) : null;
82
+ let tagDate = dateObj ? (0, date_1.formatDate)(dateObj) : "";
83
+ let shortTime = dateObj ? (0, date_1.formatTime)(dateObj) : "";
84
+ tagDate = tagDate.replace(/\s+(AM|PM)$/, "\u00A0$1");
85
+ shortTime = shortTime.replace(/\s+(AM|PM)$/, "\u00A0$1");
86
+ const timeAgoDisplay = dateObj
87
+ ? (() => {
88
+ const diff = Math.floor((Date.now() - dateObj.getTime()) / 1000);
89
+ if (Math.abs(diff) < 60)
90
+ return `${Math.abs(diff)}s ago`;
91
+ if (Math.abs(diff) < 3600)
92
+ return `${Math.round(Math.abs(diff) / 60)}m ago`;
93
+ if (Math.abs(diff) < 86400)
94
+ return `${Math.round(Math.abs(diff) / 3600)}h ago`;
95
+ return (0, date_1.formatDate)(dateObj);
96
+ })()
97
+ : "";
98
+ const relativeTimestamp = dateObj ? (0, messageHelpers_1.timeAgo)(dateObj) : "";
99
+ const BOT_AVATAR = "/profile/bot.png";
100
+ const ensureKeys = (node, prefix) => {
101
+ if (node === null || node === undefined)
102
+ return node;
103
+ const arr = react_1.default.Children.toArray(node);
104
+ return arr.map((child, i) => {
105
+ if (react_1.default.isValidElement(child)) {
106
+ const childChildren = child.props && child.props.children;
107
+ const newChildren = ensureKeys(childChildren, `${prefix}-${i}`);
108
+ const key = child.key != null ? child.key : `${prefix}-${i}`;
109
+ try {
110
+ return react_1.default.cloneElement(child, { key }, newChildren);
111
+ }
112
+ catch (e) {
113
+ return react_1.default.cloneElement(child, {}, newChildren);
114
+ }
115
+ }
116
+ return child;
117
+ });
118
+ };
119
+ const urlsToHide = new Set();
120
+ if (attachments) {
121
+ attachments.forEach((a) => {
122
+ if (a?.url)
123
+ urlsToHide.add(a.url);
124
+ if (a?.embedUrl)
125
+ urlsToHide.add(a.embedUrl);
126
+ });
127
+ }
128
+ const filteredEmbeds = embeds
129
+ ? embeds.filter((e) => {
130
+ const imgUrl = e?.image?.url || e?.thumbnail?.url;
131
+ const isEmojiEmbed = imgUrl && /cdn\.discordapp\.com\/emojis\//.test(imgUrl) && !e?.title && !e?.description && !e?.author;
132
+ return !isEmojiEmbed;
133
+ })
134
+ : [];
135
+ if (filteredEmbeds) {
136
+ filteredEmbeds.forEach((e) => {
137
+ if (e?.url)
138
+ urlsToHide.add(e.url);
139
+ if (e?.image?.url)
140
+ urlsToHide.add(e.image.url);
141
+ if (e?.thumbnail?.url)
142
+ urlsToHide.add(e.thumbnail.url);
143
+ });
144
+ }
145
+ function stripDuplicateLinks(s, extra) {
146
+ let out = s;
147
+ const all = new Set(urlsToHide);
148
+ if (extra)
149
+ extra.forEach((u) => all.add(u));
150
+ all.forEach((u) => {
151
+ out = out.split(u).join("");
152
+ });
153
+ return out;
154
+ }
155
+ const [hover, setHover] = react_1.default.useState(false);
156
+ function StickerRenderer({ st }) {
157
+ return ((0, jsx_runtime_1.jsx)("div", { className: "mb-2", style: { width: 160, height: 160 }, children: (0, jsx_runtime_1.jsx)(StickerPreview_1.default, { st: st, size: 160, className: "w-[160px] h-[160px]" }) }));
158
+ }
159
+ const getAttachmentIcon = (kind) => {
160
+ switch (kind) {
161
+ case "audio":
162
+ return FileAudioIcon_1.default;
163
+ case "code":
164
+ return FileCodeIcon_1.default;
165
+ default:
166
+ return FileDocumentIcon_1.default;
167
+ }
168
+ };
169
+ const renderContent = (c) => {
170
+ if (typeof c === "string") {
171
+ const localHide = new Set();
172
+ const emojiTokenRegex = /<(a)?:([^:>]+):(\d+)>/g;
173
+ let em;
174
+ while ((em = emojiTokenRegex.exec(c)) !== null) {
175
+ const animated = !!em[1];
176
+ const id = em[3];
177
+ const token = em[0];
178
+ const src = emojiUrls && emojiUrls[token] ? emojiUrls[token] : (0, cdnHelpers_1.buildEmojiCdnUrl)(id, animated, 96) || "";
179
+ if (src)
180
+ localHide.add(src);
181
+ }
182
+ const text = (0, messageHelpers_1.sanitizeMarkdownString)(stripDuplicateLinks(c, localHide));
183
+ return (0, markdown_1.parseMarkdown)(text, resolvedUsers, resolvedRoles, resolvedChannels, channelGuildId);
184
+ }
185
+ const rendered = (0, markdown_1.parseMarkdown)(c, resolvedUsers, resolvedRoles, resolvedChannels, channelGuildId);
186
+ try {
187
+ const problems = [];
188
+ const walk = (node, path) => {
189
+ if (node === null || node === undefined)
190
+ return;
191
+ if (Array.isArray(node)) {
192
+ node.forEach((n, i) => {
193
+ if (react_1.default.isValidElement(n) && n.key == null) {
194
+ problems.push({ path: [...path, `[${i}]`].join("/"), type: "element-no-key", el: n.type });
195
+ }
196
+ walk(n, [...path, `[${i}]`]);
197
+ });
198
+ return;
199
+ }
200
+ if (react_1.default.isValidElement(node)) {
201
+ const key = node.key;
202
+ if (key == null) {
203
+ problems.push({ path: path.join("/"), type: "element-no-key", el: node.type });
204
+ }
205
+ const children = node.props && node.props.children;
206
+ if (children)
207
+ walk(children, [...path, String(node.type || "el")]);
208
+ return;
209
+ }
210
+ if (typeof node === "object") {
211
+ react_1.default.Children.forEach(node, (child, i) => {
212
+ walk(child, [...path, `child(${i})`]);
213
+ });
214
+ }
215
+ };
216
+ const ensureKeys = (node, prefix) => {
217
+ if (node === null || node === undefined)
218
+ return node;
219
+ const arr = react_1.default.Children.toArray(node);
220
+ return arr.map((child, i) => {
221
+ if (react_1.default.isValidElement(child)) {
222
+ const childChildren = child.props && child.props.children;
223
+ const newChildren = ensureKeys(childChildren, `${prefix}-${i}`);
224
+ if (child.key == null) {
225
+ return react_1.default.cloneElement(child, { key: `${prefix}-${i}` }, newChildren);
226
+ }
227
+ return react_1.default.cloneElement(child, {}, newChildren);
228
+ }
229
+ return child;
230
+ });
231
+ };
232
+ const fixed = ensureKeys(rendered, `message-${id}`);
233
+ walk(fixed, [`message-${id}`]);
234
+ if (problems.length > 0) {
235
+ console.warn("[renderContent] missing-key problems for message:", id, problems.slice(0, 20));
236
+ }
237
+ }
238
+ catch (e) {
239
+ console.warn("[renderContent] key-check failed", e);
240
+ }
241
+ return rendered;
242
+ };
243
+ function renderSegmentWithEmojis(text) {
244
+ const out = [];
245
+ const emojiRegex = /<(a)?:([^:>]+):(\d+)>/g;
246
+ let last = 0;
247
+ let m;
248
+ let tsCallIndex = 0;
249
+ while ((m = emojiRegex.exec(text)) !== null) {
250
+ if (m.index > last) {
251
+ const part = text.substring(last, m.index);
252
+ out.push(...renderTextWithTimestamps(part, tsCallIndex++));
253
+ }
254
+ if (m.index > 0 && text[m.index - 1] === "\\") {
255
+ out.push(m[0].replace(/\\/g, ""));
256
+ }
257
+ else {
258
+ const animated = !!m[1];
259
+ const name = m[2];
260
+ const id = m[3];
261
+ const token = m[0];
262
+ const src = emojiUrls && emojiUrls[token] ? emojiUrls[token] : (0, cdnHelpers_1.buildEmojiCdnUrl)(id, animated, 96) || "";
263
+ if (src) {
264
+ out.push((0, jsx_runtime_1.jsx)("img", { src: src, alt: name, className: "inline h-6 w-6 align-text-bottom mr-0.5" }, `e-${id}-${m.index}`));
265
+ }
266
+ }
267
+ last = emojiRegex.lastIndex;
268
+ }
269
+ if (last < text.length)
270
+ out.push(...renderTextWithTimestamps(text.substring(last), tsCallIndex++));
271
+ return react_1.default.Children.toArray(out);
272
+ }
273
+ function renderTextWithTimestamps(s, callIndex = 0) {
274
+ const out = [];
275
+ const tsRegex = /<t:(\d+)(?::([tTfFR]))?>/g;
276
+ let last = 0;
277
+ let m;
278
+ function renderTextWithCommands(str, keyBase) {
279
+ const parts = [];
280
+ const cmdRegex = /<\/([A-Za-z0-9_-]+):(\d+)>/g;
281
+ let l = 0;
282
+ let mm;
283
+ let idx = 0;
284
+ while ((mm = cmdRegex.exec(str)) !== null) {
285
+ if (mm.index > l) {
286
+ parts.push((0, jsx_runtime_1.jsx)(react_1.default.Fragment, { children: (0, markdown_1.parseMarkdown)(str.substring(l, mm.index), resolvedUsers, resolvedRoles, resolvedChannels, channelGuildId) }, `cmd-txt-${keyBase}-${l}`));
287
+ }
288
+ const name = mm[1];
289
+ parts.push((0, jsx_runtime_1.jsx)("span", { className: "inline-flex items-center rounded px-2 py-0.5 text-sm font-medium mr-1", style: { backgroundColor: "#1d193f", color: "#9697ec" }, children: `/${name}` }, `cmd-pill-${keyBase}-${idx}`));
290
+ l = cmdRegex.lastIndex;
291
+ idx++;
292
+ }
293
+ if (l < str.length)
294
+ parts.push((0, jsx_runtime_1.jsx)(react_1.default.Fragment, { children: (0, markdown_1.parseMarkdown)(str.substring(l), resolvedUsers, resolvedRoles, resolvedChannels, channelGuildId) }, `cmd-txt-tail-${keyBase}`));
295
+ return parts;
296
+ }
297
+ function TimestampToken({ sec, display, full, k }) {
298
+ const [visible, setVisible] = react_1.default.useState(false);
299
+ const [pos, setPos] = react_1.default.useState(null);
300
+ const wrapRef = react_1.default.useRef(null);
301
+ const tipRef = react_1.default.useRef(null);
302
+ const onEnter = () => {
303
+ setVisible(true);
304
+ requestAnimationFrame(() => {
305
+ const wrap = wrapRef.current;
306
+ const tip = tipRef.current;
307
+ if (!wrap || !tip)
308
+ return;
309
+ const wRect = wrap.getBoundingClientRect();
310
+ const tRect = tip.getBoundingClientRect();
311
+ const centerX = wRect.left + wRect.width / 2;
312
+ const left = Math.min(Math.max(8, centerX - tRect.width / 2), window.innerWidth - tRect.width - 8);
313
+ const top = Math.max(8, wRect.top - tRect.height - 8);
314
+ setPos({ left, top });
315
+ });
316
+ };
317
+ const onLeave = () => setVisible(false);
318
+ return ((0, jsx_runtime_1.jsxs)("span", { ref: wrapRef, onMouseEnter: onEnter, onMouseLeave: onLeave, className: "inline-block", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-sm bg-[#242427] p-1 cursor-default border border-[#242427] rounded-md", children: display }), (0, jsx_runtime_1.jsx)("div", { ref: tipRef, className: "timestamp-tooltip", style: {
319
+ position: "fixed",
320
+ left: pos ? `${pos.left}px` : "50%",
321
+ top: pos ? `${pos.top}px` : "-9999px",
322
+ transform: pos ? "none" : "translateX(-50%)",
323
+ opacity: visible ? 1 : 0,
324
+ pointerEvents: "none",
325
+ transition: "opacity 150ms",
326
+ zIndex: 99999,
327
+ background: "#242427",
328
+ color: "#fff",
329
+ fontSize: "14px",
330
+ fontWeight: 600,
331
+ padding: "8px 12px",
332
+ borderRadius: 8,
333
+ boxShadow: "0 8px 24px rgba(0,0,0,0.8)",
334
+ whiteSpace: "nowrap"
335
+ }, children: full })] }, k));
336
+ }
337
+ while ((m = tsRegex.exec(s)) !== null) {
338
+ if (m.index > last) {
339
+ const substring = s.substring(last, m.index);
340
+ out.push((0, jsx_runtime_1.jsx)(react_1.default.Fragment, { children: renderTextWithCommands(substring, `${callIndex}-${last}-${m.index}`) }, `ts-pre-${id}-${callIndex}-${last}-${m.index}`));
341
+ }
342
+ const sec = Number(m[1]);
343
+ const fmt = m[2];
344
+ const date = new Date(sec * 1000);
345
+ let display = "";
346
+ if (fmt === "R") {
347
+ display = (0, messageHelpers_1.timeAgo)(date);
348
+ }
349
+ else {
350
+ display = (0, date_1.formatDate)(date);
351
+ }
352
+ const full = (0, date_1.formatDate)(date);
353
+ out.push((0, jsx_runtime_1.jsx)(TimestampToken, { sec: sec, display: display, full: full, k: `ts-${id}-${callIndex}-${sec}-${m.index}` }, `ts-${id}-${callIndex}-${sec}-${m.index}`));
354
+ last = tsRegex.lastIndex;
355
+ }
356
+ if (last < s.length) {
357
+ const substring = s.substring(last);
358
+ out.push((0, jsx_runtime_1.jsx)(react_1.default.Fragment, { children: renderTextWithCommands(substring, `${callIndex}-tail-${last}`) }, `ts-tail-${id}-${callIndex}-${last}`));
359
+ }
360
+ return react_1.default.Children.toArray(out);
361
+ }
362
+ const isPollEnd = messageType === 46 && embeds && embeds.length > 0 && embeds[0]?.fields;
363
+ const pollEndContent = isPollEnd
364
+ ? (() => {
365
+ const fields = embeds[0].fields;
366
+ const pollQuestionField = fields.find((f) => f.name === "poll_question_text");
367
+ const totalVotesField = fields.find((f) => f.name === "total_votes");
368
+ if (!pollQuestionField || !totalVotesField)
369
+ return null;
370
+ const pollQuestion = pollQuestionField.value;
371
+ const totalVotes = parseInt(totalVotesField.value, 10) || 0;
372
+ const answerFields = fields.filter((f) => f.name !== "poll_question_text" && f.name !== "total_votes");
373
+ const voteCounts = answerFields.map((f) => parseInt(f.value, 10) || 0);
374
+ const maxVotes = Math.max(...voteCounts, 0);
375
+ const highestPct = totalVotes > 0 ? Math.round((maxVotes / totalVotes) * 100) : 0;
376
+ const tiedOptions = answerFields.filter((f) => parseInt(f.value, 10) === maxVotes).length;
377
+ let resultText = "The results were tied";
378
+ if (tiedOptions === 1 && maxVotes > 0) {
379
+ resultText = `${highestPct}%`;
380
+ }
381
+ const isTied = tiedOptions > 1;
382
+ return { pollQuestion, totalVotes, resultText, highestPct, isTied };
383
+ })()
384
+ : null;
385
+ const pollReferenceId = replyTo?.id || replyTo?.messageId || replyTo?.messageIdString || replyTo?.reference || undefined;
386
+ const scrollToId = (targetId) => {
387
+ if (!targetId)
388
+ return;
389
+ const idStr = String(targetId);
390
+ let el = document.getElementById(idStr);
391
+ if (!el)
392
+ el = document.querySelector(`[data-message-id="${idStr}"]`);
393
+ if (el)
394
+ el.scrollIntoView({ behavior: "smooth", block: "center" });
395
+ };
396
+ const _messageTree = ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: isPollEnd && pollEndContent ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex gap-3 py-1 relative transition-colors duration-300 w-full mb-2", children: [(0, jsx_runtime_1.jsx)("svg", { "aria-hidden": "true", role: "img", xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", fill: "none", viewBox: "0 0 24 24", className: "flex-shrink-0 mt-2", children: (0, jsx_runtime_1.jsx)("path", { fill: "#B5BAC1", d: "M2 5c0-1.1.9-2 2-2h16a2 2 0 1 1 0 4H4a2 2 0 0 1-2-2ZM2 12c0-1.1.9-2 2-2h6a2 2 0 1 1 0 4H4a2 2 0 0 1-2-2ZM4 17a2 2 0 1 0 0 4h12a2 2 0 1 0 0-4H4Z" }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 min-w-0 ml-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-sm text-[#B5BAC1] mb-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "font-semibold", style: { color: author.color ?? "#FFFFFF" }, children: author.username }), (0, jsx_runtime_1.jsx)("span", { className: "text-[#B5BAC1]", children: "'s poll " }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "text-white font-semibold hover:underline", onClick: (ev) => {
397
+ ev.preventDefault();
398
+ scrollToId(pollReferenceId);
399
+ }, children: pollEndContent.pollQuestion }), (0, jsx_runtime_1.jsx)("span", { className: "text-[#B5BAC1]", children: " has closed. " }), (0, jsx_runtime_1.jsx)("span", { className: "text-xs text-[#72767D]", children: timeAgoDisplay })] }), (0, jsx_runtime_1.jsx)("div", { className: "bg-[#131416] rounded-lg p-4 max-w-md", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-left", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-sm text-[#DBDEE1] font-semibold", children: "The results were tied" }), (0, jsx_runtime_1.jsx)("div", { className: "text-sm text-[#72767D] font-semibold", children: pollEndContent.resultText }), pollEndContent.isTied ? ((0, jsx_runtime_1.jsxs)("div", { className: "text-2xl text-[#DBDEE1] font-extrabold mt-1", children: [pollEndContent.highestPct, "%"] })) : null] }), (0, jsx_runtime_1.jsx)("button", { className: "px-4 py-2 rounded text-sm bg-[#404249] text-white hover:bg-[#4a4d54] transition-colors flex-shrink-0 whitespace-nowrap", onClick: (ev) => {
400
+ ev.preventDefault();
401
+ scrollToId(pollReferenceId);
402
+ }, children: "View Poll" })] }) })] })] })) : messageType === 7 ? ((0, jsx_runtime_1.jsx)("article", { id: id, "data-message-id": id, className: "py-0.5", children: (0, jsx_runtime_1.jsx)(UserJoinMessage_1.default, { username: author.username }) })) : messageType === 6 ? ((0, jsx_runtime_1.jsx)("article", { id: id, "data-message-id": id, className: "py-0.5", children: (0, jsx_runtime_1.jsx)(ChannelPinnedMessage_1.default, { message: props, referencedMessage: referencedMessage, onOpenPinnedMessages: onPinIconClick || (() => { }), onNavigateToMessage: props.onNavigateToMessage }) })) : ((0, jsx_runtime_1.jsxs)("article", { id: id, "data-message-id": id, className: `flex gap-3 ${compact ? "py-0.5 mt-0" : "py-0.5 mt-4"} relative transition-colors duration-300 w-full ${interaction?.name ? "pr-4" : ""}`, onMouseEnter: () => setHover(true), onMouseLeave: () => setHover(false), children: [(0, jsx_runtime_1.jsx)("div", { className: "w-10 shrink-0 flex items-start justify-start mt-0.5 relative", children: compact ? ((0, jsx_runtime_1.jsx)("div", { className: "relative inline-block w-full text-right pr-1", children: (0, jsx_runtime_1.jsx)("span", { className: `text-xs text-[#949BA4] transition-opacity ${hover ? "opacity-100" : "opacity-0"}`, "aria-hidden": true, children: timestamp ? shortTime : "" }) })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(replyTo || interaction?.name) && ((0, jsx_runtime_1.jsx)("div", { className: "absolute left-[20px] top-[5px] w-7 h-[10px] border-l-2 border-t-2 border-[#4f545c] rounded-tl-[8px]" })), (0, jsx_runtime_1.jsx)("img", { src: author.avatar || BOT_AVATAR, alt: "avatar", className: `w-10 h-10 rounded-full object-cover mt-1 ${replyTo || interaction?.name ? "mt-5" : ""}` })] })) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 min-w-0", children: [replyTo && ((0, jsx_runtime_1.jsx)(Reply_1.default, { replyTo: replyTo, onReplyClick: handleReplyClick, defaultAvatar: BOT_AVATAR, resolvedUsers: resolvedUsers, resolvedRoles: resolvedRoles, resolvedChannels: resolvedChannels })), compact ? ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsxs)("div", { className: "text-base text-[#DBDEE1] leading-[1.375rem] min-h-[1.375rem]", children: [content ? renderContent(content) : null, editedAt ? (0, jsx_runtime_1.jsx)("span", { className: "ml-1 text-xs text-[#72767D]", children: "(edited)" }) : null] }) })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [!compact && interaction?.name && renderCommandLine(interaction, author, resolvedUsers, BOT_AVATAR), (0, jsx_runtime_1.jsxs)("header", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsxs)("strong", { className: "text-base font-semibold flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)("span", { style: { color: author.color ?? "#FFFFFF" }, children: author.username }), author.guildTag && author.guildTag.name ? ((0, jsx_runtime_1.jsxs)("span", { className: "ml-0.5 text-xs text-[#B9BBBE] bg-[#2F3136] px-1 py-0.5 rounded flex items-center gap-1", children: [author.guildTag.iconUrl ? ((0, jsx_runtime_1.jsx)("img", { src: author.guildTag.iconUrl, alt: author.guildTag.name, className: "w-3 h-3 rounded-full object-cover" })) : null, (0, jsx_runtime_1.jsx)("span", { children: author.guildTag.name })] })) : null, author.bot ? ((0, jsx_runtime_1.jsxs)("span", { className: "ml-0.5 text-xs text-white bg-[#5865F2] rounded flex items-center botTag", children: [author.verified ? (0, jsx_runtime_1.jsx)(VerifiedIcon_1.default, {}) : null, (0, jsx_runtime_1.jsx)("span", { className: "leading-none", children: "APP" })] })) : null] }), (0, jsx_runtime_1.jsx)("span", { className: "text-sm text-[#949BA4] cursor-default flex items-center gap-1 whitespace-nowrap", title: relativeTimestamp, children: timestamp ? (0, jsx_runtime_1.jsx)("span", { className: "rem_82f0793afa59e5dc-botTag botTag ml-0.5", children: tagDate }) : "" }), editedAt ? (0, jsx_runtime_1.jsx)("span", { className: "ml-1 text-xs text-[#72767D]", children: "(edited)" }) : null] }), (0, jsx_runtime_1.jsx)("div", { className: "mt-0.5 text-base text-[#DBDEE1] leading-tight", children: content ? renderContent(content) : null })] })), forwardedFrom && forwardedMessage ? ((0, jsx_runtime_1.jsx)(ForwardedMessage_1.default, { message: forwardedMessage, brokenImages: brokenImages, onImageError: handleImageError, renderContent: renderContent, defaultAvatar: BOT_AVATAR })) : null, stickers && stickers.length ? ((0, jsx_runtime_1.jsx)("div", { className: "mt-2", children: stickers.map((st, i) => ((0, jsx_runtime_1.jsx)(StickerRenderer, { st: st }, `st-${i}`))) })) : null, attachments && attachments.length ? ((0, jsx_runtime_1.jsx)("div", { className: "mt-2 flex flex-col gap-2", children: attachments.map((a, i) => {
403
+ const url = (a && (a.url || (typeof a === "string" ? a : undefined))) || "";
404
+ let filename = a && a.filename;
405
+ if (!filename)
406
+ filename = (0, messageHelpers_1.extractFilenameFromUrl)(url) || "file";
407
+ const filesize = (0, messageHelpers_1.formatFileSize)(a && a.size, a && a.size_human);
408
+ const imageLike = (0, messageHelpers_1.isImageUrl)(url);
409
+ const audioLike = (0, messageHelpers_1.isAudioUrl)(url);
410
+ const videoLike = (0, messageHelpers_1.isVideoUrl)(url);
411
+ const isBroken = brokenImages.has(url || "");
412
+ if (imageLike && !isBroken) {
413
+ return ((0, jsx_runtime_1.jsx)("div", { className: "mb-2 max-w-[520px]", children: (0, jsx_runtime_1.jsx)("img", { src: url, alt: filename, className: `rounded w-full h-auto ${isBroken ? "image-error" : ""}`, onError: () => handleImageError(url || "") }) }, `att-${i}`));
414
+ }
415
+ if (audioLike) {
416
+ return ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(AudioPlayer_1.default, { url: url, filename: filename, filesize: filesize }) }, `att-${i}`));
417
+ }
418
+ if (videoLike) {
419
+ return ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(VideoPlayer_1.default, { url: url, filename: filename, filesize: filesize }) }, `att-${i}`));
420
+ }
421
+ const kind = (0, messageHelpers_1.getAttachmentKind)(filename, url);
422
+ const Icon = getAttachmentIcon(kind);
423
+ return ((0, jsx_runtime_1.jsx)("div", { className: "group relative inline-block max-w-[432px]", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3 px-4 py-3 bg-[#1e1f22] hover:bg-[#23252b] border border-[#111214] rounded-lg transition-colors", children: [(0, jsx_runtime_1.jsx)(Icon, { className: "h-10 w-8 flex-shrink-0" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 min-w-0", children: [(0, jsx_runtime_1.jsx)("a", { href: url, download: true, className: "text-[15px] font-medium text-[#00a8fc] truncate hover:underline block", children: filename }), (0, jsx_runtime_1.jsx)("div", { className: "text-xs text-[#b5bac1] mt-1", children: filesize })] }), (0, jsx_runtime_1.jsx)("a", { href: url, download: true, className: "hover:bg-[#2c2f33] p-2 rounded transition-colors flex-shrink-0", title: "Download", children: (0, jsx_runtime_1.jsx)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", children: (0, jsx_runtime_1.jsx)("path", { fill: "#b5bac1", d: "M12 2a1 1 0 0 1 1 1v10.59l3.3-3.3a1 1 0 1 1 1.4 1.42l-5 5a1 1 0 0 1-1.4 0l-5-5a1 1 0 1 1 1.4-1.42l3.3 3.3V3a1 1 0 0 1 1-1ZM3 20a1 1 0 1 0 0 2h18a1 1 0 1 0 0-2H3Z" }) }) })] }) }, `att-${i}`));
424
+ }) })) : null, filteredEmbeds && filteredEmbeds.length ? ((0, jsx_runtime_1.jsx)("div", { className: "mt-2 space-y-2", children: filteredEmbeds.map((e, i) => ((0, jsx_runtime_1.jsx)(Embed_1.default, { ...(e || {}), resolvedUsers: resolvedUsers, resolvedRoles: resolvedRoles, resolvedChannels: resolvedChannels, interaction: interaction }, i))) })) : null, poll && (poll.question || (Array.isArray(poll.options) && poll.options.length > 0))
425
+ ? (() => {
426
+ const isClosed = poll.closed || (poll.endsAt && new Date(poll.endsAt) < new Date());
427
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "mt-3 bg-[#131416] rounded-lg p-4 max-w-md", children: [poll.question ? (0, jsx_runtime_1.jsx)("div", { className: "text-base font-semibold text-white mb-3", children: poll.question }) : null, (0, jsx_runtime_1.jsx)("div", { className: "flex flex-col gap-2", children: (() => {
428
+ const total = poll.totalVotes ??
429
+ (Array.isArray(poll.options) ? poll.options.reduce((s, o) => s + (o.count ?? 0), 0) : 0);
430
+ const maxCount = Array.isArray(poll.options)
431
+ ? Math.max(...poll.options.map((o) => o.count ?? 0), 0)
432
+ : 0;
433
+ return Array.isArray(poll.options)
434
+ ? poll.options.map((opt, oi) => {
435
+ const count = opt.count ?? 0;
436
+ const pct = total > 0 ? Math.round((count / total) * 100) : 0;
437
+ const hasVotes = count > 0;
438
+ const isWinner = isClosed && count === maxCount && total > 0;
439
+ const barColor = isClosed
440
+ ? "rgba(46, 204, 113, 0.2)"
441
+ : isWinner
442
+ ? "rgba(46, 204, 113, 0.18)"
443
+ : "rgba(88, 101, 242, 0.15)";
444
+ const borderColor = isClosed ? "#2ecc71" : isWinner ? "#2ecc71" : "#5865F2";
445
+ return ((0, jsx_runtime_1.jsxs)("div", { className: `relative bg-[#1E1F22] rounded-lg p-3 border-2 ${hasVotes ? "" : "border-transparent"} overflow-hidden`, style: {
446
+ borderColor: hasVotes ? borderColor : undefined
447
+ }, children: [hasVotes && ((0, jsx_runtime_1.jsx)("div", { style: {
448
+ position: "absolute",
449
+ left: 0,
450
+ top: 0,
451
+ bottom: 0,
452
+ width: `${pct}%`,
453
+ backgroundColor: barColor
454
+ } })), (0, jsx_runtime_1.jsxs)("div", { className: "relative flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-base text-white font-medium", children: opt.label }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-sm text-[#B5BAC1]", children: [count, " vote", count !== 1 ? "s" : ""] }), (0, jsx_runtime_1.jsxs)("div", { className: "text-sm font-semibold text-white", children: [pct, "%"] })] })] })] }, `poll-${oi}`));
455
+ })
456
+ : null;
457
+ })() }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-3 flex items-center justify-between text-sm", children: [(0, jsx_runtime_1.jsxs)("div", { className: "text-white font-semibold", children: [poll.totalVotes || 0, " vote", (poll.totalVotes || 0) !== 1 ? "s" : ""] }), isClosed ? ((0, jsx_runtime_1.jsx)("div", { className: "text-[#B5BAC1]", children: "Poll closed" })) : poll.endsAt ? ((0, jsx_runtime_1.jsx)("div", { className: "text-[#B5BAC1]", children: (() => {
458
+ const end = new Date(poll.endsAt);
459
+ const now = new Date();
460
+ const diff = Math.floor((end.getTime() - now.getTime()) / 1000);
461
+ const hours = Math.floor(diff / 3600);
462
+ const mins = Math.floor((diff % 3600) / 60);
463
+ return `${hours}h ${mins}m left`;
464
+ })() })) : null] })] }));
465
+ })()
466
+ : null, props.buttons && props.buttons.length ? ((0, jsx_runtime_1.jsx)("div", { className: "mt-2 flex flex-wrap gap-2", children: props.buttons.map((b, i) => {
467
+ const styleId = Number(b.style || 1);
468
+ let classes = "px-3 py-1 rounded text-sm transition-colors inline-flex items-center gap-2 justify-start";
469
+ let isLinkStyle = false;
470
+ switch (styleId) {
471
+ case 1: // Primary
472
+ classes += " bg-[#6652ec] hover:bg-[#5846d1] text-white";
473
+ break;
474
+ case 2: // Secondary
475
+ classes += " bg-[#18181b] hover:bg-[#0f0f10] text-white";
476
+ break;
477
+ case 3: // Success
478
+ classes += " bg-[#57F287] hover:bg-[#45c767] text-black";
479
+ break;
480
+ case 4: // Danger
481
+ classes += " bg-[#ED4245] hover:bg-[#C03537] text-white";
482
+ break;
483
+ case 5: // Link
484
+ classes += " bg-[#18181b] hover:bg-[#0f0f10] text-white";
485
+ isLinkStyle = true;
486
+ break;
487
+ default:
488
+ classes += " bg-[#2F3136] text-white";
489
+ }
490
+ const renderEmoji = () => {
491
+ if (b.emojiUrl)
492
+ return (0, jsx_runtime_1.jsx)("img", { src: b.emojiUrl, alt: "emoji", className: "inline w-4 h-4 mr-1 align-text-bottom flex-shrink-0" });
493
+ if (!b.emoji)
494
+ return null;
495
+ if (typeof b.emoji === "string")
496
+ return (0, jsx_runtime_1.jsx)("span", { className: "mr-1", children: b.emoji });
497
+ const em = b.emoji;
498
+ if (em.id) {
499
+ const animated = !!em.animated;
500
+ const src = (0, cdnHelpers_1.buildEmojiCdnUrl)(String(em.id), animated, 96) || "";
501
+ if (src) {
502
+ return ((0, jsx_runtime_1.jsx)("img", { src: src, alt: em.name || "emoji", className: "inline w-4 h-4 mr-1 align-text-bottom flex-shrink-0" }));
503
+ }
504
+ }
505
+ return (0, jsx_runtime_1.jsx)("span", { className: "mr-1", children: em.name || "" });
506
+ };
507
+ const contentNodes = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [renderEmoji(), (0, jsx_runtime_1.jsx)("span", { className: "flex-1 truncate text-left", children: b.label }), isLinkStyle ? (0, jsx_runtime_1.jsx)(ExternalLinkIcon_1.default, {}) : null] }));
508
+ if (isLinkStyle && b.url) {
509
+ return ((0, jsx_runtime_1.jsx)("a", { href: b.url, target: "_blank", rel: "noopener noreferrer", className: classes, children: contentNodes }, `btn-${i}`));
510
+ }
511
+ return ((0, jsx_runtime_1.jsx)("button", { className: classes, onClick: (ev) => {
512
+ ev.preventDefault();
513
+ if (b.url)
514
+ window.open(b.url, "_blank");
515
+ else if (b.customId)
516
+ console.log("button clicked", b.customId);
517
+ }, type: "button", children: contentNodes }, `btn-${i}`));
518
+ }) })) : null, props.selects && props.selects.length ? ((0, jsx_runtime_1.jsx)("div", { className: "mt-2 flex flex-col gap-2", children: props.selects.map((s, si) => ((0, jsx_runtime_1.jsx)("div", { className: "flex w-auto max-w-[432px] my-2 min-w-0", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex-1 rounded-md px-3 py-2", style: {
519
+ backgroundColor: "#0f1112",
520
+ border: "1px solid #1e2124"
521
+ }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between text-sm text-[#E6E6E6] cursor-default", children: [(0, jsx_runtime_1.jsx)("div", { className: "truncate", children: s.placeholder || "Select" }), (0, jsx_runtime_1.jsx)(ChevronDownIcon_1.default, { className: "ml-2 text-[#9aa0a6]" })] }), Array.isArray(s.options) && s.options.length ? ((0, jsx_runtime_1.jsx)("div", { className: "mt-2 bg-[#0e0f10] border border-[#1b1c1d] rounded p-1 text-sm text-[#9aa0a6]", children: s.options.map((opt, oi) => {
522
+ const renderOptEmoji = () => {
523
+ if (!opt.emoji)
524
+ return null;
525
+ if (typeof opt.emoji === "string")
526
+ return (0, jsx_runtime_1.jsx)("span", { className: "ml-2", children: opt.emoji });
527
+ const e = opt.emoji;
528
+ if (e.id) {
529
+ const animated = !!e.animated;
530
+ const src = (0, cdnHelpers_1.buildEmojiCdnUrl)(String(e.id), animated, 96) || "";
531
+ if (src) {
532
+ return (0, jsx_runtime_1.jsx)("img", { src: src, alt: e.name || "emoji", className: "ml-2 inline w-4 h-4" });
533
+ }
534
+ }
535
+ if (opt.emoji.name)
536
+ return (0, jsx_runtime_1.jsx)("span", { className: "ml-2", children: opt.emoji.name });
537
+ return null;
538
+ };
539
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between px-3 py-2 hover:bg-[#121314] rounded", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "font-medium text-white", children: opt.label }), opt.description ? (0, jsx_runtime_1.jsx)("div", { className: "text-xs text-[#9aa0a6]", children: opt.description }) : null] }), renderOptEmoji()] }, `opt-${si}-${oi}`));
540
+ }) })) : null] }) }, `select-${si}`))) })) : null, reactions && reactions.length ? ((0, jsx_runtime_1.jsx)("div", { className: "mt-2 flex gap-1", children: reactions.map((r, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "px-2 py-1 bg-[#1E1F22] border border-[#404249] rounded-full text-sm flex items-center gap-1 hover:bg-[#2C2F33] cursor-pointer", children: [r.emojiUrl ? (0, jsx_runtime_1.jsx)("img", { src: r.emojiUrl, alt: r.emoji || "emoji", className: "w-5 h-5 inline" }) : (0, jsx_runtime_1.jsx)("span", { children: r.emoji }), r.count ? (0, jsx_runtime_1.jsx)("span", { className: "text-xs text-[#949BA4]", children: r.count }) : null] }, i))) })) : null] })] })) }));
541
+ const _fixed = ensureKeys(_messageTree, `message-${id}`);
542
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: react_1.default.Children.toArray(_fixed) });
543
+ }
@@ -0,0 +1,6 @@
1
+ import type { MessageProps } from "../types/message";
2
+ export default function PinnedMessagesModal({ pinnedMessages, onClose, onMessageClick }: {
3
+ pinnedMessages: MessageProps[];
4
+ onClose: () => void;
5
+ onMessageClick: (messageId: string) => void;
6
+ }): import("react/jsx-runtime").JSX.Element;