@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.
- package/lib/features/chat/connectorConnect.js +2 -17
- package/lib/features/chat/connectorConnect.js.map +1 -1
- package/lib/features/messaging/ContactsScreen.js +61 -3
- package/lib/features/messaging/ContactsScreen.js.map +1 -1
- package/lib/features/messaging/MessageActions.js +11 -4
- package/lib/features/messaging/MessageActions.js.map +1 -1
- package/lib/features/messaging/NativeChatExperience.js +4 -1
- package/lib/features/messaging/NativeChatExperience.js.map +1 -1
- package/lib/features/messaging/chatInviteLink.js +2 -2
- package/lib/features/messaging/chatInviteLink.js.map +1 -1
- package/lib/features/messaging/emojiCatalog.js +26 -2
- package/lib/features/messaging/emojiCatalog.js.map +1 -1
- package/lib/features/messaging/emojiKeywords.js +1592 -0
- package/lib/features/messaging/emojiKeywords.js.map +1 -0
- package/lib/features/messaging/useContactMatches.js +13 -1
- package/lib/features/messaging/useContactMatches.js.map +1 -1
- package/lib/hooks/useHostConnectorCard.js +1 -1
- package/lib/hooks/useHostConnectorCard.js.map +1 -1
- package/lib/screens/Chat/index.js +1 -1
- package/package.json +5 -4
|
@@ -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
|
-
|
|
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
|
|
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
|
-
}
|
|
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":"
|
|
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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"}
|