@poolse/react-native 2.0.12 → 2.1.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.cjs +825 -362
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -16
- package/dist/index.d.ts +76 -16
- package/dist/index.js +828 -365
- package/dist/index.js.map +1 -1
- package/package.json +17 -4
package/dist/index.cjs
CHANGED
|
@@ -7,6 +7,7 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
7
7
|
var reactNativeSvg = require('react-native-svg');
|
|
8
8
|
var ImagePicker = require('expo-image-picker');
|
|
9
9
|
var DocumentPicker = require('expo-document-picker');
|
|
10
|
+
var expoImageManipulator = require('expo-image-manipulator');
|
|
10
11
|
|
|
11
12
|
function _interopNamespace(e) {
|
|
12
13
|
if (e && e.__esModule) return e;
|
|
@@ -29,7 +30,12 @@ function _interopNamespace(e) {
|
|
|
29
30
|
var ImagePicker__namespace = /*#__PURE__*/_interopNamespace(ImagePicker);
|
|
30
31
|
var DocumentPicker__namespace = /*#__PURE__*/_interopNamespace(DocumentPicker);
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
34
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
35
|
+
}) : x)(function(x) {
|
|
36
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
37
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
38
|
+
});
|
|
33
39
|
|
|
34
40
|
// src/theme/default.ts
|
|
35
41
|
var lightColors = {
|
|
@@ -1183,24 +1189,84 @@ async function uriToBlob(uri) {
|
|
|
1183
1189
|
}
|
|
1184
1190
|
return res.blob();
|
|
1185
1191
|
}
|
|
1192
|
+
var COMPRESSION_LONG_SIDE_PX = 1600;
|
|
1193
|
+
var COMPRESSION_QUALITY = 0.75;
|
|
1194
|
+
function getImageSize(uri) {
|
|
1195
|
+
return new Promise((resolve, reject) => {
|
|
1196
|
+
reactNative.Image.getSize(uri, (width, height) => resolve({ width, height }), reject);
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
async function compressImage({
|
|
1200
|
+
uri,
|
|
1201
|
+
width,
|
|
1202
|
+
height,
|
|
1203
|
+
filename
|
|
1204
|
+
}) {
|
|
1205
|
+
let w = width;
|
|
1206
|
+
let h = height;
|
|
1207
|
+
if (!w || !h) {
|
|
1208
|
+
try {
|
|
1209
|
+
const size = await getImageSize(uri);
|
|
1210
|
+
w = size.width;
|
|
1211
|
+
h = size.height;
|
|
1212
|
+
} catch {
|
|
1213
|
+
w = 0;
|
|
1214
|
+
h = 0;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
const longSide = Math.max(w ?? 0, h ?? 0);
|
|
1218
|
+
const scale = longSide > COMPRESSION_LONG_SIDE_PX ? COMPRESSION_LONG_SIDE_PX / longSide : 1;
|
|
1219
|
+
const actions = scale < 1 && w && h ? [{ resize: { width: Math.round(w * scale), height: Math.round(h * scale) } }] : [];
|
|
1220
|
+
const result = await expoImageManipulator.manipulateAsync(uri, actions, {
|
|
1221
|
+
compress: COMPRESSION_QUALITY,
|
|
1222
|
+
format: expoImageManipulator.SaveFormat.WEBP
|
|
1223
|
+
});
|
|
1224
|
+
const blob = await uriToBlob(result.uri);
|
|
1225
|
+
const base = (filename ?? `image-${Date.now()}`).replace(/\.[^.]+$/, "");
|
|
1226
|
+
return {
|
|
1227
|
+
blob,
|
|
1228
|
+
contentType: "image/webp",
|
|
1229
|
+
filename: `${base}.webp`
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1186
1232
|
async function defaultAdapter(kind) {
|
|
1187
1233
|
if (kind === "image") {
|
|
1188
1234
|
const res2 = await ImagePicker__namespace.launchImageLibraryAsync({
|
|
1189
1235
|
mediaTypes: "images",
|
|
1190
|
-
|
|
1236
|
+
// Quality 1 — picker hands back the source as-is and we do the
|
|
1237
|
+
// compression ourselves below. Letting the picker re-encode
|
|
1238
|
+
// first just chains two lossy passes.
|
|
1239
|
+
quality: 1
|
|
1191
1240
|
});
|
|
1192
1241
|
if (res2.canceled) return [];
|
|
1193
1242
|
return Promise.all(
|
|
1194
1243
|
res2.assets.map(async (asset) => {
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1244
|
+
try {
|
|
1245
|
+
const compressed = await compressImage({
|
|
1246
|
+
uri: asset.uri,
|
|
1247
|
+
width: asset.width,
|
|
1248
|
+
height: asset.height,
|
|
1249
|
+
filename: asset.fileName ?? `image-${Date.now()}`
|
|
1250
|
+
});
|
|
1251
|
+
return {
|
|
1252
|
+
body: compressed.blob,
|
|
1253
|
+
contentType: compressed.contentType,
|
|
1254
|
+
byteSize: compressed.blob.size,
|
|
1255
|
+
filename: compressed.filename,
|
|
1256
|
+
_previewUri: asset.uri
|
|
1257
|
+
};
|
|
1258
|
+
} catch (err) {
|
|
1259
|
+
console.warn("[poolse/picker] image compression failed, sending original:", err);
|
|
1260
|
+
const blob = await uriToBlob(asset.uri);
|
|
1261
|
+
const contentType = asset.mimeType ?? blob.type ?? "image/jpeg";
|
|
1262
|
+
return {
|
|
1263
|
+
body: blob,
|
|
1264
|
+
contentType,
|
|
1265
|
+
byteSize: asset.fileSize ?? blob.size,
|
|
1266
|
+
filename: asset.fileName ?? `image-${Date.now()}.jpg`,
|
|
1267
|
+
_previewUri: asset.uri
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1204
1270
|
})
|
|
1205
1271
|
);
|
|
1206
1272
|
}
|
|
@@ -1211,6 +1277,24 @@ async function defaultAdapter(kind) {
|
|
|
1211
1277
|
if (res.canceled) return [];
|
|
1212
1278
|
return Promise.all(
|
|
1213
1279
|
res.assets.map(async (asset) => {
|
|
1280
|
+
const isImage = (asset.mimeType ?? "").startsWith("image/");
|
|
1281
|
+
if (isImage) {
|
|
1282
|
+
try {
|
|
1283
|
+
const compressed = await compressImage({
|
|
1284
|
+
uri: asset.uri,
|
|
1285
|
+
filename: asset.name
|
|
1286
|
+
});
|
|
1287
|
+
return {
|
|
1288
|
+
body: compressed.blob,
|
|
1289
|
+
contentType: compressed.contentType,
|
|
1290
|
+
byteSize: compressed.blob.size,
|
|
1291
|
+
filename: compressed.filename,
|
|
1292
|
+
_previewUri: asset.uri
|
|
1293
|
+
};
|
|
1294
|
+
} catch (err) {
|
|
1295
|
+
console.warn("[poolse/picker] image compression failed, sending original:", err);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1214
1298
|
const blob = await uriToBlob(asset.uri);
|
|
1215
1299
|
const contentType = asset.mimeType ?? blob.type ?? "application/octet-stream";
|
|
1216
1300
|
return {
|
|
@@ -1251,6 +1335,51 @@ var styles6 = reactNative.StyleSheet.create({
|
|
|
1251
1335
|
fontWeight: "500"
|
|
1252
1336
|
}
|
|
1253
1337
|
});
|
|
1338
|
+
function DaySeparator({ label }) {
|
|
1339
|
+
const theme = usePoolseTheme();
|
|
1340
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles7.wrap, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1341
|
+
reactNative.View,
|
|
1342
|
+
{
|
|
1343
|
+
style: [
|
|
1344
|
+
styles7.chip,
|
|
1345
|
+
{
|
|
1346
|
+
backgroundColor: theme.colors.surface2,
|
|
1347
|
+
borderColor: theme.colors.border
|
|
1348
|
+
}
|
|
1349
|
+
],
|
|
1350
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1351
|
+
reactNative.Text,
|
|
1352
|
+
{
|
|
1353
|
+
style: [
|
|
1354
|
+
styles7.label,
|
|
1355
|
+
{
|
|
1356
|
+
color: theme.colors.ink2,
|
|
1357
|
+
fontFamily: theme.type.fontBody
|
|
1358
|
+
}
|
|
1359
|
+
],
|
|
1360
|
+
children: label
|
|
1361
|
+
}
|
|
1362
|
+
)
|
|
1363
|
+
}
|
|
1364
|
+
) });
|
|
1365
|
+
}
|
|
1366
|
+
var styles7 = reactNative.StyleSheet.create({
|
|
1367
|
+
wrap: {
|
|
1368
|
+
alignItems: "center",
|
|
1369
|
+
marginVertical: 8
|
|
1370
|
+
},
|
|
1371
|
+
chip: {
|
|
1372
|
+
paddingHorizontal: 12,
|
|
1373
|
+
paddingVertical: 4,
|
|
1374
|
+
borderRadius: 999,
|
|
1375
|
+
borderWidth: reactNative.StyleSheet.hairlineWidth
|
|
1376
|
+
},
|
|
1377
|
+
label: {
|
|
1378
|
+
fontSize: 11,
|
|
1379
|
+
fontWeight: "600",
|
|
1380
|
+
letterSpacing: 0.3
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1254
1383
|
function UploadQueueStrip() {
|
|
1255
1384
|
const theme = usePoolseTheme();
|
|
1256
1385
|
const upload = useSharedUpload();
|
|
@@ -1261,8 +1390,8 @@ function UploadQueueStrip() {
|
|
|
1261
1390
|
{
|
|
1262
1391
|
horizontal: true,
|
|
1263
1392
|
showsHorizontalScrollIndicator: false,
|
|
1264
|
-
contentContainerStyle:
|
|
1265
|
-
style: [
|
|
1393
|
+
contentContainerStyle: styles8.scroll,
|
|
1394
|
+
style: [styles8.scrollStyle, { marginBottom: 8 }],
|
|
1266
1395
|
children: upload.queue.map((item) => {
|
|
1267
1396
|
const isError = item.status === "error";
|
|
1268
1397
|
const isUploading = item.status === "uploading" || item.status === "pending";
|
|
@@ -1273,7 +1402,7 @@ function UploadQueueStrip() {
|
|
|
1273
1402
|
if (item.status === "ready" || isError) upload.remove(item.localId);
|
|
1274
1403
|
else upload.cancel(item.localId);
|
|
1275
1404
|
};
|
|
1276
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style:
|
|
1405
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles8.tileWrap, children: [
|
|
1277
1406
|
isImage && previewUri ? /* @__PURE__ */ jsxRuntime.jsx(ImageTile, { uri: previewUri, uploading: isUploading, error: isError }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1278
1407
|
FileTile,
|
|
1279
1408
|
{
|
|
@@ -1288,13 +1417,13 @@ function UploadQueueStrip() {
|
|
|
1288
1417
|
{
|
|
1289
1418
|
onPress: handleClose,
|
|
1290
1419
|
hitSlop: 8,
|
|
1291
|
-
style: [
|
|
1420
|
+
style: [styles8.closeBtn, { backgroundColor: theme.colors.ink }],
|
|
1292
1421
|
accessibilityLabel: isError ? "Remove failed upload" : "Cancel upload",
|
|
1293
1422
|
children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "close", size: 11, color: theme.colors.onBrand })
|
|
1294
1423
|
}
|
|
1295
1424
|
) : null,
|
|
1296
|
-
isUploading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style:
|
|
1297
|
-
isError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
1425
|
+
isUploading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles8.progressOverlay, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles8.spinner, { borderColor: theme.colors.onBrand }] }) }) : null,
|
|
1426
|
+
isError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles8.errorOverlay, { backgroundColor: "rgba(229,72,77,0.85)" }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.errorText, { color: "#fff" }], children: "Failed" }) }) : null
|
|
1298
1427
|
] }, item.localId);
|
|
1299
1428
|
})
|
|
1300
1429
|
}
|
|
@@ -1307,7 +1436,7 @@ function ImageTile({ uri, uploading, error }) {
|
|
|
1307
1436
|
{
|
|
1308
1437
|
source: { uri },
|
|
1309
1438
|
style: [
|
|
1310
|
-
|
|
1439
|
+
styles8.imageTile,
|
|
1311
1440
|
{
|
|
1312
1441
|
backgroundColor: theme.colors.surface2,
|
|
1313
1442
|
borderColor: error ? theme.colors.error : "transparent",
|
|
@@ -1329,24 +1458,24 @@ function FileTile({
|
|
|
1329
1458
|
reactNative.View,
|
|
1330
1459
|
{
|
|
1331
1460
|
style: [
|
|
1332
|
-
|
|
1461
|
+
styles8.fileTile,
|
|
1333
1462
|
{
|
|
1334
1463
|
backgroundColor: theme.colors.surface2,
|
|
1335
1464
|
borderColor: error ? theme.colors.error : uploading ? theme.colors.brand : theme.colors.border
|
|
1336
1465
|
}
|
|
1337
1466
|
],
|
|
1338
1467
|
children: [
|
|
1339
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
1340
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style:
|
|
1468
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles8.fileIconBox, { backgroundColor: theme.colors.brandSoft }], children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "attachment", size: 18, color: theme.colors.brand }) }),
|
|
1469
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles8.fileInfo, children: [
|
|
1341
1470
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1342
1471
|
reactNative.Text,
|
|
1343
1472
|
{
|
|
1344
|
-
style: [
|
|
1473
|
+
style: [styles8.fileName, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
|
|
1345
1474
|
numberOfLines: 1,
|
|
1346
1475
|
children: filename
|
|
1347
1476
|
}
|
|
1348
1477
|
),
|
|
1349
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
1478
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.fileSize, { color: theme.colors.ink3 }], children: formatBytes(size) })
|
|
1350
1479
|
] })
|
|
1351
1480
|
]
|
|
1352
1481
|
}
|
|
@@ -1360,7 +1489,7 @@ function formatBytes(n) {
|
|
|
1360
1489
|
return `${(n / 1024 / 1024 / 1024).toFixed(1)} GB`;
|
|
1361
1490
|
}
|
|
1362
1491
|
var TILE = 64;
|
|
1363
|
-
var
|
|
1492
|
+
var styles8 = reactNative.StyleSheet.create({
|
|
1364
1493
|
scrollStyle: {
|
|
1365
1494
|
overflow: "visible"
|
|
1366
1495
|
},
|
|
@@ -1460,6 +1589,22 @@ var styles7 = reactNative.StyleSheet.create({
|
|
|
1460
1589
|
fontWeight: "700"
|
|
1461
1590
|
}
|
|
1462
1591
|
});
|
|
1592
|
+
var FALLBACK = {
|
|
1593
|
+
top: reactNative.Platform.OS === "ios" ? 50 : 24,
|
|
1594
|
+
bottom: reactNative.Platform.OS === "ios" ? 34 : 0,
|
|
1595
|
+
left: 0,
|
|
1596
|
+
right: 0
|
|
1597
|
+
};
|
|
1598
|
+
var realHook = null;
|
|
1599
|
+
try {
|
|
1600
|
+
const mod = __require("react-native-safe-area-context");
|
|
1601
|
+
if (mod && typeof mod.useSafeAreaInsets === "function") {
|
|
1602
|
+
realHook = mod.useSafeAreaInsets;
|
|
1603
|
+
}
|
|
1604
|
+
} catch {
|
|
1605
|
+
}
|
|
1606
|
+
var useFallback = () => FALLBACK;
|
|
1607
|
+
var useSafeInsets = realHook ?? useFallback;
|
|
1463
1608
|
var MessageComposer = react.forwardRef(
|
|
1464
1609
|
function MessageComposer2({
|
|
1465
1610
|
onSend,
|
|
@@ -1471,13 +1616,32 @@ var MessageComposer = react.forwardRef(
|
|
|
1471
1616
|
labelFor,
|
|
1472
1617
|
attachments = true,
|
|
1473
1618
|
onAttachPress,
|
|
1474
|
-
keyboardOffset: _keyboardOffset = 0
|
|
1619
|
+
keyboardOffset: _keyboardOffset = 0,
|
|
1620
|
+
editingMessage = null,
|
|
1621
|
+
onCancelEdit,
|
|
1622
|
+
onSaveEdit
|
|
1475
1623
|
}, ref) {
|
|
1476
1624
|
const theme = usePoolseTheme();
|
|
1477
1625
|
const inputRef = react.useRef(null);
|
|
1478
1626
|
const [value, setValue] = react.useState("");
|
|
1479
1627
|
const [sending, setSending] = react.useState(false);
|
|
1628
|
+
const [keyboardOpen, setKeyboardOpen] = react.useState(false);
|
|
1480
1629
|
const upload = useSharedUpload();
|
|
1630
|
+
const insets = useSafeInsets();
|
|
1631
|
+
react.useEffect(() => {
|
|
1632
|
+
const show = reactNative.Keyboard.addListener(
|
|
1633
|
+
reactNative.Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow",
|
|
1634
|
+
() => setKeyboardOpen(true)
|
|
1635
|
+
);
|
|
1636
|
+
const hide = reactNative.Keyboard.addListener(
|
|
1637
|
+
reactNative.Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide",
|
|
1638
|
+
() => setKeyboardOpen(false)
|
|
1639
|
+
);
|
|
1640
|
+
return () => {
|
|
1641
|
+
show.remove();
|
|
1642
|
+
hide.remove();
|
|
1643
|
+
};
|
|
1644
|
+
}, []);
|
|
1481
1645
|
react.useImperativeHandle(
|
|
1482
1646
|
ref,
|
|
1483
1647
|
() => ({
|
|
@@ -1496,36 +1660,65 @@ var MessageComposer = react.forwardRef(
|
|
|
1496
1660
|
const isUploading = upload.queue.some(
|
|
1497
1661
|
(item) => item.status === "uploading" || item.status === "pending"
|
|
1498
1662
|
);
|
|
1499
|
-
const
|
|
1663
|
+
const previousDraftRef = react.useRef(null);
|
|
1664
|
+
const editingIdRef = react.useRef(null);
|
|
1665
|
+
react.useEffect(() => {
|
|
1666
|
+
const id = editingMessage?.id ?? null;
|
|
1667
|
+
if (id === editingIdRef.current) return;
|
|
1668
|
+
if (id) {
|
|
1669
|
+
previousDraftRef.current = value;
|
|
1670
|
+
setValue(editingMessage?.body ?? "");
|
|
1671
|
+
inputRef.current?.focus();
|
|
1672
|
+
} else {
|
|
1673
|
+
setValue(previousDraftRef.current ?? "");
|
|
1674
|
+
previousDraftRef.current = null;
|
|
1675
|
+
}
|
|
1676
|
+
editingIdRef.current = id;
|
|
1677
|
+
}, [editingMessage?.id, editingMessage?.body]);
|
|
1678
|
+
const isEditing = !!editingMessage;
|
|
1679
|
+
const editChanged = isEditing && value.trim() !== (editingMessage?.body?.trim() ?? "");
|
|
1680
|
+
const submitDisabled = disabled || sending || isUploading || value.trim().length === 0 || (isEditing ? !editChanged : value.trim().length === 0 && readyAttachmentIds.length === 0);
|
|
1500
1681
|
const handleSend = async () => {
|
|
1501
1682
|
if (submitDisabled) return;
|
|
1502
1683
|
const body = value.trim();
|
|
1503
|
-
const replyId = replyingTo?.id;
|
|
1504
|
-
const attachmentIds = readyAttachmentIds;
|
|
1505
|
-
setValue("");
|
|
1506
|
-
upload.reset();
|
|
1507
1684
|
setSending(true);
|
|
1508
1685
|
try {
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1686
|
+
if (isEditing && onSaveEdit) {
|
|
1687
|
+
await onSaveEdit(body);
|
|
1688
|
+
} else {
|
|
1689
|
+
const replyId = replyingTo?.id;
|
|
1690
|
+
const attachmentIds = readyAttachmentIds;
|
|
1691
|
+
setValue("");
|
|
1692
|
+
upload.reset();
|
|
1693
|
+
await onSend(body, {
|
|
1694
|
+
...replyId ? { quoted_message_id: replyId } : {},
|
|
1695
|
+
...attachmentIds.length > 0 ? { attachment_ids: attachmentIds } : {}
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1513
1698
|
} finally {
|
|
1514
1699
|
setSending(false);
|
|
1515
1700
|
}
|
|
1516
1701
|
};
|
|
1702
|
+
const paddingBottom = keyboardOpen ? 10 : Math.max(10, insets.bottom);
|
|
1517
1703
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1518
1704
|
reactNative.View,
|
|
1519
1705
|
{
|
|
1520
1706
|
style: [
|
|
1521
|
-
|
|
1707
|
+
styles9.wrap,
|
|
1522
1708
|
{
|
|
1523
1709
|
backgroundColor: theme.colors.surface,
|
|
1524
|
-
borderTopColor: theme.colors.border
|
|
1710
|
+
borderTopColor: theme.colors.border,
|
|
1711
|
+
paddingBottom
|
|
1525
1712
|
}
|
|
1526
1713
|
],
|
|
1527
1714
|
children: [
|
|
1528
|
-
|
|
1715
|
+
isEditing ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1716
|
+
EditChip,
|
|
1717
|
+
{
|
|
1718
|
+
message: editingMessage,
|
|
1719
|
+
...onCancelEdit ? { onCancel: onCancelEdit } : {}
|
|
1720
|
+
}
|
|
1721
|
+
) : replyingTo ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1529
1722
|
ReplyChip,
|
|
1530
1723
|
{
|
|
1531
1724
|
message: replyingTo,
|
|
@@ -1533,14 +1726,14 @@ var MessageComposer = react.forwardRef(
|
|
|
1533
1726
|
...onCancelReply ? { onCancel: onCancelReply } : {}
|
|
1534
1727
|
}
|
|
1535
1728
|
) : null,
|
|
1536
|
-
attachments ? /* @__PURE__ */ jsxRuntime.jsx(UploadQueueStrip, {}) : null,
|
|
1537
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style:
|
|
1729
|
+
attachments && !isEditing ? /* @__PURE__ */ jsxRuntime.jsx(UploadQueueStrip, {}) : null,
|
|
1730
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles9.row, children: [
|
|
1538
1731
|
attachments && onAttachPress ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1539
1732
|
reactNative.Pressable,
|
|
1540
1733
|
{
|
|
1541
1734
|
onPress: onAttachPress,
|
|
1542
1735
|
hitSlop: 8,
|
|
1543
|
-
style:
|
|
1736
|
+
style: styles9.iconBtn,
|
|
1544
1737
|
accessibilityLabel: "Attach file",
|
|
1545
1738
|
children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "attachment", size: 22, color: theme.colors.ink2 })
|
|
1546
1739
|
}
|
|
@@ -1559,7 +1752,7 @@ var MessageComposer = react.forwardRef(
|
|
|
1559
1752
|
multiline: true,
|
|
1560
1753
|
editable: !disabled,
|
|
1561
1754
|
style: [
|
|
1562
|
-
|
|
1755
|
+
styles9.input,
|
|
1563
1756
|
{
|
|
1564
1757
|
color: theme.colors.ink,
|
|
1565
1758
|
backgroundColor: theme.colors.surface2,
|
|
@@ -1576,7 +1769,7 @@ var MessageComposer = react.forwardRef(
|
|
|
1576
1769
|
onPress: handleSend,
|
|
1577
1770
|
disabled: submitDisabled,
|
|
1578
1771
|
style: [
|
|
1579
|
-
|
|
1772
|
+
styles9.sendBtn,
|
|
1580
1773
|
{
|
|
1581
1774
|
backgroundColor: submitDisabled ? theme.colors.ink3 : theme.colors.brand,
|
|
1582
1775
|
opacity: submitDisabled ? 0.6 : 1
|
|
@@ -1592,6 +1785,28 @@ var MessageComposer = react.forwardRef(
|
|
|
1592
1785
|
);
|
|
1593
1786
|
}
|
|
1594
1787
|
);
|
|
1788
|
+
function EditChip({ message, onCancel }) {
|
|
1789
|
+
const theme = usePoolseTheme();
|
|
1790
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1791
|
+
reactNative.View,
|
|
1792
|
+
{
|
|
1793
|
+
style: [
|
|
1794
|
+
styles9.replyChip,
|
|
1795
|
+
{
|
|
1796
|
+
backgroundColor: theme.colors.surface2,
|
|
1797
|
+
borderLeftColor: theme.colors.brand
|
|
1798
|
+
}
|
|
1799
|
+
],
|
|
1800
|
+
children: [
|
|
1801
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1 }, children: [
|
|
1802
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.replyName, { color: theme.colors.brand }], children: "Editing message" }),
|
|
1803
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
|
|
1804
|
+
] }),
|
|
1805
|
+
onCancel ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onCancel, hitSlop: 8, style: styles9.replyCancel, children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
|
|
1806
|
+
]
|
|
1807
|
+
}
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1595
1810
|
function ReplyChip({
|
|
1596
1811
|
message,
|
|
1597
1812
|
labelFor,
|
|
@@ -1604,7 +1819,7 @@ function ReplyChip({
|
|
|
1604
1819
|
reactNative.View,
|
|
1605
1820
|
{
|
|
1606
1821
|
style: [
|
|
1607
|
-
|
|
1822
|
+
styles9.replyChip,
|
|
1608
1823
|
{
|
|
1609
1824
|
backgroundColor: theme.colors.surface2,
|
|
1610
1825
|
borderLeftColor: theme.colors.brand
|
|
@@ -1612,27 +1827,26 @@ function ReplyChip({
|
|
|
1612
1827
|
],
|
|
1613
1828
|
children: [
|
|
1614
1829
|
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1 }, children: [
|
|
1615
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [
|
|
1830
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [styles9.replyName, { color: theme.colors.brand }], children: [
|
|
1616
1831
|
"Replying to ",
|
|
1617
1832
|
senderName
|
|
1618
1833
|
] }),
|
|
1619
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
1834
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
|
|
1620
1835
|
] }),
|
|
1621
|
-
onCancel ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onCancel, hitSlop: 8, style:
|
|
1836
|
+
onCancel ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onCancel, hitSlop: 8, style: styles9.replyCancel, children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
|
|
1622
1837
|
]
|
|
1623
1838
|
}
|
|
1624
1839
|
);
|
|
1625
1840
|
}
|
|
1626
|
-
var
|
|
1841
|
+
var styles9 = reactNative.StyleSheet.create({
|
|
1627
1842
|
wrap: {
|
|
1628
1843
|
borderTopWidth: reactNative.StyleSheet.hairlineWidth,
|
|
1629
1844
|
paddingTop: 8,
|
|
1630
|
-
paddingBottom: 10,
|
|
1631
1845
|
paddingHorizontal: 12
|
|
1632
|
-
//
|
|
1633
|
-
//
|
|
1634
|
-
//
|
|
1635
|
-
//
|
|
1846
|
+
// paddingBottom is applied inline — it follows the keyboard state
|
|
1847
|
+
// (10 px while open, max(10, safeBottomInset) while closed) so the
|
|
1848
|
+
// send/attach buttons stay clear of the home indicator without
|
|
1849
|
+
// double-padding above the keyboard.
|
|
1636
1850
|
},
|
|
1637
1851
|
row: {
|
|
1638
1852
|
flexDirection: "row",
|
|
@@ -1721,11 +1935,11 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
|
|
|
1721
1935
|
transparent: true,
|
|
1722
1936
|
animationType: "slide",
|
|
1723
1937
|
onRequestClose: () => setPickerOpen(false),
|
|
1724
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { style:
|
|
1938
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { style: styles10.backdrop, onPress: () => setPickerOpen(false), children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1725
1939
|
reactNative.View,
|
|
1726
1940
|
{
|
|
1727
1941
|
style: [
|
|
1728
|
-
|
|
1942
|
+
styles10.sheet,
|
|
1729
1943
|
{
|
|
1730
1944
|
backgroundColor: theme.colors.surface,
|
|
1731
1945
|
borderTopLeftRadius: theme.radii.lg,
|
|
@@ -1736,7 +1950,7 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
|
|
|
1736
1950
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1737
1951
|
reactNative.Text,
|
|
1738
1952
|
{
|
|
1739
|
-
style: [
|
|
1953
|
+
style: [styles10.title, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
|
|
1740
1954
|
children: "Mention a member"
|
|
1741
1955
|
}
|
|
1742
1956
|
),
|
|
@@ -1767,12 +1981,12 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
|
|
|
1767
1981
|
}
|
|
1768
1982
|
function MentionRow({ member, onPress }) {
|
|
1769
1983
|
const theme = usePoolseTheme();
|
|
1770
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { onPress, style:
|
|
1984
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { onPress, style: styles10.row, children: [
|
|
1771
1985
|
/* @__PURE__ */ jsxRuntime.jsx(Avatar, { src: null, name: member.external_id, seed: member.external_id, size: "sm" }),
|
|
1772
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
1986
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles10.rowText, { color: theme.colors.ink, fontFamily: theme.type.fontBody }], children: member.external_id })
|
|
1773
1987
|
] });
|
|
1774
1988
|
}
|
|
1775
|
-
var
|
|
1989
|
+
var styles10 = reactNative.StyleSheet.create({
|
|
1776
1990
|
backdrop: {
|
|
1777
1991
|
flex: 1,
|
|
1778
1992
|
backgroundColor: "rgba(0,0,0,0.35)",
|
|
@@ -1799,138 +2013,134 @@ var styles9 = reactNative.StyleSheet.create({
|
|
|
1799
2013
|
fontSize: 14
|
|
1800
2014
|
}
|
|
1801
2015
|
});
|
|
2016
|
+
var AT_BOTTOM_THRESHOLD = 32;
|
|
1802
2017
|
function MessageList({
|
|
1803
2018
|
messages,
|
|
2019
|
+
items,
|
|
1804
2020
|
loading = false,
|
|
1805
2021
|
hasMore = false,
|
|
1806
2022
|
onLoadMore,
|
|
1807
2023
|
renderItem,
|
|
1808
2024
|
ListHeader = null,
|
|
1809
2025
|
ListFooter = null,
|
|
1810
|
-
emptyState
|
|
2026
|
+
emptyState,
|
|
2027
|
+
meId = null
|
|
1811
2028
|
}) {
|
|
1812
2029
|
const theme = usePoolseTheme();
|
|
1813
|
-
const
|
|
2030
|
+
const flatListRef = react.useRef(null);
|
|
2031
|
+
const [atBottom, setAtBottom] = react.useState(true);
|
|
2032
|
+
const [newCount, setNewCount] = react.useState(0);
|
|
2033
|
+
const lastTailIdRef = react.useRef(null);
|
|
2034
|
+
const sourceItems = items ?? messages.map((msg) => ({ kind: "msg", msg }));
|
|
2035
|
+
const data = [...sourceItems].reverse();
|
|
2036
|
+
react.useEffect(() => {
|
|
2037
|
+
const tail = messages[messages.length - 1];
|
|
2038
|
+
if (!tail) return;
|
|
2039
|
+
const tailId = tail.id;
|
|
2040
|
+
if (tailId === lastTailIdRef.current) return;
|
|
2041
|
+
const isFirstSet = lastTailIdRef.current === null;
|
|
2042
|
+
lastTailIdRef.current = tailId;
|
|
2043
|
+
if (atBottom || isFirstSet) {
|
|
2044
|
+
setNewCount(0);
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
if (meId !== null && tail.sender_id === meId) return;
|
|
2048
|
+
setNewCount((n) => n + 1);
|
|
2049
|
+
}, [messages, atBottom, meId]);
|
|
2050
|
+
const handleScroll = react.useCallback((e) => {
|
|
2051
|
+
const y = e.nativeEvent.contentOffset.y;
|
|
2052
|
+
const next = y <= AT_BOTTOM_THRESHOLD;
|
|
2053
|
+
setAtBottom((prev) => prev === next ? prev : next);
|
|
2054
|
+
if (next) setNewCount(0);
|
|
2055
|
+
}, []);
|
|
2056
|
+
const scrollToBottom = react.useCallback(() => {
|
|
2057
|
+
flatListRef.current?.scrollToOffset({ offset: 0, animated: true });
|
|
2058
|
+
setNewCount(0);
|
|
2059
|
+
}, []);
|
|
1814
2060
|
const handleRender = react.useCallback(
|
|
1815
|
-
({ item, index }) =>
|
|
1816
|
-
|
|
2061
|
+
({ item, index }) => {
|
|
2062
|
+
if (item.kind === "separator") return item.node;
|
|
2063
|
+
const sourceIndex = data.length - 1 - index;
|
|
2064
|
+
return renderItem(item.msg, sourceIndex);
|
|
2065
|
+
},
|
|
2066
|
+
[renderItem, data.length]
|
|
1817
2067
|
);
|
|
1818
2068
|
if (loading && messages.length === 0) {
|
|
1819
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
2069
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles11.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: theme.colors.brand }) });
|
|
1820
2070
|
}
|
|
1821
2071
|
if (messages.length === 0) {
|
|
1822
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
2072
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles11.placeholder, { backgroundColor: theme.colors.paper }], children: typeof emptyState === "string" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: theme.colors.ink3, fontFamily: theme.type.fontBody }, children: emptyState }) : emptyState ?? null });
|
|
1823
2073
|
}
|
|
1824
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
2074
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles11.root, children: [
|
|
2075
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2076
|
+
reactNative.FlatList,
|
|
2077
|
+
{
|
|
2078
|
+
ref: flatListRef,
|
|
2079
|
+
inverted: true,
|
|
2080
|
+
data,
|
|
2081
|
+
keyExtractor: (item) => item.kind === "msg" ? item.msg.id : item.id,
|
|
2082
|
+
renderItem: handleRender,
|
|
2083
|
+
onEndReached: hasMore ? onLoadMore : void 0,
|
|
2084
|
+
onEndReachedThreshold: 0.3,
|
|
2085
|
+
onScroll: handleScroll,
|
|
2086
|
+
scrollEventThrottle: 32,
|
|
2087
|
+
ListHeaderComponent: ListHeader,
|
|
2088
|
+
ListFooterComponent: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { children: [
|
|
2089
|
+
hasMore && loading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: theme.colors.ink3, style: { marginVertical: 8 } }) : null,
|
|
2090
|
+
ListFooter
|
|
2091
|
+
] }),
|
|
2092
|
+
style: { backgroundColor: theme.colors.paper },
|
|
2093
|
+
contentContainerStyle: { paddingVertical: 8 }
|
|
2094
|
+
}
|
|
2095
|
+
),
|
|
2096
|
+
!atBottom && newCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2097
|
+
reactNative.Pressable,
|
|
2098
|
+
{
|
|
2099
|
+
onPress: scrollToBottom,
|
|
2100
|
+
style: [
|
|
2101
|
+
styles11.jumpButton,
|
|
2102
|
+
{
|
|
2103
|
+
backgroundColor: theme.colors.brand
|
|
2104
|
+
},
|
|
2105
|
+
theme.shadows.sm
|
|
2106
|
+
],
|
|
2107
|
+
accessibilityLabel: `${newCount} new ${newCount === 1 ? "message" : "messages"}, jump to latest`,
|
|
2108
|
+
children: [
|
|
2109
|
+
/* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "chevron-down", size: 14, color: theme.colors.onBrand }),
|
|
2110
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [styles11.jumpText, { color: theme.colors.onBrand }], children: [
|
|
2111
|
+
newCount,
|
|
2112
|
+
" new ",
|
|
2113
|
+
newCount === 1 ? "message" : "messages"
|
|
2114
|
+
] })
|
|
2115
|
+
]
|
|
2116
|
+
}
|
|
2117
|
+
) : null
|
|
2118
|
+
] });
|
|
1842
2119
|
}
|
|
1843
|
-
var
|
|
2120
|
+
var styles11 = reactNative.StyleSheet.create({
|
|
2121
|
+
root: {
|
|
2122
|
+
flex: 1
|
|
2123
|
+
},
|
|
1844
2124
|
placeholder: {
|
|
1845
2125
|
flex: 1,
|
|
1846
2126
|
alignItems: "center",
|
|
1847
2127
|
justifyContent: "center",
|
|
1848
2128
|
padding: 24
|
|
1849
|
-
}
|
|
1850
|
-
});
|
|
1851
|
-
function EditableMessageBubble({
|
|
1852
|
-
message,
|
|
1853
|
-
currentUserId,
|
|
1854
|
-
onCancel,
|
|
1855
|
-
onSave
|
|
1856
|
-
}) {
|
|
1857
|
-
const theme = usePoolseTheme();
|
|
1858
|
-
const isSelf = currentUserId !== null && message.sender_id === currentUserId;
|
|
1859
|
-
const [value, setValue] = react.useState(message.body ?? "");
|
|
1860
|
-
const [saving, setSaving] = react.useState(false);
|
|
1861
|
-
const submit = async () => {
|
|
1862
|
-
if (saving || !value.trim()) return;
|
|
1863
|
-
setSaving(true);
|
|
1864
|
-
try {
|
|
1865
|
-
await onSave(value.trim());
|
|
1866
|
-
} finally {
|
|
1867
|
-
setSaving(false);
|
|
1868
|
-
}
|
|
1869
|
-
};
|
|
1870
|
-
const bg = isSelf ? theme.colors.selfBubble : theme.colors.otherBubble;
|
|
1871
|
-
const ink = isSelf ? theme.colors.selfBubbleText : theme.colors.otherBubbleText;
|
|
1872
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1873
|
-
reactNative.View,
|
|
1874
|
-
{
|
|
1875
|
-
style: [
|
|
1876
|
-
styles11.wrap,
|
|
1877
|
-
{
|
|
1878
|
-
alignSelf: isSelf ? "flex-end" : "flex-start",
|
|
1879
|
-
maxWidth: "80%"
|
|
1880
|
-
}
|
|
1881
|
-
],
|
|
1882
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1883
|
-
reactNative.View,
|
|
1884
|
-
{
|
|
1885
|
-
style: [
|
|
1886
|
-
styles11.bubble,
|
|
1887
|
-
{
|
|
1888
|
-
backgroundColor: bg,
|
|
1889
|
-
borderRadius: theme.radii.bubble,
|
|
1890
|
-
paddingHorizontal: theme.spacing.md,
|
|
1891
|
-
paddingVertical: theme.spacing.sm
|
|
1892
|
-
},
|
|
1893
|
-
theme.shadows.sm
|
|
1894
|
-
],
|
|
1895
|
-
children: [
|
|
1896
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1897
|
-
reactNative.TextInput,
|
|
1898
|
-
{
|
|
1899
|
-
value,
|
|
1900
|
-
onChangeText: setValue,
|
|
1901
|
-
multiline: true,
|
|
1902
|
-
autoFocus: true,
|
|
1903
|
-
style: {
|
|
1904
|
-
color: ink,
|
|
1905
|
-
fontFamily: theme.type.fontBody,
|
|
1906
|
-
fontSize: theme.type.bodySize,
|
|
1907
|
-
minHeight: 24
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
),
|
|
1911
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles11.actions, children: [
|
|
1912
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onCancel, hitSlop: 6, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles11.btn, { color: ink, opacity: 0.7 }], children: "Cancel" }) }),
|
|
1913
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: submit, disabled: saving, hitSlop: 6, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles11.btn, { color: ink, fontWeight: "700" }], children: saving ? "Saving\u2026" : "Save" }) })
|
|
1914
|
-
] })
|
|
1915
|
-
]
|
|
1916
|
-
}
|
|
1917
|
-
)
|
|
1918
|
-
}
|
|
1919
|
-
);
|
|
1920
|
-
}
|
|
1921
|
-
var styles11 = reactNative.StyleSheet.create({
|
|
1922
|
-
wrap: {
|
|
1923
|
-
marginBottom: 4
|
|
1924
2129
|
},
|
|
1925
|
-
|
|
1926
|
-
|
|
2130
|
+
jumpButton: {
|
|
2131
|
+
position: "absolute",
|
|
2132
|
+
alignSelf: "center",
|
|
2133
|
+
bottom: 12,
|
|
1927
2134
|
flexDirection: "row",
|
|
1928
|
-
|
|
1929
|
-
gap:
|
|
1930
|
-
|
|
2135
|
+
alignItems: "center",
|
|
2136
|
+
gap: 6,
|
|
2137
|
+
paddingHorizontal: 14,
|
|
2138
|
+
paddingVertical: 8,
|
|
2139
|
+
borderRadius: 999
|
|
1931
2140
|
},
|
|
1932
|
-
|
|
1933
|
-
fontSize: 13
|
|
2141
|
+
jumpText: {
|
|
2142
|
+
fontSize: 13,
|
|
2143
|
+
fontWeight: "600"
|
|
1934
2144
|
}
|
|
1935
2145
|
});
|
|
1936
2146
|
var DEFAULT_REACTION_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F64F}", "\u{1F525}"];
|
|
@@ -2393,6 +2603,77 @@ var styles14 = reactNative.StyleSheet.create({
|
|
|
2393
2603
|
borderRadius: 20
|
|
2394
2604
|
}
|
|
2395
2605
|
});
|
|
2606
|
+
var MarkdownModule;
|
|
2607
|
+
var warned = false;
|
|
2608
|
+
function loadMarkdown() {
|
|
2609
|
+
if (MarkdownModule === void 0) {
|
|
2610
|
+
try {
|
|
2611
|
+
MarkdownModule = __require("react-native-markdown-display");
|
|
2612
|
+
} catch {
|
|
2613
|
+
MarkdownModule = null;
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
const fn = MarkdownModule?.default;
|
|
2617
|
+
if (!fn) {
|
|
2618
|
+
if (!warned) {
|
|
2619
|
+
warned = true;
|
|
2620
|
+
console.warn(
|
|
2621
|
+
"[@poolse/react-native] Markdown rendering requires `react-native-markdown-display`. Install it to enable bold/italic/code/links in message bubbles. Falling back to plain text."
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
return null;
|
|
2625
|
+
}
|
|
2626
|
+
return fn;
|
|
2627
|
+
}
|
|
2628
|
+
function renderMarkdown(body, args) {
|
|
2629
|
+
const Markdown = loadMarkdown();
|
|
2630
|
+
if (!Markdown) return null;
|
|
2631
|
+
const baseText = {
|
|
2632
|
+
color: args.textColor,
|
|
2633
|
+
fontFamily: args.fontFamily,
|
|
2634
|
+
fontSize: args.fontSize,
|
|
2635
|
+
lineHeight: args.lineHeight
|
|
2636
|
+
};
|
|
2637
|
+
const style = {
|
|
2638
|
+
body: baseText,
|
|
2639
|
+
paragraph: { ...baseText, marginTop: 0, marginBottom: 0 },
|
|
2640
|
+
text: baseText,
|
|
2641
|
+
strong: { ...baseText, fontWeight: "700" },
|
|
2642
|
+
em: { ...baseText, fontStyle: "italic" },
|
|
2643
|
+
s: { ...baseText, textDecorationLine: "line-through" },
|
|
2644
|
+
link: { color: args.linkColor, textDecorationLine: "underline" },
|
|
2645
|
+
code_inline: {
|
|
2646
|
+
...baseText,
|
|
2647
|
+
backgroundColor: "rgba(127,127,127,0.18)",
|
|
2648
|
+
borderRadius: 4,
|
|
2649
|
+
paddingHorizontal: 4
|
|
2650
|
+
},
|
|
2651
|
+
code_block: {
|
|
2652
|
+
...baseText,
|
|
2653
|
+
backgroundColor: "rgba(127,127,127,0.18)",
|
|
2654
|
+
padding: 8,
|
|
2655
|
+
borderRadius: 6
|
|
2656
|
+
},
|
|
2657
|
+
fence: {
|
|
2658
|
+
...baseText,
|
|
2659
|
+
backgroundColor: "rgba(127,127,127,0.18)",
|
|
2660
|
+
padding: 8,
|
|
2661
|
+
borderRadius: 6
|
|
2662
|
+
},
|
|
2663
|
+
bullet_list: { marginTop: 2, marginBottom: 2 },
|
|
2664
|
+
ordered_list: { marginTop: 2, marginBottom: 2 },
|
|
2665
|
+
list_item: baseText,
|
|
2666
|
+
blockquote: {
|
|
2667
|
+
...baseText,
|
|
2668
|
+
borderLeftWidth: 3,
|
|
2669
|
+
borderLeftColor: args.linkColor,
|
|
2670
|
+
paddingLeft: 8,
|
|
2671
|
+
marginVertical: 4,
|
|
2672
|
+
opacity: 0.85
|
|
2673
|
+
}
|
|
2674
|
+
};
|
|
2675
|
+
return react.createElement(Markdown, { style, onLinkPress: args.onLinkPress }, body);
|
|
2676
|
+
}
|
|
2396
2677
|
function MessageBubble({
|
|
2397
2678
|
message,
|
|
2398
2679
|
currentUserId,
|
|
@@ -2403,7 +2684,8 @@ function MessageBubble({
|
|
|
2403
2684
|
maxBodyLength = 0,
|
|
2404
2685
|
showSenderName = false,
|
|
2405
2686
|
actionsTrigger,
|
|
2406
|
-
showAttachments = true
|
|
2687
|
+
showAttachments = true,
|
|
2688
|
+
markdown = true
|
|
2407
2689
|
}) {
|
|
2408
2690
|
const theme = usePoolseTheme();
|
|
2409
2691
|
const { width: screenWidth } = reactNative.useWindowDimensions();
|
|
@@ -2488,22 +2770,53 @@ function MessageBubble({
|
|
|
2488
2770
|
) : null,
|
|
2489
2771
|
renderAttachments && images.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ImageMosaic, { images, maxWidth: contentMaxWidth }) : null,
|
|
2490
2772
|
renderAttachments && files.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: { gap: theme.spacing.xs, marginBottom: theme.spacing.xs }, children: files.map((att) => /* @__PURE__ */ jsxRuntime.jsx(AttachmentPreview, { attachment: att, maxWidth: contentMaxWidth }, att.id)) }) : null,
|
|
2491
|
-
body.length > 0 ?
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
styles15.body,
|
|
2773
|
+
body.length > 0 ? (() => {
|
|
2774
|
+
if (isDeleted) {
|
|
2775
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2776
|
+
reactNative.Text,
|
|
2496
2777
|
{
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2778
|
+
style: [
|
|
2779
|
+
styles15.body,
|
|
2780
|
+
{
|
|
2781
|
+
color: theme.colors.ink3,
|
|
2782
|
+
fontFamily: theme.type.fontBody,
|
|
2783
|
+
fontSize: theme.type.bodySize,
|
|
2784
|
+
lineHeight: theme.type.lineHeight,
|
|
2785
|
+
fontStyle: "italic"
|
|
2786
|
+
}
|
|
2787
|
+
],
|
|
2788
|
+
children: "Message deleted"
|
|
2502
2789
|
}
|
|
2503
|
-
|
|
2504
|
-
children: isDeleted ? "Message deleted" : truncated
|
|
2790
|
+
);
|
|
2505
2791
|
}
|
|
2506
|
-
|
|
2792
|
+
const md = markdown ? renderMarkdown(truncated, {
|
|
2793
|
+
textColor,
|
|
2794
|
+
linkColor: isSelf ? theme.colors.onBrand : theme.colors.brand,
|
|
2795
|
+
fontFamily: theme.type.fontBody,
|
|
2796
|
+
fontSize: theme.type.bodySize,
|
|
2797
|
+
lineHeight: theme.type.lineHeight,
|
|
2798
|
+
onLinkPress: (url) => {
|
|
2799
|
+
void reactNative.Linking.openURL(url).catch(() => void 0);
|
|
2800
|
+
return false;
|
|
2801
|
+
}
|
|
2802
|
+
}) : null;
|
|
2803
|
+
if (md) return md;
|
|
2804
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2805
|
+
reactNative.Text,
|
|
2806
|
+
{
|
|
2807
|
+
style: [
|
|
2808
|
+
styles15.body,
|
|
2809
|
+
{
|
|
2810
|
+
color: textColor,
|
|
2811
|
+
fontFamily: theme.type.fontBody,
|
|
2812
|
+
fontSize: theme.type.bodySize,
|
|
2813
|
+
lineHeight: theme.type.lineHeight
|
|
2814
|
+
}
|
|
2815
|
+
],
|
|
2816
|
+
children: truncated
|
|
2817
|
+
}
|
|
2818
|
+
);
|
|
2819
|
+
})() : null,
|
|
2507
2820
|
maxBodyLength > 0 && body.length > maxBodyLength ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2508
2821
|
reactNative.Text,
|
|
2509
2822
|
{
|
|
@@ -2596,9 +2909,9 @@ function formatTime(iso) {
|
|
|
2596
2909
|
try {
|
|
2597
2910
|
const d = new Date(iso);
|
|
2598
2911
|
const now = /* @__PURE__ */ new Date();
|
|
2599
|
-
const
|
|
2912
|
+
const sameDay2 = isSameDay(d, now);
|
|
2600
2913
|
const time = d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
|
|
2601
|
-
if (
|
|
2914
|
+
if (sameDay2) return time;
|
|
2602
2915
|
const yesterday = new Date(now);
|
|
2603
2916
|
yesterday.setDate(now.getDate() - 1);
|
|
2604
2917
|
if (isSameDay(d, yesterday)) return `Yesterday ${time}`;
|
|
@@ -2706,12 +3019,26 @@ function ReactionStrip({
|
|
|
2706
3019
|
styles16.chip,
|
|
2707
3020
|
{
|
|
2708
3021
|
backgroundColor: meReacted ? theme.colors.brandSoft : theme.colors.surface2,
|
|
2709
|
-
borderColor: meReacted ? theme.colors.brand : theme.colors.border
|
|
3022
|
+
borderColor: meReacted ? theme.colors.brand : theme.colors.border,
|
|
3023
|
+
borderWidth: meReacted ? 1.5 : 1,
|
|
3024
|
+
transform: meReacted ? [{ scale: 1.04 }] : void 0
|
|
2710
3025
|
}
|
|
2711
3026
|
],
|
|
2712
3027
|
children: [
|
|
2713
3028
|
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles16.emoji, children: emoji }),
|
|
2714
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3029
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3030
|
+
reactNative.Text,
|
|
3031
|
+
{
|
|
3032
|
+
style: [
|
|
3033
|
+
styles16.count,
|
|
3034
|
+
{
|
|
3035
|
+
color: meReacted ? theme.colors.brand : theme.colors.ink2,
|
|
3036
|
+
fontWeight: meReacted ? "700" : "600"
|
|
3037
|
+
}
|
|
3038
|
+
],
|
|
3039
|
+
children: userArr.length
|
|
3040
|
+
}
|
|
3041
|
+
)
|
|
2715
3042
|
]
|
|
2716
3043
|
},
|
|
2717
3044
|
emoji
|
|
@@ -2764,7 +3091,6 @@ var styles16 = reactNative.StyleSheet.create({
|
|
|
2764
3091
|
paddingHorizontal: 9,
|
|
2765
3092
|
paddingVertical: 4,
|
|
2766
3093
|
borderRadius: 14,
|
|
2767
|
-
borderWidth: 1,
|
|
2768
3094
|
gap: 5,
|
|
2769
3095
|
minHeight: 26,
|
|
2770
3096
|
// Subtle elevation so chips lift off the surface
|
|
@@ -2780,7 +3106,6 @@ var styles16 = reactNative.StyleSheet.create({
|
|
|
2780
3106
|
},
|
|
2781
3107
|
count: {
|
|
2782
3108
|
fontSize: 12,
|
|
2783
|
-
fontWeight: "600",
|
|
2784
3109
|
fontVariant: ["tabular-nums"]
|
|
2785
3110
|
},
|
|
2786
3111
|
backdrop: {
|
|
@@ -2818,11 +3143,10 @@ function MessageRow({
|
|
|
2818
3143
|
actions = true,
|
|
2819
3144
|
threads = true,
|
|
2820
3145
|
quotations = true,
|
|
3146
|
+
markdown = true,
|
|
2821
3147
|
readState,
|
|
2822
|
-
|
|
3148
|
+
isEditing = false,
|
|
2823
3149
|
onStartEdit,
|
|
2824
|
-
onCancelEdit,
|
|
2825
|
-
onSaveEdit,
|
|
2826
3150
|
onDelete,
|
|
2827
3151
|
onOpenThread,
|
|
2828
3152
|
onQuote,
|
|
@@ -2843,7 +3167,7 @@ function MessageRow({
|
|
|
2843
3167
|
...msg.reactions ? { initialReactions: msg.reactions } : {}
|
|
2844
3168
|
});
|
|
2845
3169
|
const translateX = react.useRef(new reactNative.Animated.Value(0)).current;
|
|
2846
|
-
const swipeEnabled = quotations && !!onQuote && !
|
|
3170
|
+
const swipeEnabled = quotations && !!onQuote && !isEditing;
|
|
2847
3171
|
const panResponder = react.useMemo(
|
|
2848
3172
|
() => reactNative.PanResponder.create({
|
|
2849
3173
|
// Tap / long-press still go to the inner Pressable.
|
|
@@ -2918,137 +3242,149 @@ function MessageRow({
|
|
|
2918
3242
|
const rowSender = react$1.useUser(showAvatarSlot ? msg.sender_external_id : null);
|
|
2919
3243
|
const avatarUrl = msg.sender_external_id ? avatarFor?.(msg.sender_external_id) ?? rowSender.profile?.avatarUrl ?? null : null;
|
|
2920
3244
|
const senderLabel = msg.sender_external_id ? labelFor?.(msg.sender_external_id) ?? rowSender.profile?.displayName ?? msg.sender_external_id : null;
|
|
2921
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
reactNative.
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "reply", size: 16, color: theme.colors.brand })
|
|
2946
|
-
}
|
|
2947
|
-
) : null,
|
|
2948
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2949
|
-
reactNative.Animated.View,
|
|
2950
|
-
{
|
|
2951
|
-
...swipeEnabled ? panResponder.panHandlers : {},
|
|
2952
|
-
style: { transform: [{ translateX }] },
|
|
2953
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2954
|
-
reactNative.Pressable,
|
|
3245
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3246
|
+
reactNative.View,
|
|
3247
|
+
{
|
|
3248
|
+
style: [
|
|
3249
|
+
styles17.row,
|
|
3250
|
+
{ alignSelf: isSelf ? "flex-end" : "flex-start" },
|
|
3251
|
+
// Lift this row above the dim backdrop the parent renders
|
|
3252
|
+
// during edit mode. zIndex covers iOS / web; elevation does
|
|
3253
|
+
// the equivalent on Android.
|
|
3254
|
+
isEditing ? styles17.rowEditing : null
|
|
3255
|
+
],
|
|
3256
|
+
children: [
|
|
3257
|
+
showAvatarSlot ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles17.avatarSlot, children: showAvatarRender ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3258
|
+
Avatar,
|
|
3259
|
+
{
|
|
3260
|
+
src: avatarUrl ?? null,
|
|
3261
|
+
name: senderLabel,
|
|
3262
|
+
seed: msg.sender_external_id ?? null,
|
|
3263
|
+
size: "sm"
|
|
3264
|
+
}
|
|
3265
|
+
) : null }) : null,
|
|
3266
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles17.bubbleCol, children: [
|
|
3267
|
+
swipeEnabled ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3268
|
+
reactNative.Animated.View,
|
|
2955
3269
|
{
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
accessibilityRole: "button",
|
|
2961
|
-
accessibilityLabel: "Message actions",
|
|
2962
|
-
children: editing ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2963
|
-
EditableMessageBubble,
|
|
3270
|
+
pointerEvents: "none",
|
|
3271
|
+
style: [
|
|
3272
|
+
styles17.replyHint,
|
|
3273
|
+
isSelf ? styles17.replyHintRight : styles17.replyHintLeft,
|
|
2964
3274
|
{
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
onSave: onSaveEdit ?? (() => void 0)
|
|
3275
|
+
opacity: replyIconOpacity,
|
|
3276
|
+
transform: [{ scale: replyIconScale }],
|
|
3277
|
+
backgroundColor: theme.colors.surface2
|
|
2969
3278
|
}
|
|
2970
|
-
|
|
2971
|
-
|
|
3279
|
+
],
|
|
3280
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "reply", size: 16, color: theme.colors.brand })
|
|
3281
|
+
}
|
|
3282
|
+
) : null,
|
|
3283
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3284
|
+
reactNative.Animated.View,
|
|
3285
|
+
{
|
|
3286
|
+
...swipeEnabled ? panResponder.panHandlers : {},
|
|
3287
|
+
style: { transform: [{ translateX }] },
|
|
3288
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3289
|
+
reactNative.Pressable,
|
|
2972
3290
|
{
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
showAttachments: attachments,
|
|
2982
|
-
actionsTrigger: actions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2983
|
-
PoolseIcon,
|
|
3291
|
+
onLongPress: () => {
|
|
3292
|
+
if (!actions) return;
|
|
3293
|
+
setActionsOpen(true);
|
|
3294
|
+
},
|
|
3295
|
+
accessibilityRole: "button",
|
|
3296
|
+
accessibilityLabel: "Message actions",
|
|
3297
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3298
|
+
MessageBubble,
|
|
2984
3299
|
{
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
3300
|
+
message: msg,
|
|
3301
|
+
currentUserId: meId,
|
|
3302
|
+
...readState ? { readState } : {},
|
|
3303
|
+
...labelFor ? { labelFor } : {},
|
|
3304
|
+
...onQuotedClick ? { onQuotedClick } : {},
|
|
3305
|
+
groupPosition,
|
|
3306
|
+
maxBodyLength,
|
|
3307
|
+
showSenderName,
|
|
3308
|
+
showAttachments: attachments,
|
|
3309
|
+
markdown,
|
|
3310
|
+
actionsTrigger: actions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3311
|
+
PoolseIcon,
|
|
3312
|
+
{
|
|
3313
|
+
name: "chevron-down",
|
|
3314
|
+
size: 14,
|
|
3315
|
+
color: isSelf ? theme.colors.onBrand : theme.colors.ink3
|
|
3316
|
+
}
|
|
3317
|
+
) }) : null
|
|
2988
3318
|
}
|
|
2989
|
-
)
|
|
3319
|
+
)
|
|
2990
3320
|
}
|
|
2991
3321
|
)
|
|
2992
3322
|
}
|
|
2993
|
-
)
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
}
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3323
|
+
),
|
|
3324
|
+
threads && (msg.reply_count ?? 0) > 0 && onOpenThread ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onOpenThread, style: styles17.threadPill, children: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [styles17.threadPillText, { color: theme.colors.brand }], children: [
|
|
3325
|
+
"\u{1F4AC} ",
|
|
3326
|
+
msg.reply_count,
|
|
3327
|
+
" ",
|
|
3328
|
+
msg.reply_count === 1 ? "reply" : "replies"
|
|
3329
|
+
] }) }) : null,
|
|
3330
|
+
reactions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles17.reactionsRow, { alignSelf: isSelf ? "flex-end" : "flex-start" }], children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3331
|
+
ReactionStrip,
|
|
3332
|
+
{
|
|
3333
|
+
messageId: msg.id,
|
|
3334
|
+
conversationId: msg.conversation_id,
|
|
3335
|
+
meId,
|
|
3336
|
+
...msg.reactions ? { initialReactions: msg.reactions } : {}
|
|
3337
|
+
}
|
|
3338
|
+
) }) : null
|
|
3339
|
+
] }),
|
|
3340
|
+
actions && actionsOpen ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3341
|
+
MessageActions,
|
|
3342
|
+
{
|
|
3343
|
+
visible: actionsOpen,
|
|
3344
|
+
onClose: () => setActionsOpen(false),
|
|
3345
|
+
isSelf,
|
|
3346
|
+
...reactions ? {
|
|
3347
|
+
// Inline quick-react row at the top of the sheet —
|
|
3348
|
+
// one tap = react + close, no second modal needed.
|
|
3349
|
+
onPickReaction: (emoji) => {
|
|
3350
|
+
void reactionsApi.addReaction(emoji);
|
|
3351
|
+
}
|
|
3352
|
+
} : {},
|
|
3353
|
+
...msg.body && !msg.deleted_at ? {
|
|
3354
|
+
onCopy: () => {
|
|
3355
|
+
setActionsOpen(false);
|
|
3356
|
+
void copyToClipboard(msg.body ?? "");
|
|
3357
|
+
}
|
|
3358
|
+
} : {},
|
|
3359
|
+
...onStartEdit ? {
|
|
3360
|
+
onEdit: () => {
|
|
3361
|
+
setActionsOpen(false);
|
|
3362
|
+
onStartEdit();
|
|
3363
|
+
}
|
|
3364
|
+
} : {},
|
|
3365
|
+
...onDelete ? {
|
|
3366
|
+
onDelete: () => {
|
|
3367
|
+
setActionsOpen(false);
|
|
3368
|
+
onDelete();
|
|
3369
|
+
}
|
|
3370
|
+
} : {},
|
|
3371
|
+
...onOpenThread ? {
|
|
3372
|
+
onReplyInThread: () => {
|
|
3373
|
+
setActionsOpen(false);
|
|
3374
|
+
onOpenThread();
|
|
3375
|
+
}
|
|
3376
|
+
} : {},
|
|
3377
|
+
...quotations && onQuote ? {
|
|
3378
|
+
onQuote: () => {
|
|
3379
|
+
setActionsOpen(false);
|
|
3380
|
+
onQuote();
|
|
3381
|
+
}
|
|
3382
|
+
} : {}
|
|
3047
3383
|
}
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3384
|
+
) : null
|
|
3385
|
+
]
|
|
3386
|
+
}
|
|
3387
|
+
);
|
|
3052
3388
|
}
|
|
3053
3389
|
var styles17 = reactNative.StyleSheet.create({
|
|
3054
3390
|
row: {
|
|
@@ -3064,6 +3400,12 @@ var styles17 = reactNative.StyleSheet.create({
|
|
|
3064
3400
|
paddingHorizontal: 10,
|
|
3065
3401
|
maxWidth: "95%"
|
|
3066
3402
|
},
|
|
3403
|
+
rowEditing: {
|
|
3404
|
+
// Above ConversationView's editBackdrop (zIndex 5). Elevation
|
|
3405
|
+
// does the equivalent stacking on Android.
|
|
3406
|
+
zIndex: 10,
|
|
3407
|
+
elevation: 10
|
|
3408
|
+
},
|
|
3067
3409
|
avatarSlot: {
|
|
3068
3410
|
width: 36,
|
|
3069
3411
|
marginRight: 6
|
|
@@ -3103,6 +3445,27 @@ var styles17 = reactNative.StyleSheet.create({
|
|
|
3103
3445
|
marginTop: 2
|
|
3104
3446
|
}
|
|
3105
3447
|
});
|
|
3448
|
+
var clipboardModule;
|
|
3449
|
+
var clipboardWarned = false;
|
|
3450
|
+
async function copyToClipboard(text) {
|
|
3451
|
+
if (clipboardModule === void 0) {
|
|
3452
|
+
try {
|
|
3453
|
+
clipboardModule = __require("expo-clipboard");
|
|
3454
|
+
} catch {
|
|
3455
|
+
clipboardModule = null;
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
if (!clipboardModule?.setStringAsync) {
|
|
3459
|
+
if (!clipboardWarned) {
|
|
3460
|
+
clipboardWarned = true;
|
|
3461
|
+
console.warn(
|
|
3462
|
+
"[@poolse/react-native] Copy action requires `expo-clipboard`. Install it to enable copy-to-clipboard."
|
|
3463
|
+
);
|
|
3464
|
+
}
|
|
3465
|
+
return;
|
|
3466
|
+
}
|
|
3467
|
+
await clipboardModule.setStringAsync(text);
|
|
3468
|
+
}
|
|
3106
3469
|
var SHEET_HEIGHT_FRACTION = 0.88;
|
|
3107
3470
|
function ThreadView({
|
|
3108
3471
|
visible,
|
|
@@ -3158,31 +3521,34 @@ function ThreadSheet({
|
|
|
3158
3521
|
const [pickerOpen, setPickerOpen] = react.useState(false);
|
|
3159
3522
|
const [editingId, setEditingId] = react.useState(null);
|
|
3160
3523
|
const renderReply = react.useCallback(
|
|
3161
|
-
(msg) =>
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3524
|
+
(msg) => {
|
|
3525
|
+
const isSelf = meId !== null && msg.sender_id === meId;
|
|
3526
|
+
const canEdit = isSelf && !msg.deleted_at;
|
|
3527
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3528
|
+
MessageRow,
|
|
3529
|
+
{
|
|
3530
|
+
msg,
|
|
3531
|
+
meId,
|
|
3532
|
+
reactions: true,
|
|
3533
|
+
attachments: true,
|
|
3534
|
+
actions: true,
|
|
3535
|
+
threads: false,
|
|
3536
|
+
quotations: false,
|
|
3537
|
+
isEditing: editingId === msg.id,
|
|
3538
|
+
...canEdit ? { onStartEdit: () => setEditingId(msg.id) } : {},
|
|
3539
|
+
onDelete: () => {
|
|
3540
|
+
void deleteReply(msg.id);
|
|
3541
|
+
},
|
|
3542
|
+
...labelFor ? { labelFor } : {},
|
|
3543
|
+
...avatarFor ? { avatarFor } : {}
|
|
3544
|
+
}
|
|
3545
|
+
);
|
|
3546
|
+
},
|
|
3547
|
+
[meId, editingId, deleteReply, labelFor, avatarFor]
|
|
3548
|
+
);
|
|
3549
|
+
const editingMessage = react.useMemo(
|
|
3550
|
+
() => editingId ? replies.find((m) => m.id === editingId) ?? null : null,
|
|
3551
|
+
[editingId, replies]
|
|
3186
3552
|
);
|
|
3187
3553
|
const rootRow = react.useMemo(
|
|
3188
3554
|
() => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3268,7 +3634,14 @@ function ThreadSheet({
|
|
|
3268
3634
|
},
|
|
3269
3635
|
placeholder: "Reply to thread\u2026",
|
|
3270
3636
|
attachments: true,
|
|
3271
|
-
onAttachPress: () => setPickerOpen(true)
|
|
3637
|
+
onAttachPress: () => setPickerOpen(true),
|
|
3638
|
+
editingMessage,
|
|
3639
|
+
onCancelEdit: () => setEditingId(null),
|
|
3640
|
+
onSaveEdit: async (body) => {
|
|
3641
|
+
if (!editingId) return;
|
|
3642
|
+
await edit(editingId, body);
|
|
3643
|
+
setEditingId(null);
|
|
3644
|
+
}
|
|
3272
3645
|
}
|
|
3273
3646
|
)
|
|
3274
3647
|
]
|
|
@@ -3432,6 +3805,7 @@ var styles19 = reactNative.StyleSheet.create({
|
|
|
3432
3805
|
fontSize: 12
|
|
3433
3806
|
}
|
|
3434
3807
|
});
|
|
3808
|
+
var GROUPING_WINDOW_MS = 5 * 60 * 1e3;
|
|
3435
3809
|
function ConversationView({
|
|
3436
3810
|
conversationId,
|
|
3437
3811
|
emptyState,
|
|
@@ -3448,6 +3822,7 @@ function ConversationView({
|
|
|
3448
3822
|
threads = true,
|
|
3449
3823
|
quotations = true,
|
|
3450
3824
|
readReceipts = true,
|
|
3825
|
+
markdown = true,
|
|
3451
3826
|
maxBodyLength = 200,
|
|
3452
3827
|
senderLabels = "auto",
|
|
3453
3828
|
avatars = "auto",
|
|
@@ -3466,9 +3841,25 @@ function ConversationView({
|
|
|
3466
3841
|
send,
|
|
3467
3842
|
edit,
|
|
3468
3843
|
delete: deleteMsg,
|
|
3469
|
-
markReadUpTo
|
|
3844
|
+
markReadUpTo,
|
|
3845
|
+
refetch
|
|
3470
3846
|
} = react$1.useMessages(conversationId);
|
|
3471
3847
|
const { typing, signalTyping } = react$1.useTyping(conversationId);
|
|
3848
|
+
const realtimeStatus = react$1.useRealtimeStatus();
|
|
3849
|
+
react.useEffect(() => {
|
|
3850
|
+
const sub = reactNative.AppState.addEventListener("change", (next) => {
|
|
3851
|
+
if (next === "active") void refetch();
|
|
3852
|
+
});
|
|
3853
|
+
return () => sub.remove();
|
|
3854
|
+
}, [refetch]);
|
|
3855
|
+
const prevStatusRef = react.useRef(realtimeStatus);
|
|
3856
|
+
react.useEffect(() => {
|
|
3857
|
+
const prev = prevStatusRef.current;
|
|
3858
|
+
prevStatusRef.current = realtimeStatus;
|
|
3859
|
+
if (realtimeStatus === "connected" && (prev === "reconnecting" || prev === "closed" || prev === "connecting")) {
|
|
3860
|
+
void refetch();
|
|
3861
|
+
}
|
|
3862
|
+
}, [realtimeStatus, refetch]);
|
|
3472
3863
|
const wantsMembers = mentions || senderLabels === "auto" || avatars === "auto";
|
|
3473
3864
|
const { members } = react$1.useMembers(wantsMembers ? conversationId : "");
|
|
3474
3865
|
const isGroupChat = members.length > 2;
|
|
@@ -3499,10 +3890,24 @@ function ConversationView({
|
|
|
3499
3890
|
});
|
|
3500
3891
|
onMarkedRead?.(conversationId, latest.id);
|
|
3501
3892
|
}, [messages, meId, readReceipts, markReadUpTo, onMarkedRead, conversationId]);
|
|
3893
|
+
const groupPositionById = react.useMemo(() => {
|
|
3894
|
+
const map = /* @__PURE__ */ new Map();
|
|
3895
|
+
for (let i = 0; i < messages.length; i++) {
|
|
3896
|
+
const msg = messages[i];
|
|
3897
|
+
if (!msg) continue;
|
|
3898
|
+
const prev = i > 0 ? messages[i - 1] ?? null : null;
|
|
3899
|
+
const next = i < messages.length - 1 ? messages[i + 1] ?? null : null;
|
|
3900
|
+
map.set(msg.id, react$1.computeGroupPosition(msg, prev, next, GROUPING_WINDOW_MS));
|
|
3901
|
+
}
|
|
3902
|
+
return map;
|
|
3903
|
+
}, [messages]);
|
|
3502
3904
|
const renderItem = react.useCallback(
|
|
3503
3905
|
(msg) => {
|
|
3504
3906
|
if (renderMessage) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderMessage(msg, meId) });
|
|
3505
3907
|
const readState = readReceipts && meId !== null && msg.sender_id === meId ? readStateForMessage(msg, members, meId, sequenceByMessageId) : void 0;
|
|
3908
|
+
const isSelf = meId !== null && msg.sender_id === meId;
|
|
3909
|
+
const canEdit = isSelf && !msg.deleted_at && (readState ?? "sent") === "sent";
|
|
3910
|
+
const groupPosition = groupPositionById.get(msg.id) ?? "standalone";
|
|
3506
3911
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3507
3912
|
MessageRow,
|
|
3508
3913
|
{
|
|
@@ -3514,13 +3919,8 @@ function ConversationView({
|
|
|
3514
3919
|
threads,
|
|
3515
3920
|
quotations,
|
|
3516
3921
|
...readState ? { readState } : {},
|
|
3517
|
-
|
|
3518
|
-
onStartEdit: () => setEditingId(msg.id),
|
|
3519
|
-
onCancelEdit: () => setEditingId(null),
|
|
3520
|
-
onSaveEdit: async (body) => {
|
|
3521
|
-
await edit(msg.id, body);
|
|
3522
|
-
setEditingId(null);
|
|
3523
|
-
},
|
|
3922
|
+
isEditing: editingId === msg.id,
|
|
3923
|
+
...canEdit ? { onStartEdit: () => setEditingId(msg.id) } : {},
|
|
3524
3924
|
onDelete: () => deleteMsg(msg.id),
|
|
3525
3925
|
...threads ? { onOpenThread: () => setThreadRoot(msg) } : {},
|
|
3526
3926
|
...quotations ? { onQuote: () => setReplyingTo(msg) } : {},
|
|
@@ -3528,7 +3928,9 @@ function ConversationView({
|
|
|
3528
3928
|
...avatarFor ? { avatarFor } : {},
|
|
3529
3929
|
showSenderName: showSenderLabels,
|
|
3530
3930
|
showAvatar: showAvatars,
|
|
3531
|
-
maxBodyLength
|
|
3931
|
+
maxBodyLength,
|
|
3932
|
+
groupPosition,
|
|
3933
|
+
markdown
|
|
3532
3934
|
}
|
|
3533
3935
|
);
|
|
3534
3936
|
},
|
|
@@ -3543,16 +3945,38 @@ function ConversationView({
|
|
|
3543
3945
|
threads,
|
|
3544
3946
|
quotations,
|
|
3545
3947
|
editingId,
|
|
3546
|
-
edit,
|
|
3547
3948
|
deleteMsg,
|
|
3548
3949
|
labelFor,
|
|
3549
3950
|
avatarFor,
|
|
3550
3951
|
showSenderLabels,
|
|
3551
3952
|
showAvatars,
|
|
3552
3953
|
maxBodyLength,
|
|
3553
|
-
sequenceByMessageId
|
|
3954
|
+
sequenceByMessageId,
|
|
3955
|
+
groupPositionById,
|
|
3956
|
+
markdown
|
|
3554
3957
|
]
|
|
3555
3958
|
);
|
|
3959
|
+
const editingMessage = react.useMemo(
|
|
3960
|
+
() => editingId ? messages.find((m) => m.id === editingId) ?? null : null,
|
|
3961
|
+
[editingId, messages]
|
|
3962
|
+
);
|
|
3963
|
+
const listItems = react.useMemo(() => {
|
|
3964
|
+
const out = [];
|
|
3965
|
+
let prev = null;
|
|
3966
|
+
for (const msg of messages) {
|
|
3967
|
+
if (prev === null || !react$1.sameDay(msg.inserted_at, prev.inserted_at)) {
|
|
3968
|
+
const label = react$1.formatDayLabel(msg.inserted_at);
|
|
3969
|
+
out.push({
|
|
3970
|
+
kind: "separator",
|
|
3971
|
+
id: `sep-${msg.id}`,
|
|
3972
|
+
node: /* @__PURE__ */ jsxRuntime.jsx(DaySeparator, { label })
|
|
3973
|
+
});
|
|
3974
|
+
}
|
|
3975
|
+
out.push({ kind: "msg", msg });
|
|
3976
|
+
prev = msg;
|
|
3977
|
+
}
|
|
3978
|
+
return out;
|
|
3979
|
+
}, [messages]);
|
|
3556
3980
|
const composerProps = react.useMemo(
|
|
3557
3981
|
() => ({
|
|
3558
3982
|
onSend: async (body, opts) => {
|
|
@@ -3566,9 +3990,28 @@ function ConversationView({
|
|
|
3566
3990
|
onCancelReply: () => setReplyingTo(null),
|
|
3567
3991
|
...labelFor ? { labelFor } : {},
|
|
3568
3992
|
attachments,
|
|
3569
|
-
keyboardOffset
|
|
3993
|
+
keyboardOffset,
|
|
3994
|
+
editingMessage,
|
|
3995
|
+
onCancelEdit: () => setEditingId(null),
|
|
3996
|
+
onSaveEdit: async (body) => {
|
|
3997
|
+
if (!editingId) return;
|
|
3998
|
+
await edit(editingId, body);
|
|
3999
|
+
setEditingId(null);
|
|
4000
|
+
}
|
|
3570
4001
|
}),
|
|
3571
|
-
[
|
|
4002
|
+
[
|
|
4003
|
+
send,
|
|
4004
|
+
signalTyping,
|
|
4005
|
+
attachments,
|
|
4006
|
+
replyingTo,
|
|
4007
|
+
labelFor,
|
|
4008
|
+
keyboardOffset,
|
|
4009
|
+
onSent,
|
|
4010
|
+
conversationId,
|
|
4011
|
+
editingMessage,
|
|
4012
|
+
editingId,
|
|
4013
|
+
edit
|
|
4014
|
+
]
|
|
3572
4015
|
);
|
|
3573
4016
|
const headerNode = header === false ? null : header === true ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3574
4017
|
ChatHeader,
|
|
@@ -3588,18 +4031,30 @@ function ConversationView({
|
|
|
3588
4031
|
keyboardVerticalOffset: keyboardOffset,
|
|
3589
4032
|
children: [
|
|
3590
4033
|
headerNode,
|
|
3591
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
4034
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles20.listWrap, children: [
|
|
4035
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4036
|
+
MessageList,
|
|
4037
|
+
{
|
|
4038
|
+
messages,
|
|
4039
|
+
items: listItems,
|
|
4040
|
+
meId,
|
|
4041
|
+
loading,
|
|
4042
|
+
hasMore,
|
|
4043
|
+
onLoadMore: loadMore,
|
|
4044
|
+
renderItem,
|
|
4045
|
+
ListHeader: /* @__PURE__ */ jsxRuntime.jsx(TypingIndicator, { typing, ...labelFor ? { labelFor } : {} }),
|
|
4046
|
+
...emptyState !== void 0 ? { emptyState } : {}
|
|
4047
|
+
}
|
|
4048
|
+
),
|
|
4049
|
+
editingId ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4050
|
+
reactNative.Pressable,
|
|
4051
|
+
{
|
|
4052
|
+
style: styles20.editBackdrop,
|
|
4053
|
+
onPress: () => setEditingId(null),
|
|
4054
|
+
accessibilityLabel: "Cancel editing"
|
|
4055
|
+
}
|
|
4056
|
+
) : null
|
|
4057
|
+
] }),
|
|
3603
4058
|
mentions ? /* @__PURE__ */ jsxRuntime.jsx(MentionInput, { conversationId, ...composerProps }) : /* @__PURE__ */ jsxRuntime.jsx(MessageComposer, { ...composerProps }),
|
|
3604
4059
|
attachments ? /* @__PURE__ */ jsxRuntime.jsx(AttachmentPicker, { visible: pickerOpen, onClose: () => setPickerOpen(false) }) : null,
|
|
3605
4060
|
threads && threadRoot ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3635,6 +4090,14 @@ var styles20 = reactNative.StyleSheet.create({
|
|
|
3635
4090
|
},
|
|
3636
4091
|
listWrap: {
|
|
3637
4092
|
flex: 1
|
|
4093
|
+
},
|
|
4094
|
+
editBackdrop: {
|
|
4095
|
+
...reactNative.StyleSheet.absoluteFillObject,
|
|
4096
|
+
backgroundColor: "rgba(0,0,0,0.45)",
|
|
4097
|
+
// Sit above message rows by default but below any row that
|
|
4098
|
+
// explicitly elevates itself (MessageRow bumps zIndex/elevation
|
|
4099
|
+
// when isEditing is true).
|
|
4100
|
+
zIndex: 5
|
|
3638
4101
|
}
|
|
3639
4102
|
});
|
|
3640
4103
|
function UserPickerSheet({
|
|
@@ -4499,7 +4962,7 @@ exports.Avatar = Avatar;
|
|
|
4499
4962
|
exports.ChatHeader = ChatHeader;
|
|
4500
4963
|
exports.ConversationList = ConversationList;
|
|
4501
4964
|
exports.ConversationView = ConversationView;
|
|
4502
|
-
exports.
|
|
4965
|
+
exports.DaySeparator = DaySeparator;
|
|
4503
4966
|
exports.GroupDetailsSheet = GroupDetailsSheet;
|
|
4504
4967
|
exports.ImageMosaic = ImageMosaic;
|
|
4505
4968
|
exports.MemberList = MemberList;
|