@k2b/ui 0.14.0 → 0.16.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.
- package/README.md +5 -0
- package/dist/base.css +1097 -0
- package/dist/browser/chat/ChatPrimitives.js +19 -16
- package/dist/browser/chat/ChatPrimitives.js.map +1 -1
- package/dist/browser/chat/ChatTimeline.js +110 -87
- package/dist/browser/chat/ChatTimeline.js.map +1 -1
- package/dist/browser/content/FileGrid.js +28 -8
- package/dist/browser/content/FileGrid.js.map +1 -1
- package/dist/browser/content/MarkdownView.js +11 -5
- package/dist/browser/content/MarkdownView.js.map +1 -1
- package/dist/browser/content/MessageRow.js +1250 -0
- package/dist/browser/content/MessageRow.js.map +1 -0
- package/dist/browser/content/StructuredDataPreview.js +4 -1
- package/dist/browser/content/StructuredDataPreview.js.map +1 -1
- package/dist/browser/content/VirtualFeed.js +1127 -0
- package/dist/browser/content/VirtualFeed.js.map +1 -0
- package/dist/browser/content/collection-selection.js +27 -11
- package/dist/browser/content/collection-selection.js.map +1 -1
- package/dist/browser/content/index.js +2 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/feedback/InstallGuide.js +199 -145
- package/dist/browser/feedback/InstallGuide.js.map +1 -1
- package/dist/browser/feedback/install.js +8 -4
- package/dist/browser/feedback/install.js.map +1 -1
- package/dist/browser/inputs/QrScanner.js +101 -21
- package/dist/browser/inputs/QrScanner.js.map +1 -1
- package/dist/browser/intl/messages.js +86 -0
- package/dist/browser/intl/messages.js.map +1 -1
- package/dist/browser/surfaces/Avatar.js +33 -17
- package/dist/browser/surfaces/Avatar.js.map +1 -1
- package/dist/ssr/chat/ChatPrimitives.js +2 -2
- package/dist/ssr/chat/ChatPrimitives.js.map +1 -1
- package/dist/ssr/chat/ChatTimeline.js +110 -87
- package/dist/ssr/chat/ChatTimeline.js.map +1 -1
- package/dist/ssr/content/FileGrid.js +17 -3
- package/dist/ssr/content/FileGrid.js.map +1 -1
- package/dist/ssr/content/MarkdownView.js +11 -5
- package/dist/ssr/content/MarkdownView.js.map +1 -1
- package/dist/ssr/content/MessageRow.js +796 -0
- package/dist/ssr/content/MessageRow.js.map +1 -0
- package/dist/ssr/content/StructuredDataPreview.js +1 -1
- package/dist/ssr/content/StructuredDataPreview.js.map +1 -1
- package/dist/ssr/content/VirtualFeed.js +989 -0
- package/dist/ssr/content/VirtualFeed.js.map +1 -0
- package/dist/ssr/content/collection-selection.js +27 -11
- package/dist/ssr/content/collection-selection.js.map +1 -1
- package/dist/ssr/content/index.js +2 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/feedback/InstallGuide.js +150 -98
- package/dist/ssr/feedback/InstallGuide.js.map +1 -1
- package/dist/ssr/feedback/install.js +8 -4
- package/dist/ssr/feedback/install.js.map +1 -1
- package/dist/ssr/inputs/QrScanner.js +101 -21
- package/dist/ssr/inputs/QrScanner.js.map +1 -1
- package/dist/ssr/intl/messages.js +86 -0
- package/dist/ssr/intl/messages.js.map +1 -1
- package/dist/ssr/surfaces/Avatar.js +14 -1
- package/dist/ssr/surfaces/Avatar.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/chat/ChatPrimitives.d.ts +2 -0
- package/dist/types/chat/ChatTimeline.d.ts +2 -0
- package/dist/types/content/FileGrid.d.ts +6 -1
- package/dist/types/content/MarkdownView.d.ts +8 -0
- package/dist/types/content/MessageRow.d.ts +168 -0
- package/dist/types/content/VirtualFeed.d.ts +81 -0
- package/dist/types/content/collection-selection.d.ts +15 -1
- package/dist/types/content/index.d.ts +4 -0
- package/dist/types/feedback/InstallGuide.d.ts +5 -3
- package/dist/types/feedback/install.d.ts +4 -3
- package/dist/types/inputs/QrScanner.d.ts +10 -2
- package/dist/types/intl/messages.d.ts +43 -0
- package/package.json +3 -1
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
import { memo as _$memo } from "solid-js/web";
|
|
2
|
+
import { ssr as _$ssr } from "solid-js/web";
|
|
3
|
+
import { ssrStyle as _$ssrStyle } from "solid-js/web";
|
|
4
|
+
import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
|
|
5
|
+
import { escape as _$escape } from "solid-js/web";
|
|
6
|
+
import { createComponent as _$createComponent } from "solid-js/web";
|
|
7
|
+
var _tmpl$ = ["<span", " style=\"", "\">", "</span>"],
|
|
8
|
+
_tmpl$2 = ["<span", " style=\"", "\" role=\"img\"", ">", "</span>"],
|
|
9
|
+
_tmpl$3 = ["<a", " style=\"", "\"", " target=\"_blank\" rel=\"noopener noreferrer\"", ">", "</a>"],
|
|
10
|
+
_tmpl$4 = ["<button type=\"button\"", " style=\"", "\"", ">", "</button>"],
|
|
11
|
+
_tmpl$5 = ["<span class=\"k2b-message-row__name\">", "</span>"],
|
|
12
|
+
_tmpl$6 = ["<span class=\"k2b-message-row__badge\">", "</span>"],
|
|
13
|
+
_tmpl$7 = ["<time class=\"k2b-message-row__time\"", ">", "</time>"],
|
|
14
|
+
_tmpl$8 = "<i class=\"ti ti-arrow-back-up\" aria-hidden=\"true\"></i>",
|
|
15
|
+
_tmpl$9 = ["<span class=\"k2b-message-row__quote-text\"><span class=\"k2b-sr-only\">", " </span><span class=\"k2b-message-row__quote-author\">", "</span> ", "</span>"],
|
|
16
|
+
_tmpl$0 = ["<span class=\"k2b-message-row__reaction\" role=\"img\"", "><span aria-hidden=\"true\">", "</span><span aria-hidden=\"true\">", "</span></span>"],
|
|
17
|
+
_tmpl$1 = ["<button type=\"button\" class=\"k2b-message-row__reaction\"", "><span aria-hidden=\"true\">", "</span><span aria-hidden=\"true\">", "</span></button>"],
|
|
18
|
+
_tmpl$10 = ["<img", " alt", " loading=\"lazy\" decoding=\"async\">"],
|
|
19
|
+
_tmpl$11 = "<span class=\"k2b-message-row__media-play\" aria-hidden=\"true\"><i class=\"ti ti-player-play\"></i></span>",
|
|
20
|
+
_tmpl$12 = ["<span class=\"k2b-message-row__media-duration\" aria-hidden=\"true\">", "</span>"],
|
|
21
|
+
_tmpl$13 = ["<span class=\"k2b-message-row__media-more\" aria-hidden=\"true\">+", "</span>"],
|
|
22
|
+
_tmpl$14 = ["<span class=\"k2b-message-row__avatar\" aria-hidden=\"true\">", "</span>"],
|
|
23
|
+
_tmpl$15 = ["<div class=\"k2b-message-row__gutter\">", "</div>"],
|
|
24
|
+
_tmpl$16 = ["<div class=\"k2b-message-row__meta\">", "</div>"],
|
|
25
|
+
_tmpl$17 = ["<div class=\"k2b-message-row__marker\"><i class=\"ti ti-arrow-forward-up\" aria-hidden=\"true\"></i><span>", "</span></div>"],
|
|
26
|
+
_tmpl$18 = ["<div", " class=\"k2b-content-markdown k2b-message-row__text\" data-heading-scale=\"compact\"", ">", "</div>"],
|
|
27
|
+
_tmpl$19 = ["<span class=\"k2b-message-row__edited\">", "</span>"],
|
|
28
|
+
_tmpl$20 = ["<div class=\"k2b-message-row__more-line\">", "", "</div>"],
|
|
29
|
+
_tmpl$21 = ["<div class=\"k2b-message-row__bubble\"", ">", "</div>"],
|
|
30
|
+
_tmpl$22 = ["<div class=\"k2b-message-row__marker\"><i class=\"ti ti-pencil\" aria-hidden=\"true\"></i><span class=\"k2b-message-row__edited\">", "</span></div>"],
|
|
31
|
+
_tmpl$23 = ["<div class=\"k2b-message-row__media\" role=\"group\"", " style=\"", "\">", "</div>"],
|
|
32
|
+
_tmpl$24 = ["<div class=\"k2b-message-row__files\">", "</div>"],
|
|
33
|
+
_tmpl$25 = ["<div class=\"k2b-message-row__slot\">", "</div>"],
|
|
34
|
+
_tmpl$26 = ["<div class=\"k2b-message-row__line\">", "</div>"],
|
|
35
|
+
_tmpl$27 = ["<div class=\"k2b-message-row__footer\">", "", "</div>"],
|
|
36
|
+
_tmpl$28 = ["<div class=\"k2b-message-row__actions\" role=\"group\"", ">", "</div>"],
|
|
37
|
+
_tmpl$29 = ["<div", ">", "<div class=\"k2b-message-row__body\">", "", "", "", "", "", "", "", "", "", "", "</div>", "</div>"],
|
|
38
|
+
_tmpl$30 = ["<span class=\"k2b-sr-only\">", ", ", ": </span>"],
|
|
39
|
+
_tmpl$31 = ["<div class=\"k2b-message-row__meta\"><span class=\"k2b-sr-only\">", ", </span><time class=\"k2b-message-row__time\"", ">", "</time></div>"],
|
|
40
|
+
_tmpl$32 = ["<div class=\"k2b-message-row__quote\">", "</div>"],
|
|
41
|
+
_tmpl$33 = ["<button type=\"button\" class=\"k2b-message-row__quote\">", "</button>"],
|
|
42
|
+
_tmpl$34 = ["<span class=\"k2b-message-row__deleted\"><i class=\"ti ti-message-off\" aria-hidden=\"true\"></i>", "</span>"],
|
|
43
|
+
_tmpl$35 = "<span class=\"k2b-message-row__writing\"><span class=\"k2b-chat-progress-dots\" aria-hidden=\"true\"><span></span><span></span><span></span></span></span>",
|
|
44
|
+
_tmpl$36 = ["<span class=\"k2b-message-row__file-icon\" aria-hidden=\"true\"><i", "></i></span>"],
|
|
45
|
+
_tmpl$37 = ["<span class=\"k2b-message-row__file-detail\">", "</span>"],
|
|
46
|
+
_tmpl$38 = ["<span class=\"k2b-message-row__file-copy\"><span class=\"k2b-message-row__file-name\">", "</span>", "</span>"],
|
|
47
|
+
_tmpl$39 = ["<span class=\"k2b-message-row__thread-people\" aria-hidden=\"true\">", "</span>"],
|
|
48
|
+
_tmpl$40 = ["<button type=\"button\" class=\"k2b-message-row__thread\">", "<span class=\"k2b-message-row__thread-count\">", "</span>", "</button>"],
|
|
49
|
+
_tmpl$41 = ["<time class=\"k2b-message-row__thread-time\"", ">", "</time>"],
|
|
50
|
+
_tmpl$42 = ["<div class=\"k2b-message-row__reactions\" role=\"group\"", ">", "", "</div>"],
|
|
51
|
+
_tmpl$43 = "<i class=\"ti ti-mood-plus\" aria-hidden=\"true\"></i>",
|
|
52
|
+
_tmpl$44 = "<i class=\"ti ti-clock\" aria-hidden=\"true\"></i>",
|
|
53
|
+
_tmpl$45 = ["<span>", "</span>"],
|
|
54
|
+
_tmpl$46 = "<i class=\"ti ti-alert-circle\" aria-hidden=\"true\"></i>",
|
|
55
|
+
_tmpl$47 = "<i class=\"ti ti-check\" aria-hidden=\"true\"></i>",
|
|
56
|
+
_tmpl$48 = ["<span class=\"k2b-sr-only\">", "</span>"],
|
|
57
|
+
_tmpl$49 = "<i class=\"ti ti-checks\" aria-hidden=\"true\"></i>",
|
|
58
|
+
_tmpl$50 = ["<span class=\"k2b-message-row__receipt\">", "</span>"],
|
|
59
|
+
_tmpl$51 = "<i class=\"ti ti-loader-2 k2b-spin\" aria-hidden=\"true\"></i>",
|
|
60
|
+
_tmpl$52 = ["<span class=\"k2b-message-row__line-text\">", "</span>"],
|
|
61
|
+
_tmpl$53 = ["<i", " aria-hidden=\"true\"></i>"],
|
|
62
|
+
_tmpl$54 = ["<time", ">", "</time>"],
|
|
63
|
+
_tmpl$55 = ["<div", ">", "<span>", "</span>", "</div>"];
|
|
64
|
+
import { fileIcons } from "@k2b/stdlib";
|
|
65
|
+
import { createMemo, createSignal, createUniqueId, For, onCleanup, Show } from "solid-js";
|
|
66
|
+
import { Button, IconButton } from "../actions/Button";
|
|
67
|
+
import { copyText } from "../actions/CopyButton";
|
|
68
|
+
import { announce } from "../feedback/announce";
|
|
69
|
+
import { useLocale } from "../intl/locale";
|
|
70
|
+
import { useUiMessages } from "../intl/messages";
|
|
71
|
+
import { ScrollArea } from "../layout/ScrollArea";
|
|
72
|
+
import { Avatar } from "../surfaces/Avatar";
|
|
73
|
+
import { createSafeMarked, createSafeRenderer, escapeHtml } from "./MarkdownView";
|
|
74
|
+
|
|
75
|
+
/** Messages by the same author within this time continue a group. */
|
|
76
|
+
const GROUP_WINDOW_MS = 5 * 60_000;
|
|
77
|
+
/**
|
|
78
|
+
* A message collapses to ten lines (`.k2b-message-row__text[data-collapsed]`) when its rendered text takes more lines
|
|
79
|
+
* than this. `renderedLines` counts the fewest lines the text takes at any width, so a collapsed message always hides
|
|
80
|
+
* some of it.
|
|
81
|
+
*/
|
|
82
|
+
const COLLAPSE_LINES = 14;
|
|
83
|
+
/** Visible characters that take at least one line of a message, even at its widest. */
|
|
84
|
+
const LINE_CHARS = 100;
|
|
85
|
+
/** Lines at the bottom of a collapsed message that fade out, as `.k2b-message-row__text[data-collapsed]` draws them. */
|
|
86
|
+
const FADE_LINES = 2;
|
|
87
|
+
const COPIED_MS = 2_000;
|
|
88
|
+
/** Links in messages may only use these schemes; other links show as their text. */
|
|
89
|
+
const LINK_PROTOCOLS = ["https:", "http:", "mailto:"];
|
|
90
|
+
/** Attachments open and load only from these schemes or from a relative URL. */
|
|
91
|
+
const ATTACHMENT_PROTOCOLS = new Set(["https:", "http:"]);
|
|
92
|
+
/**
|
|
93
|
+
* A picture may also show a file that is still uploading. A `blob:` URL runs in the page's origin when it is opened,
|
|
94
|
+
* so links never use one.
|
|
95
|
+
*/
|
|
96
|
+
const PICTURE_PROTOCOLS = new Set([...ATTACHMENT_PROTOCOLS, "blob:"]);
|
|
97
|
+
/** A grid shows this many images or videos; the last one tells how many more there are. */
|
|
98
|
+
const MEDIA_SHOWN = 4;
|
|
99
|
+
/** A single image keeps its own aspect ratio within these bounds; beyond them it is cropped to fit. */
|
|
100
|
+
const MEDIA_RATIO_MIN = 0.5;
|
|
101
|
+
const MEDIA_RATIO_MAX = 3;
|
|
102
|
+
const timestamp = value => value instanceof Date ? value.getTime() : new Date(value).getTime();
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Whether a message starts a new group: there is no previous message, either is a system row, the author changes, or
|
|
106
|
+
* more than five minutes passed. Also start a group where you show a separator such as a new day.
|
|
107
|
+
*/
|
|
108
|
+
export const startsMessageGroup = (entry, previous) => {
|
|
109
|
+
if (!previous || entry.system || previous.system || entry.author !== previous.author) return true;
|
|
110
|
+
const gap = timestamp(entry.at) - timestamp(previous.at);
|
|
111
|
+
return !(gap >= 0 && gap <= GROUP_WINDOW_MS);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** Text that wraps: every `LINE_CHARS` visible characters of a line take at least one line. */
|
|
115
|
+
const wrappedLines = text => text.split("\n").reduce((lines, line) => lines + Math.ceil(line.trim().length / LINE_CHARS), 0);
|
|
116
|
+
|
|
117
|
+
/** What inline tokens show: a link its label, never its destination. */
|
|
118
|
+
const inlineText = tokens => {
|
|
119
|
+
let text = "";
|
|
120
|
+
for (const token of tokens) {
|
|
121
|
+
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;
|
|
122
|
+
}
|
|
123
|
+
return text;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The fewest lines the rendered tokens take at any width. Code never wraps, so each of its lines is one line, blank
|
|
128
|
+
* ones too, and its bar one more. Link destinations and reference definitions show nothing. Decided from the text
|
|
129
|
+
* alone, so the row has its final height when it mounts.
|
|
130
|
+
*/
|
|
131
|
+
const renderedLines = tokens => {
|
|
132
|
+
let lines = 0;
|
|
133
|
+
for (const token of tokens) {
|
|
134
|
+
switch (token.type) {
|
|
135
|
+
case "code":
|
|
136
|
+
lines += token.text.split("\n").length + 1;
|
|
137
|
+
break;
|
|
138
|
+
case "list":
|
|
139
|
+
for (const item of token.items) lines += Math.max(1, renderedLines(item.tokens));
|
|
140
|
+
break;
|
|
141
|
+
case "table":
|
|
142
|
+
lines += token.rows.length + (token.header.some(cell => cell.text.trim()) ? 1 : 0);
|
|
143
|
+
break;
|
|
144
|
+
case "paragraph":
|
|
145
|
+
case "heading":
|
|
146
|
+
case "text":
|
|
147
|
+
lines += wrappedLines(token.tokens ? inlineText(token.tokens) : token.text);
|
|
148
|
+
break;
|
|
149
|
+
case "hr":
|
|
150
|
+
lines += 1;
|
|
151
|
+
break;
|
|
152
|
+
case "space":
|
|
153
|
+
case "def":
|
|
154
|
+
break;
|
|
155
|
+
default:
|
|
156
|
+
if ("tokens" in token && token.tokens) lines += renderedLines(token.tokens);else if ("text" in token) lines += wrappedLines(token.text);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return lines;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/** Messages link only to absolute URLs; a relative one would lead somewhere else on every page that shows it. */
|
|
163
|
+
const isAbsoluteUrl = href => {
|
|
164
|
+
try {
|
|
165
|
+
return new URL(href).protocol !== "";
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const dateTimeOf = value => {
|
|
171
|
+
if (value === undefined) return undefined;
|
|
172
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
173
|
+
return Number.isFinite(date.getTime()) ? date.toISOString() : undefined;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* An attachment URL the row may load or open: absolute with an allowed scheme, or relative to the page. A picture may
|
|
178
|
+
* also be a `blob:` URL or a `data:image/` URL, which an image element shows without running anything.
|
|
179
|
+
*/
|
|
180
|
+
const attachmentUrl = (url, picture = false) => {
|
|
181
|
+
if (!url) return undefined;
|
|
182
|
+
if (picture && /^data:image\//i.test(url)) return url;
|
|
183
|
+
try {
|
|
184
|
+
return (picture ? PICTURE_PROTOCOLS : ATTACHMENT_PROTOCOLS).has(new URL(url, "https://relative.invalid/").protocol) ? url : undefined;
|
|
185
|
+
} catch {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/** The aspect ratio a single image or video reserves, from its stored size. */
|
|
191
|
+
const mediaRatio = media => {
|
|
192
|
+
const ratio = media.width > 0 && media.height > 0 ? media.width / media.height : 4 / 3;
|
|
193
|
+
return Math.min(MEDIA_RATIO_MAX, Math.max(MEDIA_RATIO_MIN, ratio));
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/** The width of a single image or video: its stored width, at most the row's media height tall, never wider than the row. */
|
|
197
|
+
const mediaWidth = media => {
|
|
198
|
+
const natural = Number.isFinite(media.width) && media.width > 0 ? `${media.width}px, ` : "";
|
|
199
|
+
return `min(100%, ${natural}calc(var(--k2b-message-media-height) * ${mediaRatio(media)}))`;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/** Only the `ti-*` glyph of `fileIcons`, which may add colour utilities that `@k2b/ui` does not ship. */
|
|
203
|
+
const fileIcon = file => file.icon ?? `ti ${fileIcons.getFileIcon({
|
|
204
|
+
name: file.name,
|
|
205
|
+
type: "file",
|
|
206
|
+
mimeType: file.mediaType
|
|
207
|
+
}).split(/\s+/).filter(token => token.startsWith("ti-")).join(" ")}`;
|
|
208
|
+
|
|
209
|
+
/** A link, a button, or neither, depending on what the caller passes. */
|
|
210
|
+
function AttachmentTarget(props) {
|
|
211
|
+
return _$createComponent(Show, {
|
|
212
|
+
get when() {
|
|
213
|
+
return props.onOpen;
|
|
214
|
+
},
|
|
215
|
+
get fallback() {
|
|
216
|
+
return _$createComponent(Show, {
|
|
217
|
+
get when() {
|
|
218
|
+
return attachmentUrl(props.href);
|
|
219
|
+
},
|
|
220
|
+
get fallback() {
|
|
221
|
+
return _$createComponent(Show, {
|
|
222
|
+
get when() {
|
|
223
|
+
return props.label;
|
|
224
|
+
},
|
|
225
|
+
get fallback() {
|
|
226
|
+
return _$ssr(_tmpl$, _$ssrAttribute("class", _$escape(props.class, true), false), _$ssrStyle(props.style), _$escape(props.children));
|
|
227
|
+
},
|
|
228
|
+
children: label => _$ssr(_tmpl$2, _$ssrAttribute("class", _$escape(props.class, true), false), _$ssrStyle(props.style), _$ssrAttribute("aria-label", _$escape(label(), true), false), _$escape(props.children))
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
children: href => _$ssr(_tmpl$3, _$ssrAttribute("class", _$escape(props.class, true), false), _$ssrStyle(props.style), _$ssrAttribute("href", _$escape(href(), true), false), _$ssrAttribute("aria-label", _$escape(props.label, true), false), _$escape(props.children))
|
|
232
|
+
});
|
|
233
|
+
},
|
|
234
|
+
children: open => _$ssr(_tmpl$4, _$ssrAttribute("class", _$escape(props.class, true), false), _$ssrStyle(props.style), _$ssrAttribute("aria-label", _$escape(props.label, true), false), _$escape(props.children))
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Renders the safe Markdown of `MarkdownView` with line breaks kept, absolute links only, and a copy control above
|
|
240
|
+
* every code block, and counts the fewest lines it takes. An edited message that does not collapse ends with the marker.
|
|
241
|
+
*/
|
|
242
|
+
const renderMessage = (text, locale, messages, edited) => {
|
|
243
|
+
const renderer = createSafeRenderer({
|
|
244
|
+
allowImages: false,
|
|
245
|
+
linkProtocols: LINK_PROTOCOLS,
|
|
246
|
+
linkTarget: "_blank",
|
|
247
|
+
locale
|
|
248
|
+
});
|
|
249
|
+
const renderLink = renderer.link.bind(renderer);
|
|
250
|
+
renderer.link = token => isAbsoluteUrl(token.href) ? renderLink(token) : renderer.parser.parseInline(token.tokens);
|
|
251
|
+
const renderCode = renderer.code.bind(renderer);
|
|
252
|
+
renderer.code = token => {
|
|
253
|
+
const language = token.lang?.match(/^\S{1,24}/)?.[0];
|
|
254
|
+
const block = renderCode(token).replace(/^<pre>/, '<pre tabindex="0">');
|
|
255
|
+
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>`;
|
|
256
|
+
};
|
|
257
|
+
const marked = createSafeMarked(locale).setOptions({
|
|
258
|
+
breaks: true,
|
|
259
|
+
renderer
|
|
260
|
+
});
|
|
261
|
+
const tokens = marked.lexer(text);
|
|
262
|
+
const html = marked.parser(tokens);
|
|
263
|
+
const lines = renderedLines(tokens);
|
|
264
|
+
// A collapsed message would hide the end of its text, so its marker sits next to "Show more" instead.
|
|
265
|
+
if (!edited || lines > COLLAPSE_LINES) return {
|
|
266
|
+
html,
|
|
267
|
+
lines
|
|
268
|
+
};
|
|
269
|
+
// The marker ends the last paragraph, so it takes no line of its own there.
|
|
270
|
+
const marker = `<span class="k2b-message-row__edited">${escapeHtml(messages.messageEdited)}</span>`;
|
|
271
|
+
const end = html.trimEnd();
|
|
272
|
+
return {
|
|
273
|
+
html: end.endsWith("</p>") ? `${end.slice(0, -4)} ${marker}</p>` : `${html}<p>${marker}</p>`,
|
|
274
|
+
lines
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* One message in a conversation with many people: own messages on the right, others on the left with avatar, name,
|
|
280
|
+
* and time at the start of a group. Every part has its final height when the row mounts, so a virtualized list such
|
|
281
|
+
* as `VirtualFeed` keeps its positions.
|
|
282
|
+
*/
|
|
283
|
+
export function MessageRow(props) {
|
|
284
|
+
const messages = useUiMessages();
|
|
285
|
+
const locale = useLocale();
|
|
286
|
+
const textId = `k2b-message-${createUniqueId()}`;
|
|
287
|
+
const [expanded, setExpanded] = createSignal(false);
|
|
288
|
+
const rendered = createMemo(() => renderMessage(props.text, locale(), messages(), Boolean(props.edited)));
|
|
289
|
+
const collapsible = () => rendered().lines > COLLAPSE_LINES;
|
|
290
|
+
const collapsed = () => collapsible() && !expanded();
|
|
291
|
+
const groupStart = () => props.groupStart ?? true;
|
|
292
|
+
const hasLine = () => props.progress !== undefined || props.status !== undefined || props.receipt !== undefined;
|
|
293
|
+
const reactions = () => props.deleted ? undefined : props.reactions;
|
|
294
|
+
// Chips follow their keys, so a chip keeps its element, and focus, when the caller passes new objects for it.
|
|
295
|
+
const reactionKeys = createMemo(() => reactions()?.map(reaction => reaction.key) ?? []);
|
|
296
|
+
const reactionByKey = createMemo(() => new Map(reactions()?.map(reaction => [reaction.key, reaction])));
|
|
297
|
+
const hasFooter = () => hasLine() || reactions() !== undefined;
|
|
298
|
+
const writing = () => props.progress !== undefined && !props.deleted && props.text.trim() === "";
|
|
299
|
+
const hasBubble = () => props.deleted || writing() || props.text.trim() !== "";
|
|
300
|
+
const content = () => !props.deleted;
|
|
301
|
+
const media = createMemo(() => content() ? (props.attachments ?? []).filter(attachment => attachment.kind !== "file") : []);
|
|
302
|
+
const files = createMemo(() => content() ? (props.attachments ?? []).filter(attachment => attachment.kind === "file") : []);
|
|
303
|
+
const shownMedia = () => media().slice(0, MEDIA_SHOWN);
|
|
304
|
+
const hiddenMedia = () => media().length - shownMedia().length;
|
|
305
|
+
let text;
|
|
306
|
+
let copiedTimer;
|
|
307
|
+
onCleanup(() => clearTimeout(copiedTimer));
|
|
308
|
+
const copyCode = event => {
|
|
309
|
+
const button = event.target?.closest?.(".k2b-message-row__copy");
|
|
310
|
+
if (!button || !text?.contains(button)) return;
|
|
311
|
+
const code = button.closest(".k2b-message-row__code")?.querySelector("pre")?.textContent ?? "";
|
|
312
|
+
copyText(code).then(() => {
|
|
313
|
+
for (const other of text?.querySelectorAll(".k2b-message-row__copy[data-copied]") ?? []) delete other.dataset.copied;
|
|
314
|
+
button.dataset.copied = "";
|
|
315
|
+
announce(messages().copied);
|
|
316
|
+
clearTimeout(copiedTimer);
|
|
317
|
+
copiedTimer = setTimeout(() => delete button.dataset.copied, COPIED_MS);
|
|
318
|
+
}, () => {});
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// A keyboard user who reaches a link or code block in the hidden or faded part of a collapsed message gets the whole
|
|
322
|
+
// message. The collapsed text clips instead of scrolling, so the browser cannot scroll it to the target instead.
|
|
323
|
+
const revealFocus = event => {
|
|
324
|
+
if (!text || !collapsed() || !(event.target instanceof Element)) return;
|
|
325
|
+
const fade = text.getBoundingClientRect().bottom - FADE_LINES * Number.parseFloat(getComputedStyle(text).lineHeight);
|
|
326
|
+
if (event.target.getBoundingClientRect().bottom > fade) setExpanded(true);
|
|
327
|
+
};
|
|
328
|
+
const header = () => [_$ssr(_tmpl$5, _$escape(props.author.name)), _$createComponent(Show, {
|
|
329
|
+
get when() {
|
|
330
|
+
return props.badge;
|
|
331
|
+
},
|
|
332
|
+
get children() {
|
|
333
|
+
return _$ssr(_tmpl$6, _$escape(props.badge));
|
|
334
|
+
}
|
|
335
|
+
}), _$ssr(_tmpl$7, _$ssrAttribute("datetime", _$escape(dateTimeOf(props.dateTime), true), false), _$escape(props.time))];
|
|
336
|
+
const quote = value => [_$ssr(_tmpl$8), _$ssr(_tmpl$9, _$escape(messages().messageReplyTo), _$escape(value.author), _$escape(value.text))];
|
|
337
|
+
|
|
338
|
+
/** The picture's description, then that it is a video and how long, then how many more the grid hides. */
|
|
339
|
+
const mediaLabel = (item, more) => [item.alt, item.kind === "video" ? [messages().messageVideo, item.duration].filter(Boolean).join(" ") : "", more > 0 ? messages().messageMoreAttachments({
|
|
340
|
+
count: more
|
|
341
|
+
}) : ""].filter(Boolean).join(", ");
|
|
342
|
+
|
|
343
|
+
/** A toggle button, or with no `onToggleReaction` a labelled image that also says whether the reader reacted. */
|
|
344
|
+
const reactionChip = (key, reaction) => {
|
|
345
|
+
const label = own => messages().messageReaction({
|
|
346
|
+
emoji: reaction().emoji,
|
|
347
|
+
count: reaction().count,
|
|
348
|
+
names: reaction().label,
|
|
349
|
+
own
|
|
350
|
+
});
|
|
351
|
+
return _$createComponent(Show, {
|
|
352
|
+
get when() {
|
|
353
|
+
return props.onToggleReaction;
|
|
354
|
+
},
|
|
355
|
+
get fallback() {
|
|
356
|
+
return _$ssr(_tmpl$0, _$ssrAttribute("aria-label", _$escape(label(Boolean(reaction().own)), true), false) + _$ssrAttribute("title", _$escape(reaction().label, true), false) + _$ssrAttribute("data-own", reaction().own ? "" : _$escape(undefined, true), false), _$escape(reaction().emoji), _$escape(reaction().count));
|
|
357
|
+
},
|
|
358
|
+
children: toggle => _$ssr(_tmpl$1, _$ssrAttribute("aria-pressed", reaction().own ? "true" : "false", false) + _$ssrAttribute("aria-label", _$escape(label(false), true), false) + _$ssrAttribute("title", _$escape(reaction().label, true), false) + _$ssrAttribute("data-own", reaction().own ? "" : _$escape(undefined, true), false), _$escape(reaction().emoji), _$escape(reaction().count))
|
|
359
|
+
});
|
|
360
|
+
};
|
|
361
|
+
const mediaItem = (item, index) => {
|
|
362
|
+
const more = () => index() === shownMedia().length - 1 ? hiddenMedia() : 0;
|
|
363
|
+
return _$createComponent(AttachmentTarget, {
|
|
364
|
+
"class": "k2b-message-row__media-item",
|
|
365
|
+
get style() {
|
|
366
|
+
return _$memo(() => media().length === 1)() ? {
|
|
367
|
+
"aspect-ratio": String(mediaRatio(item))
|
|
368
|
+
} : undefined;
|
|
369
|
+
},
|
|
370
|
+
get label() {
|
|
371
|
+
return mediaLabel(item, more());
|
|
372
|
+
},
|
|
373
|
+
get onOpen() {
|
|
374
|
+
return item.onOpen;
|
|
375
|
+
},
|
|
376
|
+
get href() {
|
|
377
|
+
return item.href;
|
|
378
|
+
},
|
|
379
|
+
get children() {
|
|
380
|
+
return [_$ssr(_tmpl$10, _$ssrAttribute("src", _$escape(attachmentUrl(item.src, true), true), false), _$ssrAttribute("width", item.width > 0 ? _$escape(item.width, true) : _$escape(undefined, true), false) + _$ssrAttribute("height", item.height > 0 ? _$escape(item.height, true) : _$escape(undefined, true), false)), _$createComponent(Show, {
|
|
381
|
+
get when() {
|
|
382
|
+
return item.kind === "video";
|
|
383
|
+
},
|
|
384
|
+
get children() {
|
|
385
|
+
return [_$ssr(_tmpl$11), _$createComponent(Show, {
|
|
386
|
+
get when() {
|
|
387
|
+
return item.duration;
|
|
388
|
+
},
|
|
389
|
+
get children() {
|
|
390
|
+
return _$ssr(_tmpl$12, _$escape(item.duration));
|
|
391
|
+
}
|
|
392
|
+
})];
|
|
393
|
+
}
|
|
394
|
+
}), _$createComponent(Show, {
|
|
395
|
+
get when() {
|
|
396
|
+
return more() > 0;
|
|
397
|
+
},
|
|
398
|
+
get children() {
|
|
399
|
+
return _$ssr(_tmpl$13, _$escape(more()));
|
|
400
|
+
}
|
|
401
|
+
})];
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
};
|
|
405
|
+
return _$ssr(_tmpl$29, _$ssrAttribute("class", props.class ? `k2b-message-row ${_$escape(props.class, true)}` : "k2b-message-row", false) + _$ssrAttribute("data-own", props.own ? "" : _$escape(undefined, true), false) + _$ssrAttribute("data-group-start", groupStart() ? "" : _$escape(undefined, true), false) + _$ssrAttribute("data-status", _$escape(props.status, true), false) + _$ssrAttribute("data-media", media().length > 0 ? "" : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
|
|
406
|
+
get when() {
|
|
407
|
+
return !props.own;
|
|
408
|
+
},
|
|
409
|
+
get children() {
|
|
410
|
+
return _$ssr(_tmpl$15, _$escape(_$createComponent(Show, {
|
|
411
|
+
get when() {
|
|
412
|
+
return groupStart();
|
|
413
|
+
},
|
|
414
|
+
get children() {
|
|
415
|
+
return _$ssr(_tmpl$14, _$escape(_$createComponent(Avatar, {
|
|
416
|
+
get name() {
|
|
417
|
+
return props.author.name;
|
|
418
|
+
},
|
|
419
|
+
get src() {
|
|
420
|
+
return props.author.avatar;
|
|
421
|
+
},
|
|
422
|
+
get icon() {
|
|
423
|
+
return props.author.icon;
|
|
424
|
+
},
|
|
425
|
+
size: "sm"
|
|
426
|
+
})));
|
|
427
|
+
}
|
|
428
|
+
})));
|
|
429
|
+
}
|
|
430
|
+
})), _$escape(_$createComponent(Show, {
|
|
431
|
+
get when() {
|
|
432
|
+
return groupStart();
|
|
433
|
+
},
|
|
434
|
+
get fallback() {
|
|
435
|
+
return _$ssr(_tmpl$30, _$escape(props.author.name), _$escape(props.time));
|
|
436
|
+
},
|
|
437
|
+
get children() {
|
|
438
|
+
return _$createComponent(Show, {
|
|
439
|
+
get when() {
|
|
440
|
+
return !props.own;
|
|
441
|
+
},
|
|
442
|
+
get fallback() {
|
|
443
|
+
return _$ssr(_tmpl$31, _$escape(props.author.name), _$ssrAttribute("datetime", _$escape(dateTimeOf(props.dateTime), true), false), _$escape(props.time));
|
|
444
|
+
},
|
|
445
|
+
get children() {
|
|
446
|
+
return _$ssr(_tmpl$16, _$escape(header()));
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
})), _$escape(_$createComponent(Show, {
|
|
451
|
+
get when() {
|
|
452
|
+
return _$memo(() => !!content())() && props.forwarded;
|
|
453
|
+
},
|
|
454
|
+
get children() {
|
|
455
|
+
return _$ssr(_tmpl$17, _$escape(messages().messageForwarded));
|
|
456
|
+
}
|
|
457
|
+
})), _$escape(_$createComponent(Show, {
|
|
458
|
+
get when() {
|
|
459
|
+
return _$memo(() => !!content())() && props.quote;
|
|
460
|
+
},
|
|
461
|
+
children: value => _$createComponent(Show, {
|
|
462
|
+
get when() {
|
|
463
|
+
return value().onSelect;
|
|
464
|
+
},
|
|
465
|
+
get fallback() {
|
|
466
|
+
return _$ssr(_tmpl$32, _$escape(quote(value())));
|
|
467
|
+
},
|
|
468
|
+
children: select => _$ssr(_tmpl$33, _$escape(quote(value())))
|
|
469
|
+
})
|
|
470
|
+
})), _$escape(_$createComponent(Show, {
|
|
471
|
+
get when() {
|
|
472
|
+
return hasBubble();
|
|
473
|
+
},
|
|
474
|
+
get children() {
|
|
475
|
+
return _$ssr(_tmpl$21, _$ssrAttribute("data-deleted", props.deleted ? "" : _$escape(undefined, true), false) + _$ssrAttribute("aria-busy", props.progress && !props.deleted ? "true" : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
|
|
476
|
+
get when() {
|
|
477
|
+
return !props.deleted;
|
|
478
|
+
},
|
|
479
|
+
get fallback() {
|
|
480
|
+
return _$ssr(_tmpl$34, _$escape(messages().messageDeleted));
|
|
481
|
+
},
|
|
482
|
+
get children() {
|
|
483
|
+
return _$createComponent(Show, {
|
|
484
|
+
get when() {
|
|
485
|
+
return !writing();
|
|
486
|
+
},
|
|
487
|
+
get fallback() {
|
|
488
|
+
return _$ssr(_tmpl$35);
|
|
489
|
+
},
|
|
490
|
+
get children() {
|
|
491
|
+
return [_$ssr(_tmpl$18, _$ssrAttribute("id", _$escape(textId, true), false), _$ssrAttribute("data-collapsed", collapsed() ? "" : _$escape(undefined, true), false), rendered().html), _$createComponent(Show, {
|
|
492
|
+
get when() {
|
|
493
|
+
return collapsible();
|
|
494
|
+
},
|
|
495
|
+
get children() {
|
|
496
|
+
return _$ssr(_tmpl$20, _$escape(_$createComponent(Button, {
|
|
497
|
+
variant: "text",
|
|
498
|
+
size: "xs",
|
|
499
|
+
"class": "k2b-message-row__more",
|
|
500
|
+
get ["aria-expanded"]() {
|
|
501
|
+
return expanded() ? "true" : "false";
|
|
502
|
+
},
|
|
503
|
+
"aria-controls": textId,
|
|
504
|
+
onClick: () => setExpanded(value => !value),
|
|
505
|
+
get children() {
|
|
506
|
+
return expanded() ? messages().messageShowLess : messages().messageShowMore;
|
|
507
|
+
}
|
|
508
|
+
})), _$escape(_$createComponent(Show, {
|
|
509
|
+
get when() {
|
|
510
|
+
return props.edited;
|
|
511
|
+
},
|
|
512
|
+
get children() {
|
|
513
|
+
return _$ssr(_tmpl$19, _$escape(messages().messageEdited));
|
|
514
|
+
}
|
|
515
|
+
})));
|
|
516
|
+
}
|
|
517
|
+
})];
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
})));
|
|
522
|
+
}
|
|
523
|
+
})), _$escape(_$createComponent(Show, {
|
|
524
|
+
get when() {
|
|
525
|
+
return _$memo(() => !!(content() && props.edited))() && !hasBubble();
|
|
526
|
+
},
|
|
527
|
+
get children() {
|
|
528
|
+
return _$ssr(_tmpl$22, _$escape(messages().messageEdited));
|
|
529
|
+
}
|
|
530
|
+
})), _$escape(_$createComponent(Show, {
|
|
531
|
+
get when() {
|
|
532
|
+
return media().length > 0;
|
|
533
|
+
},
|
|
534
|
+
get children() {
|
|
535
|
+
return _$ssr(_tmpl$23, _$ssrAttribute("aria-label", _$escape(messages().attachments, true), false) + _$ssrAttribute("data-count", _$escape(Math.min(media().length, MEDIA_SHOWN), true), false), _$ssrStyle(media().length === 1 ? {
|
|
536
|
+
width: mediaWidth(media()[0])
|
|
537
|
+
} : undefined), _$escape(_$createComponent(For, {
|
|
538
|
+
get each() {
|
|
539
|
+
return shownMedia();
|
|
540
|
+
},
|
|
541
|
+
children: (item, index) => mediaItem(item, index)
|
|
542
|
+
})));
|
|
543
|
+
}
|
|
544
|
+
})), _$escape(_$createComponent(Show, {
|
|
545
|
+
get when() {
|
|
546
|
+
return files().length > 0;
|
|
547
|
+
},
|
|
548
|
+
get children() {
|
|
549
|
+
return _$ssr(_tmpl$24, _$escape(_$createComponent(For, {
|
|
550
|
+
get each() {
|
|
551
|
+
return files();
|
|
552
|
+
},
|
|
553
|
+
children: file => _$createComponent(AttachmentTarget, {
|
|
554
|
+
"class": "k2b-message-row__file",
|
|
555
|
+
get onOpen() {
|
|
556
|
+
return file.onOpen;
|
|
557
|
+
},
|
|
558
|
+
get href() {
|
|
559
|
+
return file.href;
|
|
560
|
+
},
|
|
561
|
+
get children() {
|
|
562
|
+
return [_$ssr(_tmpl$36, _$ssrAttribute("class", _$escape(fileIcon(file), true), false)), _$ssr(_tmpl$38, _$escape(file.name), _$escape(_$createComponent(Show, {
|
|
563
|
+
get when() {
|
|
564
|
+
return file.detail;
|
|
565
|
+
},
|
|
566
|
+
get children() {
|
|
567
|
+
return _$ssr(_tmpl$37, _$escape(file.detail));
|
|
568
|
+
}
|
|
569
|
+
})))];
|
|
570
|
+
}
|
|
571
|
+
})
|
|
572
|
+
})));
|
|
573
|
+
}
|
|
574
|
+
})), _$escape(_$createComponent(Show, {
|
|
575
|
+
get when() {
|
|
576
|
+
return _$memo(() => !!content())() && props.linkPreview;
|
|
577
|
+
},
|
|
578
|
+
get children() {
|
|
579
|
+
return _$ssr(_tmpl$25, _$escape(props.linkPreview));
|
|
580
|
+
}
|
|
581
|
+
})), _$escape(_$createComponent(Show, {
|
|
582
|
+
get when() {
|
|
583
|
+
return _$memo(() => !!content())() && props.card;
|
|
584
|
+
},
|
|
585
|
+
get children() {
|
|
586
|
+
return _$ssr(_tmpl$25, _$escape(props.card));
|
|
587
|
+
}
|
|
588
|
+
})), _$escape(_$createComponent(Show, {
|
|
589
|
+
get when() {
|
|
590
|
+
return props.thread;
|
|
591
|
+
},
|
|
592
|
+
children: thread => _$ssr(_tmpl$40, _$escape(_$createComponent(Show, {
|
|
593
|
+
get when() {
|
|
594
|
+
return (thread().participants?.length ?? 0) > 0;
|
|
595
|
+
},
|
|
596
|
+
get children() {
|
|
597
|
+
return _$ssr(_tmpl$39, _$escape(_$createComponent(For, {
|
|
598
|
+
get each() {
|
|
599
|
+
return thread().participants?.slice(0, 3);
|
|
600
|
+
},
|
|
601
|
+
children: person => _$createComponent(Avatar, {
|
|
602
|
+
get name() {
|
|
603
|
+
return person.name;
|
|
604
|
+
},
|
|
605
|
+
get src() {
|
|
606
|
+
return person.avatar;
|
|
607
|
+
},
|
|
608
|
+
get icon() {
|
|
609
|
+
return person.icon;
|
|
610
|
+
},
|
|
611
|
+
size: "xs"
|
|
612
|
+
})
|
|
613
|
+
})));
|
|
614
|
+
}
|
|
615
|
+
})), _$escape(messages().messageReplies({
|
|
616
|
+
count: thread().count
|
|
617
|
+
})), _$escape(_$createComponent(Show, {
|
|
618
|
+
get when() {
|
|
619
|
+
return thread().lastReply;
|
|
620
|
+
},
|
|
621
|
+
children: time => _$ssr(_tmpl$41, _$ssrAttribute("datetime", _$escape(dateTimeOf(thread().lastReplyDateTime), true), false), _$escape(messages().messageLastReply({
|
|
622
|
+
time: time()
|
|
623
|
+
})))
|
|
624
|
+
})))
|
|
625
|
+
})), _$escape(_$createComponent(Show, {
|
|
626
|
+
get when() {
|
|
627
|
+
return hasFooter();
|
|
628
|
+
},
|
|
629
|
+
get children() {
|
|
630
|
+
return _$ssr(_tmpl$27, _$escape(_$createComponent(Show, {
|
|
631
|
+
get when() {
|
|
632
|
+
return reactions();
|
|
633
|
+
},
|
|
634
|
+
children: list => _$ssr(_tmpl$42, _$ssrAttribute("aria-label", _$escape(messages().messageReactions, true), false) + _$ssrAttribute("data-empty", list().length === 0 ? "" : _$escape(undefined, true), false), _$escape(_$createComponent(Show, {
|
|
635
|
+
get when() {
|
|
636
|
+
return list().length > 0;
|
|
637
|
+
},
|
|
638
|
+
get children() {
|
|
639
|
+
return _$createComponent(ScrollArea, {
|
|
640
|
+
orientation: "horizontal",
|
|
641
|
+
"class": "k2b-message-row__reaction-list",
|
|
642
|
+
get tabindex() {
|
|
643
|
+
return props.onToggleReaction ? undefined : 0;
|
|
644
|
+
},
|
|
645
|
+
get children() {
|
|
646
|
+
return _$createComponent(For, {
|
|
647
|
+
get each() {
|
|
648
|
+
return reactionKeys();
|
|
649
|
+
},
|
|
650
|
+
children: key => _$createComponent(Show, {
|
|
651
|
+
get when() {
|
|
652
|
+
return reactionByKey().get(key);
|
|
653
|
+
},
|
|
654
|
+
children: reaction => reactionChip(key, reaction)
|
|
655
|
+
})
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
})), _$escape(_$createComponent(Show, {
|
|
661
|
+
get when() {
|
|
662
|
+
return props.onAddReaction;
|
|
663
|
+
},
|
|
664
|
+
children: add => _$createComponent(IconButton, {
|
|
665
|
+
get label() {
|
|
666
|
+
return messages().messageAddReaction;
|
|
667
|
+
},
|
|
668
|
+
size: "xs",
|
|
669
|
+
"class": "k2b-message-row__react",
|
|
670
|
+
onClick: event => add()(event.currentTarget),
|
|
671
|
+
get children() {
|
|
672
|
+
return _$ssr(_tmpl$43);
|
|
673
|
+
}
|
|
674
|
+
})
|
|
675
|
+
})))
|
|
676
|
+
})), _$escape(_$createComponent(Show, {
|
|
677
|
+
get when() {
|
|
678
|
+
return hasLine();
|
|
679
|
+
},
|
|
680
|
+
get children() {
|
|
681
|
+
return _$ssr(_tmpl$26, _$escape(_$createComponent(Show, {
|
|
682
|
+
get when() {
|
|
683
|
+
return props.progress;
|
|
684
|
+
},
|
|
685
|
+
get fallback() {
|
|
686
|
+
return [_$createComponent(Show, {
|
|
687
|
+
get when() {
|
|
688
|
+
return props.status === "pending";
|
|
689
|
+
},
|
|
690
|
+
get children() {
|
|
691
|
+
return [_$ssr(_tmpl$44), _$ssr(_tmpl$45, _$escape(messages().sending))];
|
|
692
|
+
}
|
|
693
|
+
}), _$createComponent(Show, {
|
|
694
|
+
get when() {
|
|
695
|
+
return props.status === "failed";
|
|
696
|
+
},
|
|
697
|
+
get children() {
|
|
698
|
+
return [_$ssr(_tmpl$46), _$ssr(_tmpl$45, _$escape(messages().messageNotSent)), _$createComponent(Show, {
|
|
699
|
+
get when() {
|
|
700
|
+
return props.onRetry;
|
|
701
|
+
},
|
|
702
|
+
children: retry => _$createComponent(Button, {
|
|
703
|
+
variant: "text",
|
|
704
|
+
size: "xs",
|
|
705
|
+
onClick: () => retry()(),
|
|
706
|
+
get children() {
|
|
707
|
+
return messages().messageRetry;
|
|
708
|
+
}
|
|
709
|
+
})
|
|
710
|
+
})];
|
|
711
|
+
}
|
|
712
|
+
}), _$createComponent(Show, {
|
|
713
|
+
get when() {
|
|
714
|
+
return _$memo(() => props.status !== "pending")() && props.status !== "failed";
|
|
715
|
+
},
|
|
716
|
+
get children() {
|
|
717
|
+
return _$createComponent(Show, {
|
|
718
|
+
get when() {
|
|
719
|
+
return props.receipt;
|
|
720
|
+
},
|
|
721
|
+
get fallback() {
|
|
722
|
+
return _$createComponent(Show, {
|
|
723
|
+
get when() {
|
|
724
|
+
return props.status === "sent";
|
|
725
|
+
},
|
|
726
|
+
get children() {
|
|
727
|
+
return [_$ssr(_tmpl$47), _$ssr(_tmpl$48, _$escape(messages().messageSent))];
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
},
|
|
731
|
+
children: receipt => [_$ssr(_tmpl$49), _$ssr(_tmpl$50, _$escape(receipt()))]
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
})];
|
|
735
|
+
},
|
|
736
|
+
children: progress => [_$ssr(_tmpl$51), _$ssr(_tmpl$52, _$escape(progress().status)), _$createComponent(Show, {
|
|
737
|
+
get when() {
|
|
738
|
+
return progress().onStop;
|
|
739
|
+
},
|
|
740
|
+
children: stop => _$createComponent(Button, {
|
|
741
|
+
variant: "text",
|
|
742
|
+
size: "xs",
|
|
743
|
+
onClick: () => stop()(),
|
|
744
|
+
get children() {
|
|
745
|
+
return messages().messageStop;
|
|
746
|
+
}
|
|
747
|
+
})
|
|
748
|
+
})]
|
|
749
|
+
})));
|
|
750
|
+
}
|
|
751
|
+
})));
|
|
752
|
+
}
|
|
753
|
+
})), _$escape(_$createComponent(Show, {
|
|
754
|
+
get when() {
|
|
755
|
+
return (props.actions?.length ?? 0) > 0;
|
|
756
|
+
},
|
|
757
|
+
get children() {
|
|
758
|
+
return _$ssr(_tmpl$28, _$ssrAttribute("aria-label", _$escape(messages().messageActions, true), false), _$escape(_$createComponent(For, {
|
|
759
|
+
get each() {
|
|
760
|
+
return props.actions;
|
|
761
|
+
},
|
|
762
|
+
children: action => _$createComponent(IconButton, {
|
|
763
|
+
get label() {
|
|
764
|
+
return action.label;
|
|
765
|
+
},
|
|
766
|
+
size: "sm",
|
|
767
|
+
get disabled() {
|
|
768
|
+
return action.disabled;
|
|
769
|
+
},
|
|
770
|
+
onClick: () => action.onSelect(),
|
|
771
|
+
get children() {
|
|
772
|
+
return _$ssr(_tmpl$53, _$ssrAttribute("class", _$escape(action.icon, true), false));
|
|
773
|
+
}
|
|
774
|
+
})
|
|
775
|
+
})));
|
|
776
|
+
}
|
|
777
|
+
})));
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/** A quiet, centered line for something that happened in the conversation, such as a person joining. */
|
|
781
|
+
export function MessageSystemRow(props) {
|
|
782
|
+
return _$ssr(_tmpl$55, _$ssrAttribute("class", props.class ? `k2b-message-system-row ${_$escape(props.class, true)}` : "k2b-message-system-row", false), _$escape(_$createComponent(Show, {
|
|
783
|
+
get when() {
|
|
784
|
+
return props.icon;
|
|
785
|
+
},
|
|
786
|
+
children: icon => _$ssr(_tmpl$53, _$ssrAttribute("class", _$escape(icon(), true), false))
|
|
787
|
+
})), _$escape(props.children), _$escape(_$createComponent(Show, {
|
|
788
|
+
get when() {
|
|
789
|
+
return props.time;
|
|
790
|
+
},
|
|
791
|
+
get children() {
|
|
792
|
+
return _$ssr(_tmpl$54, _$ssrAttribute("datetime", _$escape(dateTimeOf(props.dateTime), true), false), _$escape(props.time));
|
|
793
|
+
}
|
|
794
|
+
})));
|
|
795
|
+
}
|
|
796
|
+
//# sourceMappingURL=MessageRow.js.map
|