@sentry/junior-dashboard 0.279.0 → 0.280.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/app.js +100 -31
- package/dist/client-manifest.json +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +100 -31
- package/dist/public/chunks/AdminPage-YEKZOKH6.js +1 -0
- package/dist/public/chunks/AutomationPage-5GS3IUAK.js +3 -0
- package/dist/public/chunks/AutomationRunsPage-IPYEZU4L.js +1 -0
- package/dist/public/chunks/AutomationsPage-HSAYPC3C.js +1 -0
- package/dist/public/chunks/CodePage-IPFKXWFB.js +1 -0
- package/dist/public/chunks/ConnectionsPage-5XUEK6FS.js +1 -0
- package/dist/public/chunks/DrawerContent-RYYS57ZH.js +1 -0
- package/dist/public/chunks/LocationDetailPage-27ETUGR6.js +1 -0
- package/dist/public/chunks/LocationsPage-WOKMQAGM.js +1 -0
- package/dist/public/chunks/McpSettingsPage-3RT7JPEK.js +1 -0
- package/dist/public/chunks/PeoplePage-6YQLDOUL.js +1 -0
- package/dist/public/chunks/PersonProfilePage-GYMAZFIK.js +1 -0
- package/dist/public/chunks/PersonalTokensPage-4BCR744B.js +1 -0
- package/dist/public/chunks/PluginUserPage-IP6WL7MD.js +1 -0
- package/dist/public/chunks/RepositoryPage-TRD44LQ3.js +1 -0
- package/dist/public/chunks/SettingsPage-4E3SLQ36.js +1 -0
- package/dist/public/chunks/SystemPage-C3XBKXKW.js +1 -0
- package/dist/public/chunks/WorkspaceFormPage-2LS2X67Q.js +2 -0
- package/dist/public/chunks/WorkspacesPage-5WB3CUTX.js +1 -0
- package/dist/public/chunks/chunk-2BCS5AQL.js +1 -0
- package/dist/public/chunks/chunk-3Y3ICT6K.js +1 -0
- package/dist/public/chunks/chunk-4AKUZW6R.js +1 -0
- package/dist/public/chunks/chunk-4UGZNVJM.js +1 -0
- package/dist/public/chunks/chunk-564V7YHG.js +1 -0
- package/dist/public/chunks/chunk-5GZDBGAR.js +1 -0
- package/dist/public/chunks/chunk-5ISD3QSC.js +1 -0
- package/dist/public/chunks/chunk-5LNGNJ3P.js +1 -0
- package/dist/public/chunks/chunk-5ZA674OU.js +1 -0
- package/dist/public/chunks/chunk-6FSJ3W2W.js +1 -0
- package/dist/public/chunks/chunk-6GQL3EVJ.js +1 -0
- package/dist/public/chunks/chunk-6PHJZPWD.js +19 -0
- package/dist/public/chunks/chunk-A2QWWA7O.js +41 -0
- package/dist/public/chunks/chunk-AYXSBMWA.js +1 -0
- package/dist/public/chunks/chunk-B5ZN3SWQ.js +14 -0
- package/dist/public/chunks/chunk-B6HGRBBX.js +1 -0
- package/dist/public/chunks/chunk-BKOACADP.js +1 -0
- package/dist/public/chunks/chunk-CQY6BYUB.js +1 -0
- package/dist/public/chunks/chunk-CR2ICLSH.js +1 -0
- package/dist/public/chunks/chunk-DZB4LPI6.js +1 -0
- package/dist/public/chunks/chunk-EFEREKX4.js +1 -0
- package/dist/public/chunks/chunk-FE522UEA.js +1 -0
- package/dist/public/chunks/chunk-FVX42XAD.js +1 -0
- package/dist/public/chunks/chunk-G5NWYJTV.js +1 -0
- package/dist/public/chunks/chunk-GMZIELFU.js +1 -0
- package/dist/public/chunks/chunk-GO45RQDL.js +1 -0
- package/dist/public/chunks/chunk-GOTQ4D6W.js +1 -0
- package/dist/public/chunks/chunk-GRMX2HX6.js +1 -0
- package/dist/public/chunks/chunk-GYRM5ZIV.js +1 -0
- package/dist/public/chunks/chunk-H2ZU34CP.js +1 -0
- package/dist/public/chunks/chunk-H76B43OQ.js +1 -0
- package/dist/public/chunks/chunk-HCLRG2EC.js +1 -0
- package/dist/public/chunks/chunk-IA6BJDKN.js +1 -0
- package/dist/public/chunks/chunk-IZWTFLCC.js +1 -0
- package/dist/public/chunks/chunk-J37MXUED.js +1 -0
- package/dist/public/chunks/chunk-JAZABOBA.js +0 -0
- package/dist/public/chunks/chunk-L2H3ZWZN.js +1 -0
- package/dist/public/chunks/chunk-LBELRO5P.js +1 -0
- package/dist/public/chunks/chunk-LKW7MULF.js +1 -0
- package/dist/public/chunks/chunk-MEHLQZDG.js +3 -0
- package/dist/public/chunks/chunk-MTBUV2GJ.js +1 -0
- package/dist/public/chunks/chunk-N4AONKD3.js +1 -0
- package/dist/public/chunks/chunk-NQMDED2R.js +2 -0
- package/dist/public/chunks/chunk-NQOJAZFQ.js +1 -0
- package/dist/public/chunks/chunk-PEQNT5MO.js +1 -0
- package/dist/public/chunks/chunk-PQPAPPEI.js +1 -0
- package/dist/public/chunks/chunk-PXHLJIOT.js +1 -0
- package/dist/public/chunks/chunk-QNA7F7QB.js +1 -0
- package/dist/public/chunks/chunk-QOX5RMLM.js +1 -0
- package/dist/public/chunks/chunk-QPBLZSNX.js +1 -0
- package/dist/public/chunks/chunk-R4ORRE77.js +1 -0
- package/dist/public/chunks/chunk-RHIXHBS5.js +1 -0
- package/dist/public/chunks/chunk-RHMZNAJX.js +1 -0
- package/dist/public/chunks/chunk-SDKV3OID.js +1 -0
- package/dist/public/chunks/chunk-SIOW7OOW.js +1 -0
- package/dist/public/chunks/chunk-SUX4QBVF.js +1 -0
- package/dist/public/chunks/chunk-T4M53QQT.js +1 -0
- package/dist/public/chunks/chunk-TFJOEF3Q.js +1 -0
- package/dist/public/chunks/chunk-UAH6DWNS.js +1 -0
- package/dist/public/chunks/chunk-UJD3MBMB.js +1 -0
- package/dist/public/chunks/chunk-UK2V4UBT.js +1 -0
- package/dist/public/chunks/chunk-UT6HZPGL.js +1 -0
- package/dist/public/chunks/chunk-UZ44J2LH.js +1 -0
- package/dist/public/chunks/chunk-V5UDSIRA.js +63 -0
- package/dist/public/chunks/chunk-VC6IS564.js +1 -0
- package/dist/public/chunks/chunk-VFEBNOPX.js +1 -0
- package/dist/public/chunks/chunk-VXT3FIVL.js +55 -0
- package/dist/public/chunks/chunk-WJ3OXMVX.js +1 -0
- package/dist/public/chunks/chunk-WRXMQB7X.js +25 -0
- package/dist/public/chunks/chunk-WZVO3HG4.js +1 -0
- package/dist/public/chunks/chunk-XNR4RZEC.js +1 -0
- package/dist/public/chunks/chunk-YPMUHXYT.js +147 -0
- package/dist/public/chunks/chunk-YYNMZ4WC.js +1 -0
- package/dist/public/chunks/chunk-Z2JPRGFT.js +1 -0
- package/dist/public/chunks/chunk-ZIJ6SJDN.js +1 -0
- package/dist/public/chunks/code-highlighter-Q7WC7AEF.js +12 -0
- package/dist/public/client-TYC7JKRE.js +91 -0
- package/dist/tailwind.css +1 -1
- package/package.json +5 -4
- package/src/api/schema.ts +1 -1
- package/src/assets.ts +4 -1
- package/src/client/App.tsx +100 -23
- package/src/client/TracedRoutes.tsx +8 -4
- package/src/client/code-highlighter.ts +26 -0
- package/src/client/code.tsx +2 -22
- package/src/client/components/Drawer.tsx +12 -71
- package/src/client/components/DrawerContent.tsx +76 -0
- package/src/client/components/ProfileMenu.tsx +12 -3
- package/src/client/components/UserInputForm.tsx +28 -5
- package/src/client/conversations/ConversationCatchUp.tsx +107 -0
- package/src/client/conversations/ConversationContextTags.tsx +110 -0
- package/src/client/conversations/ConversationEventLog.tsx +213 -71
- package/src/client/conversations/ConversationHeader.tsx +35 -41
- package/src/client/conversations/ConversationHeaderActions.tsx +34 -38
- package/src/client/conversations/ConversationHomeList.tsx +41 -3
- package/src/client/conversations/ConversationMeta.tsx +6 -12
- package/src/client/conversations/ConversationOverflowMenu.tsx +15 -4
- package/src/client/conversations/ConversationPage.tsx +188 -150
- package/src/client/conversations/ShareConversationButton.tsx +5 -50
- package/src/client/conversations/SlackMark.tsx +16 -4
- package/src/client/conversations/TranscriptRows.tsx +24 -11
- package/src/client/conversations/TranscriptView.tsx +21 -13
- package/src/client/conversations/conversationArchive.ts +22 -0
- package/src/client/conversations/conversationCatchUpState.ts +96 -0
- package/src/client/conversations/conversationMemory.ts +8 -1
- package/src/client/conversations/queries.ts +11 -3
- package/src/client/conversations/{transcriptUnreadHistory.ts → transcriptAroundHistory.ts} +48 -42
- package/src/client/conversations/transcriptBottomPinning.ts +27 -3
- package/src/client/conversations/transcriptReadPosition.ts +24 -2
- package/src/client/conversations/turnContext.ts +1 -1
- package/src/client/pages/admin/AdminPage.tsx +1 -1
- package/src/client/pages/admin/ServiceAccountsSection.tsx +1 -1
- package/src/client/pages/memory/memoryDashboard.ts +1 -1
- package/src/client/pages/memory/memoryRecord.ts +1 -1
- package/src/client/pages/settings/ConnectionsPage.tsx +1 -1
- package/src/client/pages/user/PluginUserPage.tsx +0 -6
- package/src/client/pages/user/pluginUserPageRoutes.ts +5 -0
- package/src/client/primaryNav.ts +1 -1
- package/src/client/review-desk/reviewItemFormat.ts +1 -1
- package/src/mock-reporting/fixtures.ts +43 -0
- package/src/mock-reporting/routes.ts +40 -3
- package/src/public-assets.ts +28 -20
- package/src/shell.ts +12 -2
- package/dist/public/client-ZCZXZBTL.js +0 -460
|
@@ -3,6 +3,7 @@ import { Menu, PanelRight, ScrollText, X } from "lucide-react";
|
|
|
3
3
|
|
|
4
4
|
import { SearchInput } from "../components/SearchInput";
|
|
5
5
|
import { Button } from "../components/ui/button";
|
|
6
|
+
import { Spinner } from "../components/ui/spinner";
|
|
6
7
|
import { cn } from "../styles";
|
|
7
8
|
import {
|
|
8
9
|
MobileHeaderActionsPortal,
|
|
@@ -17,32 +18,29 @@ import {
|
|
|
17
18
|
type ConversationPinAction,
|
|
18
19
|
} from "./ConversationHeaderActions";
|
|
19
20
|
import { ConversationOverflowMenu } from "./ConversationOverflowMenu";
|
|
20
|
-
import { ShareConversationButton } from "./ShareConversationButton";
|
|
21
21
|
import type { TranscriptViewMode } from "./transcriptRenderModel";
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* details.
|
|
24
|
+
* Keep the conversation title and frequently used controls in the header.
|
|
25
|
+
* Run facts and the Brief live in Conversation details.
|
|
27
26
|
*/
|
|
28
27
|
export function ConversationHeader(props: {
|
|
29
28
|
/** Parent chain of a child Conversation. Root Conversations omit it. */
|
|
30
29
|
agentTree?: ReactNode;
|
|
30
|
+
contextTags?: ReactNode;
|
|
31
31
|
/** A `menu` layout copy action for the `⋯` menu. */
|
|
32
32
|
copyAction?: ReactNode;
|
|
33
|
-
/** Estimated cost. Desktop only; mobile shows it in Conversation details. */
|
|
34
|
-
cost?: ReactNode;
|
|
35
33
|
archive: ConversationArchiveAction;
|
|
36
34
|
conversationId: string;
|
|
35
|
+
/** Estimated cost. Desktop only; mobile shows it in Conversation details. */
|
|
36
|
+
cost?: ReactNode;
|
|
37
37
|
detailsOpen: boolean;
|
|
38
38
|
onDetailsClick(): void;
|
|
39
39
|
/** Links to the fork source and to forks of this conversation. */
|
|
40
40
|
forks?: ReactNode;
|
|
41
|
-
/**
|
|
41
|
+
/** Linked work links below the title on mobile. */
|
|
42
42
|
linkedWork?: ReactNode;
|
|
43
43
|
live: boolean;
|
|
44
|
-
/** Where the conversation runs, such as a Slack channel. */
|
|
45
|
-
location?: ReactNode;
|
|
46
44
|
onSearchChange(value: string): void;
|
|
47
45
|
onViewChange(value: TranscriptViewMode): void;
|
|
48
46
|
pin: ConversationPinAction;
|
|
@@ -121,16 +119,18 @@ export function ConversationHeader(props: {
|
|
|
121
119
|
searchOpenVisible ||
|
|
122
120
|
props.archive.error ||
|
|
123
121
|
props.pin.error ||
|
|
122
|
+
Boolean(props.contextTags) ||
|
|
124
123
|
Boolean(props.linkedWork) ||
|
|
125
124
|
Boolean(props.forks) ||
|
|
126
125
|
Boolean(props.agentTree);
|
|
127
126
|
|
|
128
127
|
const liveIndicator = props.live ? (
|
|
129
|
-
<span
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
<span className="inline-flex shrink-0" title="Working">
|
|
129
|
+
<Spinner
|
|
130
|
+
aria-label="Working"
|
|
131
|
+
className="size-3.5 text-dashboard-text-muted motion-safe:[animation-duration:1.25s]"
|
|
132
|
+
/>
|
|
133
|
+
</span>
|
|
134
134
|
) : null;
|
|
135
135
|
|
|
136
136
|
return (
|
|
@@ -166,28 +166,27 @@ export function ConversationHeader(props: {
|
|
|
166
166
|
!showMobileHeader && "hidden md:block",
|
|
167
167
|
)}
|
|
168
168
|
>
|
|
169
|
-
<div className="hidden h-13 min-w-0 items-center justify-between gap-
|
|
170
|
-
<div className="flex min-w-0 items-center gap-
|
|
169
|
+
<div className="hidden h-13 min-w-0 items-center justify-between gap-1.5 px-1 md:flex">
|
|
170
|
+
<div className="flex min-w-0 items-center gap-1.5">
|
|
171
171
|
{props.sidebarToggle ? (
|
|
172
172
|
<div className="shrink-0">{props.sidebarToggle}</div>
|
|
173
173
|
) : null}
|
|
174
|
-
|
|
175
|
-
<div className="min-w-0 shrink-[2]">{props.agentTree}</div>
|
|
176
|
-
) : props.location ? (
|
|
177
|
-
<span className="shrink-0 text-sm text-dashboard-text-muted">
|
|
178
|
-
{props.location}
|
|
179
|
-
</span>
|
|
180
|
-
) : null}
|
|
181
|
-
{props.agentTree || props.location ? (
|
|
182
|
-
<span aria-hidden="true" className="text-dashboard-text-faint">
|
|
183
|
-
›
|
|
184
|
-
</span>
|
|
185
|
-
) : null}
|
|
186
|
-
<h2 className="m-0 line-clamp-1 min-w-0 font-display text-base font-medium leading-tight tracking-tight">
|
|
174
|
+
<h2 className="m-0 min-w-0 truncate font-display text-base font-medium leading-6 tracking-tight">
|
|
187
175
|
{props.title}
|
|
188
176
|
</h2>
|
|
189
177
|
{liveIndicator}
|
|
190
|
-
|
|
178
|
+
{props.privacy ? (
|
|
179
|
+
<span className="shrink-0">{props.privacy}</span>
|
|
180
|
+
) : null}
|
|
181
|
+
{props.agentTree ? (
|
|
182
|
+
<div className="min-w-0 max-w-1/3 shrink-[2] border-l border-dashboard-border-subtle pl-2 text-xs text-dashboard-text-muted [&_a]:text-dashboard-text-muted [&_a:hover]:text-dashboard-text">
|
|
183
|
+
{props.agentTree}
|
|
184
|
+
</div>
|
|
185
|
+
) : props.contextTags ? (
|
|
186
|
+
<div className="flex h-6 min-w-0 max-w-[min(19rem,60%)] shrink-[2] items-center">
|
|
187
|
+
{props.contextTags}
|
|
188
|
+
</div>
|
|
189
|
+
) : null}
|
|
191
190
|
</div>
|
|
192
191
|
<div className="flex shrink-0 items-center gap-0.5">
|
|
193
192
|
{props.cost ? (
|
|
@@ -214,11 +213,6 @@ export function ConversationHeader(props: {
|
|
|
214
213
|
open={searchOpenVisible}
|
|
215
214
|
/>
|
|
216
215
|
)}
|
|
217
|
-
<ShareConversationButton
|
|
218
|
-
key={props.conversationId}
|
|
219
|
-
conversationId={props.conversationId}
|
|
220
|
-
layout="icon"
|
|
221
|
-
/>
|
|
222
216
|
<ConversationOverflowMenu>
|
|
223
217
|
{(close) => menuActions("desktop", close)}
|
|
224
218
|
</ConversationOverflowMenu>
|
|
@@ -241,12 +235,12 @@ export function ConversationHeader(props: {
|
|
|
241
235
|
{props.forks ? (
|
|
242
236
|
<div className="px-4 py-2 md:hidden">{props.forks}</div>
|
|
243
237
|
) : null}
|
|
244
|
-
{props.linkedWork ? (
|
|
245
|
-
<div
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
238
|
+
{props.contextTags || props.linkedWork ? (
|
|
239
|
+
<div className="grid min-w-0 gap-2 px-3 pb-2 pt-1.5 md:hidden">
|
|
240
|
+
{props.contextTags}
|
|
241
|
+
{props.linkedWork ? (
|
|
242
|
+
<div aria-label="Linked work">{props.linkedWork}</div>
|
|
243
|
+
) : null}
|
|
250
244
|
</div>
|
|
251
245
|
) : null}
|
|
252
246
|
{searchOpenVisible ? (
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "lucide-react";
|
|
13
13
|
|
|
14
14
|
import { Button, buttonVariants } from "../components/ui/button";
|
|
15
|
+
import { Separator } from "../components/ui/separator";
|
|
15
16
|
import {
|
|
16
17
|
Tooltip,
|
|
17
18
|
TooltipContent,
|
|
@@ -38,8 +39,8 @@ export type ConversationPinAction = {
|
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
42
|
+
* Keep secondary conversation actions in the menu, with search and details
|
|
43
|
+
* available there on mobile too.
|
|
43
44
|
*/
|
|
44
45
|
export function ConversationHeaderActions(props: {
|
|
45
46
|
archive: ConversationArchiveAction;
|
|
@@ -56,9 +57,10 @@ export function ConversationHeaderActions(props: {
|
|
|
56
57
|
view: TranscriptViewMode;
|
|
57
58
|
}) {
|
|
58
59
|
const mobile = props.placement === "mobile";
|
|
60
|
+
const eventLogOpen = props.view === "raw";
|
|
59
61
|
return (
|
|
60
62
|
<div className="grid gap-0.5">
|
|
61
|
-
{mobile &&
|
|
63
|
+
{mobile && !eventLogOpen ? (
|
|
62
64
|
<MenuActionButton
|
|
63
65
|
label={props.searchOpen ? "Hide search" : "Search transcript"}
|
|
64
66
|
onClick={props.onSearchClick}
|
|
@@ -71,40 +73,30 @@ export function ConversationHeaderActions(props: {
|
|
|
71
73
|
)}
|
|
72
74
|
</MenuActionButton>
|
|
73
75
|
) : null}
|
|
74
|
-
<MenuActionButton
|
|
75
|
-
label="Conversation"
|
|
76
|
-
onClick={() => props.onViewChange("rich")}
|
|
77
|
-
pressed={props.view === "rich"}
|
|
78
|
-
>
|
|
79
|
-
<MessagesSquare aria-hidden="true" size={16} strokeWidth={2} />
|
|
80
|
-
</MenuActionButton>
|
|
81
|
-
<MenuActionButton
|
|
82
|
-
label="Event log"
|
|
83
|
-
onClick={() => props.onViewChange("raw")}
|
|
84
|
-
pressed={props.view === "raw"}
|
|
85
|
-
>
|
|
86
|
-
<ScrollText aria-hidden="true" size={16} strokeWidth={2} />
|
|
87
|
-
</MenuActionButton>
|
|
88
76
|
{mobile ? (
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
77
|
+
<MenuActionButton
|
|
78
|
+
label="Conversation details"
|
|
79
|
+
onClick={props.onDetailsClick}
|
|
80
|
+
pressed={props.detailsOpen}
|
|
81
|
+
>
|
|
82
|
+
<Info aria-hidden="true" size={16} strokeWidth={2} />
|
|
83
|
+
</MenuActionButton>
|
|
94
84
|
) : null}
|
|
85
|
+
<ShareConversationButton
|
|
86
|
+
key={props.conversationId}
|
|
87
|
+
conversationId={props.conversationId}
|
|
88
|
+
/>
|
|
95
89
|
{props.copyAction}
|
|
96
90
|
<MenuActionButton
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
onClick={props.pin.onClick}
|
|
91
|
+
label={eventLogOpen ? "Back to conversation" : "Open event log"}
|
|
92
|
+
onClick={() => props.onViewChange(eventLogOpen ? "rich" : "raw")}
|
|
100
93
|
>
|
|
101
|
-
{
|
|
102
|
-
<
|
|
94
|
+
{eventLogOpen ? (
|
|
95
|
+
<MessagesSquare aria-hidden="true" size={16} strokeWidth={2} />
|
|
103
96
|
) : (
|
|
104
|
-
<
|
|
97
|
+
<ScrollText aria-hidden="true" size={16} strokeWidth={2} />
|
|
105
98
|
)}
|
|
106
99
|
</MenuActionButton>
|
|
107
|
-
<ArchiveConversationButton {...props.archive} />
|
|
108
100
|
{props.sentryConversationUrl ? (
|
|
109
101
|
<a
|
|
110
102
|
className={buttonVariants({ size: "menu" })}
|
|
@@ -116,15 +108,19 @@ export function ConversationHeaderActions(props: {
|
|
|
116
108
|
Open in Sentry
|
|
117
109
|
</a>
|
|
118
110
|
) : null}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
111
|
+
<Separator className="my-1 bg-dashboard-border-subtle" />
|
|
112
|
+
<MenuActionButton
|
|
113
|
+
disabled={props.pin.disabled}
|
|
114
|
+
label={props.pin.pinned ? "Unpin" : "Pin"}
|
|
115
|
+
onClick={props.pin.onClick}
|
|
116
|
+
>
|
|
117
|
+
{props.pin.pinned ? (
|
|
118
|
+
<PinOff aria-hidden="true" size={16} strokeWidth={2} />
|
|
119
|
+
) : (
|
|
120
|
+
<Pin aria-hidden="true" size={16} strokeWidth={2} />
|
|
121
|
+
)}
|
|
122
|
+
</MenuActionButton>
|
|
123
|
+
<ArchiveConversationButton {...props.archive} />
|
|
128
124
|
</div>
|
|
129
125
|
);
|
|
130
126
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useCallback, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
2
|
import { Archive, ArchiveRestore } from "lucide-react";
|
|
3
|
-
import { Link } from "react-router";
|
|
3
|
+
import { Link, useLocation, useNavigate } from "react-router";
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
conversationDisplayTitle,
|
|
@@ -20,7 +20,11 @@ import { ConversationSidebarAnnotations } from "./ConversationMeta";
|
|
|
20
20
|
import { ConversationArchiveNotices } from "./ConversationArchiveNotices";
|
|
21
21
|
import { ConversationListStatusIcon } from "./ConversationListStatusIcon";
|
|
22
22
|
import { conversationPath } from "./conversationRoutes";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
useArchiveConversation,
|
|
25
|
+
useLatestArchiveStatus,
|
|
26
|
+
type ArchivedFromConversationState,
|
|
27
|
+
} from "./conversationArchive";
|
|
24
28
|
import { DisplayText } from "../display-text/DisplayText";
|
|
25
29
|
import {
|
|
26
30
|
attentionConversationIds,
|
|
@@ -60,6 +64,7 @@ export function ConversationHomeList(props: {
|
|
|
60
64
|
);
|
|
61
65
|
setArchivedConversation(conversation);
|
|
62
66
|
}, []);
|
|
67
|
+
useConversationPageArchiveResult(handleArchived, handleArchiveError);
|
|
63
68
|
const notices = (
|
|
64
69
|
<ConversationArchiveNotices
|
|
65
70
|
archivedConversation={archivedConversation}
|
|
@@ -116,6 +121,39 @@ export function ConversationHomeList(props: {
|
|
|
116
121
|
);
|
|
117
122
|
}
|
|
118
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Report an archive started on the conversation page, which navigated home
|
|
126
|
+
* before the request finished.
|
|
127
|
+
*/
|
|
128
|
+
function useConversationPageArchiveResult(
|
|
129
|
+
onArchived: (conversation: Conversation) => void,
|
|
130
|
+
onArchiveError: (conversation: Conversation, wasArchiving: boolean) => void,
|
|
131
|
+
) {
|
|
132
|
+
const location = useLocation();
|
|
133
|
+
const navigate = useNavigate();
|
|
134
|
+
const conversation = (location.state as ArchivedFromConversationState | null)
|
|
135
|
+
?.archivedConversation;
|
|
136
|
+
const status = useLatestArchiveStatus(conversation?.id);
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (!conversation || (status !== "success" && status !== "error")) return;
|
|
139
|
+
if (status === "success") onArchived(conversation);
|
|
140
|
+
else onArchiveError(conversation, true);
|
|
141
|
+
// Clear the state so a reload or a later restore does not report again.
|
|
142
|
+
navigate(
|
|
143
|
+
{ hash: location.hash, search: location.search },
|
|
144
|
+
{ replace: true, state: null },
|
|
145
|
+
);
|
|
146
|
+
}, [
|
|
147
|
+
conversation,
|
|
148
|
+
location.hash,
|
|
149
|
+
location.search,
|
|
150
|
+
navigate,
|
|
151
|
+
onArchiveError,
|
|
152
|
+
onArchived,
|
|
153
|
+
status,
|
|
154
|
+
]);
|
|
155
|
+
}
|
|
156
|
+
|
|
119
157
|
/** Match the grouped card list while its first feed request is pending. */
|
|
120
158
|
export function ConversationHomeListLoading(props: { label?: string }) {
|
|
121
159
|
return (
|
|
@@ -42,7 +42,10 @@ import {
|
|
|
42
42
|
type SidebarAnnotation,
|
|
43
43
|
type SidebarAnnotationBadgeGroup,
|
|
44
44
|
} from "./sidebarAnnotationBadges";
|
|
45
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
linkStatusLabel,
|
|
47
|
+
type ConversationLink,
|
|
48
|
+
} from "./ConversationLinkedWork";
|
|
46
49
|
|
|
47
50
|
const MOBILE_MEDIA_QUERY = "(max-width: 767px)";
|
|
48
51
|
|
|
@@ -93,13 +96,6 @@ export function ConversationPrivacyChip(props: {
|
|
|
93
96
|
);
|
|
94
97
|
}
|
|
95
98
|
|
|
96
|
-
/** True when the conversation has at least one resource-link annotation. */
|
|
97
|
-
export function hasConversationAnnotations(
|
|
98
|
-
annotations: ConversationDetailReport["annotations"] | undefined,
|
|
99
|
-
): boolean {
|
|
100
|
-
return Boolean(annotations?.some((annotation) => Boolean(annotation.url)));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
99
|
/** Render plugin annotations as a newest-first stack in a conversation row. */
|
|
104
100
|
export function ConversationSidebarAnnotations(props: {
|
|
105
101
|
annotations: ConversationDetailReport["sidebarAnnotations"] | undefined;
|
|
@@ -401,13 +397,11 @@ function SidebarAnnotationIcon(props: {
|
|
|
401
397
|
|
|
402
398
|
/** Render linked work as a compact strip under the conversation title. */
|
|
403
399
|
export function ConversationAnnotations(props: {
|
|
404
|
-
|
|
400
|
+
links: readonly ConversationLink[];
|
|
405
401
|
}) {
|
|
406
|
-
const links = conversationLinks(props.detail);
|
|
407
|
-
if (links.length === 0) return null;
|
|
408
402
|
return (
|
|
409
403
|
<div className="flex gap-x-4 gap-y-2 overflow-x-auto">
|
|
410
|
-
{links.map((link) => (
|
|
404
|
+
{props.links.map((link) => (
|
|
411
405
|
<a
|
|
412
406
|
className="inline-flex min-w-0 max-w-full shrink-0 items-center gap-2 rounded-md px-1 py-0.5 font-sans text-xs leading-snug text-dashboard-text no-underline transition-colors hover:text-dashboard-accent-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus"
|
|
413
407
|
href={link.url}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Ellipsis } from "lucide-react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useEffect,
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
type RefObject,
|
|
8
|
+
} from "react";
|
|
3
9
|
|
|
4
10
|
import { HeaderIconButton } from "./ConversationHeaderActions";
|
|
5
11
|
|
|
@@ -9,11 +15,13 @@ import { HeaderIconButton } from "./ConversationHeaderActions";
|
|
|
9
15
|
* menu before it acts.
|
|
10
16
|
*/
|
|
11
17
|
export function ConversationOverflowMenu(props: {
|
|
12
|
-
buttonRef?:
|
|
18
|
+
buttonRef?: RefObject<HTMLButtonElement | null>;
|
|
13
19
|
children(close: () => void): ReactNode;
|
|
14
20
|
}) {
|
|
15
21
|
const [open, setOpen] = useState(false);
|
|
16
22
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
23
|
+
const localButtonRef = useRef<HTMLButtonElement>(null);
|
|
24
|
+
const buttonRef = props.buttonRef ?? localButtonRef;
|
|
17
25
|
|
|
18
26
|
useEffect(() => {
|
|
19
27
|
if (!open) return;
|
|
@@ -28,6 +36,9 @@ export function ConversationOverflowMenu(props: {
|
|
|
28
36
|
if (event.key !== "Escape" || event.defaultPrevented) return;
|
|
29
37
|
event.preventDefault();
|
|
30
38
|
setOpen(false);
|
|
39
|
+
if (menuRef.current?.contains(document.activeElement)) {
|
|
40
|
+
buttonRef.current?.focus({ preventScroll: true });
|
|
41
|
+
}
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
document.addEventListener("pointerdown", closeOnOutsideClick);
|
|
@@ -36,12 +47,12 @@ export function ConversationOverflowMenu(props: {
|
|
|
36
47
|
document.removeEventListener("pointerdown", closeOnOutsideClick);
|
|
37
48
|
document.removeEventListener("keydown", closeOnEscape, true);
|
|
38
49
|
};
|
|
39
|
-
}, [open]);
|
|
50
|
+
}, [buttonRef, open]);
|
|
40
51
|
|
|
41
52
|
return (
|
|
42
53
|
<div className="relative" ref={menuRef}>
|
|
43
54
|
<HeaderIconButton
|
|
44
|
-
ref={
|
|
55
|
+
ref={buttonRef}
|
|
45
56
|
label={open ? "Close conversation menu" : "Conversation menu"}
|
|
46
57
|
onClick={() => setOpen((value) => !value)}
|
|
47
58
|
pressed={open}
|