@youtyan/code-viewer 0.1.28 → 0.1.29

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.
@@ -280,6 +280,8 @@ var DEFAULT_WORKTREE_OMIT_DIR_NAMES = [
280
280
  "out",
281
281
  "target",
282
282
  ".gradle",
283
+ ".pnpm-store",
284
+ ".turbo",
283
285
  "__pycache__",
284
286
  ".pytest_cache",
285
287
  ".tox",
@@ -1279,6 +1281,9 @@ function startWorktreeUpdateWatch(options) {
1279
1281
  const debounceMs = options.debounceMs ?? 250;
1280
1282
  const watchers = new Map;
1281
1283
  const signatures = new Map;
1284
+ const initialScanAsync = options.initialScanMode === "async" || (!options.watch || options.watch === nodeWatch) && !options.readdirSync;
1285
+ const initialScanQueue = [];
1286
+ let initialScanTimer = null;
1282
1287
  let timer = null;
1283
1288
  const ignored = (path) => isSkippableSearchPath(normalizeRelativePath(path), options.omitDirNames, options.excludeNames);
1284
1289
  const scheduleUpdate = () => {
@@ -1301,6 +1306,11 @@ function startWorktreeUpdateWatch(options) {
1301
1306
  }
1302
1307
  };
1303
1308
  const closeAll = () => {
1309
+ if (initialScanTimer) {
1310
+ clearTimer(initialScanTimer);
1311
+ initialScanTimer = null;
1312
+ }
1313
+ initialScanQueue.length = 0;
1304
1314
  for (const watcher of [...watchers.values()]) {
1305
1315
  try {
1306
1316
  watcher.close?.();
@@ -1309,7 +1319,36 @@ function startWorktreeUpdateWatch(options) {
1309
1319
  watchers.clear();
1310
1320
  signatures.clear();
1311
1321
  };
1312
- const watchDirectory = (dir) => {
1322
+ const readChildDirectories = (dir) => {
1323
+ let entries;
1324
+ try {
1325
+ entries = readDirs(dir);
1326
+ } catch (error) {
1327
+ options.onError?.(error);
1328
+ return [];
1329
+ }
1330
+ const children = [];
1331
+ for (const entry of entries) {
1332
+ if (!entry.isDirectory())
1333
+ continue;
1334
+ children.push(join4(dir, entry.name));
1335
+ }
1336
+ return children;
1337
+ };
1338
+ const processInitialScanQueue = () => {
1339
+ initialScanTimer = null;
1340
+ const next = initialScanQueue.shift();
1341
+ if (next)
1342
+ watchDirectory(next, true);
1343
+ if (initialScanQueue.length)
1344
+ initialScanTimer = setTimer(processInitialScanQueue, 50);
1345
+ };
1346
+ const queueInitialChildren = (dir) => {
1347
+ initialScanQueue.push(...readChildDirectories(dir));
1348
+ if (!initialScanTimer)
1349
+ initialScanTimer = setTimer(processInitialScanQueue, 5000);
1350
+ };
1351
+ const watchDirectory = (dir, initialScan = false) => {
1313
1352
  if (watchers.has(dir))
1314
1353
  return;
1315
1354
  const rel = normalizeRelativePath(relative(options.root, dir));
@@ -1364,20 +1403,14 @@ function startWorktreeUpdateWatch(options) {
1364
1403
  options.onError?.(error);
1365
1404
  return;
1366
1405
  }
1367
- let entries;
1368
- try {
1369
- entries = readDirs(dir);
1370
- } catch (error) {
1371
- options.onError?.(error);
1406
+ if (initialScanAsync && initialScan) {
1407
+ queueInitialChildren(dir);
1372
1408
  return;
1373
1409
  }
1374
- for (const entry of entries) {
1375
- if (!entry.isDirectory())
1376
- continue;
1377
- watchDirectory(join4(dir, entry.name));
1378
- }
1410
+ for (const child of readChildDirectories(dir))
1411
+ watchDirectory(child);
1379
1412
  };
1380
- watchDirectory(options.root);
1413
+ watchDirectory(options.root, true);
1381
1414
  return { started: watchers.size > 0, close: closeAll };
1382
1415
  }
1383
1416
 
@@ -3147,6 +3180,7 @@ startWorktreeUpdateWatch({
3147
3180
  omitDirNames: scopeOmitDirNames,
3148
3181
  excludeNames: scopeExcludeNames,
3149
3182
  watch,
3183
+ initialScanMode: "async",
3150
3184
  onUpdate: triggerUpdate,
3151
3185
  onError: (error) => {
3152
3186
  const message = error instanceof Error ? error.message : String(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youtyan/code-viewer",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "Local browser-based code and git diff viewer",
5
5
  "type": "module",
6
6
  "bin": {
package/web/app.js CHANGED
@@ -7002,7 +7002,9 @@ ${frontmatter.yaml}
7002
7002
  let SIDEBAR_VISIBLE_ROWS = [];
7003
7003
  let SIDEBAR_ROW_BY_PATH = new Map;
7004
7004
  let SIDEBAR_VIRTUAL_ACTIVE_PATH = "";
7005
- const SIDEBAR_TREE_ITEMS_CACHE = new WeakMap;
7005
+ let SIDEBAR_TREE_ITEMS_CACHE = new WeakMap;
7006
+ const SIDEBAR_LAZY_LOADED_DIRS = new Set;
7007
+ const SIDEBAR_LAZY_LOADING_DIRS = new Map;
7006
7008
  let REPO_SORT = {
7007
7009
  key: "name",
7008
7010
  direction: "asc"
@@ -8058,6 +8060,86 @@ ${frontmatter.yaml}
8058
8060
  SIDEBAR_TREE_ITEMS_CACHE.set(node, items);
8059
8061
  return items;
8060
8062
  }
8063
+ function sidebarTreeNodeHasChildren(node) {
8064
+ return Object.keys(node.dirs).length > 0 || node.files.length > 0;
8065
+ }
8066
+ function shouldLazyLoadSidebarDir(dir) {
8067
+ return isRepositorySidebarMode() && isVirtualSidebarActive() && !dir.children_omitted && !sidebarTreeNodeHasChildren(dir) && !SIDEBAR_LAZY_LOADED_DIRS.has(dir.path);
8068
+ }
8069
+ function upsertSidebarTreeEntry(entry, order) {
8070
+ if (!SIDEBAR_TREE_ROOT)
8071
+ return;
8072
+ const parts = entry.path.split("/").filter(Boolean);
8073
+ if (!parts.length)
8074
+ return;
8075
+ let node = SIDEBAR_TREE_ROOT;
8076
+ let acc = "";
8077
+ const dirPartCount = entry.type === "tree" ? parts.length : parts.length - 1;
8078
+ for (let i2 = 0;i2 < dirPartCount; i2++) {
8079
+ const part = parts[i2];
8080
+ acc = acc ? `${acc}/${part}` : part;
8081
+ if (!node.dirs[part]) {
8082
+ node.dirs[part] = {
8083
+ name: part,
8084
+ dirs: {},
8085
+ files: [],
8086
+ path: acc,
8087
+ minOrder: order
8088
+ };
8089
+ }
8090
+ node = node.dirs[part];
8091
+ node.minOrder = Math.min(node.minOrder, order);
8092
+ }
8093
+ if (entry.type === "tree") {
8094
+ node.explicit = true;
8095
+ if (entry.children_omitted === true) {
8096
+ node.children_omitted = true;
8097
+ node.children_omitted_reason = entry.children_omitted_reason;
8098
+ }
8099
+ return;
8100
+ }
8101
+ if (!node.files.some((file) => file.path === entry.path))
8102
+ node.files.push({ ...entry, order });
8103
+ }
8104
+ function mergeSidebarTreeEntries(entries) {
8105
+ entries.forEach((entry, index) => {
8106
+ upsertSidebarTreeEntry(entry, entry.order ?? index + 1);
8107
+ });
8108
+ SIDEBAR_TREE_ITEMS_CACHE = new WeakMap;
8109
+ if (SIDEBAR_TREE_ROOT)
8110
+ buildSidebarTreeRows(SIDEBAR_TREE_ROOT);
8111
+ }
8112
+ function ensureVirtualSidebarDirLoaded(dir) {
8113
+ if (!shouldLazyLoadSidebarDir(dir))
8114
+ return Promise.resolve();
8115
+ const existing = SIDEBAR_LAZY_LOADING_DIRS.get(dir.path);
8116
+ if (existing)
8117
+ return existing;
8118
+ const params = new URLSearchParams;
8119
+ params.set("ref", REPO_SIDEBAR_REF || "worktree");
8120
+ params.set("path", dir.path);
8121
+ appendScopeParams(params);
8122
+ const load2 = trackLoad(fetch(`/_tree?${params.toString()}`).then((response) => {
8123
+ if (!response.ok)
8124
+ throw new Error("failed to load repository tree");
8125
+ return response.json();
8126
+ })).then((meta) => {
8127
+ const entries = meta.entries.map((entry, index) => ({
8128
+ order: dir.minOrder + (index + 1) / 1e5,
8129
+ path: entry.path,
8130
+ display_path: entry.path,
8131
+ type: entry.type,
8132
+ children_omitted: entry.children_omitted,
8133
+ children_omitted_reason: entry.children_omitted_reason
8134
+ }));
8135
+ mergeSidebarTreeEntries(entries);
8136
+ SIDEBAR_LAZY_LOADED_DIRS.add(dir.path);
8137
+ }).finally(() => {
8138
+ SIDEBAR_LAZY_LOADING_DIRS.delete(dir.path);
8139
+ });
8140
+ SIDEBAR_LAZY_LOADING_DIRS.set(dir.path, load2);
8141
+ return load2;
8142
+ }
8061
8143
  function createTreeDirRow(dir, depth, onFileClick) {
8062
8144
  const li = document.createElement("li");
8063
8145
  li.className = "tree-dir";
@@ -8105,16 +8187,26 @@ ${frontmatter.yaml}
8105
8187
  const updateIcon = () => {
8106
8188
  setFolderIcon(dirIcon, li.classList.contains("collapsed"));
8107
8189
  };
8108
- const toggleDir = (e2) => {
8190
+ const toggleDir = async (e2) => {
8109
8191
  e2.stopPropagation();
8110
- li.classList.toggle("collapsed");
8111
- updateIcon();
8112
- if (li.classList.contains("collapsed"))
8113
- STATE.collapsedDirs.add(dir.path);
8114
- else
8115
- STATE.collapsedDirs.delete(dir.path);
8116
- localStorage.setItem("gdp:collapsed-dirs", JSON.stringify([...STATE.collapsedDirs]));
8117
- rerenderVirtualSidebar();
8192
+ if (li.dataset.toggling === "true")
8193
+ return;
8194
+ const expanding = li.classList.contains("collapsed");
8195
+ li.dataset.toggling = "true";
8196
+ try {
8197
+ if (expanding)
8198
+ await ensureVirtualSidebarDirLoaded(dir);
8199
+ li.classList.toggle("collapsed");
8200
+ updateIcon();
8201
+ if (li.classList.contains("collapsed"))
8202
+ STATE.collapsedDirs.add(dir.path);
8203
+ else
8204
+ STATE.collapsedDirs.delete(dir.path);
8205
+ localStorage.setItem("gdp:collapsed-dirs", JSON.stringify([...STATE.collapsedDirs]));
8206
+ rerenderVirtualSidebar();
8207
+ } finally {
8208
+ delete li.dataset.toggling;
8209
+ }
8118
8210
  };
8119
8211
  li.classList.toggle("collapsed", STATE.collapsedDirs.has(dir.path));
8120
8212
  updateIcon();
@@ -8382,6 +8474,8 @@ ${frontmatter.yaml}
8382
8474
  SIDEBAR_TREE_ROWS = [];
8383
8475
  SIDEBAR_VISIBLE_ROWS = [];
8384
8476
  SIDEBAR_ROW_BY_PATH = new Map;
8477
+ SIDEBAR_LAZY_LOADED_DIRS.clear();
8478
+ SIDEBAR_LAZY_LOADING_DIRS.clear();
8385
8479
  STATE.files = files;
8386
8480
  SIDEBAR_FILES = files;
8387
8481
  SIDEBAR_ON_FILE_CLICK = onFileClick;
@@ -9851,6 +9945,14 @@ ${frontmatter.yaml}
9851
9945
  function activateRepoSidebarPath(currentPath) {
9852
9946
  markActive(currentPath, { reveal: true });
9853
9947
  applyFilter();
9948
+ const row = SIDEBAR_ROW_BY_PATH.get(currentPath);
9949
+ if (row?.kind === "dir" && row.dir && shouldLazyLoadSidebarDir(row.dir))
9950
+ ensureVirtualSidebarDirLoaded(row.dir).then(() => {
9951
+ if (SIDEBAR_VIRTUAL_ACTIVE_PATH === currentPath) {
9952
+ rerenderVirtualSidebar();
9953
+ scrollVirtualSidebarPathIntoView(currentPath);
9954
+ }
9955
+ });
9854
9956
  }
9855
9957
  function createPlaceholder(f2) {
9856
9958
  const card = document.createElement("div");