@oversk7/pi-web-dsh-style 1.3.4 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -44,6 +44,8 @@ Each visible page subscribes to its selected session, receives an initial snapsh
44
44
 
45
45
  Switching back to a newly created or previously loaded session shows its cached content immediately and keeps the composer available while refreshing. Repeated opens share pending requests, and sessions with a running process return their current snapshot without waiting for RPC refreshes.
46
46
 
47
+ In the in-app workspace directory browser, click the pencil to paste a full path. The list loads the corresponding directory and filters matching folders, including deeply nested paths. Click a folder to select it, or press Enter to navigate directly; a trailing separator previews the folder's contents. Relative file links in replies resolve against the selected session's workspace.
48
+
47
49
  The interface starts in Chinese. To switch to English, open **设置 (Settings) → 外观 (Appearance) → 界面语言 (Interface language) → English**. Your choice is saved in this browser.
48
50
 
49
51
  ### Phone and remote access
@@ -93,6 +95,8 @@ pi --web
93
95
 
94
96
  切回新建或已加载的会话时,立即显示缓存内容,后台刷新期间仍可编辑输入。重复打开会复用待完成请求;已有运行进程的会话直接返回当前快照,避免等待 RPC 刷新。
95
97
 
98
+ 在内置工作区目录浏览器中,点击铅笔图标即可粘贴完整路径,列表会加载对应目录并筛选匹配的文件夹,支持多层嵌套路径。点击文件夹选择,或按 Enter 直接跳转;路径末尾带分隔符时预览该文件夹的内容。回答中的相对文件链接按当前会话的工作区解析。
99
+
96
100
  界面默认使用中文。打开 **设置 → 外观 → 界面语言**,即可一键切换中文或 English;选择会保存在当前浏览器。
97
101
 
98
102
  ### 手机与远程访问
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oversk7/pi-web-dsh-style",
3
- "version": "1.3.4",
3
+ "version": "1.4.0",
4
4
  "description": "An unofficial DSH-styled Windows browser UI for pi coding agent with configurable LAN and native frp relay access",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/server.ts CHANGED
@@ -1943,21 +1943,11 @@ async function listDirectories(input: string): Promise<Json> {
1943
1943
  .map((e) => ({ name: e.name, path: join(target, e.name), hidden: e.name.startsWith(".") }))
1944
1944
  .sort((a, b) => a.name.localeCompare(b.name, "zh-CN"));
1945
1945
  const crumbs: { name: string; path: string; hidden: boolean }[] = [];
1946
- {
1947
- const parts: string[] = [];
1948
- let current = target;
1949
- for (;;) {
1950
- parts.unshift(current);
1951
- const up = dirname(current);
1952
- if (up === current) break;
1953
- current = up;
1954
- }
1955
- let acc = parts[0];
1956
- crumbs.push({ name: parts[0], path: parts[0], hidden: false });
1957
- for (const part of parts.slice(1)) {
1958
- acc = join(acc, part);
1959
- crumbs.push({ name: part, path: acc, hidden: part.startsWith(".") });
1960
- }
1946
+ for (let current = target; ; current = dirname(current)) {
1947
+ const isRoot = dirname(current) === current;
1948
+ const name = isRoot ? current : basename(current);
1949
+ crumbs.unshift({ name, path: current, hidden: !isRoot && name.startsWith(".") });
1950
+ if (isRoot) break;
1961
1951
  }
1962
1952
  const parent = dirname(target) === target ? null : dirname(target);
1963
1953
  return { path: target, home, parent, crumbs, entries: dirs, truncated: false };
package/web/app.js CHANGED
@@ -3278,6 +3278,7 @@ async function ensureWorkspaceThen(action) {
3278
3278
  let browser = null;
3279
3279
  let pickerResolve = null;
3280
3280
  let browserKeyHandler = null;
3281
+ let directoryRequest = null;
3281
3282
 
3282
3283
  async function adoptWorkspacePath(path) {
3283
3284
  const added = await post("/api/workspaces", { path });
@@ -3354,6 +3355,7 @@ function promptWorkspaceBrowse() {
3354
3355
  }
3355
3356
 
3356
3357
  function closeWorkspacePicker() {
3358
+ directoryRequest = null;
3357
3359
  if (browserKeyHandler) {
3358
3360
  document.removeEventListener("keydown", browserKeyHandler);
3359
3361
  browserKeyHandler = null;
@@ -3387,10 +3389,44 @@ function draftDirectory(draft) {
3387
3389
  function readDraft(listing, draft) {
3388
3390
  const directory = draftDirectory(draft);
3389
3391
  if (directory === null) return { directory: null, tail: null };
3390
- const answers = directory === levelDirectory(listing);
3392
+ const fold = (value) => separatorOf() === "\\" ? value.replaceAll("/", "\\").toLowerCase() : value;
3393
+ const answers = fold(directory) === fold(levelDirectory(listing));
3391
3394
  return { directory, tail: answers ? draft.slice(directory.length) : null };
3392
3395
  }
3393
3396
 
3397
+ async function previewPathDraft(draft) {
3398
+ const current = browser;
3399
+ const request = {};
3400
+ directoryRequest = request;
3401
+ if (!current || current.pathDraft !== draft) return;
3402
+ const listing = current.child ?? current.parent;
3403
+ const directory = draftDirectory(draft);
3404
+ current.error = null;
3405
+ if (directory === null || (listing && readDraft(listing, draft).tail !== null)) {
3406
+ current.loading = false;
3407
+ renderBrowser();
3408
+ return;
3409
+ }
3410
+ current.loading = true;
3411
+ renderBrowser();
3412
+ try {
3413
+ const data = await api(`/api/fs/list?path=${encodeURIComponent(directory)}`);
3414
+ if (browser !== current || directoryRequest !== request) return;
3415
+ if (data.error) throw new Error(data.error);
3416
+ current.parent = data;
3417
+ current.selected = null;
3418
+ current.child = null;
3419
+ current.home = data.home || current.home;
3420
+ current.loading = false;
3421
+ renderBrowser();
3422
+ } catch (error) {
3423
+ if (browser !== current || directoryRequest !== request) return;
3424
+ current.loading = false;
3425
+ current.error = error.message || String(error);
3426
+ renderBrowser();
3427
+ }
3428
+ }
3429
+
3394
3430
  function visibleEntries(entries, selectedPath, showHidden, filterPrefix) {
3395
3431
  const needle = filterPrefix === null ? "" : filterPrefix.toLowerCase();
3396
3432
  const displayable = (entry) => showHidden || !entry.hidden || needle.startsWith(".");
@@ -3429,6 +3465,9 @@ function browserTargetPath() {
3429
3465
 
3430
3466
  async function navigate(path) {
3431
3467
  if (!browser || !path) return;
3468
+ const current = browser;
3469
+ const request = {};
3470
+ directoryRequest = request;
3432
3471
  browser.loading = true;
3433
3472
  browser.error = null;
3434
3473
  browser.selected = null;
@@ -3437,7 +3476,7 @@ async function navigate(path) {
3437
3476
  let target = null;
3438
3477
  try {
3439
3478
  const data = await api(`/api/fs/list?path=${encodeURIComponent(path)}`);
3440
- if (!browser) return;
3479
+ if (browser !== current || directoryRequest !== request) return;
3441
3480
  if (data.error) throw new Error(data.error);
3442
3481
  target = data;
3443
3482
  browser.home = data.home || browser.home;
@@ -3446,7 +3485,7 @@ async function navigate(path) {
3446
3485
  browser.loading = false;
3447
3486
  renderBrowser();
3448
3487
  } catch (error) {
3449
- if (!browser) return;
3488
+ if (browser !== current || directoryRequest !== request) return;
3450
3489
  browser.loading = false;
3451
3490
  browser.error = error.message || String(error);
3452
3491
  renderBrowser();
@@ -3459,7 +3498,7 @@ async function navigate(path) {
3459
3498
  if (!parentCrumb) return;
3460
3499
  try {
3461
3500
  const data = await api(`/api/fs/list?path=${encodeURIComponent(parentCrumb.path)}`);
3462
- if (!browser || data.error) return;
3501
+ if (browser !== current || directoryRequest !== request || data.error) return;
3463
3502
  const sep = separatorOf();
3464
3503
  const fold = (value) => (sep === "\\" ? value.toLowerCase() : value);
3465
3504
  const match = (data.entries || []).find((entry) => fold(entry.path) === fold(target.path));
@@ -3475,6 +3514,10 @@ async function navigate(path) {
3475
3514
 
3476
3515
  async function selectEntry(entry) {
3477
3516
  if (!browser) return;
3517
+ const current = browser;
3518
+ const request = {};
3519
+ directoryRequest = request;
3520
+ browser.pathDraft = null;
3478
3521
  browser.selected = entry;
3479
3522
  browser.child = null;
3480
3523
  browser.loading = true;
@@ -3482,13 +3525,13 @@ async function selectEntry(entry) {
3482
3525
  renderBrowser();
3483
3526
  try {
3484
3527
  const data = await api(`/api/fs/list?path=${encodeURIComponent(entry.path)}`);
3485
- if (!browser) return;
3528
+ if (browser !== current || directoryRequest !== request) return;
3486
3529
  if (data.error) throw new Error(data.error);
3487
3530
  browser.child = data;
3488
3531
  browser.loading = false;
3489
3532
  renderBrowser();
3490
3533
  } catch (error) {
3491
- if (!browser) return;
3534
+ if (browser !== current || directoryRequest !== request) return;
3492
3535
  browser.loading = false;
3493
3536
  browser.error = error.message || String(error);
3494
3537
  browser.selected = null;
@@ -3513,6 +3556,8 @@ function openPathEditor() {
3513
3556
 
3514
3557
  function cancelPathEdit() {
3515
3558
  if (!browser) return;
3559
+ directoryRequest = null;
3560
+ browser.loading = false;
3516
3561
  browser.pathDraft = null;
3517
3562
  browser.error = null;
3518
3563
  renderBrowser();
@@ -3628,6 +3673,8 @@ function renderBrowser() {
3628
3673
  </div>
3629
3674
  </div>` : "";
3630
3675
 
3676
+ const previousPathInput = $("#dbPathInput");
3677
+ const pathSelection = previousPathInput ? [previousPathInput.selectionStart, previousPathInput.selectionEnd] : null;
3631
3678
  mount.innerHTML = `
3632
3679
  <div class="db-modal">
3633
3680
  <div class="db-modalMask" data-action="db-cancel"></div>
@@ -3654,10 +3701,9 @@ function renderBrowser() {
3654
3701
 
3655
3702
  const pathInput = $("#dbPathInput");
3656
3703
  if (pathInput) {
3657
- const start = pathInput.selectionStart;
3658
- const end = pathInput.selectionEnd;
3704
+ const [start, end] = pathSelection ?? [pathInput.selectionStart, pathInput.selectionEnd];
3659
3705
  pathInput.focus();
3660
- if (start != null && end != null && start === end) pathInput.setSelectionRange(start, end);
3706
+ if (start != null && end != null) pathInput.setSelectionRange(start, end);
3661
3707
  pathInput.addEventListener("keydown", (event) => {
3662
3708
  if (event.key === "Enter" && !event.isComposing) {
3663
3709
  event.preventDefault();
@@ -3672,7 +3718,7 @@ function renderBrowser() {
3672
3718
  pathInput.addEventListener("input", () => {
3673
3719
  const caret = pathInput.selectionStart ?? pathInput.value.length;
3674
3720
  b.pathDraft = pathInput.value;
3675
- renderBrowser();
3721
+ void previewPathDraft(b.pathDraft);
3676
3722
  const next = $("#dbPathInput");
3677
3723
  if (next) next.setSelectionRange(caret, caret);
3678
3724
  });