@builder6/rooms 3.0.5 → 3.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +10 -7
- package/.prettierrc +0 -4
- package/src/emails/UnreadMention.tsx +0 -37
- package/src/emails/UnreadReplies.tsx +0 -49
- package/src/emails/_components/comment.tsx +0 -70
- package/src/emails/_components/header.tsx +0 -26
- package/src/emails/_components/headline.tsx +0 -20
- package/src/emails/_components/layout.tsx +0 -45
- package/src/emails/_lib/types.ts +0 -10
- package/src/emails/_styles/colors.ts +0 -7
- package/src/emails/_utils/cn.ts +0 -6
- package/src/emails/_utils/comments.ts +0 -61
- package/src/emails/_utils/getProps.ts +0 -7
- package/src/plugin.module.ts +0 -3
- package/src/rooms/app.controller.ts +0 -89
- package/src/rooms/dtos/inbox_notifications.dto.ts +0 -13
- package/src/rooms/dtos/notifications.dto.ts +0 -14
- package/src/rooms/dtos/room_members.dto.ts +0 -32
- package/src/rooms/dtos/rooms.dto.ts +0 -51
- package/src/rooms/emailNotification.service.tsx +0 -126
- package/src/rooms/emails/comment-body.tsx +0 -342
- package/src/rooms/emails/comment-with-body.ts +0 -24
- package/src/rooms/emails/index.ts +0 -25
- package/src/rooms/emails/lib/batch-users-resolver.ts +0 -120
- package/src/rooms/emails/lib/css-properties.ts +0 -123
- package/src/rooms/emails/lib/warning.ts +0 -25
- package/src/rooms/emails/thread-notification.tsx +0 -583
- package/src/rooms/globals/augmentation.ts +0 -89
- package/src/rooms/index.ts +0 -5
- package/src/rooms/lib/DateToString.ts +0 -9
- package/src/rooms/lib/Json.ts +0 -34
- package/src/rooms/lib/utils.ts +0 -240
- package/src/rooms/liveblocks.service.ts +0 -25
- package/src/rooms/notifications.service.ts +0 -235
- package/src/rooms/protocol/AuthToken.ts +0 -126
- package/src/rooms/protocol/Authentication.ts +0 -18
- package/src/rooms/protocol/BaseActivitiesData.ts +0 -5
- package/src/rooms/protocol/BaseRoomInfo.ts +0 -15
- package/src/rooms/protocol/BaseUserMeta.ts +0 -28
- package/src/rooms/protocol/ClientMsg.ts +0 -94
- package/src/rooms/protocol/Comments.ts +0 -202
- package/src/rooms/protocol/InboxNotifications.ts +0 -75
- package/src/rooms/protocol/Op.ts +0 -143
- package/src/rooms/protocol/SerializedCrdt.ts +0 -61
- package/src/rooms/protocol/ServerMsg.ts +0 -307
- package/src/rooms/protocol/VersionHistory.ts +0 -9
- package/src/rooms/rooms.controller.ts +0 -587
- package/src/rooms/rooms.gateway.ts +0 -267
- package/src/rooms/rooms.guard.ts +0 -52
- package/src/rooms/rooms.module.ts +0 -38
- package/src/rooms/rooms.moleculer.ts +0 -80
- package/src/rooms/rooms.service.ts +0 -723
- package/tsconfig.json +0 -10
- package/yarn-error.log +0 -17218
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
|
|
2
|
-
import { ConfigService } from '@nestjs/config';
|
|
3
|
-
import { ThreadNotificationEvent } from '@liveblocks/node';
|
|
4
|
-
import { prepareThreadNotificationEmailAsReact } from './emails';
|
|
5
|
-
import * as React from 'react';
|
|
6
|
-
import { render } from '@react-email/render';
|
|
7
|
-
import { EmailService } from '@builder6/email';
|
|
8
|
-
import UnreadRepliesEmail from '../emails/UnreadReplies';
|
|
9
|
-
import UnreadMentionEmail from '../emails/UnreadMention';
|
|
10
|
-
import { RoomsService } from './rooms.service';
|
|
11
|
-
|
|
12
|
-
@Injectable()
|
|
13
|
-
export class EmailNotificationService {
|
|
14
|
-
private readonly logger = new Logger(EmailNotificationService.name);
|
|
15
|
-
|
|
16
|
-
constructor(
|
|
17
|
-
@Inject(forwardRef(() => RoomsService))
|
|
18
|
-
private roomsService: RoomsService,
|
|
19
|
-
private configService: ConfigService,
|
|
20
|
-
private emailService: EmailService,
|
|
21
|
-
) {}
|
|
22
|
-
|
|
23
|
-
async sendThreadNotificationEmail({
|
|
24
|
-
roomId,
|
|
25
|
-
threadId,
|
|
26
|
-
userId,
|
|
27
|
-
fromUserId,
|
|
28
|
-
inboxNotificationId,
|
|
29
|
-
spaceId,
|
|
30
|
-
}: {
|
|
31
|
-
roomId: string;
|
|
32
|
-
threadId: string;
|
|
33
|
-
userId: string;
|
|
34
|
-
fromUserId?: string;
|
|
35
|
-
inboxNotificationId: string;
|
|
36
|
-
spaceId: string;
|
|
37
|
-
}) {
|
|
38
|
-
const event: ThreadNotificationEvent = {
|
|
39
|
-
type: 'notification',
|
|
40
|
-
data: {
|
|
41
|
-
channel: 'email',
|
|
42
|
-
kind: 'thread',
|
|
43
|
-
projectId: 'my-project-id',
|
|
44
|
-
roomId,
|
|
45
|
-
userId,
|
|
46
|
-
threadId,
|
|
47
|
-
inboxNotificationId,
|
|
48
|
-
createdAt: new Date().toISOString(),
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
const emailData = await prepareThreadNotificationEmailAsReact(
|
|
52
|
-
this.roomsService as any,
|
|
53
|
-
event,
|
|
54
|
-
{
|
|
55
|
-
resolveUsers: async ({ userIds }) => {
|
|
56
|
-
return await this.roomsService.resolveUsers({ userIds });
|
|
57
|
-
},
|
|
58
|
-
resolveRoomInfo: async ({ roomId }) => {
|
|
59
|
-
const room = await this.roomsService.resolveRoomInfo({
|
|
60
|
-
roomId,
|
|
61
|
-
});
|
|
62
|
-
return room;
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
);
|
|
66
|
-
if (!emailData) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const fromUsers = await this.roomsService.resolveUsers({
|
|
71
|
-
userIds: [fromUserId],
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const fromUser = fromUsers[0] || { name: 'Somebody' };
|
|
75
|
-
const company = {
|
|
76
|
-
name: this.configService.get('tenant.name'),
|
|
77
|
-
url: this.configService.get('host'),
|
|
78
|
-
logoUrl: this.configService.get('tenant.logo.url'),
|
|
79
|
-
};
|
|
80
|
-
const room = {
|
|
81
|
-
name: emailData.roomInfo.name,
|
|
82
|
-
url: emailData.roomInfo.url,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
let email;
|
|
86
|
-
let subject;
|
|
87
|
-
|
|
88
|
-
switch (emailData.type) {
|
|
89
|
-
// Handle unread replies use case
|
|
90
|
-
case 'unreadReplies': {
|
|
91
|
-
email = (
|
|
92
|
-
<UnreadRepliesEmail
|
|
93
|
-
company={company}
|
|
94
|
-
room={room}
|
|
95
|
-
comments={emailData.comments}
|
|
96
|
-
/>
|
|
97
|
-
);
|
|
98
|
-
subject = `[${company.name}] ${fromUser.name} replyed you: ${room.name}`;
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
// Handle last unread comment with mention use case
|
|
102
|
-
case 'unreadMention': {
|
|
103
|
-
email = (
|
|
104
|
-
<UnreadMentionEmail
|
|
105
|
-
company={company}
|
|
106
|
-
room={room}
|
|
107
|
-
comment={emailData.comment}
|
|
108
|
-
/>
|
|
109
|
-
);
|
|
110
|
-
subject = `[${company.name}] ${fromUser.name} mentioned you: ${room.name}`;
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const html = await render(email);
|
|
116
|
-
|
|
117
|
-
await this.emailService.sendMailToUsers({
|
|
118
|
-
spaceId,
|
|
119
|
-
toUserIds: [userId],
|
|
120
|
-
fromUserId,
|
|
121
|
-
subject: subject,
|
|
122
|
-
html,
|
|
123
|
-
});
|
|
124
|
-
return html;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
BaseUserMeta,
|
|
3
|
-
CommentBody,
|
|
4
|
-
CommentBodyLink,
|
|
5
|
-
CommentBodyMention,
|
|
6
|
-
CommentBodyText,
|
|
7
|
-
DU,
|
|
8
|
-
OptionalPromise,
|
|
9
|
-
ResolveUsersArgs,
|
|
10
|
-
} from "@liveblocks/core";
|
|
11
|
-
import {
|
|
12
|
-
html,
|
|
13
|
-
htmlSafe,
|
|
14
|
-
isCommentBodyLink,
|
|
15
|
-
isCommentBodyMention,
|
|
16
|
-
isCommentBodyText,
|
|
17
|
-
resolveUsersInCommentBody,
|
|
18
|
-
stringifyCommentBody,
|
|
19
|
-
toAbsoluteUrl,
|
|
20
|
-
} from "@liveblocks/core";
|
|
21
|
-
import * as React from "react";
|
|
22
|
-
|
|
23
|
-
import type { CSSProperties } from "./lib/css-properties";
|
|
24
|
-
import { toInlineCSSString } from "./lib/css-properties";
|
|
25
|
-
|
|
26
|
-
export type CommentBodyContainerComponentProps = {
|
|
27
|
-
/**
|
|
28
|
-
* The blocks of the comment body
|
|
29
|
-
*/
|
|
30
|
-
children: React.ReactNode;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export type CommentBodyParagraphComponentProps = {
|
|
34
|
-
/**
|
|
35
|
-
* The text content of the paragraph.
|
|
36
|
-
*/
|
|
37
|
-
children: React.ReactNode;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export type CommentBodyTextComponentProps = {
|
|
41
|
-
/**
|
|
42
|
-
* The text element.
|
|
43
|
-
*/
|
|
44
|
-
element: CommentBodyText;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export type CommentBodyLinkComponentProps = {
|
|
48
|
-
/**
|
|
49
|
-
* The link element.
|
|
50
|
-
*/
|
|
51
|
-
element: CommentBodyLink;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* The absolute URL of the link.
|
|
55
|
-
*/
|
|
56
|
-
href: string;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export type CommentBodyMentionComponentProps<U extends BaseUserMeta = DU> = {
|
|
60
|
-
/**
|
|
61
|
-
* The mention element.
|
|
62
|
-
*/
|
|
63
|
-
element: CommentBodyMention;
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* The mention's user info, if the `resolvedUsers` option was provided.
|
|
67
|
-
*/
|
|
68
|
-
user?: U["info"];
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
export type ConvertCommentBodyAsReactComponents<U extends BaseUserMeta = DU> = {
|
|
72
|
-
/**
|
|
73
|
-
*
|
|
74
|
-
* The component used to act as a container to wrap comment body blocks,
|
|
75
|
-
*/
|
|
76
|
-
Container: React.ComponentType<CommentBodyContainerComponentProps>;
|
|
77
|
-
/**
|
|
78
|
-
* The component used to display paragraphs.
|
|
79
|
-
*/
|
|
80
|
-
Paragraph: React.ComponentType<CommentBodyParagraphComponentProps>;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* The component used to display text elements.
|
|
84
|
-
*/
|
|
85
|
-
Text: React.ComponentType<CommentBodyTextComponentProps>;
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* The component used to display links.
|
|
89
|
-
*/
|
|
90
|
-
Link: React.ComponentType<CommentBodyLinkComponentProps>;
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* The component used to display mentions.
|
|
94
|
-
*/
|
|
95
|
-
Mention: React.ComponentType<CommentBodyMentionComponentProps<U>>;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const baseComponents: ConvertCommentBodyAsReactComponents<BaseUserMeta> = {
|
|
99
|
-
Container: ({ children }) => <div>{children}</div>,
|
|
100
|
-
Paragraph: ({ children }) => <p>{children}</p>,
|
|
101
|
-
Text: ({ element }) => {
|
|
102
|
-
// Note: construction following the schema 👇
|
|
103
|
-
// <code><s><em><strong>{element.text}</strong></s></em></code>
|
|
104
|
-
let children: React.ReactNode = element.text;
|
|
105
|
-
|
|
106
|
-
if (element.bold) {
|
|
107
|
-
children = <strong>{children}</strong>;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (element.italic) {
|
|
111
|
-
children = <em>{children}</em>;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (element.strikethrough) {
|
|
115
|
-
children = <s>{children}</s>;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (element.code) {
|
|
119
|
-
children = <code>{children}</code>;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return <span>{children}</span>;
|
|
123
|
-
},
|
|
124
|
-
Link: ({ element, href }) => (
|
|
125
|
-
<a href={href} target="_blank" rel="noopener noreferrer">
|
|
126
|
-
{element.text ?? element.url}
|
|
127
|
-
</a>
|
|
128
|
-
),
|
|
129
|
-
Mention: ({ element, user }) => (
|
|
130
|
-
<span data-mention>@{user?.name ?? element.id}</span>
|
|
131
|
-
),
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
export type ConvertCommentBodyAsReactOptions<U extends BaseUserMeta = DU> = {
|
|
135
|
-
/**
|
|
136
|
-
* The components used to customize the resulting React nodes. Each components has
|
|
137
|
-
* priority over the base components inherited.
|
|
138
|
-
*/
|
|
139
|
-
components?: Partial<ConvertCommentBodyAsReactComponents<U>>;
|
|
140
|
-
/**
|
|
141
|
-
* A function that returns user info from user IDs.
|
|
142
|
-
*/
|
|
143
|
-
resolveUsers?: (
|
|
144
|
-
args: ResolveUsersArgs
|
|
145
|
-
) => OptionalPromise<(U["info"] | undefined)[] | undefined>;
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Convert a `CommentBody` into React elements
|
|
150
|
-
*/
|
|
151
|
-
export async function convertCommentBodyAsReact(
|
|
152
|
-
body: CommentBody,
|
|
153
|
-
options?: ConvertCommentBodyAsReactOptions<BaseUserMeta>
|
|
154
|
-
): Promise<React.ReactNode> {
|
|
155
|
-
const Components = {
|
|
156
|
-
...baseComponents,
|
|
157
|
-
...options?.components,
|
|
158
|
-
};
|
|
159
|
-
const resolvedUsers = await resolveUsersInCommentBody(
|
|
160
|
-
body,
|
|
161
|
-
options?.resolveUsers
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
const blocks = body.content.map((block, index) => {
|
|
165
|
-
switch (block.type) {
|
|
166
|
-
case "paragraph": {
|
|
167
|
-
const children = block.children.map((inline, inlineIndex) => {
|
|
168
|
-
if (isCommentBodyMention(inline)) {
|
|
169
|
-
return inline.id ? (
|
|
170
|
-
<Components.Mention
|
|
171
|
-
key={`lb-comment-body-mention-${inlineIndex}`}
|
|
172
|
-
element={inline}
|
|
173
|
-
user={resolvedUsers.get(inline.id)}
|
|
174
|
-
/>
|
|
175
|
-
) : null;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (isCommentBodyLink(inline)) {
|
|
179
|
-
const href = toAbsoluteUrl(inline.url) ?? inline.url;
|
|
180
|
-
return (
|
|
181
|
-
<Components.Link
|
|
182
|
-
key={`lb-comment-body-link-${inlineIndex}`}
|
|
183
|
-
element={inline}
|
|
184
|
-
href={href}
|
|
185
|
-
/>
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (isCommentBodyText(inline)) {
|
|
190
|
-
return (
|
|
191
|
-
<Components.Text
|
|
192
|
-
key={`lb-comment-body-text-${inlineIndex}`}
|
|
193
|
-
element={inline}
|
|
194
|
-
/>
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return null;
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
return (
|
|
202
|
-
<Components.Paragraph key={`lb-comment-body-paragraph-${index}`}>
|
|
203
|
-
{children}
|
|
204
|
-
</Components.Paragraph>
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
default:
|
|
208
|
-
console.warn(
|
|
209
|
-
`Unsupported comment body block type: "${JSON.stringify(block.type)}"`
|
|
210
|
-
);
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
return (
|
|
216
|
-
<Components.Container key={"lb-comment-body-container"}>
|
|
217
|
-
{blocks}
|
|
218
|
-
</Components.Container>
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
export type ConvertCommentBodyAsHtmlStyles = {
|
|
223
|
-
/**
|
|
224
|
-
* The default inline CSS styles used to display paragraphs.
|
|
225
|
-
*/
|
|
226
|
-
paragraph: CSSProperties;
|
|
227
|
-
/**
|
|
228
|
-
* The default inline CSS styles used to display text `<strong />` elements.
|
|
229
|
-
*/
|
|
230
|
-
strong: CSSProperties;
|
|
231
|
-
/**
|
|
232
|
-
* The default inline CSS styles used to display text `<code />` elements.
|
|
233
|
-
*/
|
|
234
|
-
code: CSSProperties;
|
|
235
|
-
/**
|
|
236
|
-
* The default inline CSS styles used to display links.
|
|
237
|
-
*/
|
|
238
|
-
mention: CSSProperties;
|
|
239
|
-
/**
|
|
240
|
-
* The default inline CSS styles used to display mentions.
|
|
241
|
-
*/
|
|
242
|
-
link: CSSProperties;
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
const baseStyles: ConvertCommentBodyAsHtmlStyles = {
|
|
246
|
-
paragraph: {
|
|
247
|
-
fontSize: "14px",
|
|
248
|
-
},
|
|
249
|
-
strong: {
|
|
250
|
-
fontWeight: 500,
|
|
251
|
-
},
|
|
252
|
-
code: {
|
|
253
|
-
fontFamily:
|
|
254
|
-
'ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Mono", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Consolas", "Courier New", monospace',
|
|
255
|
-
backgroundColor: "rgba(0,0,0,0.05)",
|
|
256
|
-
border: "solid 1px rgba(0,0,0,0.1)",
|
|
257
|
-
borderRadius: "4px",
|
|
258
|
-
},
|
|
259
|
-
mention: {
|
|
260
|
-
color: "blue",
|
|
261
|
-
},
|
|
262
|
-
link: {
|
|
263
|
-
textDecoration: "underline",
|
|
264
|
-
},
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
export type ConvertCommentBodyAsHtmlOptions<U extends BaseUserMeta = DU> = {
|
|
268
|
-
/**
|
|
269
|
-
* The styles used to customize the html elements in the resulting html safe string.
|
|
270
|
-
* Each styles has priority over the base styles inherited.
|
|
271
|
-
*/
|
|
272
|
-
styles?: Partial<ConvertCommentBodyAsHtmlStyles>;
|
|
273
|
-
/**
|
|
274
|
-
* A function that returns user info from user IDs.
|
|
275
|
-
*/
|
|
276
|
-
resolveUsers?: (
|
|
277
|
-
args: ResolveUsersArgs
|
|
278
|
-
) => OptionalPromise<(U["info"] | undefined)[] | undefined>;
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Convert a `CommentBody` into an html safe string
|
|
283
|
-
* with inline css styles
|
|
284
|
-
*/
|
|
285
|
-
export async function convertCommentBodyAsHtml(
|
|
286
|
-
body: CommentBody,
|
|
287
|
-
options?: ConvertCommentBodyAsHtmlOptions<BaseUserMeta>
|
|
288
|
-
): Promise<string> {
|
|
289
|
-
const styles = { ...baseStyles, ...options?.styles };
|
|
290
|
-
|
|
291
|
-
const htmlBody = await stringifyCommentBody(body, {
|
|
292
|
-
format: "html",
|
|
293
|
-
resolveUsers: options?.resolveUsers,
|
|
294
|
-
elements: {
|
|
295
|
-
// NOTE: using prettier-ignore to preserve template strings
|
|
296
|
-
paragraph: ({ children }) =>
|
|
297
|
-
// prettier-ignore
|
|
298
|
-
children ? html`<p style="${toInlineCSSString(styles.paragraph)}">${htmlSafe(children)}</p>` : children,
|
|
299
|
-
text: ({ element }) => {
|
|
300
|
-
// Note: construction following the schema 👇
|
|
301
|
-
// <code><s><em><strong>{element.text}</strong></s></em></code>
|
|
302
|
-
let children = element.text;
|
|
303
|
-
|
|
304
|
-
if (!children) {
|
|
305
|
-
return children;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (element.bold) {
|
|
309
|
-
// prettier-ignore
|
|
310
|
-
children = html`<strong style="${toInlineCSSString(styles.strong)}">${children}</strong>`;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (element.italic) {
|
|
314
|
-
// prettier-ignore
|
|
315
|
-
children = html`<em>${children}</em>`;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (element.strikethrough) {
|
|
319
|
-
// prettier-ignore
|
|
320
|
-
children = html`<s>${children}</s>`;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (element.code) {
|
|
324
|
-
// prettier-ignore
|
|
325
|
-
children = html`<code style="${toInlineCSSString(styles.code)}">${children}</code>`;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
return children;
|
|
329
|
-
},
|
|
330
|
-
link: ({ element, href }) => {
|
|
331
|
-
// prettier-ignore
|
|
332
|
-
return html`<a href="${href}" target="_blank" rel="noopener noreferrer" style="${toInlineCSSString(styles.link)}">${element.text ?? element.url}</a>`;
|
|
333
|
-
},
|
|
334
|
-
mention: ({ element, user }) => {
|
|
335
|
-
// prettier-ignore
|
|
336
|
-
return html`<span data-mention style="${toInlineCSSString(styles.mention)}">@${user?.name ?? element.id}</span>`;
|
|
337
|
-
},
|
|
338
|
-
},
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
return htmlBody;
|
|
342
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { CommentBody, CommentData } from "@liveblocks/core";
|
|
2
|
-
|
|
3
|
-
export type CommentDataWithBody = Omit<CommentData, "body" | "deletedAt"> & {
|
|
4
|
-
body: CommentBody;
|
|
5
|
-
deletedAt?: never;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
const isCommentDataWithBody = (
|
|
9
|
-
comment: CommentData
|
|
10
|
-
): comment is CommentDataWithBody => {
|
|
11
|
-
return comment.body !== undefined && comment.deletedAt === undefined;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export function filterCommentsWithBody(
|
|
15
|
-
comments: CommentData[]
|
|
16
|
-
): CommentDataWithBody[] {
|
|
17
|
-
const commentsWithBody: CommentDataWithBody[] = [];
|
|
18
|
-
for (const comment of comments) {
|
|
19
|
-
if (isCommentDataWithBody(comment)) {
|
|
20
|
-
commentsWithBody.push(comment);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return commentsWithBody;
|
|
24
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { detectDupes } from "@liveblocks/core";
|
|
2
|
-
|
|
3
|
-
export type {
|
|
4
|
-
CommentBodyContainerComponentProps,
|
|
5
|
-
CommentBodyLinkComponentProps,
|
|
6
|
-
CommentBodyMentionComponentProps,
|
|
7
|
-
CommentBodyParagraphComponentProps,
|
|
8
|
-
CommentBodyTextComponentProps,
|
|
9
|
-
ConvertCommentBodyAsHtmlStyles,
|
|
10
|
-
ConvertCommentBodyAsReactComponents,
|
|
11
|
-
} from "./comment-body";
|
|
12
|
-
export type {
|
|
13
|
-
CommentEmailAsHtmlData,
|
|
14
|
-
CommentEmailAsReactData,
|
|
15
|
-
PrepareThreadNotificationEmailAsHtmlOptions,
|
|
16
|
-
PrepareThreadNotificationEmailAsReactOptions,
|
|
17
|
-
ResolveRoomInfoArgs,
|
|
18
|
-
ThreadNotificationEmailDataAsHtml,
|
|
19
|
-
ThreadNotificationEmailDataAsReact,
|
|
20
|
-
} from "./thread-notification";
|
|
21
|
-
export {
|
|
22
|
-
prepareThreadNotificationEmailAsHtml,
|
|
23
|
-
prepareThreadNotificationEmailAsReact,
|
|
24
|
-
} from "./thread-notification";
|
|
25
|
-
export type { ResolveUsersArgs } from "@liveblocks/core";
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
BaseUserMeta,
|
|
3
|
-
DU,
|
|
4
|
-
OptionalPromise,
|
|
5
|
-
ResolveUsersArgs,
|
|
6
|
-
} from "@liveblocks/core";
|
|
7
|
-
import { Promise_withResolvers } from "@liveblocks/core";
|
|
8
|
-
|
|
9
|
-
import { createDevelopmentWarning } from "./warning";
|
|
10
|
-
|
|
11
|
-
type ResolveUserOptionalPromise<U extends BaseUserMeta> = (
|
|
12
|
-
args: ResolveUsersArgs
|
|
13
|
-
) => OptionalPromise<(U["info"] | undefined)[] | undefined>;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Batch calls to `resolveUsers` to one and only call.
|
|
17
|
-
* It will avoid any performances issues and invocation timeouts on our customers' webhook handlers.
|
|
18
|
-
*
|
|
19
|
-
* This batch call will stack pending promises referring to `resolveUsers` in a map, resolve all users given in args at once
|
|
20
|
-
* and then resolve pending promises all at once.
|
|
21
|
-
*/
|
|
22
|
-
class BatchUsersResolver<U extends BaseUserMeta> {
|
|
23
|
-
private isResolved: boolean;
|
|
24
|
-
private markAsResolved: () => void;
|
|
25
|
-
private resolvePromise: Promise<void>;
|
|
26
|
-
|
|
27
|
-
private primeResolveUsersFn: ResolveUserOptionalPromise<U> | undefined;
|
|
28
|
-
private usersById: Map<string, U["info"] | undefined>;
|
|
29
|
-
|
|
30
|
-
private warnAsAlreadyResolved: () => void;
|
|
31
|
-
|
|
32
|
-
constructor(resolveUsers: ResolveUserOptionalPromise<U> | undefined) {
|
|
33
|
-
const { promise, resolve } = Promise_withResolvers<void>();
|
|
34
|
-
|
|
35
|
-
this.isResolved = false;
|
|
36
|
-
this.markAsResolved = resolve;
|
|
37
|
-
this.resolvePromise = promise;
|
|
38
|
-
|
|
39
|
-
this.primeResolveUsersFn = resolveUsers;
|
|
40
|
-
this.usersById = new Map();
|
|
41
|
-
|
|
42
|
-
this.warnAsAlreadyResolved = createDevelopmentWarning(
|
|
43
|
-
true,
|
|
44
|
-
"Batch users resolver promise already resolved. It can only resolve once."
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
resolveUsers = async (
|
|
49
|
-
args: ResolveUsersArgs
|
|
50
|
-
): Promise<(U["info"] | undefined)[] | undefined> => {
|
|
51
|
-
if (this.isResolved) {
|
|
52
|
-
this.warnAsAlreadyResolved();
|
|
53
|
-
return undefined;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Note: register all user Ids
|
|
57
|
-
for (const userId of args.userIds) {
|
|
58
|
-
this.usersById.set(userId, undefined);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Note: waiting until the batch promise is resolved
|
|
62
|
-
await this.resolvePromise;
|
|
63
|
-
|
|
64
|
-
// Note: once the batch promise is resolved
|
|
65
|
-
// we can return safely resolved users
|
|
66
|
-
return args.userIds.map((userId) => this.usersById.get(userId));
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
async resolve(): Promise<void> {
|
|
70
|
-
if (this.isResolved) {
|
|
71
|
-
this.warnAsAlreadyResolved();
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Note: set an array of unique user ids
|
|
76
|
-
const userIds = Array.from(this.usersById.keys());
|
|
77
|
-
const users = await this.primeResolveUsersFn?.({ userIds });
|
|
78
|
-
|
|
79
|
-
for (const [index, userId] of userIds.entries()) {
|
|
80
|
-
const user = users?.[index];
|
|
81
|
-
this.usersById.set(userId, user);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
this.isResolved = true;
|
|
85
|
-
this.markAsResolved();
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export type CreateBatchUsersResolverReturnType<U extends BaseUserMeta> = {
|
|
90
|
-
resolveUsers: (
|
|
91
|
-
args: ResolveUsersArgs
|
|
92
|
-
) => Promise<(U["info"] | undefined)[] | undefined>;
|
|
93
|
-
resolve: () => Promise<void>;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
export function createBatchUsersResolver<U extends BaseUserMeta = DU>({
|
|
97
|
-
resolveUsers,
|
|
98
|
-
callerName,
|
|
99
|
-
}: {
|
|
100
|
-
resolveUsers?: (
|
|
101
|
-
args: ResolveUsersArgs
|
|
102
|
-
) => OptionalPromise<(U["info"] | undefined)[] | undefined>;
|
|
103
|
-
callerName: string;
|
|
104
|
-
}): CreateBatchUsersResolverReturnType<U> {
|
|
105
|
-
const warnIfNoResolveUsers = createDevelopmentWarning(
|
|
106
|
-
() => !resolveUsers,
|
|
107
|
-
`Set "resolveUsers" option in "${callerName}" to specify users info`
|
|
108
|
-
);
|
|
109
|
-
const batchUsersResolver = new BatchUsersResolver(resolveUsers);
|
|
110
|
-
|
|
111
|
-
const resolve = async (): Promise<void> => {
|
|
112
|
-
warnIfNoResolveUsers();
|
|
113
|
-
await batchUsersResolver.resolve();
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
resolveUsers: batchUsersResolver.resolveUsers,
|
|
118
|
-
resolve,
|
|
119
|
-
} as const;
|
|
120
|
-
}
|