@select-org/post-components 1.0.0 → 2.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.
package/dist/index.js ADDED
@@ -0,0 +1,3135 @@
1
+ import {
2
+ SPOTIFY_HEIGHT,
3
+ TruncateText,
4
+ hasMeaningfulHtml,
5
+ postItemType,
6
+ resolvePostContent
7
+ } from "./chunk-JPYOLIZH.js";
8
+
9
+ // src/components/PostCard.tsx
10
+ import React11, { useMemo as useMemo7, useState as useState5 } from "react";
11
+
12
+ // src/utils/cn.ts
13
+ import { clsx } from "clsx";
14
+ import { twMerge } from "tailwind-merge";
15
+ function cn(...inputs) {
16
+ return twMerge(clsx(inputs));
17
+ }
18
+
19
+ // src/components/PostHeader.tsx
20
+ import {
21
+ Avatar,
22
+ AvatarImage,
23
+ AvatarFallback,
24
+ AvatarBadge
25
+ } from "@select-org/ui";
26
+
27
+ // src/components/AppIcon.tsx
28
+ import {
29
+ AlarmClock,
30
+ Check,
31
+ CheckCircle2,
32
+ CirclePlay,
33
+ ExternalLink,
34
+ LoaderCircle,
35
+ MessageCircleMore,
36
+ Smile,
37
+ ThumbsDown,
38
+ ThumbsUp,
39
+ Trash2,
40
+ TriangleAlert,
41
+ Video,
42
+ X
43
+ } from "lucide-react";
44
+ import { jsx } from "react/jsx-runtime";
45
+ var ICON_MAP = {
46
+ video: Video,
47
+ circlePlay: CirclePlay,
48
+ cancel: X,
49
+ externalLink: ExternalLink,
50
+ sendEmoji: Smile,
51
+ alarmClock: AlarmClock,
52
+ messageCircleMore: MessageCircleMore,
53
+ chatErase: Trash2,
54
+ checkCircle2: CheckCircle2,
55
+ thumbUp: ThumbsUp,
56
+ thumbDown: ThumbsDown,
57
+ check: Check,
58
+ loader: LoaderCircle,
59
+ triangleAlert: TriangleAlert
60
+ };
61
+ function AppIcon({ name, ...props }) {
62
+ const Icon = ICON_MAP[name];
63
+ if (!Icon) return null;
64
+ return /* @__PURE__ */ jsx(Icon, { ...props });
65
+ }
66
+
67
+ // src/utils/postTypeHint.ts
68
+ var MEDIA_TYPES = /* @__PURE__ */ new Set([
69
+ "new:post:media",
70
+ "new:post:image",
71
+ "mediaItemToUpload"
72
+ ]);
73
+ function getPostTypeHint(postType, ctx = {}) {
74
+ const {
75
+ isInGroup = false,
76
+ isOwner = false,
77
+ isPrivate = false,
78
+ membersCount = 0,
79
+ isMetaDesc = false
80
+ } = ctx;
81
+ const isMedia = MEDIA_TYPES.has(postType);
82
+ if (isInGroup) {
83
+ if (isMetaDesc) {
84
+ if (isMedia)
85
+ return { key: "post_detail_meta_description_type_image_post" };
86
+ switch (postType) {
87
+ case "new:post:link":
88
+ return { key: "post_detail_meta_description_type_link_post" };
89
+ case "new:post:poll":
90
+ return { key: "post_detail_meta_description_type_poll_post" };
91
+ case "new:post:thread":
92
+ return {
93
+ key: "post_detail_one_liner_receive_and_send_group_thread_post"
94
+ };
95
+ case "new:post:text":
96
+ default:
97
+ return { key: "post_detail_meta_description_type_message_post" };
98
+ }
99
+ }
100
+ if (isMedia)
101
+ return { key: "post_detail_one_liner_receive_and_send_group_image_post" };
102
+ switch (postType) {
103
+ case "new:post:link":
104
+ return {
105
+ key: "post_detail_one_liner_receive_and_send_group_link_post"
106
+ };
107
+ case "new:post:poll":
108
+ return {
109
+ key: "post_detail_one_liner_receive_and_send_group_poll_post"
110
+ };
111
+ case "new:post:thread":
112
+ return {
113
+ key: "post_detail_one_liner_receive_and_send_group_thread_post"
114
+ };
115
+ case "new:post:text":
116
+ default:
117
+ return {
118
+ key: "post_detail_one_liner_receive_and_send_group_message_post"
119
+ };
120
+ }
121
+ }
122
+ if (isOwner) {
123
+ return membersCount > 1 ? sentPlural(postType, membersCount) : sentSingular(postType);
124
+ }
125
+ if (isPrivate) {
126
+ if (isMedia)
127
+ return { key: "post_detail_one_liner_receive_private_image_post" };
128
+ switch (postType) {
129
+ case "new:post:link":
130
+ return { key: "post_detail_one_liner_receive_private_link_post" };
131
+ case "new:post:poll":
132
+ return { key: "post_detail_one_liner_receive_private_poll_post" };
133
+ case "new:post:thread":
134
+ case "new:post:text":
135
+ default:
136
+ return { key: "post_detail_one_liner_receive_message_post" };
137
+ }
138
+ }
139
+ return membersCount > 1 ? sentPlural(postType, membersCount) : sentSingular(postType);
140
+ }
141
+ function sentPlural(postType, count) {
142
+ const params = { count };
143
+ if (MEDIA_TYPES.has(postType))
144
+ return { key: "post_detail_one_liner_send_private_image_post_plural", params };
145
+ switch (postType) {
146
+ case "new:post:link":
147
+ return {
148
+ key: "post_detail_one_liner_send_private_link_post_plural",
149
+ params
150
+ };
151
+ case "new:post:poll":
152
+ return {
153
+ key: "post_detail_one_liner_send_private_link_poll_plural",
154
+ params
155
+ };
156
+ case "new:post:thread":
157
+ case "new:post:text":
158
+ default:
159
+ return { key: "post_detail_one_liner_send_message_post_plural", params };
160
+ }
161
+ }
162
+ function sentSingular(postType) {
163
+ if (MEDIA_TYPES.has(postType))
164
+ return { key: "post_detail_one_liner_send_private_image_post_singular" };
165
+ switch (postType) {
166
+ case "new:post:link":
167
+ return { key: "post_detail_one_liner_send_private_link_post_singular" };
168
+ case "new:post:poll":
169
+ return { key: "post_detail_one_liner_send_private_link_poll_singular" };
170
+ case "new:post:thread":
171
+ case "new:post:text":
172
+ default:
173
+ return { key: "post_detail_one_liner_send_message_post_singular" };
174
+ }
175
+ }
176
+ var POST_TYPE_HINT_DEFAULTS = {
177
+ // in-group (one-liner)
178
+ post_detail_one_liner_receive_and_send_group_image_post: () => "shared a media post in group",
179
+ post_detail_one_liner_receive_and_send_group_link_post: () => "shared a link post in group",
180
+ post_detail_one_liner_receive_and_send_group_poll_post: () => "shared a poll in group",
181
+ post_detail_one_liner_receive_and_send_group_thread_post: () => "shared a thread in group",
182
+ post_detail_one_liner_receive_and_send_group_message_post: () => "shared a post in group",
183
+ // in-group (meta description)
184
+ post_detail_meta_description_type_image_post: () => "A media post",
185
+ post_detail_meta_description_type_link_post: () => "A link post",
186
+ post_detail_meta_description_type_poll_post: () => "A poll",
187
+ post_detail_meta_description_type_message_post: () => "A post",
188
+ // owner / 1:M sent — plural
189
+ post_detail_one_liner_send_private_image_post_plural: (p) => `sent a media post to ${p?.count ?? 0} people`,
190
+ post_detail_one_liner_send_private_link_post_plural: (p) => `sent a link post to ${p?.count ?? 0} people`,
191
+ post_detail_one_liner_send_private_link_poll_plural: (p) => `sent a poll to ${p?.count ?? 0} people`,
192
+ post_detail_one_liner_send_message_post_plural: (p) => `sent a post to ${p?.count ?? 0} people`,
193
+ // owner / 1:M sent — singular
194
+ post_detail_one_liner_send_private_image_post_singular: () => "sent a media post",
195
+ post_detail_one_liner_send_private_link_post_singular: () => "sent a link post",
196
+ post_detail_one_liner_send_private_link_poll_singular: () => "sent a poll",
197
+ post_detail_one_liner_send_message_post_singular: () => "sent a post",
198
+ // private 1:1 received
199
+ post_detail_one_liner_receive_private_image_post: () => "sent you a media post",
200
+ post_detail_one_liner_receive_private_link_post: () => "sent you a link post",
201
+ post_detail_one_liner_receive_private_poll_post: () => "sent you a poll",
202
+ post_detail_one_liner_receive_message_post: () => "sent you a post"
203
+ };
204
+
205
+ // src/utils/relativeTime.ts
206
+ var UNITS = [
207
+ ["year", 31536e3],
208
+ ["month", 2592e3],
209
+ ["week", 604800],
210
+ ["day", 86400],
211
+ ["hour", 3600],
212
+ ["minute", 60]
213
+ ];
214
+ function formatRelativeTime(iso, locale = "en") {
215
+ const diffMs = Date.now() - new Date(iso).getTime();
216
+ const seconds = Math.round(diffMs / 1e3);
217
+ if (Math.abs(seconds) < 60) return "just now";
218
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
219
+ for (const [unit, secs] of UNITS) {
220
+ if (Math.abs(seconds) >= secs) {
221
+ const value = Math.round(seconds / secs);
222
+ return rtf.format(-value, unit);
223
+ }
224
+ }
225
+ return "just now";
226
+ }
227
+
228
+ // src/components/PostHeader.tsx
229
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
230
+ var PostHeader = ({
231
+ author,
232
+ authorHref,
233
+ group,
234
+ groupHref,
235
+ postedAt,
236
+ postType,
237
+ isPrivate = false,
238
+ membersCount,
239
+ crossPostGroup,
240
+ locale,
241
+ translate,
242
+ isFeatured = false,
243
+ isPostPreview = false,
244
+ isAdmin = false,
245
+ isOwner = false,
246
+ onErasePress,
247
+ onCancelPost,
248
+ renderActionsMenu,
249
+ linkComponent: LinkComponent,
250
+ imageComponent,
251
+ labels
252
+ }) => {
253
+ const cancelLabel = labels?.cancelPost ?? "Cancel";
254
+ const { key: hintKey, params: hintParams } = getPostTypeHint(postType, {
255
+ isInGroup: !!group,
256
+ isOwner,
257
+ isPrivate,
258
+ membersCount
259
+ });
260
+ const hintText = translate?.(hintKey, hintParams) ?? POST_TYPE_HINT_DEFAULTS[hintKey]?.(hintParams) ?? hintKey;
261
+ const badgeVariant = isOwner ? "owner" : isAdmin ? "admin" : void 0;
262
+ const authorNameNode = isPostPreview ? /* @__PURE__ */ jsx2("span", { className: "flex text-base font-semibold leading-[100%] text-foreground", children: author.name }) : LinkComponent ? /* @__PURE__ */ jsx2(
263
+ LinkComponent,
264
+ {
265
+ href: authorHref,
266
+ className: "flex text-base font-semibold leading-[100%] text-foreground hover:opacity-70 bg-transparent border-none p-0 cursor-pointer no-underline",
267
+ children: author.name
268
+ }
269
+ ) : /* @__PURE__ */ jsx2(
270
+ "a",
271
+ {
272
+ href: authorHref,
273
+ onClick: (e) => e.stopPropagation(),
274
+ className: "flex text-base font-semibold leading-[100%] text-foreground hover:opacity-70 bg-transparent border-none p-0 cursor-pointer no-underline",
275
+ "aria-label": `View ${author.name}'s profile`,
276
+ children: author.name
277
+ }
278
+ );
279
+ const groupLinkNode = group && groupHref ? isPostPreview ? /* @__PURE__ */ jsxs("span", { className: "text-action-primary font-medium", children: [
280
+ "#",
281
+ group.name
282
+ ] }) : LinkComponent ? /* @__PURE__ */ jsxs(
283
+ LinkComponent,
284
+ {
285
+ href: groupHref,
286
+ className: "text-action-primary font-medium hover:underline cursor-pointer bg-transparent border-none p-0",
287
+ children: [
288
+ "#",
289
+ group.name
290
+ ]
291
+ }
292
+ ) : /* @__PURE__ */ jsxs(
293
+ "a",
294
+ {
295
+ href: groupHref,
296
+ onClick: (e) => e.stopPropagation(),
297
+ className: "text-action-primary font-medium hover:underline cursor-pointer bg-transparent border-none p-0",
298
+ title: group.name,
299
+ "aria-label": `View group ${group.name}`,
300
+ children: [
301
+ "#",
302
+ group.name
303
+ ]
304
+ }
305
+ ) : null;
306
+ return /* @__PURE__ */ jsxs("section", { className: "not-prose relative flex flex-col justify-start p-4 pb-3", children: [
307
+ /* @__PURE__ */ jsxs("section", { className: "flex items-center gap-3", children: [
308
+ /* @__PURE__ */ jsxs(
309
+ Avatar,
310
+ {
311
+ size: "md",
312
+ disabled: isPostPreview,
313
+ className: "shrink-0 ring-1 ring-border-subtle",
314
+ children: [
315
+ /* @__PURE__ */ jsx2(
316
+ AvatarImage,
317
+ {
318
+ src: author.avatar,
319
+ alt: `${author.name}'s profile picture`,
320
+ placeholder: author.avatarPlaceholder,
321
+ imageComponent
322
+ }
323
+ ),
324
+ /* @__PURE__ */ jsx2(AvatarFallback, { name: author.name }),
325
+ badgeVariant && /* @__PURE__ */ jsx2(AvatarBadge, { variant: badgeVariant })
326
+ ]
327
+ }
328
+ ),
329
+ /* @__PURE__ */ jsxs("section", { className: "flex flex-1 flex-col justify-center min-w-0", children: [
330
+ /* @__PURE__ */ jsx2("section", { className: "flex items-baseline justify-between gap-2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 min-w-0", children: [
331
+ /* @__PURE__ */ jsx2("div", { className: "font-semibold text-foreground truncate", children: authorNameNode }),
332
+ isFeatured && /* @__PURE__ */ jsx2(
333
+ "svg",
334
+ {
335
+ xmlns: "http://www.w3.org/2000/svg",
336
+ viewBox: "0 0 24 24",
337
+ width: 14,
338
+ height: 14,
339
+ className: "text-highlight fill-highlight shrink-0",
340
+ "aria-label": "Featured post",
341
+ children: /* @__PURE__ */ jsx2("path", { d: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" })
342
+ }
343
+ ),
344
+ postedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
345
+ /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground font-bold shrink-0", children: "\xB7" }),
346
+ /* @__PURE__ */ jsx2(
347
+ "time",
348
+ {
349
+ title: postedAt,
350
+ className: "text-xs text-muted-foreground whitespace-nowrap shrink-0",
351
+ dateTime: postedAt,
352
+ children: formatRelativeTime(postedAt, locale ?? "en")
353
+ }
354
+ )
355
+ ] })
356
+ ] }) }),
357
+ /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground truncate mt-0.5", children: [
358
+ /* @__PURE__ */ jsx2("span", { children: hintText }),
359
+ groupLinkNode && /* @__PURE__ */ jsxs(Fragment, { children: [
360
+ /* @__PURE__ */ jsx2("span", { className: "mx-1" }),
361
+ groupLinkNode
362
+ ] }),
363
+ crossPostGroup && /* @__PURE__ */ jsxs(Fragment, { children: [
364
+ /* @__PURE__ */ jsx2("span", { className: "mx-1", children: "via" }),
365
+ LinkComponent && crossPostGroup.href ? /* @__PURE__ */ jsxs(
366
+ LinkComponent,
367
+ {
368
+ href: crossPostGroup.href,
369
+ className: "text-action-primary font-medium hover:underline no-underline",
370
+ children: [
371
+ "#",
372
+ crossPostGroup.name
373
+ ]
374
+ }
375
+ ) : crossPostGroup.href ? /* @__PURE__ */ jsxs(
376
+ "a",
377
+ {
378
+ href: crossPostGroup.href,
379
+ onClick: (e) => e.stopPropagation(),
380
+ className: "text-action-primary font-medium hover:underline no-underline",
381
+ children: [
382
+ "#",
383
+ crossPostGroup.name
384
+ ]
385
+ }
386
+ ) : /* @__PURE__ */ jsxs("span", { className: "text-action-primary font-medium", children: [
387
+ "#",
388
+ crossPostGroup.name
389
+ ] })
390
+ ] })
391
+ ] })
392
+ ] })
393
+ ] }),
394
+ isPostPreview ? onCancelPost && /* @__PURE__ */ jsx2(
395
+ "button",
396
+ {
397
+ onClick: (e) => {
398
+ e.stopPropagation();
399
+ onCancelPost();
400
+ },
401
+ "aria-label": cancelLabel,
402
+ "data-testid": "cancel-pending-post",
403
+ className: "absolute right-4 top-4 leading-none rounded-full p-1.5 transition-colors hover:bg-feedback-danger/10 hover:text-feedback-danger",
404
+ children: /* @__PURE__ */ jsx2(AppIcon, { name: "cancel", width: 16, height: 16, strokeWidth: 1.75 })
405
+ }
406
+ ) : renderActionsMenu ? /* @__PURE__ */ jsx2("div", { className: "absolute right-2 top-2", children: renderActionsMenu() }) : onErasePress ? /* @__PURE__ */ jsx2(
407
+ "button",
408
+ {
409
+ onClick: (e) => {
410
+ e.stopPropagation();
411
+ onErasePress();
412
+ },
413
+ "aria-label": "Delete post",
414
+ className: "absolute right-4 top-4 leading-none rounded-full p-1.5 transition-colors hover:bg-feedback-danger/10 hover:text-feedback-danger",
415
+ children: /* @__PURE__ */ jsx2(AppIcon, { name: "chatErase", width: 16, height: 16, strokeWidth: 1.75 })
416
+ }
417
+ ) : null
418
+ ] });
419
+ };
420
+ var PostHeader_default = PostHeader;
421
+
422
+ // src/components/AutoLink.tsx
423
+ import { useCallback, useMemo } from "react";
424
+ import Autolinker from "autolinker";
425
+ import { truncateSmart } from "autolinker/dist/commonjs/truncate/truncate-smart";
426
+ import { truncateEnd } from "autolinker/dist/commonjs/truncate/truncate-end";
427
+ import { truncateMiddle } from "autolinker/dist/commonjs/truncate/truncate-middle";
428
+ import emojiRegex from "emoji-regex";
429
+ import { jsx as jsx3 } from "react/jsx-runtime";
430
+ var MENTION_TYPE = {
431
+ MENTION: "mention",
432
+ HASHTAG: "hashtag"
433
+ };
434
+ var EMOJI_REGEX = emojiRegex();
435
+ var getPureEmojiSize = (body) => {
436
+ let match;
437
+ let emojiCount = 0;
438
+ let emojiSize = 0;
439
+ while (match = EMOJI_REGEX.exec(body)) {
440
+ const emoji = match[0];
441
+ emojiSize += emoji.length;
442
+ emojiCount += 1;
443
+ }
444
+ if (emojiSize === body.length) {
445
+ return emojiCount;
446
+ }
447
+ return 0;
448
+ };
449
+ var checkMentionDataIndex = (mentionData = [], copyMentionData = [], body = "") => {
450
+ let mentionBody = body;
451
+ const newMentionData = [...mentionData, ...copyMentionData];
452
+ let lastMentionData = { index: 0, endIndex: 0 };
453
+ const formatNewMention = [];
454
+ for (let i = 0; i < newMentionData.length; i++) {
455
+ const mention = { ...newMentionData[i] };
456
+ if (mention.name && typeof mention.name === "string") {
457
+ mention.name = mention.name.trim();
458
+ } else {
459
+ continue;
460
+ }
461
+ if (mentionBody.indexOf(mention.name) === -1) {
462
+ continue;
463
+ }
464
+ const type = mention.type;
465
+ const name = (type === MENTION_TYPE.MENTION ? "@" : "#") + mention.name;
466
+ const index = mentionBody.indexOf(name) + lastMentionData.endIndex;
467
+ const endIndex = index + name.length;
468
+ mentionBody = body.substr(endIndex, body.length);
469
+ lastMentionData = { index, endIndex };
470
+ formatNewMention.push({
471
+ name,
472
+ type,
473
+ index,
474
+ endIndex,
475
+ data: mention
476
+ });
477
+ }
478
+ return formatNewMention;
479
+ };
480
+ var isLinkInternal = (url, supportDeepviewDomain) => {
481
+ if (!url) return false;
482
+ return supportDeepviewDomain.length > 0 && url.includes(supportDeepviewDomain);
483
+ };
484
+ var getUrl = (match, phone) => {
485
+ const type = match.getType();
486
+ switch (type) {
487
+ case "email": {
488
+ return [`mailto:${encodeURIComponent(match.getEmail())}`];
489
+ }
490
+ case "phone": {
491
+ const number = match.getNumber();
492
+ switch (phone) {
493
+ case "sms":
494
+ case "text":
495
+ return [`sms:${number}`];
496
+ default:
497
+ return [`tel:${number}`];
498
+ }
499
+ }
500
+ case "url": {
501
+ return [match.getAnchorHref()];
502
+ }
503
+ default: {
504
+ return [match.getMatchedText()];
505
+ }
506
+ }
507
+ };
508
+ var truncate = (text, { truncate: len = 32, truncateChars = "..", truncateLocation = "smart" } = {}) => {
509
+ let fn;
510
+ switch (truncateLocation) {
511
+ case "end":
512
+ fn = truncateEnd;
513
+ break;
514
+ case "middle":
515
+ fn = truncateMiddle;
516
+ break;
517
+ default:
518
+ fn = truncateSmart;
519
+ }
520
+ return fn(text, len, truncateChars);
521
+ };
522
+ var AutoLink = ({
523
+ text: textProp,
524
+ mentionData = [],
525
+ onNavigate,
526
+ supportDeepviewDomain = "",
527
+ phone = true,
528
+ email = true,
529
+ stripPrefix = false,
530
+ style = { fontSize: 12 },
531
+ mentionClassName: mentionClassNameProp,
532
+ className
533
+ }) => {
534
+ const mentionClassName = useMemo(
535
+ () => mentionClassNameProp || "font-bold text-action-primary",
536
+ [mentionClassNameProp]
537
+ );
538
+ const handleNavigate = useCallback(
539
+ (href, internal) => {
540
+ if (onNavigate) {
541
+ onNavigate(href, internal);
542
+ } else if (internal) {
543
+ window.location.href = href;
544
+ } else {
545
+ window.open(href, "_blank");
546
+ }
547
+ },
548
+ [onNavigate]
549
+ );
550
+ const renderDefaultLink = useCallback(
551
+ (text, match, index) => {
552
+ const [url] = getUrl(match, phone);
553
+ const internal = isLinkInternal(url, supportDeepviewDomain);
554
+ return /* @__PURE__ */ jsx3(
555
+ "a",
556
+ {
557
+ target: "_blank",
558
+ rel: `noreferrer noopener${!internal ? " nofollow" : ""}`,
559
+ href: url,
560
+ className: "items-center text-link",
561
+ onClick: (e) => {
562
+ e.stopPropagation();
563
+ handleNavigate(url, internal);
564
+ },
565
+ children: truncate(text, { truncate: 27, truncateLocation: "end" })
566
+ },
567
+ `${index}-autolink`
568
+ );
569
+ },
570
+ [phone, supportDeepviewDomain, handleNavigate]
571
+ );
572
+ const renderLink = useCallback(
573
+ (text, match, index) => {
574
+ const [url] = getUrl(match, phone);
575
+ if (supportDeepviewDomain && url.includes(supportDeepviewDomain)) {
576
+ const internal = true;
577
+ return /* @__PURE__ */ jsx3(
578
+ "a",
579
+ {
580
+ href: url,
581
+ className: "items-center text-link",
582
+ style: { alignSelf: "center" },
583
+ onClick: (e) => {
584
+ e.stopPropagation();
585
+ handleNavigate(url, internal);
586
+ },
587
+ children: text
588
+ },
589
+ `${index}-deeplink`
590
+ );
591
+ }
592
+ return renderDefaultLink(text, match, index);
593
+ },
594
+ [phone, supportDeepviewDomain, renderDefaultLink, handleNavigate]
595
+ );
596
+ const renderMentionData = useCallback(
597
+ (fmData, key) => {
598
+ const href = fmData.type === MENTION_TYPE.HASHTAG ? `/groups/${fmData.data.selectId}` : `/users/${fmData.data.selectId || fmData.data.id}`;
599
+ return /* @__PURE__ */ jsx3(
600
+ "a",
601
+ {
602
+ className: `context-menu-ios-disable cursor-pointer ${mentionClassName}`,
603
+ href,
604
+ onClick: (e) => {
605
+ e.stopPropagation();
606
+ e.nativeEvent.stopImmediatePropagation();
607
+ handleNavigate(href, true);
608
+ },
609
+ children: fmData.name
610
+ },
611
+ key + fmData.index
612
+ );
613
+ },
614
+ [mentionClassName, handleNavigate]
615
+ );
616
+ const renderTextNodes = useCallback(() => {
617
+ let text = textProp;
618
+ const uid = Math.floor(Math.random() * 1099511627776).toString(16);
619
+ const tokenRegexp = new RegExp(`(@__ELEMENT-${uid}-\\d+__@)`, "g");
620
+ const generateToken = /* @__PURE__ */ (() => {
621
+ let counter = 0;
622
+ return () => `@__ELEMENT-${uid}-${counter++}__@`;
623
+ })();
624
+ const matches = {};
625
+ try {
626
+ text = Autolinker.link(text || "", {
627
+ email,
628
+ phone,
629
+ urls: {
630
+ schemeMatches: true,
631
+ wwwMatches: true,
632
+ tldMatches: true
633
+ },
634
+ stripPrefix,
635
+ replaceFn: (match) => {
636
+ const token = generateToken();
637
+ matches[token] = match;
638
+ return token;
639
+ }
640
+ });
641
+ } catch {
642
+ return null;
643
+ }
644
+ const newNodes = [];
645
+ text.split(tokenRegexp).filter((part) => !!part).forEach((part, index) => {
646
+ const match = matches[part];
647
+ if (!match) {
648
+ if (!mentionData || mentionData.length === 0) {
649
+ newNodes.push(part);
650
+ } else {
651
+ const newMentionData = checkMentionDataIndex(mentionData, [], part);
652
+ if (!newMentionData || newMentionData.length === 0) {
653
+ newNodes.push(part);
654
+ return;
655
+ }
656
+ for (let j = 0; j < newMentionData.length; j++) {
657
+ const curMentionData = newMentionData[j];
658
+ if (j === 0) {
659
+ const word = part.substring(0, curMentionData.index);
660
+ newNodes.push(word);
661
+ }
662
+ newNodes.push(renderMentionData(curMentionData, part + index));
663
+ const nextMentionData = j < newMentionData.length ? newMentionData[j + 1] : null;
664
+ if (nextMentionData) {
665
+ const word = part.substring(curMentionData.endIndex, nextMentionData.index);
666
+ newNodes.push(word);
667
+ } else {
668
+ const word = part.substring(curMentionData.endIndex, part.length);
669
+ newNodes.push(word);
670
+ }
671
+ }
672
+ }
673
+ return;
674
+ }
675
+ switch (match.getType()) {
676
+ case "email":
677
+ case "phone":
678
+ case "url":
679
+ newNodes.push(renderLink(match.getAnchorText(), match, index));
680
+ break;
681
+ default:
682
+ newNodes.push(part);
683
+ break;
684
+ }
685
+ });
686
+ let textStyle = style;
687
+ const fontSize = style && style.fontSize ? style.fontSize : 12;
688
+ const pureEmojiCount = getPureEmojiSize(text.trim());
689
+ if (pureEmojiCount === 1) {
690
+ textStyle = { ...style, fontSize: fontSize * 2.5 };
691
+ } else if (pureEmojiCount === 2) {
692
+ textStyle = { ...style, fontSize: fontSize * 2 };
693
+ } else if (pureEmojiCount === 3) {
694
+ textStyle = { ...style, fontSize: fontSize * 1.5 };
695
+ }
696
+ return /* @__PURE__ */ jsx3("div", { style: textStyle, className, children: newNodes });
697
+ }, [
698
+ style,
699
+ email,
700
+ phone,
701
+ stripPrefix,
702
+ mentionData,
703
+ textProp,
704
+ className,
705
+ renderLink,
706
+ renderMentionData
707
+ ]);
708
+ return renderTextNodes();
709
+ };
710
+ AutoLink.truncate = truncate;
711
+ var AutoLink_default = AutoLink;
712
+
713
+ // src/components/PostBody.tsx
714
+ import DOMPurify2 from "dompurify";
715
+ import { useMemo as useMemo3 } from "react";
716
+
717
+ // src/components/PostEmail.tsx
718
+ import { useEffect, useMemo as useMemo2, useRef } from "react";
719
+ import DOMPurify from "dompurify";
720
+ import { jsx as jsx4 } from "react/jsx-runtime";
721
+ var EMAIL_PURIFY_CONFIG = {
722
+ ALLOWED_TAGS: [
723
+ // Structure
724
+ "html",
725
+ "head",
726
+ "body",
727
+ "style",
728
+ // Layout (email is table-based)
729
+ "table",
730
+ "thead",
731
+ "tbody",
732
+ "tr",
733
+ "th",
734
+ "td",
735
+ // Text
736
+ "p",
737
+ "span",
738
+ "div",
739
+ "h1",
740
+ "h2",
741
+ "h3",
742
+ "h4",
743
+ "h5",
744
+ "h6",
745
+ "b",
746
+ "strong",
747
+ "i",
748
+ "em",
749
+ "u",
750
+ "sub",
751
+ "sup",
752
+ "br",
753
+ "ul",
754
+ "ol",
755
+ "li",
756
+ // Media & links
757
+ "a",
758
+ "img"
759
+ ],
760
+ ALLOWED_ATTR: [
761
+ // Styling — critical for email
762
+ "style",
763
+ "class",
764
+ "bgcolor",
765
+ "color",
766
+ // Table layout attributes (email-specific)
767
+ "width",
768
+ "height",
769
+ "align",
770
+ "valign",
771
+ "cellpadding",
772
+ "cellspacing",
773
+ "border",
774
+ "colspan",
775
+ "rowspan",
776
+ // Images
777
+ "src",
778
+ "alt",
779
+ // Links
780
+ "href",
781
+ "target",
782
+ "rel",
783
+ // Accessibility
784
+ "role",
785
+ "aria-label",
786
+ "aria-roledescription",
787
+ // Email-specific
788
+ "data-open-tracking",
789
+ "data-read-online-tooltip"
790
+ ],
791
+ ALLOW_DATA_ATTR: false,
792
+ FORCE_BODY: false,
793
+ WHOLE_DOCUMENT: true
794
+ // preserves <head> with <style>
795
+ };
796
+ var sanitizeEmailHtml = (rawHtml) => {
797
+ const clean = DOMPurify.sanitize(rawHtml, EMAIL_PURIFY_CONFIG);
798
+ const doc = new DOMParser().parseFromString(clean, "text/html");
799
+ if (!doc.head) {
800
+ const head = doc.createElement("head");
801
+ doc.documentElement.insertBefore(head, doc.body);
802
+ }
803
+ const base = doc.createElement("base");
804
+ base.setAttribute("target", "_blank");
805
+ doc.head.appendChild(base);
806
+ const style = doc.createElement("style");
807
+ style.textContent = `
808
+ html, body {
809
+ margin: 0;
810
+ padding: 0;
811
+ }
812
+
813
+ @media only screen and (max-width: 640px) {
814
+ table[style*="min-width"] {
815
+ min-width: 0 !important;
816
+ width: 100% !important;
817
+ max-width: 100% !important;
818
+ }
819
+
820
+ td[style*="min-width"],
821
+ th[style*="min-width"],
822
+ div[style*="min-width"] {
823
+ min-width: 0 !important;
824
+ max-width: 100% !important;
825
+ box-sizing: border-box !important;
826
+ }
827
+
828
+ img {
829
+ max-width: 100% !important;
830
+ height: auto !important;
831
+ }
832
+ }`;
833
+ doc.head.appendChild(style);
834
+ doc.querySelectorAll("a[href]").forEach((a) => {
835
+ a.setAttribute("target", "_blank");
836
+ a.setAttribute("rel", "noopener noreferrer nofollow");
837
+ });
838
+ return "<!DOCTYPE html>\n" + doc.documentElement.outerHTML;
839
+ };
840
+ var PostEmail = ({ html }) => {
841
+ const ref = useRef(null);
842
+ const cleanHtml = useMemo2(() => sanitizeEmailHtml(html), [html]);
843
+ useEffect(() => {
844
+ const iframe = ref.current;
845
+ if (!iframe) return;
846
+ let resizeObserver = null;
847
+ let rafId = null;
848
+ let timeoutIds = [];
849
+ const resizeIframe = () => {
850
+ try {
851
+ const doc = iframe.contentDocument;
852
+ if (!doc) return;
853
+ const body = doc.body;
854
+ const height = Math.max(
855
+ Math.ceil(body.scrollHeight),
856
+ Math.ceil(body.offsetHeight),
857
+ Math.ceil(body.getBoundingClientRect().height)
858
+ );
859
+ iframe.style.height = `${height + 2}px`;
860
+ } catch {
861
+ }
862
+ };
863
+ const scheduleResize = () => {
864
+ if (rafId) cancelAnimationFrame(rafId);
865
+ rafId = requestAnimationFrame(resizeIframe);
866
+ };
867
+ const handleLoad = () => {
868
+ scheduleResize();
869
+ try {
870
+ const doc = iframe.contentDocument;
871
+ if (!doc) return;
872
+ Array.from(doc.images).forEach((img) => {
873
+ img.addEventListener("load", scheduleResize);
874
+ img.addEventListener("error", scheduleResize);
875
+ });
876
+ resizeObserver = new ResizeObserver(() => {
877
+ scheduleResize();
878
+ });
879
+ resizeObserver.observe(doc.body);
880
+ timeoutIds = [
881
+ window.setTimeout(scheduleResize, 50),
882
+ window.setTimeout(scheduleResize, 150),
883
+ window.setTimeout(scheduleResize, 300),
884
+ window.setTimeout(scheduleResize, 600)
885
+ ];
886
+ } catch {
887
+ }
888
+ };
889
+ const handleResize = () => {
890
+ scheduleResize();
891
+ };
892
+ iframe.addEventListener("load", handleLoad);
893
+ window.addEventListener("resize", handleResize);
894
+ return () => {
895
+ iframe.removeEventListener("load", handleLoad);
896
+ window.removeEventListener("resize", handleResize);
897
+ if (rafId) cancelAnimationFrame(rafId);
898
+ timeoutIds.forEach(clearTimeout);
899
+ resizeObserver?.disconnect();
900
+ try {
901
+ const doc = iframe.contentDocument;
902
+ if (!doc) return;
903
+ Array.from(doc.images).forEach((img) => {
904
+ img.removeEventListener("load", scheduleResize);
905
+ img.removeEventListener("error", scheduleResize);
906
+ });
907
+ } catch {
908
+ }
909
+ };
910
+ }, [cleanHtml]);
911
+ return /* @__PURE__ */ jsx4(
912
+ "iframe",
913
+ {
914
+ ref,
915
+ srcDoc: cleanHtml,
916
+ sandbox: "allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation",
917
+ className: "block w-full border-0"
918
+ }
919
+ );
920
+ };
921
+ var PostEmail_default = PostEmail;
922
+
923
+ // src/components/PostBody.tsx
924
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
925
+ var PURIFY_CONFIG = {
926
+ ALLOWED_TAGS: [
927
+ "p",
928
+ "br",
929
+ "b",
930
+ "i",
931
+ "em",
932
+ "strong",
933
+ "a",
934
+ "ul",
935
+ "ol",
936
+ "li",
937
+ "h1",
938
+ "h2",
939
+ "h3",
940
+ "h4",
941
+ "h5",
942
+ "h6",
943
+ "blockquote",
944
+ "code",
945
+ "pre",
946
+ "span",
947
+ "div",
948
+ "img",
949
+ "video",
950
+ "figure",
951
+ "figcaption",
952
+ "table",
953
+ "thead",
954
+ "tbody",
955
+ "tr",
956
+ "th",
957
+ "td",
958
+ "hr",
959
+ "sub",
960
+ "sup",
961
+ "s",
962
+ "u"
963
+ ],
964
+ ALLOWED_ATTR: [
965
+ "href",
966
+ "controls",
967
+ "target",
968
+ "rel",
969
+ "class",
970
+ "id",
971
+ "src",
972
+ "alt",
973
+ "title",
974
+ "width",
975
+ "height",
976
+ "data-*"
977
+ ],
978
+ ALLOW_DATA_ATTR: true,
979
+ ADD_ATTR: ["target"],
980
+ FORBID_TAGS: ["script", "style", "iframe", "object", "embed", "form"],
981
+ FORBID_ATTR: ["onerror", "onload", "onclick", "onmouseover"]
982
+ };
983
+ var VOID_TAGS = /* @__PURE__ */ new Set([
984
+ "BR",
985
+ "HR",
986
+ "IMG",
987
+ "VIDEO",
988
+ "INPUT",
989
+ "AREA",
990
+ "BASE",
991
+ "COL",
992
+ "LINK",
993
+ "META",
994
+ "PARAM",
995
+ "SOURCE",
996
+ "TRACK",
997
+ "WBR"
998
+ ]);
999
+ var removeEmpty = (el) => {
1000
+ Array.from(el.children).forEach(removeEmpty);
1001
+ if (VOID_TAGS.has(el.tagName)) return;
1002
+ if (!el.textContent?.trim() && el.children.length === 0) el.remove();
1003
+ };
1004
+ var PostBody = ({
1005
+ textContent,
1006
+ showFull = true,
1007
+ isPostDetail,
1008
+ isRichContent = false,
1009
+ hideEmailDescription = false,
1010
+ isEmailPost = false,
1011
+ onNavigate,
1012
+ supportDeepviewDomain
1013
+ }) => {
1014
+ const { plain, html, truncatedPlain, mentionData } = textContent;
1015
+ const { sanitizedHtml, feedSanitizedHtml } = useMemo3(() => {
1016
+ if (!html) return { sanitizedHtml: null, feedSanitizedHtml: null };
1017
+ const sanitized = DOMPurify2.sanitize(html, PURIFY_CONFIG);
1018
+ if (typeof document === "undefined") {
1019
+ return { sanitizedHtml: sanitized, feedSanitizedHtml: sanitized };
1020
+ }
1021
+ const feedContainer = document.createElement("div");
1022
+ feedContainer.innerHTML = sanitized;
1023
+ let keptOne = false;
1024
+ feedContainer.querySelectorAll("img, video").forEach((el) => {
1025
+ if (!keptOne) {
1026
+ keptOne = true;
1027
+ } else {
1028
+ el.remove();
1029
+ }
1030
+ });
1031
+ removeEmpty(feedContainer);
1032
+ return {
1033
+ sanitizedHtml: sanitized,
1034
+ feedSanitizedHtml: feedContainer.innerHTML
1035
+ };
1036
+ }, [html]);
1037
+ const { subject } = textContent;
1038
+ const displayText = showFull ? plain : truncatedPlain;
1039
+ const hasHtml = !!sanitizedHtml;
1040
+ const hasMeaningfulHtmlText = hasMeaningfulHtml(sanitizedHtml);
1041
+ const detailProseElement = useMemo3(
1042
+ () => sanitizedHtml ? /* @__PURE__ */ jsx5(
1043
+ "div",
1044
+ {
1045
+ className: "prose prose-p:my-2! prose-a:text-action-primary! prose-a:no-underline! prose-img:my-4! prose-video:my-4! px-5! pb-4! max-w-full leading-relaxed overflow-hidden",
1046
+ dangerouslySetInnerHTML: { __html: sanitizedHtml }
1047
+ }
1048
+ ) : null,
1049
+ [sanitizedHtml]
1050
+ );
1051
+ const feedProseElement = useMemo3(
1052
+ () => feedSanitizedHtml ? /* @__PURE__ */ jsx5(
1053
+ "div",
1054
+ {
1055
+ className: "prose *:px-4! *:data-[media='true']:px-0! *:data-link-preview:mx-4! [&>[data-media='true']>img]:rounded-none! [&>[data-media='true']>video]:rounded-none! [&>[data-media='true']>video]:size-full! prose-video:size-full! *:data-[media='true']:my-2 [&>div]:last-of-type:mb-0! prose-img:my-0! prose-img:w-full prose-video:my-0! prose-p:my-2! prose-a:text-action-primary! prose-a:no-underline! pb-4 max-w-full leading-relaxed line-clamp-4 overflow-hidden",
1056
+ dangerouslySetInnerHTML: { __html: feedSanitizedHtml }
1057
+ }
1058
+ ) : null,
1059
+ [feedSanitizedHtml]
1060
+ );
1061
+ if ((!displayText || typeof displayText !== "string") && !hasHtml) {
1062
+ return null;
1063
+ }
1064
+ const renderAutoLink = () => {
1065
+ if (!displayText || typeof displayText !== "string") return null;
1066
+ return /* @__PURE__ */ jsx5(
1067
+ AutoLink_default,
1068
+ {
1069
+ text: displayText,
1070
+ mentionData,
1071
+ onNavigate,
1072
+ supportDeepviewDomain,
1073
+ className: "whitespace-normal px-4 pb-4 leading-relaxed text-gray-800",
1074
+ style: {
1075
+ whiteSpace: "pre-wrap",
1076
+ wordBreak: "break-word"
1077
+ }
1078
+ }
1079
+ );
1080
+ };
1081
+ if (isPostDetail) {
1082
+ if (isEmailPost && html) {
1083
+ return /* @__PURE__ */ jsxs2("div", { className: "relative flex-1 max-h-screen w-full overflow-x-hidden overflow-y-auto xl:px-0 px-5", children: [
1084
+ /* @__PURE__ */ jsx5("h1", { className: "text-2xl font-semibold text-gray-900 leading-snug mt-5 mb-3 px-5", children: subject }),
1085
+ /* @__PURE__ */ jsx5(PostEmail_default, { html })
1086
+ ] });
1087
+ }
1088
+ return /* @__PURE__ */ jsxs2("div", { className: "flex-1", children: [
1089
+ !hasMeaningfulHtmlText && /* @__PURE__ */ jsx5("div", { children: renderAutoLink() }),
1090
+ sanitizedHtml && hasMeaningfulHtmlText && /* @__PURE__ */ jsx5("div", { "data-theme": "select-post-builder", className: "w-full max-w-full", children: detailProseElement })
1091
+ ] });
1092
+ }
1093
+ if (isRichContent && feedSanitizedHtml && hasMeaningfulHtmlText) {
1094
+ return /* @__PURE__ */ jsx5("div", { className: cn("flex-1 pb-4"), "data-theme": "select-post-builder", children: feedProseElement });
1095
+ }
1096
+ if (subject) {
1097
+ return /* @__PURE__ */ jsxs2("div", { className: "flex-1 px-4 pb-4", children: [
1098
+ /* @__PURE__ */ jsx5("h1", { className: "text-2xl font-semibold text-gray-900 leading-snug mb-1", children: subject }),
1099
+ !hideEmailDescription && displayText && typeof displayText === "string" && /* @__PURE__ */ jsx5(
1100
+ AutoLink_default,
1101
+ {
1102
+ text: displayText,
1103
+ mentionData,
1104
+ onNavigate,
1105
+ supportDeepviewDomain,
1106
+ className: "whitespace-normal leading-relaxed text-gray-600 line-clamp-3",
1107
+ style: { whiteSpace: "pre-wrap", wordBreak: "break-word" }
1108
+ }
1109
+ )
1110
+ ] });
1111
+ }
1112
+ return /* @__PURE__ */ jsx5("div", { className: "flex-1", children: /* @__PURE__ */ jsx5("div", { children: renderAutoLink() }) });
1113
+ };
1114
+ var PostBody_default = PostBody;
1115
+
1116
+ // src/components/MediaView.tsx
1117
+ import { useMemo as useMemo5 } from "react";
1118
+
1119
+ // src/components/AsyncImage.tsx
1120
+ import { useCallback as useCallback2, useMemo as useMemo4 } from "react";
1121
+ import { Gallery, Item } from "react-photoswipe-gallery";
1122
+ import "photoswipe/dist/photoswipe.css";
1123
+
1124
+ // src/components/Loader.tsx
1125
+ import { jsx as jsx6 } from "react/jsx-runtime";
1126
+ var Loader = ({
1127
+ type = "page_loader",
1128
+ fullScreen = false
1129
+ }) => {
1130
+ const style = {
1131
+ page_loader: "after:border-border after:border-t-action-primary",
1132
+ upload_loader: "after:border-foreground after:border-t-background bg-foreground rounded-full"
1133
+ };
1134
+ const showLoader = () => {
1135
+ return /* @__PURE__ */ jsx6("div", { className: "absolute left-1/2 top-1/2 z-20 -translate-x-1/2 -translate-y-1/2", children: /* @__PURE__ */ jsx6(
1136
+ "div",
1137
+ {
1138
+ className: `after:contents-[''] flex h-full items-center justify-center after:h-10 after:w-10 after:animate-spin after:rounded-[50%] after:border-4 ${style[type]} `
1139
+ }
1140
+ ) });
1141
+ };
1142
+ if (fullScreen) {
1143
+ return /* @__PURE__ */ jsx6("div", { className: "h-screen-safe-14 lg:h-screen-safe-16", children: showLoader() });
1144
+ }
1145
+ return showLoader();
1146
+ };
1147
+ var Loader_default = Loader;
1148
+
1149
+ // src/components/AsyncImage.tsx
1150
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1151
+ var isVideo = (url) => {
1152
+ return url?.toLowerCase().endsWith(".mp4") || url?.toLowerCase().endsWith(".webm") || url?.toLowerCase().endsWith(".ogg");
1153
+ };
1154
+ var isImage = (url) => {
1155
+ return url?.toLowerCase().endsWith(".jpeg") || url?.toLowerCase().endsWith(".jpg") || url?.toLowerCase().endsWith(".png") || url?.toLowerCase().endsWith(".webp") || url?.toLowerCase().endsWith(".gif") || url?.toLowerCase().endsWith(".avif");
1156
+ };
1157
+ var calVideoRatio = (width, height) => {
1158
+ let ratio = 1;
1159
+ if (width > 0 && height > 0) {
1160
+ ratio = width / height;
1161
+ if (ratio < 0.7) {
1162
+ ratio = 0.7;
1163
+ }
1164
+ }
1165
+ return `${ratio}`;
1166
+ };
1167
+ var AsyncImage = ({
1168
+ data,
1169
+ ratio,
1170
+ className,
1171
+ isPostDetail,
1172
+ item,
1173
+ onCardPress,
1174
+ src = "",
1175
+ alt
1176
+ }) => {
1177
+ const {
1178
+ placeholder = {
1179
+ isBase64: false,
1180
+ url: ""
1181
+ }
1182
+ } = item.sources || {};
1183
+ const placeholderUrl = useMemo4(() => {
1184
+ const url = placeholder.isBase64 ? `data:${item.imageType};base64,${placeholder.url}` : placeholder.url;
1185
+ return url;
1186
+ }, [placeholder.isBase64, placeholder.url, item.imageType]);
1187
+ const handleClick = useCallback2(
1188
+ (e) => {
1189
+ e.stopPropagation();
1190
+ if (onCardPress) {
1191
+ onCardPress();
1192
+ }
1193
+ },
1194
+ [onCardPress]
1195
+ );
1196
+ const showUploadImg = (item2) => {
1197
+ return item2.imageType === "image/heic" ? /* @__PURE__ */ jsx7(
1198
+ "img",
1199
+ {
1200
+ src,
1201
+ alt: alt ?? "",
1202
+ className,
1203
+ style: {
1204
+ aspectRatio: `${ratio}` || "auto"
1205
+ }
1206
+ }
1207
+ ) : /* @__PURE__ */ jsx7(
1208
+ "div",
1209
+ {
1210
+ className,
1211
+ style: {
1212
+ aspectRatio: `${ratio}` || "auto"
1213
+ }
1214
+ }
1215
+ );
1216
+ };
1217
+ const uploadVideoPost = item.type.includes("ToUpload");
1218
+ const showUploadingMedia = () => /* @__PURE__ */ jsxs3(
1219
+ "div",
1220
+ {
1221
+ className: "relative h-full w-full",
1222
+ style: {
1223
+ aspectRatio: `${ratio}` || "auto"
1224
+ },
1225
+ children: [
1226
+ uploadVideoPost && /* @__PURE__ */ jsx7(Loader_default, { type: "upload_loader" }),
1227
+ /* @__PURE__ */ jsx7(
1228
+ "video",
1229
+ {
1230
+ title: "Video Upload Preview",
1231
+ width: "100%",
1232
+ height: "100%",
1233
+ className,
1234
+ style: {
1235
+ aspectRatio: `${ratio}` || "auto"
1236
+ },
1237
+ children: item.file instanceof Blob && /* @__PURE__ */ jsx7("source", { src: URL.createObjectURL(item.file), type: item.file.type })
1238
+ }
1239
+ ),
1240
+ /* @__PURE__ */ jsx7(
1241
+ AppIcon,
1242
+ {
1243
+ name: "video",
1244
+ className: `absolute bottom-2 left-2 text-background`
1245
+ }
1246
+ )
1247
+ ]
1248
+ }
1249
+ );
1250
+ const renderMediaItem = useCallback2(
1251
+ (media) => {
1252
+ const originalInfo = media?.sources?.original || {};
1253
+ let thumbnailInfo = media?.sources?.thumbnail;
1254
+ if (originalInfo?.url?.endsWith("gif") || !isImage(media?.sources?.thumbnail?.url)) {
1255
+ thumbnailInfo = originalInfo;
1256
+ }
1257
+ const aspectRatio = `${ratio}` || calVideoRatio(originalInfo.width, originalInfo.height) || "auto";
1258
+ const hasStableKeys = Boolean(media.key && item.key);
1259
+ const isCurrentItem = hasStableKeys ? media.key === item.key : originalInfo.url === src;
1260
+ const display = isCurrentItem ? "block" : "none";
1261
+ const isBlobVideo = originalInfo?.url?.startsWith("blob:") && typeof media.imageType === "string" && media.imageType.startsWith("video/");
1262
+ if (isBlobVideo) {
1263
+ return /* @__PURE__ */ jsxs3(
1264
+ "div",
1265
+ {
1266
+ onClick: handleClick,
1267
+ className: className + " media-item",
1268
+ style: { aspectRatio, display },
1269
+ children: [
1270
+ /* @__PURE__ */ jsx7(
1271
+ "video",
1272
+ {
1273
+ src: originalInfo.url,
1274
+ muted: true,
1275
+ playsInline: true,
1276
+ preload: "metadata",
1277
+ className,
1278
+ style: {
1279
+ aspectRatio,
1280
+ display: "block",
1281
+ width: "100%",
1282
+ height: "100%"
1283
+ }
1284
+ }
1285
+ ),
1286
+ /* @__PURE__ */ jsx7(
1287
+ AppIcon,
1288
+ {
1289
+ name: "video",
1290
+ className: "absolute bottom-2 left-2 text-background"
1291
+ }
1292
+ ),
1293
+ /* @__PURE__ */ jsx7(
1294
+ AppIcon,
1295
+ {
1296
+ name: "circlePlay",
1297
+ className: "size-10 bg-foreground/20 hover:bg-foreground/40 transition-colors duration-300 rounded-full absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-3xl text-background"
1298
+ }
1299
+ )
1300
+ ]
1301
+ },
1302
+ media.key
1303
+ );
1304
+ }
1305
+ if (isVideo(originalInfo.url)) {
1306
+ return /* @__PURE__ */ jsx7(
1307
+ Item,
1308
+ {
1309
+ content: /* @__PURE__ */ jsx7(
1310
+ "div",
1311
+ {
1312
+ className: "flex h-full w-full items-center justify-center",
1313
+ style: {
1314
+ aspectRatio
1315
+ },
1316
+ children: /* @__PURE__ */ jsx7(
1317
+ "video",
1318
+ {
1319
+ height: "100%",
1320
+ width: "100%",
1321
+ className: "aspect-video",
1322
+ controls: true,
1323
+ autoPlay: true,
1324
+ children: /* @__PURE__ */ jsx7("source", { src: originalInfo.url, type: media.imageType })
1325
+ }
1326
+ )
1327
+ }
1328
+ ),
1329
+ children: ({ ref, open }) => /* @__PURE__ */ jsxs3("div", { onClick: isPostDetail ? open : handleClick, children: [
1330
+ /* @__PURE__ */ jsx7(
1331
+ "img",
1332
+ {
1333
+ src: thumbnailInfo.url,
1334
+ ref,
1335
+ alt: alt ?? "",
1336
+ className: className + " media-item ",
1337
+ style: {
1338
+ aspectRatio,
1339
+ display
1340
+ },
1341
+ onClick: isPostDetail ? open : handleClick
1342
+ }
1343
+ ),
1344
+ /* @__PURE__ */ jsx7(
1345
+ AppIcon,
1346
+ {
1347
+ name: "video",
1348
+ className: `absolute bottom-2 left-2 text-background`,
1349
+ onClick: isPostDetail ? open : handleClick,
1350
+ ref,
1351
+ style: {
1352
+ aspectRatio,
1353
+ display
1354
+ }
1355
+ }
1356
+ ),
1357
+ /* @__PURE__ */ jsx7(
1358
+ AppIcon,
1359
+ {
1360
+ name: "circlePlay",
1361
+ className: `size-10 bg-foreground/20 hover:bg-foreground/40 transition-colors duration-300 rounded-full absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-3xl text-background`,
1362
+ onClick: isPostDetail ? open : handleClick,
1363
+ ref,
1364
+ style: {
1365
+ aspectRatio,
1366
+ display
1367
+ }
1368
+ }
1369
+ )
1370
+ ] })
1371
+ },
1372
+ media.key
1373
+ );
1374
+ }
1375
+ return /* @__PURE__ */ jsx7(
1376
+ Item,
1377
+ {
1378
+ width: originalInfo.width,
1379
+ height: originalInfo.height,
1380
+ original: originalInfo.url,
1381
+ thumbnail: thumbnailInfo.url,
1382
+ children: ({ open, ref }) => /* @__PURE__ */ jsx7(
1383
+ "img",
1384
+ {
1385
+ onClick: isPostDetail ? open : handleClick,
1386
+ ref,
1387
+ src: originalInfo.url || thumbnailInfo.url,
1388
+ alt: alt ?? "",
1389
+ className,
1390
+ style: {
1391
+ aspectRatio,
1392
+ display
1393
+ }
1394
+ }
1395
+ )
1396
+ },
1397
+ media.key
1398
+ );
1399
+ },
1400
+ [className, handleClick, isPostDetail, item.key, ratio, src]
1401
+ );
1402
+ const handleMediaItems = () => {
1403
+ return /* @__PURE__ */ jsx7(Gallery, { options: { showHideAnimationType: "fade" }, children: data?.map((media) => renderMediaItem(media)) });
1404
+ };
1405
+ return /* @__PURE__ */ jsxs3(
1406
+ "div",
1407
+ {
1408
+ style: {
1409
+ backgroundImage: `url(${placeholderUrl})`
1410
+ },
1411
+ className: "h-full w-full bg-cover bg-center bg-no-repeat",
1412
+ children: [
1413
+ item.type === postItemType.MEDIA_ITEM_TO_UPLOAD && showUploadingMedia(),
1414
+ item.type === postItemType.IMAGE_ITEM_TO_UPLOAD && showUploadImg(item),
1415
+ item.type !== postItemType.IMAGE_ITEM_TO_UPLOAD && item.type !== postItemType.MEDIA_ITEM_TO_UPLOAD && handleMediaItems()
1416
+ ]
1417
+ }
1418
+ );
1419
+ };
1420
+ var AsyncImage_default = AsyncImage;
1421
+
1422
+ // src/components/MediaView.tsx
1423
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
1424
+ var MediaView = ({
1425
+ item,
1426
+ className,
1427
+ handleLightbox,
1428
+ additionalImagesCount,
1429
+ ratio,
1430
+ data,
1431
+ isPostDetail,
1432
+ onCardPress
1433
+ }) => {
1434
+ const mediaSrc = useMemo5(() => {
1435
+ if (item.type === postItemType.IMAGE_ITEM_TO_UPLOAD || item.type === postItemType.MEDIA_ITEM_TO_UPLOAD) {
1436
+ return URL.createObjectURL(item.file);
1437
+ }
1438
+ return item.sources?.original?.url ?? "";
1439
+ }, [item.file, item.sources?.original?.url, item.type]);
1440
+ if (item.type === postItemType.POLL_TEXT_ITEM) {
1441
+ const textViewClass = `${className} aspect-square`;
1442
+ return /* @__PURE__ */ jsx8(
1443
+ "div",
1444
+ {
1445
+ onClick: handleLightbox,
1446
+ className: textViewClass,
1447
+ style: {
1448
+ backgroundColor: item.option_color
1449
+ },
1450
+ children: /* @__PURE__ */ jsx8("div", { className: "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2", children: /* @__PURE__ */ jsx8("p", { className: "text-center text-lg font-bold text-background", children: item.text }) })
1451
+ },
1452
+ item.key
1453
+ );
1454
+ }
1455
+ if (item.type === postItemType.IMAGE_ITEM || item.type === postItemType.POLL_IMAGE_ITEM || item.type === postItemType.GIF_ITEM || item.type === postItemType.IMAGE_ITEM_TO_UPLOAD || item.type === postItemType.MEDIA_ITEM || item.type === postItemType.MEDIA_ITEM_TO_UPLOAD || item.type === postItemType.POLL_MEDIA_ITEM) {
1456
+ return /* @__PURE__ */ jsxs4("div", { onClick: handleLightbox, className, children: [
1457
+ item.type.includes("ToUpload") && /* @__PURE__ */ jsx8(Loader_default, { type: "upload_loader" }),
1458
+ /* @__PURE__ */ jsx8(
1459
+ AsyncImage_default,
1460
+ {
1461
+ alt: mediaSrc,
1462
+ src: mediaSrc,
1463
+ className,
1464
+ ratio,
1465
+ item,
1466
+ data: data || [],
1467
+ isPostDetail,
1468
+ onCardPress
1469
+ }
1470
+ ),
1471
+ additionalImagesCount && /* @__PURE__ */ jsx8("div", { className: "absolute right-1 top-0", children: /* @__PURE__ */ jsx8("p", { className: "text-4xl font-bold text-muted-foreground", children: `+${additionalImagesCount}` }) })
1472
+ ] }, item.key);
1473
+ }
1474
+ return null;
1475
+ };
1476
+ var MediaView_default = MediaView;
1477
+
1478
+ // src/components/PostPollLayout/pollUtils.ts
1479
+ var getOptionRateV2 = (item, voteResult) => {
1480
+ if (!voteResult) return "0%";
1481
+ const voteItem = voteResult.options.find(
1482
+ (option) => option.option_id === item.option_id
1483
+ );
1484
+ const totalCount = voteResult.options.reduce(
1485
+ (total, option) => total + option.count,
1486
+ 0
1487
+ );
1488
+ if (voteItem && totalCount > 0) {
1489
+ return (voteItem.count / totalCount * 100).toFixed(0) + "%";
1490
+ }
1491
+ return "0%";
1492
+ };
1493
+ var getVoteInfo = (item, voteResult) => {
1494
+ const isAppOwnerVoted = !!voteResult?.user_vote_option_id;
1495
+ const isVoteItem = !!voteResult?.options.find(
1496
+ (option) => option.option_id === item.option_id && option.option_id === voteResult.user_vote_option_id
1497
+ );
1498
+ return {
1499
+ rate: getOptionRateV2(item, voteResult),
1500
+ isAppOwnerVoted,
1501
+ isVoteItem
1502
+ };
1503
+ };
1504
+
1505
+ // src/components/PostPollLayout/PostPollIResultView.tsx
1506
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1507
+ var PostPollIResultView = ({
1508
+ type,
1509
+ item,
1510
+ voteResult,
1511
+ isPostOwner
1512
+ }) => {
1513
+ const voteInfo = getVoteInfo(item, voteResult);
1514
+ const textCss = voteInfo.isVoteItem ? "#FFFFFF" : item.option_color;
1515
+ const backgroundCss = voteInfo.isVoteItem ? item.option_color : "#FFFFFF";
1516
+ const totalVotes = voteResult?.options.reduce((sum, o) => sum + o.count, 0) ?? 0;
1517
+ if (!voteInfo.isAppOwnerVoted && !isPostOwner || totalVotes === 0) {
1518
+ return null;
1519
+ }
1520
+ if (type === "SINGLE_LEFT") {
1521
+ return /* @__PURE__ */ jsxs5(
1522
+ "div",
1523
+ {
1524
+ className: "absolute top-0 m-6 flex flex-row items-center rounded-full px-4 py-2 shadow-sm backdrop-blur-md",
1525
+ style: { backgroundColor: backgroundCss },
1526
+ children: [
1527
+ /* @__PURE__ */ jsx9("p", { className: "text-sm font-bold", style: { color: textCss }, children: voteInfo.rate }),
1528
+ /* @__PURE__ */ jsx9(
1529
+ AppIcon,
1530
+ {
1531
+ name: "thumbUp",
1532
+ size: 16,
1533
+ className: "ml-2",
1534
+ style: { color: textCss }
1535
+ }
1536
+ )
1537
+ ]
1538
+ }
1539
+ );
1540
+ }
1541
+ if (type === "SINGLE_RIGHT") {
1542
+ return /* @__PURE__ */ jsxs5(
1543
+ "div",
1544
+ {
1545
+ className: "absolute right-0 top-0 m-6 flex flex-row items-center rounded-full px-4 py-2 shadow-sm backdrop-blur-md",
1546
+ style: { backgroundColor: backgroundCss },
1547
+ children: [
1548
+ /* @__PURE__ */ jsx9("p", { className: "text-sm font-bold", style: { color: textCss }, children: voteInfo.rate }),
1549
+ /* @__PURE__ */ jsx9(
1550
+ AppIcon,
1551
+ {
1552
+ name: "thumbDown",
1553
+ size: 16,
1554
+ className: "ml-2",
1555
+ style: { color: textCss }
1556
+ }
1557
+ )
1558
+ ]
1559
+ }
1560
+ );
1561
+ }
1562
+ return /* @__PURE__ */ jsx9(
1563
+ "div",
1564
+ {
1565
+ className: "absolute top-0 m-3 flex items-center justify-center rounded-full px-4 py-2 shadow-sm backdrop-blur-md",
1566
+ style: { backgroundColor: backgroundCss },
1567
+ children: /* @__PURE__ */ jsx9("p", { className: "text-sm font-bold", style: { color: textCss }, children: voteInfo.rate })
1568
+ }
1569
+ );
1570
+ };
1571
+ var PostPollIResultView_default = PostPollIResultView;
1572
+
1573
+ // src/components/PostPollLayout/PostPollIButtonView.tsx
1574
+ import { jsx as jsx10 } from "react/jsx-runtime";
1575
+ var PostPollIButtonView = ({
1576
+ type,
1577
+ item,
1578
+ voteResult,
1579
+ onVotePress
1580
+ }) => {
1581
+ const voteInfo = getVoteInfo(item, voteResult);
1582
+ const handleVoting = (e) => {
1583
+ e.stopPropagation();
1584
+ if (!voteInfo.isVoteItem) onVotePress(item);
1585
+ };
1586
+ const voteLabel = voteInfo.isVoteItem ? `Voted: ${item.text || "this option"}` : `Vote for ${item.text || "this option"}`;
1587
+ if (type === "SINGLE_LEFT") {
1588
+ const nonVoteClassName2 = "absolute bottom-0 left-0 m-6 flex size-11 items-center justify-center rounded-full bg-black/30 backdrop-blur-sm border border-background/20 cursor-pointer transition-all duration-200 hover:bg-black/50";
1589
+ const voteClassName2 = "absolute bottom-0 left-0 m-6 flex size-11 items-center justify-center rounded-full bg-background shadow-lg cursor-pointer transition-all duration-200";
1590
+ return /* @__PURE__ */ jsx10(
1591
+ "button",
1592
+ {
1593
+ className: voteInfo.isVoteItem ? voteClassName2 : nonVoteClassName2,
1594
+ onClick: handleVoting,
1595
+ "aria-label": voteLabel,
1596
+ "aria-pressed": voteInfo.isVoteItem,
1597
+ children: /* @__PURE__ */ jsx10(
1598
+ AppIcon,
1599
+ {
1600
+ name: "thumbUp",
1601
+ size: 20,
1602
+ "aria-hidden": "true",
1603
+ className: voteInfo.isVoteItem ? "" : "text-white",
1604
+ style: voteInfo.isVoteItem ? { color: item.option_color } : {}
1605
+ }
1606
+ )
1607
+ }
1608
+ );
1609
+ }
1610
+ if (type === "SINGLE_RIGHT") {
1611
+ const nonVoteClassName2 = "absolute bottom-0 right-0 m-6 flex size-11 items-center justify-center rounded-full bg-black/30 backdrop-blur-sm border border-background/20 cursor-pointer transition-all duration-200 hover:bg-black/50";
1612
+ const voteClassName2 = "absolute bottom-0 right-0 m-6 flex size-11 items-center justify-center rounded-full bg-background shadow-lg cursor-pointer transition-all duration-200";
1613
+ return /* @__PURE__ */ jsx10(
1614
+ "button",
1615
+ {
1616
+ className: voteInfo.isVoteItem ? voteClassName2 : nonVoteClassName2,
1617
+ onClick: handleVoting,
1618
+ "aria-label": voteLabel,
1619
+ "aria-pressed": voteInfo.isVoteItem,
1620
+ children: /* @__PURE__ */ jsx10(
1621
+ AppIcon,
1622
+ {
1623
+ name: "thumbDown",
1624
+ size: 20,
1625
+ "aria-hidden": "true",
1626
+ className: voteInfo.isVoteItem ? "" : "text-white",
1627
+ style: voteInfo.isVoteItem ? { color: item.option_color } : {}
1628
+ }
1629
+ )
1630
+ }
1631
+ );
1632
+ }
1633
+ const nonVoteClassName = "absolute bottom-3 right-3 rounded-full p-1 backdrop-blur-md border border-background/40 bg-background/20 hover:bg-background/30 scale-100 transition-all duration-300 z-20 cursor-pointer";
1634
+ const voteClassName = "absolute bottom-3 right-3 rounded-full p-1 backdrop-blur-md border border-action-primary bg-action-primary scale-110 transition-all duration-300 z-20 cursor-pointer";
1635
+ return /* @__PURE__ */ jsx10(
1636
+ "button",
1637
+ {
1638
+ className: voteInfo.isVoteItem ? voteClassName : nonVoteClassName,
1639
+ style: voteInfo.isVoteItem && item.option_color ? {
1640
+ backgroundColor: item.option_color,
1641
+ borderColor: item.option_color
1642
+ } : void 0,
1643
+ onClick: handleVoting,
1644
+ "aria-label": voteLabel,
1645
+ "aria-pressed": voteInfo.isVoteItem,
1646
+ children: /* @__PURE__ */ jsx10(
1647
+ AppIcon,
1648
+ {
1649
+ name: "checkCircle2",
1650
+ "aria-hidden": "true",
1651
+ className: `size-8 ${voteInfo.isVoteItem ? "text-white" : "text-white/90 drop-shadow-md"}`
1652
+ }
1653
+ )
1654
+ }
1655
+ );
1656
+ };
1657
+ var PostPollIButtonView_default = PostPollIButtonView;
1658
+
1659
+ // src/components/PostPollLayout/PostPollLayoutOne.tsx
1660
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
1661
+ var PostPollLayoutOne = ({
1662
+ items,
1663
+ voteResult,
1664
+ onVotePress,
1665
+ onCardPress,
1666
+ isPostDetail,
1667
+ isInboxCard,
1668
+ isPostOwner
1669
+ }) => {
1670
+ const { sources } = items[0];
1671
+ let ratio = 1;
1672
+ if (!isInboxCard && sources && sources.original) {
1673
+ const { width, height } = sources.original;
1674
+ if (width > 0 && height > 0) {
1675
+ ratio = width / height;
1676
+ if (ratio < 0.7) {
1677
+ ratio = 0.7;
1678
+ }
1679
+ }
1680
+ }
1681
+ const className = "object-cover w-full";
1682
+ return /* @__PURE__ */ jsx11("div", { className: "not-prose relative w-full box-border", children: /* @__PURE__ */ jsxs6("div", { className: "relative w-full bg-gray-100", children: [
1683
+ /* @__PURE__ */ jsx11(
1684
+ MediaView_default,
1685
+ {
1686
+ item: items[0],
1687
+ className,
1688
+ data: [items[0]],
1689
+ ratio,
1690
+ onCardPress,
1691
+ isPostDetail
1692
+ }
1693
+ ),
1694
+ /* @__PURE__ */ jsx11(
1695
+ PostPollIResultView_default,
1696
+ {
1697
+ type: "SINGLE_LEFT",
1698
+ item: items[0],
1699
+ index: 0,
1700
+ voteResult,
1701
+ isPostOwner
1702
+ }
1703
+ ),
1704
+ /* @__PURE__ */ jsx11(
1705
+ PostPollIButtonView_default,
1706
+ {
1707
+ type: "SINGLE_LEFT",
1708
+ item: items[0],
1709
+ voteResult,
1710
+ onVotePress
1711
+ }
1712
+ ),
1713
+ /* @__PURE__ */ jsx11(
1714
+ PostPollIResultView_default,
1715
+ {
1716
+ type: "SINGLE_RIGHT",
1717
+ item: items[1],
1718
+ index: 1,
1719
+ voteResult,
1720
+ isPostOwner
1721
+ }
1722
+ ),
1723
+ /* @__PURE__ */ jsx11(
1724
+ PostPollIButtonView_default,
1725
+ {
1726
+ type: "SINGLE_RIGHT",
1727
+ item: items[1],
1728
+ voteResult,
1729
+ onVotePress
1730
+ }
1731
+ )
1732
+ ] }) });
1733
+ };
1734
+ var PostPollLayoutOne_default = PostPollLayoutOne;
1735
+
1736
+ // src/components/PostPollLayout/PostPollLayoutGrid.tsx
1737
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1738
+ var GRID_CLASS = {
1739
+ 2: "grid grid-flow-col grid-cols-2 grid-rows-1 gap-0.5 bg-gray-100",
1740
+ 3: "grid grid-cols-2 grid-rows-1 gap-0.5 bg-gray-100",
1741
+ 4: "grid grid-flow-row grid-cols-2 grid-rows-2 gap-0.5 bg-gray-100"
1742
+ };
1743
+ var PostPollLayoutGrid = ({
1744
+ items,
1745
+ voteResult,
1746
+ onVotePress,
1747
+ onCardPress,
1748
+ isPostDetail,
1749
+ isPostOwner
1750
+ }) => {
1751
+ const gridClass = GRID_CLASS[items.length] ?? GRID_CLASS[4];
1752
+ return /* @__PURE__ */ jsx12("div", { className: "not-prose w-full box-border", children: /* @__PURE__ */ jsx12("div", { className: gridClass, children: items.map((item, index) => /* @__PURE__ */ jsxs7("div", { className: "relative group", children: [
1753
+ /* @__PURE__ */ jsx12(
1754
+ MediaView_default,
1755
+ {
1756
+ item,
1757
+ className: "aspect-[1] col-span-1 object-cover w-full",
1758
+ data: items,
1759
+ onCardPress,
1760
+ isPostDetail
1761
+ }
1762
+ ),
1763
+ /* @__PURE__ */ jsx12(
1764
+ PostPollIResultView_default,
1765
+ {
1766
+ type: "NORMAL",
1767
+ item,
1768
+ index,
1769
+ voteResult,
1770
+ isPostOwner
1771
+ }
1772
+ ),
1773
+ /* @__PURE__ */ jsx12(
1774
+ PostPollIButtonView_default,
1775
+ {
1776
+ type: "NORMAL",
1777
+ item,
1778
+ voteResult,
1779
+ onVotePress
1780
+ }
1781
+ )
1782
+ ] }, item.option_id || index)) }) });
1783
+ };
1784
+ var PostPollLayoutGrid_default = PostPollLayoutGrid;
1785
+
1786
+ // src/components/PostPollView.tsx
1787
+ import { jsx as jsx13 } from "react/jsx-runtime";
1788
+ var PostPollView = ({
1789
+ items,
1790
+ voteResult,
1791
+ onVotePress,
1792
+ onCardPress,
1793
+ isPostDetail,
1794
+ isInboxCard,
1795
+ isPostOwner
1796
+ }) => {
1797
+ const isSingleChoicePoll = items.length === 2 && items[0].key === items[1].key;
1798
+ if (isSingleChoicePoll) {
1799
+ return /* @__PURE__ */ jsx13(
1800
+ PostPollLayoutOne_default,
1801
+ {
1802
+ isInboxCard,
1803
+ onVotePress,
1804
+ items,
1805
+ voteResult,
1806
+ onCardPress,
1807
+ isPostDetail,
1808
+ isPostOwner
1809
+ }
1810
+ );
1811
+ }
1812
+ if (items.length >= 2 && items.length <= 4) {
1813
+ return /* @__PURE__ */ jsx13(
1814
+ PostPollLayoutGrid_default,
1815
+ {
1816
+ onVotePress,
1817
+ items,
1818
+ voteResult,
1819
+ onCardPress,
1820
+ isPostDetail,
1821
+ isPostOwner
1822
+ }
1823
+ );
1824
+ }
1825
+ return null;
1826
+ };
1827
+ var PostPollView_default = PostPollView;
1828
+
1829
+ // src/components/PostTextItem.tsx
1830
+ import { jsx as jsx14 } from "react/jsx-runtime";
1831
+ var PostTextItem = ({ textItems, showFullText }) => {
1832
+ return /* @__PURE__ */ jsx14("div", { className: "flex-1", children: /* @__PURE__ */ jsx14("div", { children: textItems.map((item, index) => {
1833
+ const text = "text" in item ? item.text ?? "" : "";
1834
+ const key = "key" in item ? item.key : index;
1835
+ const mentionData = "mentionData" in item ? item.mentionData : void 0;
1836
+ return /* @__PURE__ */ jsx14(
1837
+ AutoLink_default,
1838
+ {
1839
+ text: showFullText ? text : TruncateText(text, 300),
1840
+ mentionData,
1841
+ className: "whitespace-normal px-4 pb-4 leading-relaxed text-gray-800",
1842
+ style: {
1843
+ whiteSpace: "pre-wrap",
1844
+ wordBreak: "break-word"
1845
+ }
1846
+ },
1847
+ key
1848
+ );
1849
+ }) }) });
1850
+ };
1851
+ var PostTextItem_default = PostTextItem;
1852
+
1853
+ // src/components/PostLinkItem.tsx
1854
+ import { useState } from "react";
1855
+ import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
1856
+ var getHostFromUrl = (url) => {
1857
+ if (!url) return "";
1858
+ try {
1859
+ return new URL(url).host;
1860
+ } catch {
1861
+ return url;
1862
+ }
1863
+ };
1864
+ var getLinkRel = (host, supportDeepviewDomain = "") => {
1865
+ const isInternal = supportDeepviewDomain.length > 0 && host.includes(supportDeepviewDomain);
1866
+ return isInternal ? "noreferrer" : "noreferrer nofollow";
1867
+ };
1868
+ var PostLinkItem = ({
1869
+ linkItems,
1870
+ imageComponent: ImageComponent,
1871
+ onCloseClick,
1872
+ type = "display",
1873
+ supportDeepviewDomain = ""
1874
+ }) => {
1875
+ const [imageError, setImageError] = useState(false);
1876
+ if (linkItems.length === 0) {
1877
+ return null;
1878
+ }
1879
+ const { linkUrl, linkTitle, linkDesc, linkImage } = linkItems[0] || {};
1880
+ const host = getHostFromUrl(linkUrl);
1881
+ const linkRel = getLinkRel(host, supportDeepviewDomain);
1882
+ const handleClick = (e) => {
1883
+ e.stopPropagation();
1884
+ e.nativeEvent.stopImmediatePropagation();
1885
+ if (linkUrl) window.open(linkUrl, "_blank", "noopener,noreferrer");
1886
+ };
1887
+ const renderImage = (src, alt, aspectClassName, containerClassName) => {
1888
+ if (imageError || !src) return null;
1889
+ return /* @__PURE__ */ jsx15("div", { className: containerClassName, children: /* @__PURE__ */ jsx15("div", { className: aspectClassName, children: ImageComponent ? /* @__PURE__ */ jsx15(
1890
+ ImageComponent,
1891
+ {
1892
+ src,
1893
+ alt,
1894
+ className: "w-full h-full object-cover rounded-t-md"
1895
+ }
1896
+ ) : /* @__PURE__ */ jsx15(
1897
+ "img",
1898
+ {
1899
+ src,
1900
+ alt,
1901
+ className: "w-full h-full object-cover rounded-t-md",
1902
+ onError: () => setImageError(true)
1903
+ }
1904
+ ) }) });
1905
+ };
1906
+ if (type === "preview") {
1907
+ const aspectRatio = linkImage && linkImage.height > linkImage.width && linkImage.height / linkImage.width > 0.7 ? "aspect-[0.7]" : "aspect-video";
1908
+ return /* @__PURE__ */ jsxs8(
1909
+ "section",
1910
+ {
1911
+ className: "not-prose min-w-96 relative cursor-pointer rounded-lg border border-border bg-background",
1912
+ onClick: handleClick,
1913
+ role: "link",
1914
+ tabIndex: 0,
1915
+ onKeyDown: (e) => {
1916
+ if (e.key === "Enter" || e.key === " ") {
1917
+ e.preventDefault();
1918
+ handleClick(e);
1919
+ }
1920
+ },
1921
+ children: [
1922
+ linkImage?.url && !imageError && /* @__PURE__ */ jsx15("div", { className: "relative w-full", children: ImageComponent ? /* @__PURE__ */ jsx15("div", { className: `relative w-full ${aspectRatio}`, children: /* @__PURE__ */ jsx15(
1923
+ ImageComponent,
1924
+ {
1925
+ src: linkImage.url,
1926
+ alt: linkTitle || "Link preview image",
1927
+ className: "w-full h-full object-cover rounded-t-md"
1928
+ }
1929
+ ) }) : /* @__PURE__ */ jsx15("div", { className: `relative w-full ${aspectRatio}`, children: /* @__PURE__ */ jsx15(
1930
+ "img",
1931
+ {
1932
+ src: linkImage.url,
1933
+ alt: linkTitle || "Link preview image",
1934
+ className: "w-full h-full object-cover rounded-t-md",
1935
+ onError: () => setImageError(true)
1936
+ }
1937
+ ) }) }),
1938
+ typeof onCloseClick === "function" && /* @__PURE__ */ jsx15(
1939
+ AppIcon,
1940
+ {
1941
+ name: "cancel",
1942
+ onClick: (e) => {
1943
+ e.stopPropagation();
1944
+ onCloseClick();
1945
+ },
1946
+ className: "absolute -right-2 -top-3 rounded-full bg-background size-4.5 hover:cursor-pointer hover:opacity-70"
1947
+ }
1948
+ ),
1949
+ /* @__PURE__ */ jsxs8("section", { className: "flex flex-col gap-1 p-3", children: [
1950
+ /* @__PURE__ */ jsx15("a", { href: linkUrl, className: "hidden", rel: linkRel, tabIndex: -1 }),
1951
+ /* @__PURE__ */ jsx15(
1952
+ "p",
1953
+ {
1954
+ className: "wrap-break-word text-xs font-normal leading-4 text-muted-foreground",
1955
+ "data-test-id": "post-link-host",
1956
+ children: host
1957
+ }
1958
+ ),
1959
+ /* @__PURE__ */ jsx15(
1960
+ "p",
1961
+ {
1962
+ className: "text-clip-1 text-ellipsis wrap-break-word text-base leading-6",
1963
+ "data-test-id": "post-link-title",
1964
+ children: linkTitle
1965
+ }
1966
+ ),
1967
+ /* @__PURE__ */ jsx15(
1968
+ "p",
1969
+ {
1970
+ className: "text-clip-2 text-ellipsis wrap-break-word text-xs font-normal leading-5 tracking-tight text-muted-foreground",
1971
+ "data-test-id": "post-link-desc",
1972
+ children: linkDesc
1973
+ }
1974
+ )
1975
+ ] })
1976
+ ]
1977
+ }
1978
+ );
1979
+ }
1980
+ return /* @__PURE__ */ jsx15(
1981
+ "section",
1982
+ {
1983
+ className: cn("not-prose relative cursor-pointer group overflow-hidden"),
1984
+ onClick: handleClick,
1985
+ role: "link",
1986
+ tabIndex: 0,
1987
+ onKeyDown: (e) => {
1988
+ if (e.key === "Enter" || e.key === " ") {
1989
+ e.preventDefault();
1990
+ handleClick(e);
1991
+ }
1992
+ },
1993
+ children: /* @__PURE__ */ jsxs8("div", { className: "aspect-video relative", children: [
1994
+ /* @__PURE__ */ jsx15("div", { className: "max-w-200 max-h-100 size-full relative", children: linkImage?.url && !imageError && /* @__PURE__ */ jsx15(Fragment2, { children: ImageComponent ? /* @__PURE__ */ jsx15(
1995
+ ImageComponent,
1996
+ {
1997
+ src: linkImage.url,
1998
+ alt: linkTitle || "Link preview image",
1999
+ className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-105"
2000
+ }
2001
+ ) : /* @__PURE__ */ jsx15(
2002
+ "img",
2003
+ {
2004
+ src: linkImage.url,
2005
+ alt: linkTitle || "Link preview image",
2006
+ className: "w-full h-full object-cover transition-transform duration-700 group-hover:scale-105",
2007
+ onError: () => setImageError(true)
2008
+ }
2009
+ ) }) }),
2010
+ /* @__PURE__ */ jsx15("div", { className: "absolute inset-0 bg-linear-to-t from-black/90 via-black/40 to-transparent opacity-90 group-hover:opacity-100 transition-opacity" }),
2011
+ typeof onCloseClick === "function" && /* @__PURE__ */ jsx15(
2012
+ "button",
2013
+ {
2014
+ onClick: (e) => {
2015
+ e.stopPropagation();
2016
+ onCloseClick();
2017
+ },
2018
+ className: "absolute top-2 right-2 bg-black/40 text-white rounded-full p-1 hover:bg-black/60 cursor-pointer border-none flex items-center justify-center transition-colors shadow-sm z-10",
2019
+ "aria-label": "Remove link preview",
2020
+ children: /* @__PURE__ */ jsx15(AppIcon, { name: "cancel", size: 16, strokeWidth: 2 })
2021
+ }
2022
+ ),
2023
+ /* @__PURE__ */ jsxs8("div", { className: "absolute bottom-0 left-0 right-0 p-5 transform transition-transform duration-300 z-10 pointer-events-none", children: [
2024
+ /* @__PURE__ */ jsx15(
2025
+ "a",
2026
+ {
2027
+ href: linkUrl,
2028
+ className: "hidden pointer-events-auto",
2029
+ rel: linkRel,
2030
+ tabIndex: -1
2031
+ }
2032
+ ),
2033
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2 text-xs text-white/70 mb-2", children: [
2034
+ /* @__PURE__ */ jsx15(AppIcon, { name: "externalLink", className: "w-3.5 h-3.5" }),
2035
+ /* @__PURE__ */ jsx15("span", { className: "uppercase tracking-wider font-medium", children: host })
2036
+ ] }),
2037
+ /* @__PURE__ */ jsx15("h3", { className: "font-semibold text-white text-xl leading-tight mb-1.5 group-hover:text-action-primary transition-colors line-clamp-2", children: linkTitle }),
2038
+ linkDesc && /* @__PURE__ */ jsx15("p", { className: "text-white/70 text-sm line-clamp-2", children: linkDesc })
2039
+ ] })
2040
+ ] })
2041
+ }
2042
+ );
2043
+ };
2044
+ var PostLinkItem_default = PostLinkItem;
2045
+
2046
+ // src/components/PostEmbedView.tsx
2047
+ import { useMemo as useMemo6, useState as useState2 } from "react";
2048
+ import { jsx as jsx16 } from "react/jsx-runtime";
2049
+ var TRUSTED_HOSTS = {
2050
+ youtube: ["www.youtube.com", "youtube.com"],
2051
+ spotify: ["open.spotify.com"]
2052
+ };
2053
+ var isTrustedEmbedUrl = (provider, url) => {
2054
+ try {
2055
+ const u = new URL(url);
2056
+ if (u.protocol !== "https:") return false;
2057
+ const allowed = TRUSTED_HOSTS[provider] ?? [];
2058
+ return allowed.includes(u.hostname.toLowerCase());
2059
+ } catch {
2060
+ return false;
2061
+ }
2062
+ };
2063
+ var PostEmbedView = ({ embed }) => {
2064
+ const [hasError, setHasError] = useState2(false);
2065
+ const canRender = useMemo6(() => {
2066
+ return !hasError && !!embed.embedUrl && isTrustedEmbedUrl(embed.provider, embed.embedUrl);
2067
+ }, [hasError, embed.embedUrl, embed.provider]);
2068
+ const linkItem = {
2069
+ linkUrl: embed.originalUrl,
2070
+ linkTitle: embed.preview?.title || embed.originalUrl,
2071
+ linkDesc: embed.preview?.description || "",
2072
+ linkImage: embed.preview?.image ?? void 0,
2073
+ linkIcon: "",
2074
+ type: "linkItem"
2075
+ };
2076
+ if (!canRender) return /* @__PURE__ */ jsx16(PostLinkItem_default, { linkItems: [linkItem] });
2077
+ const isYouTube = embed.provider === "youtube";
2078
+ const isSpotify = embed.provider === "spotify";
2079
+ return /* @__PURE__ */ jsx16("div", { className: "w-full px-4 pb-4 not-first:pt-4", children: /* @__PURE__ */ jsx16(
2080
+ "div",
2081
+ {
2082
+ className: "relative w-full overflow-hidden rounded-xl",
2083
+ style: { height: embed.height || SPOTIFY_HEIGHT },
2084
+ children: /* @__PURE__ */ jsx16(
2085
+ "iframe",
2086
+ {
2087
+ src: embed.embedUrl,
2088
+ className: "absolute inset-0 h-full w-full",
2089
+ loading: "lazy",
2090
+ sandbox: "allow-scripts allow-same-origin allow-presentation",
2091
+ allow: isYouTube ? "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" : isSpotify ? "autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture" : "",
2092
+ allowFullScreen: isYouTube,
2093
+ referrerPolicy: "no-referrer-when-downgrade",
2094
+ title: embed.preview?.title || `${embed.provider} embed`,
2095
+ style: { border: 0 },
2096
+ onError: () => setHasError(true)
2097
+ }
2098
+ )
2099
+ }
2100
+ ) });
2101
+ };
2102
+ var PostEmbedView_default = PostEmbedView;
2103
+
2104
+ // src/components/PostMediaLayout/MediaUploadStatusOverlay.tsx
2105
+ import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
2106
+ var MediaUploadStatusOverlay = ({ status, tileWidth }) => {
2107
+ if (!status) return null;
2108
+ const showText = !tileWidth || tileWidth >= 100;
2109
+ if (status === "uploading" || status === "processing") {
2110
+ return /* @__PURE__ */ jsxs9("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-2 bg-overlay-scrim", children: [
2111
+ /* @__PURE__ */ jsx17(AppIcon, { name: "loader", className: "size-6 animate-spin text-background" }),
2112
+ showText && /* @__PURE__ */ jsx17("span", { className: "text-xs font-medium text-background", children: status === "uploading" ? "Uploading" : "Processing" })
2113
+ ] });
2114
+ }
2115
+ if (status === "uploaded") {
2116
+ return /* @__PURE__ */ jsx17("div", { className: "absolute inset-0 flex items-center justify-center bg-feedback-success/15", children: /* @__PURE__ */ jsx17("div", { className: "flex size-8 items-center justify-center rounded-full bg-foreground/60", children: /* @__PURE__ */ jsx17(AppIcon, { name: "check", className: "size-5 text-background" }) }) });
2117
+ }
2118
+ return /* @__PURE__ */ jsx17("div", { className: "absolute inset-0 flex items-center justify-center bg-feedback-danger/20", children: /* @__PURE__ */ jsx17("div", { className: "flex size-8 items-center justify-center rounded-full bg-foreground/60", children: /* @__PURE__ */ jsx17("span", { className: "text-base font-bold text-background", children: "!" }) }) });
2119
+ };
2120
+ var MediaUploadStatusOverlay_default = MediaUploadStatusOverlay;
2121
+
2122
+ // src/components/PostMediaLayout/PostMediaLayoutCore.tsx
2123
+ import { jsx as jsx18, jsxs as jsxs10 } from "react/jsx-runtime";
2124
+ var gridLayoutData = {
2125
+ 1: {
2126
+ gridColsClass: "grid-cols-1",
2127
+ gridSpan: [1],
2128
+ colSpanClasses: ["col-span-1"],
2129
+ itemRatio: [1]
2130
+ },
2131
+ 2: {
2132
+ gridColsClass: "grid-cols-2",
2133
+ gridSpan: [1, 1],
2134
+ colSpanClasses: ["col-span-1", "col-span-1"],
2135
+ itemRatio: [1, 1]
2136
+ },
2137
+ 3: {
2138
+ gridColsClass: "grid-cols-2",
2139
+ gridSpan: [2, 1, 1],
2140
+ colSpanClasses: ["col-span-2", "col-span-1", "col-span-1"],
2141
+ itemRatio: [1.5, 1.5, 1.5]
2142
+ },
2143
+ 4: {
2144
+ gridColsClass: "grid-cols-2",
2145
+ gridSpan: [1, 1, 1, 1],
2146
+ colSpanClasses: ["col-span-1", "col-span-1", "col-span-1", "col-span-1"],
2147
+ itemRatio: [1, 1, 1, 1]
2148
+ },
2149
+ 5: {
2150
+ gridColsClass: "grid-cols-6",
2151
+ gridSpan: [3, 3, 2, 2, 2],
2152
+ colSpanClasses: [
2153
+ "col-span-3",
2154
+ "col-span-3",
2155
+ "col-span-2",
2156
+ "col-span-2",
2157
+ "col-span-2"
2158
+ ],
2159
+ itemRatio: [1.5, 1.5, 1, 1, 1]
2160
+ }
2161
+ };
2162
+ var PostMediaLayoutCore = ({
2163
+ items,
2164
+ parentId: _parentId,
2165
+ isPostDetail,
2166
+ onCardPress,
2167
+ mediaItemStatuses
2168
+ }) => {
2169
+ if (!items?.length) {
2170
+ return null;
2171
+ }
2172
+ const slicedData = items.slice(0, 5);
2173
+ const { gridColsClass, colSpanClasses, itemRatio } = gridLayoutData[slicedData.length];
2174
+ return /* @__PURE__ */ jsx18("div", { className: "not-prose w-full box-border", children: /* @__PURE__ */ jsx18(
2175
+ "div",
2176
+ {
2177
+ className: `grid ${gridColsClass} cursor-pointer grid-rows-1 gap-0.5 bg-muted`,
2178
+ children: slicedData.map((slicedItem, index) => {
2179
+ let ratio = 1;
2180
+ if (slicedData.length === 1) {
2181
+ const { width, height } = slicedItem.type.includes("ToUpload") ? slicedItem.file : slicedItem.sources?.original ?? {};
2182
+ const safeWidth = width ?? 1;
2183
+ const safeHeight = height ?? 1;
2184
+ ratio = safeWidth / safeHeight;
2185
+ if (ratio < 0.7) {
2186
+ ratio = 0.7;
2187
+ }
2188
+ } else {
2189
+ ratio = itemRatio[index];
2190
+ }
2191
+ const className = `w-full h-full relative ${colSpanClasses[index]} object-cover`;
2192
+ return /* @__PURE__ */ jsxs10("div", { className: `relative ${colSpanClasses[index]}`, children: [
2193
+ /* @__PURE__ */ jsx18(
2194
+ MediaView_default,
2195
+ {
2196
+ item: slicedItem,
2197
+ ratio,
2198
+ className,
2199
+ additionalImagesCount: index === slicedData.length - 1 && items.length > 5 ? items.length - slicedData.length : void 0,
2200
+ data: items,
2201
+ isPostDetail,
2202
+ onCardPress
2203
+ }
2204
+ ),
2205
+ /* @__PURE__ */ jsx18(
2206
+ MediaUploadStatusOverlay_default,
2207
+ {
2208
+ status: mediaItemStatuses?.[slicedItem.key]
2209
+ }
2210
+ )
2211
+ ] }, index);
2212
+ })
2213
+ }
2214
+ ) });
2215
+ };
2216
+ var PostMediaLayoutCore_default = PostMediaLayoutCore;
2217
+
2218
+ // src/components/PostItemsView.tsx
2219
+ import { jsx as jsx19 } from "react/jsx-runtime";
2220
+ var PostItemsView = ({
2221
+ primaryContent,
2222
+ isPostDetail,
2223
+ onCardPress,
2224
+ onVotePress,
2225
+ isPostOwner,
2226
+ parentId,
2227
+ isCommentScope,
2228
+ imageComponent,
2229
+ mediaItemStatuses
2230
+ }) => {
2231
+ if (!primaryContent) return null;
2232
+ switch (primaryContent.type) {
2233
+ case "embed":
2234
+ return /* @__PURE__ */ jsx19(PostEmbedView_default, { embed: primaryContent });
2235
+ case "poll":
2236
+ return /* @__PURE__ */ jsx19(
2237
+ PostPollView_default,
2238
+ {
2239
+ items: primaryContent.items,
2240
+ voteResult: primaryContent.pollResult,
2241
+ onVotePress,
2242
+ onCardPress,
2243
+ isPostDetail,
2244
+ isPostOwner
2245
+ }
2246
+ );
2247
+ case "link":
2248
+ return /* @__PURE__ */ jsx19(
2249
+ PostLinkItem_default,
2250
+ {
2251
+ linkItems: [
2252
+ {
2253
+ linkUrl: primaryContent.linkUrl,
2254
+ linkTitle: primaryContent.title,
2255
+ linkDesc: primaryContent.description,
2256
+ linkImage: primaryContent.image ?? void 0,
2257
+ linkIcon: "",
2258
+ type: "linkItem"
2259
+ }
2260
+ ],
2261
+ imageComponent
2262
+ }
2263
+ );
2264
+ case "media":
2265
+ return /* @__PURE__ */ jsx19(
2266
+ PostMediaLayoutCore_default,
2267
+ {
2268
+ items: primaryContent.items,
2269
+ parentId,
2270
+ isPostDetail,
2271
+ onCardPress,
2272
+ mediaItemStatuses
2273
+ }
2274
+ );
2275
+ case "textItem":
2276
+ return /* @__PURE__ */ jsx19(
2277
+ PostTextItem_default,
2278
+ {
2279
+ textItems: primaryContent.items,
2280
+ showFullText: isPostDetail,
2281
+ isPostDetail
2282
+ }
2283
+ );
2284
+ default:
2285
+ return null;
2286
+ }
2287
+ };
2288
+ var PostItemsView_default = PostItemsView;
2289
+
2290
+ // src/components/PostSectionsView.tsx
2291
+ import { useCallback as useCallback3 } from "react";
2292
+
2293
+ // src/utils/contentTypeConstants.ts
2294
+ var contentTypeRules = {
2295
+ NEW_MESSAGE: "new:message",
2296
+ NEW_POST: "new:post",
2297
+ NEW_COMMENT: "new:comment",
2298
+ EVENT: "event"
2299
+ };
2300
+ var contentTypeConstants = {
2301
+ TO_BE_DELETE: "tobedelete",
2302
+ NEW_GROUP: "new:group",
2303
+ LOAD_MORE_CONTENT: "loadMoreContent",
2304
+ NEW_MESSAGE: `${contentTypeRules.NEW_MESSAGE}:text`,
2305
+ NEW_MESSAGE_GUGGY: `${contentTypeRules.NEW_MESSAGE}:gif`,
2306
+ NEW_MESSAGE_VOICE: `${contentTypeRules.NEW_MESSAGE}:voice`,
2307
+ NEW_POST_IMAGE: `${contentTypeRules.NEW_POST}:image`,
2308
+ NEW_POST_MEDIA: `${contentTypeRules.NEW_POST}:media`,
2309
+ NEW_POST_POLL: `${contentTypeRules.NEW_POST}:poll`,
2310
+ NEW_POST_LINK: `${contentTypeRules.NEW_POST}:link`,
2311
+ NEW_POST_MESSAGE: `${contentTypeRules.NEW_POST}:text`,
2312
+ NEW_POST_THREAD: `${contentTypeRules.NEW_POST}:thread`,
2313
+ NEW_COMMENT: `${contentTypeRules.NEW_COMMENT}`,
2314
+ NEW_COMMENT_GUGGY: `${contentTypeRules.NEW_COMMENT}:gif`,
2315
+ NEW_COMMENT_VOICE: `${contentTypeRules.NEW_COMMENT}:voice`,
2316
+ NEW_COMMENT_IMAGE: `${contentTypeRules.NEW_COMMENT}:image`,
2317
+ NEW_COMMENT_MEDIA: `${contentTypeRules.NEW_COMMENT}:media`,
2318
+ ERASE_MESSAGE: "erase:message",
2319
+ EVENT_POLL_VOTE: "event:poll:vote",
2320
+ EVENT_NEW_GREETING_POST: "event:greeting:post",
2321
+ EVENT_UPDATE_GROUP: "event:update:group",
2322
+ EVENT_ERASE_GROUP_CONTENT: "event:erase:group:content",
2323
+ EVENT_ERASE_GROUP_COMMENT: "event:erase:group:comment",
2324
+ EVENT_NOT_A_CONTACT: "event:not:contact",
2325
+ EVENT_SCREENSHOT: "event:screenshot",
2326
+ EVENT_COMMENT_SCREENSHOT: "event:comment:screenshot",
2327
+ EVENT_TOAST_CONVERSATION: "event:toast:conversation",
2328
+ EVENT_TOAST_POST: "event:toast:post",
2329
+ UPDATE_GROUP_NAME: "group:update:name",
2330
+ UPDATE_GROUP_DESCRIPTION: "group:update:description",
2331
+ UPDATE_GROUP_BACKGROUND: "group:update:background",
2332
+ UPDATE_GROUP_DEFAULT_DEEPLINK: "group:update:default_deeplink",
2333
+ UPDATE_GROUP_AVATAR: "group:update:avatar",
2334
+ UPDATE_GROUP_NEW_USERS: "group:new:users",
2335
+ UPDATE_GROUP_REMOVE_USERS: "group:remove:users",
2336
+ UPDATE_GROUP_USER_LEAVE: "group:remove:users:leave",
2337
+ UPDATE_GROUP_NEW_OWNER: "group:new:owner",
2338
+ UPDATE_GROUP_SELECT_ID: "group:update:select_id",
2339
+ UPDATE_GROUP_SHOW_HISTORY: "group:update:show_history",
2340
+ UPDATE_GROUP_SHARE_TO_PUBLIC: "group:update:share_to_public",
2341
+ UPDATE_GROUP_PERMISSION: "group:update:permission",
2342
+ UPDATE_GROUP_NEW_FEATURED: "group:new:featured",
2343
+ UPDATE_GROUP_FEATURED_IN_GROUP: "group:featured:in:group",
2344
+ UPDATE_GROUP_FEATURED_BY_USER: "group:featured:by:user",
2345
+ UPDATE_RELATIONSHIP: "update:relationship",
2346
+ NEW_GROUP_REQUEST: "group:request:new",
2347
+ NEW_GROUP_REQUEST_ACCEPTED: "group:request:accepted",
2348
+ REMOVE_GROUP_ADMIN: "group:remove:admin",
2349
+ ADD_GROUP_ADMIN: "group:new:admin",
2350
+ UPDATE_CONTENT: "update:content",
2351
+ NEW_GROUP_REQUEST_REMOVE: "group:request:removed",
2352
+ AGGREGATED_MESSAGE_TIME: "aggregated:message:time",
2353
+ REQUEST_IMAGE_DOWNLOAD: "request:image:download"
2354
+ };
2355
+
2356
+ // src/components/PostSectionSourceInfoView.tsx
2357
+ import { jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
2358
+ var PostSectionSourceInfoView = ({ sourceInfo }) => {
2359
+ if (!sourceInfo) {
2360
+ return null;
2361
+ }
2362
+ return /* @__PURE__ */ jsx20("div", { className: "mx-4 mb-3 mt-3 border-b border-gray-100 pb-3", children: /* @__PURE__ */ jsxs11("div", { className: "flex flex-row items-center", children: [
2363
+ /* @__PURE__ */ jsx20("div", { className: "size-7 shrink-0 rounded-full overflow-hidden border border-border-subtle bg-muted flex items-center justify-center", children: sourceInfo.avatar ? /* @__PURE__ */ jsx20(
2364
+ "img",
2365
+ {
2366
+ src: sourceInfo.avatar,
2367
+ alt: sourceInfo.username,
2368
+ className: "size-full object-cover"
2369
+ }
2370
+ ) : /* @__PURE__ */ jsx20("span", { className: "text-xs font-semibold text-muted-foreground", children: sourceInfo.username?.charAt(0)?.toUpperCase() ?? "?" }) }),
2371
+ /* @__PURE__ */ jsxs11("div", { className: "ml-2 flex-col justify-center flex", children: [
2372
+ /* @__PURE__ */ jsx20("div", { className: "text-sm font-semibold text-foreground leading-none", children: sourceInfo.username }),
2373
+ /* @__PURE__ */ jsx20("div", { className: "text-xs text-muted-foreground mt-1 leading-none", children: sourceInfo.url })
2374
+ ] })
2375
+ ] }) });
2376
+ };
2377
+ var PostSectionSourceInfoView_default = PostSectionSourceInfoView;
2378
+
2379
+ // src/components/PostSectionsView.tsx
2380
+ import { jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
2381
+ var PostSectionsView = ({
2382
+ sections,
2383
+ isPostDetail,
2384
+ onCardPress
2385
+ }) => {
2386
+ const renderSectionItem = useCallback3(
2387
+ (section, index) => {
2388
+ const { type, data } = section;
2389
+ if (type === contentTypeConstants.NEW_POST_MESSAGE) {
2390
+ return /* @__PURE__ */ jsx21(
2391
+ AutoLink_default,
2392
+ {
2393
+ text: data.body || "",
2394
+ className: "whitespace-normal px-4 pb-4 leading-relaxed text-gray-800",
2395
+ style: {
2396
+ whiteSpace: "pre-wrap",
2397
+ wordBreak: "break-word"
2398
+ },
2399
+ mentionData: data.mentionData
2400
+ },
2401
+ `${index} - ${data.body}`
2402
+ );
2403
+ }
2404
+ if (type === contentTypeConstants.NEW_POST_MEDIA) {
2405
+ return /* @__PURE__ */ jsxs12("div", { className: "my-3", children: [
2406
+ data.sourceInfo && /* @__PURE__ */ jsx21(PostSectionSourceInfoView_default, { sourceInfo: data.sourceInfo }),
2407
+ data.body && /* @__PURE__ */ jsx21(
2408
+ AutoLink_default,
2409
+ {
2410
+ text: data.body || "",
2411
+ className: "whitespace-normal px-4 pb-4 leading-relaxed text-gray-800",
2412
+ style: {
2413
+ whiteSpace: "pre-wrap",
2414
+ wordBreak: "break-word"
2415
+ },
2416
+ mentionData: data.mentionData
2417
+ },
2418
+ `${index} - ${data.body}`
2419
+ ),
2420
+ data.items && /* @__PURE__ */ jsx21(
2421
+ PostMediaLayoutCore_default,
2422
+ {
2423
+ items: data.items,
2424
+ isPostDetail,
2425
+ onCardPress
2426
+ }
2427
+ )
2428
+ ] }, `${index} - ${data.body}`);
2429
+ }
2430
+ return null;
2431
+ },
2432
+ [isPostDetail, onCardPress]
2433
+ );
2434
+ if (!sections || sections.length === 0) {
2435
+ return null;
2436
+ }
2437
+ return /* @__PURE__ */ jsx21("div", { className: "space-y-2", children: sections.map((section, index) => {
2438
+ return renderSectionItem(section, index);
2439
+ }) });
2440
+ };
2441
+ var PostSectionsView_default = PostSectionsView;
2442
+
2443
+ // src/components/PostFooter.tsx
2444
+ import { useState as useState3 } from "react";
2445
+ import { NotificationBadge } from "@select-org/ui";
2446
+ import { jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
2447
+ var PostFooter = ({
2448
+ postHref,
2449
+ commentCount = 0,
2450
+ unreadCommentCount = 0,
2451
+ showFooterLink = false,
2452
+ footerLinkLabel = "Open in app",
2453
+ footerLinkHref = "/",
2454
+ isSnoozed = false,
2455
+ onSnoozeToggle,
2456
+ renderReactionTrigger,
2457
+ renderReactionModal
2458
+ }) => {
2459
+ const [isReactionOpen, setIsReactionOpen] = useState3(false);
2460
+ const handleOpen = () => setIsReactionOpen(true);
2461
+ const handleClose = () => setIsReactionOpen(false);
2462
+ const commentButtonClass = "relative text-muted-foreground hover:text-foreground hover:bg-muted p-2 rounded-full transition-all flex items-center justify-center cursor-pointer bg-transparent border-none select-none";
2463
+ const commentAriaLabel = `View comments${commentCount > 0 ? `, ${commentCount} comment${commentCount !== 1 ? "s" : ""}` : ""}`;
2464
+ const commentBadge = commentCount > 0 ? /* @__PURE__ */ jsx22("span", { className: "absolute top-0 right-0 -mt-0.5 -mr-0.5", children: /* @__PURE__ */ jsx22(
2465
+ NotificationBadge,
2466
+ {
2467
+ count: unreadCommentCount > 0 ? unreadCommentCount : commentCount,
2468
+ variant: unreadCommentCount > 0 ? "solid" : "neutral",
2469
+ size: "sm",
2470
+ className: "ring-2 ring-background"
2471
+ }
2472
+ ) }) : null;
2473
+ return /* @__PURE__ */ jsxs13("section", { className: "flex items-center justify-between p-2 px-4 border-t border-border/50", children: [
2474
+ /* @__PURE__ */ jsxs13("div", { className: "relative flex items-center gap-1", children: [
2475
+ renderReactionTrigger ? renderReactionTrigger({ onOpen: handleOpen }) : /* @__PURE__ */ jsx22(
2476
+ "button",
2477
+ {
2478
+ onClick: (e) => {
2479
+ e.preventDefault();
2480
+ e.stopPropagation();
2481
+ handleOpen();
2482
+ },
2483
+ className: "text-muted-foreground hover:text-foreground hover:bg-muted p-2 rounded-full transition-all cursor-pointer bg-transparent border-none select-none",
2484
+ "aria-label": "Add reaction",
2485
+ "aria-expanded": isReactionOpen,
2486
+ "aria-haspopup": "true",
2487
+ children: /* @__PURE__ */ jsx22(AppIcon, { name: "sendEmoji", "aria-hidden": "true" })
2488
+ }
2489
+ ),
2490
+ onSnoozeToggle && /* @__PURE__ */ jsx22(
2491
+ "button",
2492
+ {
2493
+ onClick: (e) => {
2494
+ e.preventDefault();
2495
+ e.stopPropagation();
2496
+ onSnoozeToggle();
2497
+ },
2498
+ className: cn(
2499
+ "p-2 rounded-full hover:bg-muted transition-all cursor-pointer bg-transparent border-none select-none",
2500
+ isSnoozed ? "text-feedback-warning" : "text-muted-foreground hover:text-foreground"
2501
+ ),
2502
+ "aria-label": isSnoozed ? "Unsnooze post" : "Snooze post",
2503
+ title: isSnoozed ? "Unsnooze post" : "Snooze post (30 min)",
2504
+ children: /* @__PURE__ */ jsx22(AppIcon, { name: "alarmClock" })
2505
+ }
2506
+ ),
2507
+ renderReactionModal && /* @__PURE__ */ jsx22("div", { className: "z-50 absolute bottom-full left-0 mb-2", children: renderReactionModal({
2508
+ isOpen: isReactionOpen,
2509
+ onClose: handleClose,
2510
+ onSend: (emoji) => {
2511
+ handleClose();
2512
+ }
2513
+ }) })
2514
+ ] }),
2515
+ /* @__PURE__ */ jsx22("div", { className: "relative flex items-center ml-auto", children: postHref ? /* @__PURE__ */ jsxs13(
2516
+ "a",
2517
+ {
2518
+ href: postHref,
2519
+ className: cn(commentButtonClass, "no-underline"),
2520
+ onClick: (e) => {
2521
+ e.stopPropagation();
2522
+ },
2523
+ "aria-label": commentAriaLabel,
2524
+ children: [
2525
+ /* @__PURE__ */ jsx22(AppIcon, { name: "messageCircleMore", "aria-hidden": "true" }),
2526
+ commentBadge
2527
+ ]
2528
+ }
2529
+ ) : /* @__PURE__ */ jsxs13("button", { className: commentButtonClass, "aria-label": commentAriaLabel, children: [
2530
+ /* @__PURE__ */ jsx22(AppIcon, { name: "messageCircleMore", "aria-hidden": "true" }),
2531
+ commentBadge
2532
+ ] }) }),
2533
+ showFooterLink && /* @__PURE__ */ jsx22("section", { className: "flex justify-center -mx-5 -mb-2.5 mt-2.5", children: /* @__PURE__ */ jsx22(
2534
+ "a",
2535
+ {
2536
+ href: footerLinkHref,
2537
+ className: "bg-action-primary/5 hover:bg-action-primary/10 transition-colors w-full text-center py-3 text-sm font-medium text-action-primary no-underline rounded-b-xl border-t border-action-primary/10",
2538
+ children: footerLinkLabel
2539
+ }
2540
+ ) })
2541
+ ] });
2542
+ };
2543
+ var PostFooter_default = PostFooter;
2544
+
2545
+ // src/components/PostMediaLayout/uploadStats.ts
2546
+ function deriveUploadStats(statuses) {
2547
+ let inFlight = 0;
2548
+ let done = 0;
2549
+ let failed = 0;
2550
+ for (const s of Object.values(statuses ?? {})) {
2551
+ if (s === "uploading" || s === "processing") inFlight++;
2552
+ else if (s === "uploaded") done++;
2553
+ else if (s === "error") failed++;
2554
+ }
2555
+ return { inFlight, done, failed, total: inFlight + done + failed };
2556
+ }
2557
+
2558
+ // src/components/PostUploadStrip.tsx
2559
+ import { jsx as jsx23, jsxs as jsxs14 } from "react/jsx-runtime";
2560
+ var PostUploadStrip = ({ statuses, labels }) => {
2561
+ const stats = deriveUploadStats(statuses);
2562
+ if (stats.total === 0) return null;
2563
+ if (stats.inFlight === 0 && stats.failed === 0) return null;
2564
+ const hasError = stats.failed > 0;
2565
+ const remaining = stats.inFlight;
2566
+ const remainingTemplate = labels?.uploadingRemaining ?? "Uploading {count} more";
2567
+ const doneLabel = labels?.uploadingDone ?? "All uploaded";
2568
+ const progressLabel = remaining > 0 ? remainingTemplate.replace("{count}", String(remaining)) : doneLabel;
2569
+ return /* @__PURE__ */ jsxs14(
2570
+ "div",
2571
+ {
2572
+ className: cn(
2573
+ "not-prose flex items-center gap-2.5 border-t border-border px-5 py-2.5",
2574
+ hasError ? "bg-feedback-danger/8" : "bg-feedback-info/8"
2575
+ ),
2576
+ children: [
2577
+ remaining > 0 ? /* @__PURE__ */ jsx23(
2578
+ AppIcon,
2579
+ {
2580
+ name: "loader",
2581
+ className: cn(
2582
+ "size-4 shrink-0 animate-spin",
2583
+ hasError ? "text-feedback-danger" : "text-feedback-info"
2584
+ )
2585
+ }
2586
+ ) : /* @__PURE__ */ jsx23(
2587
+ AppIcon,
2588
+ {
2589
+ name: "triangleAlert",
2590
+ className: "size-4 shrink-0 text-feedback-danger"
2591
+ }
2592
+ ),
2593
+ /* @__PURE__ */ jsx23(
2594
+ "span",
2595
+ {
2596
+ className: cn(
2597
+ "truncate text-xs font-medium",
2598
+ hasError ? "text-feedback-danger" : "text-feedback-info"
2599
+ ),
2600
+ children: progressLabel
2601
+ }
2602
+ ),
2603
+ /* @__PURE__ */ jsxs14(
2604
+ "span",
2605
+ {
2606
+ className: cn(
2607
+ "ml-auto shrink-0 text-[11px]",
2608
+ hasError ? "text-feedback-danger/80" : "text-feedback-info/80"
2609
+ ),
2610
+ children: [
2611
+ stats.done,
2612
+ " / ",
2613
+ stats.total,
2614
+ hasError && ` \xB7 ${stats.failed} failed`
2615
+ ]
2616
+ }
2617
+ )
2618
+ ]
2619
+ }
2620
+ );
2621
+ };
2622
+ var PostUploadStrip_default = PostUploadStrip;
2623
+
2624
+ // src/components/EraseConfirmModal.tsx
2625
+ import { useEffect as useEffect2, useState as useState4 } from "react";
2626
+ import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
2627
+ var EraseConfirmModal = ({
2628
+ onYesPress,
2629
+ onNoPress,
2630
+ itemType = "comment",
2631
+ isOpen,
2632
+ handleClose
2633
+ }) => {
2634
+ const [first, setFirst] = useState4(true);
2635
+ const [second, setSecond] = useState4(false);
2636
+ useEffect2(() => {
2637
+ if (!isOpen) return;
2638
+ const closeOnEscape = (e) => {
2639
+ if (e.key === "Escape") handleClose();
2640
+ };
2641
+ document.body.addEventListener("keydown", closeOnEscape);
2642
+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
2643
+ document.body.style.overflow = "hidden";
2644
+ document.body.style.paddingRight = `${scrollbarWidth}px`;
2645
+ return () => {
2646
+ document.body.removeEventListener("keydown", closeOnEscape);
2647
+ document.body.style.overflow = "";
2648
+ document.body.style.paddingRight = "";
2649
+ };
2650
+ }, [isOpen, handleClose]);
2651
+ if (!isOpen) return null;
2652
+ const handleFirst = (e) => {
2653
+ e.stopPropagation();
2654
+ e.nativeEvent.stopImmediatePropagation();
2655
+ setFirst(false);
2656
+ setSecond(true);
2657
+ };
2658
+ const handleNoPress = (e) => {
2659
+ onNoPress(e);
2660
+ setFirst(true);
2661
+ setSecond(false);
2662
+ };
2663
+ const eraseLabel = itemType === "Post" ? "Erase post" : "Erase comment";
2664
+ const confirmTitle = itemType === "Post" ? "Are you sure you want to erase this post?" : "Are you sure you want to erase this comment?";
2665
+ const confirmBody = itemType === "Post" ? "This action cannot be undone." : "This action cannot be undone.";
2666
+ return /* @__PURE__ */ jsxs15("div", { children: [
2667
+ /* @__PURE__ */ jsx24("div", { className: "fixed left-0 top-0 z-50 h-screen w-full bg-black/50" }),
2668
+ /* @__PURE__ */ jsx24(
2669
+ "div",
2670
+ {
2671
+ className: "fixed left-0 top-0 z-50 flex h-screen w-full items-center justify-center p-4",
2672
+ onClick: (e) => {
2673
+ e.stopPropagation();
2674
+ e.nativeEvent.stopImmediatePropagation();
2675
+ },
2676
+ children: /* @__PURE__ */ jsxs15("div", { className: "rounded-3xl bg-background px-8 py-6 text-center shadow-3", children: [
2677
+ first && /* @__PURE__ */ jsxs15(
2678
+ "div",
2679
+ {
2680
+ className: "flex w-[256px] flex-col justify-around",
2681
+ onClick: (e) => e.stopPropagation(),
2682
+ children: [
2683
+ /* @__PURE__ */ jsxs15(
2684
+ "div",
2685
+ {
2686
+ className: "mb-4 flex flex-row items-center hover:cursor-pointer hover:opacity-50",
2687
+ "data-testid": "modal-erase-post",
2688
+ onClick: (e) => handleFirst(e),
2689
+ children: [
2690
+ /* @__PURE__ */ jsx24("div", { children: /* @__PURE__ */ jsx24(AppIcon, { name: "chatErase", className: "size-7.5 cursor-pointer" }) }),
2691
+ /* @__PURE__ */ jsx24("p", { className: "ml-3 text-lg", children: eraseLabel })
2692
+ ]
2693
+ }
2694
+ ),
2695
+ /* @__PURE__ */ jsxs15(
2696
+ "div",
2697
+ {
2698
+ className: "flex flex-row items-center hover:cursor-pointer hover:opacity-50",
2699
+ onClick: handleNoPress,
2700
+ children: [
2701
+ /* @__PURE__ */ jsx24("div", { children: /* @__PURE__ */ jsx24(AppIcon, { name: "cancel", className: "cursor-pointer size-6" }) }),
2702
+ /* @__PURE__ */ jsx24("p", { className: "ml-3 text-lg", children: "Cancel" })
2703
+ ]
2704
+ }
2705
+ )
2706
+ ]
2707
+ }
2708
+ ),
2709
+ second && /* @__PURE__ */ jsxs15(
2710
+ "div",
2711
+ {
2712
+ onClick: (e) => e.stopPropagation(),
2713
+ className: "flex w-[296px] flex-col items-center justify-around",
2714
+ children: [
2715
+ /* @__PURE__ */ jsxs15("div", { className: "mb-4", children: [
2716
+ /* @__PURE__ */ jsx24("h4", { className: "mb-2 text-lg font-bold", children: confirmTitle }),
2717
+ /* @__PURE__ */ jsx24("p", { className: "text-muted-foreground", children: confirmBody })
2718
+ ] }),
2719
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center justify-around", children: [
2720
+ /* @__PURE__ */ jsx24(
2721
+ "button",
2722
+ {
2723
+ className: "mr-6 py-2 text-muted-foreground",
2724
+ onClick: handleNoPress,
2725
+ children: "Cancel"
2726
+ }
2727
+ ),
2728
+ /* @__PURE__ */ jsx24(
2729
+ "button",
2730
+ {
2731
+ className: "rounded-3xl bg-action-primary px-6 py-2 font-semibold text-white",
2732
+ onClick: onYesPress,
2733
+ "data-testid": "confirm-modal-erase",
2734
+ children: "Erase"
2735
+ }
2736
+ )
2737
+ ] })
2738
+ ]
2739
+ }
2740
+ )
2741
+ ] })
2742
+ }
2743
+ )
2744
+ ] });
2745
+ };
2746
+ var EraseConfirmModal_default = EraseConfirmModal;
2747
+
2748
+ // src/components/PostCard.tsx
2749
+ import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
2750
+ var PostCard = ({
2751
+ post,
2752
+ author,
2753
+ authorHref,
2754
+ group,
2755
+ groupHref,
2756
+ postHref,
2757
+ linkComponent,
2758
+ imageComponent,
2759
+ isAdmin,
2760
+ isOwner,
2761
+ isPostPreview,
2762
+ isPrivate,
2763
+ membersCount,
2764
+ crossPostGroup,
2765
+ locale,
2766
+ translate,
2767
+ onPostPress,
2768
+ onErasePress,
2769
+ onCancelPost,
2770
+ onVotePress,
2771
+ onCardPress,
2772
+ renderActionsMenu,
2773
+ renderReactionTrigger,
2774
+ renderReactionModal,
2775
+ onNavigate,
2776
+ supportDeepviewDomain,
2777
+ labels,
2778
+ mode = "feed",
2779
+ commentCount = 0,
2780
+ unreadCommentCount = 0,
2781
+ isSnoozed = false,
2782
+ onSnoozeToggle,
2783
+ mediaItemStatuses,
2784
+ showFooterLink = false,
2785
+ footerLinkLabel,
2786
+ footerLinkHref,
2787
+ className
2788
+ }) => {
2789
+ const [eraseModalOpen, setEraseModalOpen] = useState5(false);
2790
+ const isPending = !!post.is_pending;
2791
+ const isPostDetail = mode === "detail";
2792
+ const sendingLabel = labels?.sending ?? "Sending...";
2793
+ const contextData = useMemo7(
2794
+ () => ({
2795
+ body: post.body ?? "",
2796
+ htmlBody: post.htmlBody,
2797
+ jsonBody: post.jsonBody,
2798
+ textBody: post.textBody,
2799
+ items: post.items,
2800
+ mentionData: post.mentionData,
2801
+ sections: post.sections
2802
+ }),
2803
+ [post.body, post.htmlBody, post.jsonBody, post.textBody, post.items, post.mentionData, post.sections]
2804
+ );
2805
+ const isEmailPost = Boolean(post.htmlBody && !post.jsonBody);
2806
+ const resolvedContent = useMemo7(
2807
+ () => resolvePostContent(contextData, {
2808
+ postType: post.postType,
2809
+ pollResult: post.poll_result ?? null,
2810
+ postSpecialType: post.isFeatured ? 1 : 0,
2811
+ mode: isPostDetail ? "detail" : mode,
2812
+ isEmailPost
2813
+ }),
2814
+ [contextData, post.postType, post.poll_result, post.isFeatured, isPostDetail, mode, isEmailPost]
2815
+ );
2816
+ const handleErasePress = () => {
2817
+ setEraseModalOpen(true);
2818
+ };
2819
+ const handleEraseConfirm = () => {
2820
+ setEraseModalOpen(false);
2821
+ onErasePress?.();
2822
+ };
2823
+ const handleEraseCancel = () => {
2824
+ setEraseModalOpen(false);
2825
+ };
2826
+ const handleCardClick = (e) => {
2827
+ e.stopPropagation();
2828
+ e.nativeEvent.stopImmediatePropagation();
2829
+ if (isPending) return;
2830
+ onPostPress?.();
2831
+ };
2832
+ const handleCardKeyDown = (e) => {
2833
+ if (e.key === "Enter" || e.key === " ") {
2834
+ e.preventDefault();
2835
+ e.stopPropagation();
2836
+ if (isPending) return;
2837
+ onPostPress?.();
2838
+ }
2839
+ };
2840
+ return /* @__PURE__ */ jsxs16(
2841
+ "article",
2842
+ {
2843
+ className: cn(
2844
+ "w-full flex flex-col box-border bg-background",
2845
+ !isPostDetail && "cursor-pointer sm:border border-border rounded-xl sm:rounded-2xl overflow-hidden shadow-sm hover:shadow-md transition-shadow duration-300 relative mt-6",
2846
+ isPostDetail && "cursor-auto rounded-t-2xl w-full",
2847
+ post.isFeatured && !isPostDetail && "bg-highlight-surface border-highlight-border sm:border-t-4",
2848
+ isPending && "cursor-default",
2849
+ className
2850
+ ),
2851
+ onClick: !isPostDetail && onPostPress ? handleCardClick : void 0,
2852
+ onKeyDown: !isPostDetail && !isPending && onPostPress ? handleCardKeyDown : void 0,
2853
+ tabIndex: !isPostDetail && !isPending && onPostPress ? 0 : void 0,
2854
+ role: !isPostDetail && !isPending && onPostPress ? "button" : void 0,
2855
+ "aria-label": !isPostDetail && !isPending && onPostPress ? "View post details" : void 0,
2856
+ children: [
2857
+ !isPostDetail && !isPending && postHref && linkComponent && React11.createElement(linkComponent, {
2858
+ href: postHref,
2859
+ className: "sr-only",
2860
+ children: "View post details"
2861
+ }),
2862
+ !isPostDetail && !isPending && postHref && !linkComponent && /* @__PURE__ */ jsx25("a", { href: postHref, className: "sr-only", onClick: (e) => e.stopPropagation(), children: "View post details" }),
2863
+ isPending && /* @__PURE__ */ jsx25("div", { className: "absolute top-0 left-0 w-full z-10", children: /* @__PURE__ */ jsx25(
2864
+ "div",
2865
+ {
2866
+ className: "h-1 w-full overflow-hidden rounded bg-muted",
2867
+ "aria-label": sendingLabel,
2868
+ "data-testid": "post-loader",
2869
+ children: /* @__PURE__ */ jsx25("div", { className: "h-full animate-pulse bg-action-primary" })
2870
+ }
2871
+ ) }),
2872
+ !isPending && eraseModalOpen && /* @__PURE__ */ jsx25(
2873
+ EraseConfirmModal_default,
2874
+ {
2875
+ isOpen: eraseModalOpen,
2876
+ itemType: "Post",
2877
+ onYesPress: handleEraseConfirm,
2878
+ onNoPress: handleEraseCancel,
2879
+ handleClose: handleEraseCancel
2880
+ }
2881
+ ),
2882
+ /* @__PURE__ */ jsxs16("div", { className: cn(isPending && "opacity-50"), children: [
2883
+ /* @__PURE__ */ jsx25(
2884
+ PostHeader_default,
2885
+ {
2886
+ author,
2887
+ authorHref,
2888
+ group,
2889
+ groupHref,
2890
+ postedAt: post.postedAt,
2891
+ postType: post.postType,
2892
+ isFeatured: post.isFeatured,
2893
+ isPostPreview,
2894
+ isAdmin,
2895
+ isOwner,
2896
+ isPrivate,
2897
+ membersCount,
2898
+ crossPostGroup,
2899
+ locale,
2900
+ translate,
2901
+ onErasePress: onErasePress ? handleErasePress : void 0,
2902
+ onCancelPost,
2903
+ renderActionsMenu,
2904
+ linkComponent,
2905
+ imageComponent,
2906
+ labels
2907
+ }
2908
+ ),
2909
+ /* @__PURE__ */ jsx25(
2910
+ PostBody_default,
2911
+ {
2912
+ textContent: resolvedContent.textContent,
2913
+ showFull: isPostDetail,
2914
+ isPostDetail,
2915
+ isRichContent: resolvedContent.isRichContent,
2916
+ isEmailPost,
2917
+ hideEmailDescription: isEmailPost && !isPostDetail && resolvedContent.primaryContent?.type === "media",
2918
+ onNavigate,
2919
+ supportDeepviewDomain
2920
+ }
2921
+ ),
2922
+ resolvedContent.sections && /* @__PURE__ */ jsx25(
2923
+ PostSectionsView_default,
2924
+ {
2925
+ sections: resolvedContent.sections.sections,
2926
+ isPostDetail,
2927
+ onCardPress: onCardPress ?? (() => {
2928
+ })
2929
+ }
2930
+ ),
2931
+ /* @__PURE__ */ jsx25(
2932
+ PostItemsView_default,
2933
+ {
2934
+ primaryContent: resolvedContent.primaryContent,
2935
+ isPostDetail,
2936
+ onCardPress: onCardPress ?? (() => {
2937
+ }),
2938
+ onVotePress: onVotePress ?? (() => {
2939
+ }),
2940
+ isPostOwner: isOwner,
2941
+ parentId: post.context_id,
2942
+ isCommentScope: false,
2943
+ imageComponent,
2944
+ mediaItemStatuses
2945
+ }
2946
+ ),
2947
+ /* @__PURE__ */ jsx25(
2948
+ PostUploadStrip_default,
2949
+ {
2950
+ statuses: mediaItemStatuses,
2951
+ labels: {
2952
+ uploadingRemaining: labels?.uploadingRemaining,
2953
+ uploadingDone: labels?.uploadingDone
2954
+ }
2955
+ }
2956
+ )
2957
+ ] }),
2958
+ /* @__PURE__ */ jsx25("div", { className: cn(isPending && "opacity-50 pointer-events-none"), children: /* @__PURE__ */ jsx25(
2959
+ PostFooter_default,
2960
+ {
2961
+ postHref,
2962
+ commentCount,
2963
+ unreadCommentCount,
2964
+ isSnoozed,
2965
+ onSnoozeToggle,
2966
+ showFooterLink,
2967
+ footerLinkLabel,
2968
+ footerLinkHref,
2969
+ renderReactionTrigger,
2970
+ renderReactionModal
2971
+ }
2972
+ ) })
2973
+ ]
2974
+ }
2975
+ );
2976
+ };
2977
+ var PostCard_default = PostCard;
2978
+
2979
+ // src/components/PostSectionsSummaryView.tsx
2980
+ import { useCallback as useCallback4, useMemo as useMemo8 } from "react";
2981
+ import { jsx as jsx26, jsxs as jsxs17 } from "react/jsx-runtime";
2982
+ var BODY_SIZE = 80;
2983
+ var PostSectionsSummaryView = ({
2984
+ sections,
2985
+ fontColor,
2986
+ onCardPress
2987
+ }) => {
2988
+ const imageItems = useMemo8(() => {
2989
+ if (!sections || sections.length === 0) return [];
2990
+ return sections.reduce((items, section) => {
2991
+ if (section.type === contentTypeConstants.NEW_POST_MEDIA) {
2992
+ return items.concat(section.data.items || []);
2993
+ }
2994
+ return items;
2995
+ }, []);
2996
+ }, [sections]);
2997
+ const textBody = useMemo8(() => {
2998
+ if (!sections || sections.length === 0) return "";
2999
+ const allBody = sections.reduce((body, section) => {
3000
+ if (section.type === contentTypeConstants.NEW_POST_MEDIA && section.data.body) {
3001
+ return body + (body ? "\n" : "") + section.data.body;
3002
+ }
3003
+ return body;
3004
+ }, "");
3005
+ return allBody ? `${allBody.slice(0, BODY_SIZE)}...` : "";
3006
+ }, [sections]);
3007
+ const renderSectionItem = useCallback4(
3008
+ (section, index) => {
3009
+ const { type, data } = section;
3010
+ if (type === contentTypeConstants.NEW_POST_MESSAGE) {
3011
+ return /* @__PURE__ */ jsx26(
3012
+ AutoLink_default,
3013
+ {
3014
+ text: data.body || "",
3015
+ className: "whitespace-normal px-4 pb-4 leading-relaxed text-gray-800",
3016
+ style: {
3017
+ color: fontColor || "var(--color-foreground)",
3018
+ whiteSpace: "pre-wrap",
3019
+ wordBreak: "break-word"
3020
+ },
3021
+ mentionData: data.mentionData
3022
+ },
3023
+ `${index} - ${data.body}`
3024
+ );
3025
+ }
3026
+ return null;
3027
+ },
3028
+ [fontColor]
3029
+ );
3030
+ if (!sections || sections.length === 0) {
3031
+ return null;
3032
+ }
3033
+ return /* @__PURE__ */ jsxs17("div", { className: "flex-col", children: [
3034
+ sections.map((section, index) => {
3035
+ return renderSectionItem(section, index);
3036
+ }),
3037
+ textBody && /* @__PURE__ */ jsx26(
3038
+ AutoLink_default,
3039
+ {
3040
+ text: textBody,
3041
+ className: "whitespace-normal px-4 pb-4 leading-relaxed text-gray-800",
3042
+ style: {
3043
+ color: fontColor || "var(--color-foreground)",
3044
+ whiteSpace: "pre-wrap",
3045
+ wordBreak: "break-word"
3046
+ },
3047
+ mentionData: []
3048
+ },
3049
+ "body"
3050
+ ),
3051
+ /* @__PURE__ */ jsx26(
3052
+ PostMediaLayoutCore_default,
3053
+ {
3054
+ items: imageItems
3055
+ }
3056
+ )
3057
+ ] });
3058
+ };
3059
+ var PostSectionsSummaryView_default = PostSectionsSummaryView;
3060
+
3061
+ // src/modals/ReactionModal.tsx
3062
+ import { useRef as useRef2, useEffect as useEffect3 } from "react";
3063
+ import { jsx as jsx27 } from "react/jsx-runtime";
3064
+ var reactionList = ["\u{1F44D}", "\u{1F60D}", "\u{1F602}", "\u{1F631}", "\u{1F62D}", "\u{1F60E}"];
3065
+ var ReactionModal = ({ isOpen, close, sendReaction, onAnalyticsEvent }) => {
3066
+ const ref = useRef2(null);
3067
+ useEffect3(() => {
3068
+ if (!isOpen) return;
3069
+ onAnalyticsEvent?.("reaction");
3070
+ const handleOutsideClick = (e) => {
3071
+ if (ref.current && !ref.current.contains(e.target)) {
3072
+ close();
3073
+ }
3074
+ };
3075
+ document.addEventListener("click", handleOutsideClick);
3076
+ return () => {
3077
+ document.removeEventListener("click", handleOutsideClick);
3078
+ };
3079
+ }, [isOpen, close, onAnalyticsEvent]);
3080
+ if (!isOpen) return null;
3081
+ return /* @__PURE__ */ jsx27("div", { className: "z-500 flex items-center justify-center", role: "dialog", "aria-modal": "true", "aria-label": "Reaction picker", children: /* @__PURE__ */ jsx27("div", { ref, className: "rounded-3xl bg-muted p-2", children: /* @__PURE__ */ jsx27("div", { className: "flex justify-around", children: reactionList.map((reaction, index) => {
3082
+ return /* @__PURE__ */ jsx27(
3083
+ "button",
3084
+ {
3085
+ type: "button",
3086
+ onClick: (e) => {
3087
+ e.preventDefault();
3088
+ e.stopPropagation();
3089
+ onAnalyticsEvent?.("reaction_send");
3090
+ sendReaction(reaction);
3091
+ close();
3092
+ },
3093
+ className: "cursor-pointer px-2 text-3xl hover:scale-125 transition-transform",
3094
+ children: reaction
3095
+ },
3096
+ reaction + index
3097
+ );
3098
+ }) }) }) });
3099
+ };
3100
+ export {
3101
+ AutoLink_default as AutoLink,
3102
+ EraseConfirmModal_default as EraseConfirmModal,
3103
+ MediaUploadStatusOverlay_default as MediaUploadStatusOverlay,
3104
+ POST_TYPE_HINT_DEFAULTS,
3105
+ PostBody_default as PostBody,
3106
+ PostCard_default as PostCard,
3107
+ PostEmail_default as PostEmail,
3108
+ PostEmbedView_default as PostEmbedView,
3109
+ PostFooter_default as PostFooter,
3110
+ PostHeader_default as PostHeader,
3111
+ PostItemsView_default as PostItemsView,
3112
+ PostLinkItem_default as PostLinkItem,
3113
+ PostMediaLayoutCore_default as PostMediaLayoutCore,
3114
+ PostPollView_default as PostPollView,
3115
+ PostSectionSourceInfoView_default as PostSectionSourceInfoView,
3116
+ PostSectionsSummaryView_default as PostSectionsSummaryView,
3117
+ PostSectionsView_default as PostSectionsView,
3118
+ PostTextItem_default as PostTextItem,
3119
+ PostUploadStrip_default as PostUploadStrip,
3120
+ ReactionModal,
3121
+ getPostTypeHint,
3122
+ hasMeaningfulHtml,
3123
+ resolvePostContent
3124
+ };
3125
+ /*!
3126
+ * React Native Autolink
3127
+ *
3128
+ * Copyright 2016-2018 Josh Swan
3129
+ * Released under the MIT license
3130
+ * https://github.com/joshswan/react-native-autolink/blob/master/LICENSE
3131
+ *
3132
+ * Simplified and improved by Dean, to support mention list.
3133
+ * Ported to design-system post-components: routing and config dependencies removed.
3134
+ */
3135
+ //# sourceMappingURL=index.js.map