@dxos/react-ui-thread 0.12.0-next-2800d0376abcc62d5544afdd744b16b2b960434e → 0.13.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/dist/lib/index.mjs +50 -45
- package/dist/lib/index.mjs.map +1 -1
- package/dist/lib/translations.mjs.map +1 -1
- package/dist/types/src/Message/Message.d.ts +5 -4
- package/dist/types/src/Message/Message.d.ts.map +1 -1
- package/dist/types/src/Message/index.d.ts +2 -2
- package/dist/types/src/Message/index.d.ts.map +1 -1
- package/dist/types/src/Thread/Thread.d.ts +5 -4
- package/dist/types/src/Thread/Thread.d.ts.map +1 -1
- package/dist/types/src/Thread/index.d.ts +2 -2
- package/dist/types/src/Thread/index.d.ts.map +1 -1
- package/dist/types/src/context.d.ts +4 -4
- package/dist/types/src/context.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +7 -7
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/testing.d.ts +1 -1
- package/dist/types/src/testing.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +25 -20
- package/src/Message/Message.stories.tsx +3 -3
- package/src/Message/Message.tsx +44 -52
- package/src/Message/index.ts +2 -2
- package/src/Thread/Thread.stories.tsx +5 -5
- package/src/Thread/Thread.tsx +30 -37
- package/src/Thread/index.ts +2 -2
- package/src/command.ts +3 -3
- package/src/context.ts +2 -2
- package/src/index.ts +7 -7
- package/src/testing.ts +1 -1
- package/src/translations.ts +2 -2
package/dist/lib/index.mjs
CHANGED
|
@@ -2,15 +2,20 @@ import { format } from "date-fns/format";
|
|
|
2
2
|
import { startOfDay } from "date-fns/startOfDay";
|
|
3
3
|
import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { Obj, Ref } from "@dxos/echo";
|
|
5
|
-
import { Avatar, Icon, IconButton, ScrollArea, composable, composableProps, useOnTransition, useThemeContext, useTranslation } from "@dxos/react-ui";
|
|
6
5
|
import { Mosaic } from "@dxos/react-ui-mosaic";
|
|
6
|
+
import * as Button from "@dxos/react-ui/Button";
|
|
7
|
+
import * as Hooks from "@dxos/react-ui/Hooks";
|
|
8
|
+
import * as Icon from "@dxos/react-ui/Icon";
|
|
9
|
+
import * as ScrollArea from "@dxos/react-ui/ScrollArea";
|
|
10
|
+
import * as Util from "@dxos/react-ui/Util";
|
|
7
11
|
import { createBasicExtensions, createThemeExtensions, keymap, listener, tags } from "@dxos/ui-editor";
|
|
8
12
|
import { hoverableControlItem, hoverableControls, hoverableFocusedWithinControls, mx } from "@dxos/ui-theme";
|
|
9
13
|
import { translationKey } from "#translations";
|
|
10
14
|
import { HighlightStyle, StreamLanguage, syntaxHighlighting } from "@codemirror/language";
|
|
11
15
|
import { EditorView } from "@codemirror/view";
|
|
12
|
-
import { createContext } from "@
|
|
16
|
+
import { createContext } from "@dxos/react-hooks";
|
|
13
17
|
import { useTextEditor } from "@dxos/react-ui-editor";
|
|
18
|
+
import * as Avatar from "@dxos/react-ui/Avatar";
|
|
14
19
|
import { hexToEmoji, hexToHue, isTruthy } from "@dxos/util";
|
|
15
20
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
16
21
|
//#region src/command.ts
|
|
@@ -32,10 +37,10 @@ var parser = StreamLanguage.define({
|
|
|
32
37
|
});
|
|
33
38
|
var styles = HighlightStyle.define([{
|
|
34
39
|
tag: tags.tagName,
|
|
35
|
-
class: mx("dx-tag dx-tag
|
|
40
|
+
class: mx("dx-tag dx-tag-inline dx-tag-indigo mx-0.5")
|
|
36
41
|
}, {
|
|
37
42
|
tag: tags.labelName,
|
|
38
|
-
class: mx("dx-tag dx-tag
|
|
43
|
+
class: mx("dx-tag dx-tag-inline dx-tag-blue mx-0.5")
|
|
39
44
|
}]);
|
|
40
45
|
var theme = EditorView.theme({ ".dx-tag": { verticalAlign: "bottom" } });
|
|
41
46
|
var command = [
|
|
@@ -48,11 +53,11 @@ var command = [
|
|
|
48
53
|
var [ThreadContextProvider, useThreadContext] = createContext("Thread");
|
|
49
54
|
//#endregion
|
|
50
55
|
//#region src/Message/Message.tsx
|
|
51
|
-
var avatarSize =
|
|
56
|
+
var avatarSize = "md";
|
|
52
57
|
var buttonGroupClassNames = "flex flex-row items-center gap-0.5 pe-2";
|
|
53
58
|
var buttonClassNames = "p-1! transition-opacity";
|
|
54
59
|
var MessageRoot = forwardRef(({ authorImgSrc, authorId, authorName, authorAvatarProps, continues = true, controls, children, classNames, ...rootProps }, forwardedRef) => {
|
|
55
|
-
return /* @__PURE__ */
|
|
60
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
56
61
|
"data-testid": "thread.message",
|
|
57
62
|
...rootProps,
|
|
58
63
|
className: mx("grid grid-cols-[var(--dx-rail-size)_1fr_min-content] w-full", classNames),
|
|
@@ -60,11 +65,12 @@ var MessageRoot = forwardRef(({ authorImgSrc, authorId, authorName, authorAvatar
|
|
|
60
65
|
children: [
|
|
61
66
|
/* @__PURE__ */ jsxs("div", {
|
|
62
67
|
className: "flex flex-col items-center gap-2 pt-1",
|
|
63
|
-
children: [/* @__PURE__ */ jsx(Avatar.
|
|
68
|
+
children: [/* @__PURE__ */ jsx(Avatar.Root, {
|
|
64
69
|
size: avatarSize,
|
|
65
70
|
hue: authorAvatarProps?.hue || hexToHue(authorId ?? "0"),
|
|
66
71
|
fallback: authorAvatarProps?.emoji || hexToEmoji(authorId ?? "0"),
|
|
67
|
-
|
|
72
|
+
src: authorImgSrc,
|
|
73
|
+
label: authorName
|
|
68
74
|
}), continues && /* @__PURE__ */ jsx("div", { className: "w-px grow bg-separator" })]
|
|
69
75
|
}),
|
|
70
76
|
/* @__PURE__ */ jsx("div", {
|
|
@@ -76,7 +82,7 @@ var MessageRoot = forwardRef(({ authorImgSrc, authorId, authorName, authorAvatar
|
|
|
76
82
|
children: controls
|
|
77
83
|
})
|
|
78
84
|
]
|
|
79
|
-
})
|
|
85
|
+
});
|
|
80
86
|
});
|
|
81
87
|
MessageRoot.displayName = "Message.Root";
|
|
82
88
|
var MessageHeading = ({ children, classNames, timestamp, authorName, ...props }) => {
|
|
@@ -91,18 +97,18 @@ var MessageHeading = ({ children, classNames, timestamp, authorName, ...props })
|
|
|
91
97
|
};
|
|
92
98
|
MessageHeading.displayName = "Message.Heading";
|
|
93
99
|
var MessageAuthorName = ({ authorName }) => {
|
|
94
|
-
const { t } = useTranslation(translationKey);
|
|
95
|
-
return /* @__PURE__ */ jsx(
|
|
96
|
-
|
|
100
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
101
|
+
return /* @__PURE__ */ jsx("span", {
|
|
102
|
+
className: "block truncate min-w-0 shrink text-sm text-fg-subtle",
|
|
97
103
|
children: authorName ?? t("anonymous.label")
|
|
98
104
|
});
|
|
99
105
|
};
|
|
100
106
|
MessageAuthorName.displayName = "Message.AuthorName";
|
|
101
107
|
var MessageTime = ({ timestamp }) => {
|
|
102
|
-
const { dtLocale } = useTranslation(translationKey);
|
|
108
|
+
const { dtLocale } = Hooks.useTranslation(translationKey);
|
|
103
109
|
const dt = timestamp ? new Date(timestamp) : void 0;
|
|
104
110
|
return /* @__PURE__ */ jsx("time", {
|
|
105
|
-
className: "shrink-0 text-
|
|
111
|
+
className: "shrink-0 text-fg-subtle text-xs",
|
|
106
112
|
dateTime: dt?.toISOString(),
|
|
107
113
|
children: dt ? format(dt, "p", { locale: dtLocale }) : ""
|
|
108
114
|
});
|
|
@@ -147,7 +153,7 @@ var MessageBody = ({ message, isAuthor, editing, onSave }) => {
|
|
|
147
153
|
};
|
|
148
154
|
MessageBody.displayName = "Message.Body";
|
|
149
155
|
var TextBlock = ({ block, isAuthor, editing, onSave }) => {
|
|
150
|
-
const
|
|
156
|
+
const themeMode = Hooks.useThemeMode();
|
|
151
157
|
const inMemoryContentRef = useRef(block.text);
|
|
152
158
|
const handleDocumentChange = useCallback((next) => {
|
|
153
159
|
inMemoryContentRef.current = next;
|
|
@@ -155,7 +161,7 @@ var TextBlock = ({ block, isAuthor, editing, onSave }) => {
|
|
|
155
161
|
const saveDocumentChange = useCallback(() => {
|
|
156
162
|
onSave?.(inMemoryContentRef.current);
|
|
157
163
|
}, [onSave]);
|
|
158
|
-
useOnTransition(editing, true, false, saveDocumentChange);
|
|
164
|
+
Hooks.useOnTransition(editing, true, false, saveDocumentChange);
|
|
159
165
|
const { parentRef, focusAttributes, view } = useTextEditor(() => ({
|
|
160
166
|
initialValue: block.text,
|
|
161
167
|
extensions: [
|
|
@@ -239,7 +245,7 @@ MessageTextbox.displayName = "Message.Textbox";
|
|
|
239
245
|
* the unit rendered by `Thread.Messages`.
|
|
240
246
|
*/
|
|
241
247
|
var MessageTile = ({ message, classNames, continues = true }) => {
|
|
242
|
-
const { t } = useTranslation(translationKey);
|
|
248
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
243
249
|
const { getMetadata, identityDid, editable, onMessageDelete, onAcceptProposal, onAcceptChange, onRejectChange, onMessageSelect, currentMessageId } = useThreadContext("Message.Tile");
|
|
244
250
|
const [editing, setEditing] = useState(false);
|
|
245
251
|
const metadata = getMetadata(message);
|
|
@@ -266,7 +272,7 @@ var MessageTile = ({ message, classNames, continues = true }) => {
|
|
|
266
272
|
const controls = showEdit || showAccept || showAcceptChange || showRejectChange || showDelete ? /* @__PURE__ */ jsxs("div", {
|
|
267
273
|
className: buttonGroupClassNames,
|
|
268
274
|
children: [
|
|
269
|
-
showEdit && /* @__PURE__ */ jsx(
|
|
275
|
+
showEdit && /* @__PURE__ */ jsx(Button.Root, {
|
|
270
276
|
"data-testid": editing ? "thread.message.save" : "thread.message.edit",
|
|
271
277
|
variant: "ghost",
|
|
272
278
|
icon: editing ? "ph--check--regular" : "ph--pencil-simple--regular",
|
|
@@ -275,7 +281,7 @@ var MessageTile = ({ message, classNames, continues = true }) => {
|
|
|
275
281
|
classNames: [buttonClassNames, hoverableControlItem],
|
|
276
282
|
onClick: handleEdit
|
|
277
283
|
}),
|
|
278
|
-
showAccept && /* @__PURE__ */ jsx(
|
|
284
|
+
showAccept && /* @__PURE__ */ jsx(Button.Root, {
|
|
279
285
|
"data-testid": "thread.message.accept",
|
|
280
286
|
variant: "ghost",
|
|
281
287
|
icon: "ph--check--regular",
|
|
@@ -284,7 +290,7 @@ var MessageTile = ({ message, classNames, continues = true }) => {
|
|
|
284
290
|
classNames: [buttonClassNames, hoverableControlItem],
|
|
285
291
|
onClick: handleAcceptProposal
|
|
286
292
|
}),
|
|
287
|
-
showAcceptChange && /* @__PURE__ */ jsx(
|
|
293
|
+
showAcceptChange && /* @__PURE__ */ jsx(Button.Root, {
|
|
288
294
|
"data-testid": "thread.message.accept-change",
|
|
289
295
|
variant: "ghost",
|
|
290
296
|
icon: "ph--check--regular",
|
|
@@ -293,7 +299,7 @@ var MessageTile = ({ message, classNames, continues = true }) => {
|
|
|
293
299
|
classNames: [buttonClassNames, hoverableControlItem],
|
|
294
300
|
onClick: handleAcceptChange
|
|
295
301
|
}),
|
|
296
|
-
showRejectChange && /* @__PURE__ */ jsx(
|
|
302
|
+
showRejectChange && /* @__PURE__ */ jsx(Button.Root, {
|
|
297
303
|
"data-testid": "thread.message.reject-change",
|
|
298
304
|
variant: "ghost",
|
|
299
305
|
icon: "ph--x--regular",
|
|
@@ -302,7 +308,7 @@ var MessageTile = ({ message, classNames, continues = true }) => {
|
|
|
302
308
|
classNames: [buttonClassNames, hoverableControlItem],
|
|
303
309
|
onClick: handleRejectChange
|
|
304
310
|
}),
|
|
305
|
-
showDelete && /* @__PURE__ */ jsx(
|
|
311
|
+
showDelete && /* @__PURE__ */ jsx(Button.Root, {
|
|
306
312
|
"data-testid": "thread.message.delete",
|
|
307
313
|
variant: "ghost",
|
|
308
314
|
icon: "ph--x--regular",
|
|
@@ -347,7 +353,7 @@ MessageTile.displayName = "Message.Tile";
|
|
|
347
353
|
* to a single message.
|
|
348
354
|
*/
|
|
349
355
|
var MessageGroup = ({ messages, continues = true, classNames }) => {
|
|
350
|
-
const { t } = useTranslation(translationKey);
|
|
356
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
351
357
|
const { getMetadata, identityDid, editable, onMessageDelete, onAcceptProposal } = useThreadContext("Message.Group");
|
|
352
358
|
const [editing, setEditing] = useState(false);
|
|
353
359
|
const first = messages[0];
|
|
@@ -369,7 +375,7 @@ var MessageGroup = ({ messages, continues = true, classNames }) => {
|
|
|
369
375
|
const controls = showEdit || showAccept || showDelete ? /* @__PURE__ */ jsxs("div", {
|
|
370
376
|
className: buttonGroupClassNames,
|
|
371
377
|
children: [
|
|
372
|
-
showEdit && /* @__PURE__ */ jsx(
|
|
378
|
+
showEdit && /* @__PURE__ */ jsx(Button.Root, {
|
|
373
379
|
"data-testid": editing ? "thread.message.save" : "thread.message.edit",
|
|
374
380
|
variant: "ghost",
|
|
375
381
|
icon: editing ? "ph--check--regular" : "ph--pencil-simple--regular",
|
|
@@ -378,7 +384,7 @@ var MessageGroup = ({ messages, continues = true, classNames }) => {
|
|
|
378
384
|
classNames: [buttonClassNames, hoverableControlItem],
|
|
379
385
|
onClick: handleEdit
|
|
380
386
|
}),
|
|
381
|
-
showAccept && /* @__PURE__ */ jsx(
|
|
387
|
+
showAccept && /* @__PURE__ */ jsx(Button.Root, {
|
|
382
388
|
"data-testid": "thread.message.accept",
|
|
383
389
|
variant: "ghost",
|
|
384
390
|
icon: "ph--check--regular",
|
|
@@ -387,7 +393,7 @@ var MessageGroup = ({ messages, continues = true, classNames }) => {
|
|
|
387
393
|
classNames: [buttonClassNames, hoverableControlItem],
|
|
388
394
|
onClick: handleAcceptProposal
|
|
389
395
|
}),
|
|
390
|
-
showDelete && /* @__PURE__ */ jsx(
|
|
396
|
+
showDelete && /* @__PURE__ */ jsx(Button.Root, {
|
|
391
397
|
"data-testid": "thread.message.delete",
|
|
392
398
|
variant: "ghost",
|
|
393
399
|
icon: "ph--x--regular",
|
|
@@ -431,7 +437,6 @@ var Message = {
|
|
|
431
437
|
};
|
|
432
438
|
//#endregion
|
|
433
439
|
//#region src/Thread/Thread.tsx
|
|
434
|
-
var getMessageId = (message) => Obj.getURI(message);
|
|
435
440
|
/**
|
|
436
441
|
* Headless root of a thread. Provides message-tile context (metadata resolver,
|
|
437
442
|
* injected renderers, callbacks). Requires an ambient `Dnd.Root` ancestor,
|
|
@@ -462,9 +467,9 @@ var ThreadRoot = ({ children, getMetadata, components, identityDid, editable, on
|
|
|
462
467
|
};
|
|
463
468
|
ThreadRoot.displayName = "Thread.Root";
|
|
464
469
|
/** Visible thread container (the attention surface that hosts header / messages / composer). */
|
|
465
|
-
var ThreadContent = composable(({ children, current, id, ...props }, forwardedRef) => {
|
|
470
|
+
var ThreadContent = Util.composable(({ children, current, id, ...props }, forwardedRef) => {
|
|
466
471
|
return /* @__PURE__ */ jsx("div", {
|
|
467
|
-
...composableProps(props, {
|
|
472
|
+
...Util.composableProps(props, {
|
|
468
473
|
role: "group",
|
|
469
474
|
classNames: ["flex flex-col bg-[var(--surface-bg)] current-related dx-attention-surface [--controls-opacity:0]", hoverableFocusedWithinControls]
|
|
470
475
|
}),
|
|
@@ -481,15 +486,15 @@ ThreadContent.displayName = "Thread.Content";
|
|
|
481
486
|
* `[rail · 1fr · controls]` grid so the trailing controls align with message-tile
|
|
482
487
|
* controls (which use the same template) — no grid is leaked in from the caller.
|
|
483
488
|
*/
|
|
484
|
-
var ThreadHeader = composable(({ title, detached, current, controls, onSelect, ...props }, forwardedRef) => {
|
|
485
|
-
const { t } = useTranslation(translationKey);
|
|
489
|
+
var ThreadHeader = Util.composable(({ title, detached, current, controls, onSelect, ...props }, forwardedRef) => {
|
|
490
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
486
491
|
const { focusComposer } = useThreadContext("Thread.Header");
|
|
487
492
|
const handleSelect = useCallback(() => {
|
|
488
493
|
onSelect?.();
|
|
489
494
|
focusComposer();
|
|
490
495
|
}, [onSelect, focusComposer]);
|
|
491
496
|
return /* @__PURE__ */ jsxs("div", {
|
|
492
|
-
...composableProps(props, { classNames: [
|
|
497
|
+
...Util.composableProps(props, { classNames: [
|
|
493
498
|
"grid grid-cols-[var(--dx-rail-size)_1fr_min-content] items-center",
|
|
494
499
|
hoverableControls,
|
|
495
500
|
hoverableFocusedWithinControls
|
|
@@ -498,13 +503,13 @@ var ThreadHeader = composable(({ title, detached, current, controls, onSelect, .
|
|
|
498
503
|
children: [
|
|
499
504
|
/* @__PURE__ */ jsx("div", {
|
|
500
505
|
className: "flex items-center justify-center",
|
|
501
|
-
children: /* @__PURE__ */ jsx(
|
|
506
|
+
children: /* @__PURE__ */ jsx(Button.Root, {
|
|
502
507
|
iconOnly: true,
|
|
503
508
|
variant: "ghost",
|
|
504
|
-
|
|
509
|
+
size: "sm",
|
|
505
510
|
icon: "ph--caret-double-right--regular",
|
|
506
511
|
label: t("select-thread.label"),
|
|
507
|
-
classNames: "text-
|
|
512
|
+
classNames: "text-fg-muted",
|
|
508
513
|
onClick: handleSelect
|
|
509
514
|
})
|
|
510
515
|
}),
|
|
@@ -513,7 +518,7 @@ var ThreadHeader = composable(({ title, detached, current, controls, onSelect, .
|
|
|
513
518
|
children: /* @__PURE__ */ jsx("p", {
|
|
514
519
|
role: "heading",
|
|
515
520
|
"data-testid": "thread.heading",
|
|
516
|
-
className: mx("me-2 font-medium truncate italic", current ? "text-accent-text" : "text-
|
|
521
|
+
className: mx("me-2 font-medium truncate italic", current ? "text-accent-text" : "text-fg-muted", detached && "line-through decoration-1"),
|
|
517
522
|
children: title
|
|
518
523
|
})
|
|
519
524
|
}),
|
|
@@ -572,7 +577,7 @@ var groupMessages = (messages, { groupWindowMs, dayDivider, gapDividerMs, dtLoca
|
|
|
572
577
|
else {
|
|
573
578
|
currentGroup = {
|
|
574
579
|
kind: "group",
|
|
575
|
-
id:
|
|
580
|
+
id: Obj.getURI(message),
|
|
576
581
|
messages: [message]
|
|
577
582
|
};
|
|
578
583
|
items.push(currentGroup);
|
|
@@ -585,7 +590,7 @@ var groupMessages = (messages, { groupWindowMs, dayDivider, gapDividerMs, dtLoca
|
|
|
585
590
|
return items;
|
|
586
591
|
};
|
|
587
592
|
var ThreadDivider = ({ label }) => label ? /* @__PURE__ */ jsxs("div", {
|
|
588
|
-
className: "flex items-center gap-2 px-2 py-2 text-xs text-
|
|
593
|
+
className: "flex items-center gap-2 px-2 py-2 text-xs text-fg-muted",
|
|
589
594
|
children: [
|
|
590
595
|
/* @__PURE__ */ jsx("div", { className: "h-px grow bg-separator" }),
|
|
591
596
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -615,7 +620,7 @@ var DEFAULT_GROUP_WINDOW_MS = 6e4;
|
|
|
615
620
|
var DEFAULT_GAP_DIVIDER_MS = 108e5;
|
|
616
621
|
/** Virtualized stack of message tiles (via Mosaic), within an internal scroll area. */
|
|
617
622
|
var ThreadMessages = ({ messages, id, estimateSize = 80, currentId, groupWindowMs = DEFAULT_GROUP_WINDOW_MS, dayDivider = true, gapDividerMs = DEFAULT_GAP_DIVIDER_MS, classNames }) => {
|
|
618
|
-
const { dtLocale } = useTranslation(translationKey);
|
|
623
|
+
const { dtLocale } = Hooks.useTranslation(translationKey);
|
|
619
624
|
const [viewport, setViewport] = useState(null);
|
|
620
625
|
const items = useMemo(() => groupMessages(messages.filter(Boolean), {
|
|
621
626
|
groupWindowMs,
|
|
@@ -641,7 +646,7 @@ var ThreadMessages = ({ messages, id, estimateSize = 80, currentId, groupWindowM
|
|
|
641
646
|
currentId,
|
|
642
647
|
eventHandler,
|
|
643
648
|
children: /* @__PURE__ */ jsx(ScrollArea.Root, {
|
|
644
|
-
classNames: mx("col-span-2
|
|
649
|
+
classNames: mx("col-span-2 dx-grow", classNames),
|
|
645
650
|
orientation: "vertical",
|
|
646
651
|
children: /* @__PURE__ */ jsx(ScrollArea.Viewport, {
|
|
647
652
|
ref: setViewport,
|
|
@@ -660,8 +665,8 @@ var ThreadMessages = ({ messages, id, estimateSize = 80, currentId, groupWindowM
|
|
|
660
665
|
ThreadMessages.displayName = "Thread.Messages";
|
|
661
666
|
/** Message composer pinned at the foot of a thread. */
|
|
662
667
|
var ThreadTextbox = ({ placeholder, autoFocus, disabled, extensions, onSend, ...metadata }) => {
|
|
663
|
-
const { t } = useTranslation(translationKey);
|
|
664
|
-
const
|
|
668
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
669
|
+
const themeMode = Hooks.useThemeMode();
|
|
665
670
|
const { registerComposerFocus } = useThreadContext("Thread.Textbox");
|
|
666
671
|
const composerRef = useRef(null);
|
|
667
672
|
const messageRef = useRef("");
|
|
@@ -702,13 +707,13 @@ var ThreadTextbox = ({ placeholder, autoFocus, disabled, extensions, onSend, ...
|
|
|
702
707
|
};
|
|
703
708
|
ThreadTextbox.displayName = "Thread.Textbox";
|
|
704
709
|
var ThreadStatus = forwardRef(({ activity, classNames, children, ...props }, forwardedRef) => {
|
|
705
|
-
const { t } = useTranslation(translationKey);
|
|
710
|
+
const { t } = Hooks.useTranslation(translationKey);
|
|
706
711
|
return /* @__PURE__ */ jsxs("div", {
|
|
707
712
|
...props,
|
|
708
|
-
className: mx("col-start-2 grid grid-cols-[min-content_1fr_max-content] pb-2 pe-2 text-xs text-
|
|
713
|
+
className: mx("col-start-2 grid grid-cols-[min-content_1fr_max-content] pb-2 pe-2 text-xs text-fg-muted", classNames),
|
|
709
714
|
ref: forwardedRef,
|
|
710
715
|
children: [
|
|
711
|
-
/* @__PURE__ */ jsx(Icon, {
|
|
716
|
+
/* @__PURE__ */ jsx(Icon.Icon, {
|
|
712
717
|
icon: "ph--spinner--bold",
|
|
713
718
|
classNames: "w-6 h-4 invisible data-[visible=show]:visible animate-spin-slow",
|
|
714
719
|
"data-visible": activity ? "show" : "hide"
|
package/dist/lib/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/command.ts","../../src/context.ts","../../src/Message/Message.tsx","../../src/Thread/Thread.tsx"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport { HighlightStyle, StreamLanguage, syntaxHighlighting } from '@codemirror/language';\nimport { type Extension } from '@codemirror/state';\nimport { EditorView } from '@codemirror/view';\n\nimport { tags } from '@dxos/ui-editor';\nimport { mx } from '@dxos/ui-theme';\n\n/**\n * Highlights leading slash-commands (e.g. `/resolve`) and `@mention` tags\n * (e.g. `@Kai`, anywhere) in the message composer.\n * https://github.com/codemirror/stream-parser/blob/main/test/test-stream-parser.ts\n */\nconst parser = StreamLanguage.define<{ count: number }>({\n startState: () => ({ count: 0 }),\n token: (stream, state) => {\n state.count++;\n if (stream.eatSpace()) {\n return null;\n }\n // Leading slash-command (first token only).\n if (state.count === 1 && stream.match(/^\\/\\w+/)) {\n return 'tagName';\n }\n // @mention (anywhere).\n if (stream.match(/^@\\w+/)) {\n return 'labelName';\n }\n stream.next();\n return null;\n },\n});\n\nconst styles = HighlightStyle.define([\n {\n tag: tags.tagName,\n class: mx('dx-tag dx-tag--indigo'),\n },\n {\n tag: tags.labelName,\n class: mx('dx-tag dx-tag--blue'),\n },\n]);\n\n// Center the inline `.dx-tag` pills on the text line. `.dx-tag` is `inline-block`\n// with vertical padding, so on a CodeMirror line it baseline-aligns and rides\n// high; overriding `vertical-align` recentres it (cf. the `cm-reference-pill`\n// precedent in `@dxos/react-ui-chat`).\nconst theme = EditorView.theme({\n '.dx-tag': {\n verticalAlign: 'bottom',\n },\n});\n\nexport const command: Extension = [parser, syntaxHighlighting(styles), theme];\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { createContext } from '@radix-ui/react-context';\nimport { type FC, type ReactNode } from 'react';\n\n// Imported so TypeScript can name this type in the emitted .d.ts (ThreadContext → Message).\n// eslint-disable-next-line unused-imports/no-unused-imports\nimport { type Message } from '@dxos/types';\n\nimport { type ThreadContextValue } from './types';\n\n/**\n * Context provided by `Thread.Root` and consumed by its parts. Extends the\n * consumer-facing {@link ThreadContextValue} with internal coordination (e.g.\n * focusing the composer from the header's select affordance).\n */\nexport type ThreadContext = ThreadContextValue & {\n /** Register the composer's focus handler (set by `Thread.Textbox`). */\n registerComposerFocus: (focus: (() => void) | undefined) => void;\n /** Focus the thread composer (used by `Thread.Header`'s select affordance). */\n focusComposer: () => void;\n};\n\nexport const [ThreadContextProvider, useThreadContext]: readonly [\n FC<ThreadContext & { children: ReactNode }>,\n (consumerName: string) => ThreadContext,\n] = createContext<ThreadContext>('Thread');\n","//\n// Copyright 2023 DXOS.org\n//\n\nimport { EditorView } from '@codemirror/view';\nimport { format } from 'date-fns/format';\nimport React, {\n type ComponentPropsWithoutRef,\n type ComponentPropsWithRef,\n type ReactNode,\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from 'react';\n\nimport { Obj, Ref } from '@dxos/echo';\nimport {\n Avatar,\n IconButton,\n type ThemedClassName,\n useOnTransition,\n useThemeContext,\n useTranslation,\n} from '@dxos/react-ui';\nimport { type UseTextEditorProps, useTextEditor } from '@dxos/react-ui-editor';\nimport { type ContentBlock, type Message as MessageType } from '@dxos/types';\nimport { createBasicExtensions, createThemeExtensions, keymap, listener } from '@dxos/ui-editor';\nimport { hoverableControlItem, hoverableControls, hoverableFocusedWithinControls, mx } from '@dxos/ui-theme';\nimport { hexToEmoji, hexToHue, isTruthy } from '@dxos/util';\n\nimport { translationKey } from '#translations';\n\nimport { command } from '../command';\nimport { useThreadContext } from '../context';\nimport { type MessageMetadata } from '../types';\n\nconst avatarSize = 7;\n\nconst buttonGroupClassNames = 'flex flex-row items-center gap-0.5 pe-2';\nconst buttonClassNames = 'p-1! transition-opacity';\n\n//\n// Root\n//\n\nexport type MessageRootProps = ThemedClassName<\n ComponentPropsWithRef<'div'> & MessageMetadata & Partial<{ continues: boolean; controls: ReactNode }>\n>;\n\n// TODO(burdon): Show authorName on tooltip.\nconst MessageRoot = forwardRef<HTMLDivElement, MessageRootProps>(\n (\n {\n authorImgSrc,\n authorId,\n authorName,\n authorAvatarProps,\n continues = true,\n controls,\n children,\n classNames,\n ...rootProps\n },\n forwardedRef,\n ) => {\n // Must wrap the message since Avatar.Label may be used in the content.\n // Columns mirror Thread.Header (avatar/rail · content · controls) so trailing\n // controls align with the thread header's controls.\n return (\n <Avatar.Root>\n <div\n data-testid='thread.message'\n {...rootProps}\n className={mx('grid grid-cols-[var(--dx-rail-size)_1fr_min-content] w-full', classNames)}\n ref={forwardedRef}\n >\n <div className='flex flex-col items-center gap-2 pt-1'>\n <Avatar.Content\n size={avatarSize}\n hue={authorAvatarProps?.hue || hexToHue(authorId ?? '0')}\n fallback={authorAvatarProps?.emoji || hexToEmoji(authorId ?? '0')}\n {...(authorImgSrc && { imgSrc: authorImgSrc })}\n />\n {continues && <div className='w-px grow bg-separator' />}\n </div>\n <div className='py-1 min-w-0'>{children}</div>\n {controls && <div className='self-start'>{controls}</div>}\n </div>\n </Avatar.Root>\n );\n },\n);\n\nMessageRoot.displayName = 'Message.Root';\n\n//\n// Heading\n//\n\nexport type MessageHeadingProps = ThemedClassName<ComponentPropsWithoutRef<'div'>> &\n Pick<MessageMetadata, 'authorName' | 'timestamp'>;\n\nconst MessageHeading = ({ children, classNames, timestamp, authorName, ...props }: MessageHeadingProps) => {\n return (\n <div {...props} className={mx('flex gap-2 items-start', classNames)}>\n <p className='grow flex items-baseline gap-2 min-w-0'>\n <MessageAuthorName authorName={authorName} />\n {timestamp && <MessageTime timestamp={timestamp} />}\n </p>\n {children}\n </div>\n );\n};\n\nMessageHeading.displayName = 'Message.Heading';\n\nexport type MessageAuthorNameProps = Pick<MessageMetadata, 'authorName'>;\n\nconst MessageAuthorName = ({ authorName }: MessageAuthorNameProps) => {\n const { t } = useTranslation(translationKey);\n return (\n <Avatar.Label classNames='block truncate min-w-0 shrink text-sm text-subdued'>\n {authorName ?? t('anonymous.label')}\n </Avatar.Label>\n );\n};\n\nMessageAuthorName.displayName = 'Message.AuthorName';\n\nexport type MessageTimeProps = Pick<MessageMetadata, 'timestamp'>;\n\nconst MessageTime = ({ timestamp }: MessageTimeProps) => {\n const { dtLocale } = useTranslation(translationKey);\n const dt = timestamp ? new Date(timestamp) : undefined;\n return (\n <time className='shrink-0 text-subdued text-xs' dateTime={dt?.toISOString()}>\n {dt ? format(dt, 'p', { locale: dtLocale }) : ''}\n </time>\n );\n};\n\nMessageTime.displayName = 'Message.Time';\n\n//\n// Body\n//\n\nexport type MessageBodyProps = {\n /** The message whose content blocks are rendered. */\n message: MessageType.Message;\n /** When true, the local user authored this message. */\n isAuthor?: boolean;\n /** When true, the text block is editable. */\n editing?: boolean;\n /** Persist an edited text block. */\n onSave?: (text: string) => void;\n};\n\n/**\n * Renders a message's content blocks: text (via editor), proposal, and\n * object/reference tiles (delegated to the injected `Object` component).\n */\nconst MessageBody = ({ message, isAuthor, editing, onSave }: MessageBodyProps) => {\n const { components } = useThreadContext('Message.Body');\n const textBlockIndex = message.blocks.findIndex((block) => block._tag === 'text');\n const textBlock = textBlockIndex !== -1 ? (message.blocks[textBlockIndex] as ContentBlock.Text) : undefined;\n const proposalBlock = message.blocks.find((block) => block._tag === 'proposal') as ContentBlock.Proposal | undefined;\n const changeBlock = message.blocks.find((block) => block._tag === 'change') as ContentBlock.Change | undefined;\n const references = message.blocks\n .filter((block) => block._tag === 'reference')\n .map((block) => (block as ContentBlock.Reference).reference);\n const Object = components.Object;\n\n return (\n <>\n {textBlock && <TextBlock block={textBlock} isAuthor={isAuthor} editing={editing} onSave={onSave} />}\n {proposalBlock && <div className='me-4 italic'>{proposalBlock.text}</div>}\n {changeBlock && (\n <p className='me-4 text-sm break-words'>\n {changeBlock.before && <span className='line-through opacity-60'>{changeBlock.before}</span>}\n {changeBlock.before && changeBlock.after && ' '}\n {changeBlock.after && <span>{changeBlock.after}</span>}\n </p>\n )}\n {Object &&\n Ref.Array.targets(references).map((reference, index) => (\n <Object key={index} subject={reference as Obj.Unknown} />\n ))}\n </>\n );\n};\n\nMessageBody.displayName = 'Message.Body';\n\nconst TextBlock = ({\n block,\n isAuthor,\n editing,\n onSave,\n}: {\n block: ContentBlock.Text;\n isAuthor?: boolean;\n editing?: boolean;\n onSave?: (text: string) => void;\n}) => {\n const { themeMode } = useThemeContext();\n const inMemoryContentRef = useRef(block.text);\n\n const handleDocumentChange = useCallback((next: string) => {\n inMemoryContentRef.current = next;\n }, []);\n\n const saveDocumentChange = useCallback(() => {\n onSave?.(inMemoryContentRef.current);\n }, [onSave]);\n\n useOnTransition(editing, true, false, saveDocumentChange);\n\n const { parentRef, focusAttributes, view } = useTextEditor(\n () => ({\n initialValue: block.text,\n extensions: [\n // Edit mode is authorisation enough — the control that gets here is already gated on\n // `isAuthor` — and a mid-edit flip to read-only makes the editor drop input silently.\n createBasicExtensions({ readOnly: !editing }),\n createThemeExtensions({ themeMode }),\n command,\n EditorView.updateListener.of((update) => {\n if (update.docChanged) {\n handleDocumentChange(update.state.doc.toString());\n }\n }),\n ],\n }),\n // While editing, the editor owns its content and its authorisation: pinning both keeps an incoming\n // `block.text` update or member-list refresh from rebuilding the view being typed in. `editing` is\n // itself a dep, so the flip still rebuilds.\n [editing, editing ? undefined : block.text, editing ? undefined : isAuthor, themeMode, handleDocumentChange],\n );\n\n useEffect(() => {\n if (editing) {\n view?.focus();\n }\n }, [editing, view]);\n\n return <div ref={parentRef} className='me-4' {...focusAttributes} />;\n};\n\n//\n// Textbox\n//\n\nexport type MessageTextboxProps = {\n disabled?: boolean;\n onSend?: () => void;\n onClear?: () => void;\n onEditorFocus?: () => void;\n} & MessageMetadata &\n UseTextEditorProps;\n\nconst keyBindings = ({ onSend, onClear }: Pick<MessageTextboxProps, 'onSend' | 'onClear'>) => [\n {\n key: 'Enter',\n run: () => {\n if (onSend) {\n onSend();\n return true;\n }\n return false;\n },\n },\n {\n key: 'Meta+Backspace',\n run: () => {\n if (onClear) {\n onClear();\n return true;\n }\n return false;\n },\n },\n];\n\nexport type MessageTextboxHandle = { focus: () => void };\n\nconst MessageTextbox = forwardRef<MessageTextboxHandle, MessageTextboxProps>(\n (\n {\n id,\n authorId,\n authorName,\n authorImgSrc,\n authorAvatarProps,\n disabled,\n extensions,\n onSend,\n onClear,\n onEditorFocus,\n ...editorProps\n },\n forwardedRef,\n ) => {\n const { parentRef, focusAttributes, view } = useTextEditor(\n () => ({\n id,\n extensions: [\n keymap.of(keyBindings({ onSend, onClear })),\n listener({\n onFocus: ({ focusing }) => {\n if (focusing) {\n onEditorFocus?.();\n }\n },\n }),\n extensions,\n ].filter(isTruthy),\n ...editorProps,\n }),\n [id, extensions],\n );\n\n useImperativeHandle(forwardedRef, () => ({ focus: () => view?.focus() }), [view]);\n\n return (\n <MessageRoot {...{ id, authorId, authorName, authorImgSrc, authorAvatarProps }} continues={false}>\n {/*\n Addressable: the reply composer is the only textbox in a thread that is not a message body,\n so without a testid a test can only guess at it by editor order.\n */}\n <div\n ref={parentRef}\n data-testid='thread.reply'\n className={mx('py-0.5 me-1 rounded-xs dx-focus-ring', disabled && 'opacity-50')}\n {...focusAttributes}\n />\n </MessageRoot>\n );\n },\n);\n\nMessageTextbox.displayName = 'Message.Textbox';\n\n//\n// Tile\n//\n\nexport type MessageTileProps = {\n message: MessageType.Message;\n classNames?: MessageRootProps['classNames'];\n /** Whether the avatar-rail continuation line is drawn below the tile; false for the last tile. */\n continues?: boolean;\n};\n\n/**\n * Full message tile (frame + heading + controls + body). Reads `Thread.Root`\n * context for metadata resolution, injected renderers, and callbacks. This is\n * the unit rendered by `Thread.Messages`.\n */\nconst MessageTile = ({ message, classNames, continues = true }: MessageTileProps) => {\n const { t } = useTranslation(translationKey);\n const {\n getMetadata,\n identityDid,\n editable,\n onMessageDelete,\n onAcceptProposal,\n onAcceptChange,\n onRejectChange,\n onMessageSelect,\n currentMessageId,\n } = useThreadContext('Message.Tile');\n const [editing, setEditing] = useState(false);\n\n const metadata = getMetadata(message);\n const isAuthor = !!identityDid && identityDid === metadata.authorId;\n const hasProposal = message.blocks.some((block) => block._tag === 'proposal');\n const hasChange = message.blocks.some((block) => block._tag === 'change');\n\n const handleEdit = useCallback(() => setEditing((value) => !value), []);\n const handleDelete = useCallback(() => onMessageDelete?.(message.id), [onMessageDelete, message.id]);\n const handleAcceptProposal = useCallback(() => onAcceptProposal?.(message.id), [onAcceptProposal, message.id]);\n const handleAcceptChange = useCallback(() => onAcceptChange?.(message.id), [onAcceptChange, message.id]);\n const handleRejectChange = useCallback(() => onRejectChange?.(message.id), [onRejectChange, message.id]);\n const handleSelect = useCallback(() => onMessageSelect?.(message.id), [onMessageSelect, message.id]);\n const handleSave = useCallback(\n (text: string) => {\n Obj.update(message, (message) => {\n const block = message.blocks.find((block) => block._tag === 'text');\n if (block && block._tag === 'text') {\n block.text = text;\n }\n });\n },\n [message],\n );\n\n const showEdit = isAuthor && editable;\n const showAccept = hasProposal && !!onAcceptProposal;\n const showAcceptChange = hasChange && !!onAcceptChange;\n const showRejectChange = hasChange && !!onRejectChange;\n const showDelete = !!onMessageDelete;\n const controls =\n showEdit || showAccept || showAcceptChange || showRejectChange || showDelete ? (\n <div className={buttonGroupClassNames}>\n {showEdit && (\n <IconButton\n data-testid={editing ? 'thread.message.save' : 'thread.message.edit'}\n variant='ghost'\n icon={editing ? 'ph--check--regular' : 'ph--pencil-simple--regular'}\n iconOnly\n label={t(editing ? 'save-message.label' : 'edit-message.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleEdit}\n />\n )}\n {showAccept && (\n <IconButton\n data-testid='thread.message.accept'\n variant='ghost'\n icon='ph--check--regular'\n iconOnly\n label={t('accept-proposal.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleAcceptProposal}\n />\n )}\n {showAcceptChange && (\n <IconButton\n data-testid='thread.message.accept-change'\n variant='ghost'\n icon='ph--check--regular'\n iconOnly\n label={t('accept-change.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleAcceptChange}\n />\n )}\n {showRejectChange && (\n <IconButton\n data-testid='thread.message.reject-change'\n variant='ghost'\n icon='ph--x--regular'\n iconOnly\n label={t('reject-change.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleRejectChange}\n />\n )}\n {showDelete && (\n <IconButton\n data-testid='thread.message.delete'\n variant='ghost'\n icon='ph--x--regular'\n iconOnly\n label={t('delete-message.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleDelete}\n />\n )}\n </div>\n ) : undefined;\n\n return (\n <MessageRoot\n {...metadata}\n continues={continues}\n controls={controls}\n // Selecting a tile is how the host reveals what it refers to (a suggestion's range in the\n // document), so the whole tile is the target — the accent marks which one is showing.\n onClick={onMessageSelect ? handleSelect : undefined}\n aria-current={currentMessageId === message.id ? 'location' : undefined}\n classNames={[\n hoverableControls,\n hoverableFocusedWithinControls,\n onMessageSelect && 'cursor-pointer',\n currentMessageId === message.id && 'bg-current-surface',\n classNames,\n ]}\n >\n <MessageHeading authorName={metadata.authorName} timestamp={metadata.timestamp} />\n <MessageBody message={message} isAuthor={isAuthor} editing={editing} onSave={handleSave} />\n </MessageRoot>\n );\n};\n\nMessageTile.displayName = 'Message.Tile';\n\n//\n// Group\n//\n\nexport type MessageGroupProps = {\n /** Consecutive same-sender messages, in ascending time order, rendered as one tile. */\n messages: readonly MessageType.Message[];\n /** Whether the avatar-rail continuation line is drawn below the tile; false for the last tile. */\n continues?: boolean;\n classNames?: MessageRootProps['classNames'];\n};\n\n/**\n * Groups consecutive same-sender messages (see `Thread.Messages`'s grouping\n * window) into a single tile: one heading (author + first message's\n * timestamp) followed by one body per message, stacked in order. Per-message\n * edit/delete controls are not shown per-row in a group — v1 shows them for\n * the group's first message only, since the heading/controls layout is keyed\n * to a single message.\n */\nconst MessageGroup = ({ messages, continues = true, classNames }: MessageGroupProps) => {\n const { t } = useTranslation(translationKey);\n const { getMetadata, identityDid, editable, onMessageDelete, onAcceptProposal } = useThreadContext('Message.Group');\n const [editing, setEditing] = useState(false);\n\n const first = messages[0];\n const metadata = getMetadata(first);\n const isAuthor = !!identityDid && identityDid === metadata.authorId;\n const hasProposal = first.blocks.some((block) => block._tag === 'proposal');\n\n const handleEdit = useCallback(() => setEditing((value) => !value), []);\n const handleDelete = useCallback(() => onMessageDelete?.(first.id), [onMessageDelete, first.id]);\n const handleAcceptProposal = useCallback(() => onAcceptProposal?.(first.id), [onAcceptProposal, first.id]);\n const handleSave = useCallback(\n (text: string) => {\n Obj.update(first, (first) => {\n const block = first.blocks.find((block) => block._tag === 'text');\n if (block && block._tag === 'text') {\n block.text = text;\n }\n });\n },\n [first],\n );\n\n const showEdit = isAuthor && editable;\n const showAccept = hasProposal && !!onAcceptProposal;\n const showDelete = !!onMessageDelete;\n const controls =\n showEdit || showAccept || showDelete ? (\n <div className={buttonGroupClassNames}>\n {showEdit && (\n <IconButton\n data-testid={editing ? 'thread.message.save' : 'thread.message.edit'}\n variant='ghost'\n icon={editing ? 'ph--check--regular' : 'ph--pencil-simple--regular'}\n iconOnly\n label={t(editing ? 'save-message.label' : 'edit-message.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleEdit}\n />\n )}\n {showAccept && (\n <IconButton\n data-testid='thread.message.accept'\n variant='ghost'\n icon='ph--check--regular'\n iconOnly\n label={t('accept-proposal.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleAcceptProposal}\n />\n )}\n {showDelete && (\n <IconButton\n data-testid='thread.message.delete'\n variant='ghost'\n icon='ph--x--regular'\n iconOnly\n label={t('delete-message.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleDelete}\n />\n )}\n </div>\n ) : undefined;\n\n return (\n <MessageRoot\n {...metadata}\n continues={continues}\n controls={controls}\n classNames={[hoverableControls, hoverableFocusedWithinControls, classNames]}\n >\n <MessageHeading authorName={metadata.authorName} timestamp={metadata.timestamp} />\n {messages.map((message) => (\n <MessageBody\n key={message.id}\n message={message}\n isAuthor={isAuthor}\n editing={editing && message === first}\n onSave={handleSave}\n />\n ))}\n </MessageRoot>\n );\n};\n\nMessageGroup.displayName = 'Message.Group';\n\n//\n// Message\n//\n\nexport const Message = {\n Root: MessageRoot,\n Heading: MessageHeading,\n AuthorName: MessageAuthorName,\n Time: MessageTime,\n Body: MessageBody,\n Textbox: MessageTextbox,\n Tile: MessageTile,\n Group: MessageGroup,\n};\n","//\n// Copyright 2023 DXOS.org\n//\n\nimport { format } from 'date-fns/format';\nimport { type Locale } from 'date-fns/locale';\nimport { startOfDay } from 'date-fns/startOfDay';\nimport React, {\n type ComponentPropsWithRef,\n type PropsWithChildren,\n type ReactNode,\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { Obj } from '@dxos/echo';\nimport {\n type ComposableProps,\n Icon,\n IconButton,\n ScrollArea,\n type ThemedClassName,\n composable,\n composableProps,\n useThemeContext,\n useTranslation,\n} from '@dxos/react-ui';\nimport { type DndContainerHandler } from '@dxos/react-ui-dnd';\nimport { Mosaic, type MosaicTileProps } from '@dxos/react-ui-mosaic';\nimport { type Message as MessageType } from '@dxos/types';\nimport { type Extension, createBasicExtensions, createThemeExtensions, listener } from '@dxos/ui-editor';\nimport { hoverableControlItem, hoverableControls, hoverableFocusedWithinControls, mx } from '@dxos/ui-theme';\n\nimport { translationKey } from '#translations';\n\nimport { command } from '../command';\nimport { ThreadContextProvider, useThreadContext } from '../context';\nimport { Message } from '../Message';\nimport { type MessageMetadata, type ThreadContextValue } from '../types';\n\nconst getMessageId = (message: MessageType.Message) => Obj.getURI(message);\n\n//\n// Root\n//\n\nexport type ThreadRootProps = PropsWithChildren<\n Omit<ThreadContextValue, 'components'> & Partial<Pick<ThreadContextValue, 'components'>>\n>;\n\n/**\n * Headless root of a thread. Provides message-tile context (metadata resolver,\n * injected renderers, callbacks). Requires an ambient `Dnd.Root` ancestor,\n * within which `Thread.Messages` renders its virtual stack. Renders no DOM of\n * its own — wrap the visible thread chrome in `Thread.Content`.\n */\nconst ThreadRoot = ({\n children,\n getMetadata,\n components,\n identityDid,\n editable,\n onMessageDelete,\n onAcceptProposal,\n onAcceptChange,\n onRejectChange,\n onMessageSelect,\n currentMessageId,\n}: ThreadRootProps) => {\n // Composer focus handler registered by Thread.Textbox; invoked by Thread.Header's caret.\n const composerFocus = useRef<(() => void) | undefined>(undefined);\n\n const registerComposerFocus = useCallback((focus: (() => void) | undefined) => {\n composerFocus.current = focus;\n }, []);\n\n const focusComposer = useCallback(() => composerFocus.current?.(), []);\n\n return (\n <ThreadContextProvider\n getMetadata={getMetadata}\n components={components ?? {}}\n identityDid={identityDid}\n editable={editable}\n registerComposerFocus={registerComposerFocus}\n focusComposer={focusComposer}\n onMessageDelete={onMessageDelete}\n onAcceptProposal={onAcceptProposal}\n onAcceptChange={onAcceptChange}\n onRejectChange={onRejectChange}\n onMessageSelect={onMessageSelect}\n currentMessageId={currentMessageId}\n >\n {children}\n </ThreadContextProvider>\n );\n};\n\nThreadRoot.displayName = 'Thread.Root';\n\n//\n// Content\n//\n\ntype ThreadContentExtra = {\n id?: string;\n current?: boolean | string;\n} & Pick<ComponentPropsWithRef<'div'>, 'onClickCapture' | 'onFocusCapture'>;\n\nexport type ThreadContentProps = ComposableProps<ThreadContentExtra>;\n\n/** Visible thread container (the attention surface that hosts header / messages / composer). */\nconst ThreadContent = composable<HTMLDivElement, ThreadContentExtra>(\n ({ children, current, id, ...props }, forwardedRef) => {\n return (\n <div\n {...composableProps(props, {\n role: 'group',\n classNames: [\n 'flex flex-col bg-[var(--surface-bg)] current-related dx-attention-surface [--controls-opacity:0]',\n hoverableFocusedWithinControls,\n ],\n })}\n data-testid='thread'\n id={id}\n aria-current={current ? 'location' : undefined}\n ref={forwardedRef}\n >\n {children}\n </div>\n );\n },\n);\n\nThreadContent.displayName = 'Thread.Content';\n\n//\n// Header\n//\n\nexport type ThreadHeaderProps = Omit<\n ComposableProps<{\n /** Snippet text rendered inside a `<p>` (a string, not a DOM element). */\n title?: string;\n detached?: boolean;\n /** Whether this is the current/selected thread; accents the title. */\n current?: boolean;\n /** Trailing controls rendered in the right column (aligns with message-tile controls). */\n controls?: ReactNode;\n /** Invoked when the caret affordance is activated to select/focus this thread. */\n onSelect?: () => void;\n }>,\n 'children'\n>;\n\n/**\n * Thread header row: caret (rail) · snippet (content) · controls. Owns its own\n * `[rail · 1fr · controls]` grid so the trailing controls align with message-tile\n * controls (which use the same template) — no grid is leaked in from the caller.\n */\nconst ThreadHeader = composable<HTMLDivElement, ThreadHeaderProps>(\n ({ title, detached, current, controls, onSelect, ...props }, forwardedRef) => {\n const { t } = useTranslation(translationKey);\n const { focusComposer } = useThreadContext('Thread.Header');\n const handleSelect = useCallback(() => {\n onSelect?.();\n focusComposer();\n }, [onSelect, focusComposer]);\n\n return (\n <div\n {...composableProps(props, {\n classNames: [\n 'grid grid-cols-[var(--dx-rail-size)_1fr_min-content] items-center',\n hoverableControls,\n hoverableFocusedWithinControls,\n ],\n })}\n ref={forwardedRef}\n >\n <div className='flex items-center justify-center'>\n <IconButton\n iconOnly\n variant='ghost'\n density='sm'\n icon='ph--caret-double-right--regular'\n label={t('select-thread.label')}\n classNames='text-description'\n onClick={handleSelect}\n />\n </div>\n <div className='flex items-center overflow-hidden'>\n <p\n role='heading'\n data-testid='thread.heading'\n className={mx(\n 'me-2 font-medium truncate italic',\n current ? 'text-accent-text' : 'text-description',\n detached && 'line-through decoration-1',\n )}\n >\n {title}\n </p>\n </div>\n {controls}\n </div>\n );\n },\n);\n\nThreadHeader.displayName = 'Thread.Header';\n\n//\n// Messages\n//\n\n/** A run of consecutive same-sender messages (within the grouping window) rendered as one tile. */\ntype MessageGroupItem = { kind: 'group'; id: string; messages: MessageType.Message[]; last?: boolean };\n\n/**\n * A divider row between groups: day-labeled (calendar-day boundary) or plain\n * (a same-day gap exceeding `gapDividerMs`). Day boundaries take priority\n * when both coincide on the same message — see `groupMessages`.\n */\ntype DividerItem = { kind: 'divider'; id: string; label?: string };\n\ntype ThreadItem = MessageGroupItem | DividerItem;\n\nconst getThreadItemId = (item: ThreadItem) => item.id;\n\n/** Stable per-sender key: prefers the DID, then the (deprecated) identity key, then name/email, else 'anon'. */\nconst senderKey = (message: MessageType.Message): string =>\n message.sender.identityDid ?? message.sender.identityKey ?? message.sender.name ?? message.sender.email ?? 'anon';\n\n/** Message send time in epoch ms; malformed/missing `created` sorts as if sent at the epoch. */\nconst messageTime = (message: MessageType.Message): number => {\n const time = Date.parse(message.created);\n return Number.isFinite(time) ? time : 0;\n};\n\n/**\n * Preprocesses ascending, time-ordered `messages` into a flat list of divider\n * and group items for `Thread.Messages`'s virtual stack:\n * - A day divider (labeled with the calendar date) precedes the first message\n * of each calendar day.\n * - A plain divider (no label) marks a same-day gap exceeding `gapDividerMs`.\n * - When both would fall on the same message, only the day divider is emitted.\n * - Consecutive messages from the same sender within `groupWindowMs` of the\n * previous message in the group are merged into one `MessageGroupItem`; a\n * divider always starts a new group.\n */\nconst groupMessages = (\n messages: readonly MessageType.Message[],\n {\n groupWindowMs,\n dayDivider,\n gapDividerMs,\n dtLocale,\n }: { groupWindowMs: number; dayDivider: boolean; gapDividerMs: number; dtLocale?: Locale },\n): ThreadItem[] => {\n const items: ThreadItem[] = [];\n let currentGroup: MessageGroupItem | undefined;\n // Tracks the last message appended to `currentGroup`, since `Array.prototype.at(-1)`\n // on `currentGroup.messages` would need a non-null assertion despite `currentGroup`\n // always being non-empty by construction.\n let lastGroupMessage: MessageType.Message | undefined;\n let prevTime: number | undefined;\n let prevDay: number | undefined;\n\n for (const message of messages) {\n const time = messageTime(message);\n const day = startOfDay(time).getTime();\n\n const dayBoundary = dayDivider && (prevDay === undefined || day !== prevDay);\n const gapBoundary = !dayBoundary && prevTime !== undefined && time - prevTime > gapDividerMs;\n\n if (dayBoundary) {\n items.push({\n kind: 'divider',\n id: `divider:day:${day}`,\n label: format(day, 'EEEE, MMMM d', { locale: dtLocale }),\n });\n currentGroup = undefined;\n } else if (gapBoundary) {\n items.push({ kind: 'divider', id: `divider:gap:${time}` });\n currentGroup = undefined;\n }\n\n const sameSenderAsGroup = currentGroup && lastGroupMessage && senderKey(lastGroupMessage) === senderKey(message);\n const withinGroupWindow = currentGroup && lastGroupMessage && time - messageTime(lastGroupMessage) <= groupWindowMs;\n\n if (currentGroup && sameSenderAsGroup && withinGroupWindow) {\n currentGroup.messages.push(message);\n } else {\n currentGroup = { kind: 'group', id: getMessageId(message), messages: [message] };\n items.push(currentGroup);\n }\n lastGroupMessage = message;\n\n prevTime = time;\n prevDay = day;\n }\n\n // The trailing group is the last message tile; suppress its avatar-rail continuation line.\n if (currentGroup) {\n currentGroup.last = true;\n }\n\n return items;\n};\n\n//\n// Divider\n//\n\nconst ThreadDivider = ({ label }: { label?: string }) =>\n label ? (\n <div className='flex items-center gap-2 px-2 py-2 text-xs text-description'>\n <div className='h-px grow bg-separator' />\n <span className='shrink-0'>{label}</span>\n <div className='h-px grow bg-separator' />\n </div>\n ) : (\n <div className='px-2 py-2'>\n <div className='h-px bg-separator' />\n </div>\n );\n\nThreadDivider.displayName = 'Thread.Divider';\n\nconst ThreadItemAdapter = ({ id, data, location, draggable, current, selected }: MosaicTileProps<ThreadItem>) => (\n <Mosaic.Tile id={id} data={data} location={location} draggable={draggable} current={current} selected={selected}>\n {data.kind === 'divider' ? (\n <ThreadDivider label={data.label} />\n ) : (\n <Message.Group messages={data.messages} continues={!data.last} />\n )}\n </Mosaic.Tile>\n);\n\nexport type ThreadMessagesProps = ThemedClassName<{\n messages: readonly MessageType.Message[];\n /** Stable id of the owning thread; scopes the Mosaic container so multiple threads don't collide. */\n id?: string;\n /** Estimated tile height for the virtualizer. */\n estimateSize?: number;\n currentId?: string;\n /** Consecutive same-sender messages within this window (ms) are merged into one tile. */\n groupWindowMs?: number;\n /** Whether to insert a labeled divider before the first message of each calendar day. */\n dayDivider?: boolean;\n /** A same-day gap exceeding this (ms) inserts an unlabeled divider between groups. */\n gapDividerMs?: number;\n}>;\n\nconst DEFAULT_GROUP_WINDOW_MS = 60_000;\nconst DEFAULT_GAP_DIVIDER_MS = 3 * 60 * 60 * 1000;\n\n/** Virtualized stack of message tiles (via Mosaic), within an internal scroll area. */\nconst ThreadMessages = ({\n messages,\n id,\n estimateSize = 80,\n currentId,\n groupWindowMs = DEFAULT_GROUP_WINDOW_MS,\n dayDivider = true,\n gapDividerMs = DEFAULT_GAP_DIVIDER_MS,\n classNames,\n}: ThreadMessagesProps) => {\n const { dtLocale } = useTranslation(translationKey);\n const [viewport, setViewport] = useState<HTMLElement | null>(null);\n const items = useMemo(\n () => groupMessages(messages.filter(Boolean), { groupWindowMs, dayDivider, gapDividerMs, dtLocale }),\n [messages, groupWindowMs, dayDivider, gapDividerMs, dtLocale],\n );\n // Per-instance id keeps concurrent threads (incl. the same thread mounted twice) distinct in the DnD registry.\n const instanceId = useId();\n const eventHandler = useMemo<DndContainerHandler>(\n () => ({ id: `thread:${id ?? 'anon'}:${instanceId}`, canDrop: () => false }),\n [id, instanceId],\n );\n\n return (\n <Mosaic.Container\n asChild\n orientation='vertical'\n autoScroll={viewport}\n currentId={currentId}\n eventHandler={eventHandler}\n >\n <ScrollArea.Root classNames={mx('col-span-2 flex-1 min-h-0', classNames)} orientation='vertical'>\n <ScrollArea.Viewport ref={setViewport}>\n <Mosaic.VirtualStack\n Tile={ThreadItemAdapter}\n items={items}\n getId={getThreadItemId}\n draggable={false}\n getScrollElement={() => viewport}\n estimateSize={() => estimateSize}\n />\n </ScrollArea.Viewport>\n </ScrollArea.Root>\n </Mosaic.Container>\n );\n};\n\nThreadMessages.displayName = 'Thread.Messages';\n\n//\n// Textbox\n//\n\nexport type ThreadTextboxProps = MessageMetadata & {\n placeholder?: string;\n autoFocus?: boolean;\n disabled?: boolean;\n extensions?: Extension;\n /**\n * Called with the composer content on send. Return `true` to accept (the\n * editor is cleared and remounted), `false` to reject.\n */\n onSend?: (text: string) => boolean;\n};\n\n/** Message composer pinned at the foot of a thread. */\nconst ThreadTextbox = ({ placeholder, autoFocus, disabled, extensions, onSend, ...metadata }: ThreadTextboxProps) => {\n const { t } = useTranslation(translationKey);\n const { themeMode } = useThemeContext();\n const { registerComposerFocus } = useThreadContext('Thread.Textbox');\n const composerRef = useRef<{ focus: () => void } | null>(null);\n const messageRef = useRef('');\n const [count, setCount] = useState(0);\n\n // Expose the composer's focus to Thread.Root so the header caret can focus it.\n useEffect(() => {\n registerComposerFocus(() => composerRef.current?.focus());\n return () => registerComposerFocus(undefined);\n }, [registerComposerFocus]);\n\n const editorExtensions = useMemo(\n () =>\n [\n createBasicExtensions({ placeholder: placeholder ?? t('message.placeholder') }),\n createThemeExtensions({ themeMode }),\n listener({ onChange: ({ text }) => (messageRef.current = text) }),\n command,\n extensions,\n ].filter(Boolean) as Extension[],\n [themeMode, placeholder, t, extensions, count],\n );\n\n const handleSend = useCallback(() => {\n const text = messageRef.current;\n if (!text?.length || !onSend) {\n return;\n }\n if (onSend(text)) {\n messageRef.current = '';\n setCount((value) => value + 1);\n }\n }, []);\n\n return (\n <Message.Textbox\n ref={composerRef}\n {...metadata}\n autoFocus={autoFocus}\n disabled={disabled}\n extensions={editorExtensions}\n onSend={handleSend}\n />\n );\n};\n\nThreadTextbox.displayName = 'Thread.Textbox';\n\n//\n// Status\n//\n\nexport type ThreadStatusProps = ThemedClassName<PropsWithChildren> & {\n activity?: boolean;\n};\n\nconst ThreadStatus = forwardRef<HTMLDivElement, ThreadStatusProps>(\n ({ activity, classNames, children, ...props }, forwardedRef) => {\n const { t } = useTranslation(translationKey);\n return (\n <div\n {...props}\n className={mx(\n 'col-start-2 grid grid-cols-[min-content_1fr_max-content] pb-2 pe-2 text-xs text-description',\n classNames,\n )}\n ref={forwardedRef}\n >\n <Icon\n icon='ph--spinner--bold'\n classNames='w-6 h-4 invisible data-[visible=show]:visible animate-spin-slow'\n data-visible={activity ? 'show' : 'hide'}\n />\n <span className='truncate min-w-0' aria-live='polite'>\n {activity ? children : null}\n </span>\n <span className={mx('text-end', hoverableControlItem)}>{t('enter-to-send.message')}</span>\n </div>\n );\n },\n);\n\nThreadStatus.displayName = 'Thread.Status';\n\n//\n// Thread\n//\n\nexport const Thread = {\n Root: ThreadRoot,\n Content: ThreadContent,\n Header: ThreadHeader,\n Messages: ThreadMessages,\n Textbox: ThreadTextbox,\n Status: ThreadStatus,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/command.ts","../../src/context.ts","../../src/Message/Message.tsx","../../src/Thread/Thread.tsx"],"sourcesContent":["//\n// Copyright 2024 DXOS.org\n//\n\nimport { HighlightStyle, StreamLanguage, syntaxHighlighting } from '@codemirror/language';\nimport { type Extension } from '@codemirror/state';\nimport { EditorView } from '@codemirror/view';\n\nimport { tags } from '@dxos/ui-editor';\nimport { mx } from '@dxos/ui-theme';\n\n/**\n * Highlights leading slash-commands (e.g. `/resolve`) and `@mention` tags\n * (e.g. `@Kai`, anywhere) in the message composer.\n * https://github.com/codemirror/stream-parser/blob/main/test/test-stream-parser.ts\n */\nconst parser = StreamLanguage.define<{ count: number }>({\n startState: () => ({ count: 0 }),\n token: (stream, state) => {\n state.count++;\n if (stream.eatSpace()) {\n return null;\n }\n // Leading slash-command (first token only).\n if (state.count === 1 && stream.match(/^\\/\\w+/)) {\n return 'tagName';\n }\n // @mention (anywhere).\n if (stream.match(/^@\\w+/)) {\n return 'labelName';\n }\n stream.next();\n return null;\n },\n});\n\nconst styles = HighlightStyle.define([\n {\n tag: tags.tagName,\n class: mx('dx-tag dx-tag-inline dx-tag-indigo mx-0.5'),\n },\n {\n tag: tags.labelName,\n class: mx('dx-tag dx-tag-inline dx-tag-blue mx-0.5'),\n },\n]);\n\n// Center the inline `.dx-tag` pills on the text line. An inline tag is an inline box\n// with vertical padding, so on a CodeMirror line it baseline-aligns and rides\n// high; overriding `vertical-align` recentres it (cf. the `cm-reference-pill`\n// precedent in `@dxos/react-ui-chat`).\nconst theme = EditorView.theme({\n '.dx-tag': {\n verticalAlign: 'bottom',\n },\n});\n\nexport const command: Extension = [parser, syntaxHighlighting(styles), theme];\n","//\n// Copyright 2026 DXOS.org\n//\n\nimport { type FC, type ReactNode } from 'react';\n\nimport { createContext } from '@dxos/react-hooks';\n// Imported so TypeScript can name this type in the emitted .d.ts (ThreadContext → Message).\n// eslint-disable-next-line unused-imports/no-unused-imports\nimport { type Message } from '@dxos/types';\n\nimport { type ThreadContextValue } from './types.ts';\n\n/**\n * Context provided by `Thread.Root` and consumed by its parts. Extends the\n * consumer-facing {@link ThreadContextValue} with internal coordination (e.g.\n * focusing the composer from the header's select affordance).\n */\nexport type ThreadContext = ThreadContextValue & {\n /** Register the composer's focus handler (set by `Thread.Textbox`). */\n registerComposerFocus: (focus: (() => void) | undefined) => void;\n /** Focus the thread composer (used by `Thread.Header`'s select affordance). */\n focusComposer: () => void;\n};\n\nexport const [ThreadContextProvider, useThreadContext]: readonly [\n FC<ThreadContext & { children: ReactNode }>,\n (consumerName: string) => ThreadContext,\n] = createContext<ThreadContext>('Thread');\n","//\n// Copyright 2023 DXOS.org\n//\n\nimport { EditorView } from '@codemirror/view';\nimport { format } from 'date-fns/format';\nimport React, {\n type ComponentPropsWithoutRef,\n type ComponentPropsWithRef,\n type ReactNode,\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from 'react';\n\nimport { Obj, Ref } from '@dxos/echo';\nimport { type UseTextEditorProps, useTextEditor } from '@dxos/react-ui-editor';\nimport * as Avatar from '@dxos/react-ui/Avatar';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport type * as Util from '@dxos/react-ui/Util';\nimport { type ContentBlock, type Message as MessageType } from '@dxos/types';\nimport { createBasicExtensions, createThemeExtensions, keymap, listener } from '@dxos/ui-editor';\nimport { hoverableControlItem, hoverableControls, hoverableFocusedWithinControls, mx } from '@dxos/ui-theme';\nimport { hexToEmoji, hexToHue, isTruthy } from '@dxos/util';\n\nimport { translationKey } from '#translations';\n\nimport { command } from '../command.ts';\nimport { useThreadContext } from '../context.ts';\nimport { type MessageMetadata } from '../types.ts';\n\nconst avatarSize = 'md';\n\nconst buttonGroupClassNames = 'flex flex-row items-center gap-0.5 pe-2';\nconst buttonClassNames = 'p-1! transition-opacity';\n\n//\n// Root\n//\n\nexport type MessageRootProps = Util.ThemedClassName<\n ComponentPropsWithRef<'div'> & MessageMetadata & Partial<{ continues: boolean; controls: ReactNode }>\n>;\n\n// TODO(burdon): Show authorName on tooltip.\nconst MessageRoot = forwardRef<HTMLDivElement, MessageRootProps>(\n (\n {\n authorImgSrc,\n authorId,\n authorName,\n authorAvatarProps,\n continues = true,\n controls,\n children,\n classNames,\n ...rootProps\n },\n forwardedRef,\n ) => {\n // Columns mirror Thread.Header (avatar/rail · content · controls) so trailing\n // controls align with the thread header's controls.\n return (\n <div\n data-testid='thread.message'\n {...rootProps}\n className={mx('grid grid-cols-[var(--dx-rail-size)_1fr_min-content] w-full', classNames)}\n ref={forwardedRef}\n >\n <div className='flex flex-col items-center gap-2 pt-1'>\n <Avatar.Root\n size={avatarSize}\n hue={authorAvatarProps?.hue || hexToHue(authorId ?? '0')}\n fallback={authorAvatarProps?.emoji || hexToEmoji(authorId ?? '0')}\n src={authorImgSrc}\n label={authorName}\n />\n {continues && <div className='w-px grow bg-separator' />}\n </div>\n <div className='py-1 min-w-0'>{children}</div>\n {controls && <div className='self-start'>{controls}</div>}\n </div>\n );\n },\n);\n\nMessageRoot.displayName = 'Message.Root';\n\n//\n// Heading\n//\n\nexport type MessageHeadingProps = Util.ThemedClassName<ComponentPropsWithoutRef<'div'>> &\n Pick<MessageMetadata, 'authorName' | 'timestamp'>;\n\nconst MessageHeading = ({ children, classNames, timestamp, authorName, ...props }: MessageHeadingProps) => {\n return (\n <div {...props} className={mx('flex gap-2 items-start', classNames)}>\n <p className='grow flex items-baseline gap-2 min-w-0'>\n <MessageAuthorName authorName={authorName} />\n {timestamp && <MessageTime timestamp={timestamp} />}\n </p>\n {children}\n </div>\n );\n};\n\nMessageHeading.displayName = 'Message.Heading';\n\nexport type MessageAuthorNameProps = Pick<MessageMetadata, 'authorName'>;\n\nconst MessageAuthorName = ({ authorName }: MessageAuthorNameProps) => {\n const { t } = Hooks.useTranslation(translationKey);\n return (\n <span className='block truncate min-w-0 shrink text-sm text-fg-subtle'>{authorName ?? t('anonymous.label')}</span>\n );\n};\n\nMessageAuthorName.displayName = 'Message.AuthorName';\n\nexport type MessageTimeProps = Pick<MessageMetadata, 'timestamp'>;\n\nconst MessageTime = ({ timestamp }: MessageTimeProps) => {\n const { dtLocale } = Hooks.useTranslation(translationKey);\n const dt = timestamp ? new Date(timestamp) : undefined;\n return (\n <time className='shrink-0 text-fg-subtle text-xs' dateTime={dt?.toISOString()}>\n {dt ? format(dt, 'p', { locale: dtLocale }) : ''}\n </time>\n );\n};\n\nMessageTime.displayName = 'Message.Time';\n\n//\n// Body\n//\n\nexport type MessageBodyProps = {\n /** The message whose content blocks are rendered. */\n message: MessageType.Message;\n /** When true, the local user authored this message. */\n isAuthor?: boolean;\n /** When true, the text block is editable. */\n editing?: boolean;\n /** Persist an edited text block. */\n onSave?: (text: string) => void;\n};\n\n/**\n * Renders a message's content blocks: text (via editor), proposal, and\n * object/reference tiles (delegated to the injected `Object` component).\n */\nconst MessageBody = ({ message, isAuthor, editing, onSave }: MessageBodyProps) => {\n const { components } = useThreadContext('Message.Body');\n const textBlockIndex = message.blocks.findIndex((block) => block._tag === 'text');\n const textBlock = textBlockIndex !== -1 ? (message.blocks[textBlockIndex] as ContentBlock.Text) : undefined;\n const proposalBlock = message.blocks.find((block) => block._tag === 'proposal') as ContentBlock.Proposal | undefined;\n const changeBlock = message.blocks.find((block) => block._tag === 'change') as ContentBlock.Change | undefined;\n const references = message.blocks\n .filter((block) => block._tag === 'reference')\n .map((block) => (block as ContentBlock.Reference).reference);\n const Object = components.Object;\n\n return (\n <>\n {textBlock && <TextBlock block={textBlock} isAuthor={isAuthor} editing={editing} onSave={onSave} />}\n {proposalBlock && <div className='me-4 italic'>{proposalBlock.text}</div>}\n {changeBlock && (\n <p className='me-4 text-sm break-words'>\n {changeBlock.before && <span className='line-through opacity-60'>{changeBlock.before}</span>}\n {changeBlock.before && changeBlock.after && ' '}\n {changeBlock.after && <span>{changeBlock.after}</span>}\n </p>\n )}\n {Object &&\n Ref.Array.targets(references).map((reference, index) => (\n <Object key={index} subject={reference as Obj.Unknown} />\n ))}\n </>\n );\n};\n\nMessageBody.displayName = 'Message.Body';\n\nconst TextBlock = ({\n block,\n isAuthor,\n editing,\n onSave,\n}: {\n block: ContentBlock.Text;\n isAuthor?: boolean;\n editing?: boolean;\n onSave?: (text: string) => void;\n}) => {\n const themeMode = Hooks.useThemeMode();\n const inMemoryContentRef = useRef(block.text);\n\n const handleDocumentChange = useCallback((next: string) => {\n inMemoryContentRef.current = next;\n }, []);\n\n const saveDocumentChange = useCallback(() => {\n onSave?.(inMemoryContentRef.current);\n }, [onSave]);\n\n Hooks.useOnTransition(editing, true, false, saveDocumentChange);\n\n const { parentRef, focusAttributes, view } = useTextEditor(\n () => ({\n initialValue: block.text,\n extensions: [\n // Edit mode is authorisation enough — the control that gets here is already gated on\n // `isAuthor` — and a mid-edit flip to read-only makes the editor drop input silently.\n createBasicExtensions({ readOnly: !editing }),\n createThemeExtensions({ themeMode }),\n command,\n EditorView.updateListener.of((update) => {\n if (update.docChanged) {\n handleDocumentChange(update.state.doc.toString());\n }\n }),\n ],\n }),\n // While editing, the editor owns its content and its authorisation: pinning both keeps an incoming\n // `block.text` update or member-list refresh from rebuilding the view being typed in. `editing` is\n // itself a dep, so the flip still rebuilds.\n [editing, editing ? undefined : block.text, editing ? undefined : isAuthor, themeMode, handleDocumentChange],\n );\n\n useEffect(() => {\n if (editing) {\n view?.focus();\n }\n }, [editing, view]);\n\n return <div ref={parentRef} className='me-4' {...focusAttributes} />;\n};\n\n//\n// Textbox\n//\n\nexport type MessageTextboxProps = {\n disabled?: boolean;\n onSend?: () => void;\n onClear?: () => void;\n onEditorFocus?: () => void;\n} & MessageMetadata &\n UseTextEditorProps;\n\nconst keyBindings = ({ onSend, onClear }: Pick<MessageTextboxProps, 'onSend' | 'onClear'>) => [\n {\n key: 'Enter',\n run: () => {\n if (onSend) {\n onSend();\n return true;\n }\n return false;\n },\n },\n {\n key: 'Meta+Backspace',\n run: () => {\n if (onClear) {\n onClear();\n return true;\n }\n return false;\n },\n },\n];\n\nexport type MessageTextboxHandle = { focus: () => void };\n\nconst MessageTextbox = forwardRef<MessageTextboxHandle, MessageTextboxProps>(\n (\n {\n id,\n authorId,\n authorName,\n authorImgSrc,\n authorAvatarProps,\n disabled,\n extensions,\n onSend,\n onClear,\n onEditorFocus,\n ...editorProps\n },\n forwardedRef,\n ) => {\n const { parentRef, focusAttributes, view } = useTextEditor(\n () => ({\n id,\n extensions: [\n keymap.of(keyBindings({ onSend, onClear })),\n listener({\n onFocus: ({ focusing }) => {\n if (focusing) {\n onEditorFocus?.();\n }\n },\n }),\n extensions,\n ].filter(isTruthy),\n ...editorProps,\n }),\n [id, extensions],\n );\n\n useImperativeHandle(forwardedRef, () => ({ focus: () => view?.focus() }), [view]);\n\n return (\n <MessageRoot {...{ id, authorId, authorName, authorImgSrc, authorAvatarProps }} continues={false}>\n {/*\n Addressable: the reply composer is the only textbox in a thread that is not a message body,\n so without a testid a test can only guess at it by editor order.\n */}\n <div\n ref={parentRef}\n data-testid='thread.reply'\n className={mx('py-0.5 me-1 rounded-xs dx-focus-ring', disabled && 'opacity-50')}\n {...focusAttributes}\n />\n </MessageRoot>\n );\n },\n);\n\nMessageTextbox.displayName = 'Message.Textbox';\n\n//\n// Tile\n//\n\nexport type MessageTileProps = {\n message: MessageType.Message;\n classNames?: MessageRootProps['classNames'];\n /** Whether the avatar-rail continuation line is drawn below the tile; false for the last tile. */\n continues?: boolean;\n};\n\n/**\n * Full message tile (frame + heading + controls + body). Reads `Thread.Root`\n * context for metadata resolution, injected renderers, and callbacks. This is\n * the unit rendered by `Thread.Messages`.\n */\nconst MessageTile = ({ message, classNames, continues = true }: MessageTileProps) => {\n const { t } = Hooks.useTranslation(translationKey);\n const {\n getMetadata,\n identityDid,\n editable,\n onMessageDelete,\n onAcceptProposal,\n onAcceptChange,\n onRejectChange,\n onMessageSelect,\n currentMessageId,\n } = useThreadContext('Message.Tile');\n const [editing, setEditing] = useState(false);\n\n const metadata = getMetadata(message);\n const isAuthor = !!identityDid && identityDid === metadata.authorId;\n const hasProposal = message.blocks.some((block) => block._tag === 'proposal');\n const hasChange = message.blocks.some((block) => block._tag === 'change');\n\n const handleEdit = useCallback(() => setEditing((value) => !value), []);\n const handleDelete = useCallback(() => onMessageDelete?.(message.id), [onMessageDelete, message.id]);\n const handleAcceptProposal = useCallback(() => onAcceptProposal?.(message.id), [onAcceptProposal, message.id]);\n const handleAcceptChange = useCallback(() => onAcceptChange?.(message.id), [onAcceptChange, message.id]);\n const handleRejectChange = useCallback(() => onRejectChange?.(message.id), [onRejectChange, message.id]);\n const handleSelect = useCallback(() => onMessageSelect?.(message.id), [onMessageSelect, message.id]);\n const handleSave = useCallback(\n (text: string) => {\n Obj.update(message, (message) => {\n const block = message.blocks.find((block) => block._tag === 'text');\n if (block && block._tag === 'text') {\n block.text = text;\n }\n });\n },\n [message],\n );\n\n const showEdit = isAuthor && editable;\n const showAccept = hasProposal && !!onAcceptProposal;\n const showAcceptChange = hasChange && !!onAcceptChange;\n const showRejectChange = hasChange && !!onRejectChange;\n const showDelete = !!onMessageDelete;\n const controls =\n showEdit || showAccept || showAcceptChange || showRejectChange || showDelete ? (\n <div className={buttonGroupClassNames}>\n {showEdit && (\n <Button.Root\n data-testid={editing ? 'thread.message.save' : 'thread.message.edit'}\n variant='ghost'\n icon={editing ? 'ph--check--regular' : 'ph--pencil-simple--regular'}\n iconOnly\n label={t(editing ? 'save-message.label' : 'edit-message.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleEdit}\n />\n )}\n {showAccept && (\n <Button.Root\n data-testid='thread.message.accept'\n variant='ghost'\n icon='ph--check--regular'\n iconOnly\n label={t('accept-proposal.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleAcceptProposal}\n />\n )}\n {showAcceptChange && (\n <Button.Root\n data-testid='thread.message.accept-change'\n variant='ghost'\n icon='ph--check--regular'\n iconOnly\n label={t('accept-change.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleAcceptChange}\n />\n )}\n {showRejectChange && (\n <Button.Root\n data-testid='thread.message.reject-change'\n variant='ghost'\n icon='ph--x--regular'\n iconOnly\n label={t('reject-change.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleRejectChange}\n />\n )}\n {showDelete && (\n <Button.Root\n data-testid='thread.message.delete'\n variant='ghost'\n icon='ph--x--regular'\n iconOnly\n label={t('delete-message.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleDelete}\n />\n )}\n </div>\n ) : undefined;\n\n return (\n <MessageRoot\n {...metadata}\n continues={continues}\n controls={controls}\n // Selecting a tile is how the host reveals what it refers to (a suggestion's range in the\n // document), so the whole tile is the target — the accent marks which one is showing.\n onClick={onMessageSelect ? handleSelect : undefined}\n aria-current={currentMessageId === message.id ? 'location' : undefined}\n classNames={[\n hoverableControls,\n hoverableFocusedWithinControls,\n onMessageSelect && 'cursor-pointer',\n currentMessageId === message.id && 'bg-current-surface',\n classNames,\n ]}\n >\n <MessageHeading authorName={metadata.authorName} timestamp={metadata.timestamp} />\n <MessageBody message={message} isAuthor={isAuthor} editing={editing} onSave={handleSave} />\n </MessageRoot>\n );\n};\n\nMessageTile.displayName = 'Message.Tile';\n\n//\n// Group\n//\n\nexport type MessageGroupProps = {\n /** Consecutive same-sender messages, in ascending time order, rendered as one tile. */\n messages: readonly MessageType.Message[];\n /** Whether the avatar-rail continuation line is drawn below the tile; false for the last tile. */\n continues?: boolean;\n classNames?: MessageRootProps['classNames'];\n};\n\n/**\n * Groups consecutive same-sender messages (see `Thread.Messages`'s grouping\n * window) into a single tile: one heading (author + first message's\n * timestamp) followed by one body per message, stacked in order. Per-message\n * edit/delete controls are not shown per-row in a group — v1 shows them for\n * the group's first message only, since the heading/controls layout is keyed\n * to a single message.\n */\nconst MessageGroup = ({ messages, continues = true, classNames }: MessageGroupProps) => {\n const { t } = Hooks.useTranslation(translationKey);\n const { getMetadata, identityDid, editable, onMessageDelete, onAcceptProposal } = useThreadContext('Message.Group');\n const [editing, setEditing] = useState(false);\n\n const first = messages[0];\n const metadata = getMetadata(first);\n const isAuthor = !!identityDid && identityDid === metadata.authorId;\n const hasProposal = first.blocks.some((block) => block._tag === 'proposal');\n\n const handleEdit = useCallback(() => setEditing((value) => !value), []);\n const handleDelete = useCallback(() => onMessageDelete?.(first.id), [onMessageDelete, first.id]);\n const handleAcceptProposal = useCallback(() => onAcceptProposal?.(first.id), [onAcceptProposal, first.id]);\n const handleSave = useCallback(\n (text: string) => {\n Obj.update(first, (first) => {\n const block = first.blocks.find((block) => block._tag === 'text');\n if (block && block._tag === 'text') {\n block.text = text;\n }\n });\n },\n [first],\n );\n\n const showEdit = isAuthor && editable;\n const showAccept = hasProposal && !!onAcceptProposal;\n const showDelete = !!onMessageDelete;\n const controls =\n showEdit || showAccept || showDelete ? (\n <div className={buttonGroupClassNames}>\n {showEdit && (\n <Button.Root\n data-testid={editing ? 'thread.message.save' : 'thread.message.edit'}\n variant='ghost'\n icon={editing ? 'ph--check--regular' : 'ph--pencil-simple--regular'}\n iconOnly\n label={t(editing ? 'save-message.label' : 'edit-message.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleEdit}\n />\n )}\n {showAccept && (\n <Button.Root\n data-testid='thread.message.accept'\n variant='ghost'\n icon='ph--check--regular'\n iconOnly\n label={t('accept-proposal.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleAcceptProposal}\n />\n )}\n {showDelete && (\n <Button.Root\n data-testid='thread.message.delete'\n variant='ghost'\n icon='ph--x--regular'\n iconOnly\n label={t('delete-message.label')}\n classNames={[buttonClassNames, hoverableControlItem]}\n onClick={handleDelete}\n />\n )}\n </div>\n ) : undefined;\n\n return (\n <MessageRoot\n {...metadata}\n continues={continues}\n controls={controls}\n classNames={[hoverableControls, hoverableFocusedWithinControls, classNames]}\n >\n <MessageHeading authorName={metadata.authorName} timestamp={metadata.timestamp} />\n {messages.map((message) => (\n <MessageBody\n key={message.id}\n message={message}\n isAuthor={isAuthor}\n editing={editing && message === first}\n onSave={handleSave}\n />\n ))}\n </MessageRoot>\n );\n};\n\nMessageGroup.displayName = 'Message.Group';\n\n//\n// Message\n//\n\nexport const Message = {\n Root: MessageRoot,\n Heading: MessageHeading,\n AuthorName: MessageAuthorName,\n Time: MessageTime,\n Body: MessageBody,\n Textbox: MessageTextbox,\n Tile: MessageTile,\n Group: MessageGroup,\n};\n","//\n// Copyright 2023 DXOS.org\n//\n\nimport { format } from 'date-fns/format';\nimport { type Locale } from 'date-fns/locale';\nimport { startOfDay } from 'date-fns/startOfDay';\nimport React, {\n type ComponentPropsWithRef,\n type PropsWithChildren,\n type ReactNode,\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { Obj } from '@dxos/echo';\nimport { type DndContainerHandler } from '@dxos/react-ui-dnd';\nimport { Mosaic, type MosaicTileProps } from '@dxos/react-ui-mosaic';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Icon from '@dxos/react-ui/Icon';\nimport * as ScrollArea from '@dxos/react-ui/ScrollArea';\nimport * as Util from '@dxos/react-ui/Util';\nimport { type Message as MessageType } from '@dxos/types';\nimport { type Extension, createBasicExtensions, createThemeExtensions, listener } from '@dxos/ui-editor';\nimport { hoverableControlItem, hoverableControls, hoverableFocusedWithinControls, mx } from '@dxos/ui-theme';\nimport { type ComposableProps } from '@dxos/ui-types';\n\nimport { translationKey } from '#translations';\n\nimport { command } from '../command.ts';\nimport { ThreadContextProvider, useThreadContext } from '../context.ts';\nimport { Message } from '../Message/index.ts';\nimport { type MessageMetadata, type ThreadContextValue } from '../types.ts';\n\n//\n// Root\n//\n\nexport type ThreadRootProps = PropsWithChildren<\n Omit<ThreadContextValue, 'components'> & Partial<Pick<ThreadContextValue, 'components'>>\n>;\n\n/**\n * Headless root of a thread. Provides message-tile context (metadata resolver,\n * injected renderers, callbacks). Requires an ambient `Dnd.Root` ancestor,\n * within which `Thread.Messages` renders its virtual stack. Renders no DOM of\n * its own — wrap the visible thread chrome in `Thread.Content`.\n */\nconst ThreadRoot = ({\n children,\n getMetadata,\n components,\n identityDid,\n editable,\n onMessageDelete,\n onAcceptProposal,\n onAcceptChange,\n onRejectChange,\n onMessageSelect,\n currentMessageId,\n}: ThreadRootProps) => {\n // Composer focus handler registered by Thread.Textbox; invoked by Thread.Header's caret.\n const composerFocus = useRef<(() => void) | undefined>(undefined);\n\n const registerComposerFocus = useCallback((focus: (() => void) | undefined) => {\n composerFocus.current = focus;\n }, []);\n\n const focusComposer = useCallback(() => composerFocus.current?.(), []);\n\n return (\n <ThreadContextProvider\n getMetadata={getMetadata}\n components={components ?? {}}\n identityDid={identityDid}\n editable={editable}\n registerComposerFocus={registerComposerFocus}\n focusComposer={focusComposer}\n onMessageDelete={onMessageDelete}\n onAcceptProposal={onAcceptProposal}\n onAcceptChange={onAcceptChange}\n onRejectChange={onRejectChange}\n onMessageSelect={onMessageSelect}\n currentMessageId={currentMessageId}\n >\n {children}\n </ThreadContextProvider>\n );\n};\n\nThreadRoot.displayName = 'Thread.Root';\n\n//\n// Content\n//\n\ntype ThreadContentExtra = {\n id?: string;\n current?: boolean | string;\n} & Pick<ComponentPropsWithRef<'div'>, 'onClickCapture' | 'onFocusCapture'>;\n\nexport type ThreadContentProps = ComposableProps<ThreadContentExtra>;\n\n/** Visible thread container (the attention surface that hosts header / messages / composer). */\nconst ThreadContent = Util.composable<HTMLDivElement, ThreadContentExtra>(\n ({ children, current, id, ...props }, forwardedRef) => {\n return (\n <div\n {...Util.composableProps(props, {\n role: 'group',\n classNames: [\n 'flex flex-col bg-[var(--surface-bg)] current-related dx-attention-surface [--controls-opacity:0]',\n hoverableFocusedWithinControls,\n ],\n })}\n data-testid='thread'\n id={id}\n aria-current={current ? 'location' : undefined}\n ref={forwardedRef}\n >\n {children}\n </div>\n );\n },\n);\n\nThreadContent.displayName = 'Thread.Content';\n\n//\n// Header\n//\n\nexport type ThreadHeaderProps = Omit<\n ComposableProps<{\n /** Snippet text rendered inside a `<p>` (a string, not a DOM element). */\n title?: string;\n detached?: boolean;\n /** Whether this is the current/selected thread; accents the title. */\n current?: boolean;\n /** Trailing controls rendered in the right column (aligns with message-tile controls). */\n controls?: ReactNode;\n /** Invoked when the caret affordance is activated to select/focus this thread. */\n onSelect?: () => void;\n }>,\n 'children'\n>;\n\n/**\n * Thread header row: caret (rail) · snippet (content) · controls. Owns its own\n * `[rail · 1fr · controls]` grid so the trailing controls align with message-tile\n * controls (which use the same template) — no grid is leaked in from the caller.\n */\nconst ThreadHeader = Util.composable<HTMLDivElement, ThreadHeaderProps>(\n ({ title, detached, current, controls, onSelect, ...props }, forwardedRef) => {\n const { t } = Hooks.useTranslation(translationKey);\n const { focusComposer } = useThreadContext('Thread.Header');\n const handleSelect = useCallback(() => {\n onSelect?.();\n focusComposer();\n }, [onSelect, focusComposer]);\n\n return (\n <div\n {...Util.composableProps(props, {\n classNames: [\n 'grid grid-cols-[var(--dx-rail-size)_1fr_min-content] items-center',\n hoverableControls,\n hoverableFocusedWithinControls,\n ],\n })}\n ref={forwardedRef}\n >\n <div className='flex items-center justify-center'>\n <Button.Root\n iconOnly\n variant='ghost'\n size='sm'\n icon='ph--caret-double-right--regular'\n label={t('select-thread.label')}\n classNames='text-fg-muted'\n onClick={handleSelect}\n />\n </div>\n <div className='flex items-center overflow-hidden'>\n <p\n role='heading'\n data-testid='thread.heading'\n className={mx(\n 'me-2 font-medium truncate italic',\n current ? 'text-accent-text' : 'text-fg-muted',\n detached && 'line-through decoration-1',\n )}\n >\n {title}\n </p>\n </div>\n {controls}\n </div>\n );\n },\n);\n\nThreadHeader.displayName = 'Thread.Header';\n\n//\n// Messages\n//\n\n/** A run of consecutive same-sender messages (within the grouping window) rendered as one tile. */\ntype MessageGroupItem = { kind: 'group'; id: string; messages: MessageType.Message[]; last?: boolean };\n\n/**\n * A divider row between groups: day-labeled (calendar-day boundary) or plain\n * (a same-day gap exceeding `gapDividerMs`). Day boundaries take priority\n * when both coincide on the same message — see `groupMessages`.\n */\ntype DividerItem = { kind: 'divider'; id: string; label?: string };\n\ntype ThreadItem = MessageGroupItem | DividerItem;\n\nconst getThreadItemId = (item: ThreadItem) => item.id;\n\n/** Stable per-sender key: prefers the DID, then the (deprecated) identity key, then name/email, else 'anon'. */\nconst senderKey = (message: MessageType.Message): string =>\n message.sender.identityDid ?? message.sender.identityKey ?? message.sender.name ?? message.sender.email ?? 'anon';\n\n/** Message send time in epoch ms; malformed/missing `created` sorts as if sent at the epoch. */\nconst messageTime = (message: MessageType.Message): number => {\n const time = Date.parse(message.created);\n return Number.isFinite(time) ? time : 0;\n};\n\n/**\n * Preprocesses ascending, time-ordered `messages` into a flat list of divider\n * and group items for `Thread.Messages`'s virtual stack:\n * - A day divider (labeled with the calendar date) precedes the first message\n * of each calendar day.\n * - A plain divider (no label) marks a same-day gap exceeding `gapDividerMs`.\n * - When both would fall on the same message, only the day divider is emitted.\n * - Consecutive messages from the same sender within `groupWindowMs` of the\n * previous message in the group are merged into one `MessageGroupItem`; a\n * divider always starts a new group.\n */\nconst groupMessages = (\n messages: readonly MessageType.Message[],\n {\n groupWindowMs,\n dayDivider,\n gapDividerMs,\n dtLocale,\n }: { groupWindowMs: number; dayDivider: boolean; gapDividerMs: number; dtLocale?: Locale },\n): ThreadItem[] => {\n const items: ThreadItem[] = [];\n let currentGroup: MessageGroupItem | undefined;\n // Tracks the last message appended to `currentGroup`, since `Array.prototype.at(-1)`\n // on `currentGroup.messages` would need a non-null assertion despite `currentGroup`\n // always being non-empty by construction.\n let lastGroupMessage: MessageType.Message | undefined;\n let prevTime: number | undefined;\n let prevDay: number | undefined;\n\n for (const message of messages) {\n const time = messageTime(message);\n const day = startOfDay(time).getTime();\n\n const dayBoundary = dayDivider && (prevDay === undefined || day !== prevDay);\n const gapBoundary = !dayBoundary && prevTime !== undefined && time - prevTime > gapDividerMs;\n\n if (dayBoundary) {\n items.push({\n kind: 'divider',\n id: `divider:day:${day}`,\n label: format(day, 'EEEE, MMMM d', { locale: dtLocale }),\n });\n currentGroup = undefined;\n } else if (gapBoundary) {\n items.push({ kind: 'divider', id: `divider:gap:${time}` });\n currentGroup = undefined;\n }\n\n const sameSenderAsGroup = currentGroup && lastGroupMessage && senderKey(lastGroupMessage) === senderKey(message);\n const withinGroupWindow = currentGroup && lastGroupMessage && time - messageTime(lastGroupMessage) <= groupWindowMs;\n\n if (currentGroup && sameSenderAsGroup && withinGroupWindow) {\n currentGroup.messages.push(message);\n } else {\n currentGroup = { kind: 'group', id: Obj.getURI(message), messages: [message] };\n items.push(currentGroup);\n }\n lastGroupMessage = message;\n\n prevTime = time;\n prevDay = day;\n }\n\n // The trailing group is the last message tile; suppress its avatar-rail continuation line.\n if (currentGroup) {\n currentGroup.last = true;\n }\n\n return items;\n};\n\n//\n// Divider\n//\n\nconst ThreadDivider = ({ label }: { label?: string }) =>\n label ? (\n <div className='flex items-center gap-2 px-2 py-2 text-xs text-fg-muted'>\n <div className='h-px grow bg-separator' />\n <span className='shrink-0'>{label}</span>\n <div className='h-px grow bg-separator' />\n </div>\n ) : (\n <div className='px-2 py-2'>\n <div className='h-px bg-separator' />\n </div>\n );\n\nThreadDivider.displayName = 'Thread.Divider';\n\nconst ThreadItemAdapter = ({ id, data, location, draggable, current, selected }: MosaicTileProps<ThreadItem>) => (\n <Mosaic.Tile id={id} data={data} location={location} draggable={draggable} current={current} selected={selected}>\n {data.kind === 'divider' ? (\n <ThreadDivider label={data.label} />\n ) : (\n <Message.Group messages={data.messages} continues={!data.last} />\n )}\n </Mosaic.Tile>\n);\n\nexport type ThreadMessagesProps = Util.ThemedClassName<{\n messages: readonly MessageType.Message[];\n /** Stable id of the owning thread; scopes the Mosaic container so multiple threads don't collide. */\n id?: string;\n /** Estimated tile height for the virtualizer. */\n estimateSize?: number;\n currentId?: string;\n /** Consecutive same-sender messages within this window (ms) are merged into one tile. */\n groupWindowMs?: number;\n /** Whether to insert a labeled divider before the first message of each calendar day. */\n dayDivider?: boolean;\n /** A same-day gap exceeding this (ms) inserts an unlabeled divider between groups. */\n gapDividerMs?: number;\n}>;\n\nconst DEFAULT_GROUP_WINDOW_MS = 60_000;\nconst DEFAULT_GAP_DIVIDER_MS = 3 * 60 * 60 * 1000;\n\n/** Virtualized stack of message tiles (via Mosaic), within an internal scroll area. */\nconst ThreadMessages = ({\n messages,\n id,\n estimateSize = 80,\n currentId,\n groupWindowMs = DEFAULT_GROUP_WINDOW_MS,\n dayDivider = true,\n gapDividerMs = DEFAULT_GAP_DIVIDER_MS,\n classNames,\n}: ThreadMessagesProps) => {\n const { dtLocale } = Hooks.useTranslation(translationKey);\n const [viewport, setViewport] = useState<HTMLElement | null>(null);\n const items = useMemo(\n () => groupMessages(messages.filter(Boolean), { groupWindowMs, dayDivider, gapDividerMs, dtLocale }),\n [messages, groupWindowMs, dayDivider, gapDividerMs, dtLocale],\n );\n // Per-instance id keeps concurrent threads (incl. the same thread mounted twice) distinct in the DnD registry.\n const instanceId = useId();\n const eventHandler = useMemo<DndContainerHandler>(\n () => ({ id: `thread:${id ?? 'anon'}:${instanceId}`, canDrop: () => false }),\n [id, instanceId],\n );\n\n return (\n <Mosaic.Container\n asChild\n orientation='vertical'\n autoScroll={viewport}\n currentId={currentId}\n eventHandler={eventHandler}\n >\n <ScrollArea.Root classNames={mx('col-span-2 dx-grow', classNames)} orientation='vertical'>\n <ScrollArea.Viewport ref={setViewport}>\n <Mosaic.VirtualStack\n Tile={ThreadItemAdapter}\n items={items}\n getId={getThreadItemId}\n draggable={false}\n getScrollElement={() => viewport}\n estimateSize={() => estimateSize}\n />\n </ScrollArea.Viewport>\n </ScrollArea.Root>\n </Mosaic.Container>\n );\n};\n\nThreadMessages.displayName = 'Thread.Messages';\n\n//\n// Textbox\n//\n\nexport type ThreadTextboxProps = MessageMetadata & {\n placeholder?: string;\n autoFocus?: boolean;\n disabled?: boolean;\n extensions?: Extension;\n /**\n * Called with the composer content on send. Return `true` to accept (the\n * editor is cleared and remounted), `false` to reject.\n */\n onSend?: (text: string) => boolean;\n};\n\n/** Message composer pinned at the foot of a thread. */\nconst ThreadTextbox = ({ placeholder, autoFocus, disabled, extensions, onSend, ...metadata }: ThreadTextboxProps) => {\n const { t } = Hooks.useTranslation(translationKey);\n const themeMode = Hooks.useThemeMode();\n const { registerComposerFocus } = useThreadContext('Thread.Textbox');\n const composerRef = useRef<{ focus: () => void } | null>(null);\n const messageRef = useRef('');\n const [count, setCount] = useState(0);\n\n // Expose the composer's focus to Thread.Root so the header caret can focus it.\n useEffect(() => {\n registerComposerFocus(() => composerRef.current?.focus());\n return () => registerComposerFocus(undefined);\n }, [registerComposerFocus]);\n\n const editorExtensions = useMemo(\n () =>\n [\n createBasicExtensions({ placeholder: placeholder ?? t('message.placeholder') }),\n createThemeExtensions({ themeMode }),\n listener({ onChange: ({ text }) => (messageRef.current = text) }),\n command,\n extensions,\n ].filter(Boolean) as Extension[],\n [themeMode, placeholder, t, extensions, count],\n );\n\n const handleSend = useCallback(() => {\n const text = messageRef.current;\n if (!text?.length || !onSend) {\n return;\n }\n if (onSend(text)) {\n messageRef.current = '';\n setCount((value) => value + 1);\n }\n }, []);\n\n return (\n <Message.Textbox\n ref={composerRef}\n {...metadata}\n autoFocus={autoFocus}\n disabled={disabled}\n extensions={editorExtensions}\n onSend={handleSend}\n />\n );\n};\n\nThreadTextbox.displayName = 'Thread.Textbox';\n\n//\n// Status\n//\n\nexport type ThreadStatusProps = Util.ThemedClassName<PropsWithChildren> & {\n activity?: boolean;\n};\n\nconst ThreadStatus = forwardRef<HTMLDivElement, ThreadStatusProps>(\n ({ activity, classNames, children, ...props }, forwardedRef) => {\n const { t } = Hooks.useTranslation(translationKey);\n return (\n <div\n {...props}\n className={mx(\n 'col-start-2 grid grid-cols-[min-content_1fr_max-content] pb-2 pe-2 text-xs text-fg-muted',\n classNames,\n )}\n ref={forwardedRef}\n >\n <Icon.Icon\n icon='ph--spinner--bold'\n classNames='w-6 h-4 invisible data-[visible=show]:visible animate-spin-slow'\n data-visible={activity ? 'show' : 'hide'}\n />\n <span className='truncate min-w-0' aria-live='polite'>\n {activity ? children : null}\n </span>\n <span className={mx('text-end', hoverableControlItem)}>{t('enter-to-send.message')}</span>\n </div>\n );\n },\n);\n\nThreadStatus.displayName = 'Thread.Status';\n\n//\n// Thread\n//\n\nexport const Thread = {\n Root: ThreadRoot,\n Content: ThreadContent,\n Header: ThreadHeader,\n Messages: ThreadMessages,\n Textbox: ThreadTextbox,\n Status: ThreadStatus,\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translations.mjs","names":[],"sources":["../../src/translations.ts"],"sourcesContent":["//\n// Copyright 2023 DXOS.org\n//\n\nimport
|
|
1
|
+
{"version":3,"file":"translations.mjs","names":[],"sources":["../../src/translations.ts"],"sourcesContent":["//\n// Copyright 2023 DXOS.org\n//\n\nimport type * as Theme from '@dxos/react-ui/Theme';\n\nexport const translationKey = '@dxos/react-ui-thread';\n\nexport const translations = [\n {\n 'en-US': {\n [translationKey]: {\n 'plugin.name': 'Threads',\n 'thread-title.placeholder': 'New thread',\n 'thread-title.label': 'Title',\n 'delete-thread.label': 'Delete',\n 'select-thread.label': 'Select comment',\n 'create-thread.label': 'Create thread',\n 'message.placeholder': 'Enter message...',\n 'comment.placeholder': 'Enter comment...',\n 'settings-standalone.label': 'Enable standalone thread creation',\n 'anonymous.label': 'Anonymous',\n 'enter-to-send.message': 'Enter to add ⏎',\n 'edit-message.label': 'Edit',\n 'save-message.label': 'Save',\n 'delete-message.label': 'Delete',\n 'accept-proposal.label': 'Accept proposal',\n 'accept-change.label': 'Accept change',\n 'reject-change.label': 'Reject change',\n },\n },\n },\n] as const satisfies Theme.Resource[];\n"],"mappings":";AAMA,IAAa,iBAAiB;AAE9B,IAAa,eAAe,CAC1B,EACE,SAAS,GACN,iBAAiB;CAChB,eAAe;CACf,4BAA4B;CAC5B,sBAAsB;CACtB,uBAAuB;CACvB,uBAAuB;CACvB,uBAAuB;CACvB,uBAAuB;CACvB,uBAAuB;CACvB,6BAA6B;CAC7B,mBAAmB;CACnB,yBAAyB;CACzB,sBAAsB;CACtB,sBAAsB;CACtB,wBAAwB;CACxB,yBAAyB;CACzB,uBAAuB;CACvB,uBAAuB;AACzB,EACF,EACF,CACF"}
|