@planningcenter/chat-react-native 3.35.0-rc.1 → 3.35.0-rc.2
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/build/contexts/conversation_context.d.ts +6 -1
- package/build/contexts/conversation_context.d.ts.map +1 -1
- package/build/contexts/conversation_context.js +13 -3
- package/build/contexts/conversation_context.js.map +1 -1
- package/build/hooks/use_conversation_messages.d.ts +13 -6
- package/build/hooks/use_conversation_messages.d.ts.map +1 -1
- package/build/hooks/use_conversation_messages.js +56 -7
- package/build/hooks/use_conversation_messages.js.map +1 -1
- package/build/hooks/use_features.d.ts +1 -0
- package/build/hooks/use_features.d.ts.map +1 -1
- package/build/hooks/use_features.js +1 -0
- package/build/hooks/use_features.js.map +1 -1
- package/build/hooks/use_suspense_api.d.ts +1 -0
- package/build/hooks/use_suspense_api.d.ts.map +1 -1
- package/build/hooks/use_suspense_api.js +1 -1
- package/build/hooks/use_suspense_api.js.map +1 -1
- package/build/screens/conversation_screen.d.ts +1 -0
- package/build/screens/conversation_screen.d.ts.map +1 -1
- package/build/screens/conversation_screen.js +10 -4
- package/build/screens/conversation_screen.js.map +1 -1
- package/build/utils/conversation_messages.d.ts +10 -0
- package/build/utils/conversation_messages.d.ts.map +1 -0
- package/build/utils/conversation_messages.js +22 -0
- package/build/utils/conversation_messages.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/hooks/use_conversation_messages.test.tsx +109 -0
- package/src/contexts/conversation_context.tsx +28 -2
- package/src/hooks/use_conversation_messages.ts +105 -21
- package/src/hooks/use_features.ts +1 -0
- package/src/hooks/use_suspense_api.ts +1 -1
- package/src/screens/conversation_screen.tsx +13 -3
- package/src/utils/__tests__/conversation_messages.test.ts +105 -0
- package/src/utils/conversation_messages.ts +37 -0
- package/src/__tests__/hooks/use_conversation_messages.ts +0 -55
|
@@ -2,12 +2,17 @@ import React, { PropsWithChildren } from 'react';
|
|
|
2
2
|
interface ConversationContextValue {
|
|
3
3
|
conversationId: number;
|
|
4
4
|
currentPageReplyRootId: string | null;
|
|
5
|
+
initialMessageId: string | null;
|
|
6
|
+
setInitialMessageId: (id: string | null) => void;
|
|
7
|
+
initialMessageIdIsAnchor: boolean;
|
|
5
8
|
}
|
|
6
9
|
interface ConversationContextProviderProps extends PropsWithChildren {
|
|
7
10
|
conversationId: number;
|
|
8
11
|
currentPageReplyRootId: string | null;
|
|
12
|
+
initialMessageId?: string | null;
|
|
13
|
+
initialMessageIdIsAnchor?: boolean;
|
|
9
14
|
}
|
|
10
|
-
export declare const ConversationContextProvider: ({ children, conversationId, currentPageReplyRootId, }: ConversationContextProviderProps) => React.JSX.Element;
|
|
15
|
+
export declare const ConversationContextProvider: ({ children, conversationId, currentPageReplyRootId, initialMessageId: initialMessageIdProp, initialMessageIdIsAnchor, }: ConversationContextProviderProps) => React.JSX.Element;
|
|
11
16
|
export declare const useConversationContext: () => ConversationContextValue;
|
|
12
17
|
export {};
|
|
13
18
|
//# sourceMappingURL=conversation_context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation_context.d.ts","sourceRoot":"","sources":["../../src/contexts/conversation_context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"conversation_context.d.ts","sourceRoot":"","sources":["../../src/contexts/conversation_context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAEZ,iBAAiB,EAKlB,MAAM,OAAO,CAAA;AAEd,UAAU,wBAAwB;IAChC,cAAc,EAAE,MAAM,CAAA;IACtB,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,mBAAmB,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAChD,wBAAwB,EAAE,OAAO,CAAA;CAClC;AAED,UAAU,gCAAiC,SAAQ,iBAAiB;IAClE,cAAc,EAAE,MAAM,CAAA;IACtB,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,wBAAwB,CAAC,EAAE,OAAO,CAAA;CACnC;AAUD,eAAO,MAAM,2BAA2B,GAAI,yHAMzC,gCAAgC,sBAmBlC,CAAA;AAED,eAAO,MAAM,sBAAsB,gCAAwC,CAAA"}
|
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
import React, { createContext, useContext, useMemo } from 'react';
|
|
1
|
+
import React, { createContext, useContext, useEffect, useMemo, useState, } from 'react';
|
|
2
2
|
const ConversationContext = createContext({
|
|
3
3
|
conversationId: 0,
|
|
4
4
|
currentPageReplyRootId: null,
|
|
5
|
+
initialMessageId: null,
|
|
6
|
+
setInitialMessageId: () => { },
|
|
7
|
+
initialMessageIdIsAnchor: false,
|
|
5
8
|
});
|
|
6
|
-
export const ConversationContextProvider = ({ children, conversationId, currentPageReplyRootId, }) => {
|
|
9
|
+
export const ConversationContextProvider = ({ children, conversationId, currentPageReplyRootId, initialMessageId: initialMessageIdProp = null, initialMessageIdIsAnchor = false, }) => {
|
|
10
|
+
const [initialMessageId, setInitialMessageId] = useState(initialMessageIdProp);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
setInitialMessageId(initialMessageIdProp);
|
|
13
|
+
}, [initialMessageIdProp]);
|
|
7
14
|
const value = useMemo(() => ({
|
|
8
15
|
conversationId,
|
|
9
16
|
currentPageReplyRootId,
|
|
10
|
-
|
|
17
|
+
initialMessageId,
|
|
18
|
+
setInitialMessageId,
|
|
19
|
+
initialMessageIdIsAnchor,
|
|
20
|
+
}), [conversationId, currentPageReplyRootId, initialMessageId, initialMessageIdIsAnchor]);
|
|
11
21
|
return <ConversationContext.Provider value={value}>{children}</ConversationContext.Provider>;
|
|
12
22
|
};
|
|
13
23
|
export const useConversationContext = () => useContext(ConversationContext);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation_context.js","sourceRoot":"","sources":["../../src/contexts/conversation_context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"conversation_context.js","sourceRoot":"","sources":["../../src/contexts/conversation_context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EACZ,aAAa,EAEb,UAAU,EACV,SAAS,EACT,OAAO,EACP,QAAQ,GACT,MAAM,OAAO,CAAA;AAiBd,MAAM,mBAAmB,GAAG,aAAa,CAA2B;IAClE,cAAc,EAAE,CAAC;IACjB,sBAAsB,EAAE,IAAI;IAC5B,gBAAgB,EAAE,IAAI;IACtB,mBAAmB,EAAE,GAAG,EAAE,GAAE,CAAC;IAC7B,wBAAwB,EAAE,KAAK;CAChC,CAAC,CAAA;AAEF,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,EAC1C,QAAQ,EACR,cAAc,EACd,sBAAsB,EACtB,gBAAgB,EAAE,oBAAoB,GAAG,IAAI,EAC7C,wBAAwB,GAAG,KAAK,GACC,EAAE,EAAE;IACrC,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAC,oBAAoB,CAAC,CAAA;IAE9E,SAAS,CAAC,GAAG,EAAE;QACb,mBAAmB,CAAC,oBAAoB,CAAC,CAAA;IAC3C,CAAC,EAAE,CAAC,oBAAoB,CAAC,CAAC,CAAA;IAE1B,MAAM,KAAK,GAAG,OAAO,CACnB,GAAG,EAAE,CAAC,CAAC;QACL,cAAc;QACd,sBAAsB;QACtB,gBAAgB;QAChB,mBAAmB;QACnB,wBAAwB;KACzB,CAAC,EACF,CAAC,cAAc,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,wBAAwB,CAAC,CACrF,CAAA;IAED,OAAO,CAAC,mBAAmB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAA;AAC9F,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAA","sourcesContent":["import React, {\n createContext,\n PropsWithChildren,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from 'react'\n\ninterface ConversationContextValue {\n conversationId: number\n currentPageReplyRootId: string | null\n initialMessageId: string | null\n setInitialMessageId: (id: string | null) => void\n initialMessageIdIsAnchor: boolean\n}\n\ninterface ConversationContextProviderProps extends PropsWithChildren {\n conversationId: number\n currentPageReplyRootId: string | null\n initialMessageId?: string | null\n initialMessageIdIsAnchor?: boolean\n}\n\nconst ConversationContext = createContext<ConversationContextValue>({\n conversationId: 0,\n currentPageReplyRootId: null,\n initialMessageId: null,\n setInitialMessageId: () => {},\n initialMessageIdIsAnchor: false,\n})\n\nexport const ConversationContextProvider = ({\n children,\n conversationId,\n currentPageReplyRootId,\n initialMessageId: initialMessageIdProp = null,\n initialMessageIdIsAnchor = false,\n}: ConversationContextProviderProps) => {\n const [initialMessageId, setInitialMessageId] = useState(initialMessageIdProp)\n\n useEffect(() => {\n setInitialMessageId(initialMessageIdProp)\n }, [initialMessageIdProp])\n\n const value = useMemo(\n () => ({\n conversationId,\n currentPageReplyRootId,\n initialMessageId,\n setInitialMessageId,\n initialMessageIdIsAnchor,\n }),\n [conversationId, currentPageReplyRootId, initialMessageId, initialMessageIdIsAnchor]\n )\n\n return <ConversationContext.Provider value={value}>{children}</ConversationContext.Provider>\n}\n\nexport const useConversationContext = () => useContext(ConversationContext)\n"]}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { AnyUseSuspenseInfiniteQueryOptions, InfiniteData } from '@tanstack/react-query';
|
|
2
|
+
import { ApiCollection, MessageResource } from '../types';
|
|
3
|
+
import { MessagesPageParam } from '../utils/conversation_messages';
|
|
4
|
+
type Args = {
|
|
4
5
|
conversation_id: number;
|
|
5
6
|
reply_root_id?: string | null;
|
|
6
|
-
}
|
|
7
|
+
};
|
|
8
|
+
export type ConversationMessagesOptions = Omit<AnyUseSuspenseInfiniteQueryOptions, 'getNextPageParam' | 'getPreviousPageParam' | 'initialData' | 'initialPageParam' | 'queryFn' | 'queryKey'>;
|
|
9
|
+
export declare const useConversationMessages: ({ conversation_id, reply_root_id }: Args, opts?: ConversationMessagesOptions) => {
|
|
7
10
|
messages: MessageResource[];
|
|
8
|
-
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<
|
|
11
|
+
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<InfiniteData<ApiCollection<MessageResource>, MessagesPageParam>, Response>>;
|
|
9
12
|
isRefetching: boolean;
|
|
10
|
-
|
|
13
|
+
fetchOlderMessages: (options?: import("@tanstack/query-core").FetchNextPageOptions) => Promise<import("@tanstack/query-core").InfiniteQueryObserverResult<InfiniteData<ApiCollection<MessageResource>, MessagesPageParam>, Response>>;
|
|
14
|
+
hasMoreOlderMessages: boolean;
|
|
15
|
+
fetchNewerMessages: (options?: import("@tanstack/query-core").FetchPreviousPageOptions) => Promise<import("@tanstack/query-core").InfiniteQueryObserverResult<InfiniteData<ApiCollection<MessageResource>, MessagesPageParam>, Response>>;
|
|
16
|
+
hasMoreNewerMessages: boolean;
|
|
11
17
|
queryKey: import("./use_suspense_api").RequestQueryKey;
|
|
12
18
|
};
|
|
19
|
+
export {};
|
|
13
20
|
//# sourceMappingURL=use_conversation_messages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_conversation_messages.d.ts","sourceRoot":"","sources":["../../src/hooks/use_conversation_messages.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"use_conversation_messages.d.ts","sourceRoot":"","sources":["../../src/hooks/use_conversation_messages.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kCAAkC,EAClC,YAAY,EAGb,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AACzD,OAAO,EAEL,iBAAiB,EAIlB,MAAM,gCAAgC,CAAA;AAKvC,KAAK,IAAI,GAAG;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AAEtE,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAC5C,kCAAkC,EAChC,kBAAkB,GAClB,sBAAsB,GACtB,aAAa,GACb,kBAAkB,GAClB,SAAS,GACT,UAAU,CACb,CAAA;AAED,eAAO,MAAM,uBAAuB,GAClC,oCAAoC,IAAI,EACxC,OAAO,2BAA2B;;;;;;;;;CA6EnC,CAAA"}
|
|
@@ -1,13 +1,62 @@
|
|
|
1
|
+
import { useSuspenseInfiniteQuery, useSuspenseQueries, } from '@tanstack/react-query';
|
|
1
2
|
import { useMemo } from 'react';
|
|
3
|
+
import { useConversationContext } from '../contexts/conversation_context';
|
|
4
|
+
import { anchoredSeedPageParams, newerPageParam, olderPageParam, sortAndFilterMessages, } from '../utils/conversation_messages';
|
|
2
5
|
import { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages';
|
|
3
|
-
import {
|
|
6
|
+
import { useApiClient } from './use_api_client';
|
|
7
|
+
import { throwResponseError } from './use_suspense_api';
|
|
4
8
|
export const useConversationMessages = ({ conversation_id, reply_root_id }, opts) => {
|
|
5
|
-
const
|
|
9
|
+
const apiClient = useApiClient();
|
|
10
|
+
const { initialMessageId } = useConversationContext();
|
|
11
|
+
const anchored = !reply_root_id && !!initialMessageId;
|
|
12
|
+
const requestArgs = getMessagesRequestArgs({ conversation_id, reply_root_id });
|
|
6
13
|
const queryKey = getMessagesQueryKey({ conversation_id, reply_root_id });
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
const fetchPage = (pageParam) => {
|
|
15
|
+
const data = {
|
|
16
|
+
...requestArgs.data,
|
|
17
|
+
...(pageParam.where ? { where: pageParam.where } : {}),
|
|
18
|
+
...(pageParam.order ? { order: pageParam.order } : {}),
|
|
19
|
+
};
|
|
20
|
+
return apiClient.chat
|
|
21
|
+
.get({ url: requestArgs.url, data })
|
|
22
|
+
.catch(throwResponseError);
|
|
23
|
+
};
|
|
24
|
+
const seedPageParams = anchored ? anchoredSeedPageParams(initialMessageId) : [];
|
|
25
|
+
const seedQueries = useSuspenseQueries({
|
|
26
|
+
queries: seedPageParams.map((pageParam, index) => ({
|
|
27
|
+
queryKey: [...queryKey, 'seed', index],
|
|
28
|
+
queryFn: () => fetchPage(pageParam),
|
|
29
|
+
staleTime: Infinity,
|
|
30
|
+
gcTime: 0,
|
|
31
|
+
})),
|
|
32
|
+
});
|
|
33
|
+
const initialData = anchored
|
|
34
|
+
? {
|
|
35
|
+
pages: seedQueries.map(q => q.data),
|
|
36
|
+
pageParams: seedPageParams,
|
|
37
|
+
}
|
|
38
|
+
: undefined;
|
|
39
|
+
const initialPageParam = anchored ? seedPageParams[0] : {};
|
|
40
|
+
const { data, refetch, isRefetching, fetchNextPage, hasNextPage, fetchPreviousPage, hasPreviousPage, } = useSuspenseInfiniteQuery({
|
|
41
|
+
queryKey,
|
|
42
|
+
queryFn: ({ pageParam }) => fetchPage(pageParam),
|
|
43
|
+
initialPageParam,
|
|
44
|
+
initialData,
|
|
45
|
+
getNextPageParam: olderPageParam,
|
|
46
|
+
getPreviousPageParam: anchored ? newerPageParam : () => undefined,
|
|
47
|
+
...(opts || {}),
|
|
48
|
+
...(anchored ? { staleTime: Infinity, refetchOnMount: false } : {}),
|
|
49
|
+
});
|
|
50
|
+
const messages = useMemo(() => sortAndFilterMessages(data.pages), [data.pages]);
|
|
51
|
+
return {
|
|
52
|
+
messages,
|
|
53
|
+
refetch,
|
|
54
|
+
isRefetching,
|
|
55
|
+
fetchOlderMessages: fetchNextPage,
|
|
56
|
+
hasMoreOlderMessages: hasNextPage,
|
|
57
|
+
fetchNewerMessages: fetchPreviousPage,
|
|
58
|
+
hasMoreNewerMessages: hasPreviousPage,
|
|
59
|
+
queryKey,
|
|
60
|
+
};
|
|
12
61
|
};
|
|
13
62
|
//# sourceMappingURL=use_conversation_messages.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_conversation_messages.js","sourceRoot":"","sources":["../../src/hooks/use_conversation_messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"use_conversation_messages.js","sourceRoot":"","sources":["../../src/hooks/use_conversation_messages.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AnyUseSuspenseInfiniteQueryOptions,\n InfiniteData,\n useSuspenseInfiniteQuery,\n useSuspenseQueries,\n} from '@tanstack/react-query'\nimport { useMemo } from 'react'\nimport { useConversationContext } from '../contexts/conversation_context'\nimport { ApiCollection, MessageResource } from '../types'\nimport {\n anchoredSeedPageParams,\n MessagesPageParam,\n newerPageParam,\n olderPageParam,\n sortAndFilterMessages,\n} from '../utils/conversation_messages'\nimport { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'\nimport { useApiClient } from './use_api_client'\nimport { throwResponseError } from './use_suspense_api'\n\ntype Args = { conversation_id: number; reply_root_id?: string | null }\n\nexport type ConversationMessagesOptions = Omit<\n AnyUseSuspenseInfiniteQueryOptions,\n | 'getNextPageParam'\n | 'getPreviousPageParam'\n | 'initialData'\n | 'initialPageParam'\n | 'queryFn'\n | 'queryKey'\n>\n\nexport const useConversationMessages = (\n { conversation_id, reply_root_id }: Args,\n opts?: ConversationMessagesOptions\n) => {\n const apiClient = useApiClient()\n const { initialMessageId } = useConversationContext()\n const anchored = !reply_root_id && !!initialMessageId\n\n const requestArgs = getMessagesRequestArgs({ conversation_id, reply_root_id })\n const queryKey = getMessagesQueryKey({ conversation_id, reply_root_id })\n\n const fetchPage = (pageParam: MessagesPageParam) => {\n const data = {\n ...requestArgs.data,\n ...(pageParam.where ? { where: pageParam.where } : {}),\n ...(pageParam.order ? { order: pageParam.order } : {}),\n }\n return apiClient.chat\n .get<ApiCollection<MessageResource>>({ url: requestArgs.url, data })\n .catch(throwResponseError)\n }\n\n const seedPageParams = anchored ? anchoredSeedPageParams(initialMessageId) : []\n const seedQueries = useSuspenseQueries({\n queries: seedPageParams.map((pageParam, index) => ({\n queryKey: [...queryKey, 'seed', index],\n queryFn: () => fetchPage(pageParam),\n staleTime: Infinity,\n gcTime: 0,\n })),\n })\n\n const initialData: InfiniteData<ApiCollection<MessageResource>, MessagesPageParam> | undefined =\n anchored\n ? {\n pages: seedQueries.map(q => q.data),\n pageParams: seedPageParams,\n }\n : undefined\n\n const initialPageParam: MessagesPageParam = anchored ? seedPageParams[0] : {}\n\n const {\n data,\n refetch,\n isRefetching,\n fetchNextPage,\n hasNextPage,\n fetchPreviousPage,\n hasPreviousPage,\n } = useSuspenseInfiniteQuery<\n ApiCollection<MessageResource>,\n Response,\n InfiniteData<ApiCollection<MessageResource>, MessagesPageParam>,\n typeof queryKey,\n MessagesPageParam\n >({\n queryKey,\n queryFn: ({ pageParam }) => fetchPage(pageParam),\n initialPageParam,\n initialData,\n getNextPageParam: olderPageParam,\n getPreviousPageParam: anchored ? newerPageParam : () => undefined,\n ...(opts || {}),\n ...(anchored ? { staleTime: Infinity, refetchOnMount: false } : {}),\n })\n\n const messages = useMemo(() => sortAndFilterMessages(data.pages), [data.pages])\n\n return {\n messages,\n refetch,\n isRefetching,\n fetchOlderMessages: fetchNextPage,\n hasMoreOlderMessages: hasNextPage,\n fetchNewerMessages: fetchPreviousPage,\n hasMoreNewerMessages: hasPreviousPage,\n queryKey,\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_features.d.ts","sourceRoot":"","sources":["../../src/hooks/use_features.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AAI1E,wBAAgB,WAAW;;kCAiBT,MAAM;EASvB;AAED,eAAO,MAAM,iBAAiB
|
|
1
|
+
{"version":3,"file":"use_features.d.ts","sourceRoot":"","sources":["../../src/hooks/use_features.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AAI1E,wBAAgB,WAAW;;kCAiBT,MAAM;EASvB;AAED,eAAO,MAAM,iBAAiB;;;;;;CAM7B,CAAA"}
|
|
@@ -26,6 +26,7 @@ export const availableFeatures = {
|
|
|
26
26
|
message_reporting: 'ROLLOUT_MOBILE_message_reporting',
|
|
27
27
|
granular_notifications_ui: 'ROLLOUT_granular_notification_preferences_ui',
|
|
28
28
|
custom_conversation_avatars: 'ROLLOUT_custom_conversation_avatars',
|
|
29
|
+
jump_to_unread: 'ROLLOUT_jump_to_unread',
|
|
29
30
|
};
|
|
30
31
|
const stableEmptyFeatures = {
|
|
31
32
|
data: [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_features.js","sourceRoot":"","sources":["../../src/hooks/use_features.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnC,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,MAAM,UAAU,WAAW;IACzB,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,WAAW,GAAG,sBAAsB,EAAE,CAAA;IAE5C,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC;QAChC,QAAQ,EAAE,mBAAmB,EAAE;QAC/B,OAAO,EAAE,GAAG,EAAE;YACZ,OAAO,SAAS,CAAC,IAAI;iBAClB,GAAG,CAAiC,WAAW,CAAC;iBAChD,KAAK,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAA;QACrC,CAAC;QACD,SAAS,EAAE,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,YAAY;KACvC,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAA;IAE1B,MAAM,cAAc,GAAG,WAAW,CAChC,CAAC,WAAmB,EAAE,EAAE,CACtB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,EAC3E,CAAC,QAAQ,CAAC,CACX,CAAA;IAED,OAAO;QACL,QAAQ;QACR,cAAc;KACf,CAAA;AACH,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,6BAA6B,EAAE,uCAAuC;IACtE,iBAAiB,EAAE,kCAAkC;IACrD,yBAAyB,EAAE,8CAA8C;IACzE,2BAA2B,EAAE,qCAAqC;
|
|
1
|
+
{"version":3,"file":"use_features.js","sourceRoot":"","sources":["../../src/hooks/use_features.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGnC,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AAC3F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAE/C,MAAM,UAAU,WAAW;IACzB,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,WAAW,GAAG,sBAAsB,EAAE,CAAA;IAE5C,MAAM,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC;QAChC,QAAQ,EAAE,mBAAmB,EAAE;QAC/B,OAAO,EAAE,GAAG,EAAE;YACZ,OAAO,SAAS,CAAC,IAAI;iBAClB,GAAG,CAAiC,WAAW,CAAC;iBAChD,KAAK,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,CAAA;QACrC,CAAC;QACD,SAAS,EAAE,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,YAAY;KACvC,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAA;IAE1B,MAAM,cAAc,GAAG,WAAW,CAChC,CAAC,WAAmB,EAAE,EAAE,CACtB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,EAC3E,CAAC,QAAQ,CAAC,CACX,CAAA;IAED,OAAO;QACL,QAAQ;QACR,cAAc;KACf,CAAA;AACH,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,6BAA6B,EAAE,uCAAuC;IACtE,iBAAiB,EAAE,kCAAkC;IACrD,yBAAyB,EAAE,8CAA8C;IACzE,2BAA2B,EAAE,qCAAqC;IAClE,cAAc,EAAE,wBAAwB;CACzC,CAAA;AAED,MAAM,mBAAmB,GAAmC;IAC1D,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;IACT,IAAI,EAAE;QACJ,KAAK,EAAE,CAAC;QACR,UAAU,EAAE,CAAC;KACd;CACF,CAAA","sourcesContent":["import { useSuspenseQuery } from '@tanstack/react-query'\nimport { useCallback } from 'react'\nimport { ApiCollection } from '../types'\nimport type { FeatureResource } from '../types/resources/feature_resource'\nimport { getFeaturesRequestArgs, getFeaturesQueryKey } from '../utils/request/get_features'\nimport { useApiClient } from './use_api_client'\n\nexport function useFeatures() {\n const apiClient = useApiClient()\n const requestArgs = getFeaturesRequestArgs()\n\n const { data } = useSuspenseQuery({\n queryKey: getFeaturesQueryKey(),\n queryFn: () => {\n return apiClient.chat\n .get<ApiCollection<FeatureResource>>(requestArgs)\n .catch(() => stableEmptyFeatures)\n },\n staleTime: 1000 * 60 * 5, // 5 minutes\n })\n\n const features = data.data\n\n const featureEnabled = useCallback(\n (featureName: string) =>\n features.some(feature => feature.name === featureName && feature.enabled),\n [features]\n )\n\n return {\n features,\n featureEnabled,\n }\n}\n\nexport const availableFeatures = {\n gender_specific_conversations: 'ROLLOUT_gender_specific_conversations',\n message_reporting: 'ROLLOUT_MOBILE_message_reporting',\n granular_notifications_ui: 'ROLLOUT_granular_notification_preferences_ui',\n custom_conversation_avatars: 'ROLLOUT_custom_conversation_avatars',\n jump_to_unread: 'ROLLOUT_jump_to_unread',\n}\n\nconst stableEmptyFeatures: ApiCollection<FeatureResource> = {\n data: [],\n links: {},\n meta: {\n count: 0,\n totalCount: 0,\n },\n}\n"]}
|
|
@@ -70,6 +70,7 @@ export declare const useSuspensePaginator: <T extends ResourceObject>(args: Susp
|
|
|
70
70
|
isFetchPreviousPageError: boolean;
|
|
71
71
|
isFetchingPreviousPage: boolean;
|
|
72
72
|
};
|
|
73
|
+
export declare const throwResponseError: (error: unknown) => Promise<never>;
|
|
73
74
|
export type RequestQueryKey = [
|
|
74
75
|
SuspenseGetOptions['url'],
|
|
75
76
|
SuspenseGetOptions['data'],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_suspense_api.d.ts","sourceRoot":"","sources":["../../src/hooks/use_suspense_api.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kCAAkC,EAClC,YAAY,EAGZ,uBAAuB,EACxB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAExD,OAAO,EAAE,UAAU,EAAe,MAAM,iBAAiB,CAAA;AAGzD,OAAO,EAAE,GAAG,EAAgB,MAAM,kBAAkB,CAAA;AAEpD,UAAU,kBAAmB,SAAQ,UAAU;IAC7C,GAAG,CAAC,EAAE,GAAG,CAAA;IACT,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,EAAE,IAAI,IAAI,CACrF,uBAAuB,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,EACvD,UAAU,GAAG,SAAS,CACvB,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,cAAc,GAAG,cAAc,EAAE,EACxE,MAAM,kBAAkB,EACxB,UAAU,uBAAuB,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;CAiBrC,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,kCAAkC,EAClC,kBAAkB,GAAG,kBAAkB,GAAG,SAAS,GAAG,UAAU,CACjE,CAAA;AAED,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,cAAc,EAC3D,MAAM,kBAAkB,EACxB,OAAO,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqChC,CAAA;
|
|
1
|
+
{"version":3,"file":"use_suspense_api.d.ts","sourceRoot":"","sources":["../../src/hooks/use_suspense_api.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kCAAkC,EAClC,YAAY,EAGZ,uBAAuB,EACxB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAExD,OAAO,EAAE,UAAU,EAAe,MAAM,iBAAiB,CAAA;AAGzD,OAAO,EAAE,GAAG,EAAgB,MAAM,kBAAkB,CAAA;AAEpD,UAAU,kBAAmB,SAAQ,UAAU;IAC7C,GAAG,CAAC,EAAE,GAAG,CAAA;IACT,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,cAAc,GAAG,cAAc,EAAE,IAAI,IAAI,CACrF,uBAAuB,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,cAAc,CAAC,EACvD,UAAU,GAAG,SAAS,CACvB,CAAA;AAED,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,cAAc,GAAG,cAAc,EAAE,EACxE,MAAM,kBAAkB,EACxB,UAAU,uBAAuB,CAAC,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;CAiBrC,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG,IAAI,CACzC,kCAAkC,EAClC,kBAAkB,GAAG,kBAAkB,GAAG,SAAS,GAAG,UAAU,CACjE,CAAA;AAED,eAAO,MAAM,oBAAoB,GAAI,CAAC,SAAS,cAAc,EAC3D,MAAM,kBAAkB,EACxB,OAAO,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqChC,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,OAAO,OAAO,mBAMhD,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,kBAAkB,CAAC,KAAK,CAAC;IACzB,kBAAkB,CAAC,MAAM,CAAC;IAC1B,kBAAkB,CAAC,SAAS,CAAC;IAC7B,kBAAkB,CAAC,KAAK,CAAC;IACzB,kBAAkB,CAAC,eAAe,CAAC;CACpC,CAAA;AACD,eAAO,MAAM,kBAAkB,GAAI,MAAM,kBAAkB,KAAG,eAM7D,CAAA"}
|
|
@@ -44,7 +44,7 @@ export const useSuspensePaginator = (args, opts) => {
|
|
|
44
44
|
const totalCount = query.data?.pages[0]?.meta?.totalCount || data.length;
|
|
45
45
|
return { ...query, data, totalCount };
|
|
46
46
|
};
|
|
47
|
-
const throwResponseError = (error) => {
|
|
47
|
+
export const throwResponseError = (error) => {
|
|
48
48
|
if (error instanceof Response) {
|
|
49
49
|
throw new ResponseError(error);
|
|
50
50
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use_suspense_api.js","sourceRoot":"","sources":["../../src/hooks/use_suspense_api.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"use_suspense_api.js","sourceRoot":"","sources":["../../src/hooks/use_suspense_api.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,wBAAwB,EACxB,gBAAgB,GAEjB,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAE9B,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,EAAO,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAYpD,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,IAAwB,EACxB,OAAoC,EACpC,EAAE;IAEF,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAEhC,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,GAAG,gBAAgB,CAA2B;QACpE,QAAQ,EAAE,kBAAkB,CAAC,IAAI,CAAC;QAClC,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE;YACxB,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,MAAM,CAAC,GAAG,QAA2B,CAAA;YACnE,OAAO,SAAS,CAAC,GAAG,CAAC;iBAClB,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC;iBAC9B,KAAK,CAAC,kBAAkB,CAAsB,CAAA;QACnD,CAAC;QACD,GAAG,OAAO;KACX,CAAC,CAAA;IAEF,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,EAAE,CAAA;AAC9B,CAAC,CAAA;AAOD,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,IAAwB,EACxB,IAA+B,EAC/B,EAAE;IACF,MAAM,SAAS,GAAG,YAAY,EAAE,CAAA;IAChC,MAAM,KAAK,GAAG,wBAAwB,CAMpC;QACA,QAAQ,EAAE,kBAAkB,CAAC,IAAI,CAAC;QAClC,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE;YACzB,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,SAAS,EAAE,CAAC,CAAA;YAEzC,MAAM,aAAa,GAAG,SAAS,EAAE,KAAK,IAAI,EAAE,CAAA;YAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAA;YACvC,MAAM,KAAK,GAAG,EAAE,GAAG,SAAS,EAAE,GAAG,aAAa,EAAE,CAAA;YAChD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,MAAM,CAAA;YAC9B,MAAM,MAAM,GAAG,SAAS,EAAE,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAA;YACpD,MAAM,IAAI,GAAG,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;YAE5C,OAAO,SAAS,CAAC,GAAG,CAAC;iBAClB,GAAG,CAAmB;gBACrB,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,IAAI;aACL,CAAC;iBACD,KAAK,CAAC,kBAAkB,CAAC,CAAA;QAC9B,CAAC;QACD,gBAAgB,EAAE,EAA0B;QAC5C,gBAAgB,EAAE,QAAQ,CAAC,EAAE,CAAC,wBAAwB,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;QAC3E,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;KAChB,CAAC,CAAA;IAEF,MAAM,IAAI,GAAQ,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;IACpE,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,IAAI,IAAI,CAAC,MAAM,CAAA;IAExE,OAAO,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,CAAA;AACvC,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAc,EAAE,EAAE;IACnD,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,aAAa,CAAC,KAAuB,CAAC,CAAA;IAClD,CAAC;IAED,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC9B,CAAC,CAAA;AASD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAwB,EAAmB,EAAE,CAAC;IAC/E,IAAI,CAAC,GAAG;IACR,IAAI,CAAC,IAAI;IACT,IAAI,CAAC,OAAO;IACZ,IAAI,CAAC,GAAG,IAAI,MAAM;IAClB,IAAI,CAAC,aAAa;CACnB,CAAA","sourcesContent":["import {\n AnyUseSuspenseInfiniteQueryOptions,\n InfiniteData,\n useSuspenseInfiniteQuery,\n useSuspenseQuery,\n UseSuspenseQueryOptions,\n} from '@tanstack/react-query'\nimport { ApiCollection, ApiResource, ResourceObject } from '../types'\nimport { FailedResponse } from '../types/api_primitives'\nimport { Log } from '../utils'\nimport { GetRequest, RequestData } from '../utils/client'\nimport { ResponseError } from '../utils/response_error'\nimport { getNextPageParamFromMeta } from './paginator_meta'\nimport { App, useApiClient } from './use_api_client'\n\ninterface SuspenseGetOptions extends GetRequest {\n app?: App\n reply_root_id?: string | null\n}\n\nexport type SuspenseGetQueryOptions<T extends ResourceObject | ResourceObject[]> = Omit<\n UseSuspenseQueryOptions<ApiResource<T>, FailedResponse>,\n 'queryKey' | 'queryFn'\n>\n\nexport const useSuspenseGet = <T extends ResourceObject | ResourceObject[]>(\n args: SuspenseGetOptions,\n options?: SuspenseGetQueryOptions<T>\n) => {\n type Resource = ApiResource<T>\n const apiClient = useApiClient()\n\n const { data, ...query } = useSuspenseQuery<Resource, FailedResponse>({\n queryKey: getRequestQueryKey(args),\n queryFn: ({ queryKey }) => {\n const [url, d, headers, app = 'chat'] = queryKey as RequestQueryKey\n return apiClient[app]\n .get({ url, data: d, headers })\n .catch(throwResponseError) as Promise<Resource>\n },\n ...options,\n })\n\n return { ...data, ...query }\n}\n\nexport type SuspensePaginatorOptions = Omit<\n AnyUseSuspenseInfiniteQueryOptions,\n 'getNextPageParam' | 'initialPageParam' | 'queryFn' | 'queryKey'\n>\n\nexport const useSuspensePaginator = <T extends ResourceObject>(\n args: SuspenseGetOptions,\n opts?: SuspensePaginatorOptions\n) => {\n const apiClient = useApiClient()\n const query = useSuspenseInfiniteQuery<\n ApiCollection<T>,\n Response,\n InfiniteData<ApiCollection<T>>,\n any,\n Partial<RequestData> | undefined\n >({\n queryKey: getRequestQueryKey(args),\n queryFn: ({ pageParam }) => {\n Log.info('useSuspenseGet', { pageParam })\n\n const pageParmWhere = pageParam?.where || {}\n const argsWhere = args.data.where || {}\n const where = { ...argsWhere, ...pageParmWhere }\n const app = args.app || 'chat'\n const offset = pageParam?.offset || args.data.offset\n const data = { ...args.data, where, offset }\n\n return apiClient[app]\n .get<ApiCollection<T>>({\n url: args.url,\n data,\n })\n .catch(throwResponseError)\n },\n initialPageParam: {} as Partial<RequestData>,\n getNextPageParam: lastPage => getNextPageParamFromMeta(lastPage.meta?.next),\n ...(opts || {}),\n })\n\n const data: T[] = query.data?.pages.flatMap(page => page.data) || []\n const totalCount = query.data?.pages[0]?.meta?.totalCount || data.length\n\n return { ...query, data, totalCount }\n}\n\nexport const throwResponseError = (error: unknown) => {\n if (error instanceof Response) {\n throw new ResponseError(error as FailedResponse)\n }\n\n return Promise.reject(error)\n}\n\nexport type RequestQueryKey = [\n SuspenseGetOptions['url'],\n SuspenseGetOptions['data'],\n SuspenseGetOptions['headers'],\n SuspenseGetOptions['app'],\n SuspenseGetOptions['reply_root_id'],\n]\nexport const getRequestQueryKey = (args: SuspenseGetOptions): RequestQueryKey => [\n args.url,\n args.data,\n args.headers,\n args.app || 'chat',\n args.reply_root_id,\n]\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation_screen.d.ts","sourceRoot":"","sources":["../../src/screens/conversation_screen.tsx"],"names":[],"mappings":"AACA,OAAO,EAAe,gBAAgB,EAAqB,MAAM,4BAA4B,CAAA;AAC7F,OAAO,EAGL,iBAAiB,EAIlB,MAAM,0BAA0B,CAAA;AACjC,OAAO,KAAmD,MAAM,OAAO,CAAA;AAYvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iDAAiD,CAAA;
|
|
1
|
+
{"version":3,"file":"conversation_screen.d.ts","sourceRoot":"","sources":["../../src/screens/conversation_screen.tsx"],"names":[],"mappings":"AACA,OAAO,EAAe,gBAAgB,EAAqB,MAAM,4BAA4B,CAAA;AAC7F,OAAO,EAGL,iBAAiB,EAIlB,MAAM,0BAA0B,CAAA;AACjC,OAAO,KAAmD,MAAM,OAAO,CAAA;AAYvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iDAAiD,CAAA;AAiBpF,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAE,yBAAyB,EAAE,MAAM,uCAAuC,CAAA;AAMjF,MAAM,MAAM,sBAAsB,GAAG;IACnC,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,yBAAyB,CAAA;IACjC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,CAAC,sBAAsB,CAAC,CAAA;AAE/E,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,EAAE,uBAAuB,qBA0BpE;AAiKD,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAwC/E,KAAK,kBAAkB,GAAG;IACxB,IAAI,EAAE,oBAAoB,CAAA;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,oBAAoB,EAAE,OAAO,CAAA;IAC7B,iBAAiB,EAAE,OAAO,CAAA;CAC3B,CAAA;AAED,UAAU,kBAAkB;IAC1B,EAAE,EAAE,eAAe,EAAE,CAAA;IACrB,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,eAAO,MAAM,aAAa,GAAI,uBAAuB,kBAAkB,6DA8GtE,CAAA;AACD,UAAU,4BAA6B,SAAQ,gBAAgB;IAC7D,eAAe,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,yBAAyB,CAAA;IACjC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,eAAO,MAAM,uBAAuB,GAAI,8DAOrC,4BAA4B,sBAoC9B,CAAA"}
|
|
@@ -21,6 +21,7 @@ import { useConversation } from '../hooks/use_conversation';
|
|
|
21
21
|
import { useConversationJoltEvents } from '../hooks/use_conversation_jolt_events';
|
|
22
22
|
import { useConversationMessages } from '../hooks/use_conversation_messages';
|
|
23
23
|
import { useConversationMessagesJoltEvents } from '../hooks/use_conversation_messages_jolt_events';
|
|
24
|
+
import { useFeatures } from '../hooks/use_features';
|
|
24
25
|
import { useMarkLatestMessageRead } from '../hooks/use_mark_latest_message_read';
|
|
25
26
|
import { normalizeAnalyticsMetadata, usePublishProductAnalyticsEvent, } from '../hooks/use_product_analytics';
|
|
26
27
|
import { getRelativeDateStatus } from '../utils/date';
|
|
@@ -28,13 +29,18 @@ import dayjs from '../utils/dayjs';
|
|
|
28
29
|
import { CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL } from '../utils/styles';
|
|
29
30
|
import { isSystemMessage } from '../utils/system_messages';
|
|
30
31
|
export function ConversationScreen({ route }) {
|
|
31
|
-
const { conversation_id, reply_root_id } = route.params;
|
|
32
|
+
const { conversation_id, message_id, reply_root_id } = route.params;
|
|
32
33
|
const { data: conversation } = useConversation({ conversation_id });
|
|
34
|
+
const { featureEnabled } = useFeatures();
|
|
33
35
|
usePublishProductAnalyticsEvent('chat.mobile.conversations.show.opened', {
|
|
34
36
|
reply_root_id,
|
|
35
37
|
...normalizeAnalyticsMetadata(conversation),
|
|
36
38
|
});
|
|
37
|
-
|
|
39
|
+
const lastReadMessageSortKey = conversation.conversationMembership?.lastReadMessageSortKey ?? null;
|
|
40
|
+
const jumpToUnreadAnchor = featureEnabled('jump_to_unread') ? lastReadMessageSortKey : null;
|
|
41
|
+
const initialMessageId = message_id ?? jumpToUnreadAnchor;
|
|
42
|
+
const initialMessageIdIsAnchor = !!initialMessageId && !message_id;
|
|
43
|
+
return (<ConversationContextProvider conversationId={conversation_id} currentPageReplyRootId={reply_root_id ?? null} initialMessageId={initialMessageId} initialMessageIdIsAnchor={initialMessageIdIsAnchor}>
|
|
38
44
|
<ConversationScreenContent route={route}/>
|
|
39
45
|
</ConversationContextProvider>);
|
|
40
46
|
}
|
|
@@ -43,7 +49,7 @@ function ConversationScreenContent({ route }) {
|
|
|
43
49
|
const navigation = useNavigation();
|
|
44
50
|
const { conversation_id, editing_message_id, reply_root_id, reply_root_author_name } = route.params;
|
|
45
51
|
const { data: conversation } = useConversation(route.params);
|
|
46
|
-
const { messages, refetch, isRefetching,
|
|
52
|
+
const { messages, refetch, isRefetching, fetchOlderMessages } = useConversationMessages({
|
|
47
53
|
conversation_id,
|
|
48
54
|
reply_root_id,
|
|
49
55
|
});
|
|
@@ -117,7 +123,7 @@ function ConversationScreenContent({ route }) {
|
|
|
117
123
|
return <SystemMessage message={item} conversationId={conversation_id}/>;
|
|
118
124
|
}
|
|
119
125
|
return (<Message {...item} canDeleteNonAuthoredMessages={canDeleteNonAuthoredMessages} conversation_id={conversation_id} latestReadMessageSortKey={conversation?.latestReadMessageSortKey} inReplyScreen={!!reply_root_id}/>);
|
|
120
|
-
}} onEndReached={() =>
|
|
126
|
+
}} onEndReached={() => fetchOlderMessages()} ListHeaderComponent={<View style={styles.listHeader}/>}/>)}
|
|
121
127
|
<JumpToBottomButton onPress={handleReturnToBottom} visible={showJumpToBottomButton}/>
|
|
122
128
|
{!noMessages && <TypingIndicator />}
|
|
123
129
|
{showLeaderDisabledReplyBanner && <LeaderMessagesDisabledBanner />}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversation_screen.js","sourceRoot":"","sources":["../../src/screens/conversation_screen.tsx"],"names":[],"mappings":"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","sourcesContent":["import { date as formatDate } from '@planningcenter/datetime-fmt'\nimport { HeaderTitle, HeaderTitleProps, PlatformPressable } from '@react-navigation/elements'\nimport {\n CommonActions,\n RouteProp,\n StaticScreenProps,\n useNavigation,\n useTheme as useNavigationTheme,\n useRoute,\n} from '@react-navigation/native'\nimport React, { useCallback, useEffect, useRef, useState } from 'react'\nimport { FlatList, Platform, StyleSheet, View } from 'react-native'\nimport { useSafeAreaInsets } from 'react-native-safe-area-context'\nimport { Badge, Icon, Text } from '../components'\nimport { EmptyConversationBlankState } from '../components/conversation/empty_conversation_blank_state'\nimport { JumpToBottomButton } from '../components/conversation/jump_to_bottom_button'\nimport { Message } from '../components/conversation/message'\nimport { MessageForm } from '../components/conversation/message_form'\nimport {\n LeaderMessagesDisabledBanner,\n MemberMessagesDisabledBanner,\n} from '../components/conversation/messages_disabled_banners'\nimport { ReplyShadowMessage } from '../components/conversation/reply_shadow_message'\nimport { SystemMessage } from '../components/conversation/system_message'\nimport { TypingIndicator } from '../components/conversation/typing_indicator'\nimport { KeyboardView } from '../components/display/keyboard_view'\nimport BlankState from '../components/primitive/blank_state_primitive'\nimport { ConversationContextProvider } from '../contexts/conversation_context'\nimport { useTheme } from '../hooks'\nimport { useConversation } from '../hooks/use_conversation'\nimport { useConversationJoltEvents } from '../hooks/use_conversation_jolt_events'\nimport { useConversationMessages } from '../hooks/use_conversation_messages'\nimport { useConversationMessagesJoltEvents } from '../hooks/use_conversation_messages_jolt_events'\nimport { useMarkLatestMessageRead } from '../hooks/use_mark_latest_message_read'\nimport {\n normalizeAnalyticsMetadata,\n usePublishProductAnalyticsEvent,\n} from '../hooks/use_product_analytics'\nimport { MessageResource } from '../types'\nimport { ConversationBadgeResource } from '../types/resources/conversation_badge'\nimport { getRelativeDateStatus } from '../utils/date'\nimport dayjs from '../utils/dayjs'\nimport { CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL } from '../utils/styles'\nimport { isSystemMessage } from '../utils/system_messages'\n\nexport type ConversationRouteProps = {\n conversation_id: number\n reply_root_id?: string | null\n reply_root_author_name?: string\n chat_group_graph_id?: string\n clear_input?: boolean\n editing_message_id?: number | null\n title?: string\n subtitle?: string\n badge?: ConversationBadgeResource\n deleted?: boolean\n muted?: boolean\n}\n\nexport type ConversationScreenProps = StaticScreenProps<ConversationRouteProps>\n\nexport function ConversationScreen({ route }: ConversationScreenProps) {\n const { conversation_id, reply_root_id } = route.params\n\n const { data: conversation } = useConversation({ conversation_id })\n\n usePublishProductAnalyticsEvent('chat.mobile.conversations.show.opened', {\n reply_root_id,\n ...normalizeAnalyticsMetadata(conversation),\n })\n\n return (\n <ConversationContextProvider\n conversationId={conversation_id}\n currentPageReplyRootId={reply_root_id ?? null}\n >\n <ConversationScreenContent route={route} />\n </ConversationContextProvider>\n )\n}\n\nfunction ConversationScreenContent({ route }: ConversationScreenProps) {\n const styles = useStyles()\n const navigation = useNavigation()\n const { conversation_id, editing_message_id, reply_root_id, reply_root_author_name } =\n route.params\n const { data: conversation } = useConversation(route.params)\n const { messages, refetch, isRefetching, fetchNextPage } = useConversationMessages({\n conversation_id,\n reply_root_id,\n })\n useConversationJoltEvents({ conversationId: conversation_id })\n useConversationMessagesJoltEvents({ conversationId: conversation_id })\n useEnsureConversationsRouteExists()\n useMarkLatestMessageRead({ conversation, messages })\n const messagesWithSeparators = groupMessages({\n ms: messages,\n inReplyScreen: !!reply_root_id,\n })\n const noMessages = messagesWithSeparators.length === 0\n\n const { repliesDisabled, memberAbility, badges, title } = conversation\n const canReply = memberAbility?.canReply\n const showLeaderDisabledReplyBanner = canReply && repliesDisabled\n const canDeleteNonAuthoredMessages = memberAbility?.canDeleteNonAuthoredMessages ?? false\n const currentlyEditingMessage = messages.find(m => String(m.id) === String(editing_message_id))\n const replyRootAuthorFirstName = reply_root_author_name?.split(' ')[0]\n const replyHeaderTitle = replyRootAuthorFirstName\n ? `Reply to ${replyRootAuthorFirstName}`\n : 'Reply'\n // Prefer the membership for optimistic updates.\n const muted = conversation.conversationMembership?.muted ?? conversation.muted\n\n const listRef = useRef<FlatList>(null)\n const [showJumpToBottomButton, setShowJumpToBottomButton] = useState(false)\n\n const trackScroll = (event: any) => {\n const offsetY = event.nativeEvent.contentOffset.y\n setShowJumpToBottomButton(offsetY > 200)\n }\n\n const handleReturnToBottom = useCallback(() => {\n listRef.current?.scrollToOffset({\n offset: 0,\n })\n }, [])\n\n useEffect(() => {\n if (reply_root_id) {\n navigation.setParams({\n title: replyHeaderTitle,\n })\n } else {\n navigation.setParams({\n title: title,\n badge: badges?.[0],\n deleted: conversation?.deleted,\n muted,\n })\n }\n }, [navigation, title, badges, conversation?.deleted, reply_root_id, replyHeaderTitle, muted])\n\n if (!conversation || conversation.deleted) {\n return (\n <View style={styles.container}>\n <BlankState.Root>\n <BlankState.Imagery name=\"general.outlinedTextMessage\" />\n <BlankState.Content>\n <BlankState.Heading>This conversation has been deleted</BlankState.Heading>\n </BlankState.Content>\n <BlankState.Button\n onPress={navigation.goBack}\n title=\"Back to conversations\"\n accessibilityHint=\"Navigates back to the conversations list\"\n accessibilityRole=\"link\"\n />\n </BlankState.Root>\n </View>\n )\n }\n\n return (\n <View style={styles.container}>\n <KeyboardView>\n {noMessages ? (\n <EmptyConversationBlankState />\n ) : (\n <FlatList\n inverted\n ref={listRef}\n contentContainerStyle={styles.listContainer}\n refreshing={isRefetching}\n onRefresh={refetch}\n data={messagesWithSeparators}\n keyExtractor={item => item.id}\n onScroll={trackScroll}\n scrollEventThrottle={10}\n renderItem={({ item }) => {\n if (item.type === 'DateSeparator') {\n return <InlineDateSeparator {...item} />\n }\n\n if (item.type === 'ReplyShadowMessage') {\n return (\n <ReplyShadowMessage\n {...item}\n conversation_id={conversation_id}\n inReplyScreen={!!reply_root_id}\n />\n )\n }\n\n if (isSystemMessage(item)) {\n return <SystemMessage message={item} conversationId={conversation_id} />\n }\n\n return (\n <Message\n {...item}\n canDeleteNonAuthoredMessages={canDeleteNonAuthoredMessages}\n conversation_id={conversation_id}\n latestReadMessageSortKey={conversation?.latestReadMessageSortKey}\n inReplyScreen={!!reply_root_id}\n />\n )\n }}\n onEndReached={() => fetchNextPage()}\n ListHeaderComponent={<View style={styles.listHeader} />}\n />\n )}\n <JumpToBottomButton onPress={handleReturnToBottom} visible={showJumpToBottomButton} />\n {!noMessages && <TypingIndicator />}\n {showLeaderDisabledReplyBanner && <LeaderMessagesDisabledBanner />}\n {canReply ? (\n <MessageForm.Root\n replyRootAuthorFirstName={replyRootAuthorFirstName}\n conversation={conversation}\n replyRootId={reply_root_id}\n currentlyEditingMessage={currentlyEditingMessage}\n // We use a separate key so that it remounts component when switching between new\n // and edit message. This simplifies internal state handling.\n key={\n currentlyEditingMessage\n ? `edit-message-form-${currentlyEditingMessage.id}`\n : 'new-message-form'\n }\n >\n <MessageForm.AttachmentPicker />\n <MessageForm.Commands />\n <MessageForm.TextInput />\n <MessageForm.SubmitButton />\n </MessageForm.Root>\n ) : (\n <MemberMessagesDisabledBanner />\n )}\n </KeyboardView>\n </View>\n )\n}\n\nexport type DateSeparator = { type: 'DateSeparator'; id: string; date: string }\n\nfunction InlineDateSeparator({ date }: DateSeparator) {\n const styles = useDateSeparatorStyles()\n const { isThisYear } = getRelativeDateStatus(date)\n const showYear = !isThisYear\n const dateStamp = formatDate(date, { style: 'long', year: showYear })\n\n return (\n <View style={styles.container}>\n <View style={styles.separator} />\n <Text variant=\"footnote\" style={styles.dateText}>\n {dateStamp}\n </Text>\n <View style={styles.separator} />\n </View>\n )\n}\n\nconst useDateSeparatorStyles = () => {\n const theme = useTheme()\n return StyleSheet.create({\n container: {\n alignItems: 'center',\n flexDirection: 'row',\n paddingHorizontal: CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,\n paddingVertical: 16,\n },\n separator: {\n flex: 1,\n height: 1,\n borderTopWidth: 1,\n borderTopColor: theme.colors.borderColorDefaultBase,\n },\n dateText: {\n paddingHorizontal: 8,\n },\n })\n}\n\ntype ReplyShadowMessage = {\n type: 'ReplyShadowMessage'\n id: string\n messageId: string\n isReplyShadowMessage: boolean\n nextRendersAuthor: boolean\n}\n\ninterface GroupMessagesProps {\n ms: MessageResource[]\n inReplyScreen?: boolean\n}\n\nexport const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {\n let enrichedMessages: (MessageResource | DateSeparator | ReplyShadowMessage)[] = []\n let encounteredOneOfMyMessages = false\n\n ms.forEach((message, i) => {\n const prevMessage = ms[i + 1]\n const nextMessage = ms[i - 1]\n const date = dayjs(message.createdAt).format('YYYY-MM-DD')\n\n const prevMessageIsDateSeparator =\n prevMessage && date !== dayjs(prevMessage.createdAt).format('YYYY-MM-DD')\n\n if (isSystemMessage(message)) {\n message.myLatestInConversation = false\n message.lastInGroup = true\n message.renderAuthor = false\n message.nextRendersAuthor = nextMessage?.renderAuthor\n message.isReplyShadowMessage = false\n message.nextIsReplyShadowMessage = false\n message.threadPosition = null\n enrichedMessages.push(message)\n if (prevMessageIsDateSeparator) {\n enrichedMessages.push({ type: 'DateSeparator', id: `day-divider-${message.id}`, date })\n }\n return\n }\n\n const inThread = message.replyRootId !== null\n const nextMessageInThread = nextMessage?.replyRootId !== null\n const threadRoot = message.replyRootId === message.id\n const nextMessageThreadRoot = nextMessage?.replyRootId === nextMessage?.id\n const prevMessageDifferentThread = message.replyRootId !== prevMessage?.replyRootId\n const nextMessageDifferentThread = message.replyRootId !== nextMessage?.replyRootId\n const prevMessageDifferentAuthor = message.author?.id !== prevMessage?.author?.id\n const nextMessageDifferentAuthor = message.author?.id !== nextMessage?.author?.id\n const prevMessageMoreThan5Minutes =\n prevMessage &&\n new Date(message.createdAt).getTime() - new Date(prevMessage.createdAt).getTime() > 60000 * 5\n const nextMessageMoreThan5Minutes =\n nextMessage &&\n new Date(nextMessage.createdAt).getTime() - new Date(message.createdAt).getTime() > 60000 * 5\n const nextMessageIsDateSeparator =\n nextMessage && date !== dayjs(nextMessage.createdAt).format('YYYY-MM-DD')\n const insertReplyShadowMessage =\n message.replyRootId &&\n !threadRoot &&\n (prevMessageDifferentThread || prevMessageIsDateSeparator)\n const lastInGroup =\n !nextMessage ||\n nextMessageDifferentAuthor ||\n nextMessageMoreThan5Minutes ||\n nextMessageDifferentThread ||\n nextMessageIsDateSeparator\n const renderAuthor =\n !message.mine &&\n (!prevMessage ||\n prevMessageDifferentAuthor ||\n prevMessageMoreThan5Minutes ||\n prevMessageDifferentThread ||\n prevMessageIsDateSeparator)\n const nextIsReplyShadowMessage =\n nextMessageInThread &&\n !nextMessageThreadRoot &&\n (nextMessageDifferentThread || nextMessageIsDateSeparator)\n\n if (message.mine && !encounteredOneOfMyMessages) {\n encounteredOneOfMyMessages = true\n message.myLatestInConversation = true\n } else {\n message.myLatestInConversation = false\n }\n message.lastInGroup = lastInGroup\n message.renderAuthor = renderAuthor\n message.threadPosition = null\n message.nextRendersAuthor = nextMessage?.renderAuthor\n message.isReplyShadowMessage = false\n message.nextIsReplyShadowMessage = nextIsReplyShadowMessage\n\n if (!inReplyScreen && inThread) {\n message.prevIsMyReply = prevMessage?.mine\n message.nextIsMyReply = nextMessage?.mine\n\n const firstInThread = threadRoot\n const lastInThread = nextMessageDifferentThread || nextMessageIsDateSeparator\n\n if (firstInThread && lastInThread)\n message.threadPosition = null // ensures we don't render a connector for root replies that aren't immediately followed up a reply\n else if (firstInThread) message.threadPosition = 'first'\n else if (lastInThread) message.threadPosition = 'last'\n else message.threadPosition = 'center'\n }\n\n enrichedMessages.push(message)\n\n if (insertReplyShadowMessage) {\n enrichedMessages.push({\n type: 'ReplyShadowMessage',\n id: `${message.id}-${message.replyRootId}`,\n messageId: message.replyRootId!,\n isReplyShadowMessage: true,\n nextRendersAuthor: message?.renderAuthor,\n })\n }\n\n if (!prevMessage || date !== dayjs(prevMessage.createdAt).format('YYYY-MM-DD')) {\n enrichedMessages.push({ type: 'DateSeparator', id: `day-divider-${message.id}`, date })\n }\n })\n\n return enrichedMessages\n}\ninterface ConversationScreenTitleProps extends HeaderTitleProps {\n conversation_id: number\n badge?: ConversationBadgeResource\n deleted?: boolean\n muted?: boolean\n}\n\nexport const ConversationScreenTitle = ({\n conversation_id,\n badge,\n children,\n style,\n deleted,\n muted,\n}: ConversationScreenTitleProps) => {\n const styles = usePressableHeaderStyle()\n const navigation = useNavigation()\n const resourceType = badge?.pcoResourceType || ''\n const productName = badge?.appName\n const name = badge?.text || undefined\n\n return (\n <PlatformPressable\n accessibilityHint=\"Opens details about members and settings\"\n style={styles.container}\n onPress={() => {\n if (deleted) return\n\n navigation.navigate('ConversationDetails', { conversation_id })\n }}\n >\n <View style={styles.titleWrapper}>\n <View style={styles.titleTextContainer}>\n <HeaderTitle maxFontSizeMultiplier={1} style={style}>\n {children}\n </HeaderTitle>\n </View>\n {muted && <Icon name=\"general.bellMuted\" size={12} />}\n {!deleted && <Icon name=\"general.downChevron\" size={12} />}\n </View>\n <Badge\n variant=\"metaSubtle\"\n productLogoName={productName}\n label={resourceType}\n metaLabel={name}\n style={styles.badge}\n maxFontSizeMultiplier={1}\n />\n </PlatformPressable>\n )\n}\n\nconst usePressableHeaderStyle = () => {\n return StyleSheet.create({\n container: {\n alignItems: Platform.select({ android: 'flex-start', default: 'center' }),\n marginRight: Platform.select({ ios: 20, default: 16 }),\n flex: 1,\n },\n titleWrapper: {\n alignItems: 'center',\n columnGap: 4,\n flexDirection: 'row',\n flexShrink: 1,\n },\n titleTextContainer: {\n flexShrink: 1,\n minWidth: 0,\n },\n badge: {\n alignSelf: Platform.select({ android: 'flex-start', default: 'center' }),\n marginTop: 2,\n },\n })\n}\n\nconst useStyles = () => {\n const navigationTheme = useNavigationTheme()\n const { bottom } = useSafeAreaInsets()\n\n return StyleSheet.create({\n container: {\n flex: 1,\n justifyContent: 'center',\n backgroundColor: navigationTheme.colors.card,\n paddingBottom: bottom,\n },\n listContainer: {\n paddingVertical: 12,\n },\n listHeader: {\n // Just whitespace to provide space where the typing indicator can be\n height: 16,\n },\n })\n}\n\n/**\n * useEnsureConversationsRouteExists\n */\nconst useEnsureConversationsRouteExists = () => {\n const navigation = useNavigation()\n const { params } = useRoute<RouteProp<ConversationScreenProps['route']>>()\n\n useEffect(() => {\n const navigationState = navigation.getState()\n const routes = navigationState?.routes || []\n const conversationsRoute = routes.find(r => r.name === 'Conversations')\n\n if (conversationsRoute) return\n\n navigation.dispatch(state => {\n return CommonActions.reset({\n ...state,\n routes: [\n { name: 'Conversations', params: { chat_group_graph_id: params?.chat_group_graph_id } },\n ...routes,\n ],\n index: state.index + 1,\n })\n })\n }, [navigation, params?.chat_group_graph_id])\n}\n"]}
|
|
1
|
+
{"version":3,"file":"conversation_screen.js","sourceRoot":"","sources":["../../src/screens/conversation_screen.tsx"],"names":[],"mappings":"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","sourcesContent":["import { date as formatDate } from '@planningcenter/datetime-fmt'\nimport { HeaderTitle, HeaderTitleProps, PlatformPressable } from '@react-navigation/elements'\nimport {\n CommonActions,\n RouteProp,\n StaticScreenProps,\n useNavigation,\n useTheme as useNavigationTheme,\n useRoute,\n} from '@react-navigation/native'\nimport React, { useCallback, useEffect, useRef, useState } from 'react'\nimport { FlatList, Platform, StyleSheet, View } from 'react-native'\nimport { useSafeAreaInsets } from 'react-native-safe-area-context'\nimport { Badge, Icon, Text } from '../components'\nimport { EmptyConversationBlankState } from '../components/conversation/empty_conversation_blank_state'\nimport { JumpToBottomButton } from '../components/conversation/jump_to_bottom_button'\nimport { Message } from '../components/conversation/message'\nimport { MessageForm } from '../components/conversation/message_form'\nimport {\n LeaderMessagesDisabledBanner,\n MemberMessagesDisabledBanner,\n} from '../components/conversation/messages_disabled_banners'\nimport { ReplyShadowMessage } from '../components/conversation/reply_shadow_message'\nimport { SystemMessage } from '../components/conversation/system_message'\nimport { TypingIndicator } from '../components/conversation/typing_indicator'\nimport { KeyboardView } from '../components/display/keyboard_view'\nimport BlankState from '../components/primitive/blank_state_primitive'\nimport { ConversationContextProvider } from '../contexts/conversation_context'\nimport { useTheme } from '../hooks'\nimport { useConversation } from '../hooks/use_conversation'\nimport { useConversationJoltEvents } from '../hooks/use_conversation_jolt_events'\nimport { useConversationMessages } from '../hooks/use_conversation_messages'\nimport { useConversationMessagesJoltEvents } from '../hooks/use_conversation_messages_jolt_events'\nimport { useFeatures } from '../hooks/use_features'\nimport { useMarkLatestMessageRead } from '../hooks/use_mark_latest_message_read'\nimport {\n normalizeAnalyticsMetadata,\n usePublishProductAnalyticsEvent,\n} from '../hooks/use_product_analytics'\nimport { MessageResource } from '../types'\nimport { ConversationBadgeResource } from '../types/resources/conversation_badge'\nimport { getRelativeDateStatus } from '../utils/date'\nimport dayjs from '../utils/dayjs'\nimport { CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL } from '../utils/styles'\nimport { isSystemMessage } from '../utils/system_messages'\n\nexport type ConversationRouteProps = {\n conversation_id: number\n reply_root_id?: string | null\n reply_root_author_name?: string\n chat_group_graph_id?: string\n clear_input?: boolean\n editing_message_id?: number | null\n message_id?: string\n title?: string\n subtitle?: string\n badge?: ConversationBadgeResource\n deleted?: boolean\n muted?: boolean\n}\n\nexport type ConversationScreenProps = StaticScreenProps<ConversationRouteProps>\n\nexport function ConversationScreen({ route }: ConversationScreenProps) {\n const { conversation_id, message_id, reply_root_id } = route.params\n\n const { data: conversation } = useConversation({ conversation_id })\n const { featureEnabled } = useFeatures()\n\n usePublishProductAnalyticsEvent('chat.mobile.conversations.show.opened', {\n reply_root_id,\n ...normalizeAnalyticsMetadata(conversation),\n })\n\n const lastReadMessageSortKey = conversation.conversationMembership?.lastReadMessageSortKey ?? null\n const jumpToUnreadAnchor = featureEnabled('jump_to_unread') ? lastReadMessageSortKey : null\n const initialMessageId = message_id ?? jumpToUnreadAnchor\n const initialMessageIdIsAnchor = !!initialMessageId && !message_id\n\n return (\n <ConversationContextProvider\n conversationId={conversation_id}\n currentPageReplyRootId={reply_root_id ?? null}\n initialMessageId={initialMessageId}\n initialMessageIdIsAnchor={initialMessageIdIsAnchor}\n >\n <ConversationScreenContent route={route} />\n </ConversationContextProvider>\n )\n}\n\nfunction ConversationScreenContent({ route }: ConversationScreenProps) {\n const styles = useStyles()\n const navigation = useNavigation()\n const { conversation_id, editing_message_id, reply_root_id, reply_root_author_name } =\n route.params\n const { data: conversation } = useConversation(route.params)\n const { messages, refetch, isRefetching, fetchOlderMessages } = useConversationMessages({\n conversation_id,\n reply_root_id,\n })\n useConversationJoltEvents({ conversationId: conversation_id })\n useConversationMessagesJoltEvents({ conversationId: conversation_id })\n useEnsureConversationsRouteExists()\n useMarkLatestMessageRead({ conversation, messages })\n const messagesWithSeparators = groupMessages({\n ms: messages,\n inReplyScreen: !!reply_root_id,\n })\n const noMessages = messagesWithSeparators.length === 0\n\n const { repliesDisabled, memberAbility, badges, title } = conversation\n const canReply = memberAbility?.canReply\n const showLeaderDisabledReplyBanner = canReply && repliesDisabled\n const canDeleteNonAuthoredMessages = memberAbility?.canDeleteNonAuthoredMessages ?? false\n const currentlyEditingMessage = messages.find(m => String(m.id) === String(editing_message_id))\n const replyRootAuthorFirstName = reply_root_author_name?.split(' ')[0]\n const replyHeaderTitle = replyRootAuthorFirstName\n ? `Reply to ${replyRootAuthorFirstName}`\n : 'Reply'\n // Prefer the membership for optimistic updates.\n const muted = conversation.conversationMembership?.muted ?? conversation.muted\n\n const listRef = useRef<FlatList>(null)\n const [showJumpToBottomButton, setShowJumpToBottomButton] = useState(false)\n\n const trackScroll = (event: any) => {\n const offsetY = event.nativeEvent.contentOffset.y\n setShowJumpToBottomButton(offsetY > 200)\n }\n\n const handleReturnToBottom = useCallback(() => {\n listRef.current?.scrollToOffset({\n offset: 0,\n })\n }, [])\n\n useEffect(() => {\n if (reply_root_id) {\n navigation.setParams({\n title: replyHeaderTitle,\n })\n } else {\n navigation.setParams({\n title: title,\n badge: badges?.[0],\n deleted: conversation?.deleted,\n muted,\n })\n }\n }, [navigation, title, badges, conversation?.deleted, reply_root_id, replyHeaderTitle, muted])\n\n if (!conversation || conversation.deleted) {\n return (\n <View style={styles.container}>\n <BlankState.Root>\n <BlankState.Imagery name=\"general.outlinedTextMessage\" />\n <BlankState.Content>\n <BlankState.Heading>This conversation has been deleted</BlankState.Heading>\n </BlankState.Content>\n <BlankState.Button\n onPress={navigation.goBack}\n title=\"Back to conversations\"\n accessibilityHint=\"Navigates back to the conversations list\"\n accessibilityRole=\"link\"\n />\n </BlankState.Root>\n </View>\n )\n }\n\n return (\n <View style={styles.container}>\n <KeyboardView>\n {noMessages ? (\n <EmptyConversationBlankState />\n ) : (\n <FlatList\n inverted\n ref={listRef}\n contentContainerStyle={styles.listContainer}\n refreshing={isRefetching}\n onRefresh={refetch}\n data={messagesWithSeparators}\n keyExtractor={item => item.id}\n onScroll={trackScroll}\n scrollEventThrottle={10}\n renderItem={({ item }) => {\n if (item.type === 'DateSeparator') {\n return <InlineDateSeparator {...item} />\n }\n\n if (item.type === 'ReplyShadowMessage') {\n return (\n <ReplyShadowMessage\n {...item}\n conversation_id={conversation_id}\n inReplyScreen={!!reply_root_id}\n />\n )\n }\n\n if (isSystemMessage(item)) {\n return <SystemMessage message={item} conversationId={conversation_id} />\n }\n\n return (\n <Message\n {...item}\n canDeleteNonAuthoredMessages={canDeleteNonAuthoredMessages}\n conversation_id={conversation_id}\n latestReadMessageSortKey={conversation?.latestReadMessageSortKey}\n inReplyScreen={!!reply_root_id}\n />\n )\n }}\n onEndReached={() => fetchOlderMessages()}\n ListHeaderComponent={<View style={styles.listHeader} />}\n />\n )}\n <JumpToBottomButton onPress={handleReturnToBottom} visible={showJumpToBottomButton} />\n {!noMessages && <TypingIndicator />}\n {showLeaderDisabledReplyBanner && <LeaderMessagesDisabledBanner />}\n {canReply ? (\n <MessageForm.Root\n replyRootAuthorFirstName={replyRootAuthorFirstName}\n conversation={conversation}\n replyRootId={reply_root_id}\n currentlyEditingMessage={currentlyEditingMessage}\n // We use a separate key so that it remounts component when switching between new\n // and edit message. This simplifies internal state handling.\n key={\n currentlyEditingMessage\n ? `edit-message-form-${currentlyEditingMessage.id}`\n : 'new-message-form'\n }\n >\n <MessageForm.AttachmentPicker />\n <MessageForm.Commands />\n <MessageForm.TextInput />\n <MessageForm.SubmitButton />\n </MessageForm.Root>\n ) : (\n <MemberMessagesDisabledBanner />\n )}\n </KeyboardView>\n </View>\n )\n}\n\nexport type DateSeparator = { type: 'DateSeparator'; id: string; date: string }\n\nfunction InlineDateSeparator({ date }: DateSeparator) {\n const styles = useDateSeparatorStyles()\n const { isThisYear } = getRelativeDateStatus(date)\n const showYear = !isThisYear\n const dateStamp = formatDate(date, { style: 'long', year: showYear })\n\n return (\n <View style={styles.container}>\n <View style={styles.separator} />\n <Text variant=\"footnote\" style={styles.dateText}>\n {dateStamp}\n </Text>\n <View style={styles.separator} />\n </View>\n )\n}\n\nconst useDateSeparatorStyles = () => {\n const theme = useTheme()\n return StyleSheet.create({\n container: {\n alignItems: 'center',\n flexDirection: 'row',\n paddingHorizontal: CONVERSATION_MESSAGE_LIST_PADDING_HORIZONTAL,\n paddingVertical: 16,\n },\n separator: {\n flex: 1,\n height: 1,\n borderTopWidth: 1,\n borderTopColor: theme.colors.borderColorDefaultBase,\n },\n dateText: {\n paddingHorizontal: 8,\n },\n })\n}\n\ntype ReplyShadowMessage = {\n type: 'ReplyShadowMessage'\n id: string\n messageId: string\n isReplyShadowMessage: boolean\n nextRendersAuthor: boolean\n}\n\ninterface GroupMessagesProps {\n ms: MessageResource[]\n inReplyScreen?: boolean\n}\n\nexport const groupMessages = ({ ms, inReplyScreen }: GroupMessagesProps) => {\n let enrichedMessages: (MessageResource | DateSeparator | ReplyShadowMessage)[] = []\n let encounteredOneOfMyMessages = false\n\n ms.forEach((message, i) => {\n const prevMessage = ms[i + 1]\n const nextMessage = ms[i - 1]\n const date = dayjs(message.createdAt).format('YYYY-MM-DD')\n\n const prevMessageIsDateSeparator =\n prevMessage && date !== dayjs(prevMessage.createdAt).format('YYYY-MM-DD')\n\n if (isSystemMessage(message)) {\n message.myLatestInConversation = false\n message.lastInGroup = true\n message.renderAuthor = false\n message.nextRendersAuthor = nextMessage?.renderAuthor\n message.isReplyShadowMessage = false\n message.nextIsReplyShadowMessage = false\n message.threadPosition = null\n enrichedMessages.push(message)\n if (prevMessageIsDateSeparator) {\n enrichedMessages.push({ type: 'DateSeparator', id: `day-divider-${message.id}`, date })\n }\n return\n }\n\n const inThread = message.replyRootId !== null\n const nextMessageInThread = nextMessage?.replyRootId !== null\n const threadRoot = message.replyRootId === message.id\n const nextMessageThreadRoot = nextMessage?.replyRootId === nextMessage?.id\n const prevMessageDifferentThread = message.replyRootId !== prevMessage?.replyRootId\n const nextMessageDifferentThread = message.replyRootId !== nextMessage?.replyRootId\n const prevMessageDifferentAuthor = message.author?.id !== prevMessage?.author?.id\n const nextMessageDifferentAuthor = message.author?.id !== nextMessage?.author?.id\n const prevMessageMoreThan5Minutes =\n prevMessage &&\n new Date(message.createdAt).getTime() - new Date(prevMessage.createdAt).getTime() > 60000 * 5\n const nextMessageMoreThan5Minutes =\n nextMessage &&\n new Date(nextMessage.createdAt).getTime() - new Date(message.createdAt).getTime() > 60000 * 5\n const nextMessageIsDateSeparator =\n nextMessage && date !== dayjs(nextMessage.createdAt).format('YYYY-MM-DD')\n const insertReplyShadowMessage =\n message.replyRootId &&\n !threadRoot &&\n (prevMessageDifferentThread || prevMessageIsDateSeparator)\n const lastInGroup =\n !nextMessage ||\n nextMessageDifferentAuthor ||\n nextMessageMoreThan5Minutes ||\n nextMessageDifferentThread ||\n nextMessageIsDateSeparator\n const renderAuthor =\n !message.mine &&\n (!prevMessage ||\n prevMessageDifferentAuthor ||\n prevMessageMoreThan5Minutes ||\n prevMessageDifferentThread ||\n prevMessageIsDateSeparator)\n const nextIsReplyShadowMessage =\n nextMessageInThread &&\n !nextMessageThreadRoot &&\n (nextMessageDifferentThread || nextMessageIsDateSeparator)\n\n if (message.mine && !encounteredOneOfMyMessages) {\n encounteredOneOfMyMessages = true\n message.myLatestInConversation = true\n } else {\n message.myLatestInConversation = false\n }\n message.lastInGroup = lastInGroup\n message.renderAuthor = renderAuthor\n message.threadPosition = null\n message.nextRendersAuthor = nextMessage?.renderAuthor\n message.isReplyShadowMessage = false\n message.nextIsReplyShadowMessage = nextIsReplyShadowMessage\n\n if (!inReplyScreen && inThread) {\n message.prevIsMyReply = prevMessage?.mine\n message.nextIsMyReply = nextMessage?.mine\n\n const firstInThread = threadRoot\n const lastInThread = nextMessageDifferentThread || nextMessageIsDateSeparator\n\n if (firstInThread && lastInThread)\n message.threadPosition = null // ensures we don't render a connector for root replies that aren't immediately followed up a reply\n else if (firstInThread) message.threadPosition = 'first'\n else if (lastInThread) message.threadPosition = 'last'\n else message.threadPosition = 'center'\n }\n\n enrichedMessages.push(message)\n\n if (insertReplyShadowMessage) {\n enrichedMessages.push({\n type: 'ReplyShadowMessage',\n id: `${message.id}-${message.replyRootId}`,\n messageId: message.replyRootId!,\n isReplyShadowMessage: true,\n nextRendersAuthor: message?.renderAuthor,\n })\n }\n\n if (!prevMessage || date !== dayjs(prevMessage.createdAt).format('YYYY-MM-DD')) {\n enrichedMessages.push({ type: 'DateSeparator', id: `day-divider-${message.id}`, date })\n }\n })\n\n return enrichedMessages\n}\ninterface ConversationScreenTitleProps extends HeaderTitleProps {\n conversation_id: number\n badge?: ConversationBadgeResource\n deleted?: boolean\n muted?: boolean\n}\n\nexport const ConversationScreenTitle = ({\n conversation_id,\n badge,\n children,\n style,\n deleted,\n muted,\n}: ConversationScreenTitleProps) => {\n const styles = usePressableHeaderStyle()\n const navigation = useNavigation()\n const resourceType = badge?.pcoResourceType || ''\n const productName = badge?.appName\n const name = badge?.text || undefined\n\n return (\n <PlatformPressable\n accessibilityHint=\"Opens details about members and settings\"\n style={styles.container}\n onPress={() => {\n if (deleted) return\n\n navigation.navigate('ConversationDetails', { conversation_id })\n }}\n >\n <View style={styles.titleWrapper}>\n <View style={styles.titleTextContainer}>\n <HeaderTitle maxFontSizeMultiplier={1} style={style}>\n {children}\n </HeaderTitle>\n </View>\n {muted && <Icon name=\"general.bellMuted\" size={12} />}\n {!deleted && <Icon name=\"general.downChevron\" size={12} />}\n </View>\n <Badge\n variant=\"metaSubtle\"\n productLogoName={productName}\n label={resourceType}\n metaLabel={name}\n style={styles.badge}\n maxFontSizeMultiplier={1}\n />\n </PlatformPressable>\n )\n}\n\nconst usePressableHeaderStyle = () => {\n return StyleSheet.create({\n container: {\n alignItems: Platform.select({ android: 'flex-start', default: 'center' }),\n marginRight: Platform.select({ ios: 20, default: 16 }),\n flex: 1,\n },\n titleWrapper: {\n alignItems: 'center',\n columnGap: 4,\n flexDirection: 'row',\n flexShrink: 1,\n },\n titleTextContainer: {\n flexShrink: 1,\n minWidth: 0,\n },\n badge: {\n alignSelf: Platform.select({ android: 'flex-start', default: 'center' }),\n marginTop: 2,\n },\n })\n}\n\nconst useStyles = () => {\n const navigationTheme = useNavigationTheme()\n const { bottom } = useSafeAreaInsets()\n\n return StyleSheet.create({\n container: {\n flex: 1,\n justifyContent: 'center',\n backgroundColor: navigationTheme.colors.card,\n paddingBottom: bottom,\n },\n listContainer: {\n paddingVertical: 12,\n },\n listHeader: {\n // Just whitespace to provide space where the typing indicator can be\n height: 16,\n },\n })\n}\n\n/**\n * useEnsureConversationsRouteExists\n */\nconst useEnsureConversationsRouteExists = () => {\n const navigation = useNavigation()\n const { params } = useRoute<RouteProp<ConversationScreenProps['route']>>()\n\n useEffect(() => {\n const navigationState = navigation.getState()\n const routes = navigationState?.routes || []\n const conversationsRoute = routes.find(r => r.name === 'Conversations')\n\n if (conversationsRoute) return\n\n navigation.dispatch(state => {\n return CommonActions.reset({\n ...state,\n routes: [\n { name: 'Conversations', params: { chat_group_graph_id: params?.chat_group_graph_id } },\n ...routes,\n ],\n index: state.index + 1,\n })\n })\n }, [navigation, params?.chat_group_graph_id])\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ApiCollection, MessageResource } from '../types';
|
|
2
|
+
import { RequestData } from './client';
|
|
3
|
+
export type MessagesPageParam = Partial<RequestData> & {
|
|
4
|
+
order?: 'asc' | 'desc';
|
|
5
|
+
};
|
|
6
|
+
export declare const anchoredSeedPageParams: (anchor: string) => MessagesPageParam[];
|
|
7
|
+
export declare const olderPageParam: (page: ApiCollection<MessageResource>) => MessagesPageParam | undefined;
|
|
8
|
+
export declare const newerPageParam: (page: ApiCollection<MessageResource>) => MessagesPageParam | undefined;
|
|
9
|
+
export declare const sortAndFilterMessages: (pages: ApiCollection<MessageResource>[]) => MessageResource[];
|
|
10
|
+
//# sourceMappingURL=conversation_messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation_messages.d.ts","sourceRoot":"","sources":["../../src/utils/conversation_messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAEtC,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG;IACrD,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;CACvB,CAAA;AAED,eAAO,MAAM,sBAAsB,GAAI,QAAQ,MAAM,KAAG,iBAAiB,EAGxE,CAAA;AAED,eAAO,MAAM,cAAc,GACzB,MAAM,aAAa,CAAC,eAAe,CAAC,KACnC,iBAAiB,GAAG,SAItB,CAAA;AAED,eAAO,MAAM,cAAc,GACzB,MAAM,aAAa,CAAC,eAAe,CAAC,KACnC,iBAAiB,GAAG,SAItB,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,OAAO,aAAa,CAAC,eAAe,CAAC,EAAE,KAAG,eAAe,EAQjD,CAAA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const anchoredSeedPageParams = (anchor) => [
|
|
2
|
+
{ where: { id_gte: anchor }, order: 'asc' },
|
|
3
|
+
{ where: { id_lt: anchor }, order: 'desc' },
|
|
4
|
+
];
|
|
5
|
+
export const olderPageParam = (page) => {
|
|
6
|
+
const idLt = page.meta?.next?.idLt;
|
|
7
|
+
if (!idLt)
|
|
8
|
+
return undefined;
|
|
9
|
+
return { where: { id_lt: idLt }, order: 'desc' };
|
|
10
|
+
};
|
|
11
|
+
export const newerPageParam = (page) => {
|
|
12
|
+
const idGt = page.meta?.next?.idGt;
|
|
13
|
+
if (!idGt)
|
|
14
|
+
return undefined;
|
|
15
|
+
return { where: { id_gt: idGt }, order: 'asc' };
|
|
16
|
+
};
|
|
17
|
+
export const sortAndFilterMessages = (pages) => pages
|
|
18
|
+
.flatMap(page => page.data)
|
|
19
|
+
.filter(message => (!message.deletedAt || message.replyRootId) &&
|
|
20
|
+
(message.attachments?.length || message.text?.length))
|
|
21
|
+
.sort((a, b) => -a.id.localeCompare(b.id));
|
|
22
|
+
//# sourceMappingURL=conversation_messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation_messages.js","sourceRoot":"","sources":["../../src/utils/conversation_messages.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,MAAc,EAAuB,EAAE,CAAC;IAC7E,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE;IAC3C,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;CAC5C,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,IAAoC,EACL,EAAE;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAA;IAClC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAA;IAC3B,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AAClD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,IAAoC,EACL,EAAE;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAA;IAClC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAA;IAC3B,OAAO,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;AACjD,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,KAAuC,EAAqB,EAAE,CAClG,KAAK;KACF,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;KAC1B,MAAM,CACL,OAAO,CAAC,EAAE,CACR,CAAC,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC;IAC3C,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,IAAI,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CACxD;KACA,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA","sourcesContent":["import { ApiCollection, MessageResource } from '../types'\nimport { RequestData } from './client'\n\nexport type MessagesPageParam = Partial<RequestData> & {\n order?: 'asc' | 'desc'\n}\n\nexport const anchoredSeedPageParams = (anchor: string): MessagesPageParam[] => [\n { where: { id_gte: anchor }, order: 'asc' },\n { where: { id_lt: anchor }, order: 'desc' },\n]\n\nexport const olderPageParam = (\n page: ApiCollection<MessageResource>\n): MessagesPageParam | undefined => {\n const idLt = page.meta?.next?.idLt\n if (!idLt) return undefined\n return { where: { id_lt: idLt }, order: 'desc' }\n}\n\nexport const newerPageParam = (\n page: ApiCollection<MessageResource>\n): MessagesPageParam | undefined => {\n const idGt = page.meta?.next?.idGt\n if (!idGt) return undefined\n return { where: { id_gt: idGt }, order: 'asc' }\n}\n\nexport const sortAndFilterMessages = (pages: ApiCollection<MessageResource>[]): MessageResource[] =>\n pages\n .flatMap(page => page.data)\n .filter(\n message =>\n (!message.deletedAt || message.replyRootId) &&\n (message.attachments?.length || message.text?.length)\n )\n .sort((a, b) => -a.id.localeCompare(b.id))\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/chat-react-native",
|
|
3
|
-
"version": "3.35.0-rc.
|
|
3
|
+
"version": "3.35.0-rc.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"react-native-url-polyfill": "^2.0.0",
|
|
66
66
|
"typescript": "~5.9.2"
|
|
67
67
|
},
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "eecec62a4683e528bb8e2f275d19fb9915544679"
|
|
69
69
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { QueryClientProvider } from '@tanstack/react-query'
|
|
2
|
+
import { act, renderHook } from '@testing-library/react-hooks'
|
|
3
|
+
import React, { Suspense } from 'react'
|
|
4
|
+
import { buildTestQueryClient } from '../../__utils__/query_client'
|
|
5
|
+
import { ConversationContextProvider } from '../../contexts/conversation_context'
|
|
6
|
+
import * as useApiClientModule from '../../hooks/use_api_client'
|
|
7
|
+
import { useConversationMessages } from '../../hooks/use_conversation_messages'
|
|
8
|
+
import { ApiCollection, MessageResource } from '../../types'
|
|
9
|
+
|
|
10
|
+
const mockMessage = (id: string): MessageResource =>
|
|
11
|
+
({
|
|
12
|
+
id,
|
|
13
|
+
type: 'Message',
|
|
14
|
+
text: `msg ${id}`,
|
|
15
|
+
attachments: [],
|
|
16
|
+
deletedAt: null,
|
|
17
|
+
replyRootId: null,
|
|
18
|
+
}) as MessageResource
|
|
19
|
+
|
|
20
|
+
const apiResponse = (data: MessageResource[]): ApiCollection<MessageResource> => ({
|
|
21
|
+
data,
|
|
22
|
+
links: {},
|
|
23
|
+
meta: { count: data.length, totalCount: data.length, next: {} },
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const createWrapper = (initialMessageId: string | null) => {
|
|
27
|
+
const queryClient = buildTestQueryClient()
|
|
28
|
+
|
|
29
|
+
return ({ children }: { children: React.ReactNode }) => (
|
|
30
|
+
<QueryClientProvider client={queryClient}>
|
|
31
|
+
<Suspense fallback={null}>
|
|
32
|
+
<ConversationContextProvider
|
|
33
|
+
conversationId={123}
|
|
34
|
+
currentPageReplyRootId={null}
|
|
35
|
+
initialMessageId={initialMessageId}
|
|
36
|
+
initialMessageIdIsAnchor={!!initialMessageId}
|
|
37
|
+
>
|
|
38
|
+
{children}
|
|
39
|
+
</ConversationContextProvider>
|
|
40
|
+
</Suspense>
|
|
41
|
+
</QueryClientProvider>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const flushPromises = async () => {
|
|
46
|
+
await act(async () => {
|
|
47
|
+
await Promise.resolve()
|
|
48
|
+
await Promise.resolve()
|
|
49
|
+
await Promise.resolve()
|
|
50
|
+
await Promise.resolve()
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const mockApiClient = (get: jest.Mock) => {
|
|
55
|
+
jest.spyOn(useApiClientModule, 'useApiClient').mockReturnValue({
|
|
56
|
+
chat: { get },
|
|
57
|
+
} as unknown as ReturnType<typeof useApiClientModule.useApiClient>)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe('useConversationMessages', () => {
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
jest.restoreAllMocks()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('fires two parallel seed requests with id_gte/asc and id_lt/desc when anchored', async () => {
|
|
66
|
+
const get = jest.fn(({ data }: { data: { where?: Record<string, string> } }) => {
|
|
67
|
+
if (data.where?.id_gte === '01B') {
|
|
68
|
+
return Promise.resolve(apiResponse([mockMessage('01C'), mockMessage('01B')]))
|
|
69
|
+
}
|
|
70
|
+
if (data.where?.id_lt === '01B') {
|
|
71
|
+
return Promise.resolve(apiResponse([mockMessage('01A')]))
|
|
72
|
+
}
|
|
73
|
+
return Promise.resolve(apiResponse([]))
|
|
74
|
+
})
|
|
75
|
+
mockApiClient(get)
|
|
76
|
+
|
|
77
|
+
renderHook(() => useConversationMessages({ conversation_id: 123 }), {
|
|
78
|
+
wrapper: createWrapper('01B'),
|
|
79
|
+
})
|
|
80
|
+
await flushPromises()
|
|
81
|
+
|
|
82
|
+
expect(get).toHaveBeenCalledTimes(2)
|
|
83
|
+
const requested = get.mock.calls.map(
|
|
84
|
+
([req]: [{ data: { where?: Record<string, string>; order?: string } }]) => ({
|
|
85
|
+
where: req.data.where,
|
|
86
|
+
order: req.data.order,
|
|
87
|
+
})
|
|
88
|
+
)
|
|
89
|
+
expect(requested).toEqual(
|
|
90
|
+
expect.arrayContaining([
|
|
91
|
+
{ where: { id_gte: '01B' }, order: 'asc' },
|
|
92
|
+
{ where: { id_lt: '01B' }, order: 'desc' },
|
|
93
|
+
])
|
|
94
|
+
)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('fires one fetch with no cursor when not anchored', async () => {
|
|
98
|
+
const get = jest.fn(() => Promise.resolve(apiResponse([mockMessage('01A')])))
|
|
99
|
+
mockApiClient(get)
|
|
100
|
+
|
|
101
|
+
renderHook(() => useConversationMessages({ conversation_id: 123 }), {
|
|
102
|
+
wrapper: createWrapper(null),
|
|
103
|
+
})
|
|
104
|
+
await flushPromises()
|
|
105
|
+
|
|
106
|
+
expect(get).toHaveBeenCalledTimes(1)
|
|
107
|
+
expect(get.mock.calls[0][0].data.where).toBeUndefined()
|
|
108
|
+
})
|
|
109
|
+
})
|
|
@@ -1,31 +1,57 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, {
|
|
2
|
+
createContext,
|
|
3
|
+
PropsWithChildren,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useState,
|
|
8
|
+
} from 'react'
|
|
2
9
|
|
|
3
10
|
interface ConversationContextValue {
|
|
4
11
|
conversationId: number
|
|
5
12
|
currentPageReplyRootId: string | null
|
|
13
|
+
initialMessageId: string | null
|
|
14
|
+
setInitialMessageId: (id: string | null) => void
|
|
15
|
+
initialMessageIdIsAnchor: boolean
|
|
6
16
|
}
|
|
7
17
|
|
|
8
18
|
interface ConversationContextProviderProps extends PropsWithChildren {
|
|
9
19
|
conversationId: number
|
|
10
20
|
currentPageReplyRootId: string | null
|
|
21
|
+
initialMessageId?: string | null
|
|
22
|
+
initialMessageIdIsAnchor?: boolean
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
const ConversationContext = createContext<ConversationContextValue>({
|
|
14
26
|
conversationId: 0,
|
|
15
27
|
currentPageReplyRootId: null,
|
|
28
|
+
initialMessageId: null,
|
|
29
|
+
setInitialMessageId: () => {},
|
|
30
|
+
initialMessageIdIsAnchor: false,
|
|
16
31
|
})
|
|
17
32
|
|
|
18
33
|
export const ConversationContextProvider = ({
|
|
19
34
|
children,
|
|
20
35
|
conversationId,
|
|
21
36
|
currentPageReplyRootId,
|
|
37
|
+
initialMessageId: initialMessageIdProp = null,
|
|
38
|
+
initialMessageIdIsAnchor = false,
|
|
22
39
|
}: ConversationContextProviderProps) => {
|
|
40
|
+
const [initialMessageId, setInitialMessageId] = useState(initialMessageIdProp)
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setInitialMessageId(initialMessageIdProp)
|
|
44
|
+
}, [initialMessageIdProp])
|
|
45
|
+
|
|
23
46
|
const value = useMemo(
|
|
24
47
|
() => ({
|
|
25
48
|
conversationId,
|
|
26
49
|
currentPageReplyRootId,
|
|
50
|
+
initialMessageId,
|
|
51
|
+
setInitialMessageId,
|
|
52
|
+
initialMessageIdIsAnchor,
|
|
27
53
|
}),
|
|
28
|
-
[conversationId, currentPageReplyRootId]
|
|
54
|
+
[conversationId, currentPageReplyRootId, initialMessageId, initialMessageIdIsAnchor]
|
|
29
55
|
)
|
|
30
56
|
|
|
31
57
|
return <ConversationContext.Provider value={value}>{children}</ConversationContext.Provider>
|
|
@@ -1,28 +1,112 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnyUseSuspenseInfiniteQueryOptions,
|
|
3
|
+
InfiniteData,
|
|
4
|
+
useSuspenseInfiniteQuery,
|
|
5
|
+
useSuspenseQueries,
|
|
6
|
+
} from '@tanstack/react-query'
|
|
1
7
|
import { useMemo } from 'react'
|
|
2
|
-
import {
|
|
8
|
+
import { useConversationContext } from '../contexts/conversation_context'
|
|
9
|
+
import { ApiCollection, MessageResource } from '../types'
|
|
10
|
+
import {
|
|
11
|
+
anchoredSeedPageParams,
|
|
12
|
+
MessagesPageParam,
|
|
13
|
+
newerPageParam,
|
|
14
|
+
olderPageParam,
|
|
15
|
+
sortAndFilterMessages,
|
|
16
|
+
} from '../utils/conversation_messages'
|
|
3
17
|
import { getMessagesQueryKey, getMessagesRequestArgs } from '../utils/request/get_messages'
|
|
4
|
-
import {
|
|
18
|
+
import { useApiClient } from './use_api_client'
|
|
19
|
+
import { throwResponseError } from './use_suspense_api'
|
|
20
|
+
|
|
21
|
+
type Args = { conversation_id: number; reply_root_id?: string | null }
|
|
22
|
+
|
|
23
|
+
export type ConversationMessagesOptions = Omit<
|
|
24
|
+
AnyUseSuspenseInfiniteQueryOptions,
|
|
25
|
+
| 'getNextPageParam'
|
|
26
|
+
| 'getPreviousPageParam'
|
|
27
|
+
| 'initialData'
|
|
28
|
+
| 'initialPageParam'
|
|
29
|
+
| 'queryFn'
|
|
30
|
+
| 'queryKey'
|
|
31
|
+
>
|
|
5
32
|
|
|
6
33
|
export const useConversationMessages = (
|
|
7
|
-
{ conversation_id, reply_root_id }:
|
|
8
|
-
opts?:
|
|
34
|
+
{ conversation_id, reply_root_id }: Args,
|
|
35
|
+
opts?: ConversationMessagesOptions
|
|
9
36
|
) => {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
37
|
+
const apiClient = useApiClient()
|
|
38
|
+
const { initialMessageId } = useConversationContext()
|
|
39
|
+
const anchored = !reply_root_id && !!initialMessageId
|
|
40
|
+
|
|
41
|
+
const requestArgs = getMessagesRequestArgs({ conversation_id, reply_root_id })
|
|
14
42
|
const queryKey = getMessagesQueryKey({ conversation_id, reply_root_id })
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
43
|
+
|
|
44
|
+
const fetchPage = (pageParam: MessagesPageParam) => {
|
|
45
|
+
const data = {
|
|
46
|
+
...requestArgs.data,
|
|
47
|
+
...(pageParam.where ? { where: pageParam.where } : {}),
|
|
48
|
+
...(pageParam.order ? { order: pageParam.order } : {}),
|
|
49
|
+
}
|
|
50
|
+
return apiClient.chat
|
|
51
|
+
.get<ApiCollection<MessageResource>>({ url: requestArgs.url, data })
|
|
52
|
+
.catch(throwResponseError)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const seedPageParams = anchored ? anchoredSeedPageParams(initialMessageId) : []
|
|
56
|
+
const seedQueries = useSuspenseQueries({
|
|
57
|
+
queries: seedPageParams.map((pageParam, index) => ({
|
|
58
|
+
queryKey: [...queryKey, 'seed', index],
|
|
59
|
+
queryFn: () => fetchPage(pageParam),
|
|
60
|
+
staleTime: Infinity,
|
|
61
|
+
gcTime: 0,
|
|
62
|
+
})),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const initialData: InfiniteData<ApiCollection<MessageResource>, MessagesPageParam> | undefined =
|
|
66
|
+
anchored
|
|
67
|
+
? {
|
|
68
|
+
pages: seedQueries.map(q => q.data),
|
|
69
|
+
pageParams: seedPageParams,
|
|
70
|
+
}
|
|
71
|
+
: undefined
|
|
72
|
+
|
|
73
|
+
const initialPageParam: MessagesPageParam = anchored ? seedPageParams[0] : {}
|
|
74
|
+
|
|
75
|
+
const {
|
|
76
|
+
data,
|
|
77
|
+
refetch,
|
|
78
|
+
isRefetching,
|
|
79
|
+
fetchNextPage,
|
|
80
|
+
hasNextPage,
|
|
81
|
+
fetchPreviousPage,
|
|
82
|
+
hasPreviousPage,
|
|
83
|
+
} = useSuspenseInfiniteQuery<
|
|
84
|
+
ApiCollection<MessageResource>,
|
|
85
|
+
Response,
|
|
86
|
+
InfiniteData<ApiCollection<MessageResource>, MessagesPageParam>,
|
|
87
|
+
typeof queryKey,
|
|
88
|
+
MessagesPageParam
|
|
89
|
+
>({
|
|
90
|
+
queryKey,
|
|
91
|
+
queryFn: ({ pageParam }) => fetchPage(pageParam),
|
|
92
|
+
initialPageParam,
|
|
93
|
+
initialData,
|
|
94
|
+
getNextPageParam: olderPageParam,
|
|
95
|
+
getPreviousPageParam: anchored ? newerPageParam : () => undefined,
|
|
96
|
+
...(opts || {}),
|
|
97
|
+
...(anchored ? { staleTime: Infinity, refetchOnMount: false } : {}),
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const messages = useMemo(() => sortAndFilterMessages(data.pages), [data.pages])
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
messages,
|
|
104
|
+
refetch,
|
|
105
|
+
isRefetching,
|
|
106
|
+
fetchOlderMessages: fetchNextPage,
|
|
107
|
+
hasMoreOlderMessages: hasNextPage,
|
|
108
|
+
fetchNewerMessages: fetchPreviousPage,
|
|
109
|
+
hasMoreNewerMessages: hasPreviousPage,
|
|
110
|
+
queryKey,
|
|
111
|
+
}
|
|
28
112
|
}
|
|
@@ -38,6 +38,7 @@ export const availableFeatures = {
|
|
|
38
38
|
message_reporting: 'ROLLOUT_MOBILE_message_reporting',
|
|
39
39
|
granular_notifications_ui: 'ROLLOUT_granular_notification_preferences_ui',
|
|
40
40
|
custom_conversation_avatars: 'ROLLOUT_custom_conversation_avatars',
|
|
41
|
+
jump_to_unread: 'ROLLOUT_jump_to_unread',
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
const stableEmptyFeatures: ApiCollection<FeatureResource> = {
|
|
@@ -90,7 +90,7 @@ export const useSuspensePaginator = <T extends ResourceObject>(
|
|
|
90
90
|
return { ...query, data, totalCount }
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
const throwResponseError = (error: unknown) => {
|
|
93
|
+
export const throwResponseError = (error: unknown) => {
|
|
94
94
|
if (error instanceof Response) {
|
|
95
95
|
throw new ResponseError(error as FailedResponse)
|
|
96
96
|
}
|
|
@@ -31,6 +31,7 @@ import { useConversation } from '../hooks/use_conversation'
|
|
|
31
31
|
import { useConversationJoltEvents } from '../hooks/use_conversation_jolt_events'
|
|
32
32
|
import { useConversationMessages } from '../hooks/use_conversation_messages'
|
|
33
33
|
import { useConversationMessagesJoltEvents } from '../hooks/use_conversation_messages_jolt_events'
|
|
34
|
+
import { useFeatures } from '../hooks/use_features'
|
|
34
35
|
import { useMarkLatestMessageRead } from '../hooks/use_mark_latest_message_read'
|
|
35
36
|
import {
|
|
36
37
|
normalizeAnalyticsMetadata,
|
|
@@ -50,6 +51,7 @@ export type ConversationRouteProps = {
|
|
|
50
51
|
chat_group_graph_id?: string
|
|
51
52
|
clear_input?: boolean
|
|
52
53
|
editing_message_id?: number | null
|
|
54
|
+
message_id?: string
|
|
53
55
|
title?: string
|
|
54
56
|
subtitle?: string
|
|
55
57
|
badge?: ConversationBadgeResource
|
|
@@ -60,19 +62,27 @@ export type ConversationRouteProps = {
|
|
|
60
62
|
export type ConversationScreenProps = StaticScreenProps<ConversationRouteProps>
|
|
61
63
|
|
|
62
64
|
export function ConversationScreen({ route }: ConversationScreenProps) {
|
|
63
|
-
const { conversation_id, reply_root_id } = route.params
|
|
65
|
+
const { conversation_id, message_id, reply_root_id } = route.params
|
|
64
66
|
|
|
65
67
|
const { data: conversation } = useConversation({ conversation_id })
|
|
68
|
+
const { featureEnabled } = useFeatures()
|
|
66
69
|
|
|
67
70
|
usePublishProductAnalyticsEvent('chat.mobile.conversations.show.opened', {
|
|
68
71
|
reply_root_id,
|
|
69
72
|
...normalizeAnalyticsMetadata(conversation),
|
|
70
73
|
})
|
|
71
74
|
|
|
75
|
+
const lastReadMessageSortKey = conversation.conversationMembership?.lastReadMessageSortKey ?? null
|
|
76
|
+
const jumpToUnreadAnchor = featureEnabled('jump_to_unread') ? lastReadMessageSortKey : null
|
|
77
|
+
const initialMessageId = message_id ?? jumpToUnreadAnchor
|
|
78
|
+
const initialMessageIdIsAnchor = !!initialMessageId && !message_id
|
|
79
|
+
|
|
72
80
|
return (
|
|
73
81
|
<ConversationContextProvider
|
|
74
82
|
conversationId={conversation_id}
|
|
75
83
|
currentPageReplyRootId={reply_root_id ?? null}
|
|
84
|
+
initialMessageId={initialMessageId}
|
|
85
|
+
initialMessageIdIsAnchor={initialMessageIdIsAnchor}
|
|
76
86
|
>
|
|
77
87
|
<ConversationScreenContent route={route} />
|
|
78
88
|
</ConversationContextProvider>
|
|
@@ -85,7 +95,7 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
85
95
|
const { conversation_id, editing_message_id, reply_root_id, reply_root_author_name } =
|
|
86
96
|
route.params
|
|
87
97
|
const { data: conversation } = useConversation(route.params)
|
|
88
|
-
const { messages, refetch, isRefetching,
|
|
98
|
+
const { messages, refetch, isRefetching, fetchOlderMessages } = useConversationMessages({
|
|
89
99
|
conversation_id,
|
|
90
100
|
reply_root_id,
|
|
91
101
|
})
|
|
@@ -204,7 +214,7 @@ function ConversationScreenContent({ route }: ConversationScreenProps) {
|
|
|
204
214
|
/>
|
|
205
215
|
)
|
|
206
216
|
}}
|
|
207
|
-
onEndReached={() =>
|
|
217
|
+
onEndReached={() => fetchOlderMessages()}
|
|
208
218
|
ListHeaderComponent={<View style={styles.listHeader} />}
|
|
209
219
|
/>
|
|
210
220
|
)}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ApiCollection, MessageResource } from '../../types'
|
|
2
|
+
import {
|
|
3
|
+
anchoredSeedPageParams,
|
|
4
|
+
newerPageParam,
|
|
5
|
+
olderPageParam,
|
|
6
|
+
sortAndFilterMessages,
|
|
7
|
+
} from '../conversation_messages'
|
|
8
|
+
|
|
9
|
+
const message = (id: string, overrides: Partial<MessageResource> = {}): MessageResource =>
|
|
10
|
+
({
|
|
11
|
+
id,
|
|
12
|
+
type: 'Message',
|
|
13
|
+
text: `msg ${id}`,
|
|
14
|
+
attachments: [],
|
|
15
|
+
deletedAt: null,
|
|
16
|
+
replyRootId: null,
|
|
17
|
+
...overrides,
|
|
18
|
+
}) as MessageResource
|
|
19
|
+
|
|
20
|
+
const page = (
|
|
21
|
+
data: MessageResource[],
|
|
22
|
+
next?: { idLt?: string; idGt?: string }
|
|
23
|
+
): ApiCollection<MessageResource> => ({
|
|
24
|
+
data,
|
|
25
|
+
links: {},
|
|
26
|
+
meta: { count: data.length, totalCount: data.length, next },
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('anchoredSeedPageParams', () => {
|
|
30
|
+
it('returns one ascending after-page and one descending before-page', () => {
|
|
31
|
+
expect(anchoredSeedPageParams('01ABC')).toEqual([
|
|
32
|
+
{ where: { id_gte: '01ABC' }, order: 'asc' },
|
|
33
|
+
{ where: { id_lt: '01ABC' }, order: 'desc' },
|
|
34
|
+
])
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('olderPageParam', () => {
|
|
39
|
+
it('returns id_lt cursor when meta.next.idLt is present', () => {
|
|
40
|
+
expect(olderPageParam(page([], { idLt: '01XYZ' }))).toEqual({
|
|
41
|
+
where: { id_lt: '01XYZ' },
|
|
42
|
+
order: 'desc',
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('returns undefined when meta.next.idLt is missing', () => {
|
|
47
|
+
expect(olderPageParam(page([], {}))).toBeUndefined()
|
|
48
|
+
expect(olderPageParam(page([], { idGt: '01XYZ' }))).toBeUndefined()
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('newerPageParam', () => {
|
|
53
|
+
it('returns id_gt cursor when meta.next.idGt is present', () => {
|
|
54
|
+
expect(newerPageParam(page([], { idGt: '01XYZ' }))).toEqual({
|
|
55
|
+
where: { id_gt: '01XYZ' },
|
|
56
|
+
order: 'asc',
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('returns undefined when meta.next.idGt is missing', () => {
|
|
61
|
+
expect(newerPageParam(page([], {}))).toBeUndefined()
|
|
62
|
+
expect(newerPageParam(page([], { idLt: '01XYZ' }))).toBeUndefined()
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('sortAndFilterMessages', () => {
|
|
67
|
+
it('flattens pages and sorts descending by id (newest first)', () => {
|
|
68
|
+
const pages = [page([message('01B'), message('01A')]), page([message('01D'), message('01C')])]
|
|
69
|
+
|
|
70
|
+
expect(sortAndFilterMessages(pages).map(m => m.id)).toEqual(['01D', '01C', '01B', '01A'])
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('drops empty messages (no text, no attachments)', () => {
|
|
74
|
+
const pages = [page([message('01A'), message('01B', { text: '' })])]
|
|
75
|
+
|
|
76
|
+
expect(sortAndFilterMessages(pages).map(m => m.id)).toEqual(['01A'])
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('drops deleted messages outside reply threads', () => {
|
|
80
|
+
const pages = [page([message('01A'), message('01B', { deletedAt: '2026-01-01T00:00:00Z' })])]
|
|
81
|
+
|
|
82
|
+
expect(sortAndFilterMessages(pages).map(m => m.id)).toEqual(['01A'])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('keeps deleted reply-thread messages so threads do not break', () => {
|
|
86
|
+
const pages = [
|
|
87
|
+
page([
|
|
88
|
+
message('01A'),
|
|
89
|
+
message('01B', { deletedAt: '2026-01-01T00:00:00Z', replyRootId: '01A' }),
|
|
90
|
+
]),
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
expect(sortAndFilterMessages(pages).map(m => m.id)).toEqual(['01B', '01A'])
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('keeps messages with attachments even when text is empty', () => {
|
|
97
|
+
const pages = [
|
|
98
|
+
page([
|
|
99
|
+
message('01A', { text: '', attachments: [{ id: '1' }] as MessageResource['attachments'] }),
|
|
100
|
+
]),
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
expect(sortAndFilterMessages(pages).map(m => m.id)).toEqual(['01A'])
|
|
104
|
+
})
|
|
105
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ApiCollection, MessageResource } from '../types'
|
|
2
|
+
import { RequestData } from './client'
|
|
3
|
+
|
|
4
|
+
export type MessagesPageParam = Partial<RequestData> & {
|
|
5
|
+
order?: 'asc' | 'desc'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const anchoredSeedPageParams = (anchor: string): MessagesPageParam[] => [
|
|
9
|
+
{ where: { id_gte: anchor }, order: 'asc' },
|
|
10
|
+
{ where: { id_lt: anchor }, order: 'desc' },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
export const olderPageParam = (
|
|
14
|
+
page: ApiCollection<MessageResource>
|
|
15
|
+
): MessagesPageParam | undefined => {
|
|
16
|
+
const idLt = page.meta?.next?.idLt
|
|
17
|
+
if (!idLt) return undefined
|
|
18
|
+
return { where: { id_lt: idLt }, order: 'desc' }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const newerPageParam = (
|
|
22
|
+
page: ApiCollection<MessageResource>
|
|
23
|
+
): MessagesPageParam | undefined => {
|
|
24
|
+
const idGt = page.meta?.next?.idGt
|
|
25
|
+
if (!idGt) return undefined
|
|
26
|
+
return { where: { id_gt: idGt }, order: 'asc' }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const sortAndFilterMessages = (pages: ApiCollection<MessageResource>[]): MessageResource[] =>
|
|
30
|
+
pages
|
|
31
|
+
.flatMap(page => page.data)
|
|
32
|
+
.filter(
|
|
33
|
+
message =>
|
|
34
|
+
(!message.deletedAt || message.replyRootId) &&
|
|
35
|
+
(message.attachments?.length || message.text?.length)
|
|
36
|
+
)
|
|
37
|
+
.sort((a, b) => -a.id.localeCompare(b.id))
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { renderHook } from '@testing-library/react-hooks'
|
|
2
|
-
import { useConversationMessages } from '../../hooks/use_conversation_messages'
|
|
3
|
-
import * as useSuspenseApi from '../../hooks/use_suspense_api'
|
|
4
|
-
|
|
5
|
-
const mockMessages = [
|
|
6
|
-
{
|
|
7
|
-
id: '1',
|
|
8
|
-
text: 'Hello',
|
|
9
|
-
deletedAt: null,
|
|
10
|
-
attachments: [],
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
id: '2',
|
|
14
|
-
text: '',
|
|
15
|
-
deletedAt: null,
|
|
16
|
-
attachments: [{ id: 'a1' }],
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
id: '3',
|
|
20
|
-
text: '',
|
|
21
|
-
deletedAt: '2024-01-01',
|
|
22
|
-
attachments: [],
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
id: '4',
|
|
26
|
-
text: '',
|
|
27
|
-
deletedAt: null,
|
|
28
|
-
attachments: [],
|
|
29
|
-
},
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
describe('useConversationMessages', () => {
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
jest.spyOn(useSuspenseApi, 'useSuspensePaginator').mockReturnValue({
|
|
35
|
-
data: mockMessages,
|
|
36
|
-
refetch: jest.fn(),
|
|
37
|
-
isRefetching: false,
|
|
38
|
-
fetchNextPage: jest.fn(),
|
|
39
|
-
} as any)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
afterEach(() => {
|
|
43
|
-
jest.restoreAllMocks()
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('filters out empty or deleted messages and sorts by id descending', () => {
|
|
47
|
-
const { result } = renderHook(() => useConversationMessages({ conversation_id: 123 }))
|
|
48
|
-
expect(result.current.messages).toEqual([
|
|
49
|
-
mockMessages[1], // id: '2'
|
|
50
|
-
mockMessages[0], // id: '1'
|
|
51
|
-
])
|
|
52
|
-
// id: '3' is deleted and filtered out
|
|
53
|
-
// id: '4' is filtered out because it has no text or attachments
|
|
54
|
-
})
|
|
55
|
-
})
|