@adminide-stack/yantra-mobile 12.0.69-alpha.20 โ†’ 12.0.69-alpha.22

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 +1 @@
1
- {"version":3,"file":"NativeChatExperience.js","sources":["../../../src/features/messaging/NativeChatExperience.tsx"],"sourcesContent":["/**\n * The chat remote's exposed root (`chat_native/Chat`).\n *\n * Owns its own navigation as local state - the remote must not import\n * @react-navigation, because a second copy of the navigator would not share the\n * host's container. Android hardware back is handled with RN's own BackHandler.\n *\n * Everything here is plain react-native (plus expo-image and\n * @expo/vector-icons, both host-shared singletons) - no gluestack, no\n * NativeWind, no Apollo - because a federated remote may only use what the host\n * shares, and every extra dependency is another way to fail to load on a device\n * in a way the simulator will not reproduce.\n */\nimport * as React from 'react';\nimport { ActivityIndicator, AppState, BackHandler, Platform, StyleSheet, Text, View } from 'react-native';\nimport type { NativeChatExperienceProps } from './chatHost';\nimport { chatThemes, EmptyState } from './ChatComponents';\nimport { useAppTheme } from '../../theme/AppThemeProvider';\nimport { ConversationListScreen } from './ConversationListScreen';\nimport { ContactsScreen } from './ContactsScreen';\nimport { ThreadScreen } from './ThreadScreen';\nimport { useConversations, useMarkRead, useThread } from './useChatData';\nimport { VIEW_CHANNEL_MUTATION, YANTRA_CHAT_INVITER_QUERY } from './chatOperations';\nimport { buildChatInviteUrl, chatInviteMessage } from './chatInviteLink';\n\n/** Used only when the host predates `chatInviteUrl`; the host supplies the per-environment domain. */\nconst DEFAULT_INVITE_WEB_BASE = 'https://yantra.app';\nimport { useContactMatches, type MatchedContact } from './useContactMatches';\nimport type { WirePost } from './chatModel';\n\n/** The screens the remote routes between internally. */\nexport type ChatRoute = 'list' | 'thread' | 'contacts' | 'newGroup' | 'groupInfo';\n\nexport function NativeChatExperience({\n viewer,\n sessionError,\n services,\n initialChannelId,\n initialPeer,\n onActiveChannelChange,\n onInternalRouteChange,\n testID,\n}: NativeChatExperienceProps) {\n const { isDark } = useAppTheme();\n const theme = isDark ? chatThemes.dark : chatThemes.light;\n\n const [route, setRoute] = React.useState<ChatRoute>(initialChannelId ? 'thread' : 'list');\n const [activeChannelId, setActiveChannelId] = React.useState<string | null>(initialChannelId ?? null);\n const [dismissedError, setDismissedError] = React.useState<string | null>(null);\n\n const conversations = useConversations(services, viewer, { syncListRealtime: route === 'list' });\n const contactMatches = useContactMatches(services.gql, services.contacts, viewer, services.phone);\n const [busyContactId, setBusyContactId] = React.useState<string | null>(null);\n const [startChatError, setStartChatError] = React.useState<string | null>(null);\n const onThreadListUpdate = React.useCallback(\n (post: WirePost) => {\n if (!activeChannelId) return;\n conversations.applyConversationMessage(activeChannelId, post);\n // The viewer is looking at this thread, so a message arriving here is already read.\n conversations.markRead(activeChannelId);\n },\n [activeChannelId, conversations.applyConversationMessage, conversations.markRead],\n );\n const thread = useThread(services.gql, route === 'thread' ? activeChannelId : null, viewer, {\n onConversationMessage: onThreadListUpdate,\n });\n\n // Clearing the badge is local-first; the mutation is fire-and-forget.\n useMarkRead(services.gql, route === 'thread' ? activeChannelId : null, conversations.markRead);\n\n // Mark read again on LEAVING a thread, so messages that arrived while it was open\n // are not counted as unread by the server on the next list load.\n const threadOpenRef = React.useRef<string | null>(null);\n React.useEffect(() => {\n const open = route === 'thread' ? activeChannelId : null;\n const left = threadOpenRef.current;\n if (left && left !== open) void services.gql.mutate(VIEW_CHANNEL_MUTATION, { id: left });\n threadOpenRef.current = open;\n }, [route, activeChannelId, services.gql]);\n\n // The host suppresses a foreground push banner for the conversation already\n // on screen, so it has to be told every time that changes.\n React.useEffect(() => {\n onActiveChannelChange?.(route === 'thread' ? activeChannelId : null);\n }, [route, activeChannelId, onActiveChannelChange]);\n\n React.useEffect(() => {\n const internal: 'list' | 'thread' | 'contacts' | 'register' =\n route === 'thread' ? 'thread' : route === 'contacts' ? 'contacts' : 'list';\n onInternalRouteChange?.(internal);\n }, [route, onInternalRouteChange]);\n\n // The address book changes outside the app, so re-read it whenever the picker opens\n // and whenever the app returns to the foreground while the picker is showing.\n const refreshContactsRef = React.useRef(contactMatches.refresh);\n refreshContactsRef.current = contactMatches.refresh;\n const contactsGranted = contactMatches.permission === 'granted';\n React.useEffect(() => {\n if (route !== 'contacts' || !contactsGranted) return undefined;\n void refreshContactsRef.current();\n const sub = AppState.addEventListener('change', (state) => {\n if (state === 'active') void refreshContactsRef.current();\n });\n return () => sub.remove();\n }, [route, contactsGranted]);\n\n const activeConversation = React.useMemo(\n () => conversations.conversations.find((c) => c.id === activeChannelId) ?? null,\n [conversations.conversations, activeChannelId],\n );\n\n // Android back pops the internal stack before the host's navigator sees it.\n React.useEffect(() => {\n if (Platform.OS !== 'android') return undefined;\n const sub = BackHandler.addEventListener('hardwareBackPress', () => {\n if (route === 'list') return false;\n setRoute('list');\n return true;\n });\n return () => sub.remove();\n }, [route]);\n\n const openThread = React.useCallback((id: string) => {\n setActiveChannelId(id);\n setRoute('thread');\n }, []);\n\n /**\n * Open (or reuse) the conversation with a matched contact. createDirectChannel\n * is idempotent server-side, so tapping someone you already talk to lands in\n * the existing thread rather than making a second one.\n */\n const startChatWith = React.useCallback(\n async (contact: MatchedContact) => {\n if (!contact.match || busyContactId) return;\n setBusyContactId(contact.id);\n setStartChatError(null);\n try {\n const id = await conversations.openDirect(contact.match.userId, contact.match.displayName);\n if (id) openThread(id);\n else setStartChatError('Could not start this conversation. Please try again.');\n } finally {\n setBusyContactId(null);\n }\n },\n [conversations, openThread, busyContactId],\n );\n\n // Opened from a chat invite link: go straight into the conversation with the inviter\n // (createDirectChannel is idempotent, so reopening an old invite lands in the same thread).\n // The link is editable by anyone, so the server has to vouch for the inviter first, and\n // its name is used - never the one in the link.\n // Tracks the invite object, not the user: every tap on a link arrives as a new object,\n // so reopening a link retries, while re-renders of the same one do not.\n const openedPeer = React.useRef<typeof initialPeer | null>(null);\n const [inviteNotice, setInviteNotice] = React.useState<string | null>(null);\n const { openDirect } = conversations;\n React.useEffect(() => {\n const peer = initialPeer;\n if (!peer?.userId || !viewer?.userId || peer.userId === viewer.userId) return;\n if (openedPeer.current === peer) return;\n openedPeer.current = peer;\n void (async () => {\n const res = await services.gql.query<{\n yantraChatInviter?: { userId: string; displayName: string } | null;\n }>(YANTRA_CHAT_INVITER_QUERY, { userId: peer.userId }, { fetchPolicy: 'network-only' });\n const inviter = res.data?.yantraChatInviter;\n if (!inviter) {\n setInviteNotice(\n res.errors?.length\n ? 'Could not open this invite. Check your connection and open the link again.'\n : 'This invite link is not valid anymore. Ask for a new one.',\n );\n return;\n }\n const id = await openDirect(inviter.userId, inviter.displayName);\n if (id) openThread(id);\n else setInviteNotice(`Could not start a conversation with ${inviter.displayName}. Please try again.`);\n })();\n }, [initialPeer, viewer?.userId, services.gql, openDirect, openThread]);\n\n /**\n * Invite someone who is not here yet.\n *\n * With phone-based membership (issue #1312):\n * 1. Call the connector to generate a deep link\n * 2. Open SMS composer with the deep link (user approves before sending)\n * 3. Fall back to share sheet where SMS is unavailable (simulators)\n *\n * The deep link includes the invitee's phone and the inviter's phone,\n * enabling install-then-attribute and direct link-back to the inviter\n * after the app is installed.\n */\n const inviteContact = React.useCallback(\n async (contact: MatchedContact) => {\n const link = services.link;\n\n if (!link || !viewer?.userId) return;\n const smsTo = contact.rawNumbers?.length ? contact.rawNumbers.slice(0, 1) : contact.e164.slice(0, 1);\n if (!smsTo.length) return;\n\n // The link names the inviter (so the app can open this conversation on arrival) and\n // carries no phone numbers.\n const invite = { inviterId: viewer.userId, inviterName: viewer.displayName ?? null };\n const url = services.phone?.chatInviteUrl?.(invite) ?? buildChatInviteUrl(DEFAULT_INVITE_WEB_BASE, invite);\n const body = chatInviteMessage(viewer.displayName, url);\n\n // Saved numbers, not our E.164 guesses: the phone dials them in its own region.\n const result = await link.composeSms(smsTo, body);\n if (result === 'unavailable') await link.share(body);\n },\n [services.link, services.phone, viewer],\n );\n\n const activeTitle = React.useMemo(\n () => conversations.conversations.find((c) => c.id === activeChannelId)?.title ?? 'Chat',\n [conversations.conversations, activeChannelId],\n );\n\n if (sessionError) {\n return (\n <Shell theme={theme} testID={testID}>\n <EmptyState\n icon=\"alert-circle-outline\"\n title=\"Chat is unavailable\"\n detail={sessionError}\n theme={theme}\n />\n </Shell>\n );\n }\n\n // Session still resolving: a spinner, never an error. org/user arrive a beat\n // after mount and a flash of failure reads as a broken feature.\n if (!viewer) {\n return (\n <Shell theme={theme} testID={testID}>\n <View style={styles.center}>\n <ActivityIndicator />\n </View>\n </Shell>\n );\n }\n\n if (route === 'thread' && activeChannelId) {\n const threadError = thread.error === dismissedError ? null : thread.error;\n return (\n <Shell theme={theme} testID={testID}>\n <ThreadScreen\n title={activeTitle}\n avatarUrl={activeConversation?.avatarUrl}\n messages={thread.messages}\n loading={thread.loading}\n loadingMore={thread.loadingMore}\n error={threadError}\n theme={theme}\n connectionState={services.gql.connectionState}\n haptics={services.haptics}\n onBack={() => setRoute('list')}\n onSend={(body) => void thread.send(body)}\n onLoadMore={thread.loadMore}\n onRetry={thread.retry}\n onReact={(messageId, emoji) => {\n const current = thread.messages\n .find((m) => m.id === messageId)\n ?.reactions.find((r) => r.mine)?.reaction;\n // Tapping your current Tapback removes it, as in iMessage.\n thread.react(messageId, current === emoji ? null : emoji);\n }}\n onCopyText={services.clipboard ? (text) => void services.clipboard?.copy(text) : undefined}\n onShareText={services.link ? (text) => void services.link?.share(text) : undefined}\n onDismissError={() => setDismissedError(thread.error)}\n />\n </Shell>\n );\n }\n\n if (route === 'contacts') {\n return (\n <Shell theme={theme} testID={testID}>\n <ContactsScreen\n permission={contactMatches.permission}\n loading={contactMatches.loading}\n error={startChatError ?? contactMatches.error}\n onYantra={contactMatches.onYantra}\n invitable={contactMatches.invitable}\n theme={theme}\n busyContactId={busyContactId}\n onBack={() => setRoute('list')}\n onRequestAccess={() => void contactMatches.requestAccess()}\n onOpenSettings={() => void contactMatches.openSettings()}\n onStartChat={(c) => void startChatWith(c)}\n onInvite={(c) => void inviteContact(c)}\n onDismissError={() => {\n if (startChatError) setStartChatError(null);\n else void contactMatches.refresh();\n }}\n />\n </Shell>\n );\n }\n\n const listError = conversations.error === dismissedError ? null : conversations.error;\n return (\n <Shell theme={theme} testID={testID}>\n <ConversationListScreen\n conversations={conversations.conversations}\n loading={conversations.loading}\n refreshing={conversations.refreshing}\n error={listError}\n theme={theme}\n connectionState={services.gql.connectionState}\n canInvite\n onRefresh={() => {\n conversations.refresh();\n void contactMatches.refresh();\n }}\n onRetry={conversations.refresh}\n loadingMore={conversations.loadingMore}\n onLoadMore={conversations.loadMore}\n onOpen={openThread}\n onNewChat={() => setRoute('contacts')}\n onDismissError={() => setDismissedError(conversations.error)}\n notice={inviteNotice}\n onDismissNotice={() => setInviteNotice(null)}\n />\n </Shell>\n );\n}\n\nfunction Shell({ theme, testID, children }: { theme: { bg: string }; testID?: string; children: React.ReactNode }) {\n return (\n <View testID={testID ?? 'chat-native'} style={[styles.fill, { backgroundColor: theme.bg }]}>\n {children}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n center: { flex: 1, alignItems: 'center', justifyContent: 'center' },\n});\n\nexport default NativeChatExperience;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"NativeChatExperience.js","sources":["../../../src/features/messaging/NativeChatExperience.tsx"],"sourcesContent":["/**\n * The chat remote's exposed root (`chat_native/Chat`).\n *\n * Owns its own navigation as local state - the remote must not import\n * @react-navigation, because a second copy of the navigator would not share the\n * host's container. Android hardware back is handled with RN's own BackHandler.\n *\n * Everything here is plain react-native (plus expo-image and\n * @expo/vector-icons, both host-shared singletons) - no gluestack, no\n * NativeWind, no Apollo - because a federated remote may only use what the host\n * shares, and every extra dependency is another way to fail to load on a device\n * in a way the simulator will not reproduce.\n */\nimport * as React from 'react';\nimport { ActivityIndicator, AppState, BackHandler, Platform, StyleSheet, Text, View } from 'react-native';\nimport type { NativeChatExperienceProps } from './chatHost';\nimport { chatThemes, EmptyState } from './ChatComponents';\nimport { useAppTheme } from '../../theme/AppThemeProvider';\nimport { ConversationListScreen } from './ConversationListScreen';\nimport { ContactsScreen } from './ContactsScreen';\nimport { ThreadScreen } from './ThreadScreen';\nimport { useConversations, useMarkRead, useThread } from './useChatData';\nimport { VIEW_CHANNEL_MUTATION, YANTRA_CHAT_INVITER_QUERY } from './chatOperations';\nimport { buildChatInviteUrl, chatInviteMessage } from './chatInviteLink';\n\n/** Used only when the host predates `chatInviteUrl`; the host supplies the per-environment domain. */\nconst DEFAULT_INVITE_WEB_BASE = 'https://yantra.app';\nimport { useContactMatches, type MatchedContact } from './useContactMatches';\nimport type { WirePost } from './chatModel';\n\n/** The screens the remote routes between internally. */\nexport type ChatRoute = 'list' | 'thread' | 'contacts' | 'newGroup' | 'groupInfo';\n\nexport function NativeChatExperience({\n viewer,\n sessionError,\n services,\n initialChannelId,\n initialPeer,\n onActiveChannelChange,\n onInternalRouteChange,\n testID,\n}: NativeChatExperienceProps) {\n const { isDark } = useAppTheme();\n const theme = isDark ? chatThemes.dark : chatThemes.light;\n\n const [route, setRoute] = React.useState<ChatRoute>(initialChannelId ? 'thread' : 'list');\n const [activeChannelId, setActiveChannelId] = React.useState<string | null>(initialChannelId ?? null);\n const [dismissedError, setDismissedError] = React.useState<string | null>(null);\n\n const conversations = useConversations(services, viewer, { syncListRealtime: route === 'list' });\n const contactMatches = useContactMatches(services.gql, services.contacts, viewer, services.phone);\n const [busyContactId, setBusyContactId] = React.useState<string | null>(null);\n const [startChatError, setStartChatError] = React.useState<string | null>(null);\n const onThreadListUpdate = React.useCallback(\n (post: WirePost) => {\n if (!activeChannelId) return;\n conversations.applyConversationMessage(activeChannelId, post);\n // The viewer is looking at this thread, so a message arriving here is already read.\n conversations.markRead(activeChannelId);\n },\n [activeChannelId, conversations.applyConversationMessage, conversations.markRead],\n );\n const thread = useThread(services.gql, route === 'thread' ? activeChannelId : null, viewer, {\n onConversationMessage: onThreadListUpdate,\n });\n\n // Clearing the badge is local-first; the mutation is fire-and-forget.\n useMarkRead(services.gql, route === 'thread' ? activeChannelId : null, conversations.markRead);\n\n // Mark read again on LEAVING a thread, so messages that arrived while it was open\n // are not counted as unread by the server on the next list load.\n const threadOpenRef = React.useRef<string | null>(null);\n React.useEffect(() => {\n const open = route === 'thread' ? activeChannelId : null;\n const left = threadOpenRef.current;\n if (left && left !== open) void services.gql.mutate(VIEW_CHANNEL_MUTATION, { id: left });\n threadOpenRef.current = open;\n }, [route, activeChannelId, services.gql]);\n\n // The host suppresses a foreground push banner for the conversation already\n // on screen, so it has to be told every time that changes.\n React.useEffect(() => {\n onActiveChannelChange?.(route === 'thread' ? activeChannelId : null);\n }, [route, activeChannelId, onActiveChannelChange]);\n\n React.useEffect(() => {\n const internal: 'list' | 'thread' | 'contacts' | 'register' =\n route === 'thread' ? 'thread' : route === 'contacts' ? 'contacts' : 'list';\n onInternalRouteChange?.(internal);\n }, [route, onInternalRouteChange]);\n\n // The address book changes outside the app, so re-read it whenever the picker opens\n // and whenever the app returns to the foreground while the picker is showing.\n const refreshContactsRef = React.useRef(contactMatches.refresh);\n refreshContactsRef.current = contactMatches.refresh;\n const contactsGranted = contactMatches.permission === 'granted';\n React.useEffect(() => {\n if (route !== 'contacts' || !contactsGranted) return undefined;\n void refreshContactsRef.current();\n const sub = AppState.addEventListener('change', (state) => {\n if (state === 'active') void refreshContactsRef.current();\n });\n return () => sub.remove();\n }, [route, contactsGranted]);\n\n const activeConversation = React.useMemo(\n () => conversations.conversations.find((c) => c.id === activeChannelId) ?? null,\n [conversations.conversations, activeChannelId],\n );\n\n // Android back pops the internal stack before the host's navigator sees it.\n React.useEffect(() => {\n if (Platform.OS !== 'android') return undefined;\n const sub = BackHandler.addEventListener('hardwareBackPress', () => {\n if (route === 'list') return false;\n setRoute('list');\n return true;\n });\n return () => sub.remove();\n }, [route]);\n\n const openThread = React.useCallback((id: string) => {\n setActiveChannelId(id);\n setRoute('thread');\n }, []);\n\n /**\n * Open (or reuse) the conversation with a matched contact. createDirectChannel\n * is idempotent server-side, so tapping someone you already talk to lands in\n * the existing thread rather than making a second one.\n */\n const startChatWith = React.useCallback(\n async (contact: MatchedContact) => {\n if (!contact.match || busyContactId) return;\n setBusyContactId(contact.id);\n setStartChatError(null);\n try {\n const id = await conversations.openDirect(contact.match.userId, contact.match.displayName);\n if (id) openThread(id);\n else setStartChatError('Could not start this conversation. Please try again.');\n } finally {\n setBusyContactId(null);\n }\n },\n [conversations, openThread, busyContactId],\n );\n\n // Opened from a chat invite link: go straight into the conversation with the inviter\n // (createDirectChannel is idempotent, so reopening an old invite lands in the same thread).\n // The link is editable by anyone, so the server has to vouch for the inviter first, and\n // its name is used - never the one in the link.\n // Tracks the invite object, not the user: every tap on a link arrives as a new object,\n // so reopening a link retries, while re-renders of the same one do not.\n const openedPeer = React.useRef<typeof initialPeer | null>(null);\n const [inviteNotice, setInviteNotice] = React.useState<string | null>(null);\n const { openDirect } = conversations;\n React.useEffect(() => {\n const peer = initialPeer;\n if (!peer?.userId || !viewer?.userId || peer.userId === viewer.userId) return;\n if (openedPeer.current === peer) return;\n openedPeer.current = peer;\n void (async () => {\n const res = await services.gql.query<{\n yantraChatInviter?: { userId: string; displayName: string } | null;\n }>(YANTRA_CHAT_INVITER_QUERY, { userId: peer.userId }, { fetchPolicy: 'network-only' });\n const inviter = res.data?.yantraChatInviter;\n if (!inviter) {\n setInviteNotice(\n res.errors?.length\n ? 'Could not open this invite. Check your connection and open the link again.'\n : 'This invite link is not valid anymore. Ask for a new one.',\n );\n return;\n }\n const id = await openDirect(inviter.userId, inviter.displayName);\n if (id) openThread(id);\n else setInviteNotice(`Could not start a conversation with ${inviter.displayName}. Please try again.`);\n })();\n }, [initialPeer, viewer?.userId, services.gql, openDirect, openThread]);\n\n /**\n * Invite someone who is not here yet.\n *\n * With phone-based membership (issue #1312):\n * 1. Call the connector to generate a deep link\n * 2. Open SMS composer with the deep link (user approves before sending)\n * 3. Fall back to share sheet where SMS is unavailable (simulators)\n *\n * The deep link includes the invitee's phone and the inviter's phone,\n * enabling install-then-attribute and direct link-back to the inviter\n * after the app is installed.\n */\n const inviteContact = React.useCallback(\n async (contact: MatchedContact) => {\n const link = services.link;\n\n if (!link || !viewer?.userId) return;\n const smsTo = contact.rawNumbers?.length ? contact.rawNumbers.slice(0, 1) : contact.e164.slice(0, 1);\n if (!smsTo.length) return;\n\n // The link names the inviter (so the app can open this conversation on arrival) and\n // carries no phone numbers.\n const invite = { inviterId: viewer.userId, inviterName: viewer.displayName ?? null };\n const url = services.phone?.chatInviteUrl?.(invite) ?? buildChatInviteUrl(DEFAULT_INVITE_WEB_BASE, invite);\n const body = chatInviteMessage(viewer.displayName, url);\n\n // Saved numbers, not our E.164 guesses: the phone dials them in its own region.\n const result = await link.composeSms(smsTo, body);\n if (result === 'unavailable') await link.share(body);\n },\n [services.link, services.phone, viewer],\n );\n\n const activeTitle = React.useMemo(\n () => conversations.conversations.find((c) => c.id === activeChannelId)?.title ?? 'Chat',\n [conversations.conversations, activeChannelId],\n );\n\n if (sessionError) {\n return (\n <Shell theme={theme} testID={testID}>\n <EmptyState\n icon=\"alert-circle-outline\"\n title=\"Chat is unavailable\"\n detail={sessionError}\n theme={theme}\n />\n </Shell>\n );\n }\n\n // Session still resolving: a spinner, never an error. org/user arrive a beat\n // after mount and a flash of failure reads as a broken feature.\n if (!viewer) {\n return (\n <Shell theme={theme} testID={testID}>\n <View style={styles.center}>\n <ActivityIndicator />\n </View>\n </Shell>\n );\n }\n\n if (route === 'thread' && activeChannelId) {\n const threadError = thread.error === dismissedError ? null : thread.error;\n return (\n <Shell theme={theme} testID={testID}>\n <ThreadScreen\n title={activeTitle}\n avatarUrl={activeConversation?.avatarUrl}\n messages={thread.messages}\n loading={thread.loading}\n loadingMore={thread.loadingMore}\n error={threadError}\n theme={theme}\n connectionState={services.gql.connectionState}\n haptics={services.haptics}\n onBack={() => setRoute('list')}\n onSend={(body) => void thread.send(body)}\n onLoadMore={thread.loadMore}\n onRetry={thread.retry}\n onReact={(messageId, emoji) => {\n const current = thread.messages\n .find((m) => m.id === messageId)\n ?.reactions.find((r) => r.mine)?.reaction;\n // Tapping your current Tapback removes it, as in iMessage.\n thread.react(messageId, current === emoji ? null : emoji);\n }}\n onCopyText={services.clipboard ? (text) => void services.clipboard?.copy(text) : undefined}\n onShareText={services.link ? (text) => void services.link?.share(text) : undefined}\n onDismissError={() => setDismissedError(thread.error)}\n />\n </Shell>\n );\n }\n\n if (route === 'contacts') {\n return (\n <Shell theme={theme} testID={testID}>\n <ContactsScreen\n permission={contactMatches.permission}\n loading={contactMatches.loading}\n error={startChatError ?? contactMatches.error}\n onYantra={contactMatches.onYantra}\n invitable={contactMatches.invitable}\n theme={theme}\n busyContactId={busyContactId}\n onBack={() => setRoute('list')}\n onRequestAccess={() => void contactMatches.requestAccess()}\n onOpenSettings={() => void contactMatches.openSettings()}\n onStartChat={(c) => void startChatWith(c)}\n onInvite={(c) => void inviteContact(c)}\n limited={contactMatches.limited}\n onChooseMoreContacts={\n contactMatches.chooseMoreContacts ? () => void contactMatches.chooseMoreContacts?.() : undefined\n }\n onDismissError={() => {\n if (startChatError) setStartChatError(null);\n else void contactMatches.refresh();\n }}\n />\n </Shell>\n );\n }\n\n const listError = conversations.error === dismissedError ? null : conversations.error;\n return (\n <Shell theme={theme} testID={testID}>\n <ConversationListScreen\n conversations={conversations.conversations}\n loading={conversations.loading}\n refreshing={conversations.refreshing}\n error={listError}\n theme={theme}\n connectionState={services.gql.connectionState}\n canInvite\n onRefresh={() => {\n conversations.refresh();\n void contactMatches.refresh();\n }}\n onRetry={conversations.refresh}\n loadingMore={conversations.loadingMore}\n onLoadMore={conversations.loadMore}\n onOpen={openThread}\n onNewChat={() => setRoute('contacts')}\n onDismissError={() => setDismissedError(conversations.error)}\n notice={inviteNotice}\n onDismissNotice={() => setInviteNotice(null)}\n />\n </Shell>\n );\n}\n\nfunction Shell({ theme, testID, children }: { theme: { bg: string }; testID?: string; children: React.ReactNode }) {\n return (\n <View testID={testID ?? 'chat-native'} style={[styles.fill, { backgroundColor: theme.bg }]}>\n {children}\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n fill: { flex: 1 },\n center: { flex: 1, alignItems: 'center', justifyContent: 'center' },\n});\n\nexport default NativeChatExperience;\n"],"names":[],"mappings":"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"}
@@ -46,7 +46,7 @@ function parseChatInviteUrl(url) {
46
46
  }
47
47
  function chatInviteMessage(inviterName, url) {
48
48
  const who = (inviterName == null ? void 0 : inviterName.trim()) || "A friend";
49
- return `${who} invited you to chat on Yantra.
49
+ return `${who} invited you to chat on Yantra. Tap the link below to join.
50
50
 
51
- Tap to join: ${url}`;
51
+ ${url}`;
52
52
  }export{CHAT_INVITE_PATH,buildChatInviteUrl,chatInviteMessage,parseChatInviteUrl};//# sourceMappingURL=chatInviteLink.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chatInviteLink.js","sources":["../../../src/features/messaging/chatInviteLink.ts"],"sourcesContent":["/**\n * Yantra Chat invite links (issue #1312).\n *\n * https://<web>/chat/invite?by=<inviterAccountId>&n=<inviterName>\n *\n * The same link must work as a universal/app link (app installed), as a web page\n * (app not installed - the landing page offers the stores and an \"Open in Yantra\"\n * button), and through the app's custom schemes. It carries the inviter's account\n * id so the app can open the conversation straight away - and deliberately NO phone\n * numbers, which the previous `?phone=&inviter=` form leaked into every forwarded SMS.\n *\n * Hand-parsed rather than `new URL(...)`: React Native's URL has no `searchParams`\n * without a polyfill and throws on custom schemes.\n */\n\nexport const CHAT_INVITE_PATH = '/chat/invite';\nexport const CHAT_INVITE_SCHEME = 'yantraapp';\n\nexport interface ChatInvite {\n inviterId: string;\n inviterName: string | null;\n}\n\nconst ACCOUNT_ID = /^[a-f0-9]{24}$/i;\n/** `chat/invite` as whole path segments, after any scheme/host/`--` prefix. */\nconst CHAT_INVITE_SEGMENTS = /(?:^|[/:])chat\\/invite(?:$|[/?#])/i;\n\nfunction query(url: string): Record<string, string> {\n const withoutFragment = url.split('#')[0];\n const at = withoutFragment.indexOf('?');\n const out: Record<string, string> = {};\n if (at === -1) return out;\n for (const pair of withoutFragment.slice(at + 1).split('&')) {\n const eq = pair.indexOf('=');\n if (eq <= 0) continue;\n try {\n const key = decodeURIComponent(pair.slice(0, eq)).trim();\n const value = decodeURIComponent(pair.slice(eq + 1).replace(/\\+/g, ' ')).trim();\n if (key && value && !(key in out)) out[key] = value;\n } catch {\n // A malformed escape in one pair must not discard the others.\n }\n }\n return out;\n}\n\nfunction inviteQuery(invite: ChatInvite): string {\n const params = [`by=${encodeURIComponent(invite.inviterId)}`];\n const name = invite.inviterName?.trim();\n if (name) params.push(`n=${encodeURIComponent(name.slice(0, 60))}`);\n return params.join('&');\n}\n\nexport function buildChatInviteUrl(baseUrl: string, invite: ChatInvite): string {\n const base = baseUrl.trim().replace(/\\/+$/, '');\n return `${base}${CHAT_INVITE_PATH}?${inviteQuery(invite)}`;\n}\n\n/** The app's own scheme for the same invite - what the web page's \"Open in Yantra\" uses. */\nexport function chatInviteAppUrl(invite: ChatInvite): string {\n return `${CHAT_INVITE_SCHEME}://chat/invite?${inviteQuery(invite)}`;\n}\n\nexport function parseChatInviteUrl(url: string | null | undefined): ChatInvite | null {\n if (!url) return null;\n const value = String(url);\n const queryAt = value.indexOf('?');\n const path = queryAt === -1 ? value : value.slice(0, queryAt);\n if (!CHAT_INVITE_SEGMENTS.test(path)) return null;\n const params = query(value);\n const inviterId = params.by ?? '';\n if (!ACCOUNT_ID.test(inviterId)) return null;\n return { inviterId, inviterName: params.n || null };\n}\n\nexport function chatInviteMessage(inviterName: string | null | undefined, url: string): string {\n const who = inviterName?.trim() || 'A friend';\n return `${who} invited you to chat on Yantra.\\n\\nTap to join: ${url}`;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chatInviteLink.js","sources":["../../../src/features/messaging/chatInviteLink.ts"],"sourcesContent":["/**\n * Yantra Chat invite links (issue #1312).\n *\n * https://<web>/chat/invite?by=<inviterAccountId>&n=<inviterName>\n *\n * The same link must work as a universal/app link (app installed), as a web page\n * (app not installed - the landing page offers the stores and an \"Open in Yantra\"\n * button), and through the app's custom schemes. It carries the inviter's account\n * id so the app can open the conversation straight away - and deliberately NO phone\n * numbers, which the previous `?phone=&inviter=` form leaked into every forwarded SMS.\n *\n * Hand-parsed rather than `new URL(...)`: React Native's URL has no `searchParams`\n * without a polyfill and throws on custom schemes.\n */\n\nexport const CHAT_INVITE_PATH = '/chat/invite';\nexport const CHAT_INVITE_SCHEME = 'yantraapp';\n\nexport interface ChatInvite {\n inviterId: string;\n inviterName: string | null;\n}\n\nconst ACCOUNT_ID = /^[a-f0-9]{24}$/i;\n/** `chat/invite` as whole path segments, after any scheme/host/`--` prefix. */\nconst CHAT_INVITE_SEGMENTS = /(?:^|[/:])chat\\/invite(?:$|[/?#])/i;\n\nfunction query(url: string): Record<string, string> {\n const withoutFragment = url.split('#')[0];\n const at = withoutFragment.indexOf('?');\n const out: Record<string, string> = {};\n if (at === -1) return out;\n for (const pair of withoutFragment.slice(at + 1).split('&')) {\n const eq = pair.indexOf('=');\n if (eq <= 0) continue;\n try {\n const key = decodeURIComponent(pair.slice(0, eq)).trim();\n const value = decodeURIComponent(pair.slice(eq + 1).replace(/\\+/g, ' ')).trim();\n if (key && value && !(key in out)) out[key] = value;\n } catch {\n // A malformed escape in one pair must not discard the others.\n }\n }\n return out;\n}\n\nfunction inviteQuery(invite: ChatInvite): string {\n const params = [`by=${encodeURIComponent(invite.inviterId)}`];\n const name = invite.inviterName?.trim();\n if (name) params.push(`n=${encodeURIComponent(name.slice(0, 60))}`);\n return params.join('&');\n}\n\nexport function buildChatInviteUrl(baseUrl: string, invite: ChatInvite): string {\n const base = baseUrl.trim().replace(/\\/+$/, '');\n return `${base}${CHAT_INVITE_PATH}?${inviteQuery(invite)}`;\n}\n\n/** The app's own scheme for the same invite - what the web page's \"Open in Yantra\" uses. */\nexport function chatInviteAppUrl(invite: ChatInvite): string {\n return `${CHAT_INVITE_SCHEME}://chat/invite?${inviteQuery(invite)}`;\n}\n\nexport function parseChatInviteUrl(url: string | null | undefined): ChatInvite | null {\n if (!url) return null;\n const value = String(url);\n const queryAt = value.indexOf('?');\n const path = queryAt === -1 ? value : value.slice(0, queryAt);\n if (!CHAT_INVITE_SEGMENTS.test(path)) return null;\n const params = query(value);\n const inviterId = params.by ?? '';\n if (!ACCOUNT_ID.test(inviterId)) return null;\n return { inviterId, inviterName: params.n || null };\n}\n\n/**\n * The invite SMS/iMessage text. The link goes last and on its own line: iMessage turns a\n * trailing link into a preview card (the text above must read fine without it), and plain\n * SMS shows it as a tappable link.\n */\nexport function chatInviteMessage(inviterName: string | null | undefined, url: string): string {\n const who = inviterName?.trim() || 'A friend';\n return `${who} invited you to chat on Yantra. Tap the link below to join.\\n\\n${url}`;\n}\n"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- const list = (s) => Array.from(s.replace(/\s+/g, " ").trim().split(" "));
1
+ import {EMOJI_KEYWORDS_SOURCE}from'./emojiKeywords.js';const list = (s) => Array.from(s.replace(/\s+/g, " ").trim().split(" "));
2
2
  const EMOJI_CATEGORIES = [{
3
3
  key: "smileys",
4
4
  title: "Smileys & People",
@@ -33,4 +33,28 @@ function emojiFromInput(text) {
33
33
  if (!value || value.length > 16) return null;
34
34
  if (/[A-Za-z0-9\s.,;:'"!?@#$%^&*()\-_=+[\]{}<>/\\|`~]/.test(value)) return null;
35
35
  return value;
36
- }export{EMOJI_CATEGORIES,emojiFromInput};//# sourceMappingURL=emojiCatalog.js.map
36
+ }
37
+ let emojiIndex = null;
38
+ function getEmojiIndex() {
39
+ if (!emojiIndex) {
40
+ emojiIndex = EMOJI_KEYWORDS_SOURCE.split("\n").map((line) => line.trim().split(" ")).filter((parts) => parts.length > 1).map(([emoji, ...words]) => ({
41
+ emoji,
42
+ words
43
+ }));
44
+ }
45
+ return emojiIndex;
46
+ }
47
+ function searchEmoji(query, limit = 96) {
48
+ const terms = query.toLowerCase().trim().split(/\s+/).filter(Boolean);
49
+ if (!terms.length) return [];
50
+ const named = [];
51
+ const tagged = [];
52
+ for (const {
53
+ emoji,
54
+ words
55
+ } of getEmojiIndex()) {
56
+ if (!terms.every((t) => words.some((w) => w.startsWith(t)))) continue;
57
+ (words[0].startsWith(terms[0]) ? named : tagged).push(emoji);
58
+ }
59
+ return [...named, ...tagged].slice(0, limit);
60
+ }export{EMOJI_CATEGORIES,emojiFromInput,searchEmoji};//# sourceMappingURL=emojiCatalog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"emojiCatalog.js","sources":["../../../src/features/messaging/emojiCatalog.ts"],"sourcesContent":["/**\n * Emoji grid for the \"+\" Tapback picker. Plain data rather than an emoji\n * package: the remote ships no extra dependencies, and the system emoji\n * keyboard in the picker covers everything not listed here.\n */\nexport interface EmojiCategory {\n key: string;\n title: string;\n emojis: string[];\n}\n\nconst list = (s: string) => Array.from(s.replace(/\\s+/g, ' ').trim().split(' '));\n\nexport const EMOJI_CATEGORIES: EmojiCategory[] = [\n {\n key: 'smileys',\n title: 'Smileys & People',\n emojis: list(\n `๐Ÿ˜€ ๐Ÿ˜ƒ ๐Ÿ˜„ ๐Ÿ˜ ๐Ÿ˜† ๐Ÿ˜… ๐Ÿคฃ ๐Ÿ˜‚ ๐Ÿ™‚ ๐Ÿ™ƒ ๐Ÿ˜‰ ๐Ÿ˜Š ๐Ÿ˜‡ ๐Ÿฅฐ ๐Ÿ˜ ๐Ÿคฉ ๐Ÿ˜˜ ๐Ÿ˜— ๐Ÿ˜š ๐Ÿ˜™ ๐Ÿฅฒ ๐Ÿ˜‹ ๐Ÿ˜› ๐Ÿ˜œ ๐Ÿคช ๐Ÿ˜ ๐Ÿค‘ ๐Ÿค— ๐Ÿคญ ๐Ÿคซ ๐Ÿค” ๐Ÿค ๐Ÿคจ ๐Ÿ˜ ๐Ÿ˜‘ ๐Ÿ˜ถ ๐Ÿ˜ ๐Ÿ˜’ ๐Ÿ™„ ๐Ÿ˜ฌ ๐Ÿ˜ฎโ€๐Ÿ’จ ๐Ÿคฅ ๐Ÿ˜Œ ๐Ÿ˜” ๐Ÿ˜ช ๐Ÿคค ๐Ÿ˜ด ๐Ÿ˜ท ๐Ÿค’ ๐Ÿค• ๐Ÿคข ๐Ÿคฎ ๐Ÿฅต ๐Ÿฅถ ๐Ÿฅด ๐Ÿ˜ต ๐Ÿคฏ ๐Ÿค  ๐Ÿฅณ ๐Ÿฅธ ๐Ÿ˜Ž ๐Ÿค“ ๐Ÿง ๐Ÿ˜• ๐Ÿ˜Ÿ ๐Ÿ™ ๐Ÿ˜ฎ ๐Ÿ˜ฏ ๐Ÿ˜ฒ ๐Ÿ˜ณ ๐Ÿฅบ ๐Ÿ˜ฆ ๐Ÿ˜ง ๐Ÿ˜จ ๐Ÿ˜ฐ ๐Ÿ˜ฅ ๐Ÿ˜ข ๐Ÿ˜ญ ๐Ÿ˜ฑ ๐Ÿ˜– ๐Ÿ˜ฃ ๐Ÿ˜ž ๐Ÿ˜“ ๐Ÿ˜ฉ ๐Ÿ˜ซ ๐Ÿฅฑ ๐Ÿ˜ค ๐Ÿ˜ก ๐Ÿ˜  ๐Ÿคฌ ๐Ÿ˜ˆ ๐Ÿ‘ฟ ๐Ÿ’€ ๐Ÿ’ฉ ๐Ÿคก ๐Ÿ‘ป ๐Ÿ‘ฝ ๐Ÿค– ๐Ÿ˜บ ๐Ÿ˜ธ ๐Ÿ˜น ๐Ÿ˜ป ๐Ÿ˜ผ ๐Ÿ˜ฝ ๐Ÿ™€ ๐Ÿ˜ฟ ๐Ÿ˜พ`,\n ),\n },\n {\n key: 'gestures',\n title: 'Gestures & Body',\n emojis: list(\n `๐Ÿ‘‹ ๐Ÿคš ๐Ÿ–๏ธ โœ‹ ๐Ÿ–– ๐Ÿ‘Œ ๐ŸคŒ ๐Ÿค โœŒ๏ธ ๐Ÿคž ๐ŸคŸ ๐Ÿค˜ ๐Ÿค™ ๐Ÿ‘ˆ ๐Ÿ‘‰ ๐Ÿ‘† ๐Ÿ‘‡ โ˜๏ธ ๐Ÿ‘ ๐Ÿ‘Ž โœŠ ๐Ÿ‘Š ๐Ÿค› ๐Ÿคœ ๐Ÿ‘ ๐Ÿ™Œ ๐Ÿ‘ ๐Ÿคฒ ๐Ÿค ๐Ÿ™ โœ๏ธ ๐Ÿ’… ๐Ÿ’ช ๐Ÿฆพ ๐Ÿง  ๐Ÿ‘€ ๐Ÿ‘๏ธ ๐Ÿ‘… ๐Ÿ‘„ ๐Ÿ’‹ ๐Ÿซถ ๐Ÿซก ๐Ÿซ  ๐Ÿซข ๐Ÿซฃ ๐Ÿซฅ`,\n ),\n },\n {\n key: 'hearts',\n title: 'Hearts & Symbols',\n emojis: list(\n `โค๏ธ ๐Ÿงก ๐Ÿ’› ๐Ÿ’š ๐Ÿ’™ ๐Ÿ’œ ๐Ÿ–ค ๐Ÿค ๐ŸคŽ ๐Ÿ’” โค๏ธโ€๐Ÿ”ฅ โค๏ธโ€๐Ÿฉน โฃ๏ธ ๐Ÿ’• ๐Ÿ’ž ๐Ÿ’“ ๐Ÿ’— ๐Ÿ’– ๐Ÿ’˜ ๐Ÿ’ ๐Ÿ’ฏ ๐Ÿ’ข ๐Ÿ’ฅ ๐Ÿ’ซ ๐Ÿ’ฆ ๐Ÿ’จ ๐Ÿ•ณ๏ธ ๐Ÿ’ฌ ๐Ÿ’ญ ๐Ÿ’ค โœจ โญ ๐ŸŒŸ ๐Ÿ”ฅ โšก โ€ผ๏ธ โ‰๏ธ โ“ โ— โœ… โŒ โญ• ๐Ÿšซ ๐Ÿ’ก ๐ŸŽฏ ๐Ÿ”” ๐ŸŽต ๐ŸŽถ โž• โž– โœ”๏ธ ๐Ÿ†— ๐Ÿ†’ ๐Ÿ†• ๐Ÿ”`,\n ),\n },\n {\n key: 'animals',\n title: 'Animals & Nature',\n emojis: list(\n `๐Ÿถ ๐Ÿฑ ๐Ÿญ ๐Ÿน ๐Ÿฐ ๐ŸฆŠ ๐Ÿป ๐Ÿผ ๐Ÿจ ๐Ÿฏ ๐Ÿฆ ๐Ÿฎ ๐Ÿท ๐Ÿธ ๐Ÿต ๐Ÿ™ˆ ๐Ÿ™‰ ๐Ÿ™Š ๐Ÿ” ๐Ÿง ๐Ÿฆ ๐Ÿฆ† ๐Ÿฆ… ๐Ÿฆ‰ ๐Ÿบ ๐Ÿด ๐Ÿฆ„ ๐Ÿ ๐Ÿฆ‹ ๐ŸŒ ๐Ÿž ๐Ÿข ๐Ÿ ๐Ÿ™ ๐Ÿฆ€ ๐Ÿ  ๐Ÿฌ ๐Ÿณ ๐Ÿฆˆ ๐ŸŠ ๐Ÿ˜ ๐Ÿฆ’ ๐Ÿช ๐Ÿ• ๐Ÿˆ ๐ŸŒต ๐ŸŽ„ ๐ŸŒฒ ๐ŸŒด ๐ŸŒฑ ๐Ÿ€ ๐Ÿ ๐Ÿ‚ ๐ŸŒท ๐ŸŒน ๐ŸŒบ ๐ŸŒธ ๐ŸŒผ ๐ŸŒป ๐ŸŒž ๐ŸŒ ๐ŸŒš ๐ŸŒ™ ๐ŸŒˆ โ˜€๏ธ โ›… โ˜๏ธ ๐ŸŒง๏ธ โ›ˆ๏ธ โ„๏ธ โ˜ƒ๏ธ ๐ŸŒŠ`,\n ),\n },\n {\n key: 'food',\n title: 'Food & Drink',\n emojis: list(\n `๐Ÿ ๐ŸŽ ๐Ÿ ๐ŸŠ ๐Ÿ‹ ๐ŸŒ ๐Ÿ‰ ๐Ÿ‡ ๐Ÿ“ ๐Ÿซ ๐Ÿ’ ๐Ÿ‘ ๐Ÿฅญ ๐Ÿ ๐Ÿฅฅ ๐Ÿฅ ๐Ÿ… ๐Ÿฅ‘ ๐ŸŒถ๏ธ ๐ŸŒฝ ๐Ÿฅ• ๐Ÿฅ” ๐Ÿฅ ๐Ÿž ๐Ÿง€ ๐Ÿฅš ๐Ÿณ ๐Ÿฅž ๐Ÿฅ“ ๐Ÿ— ๐Ÿ– ๐ŸŒญ ๐Ÿ” ๐ŸŸ ๐Ÿ• ๐Ÿฅช ๐ŸŒฎ ๐ŸŒฏ ๐Ÿฅ— ๐Ÿ ๐Ÿœ ๐Ÿฒ ๐Ÿ› ๐Ÿฃ ๐Ÿฑ ๐ŸฅŸ ๐Ÿค ๐Ÿš ๐Ÿฆ ๐Ÿฉ ๐Ÿช ๐ŸŽ‚ ๐Ÿฐ ๐Ÿง ๐Ÿซ ๐Ÿฌ ๐Ÿญ ๐Ÿฟ โ˜• ๐Ÿต ๐Ÿงƒ ๐Ÿฅค ๐Ÿง‹ ๐Ÿบ ๐Ÿป ๐Ÿฅ‚ ๐Ÿท ๐Ÿธ ๐Ÿน`,\n ),\n },\n {\n key: 'activity',\n title: 'Activity & Objects',\n emojis: list(\n `โšฝ ๐Ÿ€ ๐Ÿˆ โšพ ๐ŸŽพ ๐Ÿ ๐Ÿ‰ ๐ŸŽฑ ๐Ÿ“ ๐Ÿธ ๐Ÿ โ›ณ ๐ŸฅŠ ๐Ÿ† ๐Ÿฅ‡ ๐Ÿฅˆ ๐Ÿฅ‰ ๐Ÿ… ๐ŸŽ–๏ธ ๐ŸŽ‰ ๐ŸŽŠ ๐ŸŽˆ ๐ŸŽ ๐ŸŽ€ ๐ŸŽฎ ๐Ÿ•น๏ธ ๐ŸŽฒ ๐Ÿงฉ ๐ŸŽจ ๐ŸŽฌ ๐ŸŽค ๐ŸŽง ๐ŸŽธ ๐ŸŽน ๐Ÿฅ ๐Ÿ“ท ๐Ÿ“ฑ ๐Ÿ’ป โŒจ๏ธ ๐Ÿ–ฅ๏ธ ๐Ÿ”‹ ๐Ÿ’ฐ ๐Ÿ’ต ๐Ÿ’ณ ๐Ÿ“ˆ ๐Ÿ“‰ ๐Ÿ“Œ ๐Ÿ“Ž โœ๏ธ ๐Ÿ“ ๐Ÿ“š ๐Ÿ“… โฐ โŒ› ๐Ÿ”‘ ๐Ÿ”’ ๐Ÿ”จ ๐Ÿ› ๏ธ ๐Ÿ’Š ๐Ÿš— ๐Ÿš• ๐ŸšŒ ๐Ÿš€ โœˆ๏ธ ๐Ÿšฒ ๐Ÿ  ๐Ÿข ๐Ÿ—บ๏ธ ๐Ÿ–๏ธ โ›ฐ๏ธ`,\n ),\n },\n {\n key: 'flags',\n title: 'Flags',\n emojis: list(`๐Ÿณ๏ธ ๐Ÿด ๐Ÿ ๐Ÿšฉ ๐Ÿณ๏ธโ€๐ŸŒˆ ๐Ÿ‡ฎ๐Ÿ‡ณ ๐Ÿ‡บ๐Ÿ‡ธ ๐Ÿ‡ฌ๐Ÿ‡ง ๐Ÿ‡จ๐Ÿ‡ฆ ๐Ÿ‡ฆ๐Ÿ‡บ ๐Ÿ‡ฉ๐Ÿ‡ช ๐Ÿ‡ซ๐Ÿ‡ท ๐Ÿ‡ช๐Ÿ‡ธ ๐Ÿ‡ฎ๐Ÿ‡น ๐Ÿ‡ฏ๐Ÿ‡ต ๐Ÿ‡ฐ๐Ÿ‡ท ๐Ÿ‡จ๐Ÿ‡ณ ๐Ÿ‡ง๐Ÿ‡ท ๐Ÿ‡ฒ๐Ÿ‡ฝ ๐Ÿ‡ฆ๐Ÿ‡ช ๐Ÿ‡ธ๐Ÿ‡ฆ ๐Ÿ‡ธ๐Ÿ‡ฌ ๐Ÿ‡ณ๐Ÿ‡ฟ ๐Ÿ‡ฟ๐Ÿ‡ฆ ๐Ÿ‡ณ๐Ÿ‡ฌ ๐Ÿ‡ท๐Ÿ‡บ ๐Ÿ‡น๐Ÿ‡ท ๐Ÿ‡ณ๐Ÿ‡ฑ ๐Ÿ‡ธ๐Ÿ‡ช ๐Ÿ‡จ๐Ÿ‡ญ`),\n },\n];\n\n/**\n * The reaction to use from what the user typed in the picker's text field, or\n * null while it holds nothing emoji-like. Letters, digits and plain punctuation\n * are ignored so the field cannot turn into a free-text reaction.\n */\nexport function emojiFromInput(text: string): string | null {\n const value = text.trim();\n if (!value || value.length > 16) return null;\n if (/[A-Za-z0-9\\s.,;:'\"!?@#$%^&*()\\-_=+[\\]{}<>/\\\\|`~]/.test(value)) return null;\n return value;\n}\n"],"names":[],"mappings":"AAUA,MAAM,IAAO,GAAA,CAAC,CAAc,KAAA,KAAA,CAAM,KAAK,CAAE,CAAA,OAAA,CAAQ,MAAQ,EAAA,GAAG,CAAE,CAAA,IAAA,EAAO,CAAA,KAAA,CAAM,GAAG,CAAC,CAAA;AACxE,MAAM,mBAAoC,CAAC;AAAA,EAChD,GAAK,EAAA,SAAA;AAAA,EACL,KAAO,EAAA,kBAAA;AAAA,EACP,MAAA,EAAQ,KAAK,CAAqU,4jCAAA,CAAA;AACpV,CAAG,EAAA;AAAA,EACD,GAAK,EAAA,UAAA;AAAA,EACL,KAAO,EAAA,iBAAA;AAAA,EACP,MAAA,EAAQ,KAAK,CAA2I,0dAAA,CAAA;AAC1J,CAAG,EAAA;AAAA,EACD,GAAK,EAAA,QAAA;AAAA,EACL,KAAO,EAAA,kBAAA;AAAA,EACP,MAAA,EAAQ,KAAK,CAAmK,gkBAAA,CAAA;AAClL,CAAG,EAAA;AAAA,EACD,GAAK,EAAA,SAAA;AAAA,EACL,KAAO,EAAA,kBAAA;AAAA,EACP,MAAA,EAAQ,KAAK,CAAyN,iuBAAA,CAAA;AACxO,CAAG,EAAA;AAAA,EACD,GAAK,EAAA,MAAA;AAAA,EACL,KAAO,EAAA,cAAA;AAAA,EACP,MAAA,EAAQ,KAAK,CAAgN,orBAAA,CAAA;AAC/N,CAAG,EAAA;AAAA,EACD,GAAK,EAAA,UAAA;AAAA,EACL,KAAO,EAAA,oBAAA;AAAA,EACP,MAAA,EAAQ,KAAK,CAAoN,4tBAAA,CAAA;AACnO,CAAG,EAAA;AAAA,EACD,GAAK,EAAA,OAAA;AAAA,EACL,KAAO,EAAA,OAAA;AAAA,EACP,MAAA,EAAQ,KAAK,CAAkJ,uiBAAA,CAAA;AACjK,CAAC;AAOM,SAAS,eAAe,IAA6B,EAAA;AAC1D,EAAM,MAAA,KAAA,GAAQ,KAAK,IAAK,EAAA;AACxB,EAAA,IAAI,CAAC,KAAA,IAAS,KAAM,CAAA,MAAA,GAAS,IAAW,OAAA,IAAA;AACxC,EAAA,IAAI,kDAAmD,CAAA,IAAA,CAAK,KAAK,CAAA,EAAU,OAAA,IAAA;AAC3E,EAAO,OAAA,KAAA;AACT"}
1
+ {"version":3,"file":"emojiCatalog.js","sources":["../../../src/features/messaging/emojiCatalog.ts"],"sourcesContent":["import { EMOJI_KEYWORDS_SOURCE } from './emojiKeywords';\n\n/**\n * Emoji grid for the \"+\" Tapback picker. Plain data rather than an emoji\n * package: the remote ships no extra dependencies, and the system emoji\n * keyboard in the picker covers everything not listed here.\n */\nexport interface EmojiCategory {\n key: string;\n title: string;\n emojis: string[];\n}\n\nconst list = (s: string) => Array.from(s.replace(/\\s+/g, ' ').trim().split(' '));\n\nexport const EMOJI_CATEGORIES: EmojiCategory[] = [\n {\n key: 'smileys',\n title: 'Smileys & People',\n emojis: list(\n `๐Ÿ˜€ ๐Ÿ˜ƒ ๐Ÿ˜„ ๐Ÿ˜ ๐Ÿ˜† ๐Ÿ˜… ๐Ÿคฃ ๐Ÿ˜‚ ๐Ÿ™‚ ๐Ÿ™ƒ ๐Ÿ˜‰ ๐Ÿ˜Š ๐Ÿ˜‡ ๐Ÿฅฐ ๐Ÿ˜ ๐Ÿคฉ ๐Ÿ˜˜ ๐Ÿ˜— ๐Ÿ˜š ๐Ÿ˜™ ๐Ÿฅฒ ๐Ÿ˜‹ ๐Ÿ˜› ๐Ÿ˜œ ๐Ÿคช ๐Ÿ˜ ๐Ÿค‘ ๐Ÿค— ๐Ÿคญ ๐Ÿคซ ๐Ÿค” ๐Ÿค ๐Ÿคจ ๐Ÿ˜ ๐Ÿ˜‘ ๐Ÿ˜ถ ๐Ÿ˜ ๐Ÿ˜’ ๐Ÿ™„ ๐Ÿ˜ฌ ๐Ÿ˜ฎโ€๐Ÿ’จ ๐Ÿคฅ ๐Ÿ˜Œ ๐Ÿ˜” ๐Ÿ˜ช ๐Ÿคค ๐Ÿ˜ด ๐Ÿ˜ท ๐Ÿค’ ๐Ÿค• ๐Ÿคข ๐Ÿคฎ ๐Ÿฅต ๐Ÿฅถ ๐Ÿฅด ๐Ÿ˜ต ๐Ÿคฏ ๐Ÿค  ๐Ÿฅณ ๐Ÿฅธ ๐Ÿ˜Ž ๐Ÿค“ ๐Ÿง ๐Ÿ˜• ๐Ÿ˜Ÿ ๐Ÿ™ ๐Ÿ˜ฎ ๐Ÿ˜ฏ ๐Ÿ˜ฒ ๐Ÿ˜ณ ๐Ÿฅบ ๐Ÿ˜ฆ ๐Ÿ˜ง ๐Ÿ˜จ ๐Ÿ˜ฐ ๐Ÿ˜ฅ ๐Ÿ˜ข ๐Ÿ˜ญ ๐Ÿ˜ฑ ๐Ÿ˜– ๐Ÿ˜ฃ ๐Ÿ˜ž ๐Ÿ˜“ ๐Ÿ˜ฉ ๐Ÿ˜ซ ๐Ÿฅฑ ๐Ÿ˜ค ๐Ÿ˜ก ๐Ÿ˜  ๐Ÿคฌ ๐Ÿ˜ˆ ๐Ÿ‘ฟ ๐Ÿ’€ ๐Ÿ’ฉ ๐Ÿคก ๐Ÿ‘ป ๐Ÿ‘ฝ ๐Ÿค– ๐Ÿ˜บ ๐Ÿ˜ธ ๐Ÿ˜น ๐Ÿ˜ป ๐Ÿ˜ผ ๐Ÿ˜ฝ ๐Ÿ™€ ๐Ÿ˜ฟ ๐Ÿ˜พ`,\n ),\n },\n {\n key: 'gestures',\n title: 'Gestures & Body',\n emojis: list(\n `๐Ÿ‘‹ ๐Ÿคš ๐Ÿ–๏ธ โœ‹ ๐Ÿ–– ๐Ÿ‘Œ ๐ŸคŒ ๐Ÿค โœŒ๏ธ ๐Ÿคž ๐ŸคŸ ๐Ÿค˜ ๐Ÿค™ ๐Ÿ‘ˆ ๐Ÿ‘‰ ๐Ÿ‘† ๐Ÿ‘‡ โ˜๏ธ ๐Ÿ‘ ๐Ÿ‘Ž โœŠ ๐Ÿ‘Š ๐Ÿค› ๐Ÿคœ ๐Ÿ‘ ๐Ÿ™Œ ๐Ÿ‘ ๐Ÿคฒ ๐Ÿค ๐Ÿ™ โœ๏ธ ๐Ÿ’… ๐Ÿ’ช ๐Ÿฆพ ๐Ÿง  ๐Ÿ‘€ ๐Ÿ‘๏ธ ๐Ÿ‘… ๐Ÿ‘„ ๐Ÿ’‹ ๐Ÿซถ ๐Ÿซก ๐Ÿซ  ๐Ÿซข ๐Ÿซฃ ๐Ÿซฅ`,\n ),\n },\n {\n key: 'hearts',\n title: 'Hearts & Symbols',\n emojis: list(\n `โค๏ธ ๐Ÿงก ๐Ÿ’› ๐Ÿ’š ๐Ÿ’™ ๐Ÿ’œ ๐Ÿ–ค ๐Ÿค ๐ŸคŽ ๐Ÿ’” โค๏ธโ€๐Ÿ”ฅ โค๏ธโ€๐Ÿฉน โฃ๏ธ ๐Ÿ’• ๐Ÿ’ž ๐Ÿ’“ ๐Ÿ’— ๐Ÿ’– ๐Ÿ’˜ ๐Ÿ’ ๐Ÿ’ฏ ๐Ÿ’ข ๐Ÿ’ฅ ๐Ÿ’ซ ๐Ÿ’ฆ ๐Ÿ’จ ๐Ÿ•ณ๏ธ ๐Ÿ’ฌ ๐Ÿ’ญ ๐Ÿ’ค โœจ โญ ๐ŸŒŸ ๐Ÿ”ฅ โšก โ€ผ๏ธ โ‰๏ธ โ“ โ— โœ… โŒ โญ• ๐Ÿšซ ๐Ÿ’ก ๐ŸŽฏ ๐Ÿ”” ๐ŸŽต ๐ŸŽถ โž• โž– โœ”๏ธ ๐Ÿ†— ๐Ÿ†’ ๐Ÿ†• ๐Ÿ”`,\n ),\n },\n {\n key: 'animals',\n title: 'Animals & Nature',\n emojis: list(\n `๐Ÿถ ๐Ÿฑ ๐Ÿญ ๐Ÿน ๐Ÿฐ ๐ŸฆŠ ๐Ÿป ๐Ÿผ ๐Ÿจ ๐Ÿฏ ๐Ÿฆ ๐Ÿฎ ๐Ÿท ๐Ÿธ ๐Ÿต ๐Ÿ™ˆ ๐Ÿ™‰ ๐Ÿ™Š ๐Ÿ” ๐Ÿง ๐Ÿฆ ๐Ÿฆ† ๐Ÿฆ… ๐Ÿฆ‰ ๐Ÿบ ๐Ÿด ๐Ÿฆ„ ๐Ÿ ๐Ÿฆ‹ ๐ŸŒ ๐Ÿž ๐Ÿข ๐Ÿ ๐Ÿ™ ๐Ÿฆ€ ๐Ÿ  ๐Ÿฌ ๐Ÿณ ๐Ÿฆˆ ๐ŸŠ ๐Ÿ˜ ๐Ÿฆ’ ๐Ÿช ๐Ÿ• ๐Ÿˆ ๐ŸŒต ๐ŸŽ„ ๐ŸŒฒ ๐ŸŒด ๐ŸŒฑ ๐Ÿ€ ๐Ÿ ๐Ÿ‚ ๐ŸŒท ๐ŸŒน ๐ŸŒบ ๐ŸŒธ ๐ŸŒผ ๐ŸŒป ๐ŸŒž ๐ŸŒ ๐ŸŒš ๐ŸŒ™ ๐ŸŒˆ โ˜€๏ธ โ›… โ˜๏ธ ๐ŸŒง๏ธ โ›ˆ๏ธ โ„๏ธ โ˜ƒ๏ธ ๐ŸŒŠ`,\n ),\n },\n {\n key: 'food',\n title: 'Food & Drink',\n emojis: list(\n `๐Ÿ ๐ŸŽ ๐Ÿ ๐ŸŠ ๐Ÿ‹ ๐ŸŒ ๐Ÿ‰ ๐Ÿ‡ ๐Ÿ“ ๐Ÿซ ๐Ÿ’ ๐Ÿ‘ ๐Ÿฅญ ๐Ÿ ๐Ÿฅฅ ๐Ÿฅ ๐Ÿ… ๐Ÿฅ‘ ๐ŸŒถ๏ธ ๐ŸŒฝ ๐Ÿฅ• ๐Ÿฅ” ๐Ÿฅ ๐Ÿž ๐Ÿง€ ๐Ÿฅš ๐Ÿณ ๐Ÿฅž ๐Ÿฅ“ ๐Ÿ— ๐Ÿ– ๐ŸŒญ ๐Ÿ” ๐ŸŸ ๐Ÿ• ๐Ÿฅช ๐ŸŒฎ ๐ŸŒฏ ๐Ÿฅ— ๐Ÿ ๐Ÿœ ๐Ÿฒ ๐Ÿ› ๐Ÿฃ ๐Ÿฑ ๐ŸฅŸ ๐Ÿค ๐Ÿš ๐Ÿฆ ๐Ÿฉ ๐Ÿช ๐ŸŽ‚ ๐Ÿฐ ๐Ÿง ๐Ÿซ ๐Ÿฌ ๐Ÿญ ๐Ÿฟ โ˜• ๐Ÿต ๐Ÿงƒ ๐Ÿฅค ๐Ÿง‹ ๐Ÿบ ๐Ÿป ๐Ÿฅ‚ ๐Ÿท ๐Ÿธ ๐Ÿน`,\n ),\n },\n {\n key: 'activity',\n title: 'Activity & Objects',\n emojis: list(\n `โšฝ ๐Ÿ€ ๐Ÿˆ โšพ ๐ŸŽพ ๐Ÿ ๐Ÿ‰ ๐ŸŽฑ ๐Ÿ“ ๐Ÿธ ๐Ÿ โ›ณ ๐ŸฅŠ ๐Ÿ† ๐Ÿฅ‡ ๐Ÿฅˆ ๐Ÿฅ‰ ๐Ÿ… ๐ŸŽ–๏ธ ๐ŸŽ‰ ๐ŸŽŠ ๐ŸŽˆ ๐ŸŽ ๐ŸŽ€ ๐ŸŽฎ ๐Ÿ•น๏ธ ๐ŸŽฒ ๐Ÿงฉ ๐ŸŽจ ๐ŸŽฌ ๐ŸŽค ๐ŸŽง ๐ŸŽธ ๐ŸŽน ๐Ÿฅ ๐Ÿ“ท ๐Ÿ“ฑ ๐Ÿ’ป โŒจ๏ธ ๐Ÿ–ฅ๏ธ ๐Ÿ”‹ ๐Ÿ’ฐ ๐Ÿ’ต ๐Ÿ’ณ ๐Ÿ“ˆ ๐Ÿ“‰ ๐Ÿ“Œ ๐Ÿ“Ž โœ๏ธ ๐Ÿ“ ๐Ÿ“š ๐Ÿ“… โฐ โŒ› ๐Ÿ”‘ ๐Ÿ”’ ๐Ÿ”จ ๐Ÿ› ๏ธ ๐Ÿ’Š ๐Ÿš— ๐Ÿš• ๐ŸšŒ ๐Ÿš€ โœˆ๏ธ ๐Ÿšฒ ๐Ÿ  ๐Ÿข ๐Ÿ—บ๏ธ ๐Ÿ–๏ธ โ›ฐ๏ธ`,\n ),\n },\n {\n key: 'flags',\n title: 'Flags',\n emojis: list(`๐Ÿณ๏ธ ๐Ÿด ๐Ÿ ๐Ÿšฉ ๐Ÿณ๏ธโ€๐ŸŒˆ ๐Ÿ‡ฎ๐Ÿ‡ณ ๐Ÿ‡บ๐Ÿ‡ธ ๐Ÿ‡ฌ๐Ÿ‡ง ๐Ÿ‡จ๐Ÿ‡ฆ ๐Ÿ‡ฆ๐Ÿ‡บ ๐Ÿ‡ฉ๐Ÿ‡ช ๐Ÿ‡ซ๐Ÿ‡ท ๐Ÿ‡ช๐Ÿ‡ธ ๐Ÿ‡ฎ๐Ÿ‡น ๐Ÿ‡ฏ๐Ÿ‡ต ๐Ÿ‡ฐ๐Ÿ‡ท ๐Ÿ‡จ๐Ÿ‡ณ ๐Ÿ‡ง๐Ÿ‡ท ๐Ÿ‡ฒ๐Ÿ‡ฝ ๐Ÿ‡ฆ๐Ÿ‡ช ๐Ÿ‡ธ๐Ÿ‡ฆ ๐Ÿ‡ธ๐Ÿ‡ฌ ๐Ÿ‡ณ๐Ÿ‡ฟ ๐Ÿ‡ฟ๐Ÿ‡ฆ ๐Ÿ‡ณ๐Ÿ‡ฌ ๐Ÿ‡ท๐Ÿ‡บ ๐Ÿ‡น๐Ÿ‡ท ๐Ÿ‡ณ๐Ÿ‡ฑ ๐Ÿ‡ธ๐Ÿ‡ช ๐Ÿ‡จ๐Ÿ‡ญ`),\n },\n];\n\n/**\n * The reaction to use from what the user typed in the picker's text field, or\n * null while it holds nothing emoji-like. Letters, digits and plain punctuation\n * are ignored so the field cannot turn into a free-text reaction.\n */\nexport function emojiFromInput(text: string): string | null {\n const value = text.trim();\n if (!value || value.length > 16) return null;\n if (/[A-Za-z0-9\\s.,;:'\"!?@#$%^&*()\\-_=+[\\]{}<>/\\\\|`~]/.test(value)) return null;\n return value;\n}\n\ninterface EmojiEntry {\n emoji: string;\n words: string[];\n}\n\nlet emojiIndex: EmojiEntry[] | null = null;\n\n/** Parsed on first search only; the picker's grid does not need it. */\nfunction getEmojiIndex(): EmojiEntry[] {\n if (!emojiIndex) {\n emojiIndex = EMOJI_KEYWORDS_SOURCE.split('\\n')\n .map((line) => line.trim().split(' '))\n .filter((parts) => parts.length > 1)\n .map(([emoji, ...words]) => ({ emoji, words }));\n }\n return emojiIndex;\n}\n\n/**\n * Emoji whose name or keywords start with every word typed (\"sleep\" -> ๐Ÿ˜ด ๐Ÿ˜ช ๐Ÿ›Œ ๐Ÿ’ค,\n * \"red heart\" -> โค๏ธ). Emoji whose name starts with the first word come first.\n */\nexport function searchEmoji(query: string, limit = 96): string[] {\n const terms = query.toLowerCase().trim().split(/\\s+/).filter(Boolean);\n if (!terms.length) return [];\n const named: string[] = [];\n const tagged: string[] = [];\n for (const { emoji, words } of getEmojiIndex()) {\n if (!terms.every((t) => words.some((w) => w.startsWith(t)))) continue;\n (words[0].startsWith(terms[0]) ? named : tagged).push(emoji);\n }\n return [...named, ...tagged].slice(0, limit);\n}\n"],"names":[],"mappings":"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"}