@droppii-org/chat-sdk 0.7.16 → 0.7.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/components/canned-response/CannedResponsePanel.d.ts.map +1 -1
  2. package/dist/components/canned-response/CannedResponsePanel.js +10 -3
  3. package/dist/components/canned-response/insertCannedMedia.d.ts +18 -0
  4. package/dist/components/canned-response/insertCannedMedia.d.ts.map +1 -0
  5. package/dist/components/canned-response/insertCannedMedia.js +101 -0
  6. package/dist/components/canned-response/useInsertCannedReply.d.ts +3 -0
  7. package/dist/components/canned-response/useInsertCannedReply.d.ts.map +1 -0
  8. package/dist/components/canned-response/useInsertCannedReply.js +33 -0
  9. package/dist/components/media-collection/ImageCollection.d.ts.map +1 -1
  10. package/dist/components/media-collection/ImageCollection.js +3 -0
  11. package/dist/components/media-resources/ImageResourceGrid.d.ts.map +1 -1
  12. package/dist/components/media-resources/ImageResourceGrid.js +3 -0
  13. package/dist/components/message/footer/MediaActions.d.ts +2 -1
  14. package/dist/components/message/footer/MediaActions.d.ts.map +1 -1
  15. package/dist/components/message/footer/MediaActions.js +5 -38
  16. package/dist/components/message/footer/MediaUploadButtons.d.ts +12 -0
  17. package/dist/components/message/footer/MediaUploadButtons.d.ts.map +1 -0
  18. package/dist/components/message/footer/MediaUploadButtons.js +38 -0
  19. package/dist/components/message/footer/mergePickedUploadFiles.d.ts +9 -0
  20. package/dist/components/message/footer/mergePickedUploadFiles.d.ts.map +1 -0
  21. package/dist/components/message/footer/mergePickedUploadFiles.js +20 -0
  22. package/dist/components/message/item/MergeImageMessage.d.ts.map +1 -1
  23. package/dist/components/message/item/MergeImageMessage.js +3 -0
  24. package/dist/components/rich-text-editor/RichTextEditor.d.ts +8 -1
  25. package/dist/components/rich-text-editor/RichTextEditor.d.ts.map +1 -1
  26. package/dist/components/rich-text-editor/RichTextEditor.js +9 -9
  27. package/dist/components/thread/SessionTagPicker.d.ts.map +1 -1
  28. package/dist/components/thread/SessionTagPicker.js +2 -1
  29. package/dist/hooks/common/usePreviewGroupContainer.d.ts +2 -0
  30. package/dist/hooks/common/usePreviewGroupContainer.d.ts.map +1 -0
  31. package/dist/hooks/common/usePreviewGroupContainer.js +14 -0
  32. package/dist/index.d.ts +4 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +2 -0
  35. package/dist/locales/vi/common.json +5 -1
  36. package/dist/styles/global.css +1 -1
  37. package/dist/types/chat.d.ts +6 -1
  38. package/dist/types/chat.d.ts.map +1 -1
  39. package/dist/types/dto.d.ts +14 -0
  40. package/dist/types/dto.d.ts.map +1 -1
  41. package/dist/utils/antdReact19Render.d.ts +3 -0
  42. package/dist/utils/antdReact19Render.d.ts.map +1 -0
  43. package/dist/utils/antdReact19Render.js +25 -0
  44. package/dist/utils/mediaUrl.d.ts +3 -0
  45. package/dist/utils/mediaUrl.d.ts.map +1 -0
  46. package/dist/utils/mediaUrl.js +3 -0
  47. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"CannedResponsePanel.d.ts","sourceRoot":"","sources":["../../../src/components/canned-response/CannedResponsePanel.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAE,mBAAmB,EAA8B,MAAM,cAAc,CAAC;AAQ/E,UAAU,wBAAwB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACnD;AAaD,QAAA,MAAM,mBAAmB,GAAI,oCAI1B,wBAAwB,4CAmJ1B,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"CannedResponsePanel.d.ts","sourceRoot":"","sources":["../../../src/components/canned-response/CannedResponsePanel.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAE,mBAAmB,EAA8B,MAAM,cAAc,CAAC;AAQ/E,UAAU,wBAAwB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;CACnD;AAaD,QAAA,MAAM,mBAAmB,GAAI,oCAI1B,wBAAwB,4CAwJ1B,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -12,7 +12,7 @@ import { useMessageFooterContext } from "../../components/message/footer";
12
12
  import { CannedResponseVisibleScope } from "../../types/chat";
13
13
  import { sanitizeHtml } from "../../utils/common";
14
14
  import emitter from "../../utils/events";
15
- import { insertCannedContent } from "./insertCannedContent";
15
+ import { useInsertCannedReply } from "./useInsertCannedReply";
16
16
  import { buildCannedScopes } from "./cannedScopes";
17
17
  import CannedScopeChips, { ALL_SCOPES_KEY } from "./CannedScopeChips";
18
18
  import CannedResponseRow from "./CannedResponseRow";
@@ -35,7 +35,14 @@ const CannedResponsePanel = ({ query, viewedTeamId, onCreate, }) => {
35
35
  const activeScopes = useMemo(() => (filter === ALL_SCOPES_KEY ? scopes : scopes.filter((s) => s.key === filter)), [scopes, filter]);
36
36
  const { rows, isLoading, isError } = useSearchCannedAcrossScopes(activeScopes, debouncedQuery);
37
37
  const settling = query !== debouncedQuery;
38
- const insertRow = useCallback((row) => insertCannedContent(editor, sanitizeHtml(row.content || "")), [editor]);
38
+ const insertReply = useInsertCannedReply();
39
+ const insertRow = useCallback((row) => {
40
+ var _a, _b;
41
+ return insertReply(sanitizeHtml(row.content || ""), {
42
+ images: (_a = row.images) !== null && _a !== void 0 ? _a : [],
43
+ files: (_b = row.files) !== null && _b !== void 0 ? _b : [],
44
+ });
45
+ }, [insertReply]);
39
46
  useEffect(() => {
40
47
  const onSelect = (index) => {
41
48
  const row = index === null ? undefined : rows[Math.min(index, rows.length - 1)];
@@ -60,7 +67,7 @@ const CannedResponsePanel = ({ query, viewedTeamId, onCreate, }) => {
60
67
  onCreate === null || onCreate === void 0 ? void 0 : onCreate({
61
68
  visibilityScope: isPersonal ? "PRIVATE" : "TEAM",
62
69
  teamId: isPersonal ? undefined : (_a = scope === null || scope === void 0 ? void 0 : scope.teamId) !== null && _a !== void 0 ? _a : viewedTeamId,
63
- onCreated: (content) => editor.focus(() => insertCannedContent(editor, sanitizeHtml(content))),
70
+ onCreated: (content, media) => editor.focus(() => insertReply(sanitizeHtml(content), media)),
64
71
  });
65
72
  };
66
73
  const noQuery = query.length === 0;
@@ -0,0 +1,18 @@
1
+ import { type UploadFile } from "antd";
2
+ import type { TFunction } from "i18next";
3
+ import type { CannedMediaPayload } from "../../types/chat";
4
+ import type { ICannedMedia } from "../../types/dto";
5
+ export declare const cannedMediaToFile: (m: ICannedMedia) => Promise<File>;
6
+ export declare const mergeCannedUploads: (prev: UploadFile[], incoming: UploadFile[]) => {
7
+ list: UploadFile[];
8
+ skippedDoc: boolean;
9
+ };
10
+ interface InsertCannedMediaParams {
11
+ media?: CannedMediaPayload;
12
+ current: UploadFile[];
13
+ setFiles: (updater: (prev: UploadFile[]) => UploadFile[]) => void;
14
+ t: TFunction;
15
+ }
16
+ export declare const insertCannedMedia: ({ media, current, setFiles, t, }: InsertCannedMediaParams) => Promise<void>;
17
+ export {};
18
+ //# sourceMappingURL=insertCannedMedia.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insertCannedMedia.d.ts","sourceRoot":"","sources":["../../../src/components/canned-response/insertCannedMedia.ts"],"names":[],"mappings":"AAAA,OAAO,EAA0B,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAchD,eAAO,MAAM,iBAAiB,GAAU,GAAG,YAAY,KAAG,OAAO,CAAC,IAAI,CAuBrE,CAAC;AAGF,eAAO,MAAM,kBAAkB,GAC7B,MAAM,UAAU,EAAE,EAClB,UAAU,UAAU,EAAE,KACrB;IAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAa3C,CAAC;AAEF,UAAU,uBAAuB;IAC/B,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,QAAQ,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,IAAI,CAAC;IAClE,CAAC,EAAE,SAAS,CAAC;CACd;AAED,eAAO,MAAM,iBAAiB,GAAU,kCAKrC,uBAAuB,kBAkDzB,CAAC"}
@@ -0,0 +1,101 @@
1
+ import { message as antdMessage } from "antd";
2
+ import { basename, isSafeMediaUrl } from "../../utils/mediaUrl";
3
+ import { createUploadFile, DOCUMENT_MIME_TYPES, MAX_DOCUMENT_SIZE_MB, MAX_IMAGE_SIZE_MB, } from "../../utils/fileValidation";
4
+ const DOWNLOAD_TIMEOUT_MS = 30000;
5
+ const isDocument = (type) => DOCUMENT_MIME_TYPES.includes(type || "");
6
+ // Downloads a saved canned attachment into a File so the normal send pipeline can upload it.
7
+ export const cannedMediaToFile = async (m) => {
8
+ var _a, _b;
9
+ const maxMb = isDocument(m.contentType) ? MAX_DOCUMENT_SIZE_MB : MAX_IMAGE_SIZE_MB;
10
+ const tooBig = (bytes) => !!bytes && bytes / 1024 / 1024 > maxMb;
11
+ if (tooBig(m.fileSize)) {
12
+ throw new Error(`Canned media ${m.fullUrl} exceeds ${maxMb}MB`);
13
+ }
14
+ const controller = new AbortController();
15
+ const timer = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS);
16
+ let blob;
17
+ try {
18
+ const res = await fetch(m.fullUrl, { signal: controller.signal });
19
+ if (!res.ok)
20
+ throw new Error(`Fetch ${m.fullUrl} failed: ${res.status}`);
21
+ blob = await res.blob();
22
+ }
23
+ finally {
24
+ clearTimeout(timer);
25
+ }
26
+ // fileSize is optional on saved media, so re-check what was actually downloaded.
27
+ if (tooBig(blob.size)) {
28
+ throw new Error(`Canned media ${m.fullUrl} exceeds ${maxMb}MB`);
29
+ }
30
+ return new File([blob], (_b = (_a = m.fileName) !== null && _a !== void 0 ? _a : m.name) !== null && _b !== void 0 ? _b : basename(m.streamId), {
31
+ type: m.contentType || blob.type,
32
+ });
33
+ };
34
+ // Appends incoming uploads; keeps at most one document and never replaces the agent's own.
35
+ export const mergeCannedUploads = (prev, incoming) => {
36
+ let hasDoc = prev.some((f) => isDocument(f.type));
37
+ let skippedDoc = false;
38
+ const accepted = incoming.filter((f) => {
39
+ if (!isDocument(f.type))
40
+ return true;
41
+ if (hasDoc) {
42
+ skippedDoc = true;
43
+ return false;
44
+ }
45
+ hasDoc = true;
46
+ return true;
47
+ });
48
+ return { list: [...prev, ...accepted], skippedDoc };
49
+ };
50
+ export const insertCannedMedia = async ({ media, current, setFiles, t, }) => {
51
+ var _a, _b;
52
+ const loadingKey = `canned-media-loading-${Date.now()}-${Math.random()}`;
53
+ try {
54
+ const items = [...((_a = media === null || media === void 0 ? void 0 : media.images) !== null && _a !== void 0 ? _a : []), ...((_b = media === null || media === void 0 ? void 0 : media.files) !== null && _b !== void 0 ? _b : [])].filter((m) => m && isSafeMediaUrl(m.fullUrl));
55
+ if (!items.length)
56
+ return;
57
+ // Skip surplus docs before downloading; the updater re-checks against the live list.
58
+ let hasDoc = current.some((f) => isDocument(f.type));
59
+ let skippedDoc = false;
60
+ const toFetch = items.filter((m) => {
61
+ if (!isDocument(m.contentType))
62
+ return true;
63
+ if (hasDoc) {
64
+ skippedDoc = true;
65
+ return false;
66
+ }
67
+ hasDoc = true;
68
+ return true;
69
+ });
70
+ if (skippedDoc)
71
+ antdMessage.warning(t("canned_media_doc_limit"));
72
+ if (!toFetch.length)
73
+ return;
74
+ antdMessage.loading({ content: t("canned_media_loading"), key: loadingKey, duration: 0 });
75
+ const results = await Promise.allSettled(toFetch.map(cannedMediaToFile));
76
+ const uploads = results.flatMap((r) => r.status === "fulfilled" ? [createUploadFile(r.value)] : []);
77
+ if (uploads.length) {
78
+ let warned = false;
79
+ setFiles((prev) => {
80
+ const merged = mergeCannedUploads(prev, uploads);
81
+ // The agent may have attached a document while downloading; updater can run twice.
82
+ if (merged.skippedDoc && !warned) {
83
+ warned = true;
84
+ antdMessage.warning(t("canned_media_doc_limit"));
85
+ }
86
+ return merged.list;
87
+ });
88
+ }
89
+ if (uploads.length < results.length) {
90
+ results.forEach((r) => r.status === "rejected" && console.error(r.reason));
91
+ antdMessage.error(t("canned_media_load_failed"));
92
+ }
93
+ }
94
+ catch (error) {
95
+ console.error("Failed to insert canned media:", error);
96
+ antdMessage.error(t("canned_media_load_failed"));
97
+ }
98
+ finally {
99
+ antdMessage.destroy(loadingKey);
100
+ }
101
+ };
@@ -0,0 +1,3 @@
1
+ import type { CannedMediaPayload } from "../../types/chat";
2
+ export declare const useInsertCannedReply: () => (content: string, media?: CannedMediaPayload) => void;
3
+ //# sourceMappingURL=useInsertCannedReply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useInsertCannedReply.d.ts","sourceRoot":"","sources":["../../../src/components/canned-response/useInsertCannedReply.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAKvD,eAAO,MAAM,oBAAoB,kBAOnB,MAAM,UAAU,kBAAkB,SAqB/C,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { useCallback } from "react";
2
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
3
+ import { useLatest } from "ahooks";
4
+ import { useTranslation } from "react-i18next";
5
+ import { useMessageFooterContext } from "../../components/message/footer";
6
+ import useConversationStore from "../../store/conversation";
7
+ import { insertCannedContent } from "./insertCannedContent";
8
+ import { insertCannedMedia } from "./insertCannedMedia";
9
+ // Inserts the reply text now, then appends its images/files to the composer as they download.
10
+ export const useInsertCannedReply = () => {
11
+ const { t } = useTranslation();
12
+ const [editor] = useLexicalComposerContext();
13
+ const { listUploadFiles, setListUploadFiles } = useMessageFooterContext();
14
+ const filesRef = useLatest(listUploadFiles);
15
+ return useCallback((content, media) => {
16
+ var _a, _b;
17
+ insertCannedContent(editor, content);
18
+ // The footer is not remounted on conversation switch, so drop late downloads for another thread.
19
+ const conversationId = (_a = useConversationStore.getState().conversationData) === null || _a === void 0 ? void 0 : _a.conversationID;
20
+ void insertCannedMedia({
21
+ media,
22
+ current: (_b = filesRef.current) !== null && _b !== void 0 ? _b : [],
23
+ setFiles: (updater) => {
24
+ var _a;
25
+ if (((_a = useConversationStore.getState().conversationData) === null || _a === void 0 ? void 0 : _a.conversationID) !==
26
+ conversationId)
27
+ return;
28
+ setListUploadFiles(updater);
29
+ },
30
+ t,
31
+ });
32
+ }, [editor, filesRef, setListUploadFiles, t]);
33
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"ImageCollection.d.ts","sourceRoot":"","sources":["../../../src/components/media-collection/ImageCollection.tsx"],"names":[],"mappings":"AAYA,QAAA,MAAM,eAAe,+CAyFpB,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"ImageCollection.d.ts","sourceRoot":"","sources":["../../../src/components/media-collection/ImageCollection.tsx"],"names":[],"mappings":"AAaA,QAAA,MAAM,eAAe,+CA2FpB,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -9,8 +9,10 @@ import useConversationStore from "../../store/conversation";
9
9
  import { images } from "../../constants/images";
10
10
  import { useTranslation } from "react-i18next";
11
11
  import { renderImagePreviewToolbar } from "../../components/message/item/imagePreviewToolbar";
12
+ import { usePreviewGroupContainer } from "../../hooks/common/usePreviewGroupContainer";
12
13
  const ImageCollection = () => {
13
14
  const { t } = useTranslation();
15
+ const getPreviewContainer = usePreviewGroupContainer();
14
16
  const selectedSourceId = useConversationStore((state) => state.selectedSourceId);
15
17
  const { groupedData, fetchNextPage, hasNextPage, dataFlatten, isLoading } = useSearchMessage({
16
18
  payload: {
@@ -32,6 +34,7 @@ const ImageCollection = () => {
32
34
  if (isLoading)
33
35
  return (_jsx("div", { className: "flex items-center justify-center", children: _jsx(Spin, {}) }));
34
36
  return (_jsx("div", { id: "scrollableImageDiv", "data-cp-name": "ImageCollection", className: "h-full overflow-auto", children: _jsx(Image.PreviewGroup, { preview: {
37
+ getContainer: getPreviewContainer,
35
38
  toolbarRender: (originalNode, info) => { var _a; return renderImagePreviewToolbar(originalNode, (_a = info === null || info === void 0 ? void 0 : info.image) === null || _a === void 0 ? void 0 : _a.url, t); },
36
39
  }, children: _jsx(InfiniteScroll, { dataLength: dataFlatten.length, next: fetchNextPage, hasMore: hasNextPage, loader: _jsx("div", { className: "flex items-center justify-center py-2", children: _jsx(Spin, {}) }), scrollableTarget: "scrollableImageDiv", children: Object.entries(groupedData).map(([date, items]) => renderItem(date, items)) }) }) }));
37
40
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ImageResourceGrid.d.ts","sourceRoot":"","sources":["../../../src/components/media-resources/ImageResourceGrid.tsx"],"names":[],"mappings":"AAYA,QAAA,MAAM,iBAAiB,+CAgDtB,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"ImageResourceGrid.d.ts","sourceRoot":"","sources":["../../../src/components/media-resources/ImageResourceGrid.tsx"],"names":[],"mappings":"AAaA,QAAA,MAAM,iBAAiB,+CAkDtB,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -4,14 +4,17 @@ import { Image, Spin } from "antd";
4
4
  import { useTranslation } from "react-i18next";
5
5
  import { images } from "../../constants/images";
6
6
  import { renderImagePreviewToolbar } from "../../components/message/item/imagePreviewToolbar";
7
+ import { usePreviewGroupContainer } from "../../hooks/common/usePreviewGroupContainer";
7
8
  import { parseMessageContent } from "../../utils/common";
8
9
  import { MediaResourceTabKey } from "./constants";
9
10
  import ResourceScrollContainer from "./ResourceScrollContainer";
10
11
  import { useResourceTab } from "./useResourceTab";
11
12
  const ImageResourceGrid = () => {
12
13
  const { t } = useTranslation();
14
+ const getPreviewContainer = usePreviewGroupContainer();
13
15
  const { items, containerProps } = useResourceTab(MediaResourceTabKey.Image);
14
16
  return (_jsx(ResourceScrollContainer, Object.assign({ "data-cp-name": "ImageResourceGrid" }, containerProps, { itemCount: items.length, children: _jsx(Image.PreviewGroup, { preview: {
17
+ getContainer: getPreviewContainer,
15
18
  toolbarRender: (originalNode, info) => { var _a; return renderImagePreviewToolbar(originalNode, (_a = info === null || info === void 0 ? void 0 : info.image) === null || _a === void 0 ? void 0 : _a.url, t); },
16
19
  }, children: _jsx("div", { className: "grid grid-cols-3 gap-2 p-3", children: items.map((item) => {
17
20
  var _a, _b, _c, _d;
@@ -6,7 +6,8 @@ interface MediaActionsProps {
6
6
  showMediaUpload?: boolean;
7
7
  crmComposer?: boolean;
8
8
  hideEmojiOnPhone?: boolean;
9
+ acceptVideo?: boolean;
9
10
  }
10
- export default function MediaActions({ listUploadFiles, onFilesChange, buttonClassName, showMediaUpload, crmComposer, hideEmojiOnPhone, }: MediaActionsProps): import("react/jsx-runtime").JSX.Element;
11
+ export default function MediaActions({ listUploadFiles, onFilesChange, buttonClassName, showMediaUpload, crmComposer, hideEmojiOnPhone, acceptVideo, }: MediaActionsProps): import("react/jsx-runtime").JSX.Element;
11
12
  export {};
12
13
  //# sourceMappingURL=MediaActions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MediaActions.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/MediaActions.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAkB,UAAU,EAA0B,MAAM,MAAM,CAAC;AAuD1E,UAAU,iBAAiB;IACzB,eAAe,EAAE,UAAU,EAAE,CAAC;IAC9B,aAAa,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,eAAe,EACf,aAAa,EACb,eAAe,EACf,eAAsB,EACtB,WAAmB,EACnB,gBAAwB,GACzB,EAAE,iBAAiB,2CA+LnB"}
1
+ {"version":3,"file":"MediaActions.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/MediaActions.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAU,UAAU,EAAE,MAAM,MAAM,CAAC;AA8B1C,UAAU,iBAAiB;IACzB,eAAe,EAAE,UAAU,EAAE,CAAC;IAC9B,aAAa,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;IAC7C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,eAAe,EACf,aAAa,EACb,eAAe,EACf,eAAsB,EACtB,WAAmB,EACnB,gBAAwB,EACxB,WAAkB,GACnB,EAAE,iBAAiB,2CA4HnB"}
@@ -1,24 +1,21 @@
1
1
  "use client";
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useRef, useState } from "react";
4
4
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
5
5
  import { $getSelection, $isRangeSelection } from "lexical";
6
- import { Button, Upload, message as antdMessage } from "antd";
6
+ import { Button } from "antd";
7
7
  import clsx from "clsx";
8
8
  import { useTranslation } from "react-i18next";
9
9
  import EmojiPicker from "./EmojiPicker";
10
10
  import { useEscapeLayer } from "../../../hooks/common/useEscapeLayer";
11
11
  import { useCloseOnSidePanelOpen } from "../../../hooks/common/useCloseOnSidePanelOpen";
12
- import { Icon } from "../../../components/icon";
13
- import { createUploadFile, validateFile, } from "../../../utils/fileValidation";
14
- import { DOCUMENT_TYPES } from "./editorConfig";
12
+ import MediaUploadButtons from "./MediaUploadButtons";
15
13
  import CannedResponseIcon from "../../../assets/svg/canned-response";
16
14
  import { useMessageFooterContext } from ".";
17
15
  import useAuthStore from "../../../store/auth";
18
16
  import { StrokeIcon } from "../../../components/icon/StrokeIcon";
19
17
  const EmojiIcon = (_jsx("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M7.59997 12.4C7.59997 12.4 8.49997 13.2 9.99997 13.2C11.5 13.2 12.4 12.4 12.4 12.4M14 8.39997C14 8.8418 13.6418 9.19997 13.2 9.19997C12.7581 9.19997 12.4 8.8418 12.4 8.39997C12.4 7.95814 12.7581 7.59997 13.2 7.59997C13.6418 7.59997 14 7.95814 14 8.39997ZM18 9.99997C18 14.4182 14.4182 18 9.99997 18C5.58169 18 1.99997 14.4182 1.99997 9.99997C1.99997 5.58169 5.58169 1.99997 9.99997 1.99997C14.4182 1.99997 18 5.58169 18 9.99997ZM7.59997 8.39997C7.59997 8.8418 7.2418 9.19997 6.79997 9.19997C6.35814 9.19997 5.99997 8.8418 5.99997 8.39997C5.99997 7.95814 6.35814 7.59997 6.79997 7.59997C7.2418 7.59997 7.59997 7.95814 7.59997 8.39997Z", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }));
20
- const AttachIcon = (_jsx("svg", { width: "22", height: "22", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M15.6 10.0001V11.2001C15.6 14.5137 12.9137 17.2001 9.59998 17.2001C6.28626 17.2001 3.59998 14.5137 3.59998 11.2001V6.79999C3.59998 4.59085 5.39084 2.79999 7.59998 2.79999C9.8091 2.79999 11.6 4.59085 11.6 6.79999V11.2C11.6 12.3045 10.7045 13.2 9.59998 13.2C8.49542 13.2 7.59998 12.3045 7.59998 11.2V7.99999", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }));
21
- export default function MediaActions({ listUploadFiles, onFilesChange, buttonClassName, showMediaUpload = true, crmComposer = false, hideEmojiOnPhone = false, }) {
18
+ export default function MediaActions({ listUploadFiles, onFilesChange, buttonClassName, showMediaUpload = true, crmComposer = false, hideEmojiOnPhone = false, acceptVideo = true, }) {
22
19
  const [editor] = useLexicalComposerContext();
23
20
  const { t } = useTranslation();
24
21
  const [showEmojiPicker, setShowEmojiPicker] = useState(false);
@@ -40,36 +37,6 @@ export default function MediaActions({ listUploadFiles, onFilesChange, buttonCla
40
37
  });
41
38
  setShowEmojiPicker(false);
42
39
  }, [editor]);
43
- const beforeUploadImagesAndVideo = (file) => {
44
- const validation = validateFile(file, t);
45
- if (!validation.isValid) {
46
- antdMessage.error(validation.error);
47
- return Upload.LIST_IGNORE;
48
- }
49
- return false;
50
- };
51
- const beforeUploadFile = (file) => {
52
- const isAllowed = DOCUMENT_TYPES.includes(file.type);
53
- if (!isAllowed) {
54
- antdMessage.error(t("invalid_document_format", { fileName: file.name }));
55
- return Upload.LIST_IGNORE;
56
- }
57
- return false;
58
- };
59
- const handleChange = (info) => {
60
- let newList = [...info.fileList];
61
- const lastFile = info.file;
62
- if (DOCUMENT_TYPES.includes(lastFile.type || "")) {
63
- newList = newList.filter((f) => !DOCUMENT_TYPES.includes(f.type || ""));
64
- newList.push(lastFile);
65
- }
66
- const normalizedList = newList.map((file) => {
67
- var _a;
68
- const originFile = ((_a = file.originFileObj) !== null && _a !== void 0 ? _a : file);
69
- return Object.assign(Object.assign({}, createUploadFile(originFile)), { uid: file.uid });
70
- });
71
- onFilesChange(normalizedList);
72
- };
73
40
  const onPressCanned = useCallback(async () => {
74
41
  if (!isOpenCanned) {
75
42
  editor.focus();
@@ -105,5 +72,5 @@ export default function MediaActions({ listUploadFiles, onFilesChange, buttonCla
105
72
  return (_jsxs("div", { "data-cp-name": "MediaActions", className: clsx("flex items-center relative", crmComposer ? "gap-0.5" : "gap-1"), ref: containerRef, children: [isCrm && (_jsx(Button, Object.assign({ type: "text", shape: "default", className: clsx(btnClass, isOpenCanned && activeCls), onClick: onPressCanned }, crmLabel("composer_quick_reply"), { children: crmComposer ? (_jsx(StrokeIcon, { name: "hash", size: 18 })) : (_jsx(CannedResponseIcon, { className: clsx("hover:text-gray-700", isOpenCanned && "text-blue-600") })) }))), _jsx(Button, Object.assign({ type: "text", shape: "default", className: clsx(btnClass, hideEmojiOnPhone && "phone:!hidden", showEmojiPicker &&
106
73
  (crmComposer
107
74
  ? activeCls
108
- : "bg-[var(--dc-color-primary-light)] text-[var(--dc-color-primary)]")), onClick: () => setShowEmojiPicker(!showEmojiPicker) }, crmLabel("composer_emoji"), { children: crmComposer ? _jsx(StrokeIcon, { name: "smile", size: 18 }) : EmojiIcon })), showMediaUpload && (_jsxs(_Fragment, { children: [_jsx(Upload, { accept: "image/jpeg, image/png, image/jpg, video/*", beforeUpload: beforeUploadImagesAndVideo, multiple: true, onChange: handleChange, showUploadList: false, fileList: listUploadFiles, children: _jsx(Button, Object.assign({ type: "text", shape: "default", className: btnClass }, crmLabel("composer_attach_image"), { children: crmComposer ? (_jsx(StrokeIcon, { name: "image", size: 18 })) : (_jsx(Icon, { icon: "image-02-o", size: 22 })) })) }), _jsx(Upload, { accept: ".doc,.docx,.pdf", beforeUpload: beforeUploadFile, onChange: handleChange, showUploadList: false, fileList: listUploadFiles, children: _jsx(Button, Object.assign({ type: "text", shape: "default", className: btnClass }, crmLabel("composer_attach_file"), { children: crmComposer ? _jsx(StrokeIcon, { name: "paperclip", size: 18 }) : AttachIcon })) })] })), showEmojiPicker && (_jsx(EmojiPicker, { className: crmComposer ? "max-lg:w-80" : undefined, onEmojiSelect: handleEmojiSelect, onClose: () => setShowEmojiPicker(false) }))] }));
75
+ : "bg-[var(--dc-color-primary-light)] text-[var(--dc-color-primary)]")), onClick: () => setShowEmojiPicker(!showEmojiPicker) }, crmLabel("composer_emoji"), { children: crmComposer ? _jsx(StrokeIcon, { name: "smile", size: 18 }) : EmojiIcon })), showMediaUpload && (_jsx(MediaUploadButtons, { listUploadFiles: listUploadFiles, onFilesChange: onFilesChange, btnClass: btnClass, crmComposer: crmComposer, crmLabel: crmLabel, acceptVideo: acceptVideo })), showEmojiPicker && (_jsx(EmojiPicker, { className: crmComposer ? "max-lg:w-80" : undefined, onEmojiSelect: handleEmojiSelect, onClose: () => setShowEmojiPicker(false) }))] }));
109
76
  }
@@ -0,0 +1,12 @@
1
+ import { UploadFile } from "antd";
2
+ interface MediaUploadButtonsProps {
3
+ listUploadFiles: UploadFile[];
4
+ onFilesChange: (files: UploadFile[]) => void;
5
+ btnClass: string;
6
+ crmComposer: boolean;
7
+ crmLabel: (key: string) => Record<string, string | undefined>;
8
+ acceptVideo?: boolean;
9
+ }
10
+ export default function MediaUploadButtons({ listUploadFiles, onFilesChange, btnClass, crmComposer, crmLabel, acceptVideo, }: MediaUploadButtonsProps): import("react/jsx-runtime").JSX.Element;
11
+ export {};
12
+ //# sourceMappingURL=MediaUploadButtons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MediaUploadButtons.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/MediaUploadButtons.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,UAAU,EAA0B,MAAM,MAAM,CAAC;AA2B1E,UAAU,uBAAuB;IAC/B,eAAe,EAAE,UAAU,EAAE,CAAC;IAC9B,aAAa,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAE9D,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EACzC,eAAe,EACf,aAAa,EACb,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,WAAkB,GACnB,EAAE,uBAAuB,2CA6EzB"}
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button, Upload, message as antdMessage } from "antd";
3
+ import { useTranslation } from "react-i18next";
4
+ import { Icon } from "../../../components/icon";
5
+ import { StrokeIcon } from "../../../components/icon/StrokeIcon";
6
+ import { validateFile } from "../../../utils/fileValidation";
7
+ import { DOCUMENT_TYPES } from "./editorConfig";
8
+ import { mergePickedUploadFiles } from "./mergePickedUploadFiles";
9
+ const AttachIcon = (_jsx("svg", { width: "22", height: "22", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: _jsx("path", { d: "M15.6 10.0001V11.2001C15.6 14.5137 12.9137 17.2001 9.59998 17.2001C6.28626 17.2001 3.59998 14.5137 3.59998 11.2001V6.79999C3.59998 4.59085 5.39084 2.79999 7.59998 2.79999C9.8091 2.79999 11.6 4.59085 11.6 6.79999V11.2C11.6 12.3045 10.7045 13.2 9.59998 13.2C8.49542 13.2 7.59998 12.3045 7.59998 11.2V7.99999", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }));
10
+ export default function MediaUploadButtons({ listUploadFiles, onFilesChange, btnClass, crmComposer, crmLabel, acceptVideo = true, }) {
11
+ const { t } = useTranslation();
12
+ const beforeUploadImagesAndVideo = (file) => {
13
+ if (!acceptVideo && file.type.startsWith("video/")) {
14
+ antdMessage.error(t("invalid_image_format", { fileName: file.name }));
15
+ return Upload.LIST_IGNORE;
16
+ }
17
+ const validation = validateFile(file, t);
18
+ if (!validation.isValid) {
19
+ antdMessage.error(validation.error);
20
+ return Upload.LIST_IGNORE;
21
+ }
22
+ return false;
23
+ };
24
+ const beforeUploadFile = (file) => {
25
+ const isAllowed = DOCUMENT_TYPES.includes(file.type);
26
+ if (!isAllowed) {
27
+ antdMessage.error(t("invalid_document_format", { fileName: file.name }));
28
+ return Upload.LIST_IGNORE;
29
+ }
30
+ return false;
31
+ };
32
+ const handleChange = (info) => {
33
+ onFilesChange(mergePickedUploadFiles(listUploadFiles, info.fileList, info.file));
34
+ };
35
+ return (_jsxs(_Fragment, { children: [_jsx(Upload, { accept: acceptVideo
36
+ ? "image/jpeg, image/png, image/jpg, video/*"
37
+ : "image/jpeg, image/png, image/jpg", beforeUpload: beforeUploadImagesAndVideo, multiple: true, onChange: handleChange, showUploadList: false, fileList: listUploadFiles, children: _jsx(Button, Object.assign({ type: "text", shape: "default", className: btnClass }, crmLabel("composer_attach_image"), { children: crmComposer ? (_jsx(StrokeIcon, { name: "image", size: 18 })) : (_jsx(Icon, { icon: "image-02-o", size: 22 })) })) }), _jsx(Upload, { accept: ".doc,.docx,.pdf", beforeUpload: beforeUploadFile, onChange: handleChange, showUploadList: false, fileList: listUploadFiles, children: _jsx(Button, Object.assign({ type: "text", shape: "default", className: btnClass }, crmLabel("composer_attach_file"), { children: crmComposer ? _jsx(StrokeIcon, { name: "paperclip", size: 18 }) : AttachIcon })) })] }));
38
+ }
@@ -0,0 +1,9 @@
1
+ import type { UploadFile } from "antd";
2
+ /**
3
+ * Turns antd's picked fileList into the composer list. Only one document is
4
+ * kept (the last picked). Items already in `current` (matched by uid) are kept
5
+ * as-is so URL-only attachments keep their url/response/status; newly picked
6
+ * Files are wrapped with createUploadFile.
7
+ */
8
+ export declare function mergePickedUploadFiles(current: UploadFile[], pickedList: UploadFile[], lastFile: UploadFile): UploadFile[];
9
+ //# sourceMappingURL=mergePickedUploadFiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mergePickedUploadFiles.d.ts","sourceRoot":"","sources":["../../../../src/components/message/footer/mergePickedUploadFiles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAKvC;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,UAAU,EAAE,EACrB,UAAU,EAAE,UAAU,EAAE,EACxB,QAAQ,EAAE,UAAU,GACnB,UAAU,EAAE,CAgBd"}
@@ -0,0 +1,20 @@
1
+ import { createUploadFile } from "../../../utils/fileValidation";
2
+ import { DOCUMENT_TYPES } from "./editorConfig";
3
+ /**
4
+ * Turns antd's picked fileList into the composer list. Only one document is
5
+ * kept (the last picked). Items already in `current` (matched by uid) are kept
6
+ * as-is so URL-only attachments keep their url/response/status; newly picked
7
+ * Files are wrapped with createUploadFile.
8
+ */
9
+ export function mergePickedUploadFiles(current, pickedList, lastFile) {
10
+ let newList = [...pickedList];
11
+ if (DOCUMENT_TYPES.includes(lastFile.type || "")) {
12
+ newList = newList.filter((f) => !DOCUMENT_TYPES.includes(f.type || ""));
13
+ newList.push(lastFile);
14
+ }
15
+ const byUid = new Map(current.map((f) => [f.uid, f]));
16
+ return newList.map((file) => {
17
+ var _a, _b;
18
+ return (_a = byUid.get(file.uid)) !== null && _a !== void 0 ? _a : Object.assign(Object.assign({}, createUploadFile(((_b = file.originFileObj) !== null && _b !== void 0 ? _b : file))), { uid: file.uid });
19
+ });
20
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"MergeImageMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/MergeImageMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiB,MAAM,8BAA8B,CAAC;AAM1E,UAAU,0BAA0B;IAClC,OAAO,EAAE,WAAW,CAAC;CACtB;AAQD,QAAA,MAAM,qBAAqB,GAAI,OAAO,0BAA0B,4CA4C/D,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"MergeImageMessage.d.ts","sourceRoot":"","sources":["../../../../src/components/message/item/MergeImageMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAiB,MAAM,8BAA8B,CAAC;AAO1E,UAAU,0BAA0B;IAClC,OAAO,EAAE,WAAW,CAAC;CACtB;AAQD,QAAA,MAAM,qBAAqB,GAAI,OAAO,0BAA0B,4CA8C/D,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
@@ -3,6 +3,7 @@ import { MessageStatus } from "@droppii-org/wasm-client-sdk";
3
3
  import { Image, Spin } from "antd";
4
4
  import clsx from "clsx";
5
5
  import { useTranslation } from "react-i18next";
6
+ import { usePreviewGroupContainer } from "../../../hooks/common/usePreviewGroupContainer";
6
7
  import { renderImagePreviewToolbar } from "./imagePreviewToolbar";
7
8
  const MERGE_IMAGE_GRID_COLS_CLASS = {
8
9
  1: "grid-cols-[repeat(1,100px)]",
@@ -13,10 +14,12 @@ const MergeImageMessageItem = (props) => {
13
14
  var _a, _b;
14
15
  const { message } = props;
15
16
  const { t } = useTranslation();
17
+ const getPreviewContainer = usePreviewGroupContainer();
16
18
  const subMessages = (_b = (_a = message.mergeElem) === null || _a === void 0 ? void 0 : _a.multiMessage) !== null && _b !== void 0 ? _b : [];
17
19
  const isSending = message.status === MessageStatus.Sending;
18
20
  const cols = Math.min(subMessages.length, 3);
19
21
  return (_jsx("div", { className: "-mx-1", "data-cp-name": "MergeImageMessageItem", children: _jsx(Spin, { spinning: isSending, children: _jsx(Image.PreviewGroup, { preview: {
22
+ getContainer: getPreviewContainer,
20
23
  toolbarRender: (originalNode, info) => { var _a; return renderImagePreviewToolbar(originalNode, (_a = info === null || info === void 0 ? void 0 : info.image) === null || _a === void 0 ? void 0 : _a.url, t); },
21
24
  }, children: _jsx("div", { className: clsx("grid gap-1", MERGE_IMAGE_GRID_COLS_CLASS[cols]), children: subMessages.map((item) => {
22
25
  var _a, _b, _c, _d, _e, _f;
@@ -8,7 +8,14 @@ interface RichTextEditorProps {
8
8
  showMediaUpload?: boolean;
9
9
  /** CRM: hide the emoji button on phones (the system keyboard has emoji). */
10
10
  hideEmojiOnPhone?: boolean;
11
+ /**
12
+ * Controlled attachment list. When provided the editor renders it and reports
13
+ * every change through onFilesChange; otherwise it keeps its own state.
14
+ */
15
+ files?: UploadFile[];
16
+ /** false: the image picker rejects video (default true). */
17
+ acceptVideo?: boolean;
11
18
  }
12
- export default function RichTextEditor({ initialHtml, placeholder, onChange, onFilesChange, minHeight, showMediaUpload, hideEmojiOnPhone, }: RichTextEditorProps): import("react/jsx-runtime").JSX.Element;
19
+ export default function RichTextEditor({ initialHtml, placeholder, onChange, onFilesChange, minHeight, showMediaUpload, hideEmojiOnPhone, files, acceptVideo, }: RichTextEditorProps): import("react/jsx-runtime").JSX.Element;
13
20
  export {};
14
21
  //# sourceMappingURL=RichTextEditor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RichTextEditor.d.ts","sourceRoot":"","sources":["../../../src/components/rich-text-editor/RichTextEditor.tsx"],"names":[],"mappings":"AAYA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AA2ClC,UAAU,mBAAmB;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EACrC,WAAW,EACX,WAAW,EACX,QAAQ,EACR,aAAa,EACb,SAAmB,EACnB,eAAsB,EACtB,gBAAwB,GACzB,EAAE,mBAAmB,2CAkErB"}
1
+ {"version":3,"file":"RichTextEditor.d.ts","sourceRoot":"","sources":["../../../src/components/rich-text-editor/RichTextEditor.tsx"],"names":[],"mappings":"AAYA,OAAO,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AA2ClC,UAAU,mBAAmB;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EACrC,WAAW,EACX,WAAW,EACX,QAAQ,EACR,aAAa,EACb,SAAmB,EACnB,eAAsB,EACtB,gBAAwB,EACxB,KAAK,EACL,WAAkB,GACnB,EAAE,mBAAmB,2CAkErB"}
@@ -39,8 +39,9 @@ function HtmlPlugin({ initialHtml }) {
39
39
  }, [editor, initialHtml]);
40
40
  return null;
41
41
  }
42
- export default function RichTextEditor({ initialHtml, placeholder, onChange, onFilesChange, minHeight = "160px", showMediaUpload = true, hideEmojiOnPhone = false, }) {
43
- const [listUploadFiles, setListUploadFiles] = useState([]);
42
+ export default function RichTextEditor({ initialHtml, placeholder, onChange, onFilesChange, minHeight = "160px", showMediaUpload = true, hideEmojiOnPhone = false, files, acceptVideo = true, }) {
43
+ const [innerFiles, setInnerFiles] = useState([]);
44
+ const listUploadFiles = files !== null && files !== void 0 ? files : innerFiles;
44
45
  const handleChange = (editorState, editor, tags) => {
45
46
  if (tags.has("initial-html"))
46
47
  return;
@@ -51,14 +52,13 @@ export default function RichTextEditor({ initialHtml, placeholder, onChange, onF
51
52
  });
52
53
  };
53
54
  const handleRemoveFile = (file) => {
54
- const newList = listUploadFiles.filter((f) => f.uid !== file.uid);
55
- setListUploadFiles(newList);
56
- onFilesChange === null || onFilesChange === void 0 ? void 0 : onFilesChange(newList);
55
+ handleFilesChange(listUploadFiles.filter((f) => f.uid !== file.uid));
57
56
  };
58
- const handleFilesChange = (files) => {
59
- setListUploadFiles(files);
60
- onFilesChange === null || onFilesChange === void 0 ? void 0 : onFilesChange(files);
57
+ const handleFilesChange = (next) => {
58
+ if (files === undefined)
59
+ setInnerFiles(next);
60
+ onFilesChange === null || onFilesChange === void 0 ? void 0 : onFilesChange(next);
61
61
  };
62
62
  return (_jsxs(LexicalComposer, { initialConfig: EDITOR_CONFIG, children: [_jsxs("div", { className: "border border-gray-200 rounded-lg", children: [_jsx(FilePreviewList, { files: listUploadFiles, onRemove: handleRemoveFile }), _jsx("div", { className: "relative", children: _jsx(RichTextPlugin, { contentEditable: _jsx(ContentEditable, { className: "px-3 py-2 text-sm outline-none", style: { minHeight } }), ErrorBoundary: LexicalErrorBoundary, placeholder: placeholder ? (_jsx("div", { className: "absolute top-2 left-3 pointer-events-none", children: _jsx("p", { className: "text-gray-400 text-sm", children: placeholder }) })) : null }) }), _jsxs("div", { className: clsx("flex items-center border-t border-gray-100 px-2 py-1", hideEmojiOnPhone &&
63
- "flex-wrap max-sm:[&_[data-cp-name=ToolbarPlugin]]:!gap-1 max-sm:[&_[data-cp-name=ToolbarPlugin]]:!px-1"), children: [_jsx(ToolbarPlugin, {}), _jsx(MediaActions, { listUploadFiles: listUploadFiles, onFilesChange: handleFilesChange, showMediaUpload: showMediaUpload, hideEmojiOnPhone: hideEmojiOnPhone })] })] }), _jsx(LinkPlugin, {}), _jsx(ListPlugin, {}), _jsx(OnChangePlugin, { onChange: handleChange, ignoreSelectionChange: true }), _jsx(HtmlPlugin, { initialHtml: initialHtml })] }));
63
+ "flex-wrap max-sm:[&_[data-cp-name=ToolbarPlugin]]:!gap-1 max-sm:[&_[data-cp-name=ToolbarPlugin]]:!px-1"), children: [_jsx(ToolbarPlugin, {}), _jsx(MediaActions, { listUploadFiles: listUploadFiles, onFilesChange: handleFilesChange, showMediaUpload: showMediaUpload, hideEmojiOnPhone: hideEmojiOnPhone, acceptVideo: acceptVideo })] })] }), _jsx(LinkPlugin, {}), _jsx(ListPlugin, {}), _jsx(OnChangePlugin, { onChange: handleChange, ignoreSelectionChange: true }), _jsx(HtmlPlugin, { initialHtml: initialHtml })] }));
64
64
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SessionTagPicker.d.ts","sourceRoot":"","sources":["../../../src/components/thread/SessionTagPicker.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,UAAU,qBAAqB;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACnC;AASD,QAAA,MAAM,gBAAgB,GAAI,gDAKvB,qBAAqB,4CAkIvB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"SessionTagPicker.d.ts","sourceRoot":"","sources":["../../../src/components/thread/SessionTagPicker.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,UAAU,qBAAqB;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CACnC;AASD,QAAA,MAAM,gBAAgB,GAAI,gDAKvB,qBAAqB,4CAmIvB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -26,7 +26,8 @@ const SessionTagPicker = ({ labelId, available, selectedIds, onChange, }) => {
26
26
  const listId = useId();
27
27
  const toggleRef = useRef(null);
28
28
  const panelRef = useRef(null);
29
- const selected = available.filter((label) => selectedIds.includes(label.id));
29
+ // Chips follow the order the tags were picked (selectedIds), not the label list order.
30
+ const selected = selectedIds.flatMap((id) => available.filter((label) => label.id === id));
30
31
  // Sized from the full list, not the results, so searching never resizes it.
31
32
  const listHeight = Math.min(Math.max(available.length, 1), MAX_VISIBLE_OPTIONS) * OPTION_HEIGHT;
32
33
  const close = ({ restoreFocus = true } = {}) => {
@@ -0,0 +1,2 @@
1
+ export declare const usePreviewGroupContainer: () => (() => HTMLElement) | undefined;
2
+ //# sourceMappingURL=usePreviewGroupContainer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePreviewGroupContainer.d.ts","sourceRoot":"","sources":["../../../src/hooks/common/usePreviewGroupContainer.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,wBAAwB,uCAapC,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { useContext, useEffect, useState } from "react";
2
+ import { ConfigProvider } from "antd";
3
+ // antd's Image.PreviewGroup ignores ConfigProvider getPopupContainer, so inside a
4
+ // host's shadow root the viewer escaped to document.body, where antd has no CSS.
5
+ export const usePreviewGroupContainer = () => {
6
+ const { getPopupContainer } = useContext(ConfigProvider.ConfigContext);
7
+ const [isInShadowRoot, setIsInShadowRoot] = useState(false);
8
+ useEffect(() => {
9
+ setIsInShadowRoot((getPopupContainer === null || getPopupContainer === void 0 ? void 0 : getPopupContainer().getRootNode()) instanceof ShadowRoot);
10
+ }, [getPopupContainer]);
11
+ return isInShadowRoot && getPopupContainer
12
+ ? () => getPopupContainer()
13
+ : undefined;
14
+ };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import "./locales/i18n";
2
+ import "./utils/antdReact19Render";
2
3
  import DChatDeskMessage from "./screens/desk-message";
3
4
  import SkipLinks from "./screens/desk-message/SkipLinks";
4
5
  import DChatBubble from "./screens/chat-bubble";
@@ -13,7 +14,9 @@ export { default as qs } from "query-string";
13
14
  export * from "antd/es/table";
14
15
  export { ChatProvider, useChatContext } from "./context/ChatContext";
15
16
  export { ConnectStatus, SyncStatus } from "./types/chat";
16
- export type { DChatTheme, ChatProviderProps, CannedCreateRequest, } from "./types/chat";
17
+ export type { DChatTheme, ChatProviderProps, CannedCreateRequest, CannedMediaPayload, } from "./types/chat";
18
+ export type { ICannedMedia } from "./types/dto";
19
+ export { isSafeMediaUrl, basename } from "./utils/mediaUrl";
17
20
  export { openLink } from "./utils/openLink";
18
21
  export { DChatDeskMessage, DChatBubble, Icon, SkipLinks };
19
22
  export { StrokeIcon } from "./components/icon/StrokeIcon";