@tutti-os/workspace-file-reference 0.0.13 → 0.0.15
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-HRC2MZ5H.js +393 -0
- package/dist/chunk-HRC2MZ5H.js.map +1 -0
- package/dist/{chunk-LS7P3J6Z.js → chunk-KSK6PILG.js} +661 -14
- package/dist/chunk-KSK6PILG.js.map +1 -0
- package/dist/contracts/index.d.ts +167 -1
- package/dist/core/index.d.ts +152 -2
- package/dist/core/index.js +37 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +37 -1
- package/dist/react/index.d.ts +134 -2
- package/dist/react/index.js +6 -2
- package/dist/referenceSourceAggregator-B8ATnJOZ.d.ts +47 -0
- package/dist/ui/index.d.ts +42 -3
- package/dist/ui/index.js +1507 -94
- package/dist/ui/index.js.map +1 -1
- package/package.json +6 -5
- package/dist/chunk-LS7P3J6Z.js.map +0 -1
- package/dist/chunk-PAR2R2G5.js +0 -23
- package/dist/chunk-PAR2R2G5.js.map +0 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SOURCE_ROOT_NODE_ID,
|
|
3
|
+
appendReferencePage,
|
|
4
|
+
nodeRefKey,
|
|
5
|
+
sortReferenceNodes,
|
|
2
6
|
uniqueWorkspaceFileReferences
|
|
3
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-HRC2MZ5H.js";
|
|
4
8
|
|
|
5
9
|
// src/react/internal/reference/WorkspaceFileReferencePickerState.ts
|
|
6
10
|
var workspaceFileReferenceDefaultExpandedDepth = 4;
|
|
@@ -158,7 +162,15 @@ var defaultDirectoryPath = "/";
|
|
|
158
162
|
var defaultSearchDebounceMs = 180;
|
|
159
163
|
function createWorkspaceFileReferencePickerController(input) {
|
|
160
164
|
const searchDebounceMs = input.searchDebounceMs ?? defaultSearchDebounceMs;
|
|
161
|
-
let
|
|
165
|
+
let nextBrowseSeq = 0;
|
|
166
|
+
const latestBrowseSeqByKey = /* @__PURE__ */ new Map();
|
|
167
|
+
const stampBrowse = (seqKey) => {
|
|
168
|
+
const sequence = ++nextBrowseSeq;
|
|
169
|
+
latestBrowseSeqByKey.set(seqKey, sequence);
|
|
170
|
+
return sequence;
|
|
171
|
+
};
|
|
172
|
+
const isBrowseStale = (seqKey, sequence) => !retained || latestBrowseSeqByKey.get(seqKey) !== sequence;
|
|
173
|
+
const REVEAL_SEQ_KEY = "\0reveal";
|
|
162
174
|
let previewObjectUrl = null;
|
|
163
175
|
let previewSequence = 0;
|
|
164
176
|
let retained = false;
|
|
@@ -219,7 +231,7 @@ function createWorkspaceFileReferencePickerController(input) {
|
|
|
219
231
|
searchAbortController = null;
|
|
220
232
|
};
|
|
221
233
|
const cancelCurrentBrowse = () => {
|
|
222
|
-
|
|
234
|
+
latestBrowseSeqByKey.clear();
|
|
223
235
|
};
|
|
224
236
|
const cancelCurrentPreview = () => {
|
|
225
237
|
previewSequence += 1;
|
|
@@ -306,7 +318,8 @@ function createWorkspaceFileReferencePickerController(input) {
|
|
|
306
318
|
if (normalizedRoot && snapshot.directoryStateByPath[normalizedRoot]?.loaded) {
|
|
307
319
|
return;
|
|
308
320
|
}
|
|
309
|
-
const
|
|
321
|
+
const seqKey = normalizedRoot ?? "";
|
|
322
|
+
const sequence = stampBrowse(seqKey);
|
|
310
323
|
setSnapshot({
|
|
311
324
|
browseError: null,
|
|
312
325
|
isBrowseLoading: true
|
|
@@ -319,7 +332,7 @@ function createWorkspaceFileReferencePickerController(input) {
|
|
|
319
332
|
prefetchDepth: 4,
|
|
320
333
|
workspaceId: input.workspaceId
|
|
321
334
|
});
|
|
322
|
-
if (
|
|
335
|
+
if (isBrowseStale(seqKey, sequence)) {
|
|
323
336
|
return;
|
|
324
337
|
}
|
|
325
338
|
setSnapshot({
|
|
@@ -334,7 +347,7 @@ function createWorkspaceFileReferencePickerController(input) {
|
|
|
334
347
|
return;
|
|
335
348
|
}
|
|
336
349
|
const listing = await loadDirectoryListing(activeBrowseRootPath);
|
|
337
|
-
if (
|
|
350
|
+
if (isBrowseStale(seqKey, sequence) || !listing) {
|
|
338
351
|
return;
|
|
339
352
|
}
|
|
340
353
|
setSnapshot((current) => ({
|
|
@@ -352,7 +365,7 @@ function createWorkspaceFileReferencePickerController(input) {
|
|
|
352
365
|
isBrowseLoading: false
|
|
353
366
|
}));
|
|
354
367
|
} catch (error) {
|
|
355
|
-
if (
|
|
368
|
+
if (isBrowseStale(seqKey, sequence)) {
|
|
356
369
|
return;
|
|
357
370
|
}
|
|
358
371
|
setSnapshot({
|
|
@@ -369,7 +382,7 @@ function createWorkspaceFileReferencePickerController(input) {
|
|
|
369
382
|
if (!retained || snapshot.directoryStateByPath[folderKey]?.loaded || snapshot.directoryStateByPath[folderKey]?.loading) {
|
|
370
383
|
return;
|
|
371
384
|
}
|
|
372
|
-
const sequence =
|
|
385
|
+
const sequence = stampBrowse(folderKey);
|
|
373
386
|
setSnapshot((current) => ({
|
|
374
387
|
...current,
|
|
375
388
|
directoryStateByPath: {
|
|
@@ -384,7 +397,7 @@ function createWorkspaceFileReferencePickerController(input) {
|
|
|
384
397
|
}));
|
|
385
398
|
try {
|
|
386
399
|
const listing = await loadDirectoryListing(folderKey);
|
|
387
|
-
if (
|
|
400
|
+
if (isBrowseStale(folderKey, sequence) || !listing) {
|
|
388
401
|
return;
|
|
389
402
|
}
|
|
390
403
|
setSnapshot((current) => ({
|
|
@@ -400,7 +413,7 @@ function createWorkspaceFileReferencePickerController(input) {
|
|
|
400
413
|
}
|
|
401
414
|
}));
|
|
402
415
|
} catch {
|
|
403
|
-
if (
|
|
416
|
+
if (isBrowseStale(folderKey, sequence)) {
|
|
404
417
|
return;
|
|
405
418
|
}
|
|
406
419
|
setSnapshot((current) => ({
|
|
@@ -538,7 +551,7 @@ function createWorkspaceFileReferencePickerController(input) {
|
|
|
538
551
|
});
|
|
539
552
|
return null;
|
|
540
553
|
}
|
|
541
|
-
const sequence =
|
|
554
|
+
const sequence = stampBrowse(REVEAL_SEQ_KEY);
|
|
542
555
|
const loadedDirectories = {};
|
|
543
556
|
const expandedDirectories = {};
|
|
544
557
|
const directoryState = (path) => loadedDirectories[path] ?? snapshot.directoryStateByPath[path];
|
|
@@ -569,7 +582,7 @@ function createWorkspaceFileReferencePickerController(input) {
|
|
|
569
582
|
loading: false
|
|
570
583
|
};
|
|
571
584
|
}
|
|
572
|
-
if (
|
|
585
|
+
if (isBrowseStale(REVEAL_SEQ_KEY, sequence)) {
|
|
573
586
|
return null;
|
|
574
587
|
}
|
|
575
588
|
setSnapshot((current) => ({
|
|
@@ -888,6 +901,638 @@ function useWorkspaceFileReferencePickerView({
|
|
|
888
901
|
};
|
|
889
902
|
}
|
|
890
903
|
|
|
904
|
+
// src/react/internal/reference/referenceSourcePickerController.ts
|
|
905
|
+
import { proxy as proxy2 } from "valtio/vanilla";
|
|
906
|
+
var ROOT_CHILDREN_KEY = nodeRefKey({
|
|
907
|
+
sourceId: "",
|
|
908
|
+
nodeId: SOURCE_ROOT_NODE_ID
|
|
909
|
+
});
|
|
910
|
+
var defaultSearchDebounceMs2 = 180;
|
|
911
|
+
var SEARCH_PAGE_SIZE = 30;
|
|
912
|
+
var SEARCH_MAX_LIMIT = 200;
|
|
913
|
+
function emptyTabState(sourceId) {
|
|
914
|
+
return {
|
|
915
|
+
sourceId,
|
|
916
|
+
expandedKeys: {},
|
|
917
|
+
childrenByKey: {},
|
|
918
|
+
mode: "browse",
|
|
919
|
+
searchQuery: "",
|
|
920
|
+
searchFilters: [],
|
|
921
|
+
searchScopeNodeId: null,
|
|
922
|
+
searchEntries: [],
|
|
923
|
+
searchNextCursor: null,
|
|
924
|
+
searchLimit: 0,
|
|
925
|
+
searchHasMore: false,
|
|
926
|
+
isSearchLoadingMore: false,
|
|
927
|
+
isSearchLoading: false,
|
|
928
|
+
searchError: null
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
function emptyChildrenState() {
|
|
932
|
+
return {
|
|
933
|
+
entries: [],
|
|
934
|
+
nextCursor: null,
|
|
935
|
+
loaded: false,
|
|
936
|
+
loading: false,
|
|
937
|
+
error: null
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function createReferenceSourcePickerController(input) {
|
|
941
|
+
const { aggregator, scope } = input;
|
|
942
|
+
const searchDebounceMs = input.searchDebounceMs ?? defaultSearchDebounceMs2;
|
|
943
|
+
let retained = false;
|
|
944
|
+
let tabsReady = Promise.resolve();
|
|
945
|
+
let tabsSequence = 0;
|
|
946
|
+
let nextBrowseSeq = 0;
|
|
947
|
+
const latestBrowseSeqByKey = /* @__PURE__ */ new Map();
|
|
948
|
+
let searchSequence = 0;
|
|
949
|
+
let searchAbortController = null;
|
|
950
|
+
let searchTimer = null;
|
|
951
|
+
let snapshot = {
|
|
952
|
+
isLoadingTabs: false,
|
|
953
|
+
tabsError: null,
|
|
954
|
+
tabs: [],
|
|
955
|
+
activeSourceId: null,
|
|
956
|
+
bySource: {},
|
|
957
|
+
selection: []
|
|
958
|
+
};
|
|
959
|
+
const store = proxy2(snapshot);
|
|
960
|
+
const setSnapshot = (update) => {
|
|
961
|
+
const next = typeof update === "function" ? update(snapshot) : { ...snapshot, ...update };
|
|
962
|
+
if (next === snapshot) {
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
snapshot = next;
|
|
966
|
+
Object.assign(store, next);
|
|
967
|
+
};
|
|
968
|
+
const updateTab = (sourceId, updater) => {
|
|
969
|
+
setSnapshot((current) => {
|
|
970
|
+
const existing = current.bySource[sourceId] ?? emptyTabState(sourceId);
|
|
971
|
+
const nextTab = updater(existing);
|
|
972
|
+
if (nextTab === existing) {
|
|
973
|
+
return current;
|
|
974
|
+
}
|
|
975
|
+
return {
|
|
976
|
+
...current,
|
|
977
|
+
bySource: { ...current.bySource, [sourceId]: nextTab }
|
|
978
|
+
};
|
|
979
|
+
});
|
|
980
|
+
};
|
|
981
|
+
const childrenKeyForNode = (node) => node ? nodeRefKey(node.ref) : ROOT_CHILDREN_KEY;
|
|
982
|
+
const setChildrenState = (sourceId, key, patch) => {
|
|
983
|
+
updateTab(sourceId, (tab) => {
|
|
984
|
+
const current = tab.childrenByKey[key] ?? emptyChildrenState();
|
|
985
|
+
return {
|
|
986
|
+
...tab,
|
|
987
|
+
childrenByKey: {
|
|
988
|
+
...tab.childrenByKey,
|
|
989
|
+
[key]: { ...current, ...patch }
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
});
|
|
993
|
+
};
|
|
994
|
+
const loadChildren = async (sourceId, node, options) => {
|
|
995
|
+
if (!retained) {
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
const key = childrenKeyForNode(node);
|
|
999
|
+
const tab = snapshot.bySource[sourceId];
|
|
1000
|
+
const existing = tab?.childrenByKey[key];
|
|
1001
|
+
const cursor = options.append ? existing?.nextCursor ?? null : null;
|
|
1002
|
+
if (existing?.loading) {
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
if (options.append && !cursor) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
const seqKey = `${sourceId}\0${key}`;
|
|
1009
|
+
const sequence = ++nextBrowseSeq;
|
|
1010
|
+
latestBrowseSeqByKey.set(seqKey, sequence);
|
|
1011
|
+
setChildrenState(sourceId, key, { loading: true, error: null });
|
|
1012
|
+
try {
|
|
1013
|
+
const result = await aggregator.listChildren(
|
|
1014
|
+
scope,
|
|
1015
|
+
node ? node.ref : { sourceId, nodeId: SOURCE_ROOT_NODE_ID },
|
|
1016
|
+
{ cursor }
|
|
1017
|
+
);
|
|
1018
|
+
if (!retained || latestBrowseSeqByKey.get(seqKey) !== sequence) {
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
const prior = snapshot.bySource[sourceId]?.childrenByKey[key]?.entries ?? [];
|
|
1022
|
+
const entries = options.append ? appendReferencePage(prior, result.entries) : result.ordered ? [...result.entries] : sortReferenceNodes(result.entries);
|
|
1023
|
+
setChildrenState(sourceId, key, {
|
|
1024
|
+
entries,
|
|
1025
|
+
nextCursor: result.nextCursor ?? null,
|
|
1026
|
+
loaded: true,
|
|
1027
|
+
loading: false,
|
|
1028
|
+
error: null
|
|
1029
|
+
});
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
if (!retained || latestBrowseSeqByKey.get(seqKey) !== sequence) {
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
setChildrenState(sourceId, key, {
|
|
1035
|
+
loading: false,
|
|
1036
|
+
error: normalizeError(error, "load children failed")
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
const ensureRootLoaded = (sourceId) => {
|
|
1041
|
+
const root = snapshot.bySource[sourceId]?.childrenByKey[ROOT_CHILDREN_KEY];
|
|
1042
|
+
if (root?.loaded || root?.loading) {
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
void loadChildren(sourceId, null, { append: false });
|
|
1046
|
+
};
|
|
1047
|
+
const loadTabs = async () => {
|
|
1048
|
+
if (!retained) {
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
const sequence = ++tabsSequence;
|
|
1052
|
+
setSnapshot({ isLoadingTabs: true, tabsError: null });
|
|
1053
|
+
try {
|
|
1054
|
+
const tabs = await aggregator.listSources(scope);
|
|
1055
|
+
if (!retained || sequence !== tabsSequence) {
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
const activeSourceId = snapshot.activeSourceId && tabs.some((tab) => tab.sourceId === snapshot.activeSourceId) ? snapshot.activeSourceId : tabs[0]?.sourceId ?? null;
|
|
1059
|
+
setSnapshot((current) => ({
|
|
1060
|
+
...current,
|
|
1061
|
+
isLoadingTabs: false,
|
|
1062
|
+
tabs,
|
|
1063
|
+
activeSourceId,
|
|
1064
|
+
bySource: Object.fromEntries(
|
|
1065
|
+
tabs.map((tab) => [
|
|
1066
|
+
tab.sourceId,
|
|
1067
|
+
current.bySource[tab.sourceId] ?? emptyTabState(tab.sourceId)
|
|
1068
|
+
])
|
|
1069
|
+
)
|
|
1070
|
+
}));
|
|
1071
|
+
if (activeSourceId) {
|
|
1072
|
+
ensureRootLoaded(activeSourceId);
|
|
1073
|
+
}
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
if (!retained || sequence !== tabsSequence) {
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
setSnapshot({
|
|
1079
|
+
isLoadingTabs: false,
|
|
1080
|
+
tabsError: normalizeError(error, "load reference sources failed")
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
const clearSearchTimer = () => {
|
|
1085
|
+
if (searchTimer !== null) {
|
|
1086
|
+
clearTimeout(searchTimer);
|
|
1087
|
+
searchTimer = null;
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
const cancelSearch = () => {
|
|
1091
|
+
clearSearchTimer();
|
|
1092
|
+
searchSequence += 1;
|
|
1093
|
+
searchAbortController?.abort();
|
|
1094
|
+
searchAbortController = null;
|
|
1095
|
+
};
|
|
1096
|
+
const runSearch = async (sourceId, query, filters, scopeNodeId, limit, loadingMore) => {
|
|
1097
|
+
if (!retained) {
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
const sequence = ++searchSequence;
|
|
1101
|
+
searchAbortController?.abort();
|
|
1102
|
+
const abortController = new AbortController();
|
|
1103
|
+
searchAbortController = abortController;
|
|
1104
|
+
updateTab(sourceId, (tab) => ({
|
|
1105
|
+
...tab,
|
|
1106
|
+
...loadingMore ? { isSearchLoadingMore: true } : { isSearchLoading: true },
|
|
1107
|
+
searchError: null
|
|
1108
|
+
}));
|
|
1109
|
+
try {
|
|
1110
|
+
const result = await aggregator.search(scope, sourceId, {
|
|
1111
|
+
query,
|
|
1112
|
+
limit,
|
|
1113
|
+
signal: abortController.signal,
|
|
1114
|
+
...filters.length > 0 ? { filters } : {},
|
|
1115
|
+
...scopeNodeId == null ? {} : { withinNodeId: scopeNodeId }
|
|
1116
|
+
});
|
|
1117
|
+
if (!retained || sequence !== searchSequence) {
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
updateTab(sourceId, (tab) => ({
|
|
1121
|
+
...tab,
|
|
1122
|
+
isSearchLoading: false,
|
|
1123
|
+
isSearchLoadingMore: false,
|
|
1124
|
+
searchEntries: sortReferenceNodes(result.entries),
|
|
1125
|
+
searchNextCursor: result.nextCursor ?? null,
|
|
1126
|
+
searchLimit: limit,
|
|
1127
|
+
// 本页返回数达到请求上限、且未触全局上限 → 认为可能还有更多(增长式分页启发式)。
|
|
1128
|
+
searchHasMore: result.entries.length >= limit && limit < SEARCH_MAX_LIMIT,
|
|
1129
|
+
searchError: null
|
|
1130
|
+
}));
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
if (isAbortError2(error) || !retained || sequence !== searchSequence) {
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
updateTab(sourceId, (tab) => ({
|
|
1136
|
+
...tab,
|
|
1137
|
+
isSearchLoading: false,
|
|
1138
|
+
isSearchLoadingMore: false,
|
|
1139
|
+
// 加载更多失败时保留已有结果,仅新查询失败才清空。
|
|
1140
|
+
...loadingMore ? {} : { searchEntries: [], searchHasMore: false },
|
|
1141
|
+
searchError: normalizeError(error, "reference search failed")
|
|
1142
|
+
}));
|
|
1143
|
+
} finally {
|
|
1144
|
+
if (sequence === searchSequence) {
|
|
1145
|
+
searchAbortController = null;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
const collectFolderFiles = async (folder) => {
|
|
1150
|
+
const files = [];
|
|
1151
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1152
|
+
const walk = async (node) => {
|
|
1153
|
+
let cursor = null;
|
|
1154
|
+
do {
|
|
1155
|
+
const result = await aggregator.listChildren(scope, node.ref, {
|
|
1156
|
+
cursor
|
|
1157
|
+
});
|
|
1158
|
+
for (const entry of result.entries) {
|
|
1159
|
+
const key = nodeRefKey(entry.ref);
|
|
1160
|
+
if (seen.has(key)) {
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
seen.add(key);
|
|
1164
|
+
if (entry.kind === "folder") {
|
|
1165
|
+
await walk(entry);
|
|
1166
|
+
} else {
|
|
1167
|
+
files.push(entry);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
cursor = result.nextCursor ?? null;
|
|
1171
|
+
} while (cursor);
|
|
1172
|
+
};
|
|
1173
|
+
await walk(folder);
|
|
1174
|
+
return files;
|
|
1175
|
+
};
|
|
1176
|
+
const scheduleSearch = (sourceId, query, filters, scopeNodeId) => {
|
|
1177
|
+
clearSearchTimer();
|
|
1178
|
+
if (!retained || !query && filters.length === 0) {
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
if (searchDebounceMs <= 0) {
|
|
1182
|
+
void runSearch(
|
|
1183
|
+
sourceId,
|
|
1184
|
+
query,
|
|
1185
|
+
filters,
|
|
1186
|
+
scopeNodeId,
|
|
1187
|
+
SEARCH_PAGE_SIZE,
|
|
1188
|
+
false
|
|
1189
|
+
);
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
searchTimer = setTimeout(() => {
|
|
1193
|
+
searchTimer = null;
|
|
1194
|
+
void runSearch(
|
|
1195
|
+
sourceId,
|
|
1196
|
+
query,
|
|
1197
|
+
filters,
|
|
1198
|
+
scopeNodeId,
|
|
1199
|
+
SEARCH_PAGE_SIZE,
|
|
1200
|
+
false
|
|
1201
|
+
);
|
|
1202
|
+
}, searchDebounceMs);
|
|
1203
|
+
};
|
|
1204
|
+
return {
|
|
1205
|
+
get store() {
|
|
1206
|
+
return store;
|
|
1207
|
+
},
|
|
1208
|
+
getSnapshot() {
|
|
1209
|
+
return snapshot;
|
|
1210
|
+
},
|
|
1211
|
+
open() {
|
|
1212
|
+
if (retained) {
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
retained = true;
|
|
1216
|
+
tabsReady = loadTabs();
|
|
1217
|
+
},
|
|
1218
|
+
close() {
|
|
1219
|
+
retained = false;
|
|
1220
|
+
cancelSearch();
|
|
1221
|
+
latestBrowseSeqByKey.clear();
|
|
1222
|
+
tabsSequence += 1;
|
|
1223
|
+
},
|
|
1224
|
+
reset() {
|
|
1225
|
+
cancelSearch();
|
|
1226
|
+
latestBrowseSeqByKey.clear();
|
|
1227
|
+
tabsSequence += 1;
|
|
1228
|
+
setSnapshot({
|
|
1229
|
+
isLoadingTabs: false,
|
|
1230
|
+
tabsError: null,
|
|
1231
|
+
tabs: [],
|
|
1232
|
+
activeSourceId: null,
|
|
1233
|
+
bySource: {},
|
|
1234
|
+
selection: []
|
|
1235
|
+
});
|
|
1236
|
+
},
|
|
1237
|
+
setActiveSource(sourceId) {
|
|
1238
|
+
if (!snapshot.tabs.some((tab) => tab.sourceId === sourceId)) {
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
const prevTab = snapshot.activeSourceId ? snapshot.bySource[snapshot.activeSourceId] : void 0;
|
|
1242
|
+
const carriedQuery = prevTab?.searchQuery ?? "";
|
|
1243
|
+
const carriedFilters = prevTab?.searchFilters ?? [];
|
|
1244
|
+
const trimmed = carriedQuery.trim();
|
|
1245
|
+
cancelSearch();
|
|
1246
|
+
setSnapshot({ activeSourceId: sourceId });
|
|
1247
|
+
if (trimmed === "" && carriedFilters.length === 0) {
|
|
1248
|
+
updateTab(
|
|
1249
|
+
sourceId,
|
|
1250
|
+
(tab) => tab.mode === "browse" && tab.searchQuery === "" && tab.searchFilters.length === 0 ? tab : {
|
|
1251
|
+
...tab,
|
|
1252
|
+
mode: "browse",
|
|
1253
|
+
searchQuery: "",
|
|
1254
|
+
searchFilters: [],
|
|
1255
|
+
searchEntries: [],
|
|
1256
|
+
searchHasMore: false,
|
|
1257
|
+
isSearchLoading: false,
|
|
1258
|
+
isSearchLoadingMore: false,
|
|
1259
|
+
searchError: null
|
|
1260
|
+
}
|
|
1261
|
+
);
|
|
1262
|
+
ensureRootLoaded(sourceId);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
const nextScopeNodeId = snapshot.bySource[sourceId]?.searchScopeNodeId ?? null;
|
|
1266
|
+
updateTab(sourceId, (tab) => ({
|
|
1267
|
+
...tab,
|
|
1268
|
+
searchQuery: carriedQuery,
|
|
1269
|
+
searchFilters: carriedFilters,
|
|
1270
|
+
searchScopeNodeId: nextScopeNodeId,
|
|
1271
|
+
mode: "search",
|
|
1272
|
+
// 沿用旧结果直到新结果就绪,避免切源瞬间闪空。
|
|
1273
|
+
isSearchLoading: true,
|
|
1274
|
+
searchError: null
|
|
1275
|
+
}));
|
|
1276
|
+
scheduleSearch(sourceId, trimmed, carriedFilters, nextScopeNodeId);
|
|
1277
|
+
},
|
|
1278
|
+
async locatePath(target) {
|
|
1279
|
+
await tabsReady;
|
|
1280
|
+
if (!retained) {
|
|
1281
|
+
return [];
|
|
1282
|
+
}
|
|
1283
|
+
const refs = await aggregator.locateTarget(
|
|
1284
|
+
scope,
|
|
1285
|
+
target.sourceId,
|
|
1286
|
+
target.params
|
|
1287
|
+
);
|
|
1288
|
+
if (!refs || refs.length === 0) {
|
|
1289
|
+
return [];
|
|
1290
|
+
}
|
|
1291
|
+
const path = [];
|
|
1292
|
+
let parent = {
|
|
1293
|
+
sourceId: target.sourceId,
|
|
1294
|
+
nodeId: SOURCE_ROOT_NODE_ID
|
|
1295
|
+
};
|
|
1296
|
+
for (const ref of refs) {
|
|
1297
|
+
const { entries } = await aggregator.listChildren(scope, parent);
|
|
1298
|
+
const targetKey = nodeRefKey(ref);
|
|
1299
|
+
const node = entries.find(
|
|
1300
|
+
(entry) => nodeRefKey(entry.ref) === targetKey
|
|
1301
|
+
);
|
|
1302
|
+
if (!node) {
|
|
1303
|
+
break;
|
|
1304
|
+
}
|
|
1305
|
+
path.push(node);
|
|
1306
|
+
parent = ref;
|
|
1307
|
+
}
|
|
1308
|
+
return path;
|
|
1309
|
+
},
|
|
1310
|
+
ensureChildren(node) {
|
|
1311
|
+
const sourceId = node ? node.ref.sourceId : snapshot.activeSourceId;
|
|
1312
|
+
if (!sourceId) {
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
const key = childrenKeyForNode(node);
|
|
1316
|
+
const childState = snapshot.bySource[sourceId]?.childrenByKey[key];
|
|
1317
|
+
if (!childState?.loaded && !childState?.loading) {
|
|
1318
|
+
void loadChildren(sourceId, node, { append: false });
|
|
1319
|
+
}
|
|
1320
|
+
},
|
|
1321
|
+
ensureSourceRoot(sourceId) {
|
|
1322
|
+
if (!sourceId || !snapshot.tabs.some((tab) => tab.sourceId === sourceId)) {
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
ensureRootLoaded(sourceId);
|
|
1326
|
+
},
|
|
1327
|
+
toggleNode(node) {
|
|
1328
|
+
if (node.kind !== "folder") {
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
const sourceId = node.ref.sourceId;
|
|
1332
|
+
const key = nodeRefKey(node.ref);
|
|
1333
|
+
const wasExpanded = snapshot.bySource[sourceId]?.expandedKeys[key] ?? false;
|
|
1334
|
+
const nextExpanded = !wasExpanded;
|
|
1335
|
+
updateTab(sourceId, (tab) => ({
|
|
1336
|
+
...tab,
|
|
1337
|
+
expandedKeys: { ...tab.expandedKeys, [key]: nextExpanded }
|
|
1338
|
+
}));
|
|
1339
|
+
const childState = snapshot.bySource[sourceId]?.childrenByKey[key];
|
|
1340
|
+
if (nextExpanded && !childState?.loaded && !childState?.loading) {
|
|
1341
|
+
void loadChildren(sourceId, node, { append: false });
|
|
1342
|
+
}
|
|
1343
|
+
},
|
|
1344
|
+
loadMore(node) {
|
|
1345
|
+
const sourceId = node ? node.ref.sourceId : snapshot.activeSourceId;
|
|
1346
|
+
if (!sourceId) {
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
void loadChildren(sourceId, node, { append: true });
|
|
1350
|
+
},
|
|
1351
|
+
loadMoreSourceRoot(sourceId) {
|
|
1352
|
+
if (!sourceId) {
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
void loadChildren(sourceId, null, { append: true });
|
|
1356
|
+
},
|
|
1357
|
+
setSearchQuery(query, scopeNodeId = null) {
|
|
1358
|
+
const sourceId = snapshot.activeSourceId;
|
|
1359
|
+
if (!sourceId) {
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
const filters = snapshot.bySource[sourceId]?.searchFilters ?? [];
|
|
1363
|
+
const trimmed = query.trim();
|
|
1364
|
+
const nextMode = trimmed || filters.length > 0 ? "search" : "browse";
|
|
1365
|
+
updateTab(sourceId, (tab) => ({
|
|
1366
|
+
...tab,
|
|
1367
|
+
searchQuery: query,
|
|
1368
|
+
searchScopeNodeId: scopeNodeId,
|
|
1369
|
+
mode: nextMode,
|
|
1370
|
+
// 进入搜索:立刻置 loading(搜索是 debounce 的,否则键入到取数之间会先渲染空态,
|
|
1371
|
+
// 造成「空态 → spinner → 结果」闪烁)。保留上次 searchEntries,细化关键词时沿用旧结果而非闪空。
|
|
1372
|
+
...nextMode === "browse" ? { isSearchLoading: false, searchEntries: [], searchError: null } : { isSearchLoading: true, searchError: null }
|
|
1373
|
+
}));
|
|
1374
|
+
if (nextMode === "search") {
|
|
1375
|
+
scheduleSearch(sourceId, trimmed, filters, scopeNodeId);
|
|
1376
|
+
} else {
|
|
1377
|
+
cancelSearch();
|
|
1378
|
+
ensureRootLoaded(sourceId);
|
|
1379
|
+
}
|
|
1380
|
+
},
|
|
1381
|
+
setSearchFilters(filters, scopeNodeId = null) {
|
|
1382
|
+
const sourceId = snapshot.activeSourceId;
|
|
1383
|
+
if (!sourceId) {
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
const tab = snapshot.bySource[sourceId];
|
|
1387
|
+
const trimmed = tab?.searchQuery.trim() ?? "";
|
|
1388
|
+
const scopeId = scopeNodeId ?? tab?.searchScopeNodeId ?? null;
|
|
1389
|
+
const nextMode = trimmed || filters.length > 0 ? "search" : "browse";
|
|
1390
|
+
updateTab(sourceId, (current) => ({
|
|
1391
|
+
...current,
|
|
1392
|
+
searchFilters: filters,
|
|
1393
|
+
searchScopeNodeId: scopeId,
|
|
1394
|
+
mode: nextMode,
|
|
1395
|
+
...nextMode === "browse" ? { isSearchLoading: false, searchEntries: [], searchError: null } : { isSearchLoading: true, searchError: null }
|
|
1396
|
+
}));
|
|
1397
|
+
if (nextMode === "search") {
|
|
1398
|
+
scheduleSearch(sourceId, trimmed, filters, scopeId);
|
|
1399
|
+
} else {
|
|
1400
|
+
cancelSearch();
|
|
1401
|
+
ensureRootLoaded(sourceId);
|
|
1402
|
+
}
|
|
1403
|
+
},
|
|
1404
|
+
setSearchScope(scopeNodeId) {
|
|
1405
|
+
const sourceId = snapshot.activeSourceId;
|
|
1406
|
+
if (!sourceId) {
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
const tab = snapshot.bySource[sourceId];
|
|
1410
|
+
if (!tab || tab.searchScopeNodeId === scopeNodeId) {
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
updateTab(sourceId, (current) => ({
|
|
1414
|
+
...current,
|
|
1415
|
+
searchScopeNodeId: scopeNodeId
|
|
1416
|
+
}));
|
|
1417
|
+
const trimmed = tab.searchQuery.trim();
|
|
1418
|
+
const filters = tab.searchFilters;
|
|
1419
|
+
if (tab.mode === "search" && (trimmed || filters.length > 0)) {
|
|
1420
|
+
scheduleSearch(sourceId, trimmed, filters, scopeNodeId);
|
|
1421
|
+
}
|
|
1422
|
+
},
|
|
1423
|
+
loadMoreSearch() {
|
|
1424
|
+
const sourceId = snapshot.activeSourceId;
|
|
1425
|
+
if (!sourceId) {
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
const tab = snapshot.bySource[sourceId];
|
|
1429
|
+
if (!tab || tab.mode !== "search" || !tab.searchHasMore || tab.isSearchLoadingMore) {
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
const trimmed = tab.searchQuery.trim();
|
|
1433
|
+
if (!trimmed && tab.searchFilters.length === 0) {
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
const nextLimit = Math.min(
|
|
1437
|
+
(tab.searchLimit || SEARCH_PAGE_SIZE) + SEARCH_PAGE_SIZE,
|
|
1438
|
+
SEARCH_MAX_LIMIT
|
|
1439
|
+
);
|
|
1440
|
+
if (nextLimit <= tab.searchLimit) {
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
clearSearchTimer();
|
|
1444
|
+
void runSearch(
|
|
1445
|
+
sourceId,
|
|
1446
|
+
trimmed,
|
|
1447
|
+
tab.searchFilters,
|
|
1448
|
+
tab.searchScopeNodeId,
|
|
1449
|
+
nextLimit,
|
|
1450
|
+
true
|
|
1451
|
+
);
|
|
1452
|
+
},
|
|
1453
|
+
toggleSelection(node) {
|
|
1454
|
+
const key = nodeRefKey(node.ref);
|
|
1455
|
+
setSnapshot((current) => {
|
|
1456
|
+
const exists = current.selection.some(
|
|
1457
|
+
(item) => nodeRefKey(item.ref) === key
|
|
1458
|
+
);
|
|
1459
|
+
return {
|
|
1460
|
+
...current,
|
|
1461
|
+
selection: exists ? current.selection.filter((item) => nodeRefKey(item.ref) !== key) : [...current.selection, node]
|
|
1462
|
+
};
|
|
1463
|
+
});
|
|
1464
|
+
},
|
|
1465
|
+
clearSelection() {
|
|
1466
|
+
setSnapshot({ selection: [] });
|
|
1467
|
+
},
|
|
1468
|
+
async confirm() {
|
|
1469
|
+
const resolved = [];
|
|
1470
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
1471
|
+
const push = (ref) => {
|
|
1472
|
+
if (seenPaths.has(ref.path)) {
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
seenPaths.add(ref.path);
|
|
1476
|
+
resolved.push(ref);
|
|
1477
|
+
};
|
|
1478
|
+
for (const node of snapshot.selection) {
|
|
1479
|
+
if (node.kind !== "folder") {
|
|
1480
|
+
push(aggregator.resolveSelection(node));
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
const navigable = aggregator.getLoadedSource(node.ref.sourceId)?.capabilities.navigable ?? false;
|
|
1484
|
+
if (!navigable) {
|
|
1485
|
+
push(aggregator.resolveSelection(node));
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1488
|
+
const files = await collectFolderFiles(node);
|
|
1489
|
+
for (const fileNode of files) {
|
|
1490
|
+
push(aggregator.resolveSelection(fileNode));
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
return resolved;
|
|
1494
|
+
},
|
|
1495
|
+
async confirmGrouped() {
|
|
1496
|
+
const files = [];
|
|
1497
|
+
const bundles = [];
|
|
1498
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
1499
|
+
const pushFile = (ref) => {
|
|
1500
|
+
if (seenPaths.has(ref.path)) {
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1503
|
+
seenPaths.add(ref.path);
|
|
1504
|
+
files.push(ref);
|
|
1505
|
+
};
|
|
1506
|
+
for (const node of snapshot.selection) {
|
|
1507
|
+
const navigable = node.kind === "folder" && (aggregator.getLoadedSource(node.ref.sourceId)?.capabilities.navigable ?? false);
|
|
1508
|
+
if (!navigable) {
|
|
1509
|
+
pushFile(aggregator.resolveSelection(node));
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
const fileNodes = await collectFolderFiles(node);
|
|
1513
|
+
const bundleSeen = /* @__PURE__ */ new Set();
|
|
1514
|
+
const bundleFiles = [];
|
|
1515
|
+
for (const fileNode of fileNodes) {
|
|
1516
|
+
const ref = aggregator.resolveSelection(fileNode);
|
|
1517
|
+
if (bundleSeen.has(ref.path)) {
|
|
1518
|
+
continue;
|
|
1519
|
+
}
|
|
1520
|
+
bundleSeen.add(ref.path);
|
|
1521
|
+
bundleFiles.push(ref);
|
|
1522
|
+
}
|
|
1523
|
+
bundles.push({ root: node, files: bundleFiles });
|
|
1524
|
+
}
|
|
1525
|
+
return { files, bundles };
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
function isAbortError2(error) {
|
|
1530
|
+
return error instanceof Error && error.name === "AbortError";
|
|
1531
|
+
}
|
|
1532
|
+
function normalizeError(error, fallback) {
|
|
1533
|
+
return error instanceof Error ? error : new Error(fallback);
|
|
1534
|
+
}
|
|
1535
|
+
|
|
891
1536
|
export {
|
|
892
1537
|
workspaceFileReferenceDefaultExpandedDepth,
|
|
893
1538
|
normalizeDirectoryPath,
|
|
@@ -896,6 +1541,8 @@ export {
|
|
|
896
1541
|
mergePrefetchedDirectoryState,
|
|
897
1542
|
createWorkspaceFileReferenceDirectoryStateFromSnapshot,
|
|
898
1543
|
prefetchReferenceTree,
|
|
899
|
-
useWorkspaceFileReferencePickerView
|
|
1544
|
+
useWorkspaceFileReferencePickerView,
|
|
1545
|
+
ROOT_CHILDREN_KEY,
|
|
1546
|
+
createReferenceSourcePickerController
|
|
900
1547
|
};
|
|
901
|
-
//# sourceMappingURL=chunk-
|
|
1548
|
+
//# sourceMappingURL=chunk-KSK6PILG.js.map
|