@liveblocks/react-ui 2.5.2 → 2.7.0-beta1

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 (104) hide show
  1. package/dist/components/Comment.js +61 -3
  2. package/dist/components/Comment.js.map +1 -1
  3. package/dist/components/Comment.mjs +62 -5
  4. package/dist/components/Comment.mjs.map +1 -1
  5. package/dist/components/Composer.js +217 -101
  6. package/dist/components/Composer.js.map +1 -1
  7. package/dist/components/Composer.mjs +220 -104
  8. package/dist/components/Composer.mjs.map +1 -1
  9. package/dist/components/InboxNotification.js +17 -4
  10. package/dist/components/InboxNotification.js.map +1 -1
  11. package/dist/components/InboxNotification.mjs +17 -4
  12. package/dist/components/InboxNotification.mjs.map +1 -1
  13. package/dist/components/Thread.js +2 -0
  14. package/dist/components/Thread.js.map +1 -1
  15. package/dist/components/Thread.mjs +2 -0
  16. package/dist/components/Thread.mjs.map +1 -1
  17. package/dist/components/internal/Attachment.js +226 -0
  18. package/dist/components/internal/Attachment.js.map +1 -0
  19. package/dist/components/internal/Attachment.mjs +224 -0
  20. package/dist/components/internal/Attachment.mjs.map +1 -0
  21. package/dist/components/internal/InboxNotificationThread.js +10 -2
  22. package/dist/components/internal/InboxNotificationThread.js.map +1 -1
  23. package/dist/components/internal/InboxNotificationThread.mjs +11 -3
  24. package/dist/components/internal/InboxNotificationThread.mjs.map +1 -1
  25. package/dist/icons/Attachment.js +15 -0
  26. package/dist/icons/Attachment.js.map +1 -0
  27. package/dist/icons/Attachment.mjs +13 -0
  28. package/dist/icons/Attachment.mjs.map +1 -0
  29. package/dist/icons/Spinner.js +3 -9
  30. package/dist/icons/Spinner.js.map +1 -1
  31. package/dist/icons/Spinner.mjs +4 -10
  32. package/dist/icons/Spinner.mjs.map +1 -1
  33. package/dist/icons/{Missing.js → Warning.js} +3 -3
  34. package/dist/icons/{Missing.js.map → Warning.js.map} +1 -1
  35. package/dist/icons/{Missing.mjs → Warning.mjs} +3 -3
  36. package/dist/icons/{Missing.mjs.map → Warning.mjs.map} +1 -1
  37. package/dist/index.d.mts +70 -4
  38. package/dist/index.d.ts +70 -4
  39. package/dist/index.js.map +1 -1
  40. package/dist/index.mjs.map +1 -1
  41. package/dist/overrides.js +5 -0
  42. package/dist/overrides.js.map +1 -1
  43. package/dist/overrides.mjs +5 -0
  44. package/dist/overrides.mjs.map +1 -1
  45. package/dist/primitives/Composer/contexts.js +17 -3
  46. package/dist/primitives/Composer/contexts.js.map +1 -1
  47. package/dist/primitives/Composer/contexts.mjs +15 -4
  48. package/dist/primitives/Composer/contexts.mjs.map +1 -1
  49. package/dist/primitives/Composer/index.js +185 -26
  50. package/dist/primitives/Composer/index.js.map +1 -1
  51. package/dist/primitives/Composer/index.mjs +188 -31
  52. package/dist/primitives/Composer/index.mjs.map +1 -1
  53. package/dist/primitives/Composer/utils.js +224 -0
  54. package/dist/primitives/Composer/utils.js.map +1 -1
  55. package/dist/primitives/Composer/utils.mjs +222 -1
  56. package/dist/primitives/Composer/utils.mjs.map +1 -1
  57. package/dist/primitives/EmojiPicker/utils.js +2 -2
  58. package/dist/primitives/EmojiPicker/utils.js.map +1 -1
  59. package/dist/primitives/EmojiPicker/utils.mjs +1 -1
  60. package/dist/primitives/EmojiPicker/utils.mjs.map +1 -1
  61. package/dist/primitives/FileSize.js +33 -0
  62. package/dist/primitives/FileSize.js.map +1 -0
  63. package/dist/primitives/FileSize.mjs +31 -0
  64. package/dist/primitives/FileSize.mjs.map +1 -0
  65. package/dist/primitives/index.d.mts +83 -3
  66. package/dist/primitives/index.d.ts +83 -3
  67. package/dist/primitives/index.js +4 -0
  68. package/dist/primitives/index.js.map +1 -1
  69. package/dist/primitives/index.mjs +2 -0
  70. package/dist/primitives/index.mjs.map +1 -1
  71. package/dist/utils/download.js +14 -0
  72. package/dist/utils/download.js.map +1 -0
  73. package/dist/utils/download.mjs +12 -0
  74. package/dist/utils/download.mjs.map +1 -0
  75. package/dist/utils/format-file-size.js +45 -0
  76. package/dist/utils/format-file-size.js.map +1 -0
  77. package/dist/utils/format-file-size.mjs +43 -0
  78. package/dist/utils/format-file-size.mjs.map +1 -0
  79. package/dist/utils/intl.js +6 -0
  80. package/dist/utils/intl.js.map +1 -1
  81. package/dist/utils/intl.mjs +6 -1
  82. package/dist/utils/intl.mjs.map +1 -1
  83. package/dist/utils/use-initial.js +2 -1
  84. package/dist/utils/use-initial.js.map +1 -1
  85. package/dist/utils/use-initial.mjs +3 -2
  86. package/dist/utils/use-initial.mjs.map +1 -1
  87. package/dist/version.js +1 -1
  88. package/dist/version.js.map +1 -1
  89. package/dist/version.mjs +1 -1
  90. package/dist/version.mjs.map +1 -1
  91. package/package.json +4 -4
  92. package/src/styles/dark/index.css +1 -0
  93. package/src/styles/index.css +296 -62
  94. package/src/styles/utils.css +44 -0
  95. package/styles/dark/attributes.css +1 -1
  96. package/styles/dark/attributes.css.map +1 -1
  97. package/styles/dark/media-query.css +1 -1
  98. package/styles/dark/media-query.css.map +1 -1
  99. package/styles.css +1 -1
  100. package/styles.css.map +1 -1
  101. package/dist/utils/chunk.js +0 -12
  102. package/dist/utils/chunk.js.map +0 -1
  103. package/dist/utils/chunk.mjs +0 -10
  104. package/dist/utils/chunk.mjs.map +0 -1
@@ -0,0 +1,226 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var react = require('@liveblocks/react');
5
+ var React = require('react');
6
+ var Cross = require('../../icons/Cross.js');
7
+ var Spinner = require('../../icons/Spinner.js');
8
+ var Warning = require('../../icons/Warning.js');
9
+ var overrides = require('../../overrides.js');
10
+ require('../../primitives/Comment/index.js');
11
+ require('../../primitives/Composer/index.js');
12
+ var contexts = require('../../primitives/Composer/contexts.js');
13
+ var utils = require('../../primitives/Composer/utils.js');
14
+ require('../../primitives/EmojiPicker/index.js');
15
+ require('../../primitives/FileSize.js');
16
+ require('../../primitives/Timestamp.js');
17
+ var classNames = require('../../utils/class-names.js');
18
+ var formatFileSize = require('../../utils/format-file-size.js');
19
+ var Tooltip = require('./Tooltip.js');
20
+
21
+ const IMAGE_PREVIEW_MAX_SIZE = 50 * 1024 * 1024;
22
+ const fileExtensionRegex = /^(.+?)(\.[^.]+)?$/;
23
+ function splitFileName(name) {
24
+ const match = name.match(fileExtensionRegex);
25
+ return { base: match?.[1] ?? name, extension: match?.[2] };
26
+ }
27
+ function getFileAttachmentIconGlyph(mimeType) {
28
+ 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") {
29
+ return /* @__PURE__ */ React.createElement("path", {
30
+ 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"
31
+ });
32
+ }
33
+ if (mimeType.startsWith("text/")) {
34
+ return /* @__PURE__ */ React.createElement("path", {
35
+ 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"
36
+ });
37
+ }
38
+ if (mimeType.startsWith("image/")) {
39
+ return /* @__PURE__ */ React.createElement("path", {
40
+ 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"
41
+ });
42
+ }
43
+ if (mimeType.startsWith("video/")) {
44
+ return /* @__PURE__ */ React.createElement("path", {
45
+ 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"
46
+ });
47
+ }
48
+ if (mimeType.startsWith("audio/")) {
49
+ return /* @__PURE__ */ React.createElement("path", {
50
+ 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"
51
+ });
52
+ }
53
+ return null;
54
+ }
55
+ const FileAttachmentIcon = React.memo(({ mimeType }) => {
56
+ const iconGlyph = React.useMemo(
57
+ () => getFileAttachmentIconGlyph(mimeType),
58
+ [mimeType]
59
+ );
60
+ return /* @__PURE__ */ React.createElement("svg", {
61
+ className: "lb-attachment-icon",
62
+ width: 30,
63
+ height: 30,
64
+ viewBox: "0 0 30 30",
65
+ fill: "currentColor",
66
+ fillRule: "evenodd",
67
+ clipRule: "evenodd",
68
+ xmlns: "http://www.w3.org/2000/svg"
69
+ }, /* @__PURE__ */ React.createElement("path", {
70
+ 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",
71
+ className: "lb-attachment-icon-shadow"
72
+ }), /* @__PURE__ */ React.createElement("path", {
73
+ 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",
74
+ className: "lb-attachment-icon-background"
75
+ }), /* @__PURE__ */ React.createElement("path", {
76
+ 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",
77
+ className: "lb-attachment-icon-fold"
78
+ }), iconGlyph && /* @__PURE__ */ React.createElement("g", {
79
+ className: "lb-attachment-icon-glyph"
80
+ }, iconGlyph));
81
+ });
82
+ function FileAttachmentImagePreview({ url }) {
83
+ const [isLoaded, setLoaded] = React.useState(false);
84
+ const handleLoad = React.useCallback(() => {
85
+ setLoaded(true);
86
+ }, []);
87
+ return url ? /* @__PURE__ */ React.createElement("div", {
88
+ className: "lb-attachment-preview-image",
89
+ "data-hidden": !isLoaded ? "" : void 0
90
+ }, /* @__PURE__ */ React.createElement("img", {
91
+ src: url,
92
+ loading: "lazy",
93
+ onLoad: handleLoad
94
+ })) : null;
95
+ }
96
+ function FileAttachmentLocalImagePreview({
97
+ attachment
98
+ }) {
99
+ const [url, setUrl] = React.useState();
100
+ React.useEffect(() => {
101
+ const url2 = URL.createObjectURL(attachment.file);
102
+ setUrl(url2);
103
+ return () => {
104
+ URL.revokeObjectURL(url2);
105
+ };
106
+ }, [attachment.file]);
107
+ return /* @__PURE__ */ React.createElement(FileAttachmentImagePreview, {
108
+ url
109
+ });
110
+ }
111
+ function FileAttachmentRemoteImagePreview({
112
+ attachment
113
+ }) {
114
+ const { url } = react.useAttachmentUrl(attachment.id);
115
+ return /* @__PURE__ */ React.createElement(FileAttachmentImagePreview, {
116
+ url
117
+ });
118
+ }
119
+ function FileAttachmentPreview({
120
+ attachment
121
+ }) {
122
+ const isInsideRoom = react.useIsInsideRoom();
123
+ const isLocal = attachment.type === "localAttachment";
124
+ if (attachment.mimeType.startsWith("image/") && attachment.size < IMAGE_PREVIEW_MAX_SIZE) {
125
+ if (isLocal) {
126
+ return /* @__PURE__ */ React.createElement(FileAttachmentLocalImagePreview, {
127
+ attachment
128
+ });
129
+ } else if (isInsideRoom) {
130
+ return /* @__PURE__ */ React.createElement(FileAttachmentRemoteImagePreview, {
131
+ attachment
132
+ });
133
+ }
134
+ }
135
+ return null;
136
+ }
137
+ function FileAttachment({
138
+ attachment,
139
+ overrides: overrides$1,
140
+ onClick,
141
+ onDeleteClick,
142
+ preventFocusOnDelete,
143
+ className,
144
+ ...props
145
+ }) {
146
+ const $ = overrides.useOverrides(overrides$1);
147
+ const composerAttachmentsContext = contexts.useComposerAttachmentsContextOrNull();
148
+ const isInComposer = Boolean(composerAttachmentsContext);
149
+ const maxAttachmentSize = composerAttachmentsContext?.maxAttachmentSize;
150
+ const { base: fileBaseName, extension: fileExtension } = React.useMemo(() => {
151
+ return splitFileName(attachment.name);
152
+ }, [attachment.name]);
153
+ const status = attachment.type === "localAttachment" ? attachment.status : void 0;
154
+ const isUploading = status === "uploading";
155
+ const isError = status === "error";
156
+ let description;
157
+ if (attachment.type === "localAttachment" && attachment.status === "error") {
158
+ if (attachment.error instanceof utils.AttachmentTooLargeError) {
159
+ description = $.ATTACHMENT_TOO_LARGE(
160
+ maxAttachmentSize ? formatFileSize.formatFileSize(maxAttachmentSize, $.locale) : void 0
161
+ );
162
+ } else {
163
+ description = $.ATTACHMENT_ERROR(attachment.error);
164
+ }
165
+ } else {
166
+ description = formatFileSize.formatFileSize(attachment.size, $.locale);
167
+ }
168
+ const deleteLabel = isInComposer ? $.COMPOSER_REMOVE_ATTACHMENT : $.COMMENT_DELETE_ATTACHMENT;
169
+ const handleDeletePointerDown = React.useCallback(
170
+ (event) => {
171
+ if (preventFocusOnDelete) {
172
+ event.preventDefault();
173
+ }
174
+ },
175
+ [preventFocusOnDelete]
176
+ );
177
+ const handleKeyDown = React.useCallback((event) => {
178
+ if (event.key === "Enter" || event.key === " ") {
179
+ event.preventDefault();
180
+ const clickEvent = new MouseEvent("click", {
181
+ bubbles: true,
182
+ cancelable: true,
183
+ view: window
184
+ });
185
+ event.target.dispatchEvent(clickEvent);
186
+ }
187
+ }, []);
188
+ return /* @__PURE__ */ React.createElement("div", {
189
+ className: classNames.classNames("lb-attachment lb-file-attachment", className),
190
+ "data-error": isError ? "" : void 0,
191
+ ...props,
192
+ role: onClick ? "button" : void 0,
193
+ onClick,
194
+ tabIndex: onClick ? 0 : -1,
195
+ onKeyDown: onClick ? handleKeyDown : void 0
196
+ }, /* @__PURE__ */ React.createElement("div", {
197
+ className: "lb-attachment-preview"
198
+ }, isUploading ? /* @__PURE__ */ React.createElement(Spinner.SpinnerIcon, null) : isError ? /* @__PURE__ */ React.createElement(Warning.WarningIcon, null) : /* @__PURE__ */ React.createElement(React.Fragment, null, !isError ? /* @__PURE__ */ React.createElement(FileAttachmentPreview, {
199
+ attachment
200
+ }) : null, /* @__PURE__ */ React.createElement(FileAttachmentIcon, {
201
+ mimeType: attachment.mimeType
202
+ }))), /* @__PURE__ */ React.createElement("div", {
203
+ className: "lb-attachment-details"
204
+ }, /* @__PURE__ */ React.createElement("span", {
205
+ className: "lb-attachment-name",
206
+ title: attachment.name
207
+ }, /* @__PURE__ */ React.createElement("span", {
208
+ className: "lb-attachment-name-base"
209
+ }, fileBaseName), fileExtension && /* @__PURE__ */ React.createElement("span", {
210
+ className: "lb-attachment-name-extension"
211
+ }, fileExtension)), /* @__PURE__ */ React.createElement("span", {
212
+ className: "lb-attachment-description",
213
+ title: description
214
+ }, description)), onDeleteClick && /* @__PURE__ */ React.createElement(Tooltip.Tooltip, {
215
+ content: deleteLabel
216
+ }, /* @__PURE__ */ React.createElement("button", {
217
+ type: "button",
218
+ className: "lb-attachment-delete",
219
+ onClick: onDeleteClick,
220
+ onPointerDown: handleDeletePointerDown,
221
+ "aria-label": deleteLabel
222
+ }, /* @__PURE__ */ React.createElement(Cross.CrossIcon, null))));
223
+ }
224
+
225
+ exports.FileAttachment = FileAttachment;
226
+ //# sourceMappingURL=Attachment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Attachment.js","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;;"}
@@ -0,0 +1,224 @@
1
+ 'use client';
2
+ import { useIsInsideRoom, useAttachmentUrl } from '@liveblocks/react';
3
+ import React__default, { memo, useMemo, useCallback, useState, useEffect } from 'react';
4
+ import { CrossIcon } from '../../icons/Cross.mjs';
5
+ import { SpinnerIcon } from '../../icons/Spinner.mjs';
6
+ import { WarningIcon } from '../../icons/Warning.mjs';
7
+ import { useOverrides } from '../../overrides.mjs';
8
+ import '../../primitives/Comment/index.mjs';
9
+ import '../../primitives/Composer/index.mjs';
10
+ import { useComposerAttachmentsContextOrNull } from '../../primitives/Composer/contexts.mjs';
11
+ import { AttachmentTooLargeError } from '../../primitives/Composer/utils.mjs';
12
+ import '../../primitives/EmojiPicker/index.mjs';
13
+ import '../../primitives/FileSize.mjs';
14
+ import '../../primitives/Timestamp.mjs';
15
+ import { classNames } from '../../utils/class-names.mjs';
16
+ import { formatFileSize } from '../../utils/format-file-size.mjs';
17
+ import { Tooltip } from './Tooltip.mjs';
18
+
19
+ const IMAGE_PREVIEW_MAX_SIZE = 50 * 1024 * 1024;
20
+ const fileExtensionRegex = /^(.+?)(\.[^.]+)?$/;
21
+ function splitFileName(name) {
22
+ const match = name.match(fileExtensionRegex);
23
+ return { base: match?.[1] ?? name, extension: match?.[2] };
24
+ }
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") {
27
+ return /* @__PURE__ */ React__default.createElement("path", {
28
+ 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
+ });
30
+ }
31
+ if (mimeType.startsWith("text/")) {
32
+ return /* @__PURE__ */ React__default.createElement("path", {
33
+ 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
+ });
35
+ }
36
+ if (mimeType.startsWith("image/")) {
37
+ return /* @__PURE__ */ React__default.createElement("path", {
38
+ 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"
39
+ });
40
+ }
41
+ if (mimeType.startsWith("video/")) {
42
+ return /* @__PURE__ */ React__default.createElement("path", {
43
+ 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"
44
+ });
45
+ }
46
+ if (mimeType.startsWith("audio/")) {
47
+ return /* @__PURE__ */ React__default.createElement("path", {
48
+ 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"
49
+ });
50
+ }
51
+ return null;
52
+ }
53
+ const FileAttachmentIcon = memo(({ mimeType }) => {
54
+ const iconGlyph = useMemo(
55
+ () => getFileAttachmentIconGlyph(mimeType),
56
+ [mimeType]
57
+ );
58
+ return /* @__PURE__ */ React__default.createElement("svg", {
59
+ className: "lb-attachment-icon",
60
+ width: 30,
61
+ height: 30,
62
+ viewBox: "0 0 30 30",
63
+ fill: "currentColor",
64
+ fillRule: "evenodd",
65
+ clipRule: "evenodd",
66
+ xmlns: "http://www.w3.org/2000/svg"
67
+ }, /* @__PURE__ */ React__default.createElement("path", {
68
+ 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",
69
+ className: "lb-attachment-icon-shadow"
70
+ }), /* @__PURE__ */ React__default.createElement("path", {
71
+ 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",
72
+ className: "lb-attachment-icon-background"
73
+ }), /* @__PURE__ */ React__default.createElement("path", {
74
+ 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",
75
+ className: "lb-attachment-icon-fold"
76
+ }), iconGlyph && /* @__PURE__ */ React__default.createElement("g", {
77
+ className: "lb-attachment-icon-glyph"
78
+ }, iconGlyph));
79
+ });
80
+ function FileAttachmentImagePreview({ url }) {
81
+ const [isLoaded, setLoaded] = useState(false);
82
+ const handleLoad = useCallback(() => {
83
+ setLoaded(true);
84
+ }, []);
85
+ return url ? /* @__PURE__ */ React__default.createElement("div", {
86
+ className: "lb-attachment-preview-image",
87
+ "data-hidden": !isLoaded ? "" : void 0
88
+ }, /* @__PURE__ */ React__default.createElement("img", {
89
+ src: url,
90
+ loading: "lazy",
91
+ 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
+ });
108
+ }
109
+ function FileAttachmentRemoteImagePreview({
110
+ attachment
111
+ }) {
112
+ const { url } = useAttachmentUrl(attachment.id);
113
+ return /* @__PURE__ */ React__default.createElement(FileAttachmentImagePreview, {
114
+ url
115
+ });
116
+ }
117
+ function FileAttachmentPreview({
118
+ attachment
119
+ }) {
120
+ 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, {
125
+ attachment
126
+ });
127
+ } else if (isInsideRoom) {
128
+ return /* @__PURE__ */ React__default.createElement(FileAttachmentRemoteImagePreview, {
129
+ attachment
130
+ });
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+ function FileAttachment({
136
+ attachment,
137
+ overrides,
138
+ onClick,
139
+ onDeleteClick,
140
+ preventFocusOnDelete,
141
+ className,
142
+ ...props
143
+ }) {
144
+ const $ = useOverrides(overrides);
145
+ const composerAttachmentsContext = useComposerAttachmentsContextOrNull();
146
+ const isInComposer = Boolean(composerAttachmentsContext);
147
+ const maxAttachmentSize = composerAttachmentsContext?.maxAttachmentSize;
148
+ const { base: fileBaseName, extension: fileExtension } = useMemo(() => {
149
+ return splitFileName(attachment.name);
150
+ }, [attachment.name]);
151
+ const status = attachment.type === "localAttachment" ? attachment.status : void 0;
152
+ const isUploading = status === "uploading";
153
+ const isError = status === "error";
154
+ let description;
155
+ if (attachment.type === "localAttachment" && attachment.status === "error") {
156
+ if (attachment.error instanceof AttachmentTooLargeError) {
157
+ description = $.ATTACHMENT_TOO_LARGE(
158
+ maxAttachmentSize ? formatFileSize(maxAttachmentSize, $.locale) : void 0
159
+ );
160
+ } else {
161
+ description = $.ATTACHMENT_ERROR(attachment.error);
162
+ }
163
+ } else {
164
+ description = formatFileSize(attachment.size, $.locale);
165
+ }
166
+ const deleteLabel = isInComposer ? $.COMPOSER_REMOVE_ATTACHMENT : $.COMMENT_DELETE_ATTACHMENT;
167
+ const handleDeletePointerDown = useCallback(
168
+ (event) => {
169
+ if (preventFocusOnDelete) {
170
+ event.preventDefault();
171
+ }
172
+ },
173
+ [preventFocusOnDelete]
174
+ );
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
+ }, []);
186
+ return /* @__PURE__ */ React__default.createElement("div", {
187
+ className: classNames("lb-attachment lb-file-attachment", className),
188
+ "data-error": isError ? "" : void 0,
189
+ ...props,
190
+ role: onClick ? "button" : void 0,
191
+ onClick,
192
+ tabIndex: onClick ? 0 : -1,
193
+ onKeyDown: onClick ? handleKeyDown : void 0
194
+ }, /* @__PURE__ */ React__default.createElement("div", {
195
+ 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, {
197
+ attachment
198
+ }) : null, /* @__PURE__ */ React__default.createElement(FileAttachmentIcon, {
199
+ mimeType: attachment.mimeType
200
+ }))), /* @__PURE__ */ React__default.createElement("div", {
201
+ 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", {
210
+ className: "lb-attachment-description",
211
+ title: description
212
+ }, description)), onDeleteClick && /* @__PURE__ */ React__default.createElement(Tooltip, {
213
+ content: deleteLabel
214
+ }, /* @__PURE__ */ React__default.createElement("button", {
215
+ type: "button",
216
+ className: "lb-attachment-delete",
217
+ onClick: onDeleteClick,
218
+ onPointerDown: handleDeletePointerDown,
219
+ "aria-label": deleteLabel
220
+ }, /* @__PURE__ */ React__default.createElement(CrossIcon, null))));
221
+ }
222
+
223
+ export { FileAttachment };
224
+ //# sourceMappingURL=Attachment.mjs.map
@@ -0,0 +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;;"}
@@ -12,6 +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
17
  overrides: overrides$1,
16
18
  className,
17
19
  ...props
@@ -37,14 +39,20 @@ function InboxNotificationComment({
37
39
  Mention: Comment.CommentMention,
38
40
  Link: Comment.CommentNonInteractiveLink
39
41
  }
40
- }), comment.reactions.length > 0 && /* @__PURE__ */ React.createElement("div", {
42
+ }), showReactions && comment.reactions.length > 0 && /* @__PURE__ */ React.createElement("div", {
41
43
  className: "lb-comment-reactions"
42
44
  }, comment.reactions.map((reaction) => /* @__PURE__ */ React.createElement(Comment.CommentNonInteractiveReaction, {
43
45
  key: reaction.emoji,
44
46
  reaction,
45
47
  overrides: overrides$1,
46
48
  disabled: true
47
- })))) : /* @__PURE__ */ React.createElement("div", {
49
+ }))), showAttachments && comment.attachments.length > 0 ? /* @__PURE__ */ React.createElement("div", {
50
+ className: "lb-comment-attachments"
51
+ }, comment.attachments.map((attachment) => /* @__PURE__ */ React.createElement(Comment.CommentNonInteractiveFileAttachment, {
52
+ key: attachment.id,
53
+ attachment,
54
+ overrides: overrides$1
55
+ }))) : null) : /* @__PURE__ */ React.createElement("div", {
48
56
  className: "lb-comment-body"
49
57
  }, /* @__PURE__ */ React.createElement("p", {
50
58
  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 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 overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\nexport function InboxNotificationComment({\n comment,\n showHeader = 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 {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 </>\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","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,\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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"}
@@ -3,13 +3,15 @@ import React__default from 'react';
3
3
  import { useOverrides } from '../../overrides.mjs';
4
4
  import { Body as CommentBody } from '../../primitives/Comment/index.mjs';
5
5
  import { classNames } from '../../utils/class-names.mjs';
6
- import { CommentMention, CommentNonInteractiveLink, CommentNonInteractiveReaction } from '../Comment.mjs';
6
+ import { CommentMention, CommentNonInteractiveLink, CommentNonInteractiveReaction, CommentNonInteractiveFileAttachment } from '../Comment.mjs';
7
7
  import { User } from './User.mjs';
8
8
 
9
9
  const INBOX_NOTIFICATION_THREAD_MAX_COMMENTS = 3;
10
10
  function InboxNotificationComment({
11
11
  comment,
12
12
  showHeader = true,
13
+ showAttachments,
14
+ showReactions,
13
15
  overrides,
14
16
  className,
15
17
  ...props
@@ -35,14 +37,20 @@ function InboxNotificationComment({
35
37
  Mention: CommentMention,
36
38
  Link: CommentNonInteractiveLink
37
39
  }
38
- }), comment.reactions.length > 0 && /* @__PURE__ */ React__default.createElement("div", {
40
+ }), showReactions && comment.reactions.length > 0 && /* @__PURE__ */ React__default.createElement("div", {
39
41
  className: "lb-comment-reactions"
40
42
  }, comment.reactions.map((reaction) => /* @__PURE__ */ React__default.createElement(CommentNonInteractiveReaction, {
41
43
  key: reaction.emoji,
42
44
  reaction,
43
45
  overrides,
44
46
  disabled: true
45
- })))) : /* @__PURE__ */ React__default.createElement("div", {
47
+ }))), showAttachments && comment.attachments.length > 0 ? /* @__PURE__ */ React__default.createElement("div", {
48
+ className: "lb-comment-attachments"
49
+ }, comment.attachments.map((attachment) => /* @__PURE__ */ React__default.createElement(CommentNonInteractiveFileAttachment, {
50
+ key: attachment.id,
51
+ attachment,
52
+ overrides
53
+ }))) : null) : /* @__PURE__ */ React__default.createElement("div", {
46
54
  className: "lb-comment-body"
47
55
  }, /* @__PURE__ */ React__default.createElement("p", {
48
56
  className: "lb-comment-deleted"