@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.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import { useUser, useMe, useConversations, useAttachmentUpload, useConversation, useMembers, usePresence, useMessages, useTyping, useReactions, useThread, useAttachmentUrl, usePoolse } from '@poolse/react';
|
|
2
|
-
import { createContext, forwardRef, useRef, useState,
|
|
3
|
-
import { StyleSheet, View, Pressable, TextInput, Text, useWindowDimensions, Animated, Easing, PanResponder, Image, ScrollView, ActivityIndicator, FlatList,
|
|
1
|
+
import { useUser, useMe, useConversations, useAttachmentUpload, useConversation, useMembers, usePresence, useMessages, useTyping, useRealtimeStatus, computeGroupPosition, formatDayLabel, useReactions, sameDay, useThread, useAttachmentUrl, usePoolse } from '@poolse/react';
|
|
2
|
+
import { createContext, forwardRef, useRef, useState, useEffect, useImperativeHandle, useMemo, useCallback, useContext, createElement } from 'react';
|
|
3
|
+
import { StyleSheet, Platform, Keyboard, View, Pressable, TextInput, Text, useWindowDimensions, Animated, Easing, PanResponder, Image, ScrollView, ActivityIndicator, FlatList, AppState, KeyboardAvoidingView, Modal, Linking, Dimensions } from 'react-native';
|
|
4
4
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
import { SvgXml, Svg, Path, Circle, G } from 'react-native-svg';
|
|
6
6
|
import * as ImagePicker from 'expo-image-picker';
|
|
7
7
|
import * as DocumentPicker from 'expo-document-picker';
|
|
8
|
+
import { manipulateAsync, SaveFormat } from 'expo-image-manipulator';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
11
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
12
|
+
}) : x)(function(x) {
|
|
13
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
14
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
15
|
+
});
|
|
10
16
|
|
|
11
17
|
// src/theme/default.ts
|
|
12
18
|
var lightColors = {
|
|
@@ -1160,24 +1166,84 @@ async function uriToBlob(uri) {
|
|
|
1160
1166
|
}
|
|
1161
1167
|
return res.blob();
|
|
1162
1168
|
}
|
|
1169
|
+
var COMPRESSION_LONG_SIDE_PX = 1600;
|
|
1170
|
+
var COMPRESSION_QUALITY = 0.75;
|
|
1171
|
+
function getImageSize(uri) {
|
|
1172
|
+
return new Promise((resolve, reject) => {
|
|
1173
|
+
Image.getSize(uri, (width, height) => resolve({ width, height }), reject);
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
async function compressImage({
|
|
1177
|
+
uri,
|
|
1178
|
+
width,
|
|
1179
|
+
height,
|
|
1180
|
+
filename
|
|
1181
|
+
}) {
|
|
1182
|
+
let w = width;
|
|
1183
|
+
let h = height;
|
|
1184
|
+
if (!w || !h) {
|
|
1185
|
+
try {
|
|
1186
|
+
const size = await getImageSize(uri);
|
|
1187
|
+
w = size.width;
|
|
1188
|
+
h = size.height;
|
|
1189
|
+
} catch {
|
|
1190
|
+
w = 0;
|
|
1191
|
+
h = 0;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
const longSide = Math.max(w ?? 0, h ?? 0);
|
|
1195
|
+
const scale = longSide > COMPRESSION_LONG_SIDE_PX ? COMPRESSION_LONG_SIDE_PX / longSide : 1;
|
|
1196
|
+
const actions = scale < 1 && w && h ? [{ resize: { width: Math.round(w * scale), height: Math.round(h * scale) } }] : [];
|
|
1197
|
+
const result = await manipulateAsync(uri, actions, {
|
|
1198
|
+
compress: COMPRESSION_QUALITY,
|
|
1199
|
+
format: SaveFormat.WEBP
|
|
1200
|
+
});
|
|
1201
|
+
const blob = await uriToBlob(result.uri);
|
|
1202
|
+
const base = (filename ?? `image-${Date.now()}`).replace(/\.[^.]+$/, "");
|
|
1203
|
+
return {
|
|
1204
|
+
blob,
|
|
1205
|
+
contentType: "image/webp",
|
|
1206
|
+
filename: `${base}.webp`
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1163
1209
|
async function defaultAdapter(kind) {
|
|
1164
1210
|
if (kind === "image") {
|
|
1165
1211
|
const res2 = await ImagePicker.launchImageLibraryAsync({
|
|
1166
1212
|
mediaTypes: "images",
|
|
1167
|
-
|
|
1213
|
+
// Quality 1 — picker hands back the source as-is and we do the
|
|
1214
|
+
// compression ourselves below. Letting the picker re-encode
|
|
1215
|
+
// first just chains two lossy passes.
|
|
1216
|
+
quality: 1
|
|
1168
1217
|
});
|
|
1169
1218
|
if (res2.canceled) return [];
|
|
1170
1219
|
return Promise.all(
|
|
1171
1220
|
res2.assets.map(async (asset) => {
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1221
|
+
try {
|
|
1222
|
+
const compressed = await compressImage({
|
|
1223
|
+
uri: asset.uri,
|
|
1224
|
+
width: asset.width,
|
|
1225
|
+
height: asset.height,
|
|
1226
|
+
filename: asset.fileName ?? `image-${Date.now()}`
|
|
1227
|
+
});
|
|
1228
|
+
return {
|
|
1229
|
+
body: compressed.blob,
|
|
1230
|
+
contentType: compressed.contentType,
|
|
1231
|
+
byteSize: compressed.blob.size,
|
|
1232
|
+
filename: compressed.filename,
|
|
1233
|
+
_previewUri: asset.uri
|
|
1234
|
+
};
|
|
1235
|
+
} catch (err) {
|
|
1236
|
+
console.warn("[poolse/picker] image compression failed, sending original:", err);
|
|
1237
|
+
const blob = await uriToBlob(asset.uri);
|
|
1238
|
+
const contentType = asset.mimeType ?? blob.type ?? "image/jpeg";
|
|
1239
|
+
return {
|
|
1240
|
+
body: blob,
|
|
1241
|
+
contentType,
|
|
1242
|
+
byteSize: asset.fileSize ?? blob.size,
|
|
1243
|
+
filename: asset.fileName ?? `image-${Date.now()}.jpg`,
|
|
1244
|
+
_previewUri: asset.uri
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1181
1247
|
})
|
|
1182
1248
|
);
|
|
1183
1249
|
}
|
|
@@ -1188,6 +1254,24 @@ async function defaultAdapter(kind) {
|
|
|
1188
1254
|
if (res.canceled) return [];
|
|
1189
1255
|
return Promise.all(
|
|
1190
1256
|
res.assets.map(async (asset) => {
|
|
1257
|
+
const isImage = (asset.mimeType ?? "").startsWith("image/");
|
|
1258
|
+
if (isImage) {
|
|
1259
|
+
try {
|
|
1260
|
+
const compressed = await compressImage({
|
|
1261
|
+
uri: asset.uri,
|
|
1262
|
+
filename: asset.name
|
|
1263
|
+
});
|
|
1264
|
+
return {
|
|
1265
|
+
body: compressed.blob,
|
|
1266
|
+
contentType: compressed.contentType,
|
|
1267
|
+
byteSize: compressed.blob.size,
|
|
1268
|
+
filename: compressed.filename,
|
|
1269
|
+
_previewUri: asset.uri
|
|
1270
|
+
};
|
|
1271
|
+
} catch (err) {
|
|
1272
|
+
console.warn("[poolse/picker] image compression failed, sending original:", err);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1191
1275
|
const blob = await uriToBlob(asset.uri);
|
|
1192
1276
|
const contentType = asset.mimeType ?? blob.type ?? "application/octet-stream";
|
|
1193
1277
|
return {
|
|
@@ -1228,6 +1312,51 @@ var styles6 = StyleSheet.create({
|
|
|
1228
1312
|
fontWeight: "500"
|
|
1229
1313
|
}
|
|
1230
1314
|
});
|
|
1315
|
+
function DaySeparator({ label }) {
|
|
1316
|
+
const theme = usePoolseTheme();
|
|
1317
|
+
return /* @__PURE__ */ jsx(View, { style: styles7.wrap, children: /* @__PURE__ */ jsx(
|
|
1318
|
+
View,
|
|
1319
|
+
{
|
|
1320
|
+
style: [
|
|
1321
|
+
styles7.chip,
|
|
1322
|
+
{
|
|
1323
|
+
backgroundColor: theme.colors.surface2,
|
|
1324
|
+
borderColor: theme.colors.border
|
|
1325
|
+
}
|
|
1326
|
+
],
|
|
1327
|
+
children: /* @__PURE__ */ jsx(
|
|
1328
|
+
Text,
|
|
1329
|
+
{
|
|
1330
|
+
style: [
|
|
1331
|
+
styles7.label,
|
|
1332
|
+
{
|
|
1333
|
+
color: theme.colors.ink2,
|
|
1334
|
+
fontFamily: theme.type.fontBody
|
|
1335
|
+
}
|
|
1336
|
+
],
|
|
1337
|
+
children: label
|
|
1338
|
+
}
|
|
1339
|
+
)
|
|
1340
|
+
}
|
|
1341
|
+
) });
|
|
1342
|
+
}
|
|
1343
|
+
var styles7 = StyleSheet.create({
|
|
1344
|
+
wrap: {
|
|
1345
|
+
alignItems: "center",
|
|
1346
|
+
marginVertical: 8
|
|
1347
|
+
},
|
|
1348
|
+
chip: {
|
|
1349
|
+
paddingHorizontal: 12,
|
|
1350
|
+
paddingVertical: 4,
|
|
1351
|
+
borderRadius: 999,
|
|
1352
|
+
borderWidth: StyleSheet.hairlineWidth
|
|
1353
|
+
},
|
|
1354
|
+
label: {
|
|
1355
|
+
fontSize: 11,
|
|
1356
|
+
fontWeight: "600",
|
|
1357
|
+
letterSpacing: 0.3
|
|
1358
|
+
}
|
|
1359
|
+
});
|
|
1231
1360
|
function UploadQueueStrip() {
|
|
1232
1361
|
const theme = usePoolseTheme();
|
|
1233
1362
|
const upload = useSharedUpload();
|
|
@@ -1238,8 +1367,8 @@ function UploadQueueStrip() {
|
|
|
1238
1367
|
{
|
|
1239
1368
|
horizontal: true,
|
|
1240
1369
|
showsHorizontalScrollIndicator: false,
|
|
1241
|
-
contentContainerStyle:
|
|
1242
|
-
style: [
|
|
1370
|
+
contentContainerStyle: styles8.scroll,
|
|
1371
|
+
style: [styles8.scrollStyle, { marginBottom: 8 }],
|
|
1243
1372
|
children: upload.queue.map((item) => {
|
|
1244
1373
|
const isError = item.status === "error";
|
|
1245
1374
|
const isUploading = item.status === "uploading" || item.status === "pending";
|
|
@@ -1250,7 +1379,7 @@ function UploadQueueStrip() {
|
|
|
1250
1379
|
if (item.status === "ready" || isError) upload.remove(item.localId);
|
|
1251
1380
|
else upload.cancel(item.localId);
|
|
1252
1381
|
};
|
|
1253
|
-
return /* @__PURE__ */ jsxs(View, { style:
|
|
1382
|
+
return /* @__PURE__ */ jsxs(View, { style: styles8.tileWrap, children: [
|
|
1254
1383
|
isImage && previewUri ? /* @__PURE__ */ jsx(ImageTile, { uri: previewUri, uploading: isUploading, error: isError }) : /* @__PURE__ */ jsx(
|
|
1255
1384
|
FileTile,
|
|
1256
1385
|
{
|
|
@@ -1265,13 +1394,13 @@ function UploadQueueStrip() {
|
|
|
1265
1394
|
{
|
|
1266
1395
|
onPress: handleClose,
|
|
1267
1396
|
hitSlop: 8,
|
|
1268
|
-
style: [
|
|
1397
|
+
style: [styles8.closeBtn, { backgroundColor: theme.colors.ink }],
|
|
1269
1398
|
accessibilityLabel: isError ? "Remove failed upload" : "Cancel upload",
|
|
1270
1399
|
children: /* @__PURE__ */ jsx(PoolseIcon, { name: "close", size: 11, color: theme.colors.onBrand })
|
|
1271
1400
|
}
|
|
1272
1401
|
) : null,
|
|
1273
|
-
isUploading ? /* @__PURE__ */ jsx(View, { style:
|
|
1274
|
-
isError ? /* @__PURE__ */ jsx(View, { style: [
|
|
1402
|
+
isUploading ? /* @__PURE__ */ jsx(View, { style: styles8.progressOverlay, children: /* @__PURE__ */ jsx(View, { style: [styles8.spinner, { borderColor: theme.colors.onBrand }] }) }) : null,
|
|
1403
|
+
isError ? /* @__PURE__ */ jsx(View, { style: [styles8.errorOverlay, { backgroundColor: "rgba(229,72,77,0.85)" }], children: /* @__PURE__ */ jsx(Text, { style: [styles8.errorText, { color: "#fff" }], children: "Failed" }) }) : null
|
|
1275
1404
|
] }, item.localId);
|
|
1276
1405
|
})
|
|
1277
1406
|
}
|
|
@@ -1284,7 +1413,7 @@ function ImageTile({ uri, uploading, error }) {
|
|
|
1284
1413
|
{
|
|
1285
1414
|
source: { uri },
|
|
1286
1415
|
style: [
|
|
1287
|
-
|
|
1416
|
+
styles8.imageTile,
|
|
1288
1417
|
{
|
|
1289
1418
|
backgroundColor: theme.colors.surface2,
|
|
1290
1419
|
borderColor: error ? theme.colors.error : "transparent",
|
|
@@ -1306,24 +1435,24 @@ function FileTile({
|
|
|
1306
1435
|
View,
|
|
1307
1436
|
{
|
|
1308
1437
|
style: [
|
|
1309
|
-
|
|
1438
|
+
styles8.fileTile,
|
|
1310
1439
|
{
|
|
1311
1440
|
backgroundColor: theme.colors.surface2,
|
|
1312
1441
|
borderColor: error ? theme.colors.error : uploading ? theme.colors.brand : theme.colors.border
|
|
1313
1442
|
}
|
|
1314
1443
|
],
|
|
1315
1444
|
children: [
|
|
1316
|
-
/* @__PURE__ */ jsx(View, { style: [
|
|
1317
|
-
/* @__PURE__ */ jsxs(View, { style:
|
|
1445
|
+
/* @__PURE__ */ jsx(View, { style: [styles8.fileIconBox, { backgroundColor: theme.colors.brandSoft }], children: /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 18, color: theme.colors.brand }) }),
|
|
1446
|
+
/* @__PURE__ */ jsxs(View, { style: styles8.fileInfo, children: [
|
|
1318
1447
|
/* @__PURE__ */ jsx(
|
|
1319
1448
|
Text,
|
|
1320
1449
|
{
|
|
1321
|
-
style: [
|
|
1450
|
+
style: [styles8.fileName, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
|
|
1322
1451
|
numberOfLines: 1,
|
|
1323
1452
|
children: filename
|
|
1324
1453
|
}
|
|
1325
1454
|
),
|
|
1326
|
-
/* @__PURE__ */ jsx(Text, { style: [
|
|
1455
|
+
/* @__PURE__ */ jsx(Text, { style: [styles8.fileSize, { color: theme.colors.ink3 }], children: formatBytes(size) })
|
|
1327
1456
|
] })
|
|
1328
1457
|
]
|
|
1329
1458
|
}
|
|
@@ -1337,7 +1466,7 @@ function formatBytes(n) {
|
|
|
1337
1466
|
return `${(n / 1024 / 1024 / 1024).toFixed(1)} GB`;
|
|
1338
1467
|
}
|
|
1339
1468
|
var TILE = 64;
|
|
1340
|
-
var
|
|
1469
|
+
var styles8 = StyleSheet.create({
|
|
1341
1470
|
scrollStyle: {
|
|
1342
1471
|
overflow: "visible"
|
|
1343
1472
|
},
|
|
@@ -1437,6 +1566,22 @@ var styles7 = StyleSheet.create({
|
|
|
1437
1566
|
fontWeight: "700"
|
|
1438
1567
|
}
|
|
1439
1568
|
});
|
|
1569
|
+
var FALLBACK = {
|
|
1570
|
+
top: Platform.OS === "ios" ? 50 : 24,
|
|
1571
|
+
bottom: Platform.OS === "ios" ? 34 : 0,
|
|
1572
|
+
left: 0,
|
|
1573
|
+
right: 0
|
|
1574
|
+
};
|
|
1575
|
+
var realHook = null;
|
|
1576
|
+
try {
|
|
1577
|
+
const mod = __require("react-native-safe-area-context");
|
|
1578
|
+
if (mod && typeof mod.useSafeAreaInsets === "function") {
|
|
1579
|
+
realHook = mod.useSafeAreaInsets;
|
|
1580
|
+
}
|
|
1581
|
+
} catch {
|
|
1582
|
+
}
|
|
1583
|
+
var useFallback = () => FALLBACK;
|
|
1584
|
+
var useSafeInsets = realHook ?? useFallback;
|
|
1440
1585
|
var MessageComposer = forwardRef(
|
|
1441
1586
|
function MessageComposer2({
|
|
1442
1587
|
onSend,
|
|
@@ -1448,13 +1593,32 @@ var MessageComposer = forwardRef(
|
|
|
1448
1593
|
labelFor,
|
|
1449
1594
|
attachments = true,
|
|
1450
1595
|
onAttachPress,
|
|
1451
|
-
keyboardOffset: _keyboardOffset = 0
|
|
1596
|
+
keyboardOffset: _keyboardOffset = 0,
|
|
1597
|
+
editingMessage = null,
|
|
1598
|
+
onCancelEdit,
|
|
1599
|
+
onSaveEdit
|
|
1452
1600
|
}, ref) {
|
|
1453
1601
|
const theme = usePoolseTheme();
|
|
1454
1602
|
const inputRef = useRef(null);
|
|
1455
1603
|
const [value, setValue] = useState("");
|
|
1456
1604
|
const [sending, setSending] = useState(false);
|
|
1605
|
+
const [keyboardOpen, setKeyboardOpen] = useState(false);
|
|
1457
1606
|
const upload = useSharedUpload();
|
|
1607
|
+
const insets = useSafeInsets();
|
|
1608
|
+
useEffect(() => {
|
|
1609
|
+
const show = Keyboard.addListener(
|
|
1610
|
+
Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow",
|
|
1611
|
+
() => setKeyboardOpen(true)
|
|
1612
|
+
);
|
|
1613
|
+
const hide = Keyboard.addListener(
|
|
1614
|
+
Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide",
|
|
1615
|
+
() => setKeyboardOpen(false)
|
|
1616
|
+
);
|
|
1617
|
+
return () => {
|
|
1618
|
+
show.remove();
|
|
1619
|
+
hide.remove();
|
|
1620
|
+
};
|
|
1621
|
+
}, []);
|
|
1458
1622
|
useImperativeHandle(
|
|
1459
1623
|
ref,
|
|
1460
1624
|
() => ({
|
|
@@ -1473,36 +1637,65 @@ var MessageComposer = forwardRef(
|
|
|
1473
1637
|
const isUploading = upload.queue.some(
|
|
1474
1638
|
(item) => item.status === "uploading" || item.status === "pending"
|
|
1475
1639
|
);
|
|
1476
|
-
const
|
|
1640
|
+
const previousDraftRef = useRef(null);
|
|
1641
|
+
const editingIdRef = useRef(null);
|
|
1642
|
+
useEffect(() => {
|
|
1643
|
+
const id = editingMessage?.id ?? null;
|
|
1644
|
+
if (id === editingIdRef.current) return;
|
|
1645
|
+
if (id) {
|
|
1646
|
+
previousDraftRef.current = value;
|
|
1647
|
+
setValue(editingMessage?.body ?? "");
|
|
1648
|
+
inputRef.current?.focus();
|
|
1649
|
+
} else {
|
|
1650
|
+
setValue(previousDraftRef.current ?? "");
|
|
1651
|
+
previousDraftRef.current = null;
|
|
1652
|
+
}
|
|
1653
|
+
editingIdRef.current = id;
|
|
1654
|
+
}, [editingMessage?.id, editingMessage?.body]);
|
|
1655
|
+
const isEditing = !!editingMessage;
|
|
1656
|
+
const editChanged = isEditing && value.trim() !== (editingMessage?.body?.trim() ?? "");
|
|
1657
|
+
const submitDisabled = disabled || sending || isUploading || value.trim().length === 0 || (isEditing ? !editChanged : value.trim().length === 0 && readyAttachmentIds.length === 0);
|
|
1477
1658
|
const handleSend = async () => {
|
|
1478
1659
|
if (submitDisabled) return;
|
|
1479
1660
|
const body = value.trim();
|
|
1480
|
-
const replyId = replyingTo?.id;
|
|
1481
|
-
const attachmentIds = readyAttachmentIds;
|
|
1482
|
-
setValue("");
|
|
1483
|
-
upload.reset();
|
|
1484
1661
|
setSending(true);
|
|
1485
1662
|
try {
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1663
|
+
if (isEditing && onSaveEdit) {
|
|
1664
|
+
await onSaveEdit(body);
|
|
1665
|
+
} else {
|
|
1666
|
+
const replyId = replyingTo?.id;
|
|
1667
|
+
const attachmentIds = readyAttachmentIds;
|
|
1668
|
+
setValue("");
|
|
1669
|
+
upload.reset();
|
|
1670
|
+
await onSend(body, {
|
|
1671
|
+
...replyId ? { quoted_message_id: replyId } : {},
|
|
1672
|
+
...attachmentIds.length > 0 ? { attachment_ids: attachmentIds } : {}
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1490
1675
|
} finally {
|
|
1491
1676
|
setSending(false);
|
|
1492
1677
|
}
|
|
1493
1678
|
};
|
|
1679
|
+
const paddingBottom = keyboardOpen ? 10 : Math.max(10, insets.bottom);
|
|
1494
1680
|
return /* @__PURE__ */ jsxs(
|
|
1495
1681
|
View,
|
|
1496
1682
|
{
|
|
1497
1683
|
style: [
|
|
1498
|
-
|
|
1684
|
+
styles9.wrap,
|
|
1499
1685
|
{
|
|
1500
1686
|
backgroundColor: theme.colors.surface,
|
|
1501
|
-
borderTopColor: theme.colors.border
|
|
1687
|
+
borderTopColor: theme.colors.border,
|
|
1688
|
+
paddingBottom
|
|
1502
1689
|
}
|
|
1503
1690
|
],
|
|
1504
1691
|
children: [
|
|
1505
|
-
|
|
1692
|
+
isEditing ? /* @__PURE__ */ jsx(
|
|
1693
|
+
EditChip,
|
|
1694
|
+
{
|
|
1695
|
+
message: editingMessage,
|
|
1696
|
+
...onCancelEdit ? { onCancel: onCancelEdit } : {}
|
|
1697
|
+
}
|
|
1698
|
+
) : replyingTo ? /* @__PURE__ */ jsx(
|
|
1506
1699
|
ReplyChip,
|
|
1507
1700
|
{
|
|
1508
1701
|
message: replyingTo,
|
|
@@ -1510,14 +1703,14 @@ var MessageComposer = forwardRef(
|
|
|
1510
1703
|
...onCancelReply ? { onCancel: onCancelReply } : {}
|
|
1511
1704
|
}
|
|
1512
1705
|
) : null,
|
|
1513
|
-
attachments ? /* @__PURE__ */ jsx(UploadQueueStrip, {}) : null,
|
|
1514
|
-
/* @__PURE__ */ jsxs(View, { style:
|
|
1706
|
+
attachments && !isEditing ? /* @__PURE__ */ jsx(UploadQueueStrip, {}) : null,
|
|
1707
|
+
/* @__PURE__ */ jsxs(View, { style: styles9.row, children: [
|
|
1515
1708
|
attachments && onAttachPress ? /* @__PURE__ */ jsx(
|
|
1516
1709
|
Pressable,
|
|
1517
1710
|
{
|
|
1518
1711
|
onPress: onAttachPress,
|
|
1519
1712
|
hitSlop: 8,
|
|
1520
|
-
style:
|
|
1713
|
+
style: styles9.iconBtn,
|
|
1521
1714
|
accessibilityLabel: "Attach file",
|
|
1522
1715
|
children: /* @__PURE__ */ jsx(PoolseIcon, { name: "attachment", size: 22, color: theme.colors.ink2 })
|
|
1523
1716
|
}
|
|
@@ -1536,7 +1729,7 @@ var MessageComposer = forwardRef(
|
|
|
1536
1729
|
multiline: true,
|
|
1537
1730
|
editable: !disabled,
|
|
1538
1731
|
style: [
|
|
1539
|
-
|
|
1732
|
+
styles9.input,
|
|
1540
1733
|
{
|
|
1541
1734
|
color: theme.colors.ink,
|
|
1542
1735
|
backgroundColor: theme.colors.surface2,
|
|
@@ -1553,7 +1746,7 @@ var MessageComposer = forwardRef(
|
|
|
1553
1746
|
onPress: handleSend,
|
|
1554
1747
|
disabled: submitDisabled,
|
|
1555
1748
|
style: [
|
|
1556
|
-
|
|
1749
|
+
styles9.sendBtn,
|
|
1557
1750
|
{
|
|
1558
1751
|
backgroundColor: submitDisabled ? theme.colors.ink3 : theme.colors.brand,
|
|
1559
1752
|
opacity: submitDisabled ? 0.6 : 1
|
|
@@ -1569,6 +1762,28 @@ var MessageComposer = forwardRef(
|
|
|
1569
1762
|
);
|
|
1570
1763
|
}
|
|
1571
1764
|
);
|
|
1765
|
+
function EditChip({ message, onCancel }) {
|
|
1766
|
+
const theme = usePoolseTheme();
|
|
1767
|
+
return /* @__PURE__ */ jsxs(
|
|
1768
|
+
View,
|
|
1769
|
+
{
|
|
1770
|
+
style: [
|
|
1771
|
+
styles9.replyChip,
|
|
1772
|
+
{
|
|
1773
|
+
backgroundColor: theme.colors.surface2,
|
|
1774
|
+
borderLeftColor: theme.colors.brand
|
|
1775
|
+
}
|
|
1776
|
+
],
|
|
1777
|
+
children: [
|
|
1778
|
+
/* @__PURE__ */ jsxs(View, { style: { flex: 1 }, children: [
|
|
1779
|
+
/* @__PURE__ */ jsx(Text, { style: [styles9.replyName, { color: theme.colors.brand }], children: "Editing message" }),
|
|
1780
|
+
/* @__PURE__ */ jsx(Text, { style: [styles9.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
|
|
1781
|
+
] }),
|
|
1782
|
+
onCancel ? /* @__PURE__ */ jsx(Pressable, { onPress: onCancel, hitSlop: 8, style: styles9.replyCancel, children: /* @__PURE__ */ jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
|
|
1783
|
+
]
|
|
1784
|
+
}
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1572
1787
|
function ReplyChip({
|
|
1573
1788
|
message,
|
|
1574
1789
|
labelFor,
|
|
@@ -1581,7 +1796,7 @@ function ReplyChip({
|
|
|
1581
1796
|
View,
|
|
1582
1797
|
{
|
|
1583
1798
|
style: [
|
|
1584
|
-
|
|
1799
|
+
styles9.replyChip,
|
|
1585
1800
|
{
|
|
1586
1801
|
backgroundColor: theme.colors.surface2,
|
|
1587
1802
|
borderLeftColor: theme.colors.brand
|
|
@@ -1589,27 +1804,26 @@ function ReplyChip({
|
|
|
1589
1804
|
],
|
|
1590
1805
|
children: [
|
|
1591
1806
|
/* @__PURE__ */ jsxs(View, { style: { flex: 1 }, children: [
|
|
1592
|
-
/* @__PURE__ */ jsxs(Text, { style: [
|
|
1807
|
+
/* @__PURE__ */ jsxs(Text, { style: [styles9.replyName, { color: theme.colors.brand }], children: [
|
|
1593
1808
|
"Replying to ",
|
|
1594
1809
|
senderName
|
|
1595
1810
|
] }),
|
|
1596
|
-
/* @__PURE__ */ jsx(Text, { style: [
|
|
1811
|
+
/* @__PURE__ */ jsx(Text, { style: [styles9.replyBody, { color: theme.colors.ink2 }], numberOfLines: 1, children: message.body ?? "\u2026" })
|
|
1597
1812
|
] }),
|
|
1598
|
-
onCancel ? /* @__PURE__ */ jsx(Pressable, { onPress: onCancel, hitSlop: 8, style:
|
|
1813
|
+
onCancel ? /* @__PURE__ */ jsx(Pressable, { onPress: onCancel, hitSlop: 8, style: styles9.replyCancel, children: /* @__PURE__ */ jsx(PoolseIcon, { name: "close", size: 16, color: theme.colors.ink2 }) }) : null
|
|
1599
1814
|
]
|
|
1600
1815
|
}
|
|
1601
1816
|
);
|
|
1602
1817
|
}
|
|
1603
|
-
var
|
|
1818
|
+
var styles9 = StyleSheet.create({
|
|
1604
1819
|
wrap: {
|
|
1605
1820
|
borderTopWidth: StyleSheet.hairlineWidth,
|
|
1606
1821
|
paddingTop: 8,
|
|
1607
|
-
paddingBottom: 10,
|
|
1608
1822
|
paddingHorizontal: 12
|
|
1609
|
-
//
|
|
1610
|
-
//
|
|
1611
|
-
//
|
|
1612
|
-
//
|
|
1823
|
+
// paddingBottom is applied inline — it follows the keyboard state
|
|
1824
|
+
// (10 px while open, max(10, safeBottomInset) while closed) so the
|
|
1825
|
+
// send/attach buttons stay clear of the home indicator without
|
|
1826
|
+
// double-padding above the keyboard.
|
|
1613
1827
|
},
|
|
1614
1828
|
row: {
|
|
1615
1829
|
flexDirection: "row",
|
|
@@ -1698,11 +1912,11 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
|
|
|
1698
1912
|
transparent: true,
|
|
1699
1913
|
animationType: "slide",
|
|
1700
1914
|
onRequestClose: () => setPickerOpen(false),
|
|
1701
|
-
children: /* @__PURE__ */ jsx(Pressable, { style:
|
|
1915
|
+
children: /* @__PURE__ */ jsx(Pressable, { style: styles10.backdrop, onPress: () => setPickerOpen(false), children: /* @__PURE__ */ jsxs(
|
|
1702
1916
|
View,
|
|
1703
1917
|
{
|
|
1704
1918
|
style: [
|
|
1705
|
-
|
|
1919
|
+
styles10.sheet,
|
|
1706
1920
|
{
|
|
1707
1921
|
backgroundColor: theme.colors.surface,
|
|
1708
1922
|
borderTopLeftRadius: theme.radii.lg,
|
|
@@ -1713,7 +1927,7 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
|
|
|
1713
1927
|
/* @__PURE__ */ jsx(
|
|
1714
1928
|
Text,
|
|
1715
1929
|
{
|
|
1716
|
-
style: [
|
|
1930
|
+
style: [styles10.title, { color: theme.colors.ink, fontFamily: theme.type.fontBody }],
|
|
1717
1931
|
children: "Mention a member"
|
|
1718
1932
|
}
|
|
1719
1933
|
),
|
|
@@ -1744,12 +1958,12 @@ function MentionInput({ conversationId, onSend, ...composerProps }) {
|
|
|
1744
1958
|
}
|
|
1745
1959
|
function MentionRow({ member, onPress }) {
|
|
1746
1960
|
const theme = usePoolseTheme();
|
|
1747
|
-
return /* @__PURE__ */ jsxs(Pressable, { onPress, style:
|
|
1961
|
+
return /* @__PURE__ */ jsxs(Pressable, { onPress, style: styles10.row, children: [
|
|
1748
1962
|
/* @__PURE__ */ jsx(Avatar, { src: null, name: member.external_id, seed: member.external_id, size: "sm" }),
|
|
1749
|
-
/* @__PURE__ */ jsx(Text, { style: [
|
|
1963
|
+
/* @__PURE__ */ jsx(Text, { style: [styles10.rowText, { color: theme.colors.ink, fontFamily: theme.type.fontBody }], children: member.external_id })
|
|
1750
1964
|
] });
|
|
1751
1965
|
}
|
|
1752
|
-
var
|
|
1966
|
+
var styles10 = StyleSheet.create({
|
|
1753
1967
|
backdrop: {
|
|
1754
1968
|
flex: 1,
|
|
1755
1969
|
backgroundColor: "rgba(0,0,0,0.35)",
|
|
@@ -1776,138 +1990,134 @@ var styles9 = StyleSheet.create({
|
|
|
1776
1990
|
fontSize: 14
|
|
1777
1991
|
}
|
|
1778
1992
|
});
|
|
1993
|
+
var AT_BOTTOM_THRESHOLD = 32;
|
|
1779
1994
|
function MessageList({
|
|
1780
1995
|
messages,
|
|
1996
|
+
items,
|
|
1781
1997
|
loading = false,
|
|
1782
1998
|
hasMore = false,
|
|
1783
1999
|
onLoadMore,
|
|
1784
2000
|
renderItem,
|
|
1785
2001
|
ListHeader = null,
|
|
1786
2002
|
ListFooter = null,
|
|
1787
|
-
emptyState
|
|
2003
|
+
emptyState,
|
|
2004
|
+
meId = null
|
|
1788
2005
|
}) {
|
|
1789
2006
|
const theme = usePoolseTheme();
|
|
1790
|
-
const
|
|
2007
|
+
const flatListRef = useRef(null);
|
|
2008
|
+
const [atBottom, setAtBottom] = useState(true);
|
|
2009
|
+
const [newCount, setNewCount] = useState(0);
|
|
2010
|
+
const lastTailIdRef = useRef(null);
|
|
2011
|
+
const sourceItems = items ?? messages.map((msg) => ({ kind: "msg", msg }));
|
|
2012
|
+
const data = [...sourceItems].reverse();
|
|
2013
|
+
useEffect(() => {
|
|
2014
|
+
const tail = messages[messages.length - 1];
|
|
2015
|
+
if (!tail) return;
|
|
2016
|
+
const tailId = tail.id;
|
|
2017
|
+
if (tailId === lastTailIdRef.current) return;
|
|
2018
|
+
const isFirstSet = lastTailIdRef.current === null;
|
|
2019
|
+
lastTailIdRef.current = tailId;
|
|
2020
|
+
if (atBottom || isFirstSet) {
|
|
2021
|
+
setNewCount(0);
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
if (meId !== null && tail.sender_id === meId) return;
|
|
2025
|
+
setNewCount((n) => n + 1);
|
|
2026
|
+
}, [messages, atBottom, meId]);
|
|
2027
|
+
const handleScroll = useCallback((e) => {
|
|
2028
|
+
const y = e.nativeEvent.contentOffset.y;
|
|
2029
|
+
const next = y <= AT_BOTTOM_THRESHOLD;
|
|
2030
|
+
setAtBottom((prev) => prev === next ? prev : next);
|
|
2031
|
+
if (next) setNewCount(0);
|
|
2032
|
+
}, []);
|
|
2033
|
+
const scrollToBottom = useCallback(() => {
|
|
2034
|
+
flatListRef.current?.scrollToOffset({ offset: 0, animated: true });
|
|
2035
|
+
setNewCount(0);
|
|
2036
|
+
}, []);
|
|
1791
2037
|
const handleRender = useCallback(
|
|
1792
|
-
({ item, index }) =>
|
|
1793
|
-
|
|
2038
|
+
({ item, index }) => {
|
|
2039
|
+
if (item.kind === "separator") return item.node;
|
|
2040
|
+
const sourceIndex = data.length - 1 - index;
|
|
2041
|
+
return renderItem(item.msg, sourceIndex);
|
|
2042
|
+
},
|
|
2043
|
+
[renderItem, data.length]
|
|
1794
2044
|
);
|
|
1795
2045
|
if (loading && messages.length === 0) {
|
|
1796
|
-
return /* @__PURE__ */ jsx(View, { style: [
|
|
2046
|
+
return /* @__PURE__ */ jsx(View, { style: [styles11.placeholder, { backgroundColor: theme.colors.paper }], children: /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.colors.brand }) });
|
|
1797
2047
|
}
|
|
1798
2048
|
if (messages.length === 0) {
|
|
1799
|
-
return /* @__PURE__ */ jsx(View, { style: [
|
|
2049
|
+
return /* @__PURE__ */ jsx(View, { style: [styles11.placeholder, { backgroundColor: theme.colors.paper }], children: typeof emptyState === "string" ? /* @__PURE__ */ jsx(Text, { style: { color: theme.colors.ink3, fontFamily: theme.type.fontBody }, children: emptyState }) : emptyState ?? null });
|
|
1800
2050
|
}
|
|
1801
|
-
return /* @__PURE__ */
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
2051
|
+
return /* @__PURE__ */ jsxs(View, { style: styles11.root, children: [
|
|
2052
|
+
/* @__PURE__ */ jsx(
|
|
2053
|
+
FlatList,
|
|
2054
|
+
{
|
|
2055
|
+
ref: flatListRef,
|
|
2056
|
+
inverted: true,
|
|
2057
|
+
data,
|
|
2058
|
+
keyExtractor: (item) => item.kind === "msg" ? item.msg.id : item.id,
|
|
2059
|
+
renderItem: handleRender,
|
|
2060
|
+
onEndReached: hasMore ? onLoadMore : void 0,
|
|
2061
|
+
onEndReachedThreshold: 0.3,
|
|
2062
|
+
onScroll: handleScroll,
|
|
2063
|
+
scrollEventThrottle: 32,
|
|
2064
|
+
ListHeaderComponent: ListHeader,
|
|
2065
|
+
ListFooterComponent: /* @__PURE__ */ jsxs(View, { children: [
|
|
2066
|
+
hasMore && loading ? /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.colors.ink3, style: { marginVertical: 8 } }) : null,
|
|
2067
|
+
ListFooter
|
|
2068
|
+
] }),
|
|
2069
|
+
style: { backgroundColor: theme.colors.paper },
|
|
2070
|
+
contentContainerStyle: { paddingVertical: 8 }
|
|
2071
|
+
}
|
|
2072
|
+
),
|
|
2073
|
+
!atBottom && newCount > 0 ? /* @__PURE__ */ jsxs(
|
|
2074
|
+
Pressable,
|
|
2075
|
+
{
|
|
2076
|
+
onPress: scrollToBottom,
|
|
2077
|
+
style: [
|
|
2078
|
+
styles11.jumpButton,
|
|
2079
|
+
{
|
|
2080
|
+
backgroundColor: theme.colors.brand
|
|
2081
|
+
},
|
|
2082
|
+
theme.shadows.sm
|
|
2083
|
+
],
|
|
2084
|
+
accessibilityLabel: `${newCount} new ${newCount === 1 ? "message" : "messages"}, jump to latest`,
|
|
2085
|
+
children: [
|
|
2086
|
+
/* @__PURE__ */ jsx(PoolseIcon, { name: "chevron-down", size: 14, color: theme.colors.onBrand }),
|
|
2087
|
+
/* @__PURE__ */ jsxs(Text, { style: [styles11.jumpText, { color: theme.colors.onBrand }], children: [
|
|
2088
|
+
newCount,
|
|
2089
|
+
" new ",
|
|
2090
|
+
newCount === 1 ? "message" : "messages"
|
|
2091
|
+
] })
|
|
2092
|
+
]
|
|
2093
|
+
}
|
|
2094
|
+
) : null
|
|
2095
|
+
] });
|
|
1819
2096
|
}
|
|
1820
|
-
var
|
|
2097
|
+
var styles11 = StyleSheet.create({
|
|
2098
|
+
root: {
|
|
2099
|
+
flex: 1
|
|
2100
|
+
},
|
|
1821
2101
|
placeholder: {
|
|
1822
2102
|
flex: 1,
|
|
1823
2103
|
alignItems: "center",
|
|
1824
2104
|
justifyContent: "center",
|
|
1825
2105
|
padding: 24
|
|
1826
|
-
}
|
|
1827
|
-
});
|
|
1828
|
-
function EditableMessageBubble({
|
|
1829
|
-
message,
|
|
1830
|
-
currentUserId,
|
|
1831
|
-
onCancel,
|
|
1832
|
-
onSave
|
|
1833
|
-
}) {
|
|
1834
|
-
const theme = usePoolseTheme();
|
|
1835
|
-
const isSelf = currentUserId !== null && message.sender_id === currentUserId;
|
|
1836
|
-
const [value, setValue] = useState(message.body ?? "");
|
|
1837
|
-
const [saving, setSaving] = useState(false);
|
|
1838
|
-
const submit = async () => {
|
|
1839
|
-
if (saving || !value.trim()) return;
|
|
1840
|
-
setSaving(true);
|
|
1841
|
-
try {
|
|
1842
|
-
await onSave(value.trim());
|
|
1843
|
-
} finally {
|
|
1844
|
-
setSaving(false);
|
|
1845
|
-
}
|
|
1846
|
-
};
|
|
1847
|
-
const bg = isSelf ? theme.colors.selfBubble : theme.colors.otherBubble;
|
|
1848
|
-
const ink = isSelf ? theme.colors.selfBubbleText : theme.colors.otherBubbleText;
|
|
1849
|
-
return /* @__PURE__ */ jsx(
|
|
1850
|
-
View,
|
|
1851
|
-
{
|
|
1852
|
-
style: [
|
|
1853
|
-
styles11.wrap,
|
|
1854
|
-
{
|
|
1855
|
-
alignSelf: isSelf ? "flex-end" : "flex-start",
|
|
1856
|
-
maxWidth: "80%"
|
|
1857
|
-
}
|
|
1858
|
-
],
|
|
1859
|
-
children: /* @__PURE__ */ jsxs(
|
|
1860
|
-
View,
|
|
1861
|
-
{
|
|
1862
|
-
style: [
|
|
1863
|
-
styles11.bubble,
|
|
1864
|
-
{
|
|
1865
|
-
backgroundColor: bg,
|
|
1866
|
-
borderRadius: theme.radii.bubble,
|
|
1867
|
-
paddingHorizontal: theme.spacing.md,
|
|
1868
|
-
paddingVertical: theme.spacing.sm
|
|
1869
|
-
},
|
|
1870
|
-
theme.shadows.sm
|
|
1871
|
-
],
|
|
1872
|
-
children: [
|
|
1873
|
-
/* @__PURE__ */ jsx(
|
|
1874
|
-
TextInput,
|
|
1875
|
-
{
|
|
1876
|
-
value,
|
|
1877
|
-
onChangeText: setValue,
|
|
1878
|
-
multiline: true,
|
|
1879
|
-
autoFocus: true,
|
|
1880
|
-
style: {
|
|
1881
|
-
color: ink,
|
|
1882
|
-
fontFamily: theme.type.fontBody,
|
|
1883
|
-
fontSize: theme.type.bodySize,
|
|
1884
|
-
minHeight: 24
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
),
|
|
1888
|
-
/* @__PURE__ */ jsxs(View, { style: styles11.actions, children: [
|
|
1889
|
-
/* @__PURE__ */ jsx(Pressable, { onPress: onCancel, hitSlop: 6, children: /* @__PURE__ */ jsx(Text, { style: [styles11.btn, { color: ink, opacity: 0.7 }], children: "Cancel" }) }),
|
|
1890
|
-
/* @__PURE__ */ jsx(Pressable, { onPress: submit, disabled: saving, hitSlop: 6, children: /* @__PURE__ */ jsx(Text, { style: [styles11.btn, { color: ink, fontWeight: "700" }], children: saving ? "Saving\u2026" : "Save" }) })
|
|
1891
|
-
] })
|
|
1892
|
-
]
|
|
1893
|
-
}
|
|
1894
|
-
)
|
|
1895
|
-
}
|
|
1896
|
-
);
|
|
1897
|
-
}
|
|
1898
|
-
var styles11 = StyleSheet.create({
|
|
1899
|
-
wrap: {
|
|
1900
|
-
marginBottom: 4
|
|
1901
2106
|
},
|
|
1902
|
-
|
|
1903
|
-
|
|
2107
|
+
jumpButton: {
|
|
2108
|
+
position: "absolute",
|
|
2109
|
+
alignSelf: "center",
|
|
2110
|
+
bottom: 12,
|
|
1904
2111
|
flexDirection: "row",
|
|
1905
|
-
|
|
1906
|
-
gap:
|
|
1907
|
-
|
|
2112
|
+
alignItems: "center",
|
|
2113
|
+
gap: 6,
|
|
2114
|
+
paddingHorizontal: 14,
|
|
2115
|
+
paddingVertical: 8,
|
|
2116
|
+
borderRadius: 999
|
|
1908
2117
|
},
|
|
1909
|
-
|
|
1910
|
-
fontSize: 13
|
|
2118
|
+
jumpText: {
|
|
2119
|
+
fontSize: 13,
|
|
2120
|
+
fontWeight: "600"
|
|
1911
2121
|
}
|
|
1912
2122
|
});
|
|
1913
2123
|
var DEFAULT_REACTION_EMOJIS = ["\u{1F44D}", "\u2764\uFE0F", "\u{1F602}", "\u{1F389}", "\u{1F64F}", "\u{1F525}"];
|
|
@@ -2370,6 +2580,77 @@ var styles14 = StyleSheet.create({
|
|
|
2370
2580
|
borderRadius: 20
|
|
2371
2581
|
}
|
|
2372
2582
|
});
|
|
2583
|
+
var MarkdownModule;
|
|
2584
|
+
var warned = false;
|
|
2585
|
+
function loadMarkdown() {
|
|
2586
|
+
if (MarkdownModule === void 0) {
|
|
2587
|
+
try {
|
|
2588
|
+
MarkdownModule = __require("react-native-markdown-display");
|
|
2589
|
+
} catch {
|
|
2590
|
+
MarkdownModule = null;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
const fn = MarkdownModule?.default;
|
|
2594
|
+
if (!fn) {
|
|
2595
|
+
if (!warned) {
|
|
2596
|
+
warned = true;
|
|
2597
|
+
console.warn(
|
|
2598
|
+
"[@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."
|
|
2599
|
+
);
|
|
2600
|
+
}
|
|
2601
|
+
return null;
|
|
2602
|
+
}
|
|
2603
|
+
return fn;
|
|
2604
|
+
}
|
|
2605
|
+
function renderMarkdown(body, args) {
|
|
2606
|
+
const Markdown = loadMarkdown();
|
|
2607
|
+
if (!Markdown) return null;
|
|
2608
|
+
const baseText = {
|
|
2609
|
+
color: args.textColor,
|
|
2610
|
+
fontFamily: args.fontFamily,
|
|
2611
|
+
fontSize: args.fontSize,
|
|
2612
|
+
lineHeight: args.lineHeight
|
|
2613
|
+
};
|
|
2614
|
+
const style = {
|
|
2615
|
+
body: baseText,
|
|
2616
|
+
paragraph: { ...baseText, marginTop: 0, marginBottom: 0 },
|
|
2617
|
+
text: baseText,
|
|
2618
|
+
strong: { ...baseText, fontWeight: "700" },
|
|
2619
|
+
em: { ...baseText, fontStyle: "italic" },
|
|
2620
|
+
s: { ...baseText, textDecorationLine: "line-through" },
|
|
2621
|
+
link: { color: args.linkColor, textDecorationLine: "underline" },
|
|
2622
|
+
code_inline: {
|
|
2623
|
+
...baseText,
|
|
2624
|
+
backgroundColor: "rgba(127,127,127,0.18)",
|
|
2625
|
+
borderRadius: 4,
|
|
2626
|
+
paddingHorizontal: 4
|
|
2627
|
+
},
|
|
2628
|
+
code_block: {
|
|
2629
|
+
...baseText,
|
|
2630
|
+
backgroundColor: "rgba(127,127,127,0.18)",
|
|
2631
|
+
padding: 8,
|
|
2632
|
+
borderRadius: 6
|
|
2633
|
+
},
|
|
2634
|
+
fence: {
|
|
2635
|
+
...baseText,
|
|
2636
|
+
backgroundColor: "rgba(127,127,127,0.18)",
|
|
2637
|
+
padding: 8,
|
|
2638
|
+
borderRadius: 6
|
|
2639
|
+
},
|
|
2640
|
+
bullet_list: { marginTop: 2, marginBottom: 2 },
|
|
2641
|
+
ordered_list: { marginTop: 2, marginBottom: 2 },
|
|
2642
|
+
list_item: baseText,
|
|
2643
|
+
blockquote: {
|
|
2644
|
+
...baseText,
|
|
2645
|
+
borderLeftWidth: 3,
|
|
2646
|
+
borderLeftColor: args.linkColor,
|
|
2647
|
+
paddingLeft: 8,
|
|
2648
|
+
marginVertical: 4,
|
|
2649
|
+
opacity: 0.85
|
|
2650
|
+
}
|
|
2651
|
+
};
|
|
2652
|
+
return createElement(Markdown, { style, onLinkPress: args.onLinkPress }, body);
|
|
2653
|
+
}
|
|
2373
2654
|
function MessageBubble({
|
|
2374
2655
|
message,
|
|
2375
2656
|
currentUserId,
|
|
@@ -2380,7 +2661,8 @@ function MessageBubble({
|
|
|
2380
2661
|
maxBodyLength = 0,
|
|
2381
2662
|
showSenderName = false,
|
|
2382
2663
|
actionsTrigger,
|
|
2383
|
-
showAttachments = true
|
|
2664
|
+
showAttachments = true,
|
|
2665
|
+
markdown = true
|
|
2384
2666
|
}) {
|
|
2385
2667
|
const theme = usePoolseTheme();
|
|
2386
2668
|
const { width: screenWidth } = useWindowDimensions();
|
|
@@ -2465,22 +2747,53 @@ function MessageBubble({
|
|
|
2465
2747
|
) : null,
|
|
2466
2748
|
renderAttachments && images.length > 0 ? /* @__PURE__ */ jsx(ImageMosaic, { images, maxWidth: contentMaxWidth }) : null,
|
|
2467
2749
|
renderAttachments && files.length > 0 ? /* @__PURE__ */ jsx(View, { style: { gap: theme.spacing.xs, marginBottom: theme.spacing.xs }, children: files.map((att) => /* @__PURE__ */ jsx(AttachmentPreview, { attachment: att, maxWidth: contentMaxWidth }, att.id)) }) : null,
|
|
2468
|
-
body.length > 0 ?
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
styles15.body,
|
|
2750
|
+
body.length > 0 ? (() => {
|
|
2751
|
+
if (isDeleted) {
|
|
2752
|
+
return /* @__PURE__ */ jsx(
|
|
2753
|
+
Text,
|
|
2473
2754
|
{
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2755
|
+
style: [
|
|
2756
|
+
styles15.body,
|
|
2757
|
+
{
|
|
2758
|
+
color: theme.colors.ink3,
|
|
2759
|
+
fontFamily: theme.type.fontBody,
|
|
2760
|
+
fontSize: theme.type.bodySize,
|
|
2761
|
+
lineHeight: theme.type.lineHeight,
|
|
2762
|
+
fontStyle: "italic"
|
|
2763
|
+
}
|
|
2764
|
+
],
|
|
2765
|
+
children: "Message deleted"
|
|
2479
2766
|
}
|
|
2480
|
-
|
|
2481
|
-
children: isDeleted ? "Message deleted" : truncated
|
|
2767
|
+
);
|
|
2482
2768
|
}
|
|
2483
|
-
|
|
2769
|
+
const md = markdown ? renderMarkdown(truncated, {
|
|
2770
|
+
textColor,
|
|
2771
|
+
linkColor: isSelf ? theme.colors.onBrand : theme.colors.brand,
|
|
2772
|
+
fontFamily: theme.type.fontBody,
|
|
2773
|
+
fontSize: theme.type.bodySize,
|
|
2774
|
+
lineHeight: theme.type.lineHeight,
|
|
2775
|
+
onLinkPress: (url) => {
|
|
2776
|
+
void Linking.openURL(url).catch(() => void 0);
|
|
2777
|
+
return false;
|
|
2778
|
+
}
|
|
2779
|
+
}) : null;
|
|
2780
|
+
if (md) return md;
|
|
2781
|
+
return /* @__PURE__ */ jsx(
|
|
2782
|
+
Text,
|
|
2783
|
+
{
|
|
2784
|
+
style: [
|
|
2785
|
+
styles15.body,
|
|
2786
|
+
{
|
|
2787
|
+
color: textColor,
|
|
2788
|
+
fontFamily: theme.type.fontBody,
|
|
2789
|
+
fontSize: theme.type.bodySize,
|
|
2790
|
+
lineHeight: theme.type.lineHeight
|
|
2791
|
+
}
|
|
2792
|
+
],
|
|
2793
|
+
children: truncated
|
|
2794
|
+
}
|
|
2795
|
+
);
|
|
2796
|
+
})() : null,
|
|
2484
2797
|
maxBodyLength > 0 && body.length > maxBodyLength ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setExpanded((e) => !e), children: /* @__PURE__ */ jsx(
|
|
2485
2798
|
Text,
|
|
2486
2799
|
{
|
|
@@ -2573,9 +2886,9 @@ function formatTime(iso) {
|
|
|
2573
2886
|
try {
|
|
2574
2887
|
const d = new Date(iso);
|
|
2575
2888
|
const now = /* @__PURE__ */ new Date();
|
|
2576
|
-
const
|
|
2889
|
+
const sameDay2 = isSameDay(d, now);
|
|
2577
2890
|
const time = d.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
|
|
2578
|
-
if (
|
|
2891
|
+
if (sameDay2) return time;
|
|
2579
2892
|
const yesterday = new Date(now);
|
|
2580
2893
|
yesterday.setDate(now.getDate() - 1);
|
|
2581
2894
|
if (isSameDay(d, yesterday)) return `Yesterday ${time}`;
|
|
@@ -2683,12 +2996,26 @@ function ReactionStrip({
|
|
|
2683
2996
|
styles16.chip,
|
|
2684
2997
|
{
|
|
2685
2998
|
backgroundColor: meReacted ? theme.colors.brandSoft : theme.colors.surface2,
|
|
2686
|
-
borderColor: meReacted ? theme.colors.brand : theme.colors.border
|
|
2999
|
+
borderColor: meReacted ? theme.colors.brand : theme.colors.border,
|
|
3000
|
+
borderWidth: meReacted ? 1.5 : 1,
|
|
3001
|
+
transform: meReacted ? [{ scale: 1.04 }] : void 0
|
|
2687
3002
|
}
|
|
2688
3003
|
],
|
|
2689
3004
|
children: [
|
|
2690
3005
|
/* @__PURE__ */ jsx(Text, { style: styles16.emoji, children: emoji }),
|
|
2691
|
-
/* @__PURE__ */ jsx(
|
|
3006
|
+
/* @__PURE__ */ jsx(
|
|
3007
|
+
Text,
|
|
3008
|
+
{
|
|
3009
|
+
style: [
|
|
3010
|
+
styles16.count,
|
|
3011
|
+
{
|
|
3012
|
+
color: meReacted ? theme.colors.brand : theme.colors.ink2,
|
|
3013
|
+
fontWeight: meReacted ? "700" : "600"
|
|
3014
|
+
}
|
|
3015
|
+
],
|
|
3016
|
+
children: userArr.length
|
|
3017
|
+
}
|
|
3018
|
+
)
|
|
2692
3019
|
]
|
|
2693
3020
|
},
|
|
2694
3021
|
emoji
|
|
@@ -2741,7 +3068,6 @@ var styles16 = StyleSheet.create({
|
|
|
2741
3068
|
paddingHorizontal: 9,
|
|
2742
3069
|
paddingVertical: 4,
|
|
2743
3070
|
borderRadius: 14,
|
|
2744
|
-
borderWidth: 1,
|
|
2745
3071
|
gap: 5,
|
|
2746
3072
|
minHeight: 26,
|
|
2747
3073
|
// Subtle elevation so chips lift off the surface
|
|
@@ -2757,7 +3083,6 @@ var styles16 = StyleSheet.create({
|
|
|
2757
3083
|
},
|
|
2758
3084
|
count: {
|
|
2759
3085
|
fontSize: 12,
|
|
2760
|
-
fontWeight: "600",
|
|
2761
3086
|
fontVariant: ["tabular-nums"]
|
|
2762
3087
|
},
|
|
2763
3088
|
backdrop: {
|
|
@@ -2795,11 +3120,10 @@ function MessageRow({
|
|
|
2795
3120
|
actions = true,
|
|
2796
3121
|
threads = true,
|
|
2797
3122
|
quotations = true,
|
|
3123
|
+
markdown = true,
|
|
2798
3124
|
readState,
|
|
2799
|
-
|
|
3125
|
+
isEditing = false,
|
|
2800
3126
|
onStartEdit,
|
|
2801
|
-
onCancelEdit,
|
|
2802
|
-
onSaveEdit,
|
|
2803
3127
|
onDelete,
|
|
2804
3128
|
onOpenThread,
|
|
2805
3129
|
onQuote,
|
|
@@ -2820,7 +3144,7 @@ function MessageRow({
|
|
|
2820
3144
|
...msg.reactions ? { initialReactions: msg.reactions } : {}
|
|
2821
3145
|
});
|
|
2822
3146
|
const translateX = useRef(new Animated.Value(0)).current;
|
|
2823
|
-
const swipeEnabled = quotations && !!onQuote && !
|
|
3147
|
+
const swipeEnabled = quotations && !!onQuote && !isEditing;
|
|
2824
3148
|
const panResponder = useMemo(
|
|
2825
3149
|
() => PanResponder.create({
|
|
2826
3150
|
// Tap / long-press still go to the inner Pressable.
|
|
@@ -2895,137 +3219,149 @@ function MessageRow({
|
|
|
2895
3219
|
const rowSender = useUser(showAvatarSlot ? msg.sender_external_id : null);
|
|
2896
3220
|
const avatarUrl = msg.sender_external_id ? avatarFor?.(msg.sender_external_id) ?? rowSender.profile?.avatarUrl ?? null : null;
|
|
2897
3221
|
const senderLabel = msg.sender_external_id ? labelFor?.(msg.sender_external_id) ?? rowSender.profile?.displayName ?? msg.sender_external_id : null;
|
|
2898
|
-
return /* @__PURE__ */ jsxs(
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
children: /* @__PURE__ */ jsx(PoolseIcon, { name: "reply", size: 16, color: theme.colors.brand })
|
|
2923
|
-
}
|
|
2924
|
-
) : null,
|
|
2925
|
-
/* @__PURE__ */ jsx(
|
|
2926
|
-
Animated.View,
|
|
2927
|
-
{
|
|
2928
|
-
...swipeEnabled ? panResponder.panHandlers : {},
|
|
2929
|
-
style: { transform: [{ translateX }] },
|
|
2930
|
-
children: /* @__PURE__ */ jsx(
|
|
2931
|
-
Pressable,
|
|
3222
|
+
return /* @__PURE__ */ jsxs(
|
|
3223
|
+
View,
|
|
3224
|
+
{
|
|
3225
|
+
style: [
|
|
3226
|
+
styles17.row,
|
|
3227
|
+
{ alignSelf: isSelf ? "flex-end" : "flex-start" },
|
|
3228
|
+
// Lift this row above the dim backdrop the parent renders
|
|
3229
|
+
// during edit mode. zIndex covers iOS / web; elevation does
|
|
3230
|
+
// the equivalent on Android.
|
|
3231
|
+
isEditing ? styles17.rowEditing : null
|
|
3232
|
+
],
|
|
3233
|
+
children: [
|
|
3234
|
+
showAvatarSlot ? /* @__PURE__ */ jsx(View, { style: styles17.avatarSlot, children: showAvatarRender ? /* @__PURE__ */ jsx(
|
|
3235
|
+
Avatar,
|
|
3236
|
+
{
|
|
3237
|
+
src: avatarUrl ?? null,
|
|
3238
|
+
name: senderLabel,
|
|
3239
|
+
seed: msg.sender_external_id ?? null,
|
|
3240
|
+
size: "sm"
|
|
3241
|
+
}
|
|
3242
|
+
) : null }) : null,
|
|
3243
|
+
/* @__PURE__ */ jsxs(View, { style: styles17.bubbleCol, children: [
|
|
3244
|
+
swipeEnabled ? /* @__PURE__ */ jsx(
|
|
3245
|
+
Animated.View,
|
|
2932
3246
|
{
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
accessibilityRole: "button",
|
|
2938
|
-
accessibilityLabel: "Message actions",
|
|
2939
|
-
children: editing ? /* @__PURE__ */ jsx(
|
|
2940
|
-
EditableMessageBubble,
|
|
3247
|
+
pointerEvents: "none",
|
|
3248
|
+
style: [
|
|
3249
|
+
styles17.replyHint,
|
|
3250
|
+
isSelf ? styles17.replyHintRight : styles17.replyHintLeft,
|
|
2941
3251
|
{
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
onSave: onSaveEdit ?? (() => void 0)
|
|
3252
|
+
opacity: replyIconOpacity,
|
|
3253
|
+
transform: [{ scale: replyIconScale }],
|
|
3254
|
+
backgroundColor: theme.colors.surface2
|
|
2946
3255
|
}
|
|
2947
|
-
|
|
2948
|
-
|
|
3256
|
+
],
|
|
3257
|
+
children: /* @__PURE__ */ jsx(PoolseIcon, { name: "reply", size: 16, color: theme.colors.brand })
|
|
3258
|
+
}
|
|
3259
|
+
) : null,
|
|
3260
|
+
/* @__PURE__ */ jsx(
|
|
3261
|
+
Animated.View,
|
|
3262
|
+
{
|
|
3263
|
+
...swipeEnabled ? panResponder.panHandlers : {},
|
|
3264
|
+
style: { transform: [{ translateX }] },
|
|
3265
|
+
children: /* @__PURE__ */ jsx(
|
|
3266
|
+
Pressable,
|
|
2949
3267
|
{
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
showAttachments: attachments,
|
|
2959
|
-
actionsTrigger: actions ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsx(
|
|
2960
|
-
PoolseIcon,
|
|
3268
|
+
onLongPress: () => {
|
|
3269
|
+
if (!actions) return;
|
|
3270
|
+
setActionsOpen(true);
|
|
3271
|
+
},
|
|
3272
|
+
accessibilityRole: "button",
|
|
3273
|
+
accessibilityLabel: "Message actions",
|
|
3274
|
+
children: /* @__PURE__ */ jsx(
|
|
3275
|
+
MessageBubble,
|
|
2961
3276
|
{
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
3277
|
+
message: msg,
|
|
3278
|
+
currentUserId: meId,
|
|
3279
|
+
...readState ? { readState } : {},
|
|
3280
|
+
...labelFor ? { labelFor } : {},
|
|
3281
|
+
...onQuotedClick ? { onQuotedClick } : {},
|
|
3282
|
+
groupPosition,
|
|
3283
|
+
maxBodyLength,
|
|
3284
|
+
showSenderName,
|
|
3285
|
+
showAttachments: attachments,
|
|
3286
|
+
markdown,
|
|
3287
|
+
actionsTrigger: actions ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setActionsOpen(true), hitSlop: 8, children: /* @__PURE__ */ jsx(
|
|
3288
|
+
PoolseIcon,
|
|
3289
|
+
{
|
|
3290
|
+
name: "chevron-down",
|
|
3291
|
+
size: 14,
|
|
3292
|
+
color: isSelf ? theme.colors.onBrand : theme.colors.ink3
|
|
3293
|
+
}
|
|
3294
|
+
) }) : null
|
|
2965
3295
|
}
|
|
2966
|
-
)
|
|
3296
|
+
)
|
|
2967
3297
|
}
|
|
2968
3298
|
)
|
|
2969
3299
|
}
|
|
2970
|
-
)
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
}
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
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
|
-
|
|
3300
|
+
),
|
|
3301
|
+
threads && (msg.reply_count ?? 0) > 0 && onOpenThread ? /* @__PURE__ */ jsx(Pressable, { onPress: onOpenThread, style: styles17.threadPill, children: /* @__PURE__ */ jsxs(Text, { style: [styles17.threadPillText, { color: theme.colors.brand }], children: [
|
|
3302
|
+
"\u{1F4AC} ",
|
|
3303
|
+
msg.reply_count,
|
|
3304
|
+
" ",
|
|
3305
|
+
msg.reply_count === 1 ? "reply" : "replies"
|
|
3306
|
+
] }) }) : null,
|
|
3307
|
+
reactions ? /* @__PURE__ */ jsx(View, { style: [styles17.reactionsRow, { alignSelf: isSelf ? "flex-end" : "flex-start" }], children: /* @__PURE__ */ jsx(
|
|
3308
|
+
ReactionStrip,
|
|
3309
|
+
{
|
|
3310
|
+
messageId: msg.id,
|
|
3311
|
+
conversationId: msg.conversation_id,
|
|
3312
|
+
meId,
|
|
3313
|
+
...msg.reactions ? { initialReactions: msg.reactions } : {}
|
|
3314
|
+
}
|
|
3315
|
+
) }) : null
|
|
3316
|
+
] }),
|
|
3317
|
+
actions && actionsOpen ? /* @__PURE__ */ jsx(
|
|
3318
|
+
MessageActions,
|
|
3319
|
+
{
|
|
3320
|
+
visible: actionsOpen,
|
|
3321
|
+
onClose: () => setActionsOpen(false),
|
|
3322
|
+
isSelf,
|
|
3323
|
+
...reactions ? {
|
|
3324
|
+
// Inline quick-react row at the top of the sheet —
|
|
3325
|
+
// one tap = react + close, no second modal needed.
|
|
3326
|
+
onPickReaction: (emoji) => {
|
|
3327
|
+
void reactionsApi.addReaction(emoji);
|
|
3328
|
+
}
|
|
3329
|
+
} : {},
|
|
3330
|
+
...msg.body && !msg.deleted_at ? {
|
|
3331
|
+
onCopy: () => {
|
|
3332
|
+
setActionsOpen(false);
|
|
3333
|
+
void copyToClipboard(msg.body ?? "");
|
|
3334
|
+
}
|
|
3335
|
+
} : {},
|
|
3336
|
+
...onStartEdit ? {
|
|
3337
|
+
onEdit: () => {
|
|
3338
|
+
setActionsOpen(false);
|
|
3339
|
+
onStartEdit();
|
|
3340
|
+
}
|
|
3341
|
+
} : {},
|
|
3342
|
+
...onDelete ? {
|
|
3343
|
+
onDelete: () => {
|
|
3344
|
+
setActionsOpen(false);
|
|
3345
|
+
onDelete();
|
|
3346
|
+
}
|
|
3347
|
+
} : {},
|
|
3348
|
+
...onOpenThread ? {
|
|
3349
|
+
onReplyInThread: () => {
|
|
3350
|
+
setActionsOpen(false);
|
|
3351
|
+
onOpenThread();
|
|
3352
|
+
}
|
|
3353
|
+
} : {},
|
|
3354
|
+
...quotations && onQuote ? {
|
|
3355
|
+
onQuote: () => {
|
|
3356
|
+
setActionsOpen(false);
|
|
3357
|
+
onQuote();
|
|
3358
|
+
}
|
|
3359
|
+
} : {}
|
|
3024
3360
|
}
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3361
|
+
) : null
|
|
3362
|
+
]
|
|
3363
|
+
}
|
|
3364
|
+
);
|
|
3029
3365
|
}
|
|
3030
3366
|
var styles17 = StyleSheet.create({
|
|
3031
3367
|
row: {
|
|
@@ -3041,6 +3377,12 @@ var styles17 = StyleSheet.create({
|
|
|
3041
3377
|
paddingHorizontal: 10,
|
|
3042
3378
|
maxWidth: "95%"
|
|
3043
3379
|
},
|
|
3380
|
+
rowEditing: {
|
|
3381
|
+
// Above ConversationView's editBackdrop (zIndex 5). Elevation
|
|
3382
|
+
// does the equivalent stacking on Android.
|
|
3383
|
+
zIndex: 10,
|
|
3384
|
+
elevation: 10
|
|
3385
|
+
},
|
|
3044
3386
|
avatarSlot: {
|
|
3045
3387
|
width: 36,
|
|
3046
3388
|
marginRight: 6
|
|
@@ -3080,6 +3422,27 @@ var styles17 = StyleSheet.create({
|
|
|
3080
3422
|
marginTop: 2
|
|
3081
3423
|
}
|
|
3082
3424
|
});
|
|
3425
|
+
var clipboardModule;
|
|
3426
|
+
var clipboardWarned = false;
|
|
3427
|
+
async function copyToClipboard(text) {
|
|
3428
|
+
if (clipboardModule === void 0) {
|
|
3429
|
+
try {
|
|
3430
|
+
clipboardModule = __require("expo-clipboard");
|
|
3431
|
+
} catch {
|
|
3432
|
+
clipboardModule = null;
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
if (!clipboardModule?.setStringAsync) {
|
|
3436
|
+
if (!clipboardWarned) {
|
|
3437
|
+
clipboardWarned = true;
|
|
3438
|
+
console.warn(
|
|
3439
|
+
"[@poolse/react-native] Copy action requires `expo-clipboard`. Install it to enable copy-to-clipboard."
|
|
3440
|
+
);
|
|
3441
|
+
}
|
|
3442
|
+
return;
|
|
3443
|
+
}
|
|
3444
|
+
await clipboardModule.setStringAsync(text);
|
|
3445
|
+
}
|
|
3083
3446
|
var SHEET_HEIGHT_FRACTION = 0.88;
|
|
3084
3447
|
function ThreadView({
|
|
3085
3448
|
visible,
|
|
@@ -3135,31 +3498,34 @@ function ThreadSheet({
|
|
|
3135
3498
|
const [pickerOpen, setPickerOpen] = useState(false);
|
|
3136
3499
|
const [editingId, setEditingId] = useState(null);
|
|
3137
3500
|
const renderReply = useCallback(
|
|
3138
|
-
(msg) =>
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3501
|
+
(msg) => {
|
|
3502
|
+
const isSelf = meId !== null && msg.sender_id === meId;
|
|
3503
|
+
const canEdit = isSelf && !msg.deleted_at;
|
|
3504
|
+
return /* @__PURE__ */ jsx(
|
|
3505
|
+
MessageRow,
|
|
3506
|
+
{
|
|
3507
|
+
msg,
|
|
3508
|
+
meId,
|
|
3509
|
+
reactions: true,
|
|
3510
|
+
attachments: true,
|
|
3511
|
+
actions: true,
|
|
3512
|
+
threads: false,
|
|
3513
|
+
quotations: false,
|
|
3514
|
+
isEditing: editingId === msg.id,
|
|
3515
|
+
...canEdit ? { onStartEdit: () => setEditingId(msg.id) } : {},
|
|
3516
|
+
onDelete: () => {
|
|
3517
|
+
void deleteReply(msg.id);
|
|
3518
|
+
},
|
|
3519
|
+
...labelFor ? { labelFor } : {},
|
|
3520
|
+
...avatarFor ? { avatarFor } : {}
|
|
3521
|
+
}
|
|
3522
|
+
);
|
|
3523
|
+
},
|
|
3524
|
+
[meId, editingId, deleteReply, labelFor, avatarFor]
|
|
3525
|
+
);
|
|
3526
|
+
const editingMessage = useMemo(
|
|
3527
|
+
() => editingId ? replies.find((m) => m.id === editingId) ?? null : null,
|
|
3528
|
+
[editingId, replies]
|
|
3163
3529
|
);
|
|
3164
3530
|
const rootRow = useMemo(
|
|
3165
3531
|
() => /* @__PURE__ */ jsx(
|
|
@@ -3245,7 +3611,14 @@ function ThreadSheet({
|
|
|
3245
3611
|
},
|
|
3246
3612
|
placeholder: "Reply to thread\u2026",
|
|
3247
3613
|
attachments: true,
|
|
3248
|
-
onAttachPress: () => setPickerOpen(true)
|
|
3614
|
+
onAttachPress: () => setPickerOpen(true),
|
|
3615
|
+
editingMessage,
|
|
3616
|
+
onCancelEdit: () => setEditingId(null),
|
|
3617
|
+
onSaveEdit: async (body) => {
|
|
3618
|
+
if (!editingId) return;
|
|
3619
|
+
await edit(editingId, body);
|
|
3620
|
+
setEditingId(null);
|
|
3621
|
+
}
|
|
3249
3622
|
}
|
|
3250
3623
|
)
|
|
3251
3624
|
]
|
|
@@ -3409,6 +3782,7 @@ var styles19 = StyleSheet.create({
|
|
|
3409
3782
|
fontSize: 12
|
|
3410
3783
|
}
|
|
3411
3784
|
});
|
|
3785
|
+
var GROUPING_WINDOW_MS = 5 * 60 * 1e3;
|
|
3412
3786
|
function ConversationView({
|
|
3413
3787
|
conversationId,
|
|
3414
3788
|
emptyState,
|
|
@@ -3425,6 +3799,7 @@ function ConversationView({
|
|
|
3425
3799
|
threads = true,
|
|
3426
3800
|
quotations = true,
|
|
3427
3801
|
readReceipts = true,
|
|
3802
|
+
markdown = true,
|
|
3428
3803
|
maxBodyLength = 200,
|
|
3429
3804
|
senderLabels = "auto",
|
|
3430
3805
|
avatars = "auto",
|
|
@@ -3443,9 +3818,25 @@ function ConversationView({
|
|
|
3443
3818
|
send,
|
|
3444
3819
|
edit,
|
|
3445
3820
|
delete: deleteMsg,
|
|
3446
|
-
markReadUpTo
|
|
3821
|
+
markReadUpTo,
|
|
3822
|
+
refetch
|
|
3447
3823
|
} = useMessages(conversationId);
|
|
3448
3824
|
const { typing, signalTyping } = useTyping(conversationId);
|
|
3825
|
+
const realtimeStatus = useRealtimeStatus();
|
|
3826
|
+
useEffect(() => {
|
|
3827
|
+
const sub = AppState.addEventListener("change", (next) => {
|
|
3828
|
+
if (next === "active") void refetch();
|
|
3829
|
+
});
|
|
3830
|
+
return () => sub.remove();
|
|
3831
|
+
}, [refetch]);
|
|
3832
|
+
const prevStatusRef = useRef(realtimeStatus);
|
|
3833
|
+
useEffect(() => {
|
|
3834
|
+
const prev = prevStatusRef.current;
|
|
3835
|
+
prevStatusRef.current = realtimeStatus;
|
|
3836
|
+
if (realtimeStatus === "connected" && (prev === "reconnecting" || prev === "closed" || prev === "connecting")) {
|
|
3837
|
+
void refetch();
|
|
3838
|
+
}
|
|
3839
|
+
}, [realtimeStatus, refetch]);
|
|
3449
3840
|
const wantsMembers = mentions || senderLabels === "auto" || avatars === "auto";
|
|
3450
3841
|
const { members } = useMembers(wantsMembers ? conversationId : "");
|
|
3451
3842
|
const isGroupChat = members.length > 2;
|
|
@@ -3476,10 +3867,24 @@ function ConversationView({
|
|
|
3476
3867
|
});
|
|
3477
3868
|
onMarkedRead?.(conversationId, latest.id);
|
|
3478
3869
|
}, [messages, meId, readReceipts, markReadUpTo, onMarkedRead, conversationId]);
|
|
3870
|
+
const groupPositionById = useMemo(() => {
|
|
3871
|
+
const map = /* @__PURE__ */ new Map();
|
|
3872
|
+
for (let i = 0; i < messages.length; i++) {
|
|
3873
|
+
const msg = messages[i];
|
|
3874
|
+
if (!msg) continue;
|
|
3875
|
+
const prev = i > 0 ? messages[i - 1] ?? null : null;
|
|
3876
|
+
const next = i < messages.length - 1 ? messages[i + 1] ?? null : null;
|
|
3877
|
+
map.set(msg.id, computeGroupPosition(msg, prev, next, GROUPING_WINDOW_MS));
|
|
3878
|
+
}
|
|
3879
|
+
return map;
|
|
3880
|
+
}, [messages]);
|
|
3479
3881
|
const renderItem = useCallback(
|
|
3480
3882
|
(msg) => {
|
|
3481
3883
|
if (renderMessage) return /* @__PURE__ */ jsx(Fragment, { children: renderMessage(msg, meId) });
|
|
3482
3884
|
const readState = readReceipts && meId !== null && msg.sender_id === meId ? readStateForMessage(msg, members, meId, sequenceByMessageId) : void 0;
|
|
3885
|
+
const isSelf = meId !== null && msg.sender_id === meId;
|
|
3886
|
+
const canEdit = isSelf && !msg.deleted_at && (readState ?? "sent") === "sent";
|
|
3887
|
+
const groupPosition = groupPositionById.get(msg.id) ?? "standalone";
|
|
3483
3888
|
return /* @__PURE__ */ jsx(
|
|
3484
3889
|
MessageRow,
|
|
3485
3890
|
{
|
|
@@ -3491,13 +3896,8 @@ function ConversationView({
|
|
|
3491
3896
|
threads,
|
|
3492
3897
|
quotations,
|
|
3493
3898
|
...readState ? { readState } : {},
|
|
3494
|
-
|
|
3495
|
-
onStartEdit: () => setEditingId(msg.id),
|
|
3496
|
-
onCancelEdit: () => setEditingId(null),
|
|
3497
|
-
onSaveEdit: async (body) => {
|
|
3498
|
-
await edit(msg.id, body);
|
|
3499
|
-
setEditingId(null);
|
|
3500
|
-
},
|
|
3899
|
+
isEditing: editingId === msg.id,
|
|
3900
|
+
...canEdit ? { onStartEdit: () => setEditingId(msg.id) } : {},
|
|
3501
3901
|
onDelete: () => deleteMsg(msg.id),
|
|
3502
3902
|
...threads ? { onOpenThread: () => setThreadRoot(msg) } : {},
|
|
3503
3903
|
...quotations ? { onQuote: () => setReplyingTo(msg) } : {},
|
|
@@ -3505,7 +3905,9 @@ function ConversationView({
|
|
|
3505
3905
|
...avatarFor ? { avatarFor } : {},
|
|
3506
3906
|
showSenderName: showSenderLabels,
|
|
3507
3907
|
showAvatar: showAvatars,
|
|
3508
|
-
maxBodyLength
|
|
3908
|
+
maxBodyLength,
|
|
3909
|
+
groupPosition,
|
|
3910
|
+
markdown
|
|
3509
3911
|
}
|
|
3510
3912
|
);
|
|
3511
3913
|
},
|
|
@@ -3520,16 +3922,38 @@ function ConversationView({
|
|
|
3520
3922
|
threads,
|
|
3521
3923
|
quotations,
|
|
3522
3924
|
editingId,
|
|
3523
|
-
edit,
|
|
3524
3925
|
deleteMsg,
|
|
3525
3926
|
labelFor,
|
|
3526
3927
|
avatarFor,
|
|
3527
3928
|
showSenderLabels,
|
|
3528
3929
|
showAvatars,
|
|
3529
3930
|
maxBodyLength,
|
|
3530
|
-
sequenceByMessageId
|
|
3931
|
+
sequenceByMessageId,
|
|
3932
|
+
groupPositionById,
|
|
3933
|
+
markdown
|
|
3531
3934
|
]
|
|
3532
3935
|
);
|
|
3936
|
+
const editingMessage = useMemo(
|
|
3937
|
+
() => editingId ? messages.find((m) => m.id === editingId) ?? null : null,
|
|
3938
|
+
[editingId, messages]
|
|
3939
|
+
);
|
|
3940
|
+
const listItems = useMemo(() => {
|
|
3941
|
+
const out = [];
|
|
3942
|
+
let prev = null;
|
|
3943
|
+
for (const msg of messages) {
|
|
3944
|
+
if (prev === null || !sameDay(msg.inserted_at, prev.inserted_at)) {
|
|
3945
|
+
const label = formatDayLabel(msg.inserted_at);
|
|
3946
|
+
out.push({
|
|
3947
|
+
kind: "separator",
|
|
3948
|
+
id: `sep-${msg.id}`,
|
|
3949
|
+
node: /* @__PURE__ */ jsx(DaySeparator, { label })
|
|
3950
|
+
});
|
|
3951
|
+
}
|
|
3952
|
+
out.push({ kind: "msg", msg });
|
|
3953
|
+
prev = msg;
|
|
3954
|
+
}
|
|
3955
|
+
return out;
|
|
3956
|
+
}, [messages]);
|
|
3533
3957
|
const composerProps = useMemo(
|
|
3534
3958
|
() => ({
|
|
3535
3959
|
onSend: async (body, opts) => {
|
|
@@ -3543,9 +3967,28 @@ function ConversationView({
|
|
|
3543
3967
|
onCancelReply: () => setReplyingTo(null),
|
|
3544
3968
|
...labelFor ? { labelFor } : {},
|
|
3545
3969
|
attachments,
|
|
3546
|
-
keyboardOffset
|
|
3970
|
+
keyboardOffset,
|
|
3971
|
+
editingMessage,
|
|
3972
|
+
onCancelEdit: () => setEditingId(null),
|
|
3973
|
+
onSaveEdit: async (body) => {
|
|
3974
|
+
if (!editingId) return;
|
|
3975
|
+
await edit(editingId, body);
|
|
3976
|
+
setEditingId(null);
|
|
3977
|
+
}
|
|
3547
3978
|
}),
|
|
3548
|
-
[
|
|
3979
|
+
[
|
|
3980
|
+
send,
|
|
3981
|
+
signalTyping,
|
|
3982
|
+
attachments,
|
|
3983
|
+
replyingTo,
|
|
3984
|
+
labelFor,
|
|
3985
|
+
keyboardOffset,
|
|
3986
|
+
onSent,
|
|
3987
|
+
conversationId,
|
|
3988
|
+
editingMessage,
|
|
3989
|
+
editingId,
|
|
3990
|
+
edit
|
|
3991
|
+
]
|
|
3549
3992
|
);
|
|
3550
3993
|
const headerNode = header === false ? null : header === true ? /* @__PURE__ */ jsx(
|
|
3551
3994
|
ChatHeader,
|
|
@@ -3565,18 +4008,30 @@ function ConversationView({
|
|
|
3565
4008
|
keyboardVerticalOffset: keyboardOffset,
|
|
3566
4009
|
children: [
|
|
3567
4010
|
headerNode,
|
|
3568
|
-
/* @__PURE__ */
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
4011
|
+
/* @__PURE__ */ jsxs(View, { style: styles20.listWrap, children: [
|
|
4012
|
+
/* @__PURE__ */ jsx(
|
|
4013
|
+
MessageList,
|
|
4014
|
+
{
|
|
4015
|
+
messages,
|
|
4016
|
+
items: listItems,
|
|
4017
|
+
meId,
|
|
4018
|
+
loading,
|
|
4019
|
+
hasMore,
|
|
4020
|
+
onLoadMore: loadMore,
|
|
4021
|
+
renderItem,
|
|
4022
|
+
ListHeader: /* @__PURE__ */ jsx(TypingIndicator, { typing, ...labelFor ? { labelFor } : {} }),
|
|
4023
|
+
...emptyState !== void 0 ? { emptyState } : {}
|
|
4024
|
+
}
|
|
4025
|
+
),
|
|
4026
|
+
editingId ? /* @__PURE__ */ jsx(
|
|
4027
|
+
Pressable,
|
|
4028
|
+
{
|
|
4029
|
+
style: styles20.editBackdrop,
|
|
4030
|
+
onPress: () => setEditingId(null),
|
|
4031
|
+
accessibilityLabel: "Cancel editing"
|
|
4032
|
+
}
|
|
4033
|
+
) : null
|
|
4034
|
+
] }),
|
|
3580
4035
|
mentions ? /* @__PURE__ */ jsx(MentionInput, { conversationId, ...composerProps }) : /* @__PURE__ */ jsx(MessageComposer, { ...composerProps }),
|
|
3581
4036
|
attachments ? /* @__PURE__ */ jsx(AttachmentPicker, { visible: pickerOpen, onClose: () => setPickerOpen(false) }) : null,
|
|
3582
4037
|
threads && threadRoot ? /* @__PURE__ */ jsx(
|
|
@@ -3612,6 +4067,14 @@ var styles20 = StyleSheet.create({
|
|
|
3612
4067
|
},
|
|
3613
4068
|
listWrap: {
|
|
3614
4069
|
flex: 1
|
|
4070
|
+
},
|
|
4071
|
+
editBackdrop: {
|
|
4072
|
+
...StyleSheet.absoluteFillObject,
|
|
4073
|
+
backgroundColor: "rgba(0,0,0,0.45)",
|
|
4074
|
+
// Sit above message rows by default but below any row that
|
|
4075
|
+
// explicitly elevates itself (MessageRow bumps zIndex/elevation
|
|
4076
|
+
// when isEditing is true).
|
|
4077
|
+
zIndex: 5
|
|
3615
4078
|
}
|
|
3616
4079
|
});
|
|
3617
4080
|
function UserPickerSheet({
|
|
@@ -4470,6 +4933,6 @@ function useAppStateLifecycle() {
|
|
|
4470
4933
|
}, [poolse]);
|
|
4471
4934
|
}
|
|
4472
4935
|
|
|
4473
|
-
export { AttachmentPicker, AttachmentPreview, Avatar, ChatHeader, ConversationList, ConversationView,
|
|
4936
|
+
export { AttachmentPicker, AttachmentPreview, Avatar, ChatHeader, ConversationList, ConversationView, DaySeparator, GroupDetailsSheet, ImageMosaic, MemberList, MentionInput, MessageActions, MessageBubble, MessageComposer, MessageList, MessageRow, PoolseIcon, PoolseInbox, PoolseLogo, PoolseTheme, primitives as Primitives, ReactionPicker, ReactionStrip, ThreadView, TypingIndicator, UploadQueueStrip, UserName, defaultDarkTheme, defaultLightTheme, useAppStateLifecycle, useDisplayName, usePoolseTheme };
|
|
4474
4937
|
//# sourceMappingURL=index.js.map
|
|
4475
4938
|
//# sourceMappingURL=index.js.map
|