@poolse/react-native 2.19.0 → 2.20.1
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/CHANGELOG.md +48 -0
- package/dist/index.cjs +304 -178
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -3
- package/dist/index.d.ts +37 -3
- package/dist/index.js +304 -178
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -113,7 +113,7 @@ var radii = {
|
|
|
113
113
|
lg: 16,
|
|
114
114
|
xl: 24,
|
|
115
115
|
pill: 999,
|
|
116
|
-
bubble:
|
|
116
|
+
bubble: 18
|
|
117
117
|
};
|
|
118
118
|
var spacing = {
|
|
119
119
|
xs: 4,
|
|
@@ -2345,7 +2345,12 @@ var styles12 = reactNative.StyleSheet.create({
|
|
|
2345
2345
|
fontWeight: "500"
|
|
2346
2346
|
}
|
|
2347
2347
|
});
|
|
2348
|
-
function AttachmentPreview({
|
|
2348
|
+
function AttachmentPreview({
|
|
2349
|
+
attachment,
|
|
2350
|
+
onPress,
|
|
2351
|
+
maxWidth = 280,
|
|
2352
|
+
tone = "default"
|
|
2353
|
+
}) {
|
|
2349
2354
|
const theme = usePoolseTheme();
|
|
2350
2355
|
const isImage = (attachment.content_type ?? "").startsWith("image/");
|
|
2351
2356
|
const { url } = react$1.useAttachmentUrl(attachment.id);
|
|
@@ -2379,6 +2384,9 @@ function AttachmentPreview({ attachment, onPress, maxWidth = 280 }) {
|
|
|
2379
2384
|
}
|
|
2380
2385
|
) });
|
|
2381
2386
|
}
|
|
2387
|
+
const onBrand = tone === "onBrand";
|
|
2388
|
+
const ink = onBrand ? theme.colors.onBrand : theme.colors.ink;
|
|
2389
|
+
const inkSoft = onBrand ? theme.colors.onBrand : theme.colors.ink3;
|
|
2382
2390
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2383
2391
|
reactNative.Pressable,
|
|
2384
2392
|
{
|
|
@@ -2387,23 +2395,23 @@ function AttachmentPreview({ attachment, onPress, maxWidth = 280 }) {
|
|
|
2387
2395
|
style: [
|
|
2388
2396
|
styles13.fileCard,
|
|
2389
2397
|
{
|
|
2390
|
-
backgroundColor: theme.colors.surface2,
|
|
2391
|
-
borderColor: theme.colors.border,
|
|
2398
|
+
backgroundColor: onBrand ? "rgba(255,255,255,0.18)" : theme.colors.surface2,
|
|
2399
|
+
borderColor: onBrand ? "transparent" : theme.colors.border,
|
|
2392
2400
|
borderRadius: theme.radii.md
|
|
2393
2401
|
}
|
|
2394
2402
|
],
|
|
2395
2403
|
children: [
|
|
2396
|
-
/* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "attachment", size: 18, color: theme.colors.ink2 }),
|
|
2404
|
+
/* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "attachment", size: 18, color: onBrand ? ink : theme.colors.ink2 }),
|
|
2397
2405
|
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1, marginLeft: 8 }, children: [
|
|
2398
2406
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2399
2407
|
reactNative.Text,
|
|
2400
2408
|
{
|
|
2401
|
-
style: [styles13.fileName, { color:
|
|
2409
|
+
style: [styles13.fileName, { color: ink, fontFamily: theme.type.fontBody }],
|
|
2402
2410
|
numberOfLines: 1,
|
|
2403
2411
|
children: attachment.original_filename ?? "file"
|
|
2404
2412
|
}
|
|
2405
2413
|
),
|
|
2406
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles13.fileSize, { color:
|
|
2414
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles13.fileSize, { color: inkSoft, opacity: onBrand ? 0.8 : 1 }], children: formatBytes2(attachment.byte_size) })
|
|
2407
2415
|
] })
|
|
2408
2416
|
]
|
|
2409
2417
|
}
|
|
@@ -2431,9 +2439,16 @@ var styles13 = reactNative.StyleSheet.create({
|
|
|
2431
2439
|
fontSize: 11
|
|
2432
2440
|
}
|
|
2433
2441
|
});
|
|
2434
|
-
function ImageMosaic({
|
|
2442
|
+
function ImageMosaic({
|
|
2443
|
+
images,
|
|
2444
|
+
maxWidth,
|
|
2445
|
+
radius,
|
|
2446
|
+
squareBottom = false,
|
|
2447
|
+
onLongPress
|
|
2448
|
+
}) {
|
|
2435
2449
|
const theme = usePoolseTheme();
|
|
2436
2450
|
const r = radius ?? theme.radii.md;
|
|
2451
|
+
const rBottom = squareBottom ? 5 : r;
|
|
2437
2452
|
const [viewerIndex, setViewerIndex] = react.useState(null);
|
|
2438
2453
|
if (images.length === 0) return null;
|
|
2439
2454
|
const visible = images.slice(0, 4);
|
|
@@ -2446,14 +2461,24 @@ function ImageMosaic({ images, maxWidth, radius }) {
|
|
|
2446
2461
|
style: [
|
|
2447
2462
|
styles14.root,
|
|
2448
2463
|
{
|
|
2449
|
-
|
|
2450
|
-
|
|
2464
|
+
borderTopLeftRadius: r,
|
|
2465
|
+
borderTopRightRadius: r,
|
|
2466
|
+
borderBottomLeftRadius: rBottom,
|
|
2467
|
+
borderBottomRightRadius: rBottom,
|
|
2468
|
+
// Explicit pixel width, not just a cap: the mosaic sits
|
|
2469
|
+
// inside an auto-sized bubble, and a percentage width
|
|
2470
|
+
// against an indefinite parent collapses (or, worse,
|
|
2471
|
+
// resolves against the image's intrinsic size and blows
|
|
2472
|
+
// the row open). Media bubbles are fixed-width — the
|
|
2473
|
+
// WhatsApp convention anyway.
|
|
2474
|
+
...maxWidth ? { width: maxWidth } : {}
|
|
2451
2475
|
}
|
|
2452
2476
|
],
|
|
2453
2477
|
children: [
|
|
2454
2478
|
n === 1 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2455
2479
|
Tile,
|
|
2456
2480
|
{
|
|
2481
|
+
onLongPress,
|
|
2457
2482
|
attachment: visible[0],
|
|
2458
2483
|
style: styles14.tileFull,
|
|
2459
2484
|
onPress: () => setViewerIndex(0),
|
|
@@ -2464,6 +2489,7 @@ function ImageMosaic({ images, maxWidth, radius }) {
|
|
|
2464
2489
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2465
2490
|
Tile,
|
|
2466
2491
|
{
|
|
2492
|
+
onLongPress,
|
|
2467
2493
|
attachment: visible[0],
|
|
2468
2494
|
style: styles14.tileHalfSquare,
|
|
2469
2495
|
onPress: () => setViewerIndex(0),
|
|
@@ -2473,6 +2499,7 @@ function ImageMosaic({ images, maxWidth, radius }) {
|
|
|
2473
2499
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2474
2500
|
Tile,
|
|
2475
2501
|
{
|
|
2502
|
+
onLongPress,
|
|
2476
2503
|
attachment: visible[1],
|
|
2477
2504
|
style: styles14.tileHalfSquare,
|
|
2478
2505
|
onPress: () => setViewerIndex(1),
|
|
@@ -2484,6 +2511,7 @@ function ImageMosaic({ images, maxWidth, radius }) {
|
|
|
2484
2511
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2485
2512
|
Tile,
|
|
2486
2513
|
{
|
|
2514
|
+
onLongPress,
|
|
2487
2515
|
attachment: visible[0],
|
|
2488
2516
|
style: styles14.tileHero,
|
|
2489
2517
|
onPress: () => setViewerIndex(0),
|
|
@@ -2494,6 +2522,7 @@ function ImageMosaic({ images, maxWidth, radius }) {
|
|
|
2494
2522
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2495
2523
|
Tile,
|
|
2496
2524
|
{
|
|
2525
|
+
onLongPress,
|
|
2497
2526
|
attachment: visible[1],
|
|
2498
2527
|
style: styles14.tileHalfSquare,
|
|
2499
2528
|
onPress: () => setViewerIndex(1),
|
|
@@ -2503,6 +2532,7 @@ function ImageMosaic({ images, maxWidth, radius }) {
|
|
|
2503
2532
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2504
2533
|
Tile,
|
|
2505
2534
|
{
|
|
2535
|
+
onLongPress,
|
|
2506
2536
|
attachment: visible[2],
|
|
2507
2537
|
style: styles14.tileHalfSquare,
|
|
2508
2538
|
onPress: () => setViewerIndex(2),
|
|
@@ -2516,6 +2546,7 @@ function ImageMosaic({ images, maxWidth, radius }) {
|
|
|
2516
2546
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2517
2547
|
Tile,
|
|
2518
2548
|
{
|
|
2549
|
+
onLongPress,
|
|
2519
2550
|
attachment: visible[0],
|
|
2520
2551
|
style: styles14.tileHalfSquare,
|
|
2521
2552
|
onPress: () => setViewerIndex(0),
|
|
@@ -2525,6 +2556,7 @@ function ImageMosaic({ images, maxWidth, radius }) {
|
|
|
2525
2556
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2526
2557
|
Tile,
|
|
2527
2558
|
{
|
|
2559
|
+
onLongPress,
|
|
2528
2560
|
attachment: visible[1],
|
|
2529
2561
|
style: styles14.tileHalfSquare,
|
|
2530
2562
|
onPress: () => setViewerIndex(1),
|
|
@@ -2536,6 +2568,7 @@ function ImageMosaic({ images, maxWidth, radius }) {
|
|
|
2536
2568
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2537
2569
|
Tile,
|
|
2538
2570
|
{
|
|
2571
|
+
onLongPress,
|
|
2539
2572
|
attachment: visible[2],
|
|
2540
2573
|
style: styles14.tileHalfSquare,
|
|
2541
2574
|
onPress: () => setViewerIndex(2),
|
|
@@ -2545,6 +2578,7 @@ function ImageMosaic({ images, maxWidth, radius }) {
|
|
|
2545
2578
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2546
2579
|
Tile,
|
|
2547
2580
|
{
|
|
2581
|
+
onLongPress,
|
|
2548
2582
|
attachment: visible[3],
|
|
2549
2583
|
style: styles14.tileHalfSquare,
|
|
2550
2584
|
onPress: () => setViewerIndex(3),
|
|
@@ -2578,11 +2612,12 @@ function Tile({
|
|
|
2578
2612
|
attachment,
|
|
2579
2613
|
style,
|
|
2580
2614
|
overlay,
|
|
2581
|
-
onPress
|
|
2615
|
+
onPress,
|
|
2616
|
+
onLongPress
|
|
2582
2617
|
}) {
|
|
2583
2618
|
const theme = usePoolseTheme();
|
|
2584
2619
|
const { url } = react$1.useAttachmentUrl(attachment.id);
|
|
2585
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { onPress, style: [styles14.tileBase, style], children: [
|
|
2620
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { onPress, onLongPress, style: [styles14.tileBase, style], children: [
|
|
2586
2621
|
url ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2587
2622
|
reactNative.Image,
|
|
2588
2623
|
{
|
|
@@ -2684,6 +2719,27 @@ var styles14 = reactNative.StyleSheet.create({
|
|
|
2684
2719
|
borderRadius: 20
|
|
2685
2720
|
}
|
|
2686
2721
|
});
|
|
2722
|
+
|
|
2723
|
+
// src/internal/bubbleText.ts
|
|
2724
|
+
function hasMarkdown(body) {
|
|
2725
|
+
return /[*_~`#>|\\]|\[.*\]|^\s*(?:[-+]|\d+[.)])\s|https?:\/\/|www\./ms.test(body);
|
|
2726
|
+
}
|
|
2727
|
+
function emojiOnlyCount(body) {
|
|
2728
|
+
const text = body.trim();
|
|
2729
|
+
if (!text || text.length > 32) return 0;
|
|
2730
|
+
try {
|
|
2731
|
+
const unit = "(?:\\p{Regional_Indicator}{2}|\\p{Extended_Pictographic})[\\u{1F3FB}-\\u{1F3FF}]?\\uFE0F?(?:\\u200D\\p{Extended_Pictographic}[\\u{1F3FB}-\\u{1F3FF}]?\\uFE0F?)*";
|
|
2732
|
+
const whole = new RegExp(`^(?:${unit}){1,3}$`, "u");
|
|
2733
|
+
if (!whole.test(text)) return 0;
|
|
2734
|
+
const each = new RegExp(unit, "gu");
|
|
2735
|
+
return (text.match(each) ?? []).length;
|
|
2736
|
+
} catch {
|
|
2737
|
+
return 0;
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
function emojiFontSize(count) {
|
|
2741
|
+
return count === 1 ? 44 : count === 2 ? 38 : 32;
|
|
2742
|
+
}
|
|
2687
2743
|
var markdownit = Markdown.MarkdownIt({ typographer: false, linkify: true });
|
|
2688
2744
|
function renderMarkdown(body, args) {
|
|
2689
2745
|
const baseText = {
|
|
@@ -2743,107 +2799,163 @@ function MessageBubble({
|
|
|
2743
2799
|
showSenderName = false,
|
|
2744
2800
|
actionsTrigger,
|
|
2745
2801
|
showAttachments = true,
|
|
2746
|
-
markdown = true
|
|
2802
|
+
markdown = true,
|
|
2803
|
+
onLongPress
|
|
2747
2804
|
}) {
|
|
2748
2805
|
const theme = usePoolseTheme();
|
|
2749
2806
|
const { width: screenWidth } = reactNative.useWindowDimensions();
|
|
2750
2807
|
const isSelf = currentUserId !== null && message.sender_id === currentUserId;
|
|
2751
2808
|
const [expanded, setExpanded] = react.useState(false);
|
|
2752
2809
|
const bubbleMaxWidth = Math.floor(screenWidth * 0.78);
|
|
2753
|
-
const
|
|
2810
|
+
const mediaWidth = Math.min(bubbleMaxWidth - 6, 420);
|
|
2754
2811
|
const sender = react$1.useUser(!isSelf ? message.sender_external_id : null);
|
|
2755
2812
|
const senderFallbackName = message.sender_external_id ?? "Unknown";
|
|
2756
2813
|
const senderLabel = sender.profile?.displayName ?? (message.sender_external_id ? labelFor?.(message.sender_external_id) ?? senderFallbackName : senderFallbackName);
|
|
2757
2814
|
const senderTint = userColor(message.sender_external_id ?? message.sender_id ?? message.id);
|
|
2758
2815
|
const tailCorner = groupPosition === "last" || groupPosition === "standalone";
|
|
2759
|
-
const
|
|
2816
|
+
const bubbleRadius = theme.radii.bubble;
|
|
2760
2817
|
const textColor = isSelf ? theme.colors.selfBubbleText : theme.colors.otherBubbleText;
|
|
2818
|
+
const mutedOnBubble = isSelf ? theme.colors.onBrand : theme.colors.ink3;
|
|
2761
2819
|
const radii2 = {
|
|
2762
|
-
topLeft:
|
|
2763
|
-
topRight:
|
|
2764
|
-
bottomLeft:
|
|
2765
|
-
bottomRight:
|
|
2820
|
+
topLeft: bubbleRadius,
|
|
2821
|
+
topRight: bubbleRadius,
|
|
2822
|
+
bottomLeft: bubbleRadius,
|
|
2823
|
+
bottomRight: bubbleRadius
|
|
2766
2824
|
};
|
|
2767
2825
|
if (tailCorner) {
|
|
2768
|
-
if (isSelf) radii2.bottomRight =
|
|
2769
|
-
else radii2.bottomLeft =
|
|
2826
|
+
if (isSelf) radii2.bottomRight = 5;
|
|
2827
|
+
else radii2.bottomLeft = 5;
|
|
2770
2828
|
}
|
|
2771
2829
|
const body = message.body ?? "";
|
|
2772
|
-
const truncated = maxBodyLength > 0 && body.length > maxBodyLength && !expanded ? body.slice(0, maxBodyLength) + "\u2026" : body;
|
|
2773
|
-
const time = formatTime(message.inserted_at);
|
|
2774
2830
|
const isDeleted = !!message.deleted_at;
|
|
2775
2831
|
const isEdited = !!message.edited_at && !isDeleted;
|
|
2776
|
-
const
|
|
2777
|
-
const
|
|
2778
|
-
const
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2832
|
+
const trimmable = maxBodyLength > 0 && body.length > maxBodyLength;
|
|
2833
|
+
const truncated = trimmable && !expanded ? body.slice(0, maxBodyLength) + "\u2026" : body;
|
|
2834
|
+
const time = formatTime(message.inserted_at);
|
|
2835
|
+
const images = showAttachments && !isDeleted ? (message.attachments ?? []).filter((a) => isImageContentType(a.content_type)) : [];
|
|
2836
|
+
const files = showAttachments && !isDeleted ? (message.attachments ?? []).filter((a) => !isImageContentType(a.content_type)) : [];
|
|
2837
|
+
const hasMedia = images.length > 0;
|
|
2838
|
+
const hasFiles = files.length > 0;
|
|
2839
|
+
const hasBody = !isDeleted && body.length > 0;
|
|
2840
|
+
const metaOverlay = hasMedia && !hasBody && !hasFiles;
|
|
2841
|
+
const jumboCount = hasBody && !hasMedia && !hasFiles && !message.quoted_message_id ? emojiOnlyCount(body) : 0;
|
|
2842
|
+
const inlineMeta = hasBody && jumboCount === 0 && !trimmable && (!markdown || !hasMarkdown(truncated));
|
|
2843
|
+
const sectionAbove = hasMedia || hasFiles || !!message.quoted_message || isDeleted;
|
|
2844
|
+
const ghost = ` ${isEdited ? "edited " : ""}${time}${isSelf && readState ? " \u2713\u2713" : ""}`;
|
|
2845
|
+
const metaRow = (variant) => {
|
|
2846
|
+
const color = variant === "overlay" ? "#FFFFFF" : variant === "jumbo" ? theme.colors.ink3 : mutedOnBubble;
|
|
2847
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2848
|
+
reactNative.View,
|
|
2849
|
+
{
|
|
2850
|
+
style: [
|
|
2851
|
+
styles15.metaRow,
|
|
2852
|
+
variant === "inline" && styles15.metaInline,
|
|
2853
|
+
variant === "overlay" && styles15.metaOverlay,
|
|
2854
|
+
variant === "block" && styles15.metaBlock,
|
|
2855
|
+
// File-only bubble: the meta row follows the cards directly —
|
|
2856
|
+
// give it air so the time doesn't sit glued to the card.
|
|
2857
|
+
variant === "block" && hasFiles && !hasBody && styles15.metaBlockAfterFiles,
|
|
2858
|
+
variant === "jumbo" && styles15.metaJumbo
|
|
2859
|
+
],
|
|
2860
|
+
pointerEvents: "none",
|
|
2861
|
+
children: [
|
|
2862
|
+
isEdited ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.editedTag, { color, opacity: 0.8 }], children: "edited" }) : null,
|
|
2863
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.meta, { color, opacity: variant === "overlay" ? 1 : 0.75 }], children: time }),
|
|
2864
|
+
isSelf && readState ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.readTick, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2865
|
+
PoolseIcon,
|
|
2866
|
+
{
|
|
2867
|
+
name: readState === "read" ? "check-double" : "check",
|
|
2868
|
+
size: 13,
|
|
2869
|
+
color
|
|
2870
|
+
}
|
|
2871
|
+
) }) : null
|
|
2872
|
+
]
|
|
2873
|
+
}
|
|
2874
|
+
);
|
|
2875
|
+
};
|
|
2876
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles15.wrap, { alignSelf: isSelf ? "flex-end" : "flex-start", maxWidth: bubbleMaxWidth }], children: [
|
|
2877
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2878
|
+
reactNative.View,
|
|
2879
|
+
{
|
|
2880
|
+
style: [
|
|
2881
|
+
jumboCount === 0 && {
|
|
2882
|
+
backgroundColor: isSelf ? theme.colors.selfBubble : theme.colors.otherBubble,
|
|
2883
|
+
borderColor: isSelf ? "transparent" : theme.colors.border,
|
|
2884
|
+
borderWidth: isSelf ? 0 : reactNative.StyleSheet.hairlineWidth,
|
|
2885
|
+
borderTopLeftRadius: radii2.topLeft,
|
|
2886
|
+
borderTopRightRadius: radii2.topRight,
|
|
2887
|
+
borderBottomLeftRadius: radii2.bottomLeft,
|
|
2888
|
+
borderBottomRightRadius: radii2.bottomRight
|
|
2889
|
+
},
|
|
2890
|
+
jumboCount === 0 && theme.shadows.sm
|
|
2891
|
+
],
|
|
2892
|
+
children: [
|
|
2893
|
+
showSenderName && !isSelf && jumboCount === 0 && (groupPosition === "first" || groupPosition === "standalone") ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2894
|
+
reactNative.Text,
|
|
2895
|
+
{
|
|
2896
|
+
style: [styles15.senderName, { color: senderTint, fontFamily: theme.type.fontBody }],
|
|
2897
|
+
children: senderLabel
|
|
2898
|
+
}
|
|
2899
|
+
) : null,
|
|
2900
|
+
message.quoted_message ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2901
|
+
QuotedCard,
|
|
2902
|
+
{
|
|
2903
|
+
quoted: message.quoted_message,
|
|
2904
|
+
isSelf,
|
|
2905
|
+
...labelFor ? { labelFor } : {},
|
|
2906
|
+
...onQuotedClick ? { onPress: () => onQuotedClick(message.quoted_message.id) } : {}
|
|
2907
|
+
}
|
|
2908
|
+
) : null,
|
|
2909
|
+
hasMedia ? (
|
|
2910
|
+
// 3px of bubble color framing the picture; the mosaic clips
|
|
2911
|
+
// itself 3px tighter than the bubble so the curves stay
|
|
2912
|
+
// concentric.
|
|
2913
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles15.mediaFrame, children: [
|
|
2914
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2915
|
+
ImageMosaic,
|
|
2916
|
+
{
|
|
2917
|
+
images,
|
|
2918
|
+
maxWidth: mediaWidth,
|
|
2919
|
+
radius: Math.max(bubbleRadius - 3, 8),
|
|
2920
|
+
squareBottom: !metaOverlay,
|
|
2921
|
+
...onLongPress ? { onLongPress } : {}
|
|
2922
|
+
}
|
|
2923
|
+
),
|
|
2924
|
+
metaOverlay ? metaRow("overlay") : null
|
|
2925
|
+
] })
|
|
2926
|
+
) : null,
|
|
2927
|
+
hasFiles ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.filesBlock, children: files.map((att) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2928
|
+
AttachmentPreview,
|
|
2798
2929
|
{
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
borderTopLeftRadius: radii2.topLeft,
|
|
2803
|
-
borderTopRightRadius: radii2.topRight,
|
|
2804
|
-
borderBottomLeftRadius: radii2.bottomLeft,
|
|
2805
|
-
borderBottomRightRadius: radii2.bottomRight,
|
|
2806
|
-
// Slightly more breathing room — 14/10 reads tighter and
|
|
2807
|
-
// matches the iMessage / WhatsApp bubble density.
|
|
2808
|
-
paddingHorizontal: 14,
|
|
2809
|
-
paddingVertical: 10,
|
|
2810
|
-
// Floor the bubble at a width that always fits the meta row
|
|
2811
|
-
// ("edited HH:MM ✓✓" → ~110px). Without this, short bodies
|
|
2812
|
-
// like "ok" produce a bubble too narrow for the meta to lay
|
|
2813
|
-
// out, and `edited` or the time gets clipped.
|
|
2814
|
-
minWidth: isSelf && isEdited ? 130 : 90
|
|
2930
|
+
attachment: att,
|
|
2931
|
+
maxWidth: bubbleMaxWidth - 12,
|
|
2932
|
+
tone: isSelf ? "onBrand" : "default"
|
|
2815
2933
|
},
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
children:
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
fontStyle: "italic"
|
|
2842
|
-
}
|
|
2843
|
-
],
|
|
2844
|
-
children: "Message deleted"
|
|
2845
|
-
}
|
|
2846
|
-
) : markdown ? renderMarkdown(truncated, {
|
|
2934
|
+
att.id
|
|
2935
|
+
)) }) : null,
|
|
2936
|
+
isDeleted ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles15.textBlock, sectionAbove && styles15.textBlockAfter], children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2937
|
+
reactNative.Text,
|
|
2938
|
+
{
|
|
2939
|
+
style: {
|
|
2940
|
+
color: mutedOnBubble,
|
|
2941
|
+
opacity: 0.7,
|
|
2942
|
+
fontFamily: theme.type.fontBody,
|
|
2943
|
+
fontSize: theme.type.bodySize,
|
|
2944
|
+
lineHeight: theme.type.lineHeight,
|
|
2945
|
+
fontStyle: "italic"
|
|
2946
|
+
},
|
|
2947
|
+
children: "Message deleted"
|
|
2948
|
+
}
|
|
2949
|
+
) }) : null,
|
|
2950
|
+
hasBody && jumboCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.jumboBlock, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2951
|
+
reactNative.Text,
|
|
2952
|
+
{
|
|
2953
|
+
style: { fontSize: emojiFontSize(jumboCount), lineHeight: emojiFontSize(jumboCount) + 8 },
|
|
2954
|
+
children: body
|
|
2955
|
+
}
|
|
2956
|
+
) }) : null,
|
|
2957
|
+
hasBody && jumboCount === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles15.textBlock, sectionAbove && styles15.textBlockAfter], children: [
|
|
2958
|
+
markdown && !inlineMeta ? renderMarkdown(truncated, {
|
|
2847
2959
|
textColor,
|
|
2848
2960
|
linkColor: isSelf ? theme.colors.onBrand : theme.colors.brand,
|
|
2849
2961
|
fontFamily: theme.type.fontBody,
|
|
@@ -2853,22 +2965,29 @@ function MessageBubble({
|
|
|
2853
2965
|
void reactNative.Linking.openURL(url).catch(() => void 0);
|
|
2854
2966
|
return false;
|
|
2855
2967
|
}
|
|
2856
|
-
}) : /* @__PURE__ */ jsxRuntime.
|
|
2968
|
+
}) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2857
2969
|
reactNative.Text,
|
|
2858
2970
|
{
|
|
2859
|
-
style:
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2971
|
+
style: {
|
|
2972
|
+
color: textColor,
|
|
2973
|
+
fontFamily: theme.type.fontBody,
|
|
2974
|
+
fontSize: theme.type.bodySize,
|
|
2975
|
+
lineHeight: theme.type.lineHeight
|
|
2976
|
+
},
|
|
2977
|
+
children: [
|
|
2978
|
+
truncated,
|
|
2979
|
+
inlineMeta ? (
|
|
2980
|
+
// The invisible twin: reserves the meta's footprint
|
|
2981
|
+
// at the end of the last line so the absolutely-
|
|
2982
|
+
// positioned real meta never covers a glyph — and
|
|
2983
|
+
// when the line is full, the reservation wraps and
|
|
2984
|
+
// the meta drops down, flush right.
|
|
2985
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles15.ghost, children: ghost })
|
|
2986
|
+
) : null
|
|
2987
|
+
]
|
|
2869
2988
|
}
|
|
2870
|
-
)
|
|
2871
|
-
|
|
2989
|
+
),
|
|
2990
|
+
trimmable ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2872
2991
|
reactNative.Text,
|
|
2873
2992
|
{
|
|
2874
2993
|
style: [
|
|
@@ -2878,48 +2997,15 @@ function MessageBubble({
|
|
|
2878
2997
|
children: expanded ? "Show less" : "Read more"
|
|
2879
2998
|
}
|
|
2880
2999
|
) }) : null,
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
}
|
|
2891
|
-
],
|
|
2892
|
-
children: "edited"
|
|
2893
|
-
}
|
|
2894
|
-
) : null,
|
|
2895
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2896
|
-
reactNative.Text,
|
|
2897
|
-
{
|
|
2898
|
-
style: [
|
|
2899
|
-
styles15.meta,
|
|
2900
|
-
{
|
|
2901
|
-
color: isSelf ? theme.colors.onBrand : theme.colors.ink3,
|
|
2902
|
-
opacity: isSelf ? 0.85 : 1
|
|
2903
|
-
}
|
|
2904
|
-
],
|
|
2905
|
-
children: time
|
|
2906
|
-
}
|
|
2907
|
-
),
|
|
2908
|
-
isSelf && readState ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.readTick, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2909
|
-
PoolseIcon,
|
|
2910
|
-
{
|
|
2911
|
-
name: readState === "read" ? "check-double" : "check",
|
|
2912
|
-
size: 13,
|
|
2913
|
-
color: theme.colors.onBrand
|
|
2914
|
-
}
|
|
2915
|
-
) }) : null
|
|
2916
|
-
] }),
|
|
2917
|
-
actionsTrigger ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.actionsSlot, children: actionsTrigger }) : null
|
|
2918
|
-
]
|
|
2919
|
-
}
|
|
2920
|
-
)
|
|
2921
|
-
}
|
|
2922
|
-
);
|
|
3000
|
+
inlineMeta ? metaRow("inline") : null
|
|
3001
|
+
] }) : null,
|
|
3002
|
+
!inlineMeta && !metaOverlay && jumboCount === 0 ? metaRow("block") : null,
|
|
3003
|
+
actionsTrigger ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.actionsSlot, children: actionsTrigger }) : null
|
|
3004
|
+
]
|
|
3005
|
+
}
|
|
3006
|
+
),
|
|
3007
|
+
jumboCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { alignSelf: isSelf ? "flex-end" : "flex-start" }, children: metaRow("jumbo") }) : null
|
|
3008
|
+
] });
|
|
2923
3009
|
}
|
|
2924
3010
|
function QuotedCard({
|
|
2925
3011
|
quoted,
|
|
@@ -2930,7 +3016,7 @@ function QuotedCard({
|
|
|
2930
3016
|
const theme = usePoolseTheme();
|
|
2931
3017
|
const resolved = react$1.useUser(quoted.sender_external_id);
|
|
2932
3018
|
const senderName = quoted.sender_external_id ? labelFor?.(quoted.sender_external_id) ?? resolved.profile?.displayName ?? quoted.sender_external_id : "Unknown";
|
|
2933
|
-
const bg = isSelf ? "rgba(
|
|
3019
|
+
const bg = isSelf ? "rgba(0,0,0,0.16)" : theme.colors.surface2;
|
|
2934
3020
|
const accent = isSelf ? theme.colors.onBrand : theme.colors.brand;
|
|
2935
3021
|
const txt = isSelf ? theme.colors.onBrand : theme.colors.ink;
|
|
2936
3022
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -2946,8 +3032,8 @@ function QuotedCard({
|
|
|
2946
3032
|
}
|
|
2947
3033
|
],
|
|
2948
3034
|
children: [
|
|
2949
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.quotedSender, { color: accent }], children: senderName }),
|
|
2950
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.quotedBody, { color: txt }], numberOfLines: 2, children: quoted.body ?? "\u2026" })
|
|
3035
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.quotedSender, { color: accent }], numberOfLines: 1, children: senderName }),
|
|
3036
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.quotedBody, { color: txt }], numberOfLines: 2, children: quoted.deleted_at ? "Original message deleted" : quoted.body ?? "\u2026" })
|
|
2951
3037
|
]
|
|
2952
3038
|
}
|
|
2953
3039
|
);
|
|
@@ -2983,16 +3069,34 @@ var styles15 = reactNative.StyleSheet.create({
|
|
|
2983
3069
|
wrap: {
|
|
2984
3070
|
marginBottom: 2
|
|
2985
3071
|
},
|
|
2986
|
-
bubble: {
|
|
2987
|
-
position: "relative"
|
|
2988
|
-
},
|
|
2989
3072
|
senderName: {
|
|
2990
3073
|
fontSize: 12,
|
|
2991
|
-
fontWeight: "
|
|
2992
|
-
|
|
3074
|
+
fontWeight: "700",
|
|
3075
|
+
paddingTop: 7,
|
|
3076
|
+
paddingHorizontal: 12
|
|
2993
3077
|
},
|
|
2994
|
-
|
|
2995
|
-
|
|
3078
|
+
mediaFrame: {
|
|
3079
|
+
margin: 3
|
|
3080
|
+
},
|
|
3081
|
+
filesBlock: {
|
|
3082
|
+
paddingHorizontal: 6,
|
|
3083
|
+
paddingTop: 6,
|
|
3084
|
+
gap: 4
|
|
3085
|
+
},
|
|
3086
|
+
textBlock: {
|
|
3087
|
+
paddingHorizontal: 12,
|
|
3088
|
+
paddingVertical: 7
|
|
3089
|
+
},
|
|
3090
|
+
// A hair less air when media / files / a quote already sit above.
|
|
3091
|
+
textBlockAfter: {
|
|
3092
|
+
paddingTop: 6
|
|
3093
|
+
},
|
|
3094
|
+
jumboBlock: {
|
|
3095
|
+
paddingVertical: 2
|
|
3096
|
+
},
|
|
3097
|
+
ghost: {
|
|
3098
|
+
color: "transparent",
|
|
3099
|
+
fontSize: 11
|
|
2996
3100
|
},
|
|
2997
3101
|
readMore: {
|
|
2998
3102
|
fontSize: 12,
|
|
@@ -3002,10 +3106,36 @@ var styles15 = reactNative.StyleSheet.create({
|
|
|
3002
3106
|
metaRow: {
|
|
3003
3107
|
flexDirection: "row",
|
|
3004
3108
|
alignItems: "center",
|
|
3109
|
+
gap: 4
|
|
3110
|
+
},
|
|
3111
|
+
metaInline: {
|
|
3112
|
+
position: "absolute",
|
|
3113
|
+
right: 12,
|
|
3114
|
+
// Optically centers the meta against the body's last line (22px
|
|
3115
|
+
// line over the 7px bottom padding): 7 + (22 - 14) / 2. At 8 it
|
|
3116
|
+
// sat visibly low beside a one-line message.
|
|
3117
|
+
bottom: 11
|
|
3118
|
+
},
|
|
3119
|
+
metaOverlay: {
|
|
3120
|
+
position: "absolute",
|
|
3121
|
+
right: 6,
|
|
3122
|
+
bottom: 6,
|
|
3123
|
+
paddingHorizontal: 8,
|
|
3124
|
+
paddingVertical: 3,
|
|
3125
|
+
borderRadius: 999,
|
|
3126
|
+
backgroundColor: "rgba(0,0,0,0.42)"
|
|
3127
|
+
},
|
|
3128
|
+
metaBlock: {
|
|
3005
3129
|
justifyContent: "flex-end",
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3130
|
+
paddingHorizontal: 12,
|
|
3131
|
+
paddingBottom: 6,
|
|
3132
|
+
marginTop: -2
|
|
3133
|
+
},
|
|
3134
|
+
metaBlockAfterFiles: {
|
|
3135
|
+
marginTop: 6
|
|
3136
|
+
},
|
|
3137
|
+
metaJumbo: {
|
|
3138
|
+
paddingTop: 2
|
|
3009
3139
|
},
|
|
3010
3140
|
meta: {
|
|
3011
3141
|
fontSize: 11,
|
|
@@ -3018,7 +3148,7 @@ var styles15 = reactNative.StyleSheet.create({
|
|
|
3018
3148
|
letterSpacing: 0.1
|
|
3019
3149
|
},
|
|
3020
3150
|
readTick: {
|
|
3021
|
-
marginLeft:
|
|
3151
|
+
marginLeft: 1,
|
|
3022
3152
|
marginTop: 1
|
|
3023
3153
|
},
|
|
3024
3154
|
actionsSlot: {
|
|
@@ -3028,20 +3158,21 @@ var styles15 = reactNative.StyleSheet.create({
|
|
|
3028
3158
|
},
|
|
3029
3159
|
quoted: {
|
|
3030
3160
|
borderLeftWidth: 3,
|
|
3031
|
-
paddingLeft:
|
|
3032
|
-
paddingVertical:
|
|
3033
|
-
paddingRight:
|
|
3034
|
-
|
|
3035
|
-
|
|
3161
|
+
paddingLeft: 9,
|
|
3162
|
+
paddingVertical: 5,
|
|
3163
|
+
paddingRight: 8,
|
|
3164
|
+
margin: 4,
|
|
3165
|
+
marginBottom: 0,
|
|
3166
|
+
borderRadius: 10
|
|
3036
3167
|
},
|
|
3037
3168
|
quotedSender: {
|
|
3038
3169
|
fontSize: 12,
|
|
3039
|
-
fontWeight: "
|
|
3040
|
-
marginBottom:
|
|
3170
|
+
fontWeight: "700",
|
|
3171
|
+
marginBottom: 1
|
|
3041
3172
|
},
|
|
3042
3173
|
quotedBody: {
|
|
3043
|
-
fontSize: 12,
|
|
3044
|
-
opacity: 0.
|
|
3174
|
+
fontSize: 12.5,
|
|
3175
|
+
opacity: 0.8
|
|
3045
3176
|
}
|
|
3046
3177
|
});
|
|
3047
3178
|
var DEFAULT_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F64F}", "\u{1F525}"];
|
|
@@ -3358,14 +3489,9 @@ function MessageRow({
|
|
|
3358
3489
|
showSenderName,
|
|
3359
3490
|
showAttachments: attachments,
|
|
3360
3491
|
markdown,
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
name: "chevron-down",
|
|
3365
|
-
size: 14,
|
|
3366
|
-
color: isSelf ? theme.colors.onBrand : theme.colors.ink3
|
|
3367
|
-
}
|
|
3368
|
-
) }) : null
|
|
3492
|
+
onLongPress: () => {
|
|
3493
|
+
if (actions) setActionsOpen(true);
|
|
3494
|
+
}
|
|
3369
3495
|
}
|
|
3370
3496
|
)
|
|
3371
3497
|
}
|