@ttt-productions/chat-react 0.2.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/README.md +31 -0
- package/dist/context/ChatNameResolverContext.d.ts +37 -0
- package/dist/context/ChatNameResolverContext.d.ts.map +1 -0
- package/dist/context/ChatNameResolverContext.js +44 -0
- package/dist/context/ChatNameResolverContext.js.map +1 -0
- package/dist/firestore/queries.d.ts +5 -0
- package/dist/firestore/queries.d.ts.map +1 -0
- package/dist/firestore/queries.js +16 -0
- package/dist/firestore/queries.js.map +1 -0
- package/dist/hooks/useChatMessages.d.ts +12 -0
- package/dist/hooks/useChatMessages.d.ts.map +1 -0
- package/dist/hooks/useChatMessages.js +139 -0
- package/dist/hooks/useChatMessages.js.map +1 -0
- package/dist/hooks/useChatThreadAccess.d.ts +8 -0
- package/dist/hooks/useChatThreadAccess.d.ts.map +1 -0
- package/dist/hooks/useChatThreadAccess.js +19 -0
- package/dist/hooks/useChatThreadAccess.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/mentions/MentionAutocomplete.d.ts +22 -0
- package/dist/mentions/MentionAutocomplete.d.ts.map +1 -0
- package/dist/mentions/MentionAutocomplete.js +21 -0
- package/dist/mentions/MentionAutocomplete.js.map +1 -0
- package/dist/mentions/MessageText.d.ts +27 -0
- package/dist/mentions/MessageText.d.ts.map +1 -0
- package/dist/mentions/MessageText.js +20 -0
- package/dist/mentions/MessageText.js.map +1 -0
- package/dist/mentions/use-mention-autocomplete.d.ts +82 -0
- package/dist/mentions/use-mention-autocomplete.d.ts.map +1 -0
- package/dist/mentions/use-mention-autocomplete.js +323 -0
- package/dist/mentions/use-mention-autocomplete.js.map +1 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/ChatShell.d.ts +23 -0
- package/dist/ui/ChatShell.d.ts.map +1 -0
- package/dist/ui/ChatShell.js +22 -0
- package/dist/ui/ChatShell.js.map +1 -0
- package/dist/ui/Composer.d.ts +24 -0
- package/dist/ui/Composer.d.ts.map +1 -0
- package/dist/ui/Composer.js +140 -0
- package/dist/ui/Composer.js.map +1 -0
- package/dist/ui/MessageItemDefault.d.ts +11 -0
- package/dist/ui/MessageItemDefault.d.ts.map +1 -0
- package/dist/ui/MessageItemDefault.js +51 -0
- package/dist/ui/MessageItemDefault.js.map +1 -0
- package/dist/ui/MessageList.d.ts +18 -0
- package/dist/ui/MessageList.d.ts.map +1 -0
- package/dist/ui/MessageList.js +90 -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 +78 -0
- package/src/styles/chat.css +153 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# @ttt-productions/chat-react
|
|
2
|
+
|
|
3
|
+
Chat **React UI** for TTT Productions apps: chat shell, composer, message list,
|
|
4
|
+
attachment UI, the generic mention autocomplete UI, the name-resolver context,
|
|
5
|
+
and the realtime-newest-window + infinite-older hooks.
|
|
6
|
+
|
|
7
|
+
Built on the pure [`@ttt-productions/chat-core`](../chat-core) package
|
|
8
|
+
(contracts, mention parser/serializer, grouping). A non-React consumer (a Cloud
|
|
9
|
+
Function, a script, a future native/TV client) installs `chat-core` and pulls
|
|
10
|
+
in none of this frontend tree.
|
|
11
|
+
|
|
12
|
+
## Entry points
|
|
13
|
+
|
|
14
|
+
- `.` — React components, hooks, the Firebase-client adapter config types
|
|
15
|
+
(`ChatCoreConfig`, `ChatAttachmentConfig`, `ChatUploadAdapter`,
|
|
16
|
+
`ChatMentionConfig`), and the React render types (`MessageRenderer`,
|
|
17
|
+
`RenderableMentionProvider`, `MentionResultRenderer`).
|
|
18
|
+
- `./styles` — chat CSS. Import once in your app layout:
|
|
19
|
+
`import "@ttt-productions/chat-react/styles";`
|
|
20
|
+
|
|
21
|
+
## Peers
|
|
22
|
+
|
|
23
|
+
`react`, `react-dom`, `firebase`, `@tanstack/react-query`, and `lucide-react`
|
|
24
|
+
are optional peers — provided by the consuming app.
|
|
25
|
+
|
|
26
|
+
## Boundary
|
|
27
|
+
|
|
28
|
+
`chat-react` does not import `ttt-core`, does not hardcode TTT origins, and does
|
|
29
|
+
not build TTT storage paths. Consumers pass a chat upload adapter with opaque
|
|
30
|
+
origin id, upload path builder, and optional metadata builder. TTT mention
|
|
31
|
+
kinds, permissions, provider lists, routing, and search live in TTT code.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { ChatNameResolver, ChatPrewarmSenders } from "@ttt-productions/chat-core";
|
|
3
|
+
type ChatNameResolverContextValue = {
|
|
4
|
+
resolveName: ChatNameResolver;
|
|
5
|
+
prewarm?: ChatPrewarmSenders;
|
|
6
|
+
};
|
|
7
|
+
export type ChatNameResolverProviderProps = {
|
|
8
|
+
resolveName: ChatNameResolver;
|
|
9
|
+
prewarm?: ChatPrewarmSenders;
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Wraps any chat-core consumer (typically the whole app shell) and provides
|
|
14
|
+
* a name resolver. Required for any tree that renders <ChatShell>, <MessageList>,
|
|
15
|
+
* or <MessageItemDefault>.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ChatNameResolverProvider(props: ChatNameResolverProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
/**
|
|
19
|
+
* Strict hook — throws if no provider is wrapped. Used inside chat-core's
|
|
20
|
+
* own renderers (MessageItemDefault, ReplyQuote) so a missing setup surfaces
|
|
21
|
+
* immediately on first render rather than silently rendering "User".
|
|
22
|
+
*/
|
|
23
|
+
export declare function useChatNameResolver(): ChatNameResolverContextValue;
|
|
24
|
+
/**
|
|
25
|
+
* Optional variant — returns null instead of throwing when no provider is
|
|
26
|
+
* wrapped. Used internally by useChatMessages so headless callers (tests,
|
|
27
|
+
* data-only consumers that don't render UI) can still use the hook.
|
|
28
|
+
*/
|
|
29
|
+
export declare function useOptionalChatNameResolver(): ChatNameResolverContextValue | null;
|
|
30
|
+
/**
|
|
31
|
+
* Convenience hook for renderers. Returns a resolved string — never null —
|
|
32
|
+
* by falling back to "User" when the cache hasn't filled yet. Re-renders
|
|
33
|
+
* automatically once the upstream cache settles.
|
|
34
|
+
*/
|
|
35
|
+
export declare function useResolvedSenderName(senderId: string): string;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=ChatNameResolverContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatNameResolverContext.d.ts","sourceRoot":"","sources":["../../src/context/ChatNameResolverContext.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAEvF,KAAK,4BAA4B,GAAG;IAClC,WAAW,EAAE,gBAAgB,CAAC;IAC9B,OAAO,CAAC,EAAE,kBAAkB,CAAC;CAC9B,CAAC;AAIF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,WAAW,EAAE,gBAAgB,CAAC;IAC9B,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,6BAA6B,2CAQ5E;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,4BAA4B,CAQlE;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,IAAI,4BAA4B,GAAG,IAAI,CAEjF;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG9D"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
const ChatNameResolverContext = React.createContext(null);
|
|
5
|
+
/**
|
|
6
|
+
* Wraps any chat-core consumer (typically the whole app shell) and provides
|
|
7
|
+
* a name resolver. Required for any tree that renders <ChatShell>, <MessageList>,
|
|
8
|
+
* or <MessageItemDefault>.
|
|
9
|
+
*/
|
|
10
|
+
export function ChatNameResolverProvider(props) {
|
|
11
|
+
const { resolveName, prewarm, children } = props;
|
|
12
|
+
const value = React.useMemo(() => ({ resolveName, prewarm }), [resolveName, prewarm]);
|
|
13
|
+
return (_jsx(ChatNameResolverContext.Provider, { value: value, children: children }));
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Strict hook — throws if no provider is wrapped. Used inside chat-core's
|
|
17
|
+
* own renderers (MessageItemDefault, ReplyQuote) so a missing setup surfaces
|
|
18
|
+
* immediately on first render rather than silently rendering "User".
|
|
19
|
+
*/
|
|
20
|
+
export function useChatNameResolver() {
|
|
21
|
+
const ctx = React.useContext(ChatNameResolverContext);
|
|
22
|
+
if (!ctx) {
|
|
23
|
+
throw new Error("[chat-core] ChatNameResolverProvider is required. Wrap your ChatShell tree with <ChatNameResolverProvider>.");
|
|
24
|
+
}
|
|
25
|
+
return ctx;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Optional variant — returns null instead of throwing when no provider is
|
|
29
|
+
* wrapped. Used internally by useChatMessages so headless callers (tests,
|
|
30
|
+
* data-only consumers that don't render UI) can still use the hook.
|
|
31
|
+
*/
|
|
32
|
+
export function useOptionalChatNameResolver() {
|
|
33
|
+
return React.useContext(ChatNameResolverContext);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Convenience hook for renderers. Returns a resolved string — never null —
|
|
37
|
+
* by falling back to "User" when the cache hasn't filled yet. Re-renders
|
|
38
|
+
* automatically once the upstream cache settles.
|
|
39
|
+
*/
|
|
40
|
+
export function useResolvedSenderName(senderId) {
|
|
41
|
+
const { resolveName } = useChatNameResolver();
|
|
42
|
+
return resolveName(senderId) ?? "User";
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=ChatNameResolverContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatNameResolverContext.js","sourceRoot":"","sources":["../../src/context/ChatNameResolverContext.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAQ/B,MAAM,uBAAuB,GAAG,KAAK,CAAC,aAAa,CAAsC,IAAI,CAAC,CAAC;AAQ/F;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAoC;IAC3E,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IACjD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;IACtF,OAAO,CACL,KAAC,uBAAuB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAC3C,QAAQ,GACwB,CACpC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,GAAG,GAAG,KAAK,CAAC,UAAU,CAAC,uBAAuB,CAAC,CAAC;IACtD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,6GAA6G,CAC9G,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B;IACzC,OAAO,KAAK,CAAC,UAAU,CAAC,uBAAuB,CAAC,CAAC;AACnD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,MAAM,EAAE,WAAW,EAAE,GAAG,mBAAmB,EAAE,CAAC;IAC9C,OAAO,WAAW,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Firestore, Query, DocumentData } from "firebase/firestore";
|
|
2
|
+
export declare function threadDocPath(chatCollectionPath: string | string[], threadId: string): string[];
|
|
3
|
+
export declare function messagesColPath(chatCollectionPath: string | string[], threadId: string, messagesSubcollection?: string): [string, string, ...string[]];
|
|
4
|
+
export declare function newestWindowQuery(db: Firestore, chatCollectionPath: string | string[], threadId: string, createdAtField: string, pageSize: number, messagesSubcollection?: string): 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;AAOzE,wBAAgB,aAAa,CAAC,kBAAkB,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/F;AAED,wBAAgB,eAAe,CAC7B,kBAAkB,EAAE,MAAM,GAAG,MAAM,EAAE,EACrC,QAAQ,EAAE,MAAM,EAChB,qBAAqB,SAAa,GACjC,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,CAG/B;AAED,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,SAAS,EACb,kBAAkB,EAAE,MAAM,GAAG,MAAM,EAAE,EACrC,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,EAChB,qBAAqB,SAAa,GACjC,KAAK,CAAC,YAAY,CAAC,CAOrB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { collection, query, orderBy, limit } from "firebase/firestore";
|
|
2
|
+
function normalizeSegments(path) {
|
|
3
|
+
return Array.isArray(path) ? path : [path];
|
|
4
|
+
}
|
|
5
|
+
export function threadDocPath(chatCollectionPath, threadId) {
|
|
6
|
+
return [...normalizeSegments(chatCollectionPath), threadId];
|
|
7
|
+
}
|
|
8
|
+
export function messagesColPath(chatCollectionPath, threadId, messagesSubcollection = "messages") {
|
|
9
|
+
const segments = [...normalizeSegments(chatCollectionPath), threadId, messagesSubcollection];
|
|
10
|
+
return segments;
|
|
11
|
+
}
|
|
12
|
+
export function newestWindowQuery(db, chatCollectionPath, threadId, createdAtField, pageSize, messagesSubcollection = "messages") {
|
|
13
|
+
const path = messagesColPath(chatCollectionPath, threadId, messagesSubcollection);
|
|
14
|
+
return query(collection(db, path[0], ...path.slice(1)), orderBy(createdAtField, "desc"), limit(pageSize));
|
|
15
|
+
}
|
|
16
|
+
//# 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,SAAS,iBAAiB,CAAC,IAAuB;IAChD,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,kBAAqC,EAAE,QAAgB;IACnF,OAAO,CAAC,GAAG,iBAAiB,CAAC,kBAAkB,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,kBAAqC,EACrC,QAAgB,EAChB,qBAAqB,GAAG,UAAU;IAElC,MAAM,QAAQ,GAAG,CAAC,GAAG,iBAAiB,CAAC,kBAAkB,CAAC,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CAAC;IAC7F,OAAO,QAAyC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,EAAa,EACb,kBAAqC,EACrC,QAAgB,EAChB,cAAsB,EACtB,QAAgB,EAChB,qBAAqB,GAAG,UAAU;IAElC,MAAM,IAAI,GAAG,eAAe,CAAC,kBAAkB,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CAAC;IAClF,OAAO,KAAK,CACV,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EACzC,OAAO,CAAC,cAAc,EAAE,MAAM,CAAC,EAC/B,KAAK,CAAC,QAAQ,CAAC,CAChB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChatMessageV1 } from "@ttt-productions/chat-core";
|
|
2
|
+
import type { ChatCoreConfig } from "../types.js";
|
|
3
|
+
export type UseChatMessagesResult = {
|
|
4
|
+
allowed: boolean;
|
|
5
|
+
isInitialLoading: boolean;
|
|
6
|
+
messages: ChatMessageV1[];
|
|
7
|
+
fetchOlder: () => Promise<void>;
|
|
8
|
+
hasOlder: boolean;
|
|
9
|
+
isFetchingOlder: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function useChatMessages(config: ChatCoreConfig): UseChatMessagesResult;
|
|
12
|
+
//# sourceMappingURL=useChatMessages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useChatMessages.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatMessages.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAclD,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;AAsDF,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,qBAAqB,CA4I7E"}
|
|
@@ -0,0 +1,139 @@
|
|
|
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 { toMillis } from "@ttt-productions/firebase-helpers";
|
|
6
|
+
import { canAccessThread } from "./useChatThreadAccess.js";
|
|
7
|
+
import { messagesColPath, newestWindowQuery } from "../firestore/queries.js";
|
|
8
|
+
import { useOptionalChatNameResolver } from "../context/ChatNameResolverContext.js";
|
|
9
|
+
// Safety limit to prevent memory exhaustion
|
|
10
|
+
const MAX_PAGE_SIZE = 100;
|
|
11
|
+
function mapMsg(doc, threadId) {
|
|
12
|
+
const d = doc.data();
|
|
13
|
+
const createdAt = toMillis(d.createdAt);
|
|
14
|
+
if (!createdAt) {
|
|
15
|
+
console.error('[useChatMessages] Message missing valid createdAt:', doc.id, d.createdAt);
|
|
16
|
+
}
|
|
17
|
+
// replyTo: the new shape uses senderId, not senderUsername. Tolerate older
|
|
18
|
+
// docs that still have senderUsername — drop the name and keep what we can.
|
|
19
|
+
const replyTo = d.replyTo
|
|
20
|
+
? {
|
|
21
|
+
messageId: d.replyTo.messageId,
|
|
22
|
+
senderId: d.replyTo.senderId ?? "",
|
|
23
|
+
messagePreview: d.replyTo.messagePreview,
|
|
24
|
+
}
|
|
25
|
+
: undefined;
|
|
26
|
+
return {
|
|
27
|
+
messageId: doc.id,
|
|
28
|
+
threadId,
|
|
29
|
+
createdAt: createdAt || 0,
|
|
30
|
+
senderId: d.senderId,
|
|
31
|
+
text: d.text ?? "",
|
|
32
|
+
type: d.type,
|
|
33
|
+
attachment: d.attachment ?? undefined,
|
|
34
|
+
replyTo,
|
|
35
|
+
isSystemMessage: d.isSystemMessage ?? undefined,
|
|
36
|
+
meta: d.meta,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function dedupeAndSortAsc(all) {
|
|
40
|
+
const m = new Map();
|
|
41
|
+
for (const x of all)
|
|
42
|
+
m.set(x.messageId, x);
|
|
43
|
+
return Array.from(m.values()).sort((a, b) => a.createdAt - b.createdAt);
|
|
44
|
+
}
|
|
45
|
+
function messagesCol(db, chatCollectionPath, threadId, messagesSubcollection) {
|
|
46
|
+
const path = messagesColPath(chatCollectionPath, threadId, messagesSubcollection);
|
|
47
|
+
return collection(db, path[0], ...path.slice(1));
|
|
48
|
+
}
|
|
49
|
+
export function useChatMessages(config) {
|
|
50
|
+
const { db, chatCollectionPath, messagesSubcollection = "messages", threadId, pageSize: requestedPageSize = 20, currentUserId, isAdmin, accessMode, threadAllowedUserIds, createdAtField = "createdAt", } = config;
|
|
51
|
+
const pageSize = Math.min(requestedPageSize, MAX_PAGE_SIZE);
|
|
52
|
+
if (requestedPageSize > MAX_PAGE_SIZE) {
|
|
53
|
+
console.warn(`[useChatMessages] Requested pageSize ${requestedPageSize} exceeds maximum ${MAX_PAGE_SIZE}. Using ${MAX_PAGE_SIZE}.`);
|
|
54
|
+
}
|
|
55
|
+
const pathKey = JSON.stringify(Array.isArray(chatCollectionPath) ? chatCollectionPath : [chatCollectionPath]);
|
|
56
|
+
const allowed = React.useMemo(() => canAccessThread({
|
|
57
|
+
accessMode,
|
|
58
|
+
isAdmin,
|
|
59
|
+
currentUserId,
|
|
60
|
+
allowedUserIds: threadAllowedUserIds,
|
|
61
|
+
}), [accessMode, isAdmin, currentUserId, threadAllowedUserIds]);
|
|
62
|
+
const [newest, setNewest] = React.useState({
|
|
63
|
+
ready: false,
|
|
64
|
+
newestDesc: [],
|
|
65
|
+
oldestDocInWindow: null,
|
|
66
|
+
});
|
|
67
|
+
React.useEffect(() => {
|
|
68
|
+
if (!allowed)
|
|
69
|
+
return;
|
|
70
|
+
const q = newestWindowQuery(db, chatCollectionPath, threadId, createdAtField, pageSize, messagesSubcollection);
|
|
71
|
+
const unsub = onSnapshot(q, (snap) => {
|
|
72
|
+
const docs = snap.docs;
|
|
73
|
+
const mapped = docs.map((d) => mapMsg(d, threadId));
|
|
74
|
+
setNewest({
|
|
75
|
+
ready: true,
|
|
76
|
+
newestDesc: mapped,
|
|
77
|
+
oldestDocInWindow: docs[docs.length - 1] ?? null,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
return () => unsub();
|
|
81
|
+
}, [allowed, db, pathKey, threadId, createdAtField, pageSize, messagesSubcollection]);
|
|
82
|
+
const older = useInfiniteQuery({
|
|
83
|
+
queryKey: ["chat-core", "older", pathKey, threadId, messagesSubcollection, pageSize],
|
|
84
|
+
enabled: allowed && newest.ready,
|
|
85
|
+
initialPageParam: undefined,
|
|
86
|
+
queryFn: async ({ pageParam }) => {
|
|
87
|
+
const baseCol = messagesCol(db, chatCollectionPath, threadId, messagesSubcollection);
|
|
88
|
+
const cursor = pageParam ?? (newest.oldestDocInWindow ?? undefined);
|
|
89
|
+
if (!cursor) {
|
|
90
|
+
return {
|
|
91
|
+
itemsDesc: [],
|
|
92
|
+
nextCursor: undefined,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const q = query(baseCol, orderBy(createdAtField, "desc"), startAfter(cursor), limit(pageSize));
|
|
96
|
+
const snap = await getDocs(q);
|
|
97
|
+
const docs = snap.docs;
|
|
98
|
+
const itemsDesc = docs.map((d) => mapMsg(d, threadId));
|
|
99
|
+
const nextCursor = docs.length === pageSize ? docs[docs.length - 1] : undefined;
|
|
100
|
+
return { itemsDesc, nextCursor };
|
|
101
|
+
},
|
|
102
|
+
getNextPageParam: (last) => last.nextCursor,
|
|
103
|
+
});
|
|
104
|
+
const fetchOlder = React.useCallback(async () => {
|
|
105
|
+
await older.fetchNextPage();
|
|
106
|
+
}, [older.fetchNextPage]);
|
|
107
|
+
const messages = React.useMemo(() => {
|
|
108
|
+
if (!allowed)
|
|
109
|
+
return [];
|
|
110
|
+
const olderDesc = (older.data?.pages ?? []).flatMap((p) => p.itemsDesc);
|
|
111
|
+
return dedupeAndSortAsc([...olderDesc, ...newest.newestDesc]);
|
|
112
|
+
}, [allowed, older.data, newest.newestDesc]);
|
|
113
|
+
// Pre-warm name cache for all visible senders (and reply-to senders).
|
|
114
|
+
// Optional resolver: silent no-op if no provider is wrapped.
|
|
115
|
+
const resolverCtx = useOptionalChatNameResolver();
|
|
116
|
+
const senderIds = React.useMemo(() => {
|
|
117
|
+
const set = new Set();
|
|
118
|
+
for (const m of messages) {
|
|
119
|
+
set.add(m.senderId);
|
|
120
|
+
if (m.replyTo?.senderId)
|
|
121
|
+
set.add(m.replyTo.senderId);
|
|
122
|
+
}
|
|
123
|
+
return Array.from(set);
|
|
124
|
+
}, [messages]);
|
|
125
|
+
React.useEffect(() => {
|
|
126
|
+
if (resolverCtx?.prewarm && senderIds.length > 0) {
|
|
127
|
+
resolverCtx.prewarm(senderIds);
|
|
128
|
+
}
|
|
129
|
+
}, [resolverCtx, senderIds]);
|
|
130
|
+
return {
|
|
131
|
+
allowed,
|
|
132
|
+
isInitialLoading: allowed ? !newest.ready : false,
|
|
133
|
+
messages,
|
|
134
|
+
fetchOlder,
|
|
135
|
+
hasOlder: Boolean(older.hasNextPage),
|
|
136
|
+
isFetchingOlder: older.isFetchingNextPage,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# 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,8 @@
|
|
|
1
|
+
import type { ChatAccessMode } from "@ttt-productions/chat-core";
|
|
2
|
+
export declare function canAccessThread(args: {
|
|
3
|
+
accessMode: ChatAccessMode;
|
|
4
|
+
isAdmin: boolean;
|
|
5
|
+
currentUserId: string;
|
|
6
|
+
allowedUserIds?: string[];
|
|
7
|
+
}): boolean;
|
|
8
|
+
//# sourceMappingURL=useChatThreadAccess.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useChatThreadAccess.d.ts","sourceRoot":"","sources":["../../src/hooks/useChatThreadAccess.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,UAAU,EAAE,cAAc,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,GAAG,OAAO,CAkBV"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function canAccessThread(args) {
|
|
2
|
+
const { accessMode, isAdmin, currentUserId, allowedUserIds } = args;
|
|
3
|
+
// Admin always bypasses access checks (admin moderation surfaces).
|
|
4
|
+
if (isAdmin)
|
|
5
|
+
return true;
|
|
6
|
+
// Unauthenticated users never have access regardless of mode.
|
|
7
|
+
if (!currentUserId)
|
|
8
|
+
return false;
|
|
9
|
+
if (accessMode === "firestore-rules") {
|
|
10
|
+
// Defer to Firestore rules. If rules deny, onSnapshot will surface
|
|
11
|
+
// permission-denied, but chat-core does not pre-deny.
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
// accessMode === "explicit-allowlist"
|
|
15
|
+
if (!allowedUserIds)
|
|
16
|
+
return false;
|
|
17
|
+
return allowedUserIds.includes(currentUserId);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=useChatThreadAccess.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useChatThreadAccess.js","sourceRoot":"","sources":["../../src/hooks/useChatThreadAccess.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,eAAe,CAAC,IAK/B;IACC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC;IAEpE,mEAAmE;IACnE,IAAI,OAAO;QAAE,OAAO,IAAI,CAAC;IAEzB,8DAA8D;IAC9D,IAAI,CAAC,aAAa;QAAE,OAAO,KAAK,CAAC;IAEjC,IAAI,UAAU,KAAK,iBAAiB,EAAE,CAAC;QACrC,mEAAmE;QACnE,sDAAsD;QACtD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,sCAAsC;IACtC,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,18 @@
|
|
|
1
|
+
export type { ChatCoreConfig, ChatAttachmentConfig, ChatUploadAdapter, ChatMentionConfig, MessageRenderer, MessageRendererRegistry, RenderableMentionProvider, MentionResultRenderer, } from "./types.js";
|
|
2
|
+
export type { ChatNameResolverProviderProps } from "./context/ChatNameResolverContext.js";
|
|
3
|
+
export { ChatNameResolverProvider, useChatNameResolver, useOptionalChatNameResolver, useResolvedSenderName, } from "./context/ChatNameResolverContext.js";
|
|
4
|
+
export type { UseChatMessagesResult } from "./hooks/useChatMessages.js";
|
|
5
|
+
export { useChatMessages } from "./hooks/useChatMessages.js";
|
|
6
|
+
export { canAccessThread } from "./hooks/useChatThreadAccess.js";
|
|
7
|
+
export type { ChatShellProps } from "./ui/ChatShell.js";
|
|
8
|
+
export { ChatShell } from "./ui/ChatShell.js";
|
|
9
|
+
export { MessageList } from "./ui/MessageList.js";
|
|
10
|
+
export type { ComposerProps } from "./ui/Composer.js";
|
|
11
|
+
export { Composer } from "./ui/Composer.js";
|
|
12
|
+
export type { MessageItemDefaultProps } from "./ui/MessageItemDefault.js";
|
|
13
|
+
export { MessageItemDefault } from "./ui/MessageItemDefault.js";
|
|
14
|
+
export { MessageActions, ThreadActions } from "./ui/menus.js";
|
|
15
|
+
export { MessageText, type MessageTextProps } from "./mentions/MessageText.js";
|
|
16
|
+
export { MentionAutocomplete, type MentionAutocompleteProps, } from "./mentions/MentionAutocomplete.js";
|
|
17
|
+
export { useMentionAutocomplete, type UseMentionAutocompleteArgs, type UseMentionAutocompleteResult, type AutocompleteState, type AutocompleteResultGroup, } from "./mentions/use-mention-autocomplete.js";
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACvB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AAGpB,YAAY,EAAE,6BAA6B,EAAE,MAAM,sCAAsC,CAAC;AAC1F,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,2BAA2B,EAC3B,qBAAqB,GACtB,MAAM,sCAAsC,CAAC;AAG9C,YAAY,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAGjE,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,YAAY,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,GAC9B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,sBAAsB,EACtB,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,EACjC,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,GAC7B,MAAM,wCAAwC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
export { ChatNameResolverProvider, useChatNameResolver, useOptionalChatNameResolver, useResolvedSenderName, } from "./context/ChatNameResolverContext.js";
|
|
3
|
+
export { useChatMessages } from "./hooks/useChatMessages.js";
|
|
4
|
+
export { canAccessThread } from "./hooks/useChatThreadAccess.js";
|
|
5
|
+
export { ChatShell } from "./ui/ChatShell.js";
|
|
6
|
+
export { MessageList } from "./ui/MessageList.js";
|
|
7
|
+
export { Composer } from "./ui/Composer.js";
|
|
8
|
+
export { MessageItemDefault } from "./ui/MessageItemDefault.js";
|
|
9
|
+
export { MessageActions, ThreadActions } from "./ui/menus.js";
|
|
10
|
+
export { MessageText } from "./mentions/MessageText.js";
|
|
11
|
+
export { MentionAutocomplete, } from "./mentions/MentionAutocomplete.js";
|
|
12
|
+
export { useMentionAutocomplete, } from "./mentions/use-mention-autocomplete.js";
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAsBb,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,2BAA2B,EAC3B,qBAAqB,GACtB,MAAM,sCAAsC,CAAC;AAI9C,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAIjE,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAyB,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EACL,mBAAmB,GAEpB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,sBAAsB,GAKvB,MAAM,wCAAwC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visual autocomplete dropdown for the mention system.
|
|
3
|
+
*
|
|
4
|
+
* Pure presentation — driven entirely by `state` from `useMentionAutocomplete`.
|
|
5
|
+
* The consumer is expected to absolutely-position this near the textarea (or
|
|
6
|
+
* use its caret position via a wrapping container). chat-core does not
|
|
7
|
+
* compute textarea-caret coordinates; that's the consumer's job.
|
|
8
|
+
*
|
|
9
|
+
* Selecting a result fires `onSelect(ref)`. Hovering / arrow-keying updates
|
|
10
|
+
* the highlight via `onHighlight(index)`.
|
|
11
|
+
*/
|
|
12
|
+
import type { AutocompleteState } from './use-mention-autocomplete.js';
|
|
13
|
+
import type { MentionRef } from '@ttt-productions/chat-core';
|
|
14
|
+
export interface MentionAutocompleteProps<TKind extends string = string> {
|
|
15
|
+
state: AutocompleteState<TKind>;
|
|
16
|
+
onSelect: (ref: MentionRef<TKind>) => void;
|
|
17
|
+
onHighlight?: (flatIndex: number) => void;
|
|
18
|
+
/** Class name applied to the outer container. */
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function MentionAutocomplete<TKind extends string = string>(props: MentionAutocompleteProps<TKind>): import("react/jsx-runtime").JSX.Element | null;
|
|
22
|
+
//# sourceMappingURL=MentionAutocomplete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MentionAutocomplete.d.ts","sourceRoot":"","sources":["../../src/mentions/MentionAutocomplete.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAG7D,MAAM,WAAW,wBAAwB,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IACrE,KAAK,EAAE,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAChC,QAAQ,EAAE,CAAC,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;IAC3C,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,EAC/D,KAAK,EAAE,wBAAwB,CAAC,KAAK,CAAC,kDAoEvC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from '@ttt-productions/ui-core';
|
|
4
|
+
export function MentionAutocomplete(props) {
|
|
5
|
+
const { state, onSelect, onHighlight, className } = props;
|
|
6
|
+
if (!state.open)
|
|
7
|
+
return null;
|
|
8
|
+
const anyResults = state.flatResults.length > 0;
|
|
9
|
+
const anyLoading = state.groups.some((g) => g.loading);
|
|
10
|
+
let flatIndex = 0;
|
|
11
|
+
return (_jsxs("div", { role: "listbox", "aria-label": "Mentions", className: cn('chat-mention-autocomplete', 'rounded-md border bg-popover text-popover-foreground shadow-md', 'max-h-72 overflow-y-auto min-w-[16rem]', className), children: [!anyResults && anyLoading && (_jsx("div", { className: "px-3 py-2 text-xs text-muted-foreground", children: "Searching\u2026" })), !anyResults && !anyLoading && (_jsx("div", { className: "px-3 py-2 text-xs text-muted-foreground", children: state.showingRecent ? 'No recent mentions' : 'No matches' })), state.groups.map((group) => (_jsxs("div", { className: "chat-mention-group", children: [_jsx("div", { className: "px-3 pt-2 pb-1 text-xs font-semibold text-muted-foreground", children: state.showingRecent ? `Recent ${group.label}` : group.label }), group.loading && group.results.length === 0 && (_jsx("div", { className: "px-3 pb-2 text-xs text-muted-foreground", children: "Loading\u2026" })), _jsx("ul", { children: group.results.map((ref) => {
|
|
12
|
+
const myIndex = flatIndex;
|
|
13
|
+
flatIndex += 1;
|
|
14
|
+
const highlighted = myIndex === state.highlightedIndex;
|
|
15
|
+
return (_jsx("li", { children: _jsx("button", { type: "button", role: "option", "aria-selected": highlighted, className: cn('w-full text-left px-3 py-2 text-sm hover:bg-accent hover:text-accent-foreground', highlighted && 'bg-accent text-accent-foreground'), onMouseEnter: () => onHighlight?.(myIndex), onClick: (e) => {
|
|
16
|
+
e.preventDefault();
|
|
17
|
+
onSelect(ref);
|
|
18
|
+
}, children: group.renderResult ? group.renderResult(ref) : _jsxs("span", { children: ["@", ref.displayText] }) }) }, `${ref.kind}:${ref.id}`));
|
|
19
|
+
}) })] }, group.kind)))] }));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=MentionAutocomplete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MentionAutocomplete.js","sourceRoot":"","sources":["../../src/mentions/MentionAutocomplete.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAgBb,OAAO,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAU9C,MAAM,UAAU,mBAAmB,CACjC,KAAsC;IAEtC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IAC1D,IAAI,CAAC,KAAK,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAE7B,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;IAChD,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAEvD,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,OAAO,CACL,eACE,IAAI,EAAC,SAAS,gBACH,UAAU,EACrB,SAAS,EAAE,EAAE,CACX,2BAA2B,EAC3B,gEAAgE,EAChE,wCAAwC,EACxC,SAAS,CACV,aAEA,CAAC,UAAU,IAAI,UAAU,IAAI,CAC5B,cAAK,SAAS,EAAC,yCAAyC,gCAAiB,CAC1E,EACA,CAAC,UAAU,IAAI,CAAC,UAAU,IAAI,CAC7B,cAAK,SAAS,EAAC,yCAAyC,YACrD,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,YAAY,GACtD,CACP,EACA,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAC3B,eAAsB,SAAS,EAAC,oBAAoB,aAClD,cAAK,SAAS,EAAC,4DAA4D,YACxE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,GACxD,EACL,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,CAC9C,cAAK,SAAS,EAAC,yCAAyC,8BAAe,CACxE,EACD,uBACG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;4BACzB,MAAM,OAAO,GAAG,SAAS,CAAC;4BAC1B,SAAS,IAAI,CAAC,CAAC;4BACf,MAAM,WAAW,GAAG,OAAO,KAAK,KAAK,CAAC,gBAAgB,CAAC;4BACvD,OAAO,CACL,uBACE,iBACE,IAAI,EAAC,QAAQ,EACb,IAAI,EAAC,QAAQ,mBACE,WAAW,EAC1B,SAAS,EAAE,EAAE,CACX,iFAAiF,EACjF,WAAW,IAAI,kCAAkC,CAClD,EACD,YAAY,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,EAC1C,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;wCACb,CAAC,CAAC,cAAc,EAAE,CAAC;wCACnB,QAAQ,CAAC,GAAG,CAAC,CAAC;oCAChB,CAAC,YAEA,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,gCAAQ,GAAG,CAAC,WAAW,IAAQ,GACxE,IAhBF,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,EAAE,CAiB3B,CACN,CAAC;wBACJ,CAAC,CAAC,GACC,KAjCG,KAAK,CAAC,IAAI,CAkCd,CACP,CAAC,IACE,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders message text with inline mention chips.
|
|
3
|
+
*
|
|
4
|
+
* Walks the parser output and renders each segment: text segments as
|
|
5
|
+
* `<span>`, mention segments via the consumer-supplied `renderMention`
|
|
6
|
+
* callback (or a default chip when omitted).
|
|
7
|
+
*
|
|
8
|
+
* Pure, no context. Safe to use inside MessageItem renderers, ReplyQuote
|
|
9
|
+
* previews, or any other text surface that should honor mention tokens.
|
|
10
|
+
*/
|
|
11
|
+
import type { ReactNode } from 'react';
|
|
12
|
+
import type { MentionRef } from '@ttt-productions/chat-core';
|
|
13
|
+
export interface MessageTextProps<TKind extends string = string> {
|
|
14
|
+
/** Raw message text containing optional `@[kind:id|displayText]` tokens. */
|
|
15
|
+
text: string;
|
|
16
|
+
/**
|
|
17
|
+
* Custom mention renderer. When omitted, mentions render as a
|
|
18
|
+
* `<span class="chat-mention-chip">@{displayText}</span>` chip — consumers
|
|
19
|
+
* style via CSS. To make mentions clickable / linked / role-aware, supply
|
|
20
|
+
* a renderMention.
|
|
21
|
+
*/
|
|
22
|
+
renderMention?: (ref: MentionRef<TKind>) => ReactNode;
|
|
23
|
+
/** Optional className applied to the wrapping `<span>`. */
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function MessageText<TKind extends string = string>(props: MessageTextProps<TKind>): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
//# sourceMappingURL=MessageText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageText.d.ts","sourceRoot":"","sources":["../../src/mentions/MessageText.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAG7D,MAAM,WAAW,gBAAgB,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IAC7D,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC;IACtD,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAUD,wBAAgB,WAAW,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,2CAiBxF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { parseMentionTokens } from '@ttt-productions/chat-core';
|
|
4
|
+
function DefaultMentionChip({ ref }) {
|
|
5
|
+
return (_jsxs("span", { className: "chat-mention-chip", "data-mention-kind": ref.kind, "data-mention-id": ref.id, children: ["@", ref.displayText] }));
|
|
6
|
+
}
|
|
7
|
+
export function MessageText(props) {
|
|
8
|
+
const { text, renderMention, className } = props;
|
|
9
|
+
const segments = parseMentionTokens(text);
|
|
10
|
+
return (_jsx("span", { className: className, children: segments.map((seg, i) => {
|
|
11
|
+
if (seg.type === 'text') {
|
|
12
|
+
return _jsx("span", { children: seg.text }, i);
|
|
13
|
+
}
|
|
14
|
+
if (renderMention) {
|
|
15
|
+
return _jsx("span", { children: renderMention(seg.ref) }, i);
|
|
16
|
+
}
|
|
17
|
+
return _jsx(DefaultMentionChip, { ref: seg.ref }, i);
|
|
18
|
+
}) }));
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=MessageText.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageText.js","sourceRoot":"","sources":["../../src/mentions/MessageText.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAeb,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAgBhE,SAAS,kBAAkB,CAAuB,EAAE,GAAG,EAA8B;IACnF,OAAO,CACL,gBAAM,SAAS,EAAC,mBAAmB,uBAAoB,GAAG,CAAC,IAAI,qBAAmB,GAAG,CAAC,EAAE,kBACpF,GAAG,CAAC,WAAW,IACZ,CACR,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAgC,KAA8B;IACvF,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IACjD,MAAM,QAAQ,GAAG,kBAAkB,CAAQ,IAAI,CAAC,CAAC;IAEjD,OAAO,CACL,eAAM,SAAS,EAAE,SAAS,YACvB,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;YACvB,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACxB,OAAO,yBAAe,GAAG,CAAC,IAAI,IAAZ,CAAC,CAAmB,CAAC;YACzC,CAAC;YACD,IAAI,aAAa,EAAE,CAAC;gBAClB,OAAO,yBAAe,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,IAA1B,CAAC,CAAiC,CAAC;YACvD,CAAC;YACD,OAAO,KAAC,kBAAkB,IAAS,GAAG,EAAE,GAAG,CAAC,GAAG,IAAf,CAAC,CAAkB,CAAC;QACtD,CAAC,CAAC,GACG,CACR,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composer-side mention autocomplete state hook.
|
|
3
|
+
*
|
|
4
|
+
* Detects when the user types the trigger character (default `@`) inside the
|
|
5
|
+
* provided textarea, captures the live query (the text between the trigger
|
|
6
|
+
* and the cursor), debounces searches across all configured providers, and
|
|
7
|
+
* tracks the anchor positions of inserted mentions so the composer can
|
|
8
|
+
* convert display text back to wire tokens on send.
|
|
9
|
+
*
|
|
10
|
+
* Returns an `AutocompleteState` value tailored for the `<MentionAutocomplete>`
|
|
11
|
+
* UI component, plus an `insertMention` action and a `getValueWithTokens`
|
|
12
|
+
* accessor the composer calls on send.
|
|
13
|
+
*
|
|
14
|
+
* Generic over `TKind` and `TContext`; chat-core inspects neither.
|
|
15
|
+
*/
|
|
16
|
+
import { type RefObject } from 'react';
|
|
17
|
+
import type { MentionAnchor, MentionRef, RecentMentionsAdapter } from '@ttt-productions/chat-core';
|
|
18
|
+
import type { RenderableMentionProvider } from '../types.js';
|
|
19
|
+
export type AutocompleteResultGroup<TKind extends string = string> = {
|
|
20
|
+
kind: TKind;
|
|
21
|
+
label: string;
|
|
22
|
+
results: MentionRef<TKind>[];
|
|
23
|
+
/** True if `search` is in flight for this provider. */
|
|
24
|
+
loading: boolean;
|
|
25
|
+
/** Provider-supplied custom renderer for individual results, if any. */
|
|
26
|
+
renderResult?: (ref: MentionRef<TKind>) => import('react').ReactNode;
|
|
27
|
+
};
|
|
28
|
+
export type AutocompleteState<TKind extends string = string> = {
|
|
29
|
+
/** True iff the dropdown should be visible. */
|
|
30
|
+
open: boolean;
|
|
31
|
+
/** Current query text (everything after the trigger up to the cursor). */
|
|
32
|
+
query: string;
|
|
33
|
+
/** Results grouped by provider, in the order the consumer passed providers. */
|
|
34
|
+
groups: AutocompleteResultGroup<TKind>[];
|
|
35
|
+
/** When true, groups are recent picks (`getRecent`) rather than search results. */
|
|
36
|
+
showingRecent: boolean;
|
|
37
|
+
/** Index of the currently highlighted result, flattened across all groups. */
|
|
38
|
+
highlightedIndex: number;
|
|
39
|
+
/** Flat array of all results in display order. Used internally for keyboard nav. */
|
|
40
|
+
flatResults: MentionRef<TKind>[];
|
|
41
|
+
};
|
|
42
|
+
export interface UseMentionAutocompleteArgs<TKind extends string, TContext> {
|
|
43
|
+
textareaRef: RefObject<HTMLTextAreaElement | null>;
|
|
44
|
+
/** Current textarea value (controlled). */
|
|
45
|
+
value: string;
|
|
46
|
+
/** Setter for the textarea value. */
|
|
47
|
+
onChange: (next: string) => void;
|
|
48
|
+
/** Providers in display order. Empty list disables the system entirely. */
|
|
49
|
+
providers: RenderableMentionProvider<TKind, TContext>[];
|
|
50
|
+
/** Consumer-controlled context passed to each provider's `search`. */
|
|
51
|
+
context: TContext;
|
|
52
|
+
/** Optional recent-mentions adapter. */
|
|
53
|
+
recent?: RecentMentionsAdapter<TKind>;
|
|
54
|
+
/** Trigger character. Defaults to `'@'`. Must be exactly one character. */
|
|
55
|
+
trigger?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Minimum query length to fire `search`. Below this, recent picks are shown
|
|
58
|
+
* (if `recent` is supplied) or the dropdown stays empty. Default: 0 (search
|
|
59
|
+
* fires immediately on trigger).
|
|
60
|
+
*/
|
|
61
|
+
minQueryLength?: number;
|
|
62
|
+
/** Debounce window in ms for search calls. Default: 200. */
|
|
63
|
+
searchDebounceMs?: number;
|
|
64
|
+
}
|
|
65
|
+
export interface UseMentionAutocompleteResult<TKind extends string = string> {
|
|
66
|
+
state: AutocompleteState<TKind>;
|
|
67
|
+
/** Handle keydown from the textarea — returns true if the event was handled. */
|
|
68
|
+
handleKeyDown: (event: import('react').KeyboardEvent<HTMLTextAreaElement>) => boolean;
|
|
69
|
+
/** Programmatically insert a mention at the trigger position (called by the autocomplete UI). */
|
|
70
|
+
insertMention: (ref: MentionRef<TKind>) => void;
|
|
71
|
+
/**
|
|
72
|
+
* Returns the current textarea value with all tracked mention anchors
|
|
73
|
+
* substituted back to wire tokens. Call this from the composer's send path.
|
|
74
|
+
*/
|
|
75
|
+
getValueWithTokens: () => string;
|
|
76
|
+
/** Anchor list — exposed for tests and consumers that need the raw positions. */
|
|
77
|
+
anchors: MentionAnchor<TKind>[];
|
|
78
|
+
/** Close the dropdown imperatively (e.g. on send). */
|
|
79
|
+
close: () => void;
|
|
80
|
+
}
|
|
81
|
+
export declare function useMentionAutocomplete<TKind extends string, TContext>(args: UseMentionAutocompleteArgs<TKind, TContext>): UseMentionAutocompleteResult<TKind>;
|
|
82
|
+
//# sourceMappingURL=use-mention-autocomplete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-mention-autocomplete.d.ts","sourceRoot":"","sources":["../../src/mentions/use-mention-autocomplete.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAqD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE1F,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,qBAAqB,EACtB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,MAAM,uBAAuB,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,IAAI;IACnE,IAAI,EAAE,KAAK,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;IAC7B,uDAAuD;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,OAAO,OAAO,EAAE,SAAS,CAAC;CACtE,CAAC;AAEF,MAAM,MAAM,iBAAiB,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,IAAI;IAC7D,+CAA+C;IAC/C,IAAI,EAAE,OAAO,CAAC;IACd,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,MAAM,EAAE,uBAAuB,CAAC,KAAK,CAAC,EAAE,CAAC;IACzC,mFAAmF;IACnF,aAAa,EAAE,OAAO,CAAC;IACvB,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,WAAW,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;CAClC,CAAC;AAEF,MAAM,WAAW,0BAA0B,CAAC,KAAK,SAAS,MAAM,EAAE,QAAQ;IACxE,WAAW,EAAE,SAAS,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;IACnD,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,2EAA2E;IAC3E,SAAS,EAAE,yBAAyB,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;IACxD,sEAAsE;IACtE,OAAO,EAAE,QAAQ,CAAC;IAClB,wCAAwC;IACxC,MAAM,CAAC,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAC;IACtC,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,4BAA4B,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IACzE,KAAK,EAAE,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAChC,gFAAgF;IAChF,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,OAAO,EAAE,aAAa,CAAC,mBAAmB,CAAC,KAAK,OAAO,CAAC;IACtF,iGAAiG;IACjG,aAAa,EAAE,CAAC,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;IAChD;;;OAGG;IACH,kBAAkB,EAAE,MAAM,MAAM,CAAC;IACjC,iFAAiF;IACjF,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;IAChC,sDAAsD;IACtD,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAMD,wBAAgB,sBAAsB,CAAC,KAAK,SAAS,MAAM,EAAE,QAAQ,EACnE,IAAI,EAAE,0BAA0B,CAAC,KAAK,EAAE,QAAQ,CAAC,GAChD,4BAA4B,CAAC,KAAK,CAAC,CAgVrC"}
|