@liveblocks/react-ui 2.7.0-beta1 → 2.7.0-beta3

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 (52) hide show
  1. package/dist/components/Comment.js +53 -8
  2. package/dist/components/Comment.js.map +1 -1
  3. package/dist/components/Comment.mjs +54 -9
  4. package/dist/components/Comment.mjs.map +1 -1
  5. package/dist/components/Composer.js +13 -16
  6. package/dist/components/Composer.js.map +1 -1
  7. package/dist/components/Composer.mjs +14 -17
  8. package/dist/components/Composer.mjs.map +1 -1
  9. package/dist/components/InboxNotification.js +4 -1
  10. package/dist/components/InboxNotification.js.map +1 -1
  11. package/dist/components/InboxNotification.mjs +4 -1
  12. package/dist/components/InboxNotification.mjs.map +1 -1
  13. package/dist/components/internal/Attachment.js +161 -74
  14. package/dist/components/internal/Attachment.js.map +1 -1
  15. package/dist/components/internal/Attachment.mjs +161 -76
  16. package/dist/components/internal/Attachment.mjs.map +1 -1
  17. package/dist/components/internal/InboxNotificationThread.js +5 -3
  18. package/dist/components/internal/InboxNotificationThread.js.map +1 -1
  19. package/dist/components/internal/InboxNotificationThread.mjs +5 -3
  20. package/dist/components/internal/InboxNotificationThread.mjs.map +1 -1
  21. package/dist/index.d.mts +4 -0
  22. package/dist/index.d.ts +4 -0
  23. package/dist/primitives/Composer/contexts.js.map +1 -1
  24. package/dist/primitives/Composer/contexts.mjs.map +1 -1
  25. package/dist/primitives/Composer/index.js +33 -26
  26. package/dist/primitives/Composer/index.js.map +1 -1
  27. package/dist/primitives/Composer/index.mjs +33 -26
  28. package/dist/primitives/Composer/index.mjs.map +1 -1
  29. package/dist/primitives/Composer/utils.js +73 -66
  30. package/dist/primitives/Composer/utils.js.map +1 -1
  31. package/dist/primitives/Composer/utils.mjs +73 -66
  32. package/dist/primitives/Composer/utils.mjs.map +1 -1
  33. package/dist/primitives/index.d.mts +4 -0
  34. package/dist/primitives/index.d.ts +4 -0
  35. package/dist/slate/plugins/{paste-html.js → paste.js} +11 -3
  36. package/dist/slate/plugins/paste.js.map +1 -0
  37. package/dist/slate/plugins/{paste-html.mjs → paste.mjs} +11 -3
  38. package/dist/slate/plugins/paste.mjs.map +1 -0
  39. package/dist/utils/data-transfer.js +14 -0
  40. package/dist/utils/data-transfer.js.map +1 -0
  41. package/dist/utils/data-transfer.mjs +12 -0
  42. package/dist/utils/data-transfer.mjs.map +1 -0
  43. package/dist/utils/use-latest.js.map +1 -1
  44. package/dist/utils/use-latest.mjs.map +1 -1
  45. package/dist/version.js +1 -1
  46. package/dist/version.mjs +1 -1
  47. package/package.json +4 -4
  48. package/src/styles/index.css +91 -44
  49. package/styles.css +1 -1
  50. package/styles.css.map +1 -1
  51. package/dist/slate/plugins/paste-html.js.map +0 -1
  52. package/dist/slate/plugins/paste-html.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { useIsInsideRoom, useAttachmentUrl } from '@liveblocks/react';
3
- import React__default, { memo, useMemo, useCallback, useState, useEffect } from 'react';
3
+ import React__default, { memo, useMemo, useCallback, useState } from 'react';
4
4
  import { CrossIcon } from '../../icons/Cross.mjs';
5
5
  import { SpinnerIcon } from '../../icons/Spinner.mjs';
6
6
  import { WarningIcon } from '../../icons/Warning.mjs';
@@ -16,19 +16,18 @@ import { classNames } from '../../utils/class-names.mjs';
16
16
  import { formatFileSize } from '../../utils/format-file-size.mjs';
17
17
  import { Tooltip } from './Tooltip.mjs';
18
18
 
19
- const IMAGE_PREVIEW_MAX_SIZE = 50 * 1024 * 1024;
20
19
  const fileExtensionRegex = /^(.+?)(\.[^.]+)?$/;
21
20
  function splitFileName(name) {
22
21
  const match = name.match(fileExtensionRegex);
23
22
  return { base: match?.[1] ?? name, extension: match?.[2] };
24
23
  }
25
- function getFileAttachmentIconGlyph(mimeType) {
26
- if (mimeType === "application/zip" || mimeType === "application/x-rar-compressed" || mimeType === "application/x-7z-compressed" || mimeType === "application/x-zip-compressed" || mimeType === "application/x-tar" || mimeType === "application/gzip") {
24
+ function getAttachmentIconGlyph(mimeType) {
25
+ if (mimeType === "application/zip" || mimeType === "application/gzip" || mimeType === "application/vnd.rar" || mimeType === "application/x-rar-compressed" || mimeType === "application/x-7z-compressed" || mimeType === "application/x-zip-compressed" || mimeType === "application/x-tar" || mimeType === "application/x-bzip" || mimeType === "application/x-bzip2") {
27
26
  return /* @__PURE__ */ React__default.createElement("path", {
28
27
  d: "M13 15h2v1h-1.5a.5.5 0 0 0 0 1H15v1h-1.5a.5.5 0 0 0 0 1H15v1h-1.5a.5.5 0 0 0 0 1h1a.5.5 0 0 0 .5-.5V20h1.5a.5.5 0 0 0 0-1H15v-1h1.5a.5.5 0 0 0 0-1H15v-1h1.5a.5.5 0 0 0 .5-.5V15a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2Z"
29
28
  });
30
29
  }
31
- if (mimeType.startsWith("text/")) {
30
+ if (mimeType.startsWith("text/") || mimeType.startsWith("font/") || mimeType.startsWith("application/")) {
32
31
  return /* @__PURE__ */ React__default.createElement("path", {
33
32
  d: "M10 16a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1h-8a.5.5 0 0 1-.5-.5Z"
34
33
  });
@@ -50,11 +49,8 @@ function getFileAttachmentIconGlyph(mimeType) {
50
49
  }
51
50
  return null;
52
51
  }
53
- const FileAttachmentIcon = memo(({ mimeType }) => {
54
- const iconGlyph = useMemo(
55
- () => getFileAttachmentIconGlyph(mimeType),
56
- [mimeType]
57
- );
52
+ const AttachmentFileIcon = memo(({ mimeType }) => {
53
+ const iconGlyph = useMemo(() => getAttachmentIconGlyph(mimeType), [mimeType]);
58
54
  return /* @__PURE__ */ React__default.createElement("svg", {
59
55
  className: "lb-attachment-icon",
60
56
  width: 30,
@@ -77,77 +73,101 @@ const FileAttachmentIcon = memo(({ mimeType }) => {
77
73
  className: "lb-attachment-icon-glyph"
78
74
  }, iconGlyph));
79
75
  });
80
- function FileAttachmentImagePreview({ url }) {
76
+ function AttachmentImagePreview({
77
+ attachment
78
+ }) {
79
+ const { url } = useAttachmentUrl(attachment.id);
81
80
  const [isLoaded, setLoaded] = useState(false);
82
81
  const handleLoad = useCallback(() => {
83
82
  setLoaded(true);
84
83
  }, []);
85
- return url ? /* @__PURE__ */ React__default.createElement("div", {
86
- className: "lb-attachment-preview-image",
84
+ return /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, !isLoaded ? /* @__PURE__ */ React__default.createElement(SpinnerIcon, null) : null, url ? /* @__PURE__ */ React__default.createElement("div", {
85
+ className: "lb-attachment-preview-media",
87
86
  "data-hidden": !isLoaded ? "" : void 0
88
87
  }, /* @__PURE__ */ React__default.createElement("img", {
89
88
  src: url,
90
89
  loading: "lazy",
91
90
  onLoad: handleLoad
92
- })) : null;
93
- }
94
- function FileAttachmentLocalImagePreview({
95
- attachment
96
- }) {
97
- const [url, setUrl] = useState();
98
- useEffect(() => {
99
- const url2 = URL.createObjectURL(attachment.file);
100
- setUrl(url2);
101
- return () => {
102
- URL.revokeObjectURL(url2);
103
- };
104
- }, [attachment.file]);
105
- return /* @__PURE__ */ React__default.createElement(FileAttachmentImagePreview, {
106
- url
107
- });
91
+ })) : null);
108
92
  }
109
- function FileAttachmentRemoteImagePreview({
93
+ function AttachmentVideoPreview({
110
94
  attachment
111
95
  }) {
112
96
  const { url } = useAttachmentUrl(attachment.id);
113
- return /* @__PURE__ */ React__default.createElement(FileAttachmentImagePreview, {
114
- url
115
- });
97
+ const [isLoaded, setLoaded] = useState(false);
98
+ const handleLoad = useCallback(() => {
99
+ setLoaded(true);
100
+ }, []);
101
+ return /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, !isLoaded ? /* @__PURE__ */ React__default.createElement(SpinnerIcon, null) : null, url ? /* @__PURE__ */ React__default.createElement("div", {
102
+ className: "lb-attachment-preview-media",
103
+ "data-hidden": !isLoaded ? "" : void 0
104
+ }, /* @__PURE__ */ React__default.createElement("video", {
105
+ src: url,
106
+ onLoadedData: handleLoad
107
+ })) : null);
116
108
  }
117
- function FileAttachmentPreview({
109
+ function AttachmentPreview({
118
110
  attachment
119
111
  }) {
120
112
  const isInsideRoom = useIsInsideRoom();
121
- const isLocal = attachment.type === "localAttachment";
122
- if (attachment.mimeType.startsWith("image/") && attachment.size < IMAGE_PREVIEW_MAX_SIZE) {
123
- if (isLocal) {
124
- return /* @__PURE__ */ React__default.createElement(FileAttachmentLocalImagePreview, {
113
+ const isUploaded = attachment.type === "attachment" || attachment.status === "uploaded";
114
+ if (isUploaded && isInsideRoom) {
115
+ if (attachment.mimeType.startsWith("image/")) {
116
+ return /* @__PURE__ */ React__default.createElement(AttachmentImagePreview, {
125
117
  attachment
126
118
  });
127
- } else if (isInsideRoom) {
128
- return /* @__PURE__ */ React__default.createElement(FileAttachmentRemoteImagePreview, {
119
+ }
120
+ if (attachment.mimeType.startsWith("video/")) {
121
+ return /* @__PURE__ */ React__default.createElement(AttachmentVideoPreview, {
129
122
  attachment
130
123
  });
131
124
  }
132
125
  }
133
- return null;
126
+ return /* @__PURE__ */ React__default.createElement(AttachmentFileIcon, {
127
+ mimeType: attachment.mimeType
128
+ });
134
129
  }
135
- function FileAttachment({
136
- attachment,
137
- overrides,
138
- onClick,
139
- onDeleteClick,
140
- preventFocusOnDelete,
141
- className,
142
- ...props
130
+ function AttachmentName({
131
+ attachment
143
132
  }) {
133
+ const { base: fileBaseName, extension: fileExtension } = useMemo(() => {
134
+ return splitFileName(attachment.name);
135
+ }, [attachment.name]);
136
+ return /* @__PURE__ */ React__default.createElement("span", {
137
+ className: "lb-attachment-name",
138
+ title: attachment.name
139
+ }, /* @__PURE__ */ React__default.createElement("span", {
140
+ className: "lb-attachment-name-base"
141
+ }, fileBaseName), fileExtension && /* @__PURE__ */ React__default.createElement("span", {
142
+ className: "lb-attachment-name-extension"
143
+ }, fileExtension));
144
+ }
145
+ function useClickOnKeyDown(onKeyDown) {
146
+ const handleKeyDown = useCallback(
147
+ (event) => {
148
+ onKeyDown?.(event);
149
+ if (event.isDefaultPrevented()) {
150
+ return;
151
+ }
152
+ if (event.key === "Enter" || event.key === " ") {
153
+ event.preventDefault();
154
+ const clickEvent = new MouseEvent("click", {
155
+ bubbles: true,
156
+ cancelable: true,
157
+ view: window
158
+ });
159
+ event.target.dispatchEvent(clickEvent);
160
+ }
161
+ },
162
+ [onKeyDown]
163
+ );
164
+ return handleKeyDown;
165
+ }
166
+ function useAttachmentContent(attachment, overrides) {
144
167
  const $ = useOverrides(overrides);
145
168
  const composerAttachmentsContext = useComposerAttachmentsContextOrNull();
146
169
  const isInComposer = Boolean(composerAttachmentsContext);
147
170
  const maxAttachmentSize = composerAttachmentsContext?.maxAttachmentSize;
148
- const { base: fileBaseName, extension: fileExtension } = useMemo(() => {
149
- return splitFileName(attachment.name);
150
- }, [attachment.name]);
151
171
  const status = attachment.type === "localAttachment" ? attachment.status : void 0;
152
172
  const isUploading = status === "uploading";
153
173
  const isError = status === "error";
@@ -164,6 +184,24 @@ function FileAttachment({
164
184
  description = formatFileSize(attachment.size, $.locale);
165
185
  }
166
186
  const deleteLabel = isInComposer ? $.COMPOSER_REMOVE_ATTACHMENT : $.COMMENT_DELETE_ATTACHMENT;
187
+ return {
188
+ isUploading,
189
+ isError,
190
+ description,
191
+ deleteLabel
192
+ };
193
+ }
194
+ function MediaAttachment({
195
+ attachment,
196
+ overrides,
197
+ onClick,
198
+ onDeleteClick,
199
+ preventFocusOnDelete,
200
+ className,
201
+ onKeyDown,
202
+ ...props
203
+ }) {
204
+ const { isUploading, isError, description, deleteLabel } = useAttachmentContent(attachment, overrides);
167
205
  const handleDeletePointerDown = useCallback(
168
206
  (event) => {
169
207
  if (preventFocusOnDelete) {
@@ -172,17 +210,56 @@ function FileAttachment({
172
210
  },
173
211
  [preventFocusOnDelete]
174
212
  );
175
- const handleKeyDown = useCallback((event) => {
176
- if (event.key === "Enter" || event.key === " ") {
177
- event.preventDefault();
178
- const clickEvent = new MouseEvent("click", {
179
- bubbles: true,
180
- cancelable: true,
181
- view: window
182
- });
183
- event.target.dispatchEvent(clickEvent);
184
- }
185
- }, []);
213
+ const handleKeyDown = useClickOnKeyDown(onKeyDown);
214
+ return /* @__PURE__ */ React__default.createElement("div", {
215
+ className: classNames("lb-attachment lb-media-attachment", className),
216
+ "data-error": isError ? "" : void 0,
217
+ ...props,
218
+ role: onClick ? "button" : void 0,
219
+ onClick,
220
+ tabIndex: onClick ? 0 : -1,
221
+ onKeyDown: onClick ? handleKeyDown : void 0
222
+ }, /* @__PURE__ */ React__default.createElement("div", {
223
+ className: "lb-attachment-preview"
224
+ }, isUploading ? /* @__PURE__ */ React__default.createElement(SpinnerIcon, null) : isError ? /* @__PURE__ */ React__default.createElement(WarningIcon, null) : /* @__PURE__ */ React__default.createElement(AttachmentPreview, {
225
+ attachment
226
+ })), /* @__PURE__ */ React__default.createElement("div", {
227
+ className: "lb-attachment-details"
228
+ }, /* @__PURE__ */ React__default.createElement(AttachmentName, {
229
+ attachment
230
+ }), /* @__PURE__ */ React__default.createElement("span", {
231
+ className: "lb-attachment-description",
232
+ title: description
233
+ }, description)), onDeleteClick && /* @__PURE__ */ React__default.createElement(Tooltip, {
234
+ content: deleteLabel
235
+ }, /* @__PURE__ */ React__default.createElement("button", {
236
+ type: "button",
237
+ className: "lb-attachment-delete",
238
+ onClick: onDeleteClick,
239
+ onPointerDown: handleDeletePointerDown,
240
+ "aria-label": deleteLabel
241
+ }, /* @__PURE__ */ React__default.createElement(CrossIcon, null))));
242
+ }
243
+ function FileAttachment({
244
+ attachment,
245
+ overrides,
246
+ onClick,
247
+ onDeleteClick,
248
+ preventFocusOnDelete,
249
+ className,
250
+ onKeyDown,
251
+ ...props
252
+ }) {
253
+ const { isUploading, isError, description, deleteLabel } = useAttachmentContent(attachment, overrides);
254
+ const handleDeletePointerDown = useCallback(
255
+ (event) => {
256
+ if (preventFocusOnDelete) {
257
+ event.preventDefault();
258
+ }
259
+ },
260
+ [preventFocusOnDelete]
261
+ );
262
+ const handleKeyDown = useClickOnKeyDown(onKeyDown);
186
263
  return /* @__PURE__ */ React__default.createElement("div", {
187
264
  className: classNames("lb-attachment lb-file-attachment", className),
188
265
  "data-error": isError ? "" : void 0,
@@ -193,20 +270,13 @@ function FileAttachment({
193
270
  onKeyDown: onClick ? handleKeyDown : void 0
194
271
  }, /* @__PURE__ */ React__default.createElement("div", {
195
272
  className: "lb-attachment-preview"
196
- }, isUploading ? /* @__PURE__ */ React__default.createElement(SpinnerIcon, null) : isError ? /* @__PURE__ */ React__default.createElement(WarningIcon, null) : /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, !isError ? /* @__PURE__ */ React__default.createElement(FileAttachmentPreview, {
273
+ }, isUploading ? /* @__PURE__ */ React__default.createElement(SpinnerIcon, null) : isError ? /* @__PURE__ */ React__default.createElement(WarningIcon, null) : /* @__PURE__ */ React__default.createElement(AttachmentPreview, {
197
274
  attachment
198
- }) : null, /* @__PURE__ */ React__default.createElement(FileAttachmentIcon, {
199
- mimeType: attachment.mimeType
200
- }))), /* @__PURE__ */ React__default.createElement("div", {
275
+ })), /* @__PURE__ */ React__default.createElement("div", {
201
276
  className: "lb-attachment-details"
202
- }, /* @__PURE__ */ React__default.createElement("span", {
203
- className: "lb-attachment-name",
204
- title: attachment.name
205
- }, /* @__PURE__ */ React__default.createElement("span", {
206
- className: "lb-attachment-name-base"
207
- }, fileBaseName), fileExtension && /* @__PURE__ */ React__default.createElement("span", {
208
- className: "lb-attachment-name-extension"
209
- }, fileExtension)), /* @__PURE__ */ React__default.createElement("span", {
277
+ }, /* @__PURE__ */ React__default.createElement(AttachmentName, {
278
+ attachment
279
+ }), /* @__PURE__ */ React__default.createElement("span", {
210
280
  className: "lb-attachment-description",
211
281
  title: description
212
282
  }, description)), onDeleteClick && /* @__PURE__ */ React__default.createElement(Tooltip, {
@@ -219,6 +289,21 @@ function FileAttachment({
219
289
  "aria-label": deleteLabel
220
290
  }, /* @__PURE__ */ React__default.createElement(CrossIcon, null))));
221
291
  }
292
+ function separateMediaAttachments(attachments) {
293
+ const mediaAttachments = [];
294
+ const fileAttachments = [];
295
+ for (const attachment of attachments) {
296
+ if (attachment.mimeType.startsWith("image/") || attachment.mimeType.startsWith("video/")) {
297
+ mediaAttachments.push(attachment);
298
+ } else {
299
+ fileAttachments.push(attachment);
300
+ }
301
+ }
302
+ return {
303
+ mediaAttachments,
304
+ fileAttachments
305
+ };
306
+ }
222
307
 
223
- export { FileAttachment };
308
+ export { FileAttachment, MediaAttachment, separateMediaAttachments };
224
309
  //# sourceMappingURL=Attachment.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Attachment.mjs","sources":["../../../src/components/internal/Attachment.tsx"],"sourcesContent":["\"use client\";\n\nimport type {\n CommentAttachment,\n CommentLocalAttachment,\n CommentMixedAttachment,\n} from \"@liveblocks/core\";\nimport { useAttachmentUrl, useIsInsideRoom } from \"@liveblocks/react\";\nimport type {\n ComponentPropsWithoutRef,\n KeyboardEvent,\n MouseEventHandler,\n PointerEvent,\n} from \"react\";\nimport React, { memo, useCallback, useEffect, useMemo, useState } from \"react\";\n\nimport { CrossIcon } from \"../../icons/Cross\";\nimport { SpinnerIcon } from \"../../icons/Spinner\";\nimport { WarningIcon } from \"../../icons/Warning\";\nimport type { GlobalOverrides } from \"../../overrides\";\nimport { useOverrides } from \"../../overrides\";\nimport { AttachmentTooLargeError } from \"../../primitives\";\nimport { useComposerAttachmentsContextOrNull } from \"../../primitives/Composer/contexts\";\nimport { classNames } from \"../../utils/class-names\";\nimport { formatFileSize } from \"../../utils/format-file-size\";\nimport { Tooltip } from \"./Tooltip\";\n\ninterface FileAttachmentProps extends ComponentPropsWithoutRef<\"div\"> {\n attachment: CommentMixedAttachment;\n onDeleteClick?: MouseEventHandler<HTMLButtonElement>;\n preventFocusOnDelete?: boolean;\n overrides?: Partial<GlobalOverrides>;\n}\n\nconst IMAGE_PREVIEW_MAX_SIZE = 50 * 1024 * 1024; // 50 MB\n\nconst fileExtensionRegex = /^(.+?)(\\.[^.]+)?$/;\n\nfunction splitFileName(name: string) {\n const match = name.match(fileExtensionRegex);\n\n return { base: match?.[1] ?? name, extension: match?.[2] };\n}\n\nfunction getFileAttachmentIconGlyph(mimeType: string) {\n if (\n mimeType === \"application/zip\" ||\n mimeType === \"application/x-rar-compressed\" ||\n mimeType === \"application/x-7z-compressed\" ||\n mimeType === \"application/x-zip-compressed\" ||\n mimeType === \"application/x-tar\" ||\n mimeType === \"application/gzip\"\n ) {\n return (\n <path d=\"M13 15h2v1h-1.5a.5.5 0 0 0 0 1H15v1h-1.5a.5.5 0 0 0 0 1H15v1h-1.5a.5.5 0 0 0 0 1h1a.5.5 0 0 0 .5-.5V20h1.5a.5.5 0 0 0 0-1H15v-1h1.5a.5.5 0 0 0 0-1H15v-1h1.5a.5.5 0 0 0 .5-.5V15a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2Z\" />\n );\n }\n\n if (mimeType.startsWith(\"text/\")) {\n return (\n <path d=\"M10 16a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1h-8a.5.5 0 0 1-.5-.5Z\" />\n );\n }\n\n if (mimeType.startsWith(\"image/\")) {\n return (\n <path d=\"M12 16h6a1 1 0 0 1 1 1v3l-1.293-1.293a1 1 0 0 0-1.414 0L14.09 20.91l-.464-.386a1 1 0 0 0-1.265-.013l-1.231.985A.995.995 0 0 1 11 21v-4a1 1 0 0 1 1-1Zm-2 1a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-6a2 2 0 0 1-2-2v-4Zm3 2a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z\" />\n );\n }\n\n if (mimeType.startsWith(\"video/\")) {\n return (\n <path d=\"M12 15.71a1 1 0 0 1 1.49-.872l4.96 2.79a1 1 0 0 1 0 1.744l-4.96 2.79A1 1 0 0 1 12 21.29v-5.58Z\" />\n );\n }\n\n if (mimeType.startsWith(\"audio/\")) {\n return (\n <path d=\"M15 15a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 1 0v-7a.5.5 0 0 0-.5-.5Zm-2.5 2.5a.5.5 0 0 1 1 0v3a.5.5 0 0 1-1 0v-3Zm-2 1a.5.5 0 0 1 1 0v1a.5.5 0 0 1-1 0v-1Zm6-1a.5.5 0 0 1 1 0v3a.5.5 0 0 1-1 0v-3ZM19 16a.5.5 0 0 0-.5.5v5a.5.5 0 0 0 1 0v-5a.5.5 0 0 0-.5-.5Z\" />\n );\n }\n\n return null;\n}\n\nconst FileAttachmentIcon = memo(({ mimeType }: { mimeType: string }) => {\n const iconGlyph = useMemo(\n () => getFileAttachmentIconGlyph(mimeType),\n [mimeType]\n );\n\n return (\n <svg\n className=\"lb-attachment-icon\"\n width={30}\n height={30}\n viewBox=\"0 0 30 30\"\n fill=\"currentColor\"\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M6 5a2 2 0 0 1 2-2h5.843a4 4 0 0 1 2.829 1.172l6.156 6.156A4 4 0 0 1 24 13.157V25a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V5Z\"\n className=\"lb-attachment-icon-shadow\"\n />\n <path\n d=\"M6 5a2 2 0 0 1 2-2h5.843a4 4 0 0 1 2.829 1.172l6.156 6.156A4 4 0 0 1 24 13.157V25a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V5Z\"\n className=\"lb-attachment-icon-background\"\n />\n <path\n d=\"M14.382 3.037a4 4 0 0 1 2.29 1.135l6.156 6.157a4 4 0 0 1 1.136 2.289A2 2 0 0 0 22 11h-4a2 2 0 0 1-2-2V5a2 2 0 0 0-1.618-1.963Z\"\n className=\"lb-attachment-icon-fold\"\n />\n\n {iconGlyph && <g className=\"lb-attachment-icon-glyph\">{iconGlyph}</g>}\n </svg>\n );\n});\n\nfunction FileAttachmentImagePreview({ url }: { url?: string }) {\n const [isLoaded, setLoaded] = useState(false);\n\n const handleLoad = useCallback(() => {\n setLoaded(true);\n }, []);\n\n return url ? (\n <div\n className=\"lb-attachment-preview-image\"\n data-hidden={!isLoaded ? \"\" : undefined}\n >\n <img src={url} loading=\"lazy\" onLoad={handleLoad} />\n </div>\n ) : null;\n}\n\nfunction FileAttachmentLocalImagePreview({\n attachment,\n}: {\n attachment: CommentLocalAttachment;\n}) {\n const [url, setUrl] = useState<string>();\n\n useEffect(() => {\n const url = URL.createObjectURL(attachment.file);\n\n setUrl(url);\n\n return () => {\n URL.revokeObjectURL(url);\n };\n }, [attachment.file]);\n\n return <FileAttachmentImagePreview url={url} />;\n}\n\nfunction FileAttachmentRemoteImagePreview({\n attachment,\n}: {\n attachment: CommentAttachment;\n}) {\n const { url } = useAttachmentUrl(attachment.id);\n\n return <FileAttachmentImagePreview url={url} />;\n}\n\nfunction FileAttachmentPreview({\n attachment,\n}: {\n attachment: CommentMixedAttachment;\n}) {\n const isInsideRoom = useIsInsideRoom();\n const isLocal = attachment.type === \"localAttachment\";\n\n if (\n attachment.mimeType.startsWith(\"image/\") &&\n attachment.size < IMAGE_PREVIEW_MAX_SIZE\n ) {\n if (isLocal) {\n return <FileAttachmentLocalImagePreview attachment={attachment} />;\n } else if (isInsideRoom) {\n return <FileAttachmentRemoteImagePreview attachment={attachment} />;\n }\n }\n\n return null;\n}\n\nexport function FileAttachment({\n attachment,\n overrides,\n onClick,\n onDeleteClick,\n preventFocusOnDelete,\n className,\n ...props\n}: FileAttachmentProps) {\n const $ = useOverrides(overrides);\n const composerAttachmentsContext = useComposerAttachmentsContextOrNull();\n const isInComposer = Boolean(composerAttachmentsContext);\n const maxAttachmentSize = composerAttachmentsContext?.maxAttachmentSize;\n const { base: fileBaseName, extension: fileExtension } = useMemo(() => {\n return splitFileName(attachment.name);\n }, [attachment.name]);\n const status =\n attachment.type === \"localAttachment\" ? attachment.status : undefined;\n const isUploading = status === \"uploading\";\n const isError = status === \"error\";\n\n let description: string;\n\n if (attachment.type === \"localAttachment\" && attachment.status === \"error\") {\n if (attachment.error instanceof AttachmentTooLargeError) {\n description = $.ATTACHMENT_TOO_LARGE(\n maxAttachmentSize\n ? formatFileSize(maxAttachmentSize, $.locale)\n : undefined\n );\n } else {\n description = $.ATTACHMENT_ERROR(attachment.error);\n }\n } else {\n description = formatFileSize(attachment.size, $.locale);\n }\n\n const deleteLabel = isInComposer\n ? $.COMPOSER_REMOVE_ATTACHMENT\n : $.COMMENT_DELETE_ATTACHMENT;\n\n const handleDeletePointerDown = useCallback(\n (event: PointerEvent<HTMLButtonElement>) => {\n if (preventFocusOnDelete) {\n event.preventDefault();\n }\n },\n [preventFocusOnDelete]\n );\n\n const handleKeyDown = useCallback((event: KeyboardEvent<HTMLDivElement>) => {\n // Simulate a click event on Enter or Space because it's a div\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n\n const clickEvent = new MouseEvent(\"click\", {\n bubbles: true,\n cancelable: true,\n view: window,\n });\n event.target.dispatchEvent(clickEvent);\n }\n }, []);\n\n return (\n <div\n className={classNames(\"lb-attachment lb-file-attachment\", className)}\n data-error={isError ? \"\" : undefined}\n {...props}\n role={onClick ? \"button\" : undefined}\n onClick={onClick}\n tabIndex={onClick ? 0 : -1}\n onKeyDown={onClick ? handleKeyDown : undefined}\n >\n <div className=\"lb-attachment-preview\">\n {isUploading ? (\n <SpinnerIcon />\n ) : isError ? (\n <WarningIcon />\n ) : (\n <>\n {!isError ? (\n <FileAttachmentPreview attachment={attachment} />\n ) : null}\n <FileAttachmentIcon mimeType={attachment.mimeType} />\n </>\n )}\n </div>\n <div className=\"lb-attachment-details\">\n <span className=\"lb-attachment-name\" title={attachment.name}>\n <span className=\"lb-attachment-name-base\">{fileBaseName}</span>\n {fileExtension && (\n <span className=\"lb-attachment-name-extension\">\n {fileExtension}\n </span>\n )}\n </span>\n <span className=\"lb-attachment-description\" title={description}>\n {description}\n </span>\n </div>\n {onDeleteClick && (\n <Tooltip content={deleteLabel}>\n <button\n type=\"button\"\n className=\"lb-attachment-delete\"\n onClick={onDeleteClick}\n onPointerDown={handleDeletePointerDown}\n aria-label={deleteLabel}\n >\n <CrossIcon />\n </button>\n </Tooltip>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAkCA;AAEA;AAEA;AACE;AAEA;AACF;AAEA;AACE;AAQE;AACG;AAAO;AAAoP;AAIhQ;AACE;AACG;AAAO;AAAiP;AAI7P;AACE;AACG;AAAO;AAAiQ;AAI7Q;AACE;AACG;AAAO;AAAiG;AAI7G;AACE;AACG;AAAO;AAAyP;AAIrQ;AACF;AAEA;AACE;AAAkB;AACyB;AAChC;AAGX;AACG;AACW;AACH;AACC;AACA;AACH;AACI;AACA;AACH;AAEL;AACG;AACQ;AAEX;AACG;AACQ;AAEX;AACG;AACQ;AAGG;AAAY;AAGjC;AAEA;AACE;AAEA;AACE;AAAc;AAGhB;AACG;AACW;AACoB;AAE7B;AAAS;AAAa;AAAe;AAG5C;AAEA;AAAyC;AAEzC;AAGE;AAEA;AACE;AAEA;AAEA;AACE;AAAuB;AACzB;AAGF;AAAQ;AAA2B;AACrC;AAEA;AAA0C;AAE1C;AAGE;AAEA;AAAQ;AAA2B;AACrC;AAEA;AAA+B;AAE/B;AAGE;AACA;AAEA;AAIE;AACE;AAAQ;AAAgC;AAAwB;AAEhE;AAAQ;AAAiC;AAAwB;AACnE;AAGF;AACF;AAEO;AAAwB;AAC7B;AACA;AACA;AACA;AACA;AACA;AAEF;AACE;AACA;AACA;AACA;AACA;AACE;AAAoC;AAEtC;AAEA;AACA;AAEA;AAEA;AACE;AACE;AAAgB;AAGV;AACN;AAEA;AAAiD;AACnD;AAEA;AAAsD;AAGxD;AAIA;AAAgC;AAE5B;AACE;AAAqB;AACvB;AACF;AACqB;AAGvB;AAEE;AACE;AAEA;AAA2C;AAChC;AACG;AACN;AAER;AAAqC;AACvC;AAGF;AACG;AACoE;AACxC;AACvB;AACuB;AAC3B;AACwB;AACa;AAEpC;AAAc;AAQN;AAAsB;AAExB;AAAwC;AAI9C;AAAc;AACZ;AAAe;AAAuC;AACpD;AAAe;AAEb;AAAe;AAKnB;AAAe;AAAmC;AAKlD;AAAiB;AACf;AACM;AACK;AACD;AACM;AACH;AAQxB;;"}
1
+ {"version":3,"file":"Attachment.mjs","sources":["../../../src/components/internal/Attachment.tsx"],"sourcesContent":["\"use client\";\n\nimport type { CommentMixedAttachment } from \"@liveblocks/core\";\nimport { useAttachmentUrl, useIsInsideRoom } from \"@liveblocks/react\";\nimport type {\n ComponentPropsWithoutRef,\n KeyboardEvent,\n MouseEventHandler,\n PointerEvent,\n} from \"react\";\nimport React, { memo, useCallback, useMemo, useState } from \"react\";\n\nimport { CrossIcon } from \"../../icons/Cross\";\nimport { SpinnerIcon } from \"../../icons/Spinner\";\nimport { WarningIcon } from \"../../icons/Warning\";\nimport type { Overrides } from \"../../overrides\";\nimport { useOverrides } from \"../../overrides\";\nimport { AttachmentTooLargeError } from \"../../primitives\";\nimport { useComposerAttachmentsContextOrNull } from \"../../primitives/Composer/contexts\";\nimport { classNames } from \"../../utils/class-names\";\nimport { formatFileSize } from \"../../utils/format-file-size\";\nimport { Tooltip } from \"./Tooltip\";\n\ninterface AttachmentProps extends ComponentPropsWithoutRef<\"div\"> {\n attachment: CommentMixedAttachment;\n onDeleteClick?: MouseEventHandler<HTMLButtonElement>;\n preventFocusOnDelete?: boolean;\n overrides?: Partial<Overrides>;\n}\n\nconst fileExtensionRegex = /^(.+?)(\\.[^.]+)?$/;\n\nfunction splitFileName(name: string) {\n const match = name.match(fileExtensionRegex);\n\n return { base: match?.[1] ?? name, extension: match?.[2] };\n}\n\nfunction getAttachmentIconGlyph(mimeType: string) {\n if (\n mimeType === \"application/zip\" ||\n mimeType === \"application/gzip\" ||\n mimeType === \"application/vnd.rar\" ||\n mimeType === \"application/x-rar-compressed\" ||\n mimeType === \"application/x-7z-compressed\" ||\n mimeType === \"application/x-zip-compressed\" ||\n mimeType === \"application/x-tar\" ||\n mimeType === \"application/x-bzip\" ||\n mimeType === \"application/x-bzip2\"\n ) {\n return (\n <path d=\"M13 15h2v1h-1.5a.5.5 0 0 0 0 1H15v1h-1.5a.5.5 0 0 0 0 1H15v1h-1.5a.5.5 0 0 0 0 1h1a.5.5 0 0 0 .5-.5V20h1.5a.5.5 0 0 0 0-1H15v-1h1.5a.5.5 0 0 0 0-1H15v-1h1.5a.5.5 0 0 0 .5-.5V15a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2Z\" />\n );\n }\n\n if (\n mimeType.startsWith(\"text/\") ||\n mimeType.startsWith(\"font/\") ||\n mimeType.startsWith(\"application/\")\n ) {\n return (\n <path d=\"M10 16a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1h-8a.5.5 0 0 1-.5-.5Z\" />\n );\n }\n\n if (mimeType.startsWith(\"image/\")) {\n return (\n <path d=\"M12 16h6a1 1 0 0 1 1 1v3l-1.293-1.293a1 1 0 0 0-1.414 0L14.09 20.91l-.464-.386a1 1 0 0 0-1.265-.013l-1.231.985A.995.995 0 0 1 11 21v-4a1 1 0 0 1 1-1Zm-2 1a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-6a2 2 0 0 1-2-2v-4Zm3 2a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z\" />\n );\n }\n\n if (mimeType.startsWith(\"video/\")) {\n return (\n <path d=\"M12 15.71a1 1 0 0 1 1.49-.872l4.96 2.79a1 1 0 0 1 0 1.744l-4.96 2.79A1 1 0 0 1 12 21.29v-5.58Z\" />\n );\n }\n\n if (mimeType.startsWith(\"audio/\")) {\n return (\n <path d=\"M15 15a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 1 0v-7a.5.5 0 0 0-.5-.5Zm-2.5 2.5a.5.5 0 0 1 1 0v3a.5.5 0 0 1-1 0v-3Zm-2 1a.5.5 0 0 1 1 0v1a.5.5 0 0 1-1 0v-1Zm6-1a.5.5 0 0 1 1 0v3a.5.5 0 0 1-1 0v-3ZM19 16a.5.5 0 0 0-.5.5v5a.5.5 0 0 0 1 0v-5a.5.5 0 0 0-.5-.5Z\" />\n );\n }\n\n return null;\n}\n\nconst AttachmentFileIcon = memo(({ mimeType }: { mimeType: string }) => {\n const iconGlyph = useMemo(() => getAttachmentIconGlyph(mimeType), [mimeType]);\n\n return (\n <svg\n className=\"lb-attachment-icon\"\n width={30}\n height={30}\n viewBox=\"0 0 30 30\"\n fill=\"currentColor\"\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M6 5a2 2 0 0 1 2-2h5.843a4 4 0 0 1 2.829 1.172l6.156 6.156A4 4 0 0 1 24 13.157V25a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V5Z\"\n className=\"lb-attachment-icon-shadow\"\n />\n <path\n d=\"M6 5a2 2 0 0 1 2-2h5.843a4 4 0 0 1 2.829 1.172l6.156 6.156A4 4 0 0 1 24 13.157V25a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V5Z\"\n className=\"lb-attachment-icon-background\"\n />\n <path\n d=\"M14.382 3.037a4 4 0 0 1 2.29 1.135l6.156 6.157a4 4 0 0 1 1.136 2.289A2 2 0 0 0 22 11h-4a2 2 0 0 1-2-2V5a2 2 0 0 0-1.618-1.963Z\"\n className=\"lb-attachment-icon-fold\"\n />\n\n {iconGlyph && <g className=\"lb-attachment-icon-glyph\">{iconGlyph}</g>}\n </svg>\n );\n});\n\nfunction AttachmentImagePreview({\n attachment,\n}: {\n attachment: CommentMixedAttachment;\n}) {\n const { url } = useAttachmentUrl(attachment.id);\n const [isLoaded, setLoaded] = useState(false);\n\n const handleLoad = useCallback(() => {\n setLoaded(true);\n }, []);\n\n return (\n <>\n {!isLoaded ? <SpinnerIcon /> : null}\n {url ? (\n <div\n className=\"lb-attachment-preview-media\"\n data-hidden={!isLoaded ? \"\" : undefined}\n >\n <img src={url} loading=\"lazy\" onLoad={handleLoad} />\n </div>\n ) : null}\n </>\n );\n}\n\nfunction AttachmentVideoPreview({\n attachment,\n}: {\n attachment: CommentMixedAttachment;\n}) {\n const { url } = useAttachmentUrl(attachment.id);\n const [isLoaded, setLoaded] = useState(false);\n\n const handleLoad = useCallback(() => {\n setLoaded(true);\n }, []);\n\n return (\n <>\n {!isLoaded ? <SpinnerIcon /> : null}\n {url ? (\n <div\n className=\"lb-attachment-preview-media\"\n data-hidden={!isLoaded ? \"\" : undefined}\n >\n <video src={url} onLoadedData={handleLoad} />\n </div>\n ) : null}\n </>\n );\n}\n\nfunction AttachmentPreview({\n attachment,\n}: {\n attachment: CommentMixedAttachment;\n}) {\n const isInsideRoom = useIsInsideRoom();\n const isUploaded =\n attachment.type === \"attachment\" || attachment.status === \"uploaded\";\n\n if (isUploaded && isInsideRoom) {\n if (attachment.mimeType.startsWith(\"image/\")) {\n return <AttachmentImagePreview attachment={attachment} />;\n }\n\n if (attachment.mimeType.startsWith(\"video/\")) {\n return <AttachmentVideoPreview attachment={attachment} />;\n }\n }\n\n return <AttachmentFileIcon mimeType={attachment.mimeType} />;\n}\n\nfunction AttachmentName({\n attachment,\n}: {\n attachment: CommentMixedAttachment;\n}) {\n const { base: fileBaseName, extension: fileExtension } = useMemo(() => {\n return splitFileName(attachment.name);\n }, [attachment.name]);\n\n return (\n <span className=\"lb-attachment-name\" title={attachment.name}>\n <span className=\"lb-attachment-name-base\">{fileBaseName}</span>\n {fileExtension && (\n <span className=\"lb-attachment-name-extension\">{fileExtension}</span>\n )}\n </span>\n );\n}\n\nfunction useClickOnKeyDown(\n onKeyDown?: (event: KeyboardEvent<HTMLDivElement>) => void\n) {\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n onKeyDown?.(event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n // Simulate a click event on Enter or Space because it's a div\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n\n const clickEvent = new MouseEvent(\"click\", {\n bubbles: true,\n cancelable: true,\n view: window,\n });\n event.target.dispatchEvent(clickEvent);\n }\n },\n [onKeyDown]\n );\n\n return handleKeyDown;\n}\n\nfunction useAttachmentContent(\n attachment: CommentMixedAttachment,\n overrides?: Partial<Overrides>\n) {\n const $ = useOverrides(overrides);\n const composerAttachmentsContext = useComposerAttachmentsContextOrNull();\n const isInComposer = Boolean(composerAttachmentsContext);\n const maxAttachmentSize = composerAttachmentsContext?.maxAttachmentSize;\n\n const status =\n attachment.type === \"localAttachment\" ? attachment.status : undefined;\n const isUploading = status === \"uploading\";\n const isError = status === \"error\";\n\n let description: string;\n\n if (attachment.type === \"localAttachment\" && attachment.status === \"error\") {\n if (attachment.error instanceof AttachmentTooLargeError) {\n description = $.ATTACHMENT_TOO_LARGE(\n maxAttachmentSize\n ? formatFileSize(maxAttachmentSize, $.locale)\n : undefined\n );\n } else {\n description = $.ATTACHMENT_ERROR(attachment.error);\n }\n } else {\n description = formatFileSize(attachment.size, $.locale);\n }\n\n const deleteLabel = isInComposer\n ? $.COMPOSER_REMOVE_ATTACHMENT\n : $.COMMENT_DELETE_ATTACHMENT;\n\n return {\n isUploading,\n isError,\n description,\n deleteLabel,\n };\n}\n\nexport function MediaAttachment({\n attachment,\n overrides,\n onClick,\n onDeleteClick,\n preventFocusOnDelete,\n className,\n onKeyDown,\n ...props\n}: AttachmentProps) {\n const { isUploading, isError, description, deleteLabel } =\n useAttachmentContent(attachment, overrides);\n\n const handleDeletePointerDown = useCallback(\n (event: PointerEvent<HTMLButtonElement>) => {\n if (preventFocusOnDelete) {\n event.preventDefault();\n }\n },\n [preventFocusOnDelete]\n );\n\n const handleKeyDown = useClickOnKeyDown(onKeyDown);\n\n return (\n <div\n className={classNames(\"lb-attachment lb-media-attachment\", className)}\n data-error={isError ? \"\" : undefined}\n {...props}\n role={onClick ? \"button\" : undefined}\n onClick={onClick}\n tabIndex={onClick ? 0 : -1}\n onKeyDown={onClick ? handleKeyDown : undefined}\n >\n <div className=\"lb-attachment-preview\">\n {isUploading ? (\n <SpinnerIcon />\n ) : isError ? (\n <WarningIcon />\n ) : (\n <AttachmentPreview attachment={attachment} />\n )}\n </div>\n <div className=\"lb-attachment-details\">\n <AttachmentName attachment={attachment} />\n <span className=\"lb-attachment-description\" title={description}>\n {description}\n </span>\n </div>\n {onDeleteClick && (\n <Tooltip content={deleteLabel}>\n <button\n type=\"button\"\n className=\"lb-attachment-delete\"\n onClick={onDeleteClick}\n onPointerDown={handleDeletePointerDown}\n aria-label={deleteLabel}\n >\n <CrossIcon />\n </button>\n </Tooltip>\n )}\n </div>\n );\n}\n\nexport function FileAttachment({\n attachment,\n overrides,\n onClick,\n onDeleteClick,\n preventFocusOnDelete,\n className,\n onKeyDown,\n ...props\n}: AttachmentProps) {\n const { isUploading, isError, description, deleteLabel } =\n useAttachmentContent(attachment, overrides);\n\n const handleDeletePointerDown = useCallback(\n (event: PointerEvent<HTMLButtonElement>) => {\n if (preventFocusOnDelete) {\n event.preventDefault();\n }\n },\n [preventFocusOnDelete]\n );\n\n const handleKeyDown = useClickOnKeyDown(onKeyDown);\n\n return (\n <div\n className={classNames(\"lb-attachment lb-file-attachment\", className)}\n data-error={isError ? \"\" : undefined}\n {...props}\n role={onClick ? \"button\" : undefined}\n onClick={onClick}\n tabIndex={onClick ? 0 : -1}\n onKeyDown={onClick ? handleKeyDown : undefined}\n >\n <div className=\"lb-attachment-preview\">\n {isUploading ? (\n <SpinnerIcon />\n ) : isError ? (\n <WarningIcon />\n ) : (\n <AttachmentPreview attachment={attachment} />\n )}\n </div>\n <div className=\"lb-attachment-details\">\n <AttachmentName attachment={attachment} />\n <span className=\"lb-attachment-description\" title={description}>\n {description}\n </span>\n </div>\n {onDeleteClick && (\n <Tooltip content={deleteLabel}>\n <button\n type=\"button\"\n className=\"lb-attachment-delete\"\n onClick={onDeleteClick}\n onPointerDown={handleDeletePointerDown}\n aria-label={deleteLabel}\n >\n <CrossIcon />\n </button>\n </Tooltip>\n )}\n </div>\n );\n}\n\nexport function separateMediaAttachments<T extends CommentMixedAttachment>(\n attachments: T[]\n) {\n const mediaAttachments: T[] = [];\n const fileAttachments: T[] = [];\n\n for (const attachment of attachments) {\n if (\n attachment.mimeType.startsWith(\"image/\") ||\n attachment.mimeType.startsWith(\"video/\")\n ) {\n mediaAttachments.push(attachment);\n } else {\n fileAttachments.push(attachment);\n }\n }\n\n return {\n mediaAttachments,\n fileAttachments,\n };\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AA8BA;AAEA;AACE;AAEA;AACF;AAEA;AACE;AAWE;AACG;AAAO;AAAoP;AAIhQ;AAKE;AACG;AAAO;AAAiP;AAI7P;AACE;AACG;AAAO;AAAiQ;AAI7Q;AACE;AACG;AAAO;AAAiG;AAI7G;AACE;AACG;AAAO;AAAyP;AAIrQ;AACF;AAEA;AACE;AAEA;AACG;AACW;AACH;AACC;AACA;AACH;AACI;AACA;AACH;AAEL;AACG;AACQ;AAEX;AACG;AACQ;AAEX;AACG;AACQ;AAGG;AAAY;AAGjC;AAEA;AAAgC;AAEhC;AAGE;AACA;AAEA;AACE;AAAc;AAGhB;AAIO;AACW;AACoB;AAE7B;AAAS;AAAa;AAAe;AAKhD;AAEA;AAAgC;AAEhC;AAGE;AACA;AAEA;AACE;AAAc;AAGhB;AAIO;AACW;AACoB;AAE7B;AAAW;AAAmB;AAKzC;AAEA;AAA2B;AAE3B;AAGE;AACA;AAGA;AACE;AACE;AAAQ;AAAuB;AAAwB;AAGzD;AACE;AAAQ;AAAuB;AAAwB;AACzD;AAGF;AAAQ;AAAwC;AAClD;AAEA;AAAwB;AAExB;AAGE;AACE;AAAoC;AAGtC;AACG;AAAe;AAAuC;AACpD;AAAe;AAEb;AAAe;AAIxB;AAEA;AAGE;AAAsB;AAElB;AAEA;AACE;AAAA;AAIF;AACE;AAEA;AAA2C;AAChC;AACG;AACN;AAER;AAAqC;AACvC;AACF;AACU;AAGZ;AACF;AAEA;AAIE;AACA;AACA;AACA;AAEA;AAEA;AACA;AAEA;AAEA;AACE;AACE;AAAgB;AAGV;AACN;AAEA;AAAiD;AACnD;AAEA;AAAsD;AAGxD;AAIA;AAAO;AACL;AACA;AACA;AACA;AAEJ;AAEO;AAAyB;AAC9B;AACA;AACA;AACA;AACA;AACA;AACA;AAEF;AACE;AAGA;AAAgC;AAE5B;AACE;AAAqB;AACvB;AACF;AACqB;AAGvB;AAEA;AACG;AACqE;AACzC;AACvB;AACuB;AAC3B;AACwB;AACa;AAEpC;AAAc;AAMV;AAAkB;AAGtB;AAAc;AACZ;AAAe;AACf;AAAe;AAAmC;AAKlD;AAAiB;AACf;AACM;AACK;AACD;AACM;AACH;AAQxB;AAEO;AAAwB;AAC7B;AACA;AACA;AACA;AACA;AACA;AACA;AAEF;AACE;AAGA;AAAgC;AAE5B;AACE;AAAqB;AACvB;AACF;AACqB;AAGvB;AAEA;AACG;AACoE;AACxC;AACvB;AACuB;AAC3B;AACwB;AACa;AAEpC;AAAc;AAMV;AAAkB;AAGtB;AAAc;AACZ;AAAe;AACf;AAAe;AAAmC;AAKlD;AAAiB;AACf;AACM;AACK;AACD;AACM;AACH;AAQxB;AAEO;AAGL;AACA;AAEA;AACE;AAIE;AAAgC;AAEhC;AAA+B;AACjC;AAGF;AAAO;AACL;AACA;AAEJ;;"}
@@ -12,8 +12,8 @@ const INBOX_NOTIFICATION_THREAD_MAX_COMMENTS = 3;
12
12
  function InboxNotificationComment({
13
13
  comment,
14
14
  showHeader = true,
15
- showAttachments,
16
- showReactions,
15
+ showAttachments = true,
16
+ showReactions = true,
17
17
  overrides: overrides$1,
18
18
  className,
19
19
  ...props
@@ -48,11 +48,13 @@ function InboxNotificationComment({
48
48
  disabled: true
49
49
  }))), showAttachments && comment.attachments.length > 0 ? /* @__PURE__ */ React.createElement("div", {
50
50
  className: "lb-comment-attachments"
51
+ }, /* @__PURE__ */ React.createElement("div", {
52
+ className: "lb-attachments"
51
53
  }, comment.attachments.map((attachment) => /* @__PURE__ */ React.createElement(Comment.CommentNonInteractiveFileAttachment, {
52
54
  key: attachment.id,
53
55
  attachment,
54
56
  overrides: overrides$1
55
- }))) : null) : /* @__PURE__ */ React.createElement("div", {
57
+ })))) : null) : /* @__PURE__ */ React.createElement("div", {
56
58
  className: "lb-comment-body"
57
59
  }, /* @__PURE__ */ React.createElement("p", {
58
60
  className: "lb-comment-deleted"
@@ -1 +1 @@
1
- {"version":3,"file":"InboxNotificationThread.js","sources":["../../../src/components/internal/InboxNotificationThread.tsx"],"sourcesContent":["import type {\n BaseMetadata,\n CommentData,\n InboxNotificationThreadData,\n ThreadData,\n} from \"@liveblocks/core\";\nimport { getMentionedIdsFromCommentBody } from \"@liveblocks/core\";\nimport type { ComponentProps } from \"react\";\nimport React from \"react\";\n\nimport {\n type CommentOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../../overrides\";\nimport * as CommentPrimitive from \"../../primitives/Comment\";\nimport { classNames } from \"../../utils/class-names\";\nimport {\n CommentMention,\n CommentNonInteractiveFileAttachment,\n CommentNonInteractiveLink,\n CommentNonInteractiveReaction,\n} from \"../Comment\";\nimport { User } from \"./User\";\n\ntype InboxNotificationThreadCommentsContents = {\n type: \"comments\";\n unread: boolean;\n comments: CommentData[];\n userIds: string[];\n date: Date;\n};\n\ntype InboxNotificationThreadMentionContents = {\n type: \"mention\";\n unread: boolean;\n comments: CommentData[];\n userIds: string[];\n date: Date;\n};\n\nexport const INBOX_NOTIFICATION_THREAD_MAX_COMMENTS = 3;\n\ntype InboxNotificationThreadContents =\n | InboxNotificationThreadCommentsContents\n | InboxNotificationThreadMentionContents;\n\ninterface InboxNotificationCommentProps extends ComponentProps<\"div\"> {\n comment: CommentData;\n showHeader?: boolean;\n showAttachments?: boolean;\n showReactions?: boolean;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\nexport function InboxNotificationComment({\n comment,\n showHeader = true,\n showAttachments,\n showReactions,\n overrides,\n className,\n ...props\n}: InboxNotificationCommentProps) {\n const $ = useOverrides(overrides);\n\n return (\n <div\n className={classNames(\n \"lb-root lb-inbox-notification-comment lb-comment\",\n className\n )}\n {...props}\n >\n {showHeader && (\n <div className=\"lb-comment-header\">\n <User className=\"lb-comment-author\" userId={comment.userId} />\n </div>\n )}\n <div className=\"lb-comment-content\">\n {comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n body={comment.body}\n components={{\n Mention: CommentMention,\n Link: CommentNonInteractiveLink,\n }}\n />\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentNonInteractiveReaction\n key={reaction.emoji}\n reaction={reaction}\n overrides={overrides}\n disabled\n />\n ))}\n </div>\n )}\n {showAttachments && comment.attachments.length > 0 ? (\n <div className=\"lb-comment-attachments\">\n {comment.attachments.map((attachment) => (\n <CommentNonInteractiveFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n />\n ))}\n </div>\n ) : null}\n </>\n ) : (\n <div className=\"lb-comment-body\">\n <p className=\"lb-comment-deleted\">{$.COMMENT_DELETED}</p>\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * Find the last comment with a mention for the given user ID,\n * unless the comment was created by the user themselves.\n */\nfunction findLastCommentWithMentionedId(\n comments: CommentData[],\n mentionedId: string\n) {\n for (let i = comments.length - 1; i >= 0; i--) {\n const comment = comments[i];\n\n if (comment.userId === mentionedId) {\n continue;\n }\n\n if (comment.body) {\n const mentionedIds = getMentionedIdsFromCommentBody(comment.body);\n\n if (mentionedIds.includes(mentionedId)) {\n return comment;\n }\n }\n }\n\n return;\n}\n\nfunction getUserIdsFromComments(comments: CommentData[]) {\n return Array.from(new Set(comments.map((comment) => comment.userId)));\n}\n\nexport function generateInboxNotificationThreadContents(\n inboxNotification: InboxNotificationThreadData,\n thread: ThreadData<BaseMetadata>,\n userId: string\n): InboxNotificationThreadContents {\n const unreadComments = thread.comments.filter((comment) => {\n if (!comment.body) {\n return false;\n }\n\n return inboxNotification.readAt\n ? comment.createdAt > inboxNotification.readAt &&\n comment.createdAt <= inboxNotification.notifiedAt\n : comment.createdAt <= inboxNotification.notifiedAt;\n });\n\n // If the thread is read, show the last comments.\n if (unreadComments.length === 0) {\n const lastComments = thread.comments\n .filter((comment) => comment.body)\n .slice(-INBOX_NOTIFICATION_THREAD_MAX_COMMENTS);\n\n return {\n type: \"comments\",\n unread: false,\n comments: lastComments,\n userIds: getUserIdsFromComments(lastComments),\n date: inboxNotification.notifiedAt,\n };\n }\n\n const commentWithMention = findLastCommentWithMentionedId(\n unreadComments,\n userId\n );\n\n // If the thread contains one or more mentions for the current user, show the last comment with a mention.\n if (commentWithMention) {\n return {\n type: \"mention\",\n unread: true,\n comments: [commentWithMention],\n userIds: [commentWithMention.userId],\n date: commentWithMention.createdAt,\n };\n }\n\n const lastUnreadComments = unreadComments.slice(\n -INBOX_NOTIFICATION_THREAD_MAX_COMMENTS\n );\n\n // Otherwise, show the last unread comments.\n return {\n type: \"comments\",\n unread: true,\n comments: lastUnreadComments,\n userIds: getUserIdsFromComments(unreadComments),\n date: inboxNotification.notifiedAt,\n };\n}\n"],"names":["overrides","useOverrides","classNames","User","CommentPrimitive.Body","CommentMention","CommentNonInteractiveLink","CommentNonInteractiveReaction","CommentNonInteractiveFileAttachment","getMentionedIdsFromCommentBody"],"mappings":"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"}
1
+ {"version":3,"file":"InboxNotificationThread.js","sources":["../../../src/components/internal/InboxNotificationThread.tsx"],"sourcesContent":["import type {\n BaseMetadata,\n CommentData,\n InboxNotificationThreadData,\n ThreadData,\n} from \"@liveblocks/core\";\nimport { getMentionedIdsFromCommentBody } from \"@liveblocks/core\";\nimport type { ComponentProps } from \"react\";\nimport React from \"react\";\n\nimport {\n type CommentOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../../overrides\";\nimport * as CommentPrimitive from \"../../primitives/Comment\";\nimport { classNames } from \"../../utils/class-names\";\nimport {\n CommentMention,\n CommentNonInteractiveFileAttachment,\n CommentNonInteractiveLink,\n CommentNonInteractiveReaction,\n} from \"../Comment\";\nimport { User } from \"./User\";\n\ntype InboxNotificationThreadCommentsContents = {\n type: \"comments\";\n unread: boolean;\n comments: CommentData[];\n userIds: string[];\n date: Date;\n};\n\ntype InboxNotificationThreadMentionContents = {\n type: \"mention\";\n unread: boolean;\n comments: CommentData[];\n userIds: string[];\n date: Date;\n};\n\nexport const INBOX_NOTIFICATION_THREAD_MAX_COMMENTS = 3;\n\ntype InboxNotificationThreadContents =\n | InboxNotificationThreadCommentsContents\n | InboxNotificationThreadMentionContents;\n\ninterface InboxNotificationCommentProps extends ComponentProps<\"div\"> {\n comment: CommentData;\n showHeader?: boolean;\n showAttachments?: boolean;\n showReactions?: boolean;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\nexport function InboxNotificationComment({\n comment,\n showHeader = true,\n showAttachments = true,\n showReactions = true,\n overrides,\n className,\n ...props\n}: InboxNotificationCommentProps) {\n const $ = useOverrides(overrides);\n\n return (\n <div\n className={classNames(\n \"lb-root lb-inbox-notification-comment lb-comment\",\n className\n )}\n {...props}\n >\n {showHeader && (\n <div className=\"lb-comment-header\">\n <User className=\"lb-comment-author\" userId={comment.userId} />\n </div>\n )}\n <div className=\"lb-comment-content\">\n {comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n body={comment.body}\n components={{\n Mention: CommentMention,\n Link: CommentNonInteractiveLink,\n }}\n />\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentNonInteractiveReaction\n key={reaction.emoji}\n reaction={reaction}\n overrides={overrides}\n disabled\n />\n ))}\n </div>\n )}\n {showAttachments && comment.attachments.length > 0 ? (\n <div className=\"lb-comment-attachments\">\n <div className=\"lb-attachments\">\n {comment.attachments.map((attachment) => (\n <CommentNonInteractiveFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n />\n ))}\n </div>\n </div>\n ) : null}\n </>\n ) : (\n <div className=\"lb-comment-body\">\n <p className=\"lb-comment-deleted\">{$.COMMENT_DELETED}</p>\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * Find the last comment with a mention for the given user ID,\n * unless the comment was created by the user themselves.\n */\nfunction findLastCommentWithMentionedId(\n comments: CommentData[],\n mentionedId: string\n) {\n for (let i = comments.length - 1; i >= 0; i--) {\n const comment = comments[i];\n\n if (comment.userId === mentionedId) {\n continue;\n }\n\n if (comment.body) {\n const mentionedIds = getMentionedIdsFromCommentBody(comment.body);\n\n if (mentionedIds.includes(mentionedId)) {\n return comment;\n }\n }\n }\n\n return;\n}\n\nfunction getUserIdsFromComments(comments: CommentData[]) {\n return Array.from(new Set(comments.map((comment) => comment.userId)));\n}\n\nexport function generateInboxNotificationThreadContents(\n inboxNotification: InboxNotificationThreadData,\n thread: ThreadData<BaseMetadata>,\n userId: string\n): InboxNotificationThreadContents {\n const unreadComments = thread.comments.filter((comment) => {\n if (!comment.body) {\n return false;\n }\n\n return inboxNotification.readAt\n ? comment.createdAt > inboxNotification.readAt &&\n comment.createdAt <= inboxNotification.notifiedAt\n : comment.createdAt <= inboxNotification.notifiedAt;\n });\n\n // If the thread is read, show the last comments.\n if (unreadComments.length === 0) {\n const lastComments = thread.comments\n .filter((comment) => comment.body)\n .slice(-INBOX_NOTIFICATION_THREAD_MAX_COMMENTS);\n\n return {\n type: \"comments\",\n unread: false,\n comments: lastComments,\n userIds: getUserIdsFromComments(lastComments),\n date: inboxNotification.notifiedAt,\n };\n }\n\n const commentWithMention = findLastCommentWithMentionedId(\n unreadComments,\n userId\n );\n\n // If the thread contains one or more mentions for the current user, show the last comment with a mention.\n if (commentWithMention) {\n return {\n type: \"mention\",\n unread: true,\n comments: [commentWithMention],\n userIds: [commentWithMention.userId],\n date: commentWithMention.createdAt,\n };\n }\n\n const lastUnreadComments = unreadComments.slice(\n -INBOX_NOTIFICATION_THREAD_MAX_COMMENTS\n );\n\n // Otherwise, show the last unread comments.\n return {\n type: \"comments\",\n unread: true,\n comments: lastUnreadComments,\n userIds: getUserIdsFromComments(unreadComments),\n date: inboxNotification.notifiedAt,\n };\n}\n"],"names":["overrides","useOverrides","classNames","User","CommentPrimitive.Body","CommentMention","CommentNonInteractiveLink","CommentNonInteractiveReaction","CommentNonInteractiveFileAttachment","getMentionedIdsFromCommentBody"],"mappings":"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"}
@@ -10,8 +10,8 @@ const INBOX_NOTIFICATION_THREAD_MAX_COMMENTS = 3;
10
10
  function InboxNotificationComment({
11
11
  comment,
12
12
  showHeader = true,
13
- showAttachments,
14
- showReactions,
13
+ showAttachments = true,
14
+ showReactions = true,
15
15
  overrides,
16
16
  className,
17
17
  ...props
@@ -46,11 +46,13 @@ function InboxNotificationComment({
46
46
  disabled: true
47
47
  }))), showAttachments && comment.attachments.length > 0 ? /* @__PURE__ */ React__default.createElement("div", {
48
48
  className: "lb-comment-attachments"
49
+ }, /* @__PURE__ */ React__default.createElement("div", {
50
+ className: "lb-attachments"
49
51
  }, comment.attachments.map((attachment) => /* @__PURE__ */ React__default.createElement(CommentNonInteractiveFileAttachment, {
50
52
  key: attachment.id,
51
53
  attachment,
52
54
  overrides
53
- }))) : null) : /* @__PURE__ */ React__default.createElement("div", {
55
+ })))) : null) : /* @__PURE__ */ React__default.createElement("div", {
54
56
  className: "lb-comment-body"
55
57
  }, /* @__PURE__ */ React__default.createElement("p", {
56
58
  className: "lb-comment-deleted"
@@ -1 +1 @@
1
- {"version":3,"file":"InboxNotificationThread.mjs","sources":["../../../src/components/internal/InboxNotificationThread.tsx"],"sourcesContent":["import type {\n BaseMetadata,\n CommentData,\n InboxNotificationThreadData,\n ThreadData,\n} from \"@liveblocks/core\";\nimport { getMentionedIdsFromCommentBody } from \"@liveblocks/core\";\nimport type { ComponentProps } from \"react\";\nimport React from \"react\";\n\nimport {\n type CommentOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../../overrides\";\nimport * as CommentPrimitive from \"../../primitives/Comment\";\nimport { classNames } from \"../../utils/class-names\";\nimport {\n CommentMention,\n CommentNonInteractiveFileAttachment,\n CommentNonInteractiveLink,\n CommentNonInteractiveReaction,\n} from \"../Comment\";\nimport { User } from \"./User\";\n\ntype InboxNotificationThreadCommentsContents = {\n type: \"comments\";\n unread: boolean;\n comments: CommentData[];\n userIds: string[];\n date: Date;\n};\n\ntype InboxNotificationThreadMentionContents = {\n type: \"mention\";\n unread: boolean;\n comments: CommentData[];\n userIds: string[];\n date: Date;\n};\n\nexport const INBOX_NOTIFICATION_THREAD_MAX_COMMENTS = 3;\n\ntype InboxNotificationThreadContents =\n | InboxNotificationThreadCommentsContents\n | InboxNotificationThreadMentionContents;\n\ninterface InboxNotificationCommentProps extends ComponentProps<\"div\"> {\n comment: CommentData;\n showHeader?: boolean;\n showAttachments?: boolean;\n showReactions?: boolean;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\nexport function InboxNotificationComment({\n comment,\n showHeader = true,\n showAttachments,\n showReactions,\n overrides,\n className,\n ...props\n}: InboxNotificationCommentProps) {\n const $ = useOverrides(overrides);\n\n return (\n <div\n className={classNames(\n \"lb-root lb-inbox-notification-comment lb-comment\",\n className\n )}\n {...props}\n >\n {showHeader && (\n <div className=\"lb-comment-header\">\n <User className=\"lb-comment-author\" userId={comment.userId} />\n </div>\n )}\n <div className=\"lb-comment-content\">\n {comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n body={comment.body}\n components={{\n Mention: CommentMention,\n Link: CommentNonInteractiveLink,\n }}\n />\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentNonInteractiveReaction\n key={reaction.emoji}\n reaction={reaction}\n overrides={overrides}\n disabled\n />\n ))}\n </div>\n )}\n {showAttachments && comment.attachments.length > 0 ? (\n <div className=\"lb-comment-attachments\">\n {comment.attachments.map((attachment) => (\n <CommentNonInteractiveFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n />\n ))}\n </div>\n ) : null}\n </>\n ) : (\n <div className=\"lb-comment-body\">\n <p className=\"lb-comment-deleted\">{$.COMMENT_DELETED}</p>\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * Find the last comment with a mention for the given user ID,\n * unless the comment was created by the user themselves.\n */\nfunction findLastCommentWithMentionedId(\n comments: CommentData[],\n mentionedId: string\n) {\n for (let i = comments.length - 1; i >= 0; i--) {\n const comment = comments[i];\n\n if (comment.userId === mentionedId) {\n continue;\n }\n\n if (comment.body) {\n const mentionedIds = getMentionedIdsFromCommentBody(comment.body);\n\n if (mentionedIds.includes(mentionedId)) {\n return comment;\n }\n }\n }\n\n return;\n}\n\nfunction getUserIdsFromComments(comments: CommentData[]) {\n return Array.from(new Set(comments.map((comment) => comment.userId)));\n}\n\nexport function generateInboxNotificationThreadContents(\n inboxNotification: InboxNotificationThreadData,\n thread: ThreadData<BaseMetadata>,\n userId: string\n): InboxNotificationThreadContents {\n const unreadComments = thread.comments.filter((comment) => {\n if (!comment.body) {\n return false;\n }\n\n return inboxNotification.readAt\n ? comment.createdAt > inboxNotification.readAt &&\n comment.createdAt <= inboxNotification.notifiedAt\n : comment.createdAt <= inboxNotification.notifiedAt;\n });\n\n // If the thread is read, show the last comments.\n if (unreadComments.length === 0) {\n const lastComments = thread.comments\n .filter((comment) => comment.body)\n .slice(-INBOX_NOTIFICATION_THREAD_MAX_COMMENTS);\n\n return {\n type: \"comments\",\n unread: false,\n comments: lastComments,\n userIds: getUserIdsFromComments(lastComments),\n date: inboxNotification.notifiedAt,\n };\n }\n\n const commentWithMention = findLastCommentWithMentionedId(\n unreadComments,\n userId\n );\n\n // If the thread contains one or more mentions for the current user, show the last comment with a mention.\n if (commentWithMention) {\n return {\n type: \"mention\",\n unread: true,\n comments: [commentWithMention],\n userIds: [commentWithMention.userId],\n date: commentWithMention.createdAt,\n };\n }\n\n const lastUnreadComments = unreadComments.slice(\n -INBOX_NOTIFICATION_THREAD_MAX_COMMENTS\n );\n\n // Otherwise, show the last unread comments.\n return {\n type: \"comments\",\n unread: true,\n comments: lastUnreadComments,\n userIds: getUserIdsFromComments(unreadComments),\n date: inboxNotification.notifiedAt,\n };\n}\n"],"names":["React","CommentPrimitive.Body"],"mappings":"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"}
1
+ {"version":3,"file":"InboxNotificationThread.mjs","sources":["../../../src/components/internal/InboxNotificationThread.tsx"],"sourcesContent":["import type {\n BaseMetadata,\n CommentData,\n InboxNotificationThreadData,\n ThreadData,\n} from \"@liveblocks/core\";\nimport { getMentionedIdsFromCommentBody } from \"@liveblocks/core\";\nimport type { ComponentProps } from \"react\";\nimport React from \"react\";\n\nimport {\n type CommentOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../../overrides\";\nimport * as CommentPrimitive from \"../../primitives/Comment\";\nimport { classNames } from \"../../utils/class-names\";\nimport {\n CommentMention,\n CommentNonInteractiveFileAttachment,\n CommentNonInteractiveLink,\n CommentNonInteractiveReaction,\n} from \"../Comment\";\nimport { User } from \"./User\";\n\ntype InboxNotificationThreadCommentsContents = {\n type: \"comments\";\n unread: boolean;\n comments: CommentData[];\n userIds: string[];\n date: Date;\n};\n\ntype InboxNotificationThreadMentionContents = {\n type: \"mention\";\n unread: boolean;\n comments: CommentData[];\n userIds: string[];\n date: Date;\n};\n\nexport const INBOX_NOTIFICATION_THREAD_MAX_COMMENTS = 3;\n\ntype InboxNotificationThreadContents =\n | InboxNotificationThreadCommentsContents\n | InboxNotificationThreadMentionContents;\n\ninterface InboxNotificationCommentProps extends ComponentProps<\"div\"> {\n comment: CommentData;\n showHeader?: boolean;\n showAttachments?: boolean;\n showReactions?: boolean;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\nexport function InboxNotificationComment({\n comment,\n showHeader = true,\n showAttachments = true,\n showReactions = true,\n overrides,\n className,\n ...props\n}: InboxNotificationCommentProps) {\n const $ = useOverrides(overrides);\n\n return (\n <div\n className={classNames(\n \"lb-root lb-inbox-notification-comment lb-comment\",\n className\n )}\n {...props}\n >\n {showHeader && (\n <div className=\"lb-comment-header\">\n <User className=\"lb-comment-author\" userId={comment.userId} />\n </div>\n )}\n <div className=\"lb-comment-content\">\n {comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n body={comment.body}\n components={{\n Mention: CommentMention,\n Link: CommentNonInteractiveLink,\n }}\n />\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentNonInteractiveReaction\n key={reaction.emoji}\n reaction={reaction}\n overrides={overrides}\n disabled\n />\n ))}\n </div>\n )}\n {showAttachments && comment.attachments.length > 0 ? (\n <div className=\"lb-comment-attachments\">\n <div className=\"lb-attachments\">\n {comment.attachments.map((attachment) => (\n <CommentNonInteractiveFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n />\n ))}\n </div>\n </div>\n ) : null}\n </>\n ) : (\n <div className=\"lb-comment-body\">\n <p className=\"lb-comment-deleted\">{$.COMMENT_DELETED}</p>\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * Find the last comment with a mention for the given user ID,\n * unless the comment was created by the user themselves.\n */\nfunction findLastCommentWithMentionedId(\n comments: CommentData[],\n mentionedId: string\n) {\n for (let i = comments.length - 1; i >= 0; i--) {\n const comment = comments[i];\n\n if (comment.userId === mentionedId) {\n continue;\n }\n\n if (comment.body) {\n const mentionedIds = getMentionedIdsFromCommentBody(comment.body);\n\n if (mentionedIds.includes(mentionedId)) {\n return comment;\n }\n }\n }\n\n return;\n}\n\nfunction getUserIdsFromComments(comments: CommentData[]) {\n return Array.from(new Set(comments.map((comment) => comment.userId)));\n}\n\nexport function generateInboxNotificationThreadContents(\n inboxNotification: InboxNotificationThreadData,\n thread: ThreadData<BaseMetadata>,\n userId: string\n): InboxNotificationThreadContents {\n const unreadComments = thread.comments.filter((comment) => {\n if (!comment.body) {\n return false;\n }\n\n return inboxNotification.readAt\n ? comment.createdAt > inboxNotification.readAt &&\n comment.createdAt <= inboxNotification.notifiedAt\n : comment.createdAt <= inboxNotification.notifiedAt;\n });\n\n // If the thread is read, show the last comments.\n if (unreadComments.length === 0) {\n const lastComments = thread.comments\n .filter((comment) => comment.body)\n .slice(-INBOX_NOTIFICATION_THREAD_MAX_COMMENTS);\n\n return {\n type: \"comments\",\n unread: false,\n comments: lastComments,\n userIds: getUserIdsFromComments(lastComments),\n date: inboxNotification.notifiedAt,\n };\n }\n\n const commentWithMention = findLastCommentWithMentionedId(\n unreadComments,\n userId\n );\n\n // If the thread contains one or more mentions for the current user, show the last comment with a mention.\n if (commentWithMention) {\n return {\n type: \"mention\",\n unread: true,\n comments: [commentWithMention],\n userIds: [commentWithMention.userId],\n date: commentWithMention.createdAt,\n };\n }\n\n const lastUnreadComments = unreadComments.slice(\n -INBOX_NOTIFICATION_THREAD_MAX_COMMENTS\n );\n\n // Otherwise, show the last unread comments.\n return {\n type: \"comments\",\n unread: true,\n comments: lastUnreadComments,\n userIds: getUserIdsFromComments(unreadComments),\n date: inboxNotification.notifiedAt,\n };\n}\n"],"names":["React","CommentPrimitive.Body"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -226,6 +226,10 @@ interface ComposerFormProps extends ComponentPropsWithSlot<"form"> {
226
226
  * The composer's initial attachments.
227
227
  */
228
228
  defaultAttachments?: CommentAttachment[];
229
+ /**
230
+ * Whether to create attachments when pasting files into the editor.
231
+ */
232
+ pasteFilesAsAttachments?: boolean;
229
233
  }
230
234
  interface ComposerSubmitComment {
231
235
  /**
package/dist/index.d.ts CHANGED
@@ -226,6 +226,10 @@ interface ComposerFormProps extends ComponentPropsWithSlot<"form"> {
226
226
  * The composer's initial attachments.
227
227
  */
228
228
  defaultAttachments?: CommentAttachment[];
229
+ /**
230
+ * Whether to create attachments when pasting files into the editor.
231
+ */
232
+ pasteFilesAsAttachments?: boolean;
229
233
  }
230
234
  interface ComposerSubmitComment {
231
235
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"contexts.js","sources":["../../../src/primitives/Composer/contexts.ts"],"sourcesContent":["import type { Placement } from \"@floating-ui/react-dom\";\nimport type { CommentMixedAttachment } from \"@liveblocks/core\";\nimport { nn } from \"@liveblocks/core\";\nimport type { Direction } from \"@radix-ui/react-dropdown-menu\";\nimport type { Dispatch, Ref, SetStateAction } from \"react\";\nimport { createContext, useContext } from \"react\";\nimport type { Editor as SlateEditor, Element as SlateElement } from \"slate\";\n\nexport type ComposerContext = {\n /**\n * Whether the composer is currently disabled.\n */\n isDisabled: boolean;\n\n /**\n * Whether the composer can currently be submitted.\n */\n canSubmit: boolean;\n\n /**\n * Whether the editor is currently focused.\n */\n isFocused: boolean;\n\n /**\n * Whether the editor is currently empty.\n */\n isEmpty: boolean;\n\n /**\n * Submit the composer programmatically.\n */\n submit: () => void;\n\n /**\n * Clear the composer programmatically.\n */\n clear: () => void;\n\n /**\n * Select the editor programmatically.\n */\n select: () => void;\n\n /**\n * Focus the editor programmatically.\n */\n focus: () => void;\n\n /**\n * Blur the editor programmatically.\n */\n blur: () => void;\n\n /**\n * Start creating a mention at the current selection.\n */\n createMention: () => void;\n\n /**\n * Insert text at the current selection.\n */\n insertText: (text: string) => void;\n\n /**\n * Open a file picker programmatically to create attachments.\n */\n attachFiles: () => void;\n\n /**\n * The composer's current attachments.\n */\n attachments: CommentMixedAttachment[];\n\n /**\n * Remove an attachment by its ID.\n */\n removeAttachment: (attachmentId: string) => void;\n};\n\nexport type ComposerEditorContext = {\n validate: (value: SlateElement[]) => void;\n editor: SlateEditor;\n setFocused: Dispatch<SetStateAction<boolean>>;\n};\n\nexport type ComposerAttachmentsContext = {\n canAddAttachments: boolean;\n createAttachments: (files: File[]) => void;\n isUploadingAttachments: boolean;\n maxAttachments: number;\n maxAttachmentSize: number;\n};\n\nexport type ComposerSuggestionsContext = {\n dir?: Direction;\n id: string;\n itemId: (value?: string) => string | undefined;\n placement: Placement;\n selectedValue?: string;\n setSelectedValue: (value: string) => void;\n onItemSelect: (value: string) => void;\n ref: Ref<HTMLDivElement>;\n};\n\nexport const ComposerContext = createContext<ComposerContext | null>(null);\nexport const ComposerEditorContext =\n createContext<ComposerEditorContext | null>(null);\nexport const ComposerAttachmentsContext =\n createContext<ComposerAttachmentsContext | null>(null);\nexport const ComposerSuggestionsContext =\n createContext<ComposerSuggestionsContext | null>(null);\n\nexport function useComposerEditorContext() {\n const composerEditorContext = useContext(ComposerEditorContext);\n\n return nn(\n composerEditorContext,\n \"Composer.Form is missing from the React tree.\"\n );\n}\n\nexport function useComposerAttachmentsContextOrNull() {\n return useContext(ComposerAttachmentsContext);\n}\n\nexport function useComposerAttachmentsContext() {\n const composerAttachmentsContext = useComposerAttachmentsContextOrNull();\n\n return nn(\n composerAttachmentsContext,\n \"Composer.Form is missing from the React tree.\"\n );\n}\n\nexport function useComposerSuggestionsContext(\n source = \"useComposerSuggestionsContext\"\n) {\n const composerSuggestionsContext = useContext(ComposerSuggestionsContext);\n\n return nn(\n composerSuggestionsContext,\n `${source} can’t be used outside of Composer.Editor.`\n );\n}\n\nexport function useComposer(): ComposerContext {\n const composerContext = useContext(ComposerContext);\n\n return nn(composerContext, \"Composer.Form is missing from the React tree.\");\n}\n"],"names":["createContext","useContext","nn"],"mappings":";;;;;AAyGa,MAAA,eAAA,GAAkBA,oBAAsC,IAAI,EAAA;AAC5D,MAAA,qBAAA,GACXA,oBAA4C,IAAI,EAAA;AACrC,MAAA,0BAAA,GACXA,oBAAiD,IAAI,EAAA;AAC1C,MAAA,0BAAA,GACXA,oBAAiD,IAAI,EAAA;AAEhD,SAAS,wBAA2B,GAAA;AACzC,EAAM,MAAA,qBAAA,GAAwBC,iBAAW,qBAAqB,CAAA,CAAA;AAE9D,EAAO,OAAAC,OAAA;AAAA,IACL,qBAAA;AAAA,IACA,+CAAA;AAAA,GACF,CAAA;AACF,CAAA;AAEO,SAAS,mCAAsC,GAAA;AACpD,EAAA,OAAOD,iBAAW,0BAA0B,CAAA,CAAA;AAC9C,CAAA;AAEO,SAAS,6BAAgC,GAAA;AAC9C,EAAA,MAAM,6BAA6B,mCAAoC,EAAA,CAAA;AAEvE,EAAO,OAAAC,OAAA;AAAA,IACL,0BAAA;AAAA,IACA,+CAAA;AAAA,GACF,CAAA;AACF,CAAA;AAEgB,SAAA,6BAAA,CACd,SAAS,+BACT,EAAA;AACA,EAAM,MAAA,0BAAA,GAA6BD,iBAAW,0BAA0B,CAAA,CAAA;AAExE,EAAO,OAAAC,OAAA;AAAA,IACL,0BAAA;AAAA,IACA,CAAG,EAAA,MAAA,CAAA,+CAAA,CAAA;AAAA,GACL,CAAA;AACF,CAAA;AAEO,SAAS,WAA+B,GAAA;AAC7C,EAAM,MAAA,eAAA,GAAkBD,iBAAW,eAAe,CAAA,CAAA;AAElD,EAAO,OAAAC,OAAA,CAAG,iBAAiB,+CAA+C,CAAA,CAAA;AAC5E;;;;;;;;;;;;"}
1
+ {"version":3,"file":"contexts.js","sources":["../../../src/primitives/Composer/contexts.ts"],"sourcesContent":["import type { Placement } from \"@floating-ui/react-dom\";\nimport type { CommentMixedAttachment } from \"@liveblocks/core\";\nimport { nn } from \"@liveblocks/core\";\nimport type { Direction } from \"@radix-ui/react-dropdown-menu\";\nimport type { Dispatch, Ref, SetStateAction } from \"react\";\nimport { createContext, useContext } from \"react\";\nimport type { Editor as SlateEditor, Element as SlateElement } from \"slate\";\n\nexport type ComposerContext = {\n /**\n * Whether the composer is currently disabled.\n */\n isDisabled: boolean;\n\n /**\n * Whether the composer can currently be submitted.\n */\n canSubmit: boolean;\n\n /**\n * Whether the editor is currently focused.\n */\n isFocused: boolean;\n\n /**\n * Whether the editor is currently empty.\n */\n isEmpty: boolean;\n\n /**\n * Submit the composer programmatically.\n */\n submit: () => void;\n\n /**\n * Clear the composer programmatically.\n */\n clear: () => void;\n\n /**\n * Select the editor programmatically.\n */\n select: () => void;\n\n /**\n * Focus the editor programmatically.\n */\n focus: () => void;\n\n /**\n * Blur the editor programmatically.\n */\n blur: () => void;\n\n /**\n * Start creating a mention at the current selection.\n */\n createMention: () => void;\n\n /**\n * Insert text at the current selection.\n */\n insertText: (text: string) => void;\n\n /**\n * Open a file picker programmatically to create attachments.\n */\n attachFiles: () => void;\n\n /**\n * The composer's current attachments.\n */\n attachments: CommentMixedAttachment[];\n\n /**\n * Remove an attachment by its ID.\n */\n removeAttachment: (attachmentId: string) => void;\n};\n\nexport type ComposerEditorContext = {\n validate: (value: SlateElement[]) => void;\n editor: SlateEditor;\n setFocused: Dispatch<SetStateAction<boolean>>;\n};\n\nexport type ComposerAttachmentsContext = {\n hasMaxAttachments: boolean;\n createAttachments: (files: File[]) => void;\n isUploadingAttachments: boolean;\n maxAttachments: number;\n maxAttachmentSize: number;\n};\n\nexport type ComposerSuggestionsContext = {\n dir?: Direction;\n id: string;\n itemId: (value?: string) => string | undefined;\n placement: Placement;\n selectedValue?: string;\n setSelectedValue: (value: string) => void;\n onItemSelect: (value: string) => void;\n ref: Ref<HTMLDivElement>;\n};\n\nexport const ComposerContext = createContext<ComposerContext | null>(null);\nexport const ComposerEditorContext =\n createContext<ComposerEditorContext | null>(null);\nexport const ComposerAttachmentsContext =\n createContext<ComposerAttachmentsContext | null>(null);\nexport const ComposerSuggestionsContext =\n createContext<ComposerSuggestionsContext | null>(null);\n\nexport function useComposerEditorContext() {\n const composerEditorContext = useContext(ComposerEditorContext);\n\n return nn(\n composerEditorContext,\n \"Composer.Form is missing from the React tree.\"\n );\n}\n\nexport function useComposerAttachmentsContextOrNull() {\n return useContext(ComposerAttachmentsContext);\n}\n\nexport function useComposerAttachmentsContext() {\n const composerAttachmentsContext = useComposerAttachmentsContextOrNull();\n\n return nn(\n composerAttachmentsContext,\n \"Composer.Form is missing from the React tree.\"\n );\n}\n\nexport function useComposerSuggestionsContext(\n source = \"useComposerSuggestionsContext\"\n) {\n const composerSuggestionsContext = useContext(ComposerSuggestionsContext);\n\n return nn(\n composerSuggestionsContext,\n `${source} can’t be used outside of Composer.Editor.`\n );\n}\n\nexport function useComposer(): ComposerContext {\n const composerContext = useContext(ComposerContext);\n\n return nn(composerContext, \"Composer.Form is missing from the React tree.\");\n}\n"],"names":["createContext","useContext","nn"],"mappings":";;;;;AAyGa,MAAA,eAAA,GAAkBA,oBAAsC,IAAI,EAAA;AAC5D,MAAA,qBAAA,GACXA,oBAA4C,IAAI,EAAA;AACrC,MAAA,0BAAA,GACXA,oBAAiD,IAAI,EAAA;AAC1C,MAAA,0BAAA,GACXA,oBAAiD,IAAI,EAAA;AAEhD,SAAS,wBAA2B,GAAA;AACzC,EAAM,MAAA,qBAAA,GAAwBC,iBAAW,qBAAqB,CAAA,CAAA;AAE9D,EAAO,OAAAC,OAAA;AAAA,IACL,qBAAA;AAAA,IACA,+CAAA;AAAA,GACF,CAAA;AACF,CAAA;AAEO,SAAS,mCAAsC,GAAA;AACpD,EAAA,OAAOD,iBAAW,0BAA0B,CAAA,CAAA;AAC9C,CAAA;AAEO,SAAS,6BAAgC,GAAA;AAC9C,EAAA,MAAM,6BAA6B,mCAAoC,EAAA,CAAA;AAEvE,EAAO,OAAAC,OAAA;AAAA,IACL,0BAAA;AAAA,IACA,+CAAA;AAAA,GACF,CAAA;AACF,CAAA;AAEgB,SAAA,6BAAA,CACd,SAAS,+BACT,EAAA;AACA,EAAM,MAAA,0BAAA,GAA6BD,iBAAW,0BAA0B,CAAA,CAAA;AAExE,EAAO,OAAAC,OAAA;AAAA,IACL,0BAAA;AAAA,IACA,CAAG,EAAA,MAAA,CAAA,+CAAA,CAAA;AAAA,GACL,CAAA;AACF,CAAA;AAEO,SAAS,WAA+B,GAAA;AAC7C,EAAM,MAAA,eAAA,GAAkBD,iBAAW,eAAe,CAAA,CAAA;AAElD,EAAO,OAAAC,OAAA,CAAG,iBAAiB,+CAA+C,CAAA,CAAA;AAC5E;;;;;;;;;;;;"}