@tutti-os/workspace-file-reference 0.0.14 → 0.0.16
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/{chunk-7C3RZYQE.js → chunk-JURSITRX.js} +121 -3
- package/dist/chunk-JURSITRX.js.map +1 -0
- package/dist/{chunk-CD3YNHYJ.js → chunk-LJZYN4MH.js} +284 -39
- package/dist/chunk-LJZYN4MH.js.map +1 -0
- package/dist/contracts/index.d.ts +67 -4
- package/dist/core/index.d.ts +63 -3
- package/dist/core/index.js +7 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +7 -1
- package/dist/react/index.d.ts +76 -3
- package/dist/react/index.js +2 -2
- package/dist/{referenceSourceAggregator-yyyLVOPi.d.ts → referenceSourceAggregator-B8ATnJOZ.d.ts} +5 -0
- package/dist/ui/index.d.ts +38 -5
- package/dist/ui/index.js +964 -361
- package/dist/ui/index.js.map +1 -1
- package/package.json +6 -6
- package/dist/chunk-7C3RZYQE.js.map +0 -1
- package/dist/chunk-CD3YNHYJ.js.map +0 -1
package/dist/ui/index.js
CHANGED
|
@@ -3,27 +3,25 @@ import {
|
|
|
3
3
|
createReferenceSourcePickerController,
|
|
4
4
|
normalizeDirectoryPath,
|
|
5
5
|
useWorkspaceFileReferencePickerView
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-LJZYN4MH.js";
|
|
7
7
|
import {
|
|
8
|
+
REFERENCE_FILTER_CATEGORIES,
|
|
8
9
|
WORKSPACE_ROOT_GROUP_NODE_ID,
|
|
9
10
|
nodeRefKey,
|
|
10
11
|
selectedReferenceToWorkspaceFileReference
|
|
11
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-JURSITRX.js";
|
|
12
13
|
|
|
13
14
|
// src/ui/internal/reference/WorkspaceFileReferencePicker.tsx
|
|
14
|
-
import { useId } from "react";
|
|
15
|
-
import { createPortal } from "react-dom";
|
|
16
15
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
CardTitle,
|
|
22
|
-
CloseIcon,
|
|
16
|
+
Dialog,
|
|
17
|
+
DialogContent,
|
|
18
|
+
DialogHeader,
|
|
19
|
+
DialogTitle,
|
|
23
20
|
cn as cn2
|
|
24
21
|
} from "@tutti-os/ui-system";
|
|
25
22
|
|
|
26
23
|
// src/ui/internal/reference/WorkspaceFileReferencePickerSections.tsx
|
|
24
|
+
import { useId, useState as useState2 } from "react";
|
|
27
25
|
import {
|
|
28
26
|
Badge,
|
|
29
27
|
Button as Button2,
|
|
@@ -47,6 +45,9 @@ import {
|
|
|
47
45
|
ScrollArea,
|
|
48
46
|
SearchIcon,
|
|
49
47
|
Spinner,
|
|
48
|
+
Tooltip,
|
|
49
|
+
TooltipContent,
|
|
50
|
+
TooltipTrigger,
|
|
50
51
|
cn
|
|
51
52
|
} from "@tutti-os/ui-system";
|
|
52
53
|
import { AddIcon as AddLinedIcon } from "@tutti-os/ui-system/icons";
|
|
@@ -160,6 +161,12 @@ function WorkspaceFileReferencePickerTreeEntry({
|
|
|
160
161
|
}, workspaceFileReferenceTreeCollapseDurationMs);
|
|
161
162
|
return () => window.clearTimeout(timeoutId);
|
|
162
163
|
}, [expanded]);
|
|
164
|
+
const handleRowSelect = () => {
|
|
165
|
+
onFocusPath(entry.path);
|
|
166
|
+
if (isFolder) {
|
|
167
|
+
onToggleFolder(entry);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
163
170
|
const shouldBuildChildContent = expanded || shouldRenderChildContent;
|
|
164
171
|
const childContent = shouldBuildChildContent ? childState?.loading ? /* @__PURE__ */ jsxs(
|
|
165
172
|
"div",
|
|
@@ -202,14 +209,24 @@ function WorkspaceFileReferencePickerTreeEntry({
|
|
|
202
209
|
/* @__PURE__ */ jsxs(
|
|
203
210
|
"div",
|
|
204
211
|
{
|
|
212
|
+
"aria-current": focused ? "true" : void 0,
|
|
205
213
|
className: cn(
|
|
206
|
-
"flex items-center gap-2 rounded-[6px] py-1.5 pr-1 transition-colors",
|
|
214
|
+
"flex cursor-pointer items-center gap-2 rounded-[6px] py-1.5 pr-1 transition-colors select-none",
|
|
207
215
|
"nodrag [-webkit-app-region:no-drag]",
|
|
208
216
|
focused || selected ? "bg-transparency-block" : "hover:bg-transparency-block"
|
|
209
217
|
),
|
|
218
|
+
role: "button",
|
|
219
|
+
tabIndex: 0,
|
|
210
220
|
style: {
|
|
211
221
|
paddingLeft: `${(childDepth - 1) * workspaceFileReferenceTreeIndent + 8}px`
|
|
212
222
|
},
|
|
223
|
+
onClick: handleRowSelect,
|
|
224
|
+
onKeyDown: (event) => {
|
|
225
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
226
|
+
event.preventDefault();
|
|
227
|
+
handleRowSelect();
|
|
228
|
+
}
|
|
229
|
+
},
|
|
213
230
|
children: [
|
|
214
231
|
isFolder ? /* @__PURE__ */ jsx(
|
|
215
232
|
"button",
|
|
@@ -232,24 +249,10 @@ function WorkspaceFileReferencePickerTreeEntry({
|
|
|
232
249
|
)
|
|
233
250
|
}
|
|
234
251
|
) : null,
|
|
235
|
-
/* @__PURE__ */ jsxs(
|
|
236
|
-
"
|
|
237
|
-
{
|
|
238
|
-
|
|
239
|
-
type: "button",
|
|
240
|
-
onClick: (event) => {
|
|
241
|
-
event.stopPropagation();
|
|
242
|
-
onFocusPath(entry.path);
|
|
243
|
-
if (entry.kind === "folder") {
|
|
244
|
-
onToggleFolder(entry);
|
|
245
|
-
}
|
|
246
|
-
},
|
|
247
|
-
children: [
|
|
248
|
-
isFolder ? /* @__PURE__ */ jsx(FolderFilledIcon, { className: "size-4 shrink-0 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx(FileIcon, { className: "size-4 shrink-0 text-[var(--text-tertiary)]" }),
|
|
249
|
-
/* @__PURE__ */ jsx("span", { className: "truncate text-[13px] text-[var(--text-primary)]", children: resolveWorkspaceFileReferenceLabel(entry) })
|
|
250
|
-
]
|
|
251
|
-
}
|
|
252
|
-
),
|
|
252
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2 text-left", children: [
|
|
253
|
+
isFolder ? /* @__PURE__ */ jsx(FolderFilledIcon, { className: "size-4 shrink-0 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx(FileIcon, { className: "size-4 shrink-0 text-[var(--text-tertiary)]" }),
|
|
254
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-[13px] text-[var(--text-primary)]", children: resolveWorkspaceFileReferenceLabel(entry) })
|
|
255
|
+
] }),
|
|
253
256
|
/* @__PURE__ */ jsx(
|
|
254
257
|
Button,
|
|
255
258
|
{
|
|
@@ -305,30 +308,39 @@ function WorkspaceFileReferencePickerSearchEntry({
|
|
|
305
308
|
return /* @__PURE__ */ jsxs(
|
|
306
309
|
"div",
|
|
307
310
|
{
|
|
311
|
+
"aria-current": focused ? "true" : void 0,
|
|
308
312
|
className: cn(
|
|
309
|
-
"grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-[6px] border py-2.5 pr-1 pl-3 transition-colors",
|
|
313
|
+
"grid cursor-pointer grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-[6px] border py-2.5 pr-1 pl-3 transition-colors select-none",
|
|
310
314
|
"nodrag [-webkit-app-region:no-drag]",
|
|
311
315
|
focused || selected ? "border-border bg-transparency-block" : "border-transparent bg-transparent hover:border-border/70 hover:bg-transparency-block"
|
|
312
316
|
),
|
|
317
|
+
role: "button",
|
|
318
|
+
tabIndex: 0,
|
|
319
|
+
onClick: () => onFocusPath(entry.path),
|
|
320
|
+
onKeyDown: (event) => {
|
|
321
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
322
|
+
event.preventDefault();
|
|
323
|
+
onFocusPath(entry.path);
|
|
324
|
+
}
|
|
325
|
+
},
|
|
313
326
|
children: [
|
|
314
|
-
/* @__PURE__ */ jsxs(
|
|
315
|
-
"
|
|
316
|
-
{
|
|
317
|
-
className: "
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
),
|
|
327
|
+
/* @__PURE__ */ jsxs("div", { className: "nodrag flex min-w-0 items-center gap-3 text-left [-webkit-app-region:no-drag]", children: [
|
|
328
|
+
/* @__PURE__ */ jsx("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-[var(--transparency-block)] text-[var(--text-tertiary)]", children: isFolder ? /* @__PURE__ */ jsx(FolderFilledIcon, { className: "size-4 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx(FileIcon, { className: "size-4 text-[var(--text-tertiary)]" }) }),
|
|
329
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
330
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-[13px] font-medium text-[var(--text-primary)]", children: resolveWorkspaceFileReferenceLabel(entry) }),
|
|
331
|
+
/* @__PURE__ */ jsxs(Tooltip, { delayDuration: 300, children: [
|
|
332
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx("span", { className: "block truncate text-[11px] text-[var(--text-secondary)]", children: entry.path }) }),
|
|
333
|
+
/* @__PURE__ */ jsx(
|
|
334
|
+
TooltipContent,
|
|
335
|
+
{
|
|
336
|
+
className: "max-w-[min(520px,calc(100vw-32px))] whitespace-normal text-left [overflow-wrap:anywhere]",
|
|
337
|
+
side: "top",
|
|
338
|
+
children: entry.path
|
|
339
|
+
}
|
|
340
|
+
)
|
|
341
|
+
] })
|
|
342
|
+
] })
|
|
343
|
+
] }),
|
|
332
344
|
/* @__PURE__ */ jsx(
|
|
333
345
|
Button,
|
|
334
346
|
{
|
|
@@ -394,6 +406,8 @@ function WorkspaceFileReferencePreviewSurface({
|
|
|
394
406
|
directoryMessage: copy.t("referencePicker.previewFolder"),
|
|
395
407
|
emptyMessage: copy.t("referencePicker.previewUnavailable"),
|
|
396
408
|
frameClassName: "flex aspect-[3/2] w-full flex-col items-center justify-center overflow-hidden rounded-[8px] border border-[var(--line-2,var(--border-2))] bg-[var(--transparency-block)] p-0 text-center",
|
|
409
|
+
htmlFrameClassName: "items-stretch justify-stretch bg-white",
|
|
410
|
+
htmlTitle: resolveWorkspaceFileReferenceLabel,
|
|
397
411
|
imageAlt: resolveWorkspaceFileReferenceLabel,
|
|
398
412
|
imageFrameClassName: "p-3",
|
|
399
413
|
loadingIndicator: /* @__PURE__ */ jsx2("span", { className: "mx-auto grid size-11 place-items-center rounded-[6px] bg-[var(--transparency-block)]", children: /* @__PURE__ */ jsx2(LoadingIcon, { className: "size-4 animate-spin" }) }),
|
|
@@ -427,7 +441,9 @@ function resolveWorkspaceFileReferenceSurfaceState(copy, focusedEntry, previewSt
|
|
|
427
441
|
switch (previewState.status) {
|
|
428
442
|
case "loading":
|
|
429
443
|
case "image":
|
|
444
|
+
case "video":
|
|
430
445
|
case "text":
|
|
446
|
+
case "html":
|
|
431
447
|
return {
|
|
432
448
|
...previewState,
|
|
433
449
|
entry: focusedEntry
|
|
@@ -488,6 +504,9 @@ function WorkspaceFileReferencePickerFooter({
|
|
|
488
504
|
onConfirm,
|
|
489
505
|
selectedRefs
|
|
490
506
|
}) {
|
|
507
|
+
const selectedRefsTooltipId = useId();
|
|
508
|
+
const [selectedRefsTooltipOpen, setSelectedRefsTooltipOpen] = useState2(false);
|
|
509
|
+
const selectedRefsLabel = selectedRefs.map((ref) => resolveWorkspaceFileReferenceLabel(ref)).join("\n");
|
|
491
510
|
return /* @__PURE__ */ jsxs2("div", { className: "nodrag flex flex-col gap-3 border-t border-[var(--line-1)] px-4 py-4 [-webkit-app-region:no-drag] sm:px-6 lg:flex-row lg:items-center lg:justify-between", children: [
|
|
492
511
|
/* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-wrap items-center gap-2 lg:flex-1", children: [
|
|
493
512
|
/* @__PURE__ */ jsx2("span", { className: "text-[13px] text-[var(--text-secondary)]", children: copy.t("referencePicker.selectedCount", {
|
|
@@ -503,13 +522,48 @@ function WorkspaceFileReferencePickerFooter({
|
|
|
503
522
|
ref.path
|
|
504
523
|
)),
|
|
505
524
|
selectedRefs.length > 2 ? /* @__PURE__ */ jsxs2(
|
|
506
|
-
|
|
525
|
+
"span",
|
|
507
526
|
{
|
|
508
|
-
className:
|
|
509
|
-
|
|
527
|
+
className: "relative inline-flex",
|
|
528
|
+
onBlur: () => setSelectedRefsTooltipOpen(false),
|
|
529
|
+
onFocus: () => setSelectedRefsTooltipOpen(true),
|
|
530
|
+
onMouseEnter: () => setSelectedRefsTooltipOpen(true),
|
|
531
|
+
onMouseLeave: () => setSelectedRefsTooltipOpen(false),
|
|
510
532
|
children: [
|
|
511
|
-
|
|
512
|
-
|
|
533
|
+
/* @__PURE__ */ jsx2(
|
|
534
|
+
Badge,
|
|
535
|
+
{
|
|
536
|
+
asChild: true,
|
|
537
|
+
className: `${workspaceFileReferencePickerSelectedBadgeClassName} cursor-default`,
|
|
538
|
+
variant: "secondary",
|
|
539
|
+
children: /* @__PURE__ */ jsxs2(
|
|
540
|
+
"button",
|
|
541
|
+
{
|
|
542
|
+
"aria-describedby": selectedRefsTooltipId,
|
|
543
|
+
"aria-label": selectedRefsLabel,
|
|
544
|
+
type: "button",
|
|
545
|
+
children: [
|
|
546
|
+
"+",
|
|
547
|
+
selectedRefs.length - 2
|
|
548
|
+
]
|
|
549
|
+
}
|
|
550
|
+
)
|
|
551
|
+
}
|
|
552
|
+
),
|
|
553
|
+
/* @__PURE__ */ jsx2(
|
|
554
|
+
"span",
|
|
555
|
+
{
|
|
556
|
+
"aria-hidden": !selectedRefsTooltipOpen,
|
|
557
|
+
className: "pointer-events-none absolute bottom-[calc(100%+8px)] left-0 z-[var(--z-tooltip,100700)] max-h-[min(20rem,calc(100vh-96px))] w-max max-w-[min(28rem,calc(100vw-32px))] overflow-auto whitespace-pre-line rounded-md border border-[var(--border-1)] bg-[var(--background-fronted)] px-2 py-1 text-left text-[13px] leading-[1.3] text-[var(--text-primary)] shadow-soft transition-opacity duration-100 [overflow-wrap:anywhere]",
|
|
558
|
+
id: selectedRefsTooltipId,
|
|
559
|
+
role: "tooltip",
|
|
560
|
+
style: {
|
|
561
|
+
opacity: selectedRefsTooltipOpen ? 1 : 0,
|
|
562
|
+
visibility: selectedRefsTooltipOpen ? "visible" : "hidden"
|
|
563
|
+
},
|
|
564
|
+
children: selectedRefsLabel
|
|
565
|
+
}
|
|
566
|
+
)
|
|
513
567
|
]
|
|
514
568
|
}
|
|
515
569
|
) : null
|
|
@@ -546,8 +600,6 @@ function WorkspaceFileReferencePickerFeedback2({
|
|
|
546
600
|
|
|
547
601
|
// src/ui/internal/reference/WorkspaceFileReferencePicker.tsx
|
|
548
602
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
549
|
-
var workspaceFileReferencePickerBackdropMotionClassName = "motion-safe:animate-in motion-safe:fade-in-0 motion-safe:duration-[180ms] motion-safe:ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:animate-none";
|
|
550
|
-
var workspaceFileReferencePickerPanelMotionClassName = "motion-safe:animate-in motion-safe:fade-in-0 motion-safe:zoom-in-[0.96] motion-safe:duration-[250ms] motion-safe:ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:animate-none";
|
|
551
603
|
function WorkspaceFileReferencePicker({
|
|
552
604
|
copy,
|
|
553
605
|
fileAdapter,
|
|
@@ -555,9 +607,9 @@ function WorkspaceFileReferencePicker({
|
|
|
555
607
|
onClose,
|
|
556
608
|
onConfirm,
|
|
557
609
|
open,
|
|
610
|
+
scoped = false,
|
|
558
611
|
workspaceId
|
|
559
612
|
}) {
|
|
560
|
-
const titleId = useId();
|
|
561
613
|
const {
|
|
562
614
|
browseRootEntries,
|
|
563
615
|
directoryStateByPath,
|
|
@@ -582,52 +634,27 @@ function WorkspaceFileReferencePicker({
|
|
|
582
634
|
open,
|
|
583
635
|
workspaceId
|
|
584
636
|
});
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
588
|
-
const dialog = /* @__PURE__ */ jsx3(
|
|
589
|
-
"div",
|
|
637
|
+
return /* @__PURE__ */ jsx3(
|
|
638
|
+
Dialog,
|
|
590
639
|
{
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
640
|
+
open,
|
|
641
|
+
onOpenChange: (nextOpen) => {
|
|
642
|
+
if (!nextOpen) {
|
|
643
|
+
onClose();
|
|
644
|
+
}
|
|
645
|
+
},
|
|
597
646
|
children: /* @__PURE__ */ jsxs3(
|
|
598
|
-
|
|
647
|
+
DialogContent,
|
|
599
648
|
{
|
|
600
|
-
"aria-
|
|
601
|
-
"aria-modal": "true",
|
|
649
|
+
"aria-describedby": void 0,
|
|
602
650
|
className: cn2(
|
|
603
|
-
"nodrag flex h-[min(88vh,44rem)] w-full max-w-5xl
|
|
604
|
-
workspaceFileReferencePickerPanelMotionClassName
|
|
651
|
+
"nodrag flex h-[min(88vh,44rem)] w-full max-w-5xl flex-col gap-0 overflow-hidden border-[var(--line-1)] bg-[var(--background-fronted)] p-0 text-[var(--text-primary)] shadow-panel [-webkit-app-region:no-drag] sm:h-[min(82vh,44rem)] sm:max-w-5xl"
|
|
605
652
|
),
|
|
606
|
-
|
|
607
|
-
|
|
653
|
+
overlayClassName: cn2("nodrag", scoped && "!absolute"),
|
|
654
|
+
portaled: !scoped,
|
|
608
655
|
children: [
|
|
609
|
-
/* @__PURE__ */ jsx3(
|
|
610
|
-
|
|
611
|
-
{
|
|
612
|
-
className: "border-b px-4 py-4 sm:px-6 sm:py-5",
|
|
613
|
-
style: { borderBottomColor: "var(--line-1)" },
|
|
614
|
-
children: /* @__PURE__ */ jsxs3("div", { className: "flex items-start justify-between gap-4", children: [
|
|
615
|
-
/* @__PURE__ */ jsx3("div", { className: "min-w-0", children: /* @__PURE__ */ jsx3(CardTitle, { id: titleId, children: copy.t("referencePicker.title") }) }),
|
|
616
|
-
/* @__PURE__ */ jsx3(
|
|
617
|
-
Button3,
|
|
618
|
-
{
|
|
619
|
-
"aria-label": copy.t("actions.cancel"),
|
|
620
|
-
size: "icon-sm",
|
|
621
|
-
type: "button",
|
|
622
|
-
variant: "ghost",
|
|
623
|
-
onClick: onClose,
|
|
624
|
-
children: /* @__PURE__ */ jsx3(CloseIcon, { size: 16 })
|
|
625
|
-
}
|
|
626
|
-
)
|
|
627
|
-
] })
|
|
628
|
-
}
|
|
629
|
-
),
|
|
630
|
-
/* @__PURE__ */ jsxs3(CardContent, { className: "grid min-h-0 flex-1 grid-rows-[minmax(0,1fr)_auto] gap-0 overflow-hidden p-0 lg:grid-cols-[minmax(0,1.15fr)_minmax(20rem,0.85fr)] lg:grid-rows-1", children: [
|
|
656
|
+
/* @__PURE__ */ jsx3(DialogHeader, { className: "flex-none border-b border-[var(--line-1)] px-4 py-4 sm:px-6 sm:py-5", children: /* @__PURE__ */ jsx3(DialogTitle, { children: copy.t("referencePicker.title") }) }),
|
|
657
|
+
/* @__PURE__ */ jsxs3("div", { className: "grid min-h-0 flex-1 grid-rows-[minmax(0,1fr)_auto] gap-0 overflow-hidden p-0 lg:grid-cols-[minmax(0,1.15fr)_minmax(20rem,0.85fr)] lg:grid-rows-1", children: [
|
|
631
658
|
/* @__PURE__ */ jsx3(
|
|
632
659
|
WorkspaceFileReferencePickerBrowserPane,
|
|
633
660
|
{
|
|
@@ -671,10 +698,6 @@ function WorkspaceFileReferencePicker({
|
|
|
671
698
|
)
|
|
672
699
|
}
|
|
673
700
|
);
|
|
674
|
-
if (typeof document === "undefined") {
|
|
675
|
-
return dialog;
|
|
676
|
-
}
|
|
677
|
-
return createPortal(dialog, document.body);
|
|
678
701
|
}
|
|
679
702
|
|
|
680
703
|
// src/ui/internal/reference/ReferenceSourcePicker.tsx
|
|
@@ -682,45 +705,58 @@ import {
|
|
|
682
705
|
useEffect as useEffect3,
|
|
683
706
|
useId as useId2,
|
|
684
707
|
useRef as useRef2,
|
|
685
|
-
useState as
|
|
708
|
+
useState as useState4
|
|
686
709
|
} from "react";
|
|
687
|
-
import { createPortal
|
|
710
|
+
import { createPortal } from "react-dom";
|
|
688
711
|
import {
|
|
689
712
|
ArrowRightIcon as ArrowRightIcon2,
|
|
690
713
|
Badge as Badge2,
|
|
691
|
-
Button as
|
|
692
|
-
Card
|
|
693
|
-
CardContent
|
|
694
|
-
CardHeader
|
|
695
|
-
CardTitle
|
|
714
|
+
Button as Button3,
|
|
715
|
+
Card,
|
|
716
|
+
CardContent,
|
|
717
|
+
CardHeader,
|
|
718
|
+
CardTitle,
|
|
696
719
|
CheckIcon as CheckIcon2,
|
|
697
|
-
|
|
720
|
+
ChevronDownIcon,
|
|
721
|
+
CloseIcon,
|
|
698
722
|
FileIcon as FileIcon3,
|
|
699
723
|
FolderFilledIcon as FolderFilledIcon3,
|
|
700
724
|
Input as Input2,
|
|
725
|
+
IssueIcon,
|
|
701
726
|
ResizableHandle,
|
|
702
727
|
ResizablePanel,
|
|
703
728
|
ResizablePanelGroup,
|
|
704
729
|
ScrollArea as ScrollArea2,
|
|
705
730
|
SearchIcon as SearchIcon2,
|
|
706
731
|
Spinner as Spinner2,
|
|
732
|
+
Tooltip as Tooltip2,
|
|
733
|
+
TooltipContent as TooltipContent2,
|
|
734
|
+
TooltipTrigger as TooltipTrigger2,
|
|
707
735
|
cn as cn3
|
|
708
736
|
} from "@tutti-os/ui-system";
|
|
709
|
-
import {
|
|
737
|
+
import { AddLinedIcon as AddLinedIcon2 } from "@tutti-os/ui-system/icons";
|
|
738
|
+
import {
|
|
739
|
+
WorkspaceFilePreviewSurface
|
|
740
|
+
} from "@tutti-os/workspace-file-preview/react";
|
|
710
741
|
|
|
711
742
|
// src/react/internal/reference/useReferenceSourcePickerView.ts
|
|
712
|
-
import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as
|
|
743
|
+
import { useCallback, useEffect as useEffect2, useMemo, useRef, useState as useState3 } from "react";
|
|
713
744
|
import { useSnapshot } from "valtio";
|
|
714
745
|
import {
|
|
715
746
|
sortWorkspaceFileEntriesForArrangeMode
|
|
716
747
|
} from "@tutti-os/workspace-file-manager/services";
|
|
717
|
-
|
|
748
|
+
import {
|
|
749
|
+
createWorkspaceFilePreviewLoadedState
|
|
750
|
+
} from "@tutti-os/workspace-file-preview";
|
|
718
751
|
function useReferenceSourcePickerView({
|
|
719
752
|
aggregator,
|
|
720
753
|
workspaceId,
|
|
721
754
|
open,
|
|
755
|
+
workspaceRootGroupLabel,
|
|
756
|
+
initialTarget = null,
|
|
722
757
|
onClose,
|
|
723
|
-
onConfirm
|
|
758
|
+
onConfirm,
|
|
759
|
+
onConfirmBundles
|
|
724
760
|
}) {
|
|
725
761
|
const readSnapshot = useSnapshot;
|
|
726
762
|
const scope = useMemo(() => ({ workspaceId }), [workspaceId]);
|
|
@@ -729,11 +765,9 @@ function useReferenceSourcePickerView({
|
|
|
729
765
|
[aggregator, scope]
|
|
730
766
|
);
|
|
731
767
|
const snapshot = readSnapshot(controller.store);
|
|
732
|
-
const [breadcrumbBySource, setBreadcrumbBySource] =
|
|
733
|
-
const [focusedNode, setFocusedNode] =
|
|
734
|
-
const [arrangeMode, setArrangeMode] =
|
|
735
|
-
const [expandedSources, setExpandedSources] = useState2({});
|
|
736
|
-
const expandSeededRef = useRef(false);
|
|
768
|
+
const [breadcrumbBySource, setBreadcrumbBySource] = useState3({});
|
|
769
|
+
const [focusedNode, setFocusedNode] = useState3(null);
|
|
770
|
+
const [arrangeMode, setArrangeMode] = useState3("none");
|
|
737
771
|
const sortNodes = useCallback(
|
|
738
772
|
(nodes) => {
|
|
739
773
|
if (arrangeMode === "none") {
|
|
@@ -757,6 +791,7 @@ function useReferenceSourcePickerView({
|
|
|
757
791
|
[arrangeMode]
|
|
758
792
|
);
|
|
759
793
|
const autoEnteredSourcesRef = useRef(/* @__PURE__ */ new Set());
|
|
794
|
+
const appliedInitialTargetRef = useRef(null);
|
|
760
795
|
useEffect2(() => {
|
|
761
796
|
if (!open) {
|
|
762
797
|
return;
|
|
@@ -765,13 +800,47 @@ function useReferenceSourcePickerView({
|
|
|
765
800
|
controller.open();
|
|
766
801
|
setBreadcrumbBySource({});
|
|
767
802
|
setFocusedNode(null);
|
|
768
|
-
setExpandedSources({});
|
|
769
|
-
expandSeededRef.current = false;
|
|
770
803
|
autoEnteredSourcesRef.current = /* @__PURE__ */ new Set();
|
|
804
|
+
appliedInitialTargetRef.current = null;
|
|
771
805
|
return () => {
|
|
772
806
|
controller.close();
|
|
773
807
|
};
|
|
774
808
|
}, [controller, open]);
|
|
809
|
+
useEffect2(() => {
|
|
810
|
+
const target = initialTarget;
|
|
811
|
+
if (!open || !target || appliedInitialTargetRef.current === target) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
appliedInitialTargetRef.current = target;
|
|
815
|
+
autoEnteredSourcesRef.current.add(target.sourceId);
|
|
816
|
+
let canceled = false;
|
|
817
|
+
void controller.locatePath(target).then((path) => {
|
|
818
|
+
if (canceled) {
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
const group = path[0];
|
|
822
|
+
if (!group) {
|
|
823
|
+
autoEnteredSourcesRef.current.delete(target.sourceId);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
controller.setActiveSource(target.sourceId);
|
|
827
|
+
setBreadcrumbBySource((current) => ({
|
|
828
|
+
...current,
|
|
829
|
+
[group.ref.sourceId]: [group]
|
|
830
|
+
}));
|
|
831
|
+
controller.ensureChildren(group);
|
|
832
|
+
for (const node of path.slice(1)) {
|
|
833
|
+
controller.expandNode(node);
|
|
834
|
+
}
|
|
835
|
+
const deepest = path[path.length - 1];
|
|
836
|
+
setFocusedNode(path.length > 1 && deepest ? deepest : null);
|
|
837
|
+
}).catch(() => {
|
|
838
|
+
autoEnteredSourcesRef.current.delete(target.sourceId);
|
|
839
|
+
});
|
|
840
|
+
return () => {
|
|
841
|
+
canceled = true;
|
|
842
|
+
};
|
|
843
|
+
}, [open, initialTarget, controller]);
|
|
775
844
|
const activeSourceId = snapshot.activeSourceId;
|
|
776
845
|
const activeTab = useMemo(
|
|
777
846
|
() => snapshot.tabs.find((tab) => tab.sourceId === activeSourceId) ?? null,
|
|
@@ -782,7 +851,8 @@ function useReferenceSourcePickerView({
|
|
|
782
851
|
const currentNode = breadcrumb.at(-1) ?? null;
|
|
783
852
|
const currentKey = currentNode ? nodeRefKey(currentNode.ref) : ROOT_CHILDREN_KEY;
|
|
784
853
|
const activeTabState = activeSourceId ? snapshot.bySource[activeSourceId] : void 0;
|
|
785
|
-
const
|
|
854
|
+
const activeFilters = activeTabState?.searchFilters ?? [];
|
|
855
|
+
const isQuery = activeTabState?.mode === "search" && (activeTabState.searchQuery.trim() !== "" || activeFilters.length > 0);
|
|
786
856
|
const currentChildren = activeTabState?.childrenByKey[currentKey];
|
|
787
857
|
const currentEntries = useMemo(
|
|
788
858
|
() => sortNodes(currentChildren?.entries ?? []),
|
|
@@ -811,18 +881,51 @@ function useReferenceSourcePickerView({
|
|
|
811
881
|
const workspaceRoot = {
|
|
812
882
|
ref: { sourceId, nodeId: WORKSPACE_ROOT_GROUP_NODE_ID },
|
|
813
883
|
kind: "folder",
|
|
814
|
-
displayName:
|
|
884
|
+
displayName: workspaceRootGroupLabel
|
|
815
885
|
};
|
|
816
886
|
result[sourceId] = [workspaceRoot, ...folders];
|
|
817
887
|
}
|
|
818
888
|
}
|
|
819
889
|
return result;
|
|
890
|
+
}, [
|
|
891
|
+
snapshot.tabs,
|
|
892
|
+
snapshot.bySource,
|
|
893
|
+
aggregator,
|
|
894
|
+
scope,
|
|
895
|
+
workspaceRootGroupLabel
|
|
896
|
+
]);
|
|
897
|
+
const sidebarHasMoreBySource = useMemo(() => {
|
|
898
|
+
const result = {};
|
|
899
|
+
for (const tab of snapshot.tabs) {
|
|
900
|
+
const sourceId = tab.sourceId;
|
|
901
|
+
const provided = aggregator.getLoadedSource(sourceId)?.listSidebarGroups?.(scope);
|
|
902
|
+
if (provided && provided.length > 0) {
|
|
903
|
+
result[sourceId] = false;
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
const root = snapshot.bySource[sourceId]?.childrenByKey[ROOT_CHILDREN_KEY];
|
|
907
|
+
result[sourceId] = Boolean(root?.nextCursor);
|
|
908
|
+
}
|
|
909
|
+
return result;
|
|
820
910
|
}, [snapshot.tabs, snapshot.bySource, aggregator, scope]);
|
|
911
|
+
const sidebarLoadingMoreBySource = useMemo(() => {
|
|
912
|
+
const result = {};
|
|
913
|
+
for (const tab of snapshot.tabs) {
|
|
914
|
+
const root = snapshot.bySource[tab.sourceId]?.childrenByKey[ROOT_CHILDREN_KEY];
|
|
915
|
+
result[tab.sourceId] = Boolean(root?.loaded && root.loading);
|
|
916
|
+
}
|
|
917
|
+
return result;
|
|
918
|
+
}, [snapshot.tabs, snapshot.bySource]);
|
|
821
919
|
const sidebarGroups = activeSourceId ? sidebarGroupsBySource[activeSourceId] ?? [] : [];
|
|
822
|
-
const
|
|
920
|
+
const rootGroupNode = breadcrumb[0] ?? null;
|
|
921
|
+
const searchScopeNodeId = rootGroupNode ? rootGroupNode.ref.nodeId : null;
|
|
922
|
+
const selectedGroupKey = rootGroupNode ? nodeRefKey(rootGroupNode.ref) : activeSourceId && !capabilities?.navigable ? nodeRefKey({
|
|
823
923
|
sourceId: activeSourceId,
|
|
824
924
|
nodeId: WORKSPACE_ROOT_GROUP_NODE_ID
|
|
825
925
|
}) : null;
|
|
926
|
+
useEffect2(() => {
|
|
927
|
+
controller.setSearchScope(searchScopeNodeId);
|
|
928
|
+
}, [controller, searchScopeNodeId]);
|
|
826
929
|
const setActiveSource = useCallback(
|
|
827
930
|
(sourceId) => {
|
|
828
931
|
controller.setActiveSource(sourceId);
|
|
@@ -850,7 +953,7 @@ function useReferenceSourcePickerView({
|
|
|
850
953
|
[controller]
|
|
851
954
|
);
|
|
852
955
|
useEffect2(() => {
|
|
853
|
-
if (!open || !activeSourceId
|
|
956
|
+
if (!open || !activeSourceId) {
|
|
854
957
|
return;
|
|
855
958
|
}
|
|
856
959
|
if (autoEnteredSourcesRef.current.has(activeSourceId)) {
|
|
@@ -867,23 +970,15 @@ function useReferenceSourcePickerView({
|
|
|
867
970
|
}
|
|
868
971
|
autoEnteredSourcesRef.current.add(activeSourceId);
|
|
869
972
|
enterFolder(firstGroup);
|
|
870
|
-
}, [
|
|
871
|
-
open,
|
|
872
|
-
activeSourceId,
|
|
873
|
-
capabilities?.navigable,
|
|
874
|
-
sidebarGroups,
|
|
875
|
-
breadcrumbBySource,
|
|
876
|
-
enterFolder
|
|
877
|
-
]);
|
|
973
|
+
}, [open, activeSourceId, sidebarGroups, breadcrumbBySource, enterFolder]);
|
|
878
974
|
useEffect2(() => {
|
|
879
|
-
if (!open
|
|
975
|
+
if (!open) {
|
|
880
976
|
return;
|
|
881
977
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
}, [open, activeSourceId]);
|
|
978
|
+
for (const tab of snapshot.tabs) {
|
|
979
|
+
controller.ensureSourceRoot(tab.sourceId);
|
|
980
|
+
}
|
|
981
|
+
}, [open, snapshot.tabs, controller]);
|
|
887
982
|
const navigateToBreadcrumb = useCallback(
|
|
888
983
|
(index) => {
|
|
889
984
|
if (!activeSourceId) {
|
|
@@ -924,26 +1019,11 @@ function useReferenceSourcePickerView({
|
|
|
924
1019
|
navigateToRoot(sourceId);
|
|
925
1020
|
return;
|
|
926
1021
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
);
|
|
931
|
-
const isSourceExpanded = useCallback(
|
|
932
|
-
(sourceId) => expandedSources[sourceId] ?? false,
|
|
933
|
-
[expandedSources]
|
|
934
|
-
);
|
|
935
|
-
const toggleSourceExpanded = useCallback(
|
|
936
|
-
(sourceId) => {
|
|
937
|
-
const willExpand = !(expandedSources[sourceId] ?? false);
|
|
938
|
-
setExpandedSources((current) => ({
|
|
939
|
-
...current,
|
|
940
|
-
[sourceId]: !(current[sourceId] ?? false)
|
|
941
|
-
}));
|
|
942
|
-
if (willExpand && !aggregator.getLoadedSource(sourceId)?.listSidebarGroups) {
|
|
943
|
-
controller.ensureSourceRoot(sourceId);
|
|
944
|
-
}
|
|
1022
|
+
controller.ensureChildren(node);
|
|
1023
|
+
setBreadcrumbBySource((current) => ({ ...current, [sourceId]: [node] }));
|
|
1024
|
+
setFocusedNode(null);
|
|
945
1025
|
},
|
|
946
|
-
[
|
|
1026
|
+
[controller, snapshot.activeSourceId, navigateToRoot]
|
|
947
1027
|
);
|
|
948
1028
|
const isSelected = useCallback(
|
|
949
1029
|
(node) => snapshot.selection.some(
|
|
@@ -951,23 +1031,144 @@ function useReferenceSourcePickerView({
|
|
|
951
1031
|
),
|
|
952
1032
|
[snapshot.selection]
|
|
953
1033
|
);
|
|
954
|
-
const [isConfirming, setIsConfirming] =
|
|
1034
|
+
const [isConfirming, setIsConfirming] = useState3(false);
|
|
955
1035
|
const confirm = useCallback(async () => {
|
|
956
1036
|
if (isConfirming) {
|
|
957
1037
|
return;
|
|
958
1038
|
}
|
|
959
1039
|
setIsConfirming(true);
|
|
960
1040
|
try {
|
|
961
|
-
|
|
962
|
-
|
|
1041
|
+
if (onConfirmBundles) {
|
|
1042
|
+
const grouped = await controller.confirmGrouped();
|
|
1043
|
+
onConfirmBundles({
|
|
1044
|
+
files: grouped.files.map(selectedReferenceToWorkspaceFileReference),
|
|
1045
|
+
bundles: grouped.bundles.map((bundle) => ({
|
|
1046
|
+
sourceId: bundle.root.ref.sourceId,
|
|
1047
|
+
nodeId: bundle.root.ref.nodeId,
|
|
1048
|
+
displayName: bundle.root.displayName,
|
|
1049
|
+
iconUrl: bundle.root.iconUrl ?? null,
|
|
1050
|
+
handle: bundle.handle,
|
|
1051
|
+
// 展示用文件数:取节点 childCount(不再展开文件);缺省回退 0。
|
|
1052
|
+
fileCount: bundle.root.childCount ?? 0
|
|
1053
|
+
}))
|
|
1054
|
+
});
|
|
1055
|
+
} else {
|
|
1056
|
+
const selected = await controller.confirm();
|
|
1057
|
+
onConfirm(selected.map(selectedReferenceToWorkspaceFileReference));
|
|
1058
|
+
}
|
|
963
1059
|
onClose();
|
|
964
1060
|
} finally {
|
|
965
1061
|
setIsConfirming(false);
|
|
966
1062
|
}
|
|
967
|
-
}, [controller, isConfirming, onClose, onConfirm]);
|
|
1063
|
+
}, [controller, isConfirming, onClose, onConfirm, onConfirmBundles]);
|
|
1064
|
+
const [previewState, setPreviewState] = useState3({
|
|
1065
|
+
status: "empty"
|
|
1066
|
+
});
|
|
1067
|
+
const previewObjectUrlRef = useRef(null);
|
|
1068
|
+
const revokePreviewObjectUrl = useCallback(() => {
|
|
1069
|
+
if (previewObjectUrlRef.current) {
|
|
1070
|
+
URL.revokeObjectURL(previewObjectUrlRef.current);
|
|
1071
|
+
previewObjectUrlRef.current = null;
|
|
1072
|
+
}
|
|
1073
|
+
}, []);
|
|
1074
|
+
useEffect2(() => {
|
|
1075
|
+
const node = focusedNode;
|
|
1076
|
+
if (!node) {
|
|
1077
|
+
revokePreviewObjectUrl();
|
|
1078
|
+
setPreviewState({ status: "empty" });
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
if (node.kind === "folder") {
|
|
1082
|
+
revokePreviewObjectUrl();
|
|
1083
|
+
setPreviewState({ node, status: "directory" });
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
const previewable = aggregator.getLoadedSource(node.ref.sourceId)?.capabilities.previewable ?? false;
|
|
1087
|
+
if (!previewable) {
|
|
1088
|
+
revokePreviewObjectUrl();
|
|
1089
|
+
setPreviewState({ node, status: "unsupported" });
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
let cancelled = false;
|
|
1093
|
+
revokePreviewObjectUrl();
|
|
1094
|
+
setPreviewState({ node, status: "loading" });
|
|
1095
|
+
void (async () => {
|
|
1096
|
+
try {
|
|
1097
|
+
const preview = await aggregator.readPreview(scope, node);
|
|
1098
|
+
if (cancelled) {
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
if (!preview) {
|
|
1102
|
+
setPreviewState({ node, status: "unsupported" });
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
const loaded = createWorkspaceFilePreviewLoadedState({
|
|
1106
|
+
bytes: preview.bytes,
|
|
1107
|
+
contentType: preview.contentType,
|
|
1108
|
+
entry: {
|
|
1109
|
+
kind: node.kind,
|
|
1110
|
+
name: node.displayName,
|
|
1111
|
+
path: node.ref.nodeId,
|
|
1112
|
+
mtimeMs: node.mtimeMs ?? null,
|
|
1113
|
+
sizeBytes: node.sizeBytes ?? null
|
|
1114
|
+
},
|
|
1115
|
+
renderHtml: true,
|
|
1116
|
+
target: {
|
|
1117
|
+
fileKind: preview.kind,
|
|
1118
|
+
name: node.displayName,
|
|
1119
|
+
path: node.ref.nodeId,
|
|
1120
|
+
mtimeMs: node.mtimeMs ?? null,
|
|
1121
|
+
sizeBytes: node.sizeBytes ?? null
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
if (cancelled) {
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
if (loaded.status === "image") {
|
|
1128
|
+
const objectUrl = URL.createObjectURL(
|
|
1129
|
+
new Blob([loaded.bytes], { type: loaded.contentType })
|
|
1130
|
+
);
|
|
1131
|
+
previewObjectUrlRef.current = objectUrl;
|
|
1132
|
+
setPreviewState({ node, objectUrl, status: "image" });
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
if (loaded.status === "video") {
|
|
1136
|
+
const objectUrl = URL.createObjectURL(
|
|
1137
|
+
new Blob([loaded.bytes], { type: loaded.contentType })
|
|
1138
|
+
);
|
|
1139
|
+
previewObjectUrlRef.current = objectUrl;
|
|
1140
|
+
setPreviewState({ node, objectUrl, status: "video" });
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
if (loaded.status === "text") {
|
|
1144
|
+
setPreviewState({ content: loaded.content, node, status: "text" });
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
if (loaded.status === "html") {
|
|
1148
|
+
setPreviewState({ content: loaded.content, node, status: "html" });
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
setPreviewState({
|
|
1152
|
+
node,
|
|
1153
|
+
reason: loaded.reason,
|
|
1154
|
+
...loaded.maxSizeBytes == null ? {} : { maxSizeBytes: loaded.maxSizeBytes },
|
|
1155
|
+
status: "readonly"
|
|
1156
|
+
});
|
|
1157
|
+
} catch {
|
|
1158
|
+
if (!cancelled) {
|
|
1159
|
+
setPreviewState({ node, status: "error" });
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
})();
|
|
1163
|
+
return () => {
|
|
1164
|
+
cancelled = true;
|
|
1165
|
+
};
|
|
1166
|
+
}, [aggregator, focusedNode, revokePreviewObjectUrl, scope]);
|
|
1167
|
+
useEffect2(() => revokePreviewObjectUrl, [revokePreviewObjectUrl]);
|
|
968
1168
|
return {
|
|
969
1169
|
tabs: snapshot.tabs,
|
|
970
1170
|
activeSourceId,
|
|
1171
|
+
previewState,
|
|
971
1172
|
activeTabLabel: activeTab?.label ?? "",
|
|
972
1173
|
capabilities,
|
|
973
1174
|
// 内容区递归就地树:当前选中二级节点的子条目(本地根时为源根子条目)。
|
|
@@ -983,15 +1184,25 @@ function useReferenceSourcePickerView({
|
|
|
983
1184
|
currentNode,
|
|
984
1185
|
sidebarGroups,
|
|
985
1186
|
sidebarGroupsBySource,
|
|
986
|
-
|
|
987
|
-
|
|
1187
|
+
sidebarHasMoreBySource,
|
|
1188
|
+
sidebarLoadingMoreBySource,
|
|
1189
|
+
loadMoreSidebarGroups: (sourceId) => controller.loadMoreSourceRoot(sourceId),
|
|
988
1190
|
selectedGroupKey,
|
|
989
1191
|
arrangeMode,
|
|
990
1192
|
setArrangeMode,
|
|
991
|
-
|
|
1193
|
+
// 查询态(关键词或筛选任一非空)→ 平铺结果;否则浏览树。
|
|
1194
|
+
isQuery,
|
|
992
1195
|
searchQuery: activeTabState?.searchQuery ?? "",
|
|
993
|
-
|
|
994
|
-
|
|
1196
|
+
// 当前源支持的文件类型筛选分类(不支持则空数组,picker 据此决定是否展示筛选下拉)。
|
|
1197
|
+
filterCategories: capabilities?.filterable ? REFERENCE_FILTER_CATEGORIES : [],
|
|
1198
|
+
activeFilters,
|
|
1199
|
+
// 搜索态:仅在「还没有任何结果」时显示 spinner;细化关键词(已有结果)时
|
|
1200
|
+
// 保留旧结果直到新结果就绪,避免内容区在 spinner/结果间反复切换造成闪烁。
|
|
1201
|
+
isLoading: isQuery ? (activeTabState?.isSearchLoading ?? false) && (activeTabState?.searchEntries.length ?? 0) === 0 : currentChildren?.loading ?? false,
|
|
1202
|
+
// 查询态:增长式分页是否还有更多;浏览态:cursor 是否有下一页。
|
|
1203
|
+
hasMore: isQuery ? activeTabState?.searchHasMore ?? false : Boolean(currentChildren?.nextCursor),
|
|
1204
|
+
// 底部「加载更多」在途(查询态 = 增长重查;浏览态 = cursor append)。
|
|
1205
|
+
isLoadingMore: isQuery ? activeTabState?.isSearchLoadingMore ?? false : currentChildren?.loading ?? false,
|
|
995
1206
|
focusedNode,
|
|
996
1207
|
selection: snapshot.selection,
|
|
997
1208
|
selectionCount: snapshot.selection.length,
|
|
@@ -1001,9 +1212,10 @@ function useReferenceSourcePickerView({
|
|
|
1001
1212
|
navigateToBreadcrumb,
|
|
1002
1213
|
navigateToRoot,
|
|
1003
1214
|
setFocusedNode,
|
|
1004
|
-
setSearchQuery: (query) => controller.setSearchQuery(query),
|
|
1215
|
+
setSearchQuery: (query) => controller.setSearchQuery(query, searchScopeNodeId),
|
|
1216
|
+
setFilters: (filters) => controller.setSearchFilters(filters, searchScopeNodeId),
|
|
1005
1217
|
toggleSelection: (node) => controller.toggleSelection(node),
|
|
1006
|
-
loadMore: () => controller.loadMore(currentNode),
|
|
1218
|
+
loadMore: () => isQuery ? controller.loadMoreSearch() : controller.loadMore(currentNode),
|
|
1007
1219
|
isSelected,
|
|
1008
1220
|
confirm,
|
|
1009
1221
|
isConfirming
|
|
@@ -1012,17 +1224,7 @@ function useReferenceSourcePickerView({
|
|
|
1012
1224
|
|
|
1013
1225
|
// src/ui/internal/reference/ReferenceSourcePicker.tsx
|
|
1014
1226
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1015
|
-
var
|
|
1016
|
-
sourceColumn: "\u5206\u7C7B",
|
|
1017
|
-
selectGroupHint: "\u4ECE\u5DE6\u4FA7\u9009\u62E9\u4E00\u4E2A\u76EE\u5F55",
|
|
1018
|
-
previewSource: "\u4EA7\u51FA\u6765\u6E90",
|
|
1019
|
-
previewModified: "\u4EA7\u51FA\u65F6\u95F4",
|
|
1020
|
-
previewSize: "\u6587\u4EF6\u5927\u5C0F",
|
|
1021
|
-
previewHierarchy: "\u6240\u5C5E\u5C42\u7EA7",
|
|
1022
|
-
reference: "\u5F15\u7528",
|
|
1023
|
-
loadMore: "\u52A0\u8F7D\u66F4\u591A",
|
|
1024
|
-
emptyPreview: "\u9009\u62E9\u4E00\u4E2A\u6587\u4EF6\u67E5\u770B\u8BE6\u60C5"
|
|
1025
|
-
};
|
|
1227
|
+
var SIDEBAR_GROUP_PAGE_SIZE = 5;
|
|
1026
1228
|
function autoFitPanelWidth(groupEl, contentEl, panel, trailingPx) {
|
|
1027
1229
|
if (!groupEl || !contentEl || !panel) {
|
|
1028
1230
|
return;
|
|
@@ -1051,8 +1253,10 @@ function autoFitPanelWidth(groupEl, contentEl, panel, trailingPx) {
|
|
|
1051
1253
|
function ReferenceSourcePicker({
|
|
1052
1254
|
aggregator,
|
|
1053
1255
|
copy,
|
|
1256
|
+
initialTarget,
|
|
1054
1257
|
onClose,
|
|
1055
1258
|
onConfirm,
|
|
1259
|
+
onConfirmBundles,
|
|
1056
1260
|
open,
|
|
1057
1261
|
workspaceId
|
|
1058
1262
|
}) {
|
|
@@ -1061,9 +1265,23 @@ function ReferenceSourcePicker({
|
|
|
1061
1265
|
aggregator,
|
|
1062
1266
|
workspaceId,
|
|
1063
1267
|
open,
|
|
1268
|
+
workspaceRootGroupLabel: copy.t("referencePicker.workspaceRootGroup"),
|
|
1269
|
+
initialTarget,
|
|
1064
1270
|
onClose,
|
|
1065
|
-
onConfirm
|
|
1271
|
+
onConfirm,
|
|
1272
|
+
onConfirmBundles
|
|
1066
1273
|
});
|
|
1274
|
+
const activeFilterSet = new Set(view.activeFilters);
|
|
1275
|
+
const toggleFilter = (id) => {
|
|
1276
|
+
const next = new Set(activeFilterSet);
|
|
1277
|
+
if (next.has(id)) {
|
|
1278
|
+
next.delete(id);
|
|
1279
|
+
} else {
|
|
1280
|
+
next.add(id);
|
|
1281
|
+
}
|
|
1282
|
+
view.setFilters([...next]);
|
|
1283
|
+
};
|
|
1284
|
+
const clearFilters = () => view.setFilters([]);
|
|
1067
1285
|
const layoutRef = useRef2(null);
|
|
1068
1286
|
const sidebarContentRef = useRef2(null);
|
|
1069
1287
|
const middleContentRef = useRef2(null);
|
|
@@ -1092,7 +1310,7 @@ function ReferenceSourcePicker({
|
|
|
1092
1310
|
style: { zIndex: "var(--z-panel)" },
|
|
1093
1311
|
onClick: onClose,
|
|
1094
1312
|
children: /* @__PURE__ */ jsxs4(
|
|
1095
|
-
|
|
1313
|
+
Card,
|
|
1096
1314
|
{
|
|
1097
1315
|
"aria-labelledby": titleId,
|
|
1098
1316
|
"aria-modal": "true",
|
|
@@ -1100,38 +1318,45 @@ function ReferenceSourcePicker({
|
|
|
1100
1318
|
role: "dialog",
|
|
1101
1319
|
onClick: (event) => event.stopPropagation(),
|
|
1102
1320
|
children: [
|
|
1103
|
-
/* @__PURE__ */ jsx4(
|
|
1104
|
-
/* @__PURE__ */ jsx4(
|
|
1321
|
+
/* @__PURE__ */ jsx4(CardHeader, { className: "gap-3 px-4 pt-4 pb-4 sm:px-6", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-start justify-between gap-4", children: [
|
|
1322
|
+
/* @__PURE__ */ jsx4(CardTitle, { id: titleId, children: copy.t("referencePicker.title") }),
|
|
1105
1323
|
/* @__PURE__ */ jsx4(
|
|
1106
|
-
|
|
1324
|
+
Button3,
|
|
1107
1325
|
{
|
|
1108
1326
|
"aria-label": copy.t("actions.cancel"),
|
|
1109
1327
|
size: "icon-sm",
|
|
1110
1328
|
type: "button",
|
|
1111
1329
|
variant: "ghost",
|
|
1112
1330
|
onClick: onClose,
|
|
1113
|
-
children: /* @__PURE__ */ jsx4(
|
|
1331
|
+
children: /* @__PURE__ */ jsx4(CloseIcon, { size: 16 })
|
|
1114
1332
|
}
|
|
1115
1333
|
)
|
|
1116
1334
|
] }) }),
|
|
1117
|
-
/* @__PURE__ */ jsx4(
|
|
1335
|
+
/* @__PURE__ */ jsx4(CardContent, { className: "flex min-h-0 flex-1 overflow-hidden border-t border-[var(--line-1)] p-0", children: /* @__PURE__ */ jsx4("div", { ref: layoutRef, className: "flex min-h-0 min-w-0 flex-1", children: /* @__PURE__ */ jsxs4(
|
|
1118
1336
|
ResizablePanelGroup,
|
|
1119
1337
|
{
|
|
1120
|
-
className: "min-h-0 flex-1",
|
|
1338
|
+
className: "min-h-0 min-w-0 flex-1",
|
|
1121
1339
|
orientation: "horizontal",
|
|
1122
|
-
defaultLayout: { sidebar: 2, middle: 5, preview: 3 },
|
|
1340
|
+
defaultLayout: { sidebar: 2.5, middle: 4.5, preview: 3 },
|
|
1123
1341
|
children: [
|
|
1124
1342
|
/* @__PURE__ */ jsx4(
|
|
1125
1343
|
ResizablePanel,
|
|
1126
1344
|
{
|
|
1127
1345
|
id: "sidebar",
|
|
1128
|
-
className: "min-h-0
|
|
1129
|
-
defaultSize:
|
|
1130
|
-
minSize: "
|
|
1346
|
+
className: "min-h-0 min-w-0",
|
|
1347
|
+
defaultSize: 15,
|
|
1348
|
+
minSize: "150px",
|
|
1131
1349
|
panelRef: (handle) => {
|
|
1132
1350
|
sidebarPanelRef.current = handle;
|
|
1133
1351
|
},
|
|
1134
|
-
children: /* @__PURE__ */ jsx4(
|
|
1352
|
+
children: /* @__PURE__ */ jsx4(
|
|
1353
|
+
SourceSidebar,
|
|
1354
|
+
{
|
|
1355
|
+
contentRef: sidebarContentRef,
|
|
1356
|
+
copy,
|
|
1357
|
+
view
|
|
1358
|
+
}
|
|
1359
|
+
)
|
|
1135
1360
|
}
|
|
1136
1361
|
),
|
|
1137
1362
|
/* @__PURE__ */ jsx4(
|
|
@@ -1139,6 +1364,7 @@ function ReferenceSourcePicker({
|
|
|
1139
1364
|
{
|
|
1140
1365
|
disableDoubleClick: true,
|
|
1141
1366
|
withHandle: true,
|
|
1367
|
+
className: "after:bg-[var(--line-1)]",
|
|
1142
1368
|
onDoubleClick: fitSidebar
|
|
1143
1369
|
}
|
|
1144
1370
|
),
|
|
@@ -1146,73 +1372,105 @@ function ReferenceSourcePicker({
|
|
|
1146
1372
|
ResizablePanel,
|
|
1147
1373
|
{
|
|
1148
1374
|
id: "middle",
|
|
1149
|
-
className: "min-h-0",
|
|
1375
|
+
className: "min-h-0 min-w-0",
|
|
1150
1376
|
defaultSize: 50,
|
|
1151
1377
|
minSize: "260px",
|
|
1152
1378
|
panelRef: (handle) => {
|
|
1153
1379
|
middlePanelRef.current = handle;
|
|
1154
1380
|
},
|
|
1155
1381
|
children: /* @__PURE__ */ jsxs4("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
1156
|
-
/* @__PURE__ */
|
|
1157
|
-
/* @__PURE__ */
|
|
1158
|
-
|
|
1159
|
-
|
|
1382
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 border-b border-[var(--line-1)] p-3", children: [
|
|
1383
|
+
/* @__PURE__ */ jsxs4("div", { className: "relative flex-1", children: [
|
|
1384
|
+
/* @__PURE__ */ jsx4(SearchIcon2, { className: "pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-[var(--text-tertiary)]" }),
|
|
1385
|
+
/* @__PURE__ */ jsx4(
|
|
1386
|
+
Input2,
|
|
1387
|
+
{
|
|
1388
|
+
className: "pl-9",
|
|
1389
|
+
placeholder: copy.t(
|
|
1390
|
+
"referencePicker.searchPlaceholder"
|
|
1391
|
+
),
|
|
1392
|
+
value: view.searchQuery,
|
|
1393
|
+
onChange: (event) => view.setSearchQuery(event.target.value)
|
|
1394
|
+
}
|
|
1395
|
+
)
|
|
1396
|
+
] }),
|
|
1397
|
+
view.capabilities?.filterable && view.filterCategories.length > 0 ? /* @__PURE__ */ jsx4(
|
|
1398
|
+
FilterCategoryFilter,
|
|
1160
1399
|
{
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
onChange: (event) => view.setSearchQuery(event.target.value)
|
|
1400
|
+
categories: view.filterCategories,
|
|
1401
|
+
copy,
|
|
1402
|
+
selected: activeFilterSet,
|
|
1403
|
+
onClear: clearFilters,
|
|
1404
|
+
onToggle: toggleFilter
|
|
1167
1405
|
}
|
|
1168
|
-
)
|
|
1169
|
-
] })
|
|
1170
|
-
/* @__PURE__ */ jsx4(
|
|
1171
|
-
|
|
1406
|
+
) : null
|
|
1407
|
+
] }),
|
|
1408
|
+
/* @__PURE__ */ jsx4(
|
|
1409
|
+
ScrollArea2,
|
|
1172
1410
|
{
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
focused: isFocused(view.focusedNode, node),
|
|
1182
|
-
node,
|
|
1183
|
-
selected: view.isSelected(node),
|
|
1184
|
-
onFocus: view.setFocusedNode,
|
|
1185
|
-
onToggle: view.toggleSelection
|
|
1186
|
-
},
|
|
1187
|
-
nodeRefKey(node.ref)
|
|
1188
|
-
))
|
|
1189
|
-
) : !hasSelectedGroup ? /* @__PURE__ */ jsx4(Feedback, { children: L.selectGroupHint }) : view.currentEntries.length === 0 ? /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.emptyDirectory") }) : (
|
|
1190
|
-
// 浏览:就地递归展开树(复刻 agent 引用面板文件树交互)
|
|
1191
|
-
view.currentEntries.map((node) => /* @__PURE__ */ jsx4(
|
|
1192
|
-
TreeNodeRow,
|
|
1193
|
-
{
|
|
1194
|
-
copy,
|
|
1195
|
-
depth: 0,
|
|
1196
|
-
node,
|
|
1197
|
-
view
|
|
1198
|
-
},
|
|
1199
|
-
nodeRefKey(node.ref)
|
|
1200
|
-
))
|
|
1201
|
-
),
|
|
1202
|
-
view.hasMore && hasSelectedGroup && !view.isSearch ? /* @__PURE__ */ jsx4(
|
|
1203
|
-
Button4,
|
|
1204
|
-
{
|
|
1205
|
-
className: "mt-1 w-full",
|
|
1206
|
-
size: "sm",
|
|
1207
|
-
type: "button",
|
|
1208
|
-
variant: "ghost",
|
|
1209
|
-
onClick: view.loadMore,
|
|
1210
|
-
children: L.loadMore
|
|
1411
|
+
className: "min-h-0 flex-1",
|
|
1412
|
+
viewportProps: {
|
|
1413
|
+
// 拉到底部(距底 <120px)自动加载更多 —— 查询态走增长式分页,
|
|
1414
|
+
// 浏览态走 cursor 续页。已在加载/无更多时由 loadMore 内部 no-op。
|
|
1415
|
+
onScroll: (event) => {
|
|
1416
|
+
const el = event.currentTarget;
|
|
1417
|
+
if (view.hasMore && !view.isLoading && !view.isLoadingMore && el.scrollHeight - el.scrollTop - el.clientHeight < 120) {
|
|
1418
|
+
view.loadMore();
|
|
1211
1419
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1420
|
+
}
|
|
1421
|
+
},
|
|
1422
|
+
children: /* @__PURE__ */ jsxs4(
|
|
1423
|
+
"div",
|
|
1424
|
+
{
|
|
1425
|
+
ref: middleContentRef,
|
|
1426
|
+
className: "flex flex-col gap-[2px] p-3",
|
|
1427
|
+
children: [
|
|
1428
|
+
view.isLoading ? /* @__PURE__ */ jsx4(Feedback, { children: /* @__PURE__ */ jsx4(Spinner2, { size: 16 }) }) : view.isQuery ? (
|
|
1429
|
+
// 查询态(关键词或筛选):扁平结果
|
|
1430
|
+
view.searchResults.length === 0 ? /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.emptySearch") }) : view.searchResults.map((node) => /* @__PURE__ */ jsx4(
|
|
1431
|
+
SearchResultRow,
|
|
1432
|
+
{
|
|
1433
|
+
focused: isFocused(view.focusedNode, node),
|
|
1434
|
+
node,
|
|
1435
|
+
selected: view.isSelected(node),
|
|
1436
|
+
onFocus: view.setFocusedNode,
|
|
1437
|
+
onToggle: view.toggleSelection
|
|
1438
|
+
},
|
|
1439
|
+
nodeRefKey(node.ref)
|
|
1440
|
+
))
|
|
1441
|
+
) : !hasSelectedGroup ? /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.selectGroupHint") }) : view.currentEntries.length === 0 ? /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.emptyDirectory") }) : (
|
|
1442
|
+
// 浏览:就地递归展开树(复刻 agent 引用面板文件树交互)
|
|
1443
|
+
view.currentEntries.map((node) => /* @__PURE__ */ jsx4(
|
|
1444
|
+
TreeNodeRow,
|
|
1445
|
+
{
|
|
1446
|
+
copy,
|
|
1447
|
+
depth: 0,
|
|
1448
|
+
node,
|
|
1449
|
+
view
|
|
1450
|
+
},
|
|
1451
|
+
nodeRefKey(node.ref)
|
|
1452
|
+
))
|
|
1453
|
+
),
|
|
1454
|
+
view.hasMore && (view.isQuery || hasSelectedGroup) ? /* @__PURE__ */ jsxs4(
|
|
1455
|
+
Button3,
|
|
1456
|
+
{
|
|
1457
|
+
className: "mt-1 w-full",
|
|
1458
|
+
disabled: view.isLoadingMore,
|
|
1459
|
+
size: "sm",
|
|
1460
|
+
type: "button",
|
|
1461
|
+
variant: "ghost",
|
|
1462
|
+
onClick: view.loadMore,
|
|
1463
|
+
children: [
|
|
1464
|
+
view.isLoadingMore ? /* @__PURE__ */ jsx4(Spinner2, { className: "text-current", size: 14 }) : null,
|
|
1465
|
+
copy.t("referencePicker.loadMore")
|
|
1466
|
+
]
|
|
1467
|
+
}
|
|
1468
|
+
) : null
|
|
1469
|
+
]
|
|
1470
|
+
}
|
|
1471
|
+
)
|
|
1214
1472
|
}
|
|
1215
|
-
)
|
|
1473
|
+
)
|
|
1216
1474
|
] })
|
|
1217
1475
|
}
|
|
1218
1476
|
),
|
|
@@ -1221,6 +1479,7 @@ function ReferenceSourcePicker({
|
|
|
1221
1479
|
{
|
|
1222
1480
|
disableDoubleClick: true,
|
|
1223
1481
|
withHandle: true,
|
|
1482
|
+
className: "after:bg-[var(--line-1)]",
|
|
1224
1483
|
onDoubleClick: fitMiddle
|
|
1225
1484
|
}
|
|
1226
1485
|
),
|
|
@@ -1228,17 +1487,16 @@ function ReferenceSourcePicker({
|
|
|
1228
1487
|
ResizablePanel,
|
|
1229
1488
|
{
|
|
1230
1489
|
id: "preview",
|
|
1231
|
-
className: "min-h-0
|
|
1490
|
+
className: "min-h-0 min-w-0",
|
|
1232
1491
|
defaultSize: 30,
|
|
1233
1492
|
minSize: "200px",
|
|
1234
1493
|
children: /* @__PURE__ */ jsx4(
|
|
1235
1494
|
PreviewInfoPane,
|
|
1236
1495
|
{
|
|
1496
|
+
copy,
|
|
1237
1497
|
node: view.focusedNode,
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
onReference: view.toggleSelection,
|
|
1241
|
-
referenced: view.focusedNode ? view.isSelected(view.focusedNode) : false
|
|
1498
|
+
previewState: view.previewState,
|
|
1499
|
+
sourceLabel: view.activeTabLabel
|
|
1242
1500
|
}
|
|
1243
1501
|
)
|
|
1244
1502
|
}
|
|
@@ -1269,52 +1527,73 @@ function ReferenceSourcePicker({
|
|
|
1269
1527
|
if (typeof document === "undefined") {
|
|
1270
1528
|
return dialog;
|
|
1271
1529
|
}
|
|
1272
|
-
return
|
|
1530
|
+
return createPortal(dialog, document.body);
|
|
1531
|
+
}
|
|
1532
|
+
function GroupFallbackIcon({
|
|
1533
|
+
icon,
|
|
1534
|
+
className
|
|
1535
|
+
}) {
|
|
1536
|
+
if (icon === "issue") {
|
|
1537
|
+
return /* @__PURE__ */ jsx4(IssueIcon, { className });
|
|
1538
|
+
}
|
|
1539
|
+
return /* @__PURE__ */ jsx4(FolderFilledIcon3, { className });
|
|
1273
1540
|
}
|
|
1274
1541
|
function SourceSidebar({
|
|
1542
|
+
copy,
|
|
1275
1543
|
view,
|
|
1276
1544
|
contentRef
|
|
1277
1545
|
}) {
|
|
1546
|
+
const selectedGroupRef = useRef2(null);
|
|
1547
|
+
useEffect3(() => {
|
|
1548
|
+
selectedGroupRef.current?.scrollIntoView({ block: "nearest" });
|
|
1549
|
+
}, [view.selectedGroupKey]);
|
|
1550
|
+
const [shownBySource, setShownBySource] = useState4(
|
|
1551
|
+
{}
|
|
1552
|
+
);
|
|
1553
|
+
const loadMoreGroups = (sourceId) => {
|
|
1554
|
+
const groups = view.sidebarGroupsBySource[sourceId] ?? [];
|
|
1555
|
+
const limit = shownBySource[sourceId] ?? SIDEBAR_GROUP_PAGE_SIZE;
|
|
1556
|
+
const visibleCount = Math.max(groups.length, limit);
|
|
1557
|
+
if (groups.length > limit) {
|
|
1558
|
+
setShownBySource((prev) => ({ ...prev, [sourceId]: visibleCount }));
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
if (view.sidebarHasMoreBySource[sourceId] ?? false) {
|
|
1562
|
+
view.loadMoreSidebarGroups(sourceId);
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1278
1565
|
return /* @__PURE__ */ jsx4(ScrollArea2, { className: "h-full min-h-0 w-full", children: /* @__PURE__ */ jsxs4("div", { ref: contentRef, className: "flex flex-col gap-0.5 p-2", children: [
|
|
1279
|
-
/* @__PURE__ */ jsx4("p", { className: "px-2 py-1 text-[11px] font-semibold text-[var(--text-tertiary)]", children:
|
|
1566
|
+
/* @__PURE__ */ jsx4("p", { className: "px-2 py-1 text-[11px] font-semibold text-[var(--text-tertiary)]", children: copy.t("referencePicker.sourceColumn") }),
|
|
1280
1567
|
view.tabs.map((tab) => {
|
|
1281
|
-
const active = tab.sourceId === view.activeSourceId;
|
|
1282
|
-
const expanded = view.isSourceExpanded(tab.sourceId);
|
|
1283
1568
|
const groups = view.sidebarGroupsBySource[tab.sourceId] ?? [];
|
|
1569
|
+
const limit = shownBySource[tab.sourceId] ?? SIDEBAR_GROUP_PAGE_SIZE;
|
|
1570
|
+
const selectedIndex = groups.findIndex(
|
|
1571
|
+
(group) => nodeRefKey(group.ref) === view.selectedGroupKey
|
|
1572
|
+
);
|
|
1573
|
+
const effectiveLimit = selectedIndex >= limit ? selectedIndex + 1 : limit;
|
|
1574
|
+
const visibleGroups = groups.slice(0, effectiveLimit);
|
|
1575
|
+
const loadingMore = view.sidebarLoadingMoreBySource[tab.sourceId] ?? false;
|
|
1576
|
+
const hasMore = groups.length > effectiveLimit || (view.sidebarHasMoreBySource[tab.sourceId] ?? false);
|
|
1284
1577
|
return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-0.5", children: [
|
|
1285
|
-
/* @__PURE__ */
|
|
1286
|
-
"
|
|
1578
|
+
/* @__PURE__ */ jsx4(
|
|
1579
|
+
"p",
|
|
1287
1580
|
{
|
|
1288
|
-
"
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
active ? "text-[var(--text-primary)]" : "text-[var(--text-secondary)]"
|
|
1292
|
-
),
|
|
1293
|
-
type: "button",
|
|
1294
|
-
onClick: () => view.toggleSourceExpanded(tab.sourceId),
|
|
1295
|
-
children: [
|
|
1296
|
-
/* @__PURE__ */ jsx4(
|
|
1297
|
-
ArrowRightIcon2,
|
|
1298
|
-
{
|
|
1299
|
-
className: cn3(
|
|
1300
|
-
"size-3 shrink-0 text-[var(--text-tertiary)] transition-transform",
|
|
1301
|
-
expanded && "rotate-90"
|
|
1302
|
-
)
|
|
1303
|
-
}
|
|
1304
|
-
),
|
|
1305
|
-
/* @__PURE__ */ jsx4("span", { className: "truncate", "data-autofit-label": true, children: tab.label })
|
|
1306
|
-
]
|
|
1581
|
+
className: "px-2 pt-1.5 pb-0.5 text-[11px] font-semibold text-[var(--text-tertiary)]",
|
|
1582
|
+
"data-autofit-label": true,
|
|
1583
|
+
children: tab.label
|
|
1307
1584
|
}
|
|
1308
1585
|
),
|
|
1309
|
-
|
|
1586
|
+
groups.length === 0 ? view.isLoadingTabs ? /* @__PURE__ */ jsx4("p", { className: "px-2 py-1 text-[12px] text-[var(--text-tertiary)]", children: "\u2026" }) : null : visibleGroups.map((group) => {
|
|
1310
1587
|
const key = nodeRefKey(group.ref);
|
|
1311
1588
|
const selected = key === view.selectedGroupKey;
|
|
1312
1589
|
return /* @__PURE__ */ jsxs4(
|
|
1313
1590
|
"button",
|
|
1314
1591
|
{
|
|
1592
|
+
ref: selected ? selectedGroupRef : void 0,
|
|
1593
|
+
"aria-current": selected ? "true" : void 0,
|
|
1315
1594
|
className: cn3(
|
|
1316
|
-
"flex items-center gap-2 rounded-[6px] py-1.5
|
|
1317
|
-
selected ? "bg-
|
|
1595
|
+
"flex items-center gap-2 rounded-[6px] px-2 py-1.5 text-left text-[13px] transition-colors",
|
|
1596
|
+
selected ? "bg-primary/10 font-medium text-[var(--text-primary)] hover:bg-primary/15" : "text-[var(--text-secondary)] hover:bg-[var(--transparency-hover)] hover:text-[var(--text-primary)]"
|
|
1318
1597
|
),
|
|
1319
1598
|
type: "button",
|
|
1320
1599
|
onClick: () => view.selectGroup(group),
|
|
@@ -1326,21 +1605,52 @@ function SourceSidebar({
|
|
|
1326
1605
|
className: "size-4 shrink-0 rounded-[3px] object-cover",
|
|
1327
1606
|
src: group.iconUrl
|
|
1328
1607
|
}
|
|
1329
|
-
) : /* @__PURE__ */ jsx4(
|
|
1330
|
-
|
|
1608
|
+
) : /* @__PURE__ */ jsx4(
|
|
1609
|
+
GroupFallbackIcon,
|
|
1610
|
+
{
|
|
1611
|
+
className: "size-4 shrink-0 text-[var(--rich-text-folder)]",
|
|
1612
|
+
icon: tab.icon
|
|
1613
|
+
}
|
|
1614
|
+
),
|
|
1615
|
+
/* @__PURE__ */ jsx4(FullTextTooltip, { content: group.displayName, children: /* @__PURE__ */ jsx4(
|
|
1331
1616
|
"span",
|
|
1332
1617
|
{
|
|
1333
1618
|
className: "min-w-0 flex-1 truncate",
|
|
1334
1619
|
"data-autofit-label": true,
|
|
1335
1620
|
children: group.displayName
|
|
1336
1621
|
}
|
|
1337
|
-
),
|
|
1622
|
+
) }),
|
|
1338
1623
|
group.childCount != null ? /* @__PURE__ */ jsx4("span", { className: "shrink-0 text-[11px] text-[var(--text-tertiary)]", children: group.childCount }) : null
|
|
1339
1624
|
]
|
|
1340
1625
|
},
|
|
1341
1626
|
key
|
|
1342
1627
|
);
|
|
1343
|
-
})
|
|
1628
|
+
}),
|
|
1629
|
+
hasMore ? /* @__PURE__ */ jsxs4(
|
|
1630
|
+
"button",
|
|
1631
|
+
{
|
|
1632
|
+
className: "flex items-center gap-1.5 rounded-[6px] px-2 py-1.5 text-left text-[12px] text-[var(--text-secondary)] transition-colors hover:bg-[var(--transparency-hover)] hover:text-[var(--text-primary)] disabled:opacity-60",
|
|
1633
|
+
disabled: loadingMore,
|
|
1634
|
+
type: "button",
|
|
1635
|
+
onClick: () => loadMoreGroups(tab.sourceId),
|
|
1636
|
+
children: [
|
|
1637
|
+
loadingMore ? /* @__PURE__ */ jsx4(
|
|
1638
|
+
Spinner2,
|
|
1639
|
+
{
|
|
1640
|
+
className: "text-[var(--text-secondary)]",
|
|
1641
|
+
size: 12
|
|
1642
|
+
}
|
|
1643
|
+
) : /* @__PURE__ */ jsx4(
|
|
1644
|
+
ChevronDownIcon,
|
|
1645
|
+
{
|
|
1646
|
+
className: "shrink-0 text-[var(--text-secondary)]",
|
|
1647
|
+
size: 12
|
|
1648
|
+
}
|
|
1649
|
+
),
|
|
1650
|
+
/* @__PURE__ */ jsx4("span", { children: copy.t("referencePicker.loadMoreGroups") })
|
|
1651
|
+
]
|
|
1652
|
+
}
|
|
1653
|
+
) : null
|
|
1344
1654
|
] }, tab.sourceId);
|
|
1345
1655
|
})
|
|
1346
1656
|
] }) });
|
|
@@ -1353,77 +1663,215 @@ function SearchResultRow({
|
|
|
1353
1663
|
onToggle
|
|
1354
1664
|
}) {
|
|
1355
1665
|
const isFolder = node.kind === "folder";
|
|
1666
|
+
const contextLabel = node.contextLabel ?? node.ref.nodeId;
|
|
1356
1667
|
return /* @__PURE__ */ jsxs4(
|
|
1357
1668
|
"div",
|
|
1358
1669
|
{
|
|
1359
1670
|
className: cn3(
|
|
1360
|
-
"grid grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-[6px] border py-2.5 pr-1 pl-3 transition-colors",
|
|
1671
|
+
"grid cursor-pointer grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-[6px] border py-2.5 pr-1 pl-3 transition-colors",
|
|
1361
1672
|
focused || selected ? "border-border bg-transparency-block" : "border-transparent bg-transparent hover:border-border/70 hover:bg-transparency-block"
|
|
1362
1673
|
),
|
|
1674
|
+
onClick: () => onFocus(node),
|
|
1363
1675
|
children: [
|
|
1364
|
-
/* @__PURE__ */ jsxs4(
|
|
1365
|
-
"
|
|
1366
|
-
{
|
|
1367
|
-
className: "
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
/* @__PURE__ */ jsx4("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-[var(--transparency-block)] text-[var(--text-tertiary)]", children: isFolder ? /* @__PURE__ */ jsx4(FolderFilledIcon3, { className: "size-4 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx4(FileIcon3, { className: "size-4 text-[var(--text-tertiary)]" }) }),
|
|
1372
|
-
/* @__PURE__ */ jsxs4("span", { className: "min-w-0", children: [
|
|
1373
|
-
/* @__PURE__ */ jsx4("span", { className: "block truncate text-[13px] font-medium text-[var(--text-primary)]", children: node.displayName }),
|
|
1374
|
-
/* @__PURE__ */ jsx4("span", { className: "block truncate text-[11px] text-[var(--text-secondary)]", children: node.ref.nodeId })
|
|
1375
|
-
] })
|
|
1376
|
-
]
|
|
1377
|
-
}
|
|
1378
|
-
),
|
|
1676
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 items-center gap-3 text-left", children: [
|
|
1677
|
+
/* @__PURE__ */ jsx4("span", { className: "grid size-9 shrink-0 place-items-center rounded-lg bg-[var(--transparency-block)] text-[var(--text-tertiary)]", children: isFolder ? /* @__PURE__ */ jsx4(FolderFilledIcon3, { className: "size-4 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx4(FileIcon3, { className: "size-4 text-[var(--text-tertiary)]" }) }),
|
|
1678
|
+
/* @__PURE__ */ jsxs4("span", { className: "min-w-0", children: [
|
|
1679
|
+
/* @__PURE__ */ jsx4(FullTextTooltip, { content: node.displayName, children: /* @__PURE__ */ jsx4("span", { className: "block truncate text-[13px] font-medium text-[var(--text-primary)]", children: node.displayName }) }),
|
|
1680
|
+
/* @__PURE__ */ jsx4(FullTextTooltip, { content: contextLabel, children: /* @__PURE__ */ jsx4("span", { className: "block truncate text-[11px] text-[var(--text-secondary)]", children: contextLabel }) })
|
|
1681
|
+
] })
|
|
1682
|
+
] }),
|
|
1379
1683
|
/* @__PURE__ */ jsx4(
|
|
1380
|
-
|
|
1684
|
+
Button3,
|
|
1381
1685
|
{
|
|
1382
1686
|
"aria-label": node.displayName,
|
|
1383
1687
|
"aria-pressed": selected,
|
|
1384
1688
|
size: "icon-sm",
|
|
1385
1689
|
type: "button",
|
|
1386
1690
|
variant: "ghost",
|
|
1387
|
-
onClick: () => {
|
|
1691
|
+
onClick: (event) => {
|
|
1692
|
+
event.stopPropagation();
|
|
1388
1693
|
onFocus(node);
|
|
1389
1694
|
onToggle(node);
|
|
1390
1695
|
},
|
|
1391
|
-
children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(
|
|
1696
|
+
children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(AddLinedIcon2, { className: "text-[var(--text-secondary)]", size: 16 })
|
|
1392
1697
|
}
|
|
1393
1698
|
)
|
|
1394
1699
|
]
|
|
1395
1700
|
}
|
|
1396
1701
|
);
|
|
1397
1702
|
}
|
|
1703
|
+
function toPreviewSurfaceState(node, previewState, copy) {
|
|
1704
|
+
if (!("node" in previewState) || nodeRefKey(previewState.node.ref) !== nodeRefKey(node.ref)) {
|
|
1705
|
+
return node.kind === "folder" ? { entry: node, status: "directory" } : { entry: node, status: "loading" };
|
|
1706
|
+
}
|
|
1707
|
+
switch (previewState.status) {
|
|
1708
|
+
case "directory":
|
|
1709
|
+
return { entry: node, status: "directory" };
|
|
1710
|
+
case "loading":
|
|
1711
|
+
return { entry: node, status: "loading" };
|
|
1712
|
+
case "image":
|
|
1713
|
+
return {
|
|
1714
|
+
entry: node,
|
|
1715
|
+
objectUrl: previewState.objectUrl,
|
|
1716
|
+
status: "image"
|
|
1717
|
+
};
|
|
1718
|
+
case "video":
|
|
1719
|
+
return {
|
|
1720
|
+
entry: node,
|
|
1721
|
+
objectUrl: previewState.objectUrl,
|
|
1722
|
+
status: "video"
|
|
1723
|
+
};
|
|
1724
|
+
case "text":
|
|
1725
|
+
return { content: previewState.content, entry: node, status: "text" };
|
|
1726
|
+
case "html":
|
|
1727
|
+
return { content: previewState.content, entry: node, status: "html" };
|
|
1728
|
+
case "readonly":
|
|
1729
|
+
return {
|
|
1730
|
+
entry: node,
|
|
1731
|
+
message: previewState.reason === "binary" ? copy.t("referencePicker.previewBinary") : previewState.reason === "file_too_large" || previewState.reason === "text_too_large" ? copy.t("referencePicker.previewTooLarge") : copy.t("referencePicker.previewUnsupported"),
|
|
1732
|
+
status: "readonly"
|
|
1733
|
+
};
|
|
1734
|
+
case "error":
|
|
1735
|
+
return {
|
|
1736
|
+
entry: node,
|
|
1737
|
+
message: copy.t("referencePicker.previewError"),
|
|
1738
|
+
status: "error"
|
|
1739
|
+
};
|
|
1740
|
+
case "unsupported":
|
|
1741
|
+
return {
|
|
1742
|
+
entry: node,
|
|
1743
|
+
message: copy.t("referencePicker.previewUnsupported"),
|
|
1744
|
+
status: "unsupported"
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
function sourceBadgeClassName(sourceId) {
|
|
1749
|
+
return SOURCE_BADGE_CLASSES[sourceId];
|
|
1750
|
+
}
|
|
1751
|
+
var SOURCE_BADGE_CLASSES = {
|
|
1752
|
+
"workspace-file": "bg-[color-mix(in_srgb,var(--rich-text-mention-file)_12%,transparent)] text-[var(--rich-text-mention-file)]",
|
|
1753
|
+
"app-artifact": "bg-[color-mix(in_srgb,var(--rich-text-mention-app)_12%,transparent)] text-[var(--rich-text-mention-app)]",
|
|
1754
|
+
"issue-file": "bg-[color-mix(in_srgb,var(--rich-text-mention-issue)_12%,transparent)] text-[var(--rich-text-mention-issue)]"
|
|
1755
|
+
};
|
|
1756
|
+
function FullTextTooltip({
|
|
1757
|
+
children,
|
|
1758
|
+
content
|
|
1759
|
+
}) {
|
|
1760
|
+
return /* @__PURE__ */ jsxs4(Tooltip2, { delayDuration: 300, children: [
|
|
1761
|
+
/* @__PURE__ */ jsx4(TooltipTrigger2, { asChild: true, children }),
|
|
1762
|
+
/* @__PURE__ */ jsx4(
|
|
1763
|
+
TooltipContent2,
|
|
1764
|
+
{
|
|
1765
|
+
className: "max-w-[min(520px,calc(100vw-32px))] whitespace-normal text-left [overflow-wrap:anywhere]",
|
|
1766
|
+
side: "top",
|
|
1767
|
+
style: {
|
|
1768
|
+
maxWidth: "min(520px, calc(100vw - 32px))",
|
|
1769
|
+
overflowWrap: "anywhere",
|
|
1770
|
+
whiteSpace: "normal",
|
|
1771
|
+
backgroundColor: "var(--background-fronted)",
|
|
1772
|
+
border: "1px solid var(--border-1)",
|
|
1773
|
+
borderRadius: 6,
|
|
1774
|
+
boxShadow: "var(--shadow-soft)",
|
|
1775
|
+
color: "var(--text-primary)",
|
|
1776
|
+
padding: "4px 8px"
|
|
1777
|
+
},
|
|
1778
|
+
children: content
|
|
1779
|
+
}
|
|
1780
|
+
)
|
|
1781
|
+
] });
|
|
1782
|
+
}
|
|
1398
1783
|
function PreviewInfoPane({
|
|
1784
|
+
copy,
|
|
1399
1785
|
node,
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
referenced,
|
|
1403
|
-
onReference
|
|
1786
|
+
previewState,
|
|
1787
|
+
sourceLabel
|
|
1404
1788
|
}) {
|
|
1405
1789
|
return /* @__PURE__ */ jsx4("aside", { className: "flex h-full min-h-0 w-full flex-col bg-[var(--background-fronted)]", children: node ? /* @__PURE__ */ jsxs4("div", { className: "flex min-h-0 flex-1 flex-col gap-4 p-4", children: [
|
|
1406
|
-
/* @__PURE__ */ jsx4(
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1790
|
+
/* @__PURE__ */ jsx4(
|
|
1791
|
+
WorkspaceFilePreviewSurface,
|
|
1792
|
+
{
|
|
1793
|
+
directoryMessage: copy.t("referencePicker.previewFolder"),
|
|
1794
|
+
emptyMessage: copy.t("referencePicker.emptyPreview"),
|
|
1795
|
+
frameClassName: "flex aspect-[3/2] w-full flex-col items-center justify-center overflow-hidden rounded-[8px] border border-[var(--line-2,var(--border-2))] bg-[var(--transparency-block)] p-0 text-center",
|
|
1796
|
+
imageAlt: (entry) => entry.displayName,
|
|
1797
|
+
htmlFrameClassName: "items-stretch justify-stretch bg-white",
|
|
1798
|
+
htmlTitle: (entry) => entry.displayName,
|
|
1799
|
+
imageFrameClassName: "p-3",
|
|
1800
|
+
loadingIndicator: /* @__PURE__ */ jsx4(Spinner2, { size: 16 }),
|
|
1801
|
+
loadingMessage: copy.t("referencePicker.previewLoading"),
|
|
1802
|
+
messageClassName: "mx-auto max-w-[24ch] text-[13px] leading-5 text-[var(--text-secondary)] [overflow-wrap:anywhere]",
|
|
1803
|
+
renderIcon: (entry) => entry.kind === "folder" ? /* @__PURE__ */ jsx4(FolderFilledIcon3, { className: "size-9 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx4(FileIcon3, { className: "size-9 text-[var(--text-tertiary)]" }),
|
|
1804
|
+
state: toPreviewSurfaceState(node, previewState, copy),
|
|
1805
|
+
textClassName: "h-full w-full overflow-auto p-3 text-left text-[11px] leading-5 whitespace-pre-wrap break-words text-[var(--text-primary)]",
|
|
1806
|
+
textFrameClassName: "items-stretch justify-stretch"
|
|
1807
|
+
}
|
|
1808
|
+
),
|
|
1809
|
+
/* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
|
|
1810
|
+
/* @__PURE__ */ jsx4("p", { className: "truncate text-[15px] font-semibold", children: node.displayName }),
|
|
1811
|
+
/* @__PURE__ */ jsx4(ReferencePathText, { node })
|
|
1412
1812
|
] }),
|
|
1413
|
-
|
|
1414
|
-
/* @__PURE__ */ jsx4(
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1813
|
+
/* @__PURE__ */ jsxs4("dl", { className: "space-y-2 text-[13px]", children: [
|
|
1814
|
+
/* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewSource"), children: /* @__PURE__ */ jsx4(
|
|
1815
|
+
Badge2,
|
|
1816
|
+
{
|
|
1817
|
+
variant: "secondary",
|
|
1818
|
+
className: sourceBadgeClassName(node.ref.sourceId),
|
|
1819
|
+
children: sourceLabel
|
|
1820
|
+
}
|
|
1821
|
+
) }),
|
|
1822
|
+
node.mtimeMs != null ? /* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewModified"), children: formatDateTime(node.mtimeMs) }) : null,
|
|
1823
|
+
node.sizeBytes != null ? /* @__PURE__ */ jsx4(InfoRow, { label: copy.t("referencePicker.previewSize"), children: formatBytes(node.sizeBytes) }) : null
|
|
1824
|
+
] })
|
|
1825
|
+
] }) : /* @__PURE__ */ jsx4(Feedback, { children: copy.t("referencePicker.emptyPreview") }) });
|
|
1826
|
+
}
|
|
1827
|
+
function ReferencePathText({ node }) {
|
|
1828
|
+
const pathText = getReferenceNodePathText(node);
|
|
1829
|
+
const lastSlashIndex = pathText.lastIndexOf("/");
|
|
1830
|
+
if (lastSlashIndex <= 0 || lastSlashIndex === pathText.length - 1) {
|
|
1831
|
+
return /* @__PURE__ */ jsx4(
|
|
1832
|
+
"p",
|
|
1419
1833
|
{
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
children: L.reference
|
|
1834
|
+
className: "truncate text-[12px] leading-5 text-[var(--text-tertiary)]",
|
|
1835
|
+
title: pathText,
|
|
1836
|
+
children: pathText
|
|
1424
1837
|
}
|
|
1425
|
-
)
|
|
1426
|
-
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
return /* @__PURE__ */ jsxs4(
|
|
1841
|
+
"p",
|
|
1842
|
+
{
|
|
1843
|
+
className: "flex min-w-0 items-center text-[12px] leading-5 text-[var(--text-tertiary)]",
|
|
1844
|
+
title: pathText,
|
|
1845
|
+
children: [
|
|
1846
|
+
/* @__PURE__ */ jsx4("span", { className: "min-w-0 truncate", children: pathText.slice(0, lastSlashIndex + 1) }),
|
|
1847
|
+
/* @__PURE__ */ jsx4("span", { className: "max-w-[65%] shrink-0 truncate", children: pathText.slice(lastSlashIndex + 1) })
|
|
1848
|
+
]
|
|
1849
|
+
}
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
function getReferenceNodePathText(node) {
|
|
1853
|
+
const decodedPath = decodeReferenceListFileNodeId(node.ref.nodeId);
|
|
1854
|
+
if (decodedPath) {
|
|
1855
|
+
return decodedPath;
|
|
1856
|
+
}
|
|
1857
|
+
return node.contextLabel?.trim() || node.ref.nodeId;
|
|
1858
|
+
}
|
|
1859
|
+
function decodeReferenceListFileNodeId(nodeId) {
|
|
1860
|
+
if (!nodeId.startsWith("f:")) {
|
|
1861
|
+
return null;
|
|
1862
|
+
}
|
|
1863
|
+
try {
|
|
1864
|
+
const normalized = nodeId.slice(2).replace(/-/g, "+").replace(/_/g, "/");
|
|
1865
|
+
const padded = normalized.padEnd(
|
|
1866
|
+
normalized.length + (4 - normalized.length % 4) % 4,
|
|
1867
|
+
"="
|
|
1868
|
+
);
|
|
1869
|
+
const binary = atob(padded);
|
|
1870
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
1871
|
+
return new TextDecoder().decode(bytes);
|
|
1872
|
+
} catch {
|
|
1873
|
+
return null;
|
|
1874
|
+
}
|
|
1427
1875
|
}
|
|
1428
1876
|
function InfoRow({
|
|
1429
1877
|
label,
|
|
@@ -1444,19 +1892,72 @@ function Footer({
|
|
|
1444
1892
|
onClose,
|
|
1445
1893
|
onConfirm
|
|
1446
1894
|
}) {
|
|
1895
|
+
const selectionTooltipId = useId2();
|
|
1896
|
+
const [selectionTooltipOpen, setSelectionTooltipOpen] = useState4(false);
|
|
1897
|
+
const selectionTooltipLabel = selection.map((node) => node.displayName).join("\n");
|
|
1447
1898
|
return /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between gap-3 border-t border-[var(--line-1)] px-4 py-3 sm:px-6", children: [
|
|
1448
1899
|
/* @__PURE__ */ jsxs4("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
1449
|
-
/* @__PURE__ */ jsx4("span", { className: "text-[13px] text-[var(--text-secondary)]", children: countLabel }),
|
|
1450
|
-
selection.slice(0, 2).map((node) => /* @__PURE__ */ jsx4(
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1900
|
+
/* @__PURE__ */ jsx4("span", { className: "shrink-0 text-[13px] text-[var(--text-secondary)]", children: countLabel }),
|
|
1901
|
+
selection.slice(0, 2).map((node) => /* @__PURE__ */ jsx4(
|
|
1902
|
+
Badge2,
|
|
1903
|
+
{
|
|
1904
|
+
className: "min-w-0 max-w-[12rem]",
|
|
1905
|
+
variant: "secondary",
|
|
1906
|
+
children: /* @__PURE__ */ jsx4(FullTextTooltip, { content: node.displayName, children: /* @__PURE__ */ jsx4("span", { className: "truncate", children: node.displayName }) })
|
|
1907
|
+
},
|
|
1908
|
+
nodeRefKey(node.ref)
|
|
1909
|
+
)),
|
|
1910
|
+
selection.length > 2 ? /* @__PURE__ */ jsxs4(
|
|
1911
|
+
"span",
|
|
1912
|
+
{
|
|
1913
|
+
className: "relative inline-flex shrink-0",
|
|
1914
|
+
onBlur: () => setSelectionTooltipOpen(false),
|
|
1915
|
+
onFocus: () => setSelectionTooltipOpen(true),
|
|
1916
|
+
onMouseEnter: () => setSelectionTooltipOpen(true),
|
|
1917
|
+
onMouseLeave: () => setSelectionTooltipOpen(false),
|
|
1918
|
+
children: [
|
|
1919
|
+
/* @__PURE__ */ jsx4(
|
|
1920
|
+
Badge2,
|
|
1921
|
+
{
|
|
1922
|
+
asChild: true,
|
|
1923
|
+
className: "shrink-0 cursor-default",
|
|
1924
|
+
variant: "secondary",
|
|
1925
|
+
children: /* @__PURE__ */ jsxs4(
|
|
1926
|
+
"button",
|
|
1927
|
+
{
|
|
1928
|
+
"aria-describedby": selectionTooltipId,
|
|
1929
|
+
"aria-label": selectionTooltipLabel,
|
|
1930
|
+
type: "button",
|
|
1931
|
+
children: [
|
|
1932
|
+
"+",
|
|
1933
|
+
selection.length - 2
|
|
1934
|
+
]
|
|
1935
|
+
}
|
|
1936
|
+
)
|
|
1937
|
+
}
|
|
1938
|
+
),
|
|
1939
|
+
/* @__PURE__ */ jsx4(
|
|
1940
|
+
"span",
|
|
1941
|
+
{
|
|
1942
|
+
"aria-hidden": !selectionTooltipOpen,
|
|
1943
|
+
className: "pointer-events-none absolute bottom-[calc(100%+8px)] left-0 z-[var(--z-tooltip,100700)] max-h-[min(20rem,calc(100vh-96px))] w-max max-w-[min(28rem,calc(100vw-32px))] overflow-auto whitespace-pre-line rounded-md border border-[var(--border-1)] bg-[var(--background-fronted)] px-2 py-1 text-left text-[13px] leading-[1.3] text-[var(--text-primary)] shadow-soft transition-opacity duration-100 [overflow-wrap:anywhere]",
|
|
1944
|
+
id: selectionTooltipId,
|
|
1945
|
+
role: "tooltip",
|
|
1946
|
+
style: {
|
|
1947
|
+
opacity: selectionTooltipOpen ? 1 : 0,
|
|
1948
|
+
visibility: selectionTooltipOpen ? "visible" : "hidden"
|
|
1949
|
+
},
|
|
1950
|
+
children: selectionTooltipLabel
|
|
1951
|
+
}
|
|
1952
|
+
)
|
|
1953
|
+
]
|
|
1954
|
+
}
|
|
1955
|
+
) : null
|
|
1455
1956
|
] }),
|
|
1456
1957
|
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
|
|
1457
|
-
/* @__PURE__ */ jsx4(
|
|
1958
|
+
/* @__PURE__ */ jsx4(Button3, { type: "button", variant: "secondary", onClick: onClose, children: cancelLabel }),
|
|
1458
1959
|
/* @__PURE__ */ jsxs4(
|
|
1459
|
-
|
|
1960
|
+
Button3,
|
|
1460
1961
|
{
|
|
1461
1962
|
disabled: disabled || loading,
|
|
1462
1963
|
type: "button",
|
|
@@ -1473,6 +1974,112 @@ function Footer({
|
|
|
1473
1974
|
function Feedback({ children }) {
|
|
1474
1975
|
return /* @__PURE__ */ jsx4("div", { className: "grid min-h-0 flex-1 place-items-center px-4 py-8 text-center text-[13px] text-[var(--text-secondary)]", children });
|
|
1475
1976
|
}
|
|
1977
|
+
function FilterCategoryFilter({
|
|
1978
|
+
categories,
|
|
1979
|
+
copy,
|
|
1980
|
+
selected,
|
|
1981
|
+
onClear,
|
|
1982
|
+
onToggle
|
|
1983
|
+
}) {
|
|
1984
|
+
const [open, setOpen] = useState4(false);
|
|
1985
|
+
const containerRef = useRef2(null);
|
|
1986
|
+
useEffect3(() => {
|
|
1987
|
+
if (!open) {
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
const onPointerDown = (event) => {
|
|
1991
|
+
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
1992
|
+
setOpen(false);
|
|
1993
|
+
}
|
|
1994
|
+
};
|
|
1995
|
+
const onKeyDown = (event) => {
|
|
1996
|
+
if (event.key === "Escape") {
|
|
1997
|
+
setOpen(false);
|
|
1998
|
+
}
|
|
1999
|
+
};
|
|
2000
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
2001
|
+
document.addEventListener("keydown", onKeyDown);
|
|
2002
|
+
return () => {
|
|
2003
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
2004
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
2005
|
+
};
|
|
2006
|
+
}, [open]);
|
|
2007
|
+
const selectedLabels = categories.filter((category) => selected.has(category.id)).map((category) => copy.t(category.labelKey));
|
|
2008
|
+
const count = selectedLabels.length;
|
|
2009
|
+
const labelText = count > 0 ? selectedLabels.join(copy.t("referencePicker.fileTypeSeparator")) : copy.t("referencePicker.fileTypeAll");
|
|
2010
|
+
return (
|
|
2011
|
+
// shrink-0:不被搜索框挤压;固定宽度让控件不随选中文案变宽。
|
|
2012
|
+
/* @__PURE__ */ jsxs4("div", { ref: containerRef, className: "relative w-[124px] shrink-0", children: [
|
|
2013
|
+
/* @__PURE__ */ jsxs4(
|
|
2014
|
+
Button3,
|
|
2015
|
+
{
|
|
2016
|
+
"aria-expanded": open,
|
|
2017
|
+
"aria-haspopup": "menu",
|
|
2018
|
+
className: "h-8 w-full justify-between gap-1.5 border-0 px-2.5",
|
|
2019
|
+
size: "default",
|
|
2020
|
+
type: "button",
|
|
2021
|
+
variant: "secondary",
|
|
2022
|
+
onClick: () => setOpen((current) => !current),
|
|
2023
|
+
children: [
|
|
2024
|
+
/* @__PURE__ */ jsx4("span", { className: "min-w-0 flex-1 truncate text-left", title: labelText, children: labelText }),
|
|
2025
|
+
count > 1 ? /* @__PURE__ */ jsx4(Badge2, { className: "shrink-0 px-1.5", variant: "secondary", children: count }) : null,
|
|
2026
|
+
count > 0 ? (
|
|
2027
|
+
// 有选中时,右侧箭头位让位给「清除」:role=button 的 span(避免 button 嵌
|
|
2028
|
+
// button 的非法结构),stopPropagation 让点击只清空筛选、不触发触发器的开合。
|
|
2029
|
+
/* @__PURE__ */ jsx4(
|
|
2030
|
+
"span",
|
|
2031
|
+
{
|
|
2032
|
+
"aria-label": copy.t("referencePicker.clearFilter"),
|
|
2033
|
+
className: "grid size-4 shrink-0 cursor-pointer place-items-center rounded-full text-[var(--text-tertiary)] transition-colors hover:bg-[var(--transparency-hover)] hover:text-[var(--text-primary)]",
|
|
2034
|
+
role: "button",
|
|
2035
|
+
tabIndex: 0,
|
|
2036
|
+
onClick: (event) => {
|
|
2037
|
+
event.stopPropagation();
|
|
2038
|
+
onClear();
|
|
2039
|
+
},
|
|
2040
|
+
onKeyDown: (event) => {
|
|
2041
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
2042
|
+
event.preventDefault();
|
|
2043
|
+
event.stopPropagation();
|
|
2044
|
+
onClear();
|
|
2045
|
+
}
|
|
2046
|
+
},
|
|
2047
|
+
children: /* @__PURE__ */ jsx4(CloseIcon, { size: 12 })
|
|
2048
|
+
}
|
|
2049
|
+
)
|
|
2050
|
+
) : /* @__PURE__ */ jsx4(ChevronDownIcon, { className: "size-3.5 shrink-0 text-[var(--text-tertiary)]" })
|
|
2051
|
+
]
|
|
2052
|
+
}
|
|
2053
|
+
),
|
|
2054
|
+
open ? /* @__PURE__ */ jsx4(
|
|
2055
|
+
"div",
|
|
2056
|
+
{
|
|
2057
|
+
className: "absolute top-[calc(100%+4px)] right-0 min-w-40 overflow-hidden rounded-[8px] border border-[var(--line-1)] bg-[var(--background-fronted)] p-1 shadow-panel",
|
|
2058
|
+
role: "menu",
|
|
2059
|
+
style: { zIndex: "var(--z-panel-popover)" },
|
|
2060
|
+
children: categories.map((category) => {
|
|
2061
|
+
const checked = selected.has(category.id);
|
|
2062
|
+
return /* @__PURE__ */ jsxs4(
|
|
2063
|
+
"button",
|
|
2064
|
+
{
|
|
2065
|
+
"aria-checked": checked,
|
|
2066
|
+
className: "flex w-full items-center gap-2 rounded-[6px] px-2 py-1.5 text-left text-[13px] text-[var(--text-primary)] transition-colors hover:bg-[var(--transparency-hover)]",
|
|
2067
|
+
role: "menuitemcheckbox",
|
|
2068
|
+
type: "button",
|
|
2069
|
+
onClick: () => onToggle(category.id),
|
|
2070
|
+
children: [
|
|
2071
|
+
/* @__PURE__ */ jsx4("span", { className: "grid size-4 shrink-0 place-items-center", children: checked ? /* @__PURE__ */ jsx4(CheckIcon2, { className: "size-3.5 text-[var(--tutti-purple)]" }) : null }),
|
|
2072
|
+
/* @__PURE__ */ jsx4("span", { className: "flex-1", children: copy.t(category.labelKey) })
|
|
2073
|
+
]
|
|
2074
|
+
},
|
|
2075
|
+
category.id
|
|
2076
|
+
);
|
|
2077
|
+
})
|
|
2078
|
+
}
|
|
2079
|
+
) : null
|
|
2080
|
+
] })
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
1476
2083
|
var TREE_INDENT = 28;
|
|
1477
2084
|
var TREE_COLLAPSE_DURATION_MS = 200;
|
|
1478
2085
|
function isFocused(focused, node) {
|
|
@@ -1491,7 +2098,7 @@ function TreeNodeRow({
|
|
|
1491
2098
|
const childEntries = view.sortNodes(childState?.entries ?? []);
|
|
1492
2099
|
const selected = view.isSelected(node);
|
|
1493
2100
|
const focused = isFocused(view.focusedNode, node);
|
|
1494
|
-
const [shouldRenderChildContent, setShouldRenderChildContent] =
|
|
2101
|
+
const [shouldRenderChildContent, setShouldRenderChildContent] = useState4(expanded);
|
|
1495
2102
|
useEffect3(() => {
|
|
1496
2103
|
if (expanded) {
|
|
1497
2104
|
setShouldRenderChildContent(true);
|
|
@@ -1535,10 +2142,16 @@ function TreeNodeRow({
|
|
|
1535
2142
|
"div",
|
|
1536
2143
|
{
|
|
1537
2144
|
className: cn3(
|
|
1538
|
-
"flex items-center gap-2 rounded-[6px] py-1.5 pr-1 transition-colors",
|
|
2145
|
+
"flex cursor-pointer items-center gap-2 rounded-[6px] py-1.5 pr-1 transition-colors",
|
|
1539
2146
|
focused || selected ? "bg-transparency-block" : "hover:bg-transparency-block"
|
|
1540
2147
|
),
|
|
1541
2148
|
style: { paddingLeft: `${depth * TREE_INDENT + 8}px` },
|
|
2149
|
+
onClick: () => {
|
|
2150
|
+
view.setFocusedNode(node);
|
|
2151
|
+
if (isFolder) {
|
|
2152
|
+
view.toggleNode(node);
|
|
2153
|
+
}
|
|
2154
|
+
},
|
|
1542
2155
|
children: [
|
|
1543
2156
|
isFolder ? /* @__PURE__ */ jsx4(
|
|
1544
2157
|
"button",
|
|
@@ -1546,7 +2159,11 @@ function TreeNodeRow({
|
|
|
1546
2159
|
"aria-label": node.displayName,
|
|
1547
2160
|
className: "grid size-5 shrink-0 place-items-center rounded-sm text-[var(--text-secondary)] hover:bg-[var(--transparency-hover)]",
|
|
1548
2161
|
type: "button",
|
|
1549
|
-
onClick: () =>
|
|
2162
|
+
onClick: (event) => {
|
|
2163
|
+
event.stopPropagation();
|
|
2164
|
+
view.setFocusedNode(node);
|
|
2165
|
+
view.toggleNode(node);
|
|
2166
|
+
},
|
|
1550
2167
|
children: /* @__PURE__ */ jsx4(
|
|
1551
2168
|
ArrowRightIcon2,
|
|
1552
2169
|
{
|
|
@@ -1558,32 +2175,17 @@ function TreeNodeRow({
|
|
|
1558
2175
|
)
|
|
1559
2176
|
}
|
|
1560
2177
|
) : null,
|
|
1561
|
-
/* @__PURE__ */
|
|
1562
|
-
|
|
2178
|
+
isFolder ? /* @__PURE__ */ jsx4(FolderFilledIcon3, { className: "size-4 shrink-0 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx4(FileIcon3, { className: "size-4 shrink-0 text-[var(--text-tertiary)]" }),
|
|
2179
|
+
/* @__PURE__ */ jsx4(FullTextTooltip, { content: node.displayName, children: /* @__PURE__ */ jsx4(
|
|
2180
|
+
"span",
|
|
1563
2181
|
{
|
|
1564
|
-
className: "
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
view.setFocusedNode(node);
|
|
1568
|
-
if (isFolder) {
|
|
1569
|
-
view.toggleNode(node);
|
|
1570
|
-
}
|
|
1571
|
-
},
|
|
1572
|
-
children: [
|
|
1573
|
-
isFolder ? /* @__PURE__ */ jsx4(FolderFilledIcon3, { className: "size-4 shrink-0 text-[var(--rich-text-folder)]" }) : /* @__PURE__ */ jsx4(FileIcon3, { className: "size-4 shrink-0 text-[var(--text-tertiary)]" }),
|
|
1574
|
-
/* @__PURE__ */ jsx4(
|
|
1575
|
-
"span",
|
|
1576
|
-
{
|
|
1577
|
-
className: "truncate text-[13px] text-[var(--text-primary)]",
|
|
1578
|
-
"data-autofit-label": true,
|
|
1579
|
-
children: node.displayName
|
|
1580
|
-
}
|
|
1581
|
-
)
|
|
1582
|
-
]
|
|
2182
|
+
className: "min-w-0 flex-1 truncate text-[13px] text-[var(--text-primary)]",
|
|
2183
|
+
"data-autofit-label": true,
|
|
2184
|
+
children: node.displayName
|
|
1583
2185
|
}
|
|
1584
|
-
),
|
|
2186
|
+
) }),
|
|
1585
2187
|
/* @__PURE__ */ jsx4(
|
|
1586
|
-
|
|
2188
|
+
Button3,
|
|
1587
2189
|
{
|
|
1588
2190
|
"aria-label": node.displayName,
|
|
1589
2191
|
"aria-pressed": selected,
|
|
@@ -1591,11 +2193,12 @@ function TreeNodeRow({
|
|
|
1591
2193
|
size: "icon-sm",
|
|
1592
2194
|
type: "button",
|
|
1593
2195
|
variant: "ghost",
|
|
1594
|
-
onClick: () => {
|
|
2196
|
+
onClick: (event) => {
|
|
2197
|
+
event.stopPropagation();
|
|
1595
2198
|
view.setFocusedNode(node);
|
|
1596
2199
|
view.toggleSelection(node);
|
|
1597
2200
|
},
|
|
1598
|
-
children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(
|
|
2201
|
+
children: selected ? /* @__PURE__ */ jsx4(CheckIcon2, { size: 14 }) : /* @__PURE__ */ jsx4(AddLinedIcon2, { className: "text-[var(--text-secondary)]", size: 16 })
|
|
1599
2202
|
}
|
|
1600
2203
|
)
|
|
1601
2204
|
]
|