@poolse/react-native 2.0.13 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +742 -375
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +90 -18
- package/dist/index.d.ts +90 -18
- package/dist/index.js +741 -378
- package/dist/index.js.map +1 -1
- package/package.json +6 -4
package/dist/index.cjs
CHANGED
|
@@ -8,6 +8,10 @@ var reactNativeSvg = require('react-native-svg');
|
|
|
8
8
|
var ImagePicker = require('expo-image-picker');
|
|
9
9
|
var DocumentPicker = require('expo-document-picker');
|
|
10
10
|
var expoImageManipulator = require('expo-image-manipulator');
|
|
11
|
+
var Clipboard = require('expo-clipboard');
|
|
12
|
+
var Markdown = require('react-native-markdown-display');
|
|
13
|
+
|
|
14
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
15
|
|
|
12
16
|
function _interopNamespace(e) {
|
|
13
17
|
if (e && e.__esModule) return e;
|
|
@@ -29,8 +33,15 @@ function _interopNamespace(e) {
|
|
|
29
33
|
|
|
30
34
|
var ImagePicker__namespace = /*#__PURE__*/_interopNamespace(ImagePicker);
|
|
31
35
|
var DocumentPicker__namespace = /*#__PURE__*/_interopNamespace(DocumentPicker);
|
|
36
|
+
var Clipboard__namespace = /*#__PURE__*/_interopNamespace(Clipboard);
|
|
37
|
+
var Markdown__default = /*#__PURE__*/_interopDefault(Markdown);
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
40
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
41
|
+
}) : x)(function(x) {
|
|
42
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
43
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
44
|
+
});
|
|
34
45
|
|
|
35
46
|
// src/theme/default.ts
|
|
36
47
|
var lightColors = {
|
|
@@ -311,6 +322,10 @@ var ICONS = {
|
|
|
311
322
|
vb: "0 0 24 24",
|
|
312
323
|
d: '<path d="M6 9.2a6 6 0 0 1 12 0c0 4.8 1.4 6.3 1.9 6.8H4.1C4.6 15.5 6 14 6 9.2Z"/><path d="M9.6 19.8a2.5 2.5 0 0 0 4.8 0"/>'
|
|
313
324
|
},
|
|
325
|
+
camera: {
|
|
326
|
+
vb: "0 0 24 24",
|
|
327
|
+
d: '<path d="M3 8.5a2 2 0 0 1 2-2h2.4l1.4-2h6.4l1.4 2H19a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-9Z"/><circle cx="12" cy="13" r="3.5"/>'
|
|
328
|
+
},
|
|
314
329
|
"check-double": {
|
|
315
330
|
vb: "0 0 24 24",
|
|
316
331
|
d: '<path d="M2.5 12.8 6.9 17 15.4 7.6"/><path d="M11.4 16.4 12.3 17.2 21 7.6"/>'
|
|
@@ -1130,7 +1145,12 @@ function useUploadPreview() {
|
|
|
1130
1145
|
getPreview: ctx?.getPreview ?? (() => null)
|
|
1131
1146
|
};
|
|
1132
1147
|
}
|
|
1133
|
-
function AttachmentPicker({
|
|
1148
|
+
function AttachmentPicker({
|
|
1149
|
+
visible,
|
|
1150
|
+
onClose,
|
|
1151
|
+
onPickInputs,
|
|
1152
|
+
camera = true
|
|
1153
|
+
}) {
|
|
1134
1154
|
const theme = usePoolseTheme();
|
|
1135
1155
|
const upload = useSharedUpload();
|
|
1136
1156
|
const { setPreview } = useUploadPreview();
|
|
@@ -1164,8 +1184,12 @@ function AttachmentPicker({ visible, onClose, onPickInputs }) {
|
|
|
1164
1184
|
theme.shadows.lg
|
|
1165
1185
|
],
|
|
1166
1186
|
children: [
|
|
1187
|
+
camera ? /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { style: styles6.item, onPress: () => pickFromKind("camera"), children: [
|
|
1188
|
+
/* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "camera", size: 20, color: theme.colors.ink2 }),
|
|
1189
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.itemText, { color: theme.colors.ink }], children: "Camera" })
|
|
1190
|
+
] }) : null,
|
|
1167
1191
|
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { style: styles6.item, onPress: () => pickFromKind("image"), children: [
|
|
1168
|
-
/* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "
|
|
1192
|
+
/* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "image", size: 20, color: theme.colors.ink2 }),
|
|
1169
1193
|
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles6.itemText, { color: theme.colors.ink }], children: "Photo or video" })
|
|
1170
1194
|
] }),
|
|
1171
1195
|
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { style: styles6.item, onPress: () => pickFromKind("document"), children: [
|
|
@@ -1224,7 +1248,52 @@ async function compressImage({
|
|
|
1224
1248
|
filename: `${base}.webp`
|
|
1225
1249
|
};
|
|
1226
1250
|
}
|
|
1251
|
+
async function assetsToInputs(assets) {
|
|
1252
|
+
return Promise.all(
|
|
1253
|
+
assets.map(async (asset) => {
|
|
1254
|
+
try {
|
|
1255
|
+
const compressed = await compressImage({
|
|
1256
|
+
uri: asset.uri,
|
|
1257
|
+
width: asset.width,
|
|
1258
|
+
height: asset.height,
|
|
1259
|
+
filename: asset.fileName ?? `image-${Date.now()}`
|
|
1260
|
+
});
|
|
1261
|
+
return {
|
|
1262
|
+
body: compressed.blob,
|
|
1263
|
+
contentType: compressed.contentType,
|
|
1264
|
+
byteSize: compressed.blob.size,
|
|
1265
|
+
filename: compressed.filename,
|
|
1266
|
+
_previewUri: asset.uri
|
|
1267
|
+
};
|
|
1268
|
+
} catch (err) {
|
|
1269
|
+
console.warn("[poolse/picker] image compression failed, sending original:", err);
|
|
1270
|
+
const blob = await uriToBlob(asset.uri);
|
|
1271
|
+
const contentType = asset.mimeType ?? blob.type ?? "image/jpeg";
|
|
1272
|
+
return {
|
|
1273
|
+
body: blob,
|
|
1274
|
+
contentType,
|
|
1275
|
+
byteSize: asset.fileSize ?? blob.size,
|
|
1276
|
+
filename: asset.fileName ?? `image-${Date.now()}.jpg`,
|
|
1277
|
+
_previewUri: asset.uri
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
})
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1227
1283
|
async function defaultAdapter(kind) {
|
|
1284
|
+
if (kind === "camera") {
|
|
1285
|
+
const perm = await ImagePicker__namespace.requestCameraPermissionsAsync();
|
|
1286
|
+
if (!perm.granted) {
|
|
1287
|
+
console.warn("[poolse/picker] camera permission denied");
|
|
1288
|
+
return [];
|
|
1289
|
+
}
|
|
1290
|
+
const res2 = await ImagePicker__namespace.launchCameraAsync({
|
|
1291
|
+
mediaTypes: "images",
|
|
1292
|
+
quality: 1
|
|
1293
|
+
});
|
|
1294
|
+
if (res2.canceled) return [];
|
|
1295
|
+
return assetsToInputs(res2.assets);
|
|
1296
|
+
}
|
|
1228
1297
|
if (kind === "image") {
|
|
1229
1298
|
const res2 = await ImagePicker__namespace.launchImageLibraryAsync({
|
|
1230
1299
|
mediaTypes: "images",
|
|
@@ -1234,36 +1303,7 @@ async function defaultAdapter(kind) {
|
|
|
1234
1303
|
quality: 1
|
|
1235
1304
|
});
|
|
1236
1305
|
if (res2.canceled) return [];
|
|
1237
|
-
return
|
|
1238
|
-
res2.assets.map(async (asset) => {
|
|
1239
|
-
try {
|
|
1240
|
-
const compressed = await compressImage({
|
|
1241
|
-
uri: asset.uri,
|
|
1242
|
-
width: asset.width,
|
|
1243
|
-
height: asset.height,
|
|
1244
|
-
filename: asset.fileName ?? `image-${Date.now()}`
|
|
1245
|
-
});
|
|
1246
|
-
return {
|
|
1247
|
-
body: compressed.blob,
|
|
1248
|
-
contentType: compressed.contentType,
|
|
1249
|
-
byteSize: compressed.blob.size,
|
|
1250
|
-
filename: compressed.filename,
|
|
1251
|
-
_previewUri: asset.uri
|
|
1252
|
-
};
|
|
1253
|
-
} catch (err) {
|
|
1254
|
-
console.warn("[poolse/picker] image compression failed, sending original:", err);
|
|
1255
|
-
const blob = await uriToBlob(asset.uri);
|
|
1256
|
-
const contentType = asset.mimeType ?? blob.type ?? "image/jpeg";
|
|
1257
|
-
return {
|
|
1258
|
-
body: blob,
|
|
1259
|
-
contentType,
|
|
1260
|
-
byteSize: asset.fileSize ?? blob.size,
|
|
1261
|
-
filename: asset.fileName ?? `image-${Date.now()}.jpg`,
|
|
1262
|
-
_previewUri: asset.uri
|
|
1263
|
-
};
|
|
1264
|
-
}
|
|
1265
|
-
})
|
|
1266
|
-
);
|
|
1306
|
+
return assetsToInputs(res2.assets);
|
|
1267
1307
|
}
|
|
1268
1308
|
const res = await DocumentPicker__namespace.getDocumentAsync({
|
|
1269
1309
|
copyToCacheDirectory: true,
|
|
@@ -1330,6 +1370,51 @@ var styles6 = reactNative.StyleSheet.create({
|
|
|
1330
1370
|
fontWeight: "500"
|
|
1331
1371
|
}
|
|
1332
1372
|
});
|
|
1373
|
+
function DaySeparator({ label }) {
|
|
1374
|
+
const theme = usePoolseTheme();
|
|
1375
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles7.wrap, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1376
|
+
reactNative.View,
|
|
1377
|
+
{
|
|
1378
|
+
style: [
|
|
1379
|
+
styles7.chip,
|
|
1380
|
+
{
|
|
1381
|
+
backgroundColor: theme.colors.surface2,
|
|
1382
|
+
borderColor: theme.colors.border
|
|
1383
|
+
}
|
|
1384
|
+
],
|
|
1385
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1386
|
+
reactNative.Text,
|
|
1387
|
+
{
|
|
1388
|
+
style: [
|
|
1389
|
+
styles7.label,
|
|
1390
|
+
{
|
|
1391
|
+
color: theme.colors.ink2,
|
|
1392
|
+
fontFamily: theme.type.fontBody
|
|
1393
|
+
}
|
|
1394
|
+
],
|
|
1395
|
+
children: label
|
|
1396
|
+
}
|
|
1397
|
+
)
|
|
1398
|
+
}
|
|
1399
|
+
) });
|
|
1400
|
+
}
|
|
1401
|
+
var styles7 = reactNative.StyleSheet.create({
|
|
1402
|
+
wrap: {
|
|
1403
|
+
alignItems: "center",
|
|
1404
|
+
marginVertical: 8
|
|
1405
|
+
},
|
|
1406
|
+
chip: {
|
|
1407
|
+
paddingHorizontal: 12,
|
|
1408
|
+
paddingVertical: 4,
|
|
1409
|
+
borderRadius: 999,
|
|
1410
|
+
borderWidth: reactNative.StyleSheet.hairlineWidth
|
|
1411
|
+
},
|
|
1412
|
+
label: {
|
|
1413
|
+
fontSize: 11,
|
|
1414
|
+
fontWeight: "600",
|
|
1415
|
+
letterSpacing: 0.3
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1333
1418
|
function UploadQueueStrip() {
|
|
1334
1419
|
const theme = usePoolseTheme();
|
|
1335
1420
|
const upload = useSharedUpload();
|
|
@@ -1340,8 +1425,8 @@ function UploadQueueStrip() {
|
|
|
1340
1425
|
{
|
|
1341
1426
|
horizontal: true,
|
|
1342
1427
|
showsHorizontalScrollIndicator: false,
|
|
1343
|
-
contentContainerStyle:
|
|
1344
|
-
style: [
|
|
1428
|
+
contentContainerStyle: styles8.scroll,
|
|
1429
|
+
style: [styles8.scrollStyle, { marginBottom: 8 }],
|
|
1345
1430
|
children: upload.queue.map((item) => {
|
|
1346
1431
|
const isError = item.status === "error";
|
|
1347
1432
|
const isUploading = item.status === "uploading" || item.status === "pending";
|
|
@@ -1352,7 +1437,7 @@ function UploadQueueStrip() {
|
|
|
1352
1437
|
if (item.status === "ready" || isError) upload.remove(item.localId);
|
|
1353
1438
|
else upload.cancel(item.localId);
|
|
1354
1439
|
};
|
|
1355
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style:
|
|
1440
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles8.tileWrap, children: [
|
|
1356
1441
|
isImage && previewUri ? /* @__PURE__ */ jsxRuntime.jsx(ImageTile, { uri: previewUri, uploading: isUploading, error: isError }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1357
1442
|
FileTile,
|
|
1358
1443
|
{
|
|
@@ -1367,13 +1452,13 @@ function UploadQueueStrip() {
|
|
|
1367
1452
|
{
|
|
1368
1453
|
onPress: handleClose,
|
|
1369
1454
|
hitSlop: 8,
|
|
1370
|
-
style: [
|
|
1455
|
+
style: [styles8.closeBtn, { backgroundColor: theme.colors.ink }],
|
|
1371
1456
|
accessibilityLabel: isError ? "Remove failed upload" : "Cancel upload",
|
|
1372
1457
|
children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "close", size: 11, color: theme.colors.onBrand })
|
|
1373
1458
|
}
|
|
1374
1459
|
) : null,
|
|
1375
|
-
isUploading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style:
|
|
1376
|
-
isError ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
1460
|
+
isUploading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles8.progressOverlay, children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles8.spinner, { borderColor: theme.colors.onBrand }] }) }) : null,
|
|
1461
|
+
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
|
|
1377
1462
|
] }, item.localId);
|
|
1378
1463
|
})
|
|
1379
1464
|
}
|
|
@@ -1386,7 +1471,7 @@ function ImageTile({ uri, uploading, error }) {
|
|
|
1386
1471
|
{
|
|
1387
1472
|
source: { uri },
|
|
1388
1473
|
style: [
|
|
1389
|
-
|
|
1474
|
+
styles8.imageTile,
|
|
1390
1475
|
{
|
|
1391
1476
|
backgroundColor: theme.colors.surface2,
|
|
1392
1477
|
borderColor: error ? theme.colors.error : "transparent",
|
|
@@ -1408,24 +1493,24 @@ function FileTile({
|
|
|
1408
1493
|
reactNative.View,
|
|
1409
1494
|
{
|
|
1410
1495
|
style: [
|
|
1411
|
-
|
|
1496
|
+
styles8.fileTile,
|
|
1412
1497
|
{
|
|
1413
1498
|
backgroundColor: theme.colors.surface2,
|
|
1414
1499
|
borderColor: error ? theme.colors.error : uploading ? theme.colors.brand : theme.colors.border
|
|
1415
1500
|
}
|
|
1416
1501
|
],
|
|
1417
1502
|
children: [
|
|
1418
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
1419
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style:
|
|
1503
|
+
/* @__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 }) }),
|
|
1504
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles8.fileInfo, children: [
|
|
1420
1505
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1421
1506
|
reactNative.Text,
|
|
1422
1507
|
{
|
|
1423
|
-
style: [
|
|
1508
|
+
style: [styles8.fileName, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
|
|
1424
1509
|
numberOfLines: 1,
|
|
1425
1510
|
children: filename
|
|
1426
1511
|
}
|
|
1427
1512
|
),
|
|
1428
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
1513
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles8.fileSize, { color: theme.colors.ink3 }], children: formatBytes(size) })
|
|
1429
1514
|
] })
|
|
1430
1515
|
]
|
|
1431
1516
|
}
|
|
@@ -1439,7 +1524,7 @@ function formatBytes(n) {
|
|
|
1439
1524
|
return `${(n / 1024 / 1024 / 1024).toFixed(1)} GB`;
|
|
1440
1525
|
}
|
|
1441
1526
|
var TILE = 64;
|
|
1442
|
-
var
|
|
1527
|
+
var styles8 = reactNative.StyleSheet.create({
|
|
1443
1528
|
scrollStyle: {
|
|
1444
1529
|
overflow: "visible"
|
|
1445
1530
|
},
|
|
@@ -1539,6 +1624,22 @@ var styles7 = reactNative.StyleSheet.create({
|
|
|
1539
1624
|
fontWeight: "700"
|
|
1540
1625
|
}
|
|
1541
1626
|
});
|
|
1627
|
+
var FALLBACK = {
|
|
1628
|
+
top: reactNative.Platform.OS === "ios" ? 50 : 24,
|
|
1629
|
+
bottom: reactNative.Platform.OS === "ios" ? 34 : 0,
|
|
1630
|
+
left: 0,
|
|
1631
|
+
right: 0
|
|
1632
|
+
};
|
|
1633
|
+
var realHook = null;
|
|
1634
|
+
try {
|
|
1635
|
+
const mod = __require("react-native-safe-area-context");
|
|
1636
|
+
if (mod && typeof mod.useSafeAreaInsets === "function") {
|
|
1637
|
+
realHook = mod.useSafeAreaInsets;
|
|
1638
|
+
}
|
|
1639
|
+
} catch {
|
|
1640
|
+
}
|
|
1641
|
+
var useFallback = () => FALLBACK;
|
|
1642
|
+
var useSafeInsets = realHook ?? useFallback;
|
|
1542
1643
|
var MessageComposer = react.forwardRef(
|
|
1543
1644
|
function MessageComposer2({
|
|
1544
1645
|
onSend,
|
|
@@ -1550,13 +1651,32 @@ var MessageComposer = react.forwardRef(
|
|
|
1550
1651
|
labelFor,
|
|
1551
1652
|
attachments = true,
|
|
1552
1653
|
onAttachPress,
|
|
1553
|
-
keyboardOffset: _keyboardOffset = 0
|
|
1654
|
+
keyboardOffset: _keyboardOffset = 0,
|
|
1655
|
+
editingMessage = null,
|
|
1656
|
+
onCancelEdit,
|
|
1657
|
+
onSaveEdit
|
|
1554
1658
|
}, ref) {
|
|
1555
1659
|
const theme = usePoolseTheme();
|
|
1556
1660
|
const inputRef = react.useRef(null);
|
|
1557
1661
|
const [value, setValue] = react.useState("");
|
|
1558
1662
|
const [sending, setSending] = react.useState(false);
|
|
1663
|
+
const [keyboardOpen, setKeyboardOpen] = react.useState(false);
|
|
1559
1664
|
const upload = useSharedUpload();
|
|
1665
|
+
const insets = useSafeInsets();
|
|
1666
|
+
react.useEffect(() => {
|
|
1667
|
+
const show = reactNative.Keyboard.addListener(
|
|
1668
|
+
reactNative.Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow",
|
|
1669
|
+
() => setKeyboardOpen(true)
|
|
1670
|
+
);
|
|
1671
|
+
const hide = reactNative.Keyboard.addListener(
|
|
1672
|
+
reactNative.Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide",
|
|
1673
|
+
() => setKeyboardOpen(false)
|
|
1674
|
+
);
|
|
1675
|
+
return () => {
|
|
1676
|
+
show.remove();
|
|
1677
|
+
hide.remove();
|
|
1678
|
+
};
|
|
1679
|
+
}, []);
|
|
1560
1680
|
react.useImperativeHandle(
|
|
1561
1681
|
ref,
|
|
1562
1682
|
() => ({
|
|
@@ -1575,36 +1695,65 @@ var MessageComposer = react.forwardRef(
|
|
|
1575
1695
|
const isUploading = upload.queue.some(
|
|
1576
1696
|
(item) => item.status === "uploading" || item.status === "pending"
|
|
1577
1697
|
);
|
|
1578
|
-
const
|
|
1698
|
+
const previousDraftRef = react.useRef(null);
|
|
1699
|
+
const editingIdRef = react.useRef(null);
|
|
1700
|
+
react.useEffect(() => {
|
|
1701
|
+
const id = editingMessage?.id ?? null;
|
|
1702
|
+
if (id === editingIdRef.current) return;
|
|
1703
|
+
if (id) {
|
|
1704
|
+
previousDraftRef.current = value;
|
|
1705
|
+
setValue(editingMessage?.body ?? "");
|
|
1706
|
+
inputRef.current?.focus();
|
|
1707
|
+
} else {
|
|
1708
|
+
setValue(previousDraftRef.current ?? "");
|
|
1709
|
+
previousDraftRef.current = null;
|
|
1710
|
+
}
|
|
1711
|
+
editingIdRef.current = id;
|
|
1712
|
+
}, [editingMessage?.id, editingMessage?.body]);
|
|
1713
|
+
const isEditing = !!editingMessage;
|
|
1714
|
+
const editChanged = isEditing && value.trim() !== (editingMessage?.body?.trim() ?? "");
|
|
1715
|
+
const submitDisabled = disabled || sending || isUploading || value.trim().length === 0 || (isEditing ? !editChanged : value.trim().length === 0 && readyAttachmentIds.length === 0);
|
|
1579
1716
|
const handleSend = async () => {
|
|
1580
1717
|
if (submitDisabled) return;
|
|
1581
1718
|
const body = value.trim();
|
|
1582
|
-
const replyId = replyingTo?.id;
|
|
1583
|
-
const attachmentIds = readyAttachmentIds;
|
|
1584
|
-
setValue("");
|
|
1585
|
-
upload.reset();
|
|
1586
1719
|
setSending(true);
|
|
1587
1720
|
try {
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1721
|
+
if (isEditing && onSaveEdit) {
|
|
1722
|
+
await onSaveEdit(body);
|
|
1723
|
+
} else {
|
|
1724
|
+
const replyId = replyingTo?.id;
|
|
1725
|
+
const attachmentIds = readyAttachmentIds;
|
|
1726
|
+
setValue("");
|
|
1727
|
+
upload.reset();
|
|
1728
|
+
await onSend(body, {
|
|
1729
|
+
...replyId ? { quoted_message_id: replyId } : {},
|
|
1730
|
+
...attachmentIds.length > 0 ? { attachment_ids: attachmentIds } : {}
|
|
1731
|
+
});
|
|
1732
|
+
}
|
|
1592
1733
|
} finally {
|
|
1593
1734
|
setSending(false);
|
|
1594
1735
|
}
|
|
1595
1736
|
};
|
|
1737
|
+
const paddingBottom = keyboardOpen ? 10 : Math.max(10, insets.bottom);
|
|
1596
1738
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1597
1739
|
reactNative.View,
|
|
1598
1740
|
{
|
|
1599
1741
|
style: [
|
|
1600
|
-
|
|
1742
|
+
styles9.wrap,
|
|
1601
1743
|
{
|
|
1602
1744
|
backgroundColor: theme.colors.surface,
|
|
1603
|
-
borderTopColor: theme.colors.border
|
|
1745
|
+
borderTopColor: theme.colors.border,
|
|
1746
|
+
paddingBottom
|
|
1604
1747
|
}
|
|
1605
1748
|
],
|
|
1606
1749
|
children: [
|
|
1607
|
-
|
|
1750
|
+
isEditing ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1751
|
+
EditChip,
|
|
1752
|
+
{
|
|
1753
|
+
message: editingMessage,
|
|
1754
|
+
...onCancelEdit ? { onCancel: onCancelEdit } : {}
|
|
1755
|
+
}
|
|
1756
|
+
) : replyingTo ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1608
1757
|
ReplyChip,
|
|
1609
1758
|
{
|
|
1610
1759
|
message: replyingTo,
|
|
@@ -1612,14 +1761,14 @@ var MessageComposer = react.forwardRef(
|
|
|
1612
1761
|
...onCancelReply ? { onCancel: onCancelReply } : {}
|
|
1613
1762
|
}
|
|
1614
1763
|
) : null,
|
|
1615
|
-
attachments ? /* @__PURE__ */ jsxRuntime.jsx(UploadQueueStrip, {}) : null,
|
|
1616
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style:
|
|
1764
|
+
attachments && !isEditing ? /* @__PURE__ */ jsxRuntime.jsx(UploadQueueStrip, {}) : null,
|
|
1765
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles9.row, children: [
|
|
1617
1766
|
attachments && onAttachPress ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1618
1767
|
reactNative.Pressable,
|
|
1619
1768
|
{
|
|
1620
1769
|
onPress: onAttachPress,
|
|
1621
1770
|
hitSlop: 8,
|
|
1622
|
-
style:
|
|
1771
|
+
style: styles9.iconBtn,
|
|
1623
1772
|
accessibilityLabel: "Attach file",
|
|
1624
1773
|
children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "attachment", size: 22, color: theme.colors.ink2 })
|
|
1625
1774
|
}
|
|
@@ -1638,7 +1787,7 @@ var MessageComposer = react.forwardRef(
|
|
|
1638
1787
|
multiline: true,
|
|
1639
1788
|
editable: !disabled,
|
|
1640
1789
|
style: [
|
|
1641
|
-
|
|
1790
|
+
styles9.input,
|
|
1642
1791
|
{
|
|
1643
1792
|
color: theme.colors.ink,
|
|
1644
1793
|
backgroundColor: theme.colors.surface2,
|
|
@@ -1655,7 +1804,7 @@ var MessageComposer = react.forwardRef(
|
|
|
1655
1804
|
onPress: handleSend,
|
|
1656
1805
|
disabled: submitDisabled,
|
|
1657
1806
|
style: [
|
|
1658
|
-
|
|
1807
|
+
styles9.sendBtn,
|
|
1659
1808
|
{
|
|
1660
1809
|
backgroundColor: submitDisabled ? theme.colors.ink3 : theme.colors.brand,
|
|
1661
1810
|
opacity: submitDisabled ? 0.6 : 1
|
|
@@ -1671,6 +1820,28 @@ var MessageComposer = react.forwardRef(
|
|
|
1671
1820
|
);
|
|
1672
1821
|
}
|
|
1673
1822
|
);
|
|
1823
|
+
function EditChip({ message, onCancel }) {
|
|
1824
|
+
const theme = usePoolseTheme();
|
|
1825
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1826
|
+
reactNative.View,
|
|
1827
|
+
{
|
|
1828
|
+
style: [
|
|
1829
|
+
styles9.replyChip,
|
|
1830
|
+
{
|
|
1831
|
+
backgroundColor: theme.colors.surface2,
|
|
1832
|
+
borderLeftColor: theme.colors.brand
|
|
1833
|
+
}
|
|
1834
|
+
],
|
|
1835
|
+
children: [
|
|
1836
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1 }, children: [
|
|
1837
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.replyName, { color: theme.colors.brand }], children: "Editing message" }),
|
|
1838
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
|
|
1839
|
+
] }),
|
|
1840
|
+
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
|
|
1841
|
+
]
|
|
1842
|
+
}
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1674
1845
|
function ReplyChip({
|
|
1675
1846
|
message,
|
|
1676
1847
|
labelFor,
|
|
@@ -1683,7 +1854,7 @@ function ReplyChip({
|
|
|
1683
1854
|
reactNative.View,
|
|
1684
1855
|
{
|
|
1685
1856
|
style: [
|
|
1686
|
-
|
|
1857
|
+
styles9.replyChip,
|
|
1687
1858
|
{
|
|
1688
1859
|
backgroundColor: theme.colors.surface2,
|
|
1689
1860
|
borderLeftColor: theme.colors.brand
|
|
@@ -1691,27 +1862,26 @@ function ReplyChip({
|
|
|
1691
1862
|
],
|
|
1692
1863
|
children: [
|
|
1693
1864
|
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: { flex: 1 }, children: [
|
|
1694
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [
|
|
1865
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [styles9.replyName, { color: theme.colors.brand }], children: [
|
|
1695
1866
|
"Replying to ",
|
|
1696
1867
|
senderName
|
|
1697
1868
|
] }),
|
|
1698
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
1869
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles9.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
|
|
1699
1870
|
] }),
|
|
1700
|
-
onCancel ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: onCancel, hitSlop: 8, style:
|
|
1871
|
+
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
|
|
1701
1872
|
]
|
|
1702
1873
|
}
|
|
1703
1874
|
);
|
|
1704
1875
|
}
|
|
1705
|
-
var
|
|
1876
|
+
var styles9 = reactNative.StyleSheet.create({
|
|
1706
1877
|
wrap: {
|
|
1707
1878
|
borderTopWidth: reactNative.StyleSheet.hairlineWidth,
|
|
1708
1879
|
paddingTop: 8,
|
|
1709
|
-
paddingBottom: 10,
|
|
1710
1880
|
paddingHorizontal: 12
|
|
1711
|
-
//
|
|
1712
|
-
//
|
|
1713
|
-
//
|
|
1714
|
-
//
|
|
1881
|
+
// paddingBottom is applied inline — it follows the keyboard state
|
|
1882
|
+
// (10 px while open, max(10, safeBottomInset) while closed) so the
|
|
1883
|
+
// send/attach buttons stay clear of the home indicator without
|
|
1884
|
+
// double-padding above the keyboard.
|
|
1715
1885
|
},
|
|
1716
1886
|
row: {
|
|
1717
1887
|
flexDirection: "row",
|
|
@@ -1800,11 +1970,11 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
|
|
|
1800
1970
|
transparent: true,
|
|
1801
1971
|
animationType: "slide",
|
|
1802
1972
|
onRequestClose: () => setPickerOpen(false),
|
|
1803
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { style:
|
|
1973
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { style: styles10.backdrop, onPress: () => setPickerOpen(false), children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1804
1974
|
reactNative.View,
|
|
1805
1975
|
{
|
|
1806
1976
|
style: [
|
|
1807
|
-
|
|
1977
|
+
styles10.sheet,
|
|
1808
1978
|
{
|
|
1809
1979
|
backgroundColor: theme.colors.surface,
|
|
1810
1980
|
borderTopLeftRadius: theme.radii.lg,
|
|
@@ -1815,7 +1985,7 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
|
|
|
1815
1985
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1816
1986
|
reactNative.Text,
|
|
1817
1987
|
{
|
|
1818
|
-
style: [
|
|
1988
|
+
style: [styles10.title, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
|
|
1819
1989
|
children: "Mention a member"
|
|
1820
1990
|
}
|
|
1821
1991
|
),
|
|
@@ -1846,12 +2016,12 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
|
|
|
1846
2016
|
}
|
|
1847
2017
|
function MentionRow({ member, onPress }) {
|
|
1848
2018
|
const theme = usePoolseTheme();
|
|
1849
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { onPress, style:
|
|
2019
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.Pressable, { onPress, style: styles10.row, children: [
|
|
1850
2020
|
/* @__PURE__ */ jsxRuntime.jsx(Avatar, { src: null, name: member.external_id, seed: member.external_id, size: "sm" }),
|
|
1851
|
-
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [
|
|
2021
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles10.rowText, { color: theme.colors.ink, fontFamily: theme.type.fontBody }], children: member.external_id })
|
|
1852
2022
|
] });
|
|
1853
2023
|
}
|
|
1854
|
-
var
|
|
2024
|
+
var styles10 = reactNative.StyleSheet.create({
|
|
1855
2025
|
backdrop: {
|
|
1856
2026
|
flex: 1,
|
|
1857
2027
|
backgroundColor: "rgba(0,0,0,0.35)",
|
|
@@ -1878,138 +2048,134 @@ var styles9 = reactNative.StyleSheet.create({
|
|
|
1878
2048
|
fontSize: 14
|
|
1879
2049
|
}
|
|
1880
2050
|
});
|
|
2051
|
+
var AT_BOTTOM_THRESHOLD = 32;
|
|
1881
2052
|
function MessageList({
|
|
1882
2053
|
messages,
|
|
2054
|
+
items,
|
|
1883
2055
|
loading = false,
|
|
1884
2056
|
hasMore = false,
|
|
1885
2057
|
onLoadMore,
|
|
1886
2058
|
renderItem,
|
|
1887
2059
|
ListHeader = null,
|
|
1888
2060
|
ListFooter = null,
|
|
1889
|
-
emptyState
|
|
2061
|
+
emptyState,
|
|
2062
|
+
meId = null
|
|
1890
2063
|
}) {
|
|
1891
2064
|
const theme = usePoolseTheme();
|
|
1892
|
-
const
|
|
2065
|
+
const flatListRef = react.useRef(null);
|
|
2066
|
+
const [atBottom, setAtBottom] = react.useState(true);
|
|
2067
|
+
const [newCount, setNewCount] = react.useState(0);
|
|
2068
|
+
const lastTailIdRef = react.useRef(null);
|
|
2069
|
+
const sourceItems = items ?? messages.map((msg) => ({ kind: "msg", msg }));
|
|
2070
|
+
const data = [...sourceItems].reverse();
|
|
2071
|
+
react.useEffect(() => {
|
|
2072
|
+
const tail = messages[messages.length - 1];
|
|
2073
|
+
if (!tail) return;
|
|
2074
|
+
const tailId = tail.id;
|
|
2075
|
+
if (tailId === lastTailIdRef.current) return;
|
|
2076
|
+
const isFirstSet = lastTailIdRef.current === null;
|
|
2077
|
+
lastTailIdRef.current = tailId;
|
|
2078
|
+
if (atBottom || isFirstSet) {
|
|
2079
|
+
setNewCount(0);
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
if (meId !== null && tail.sender_id === meId) return;
|
|
2083
|
+
setNewCount((n) => n + 1);
|
|
2084
|
+
}, [messages, atBottom, meId]);
|
|
2085
|
+
const handleScroll = react.useCallback((e) => {
|
|
2086
|
+
const y = e.nativeEvent.contentOffset.y;
|
|
2087
|
+
const next = y <= AT_BOTTOM_THRESHOLD;
|
|
2088
|
+
setAtBottom((prev) => prev === next ? prev : next);
|
|
2089
|
+
if (next) setNewCount(0);
|
|
2090
|
+
}, []);
|
|
2091
|
+
const scrollToBottom = react.useCallback(() => {
|
|
2092
|
+
flatListRef.current?.scrollToOffset({ offset: 0, animated: true });
|
|
2093
|
+
setNewCount(0);
|
|
2094
|
+
}, []);
|
|
1893
2095
|
const handleRender = react.useCallback(
|
|
1894
|
-
({ item, index }) =>
|
|
1895
|
-
|
|
2096
|
+
({ item, index }) => {
|
|
2097
|
+
if (item.kind === "separator") return item.node;
|
|
2098
|
+
const sourceIndex = data.length - 1 - index;
|
|
2099
|
+
return renderItem(item.msg, sourceIndex);
|
|
2100
|
+
},
|
|
2101
|
+
[renderItem, data.length]
|
|
1896
2102
|
);
|
|
1897
2103
|
if (loading && messages.length === 0) {
|
|
1898
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
2104
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles11.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: theme.colors.brand }) });
|
|
1899
2105
|
}
|
|
1900
2106
|
if (messages.length === 0) {
|
|
1901
|
-
return /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [
|
|
2107
|
+
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 });
|
|
1902
2108
|
}
|
|
1903
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
2109
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles11.root, children: [
|
|
2110
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2111
|
+
reactNative.FlatList,
|
|
2112
|
+
{
|
|
2113
|
+
ref: flatListRef,
|
|
2114
|
+
inverted: true,
|
|
2115
|
+
data,
|
|
2116
|
+
keyExtractor: (item) => item.kind === "msg" ? item.msg.id : item.id,
|
|
2117
|
+
renderItem: handleRender,
|
|
2118
|
+
onEndReached: hasMore ? onLoadMore : void 0,
|
|
2119
|
+
onEndReachedThreshold: 0.3,
|
|
2120
|
+
onScroll: handleScroll,
|
|
2121
|
+
scrollEventThrottle: 32,
|
|
2122
|
+
ListHeaderComponent: ListHeader,
|
|
2123
|
+
ListFooterComponent: /* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { children: [
|
|
2124
|
+
hasMore && loading ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.ActivityIndicator, { color: theme.colors.ink3, style: { marginVertical: 8 } }) : null,
|
|
2125
|
+
ListFooter
|
|
2126
|
+
] }),
|
|
2127
|
+
style: { backgroundColor: theme.colors.paper },
|
|
2128
|
+
contentContainerStyle: { paddingVertical: 8 }
|
|
2129
|
+
}
|
|
2130
|
+
),
|
|
2131
|
+
!atBottom && newCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2132
|
+
reactNative.Pressable,
|
|
2133
|
+
{
|
|
2134
|
+
onPress: scrollToBottom,
|
|
2135
|
+
style: [
|
|
2136
|
+
styles11.jumpButton,
|
|
2137
|
+
{
|
|
2138
|
+
backgroundColor: theme.colors.brand
|
|
2139
|
+
},
|
|
2140
|
+
theme.shadows.sm
|
|
2141
|
+
],
|
|
2142
|
+
accessibilityLabel: `${newCount} new ${newCount === 1 ? "message" : "messages"}, jump to latest`,
|
|
2143
|
+
children: [
|
|
2144
|
+
/* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "chevron-down", size: 14, color: theme.colors.onBrand }),
|
|
2145
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.Text, { style: [styles11.jumpText, { color: theme.colors.onBrand }], children: [
|
|
2146
|
+
newCount,
|
|
2147
|
+
" new ",
|
|
2148
|
+
newCount === 1 ? "message" : "messages"
|
|
2149
|
+
] })
|
|
2150
|
+
]
|
|
2151
|
+
}
|
|
2152
|
+
) : null
|
|
2153
|
+
] });
|
|
1921
2154
|
}
|
|
1922
|
-
var
|
|
2155
|
+
var styles11 = reactNative.StyleSheet.create({
|
|
2156
|
+
root: {
|
|
2157
|
+
flex: 1
|
|
2158
|
+
},
|
|
1923
2159
|
placeholder: {
|
|
1924
2160
|
flex: 1,
|
|
1925
2161
|
alignItems: "center",
|
|
1926
2162
|
justifyContent: "center",
|
|
1927
2163
|
padding: 24
|
|
1928
|
-
}
|
|
1929
|
-
});
|
|
1930
|
-
function EditableMessageBubble({
|
|
1931
|
-
message,
|
|
1932
|
-
currentUserId,
|
|
1933
|
-
onCancel,
|
|
1934
|
-
onSave
|
|
1935
|
-
}) {
|
|
1936
|
-
const theme = usePoolseTheme();
|
|
1937
|
-
const isSelf = currentUserId !== null && message.sender_id === currentUserId;
|
|
1938
|
-
const [value, setValue] = react.useState(message.body ?? "");
|
|
1939
|
-
const [saving, setSaving] = react.useState(false);
|
|
1940
|
-
const submit = async () => {
|
|
1941
|
-
if (saving || !value.trim()) return;
|
|
1942
|
-
setSaving(true);
|
|
1943
|
-
try {
|
|
1944
|
-
await onSave(value.trim());
|
|
1945
|
-
} finally {
|
|
1946
|
-
setSaving(false);
|
|
1947
|
-
}
|
|
1948
|
-
};
|
|
1949
|
-
const bg = isSelf ? theme.colors.selfBubble : theme.colors.otherBubble;
|
|
1950
|
-
const ink = isSelf ? theme.colors.selfBubbleText : theme.colors.otherBubbleText;
|
|
1951
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1952
|
-
reactNative.View,
|
|
1953
|
-
{
|
|
1954
|
-
style: [
|
|
1955
|
-
styles11.wrap,
|
|
1956
|
-
{
|
|
1957
|
-
alignSelf: isSelf ? "flex-end" : "flex-start",
|
|
1958
|
-
maxWidth: "80%"
|
|
1959
|
-
}
|
|
1960
|
-
],
|
|
1961
|
-
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1962
|
-
reactNative.View,
|
|
1963
|
-
{
|
|
1964
|
-
style: [
|
|
1965
|
-
styles11.bubble,
|
|
1966
|
-
{
|
|
1967
|
-
backgroundColor: bg,
|
|
1968
|
-
borderRadius: theme.radii.bubble,
|
|
1969
|
-
paddingHorizontal: theme.spacing.md,
|
|
1970
|
-
paddingVertical: theme.spacing.sm
|
|
1971
|
-
},
|
|
1972
|
-
theme.shadows.sm
|
|
1973
|
-
],
|
|
1974
|
-
children: [
|
|
1975
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1976
|
-
reactNative.TextInput,
|
|
1977
|
-
{
|
|
1978
|
-
value,
|
|
1979
|
-
onChangeText: setValue,
|
|
1980
|
-
multiline: true,
|
|
1981
|
-
autoFocus: true,
|
|
1982
|
-
style: {
|
|
1983
|
-
color: ink,
|
|
1984
|
-
fontFamily: theme.type.fontBody,
|
|
1985
|
-
fontSize: theme.type.bodySize,
|
|
1986
|
-
minHeight: 24
|
|
1987
|
-
}
|
|
1988
|
-
}
|
|
1989
|
-
),
|
|
1990
|
-
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles11.actions, children: [
|
|
1991
|
-
/* @__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" }) }),
|
|
1992
|
-
/* @__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" }) })
|
|
1993
|
-
] })
|
|
1994
|
-
]
|
|
1995
|
-
}
|
|
1996
|
-
)
|
|
1997
|
-
}
|
|
1998
|
-
);
|
|
1999
|
-
}
|
|
2000
|
-
var styles11 = reactNative.StyleSheet.create({
|
|
2001
|
-
wrap: {
|
|
2002
|
-
marginBottom: 4
|
|
2003
2164
|
},
|
|
2004
|
-
|
|
2005
|
-
|
|
2165
|
+
jumpButton: {
|
|
2166
|
+
position: "absolute",
|
|
2167
|
+
alignSelf: "center",
|
|
2168
|
+
bottom: 12,
|
|
2006
2169
|
flexDirection: "row",
|
|
2007
|
-
|
|
2008
|
-
gap:
|
|
2009
|
-
|
|
2170
|
+
alignItems: "center",
|
|
2171
|
+
gap: 6,
|
|
2172
|
+
paddingHorizontal: 14,
|
|
2173
|
+
paddingVertical: 8,
|
|
2174
|
+
borderRadius: 999
|
|
2010
2175
|
},
|
|
2011
|
-
|
|
2012
|
-
fontSize: 13
|
|
2176
|
+
jumpText: {
|
|
2177
|
+
fontSize: 13,
|
|
2178
|
+
fontWeight: "600"
|
|
2013
2179
|
}
|
|
2014
2180
|
});
|
|
2015
2181
|
var DEFAULT_REACTION_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F64F}", "\u{1F525}"];
|
|
@@ -2472,6 +2638,53 @@ var styles14 = reactNative.StyleSheet.create({
|
|
|
2472
2638
|
borderRadius: 20
|
|
2473
2639
|
}
|
|
2474
2640
|
});
|
|
2641
|
+
function renderMarkdown(body, args) {
|
|
2642
|
+
const baseText = {
|
|
2643
|
+
color: args.textColor,
|
|
2644
|
+
fontFamily: args.fontFamily,
|
|
2645
|
+
fontSize: args.fontSize,
|
|
2646
|
+
lineHeight: args.lineHeight
|
|
2647
|
+
};
|
|
2648
|
+
const style = {
|
|
2649
|
+
body: baseText,
|
|
2650
|
+
paragraph: { ...baseText, marginTop: 0, marginBottom: 0 },
|
|
2651
|
+
text: baseText,
|
|
2652
|
+
strong: { ...baseText, fontWeight: "700" },
|
|
2653
|
+
em: { ...baseText, fontStyle: "italic" },
|
|
2654
|
+
s: { ...baseText, textDecorationLine: "line-through" },
|
|
2655
|
+
link: { color: args.linkColor, textDecorationLine: "underline" },
|
|
2656
|
+
code_inline: {
|
|
2657
|
+
...baseText,
|
|
2658
|
+
backgroundColor: "rgba(127,127,127,0.18)",
|
|
2659
|
+
borderRadius: 4,
|
|
2660
|
+
paddingHorizontal: 4
|
|
2661
|
+
},
|
|
2662
|
+
code_block: {
|
|
2663
|
+
...baseText,
|
|
2664
|
+
backgroundColor: "rgba(127,127,127,0.18)",
|
|
2665
|
+
padding: 8,
|
|
2666
|
+
borderRadius: 6
|
|
2667
|
+
},
|
|
2668
|
+
fence: {
|
|
2669
|
+
...baseText,
|
|
2670
|
+
backgroundColor: "rgba(127,127,127,0.18)",
|
|
2671
|
+
padding: 8,
|
|
2672
|
+
borderRadius: 6
|
|
2673
|
+
},
|
|
2674
|
+
bullet_list: { marginTop: 2, marginBottom: 2 },
|
|
2675
|
+
ordered_list: { marginTop: 2, marginBottom: 2 },
|
|
2676
|
+
list_item: baseText,
|
|
2677
|
+
blockquote: {
|
|
2678
|
+
...baseText,
|
|
2679
|
+
borderLeftWidth: 3,
|
|
2680
|
+
borderLeftColor: args.linkColor,
|
|
2681
|
+
paddingLeft: 8,
|
|
2682
|
+
marginVertical: 4,
|
|
2683
|
+
opacity: 0.85
|
|
2684
|
+
}
|
|
2685
|
+
};
|
|
2686
|
+
return react.createElement(Markdown__default.default, { style, onLinkPress: args.onLinkPress }, body);
|
|
2687
|
+
}
|
|
2475
2688
|
function MessageBubble({
|
|
2476
2689
|
message,
|
|
2477
2690
|
currentUserId,
|
|
@@ -2482,7 +2695,8 @@ function MessageBubble({
|
|
|
2482
2695
|
maxBodyLength = 0,
|
|
2483
2696
|
showSenderName = false,
|
|
2484
2697
|
actionsTrigger,
|
|
2485
|
-
showAttachments = true
|
|
2698
|
+
showAttachments = true,
|
|
2699
|
+
markdown = true
|
|
2486
2700
|
}) {
|
|
2487
2701
|
const theme = usePoolseTheme();
|
|
2488
2702
|
const { width: screenWidth } = reactNative.useWindowDimensions();
|
|
@@ -2567,20 +2781,44 @@ function MessageBubble({
|
|
|
2567
2781
|
) : null,
|
|
2568
2782
|
renderAttachments && images.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(ImageMosaic, { images, maxWidth: contentMaxWidth }) : null,
|
|
2569
2783
|
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,
|
|
2570
|
-
body.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2784
|
+
body.length > 0 ? isDeleted ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2571
2785
|
reactNative.Text,
|
|
2572
2786
|
{
|
|
2573
2787
|
style: [
|
|
2574
2788
|
styles15.body,
|
|
2575
2789
|
{
|
|
2576
|
-
color:
|
|
2790
|
+
color: theme.colors.ink3,
|
|
2577
2791
|
fontFamily: theme.type.fontBody,
|
|
2578
2792
|
fontSize: theme.type.bodySize,
|
|
2579
2793
|
lineHeight: theme.type.lineHeight,
|
|
2580
|
-
fontStyle:
|
|
2794
|
+
fontStyle: "italic"
|
|
2581
2795
|
}
|
|
2582
2796
|
],
|
|
2583
|
-
children:
|
|
2797
|
+
children: "Message deleted"
|
|
2798
|
+
}
|
|
2799
|
+
) : markdown ? renderMarkdown(truncated, {
|
|
2800
|
+
textColor,
|
|
2801
|
+
linkColor: isSelf ? theme.colors.onBrand : theme.colors.brand,
|
|
2802
|
+
fontFamily: theme.type.fontBody,
|
|
2803
|
+
fontSize: theme.type.bodySize,
|
|
2804
|
+
lineHeight: theme.type.lineHeight,
|
|
2805
|
+
onLinkPress: (url) => {
|
|
2806
|
+
void reactNative.Linking.openURL(url).catch(() => void 0);
|
|
2807
|
+
return false;
|
|
2808
|
+
}
|
|
2809
|
+
}) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2810
|
+
reactNative.Text,
|
|
2811
|
+
{
|
|
2812
|
+
style: [
|
|
2813
|
+
styles15.body,
|
|
2814
|
+
{
|
|
2815
|
+
color: textColor,
|
|
2816
|
+
fontFamily: theme.type.fontBody,
|
|
2817
|
+
fontSize: theme.type.bodySize,
|
|
2818
|
+
lineHeight: theme.type.lineHeight
|
|
2819
|
+
}
|
|
2820
|
+
],
|
|
2821
|
+
children: truncated
|
|
2584
2822
|
}
|
|
2585
2823
|
) : null,
|
|
2586
2824
|
maxBodyLength > 0 && body.length > maxBodyLength ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2675,9 +2913,9 @@ function formatTime(iso) {
|
|
|
2675
2913
|
try {
|
|
2676
2914
|
const d = new Date(iso);
|
|
2677
2915
|
const now = /* @__PURE__ */ new Date();
|
|
2678
|
-
const
|
|
2916
|
+
const sameDay2 = isSameDay(d, now);
|
|
2679
2917
|
const time = d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
|
|
2680
|
-
if (
|
|
2918
|
+
if (sameDay2) return time;
|
|
2681
2919
|
const yesterday = new Date(now);
|
|
2682
2920
|
yesterday.setDate(now.getDate() - 1);
|
|
2683
2921
|
if (isSameDay(d, yesterday)) return `Yesterday ${time}`;
|
|
@@ -2785,12 +3023,26 @@ function ReactionStrip({
|
|
|
2785
3023
|
styles16.chip,
|
|
2786
3024
|
{
|
|
2787
3025
|
backgroundColor: meReacted ? theme.colors.brandSoft : theme.colors.surface2,
|
|
2788
|
-
borderColor: meReacted ? theme.colors.brand : theme.colors.border
|
|
3026
|
+
borderColor: meReacted ? theme.colors.brand : theme.colors.border,
|
|
3027
|
+
borderWidth: meReacted ? 1.5 : 1,
|
|
3028
|
+
transform: meReacted ? [{ scale: 1.04 }] : void 0
|
|
2789
3029
|
}
|
|
2790
3030
|
],
|
|
2791
3031
|
children: [
|
|
2792
3032
|
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: styles16.emoji, children: emoji }),
|
|
2793
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3033
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3034
|
+
reactNative.Text,
|
|
3035
|
+
{
|
|
3036
|
+
style: [
|
|
3037
|
+
styles16.count,
|
|
3038
|
+
{
|
|
3039
|
+
color: meReacted ? theme.colors.brand : theme.colors.ink2,
|
|
3040
|
+
fontWeight: meReacted ? "700" : "600"
|
|
3041
|
+
}
|
|
3042
|
+
],
|
|
3043
|
+
children: userArr.length
|
|
3044
|
+
}
|
|
3045
|
+
)
|
|
2794
3046
|
]
|
|
2795
3047
|
},
|
|
2796
3048
|
emoji
|
|
@@ -2843,7 +3095,6 @@ var styles16 = reactNative.StyleSheet.create({
|
|
|
2843
3095
|
paddingHorizontal: 9,
|
|
2844
3096
|
paddingVertical: 4,
|
|
2845
3097
|
borderRadius: 14,
|
|
2846
|
-
borderWidth: 1,
|
|
2847
3098
|
gap: 5,
|
|
2848
3099
|
minHeight: 26,
|
|
2849
3100
|
// Subtle elevation so chips lift off the surface
|
|
@@ -2859,7 +3110,6 @@ var styles16 = reactNative.StyleSheet.create({
|
|
|
2859
3110
|
},
|
|
2860
3111
|
count: {
|
|
2861
3112
|
fontSize: 12,
|
|
2862
|
-
fontWeight: "600",
|
|
2863
3113
|
fontVariant: ["tabular-nums"]
|
|
2864
3114
|
},
|
|
2865
3115
|
backdrop: {
|
|
@@ -2897,11 +3147,10 @@ function MessageRow({
|
|
|
2897
3147
|
actions = true,
|
|
2898
3148
|
threads = true,
|
|
2899
3149
|
quotations = true,
|
|
3150
|
+
markdown = true,
|
|
2900
3151
|
readState,
|
|
2901
|
-
|
|
3152
|
+
isEditing = false,
|
|
2902
3153
|
onStartEdit,
|
|
2903
|
-
onCancelEdit,
|
|
2904
|
-
onSaveEdit,
|
|
2905
3154
|
onDelete,
|
|
2906
3155
|
onOpenThread,
|
|
2907
3156
|
onQuote,
|
|
@@ -2922,7 +3171,7 @@ function MessageRow({
|
|
|
2922
3171
|
...msg.reactions ? { initialReactions: msg.reactions } : {}
|
|
2923
3172
|
});
|
|
2924
3173
|
const translateX = react.useRef(new reactNative.Animated.Value(0)).current;
|
|
2925
|
-
const swipeEnabled = quotations && !!onQuote && !
|
|
3174
|
+
const swipeEnabled = quotations && !!onQuote && !isEditing;
|
|
2926
3175
|
const panResponder = react.useMemo(
|
|
2927
3176
|
() => reactNative.PanResponder.create({
|
|
2928
3177
|
// Tap / long-press still go to the inner Pressable.
|
|
@@ -2997,137 +3246,149 @@ function MessageRow({
|
|
|
2997
3246
|
const rowSender = react$1.useUser(showAvatarSlot ? msg.sender_external_id : null);
|
|
2998
3247
|
const avatarUrl = msg.sender_external_id ? avatarFor?.(msg.sender_external_id) ?? rowSender.profile?.avatarUrl ?? null : null;
|
|
2999
3248
|
const senderLabel = msg.sender_external_id ? labelFor?.(msg.sender_external_id) ?? rowSender.profile?.displayName ?? msg.sender_external_id : null;
|
|
3000
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
reactNative.
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "reply", size: 16, color: theme.colors.brand })
|
|
3025
|
-
}
|
|
3026
|
-
) : null,
|
|
3027
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3028
|
-
reactNative.Animated.View,
|
|
3029
|
-
{
|
|
3030
|
-
...swipeEnabled ? panResponder.panHandlers : {},
|
|
3031
|
-
style: { transform: [{ translateX }] },
|
|
3032
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3033
|
-
reactNative.Pressable,
|
|
3249
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3250
|
+
reactNative.View,
|
|
3251
|
+
{
|
|
3252
|
+
style: [
|
|
3253
|
+
styles17.row,
|
|
3254
|
+
{ alignSelf: isSelf ? "flex-end" : "flex-start" },
|
|
3255
|
+
// Lift this row above the dim backdrop the parent renders
|
|
3256
|
+
// during edit mode. zIndex covers iOS / web; elevation does
|
|
3257
|
+
// the equivalent on Android.
|
|
3258
|
+
isEditing ? styles17.rowEditing : null
|
|
3259
|
+
],
|
|
3260
|
+
children: [
|
|
3261
|
+
showAvatarSlot ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: styles17.avatarSlot, children: showAvatarRender ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3262
|
+
Avatar,
|
|
3263
|
+
{
|
|
3264
|
+
src: avatarUrl ?? null,
|
|
3265
|
+
name: senderLabel,
|
|
3266
|
+
seed: msg.sender_external_id ?? null,
|
|
3267
|
+
size: "sm"
|
|
3268
|
+
}
|
|
3269
|
+
) : null }) : null,
|
|
3270
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles17.bubbleCol, children: [
|
|
3271
|
+
swipeEnabled ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3272
|
+
reactNative.Animated.View,
|
|
3034
3273
|
{
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
accessibilityRole: "button",
|
|
3040
|
-
accessibilityLabel: "Message actions",
|
|
3041
|
-
children: editing ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3042
|
-
EditableMessageBubble,
|
|
3274
|
+
pointerEvents: "none",
|
|
3275
|
+
style: [
|
|
3276
|
+
styles17.replyHint,
|
|
3277
|
+
isSelf ? styles17.replyHintRight : styles17.replyHintLeft,
|
|
3043
3278
|
{
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
onSave: onSaveEdit ?? (() => void 0)
|
|
3279
|
+
opacity: replyIconOpacity,
|
|
3280
|
+
transform: [{ scale: replyIconScale }],
|
|
3281
|
+
backgroundColor: theme.colors.surface2
|
|
3048
3282
|
}
|
|
3049
|
-
|
|
3050
|
-
|
|
3283
|
+
],
|
|
3284
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(PoolseIcon, { name: "reply", size: 16, color: theme.colors.brand })
|
|
3285
|
+
}
|
|
3286
|
+
) : null,
|
|
3287
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3288
|
+
reactNative.Animated.View,
|
|
3289
|
+
{
|
|
3290
|
+
...swipeEnabled ? panResponder.panHandlers : {},
|
|
3291
|
+
style: { transform: [{ translateX }] },
|
|
3292
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3293
|
+
reactNative.Pressable,
|
|
3051
3294
|
{
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
showAttachments: attachments,
|
|
3061
|
-
actionsTrigger: actions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3062
|
-
PoolseIcon,
|
|
3295
|
+
onLongPress: () => {
|
|
3296
|
+
if (!actions) return;
|
|
3297
|
+
setActionsOpen(true);
|
|
3298
|
+
},
|
|
3299
|
+
accessibilityRole: "button",
|
|
3300
|
+
accessibilityLabel: "Message actions",
|
|
3301
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3302
|
+
MessageBubble,
|
|
3063
3303
|
{
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3304
|
+
message: msg,
|
|
3305
|
+
currentUserId: meId,
|
|
3306
|
+
...readState ? { readState } : {},
|
|
3307
|
+
...labelFor ? { labelFor } : {},
|
|
3308
|
+
...onQuotedClick ? { onQuotedClick } : {},
|
|
3309
|
+
groupPosition,
|
|
3310
|
+
maxBodyLength,
|
|
3311
|
+
showSenderName,
|
|
3312
|
+
showAttachments: attachments,
|
|
3313
|
+
markdown,
|
|
3314
|
+
actionsTrigger: actions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3315
|
+
PoolseIcon,
|
|
3316
|
+
{
|
|
3317
|
+
name: "chevron-down",
|
|
3318
|
+
size: 14,
|
|
3319
|
+
color: isSelf ? theme.colors.onBrand : theme.colors.ink3
|
|
3320
|
+
}
|
|
3321
|
+
) }) : null
|
|
3067
3322
|
}
|
|
3068
|
-
)
|
|
3323
|
+
)
|
|
3069
3324
|
}
|
|
3070
3325
|
)
|
|
3071
3326
|
}
|
|
3072
|
-
)
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
}
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3327
|
+
),
|
|
3328
|
+
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: [
|
|
3329
|
+
"\u{1F4AC} ",
|
|
3330
|
+
msg.reply_count,
|
|
3331
|
+
" ",
|
|
3332
|
+
msg.reply_count === 1 ? "reply" : "replies"
|
|
3333
|
+
] }) }) : null,
|
|
3334
|
+
reactions ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [styles17.reactionsRow, { alignSelf: isSelf ? "flex-end" : "flex-start" }], children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3335
|
+
ReactionStrip,
|
|
3336
|
+
{
|
|
3337
|
+
messageId: msg.id,
|
|
3338
|
+
conversationId: msg.conversation_id,
|
|
3339
|
+
meId,
|
|
3340
|
+
...msg.reactions ? { initialReactions: msg.reactions } : {}
|
|
3341
|
+
}
|
|
3342
|
+
) }) : null
|
|
3343
|
+
] }),
|
|
3344
|
+
actions && actionsOpen ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3345
|
+
MessageActions,
|
|
3346
|
+
{
|
|
3347
|
+
visible: actionsOpen,
|
|
3348
|
+
onClose: () => setActionsOpen(false),
|
|
3349
|
+
isSelf,
|
|
3350
|
+
...reactions ? {
|
|
3351
|
+
// Inline quick-react row at the top of the sheet —
|
|
3352
|
+
// one tap = react + close, no second modal needed.
|
|
3353
|
+
onPickReaction: (emoji) => {
|
|
3354
|
+
void reactionsApi.addReaction(emoji);
|
|
3355
|
+
}
|
|
3356
|
+
} : {},
|
|
3357
|
+
...msg.body && !msg.deleted_at ? {
|
|
3358
|
+
onCopy: () => {
|
|
3359
|
+
setActionsOpen(false);
|
|
3360
|
+
void Clipboard__namespace.setStringAsync(msg.body ?? "");
|
|
3361
|
+
}
|
|
3362
|
+
} : {},
|
|
3363
|
+
...onStartEdit ? {
|
|
3364
|
+
onEdit: () => {
|
|
3365
|
+
setActionsOpen(false);
|
|
3366
|
+
onStartEdit();
|
|
3367
|
+
}
|
|
3368
|
+
} : {},
|
|
3369
|
+
...onDelete ? {
|
|
3370
|
+
onDelete: () => {
|
|
3371
|
+
setActionsOpen(false);
|
|
3372
|
+
onDelete();
|
|
3373
|
+
}
|
|
3374
|
+
} : {},
|
|
3375
|
+
...onOpenThread ? {
|
|
3376
|
+
onReplyInThread: () => {
|
|
3377
|
+
setActionsOpen(false);
|
|
3378
|
+
onOpenThread();
|
|
3379
|
+
}
|
|
3380
|
+
} : {},
|
|
3381
|
+
...quotations && onQuote ? {
|
|
3382
|
+
onQuote: () => {
|
|
3383
|
+
setActionsOpen(false);
|
|
3384
|
+
onQuote();
|
|
3385
|
+
}
|
|
3386
|
+
} : {}
|
|
3126
3387
|
}
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3388
|
+
) : null
|
|
3389
|
+
]
|
|
3390
|
+
}
|
|
3391
|
+
);
|
|
3131
3392
|
}
|
|
3132
3393
|
var styles17 = reactNative.StyleSheet.create({
|
|
3133
3394
|
row: {
|
|
@@ -3143,6 +3404,12 @@ var styles17 = reactNative.StyleSheet.create({
|
|
|
3143
3404
|
paddingHorizontal: 10,
|
|
3144
3405
|
maxWidth: "95%"
|
|
3145
3406
|
},
|
|
3407
|
+
rowEditing: {
|
|
3408
|
+
// Above ConversationView's editBackdrop (zIndex 5). Elevation
|
|
3409
|
+
// does the equivalent stacking on Android.
|
|
3410
|
+
zIndex: 10,
|
|
3411
|
+
elevation: 10
|
|
3412
|
+
},
|
|
3146
3413
|
avatarSlot: {
|
|
3147
3414
|
width: 36,
|
|
3148
3415
|
marginRight: 6
|
|
@@ -3237,31 +3504,34 @@ function ThreadSheet({
|
|
|
3237
3504
|
const [pickerOpen, setPickerOpen] = react.useState(false);
|
|
3238
3505
|
const [editingId, setEditingId] = react.useState(null);
|
|
3239
3506
|
const renderReply = react.useCallback(
|
|
3240
|
-
(msg) =>
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3507
|
+
(msg) => {
|
|
3508
|
+
const isSelf = meId !== null && msg.sender_id === meId;
|
|
3509
|
+
const canEdit = isSelf && !msg.deleted_at;
|
|
3510
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3511
|
+
MessageRow,
|
|
3512
|
+
{
|
|
3513
|
+
msg,
|
|
3514
|
+
meId,
|
|
3515
|
+
reactions: true,
|
|
3516
|
+
attachments: true,
|
|
3517
|
+
actions: true,
|
|
3518
|
+
threads: false,
|
|
3519
|
+
quotations: false,
|
|
3520
|
+
isEditing: editingId === msg.id,
|
|
3521
|
+
...canEdit ? { onStartEdit: () => setEditingId(msg.id) } : {},
|
|
3522
|
+
onDelete: () => {
|
|
3523
|
+
void deleteReply(msg.id);
|
|
3524
|
+
},
|
|
3525
|
+
...labelFor ? { labelFor } : {},
|
|
3526
|
+
...avatarFor ? { avatarFor } : {}
|
|
3527
|
+
}
|
|
3528
|
+
);
|
|
3529
|
+
},
|
|
3530
|
+
[meId, editingId, deleteReply, labelFor, avatarFor]
|
|
3531
|
+
);
|
|
3532
|
+
const editingMessage = react.useMemo(
|
|
3533
|
+
() => editingId ? replies.find((m) => m.id === editingId) ?? null : null,
|
|
3534
|
+
[editingId, replies]
|
|
3265
3535
|
);
|
|
3266
3536
|
const rootRow = react.useMemo(
|
|
3267
3537
|
() => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3347,7 +3617,14 @@ function ThreadSheet({
|
|
|
3347
3617
|
},
|
|
3348
3618
|
placeholder: "Reply to thread\u2026",
|
|
3349
3619
|
attachments: true,
|
|
3350
|
-
onAttachPress: () => setPickerOpen(true)
|
|
3620
|
+
onAttachPress: () => setPickerOpen(true),
|
|
3621
|
+
editingMessage,
|
|
3622
|
+
onCancelEdit: () => setEditingId(null),
|
|
3623
|
+
onSaveEdit: async (body) => {
|
|
3624
|
+
if (!editingId) return;
|
|
3625
|
+
await edit(editingId, body);
|
|
3626
|
+
setEditingId(null);
|
|
3627
|
+
}
|
|
3351
3628
|
}
|
|
3352
3629
|
)
|
|
3353
3630
|
]
|
|
@@ -3511,6 +3788,7 @@ var styles19 = reactNative.StyleSheet.create({
|
|
|
3511
3788
|
fontSize: 12
|
|
3512
3789
|
}
|
|
3513
3790
|
});
|
|
3791
|
+
var GROUPING_WINDOW_MS = 5 * 60 * 1e3;
|
|
3514
3792
|
function ConversationView({
|
|
3515
3793
|
conversationId,
|
|
3516
3794
|
emptyState,
|
|
@@ -3527,6 +3805,7 @@ function ConversationView({
|
|
|
3527
3805
|
threads = true,
|
|
3528
3806
|
quotations = true,
|
|
3529
3807
|
readReceipts = true,
|
|
3808
|
+
markdown = true,
|
|
3530
3809
|
maxBodyLength = 200,
|
|
3531
3810
|
senderLabels = "auto",
|
|
3532
3811
|
avatars = "auto",
|
|
@@ -3545,9 +3824,25 @@ function ConversationView({
|
|
|
3545
3824
|
send,
|
|
3546
3825
|
edit,
|
|
3547
3826
|
delete: deleteMsg,
|
|
3548
|
-
markReadUpTo
|
|
3827
|
+
markReadUpTo,
|
|
3828
|
+
refetch
|
|
3549
3829
|
} = react$1.useMessages(conversationId);
|
|
3550
3830
|
const { typing, signalTyping } = react$1.useTyping(conversationId);
|
|
3831
|
+
const realtimeStatus = react$1.useRealtimeStatus();
|
|
3832
|
+
react.useEffect(() => {
|
|
3833
|
+
const sub = reactNative.AppState.addEventListener("change", (next) => {
|
|
3834
|
+
if (next === "active") void refetch();
|
|
3835
|
+
});
|
|
3836
|
+
return () => sub.remove();
|
|
3837
|
+
}, [refetch]);
|
|
3838
|
+
const prevStatusRef = react.useRef(realtimeStatus);
|
|
3839
|
+
react.useEffect(() => {
|
|
3840
|
+
const prev = prevStatusRef.current;
|
|
3841
|
+
prevStatusRef.current = realtimeStatus;
|
|
3842
|
+
if (realtimeStatus === "connected" && (prev === "reconnecting" || prev === "closed" || prev === "connecting")) {
|
|
3843
|
+
void refetch();
|
|
3844
|
+
}
|
|
3845
|
+
}, [realtimeStatus, refetch]);
|
|
3551
3846
|
const wantsMembers = mentions || senderLabels === "auto" || avatars === "auto";
|
|
3552
3847
|
const { members } = react$1.useMembers(wantsMembers ? conversationId : "");
|
|
3553
3848
|
const isGroupChat = members.length > 2;
|
|
@@ -3578,10 +3873,24 @@ function ConversationView({
|
|
|
3578
3873
|
});
|
|
3579
3874
|
onMarkedRead?.(conversationId, latest.id);
|
|
3580
3875
|
}, [messages, meId, readReceipts, markReadUpTo, onMarkedRead, conversationId]);
|
|
3876
|
+
const groupPositionById = react.useMemo(() => {
|
|
3877
|
+
const map = /* @__PURE__ */ new Map();
|
|
3878
|
+
for (let i = 0; i < messages.length; i++) {
|
|
3879
|
+
const msg = messages[i];
|
|
3880
|
+
if (!msg) continue;
|
|
3881
|
+
const prev = i > 0 ? messages[i - 1] ?? null : null;
|
|
3882
|
+
const next = i < messages.length - 1 ? messages[i + 1] ?? null : null;
|
|
3883
|
+
map.set(msg.id, react$1.computeGroupPosition(msg, prev, next, GROUPING_WINDOW_MS));
|
|
3884
|
+
}
|
|
3885
|
+
return map;
|
|
3886
|
+
}, [messages]);
|
|
3581
3887
|
const renderItem = react.useCallback(
|
|
3582
3888
|
(msg) => {
|
|
3583
3889
|
if (renderMessage) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderMessage(msg, meId) });
|
|
3584
3890
|
const readState = readReceipts && meId !== null && msg.sender_id === meId ? readStateForMessage(msg, members, meId, sequenceByMessageId) : void 0;
|
|
3891
|
+
const isSelf = meId !== null && msg.sender_id === meId;
|
|
3892
|
+
const canEdit = isSelf && !msg.deleted_at && (readState ?? "sent") === "sent";
|
|
3893
|
+
const groupPosition = groupPositionById.get(msg.id) ?? "standalone";
|
|
3585
3894
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3586
3895
|
MessageRow,
|
|
3587
3896
|
{
|
|
@@ -3593,13 +3902,8 @@ function ConversationView({
|
|
|
3593
3902
|
threads,
|
|
3594
3903
|
quotations,
|
|
3595
3904
|
...readState ? { readState } : {},
|
|
3596
|
-
|
|
3597
|
-
onStartEdit: () => setEditingId(msg.id),
|
|
3598
|
-
onCancelEdit: () => setEditingId(null),
|
|
3599
|
-
onSaveEdit: async (body) => {
|
|
3600
|
-
await edit(msg.id, body);
|
|
3601
|
-
setEditingId(null);
|
|
3602
|
-
},
|
|
3905
|
+
isEditing: editingId === msg.id,
|
|
3906
|
+
...canEdit ? { onStartEdit: () => setEditingId(msg.id) } : {},
|
|
3603
3907
|
onDelete: () => deleteMsg(msg.id),
|
|
3604
3908
|
...threads ? { onOpenThread: () => setThreadRoot(msg) } : {},
|
|
3605
3909
|
...quotations ? { onQuote: () => setReplyingTo(msg) } : {},
|
|
@@ -3607,7 +3911,9 @@ function ConversationView({
|
|
|
3607
3911
|
...avatarFor ? { avatarFor } : {},
|
|
3608
3912
|
showSenderName: showSenderLabels,
|
|
3609
3913
|
showAvatar: showAvatars,
|
|
3610
|
-
maxBodyLength
|
|
3914
|
+
maxBodyLength,
|
|
3915
|
+
groupPosition,
|
|
3916
|
+
markdown
|
|
3611
3917
|
}
|
|
3612
3918
|
);
|
|
3613
3919
|
},
|
|
@@ -3622,16 +3928,38 @@ function ConversationView({
|
|
|
3622
3928
|
threads,
|
|
3623
3929
|
quotations,
|
|
3624
3930
|
editingId,
|
|
3625
|
-
edit,
|
|
3626
3931
|
deleteMsg,
|
|
3627
3932
|
labelFor,
|
|
3628
3933
|
avatarFor,
|
|
3629
3934
|
showSenderLabels,
|
|
3630
3935
|
showAvatars,
|
|
3631
3936
|
maxBodyLength,
|
|
3632
|
-
sequenceByMessageId
|
|
3937
|
+
sequenceByMessageId,
|
|
3938
|
+
groupPositionById,
|
|
3939
|
+
markdown
|
|
3633
3940
|
]
|
|
3634
3941
|
);
|
|
3942
|
+
const editingMessage = react.useMemo(
|
|
3943
|
+
() => editingId ? messages.find((m) => m.id === editingId) ?? null : null,
|
|
3944
|
+
[editingId, messages]
|
|
3945
|
+
);
|
|
3946
|
+
const listItems = react.useMemo(() => {
|
|
3947
|
+
const out = [];
|
|
3948
|
+
let prev = null;
|
|
3949
|
+
for (const msg of messages) {
|
|
3950
|
+
if (prev === null || !react$1.sameDay(msg.inserted_at, prev.inserted_at)) {
|
|
3951
|
+
const label = react$1.formatDayLabel(msg.inserted_at);
|
|
3952
|
+
out.push({
|
|
3953
|
+
kind: "separator",
|
|
3954
|
+
id: `sep-${msg.id}`,
|
|
3955
|
+
node: /* @__PURE__ */ jsxRuntime.jsx(DaySeparator, { label })
|
|
3956
|
+
});
|
|
3957
|
+
}
|
|
3958
|
+
out.push({ kind: "msg", msg });
|
|
3959
|
+
prev = msg;
|
|
3960
|
+
}
|
|
3961
|
+
return out;
|
|
3962
|
+
}, [messages]);
|
|
3635
3963
|
const composerProps = react.useMemo(
|
|
3636
3964
|
() => ({
|
|
3637
3965
|
onSend: async (body, opts) => {
|
|
@@ -3645,9 +3973,28 @@ function ConversationView({
|
|
|
3645
3973
|
onCancelReply: () => setReplyingTo(null),
|
|
3646
3974
|
...labelFor ? { labelFor } : {},
|
|
3647
3975
|
attachments,
|
|
3648
|
-
keyboardOffset
|
|
3976
|
+
keyboardOffset,
|
|
3977
|
+
editingMessage,
|
|
3978
|
+
onCancelEdit: () => setEditingId(null),
|
|
3979
|
+
onSaveEdit: async (body) => {
|
|
3980
|
+
if (!editingId) return;
|
|
3981
|
+
await edit(editingId, body);
|
|
3982
|
+
setEditingId(null);
|
|
3983
|
+
}
|
|
3649
3984
|
}),
|
|
3650
|
-
[
|
|
3985
|
+
[
|
|
3986
|
+
send,
|
|
3987
|
+
signalTyping,
|
|
3988
|
+
attachments,
|
|
3989
|
+
replyingTo,
|
|
3990
|
+
labelFor,
|
|
3991
|
+
keyboardOffset,
|
|
3992
|
+
onSent,
|
|
3993
|
+
conversationId,
|
|
3994
|
+
editingMessage,
|
|
3995
|
+
editingId,
|
|
3996
|
+
edit
|
|
3997
|
+
]
|
|
3651
3998
|
);
|
|
3652
3999
|
const headerNode = header === false ? null : header === true ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3653
4000
|
ChatHeader,
|
|
@@ -3667,18 +4014,30 @@ function ConversationView({
|
|
|
3667
4014
|
keyboardVerticalOffset: keyboardOffset,
|
|
3668
4015
|
children: [
|
|
3669
4016
|
headerNode,
|
|
3670
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
4017
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.View, { style: styles20.listWrap, children: [
|
|
4018
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4019
|
+
MessageList,
|
|
4020
|
+
{
|
|
4021
|
+
messages,
|
|
4022
|
+
items: listItems,
|
|
4023
|
+
meId,
|
|
4024
|
+
loading,
|
|
4025
|
+
hasMore,
|
|
4026
|
+
onLoadMore: loadMore,
|
|
4027
|
+
renderItem,
|
|
4028
|
+
ListHeader: /* @__PURE__ */ jsxRuntime.jsx(TypingIndicator, { typing, ...labelFor ? { labelFor } : {} }),
|
|
4029
|
+
...emptyState !== void 0 ? { emptyState } : {}
|
|
4030
|
+
}
|
|
4031
|
+
),
|
|
4032
|
+
editingId ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4033
|
+
reactNative.Pressable,
|
|
4034
|
+
{
|
|
4035
|
+
style: styles20.editBackdrop,
|
|
4036
|
+
onPress: () => setEditingId(null),
|
|
4037
|
+
accessibilityLabel: "Cancel editing"
|
|
4038
|
+
}
|
|
4039
|
+
) : null
|
|
4040
|
+
] }),
|
|
3682
4041
|
mentions ? /* @__PURE__ */ jsxRuntime.jsx(MentionInput, { conversationId, ...composerProps }) : /* @__PURE__ */ jsxRuntime.jsx(MessageComposer, { ...composerProps }),
|
|
3683
4042
|
attachments ? /* @__PURE__ */ jsxRuntime.jsx(AttachmentPicker, { visible: pickerOpen, onClose: () => setPickerOpen(false) }) : null,
|
|
3684
4043
|
threads && threadRoot ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3714,6 +4073,14 @@ var styles20 = reactNative.StyleSheet.create({
|
|
|
3714
4073
|
},
|
|
3715
4074
|
listWrap: {
|
|
3716
4075
|
flex: 1
|
|
4076
|
+
},
|
|
4077
|
+
editBackdrop: {
|
|
4078
|
+
...reactNative.StyleSheet.absoluteFillObject,
|
|
4079
|
+
backgroundColor: "rgba(0,0,0,0.45)",
|
|
4080
|
+
// Sit above message rows by default but below any row that
|
|
4081
|
+
// explicitly elevates itself (MessageRow bumps zIndex/elevation
|
|
4082
|
+
// when isEditing is true).
|
|
4083
|
+
zIndex: 5
|
|
3717
4084
|
}
|
|
3718
4085
|
});
|
|
3719
4086
|
function UserPickerSheet({
|
|
@@ -4578,7 +4945,7 @@ exports.Avatar = Avatar;
|
|
|
4578
4945
|
exports.ChatHeader = ChatHeader;
|
|
4579
4946
|
exports.ConversationList = ConversationList;
|
|
4580
4947
|
exports.ConversationView = ConversationView;
|
|
4581
|
-
exports.
|
|
4948
|
+
exports.DaySeparator = DaySeparator;
|
|
4582
4949
|
exports.GroupDetailsSheet = GroupDetailsSheet;
|
|
4583
4950
|
exports.ImageMosaic = ImageMosaic;
|
|
4584
4951
|
exports.MemberList = MemberList;
|