@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.
- package/dist/firestore/queries.d.ts +5 -0
- package/dist/firestore/queries.d.ts.map +1 -0
- package/dist/firestore/queries.js +11 -0
- package/dist/firestore/queries.js.map +1 -0
- package/dist/hooks/useChatMessages.d.ts +11 -0
- package/dist/hooks/useChatMessages.d.ts.map +1 -0
- package/dist/hooks/useChatMessages.js +100 -0
- package/dist/hooks/useChatMessages.js.map +1 -0
- package/dist/hooks/useChatThreadAccess.d.ts +6 -0
- package/dist/hooks/useChatThreadAccess.d.ts.map +1 -0
- package/dist/hooks/useChatThreadAccess.js +9 -0
- package/dist/hooks/useChatThreadAccess.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/templates/AdminThreadChat.d.ts +9 -0
- package/dist/templates/AdminThreadChat.d.ts.map +1 -0
- package/dist/templates/AdminThreadChat.js +8 -0
- package/dist/templates/AdminThreadChat.js.map +1 -0
- package/dist/templates/ChannelChat.d.ts +11 -0
- package/dist/templates/ChannelChat.d.ts.map +1 -0
- package/dist/templates/ChannelChat.js +8 -0
- package/dist/templates/ChannelChat.js.map +1 -0
- package/dist/templates/InviteChat.d.ts +9 -0
- package/dist/templates/InviteChat.d.ts.map +1 -0
- package/dist/templates/InviteChat.js +8 -0
- package/dist/templates/InviteChat.js.map +1 -0
- package/dist/types.d.ts +44 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/ChatShell.d.ts +13 -0
- package/dist/ui/ChatShell.d.ts.map +1 -0
- package/dist/ui/ChatShell.js +21 -0
- package/dist/ui/ChatShell.js.map +1 -0
- package/dist/ui/Composer.d.ts +7 -0
- package/dist/ui/Composer.d.ts.map +1 -0
- package/dist/ui/Composer.js +29 -0
- package/dist/ui/Composer.js.map +1 -0
- package/dist/ui/MessageItemDefault.d.ts +8 -0
- package/dist/ui/MessageItemDefault.d.ts.map +1 -0
- package/dist/ui/MessageItemDefault.js +10 -0
- package/dist/ui/MessageItemDefault.js.map +1 -0
- package/dist/ui/MessageList.d.ts +16 -0
- package/dist/ui/MessageList.d.ts.map +1 -0
- package/dist/ui/MessageList.js +67 -0
- package/dist/ui/MessageList.js.map +1 -0
- package/dist/ui/menus.d.ts +12 -0
- package/dist/ui/menus.d.ts.map +1 -0
- package/dist/ui/menus.js +12 -0
- package/dist/ui/menus.js.map +1 -0
- 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 @@
|
|
|
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"}
|
package/dist/index.d.ts
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.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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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 @@
|
|
|
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"}
|
package/dist/ui/menus.js
ADDED
|
@@ -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
|
+
}
|