@k2b/ui 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/browser/content/FileGrid.js +28 -8
  2. package/dist/browser/content/FileGrid.js.map +1 -1
  3. package/dist/browser/content/MarkdownView.js +11 -5
  4. package/dist/browser/content/MarkdownView.js.map +1 -1
  5. package/dist/browser/content/MessageRow.js +1250 -0
  6. package/dist/browser/content/MessageRow.js.map +1 -0
  7. package/dist/browser/content/VirtualFeed.js +1127 -0
  8. package/dist/browser/content/VirtualFeed.js.map +1 -0
  9. package/dist/browser/content/collection-selection.js +27 -11
  10. package/dist/browser/content/collection-selection.js.map +1 -1
  11. package/dist/browser/content/index.js +2 -0
  12. package/dist/browser/content/index.js.map +1 -1
  13. package/dist/browser/feedback/InstallGuide.js +199 -145
  14. package/dist/browser/feedback/InstallGuide.js.map +1 -1
  15. package/dist/browser/feedback/install.js +8 -4
  16. package/dist/browser/feedback/install.js.map +1 -1
  17. package/dist/browser/inputs/QrScanner.js +101 -21
  18. package/dist/browser/inputs/QrScanner.js.map +1 -1
  19. package/dist/browser/intl/messages.js +82 -0
  20. package/dist/browser/intl/messages.js.map +1 -1
  21. package/dist/browser/surfaces/Avatar.js +33 -17
  22. package/dist/browser/surfaces/Avatar.js.map +1 -1
  23. package/dist/ssr/content/FileGrid.js +17 -3
  24. package/dist/ssr/content/FileGrid.js.map +1 -1
  25. package/dist/ssr/content/MarkdownView.js +11 -5
  26. package/dist/ssr/content/MarkdownView.js.map +1 -1
  27. package/dist/ssr/content/MessageRow.js +796 -0
  28. package/dist/ssr/content/MessageRow.js.map +1 -0
  29. package/dist/ssr/content/VirtualFeed.js +989 -0
  30. package/dist/ssr/content/VirtualFeed.js.map +1 -0
  31. package/dist/ssr/content/collection-selection.js +27 -11
  32. package/dist/ssr/content/collection-selection.js.map +1 -1
  33. package/dist/ssr/content/index.js +2 -0
  34. package/dist/ssr/content/index.js.map +1 -1
  35. package/dist/ssr/feedback/InstallGuide.js +150 -98
  36. package/dist/ssr/feedback/InstallGuide.js.map +1 -1
  37. package/dist/ssr/feedback/install.js +8 -4
  38. package/dist/ssr/feedback/install.js.map +1 -1
  39. package/dist/ssr/inputs/QrScanner.js +101 -21
  40. package/dist/ssr/inputs/QrScanner.js.map +1 -1
  41. package/dist/ssr/intl/messages.js +82 -0
  42. package/dist/ssr/intl/messages.js.map +1 -1
  43. package/dist/ssr/surfaces/Avatar.js +14 -1
  44. package/dist/ssr/surfaces/Avatar.js.map +1 -1
  45. package/dist/styles.css +1 -1
  46. package/dist/types/content/FileGrid.d.ts +6 -1
  47. package/dist/types/content/MarkdownView.d.ts +8 -0
  48. package/dist/types/content/MessageRow.d.ts +168 -0
  49. package/dist/types/content/VirtualFeed.d.ts +81 -0
  50. package/dist/types/content/collection-selection.d.ts +15 -1
  51. package/dist/types/content/index.d.ts +4 -0
  52. package/dist/types/feedback/InstallGuide.d.ts +5 -3
  53. package/dist/types/feedback/install.d.ts +4 -3
  54. package/dist/types/inputs/QrScanner.d.ts +10 -2
  55. package/dist/types/intl/messages.d.ts +41 -0
  56. package/package.json +1 -1
@@ -0,0 +1,1250 @@
1
+ import { template as _$template } from "solid-js/web";
2
+ import { delegateEvents as _$delegateEvents } from "solid-js/web";
3
+ import { setProperty as _$setProperty } from "solid-js/web";
4
+ import { use as _$use } from "solid-js/web";
5
+ import { memo as _$memo } from "solid-js/web";
6
+ import { getNextMarker as _$getNextMarker } from "solid-js/web";
7
+ import { runHydrationEvents as _$runHydrationEvents } from "solid-js/web";
8
+ import { setAttribute as _$setAttribute } from "solid-js/web";
9
+ import { style as _$style } from "solid-js/web";
10
+ import { className as _$className } from "solid-js/web";
11
+ import { effect as _$effect } from "solid-js/web";
12
+ import { getNextElement as _$getNextElement } from "solid-js/web";
13
+ import { insert as _$insert } from "solid-js/web";
14
+ import { createComponent as _$createComponent } from "solid-js/web";
15
+ var _tmpl$ = /*#__PURE__*/_$template(`<span>`),
16
+ _tmpl$2 = /*#__PURE__*/_$template(`<span role=img>`),
17
+ _tmpl$3 = /*#__PURE__*/_$template(`<a target=_blank rel="noopener noreferrer">`),
18
+ _tmpl$4 = /*#__PURE__*/_$template(`<button type=button>`),
19
+ _tmpl$5 = /*#__PURE__*/_$template(`<span class=k2b-message-row__name>`),
20
+ _tmpl$6 = /*#__PURE__*/_$template(`<span class=k2b-message-row__badge>`),
21
+ _tmpl$7 = /*#__PURE__*/_$template(`<time class=k2b-message-row__time>`),
22
+ _tmpl$8 = /*#__PURE__*/_$template(`<i class="ti ti-arrow-back-up"aria-hidden=true>`),
23
+ _tmpl$9 = /*#__PURE__*/_$template(`<span class=k2b-message-row__quote-text><span class=k2b-sr-only><!$><!/> </span><span class=k2b-message-row__quote-author></span> <!$><!/>`),
24
+ _tmpl$0 = /*#__PURE__*/_$template(`<span class=k2b-message-row__reaction role=img><span aria-hidden=true></span><span aria-hidden=true>`),
25
+ _tmpl$1 = /*#__PURE__*/_$template(`<button type=button class=k2b-message-row__reaction><span aria-hidden=true></span><span aria-hidden=true>`),
26
+ _tmpl$10 = /*#__PURE__*/_$template(`<img alt loading=lazy decoding=async>`, true, false, false),
27
+ _tmpl$11 = /*#__PURE__*/_$template(`<span class=k2b-message-row__media-play aria-hidden=true><i class="ti ti-player-play">`),
28
+ _tmpl$12 = /*#__PURE__*/_$template(`<span class=k2b-message-row__media-duration aria-hidden=true>`),
29
+ _tmpl$13 = /*#__PURE__*/_$template(`<span class=k2b-message-row__media-more aria-hidden=true>+<!$><!/>`),
30
+ _tmpl$14 = /*#__PURE__*/_$template(`<span class=k2b-message-row__avatar aria-hidden=true>`),
31
+ _tmpl$15 = /*#__PURE__*/_$template(`<div class=k2b-message-row__gutter>`),
32
+ _tmpl$16 = /*#__PURE__*/_$template(`<div class=k2b-message-row__meta>`),
33
+ _tmpl$17 = /*#__PURE__*/_$template(`<div class=k2b-message-row__marker><i class="ti ti-arrow-forward-up"aria-hidden=true></i><span>`),
34
+ _tmpl$18 = /*#__PURE__*/_$template(`<div class="k2b-content-markdown k2b-message-row__text"data-heading-scale=compact>`),
35
+ _tmpl$19 = /*#__PURE__*/_$template(`<span class=k2b-message-row__edited>`),
36
+ _tmpl$20 = /*#__PURE__*/_$template(`<div class=k2b-message-row__more-line><!$><!/><!$><!/>`),
37
+ _tmpl$21 = /*#__PURE__*/_$template(`<div class=k2b-message-row__bubble>`),
38
+ _tmpl$22 = /*#__PURE__*/_$template(`<div class=k2b-message-row__marker><i class="ti ti-pencil"aria-hidden=true></i><span class=k2b-message-row__edited>`),
39
+ _tmpl$23 = /*#__PURE__*/_$template(`<div class=k2b-message-row__media role=group>`),
40
+ _tmpl$24 = /*#__PURE__*/_$template(`<div class=k2b-message-row__files>`),
41
+ _tmpl$25 = /*#__PURE__*/_$template(`<div class=k2b-message-row__slot>`),
42
+ _tmpl$26 = /*#__PURE__*/_$template(`<div class=k2b-message-row__line>`),
43
+ _tmpl$27 = /*#__PURE__*/_$template(`<div class=k2b-message-row__footer><!$><!/><!$><!/>`),
44
+ _tmpl$28 = /*#__PURE__*/_$template(`<div class=k2b-message-row__actions role=group>`),
45
+ _tmpl$29 = /*#__PURE__*/_$template(`<div><!$><!/><div class=k2b-message-row__body><!$><!/><!$><!/><!$><!/><!$><!/><!$><!/><!$><!/><!$><!/><!$><!/><!$><!/><!$><!/><!$><!/></div><!$><!/>`),
46
+ _tmpl$30 = /*#__PURE__*/_$template(`<span class=k2b-sr-only><!$><!/>, <!$><!/>: `),
47
+ _tmpl$31 = /*#__PURE__*/_$template(`<div class=k2b-message-row__meta><span class=k2b-sr-only><!$><!/>, </span><time class=k2b-message-row__time>`),
48
+ _tmpl$32 = /*#__PURE__*/_$template(`<div class=k2b-message-row__quote>`),
49
+ _tmpl$33 = /*#__PURE__*/_$template(`<button type=button class=k2b-message-row__quote>`),
50
+ _tmpl$34 = /*#__PURE__*/_$template(`<span class=k2b-message-row__deleted><i class="ti ti-message-off"aria-hidden=true></i><!$><!/>`),
51
+ _tmpl$35 = /*#__PURE__*/_$template(`<span class=k2b-message-row__writing><span class=k2b-chat-progress-dots aria-hidden=true><span></span><span></span><span>`),
52
+ _tmpl$36 = /*#__PURE__*/_$template(`<span class=k2b-message-row__file-icon aria-hidden=true><i>`),
53
+ _tmpl$37 = /*#__PURE__*/_$template(`<span class=k2b-message-row__file-detail>`),
54
+ _tmpl$38 = /*#__PURE__*/_$template(`<span class=k2b-message-row__file-copy><span class=k2b-message-row__file-name></span><!$><!/>`),
55
+ _tmpl$39 = /*#__PURE__*/_$template(`<span class=k2b-message-row__thread-people aria-hidden=true>`),
56
+ _tmpl$40 = /*#__PURE__*/_$template(`<button type=button class=k2b-message-row__thread><!$><!/><span class=k2b-message-row__thread-count></span><!$><!/>`),
57
+ _tmpl$41 = /*#__PURE__*/_$template(`<time class=k2b-message-row__thread-time>`),
58
+ _tmpl$42 = /*#__PURE__*/_$template(`<div class=k2b-message-row__reactions role=group><!$><!/><!$><!/>`),
59
+ _tmpl$43 = /*#__PURE__*/_$template(`<i class="ti ti-mood-plus"aria-hidden=true>`),
60
+ _tmpl$44 = /*#__PURE__*/_$template(`<i class="ti ti-clock"aria-hidden=true>`),
61
+ _tmpl$45 = /*#__PURE__*/_$template(`<i class="ti ti-alert-circle"aria-hidden=true>`),
62
+ _tmpl$46 = /*#__PURE__*/_$template(`<i class="ti ti-check"aria-hidden=true>`),
63
+ _tmpl$47 = /*#__PURE__*/_$template(`<span class=k2b-sr-only>`),
64
+ _tmpl$48 = /*#__PURE__*/_$template(`<i class="ti ti-checks"aria-hidden=true>`),
65
+ _tmpl$49 = /*#__PURE__*/_$template(`<span class=k2b-message-row__receipt>`),
66
+ _tmpl$50 = /*#__PURE__*/_$template(`<i class="ti ti-loader-2 k2b-spin"aria-hidden=true>`),
67
+ _tmpl$51 = /*#__PURE__*/_$template(`<span class=k2b-message-row__line-text>`),
68
+ _tmpl$52 = /*#__PURE__*/_$template(`<i aria-hidden=true>`),
69
+ _tmpl$53 = /*#__PURE__*/_$template(`<time>`),
70
+ _tmpl$54 = /*#__PURE__*/_$template(`<div><!$><!/><span></span><!$><!/>`);
71
+ import { fileIcons } from "@k2b/stdlib";
72
+ import { createMemo, createSignal, createUniqueId, For, onCleanup, Show } from "solid-js";
73
+ import { Button, IconButton } from "../actions/Button";
74
+ import { copyText } from "../actions/CopyButton";
75
+ import { announce } from "../feedback/announce";
76
+ import { useLocale } from "../intl/locale";
77
+ import { useUiMessages } from "../intl/messages";
78
+ import { ScrollArea } from "../layout/ScrollArea";
79
+ import { Avatar } from "../surfaces/Avatar";
80
+ import { createSafeMarked, createSafeRenderer, escapeHtml } from "./MarkdownView";
81
+
82
+ /** Messages by the same author within this time continue a group. */
83
+ const GROUP_WINDOW_MS = 5 * 60_000;
84
+ /**
85
+ * A message collapses to ten lines (`.k2b-message-row__text[data-collapsed]`) when its rendered text takes more lines
86
+ * than this. `renderedLines` counts the fewest lines the text takes at any width, so a collapsed message always hides
87
+ * some of it.
88
+ */
89
+ const COLLAPSE_LINES = 14;
90
+ /** Visible characters that take at least one line of a message, even at its widest. */
91
+ const LINE_CHARS = 100;
92
+ /** Lines at the bottom of a collapsed message that fade out, as `.k2b-message-row__text[data-collapsed]` draws them. */
93
+ const FADE_LINES = 2;
94
+ const COPIED_MS = 2_000;
95
+ /** Links in messages may only use these schemes; other links show as their text. */
96
+ const LINK_PROTOCOLS = ["https:", "http:", "mailto:"];
97
+ /** Attachments open and load only from these schemes or from a relative URL. */
98
+ const ATTACHMENT_PROTOCOLS = new Set(["https:", "http:"]);
99
+ /**
100
+ * A picture may also show a file that is still uploading. A `blob:` URL runs in the page's origin when it is opened,
101
+ * so links never use one.
102
+ */
103
+ const PICTURE_PROTOCOLS = new Set([...ATTACHMENT_PROTOCOLS, "blob:"]);
104
+ /** A grid shows this many images or videos; the last one tells how many more there are. */
105
+ const MEDIA_SHOWN = 4;
106
+ /** A single image keeps its own aspect ratio within these bounds; beyond them it is cropped to fit. */
107
+ const MEDIA_RATIO_MIN = 0.5;
108
+ const MEDIA_RATIO_MAX = 3;
109
+ const timestamp = value => value instanceof Date ? value.getTime() : new Date(value).getTime();
110
+
111
+ /**
112
+ * Whether a message starts a new group: there is no previous message, either is a system row, the author changes, or
113
+ * more than five minutes passed. Also start a group where you show a separator such as a new day.
114
+ */
115
+ export const startsMessageGroup = (entry, previous) => {
116
+ if (!previous || entry.system || previous.system || entry.author !== previous.author) return true;
117
+ const gap = timestamp(entry.at) - timestamp(previous.at);
118
+ return !(gap >= 0 && gap <= GROUP_WINDOW_MS);
119
+ };
120
+
121
+ /** Text that wraps: every `LINE_CHARS` visible characters of a line take at least one line. */
122
+ const wrappedLines = text => text.split("\n").reduce((lines, line) => lines + Math.ceil(line.trim().length / LINE_CHARS), 0);
123
+
124
+ /** What inline tokens show: a link its label, never its destination. */
125
+ const inlineText = tokens => {
126
+ let text = "";
127
+ for (const token of tokens) {
128
+ if (token.type === "br") text += "\n";else if ("tokens" in token && token.tokens) text += inlineText(token.tokens);else if ("text" in token) text += token.text;
129
+ }
130
+ return text;
131
+ };
132
+
133
+ /**
134
+ * The fewest lines the rendered tokens take at any width. Code never wraps, so each of its lines is one line, blank
135
+ * ones too, and its bar one more. Link destinations and reference definitions show nothing. Decided from the text
136
+ * alone, so the row has its final height when it mounts.
137
+ */
138
+ const renderedLines = tokens => {
139
+ let lines = 0;
140
+ for (const token of tokens) {
141
+ switch (token.type) {
142
+ case "code":
143
+ lines += token.text.split("\n").length + 1;
144
+ break;
145
+ case "list":
146
+ for (const item of token.items) lines += Math.max(1, renderedLines(item.tokens));
147
+ break;
148
+ case "table":
149
+ lines += token.rows.length + (token.header.some(cell => cell.text.trim()) ? 1 : 0);
150
+ break;
151
+ case "paragraph":
152
+ case "heading":
153
+ case "text":
154
+ lines += wrappedLines(token.tokens ? inlineText(token.tokens) : token.text);
155
+ break;
156
+ case "hr":
157
+ lines += 1;
158
+ break;
159
+ case "space":
160
+ case "def":
161
+ break;
162
+ default:
163
+ if ("tokens" in token && token.tokens) lines += renderedLines(token.tokens);else if ("text" in token) lines += wrappedLines(token.text);
164
+ }
165
+ }
166
+ return lines;
167
+ };
168
+
169
+ /** Messages link only to absolute URLs; a relative one would lead somewhere else on every page that shows it. */
170
+ const isAbsoluteUrl = href => {
171
+ try {
172
+ return new URL(href).protocol !== "";
173
+ } catch {
174
+ return false;
175
+ }
176
+ };
177
+ const dateTimeOf = value => {
178
+ if (value === undefined) return undefined;
179
+ const date = value instanceof Date ? value : new Date(value);
180
+ return Number.isFinite(date.getTime()) ? date.toISOString() : undefined;
181
+ };
182
+
183
+ /**
184
+ * An attachment URL the row may load or open: absolute with an allowed scheme, or relative to the page. A picture may
185
+ * also be a `blob:` URL or a `data:image/` URL, which an image element shows without running anything.
186
+ */
187
+ const attachmentUrl = (url, picture = false) => {
188
+ if (!url) return undefined;
189
+ if (picture && /^data:image\//i.test(url)) return url;
190
+ try {
191
+ return (picture ? PICTURE_PROTOCOLS : ATTACHMENT_PROTOCOLS).has(new URL(url, "https://relative.invalid/").protocol) ? url : undefined;
192
+ } catch {
193
+ return undefined;
194
+ }
195
+ };
196
+
197
+ /** The aspect ratio a single image or video reserves, from its stored size. */
198
+ const mediaRatio = media => {
199
+ const ratio = media.width > 0 && media.height > 0 ? media.width / media.height : 4 / 3;
200
+ return Math.min(MEDIA_RATIO_MAX, Math.max(MEDIA_RATIO_MIN, ratio));
201
+ };
202
+
203
+ /** The width of a single image or video: its stored width, at most the row's media height tall, never wider than the row. */
204
+ const mediaWidth = media => {
205
+ const natural = Number.isFinite(media.width) && media.width > 0 ? `${media.width}px, ` : "";
206
+ return `min(100%, ${natural}calc(var(--k2b-message-media-height) * ${mediaRatio(media)}))`;
207
+ };
208
+
209
+ /** Only the `ti-*` glyph of `fileIcons`, which may add colour utilities that `@k2b/ui` does not ship. */
210
+ const fileIcon = file => file.icon ?? `ti ${fileIcons.getFileIcon({
211
+ name: file.name,
212
+ type: "file",
213
+ mimeType: file.mediaType
214
+ }).split(/\s+/).filter(token => token.startsWith("ti-")).join(" ")}`;
215
+
216
+ /** A link, a button, or neither, depending on what the caller passes. */
217
+ function AttachmentTarget(props) {
218
+ return _$createComponent(Show, {
219
+ get when() {
220
+ return props.onOpen;
221
+ },
222
+ get fallback() {
223
+ return _$createComponent(Show, {
224
+ get when() {
225
+ return attachmentUrl(props.href);
226
+ },
227
+ get fallback() {
228
+ return _$createComponent(Show, {
229
+ get when() {
230
+ return props.label;
231
+ },
232
+ get fallback() {
233
+ return (() => {
234
+ var _el$ = _$getNextElement(_tmpl$);
235
+ _$insert(_el$, () => props.children);
236
+ _$effect(_p$ => {
237
+ var _v$ = props.class,
238
+ _v$2 = props.style;
239
+ _v$ !== _p$.e && _$className(_el$, _p$.e = _v$);
240
+ _p$.t = _$style(_el$, _v$2, _p$.t);
241
+ return _p$;
242
+ }, {
243
+ e: undefined,
244
+ t: undefined
245
+ });
246
+ return _el$;
247
+ })();
248
+ },
249
+ children: label => (() => {
250
+ var _el$2 = _$getNextElement(_tmpl$2);
251
+ _$insert(_el$2, () => props.children);
252
+ _$effect(_p$ => {
253
+ var _v$3 = props.class,
254
+ _v$4 = props.style,
255
+ _v$5 = label();
256
+ _v$3 !== _p$.e && _$className(_el$2, _p$.e = _v$3);
257
+ _p$.t = _$style(_el$2, _v$4, _p$.t);
258
+ _v$5 !== _p$.a && _$setAttribute(_el$2, "aria-label", _p$.a = _v$5);
259
+ return _p$;
260
+ }, {
261
+ e: undefined,
262
+ t: undefined,
263
+ a: undefined
264
+ });
265
+ return _el$2;
266
+ })()
267
+ });
268
+ },
269
+ children: href => (() => {
270
+ var _el$3 = _$getNextElement(_tmpl$3);
271
+ _$insert(_el$3, () => props.children);
272
+ _$effect(_p$ => {
273
+ var _v$6 = props.class,
274
+ _v$7 = props.style,
275
+ _v$8 = href(),
276
+ _v$9 = props.label;
277
+ _v$6 !== _p$.e && _$className(_el$3, _p$.e = _v$6);
278
+ _p$.t = _$style(_el$3, _v$7, _p$.t);
279
+ _v$8 !== _p$.a && _$setAttribute(_el$3, "href", _p$.a = _v$8);
280
+ _v$9 !== _p$.o && _$setAttribute(_el$3, "aria-label", _p$.o = _v$9);
281
+ return _p$;
282
+ }, {
283
+ e: undefined,
284
+ t: undefined,
285
+ a: undefined,
286
+ o: undefined
287
+ });
288
+ return _el$3;
289
+ })()
290
+ });
291
+ },
292
+ children: open => (() => {
293
+ var _el$4 = _$getNextElement(_tmpl$4);
294
+ _el$4.$$click = () => open()();
295
+ _$insert(_el$4, () => props.children);
296
+ _$effect(_p$ => {
297
+ var _v$0 = props.class,
298
+ _v$1 = props.style,
299
+ _v$10 = props.label;
300
+ _v$0 !== _p$.e && _$className(_el$4, _p$.e = _v$0);
301
+ _p$.t = _$style(_el$4, _v$1, _p$.t);
302
+ _v$10 !== _p$.a && _$setAttribute(_el$4, "aria-label", _p$.a = _v$10);
303
+ return _p$;
304
+ }, {
305
+ e: undefined,
306
+ t: undefined,
307
+ a: undefined
308
+ });
309
+ _$runHydrationEvents();
310
+ return _el$4;
311
+ })()
312
+ });
313
+ }
314
+
315
+ /**
316
+ * Renders the safe Markdown of `MarkdownView` with line breaks kept, absolute links only, and a copy control above
317
+ * every code block, and counts the fewest lines it takes. An edited message that does not collapse ends with the marker.
318
+ */
319
+ const renderMessage = (text, locale, messages, edited) => {
320
+ const renderer = createSafeRenderer({
321
+ allowImages: false,
322
+ linkProtocols: LINK_PROTOCOLS,
323
+ linkTarget: "_blank",
324
+ locale
325
+ });
326
+ const renderLink = renderer.link.bind(renderer);
327
+ renderer.link = token => isAbsoluteUrl(token.href) ? renderLink(token) : renderer.parser.parseInline(token.tokens);
328
+ const renderCode = renderer.code.bind(renderer);
329
+ renderer.code = token => {
330
+ const language = token.lang?.match(/^\S{1,24}/)?.[0];
331
+ const block = renderCode(token).replace(/^<pre>/, '<pre tabindex="0">');
332
+ return '<div class="k2b-message-row__code"><div class="k2b-message-row__code-bar">' + `<span class="k2b-message-row__code-label">${escapeHtml(language ?? messages.code)}</span>` + '<button type="button" class="k2b-message-row__copy">' + `<span class="k2b-message-row__copy-idle"><i class="ti ti-copy" aria-hidden="true"></i>${escapeHtml(messages.copy)}</span>` + `<span class="k2b-message-row__copy-done"><i class="ti ti-check" aria-hidden="true"></i>${escapeHtml(messages.copied)}</span>` + `</button></div>${block}</div>`;
333
+ };
334
+ const marked = createSafeMarked(locale).setOptions({
335
+ breaks: true,
336
+ renderer
337
+ });
338
+ const tokens = marked.lexer(text);
339
+ const html = marked.parser(tokens);
340
+ const lines = renderedLines(tokens);
341
+ // A collapsed message would hide the end of its text, so its marker sits next to "Show more" instead.
342
+ if (!edited || lines > COLLAPSE_LINES) return {
343
+ html,
344
+ lines
345
+ };
346
+ // The marker ends the last paragraph, so it takes no line of its own there.
347
+ const marker = `<span class="k2b-message-row__edited">${escapeHtml(messages.messageEdited)}</span>`;
348
+ const end = html.trimEnd();
349
+ return {
350
+ html: end.endsWith("</p>") ? `${end.slice(0, -4)} ${marker}</p>` : `${html}<p>${marker}</p>`,
351
+ lines
352
+ };
353
+ };
354
+
355
+ /**
356
+ * One message in a conversation with many people: own messages on the right, others on the left with avatar, name,
357
+ * and time at the start of a group. Every part has its final height when the row mounts, so a virtualized list such
358
+ * as `VirtualFeed` keeps its positions.
359
+ */
360
+ export function MessageRow(props) {
361
+ const messages = useUiMessages();
362
+ const locale = useLocale();
363
+ const textId = `k2b-message-${createUniqueId()}`;
364
+ const [expanded, setExpanded] = createSignal(false);
365
+ const rendered = createMemo(() => renderMessage(props.text, locale(), messages(), Boolean(props.edited)));
366
+ const collapsible = () => rendered().lines > COLLAPSE_LINES;
367
+ const collapsed = () => collapsible() && !expanded();
368
+ const groupStart = () => props.groupStart ?? true;
369
+ const hasLine = () => props.progress !== undefined || props.status !== undefined || props.receipt !== undefined;
370
+ const reactions = () => props.deleted ? undefined : props.reactions;
371
+ // Chips follow their keys, so a chip keeps its element, and focus, when the caller passes new objects for it.
372
+ const reactionKeys = createMemo(() => reactions()?.map(reaction => reaction.key) ?? []);
373
+ const reactionByKey = createMemo(() => new Map(reactions()?.map(reaction => [reaction.key, reaction])));
374
+ const hasFooter = () => hasLine() || reactions() !== undefined;
375
+ const writing = () => props.progress !== undefined && !props.deleted && props.text.trim() === "";
376
+ const hasBubble = () => props.deleted || writing() || props.text.trim() !== "";
377
+ const content = () => !props.deleted;
378
+ const media = createMemo(() => content() ? (props.attachments ?? []).filter(attachment => attachment.kind !== "file") : []);
379
+ const files = createMemo(() => content() ? (props.attachments ?? []).filter(attachment => attachment.kind === "file") : []);
380
+ const shownMedia = () => media().slice(0, MEDIA_SHOWN);
381
+ const hiddenMedia = () => media().length - shownMedia().length;
382
+ let text;
383
+ let copiedTimer;
384
+ onCleanup(() => clearTimeout(copiedTimer));
385
+ const copyCode = event => {
386
+ const button = event.target?.closest?.(".k2b-message-row__copy");
387
+ if (!button || !text?.contains(button)) return;
388
+ const code = button.closest(".k2b-message-row__code")?.querySelector("pre")?.textContent ?? "";
389
+ copyText(code).then(() => {
390
+ for (const other of text?.querySelectorAll(".k2b-message-row__copy[data-copied]") ?? []) delete other.dataset.copied;
391
+ button.dataset.copied = "";
392
+ announce(messages().copied);
393
+ clearTimeout(copiedTimer);
394
+ copiedTimer = setTimeout(() => delete button.dataset.copied, COPIED_MS);
395
+ }, () => {});
396
+ };
397
+
398
+ // A keyboard user who reaches a link or code block in the hidden or faded part of a collapsed message gets the whole
399
+ // message. The collapsed text clips instead of scrolling, so the browser cannot scroll it to the target instead.
400
+ const revealFocus = event => {
401
+ if (!text || !collapsed() || !(event.target instanceof Element)) return;
402
+ const fade = text.getBoundingClientRect().bottom - FADE_LINES * Number.parseFloat(getComputedStyle(text).lineHeight);
403
+ if (event.target.getBoundingClientRect().bottom > fade) setExpanded(true);
404
+ };
405
+ const header = () => [(() => {
406
+ var _el$5 = _$getNextElement(_tmpl$5);
407
+ _$insert(_el$5, () => props.author.name);
408
+ return _el$5;
409
+ })(), _$createComponent(Show, {
410
+ get when() {
411
+ return props.badge;
412
+ },
413
+ get children() {
414
+ var _el$6 = _$getNextElement(_tmpl$6);
415
+ _$insert(_el$6, () => props.badge);
416
+ return _el$6;
417
+ }
418
+ }), (() => {
419
+ var _el$7 = _$getNextElement(_tmpl$7);
420
+ _$insert(_el$7, () => props.time);
421
+ _$effect(() => _$setAttribute(_el$7, "datetime", dateTimeOf(props.dateTime)));
422
+ return _el$7;
423
+ })()];
424
+ const quote = value => [_$getNextElement(_tmpl$8), (() => {
425
+ var _el$9 = _$getNextElement(_tmpl$9),
426
+ _el$0 = _el$9.firstChild,
427
+ _el$10 = _el$0.firstChild,
428
+ [_el$11, _co$] = _$getNextMarker(_el$10.nextSibling),
429
+ _el$1 = _el$11.nextSibling,
430
+ _el$12 = _el$0.nextSibling,
431
+ _el$13 = _el$12.nextSibling,
432
+ _el$14 = _el$13.nextSibling,
433
+ [_el$15, _co$2] = _$getNextMarker(_el$14.nextSibling);
434
+ _$insert(_el$0, () => messages().messageReplyTo, _el$11, _co$);
435
+ _$insert(_el$12, () => value.author);
436
+ _$insert(_el$9, () => value.text, _el$15, _co$2);
437
+ return _el$9;
438
+ })()];
439
+
440
+ /** The picture's description, then that it is a video and how long, then how many more the grid hides. */
441
+ const mediaLabel = (item, more) => [item.alt, item.kind === "video" ? [messages().messageVideo, item.duration].filter(Boolean).join(" ") : "", more > 0 ? messages().messageMoreAttachments({
442
+ count: more
443
+ }) : ""].filter(Boolean).join(", ");
444
+
445
+ /** A toggle button, or with no `onToggleReaction` a labelled image that also says whether the reader reacted. */
446
+ const reactionChip = (key, reaction) => {
447
+ const label = own => messages().messageReaction({
448
+ emoji: reaction().emoji,
449
+ count: reaction().count,
450
+ names: reaction().label,
451
+ own
452
+ });
453
+ return _$createComponent(Show, {
454
+ get when() {
455
+ return props.onToggleReaction;
456
+ },
457
+ get fallback() {
458
+ return (() => {
459
+ var _el$16 = _$getNextElement(_tmpl$0),
460
+ _el$17 = _el$16.firstChild,
461
+ _el$18 = _el$17.nextSibling;
462
+ _$insert(_el$17, () => reaction().emoji);
463
+ _$insert(_el$18, () => reaction().count);
464
+ _$effect(_p$ => {
465
+ var _v$11 = label(Boolean(reaction().own)),
466
+ _v$12 = reaction().label,
467
+ _v$13 = reaction().own ? "" : undefined;
468
+ _v$11 !== _p$.e && _$setAttribute(_el$16, "aria-label", _p$.e = _v$11);
469
+ _v$12 !== _p$.t && _$setAttribute(_el$16, "title", _p$.t = _v$12);
470
+ _v$13 !== _p$.a && _$setAttribute(_el$16, "data-own", _p$.a = _v$13);
471
+ return _p$;
472
+ }, {
473
+ e: undefined,
474
+ t: undefined,
475
+ a: undefined
476
+ });
477
+ return _el$16;
478
+ })();
479
+ },
480
+ children: toggle => (() => {
481
+ var _el$19 = _$getNextElement(_tmpl$1),
482
+ _el$20 = _el$19.firstChild,
483
+ _el$21 = _el$20.nextSibling;
484
+ _el$19.$$click = () => toggle()(key);
485
+ _$insert(_el$20, () => reaction().emoji);
486
+ _$insert(_el$21, () => reaction().count);
487
+ _$effect(_p$ => {
488
+ var _v$14 = reaction().own ? "true" : "false",
489
+ _v$15 = label(false),
490
+ _v$16 = reaction().label,
491
+ _v$17 = reaction().own ? "" : undefined;
492
+ _v$14 !== _p$.e && _$setAttribute(_el$19, "aria-pressed", _p$.e = _v$14);
493
+ _v$15 !== _p$.t && _$setAttribute(_el$19, "aria-label", _p$.t = _v$15);
494
+ _v$16 !== _p$.a && _$setAttribute(_el$19, "title", _p$.a = _v$16);
495
+ _v$17 !== _p$.o && _$setAttribute(_el$19, "data-own", _p$.o = _v$17);
496
+ return _p$;
497
+ }, {
498
+ e: undefined,
499
+ t: undefined,
500
+ a: undefined,
501
+ o: undefined
502
+ });
503
+ _$runHydrationEvents();
504
+ return _el$19;
505
+ })()
506
+ });
507
+ };
508
+ const mediaItem = (item, index) => {
509
+ const more = () => index() === shownMedia().length - 1 ? hiddenMedia() : 0;
510
+ return _$createComponent(AttachmentTarget, {
511
+ "class": "k2b-message-row__media-item",
512
+ get style() {
513
+ return _$memo(() => media().length === 1)() ? {
514
+ "aspect-ratio": String(mediaRatio(item))
515
+ } : undefined;
516
+ },
517
+ get label() {
518
+ return mediaLabel(item, more());
519
+ },
520
+ get onOpen() {
521
+ return item.onOpen;
522
+ },
523
+ get href() {
524
+ return item.href;
525
+ },
526
+ get children() {
527
+ return [(() => {
528
+ var _el$22 = _$getNextElement(_tmpl$10);
529
+ _$effect(_p$ => {
530
+ var _v$18 = attachmentUrl(item.src, true),
531
+ _v$19 = item.width > 0 ? item.width : undefined,
532
+ _v$20 = item.height > 0 ? item.height : undefined;
533
+ _v$18 !== _p$.e && _$setAttribute(_el$22, "src", _p$.e = _v$18);
534
+ _v$19 !== _p$.t && _$setAttribute(_el$22, "width", _p$.t = _v$19);
535
+ _v$20 !== _p$.a && _$setAttribute(_el$22, "height", _p$.a = _v$20);
536
+ return _p$;
537
+ }, {
538
+ e: undefined,
539
+ t: undefined,
540
+ a: undefined
541
+ });
542
+ return _el$22;
543
+ })(), _$createComponent(Show, {
544
+ get when() {
545
+ return item.kind === "video";
546
+ },
547
+ get children() {
548
+ return [_$getNextElement(_tmpl$11), _$createComponent(Show, {
549
+ get when() {
550
+ return item.duration;
551
+ },
552
+ get children() {
553
+ var _el$24 = _$getNextElement(_tmpl$12);
554
+ _$insert(_el$24, () => item.duration);
555
+ return _el$24;
556
+ }
557
+ })];
558
+ }
559
+ }), _$createComponent(Show, {
560
+ get when() {
561
+ return more() > 0;
562
+ },
563
+ get children() {
564
+ var _el$25 = _$getNextElement(_tmpl$13),
565
+ _el$26 = _el$25.firstChild,
566
+ _el$27 = _el$26.nextSibling,
567
+ [_el$28, _co$3] = _$getNextMarker(_el$27.nextSibling);
568
+ _$insert(_el$25, more, _el$28, _co$3);
569
+ return _el$25;
570
+ }
571
+ })];
572
+ }
573
+ });
574
+ };
575
+ return (() => {
576
+ var _el$29 = _$getNextElement(_tmpl$29),
577
+ _el$81 = _el$29.firstChild,
578
+ [_el$82, _co$17] = _$getNextMarker(_el$81.nextSibling),
579
+ _el$32 = _el$82.nextSibling,
580
+ _el$58 = _el$32.firstChild,
581
+ [_el$59, _co$8] = _$getNextMarker(_el$58.nextSibling),
582
+ _el$60 = _el$59.nextSibling,
583
+ [_el$61, _co$9] = _$getNextMarker(_el$60.nextSibling),
584
+ _el$62 = _el$61.nextSibling,
585
+ [_el$63, _co$0] = _$getNextMarker(_el$62.nextSibling),
586
+ _el$64 = _el$63.nextSibling,
587
+ [_el$65, _co$1] = _$getNextMarker(_el$64.nextSibling),
588
+ _el$66 = _el$65.nextSibling,
589
+ [_el$67, _co$10] = _$getNextMarker(_el$66.nextSibling),
590
+ _el$68 = _el$67.nextSibling,
591
+ [_el$69, _co$11] = _$getNextMarker(_el$68.nextSibling),
592
+ _el$70 = _el$69.nextSibling,
593
+ [_el$71, _co$12] = _$getNextMarker(_el$70.nextSibling),
594
+ _el$72 = _el$71.nextSibling,
595
+ [_el$73, _co$13] = _$getNextMarker(_el$72.nextSibling),
596
+ _el$74 = _el$73.nextSibling,
597
+ [_el$75, _co$14] = _$getNextMarker(_el$74.nextSibling),
598
+ _el$76 = _el$75.nextSibling,
599
+ [_el$77, _co$15] = _$getNextMarker(_el$76.nextSibling),
600
+ _el$78 = _el$77.nextSibling,
601
+ [_el$79, _co$16] = _$getNextMarker(_el$78.nextSibling),
602
+ _el$83 = _el$32.nextSibling,
603
+ [_el$84, _co$18] = _$getNextMarker(_el$83.nextSibling);
604
+ _$insert(_el$29, _$createComponent(Show, {
605
+ get when() {
606
+ return !props.own;
607
+ },
608
+ get children() {
609
+ var _el$30 = _$getNextElement(_tmpl$15);
610
+ _$insert(_el$30, _$createComponent(Show, {
611
+ get when() {
612
+ return groupStart();
613
+ },
614
+ get children() {
615
+ var _el$31 = _$getNextElement(_tmpl$14);
616
+ _$insert(_el$31, _$createComponent(Avatar, {
617
+ get name() {
618
+ return props.author.name;
619
+ },
620
+ get src() {
621
+ return props.author.avatar;
622
+ },
623
+ get icon() {
624
+ return props.author.icon;
625
+ },
626
+ size: "sm"
627
+ }));
628
+ return _el$31;
629
+ }
630
+ }));
631
+ return _el$30;
632
+ }
633
+ }), _el$82, _co$17);
634
+ _$insert(_el$32, _$createComponent(Show, {
635
+ get when() {
636
+ return groupStart();
637
+ },
638
+ get fallback() {
639
+ return (() => {
640
+ var _el$85 = _$getNextElement(_tmpl$30),
641
+ _el$88 = _el$85.firstChild,
642
+ [_el$89, _co$19] = _$getNextMarker(_el$88.nextSibling),
643
+ _el$86 = _el$89.nextSibling,
644
+ _el$90 = _el$86.nextSibling,
645
+ [_el$91, _co$20] = _$getNextMarker(_el$90.nextSibling),
646
+ _el$87 = _el$91.nextSibling;
647
+ _$insert(_el$85, () => props.author.name, _el$89, _co$19);
648
+ _$insert(_el$85, () => props.time, _el$91, _co$20);
649
+ return _el$85;
650
+ })();
651
+ },
652
+ get children() {
653
+ return _$createComponent(Show, {
654
+ get when() {
655
+ return !props.own;
656
+ },
657
+ get fallback() {
658
+ return (() => {
659
+ var _el$92 = _$getNextElement(_tmpl$31),
660
+ _el$93 = _el$92.firstChild,
661
+ _el$95 = _el$93.firstChild,
662
+ [_el$96, _co$21] = _$getNextMarker(_el$95.nextSibling),
663
+ _el$94 = _el$96.nextSibling,
664
+ _el$97 = _el$93.nextSibling;
665
+ _$insert(_el$93, () => props.author.name, _el$96, _co$21);
666
+ _$insert(_el$97, () => props.time);
667
+ _$effect(() => _$setAttribute(_el$97, "datetime", dateTimeOf(props.dateTime)));
668
+ return _el$92;
669
+ })();
670
+ },
671
+ get children() {
672
+ var _el$33 = _$getNextElement(_tmpl$16);
673
+ _$insert(_el$33, header);
674
+ return _el$33;
675
+ }
676
+ });
677
+ }
678
+ }), _el$59, _co$8);
679
+ _$insert(_el$32, _$createComponent(Show, {
680
+ get when() {
681
+ return _$memo(() => !!content())() && props.forwarded;
682
+ },
683
+ get children() {
684
+ var _el$34 = _$getNextElement(_tmpl$17),
685
+ _el$35 = _el$34.firstChild,
686
+ _el$36 = _el$35.nextSibling;
687
+ _$insert(_el$36, () => messages().messageForwarded);
688
+ return _el$34;
689
+ }
690
+ }), _el$61, _co$9);
691
+ _$insert(_el$32, _$createComponent(Show, {
692
+ get when() {
693
+ return _$memo(() => !!content())() && props.quote;
694
+ },
695
+ children: value => _$createComponent(Show, {
696
+ get when() {
697
+ return value().onSelect;
698
+ },
699
+ get fallback() {
700
+ return (() => {
701
+ var _el$98 = _$getNextElement(_tmpl$32);
702
+ _$insert(_el$98, () => quote(value()));
703
+ return _el$98;
704
+ })();
705
+ },
706
+ children: select => (() => {
707
+ var _el$99 = _$getNextElement(_tmpl$33);
708
+ _el$99.$$click = () => select()();
709
+ _$insert(_el$99, () => quote(value()));
710
+ _$runHydrationEvents();
711
+ return _el$99;
712
+ })()
713
+ })
714
+ }), _el$63, _co$0);
715
+ _$insert(_el$32, _$createComponent(Show, {
716
+ get when() {
717
+ return hasBubble();
718
+ },
719
+ get children() {
720
+ var _el$37 = _$getNextElement(_tmpl$21);
721
+ _$insert(_el$37, _$createComponent(Show, {
722
+ get when() {
723
+ return !props.deleted;
724
+ },
725
+ get fallback() {
726
+ return (() => {
727
+ var _el$100 = _$getNextElement(_tmpl$34),
728
+ _el$101 = _el$100.firstChild,
729
+ _el$102 = _el$101.nextSibling,
730
+ [_el$103, _co$22] = _$getNextMarker(_el$102.nextSibling);
731
+ _$insert(_el$100, () => messages().messageDeleted, _el$103, _co$22);
732
+ return _el$100;
733
+ })();
734
+ },
735
+ get children() {
736
+ return _$createComponent(Show, {
737
+ get when() {
738
+ return !writing();
739
+ },
740
+ get fallback() {
741
+ return _$getNextElement(_tmpl$35);
742
+ },
743
+ get children() {
744
+ return [(() => {
745
+ var _el$38 = _$getNextElement(_tmpl$18);
746
+ _el$38.$$focusin = revealFocus;
747
+ _el$38.$$click = copyCode;
748
+ var _ref$ = text;
749
+ typeof _ref$ === "function" ? _$use(_ref$, _el$38) : text = _el$38;
750
+ _$setAttribute(_el$38, "id", textId);
751
+ _$effect(_p$ => {
752
+ var _v$21 = collapsed() ? "" : undefined,
753
+ _v$22 = rendered().html;
754
+ _v$21 !== _p$.e && _$setAttribute(_el$38, "data-collapsed", _p$.e = _v$21);
755
+ _v$22 !== _p$.t && _$setProperty(_el$38, "innerHTML", _p$.t = _v$22);
756
+ return _p$;
757
+ }, {
758
+ e: undefined,
759
+ t: undefined
760
+ });
761
+ _$runHydrationEvents();
762
+ return _el$38;
763
+ })(), _$createComponent(Show, {
764
+ get when() {
765
+ return collapsible();
766
+ },
767
+ get children() {
768
+ var _el$39 = _$getNextElement(_tmpl$20),
769
+ _el$41 = _el$39.firstChild,
770
+ [_el$42, _co$4] = _$getNextMarker(_el$41.nextSibling),
771
+ _el$43 = _el$42.nextSibling,
772
+ [_el$44, _co$5] = _$getNextMarker(_el$43.nextSibling);
773
+ _$insert(_el$39, _$createComponent(Button, {
774
+ variant: "text",
775
+ size: "xs",
776
+ "class": "k2b-message-row__more",
777
+ get ["aria-expanded"]() {
778
+ return expanded() ? "true" : "false";
779
+ },
780
+ "aria-controls": textId,
781
+ onClick: () => setExpanded(value => !value),
782
+ get children() {
783
+ return _$memo(() => !!expanded())() ? messages().messageShowLess : messages().messageShowMore;
784
+ }
785
+ }), _el$42, _co$4);
786
+ _$insert(_el$39, _$createComponent(Show, {
787
+ get when() {
788
+ return props.edited;
789
+ },
790
+ get children() {
791
+ var _el$40 = _$getNextElement(_tmpl$19);
792
+ _$insert(_el$40, () => messages().messageEdited);
793
+ return _el$40;
794
+ }
795
+ }), _el$44, _co$5);
796
+ return _el$39;
797
+ }
798
+ })];
799
+ }
800
+ });
801
+ }
802
+ }));
803
+ _$effect(_p$ => {
804
+ var _v$23 = props.deleted ? "" : undefined,
805
+ _v$24 = props.progress && !props.deleted ? "true" : undefined;
806
+ _v$23 !== _p$.e && _$setAttribute(_el$37, "data-deleted", _p$.e = _v$23);
807
+ _v$24 !== _p$.t && _$setAttribute(_el$37, "aria-busy", _p$.t = _v$24);
808
+ return _p$;
809
+ }, {
810
+ e: undefined,
811
+ t: undefined
812
+ });
813
+ return _el$37;
814
+ }
815
+ }), _el$65, _co$1);
816
+ _$insert(_el$32, _$createComponent(Show, {
817
+ get when() {
818
+ return _$memo(() => !!(content() && props.edited))() && !hasBubble();
819
+ },
820
+ get children() {
821
+ var _el$45 = _$getNextElement(_tmpl$22),
822
+ _el$46 = _el$45.firstChild,
823
+ _el$47 = _el$46.nextSibling;
824
+ _$insert(_el$47, () => messages().messageEdited);
825
+ return _el$45;
826
+ }
827
+ }), _el$67, _co$10);
828
+ _$insert(_el$32, _$createComponent(Show, {
829
+ get when() {
830
+ return media().length > 0;
831
+ },
832
+ get children() {
833
+ var _el$48 = _$getNextElement(_tmpl$23);
834
+ _$insert(_el$48, _$createComponent(For, {
835
+ get each() {
836
+ return shownMedia();
837
+ },
838
+ children: (item, index) => mediaItem(item, index)
839
+ }));
840
+ _$effect(_p$ => {
841
+ var _v$25 = messages().attachments,
842
+ _v$26 = Math.min(media().length, MEDIA_SHOWN),
843
+ _v$27 = media().length === 1 ? {
844
+ width: mediaWidth(media()[0])
845
+ } : undefined;
846
+ _v$25 !== _p$.e && _$setAttribute(_el$48, "aria-label", _p$.e = _v$25);
847
+ _v$26 !== _p$.t && _$setAttribute(_el$48, "data-count", _p$.t = _v$26);
848
+ _p$.a = _$style(_el$48, _v$27, _p$.a);
849
+ return _p$;
850
+ }, {
851
+ e: undefined,
852
+ t: undefined,
853
+ a: undefined
854
+ });
855
+ return _el$48;
856
+ }
857
+ }), _el$69, _co$11);
858
+ _$insert(_el$32, _$createComponent(Show, {
859
+ get when() {
860
+ return files().length > 0;
861
+ },
862
+ get children() {
863
+ var _el$49 = _$getNextElement(_tmpl$24);
864
+ _$insert(_el$49, _$createComponent(For, {
865
+ get each() {
866
+ return files();
867
+ },
868
+ children: file => _$createComponent(AttachmentTarget, {
869
+ "class": "k2b-message-row__file",
870
+ get onOpen() {
871
+ return file.onOpen;
872
+ },
873
+ get href() {
874
+ return file.href;
875
+ },
876
+ get children() {
877
+ return [(() => {
878
+ var _el$105 = _$getNextElement(_tmpl$36),
879
+ _el$106 = _el$105.firstChild;
880
+ _$effect(() => _$className(_el$106, fileIcon(file)));
881
+ return _el$105;
882
+ })(), (() => {
883
+ var _el$107 = _$getNextElement(_tmpl$38),
884
+ _el$108 = _el$107.firstChild,
885
+ _el$110 = _el$108.nextSibling,
886
+ [_el$111, _co$23] = _$getNextMarker(_el$110.nextSibling);
887
+ _$insert(_el$108, () => file.name);
888
+ _$insert(_el$107, _$createComponent(Show, {
889
+ get when() {
890
+ return file.detail;
891
+ },
892
+ get children() {
893
+ var _el$109 = _$getNextElement(_tmpl$37);
894
+ _$insert(_el$109, () => file.detail);
895
+ return _el$109;
896
+ }
897
+ }), _el$111, _co$23);
898
+ return _el$107;
899
+ })()];
900
+ }
901
+ })
902
+ }));
903
+ return _el$49;
904
+ }
905
+ }), _el$71, _co$12);
906
+ _$insert(_el$32, _$createComponent(Show, {
907
+ get when() {
908
+ return _$memo(() => !!content())() && props.linkPreview;
909
+ },
910
+ get children() {
911
+ var _el$50 = _$getNextElement(_tmpl$25);
912
+ _$insert(_el$50, () => props.linkPreview);
913
+ return _el$50;
914
+ }
915
+ }), _el$73, _co$13);
916
+ _$insert(_el$32, _$createComponent(Show, {
917
+ get when() {
918
+ return _$memo(() => !!content())() && props.card;
919
+ },
920
+ get children() {
921
+ var _el$51 = _$getNextElement(_tmpl$25);
922
+ _$insert(_el$51, () => props.card);
923
+ return _el$51;
924
+ }
925
+ }), _el$75, _co$14);
926
+ _$insert(_el$32, _$createComponent(Show, {
927
+ get when() {
928
+ return props.thread;
929
+ },
930
+ children: thread => (() => {
931
+ var _el$112 = _$getNextElement(_tmpl$40),
932
+ _el$115 = _el$112.firstChild,
933
+ [_el$116, _co$24] = _$getNextMarker(_el$115.nextSibling),
934
+ _el$114 = _el$116.nextSibling,
935
+ _el$117 = _el$114.nextSibling,
936
+ [_el$118, _co$25] = _$getNextMarker(_el$117.nextSibling);
937
+ _el$112.$$click = () => thread().onOpen();
938
+ _$insert(_el$112, _$createComponent(Show, {
939
+ get when() {
940
+ return (thread().participants?.length ?? 0) > 0;
941
+ },
942
+ get children() {
943
+ var _el$113 = _$getNextElement(_tmpl$39);
944
+ _$insert(_el$113, _$createComponent(For, {
945
+ get each() {
946
+ return thread().participants?.slice(0, 3);
947
+ },
948
+ children: person => _$createComponent(Avatar, {
949
+ get name() {
950
+ return person.name;
951
+ },
952
+ get src() {
953
+ return person.avatar;
954
+ },
955
+ get icon() {
956
+ return person.icon;
957
+ },
958
+ size: "xs"
959
+ })
960
+ }));
961
+ return _el$113;
962
+ }
963
+ }), _el$116, _co$24);
964
+ _$insert(_el$114, () => messages().messageReplies({
965
+ count: thread().count
966
+ }));
967
+ _$insert(_el$112, _$createComponent(Show, {
968
+ get when() {
969
+ return thread().lastReply;
970
+ },
971
+ children: time => (() => {
972
+ var _el$119 = _$getNextElement(_tmpl$41);
973
+ _$insert(_el$119, () => messages().messageLastReply({
974
+ time: time()
975
+ }));
976
+ _$effect(() => _$setAttribute(_el$119, "datetime", dateTimeOf(thread().lastReplyDateTime)));
977
+ return _el$119;
978
+ })()
979
+ }), _el$118, _co$25);
980
+ _$runHydrationEvents();
981
+ return _el$112;
982
+ })()
983
+ }), _el$77, _co$15);
984
+ _$insert(_el$32, _$createComponent(Show, {
985
+ get when() {
986
+ return hasFooter();
987
+ },
988
+ get children() {
989
+ var _el$52 = _$getNextElement(_tmpl$27),
990
+ _el$54 = _el$52.firstChild,
991
+ [_el$55, _co$6] = _$getNextMarker(_el$54.nextSibling),
992
+ _el$56 = _el$55.nextSibling,
993
+ [_el$57, _co$7] = _$getNextMarker(_el$56.nextSibling);
994
+ _$insert(_el$52, _$createComponent(Show, {
995
+ get when() {
996
+ return reactions();
997
+ },
998
+ children: list => (() => {
999
+ var _el$120 = _$getNextElement(_tmpl$42),
1000
+ _el$121 = _el$120.firstChild,
1001
+ [_el$122, _co$26] = _$getNextMarker(_el$121.nextSibling),
1002
+ _el$123 = _el$122.nextSibling,
1003
+ [_el$124, _co$27] = _$getNextMarker(_el$123.nextSibling);
1004
+ _$insert(_el$120, _$createComponent(Show, {
1005
+ get when() {
1006
+ return list().length > 0;
1007
+ },
1008
+ get children() {
1009
+ return _$createComponent(ScrollArea, {
1010
+ orientation: "horizontal",
1011
+ "class": "k2b-message-row__reaction-list",
1012
+ get tabindex() {
1013
+ return props.onToggleReaction ? undefined : 0;
1014
+ },
1015
+ get children() {
1016
+ return _$createComponent(For, {
1017
+ get each() {
1018
+ return reactionKeys();
1019
+ },
1020
+ children: key => _$createComponent(Show, {
1021
+ get when() {
1022
+ return reactionByKey().get(key);
1023
+ },
1024
+ children: reaction => reactionChip(key, reaction)
1025
+ })
1026
+ });
1027
+ }
1028
+ });
1029
+ }
1030
+ }), _el$122, _co$26);
1031
+ _$insert(_el$120, _$createComponent(Show, {
1032
+ get when() {
1033
+ return props.onAddReaction;
1034
+ },
1035
+ children: add => _$createComponent(IconButton, {
1036
+ get label() {
1037
+ return messages().messageAddReaction;
1038
+ },
1039
+ size: "xs",
1040
+ "class": "k2b-message-row__react",
1041
+ onClick: event => add()(event.currentTarget),
1042
+ get children() {
1043
+ return _$getNextElement(_tmpl$43);
1044
+ }
1045
+ })
1046
+ }), _el$124, _co$27);
1047
+ _$effect(_p$ => {
1048
+ var _v$33 = messages().messageReactions,
1049
+ _v$34 = list().length === 0 ? "" : undefined;
1050
+ _v$33 !== _p$.e && _$setAttribute(_el$120, "aria-label", _p$.e = _v$33);
1051
+ _v$34 !== _p$.t && _$setAttribute(_el$120, "data-empty", _p$.t = _v$34);
1052
+ return _p$;
1053
+ }, {
1054
+ e: undefined,
1055
+ t: undefined
1056
+ });
1057
+ return _el$120;
1058
+ })()
1059
+ }), _el$55, _co$6);
1060
+ _$insert(_el$52, _$createComponent(Show, {
1061
+ get when() {
1062
+ return hasLine();
1063
+ },
1064
+ get children() {
1065
+ var _el$53 = _$getNextElement(_tmpl$26);
1066
+ _$insert(_el$53, _$createComponent(Show, {
1067
+ get when() {
1068
+ return props.progress;
1069
+ },
1070
+ get fallback() {
1071
+ return [_$createComponent(Show, {
1072
+ get when() {
1073
+ return props.status === "pending";
1074
+ },
1075
+ get children() {
1076
+ return [_$getNextElement(_tmpl$44), (() => {
1077
+ var _el$127 = _$getNextElement(_tmpl$);
1078
+ _$insert(_el$127, () => messages().sending);
1079
+ return _el$127;
1080
+ })()];
1081
+ }
1082
+ }), _$createComponent(Show, {
1083
+ get when() {
1084
+ return props.status === "failed";
1085
+ },
1086
+ get children() {
1087
+ return [_$getNextElement(_tmpl$45), (() => {
1088
+ var _el$129 = _$getNextElement(_tmpl$);
1089
+ _$insert(_el$129, () => messages().messageNotSent);
1090
+ return _el$129;
1091
+ })(), _$createComponent(Show, {
1092
+ get when() {
1093
+ return props.onRetry;
1094
+ },
1095
+ children: retry => _$createComponent(Button, {
1096
+ variant: "text",
1097
+ size: "xs",
1098
+ onClick: () => retry()(),
1099
+ get children() {
1100
+ return messages().messageRetry;
1101
+ }
1102
+ })
1103
+ })];
1104
+ }
1105
+ }), _$createComponent(Show, {
1106
+ get when() {
1107
+ return _$memo(() => props.status !== "pending")() && props.status !== "failed";
1108
+ },
1109
+ get children() {
1110
+ return _$createComponent(Show, {
1111
+ get when() {
1112
+ return props.receipt;
1113
+ },
1114
+ get fallback() {
1115
+ return _$createComponent(Show, {
1116
+ get when() {
1117
+ return props.status === "sent";
1118
+ },
1119
+ get children() {
1120
+ return [_$getNextElement(_tmpl$46), (() => {
1121
+ var _el$131 = _$getNextElement(_tmpl$47);
1122
+ _$insert(_el$131, () => messages().messageSent);
1123
+ return _el$131;
1124
+ })()];
1125
+ }
1126
+ });
1127
+ },
1128
+ children: receipt => [_$getNextElement(_tmpl$48), (() => {
1129
+ var _el$133 = _$getNextElement(_tmpl$49);
1130
+ _$insert(_el$133, receipt);
1131
+ return _el$133;
1132
+ })()]
1133
+ });
1134
+ }
1135
+ })];
1136
+ },
1137
+ children: progress => [_$getNextElement(_tmpl$50), (() => {
1138
+ var _el$135 = _$getNextElement(_tmpl$51);
1139
+ _$insert(_el$135, () => progress().status);
1140
+ return _el$135;
1141
+ })(), _$createComponent(Show, {
1142
+ get when() {
1143
+ return progress().onStop;
1144
+ },
1145
+ children: stop => _$createComponent(Button, {
1146
+ variant: "text",
1147
+ size: "xs",
1148
+ onClick: () => stop()(),
1149
+ get children() {
1150
+ return messages().messageStop;
1151
+ }
1152
+ })
1153
+ })]
1154
+ }));
1155
+ return _el$53;
1156
+ }
1157
+ }), _el$57, _co$7);
1158
+ return _el$52;
1159
+ }
1160
+ }), _el$79, _co$16);
1161
+ _$insert(_el$29, _$createComponent(Show, {
1162
+ get when() {
1163
+ return (props.actions?.length ?? 0) > 0;
1164
+ },
1165
+ get children() {
1166
+ var _el$80 = _$getNextElement(_tmpl$28);
1167
+ _$insert(_el$80, _$createComponent(For, {
1168
+ get each() {
1169
+ return props.actions;
1170
+ },
1171
+ children: action => _$createComponent(IconButton, {
1172
+ get label() {
1173
+ return action.label;
1174
+ },
1175
+ size: "sm",
1176
+ get disabled() {
1177
+ return action.disabled;
1178
+ },
1179
+ onClick: () => action.onSelect(),
1180
+ get children() {
1181
+ var _el$136 = _$getNextElement(_tmpl$52);
1182
+ _$effect(() => _$className(_el$136, action.icon));
1183
+ return _el$136;
1184
+ }
1185
+ })
1186
+ }));
1187
+ _$effect(() => _$setAttribute(_el$80, "aria-label", messages().messageActions));
1188
+ return _el$80;
1189
+ }
1190
+ }), _el$84, _co$18);
1191
+ _$effect(_p$ => {
1192
+ var _v$28 = props.class ? `k2b-message-row ${props.class}` : "k2b-message-row",
1193
+ _v$29 = props.own ? "" : undefined,
1194
+ _v$30 = groupStart() ? "" : undefined,
1195
+ _v$31 = props.status,
1196
+ _v$32 = media().length > 0 ? "" : undefined;
1197
+ _v$28 !== _p$.e && _$className(_el$29, _p$.e = _v$28);
1198
+ _v$29 !== _p$.t && _$setAttribute(_el$29, "data-own", _p$.t = _v$29);
1199
+ _v$30 !== _p$.a && _$setAttribute(_el$29, "data-group-start", _p$.a = _v$30);
1200
+ _v$31 !== _p$.o && _$setAttribute(_el$29, "data-status", _p$.o = _v$31);
1201
+ _v$32 !== _p$.i && _$setAttribute(_el$29, "data-media", _p$.i = _v$32);
1202
+ return _p$;
1203
+ }, {
1204
+ e: undefined,
1205
+ t: undefined,
1206
+ a: undefined,
1207
+ o: undefined,
1208
+ i: undefined
1209
+ });
1210
+ return _el$29;
1211
+ })();
1212
+ }
1213
+
1214
+ /** A quiet, centered line for something that happened in the conversation, such as a person joining. */
1215
+ export function MessageSystemRow(props) {
1216
+ return (() => {
1217
+ var _el$137 = _$getNextElement(_tmpl$54),
1218
+ _el$140 = _el$137.firstChild,
1219
+ [_el$141, _co$28] = _$getNextMarker(_el$140.nextSibling),
1220
+ _el$138 = _el$141.nextSibling,
1221
+ _el$142 = _el$138.nextSibling,
1222
+ [_el$143, _co$29] = _$getNextMarker(_el$142.nextSibling);
1223
+ _$insert(_el$137, _$createComponent(Show, {
1224
+ get when() {
1225
+ return props.icon;
1226
+ },
1227
+ children: icon => (() => {
1228
+ var _el$144 = _$getNextElement(_tmpl$52);
1229
+ _$effect(() => _$className(_el$144, icon()));
1230
+ return _el$144;
1231
+ })()
1232
+ }), _el$141, _co$28);
1233
+ _$insert(_el$138, () => props.children);
1234
+ _$insert(_el$137, _$createComponent(Show, {
1235
+ get when() {
1236
+ return props.time;
1237
+ },
1238
+ get children() {
1239
+ var _el$139 = _$getNextElement(_tmpl$53);
1240
+ _$insert(_el$139, () => props.time);
1241
+ _$effect(() => _$setAttribute(_el$139, "datetime", dateTimeOf(props.dateTime)));
1242
+ return _el$139;
1243
+ }
1244
+ }), _el$143, _co$29);
1245
+ _$effect(() => _$className(_el$137, props.class ? `k2b-message-system-row ${props.class}` : "k2b-message-system-row"));
1246
+ return _el$137;
1247
+ })();
1248
+ }
1249
+ _$delegateEvents(["click", "focusin"]);
1250
+ //# sourceMappingURL=MessageRow.js.map