@poolse/react-native 2.19.0 → 2.20.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/CHANGELOG.md +38 -0
- package/dist/index.cjs +280 -176
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -2
- package/dist/index.d.ts +22 -2
- package/dist/index.js +280 -176
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,44 @@ All notable changes to `@poolse/react-native` are documented here.
|
|
|
4
4
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
|
|
5
5
|
versions follow [semver](https://semver.org).
|
|
6
6
|
|
|
7
|
+
## [2.20.0] — 2026-10-13
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **The message bubble, rebuilt.** Same props, new rendering:
|
|
12
|
+
- The timestamp (with `edited` and the read ticks) sits on the last
|
|
13
|
+
line of plain-text bodies — an invisible twin reserves its exact
|
|
14
|
+
footprint, so it shares the line or wraps flush right. Short
|
|
15
|
+
messages are one-line bubbles again; the `minWidth` floor that
|
|
16
|
+
padded them out is gone.
|
|
17
|
+
- Media bleeds to a 3px frame of bubble color, capped at 420pt,
|
|
18
|
+
corners concentric with the bubble (default radius 16 → 18). When
|
|
19
|
+
a caption or file cards follow, the mosaic squares its bottom
|
|
20
|
+
corners into the text instead of leaving "ears" of bubble color.
|
|
21
|
+
- Image-only messages float the meta on a scrim pill over the
|
|
22
|
+
picture, matching the web package.
|
|
23
|
+
- 1–3 emoji and nothing else render large, without bubble chrome.
|
|
24
|
+
- Quote cards: 10pt radius, darker-than-bubble tint on both sides,
|
|
25
|
+
and a quoted-of-deleted fallback matching web.
|
|
26
|
+
- A deleted message renders its tombstone even when the body was
|
|
27
|
+
cleared server-side.
|
|
28
|
+
|
|
29
|
+
- **The always-visible corner chevron is gone.** It stamped a control
|
|
30
|
+
into the bubble's rounded corner and duplicated affordances a phone
|
|
31
|
+
already has — long-press opens the actions sheet, swipe quotes.
|
|
32
|
+
`MessageBubble`'s `actionsTrigger` slot remains for custom rows.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- `AttachmentPreview tone="onBrand"` — restyles the file card for
|
|
37
|
+
brand-colored self bubbles (translucent white instead of a surface
|
|
38
|
+
patch). `MessageBubble` passes it automatically.
|
|
39
|
+
- `ImageMosaic squareBottom` — small bottom radii for mosaics that
|
|
40
|
+
flow into a caption; `radius` now applies per-corner. The mosaic
|
|
41
|
+
also takes an explicit pixel width from the bubble, fixing a layout
|
|
42
|
+
where a percentage against the auto-sized bubble could collapse or
|
|
43
|
+
blow the row open.
|
|
44
|
+
|
|
7
45
|
## [2.19.0] — 2026-08-25
|
|
8
46
|
|
|
9
47
|
### Added
|
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,10 @@ var styles13 = reactNative.StyleSheet.create({
|
|
|
2431
2439
|
fontSize: 11
|
|
2432
2440
|
}
|
|
2433
2441
|
});
|
|
2434
|
-
function ImageMosaic({ images, maxWidth, radius }) {
|
|
2442
|
+
function ImageMosaic({ images, maxWidth, radius, squareBottom = false }) {
|
|
2435
2443
|
const theme = usePoolseTheme();
|
|
2436
2444
|
const r = radius ?? theme.radii.md;
|
|
2445
|
+
const rBottom = squareBottom ? 5 : r;
|
|
2437
2446
|
const [viewerIndex, setViewerIndex] = react.useState(null);
|
|
2438
2447
|
if (images.length === 0) return null;
|
|
2439
2448
|
const visible = images.slice(0, 4);
|
|
@@ -2446,8 +2455,17 @@ function ImageMosaic({ images, maxWidth, radius }) {
|
|
|
2446
2455
|
style: [
|
|
2447
2456
|
styles14.root,
|
|
2448
2457
|
{
|
|
2449
|
-
|
|
2450
|
-
|
|
2458
|
+
borderTopLeftRadius: r,
|
|
2459
|
+
borderTopRightRadius: r,
|
|
2460
|
+
borderBottomLeftRadius: rBottom,
|
|
2461
|
+
borderBottomRightRadius: rBottom,
|
|
2462
|
+
// Explicit pixel width, not just a cap: the mosaic sits
|
|
2463
|
+
// inside an auto-sized bubble, and a percentage width
|
|
2464
|
+
// against an indefinite parent collapses (or, worse,
|
|
2465
|
+
// resolves against the image's intrinsic size and blows
|
|
2466
|
+
// the row open). Media bubbles are fixed-width — the
|
|
2467
|
+
// WhatsApp convention anyway.
|
|
2468
|
+
...maxWidth ? { width: maxWidth } : {}
|
|
2451
2469
|
}
|
|
2452
2470
|
],
|
|
2453
2471
|
children: [
|
|
@@ -2684,6 +2702,27 @@ var styles14 = reactNative.StyleSheet.create({
|
|
|
2684
2702
|
borderRadius: 20
|
|
2685
2703
|
}
|
|
2686
2704
|
});
|
|
2705
|
+
|
|
2706
|
+
// src/internal/bubbleText.ts
|
|
2707
|
+
function hasMarkdown(body) {
|
|
2708
|
+
return /[*_~`#>|\\]|\[.*\]|^\s*(?:[-+]|\d+[.)])\s|https?:\/\/|www\./ms.test(body);
|
|
2709
|
+
}
|
|
2710
|
+
function emojiOnlyCount(body) {
|
|
2711
|
+
const text = body.trim();
|
|
2712
|
+
if (!text || text.length > 32) return 0;
|
|
2713
|
+
try {
|
|
2714
|
+
const unit = "(?:\\p{Regional_Indicator}{2}|\\p{Extended_Pictographic})[\\u{1F3FB}-\\u{1F3FF}]?\\uFE0F?(?:\\u200D\\p{Extended_Pictographic}[\\u{1F3FB}-\\u{1F3FF}]?\\uFE0F?)*";
|
|
2715
|
+
const whole = new RegExp(`^(?:${unit}){1,3}$`, "u");
|
|
2716
|
+
if (!whole.test(text)) return 0;
|
|
2717
|
+
const each = new RegExp(unit, "gu");
|
|
2718
|
+
return (text.match(each) ?? []).length;
|
|
2719
|
+
} catch {
|
|
2720
|
+
return 0;
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
function emojiFontSize(count) {
|
|
2724
|
+
return count === 1 ? 44 : count === 2 ? 38 : 32;
|
|
2725
|
+
}
|
|
2687
2726
|
var markdownit = Markdown.MarkdownIt({ typographer: false, linkify: true });
|
|
2688
2727
|
function renderMarkdown(body, args) {
|
|
2689
2728
|
const baseText = {
|
|
@@ -2750,100 +2789,154 @@ function MessageBubble({
|
|
|
2750
2789
|
const isSelf = currentUserId !== null && message.sender_id === currentUserId;
|
|
2751
2790
|
const [expanded, setExpanded] = react.useState(false);
|
|
2752
2791
|
const bubbleMaxWidth = Math.floor(screenWidth * 0.78);
|
|
2753
|
-
const
|
|
2792
|
+
const mediaWidth = Math.min(bubbleMaxWidth - 6, 420);
|
|
2754
2793
|
const sender = react$1.useUser(!isSelf ? message.sender_external_id : null);
|
|
2755
2794
|
const senderFallbackName = message.sender_external_id ?? "Unknown";
|
|
2756
2795
|
const senderLabel = sender.profile?.displayName ?? (message.sender_external_id ? labelFor?.(message.sender_external_id) ?? senderFallbackName : senderFallbackName);
|
|
2757
2796
|
const senderTint = userColor(message.sender_external_id ?? message.sender_id ?? message.id);
|
|
2758
2797
|
const tailCorner = groupPosition === "last" || groupPosition === "standalone";
|
|
2759
|
-
const
|
|
2798
|
+
const bubbleRadius = theme.radii.bubble;
|
|
2760
2799
|
const textColor = isSelf ? theme.colors.selfBubbleText : theme.colors.otherBubbleText;
|
|
2800
|
+
const mutedOnBubble = isSelf ? theme.colors.onBrand : theme.colors.ink3;
|
|
2761
2801
|
const radii2 = {
|
|
2762
|
-
topLeft:
|
|
2763
|
-
topRight:
|
|
2764
|
-
bottomLeft:
|
|
2765
|
-
bottomRight:
|
|
2802
|
+
topLeft: bubbleRadius,
|
|
2803
|
+
topRight: bubbleRadius,
|
|
2804
|
+
bottomLeft: bubbleRadius,
|
|
2805
|
+
bottomRight: bubbleRadius
|
|
2766
2806
|
};
|
|
2767
2807
|
if (tailCorner) {
|
|
2768
|
-
if (isSelf) radii2.bottomRight =
|
|
2769
|
-
else radii2.bottomLeft =
|
|
2808
|
+
if (isSelf) radii2.bottomRight = 5;
|
|
2809
|
+
else radii2.bottomLeft = 5;
|
|
2770
2810
|
}
|
|
2771
2811
|
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
2812
|
const isDeleted = !!message.deleted_at;
|
|
2775
2813
|
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
|
-
|
|
2814
|
+
const trimmable = maxBodyLength > 0 && body.length > maxBodyLength;
|
|
2815
|
+
const truncated = trimmable && !expanded ? body.slice(0, maxBodyLength) + "\u2026" : body;
|
|
2816
|
+
const time = formatTime(message.inserted_at);
|
|
2817
|
+
const images = showAttachments && !isDeleted ? (message.attachments ?? []).filter((a) => isImageContentType(a.content_type)) : [];
|
|
2818
|
+
const files = showAttachments && !isDeleted ? (message.attachments ?? []).filter((a) => !isImageContentType(a.content_type)) : [];
|
|
2819
|
+
const hasMedia = images.length > 0;
|
|
2820
|
+
const hasFiles = files.length > 0;
|
|
2821
|
+
const hasBody = !isDeleted && body.length > 0;
|
|
2822
|
+
const metaOverlay = hasMedia && !hasBody && !hasFiles;
|
|
2823
|
+
const jumboCount = hasBody && !hasMedia && !hasFiles && !message.quoted_message_id ? emojiOnlyCount(body) : 0;
|
|
2824
|
+
const inlineMeta = hasBody && jumboCount === 0 && !trimmable && (!markdown || !hasMarkdown(truncated));
|
|
2825
|
+
const sectionAbove = hasMedia || hasFiles || !!message.quoted_message || isDeleted;
|
|
2826
|
+
const ghost = ` ${isEdited ? "edited " : ""}${time}${isSelf && readState ? " \u2713\u2713" : ""}`;
|
|
2827
|
+
const metaRow = (variant) => {
|
|
2828
|
+
const color = variant === "overlay" ? "#FFFFFF" : variant === "jumbo" ? theme.colors.ink3 : mutedOnBubble;
|
|
2829
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2830
|
+
reactNative.View,
|
|
2831
|
+
{
|
|
2832
|
+
style: [
|
|
2833
|
+
styles15.metaRow,
|
|
2834
|
+
variant === "inline" && styles15.metaInline,
|
|
2835
|
+
variant === "overlay" && styles15.metaOverlay,
|
|
2836
|
+
variant === "block" && styles15.metaBlock,
|
|
2837
|
+
// File-only bubble: the meta row follows the cards directly —
|
|
2838
|
+
// give it air so the time doesn't sit glued to the card.
|
|
2839
|
+
variant === "block" && hasFiles && !hasBody && styles15.metaBlockAfterFiles,
|
|
2840
|
+
variant === "jumbo" && styles15.metaJumbo
|
|
2841
|
+
],
|
|
2842
|
+
pointerEvents: "none",
|
|
2843
|
+
children: [
|
|
2844
|
+
isEdited ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.editedTag, { color, opacity: 0.8 }], children: "edited" }) : null,
|
|
2845
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.meta, { color, opacity: variant === "overlay" ? 1 : 0.75 }], children: time }),
|
|
2846
|
+
isSelf && readState ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.readTick, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2847
|
+
PoolseIcon,
|
|
2848
|
+
{
|
|
2849
|
+
name: readState === "read" ? "check-double" : "check",
|
|
2850
|
+
size: 13,
|
|
2851
|
+
color
|
|
2852
|
+
}
|
|
2853
|
+
) }) : null
|
|
2854
|
+
]
|
|
2855
|
+
}
|
|
2856
|
+
);
|
|
2857
|
+
};
|
|
2858
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles15.wrap, { alignSelf: isSelf ? "flex-end" : "flex-start", maxWidth: bubbleMaxWidth }], children: [
|
|
2859
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2860
|
+
reactNative.View,
|
|
2861
|
+
{
|
|
2862
|
+
style: [
|
|
2863
|
+
jumboCount === 0 && {
|
|
2864
|
+
backgroundColor: isSelf ? theme.colors.selfBubble : theme.colors.otherBubble,
|
|
2865
|
+
borderColor: isSelf ? "transparent" : theme.colors.border,
|
|
2866
|
+
borderWidth: isSelf ? 0 : reactNative.StyleSheet.hairlineWidth,
|
|
2867
|
+
borderTopLeftRadius: radii2.topLeft,
|
|
2868
|
+
borderTopRightRadius: radii2.topRight,
|
|
2869
|
+
borderBottomLeftRadius: radii2.bottomLeft,
|
|
2870
|
+
borderBottomRightRadius: radii2.bottomRight
|
|
2871
|
+
},
|
|
2872
|
+
jumboCount === 0 && theme.shadows.sm
|
|
2873
|
+
],
|
|
2874
|
+
children: [
|
|
2875
|
+
showSenderName && !isSelf && jumboCount === 0 && (groupPosition === "first" || groupPosition === "standalone") ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2876
|
+
reactNative.Text,
|
|
2877
|
+
{
|
|
2878
|
+
style: [styles15.senderName, { color: senderTint, fontFamily: theme.type.fontBody }],
|
|
2879
|
+
children: senderLabel
|
|
2880
|
+
}
|
|
2881
|
+
) : null,
|
|
2882
|
+
message.quoted_message ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2883
|
+
QuotedCard,
|
|
2884
|
+
{
|
|
2885
|
+
quoted: message.quoted_message,
|
|
2886
|
+
isSelf,
|
|
2887
|
+
...labelFor ? { labelFor } : {},
|
|
2888
|
+
...onQuotedClick ? { onPress: () => onQuotedClick(message.quoted_message.id) } : {}
|
|
2889
|
+
}
|
|
2890
|
+
) : null,
|
|
2891
|
+
hasMedia ? (
|
|
2892
|
+
// 3px of bubble color framing the picture; the mosaic clips
|
|
2893
|
+
// itself 3px tighter than the bubble so the curves stay
|
|
2894
|
+
// concentric.
|
|
2895
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles15.mediaFrame, children: [
|
|
2896
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2897
|
+
ImageMosaic,
|
|
2898
|
+
{
|
|
2899
|
+
images,
|
|
2900
|
+
maxWidth: mediaWidth,
|
|
2901
|
+
radius: Math.max(bubbleRadius - 3, 8),
|
|
2902
|
+
squareBottom: !metaOverlay
|
|
2903
|
+
}
|
|
2904
|
+
),
|
|
2905
|
+
metaOverlay ? metaRow("overlay") : null
|
|
2906
|
+
] })
|
|
2907
|
+
) : null,
|
|
2908
|
+
hasFiles ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.filesBlock, children: files.map((att) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2909
|
+
AttachmentPreview,
|
|
2798
2910
|
{
|
|
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
|
|
2911
|
+
attachment: att,
|
|
2912
|
+
maxWidth: bubbleMaxWidth - 12,
|
|
2913
|
+
tone: isSelf ? "onBrand" : "default"
|
|
2815
2914
|
},
|
|
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, {
|
|
2915
|
+
att.id
|
|
2916
|
+
)) }) : null,
|
|
2917
|
+
isDeleted ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles15.textBlock, sectionAbove && styles15.textBlockAfter], children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2918
|
+
reactNative.Text,
|
|
2919
|
+
{
|
|
2920
|
+
style: {
|
|
2921
|
+
color: mutedOnBubble,
|
|
2922
|
+
opacity: 0.7,
|
|
2923
|
+
fontFamily: theme.type.fontBody,
|
|
2924
|
+
fontSize: theme.type.bodySize,
|
|
2925
|
+
lineHeight: theme.type.lineHeight,
|
|
2926
|
+
fontStyle: "italic"
|
|
2927
|
+
},
|
|
2928
|
+
children: "Message deleted"
|
|
2929
|
+
}
|
|
2930
|
+
) }) : null,
|
|
2931
|
+
hasBody && jumboCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.jumboBlock, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2932
|
+
reactNative.Text,
|
|
2933
|
+
{
|
|
2934
|
+
style: { fontSize: emojiFontSize(jumboCount), lineHeight: emojiFontSize(jumboCount) + 8 },
|
|
2935
|
+
children: body
|
|
2936
|
+
}
|
|
2937
|
+
) }) : null,
|
|
2938
|
+
hasBody && jumboCount === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: [styles15.textBlock, sectionAbove && styles15.textBlockAfter], children: [
|
|
2939
|
+
markdown && !inlineMeta ? renderMarkdown(truncated, {
|
|
2847
2940
|
textColor,
|
|
2848
2941
|
linkColor: isSelf ? theme.colors.onBrand : theme.colors.brand,
|
|
2849
2942
|
fontFamily: theme.type.fontBody,
|
|
@@ -2853,22 +2946,29 @@ function MessageBubble({
|
|
|
2853
2946
|
void reactNative.Linking.openURL(url).catch(() => void 0);
|
|
2854
2947
|
return false;
|
|
2855
2948
|
}
|
|
2856
|
-
}) : /* @__PURE__ */ jsxRuntime.
|
|
2949
|
+
}) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2857
2950
|
reactNative.Text,
|
|
2858
2951
|
{
|
|
2859
|
-
style:
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2952
|
+
style: {
|
|
2953
|
+
color: textColor,
|
|
2954
|
+
fontFamily: theme.type.fontBody,
|
|
2955
|
+
fontSize: theme.type.bodySize,
|
|
2956
|
+
lineHeight: theme.type.lineHeight
|
|
2957
|
+
},
|
|
2958
|
+
children: [
|
|
2959
|
+
truncated,
|
|
2960
|
+
inlineMeta ? (
|
|
2961
|
+
// The invisible twin: reserves the meta's footprint
|
|
2962
|
+
// at the end of the last line so the absolutely-
|
|
2963
|
+
// positioned real meta never covers a glyph — and
|
|
2964
|
+
// when the line is full, the reservation wraps and
|
|
2965
|
+
// the meta drops down, flush right.
|
|
2966
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles15.ghost, children: ghost })
|
|
2967
|
+
) : null
|
|
2968
|
+
]
|
|
2869
2969
|
}
|
|
2870
|
-
)
|
|
2871
|
-
|
|
2970
|
+
),
|
|
2971
|
+
trimmable ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2872
2972
|
reactNative.Text,
|
|
2873
2973
|
{
|
|
2874
2974
|
style: [
|
|
@@ -2878,48 +2978,15 @@ function MessageBubble({
|
|
|
2878
2978
|
children: expanded ? "Show less" : "Read more"
|
|
2879
2979
|
}
|
|
2880
2980
|
) }) : 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
|
-
);
|
|
2981
|
+
inlineMeta ? metaRow("inline") : null
|
|
2982
|
+
] }) : null,
|
|
2983
|
+
!inlineMeta && !metaOverlay && jumboCount === 0 ? metaRow("block") : null,
|
|
2984
|
+
actionsTrigger ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles15.actionsSlot, children: actionsTrigger }) : null
|
|
2985
|
+
]
|
|
2986
|
+
}
|
|
2987
|
+
),
|
|
2988
|
+
jumboCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { alignSelf: isSelf ? "flex-end" : "flex-start" }, children: metaRow("jumbo") }) : null
|
|
2989
|
+
] });
|
|
2923
2990
|
}
|
|
2924
2991
|
function QuotedCard({
|
|
2925
2992
|
quoted,
|
|
@@ -2930,7 +2997,7 @@ function QuotedCard({
|
|
|
2930
2997
|
const theme = usePoolseTheme();
|
|
2931
2998
|
const resolved = react$1.useUser(quoted.sender_external_id);
|
|
2932
2999
|
const senderName = quoted.sender_external_id ? labelFor?.(quoted.sender_external_id) ?? resolved.profile?.displayName ?? quoted.sender_external_id : "Unknown";
|
|
2933
|
-
const bg = isSelf ? "rgba(
|
|
3000
|
+
const bg = isSelf ? "rgba(0,0,0,0.16)" : theme.colors.surface2;
|
|
2934
3001
|
const accent = isSelf ? theme.colors.onBrand : theme.colors.brand;
|
|
2935
3002
|
const txt = isSelf ? theme.colors.onBrand : theme.colors.ink;
|
|
2936
3003
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -2946,8 +3013,8 @@ function QuotedCard({
|
|
|
2946
3013
|
}
|
|
2947
3014
|
],
|
|
2948
3015
|
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" })
|
|
3016
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.quotedSender, { color: accent }], numberOfLines: 1, children: senderName }),
|
|
3017
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles15.quotedBody, { color: txt }], numberOfLines: 2, children: quoted.deleted_at ? "Original message deleted" : quoted.body ?? "\u2026" })
|
|
2951
3018
|
]
|
|
2952
3019
|
}
|
|
2953
3020
|
);
|
|
@@ -2983,16 +3050,34 @@ var styles15 = reactNative.StyleSheet.create({
|
|
|
2983
3050
|
wrap: {
|
|
2984
3051
|
marginBottom: 2
|
|
2985
3052
|
},
|
|
2986
|
-
bubble: {
|
|
2987
|
-
position: "relative"
|
|
2988
|
-
},
|
|
2989
3053
|
senderName: {
|
|
2990
3054
|
fontSize: 12,
|
|
2991
|
-
fontWeight: "
|
|
2992
|
-
|
|
3055
|
+
fontWeight: "700",
|
|
3056
|
+
paddingTop: 7,
|
|
3057
|
+
paddingHorizontal: 12
|
|
2993
3058
|
},
|
|
2994
|
-
|
|
2995
|
-
|
|
3059
|
+
mediaFrame: {
|
|
3060
|
+
margin: 3
|
|
3061
|
+
},
|
|
3062
|
+
filesBlock: {
|
|
3063
|
+
paddingHorizontal: 6,
|
|
3064
|
+
paddingTop: 6,
|
|
3065
|
+
gap: 4
|
|
3066
|
+
},
|
|
3067
|
+
textBlock: {
|
|
3068
|
+
paddingHorizontal: 12,
|
|
3069
|
+
paddingVertical: 7
|
|
3070
|
+
},
|
|
3071
|
+
// A hair less air when media / files / a quote already sit above.
|
|
3072
|
+
textBlockAfter: {
|
|
3073
|
+
paddingTop: 6
|
|
3074
|
+
},
|
|
3075
|
+
jumboBlock: {
|
|
3076
|
+
paddingVertical: 2
|
|
3077
|
+
},
|
|
3078
|
+
ghost: {
|
|
3079
|
+
color: "transparent",
|
|
3080
|
+
fontSize: 11
|
|
2996
3081
|
},
|
|
2997
3082
|
readMore: {
|
|
2998
3083
|
fontSize: 12,
|
|
@@ -3002,10 +3087,36 @@ var styles15 = reactNative.StyleSheet.create({
|
|
|
3002
3087
|
metaRow: {
|
|
3003
3088
|
flexDirection: "row",
|
|
3004
3089
|
alignItems: "center",
|
|
3090
|
+
gap: 4
|
|
3091
|
+
},
|
|
3092
|
+
metaInline: {
|
|
3093
|
+
position: "absolute",
|
|
3094
|
+
right: 12,
|
|
3095
|
+
// Optically centers the meta against the body's last line (22px
|
|
3096
|
+
// line over the 7px bottom padding): 7 + (22 - 14) / 2. At 8 it
|
|
3097
|
+
// sat visibly low beside a one-line message.
|
|
3098
|
+
bottom: 11
|
|
3099
|
+
},
|
|
3100
|
+
metaOverlay: {
|
|
3101
|
+
position: "absolute",
|
|
3102
|
+
right: 6,
|
|
3103
|
+
bottom: 6,
|
|
3104
|
+
paddingHorizontal: 8,
|
|
3105
|
+
paddingVertical: 3,
|
|
3106
|
+
borderRadius: 999,
|
|
3107
|
+
backgroundColor: "rgba(0,0,0,0.42)"
|
|
3108
|
+
},
|
|
3109
|
+
metaBlock: {
|
|
3005
3110
|
justifyContent: "flex-end",
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3111
|
+
paddingHorizontal: 12,
|
|
3112
|
+
paddingBottom: 6,
|
|
3113
|
+
marginTop: -2
|
|
3114
|
+
},
|
|
3115
|
+
metaBlockAfterFiles: {
|
|
3116
|
+
marginTop: 6
|
|
3117
|
+
},
|
|
3118
|
+
metaJumbo: {
|
|
3119
|
+
paddingTop: 2
|
|
3009
3120
|
},
|
|
3010
3121
|
meta: {
|
|
3011
3122
|
fontSize: 11,
|
|
@@ -3018,7 +3129,7 @@ var styles15 = reactNative.StyleSheet.create({
|
|
|
3018
3129
|
letterSpacing: 0.1
|
|
3019
3130
|
},
|
|
3020
3131
|
readTick: {
|
|
3021
|
-
marginLeft:
|
|
3132
|
+
marginLeft: 1,
|
|
3022
3133
|
marginTop: 1
|
|
3023
3134
|
},
|
|
3024
3135
|
actionsSlot: {
|
|
@@ -3028,20 +3139,21 @@ var styles15 = reactNative.StyleSheet.create({
|
|
|
3028
3139
|
},
|
|
3029
3140
|
quoted: {
|
|
3030
3141
|
borderLeftWidth: 3,
|
|
3031
|
-
paddingLeft:
|
|
3032
|
-
paddingVertical:
|
|
3033
|
-
paddingRight:
|
|
3034
|
-
|
|
3035
|
-
|
|
3142
|
+
paddingLeft: 9,
|
|
3143
|
+
paddingVertical: 5,
|
|
3144
|
+
paddingRight: 8,
|
|
3145
|
+
margin: 4,
|
|
3146
|
+
marginBottom: 0,
|
|
3147
|
+
borderRadius: 10
|
|
3036
3148
|
},
|
|
3037
3149
|
quotedSender: {
|
|
3038
3150
|
fontSize: 12,
|
|
3039
|
-
fontWeight: "
|
|
3040
|
-
marginBottom:
|
|
3151
|
+
fontWeight: "700",
|
|
3152
|
+
marginBottom: 1
|
|
3041
3153
|
},
|
|
3042
3154
|
quotedBody: {
|
|
3043
|
-
fontSize: 12,
|
|
3044
|
-
opacity: 0.
|
|
3155
|
+
fontSize: 12.5,
|
|
3156
|
+
opacity: 0.8
|
|
3045
3157
|
}
|
|
3046
3158
|
});
|
|
3047
3159
|
var DEFAULT_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F64F}", "\u{1F525}"];
|
|
@@ -3357,15 +3469,7 @@ function MessageRow({
|
|
|
3357
3469
|
maxBodyLength,
|
|
3358
3470
|
showSenderName,
|
|
3359
3471
|
showAttachments: attachments,
|
|
3360
|
-
markdown
|
|
3361
|
-
actionsTrigger: actions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3362
|
-
PoolseIcon,
|
|
3363
|
-
{
|
|
3364
|
-
name: "chevron-down",
|
|
3365
|
-
size: 14,
|
|
3366
|
-
color: isSelf ? theme.colors.onBrand : theme.colors.ink3
|
|
3367
|
-
}
|
|
3368
|
-
) }) : null
|
|
3472
|
+
markdown
|
|
3369
3473
|
}
|
|
3370
3474
|
)
|
|
3371
3475
|
}
|