@adminide-stack/yantra-mobile 12.0.70-alpha.4 → 12.0.70

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.
@@ -1,4 +1,4 @@
1
- import {jsxs,jsx}from'react/jsx-runtime';import*as React from'react';import {StyleSheet,Platform,KeyboardAvoidingView,View,Pressable,Text,ActivityIndicator,FlatList,TextInput}from'react-native';import {SafeAreaView}from'react-native-safe-area-context';import {MaterialCommunityIcons}from'@expo/vector-icons';import {Avatar,ConnectionBanner,InlineBanner,EmptyState,MessageBubble,DaySeparator}from'./ChatComponents.js';import {dayLabel}from'./chatModel.js';import {MessageActionsOverlay}from'./MessageActions.js';function ThreadScreen({
1
+ import {jsxs,jsx}from'react/jsx-runtime';import*as React from'react';import {StyleSheet,Platform,KeyboardAvoidingView,View,Pressable,Text,ActivityIndicator,FlatList,TextInput}from'react-native';import {SafeAreaView}from'react-native-safe-area-context';import {MaterialCommunityIcons}from'@expo/vector-icons';import {Avatar,ConnectionBanner,InlineBanner,EmptyState,MessageBubble,DaySeparator}from'./ChatComponents.js';import {dayLabel}from'./chatModel.js';import {MessageActionsOverlay}from'./MessageActions.js';import {StagedAttachments,RecordingBar,AttachSheet,ImageViewer}from'./ChatAttachments.js';function ThreadScreen({
2
2
  title,
3
3
  avatarUrl,
4
4
  messages,
@@ -15,10 +15,20 @@ import {jsxs,jsx}from'react/jsx-runtime';import*as React from'react';import {Sty
15
15
  onDismissError,
16
16
  onReact,
17
17
  onCopyText,
18
- onShareText
18
+ onShareText,
19
+ media,
20
+ audio,
21
+ onSendAttachments
19
22
  }) {
20
23
  var _a;
21
24
  const [draft, setDraft] = React.useState("");
25
+ const [staged, setStaged] = React.useState([]);
26
+ const [attachOpen, setAttachOpen] = React.useState(false);
27
+ const [recordingSince, setRecordingSince] = React.useState(null);
28
+ const [viewerUrl, setViewerUrl] = React.useState(null);
29
+ const [composerError, setComposerError] = React.useState(null);
30
+ const canAttach = Boolean(media && onSendAttachments);
31
+ const canRecord = Boolean(audio && onSendAttachments);
22
32
  const [selected, setSelected] = React.useState(null);
23
33
  const selectedMessage = React.useMemo(() => {
24
34
  var _a2;
@@ -33,7 +43,7 @@ import {jsxs,jsx}from'react/jsx-runtime';import*as React from'react';import {Sty
33
43
  }, [onShareText]);
34
44
  const actions = React.useMemo(() => {
35
45
  var _a2;
36
- const body = (_a2 = selectedMessage == null ? void 0 : selectedMessage.body) != null ? _a2 : "";
46
+ const body = (_a2 = selectedMessage == null ? void 0 : selectedMessage.body.trim()) != null ? _a2 : "";
37
47
  const list = [];
38
48
  if (onCopyText && body) {
39
49
  list.push({
@@ -62,14 +72,77 @@ import {jsxs,jsx}from'react/jsx-runtime';import*as React from'react';import {Sty
62
72
  }, [selectedMessage, onCopyText, onShareText, flushPendingShare]);
63
73
  const now = React.useMemo(() => Date.now(), []);
64
74
  const rendered = React.useMemo(() => messages.slice().reverse(), [messages]);
65
- const canSend = draft.trim().length > 0;
75
+ const canSend = draft.trim().length > 0 || staged.length > 0;
66
76
  const submit = React.useCallback(() => {
67
77
  const text = draft.trim();
78
+ if (staged.length && onSendAttachments) {
79
+ setDraft("");
80
+ setStaged([]);
81
+ haptics == null ? void 0 : haptics.impact("light");
82
+ onSendAttachments(staged, text, "image");
83
+ return;
84
+ }
68
85
  if (!text) return;
69
86
  setDraft("");
70
87
  haptics == null ? void 0 : haptics.impact("light");
71
88
  onSend(text);
72
- }, [draft, haptics, onSend]);
89
+ }, [draft, staged, haptics, onSend, onSendAttachments]);
90
+ const pendingPick = React.useRef(null);
91
+ const runPendingPick = React.useCallback(() => {
92
+ const source = pendingPick.current;
93
+ pendingPick.current = null;
94
+ if (!source || !media) return;
95
+ const run = source === "library" ? media.pickFromLibrary({
96
+ limit: Math.max(1, 10 - staged.length)
97
+ }) : media.captureFromCamera();
98
+ run.then((files) => {
99
+ if (files.length) setStaged((prev) => [...prev, ...files].slice(0, 10));
100
+ }).catch((e) => setComposerError(e instanceof Error ? e.message : "Could not open photos."));
101
+ }, [media, staged.length]);
102
+ const pick = React.useCallback((source) => {
103
+ if (!media) return;
104
+ setComposerError(null);
105
+ pendingPick.current = source;
106
+ setAttachOpen(false);
107
+ if (Platform.OS !== "ios") setTimeout(runPendingPick, 400);
108
+ }, [media, runPendingPick]);
109
+ const startRecording = React.useCallback(async () => {
110
+ if (!audio) return;
111
+ setComposerError(null);
112
+ try {
113
+ const ok = await audio.startRecording();
114
+ if (!ok) {
115
+ setComposerError("Microphone access is off. Allow it in Settings to send voice messages.");
116
+ return;
117
+ }
118
+ haptics == null ? void 0 : haptics.impact("medium");
119
+ setRecordingSince(Date.now());
120
+ } catch (e) {
121
+ setComposerError(e instanceof Error ? e.message : "Could not start recording.");
122
+ }
123
+ }, [audio, haptics]);
124
+ const finishRecording = React.useCallback(async (send) => {
125
+ if (!audio) return;
126
+ setRecordingSince(null);
127
+ try {
128
+ if (!send) {
129
+ await audio.cancelRecording();
130
+ return;
131
+ }
132
+ const file = await audio.stopRecording();
133
+ if (file && onSendAttachments) {
134
+ haptics == null ? void 0 : haptics.impact("light");
135
+ onSendAttachments([file], "", "audio");
136
+ }
137
+ } catch (e) {
138
+ setComposerError(e instanceof Error ? e.message : "Could not send the recording.");
139
+ }
140
+ }, [audio, haptics, onSendAttachments]);
141
+ const recordingRef = React.useRef(recordingSince);
142
+ recordingRef.current = recordingSince;
143
+ React.useEffect(() => () => {
144
+ if (recordingRef.current) void (audio == null ? void 0 : audio.cancelRecording());
145
+ }, [audio]);
73
146
  return /* @__PURE__ */ jsxs(KeyboardAvoidingView, { style: styles.fill, behavior: Platform.OS === "ios" ? "padding" : void 0, keyboardVerticalOffset: 0, children: [
74
147
  /* @__PURE__ */ jsx(SafeAreaView, { edges: ["top"], style: {
75
148
  backgroundColor: theme.headerBg
@@ -103,6 +176,7 @@ import {jsxs,jsx}from'react/jsx-runtime';import*as React from'react';import {Sty
103
176
  ] }) }),
104
177
  /* @__PURE__ */ jsx(ConnectionBanner, { state: connectionState, theme }),
105
178
  /* @__PURE__ */ jsx(InlineBanner, { message: error, theme, onDismiss: onDismissError }),
179
+ /* @__PURE__ */ jsx(InlineBanner, { message: composerError, theme, onDismiss: () => setComposerError(null) }),
106
180
  loading && messages.length === 0 ? /* @__PURE__ */ jsx(View, { style: [styles.center, {
107
181
  backgroundColor: theme.threadBg,
108
182
  flex: 1
@@ -122,7 +196,7 @@ import {jsxs,jsx}from'react/jsx-runtime';import*as React from'react';import {Sty
122
196
  const label = dayLabel(item.createdAt, now);
123
197
  const olderLabel = older ? dayLabel(older.createdAt, now) : null;
124
198
  return /* @__PURE__ */ jsxs(View, { children: [
125
- /* @__PURE__ */ jsx(MessageBubble, { message: item, theme, showAuthor, groupWithPrevious, groupWithNext, onRetry: () => onRetry(item.id), onLongPress: (
199
+ /* @__PURE__ */ jsx(MessageBubble, { message: item, theme, fileBaseUrl: media == null ? void 0 : media.fileBaseUrl, audio, onOpenImage: setViewerUrl, showAuthor, groupWithPrevious, groupWithNext, onRetry: () => onRetry(item.id), onLongPress: (
126
200
  // Only confirmed messages can be reacted to or acted on.
127
201
  !item.isDeleted && (!item.sendState || item.sendState === "sent") ? (frame) => {
128
202
  haptics == null ? void 0 : haptics.impact("medium");
@@ -135,14 +209,15 @@ import {jsxs,jsx}from'react/jsx-runtime';import*as React from'react';import {Sty
135
209
  olderLabel !== label ? /* @__PURE__ */ jsx(DaySeparator, { label, theme }) : null
136
210
  ] });
137
211
  }, onEndReached: onLoadMore, onEndReachedThreshold: 0.4, ListFooterComponent: loadingMore ? /* @__PURE__ */ jsx(ActivityIndicator, { style: styles.moreSpinner, size: "small" }) : null, contentContainerStyle: rendered.length === 0 ? styles.listContentEmpty : void 0, ListEmptyComponent: /* @__PURE__ */ jsx(View, { style: styles.emptyInverted, children: /* @__PURE__ */ jsx(EmptyState, { icon: "message-text-outline", title: "No messages yet", detail: "Messages you send stay in this chat.", theme }) }), keyboardDismissMode: "interactive", keyboardShouldPersistTaps: "handled", testID: "chat-thread" }),
138
- /* @__PURE__ */ jsxs(View, { style: {
212
+ /* @__PURE__ */ jsx(StagedAttachments, { files: staged, theme, onRemove: (index) => setStaged((prev) => prev.filter((_, i) => i !== index)) }),
213
+ recordingSince ? /* @__PURE__ */ jsx(RecordingBar, { theme, startedAt: recordingSince, onCancel: () => void finishRecording(false), onSend: () => void finishRecording(true) }) : /* @__PURE__ */ jsxs(View, { style: {
139
214
  flexDirection: "row",
140
215
  alignItems: "flex-end",
141
216
  paddingHorizontal: 8,
142
217
  paddingVertical: 8,
143
218
  backgroundColor: theme.composerBar
144
219
  }, children: [
145
- /* @__PURE__ */ jsx(Pressable, { hitSlop: 6, accessibilityRole: "button", accessibilityLabel: "Add attachment", style: styles.composerIcon, children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "plus-circle-outline", size: 28, color: theme.textMuted }) }),
220
+ canAttach ? /* @__PURE__ */ jsx(Pressable, { onPress: () => setAttachOpen(true), hitSlop: 6, accessibilityRole: "button", accessibilityLabel: "Add photos", style: styles.composerIcon, testID: "chat-composer-attach", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "plus-circle-outline", size: 28, color: theme.textMuted }) }) : null,
146
221
  /* @__PURE__ */ jsx(View, { style: {
147
222
  flex: 1,
148
223
  flexDirection: "row",
@@ -161,12 +236,12 @@ import {jsxs,jsx}from'react/jsx-runtime';import*as React from'react';import {Sty
161
236
  paddingVertical: Platform.OS === "ios" ? 8 : 6,
162
237
  maxHeight: 120
163
238
  }, multiline: true, blurOnSubmit: false, testID: "chat-composer-input" }) }),
164
- /* @__PURE__ */ jsx(Pressable, { onPress: canSend ? submit : void 0, accessibilityRole: "button", accessibilityLabel: canSend ? "Send message" : "Voice message", style: [styles.composerSend, canSend ? {
165
- backgroundColor: theme.accent
166
- } : {
167
- backgroundColor: "transparent"
168
- }], testID: "chat-composer-send", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: canSend ? "arrow-up" : "microphone-outline", size: canSend ? 22 : 26, color: canSend ? theme.onAccent : theme.textMuted }) })
239
+ canSend || !canRecord ? /* @__PURE__ */ jsx(Pressable, { onPress: canSend ? submit : void 0, disabled: !canSend, accessibilityRole: "button", accessibilityLabel: "Send message", style: [styles.composerSend, {
240
+ backgroundColor: canSend ? theme.accent : theme.border
241
+ }], testID: "chat-composer-send", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "arrow-up", size: 22, color: theme.onAccent }) }) : /* @__PURE__ */ jsx(Pressable, { onPress: () => void startRecording(), hitSlop: 6, accessibilityRole: "button", accessibilityLabel: "Record voice message", style: styles.composerSend, testID: "chat-composer-mic", children: /* @__PURE__ */ jsx(MaterialCommunityIcons, { name: "microphone-outline", size: 26, color: theme.textMuted }) })
169
242
  ] }),
243
+ /* @__PURE__ */ jsx(AttachSheet, { visible: attachOpen, theme, onClose: () => setAttachOpen(false), onPhotos: () => pick("library"), onCamera: () => pick("camera"), onDismissed: Platform.OS === "ios" ? runPendingPick : void 0 }),
244
+ /* @__PURE__ */ jsx(ImageViewer, { url: viewerUrl, onClose: () => setViewerUrl(null) }),
170
245
  /* @__PURE__ */ jsx(MessageActionsOverlay, { message: selectedMessage, frame: (_a = selected == null ? void 0 : selected.frame) != null ? _a : null, theme, actions, onReact: (emoji) => {
171
246
  if (selectedMessage && onReact) {
172
247
  haptics == null ? void 0 : haptics.impact("selection");
@@ -1 +1 @@
1
- {"version":3,"file":"ThreadScreen.js","sources":["../../../src/features/messaging/ThreadScreen.tsx"],"sourcesContent":["/**\n * One conversation: transcript plus composer.\n *\n * The list is INVERTED. That is the standard trick for a chat transcript - it\n * pins to the bottom for free, keeps position when older pages prepend, and\n * makes \"scrolled to newest\" simply `offset === 0`. The data is therefore\n * reversed for rendering while `chatModel` keeps it oldest-first everywhere\n * else, which is the order every merge and comparison reasons about.\n */\nimport * as React from 'react';\nimport {\n ActivityIndicator,\n FlatList,\n KeyboardAvoidingView,\n Platform,\n Pressable,\n StyleSheet,\n Text,\n TextInput,\n View,\n} from 'react-native';\nimport { SafeAreaView } from 'react-native-safe-area-context';\nimport { MaterialCommunityIcons } from '@expo/vector-icons';\nimport {\n Avatar,\n ConnectionBanner,\n DaySeparator,\n EmptyState,\n InlineBanner,\n MessageBubble,\n type ChatTheme,\n} from './ChatComponents';\nimport { dayLabel, type ChatMessage } from './chatModel';\nimport { MessageActionsOverlay, type BubbleFrame, type MessageAction } from './MessageActions';\nimport type { ChatConnectionState, ChatHapticsSeam } from './chatHost';\n\nexport function ThreadScreen({\n title,\n avatarUrl,\n messages,\n loading,\n loadingMore,\n error,\n theme,\n connectionState,\n haptics,\n onBack,\n onSend,\n onLoadMore,\n onRetry,\n onDismissError,\n onReact,\n onCopyText,\n onShareText,\n}: {\n title: string;\n avatarUrl?: string | null;\n messages: ChatMessage[];\n loading: boolean;\n loadingMore: boolean;\n error: string | null;\n theme: ChatTheme;\n connectionState: ChatConnectionState;\n haptics?: ChatHapticsSeam;\n onBack: () => void;\n onSend: (body: string) => void;\n onLoadMore: () => void;\n onRetry: (id: string) => void;\n onDismissError: () => void;\n /** Pick or remove the viewer's Tapback; absent disables long-press reactions. */\n onReact?: (messageId: string, emoji: string) => void;\n /** Present only when the host provides a clipboard. */\n onCopyText?: (text: string) => void;\n onShareText?: (text: string) => void;\n}) {\n const [draft, setDraft] = React.useState('');\n const [selected, setSelected] = React.useState<{ id: string; frame: BubbleFrame } | null>(null);\n const selectedMessage = React.useMemo(\n () => (selected ? (messages.find((m) => m.id === selected.id) ?? null) : null),\n [selected, messages],\n );\n const closeActions = React.useCallback(() => setSelected(null), []);\n // Share must open AFTER the overlay's modal is gone: iOS refuses to present the\n // share sheet from a view controller that is still dismissing, and fails silently.\n const pendingShare = React.useRef<string | null>(null);\n const flushPendingShare = React.useCallback(() => {\n const text = pendingShare.current;\n pendingShare.current = null;\n if (text) onShareText?.(text);\n }, [onShareText]);\n\n const actions = React.useMemo<MessageAction[]>(() => {\n const body = selectedMessage?.body ?? '';\n const list: MessageAction[] = [];\n if (onCopyText && body) {\n list.push({\n key: 'copy',\n label: 'Copy',\n icon: 'content-copy',\n onPress: () => {\n onCopyText(body);\n setSelected(null);\n },\n });\n }\n if (onShareText && body) {\n list.push({\n key: 'share',\n label: 'Share',\n icon: 'export-variant',\n onPress: () => {\n pendingShare.current = body;\n setSelected(null);\n // Android has no Modal onDismiss; its modal is gone after the fade.\n if (Platform.OS !== 'ios') setTimeout(flushPendingShare, 300);\n },\n });\n }\n return list;\n }, [selectedMessage, onCopyText, onShareText, flushPendingShare]);\n const now = React.useMemo(() => Date.now(), []);\n const rendered = React.useMemo(() => messages.slice().reverse(), [messages]);\n const canSend = draft.trim().length > 0;\n\n const submit = React.useCallback(() => {\n const text = draft.trim();\n if (!text) return;\n setDraft('');\n haptics?.impact('light');\n onSend(text);\n }, [draft, haptics, onSend]);\n\n return (\n <KeyboardAvoidingView\n style={styles.fill}\n behavior={Platform.OS === 'ios' ? 'padding' : undefined}\n keyboardVerticalOffset={0}\n >\n <SafeAreaView edges={['top']} style={{ backgroundColor: theme.headerBg }}>\n <View\n style={{\n flexDirection: 'row',\n alignItems: 'center',\n paddingHorizontal: 4,\n paddingVertical: 6,\n borderBottomWidth: StyleSheet.hairlineWidth,\n borderBottomColor: theme.border,\n backgroundColor: theme.headerBg,\n }}\n >\n <Pressable onPress={onBack} hitSlop={10} accessibilityRole=\"button\" accessibilityLabel=\"Back\">\n <MaterialCommunityIcons name=\"chevron-left\" size={32} color={theme.accent} />\n </Pressable>\n <Pressable\n onPress={onBack}\n accessibilityRole=\"button\"\n style={{\n flexDirection: 'row',\n alignItems: 'center',\n flex: 1,\n marginLeft: 4,\n minWidth: 0,\n }}\n >\n <Avatar name={title} uri={avatarUrl} theme={theme} size={36} />\n <Text\n numberOfLines={1}\n style={{\n flex: 1,\n fontSize: 17,\n fontWeight: '600',\n color: theme.text,\n marginLeft: 10,\n }}\n >\n {title}\n </Text>\n </Pressable>\n <Pressable hitSlop={8} accessibilityRole=\"button\" accessibilityLabel=\"More options\">\n <MaterialCommunityIcons name=\"dots-vertical\" size={22} color={theme.textMuted} />\n </Pressable>\n </View>\n </SafeAreaView>\n\n <ConnectionBanner state={connectionState} theme={theme} />\n <InlineBanner message={error} theme={theme} onDismiss={onDismissError} />\n\n {loading && messages.length === 0 ? (\n <View style={[styles.center, { backgroundColor: theme.threadBg, flex: 1 }]}>\n <ActivityIndicator color={theme.accent} />\n </View>\n ) : (\n <FlatList\n inverted\n data={rendered}\n style={{ flex: 1, backgroundColor: theme.threadBg }}\n keyExtractor={(m) => m.id}\n renderItem={({ item, index }) => {\n const older = rendered[index + 1];\n const newer = index > 0 ? rendered[index - 1] : null;\n const sameAuthor = (a: ChatMessage | null | undefined, b: ChatMessage) =>\n Boolean(a && !a.isDeleted && !b.isDeleted && a.authorId && a.authorId === b.authorId);\n const showAuthor = !older || older.authorId !== item.authorId;\n const groupWithPrevious = sameAuthor(older, item);\n const groupWithNext = sameAuthor(newer, item);\n const label = dayLabel(item.createdAt, now);\n const olderLabel = older ? dayLabel(older.createdAt, now) : null;\n return (\n <View>\n <MessageBubble\n message={item}\n theme={theme}\n showAuthor={showAuthor}\n groupWithPrevious={groupWithPrevious}\n groupWithNext={groupWithNext}\n onRetry={() => onRetry(item.id)}\n onLongPress={\n // Only confirmed messages can be reacted to or acted on.\n !item.isDeleted && (!item.sendState || item.sendState === 'sent')\n ? (frame) => {\n haptics?.impact('medium');\n setSelected({ id: item.id, frame });\n }\n : undefined\n }\n />\n {olderLabel !== label ? <DaySeparator label={label} theme={theme} /> : null}\n </View>\n );\n }}\n onEndReached={onLoadMore}\n onEndReachedThreshold={0.4}\n ListFooterComponent={\n loadingMore ? <ActivityIndicator style={styles.moreSpinner} size=\"small\" /> : null\n }\n contentContainerStyle={rendered.length === 0 ? styles.listContentEmpty : undefined}\n ListEmptyComponent={\n <View style={styles.emptyInverted}>\n <EmptyState\n icon=\"message-text-outline\"\n title=\"No messages yet\"\n detail=\"Messages you send stay in this chat.\"\n theme={theme}\n />\n </View>\n }\n keyboardDismissMode=\"interactive\"\n keyboardShouldPersistTaps=\"handled\"\n testID=\"chat-thread\"\n />\n )}\n\n <View\n style={{\n flexDirection: 'row',\n alignItems: 'flex-end',\n paddingHorizontal: 8,\n paddingVertical: 8,\n backgroundColor: theme.composerBar,\n }}\n >\n <Pressable\n hitSlop={6}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Add attachment\"\n style={styles.composerIcon}\n >\n <MaterialCommunityIcons name=\"plus-circle-outline\" size={28} color={theme.textMuted} />\n </Pressable>\n <View\n style={{\n flex: 1,\n flexDirection: 'row',\n alignItems: 'flex-end',\n backgroundColor: theme.bg,\n borderRadius: 20,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: theme.border,\n paddingHorizontal: 12,\n minHeight: 38,\n marginHorizontal: 6,\n }}\n >\n <TextInput\n value={draft}\n onChangeText={setDraft}\n placeholder=\"Message\"\n placeholderTextColor={theme.textMuted}\n style={{\n flex: 1,\n color: theme.text,\n fontSize: 17,\n paddingVertical: Platform.OS === 'ios' ? 8 : 6,\n maxHeight: 120,\n }}\n multiline\n blurOnSubmit={false}\n testID=\"chat-composer-input\"\n />\n </View>\n <Pressable\n onPress={canSend ? submit : undefined}\n accessibilityRole=\"button\"\n accessibilityLabel={canSend ? 'Send message' : 'Voice message'}\n style={[\n styles.composerSend,\n canSend ? { backgroundColor: theme.accent } : { backgroundColor: 'transparent' },\n ]}\n testID=\"chat-composer-send\"\n >\n <MaterialCommunityIcons\n name={canSend ? 'arrow-up' : 'microphone-outline'}\n size={canSend ? 22 : 26}\n color={canSend ? theme.onAccent : theme.textMuted}\n />\n </Pressable>\n </View>\n\n <MessageActionsOverlay\n message={selectedMessage}\n frame={selected?.frame ?? null}\n theme={theme}\n actions={actions}\n onReact={(emoji) => {\n if (selectedMessage && onReact) {\n haptics?.impact('selection');\n onReact(selectedMessage.id, emoji);\n }\n setSelected(null);\n }}\n onClose={closeActions}\n onDismissed={Platform.OS === 'ios' ? flushPendingShare : undefined}\n />\n </KeyboardAvoidingView>\n );\n}\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n center: { flex: 1, alignItems: 'center', justifyContent: 'center' },\n listContentEmpty: { flexGrow: 1, justifyContent: 'center' },\n moreSpinner: { paddingVertical: 12 },\n emptyInverted: { transform: [{ scaleY: -1 }], paddingVertical: 24, alignItems: 'center' },\n composerIcon: {\n width: 36,\n height: 36,\n alignItems: 'center',\n justifyContent: 'center',\n },\n composerSend: {\n width: 32,\n height: 32,\n borderRadius: 16,\n alignItems: 'center',\n justifyContent: 'center',\n },\n});\n"],"names":["_a"],"mappings":"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"}
1
+ {"version":3,"file":"ThreadScreen.js","sources":["../../../src/features/messaging/ThreadScreen.tsx"],"sourcesContent":["/**\n * One conversation: transcript plus composer.\n *\n * The list is INVERTED. That is the standard trick for a chat transcript - it\n * pins to the bottom for free, keeps position when older pages prepend, and\n * makes \"scrolled to newest\" simply `offset === 0`. The data is therefore\n * reversed for rendering while `chatModel` keeps it oldest-first everywhere\n * else, which is the order every merge and comparison reasons about.\n */\nimport * as React from 'react';\nimport {\n ActivityIndicator,\n FlatList,\n KeyboardAvoidingView,\n Platform,\n Pressable,\n StyleSheet,\n Text,\n TextInput,\n View,\n} from 'react-native';\nimport { SafeAreaView } from 'react-native-safe-area-context';\nimport { MaterialCommunityIcons } from '@expo/vector-icons';\nimport {\n Avatar,\n ConnectionBanner,\n DaySeparator,\n EmptyState,\n InlineBanner,\n MessageBubble,\n type ChatTheme,\n} from './ChatComponents';\nimport { dayLabel, type ChatMessage } from './chatModel';\nimport { MessageActionsOverlay, type BubbleFrame, type MessageAction } from './MessageActions';\nimport type { ChatAudioSeam, ChatConnectionState, ChatHapticsSeam, ChatMediaSeam, ChatPickedImage } from './chatHost';\nimport type { ChatAttachmentKind } from './chatModel';\nimport { AttachSheet, ImageViewer, RecordingBar, StagedAttachments } from './ChatAttachments';\n\nexport function ThreadScreen({\n title,\n avatarUrl,\n messages,\n loading,\n loadingMore,\n error,\n theme,\n connectionState,\n haptics,\n onBack,\n onSend,\n onLoadMore,\n onRetry,\n onDismissError,\n onReact,\n onCopyText,\n onShareText,\n media,\n audio,\n onSendAttachments,\n}: {\n title: string;\n avatarUrl?: string | null;\n messages: ChatMessage[];\n loading: boolean;\n loadingMore: boolean;\n error: string | null;\n theme: ChatTheme;\n connectionState: ChatConnectionState;\n haptics?: ChatHapticsSeam;\n onBack: () => void;\n onSend: (body: string) => void;\n onLoadMore: () => void;\n onRetry: (id: string) => void;\n onDismissError: () => void;\n /** Pick or remove the viewer's Tapback; absent disables long-press reactions. */\n onReact?: (messageId: string, emoji: string) => void;\n /** Present only when the host provides a clipboard. */\n onCopyText?: (text: string) => void;\n onShareText?: (text: string) => void;\n /** Photos / camera; without it (or `onSendAttachments`) the \"+\" button is hidden. */\n media?: ChatMediaSeam;\n /** Voice notes; without it the mic button is hidden. */\n audio?: ChatAudioSeam;\n onSendAttachments?: (files: ChatPickedImage[], caption: string, kind: ChatAttachmentKind) => void;\n}) {\n const [draft, setDraft] = React.useState('');\n const [staged, setStaged] = React.useState<ChatPickedImage[]>([]);\n const [attachOpen, setAttachOpen] = React.useState(false);\n const [recordingSince, setRecordingSince] = React.useState<number | null>(null);\n const [viewerUrl, setViewerUrl] = React.useState<string | null>(null);\n const [composerError, setComposerError] = React.useState<string | null>(null);\n const canAttach = Boolean(media && onSendAttachments);\n const canRecord = Boolean(audio && onSendAttachments);\n const [selected, setSelected] = React.useState<{ id: string; frame: BubbleFrame } | null>(null);\n const selectedMessage = React.useMemo(\n () => (selected ? (messages.find((m) => m.id === selected.id) ?? null) : null),\n [selected, messages],\n );\n const closeActions = React.useCallback(() => setSelected(null), []);\n // Share must open AFTER the overlay's modal is gone: iOS refuses to present the\n // share sheet from a view controller that is still dismissing, and fails silently.\n const pendingShare = React.useRef<string | null>(null);\n const flushPendingShare = React.useCallback(() => {\n const text = pendingShare.current;\n pendingShare.current = null;\n if (text) onShareText?.(text);\n }, [onShareText]);\n\n const actions = React.useMemo<MessageAction[]>(() => {\n const body = selectedMessage?.body.trim() ?? '';\n const list: MessageAction[] = [];\n if (onCopyText && body) {\n list.push({\n key: 'copy',\n label: 'Copy',\n icon: 'content-copy',\n onPress: () => {\n onCopyText(body);\n setSelected(null);\n },\n });\n }\n if (onShareText && body) {\n list.push({\n key: 'share',\n label: 'Share',\n icon: 'export-variant',\n onPress: () => {\n pendingShare.current = body;\n setSelected(null);\n // Android has no Modal onDismiss; its modal is gone after the fade.\n if (Platform.OS !== 'ios') setTimeout(flushPendingShare, 300);\n },\n });\n }\n return list;\n }, [selectedMessage, onCopyText, onShareText, flushPendingShare]);\n const now = React.useMemo(() => Date.now(), []);\n const rendered = React.useMemo(() => messages.slice().reverse(), [messages]);\n const canSend = draft.trim().length > 0 || staged.length > 0;\n\n const submit = React.useCallback(() => {\n const text = draft.trim();\n if (staged.length && onSendAttachments) {\n setDraft('');\n setStaged([]);\n haptics?.impact('light');\n onSendAttachments(staged, text, 'image');\n return;\n }\n if (!text) return;\n setDraft('');\n haptics?.impact('light');\n onSend(text);\n }, [draft, staged, haptics, onSend, onSendAttachments]);\n\n // The menu must be fully closed before the picker opens: iOS silently refuses to present over\n // a modal that is still animating away (same as Share). iOS waits for the sheet's onDismiss;\n // Android has no onDismiss, so it waits out the slide.\n const pendingPick = React.useRef<'library' | 'camera' | null>(null);\n const runPendingPick = React.useCallback(() => {\n const source = pendingPick.current;\n pendingPick.current = null;\n if (!source || !media) return;\n const run =\n source === 'library'\n ? media.pickFromLibrary({ limit: Math.max(1, 10 - staged.length) })\n : media.captureFromCamera();\n run.then((files) => {\n if (files.length) setStaged((prev) => [...prev, ...files].slice(0, 10));\n }).catch((e: unknown) => setComposerError(e instanceof Error ? e.message : 'Could not open photos.'));\n }, [media, staged.length]);\n const pick = React.useCallback(\n (source: 'library' | 'camera') => {\n if (!media) return;\n setComposerError(null);\n pendingPick.current = source;\n setAttachOpen(false);\n if (Platform.OS !== 'ios') setTimeout(runPendingPick, 400);\n },\n [media, runPendingPick],\n );\n\n const startRecording = React.useCallback(async () => {\n if (!audio) return;\n setComposerError(null);\n try {\n const ok = await audio.startRecording();\n if (!ok) {\n setComposerError('Microphone access is off. Allow it in Settings to send voice messages.');\n return;\n }\n haptics?.impact('medium');\n setRecordingSince(Date.now());\n } catch (e) {\n setComposerError(e instanceof Error ? e.message : 'Could not start recording.');\n }\n }, [audio, haptics]);\n\n const finishRecording = React.useCallback(\n async (send: boolean) => {\n if (!audio) return;\n setRecordingSince(null);\n try {\n if (!send) {\n await audio.cancelRecording();\n return;\n }\n const file = await audio.stopRecording();\n if (file && onSendAttachments) {\n haptics?.impact('light');\n onSendAttachments([file], '', 'audio');\n }\n } catch (e) {\n setComposerError(e instanceof Error ? e.message : 'Could not send the recording.');\n }\n },\n [audio, haptics, onSendAttachments],\n );\n\n // Leaving the thread mid-recording throws the recording away.\n const recordingRef = React.useRef(recordingSince);\n recordingRef.current = recordingSince;\n React.useEffect(\n () => () => {\n if (recordingRef.current) void audio?.cancelRecording();\n },\n [audio],\n );\n\n return (\n <KeyboardAvoidingView\n style={styles.fill}\n behavior={Platform.OS === 'ios' ? 'padding' : undefined}\n keyboardVerticalOffset={0}\n >\n <SafeAreaView edges={['top']} style={{ backgroundColor: theme.headerBg }}>\n <View\n style={{\n flexDirection: 'row',\n alignItems: 'center',\n paddingHorizontal: 4,\n paddingVertical: 6,\n borderBottomWidth: StyleSheet.hairlineWidth,\n borderBottomColor: theme.border,\n backgroundColor: theme.headerBg,\n }}\n >\n <Pressable onPress={onBack} hitSlop={10} accessibilityRole=\"button\" accessibilityLabel=\"Back\">\n <MaterialCommunityIcons name=\"chevron-left\" size={32} color={theme.accent} />\n </Pressable>\n <Pressable\n onPress={onBack}\n accessibilityRole=\"button\"\n style={{\n flexDirection: 'row',\n alignItems: 'center',\n flex: 1,\n marginLeft: 4,\n minWidth: 0,\n }}\n >\n <Avatar name={title} uri={avatarUrl} theme={theme} size={36} />\n <Text\n numberOfLines={1}\n style={{\n flex: 1,\n fontSize: 17,\n fontWeight: '600',\n color: theme.text,\n marginLeft: 10,\n }}\n >\n {title}\n </Text>\n </Pressable>\n <Pressable hitSlop={8} accessibilityRole=\"button\" accessibilityLabel=\"More options\">\n <MaterialCommunityIcons name=\"dots-vertical\" size={22} color={theme.textMuted} />\n </Pressable>\n </View>\n </SafeAreaView>\n\n <ConnectionBanner state={connectionState} theme={theme} />\n <InlineBanner message={error} theme={theme} onDismiss={onDismissError} />\n <InlineBanner message={composerError} theme={theme} onDismiss={() => setComposerError(null)} />\n\n {loading && messages.length === 0 ? (\n <View style={[styles.center, { backgroundColor: theme.threadBg, flex: 1 }]}>\n <ActivityIndicator color={theme.accent} />\n </View>\n ) : (\n <FlatList\n inverted\n data={rendered}\n style={{ flex: 1, backgroundColor: theme.threadBg }}\n keyExtractor={(m) => m.id}\n renderItem={({ item, index }) => {\n const older = rendered[index + 1];\n const newer = index > 0 ? rendered[index - 1] : null;\n const sameAuthor = (a: ChatMessage | null | undefined, b: ChatMessage) =>\n Boolean(a && !a.isDeleted && !b.isDeleted && a.authorId && a.authorId === b.authorId);\n const showAuthor = !older || older.authorId !== item.authorId;\n const groupWithPrevious = sameAuthor(older, item);\n const groupWithNext = sameAuthor(newer, item);\n const label = dayLabel(item.createdAt, now);\n const olderLabel = older ? dayLabel(older.createdAt, now) : null;\n return (\n <View>\n <MessageBubble\n message={item}\n theme={theme}\n fileBaseUrl={media?.fileBaseUrl}\n audio={audio}\n onOpenImage={setViewerUrl}\n showAuthor={showAuthor}\n groupWithPrevious={groupWithPrevious}\n groupWithNext={groupWithNext}\n onRetry={() => onRetry(item.id)}\n onLongPress={\n // Only confirmed messages can be reacted to or acted on.\n !item.isDeleted && (!item.sendState || item.sendState === 'sent')\n ? (frame) => {\n haptics?.impact('medium');\n setSelected({ id: item.id, frame });\n }\n : undefined\n }\n />\n {olderLabel !== label ? <DaySeparator label={label} theme={theme} /> : null}\n </View>\n );\n }}\n onEndReached={onLoadMore}\n onEndReachedThreshold={0.4}\n ListFooterComponent={\n loadingMore ? <ActivityIndicator style={styles.moreSpinner} size=\"small\" /> : null\n }\n contentContainerStyle={rendered.length === 0 ? styles.listContentEmpty : undefined}\n ListEmptyComponent={\n <View style={styles.emptyInverted}>\n <EmptyState\n icon=\"message-text-outline\"\n title=\"No messages yet\"\n detail=\"Messages you send stay in this chat.\"\n theme={theme}\n />\n </View>\n }\n keyboardDismissMode=\"interactive\"\n keyboardShouldPersistTaps=\"handled\"\n testID=\"chat-thread\"\n />\n )}\n\n <StagedAttachments\n files={staged}\n theme={theme}\n onRemove={(index) => setStaged((prev) => prev.filter((_, i) => i !== index))}\n />\n {recordingSince ? (\n <RecordingBar\n theme={theme}\n startedAt={recordingSince}\n onCancel={() => void finishRecording(false)}\n onSend={() => void finishRecording(true)}\n />\n ) : (\n <View\n style={{\n flexDirection: 'row',\n alignItems: 'flex-end',\n paddingHorizontal: 8,\n paddingVertical: 8,\n backgroundColor: theme.composerBar,\n }}\n >\n {canAttach ? (\n <Pressable\n onPress={() => setAttachOpen(true)}\n hitSlop={6}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Add photos\"\n style={styles.composerIcon}\n testID=\"chat-composer-attach\"\n >\n <MaterialCommunityIcons name=\"plus-circle-outline\" size={28} color={theme.textMuted} />\n </Pressable>\n ) : null}\n <View\n style={{\n flex: 1,\n flexDirection: 'row',\n alignItems: 'flex-end',\n backgroundColor: theme.bg,\n borderRadius: 20,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: theme.border,\n paddingHorizontal: 12,\n minHeight: 38,\n marginHorizontal: 6,\n }}\n >\n <TextInput\n value={draft}\n onChangeText={setDraft}\n placeholder=\"Message\"\n placeholderTextColor={theme.textMuted}\n style={{\n flex: 1,\n color: theme.text,\n fontSize: 17,\n paddingVertical: Platform.OS === 'ios' ? 8 : 6,\n maxHeight: 120,\n }}\n multiline\n blurOnSubmit={false}\n testID=\"chat-composer-input\"\n />\n </View>\n {canSend || !canRecord ? (\n <Pressable\n onPress={canSend ? submit : undefined}\n disabled={!canSend}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Send message\"\n style={[styles.composerSend, { backgroundColor: canSend ? theme.accent : theme.border }]}\n testID=\"chat-composer-send\"\n >\n <MaterialCommunityIcons name=\"arrow-up\" size={22} color={theme.onAccent} />\n </Pressable>\n ) : (\n <Pressable\n onPress={() => void startRecording()}\n hitSlop={6}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Record voice message\"\n style={styles.composerSend}\n testID=\"chat-composer-mic\"\n >\n <MaterialCommunityIcons name=\"microphone-outline\" size={26} color={theme.textMuted} />\n </Pressable>\n )}\n </View>\n )}\n\n <AttachSheet\n visible={attachOpen}\n theme={theme}\n onClose={() => setAttachOpen(false)}\n onPhotos={() => pick('library')}\n onCamera={() => pick('camera')}\n onDismissed={Platform.OS === 'ios' ? runPendingPick : undefined}\n />\n <ImageViewer url={viewerUrl} onClose={() => setViewerUrl(null)} />\n\n <MessageActionsOverlay\n message={selectedMessage}\n frame={selected?.frame ?? null}\n theme={theme}\n actions={actions}\n onReact={(emoji) => {\n if (selectedMessage && onReact) {\n haptics?.impact('selection');\n onReact(selectedMessage.id, emoji);\n }\n setSelected(null);\n }}\n onClose={closeActions}\n onDismissed={Platform.OS === 'ios' ? flushPendingShare : undefined}\n />\n </KeyboardAvoidingView>\n );\n}\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n center: { flex: 1, alignItems: 'center', justifyContent: 'center' },\n listContentEmpty: { flexGrow: 1, justifyContent: 'center' },\n moreSpinner: { paddingVertical: 12 },\n emptyInverted: { transform: [{ scaleY: -1 }], paddingVertical: 24, alignItems: 'center' },\n composerIcon: {\n width: 36,\n height: 36,\n alignItems: 'center',\n justifyContent: 'center',\n },\n composerSend: {\n width: 32,\n height: 32,\n borderRadius: 16,\n alignItems: 'center',\n justifyContent: 'center',\n },\n});\n"],"names":["_a"],"mappings":"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"}
@@ -45,22 +45,97 @@ function toEpoch(value) {
45
45
  return Number.isNaN(parsed) ? 0 : parsed;
46
46
  }
47
47
  function toChatMessage(post, viewerId) {
48
- var _a, _b, _c, _d, _e;
48
+ var _a, _b, _c;
49
49
  const author = pickAuthor(post);
50
+ const attachments = toChatAttachments(post);
50
51
  return {
51
52
  id: String(post.id),
52
- body: (_a = post.message) != null ? _a : "",
53
+ body: bodyOf(post, attachments.length > 0),
53
54
  createdAt: toEpoch(post.createdAt),
54
55
  authorId: (author == null ? void 0 : author.id) ? String(author.id) : null,
55
56
  authorName: displayNameOf(author),
56
- authorAvatar: (_d = (_c = author == null ? void 0 : author.picture) != null ? _c : (_b = author == null ? void 0 : author.profile) == null ? void 0 : _b.picture) != null ? _d : null,
57
+ authorAvatar: (_c = (_b = author == null ? void 0 : author.picture) != null ? _b : (_a = author == null ? void 0 : author.profile) == null ? void 0 : _a.picture) != null ? _c : null,
57
58
  isMine: !!(author == null ? void 0 : author.id) && String(author.id) === viewerId,
58
59
  isDeleted: !!post.deletedAt,
59
60
  isEdited: !!post.editedAt,
60
- attachments: ((_e = post.filenames) != null ? _e : []).filter((f) => typeof f === "string" && f.length > 0),
61
+ attachments,
61
62
  reactions: toChatReactions(post.reactionSummary, viewerId)
62
63
  };
63
64
  }
65
+ function kindOfMime(mimeType) {
66
+ if (mimeType.startsWith("image/")) return "image";
67
+ if (mimeType.startsWith("audio/")) return "audio";
68
+ return "file";
69
+ }
70
+ function fileKeyFromUrl(url) {
71
+ const value = (url != null ? url : "").trim();
72
+ if (!value) return null;
73
+ const resized = value.match(/\/fit-in\/\d+x\d+\/(.+)$/);
74
+ if (resized) return decodeURIComponent(resized[1].split("?")[0]);
75
+ if (!/^[a-z]+:\/\//i.test(value)) return value.split("?")[0];
76
+ if (/^(file|content|ph|assets-library):/i.test(value)) return null;
77
+ try {
78
+ return decodeURIComponent(new URL(value).pathname.replace(/^\/+/, "")) || null;
79
+ } catch (e) {
80
+ return null;
81
+ }
82
+ }
83
+ function attachmentUrl(attachment, fileBaseUrl) {
84
+ if (!attachment.key || !fileBaseUrl) return attachment.url;
85
+ return `${fileBaseUrl.replace(/\/+$/, "")}/${attachment.key}`;
86
+ }
87
+ function propsOf(post) {
88
+ const raw = post == null ? void 0 : post.props;
89
+ if (raw && typeof raw === "object") return raw;
90
+ if (typeof raw === "string") {
91
+ try {
92
+ const parsed = JSON.parse(raw);
93
+ return parsed && typeof parsed === "object" ? parsed : {};
94
+ } catch (e) {
95
+ return {};
96
+ }
97
+ }
98
+ return {};
99
+ }
100
+ function toChatAttachments(post) {
101
+ var _a, _b;
102
+ const props = propsOf(post);
103
+ const durationMs = typeof props.voiceDurationMs === "number" ? props.voiceDurationMs : null;
104
+ return ((_b = (_a = post.files) == null ? void 0 : _a.data) != null ? _b : []).filter((f) => Boolean(f == null ? void 0 : f.url)).map((f, i) => {
105
+ var _a2, _b2, _c, _d, _e;
106
+ const mimeType = (_a2 = f.mimeType) != null ? _a2 : "application/octet-stream";
107
+ const kind = kindOfMime(mimeType);
108
+ return {
109
+ id: String((_b2 = f.id) != null ? _b2 : `${post.id}-${i}`),
110
+ kind,
111
+ name: (_c = f.name) != null ? _c : "file",
112
+ mimeType,
113
+ url: String(f.url),
114
+ key: fileKeyFromUrl(f.url),
115
+ width: (_d = f.width) != null ? _d : null,
116
+ height: (_e = f.height) != null ? _e : null,
117
+ durationMs: kind === "audio" ? durationMs : null
118
+ };
119
+ });
120
+ }
121
+ const ATTACHMENT_LABELS = {
122
+ image: "\u{1F4F7} Photo",
123
+ audio: "\u{1F3A4} Voice message",
124
+ file: "\u{1F4CE} Attachment"
125
+ };
126
+ function attachmentPreview(post) {
127
+ const kind = propsOf(post).attachmentKind;
128
+ return typeof kind === "string" && kind in ATTACHMENT_LABELS ? ATTACHMENT_LABELS[kind] : "";
129
+ }
130
+ const LABEL_TEXTS = new Set(Object.values(ATTACHMENT_LABELS));
131
+ function bodyOf(post, hasAttachments) {
132
+ var _a;
133
+ const body = (_a = post.message) != null ? _a : "";
134
+ const trimmed = body.trim();
135
+ const label = attachmentPreview(post);
136
+ if (label && trimmed === label) return "";
137
+ return hasAttachments && LABEL_TEXTS.has(trimmed) ? "" : body;
138
+ }
64
139
  function toChatReactions(summary, viewerId) {
65
140
  return (summary != null ? summary : []).filter((r) => r && r.reaction && r.count > 0).map((r) => {
66
141
  var _a;
@@ -113,6 +188,17 @@ function mergeMessages(existing, incoming) {
113
188
  }
114
189
  function reconcileOptimistic(messages, confirmed, windowMs = 6e4) {
115
190
  if (!confirmed.isMine) return mergeMessages(messages, [confirmed]);
191
+ const sameIdIdx = messages.findIndex((m) => m.id === confirmed.id && m.sendState === "sending");
192
+ if (sameIdIdx !== -1) {
193
+ const next2 = messages.slice();
194
+ const pending = messages[sameIdIdx];
195
+ next2[sameIdIdx] = __spreadProps(__spreadValues({}, confirmed), {
196
+ // Keep the local previews until the server copy lists its files.
197
+ attachments: confirmed.attachments.length ? confirmed.attachments : pending.attachments,
198
+ sendState: "sent"
199
+ });
200
+ return next2;
201
+ }
116
202
  const pendingIdx = messages.findIndex((m) => m.sendState === "sending" && m.isMine && m.body === confirmed.body && // Same author, not merely both "mine" - guards the same collision
117
203
  // when authorId is populated on both sides.
118
204
  (!m.authorId || !confirmed.authorId || m.authorId === confirmed.authorId) && Math.abs(m.createdAt - confirmed.createdAt) <= windowMs);
@@ -172,7 +258,7 @@ function toConversation(channel, viewerId) {
172
258
  const storedLabel = !looksLikeMachineTitle(channel.displayName) ? channel.displayName.trim() : !looksLikeMachineTitle(channel.title) ? channel.title.trim() : "";
173
259
  const title = isDirect ? displayNameOf(peer, storedLabel || lastAuthorName || "Direct message") : storedLabel || groupFallback;
174
260
  const topic = looksLikeMachineTitle(channel.topic) ? "" : ((_c = channel.topic) != null ? _c : "").trim();
175
- const preview = ((_e = (_d = channel.lastMessage) == null ? void 0 : _d.message) == null ? void 0 : _e.trim()) || topic;
261
+ const preview = ((_e = (_d = channel.lastMessage) == null ? void 0 : _d.message) == null ? void 0 : _e.trim()) || attachmentPreview(channel.lastMessage) || topic;
176
262
  return {
177
263
  id: String(channel.id),
178
264
  title,
@@ -219,7 +305,7 @@ function mergeConversations(existing, incoming) {
219
305
  }
220
306
  function patchConversationAfterPost(conv, post, viewerId) {
221
307
  var _a;
222
- const preview = (_a = post.message) == null ? void 0 : _a.trim();
308
+ const preview = ((_a = post.message) == null ? void 0 : _a.trim()) || attachmentPreview(post);
223
309
  const nextSubtitle = preview == null || preview === "" ? conv.subtitle : preview === conv.title ? "" : preview;
224
310
  const activity = toEpoch(post.createdAt) || Date.now();
225
311
  const author = pickAuthor(post);
@@ -275,4 +361,4 @@ function initialsOf(name) {
275
361
  if (!parts.length) return "?";
276
362
  if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
277
363
  return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
278
- }export{applyReactionSummary,conversationListKey,dayLabel,dedupeConversationList,displayNameOf,initialsOf,mergeConversations,mergeMessages,patchConversationAfterPost,peerOf,pickAuthor,reconcileOptimistic,relativeLabel,sortConversations,timeLabel,toChatMessage,toChatReactions,toConversation,toEpoch,unreadOf,withViewerReaction};//# sourceMappingURL=chatModel.js.map
364
+ }export{ATTACHMENT_LABELS,applyReactionSummary,attachmentPreview,attachmentUrl,conversationListKey,dayLabel,dedupeConversationList,displayNameOf,fileKeyFromUrl,initialsOf,mergeConversations,mergeMessages,patchConversationAfterPost,peerOf,pickAuthor,reconcileOptimistic,relativeLabel,sortConversations,timeLabel,toChatAttachments,toChatMessage,toChatReactions,toConversation,toEpoch,unreadOf,withViewerReaction};//# sourceMappingURL=chatModel.js.map