@ttt-productions/chat-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/firestore/queries.d.ts +5 -0
  2. package/dist/firestore/queries.d.ts.map +1 -0
  3. package/dist/firestore/queries.js +11 -0
  4. package/dist/firestore/queries.js.map +1 -0
  5. package/dist/hooks/useChatMessages.d.ts +11 -0
  6. package/dist/hooks/useChatMessages.d.ts.map +1 -0
  7. package/dist/hooks/useChatMessages.js +100 -0
  8. package/dist/hooks/useChatMessages.js.map +1 -0
  9. package/dist/hooks/useChatThreadAccess.d.ts +6 -0
  10. package/dist/hooks/useChatThreadAccess.d.ts.map +1 -0
  11. package/dist/hooks/useChatThreadAccess.js +9 -0
  12. package/dist/hooks/useChatThreadAccess.js.map +1 -0
  13. package/dist/index.d.ts +12 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +12 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/templates/AdminThreadChat.d.ts +9 -0
  18. package/dist/templates/AdminThreadChat.d.ts.map +1 -0
  19. package/dist/templates/AdminThreadChat.js +8 -0
  20. package/dist/templates/AdminThreadChat.js.map +1 -0
  21. package/dist/templates/ChannelChat.d.ts +11 -0
  22. package/dist/templates/ChannelChat.d.ts.map +1 -0
  23. package/dist/templates/ChannelChat.js +8 -0
  24. package/dist/templates/ChannelChat.js.map +1 -0
  25. package/dist/templates/InviteChat.d.ts +9 -0
  26. package/dist/templates/InviteChat.d.ts.map +1 -0
  27. package/dist/templates/InviteChat.js +8 -0
  28. package/dist/templates/InviteChat.js.map +1 -0
  29. package/dist/types.d.ts +44 -0
  30. package/dist/types.d.ts.map +1 -0
  31. package/dist/types.js +2 -0
  32. package/dist/types.js.map +1 -0
  33. package/dist/ui/ChatShell.d.ts +13 -0
  34. package/dist/ui/ChatShell.d.ts.map +1 -0
  35. package/dist/ui/ChatShell.js +21 -0
  36. package/dist/ui/ChatShell.js.map +1 -0
  37. package/dist/ui/Composer.d.ts +7 -0
  38. package/dist/ui/Composer.d.ts.map +1 -0
  39. package/dist/ui/Composer.js +29 -0
  40. package/dist/ui/Composer.js.map +1 -0
  41. package/dist/ui/MessageItemDefault.d.ts +8 -0
  42. package/dist/ui/MessageItemDefault.d.ts.map +1 -0
  43. package/dist/ui/MessageItemDefault.js +10 -0
  44. package/dist/ui/MessageItemDefault.js.map +1 -0
  45. package/dist/ui/MessageList.d.ts +16 -0
  46. package/dist/ui/MessageList.d.ts.map +1 -0
  47. package/dist/ui/MessageList.js +67 -0
  48. package/dist/ui/MessageList.js.map +1 -0
  49. package/dist/ui/menus.d.ts +12 -0
  50. package/dist/ui/menus.d.ts.map +1 -0
  51. package/dist/ui/menus.js +12 -0
  52. package/dist/ui/menus.js.map +1 -0
  53. package/package.json +43 -0
@@ -0,0 +1,5 @@
1
+ import type { Firestore, Query, DocumentData } from "firebase/firestore";
2
+ export declare function threadDocPath(chatCollection: string, threadId: string): readonly [string, string];
3
+ export declare function messagesColPath(chatCollection: string, threadId: string): readonly [string, string, "messages"];
4
+ export declare function newestWindowQuery(db: Firestore, chatCollection: string, threadId: string, createdAtField: string, pageSize: number): Query<DocumentData>;
5
+ //# sourceMappingURL=queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/firestore/queries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGzE,wBAAgB,aAAa,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,6BAErE;AAED,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,yCAEvE;AAED,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,SAAS,EACb,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,GACf,KAAK,CAAC,YAAY,CAAC,CAMrB"}
@@ -0,0 +1,11 @@
1
+ import { collection, query, orderBy, limit } from "firebase/firestore";
2
+ export function threadDocPath(chatCollection, threadId) {
3
+ return [chatCollection, threadId];
4
+ }
5
+ export function messagesColPath(chatCollection, threadId) {
6
+ return [chatCollection, threadId, "messages"];
7
+ }
8
+ export function newestWindowQuery(db, chatCollection, threadId, createdAtField, pageSize) {
9
+ return query(collection(db, ...messagesColPath(chatCollection, threadId)), orderBy(createdAtField, "desc"), limit(pageSize));
10
+ }
11
+ //# sourceMappingURL=queries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/firestore/queries.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAEvE,MAAM,UAAU,aAAa,CAAC,cAAsB,EAAE,QAAgB;IACpE,OAAO,CAAC,cAAc,EAAE,QAAQ,CAAU,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,cAAsB,EAAE,QAAgB;IACtE,OAAO,CAAC,cAAc,EAAE,QAAQ,EAAE,UAAU,CAAU,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,EAAa,EACb,cAAsB,EACtB,QAAgB,EAChB,cAAsB,EACtB,QAAgB;IAEhB,OAAO,KAAK,CACV,UAAU,CAAC,EAAE,EAAE,GAAG,eAAe,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,EAC5D,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,EAC/B,KAAK,CAAC,QAAQ,CAAC,CAChB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { ChatCoreConfig, ChatMessageV1 } from "../types";
2
+ export type UseChatMessagesResult = {
3
+ allowed: boolean;
4
+ isInitialLoading: boolean;
5
+ messages: ChatMessageV1[];
6
+ fetchOlder: () => Promise<void>;
7
+ hasOlder: boolean;
8
+ isFetchingOlder: boolean;
9
+ };
10
+ export declare function useChatMessages(config: ChatCoreConfig): UseChatMessagesResult;
11
+ //# sourceMappingURL=useChatMessages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useChatMessages.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatMessages.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAU9D,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAoCF,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,qBAAqB,CA4G7E"}
@@ -0,0 +1,100 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ import { collection, getDocs, onSnapshot, orderBy, query, startAfter, limit, } from "firebase/firestore";
4
+ import { useInfiniteQuery } from "@tanstack/react-query";
5
+ import { canAccessThread } from "./useChatThreadAccess";
6
+ import { messagesColPath, newestWindowQuery } from "../firestore/queries";
7
+ function mapMsg(doc, threadId) {
8
+ const d = doc.data();
9
+ return {
10
+ messageId: doc.id,
11
+ threadId,
12
+ createdAt: typeof d.createdAt === "number"
13
+ ? d.createdAt
14
+ : d.createdAt?.toMillis?.() ?? Date.now(),
15
+ senderId: d.senderId,
16
+ senderUsername: d.senderUsername,
17
+ text: d.message ?? d.text ?? "",
18
+ type: d.type,
19
+ meta: d.meta,
20
+ };
21
+ }
22
+ function dedupeAndSortAsc(all) {
23
+ const m = new Map();
24
+ for (const x of all)
25
+ m.set(x.messageId, x);
26
+ return Array.from(m.values()).sort((a, b) => a.createdAt - b.createdAt);
27
+ }
28
+ function messagesCol(db, chatCollection, threadId) {
29
+ return collection(db, ...messagesColPath(chatCollection, threadId));
30
+ }
31
+ export function useChatMessages(config) {
32
+ const { db, chatCollection, threadId, pageSize = 20, currentUserId, isAdmin, threadAllowedUserIds, createdAtField = "createdAt", } = config;
33
+ const allowed = React.useMemo(() => canAccessThread({
34
+ isAdmin,
35
+ currentUserId,
36
+ allowedUserIds: threadAllowedUserIds,
37
+ }), [isAdmin, currentUserId, threadAllowedUserIds]);
38
+ const [newest, setNewest] = React.useState({
39
+ ready: false,
40
+ newestDesc: [],
41
+ oldestDocInWindow: null,
42
+ });
43
+ // realtime newest window only
44
+ React.useEffect(() => {
45
+ if (!allowed)
46
+ return;
47
+ const q = newestWindowQuery(db, chatCollection, threadId, createdAtField, pageSize);
48
+ const unsub = onSnapshot(q, (snap) => {
49
+ const docs = snap.docs;
50
+ const mapped = docs.map((d) => mapMsg(d, threadId)); // desc
51
+ setNewest({
52
+ ready: true,
53
+ newestDesc: mapped,
54
+ oldestDocInWindow: docs[docs.length - 1] ?? null,
55
+ });
56
+ });
57
+ return () => unsub();
58
+ }, [allowed, db, chatCollection, threadId, createdAtField, pageSize]);
59
+ // older pagination (beyond newest window)
60
+ const older = useInfiniteQuery({
61
+ queryKey: ["chat-core", "older", chatCollection, threadId, pageSize],
62
+ enabled: allowed && newest.ready,
63
+ initialPageParam: undefined,
64
+ queryFn: async ({ pageParam }) => {
65
+ const baseCol = messagesCol(db, chatCollection, threadId);
66
+ const cursor = pageParam ?? (newest.oldestDocInWindow ?? undefined);
67
+ if (!cursor) {
68
+ return {
69
+ itemsDesc: [],
70
+ nextCursor: undefined,
71
+ };
72
+ }
73
+ const q = query(baseCol, orderBy(createdAtField, "desc"), startAfter(cursor), limit(pageSize));
74
+ const snap = await getDocs(q);
75
+ const docs = snap.docs;
76
+ const itemsDesc = docs.map((d) => mapMsg(d, threadId)); // desc
77
+ const nextCursor = docs.length === pageSize ? docs[docs.length - 1] : undefined;
78
+ return { itemsDesc, nextCursor };
79
+ },
80
+ getNextPageParam: (last) => last.nextCursor,
81
+ });
82
+ const fetchOlder = React.useCallback(async () => {
83
+ await older.fetchNextPage();
84
+ }, [older.fetchNextPage]);
85
+ const messages = React.useMemo(() => {
86
+ if (!allowed)
87
+ return [];
88
+ const olderDesc = (older.data?.pages ?? []).flatMap((p) => p.itemsDesc);
89
+ return dedupeAndSortAsc([...olderDesc, ...newest.newestDesc]);
90
+ }, [allowed, older.data, newest.newestDesc]);
91
+ return {
92
+ allowed,
93
+ isInitialLoading: allowed ? !newest.ready : false,
94
+ messages,
95
+ fetchOlder,
96
+ hasOlder: Boolean(older.hasNextPage),
97
+ isFetchingOlder: older.isFetchingNextPage,
98
+ };
99
+ }
100
+ //# sourceMappingURL=useChatMessages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useChatMessages.js","sourceRoot":"","sources":["../../src/hooks/useChatMessages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare function canAccessThread(args: {
2
+ isAdmin: boolean;
3
+ currentUserId: string;
4
+ allowedUserIds?: string[];
5
+ }): boolean;
6
+ //# sourceMappingURL=useChatThreadAccess.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useChatThreadAccess.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatThreadAccess.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,CAAC,IAAI,EAAE;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,WAKA"}
@@ -0,0 +1,9 @@
1
+ export function canAccessThread(args) {
2
+ const { isAdmin, currentUserId, allowedUserIds } = args;
3
+ if (isAdmin)
4
+ return true;
5
+ if (!allowedUserIds)
6
+ return false;
7
+ return allowedUserIds.includes(currentUserId);
8
+ }
9
+ //# sourceMappingURL=useChatThreadAccess.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useChatThreadAccess.js","sourceRoot":"","sources":["../../src/hooks/useChatThreadAccess.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,eAAe,CAAC,IAI7B;IACC,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IACxD,IAAI,OAAO;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,CAAC,cAAc;QAAE,OAAO,KAAK,CAAC;IAClC,OAAO,cAAc,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;AAChD,CAAC"}
@@ -0,0 +1,12 @@
1
+ export * from "./types";
2
+ export * from "./hooks/useChatMessages";
3
+ export * from "./hooks/useChatThreadAccess";
4
+ export * from "./ui/ChatShell";
5
+ export * from "./ui/MessageList";
6
+ export * from "./ui/Composer";
7
+ export * from "./ui/MessageItemDefault";
8
+ export * from "./ui/menus";
9
+ export * from "./templates/ChannelChat";
10
+ export * from "./templates/AdminThreadChat";
11
+ export * from "./templates/InviteChat";
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AAExB,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAE5C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC;AAE3B,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ export * from "./types";
2
+ export * from "./hooks/useChatMessages";
3
+ export * from "./hooks/useChatThreadAccess";
4
+ export * from "./ui/ChatShell";
5
+ export * from "./ui/MessageList";
6
+ export * from "./ui/Composer";
7
+ export * from "./ui/MessageItemDefault";
8
+ export * from "./ui/menus";
9
+ export * from "./templates/ChannelChat";
10
+ export * from "./templates/AdminThreadChat";
11
+ export * from "./templates/InviteChat";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AAExB,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAE5C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC;AAE3B,cAAc,yBAAyB,CAAC;AACxC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wBAAwB,CAAC"}
@@ -0,0 +1,9 @@
1
+ import * as React from "react";
2
+ import type { ChatCoreConfig, ModerationHandlers } from "../types";
3
+ export declare function AdminThreadChat(props: {
4
+ config: ChatCoreConfig;
5
+ onSend: (text: string) => void | Promise<void>;
6
+ renderThreadActions?: () => React.ReactNode;
7
+ handlers?: ModerationHandlers;
8
+ }): import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=AdminThreadChat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AdminThreadChat.d.ts","sourceRoot":"","sources":["../../src/templates/AdminThreadChat.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAGnE,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,mBAAmB,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IAC5C,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,2CAWA"}
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { ChatShell } from "../ui/ChatShell";
4
+ export function AdminThreadChat(props) {
5
+ const { config, onSend, renderThreadActions, handlers } = props;
6
+ return (_jsx(ChatShell, { config: config, header: _jsx("div", { className: "font-medium", children: "Support" }), renderThreadActions: renderThreadActions, onSend: onSend, handlers: handlers }));
7
+ }
8
+ //# sourceMappingURL=AdminThreadChat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AdminThreadChat.js","sourceRoot":"","sources":["../../src/templates/AdminThreadChat.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAIb,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,UAAU,eAAe,CAAC,KAK/B;IACC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAChE,OAAO,CACL,KAAC,SAAS,IACR,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,cAAK,SAAS,EAAC,aAAa,wBAAc,EAClD,mBAAmB,EAAE,mBAAmB,EACxC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,GAClB,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ import * as React from "react";
2
+ import type { ChatCoreConfig, ModerationHandlers, MessageRendererRegistry } from "../types";
3
+ export declare function ChannelChat(props: {
4
+ config: ChatCoreConfig;
5
+ title?: string;
6
+ onSend: (text: string) => void | Promise<void>;
7
+ renderMessage?: (m: any) => React.ReactNode;
8
+ messageRenderers?: MessageRendererRegistry;
9
+ handlers?: ModerationHandlers;
10
+ }): import("react/jsx-runtime").JSX.Element;
11
+ //# sourceMappingURL=ChannelChat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChannelChat.d.ts","sourceRoot":"","sources":["../../src/templates/ChannelChat.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAG5F,wBAAgB,WAAW,CAAC,KAAK,EAAE;IACjC,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,KAAK,CAAC,SAAS,CAAC;IAC5C,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAC3C,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,2CAYA"}
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { ChatShell } from "../ui/ChatShell";
4
+ export function ChannelChat(props) {
5
+ const { config, title, onSend, renderMessage, messageRenderers, handlers } = props;
6
+ return (_jsx(ChatShell, { config: config, header: _jsx("div", { className: "font-medium", children: title ?? "Channel" }), onSend: onSend, renderMessage: renderMessage, messageRenderers: messageRenderers, handlers: handlers }));
7
+ }
8
+ //# sourceMappingURL=ChannelChat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChannelChat.js","sourceRoot":"","sources":["../../src/templates/ChannelChat.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAIb,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,UAAU,WAAW,CAAC,KAO3B;IACC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,gBAAgB,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACnF,OAAO,CACL,KAAC,SAAS,IACR,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,cAAK,SAAS,EAAC,aAAa,YAAE,KAAK,IAAI,SAAS,GAAO,EAC/D,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,aAAa,EAC5B,gBAAgB,EAAE,gBAAgB,EAClC,QAAQ,EAAE,QAAQ,GAClB,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ import * as React from "react";
2
+ import type { ChatCoreConfig, ModerationHandlers } from "../types";
3
+ export declare function InviteChat(props: {
4
+ config: ChatCoreConfig;
5
+ onSend: (text: string) => void | Promise<void>;
6
+ renderThreadActions?: () => React.ReactNode;
7
+ handlers?: ModerationHandlers;
8
+ }): import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=InviteChat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InviteChat.d.ts","sourceRoot":"","sources":["../../src/templates/InviteChat.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAGnE,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAChC,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,mBAAmB,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IAC5C,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,2CAWA"}
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { ChatShell } from "../ui/ChatShell";
4
+ export function InviteChat(props) {
5
+ const { config, onSend, renderThreadActions, handlers } = props;
6
+ return (_jsx(ChatShell, { config: config, header: _jsx("div", { className: "font-medium", children: "Invite" }), renderThreadActions: renderThreadActions, onSend: onSend, handlers: handlers }));
7
+ }
8
+ //# sourceMappingURL=InviteChat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InviteChat.js","sourceRoot":"","sources":["../../src/templates/InviteChat.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAIb,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,UAAU,UAAU,CAAC,KAK1B;IACC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAChE,OAAO,CACL,KAAC,SAAS,IACR,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,cAAK,SAAS,EAAC,aAAa,uBAAa,EACjD,mBAAmB,EAAE,mBAAmB,EACxC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,GAClB,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { Firestore } from "firebase/firestore";
2
+ export type ChatId = string;
3
+ export type ChatThreadV1 = {
4
+ allowedUserIds: string[];
5
+ participantUserIds?: string[];
6
+ createdAt: number;
7
+ lastMessageAt: number;
8
+ status?: string;
9
+ meta?: Record<string, unknown>;
10
+ };
11
+ export type ChatMessageV1 = {
12
+ messageId: string;
13
+ threadId: string;
14
+ createdAt: number;
15
+ senderId: string;
16
+ senderUsername?: string;
17
+ text?: string;
18
+ type?: string;
19
+ meta?: Record<string, unknown>;
20
+ };
21
+ export type ChatCollectionConfig = {
22
+ /** top-level collection name, e.g. "projectChats" | "adminThreads" | "inviteChats" */
23
+ chatCollection: string;
24
+ };
25
+ export type ChatCoreConfig = ChatCollectionConfig & {
26
+ db: Firestore;
27
+ pageSize?: number;
28
+ threadId: string;
29
+ currentUserId: string;
30
+ isAdmin: boolean;
31
+ /** If provided, used to gate access; otherwise only isAdmin applies */
32
+ threadAllowedUserIds?: string[];
33
+ /** Optional: tell chat-core which field to order by; must be indexed */
34
+ createdAtField?: string;
35
+ };
36
+ export type ModerationHandlers = {
37
+ onReportMessage?: (messageId: string, reason?: string) => void | Promise<void>;
38
+ onReportThread?: (threadId: string, reason?: string) => void | Promise<void>;
39
+ onDeleteMessage?: (messageId: string) => void | Promise<void>;
40
+ onDeleteThread?: (threadId: string) => void | Promise<void>;
41
+ };
42
+ export type MessageRenderer = (m: ChatMessageV1) => React.ReactNode;
43
+ export type MessageRendererRegistry = Record<string, MessageRenderer>;
44
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC;AAE5B,MAAM,MAAM,YAAY,GAAG;IACzB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,sFAAsF;IACtF,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,oBAAoB,GAAG;IAClD,EAAE,EAAE,SAAS,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IAEjB,uEAAuE;IACvE,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAEhC,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,EAAE,aAAa,KAAK,KAAK,CAAC,SAAS,CAAC;AAEpE,MAAM,MAAM,uBAAuB,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ import * as React from "react";
2
+ import type { ChatCoreConfig, MessageRendererRegistry, ModerationHandlers } from "../types";
3
+ export declare function ChatShell(props: {
4
+ config: ChatCoreConfig;
5
+ header?: React.ReactNode;
6
+ renderThreadActions?: () => React.ReactNode;
7
+ onSend: (text: string) => void | Promise<void>;
8
+ renderMessage?: (m: any) => React.ReactNode;
9
+ messageRenderers?: MessageRendererRegistry;
10
+ composerAutoFocus?: boolean;
11
+ handlers?: ModerationHandlers;
12
+ }): import("react/jsx-runtime").JSX.Element;
13
+ //# sourceMappingURL=ChatShell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatShell.d.ts","sourceRoot":"","sources":["../../src/ui/ChatShell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAO5F,wBAAgB,SAAS,CAAC,KAAK,EAAE;IAC/B,MAAM,EAAE,cAAc,CAAC;IAEvB,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,KAAK,CAAC,SAAS,CAAC;IAE5C,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,KAAK,CAAC,SAAS,CAAC;IAC5C,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAE3C,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAE5B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,2CAwEA"}
@@ -0,0 +1,21 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Card, CardHeader, CardContent, CardFooter, Skeleton } from "@ttt-productions/ui-core";
5
+ import { useChatMessages } from "../hooks/useChatMessages";
6
+ import { MessageList } from "./MessageList";
7
+ import { Composer } from "./Composer";
8
+ import { ThreadActions } from "./menus";
9
+ export function ChatShell(props) {
10
+ const { config, header, renderThreadActions, onSend, renderMessage, messageRenderers, composerAutoFocus = false, handlers } = props;
11
+ const { allowed, isInitialLoading, messages, fetchOlder, hasOlder, isFetchingOlder } = useChatMessages(config);
12
+ const [showScrollToBottom, setShowScrollToBottom] = React.useState(false);
13
+ if (!allowed) {
14
+ return _jsx("div", { className: "p-4 text-sm opacity-70", children: "You don\u2019t have access to this thread." });
15
+ }
16
+ if (isInitialLoading) {
17
+ return (_jsxs(Card, { className: "w-full", children: [_jsxs(CardHeader, { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-5 w-40" }), _jsx(Skeleton, { className: "h-3 w-56" })] }), _jsx(CardContent, { children: _jsx(Skeleton, { className: "h-[240px] w-full" }) }), _jsx(CardFooter, { children: _jsx(Skeleton, { className: "h-10 w-full" }) })] }));
18
+ }
19
+ return (_jsxs(Card, { className: "w-full", children: [(header || renderThreadActions || handlers) && (_jsxs(CardHeader, { className: "flex flex-row items-start justify-between gap-4 space-y-0", children: [_jsx("div", { children: header }), _jsxs("div", { className: "flex flex-col items-end gap-2", children: [renderThreadActions?.(), _jsx(ThreadActions, { threadId: config.threadId, isAdmin: config.isAdmin, handlers: handlers })] })] })), _jsx(CardContent, { className: "p-0", children: _jsx(MessageList, { messages: messages, currentUserId: config.currentUserId, isAdmin: config.isAdmin, isFetchingOlder: isFetchingOlder, hasOlder: hasOlder, onLoadOlder: () => fetchOlder(), renderMessage: renderMessage, messageRenderers: messageRenderers, showScrollToBottom: showScrollToBottom, onScrollToBottom: () => setShowScrollToBottom(false), handlers: handlers }) }), _jsx(CardFooter, { className: "border-t", children: _jsx("div", { className: "w-full", children: _jsx(Composer, { disabled: false, autoFocus: composerAutoFocus, onSend: onSend }) }) })] }));
20
+ }
21
+ //# sourceMappingURL=ChatShell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ChatShell.js","sourceRoot":"","sources":["../../src/ui/ChatShell.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAC/F,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,MAAM,UAAU,SAAS,CAAC,KAczB;IACC,MAAM,EACJ,MAAM,EACN,MAAM,EACN,mBAAmB,EACnB,MAAM,EACN,aAAa,EACb,gBAAgB,EAChB,iBAAiB,GAAG,KAAK,EACzB,QAAQ,EACT,GAAG,KAAK,CAAC;IAEV,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAE/G,MAAM,CAAC,kBAAkB,EAAE,qBAAqB,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE1E,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,cAAK,SAAS,EAAC,wBAAwB,2DAA4C,CAAC;IAC7F,CAAC;IAED,IAAI,gBAAgB,EAAE,CAAC;QACrB,OAAO,CACL,MAAC,IAAI,IAAC,SAAS,EAAC,QAAQ,aACtB,MAAC,UAAU,IAAC,SAAS,EAAC,WAAW,aAC/B,KAAC,QAAQ,IAAC,SAAS,EAAC,UAAU,GAAG,EACjC,KAAC,QAAQ,IAAC,SAAS,EAAC,UAAU,GAAG,IACtB,EACb,KAAC,WAAW,cACV,KAAC,QAAQ,IAAC,SAAS,EAAC,kBAAkB,GAAG,GAC7B,EACd,KAAC,UAAU,cACT,KAAC,QAAQ,IAAC,SAAS,EAAC,aAAa,GAAG,GACzB,IACR,CACR,CAAC;IACJ,CAAC;IAED,OAAO,CACL,MAAC,IAAI,IAAC,SAAS,EAAC,QAAQ,aACrB,CAAC,MAAM,IAAI,mBAAmB,IAAI,QAAQ,CAAC,IAAI,CAC9C,MAAC,UAAU,IAAC,SAAS,EAAC,2DAA2D,aAC/E,wBAAM,MAAM,GAAO,EACnB,eAAK,SAAS,EAAC,+BAA+B,aAC3C,mBAAmB,EAAE,EAAE,EACxB,KAAC,aAAa,IAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAI,IACrF,IACK,CACd,EAED,KAAC,WAAW,IAAC,SAAS,EAAC,KAAK,YAC1B,KAAC,WAAW,IACV,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,MAAM,CAAC,aAAa,EACnC,OAAO,EAAE,MAAM,CAAC,OAAO,EACvB,eAAe,EAAE,eAAe,EAChC,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE,EAC/B,aAAa,EAAE,aAAa,EAC5B,gBAAgB,EAAE,gBAAgB,EAClC,kBAAkB,EAAE,kBAAkB,EACtC,gBAAgB,EAAE,GAAG,EAAE,CAAC,qBAAqB,CAAC,KAAK,CAAC,EACpD,QAAQ,EAAE,QAAQ,GAClB,GACU,EAEd,KAAC,UAAU,IAAC,SAAS,EAAC,UAAU,YAC9B,cAAK,SAAS,EAAC,QAAQ,YACrB,KAAC,QAAQ,IAAC,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,GAAI,GACvE,GACK,IACR,CACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare function Composer(props: {
2
+ disabled?: boolean;
3
+ autoFocus?: boolean;
4
+ placeholder?: string;
5
+ onSend: (text: string) => void | Promise<void>;
6
+ }): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=Composer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Composer.d.ts","sourceRoot":"","sources":["../../src/ui/Composer.tsx"],"names":[],"mappings":"AAKA,wBAAgB,QAAQ,CAAC,KAAK,EAAE;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD,2CA6CA"}
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Button, Textarea, cn } from "@ttt-productions/ui-core";
5
+ export function Composer(props) {
6
+ const { disabled, autoFocus = false, placeholder = "Type a message...", onSend } = props;
7
+ const [text, setText] = React.useState("");
8
+ const ref = React.useRef(null);
9
+ // focus stability: never steal focus unless explicitly enabled
10
+ React.useEffect(() => {
11
+ if (!autoFocus)
12
+ return;
13
+ ref.current?.focus();
14
+ }, [autoFocus]);
15
+ const send = async () => {
16
+ const v = text.trim();
17
+ if (!v)
18
+ return;
19
+ setText("");
20
+ await onSend(v);
21
+ };
22
+ return (_jsxs("div", { className: "flex items-end gap-2", children: [_jsx(Textarea, { ref: ref, value: text, onChange: (e) => setText(e.target.value), placeholder: placeholder, disabled: disabled, rows: 1, className: cn("min-h-[40px] resize-none"), onKeyDown: (e) => {
23
+ if (e.key === "Enter" && !e.shiftKey) {
24
+ e.preventDefault();
25
+ send();
26
+ }
27
+ } }), _jsx(Button, { type: "button", variant: "default", disabled: disabled || !text.trim(), onClick: send, children: "Send" })] }));
28
+ }
29
+ //# sourceMappingURL=Composer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Composer.js","sourceRoot":"","sources":["../../src/ui/Composer.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAEhE,MAAM,UAAU,QAAQ,CAAC,KAKxB;IACC,MAAM,EAAE,QAAQ,EAAE,SAAS,GAAG,KAAK,EAAE,WAAW,GAAG,mBAAmB,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IACzF,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAsB,IAAI,CAAC,CAAC;IAEpD,+DAA+D;IAC/D,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,CAAC,SAAS;YAAE,OAAO;QACvB,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;IACvB,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhB,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;QACtB,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,OAAO,CAAC,EAAE,CAAC,CAAC;QACZ,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO,CACL,eAAK,SAAS,EAAC,sBAAsB,aACnC,KAAC,QAAQ,IACP,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,IAAI,EACX,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EACxC,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,CAAC,EACP,SAAS,EAAE,EAAE,CAAC,0BAA0B,CAAC,EACzC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE;oBACf,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;wBACrC,CAAC,CAAC,cAAc,EAAE,CAAC;wBACnB,IAAI,EAAE,CAAC;oBACT,CAAC;gBACH,CAAC,GACD,EACF,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,SAAS,EACjB,QAAQ,EAAE,QAAQ,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAClC,OAAO,EAAE,IAAI,qBAGN,IACL,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { ChatMessageV1, ModerationHandlers } from "../types";
2
+ export declare function MessageItemDefault(props: {
3
+ m: ChatMessageV1;
4
+ currentUserId: string;
5
+ isAdmin: boolean;
6
+ handlers?: ModerationHandlers;
7
+ }): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=MessageItemDefault.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageItemDefault.d.ts","sourceRoot":"","sources":["../../src/ui/MessageItemDefault.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAIlE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,CAAC,EAAE,aAAa,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,2CAyBA"}
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cn } from "@ttt-productions/ui-core";
4
+ import { MessageActions } from "./menus";
5
+ export function MessageItemDefault(props) {
6
+ const { m, currentUserId, isAdmin, handlers } = props;
7
+ const mine = m.senderId === currentUserId;
8
+ return (_jsxs("div", { className: cn("flex flex-col w-fit max-w-[85%]", mine ? "ml-auto items-end" : "mr-auto items-start"), children: [_jsxs("div", { className: cn("p-3 rounded-lg", mine ? "bg-primary/10" : "bg-muted"), children: [_jsxs("div", { className: "flex items-center gap-2 text-xs opacity-80 mb-1", children: [_jsx("span", { className: "font-medium", children: m.senderUsername ?? "User" }), _jsx("span", { children: "\u00B7" }), _jsx("span", { children: new Date(m.createdAt).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" }) })] }), m.text && _jsx("p", { className: "text-sm whitespace-pre-wrap", children: m.text })] }), _jsx("div", { className: "mt-1", children: _jsx(MessageActions, { messageId: m.messageId, isAdmin: isAdmin, handlers: handlers }) })] }));
9
+ }
10
+ //# sourceMappingURL=MessageItemDefault.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageItemDefault.js","sourceRoot":"","sources":["../../src/ui/MessageItemDefault.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,UAAU,kBAAkB,CAAC,KAKlC;IACC,MAAM,EAAE,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACtD,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,KAAK,aAAa,CAAC;IAE1C,OAAO,CACL,eACE,SAAS,EAAE,EAAE,CACX,iCAAiC,EACjC,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,qBAAqB,CACnD,aAED,eAAK,SAAS,EAAE,EAAE,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,UAAU,CAAC,aACvE,eAAK,SAAS,EAAC,iDAAiD,aAC9D,eAAM,SAAS,EAAC,aAAa,YAAE,CAAC,CAAC,cAAc,IAAI,MAAM,GAAQ,EACjE,oCAAc,EACd,yBAAO,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,kBAAkB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,GAAQ,IAC/F,EACL,CAAC,CAAC,IAAI,IAAI,YAAG,SAAS,EAAC,6BAA6B,YAAE,CAAC,CAAC,IAAI,GAAK,IAC9D,EAEN,cAAK,SAAS,EAAC,MAAM,YACnB,KAAC,cAAc,IAAC,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,GAAI,GAC5E,IACF,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ import type { ChatMessageV1, MessageRendererRegistry, ModerationHandlers } from "../types";
3
+ export declare function MessageList(props: {
4
+ messages: ChatMessageV1[];
5
+ currentUserId: string;
6
+ isAdmin: boolean;
7
+ isFetchingOlder?: boolean;
8
+ hasOlder?: boolean;
9
+ onLoadOlder?: () => void;
10
+ renderMessage?: (m: ChatMessageV1) => React.ReactNode;
11
+ messageRenderers?: MessageRendererRegistry;
12
+ showScrollToBottom?: boolean;
13
+ onScrollToBottom?: () => void;
14
+ handlers?: ModerationHandlers;
15
+ }): import("react/jsx-runtime").JSX.Element;
16
+ //# sourceMappingURL=MessageList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageList.d.ts","sourceRoot":"","sources":["../../src/ui/MessageList.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAI3F,wBAAgB,WAAW,CAAC,KAAK,EAAE;IACjC,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IAEjB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IAEzB,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,KAAK,KAAK,CAAC,SAAS,CAAC;IACtD,gBAAgB,CAAC,EAAE,uBAAuB,CAAC;IAE3C,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAE9B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,2CAqIA"}
@@ -0,0 +1,67 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Button } from "@ttt-productions/ui-core";
5
+ import { MessageItemDefault } from "./MessageItemDefault";
6
+ export function MessageList(props) {
7
+ const { messages, currentUserId, isAdmin, isFetchingOlder, hasOlder, onLoadOlder, renderMessage, messageRenderers, showScrollToBottom, onScrollToBottom, handlers } = props;
8
+ const scrollRef = React.useRef(null);
9
+ const topSentinelRef = React.useRef(null);
10
+ const isAtBottomRef = React.useRef(true);
11
+ const prevScrollHeightRef = React.useRef(null);
12
+ const prevCountRef = React.useRef(0);
13
+ React.useEffect(() => {
14
+ const root = scrollRef.current;
15
+ const target = topSentinelRef.current;
16
+ if (!root || !target)
17
+ return;
18
+ if (typeof IntersectionObserver === "undefined")
19
+ return;
20
+ const io = new IntersectionObserver((entries) => {
21
+ const e = entries[0];
22
+ if (!e?.isIntersecting)
23
+ return;
24
+ if (!hasOlder || isFetchingOlder)
25
+ return;
26
+ prevScrollHeightRef.current = root.scrollHeight;
27
+ onLoadOlder?.();
28
+ }, { root, threshold: 0 });
29
+ io.observe(target);
30
+ return () => io.disconnect();
31
+ }, [hasOlder, isFetchingOlder, onLoadOlder]);
32
+ React.useLayoutEffect(() => {
33
+ const el = scrollRef.current;
34
+ if (!el)
35
+ return;
36
+ const count = messages.length;
37
+ const prev = prevCountRef.current;
38
+ if (prev === 0 && count > 0) {
39
+ el.scrollTop = el.scrollHeight;
40
+ isAtBottomRef.current = true;
41
+ }
42
+ else if (count > prev && prevScrollHeightRef.current != null) {
43
+ const diff = el.scrollHeight - prevScrollHeightRef.current;
44
+ el.scrollTop += diff;
45
+ prevScrollHeightRef.current = null;
46
+ }
47
+ else if (count > prev && isAtBottomRef.current) {
48
+ el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
49
+ }
50
+ prevCountRef.current = count;
51
+ }, [messages]);
52
+ const onScroll = (e) => {
53
+ const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
54
+ isAtBottomRef.current = scrollHeight - (scrollTop + clientHeight) < 50;
55
+ };
56
+ let lastDay = null;
57
+ return (_jsxs("div", { className: "relative", children: [_jsxs("div", { ref: scrollRef, className: "h-[400px] overflow-y-auto p-4", onScroll: onScroll, children: [isFetchingOlder && _jsx("div", { className: "text-center text-xs opacity-70 mb-2", children: "Loading\u2026" }), _jsx("div", { ref: topSentinelRef }), messages.length === 0 ? (_jsx("div", { className: "h-full flex items-center justify-center text-sm opacity-70", children: "No messages yet" })) : (_jsx("div", { className: "flex flex-col gap-3", children: messages.map((m) => {
58
+ const day = new Date(m.createdAt).toDateString();
59
+ const showDay = day !== lastDay;
60
+ lastDay = day;
61
+ const byType = m.type && messageRenderers?.[m.type] ? messageRenderers[m.type](m) : null;
62
+ const body = renderMessage?.(m) ??
63
+ byType ?? (_jsx(MessageItemDefault, { m: m, currentUserId: currentUserId, isAdmin: isAdmin, handlers: handlers }));
64
+ return (_jsxs(React.Fragment, { children: [showDay && (_jsx("div", { className: "my-2 flex justify-center", children: _jsx("div", { className: "chat-date-separator", children: new Date(m.createdAt).toLocaleDateString() }) })), body] }, m.messageId));
65
+ }) }))] }), showScrollToBottom && (_jsx(Button, { type: "button", variant: "outline", size: "icon", className: "absolute bottom-4 left-1/2 -translate-x-1/2 rounded-full shadow-sm", onClick: onScrollToBottom, children: "\u2193" }))] }));
66
+ }
67
+ //# sourceMappingURL=MessageList.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageList.js","sourceRoot":"","sources":["../../src/ui/MessageList.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { ModerationHandlers } from "../types";
2
+ export declare function MessageActions(props: {
3
+ messageId: string;
4
+ isAdmin: boolean;
5
+ handlers?: ModerationHandlers;
6
+ }): import("react/jsx-runtime").JSX.Element;
7
+ export declare function ThreadActions(props: {
8
+ threadId: string;
9
+ isAdmin: boolean;
10
+ handlers?: ModerationHandlers;
11
+ }): import("react/jsx-runtime").JSX.Element;
12
+ //# sourceMappingURL=menus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menus.d.ts","sourceRoot":"","sources":["../../src/ui/menus.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAGnD,wBAAgB,cAAc,CAAC,KAAK,EAAE;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,2CA6BA;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,kBAAkB,CAAC;CAC/B,2CA6BA"}
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Button } from "@ttt-productions/ui-core";
4
+ export function MessageActions(props) {
5
+ const { messageId, isAdmin, handlers } = props;
6
+ return (_jsxs("div", { className: "flex items-center gap-2", children: [handlers?.onReportMessage && (_jsx(Button, { type: "button", variant: "link", size: "sm", className: "h-auto p-0 text-xs opacity-70 hover:opacity-100", onClick: () => handlers.onReportMessage?.(messageId), children: "Report" })), isAdmin && handlers?.onDeleteMessage && (_jsx(Button, { type: "button", variant: "link", size: "sm", className: "h-auto p-0 text-xs opacity-70 hover:opacity-100", onClick: () => handlers.onDeleteMessage?.(messageId), children: "Delete" }))] }));
7
+ }
8
+ export function ThreadActions(props) {
9
+ const { threadId, isAdmin, handlers } = props;
10
+ return (_jsxs("div", { className: "flex items-center gap-3", children: [handlers?.onReportThread && (_jsx(Button, { type: "button", variant: "link", size: "sm", className: "h-auto p-0 text-xs opacity-70 hover:opacity-100", onClick: () => handlers.onReportThread?.(threadId), children: "Report thread" })), isAdmin && handlers?.onDeleteThread && (_jsx(Button, { type: "button", variant: "link", size: "sm", className: "h-auto p-0 text-xs opacity-70 hover:opacity-100", onClick: () => handlers.onDeleteThread?.(threadId), children: "Delete thread" }))] }));
11
+ }
12
+ //# sourceMappingURL=menus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menus.js","sourceRoot":"","sources":["../../src/ui/menus.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAElD,MAAM,UAAU,cAAc,CAAC,KAI9B;IACC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAE/C,OAAO,CACL,eAAK,SAAS,EAAC,yBAAyB,aACrC,QAAQ,EAAE,eAAe,IAAI,CAC5B,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,MAAM,EACd,IAAI,EAAC,IAAI,EACT,SAAS,EAAC,iDAAiD,EAC3D,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,eAAe,EAAE,CAAC,SAAS,CAAC,uBAG7C,CACV,EACA,OAAO,IAAI,QAAQ,EAAE,eAAe,IAAI,CACvC,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,MAAM,EACd,IAAI,EAAC,IAAI,EACT,SAAS,EAAC,iDAAiD,EAC3D,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,eAAe,EAAE,CAAC,SAAS,CAAC,uBAG7C,CACV,IACG,CACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAI7B;IACC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAE9C,OAAO,CACL,eAAK,SAAS,EAAC,yBAAyB,aACrC,QAAQ,EAAE,cAAc,IAAI,CAC3B,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,MAAM,EACd,IAAI,EAAC,IAAI,EACT,SAAS,EAAC,iDAAiD,EAC3D,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC,QAAQ,CAAC,8BAG3C,CACV,EACA,OAAO,IAAI,QAAQ,EAAE,cAAc,IAAI,CACtC,KAAC,MAAM,IACL,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,MAAM,EACd,IAAI,EAAC,IAAI,EACT,SAAS,EAAC,iDAAiD,EAC3D,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,cAAc,EAAE,CAAC,QAAQ,CAAC,8BAG3C,CACV,IACG,CACP,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@ttt-productions/chat-core",
3
+ "version": "0.1.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/ttt-productions/ttt-packages.git",
7
+ "directory": "packages/chat-core"
8
+ },
9
+ "description": "Shared Firestore chat core (realtime newest window + infinite older pagination) for TTT Productions apps",
10
+ "main": "dist/index.js",
11
+ "module": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "files": ["dist"],
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ }
19
+ },
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "clean": "rm -rf dist *.tsbuildinfo",
23
+ "typecheck": "tsc --noEmit",
24
+ "prepublishOnly": "npm run clean && npm run build"
25
+ },
26
+ "peerDependencies": {
27
+ "@tanstack/react-query": "^5.0.0",
28
+ "firebase": "^10.0.0",
29
+ "react": "^19.0.0",
30
+ "react-dom": "^19.0.0"
31
+ },
32
+ "dependencies": {
33
+ "@ttt-productions/ui-core": "^0.1.25"
34
+ },
35
+ "devDependencies": {
36
+ "@ttt-productions/ui-core": "^0.1.25",
37
+ "@types/react": "^19.0.0",
38
+ "react": "^19.0.0",
39
+ "typescript": "^5.8.3"
40
+ },
41
+ "author": "DJ (TTT Productions)",
42
+ "license": "MIT"
43
+ }