dsh-mobile 0.6.17 → 0.6.18
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/CHANGELOG.md +25 -10
- package/CONTRIBUTORS.md +11 -5
- package/README.en.md +16 -10
- package/README.md +15 -10
- package/SECURITY.md +1 -1
- package/cordis.patch.yml +5 -9
- package/docs/ATTACH_EXISTING_FRPS.en.md +32 -2
- package/docs/ATTACH_EXISTING_FRPS.md +2 -2
- package/docs/MOBILE_PLUGIN_ADAPTATION.en.md +188 -0
- package/docs/MOBILE_PLUGIN_ADAPTATION.md +188 -0
- package/docs/README.md +8 -0
- package/docs/SELF_HOSTED_ORIGIN.en.md +1 -1
- package/docs/SELF_HOSTED_ORIGIN.md +2 -2
- package/docs/SLOW_CONNECTIONS.en.md +2 -2
- package/docs/SLOW_CONNECTIONS.md +2 -2
- package/docs/TRUSTED_NETWORKS.en.md +17 -0
- package/docs/TRUSTED_NETWORKS.md +17 -0
- package/lib/cli.js.map +1 -1
- package/lib/client.js +420 -42
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +3 -0
- package/lib/index.mjs +115 -27
- package/lib/index.mjs.map +1 -1
- package/package.json +5 -1
- package/packages/question-fixes/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -8145,7 +8145,8 @@ window.__ModuleLoader__.load({
|
|
|
8145
8145
|
draft: "",
|
|
8146
8146
|
error: null,
|
|
8147
8147
|
hostBusy: false,
|
|
8148
|
-
committed: false
|
|
8148
|
+
committed: false,
|
|
8149
|
+
creating: false
|
|
8149
8150
|
});
|
|
8150
8151
|
/** Read navigation stops on close; committing is final until the owner withdraws this flow. */
|
|
8151
8152
|
var MobileDirectoryBrowser = class {
|
|
@@ -8199,6 +8200,14 @@ window.__ModuleLoader__.load({
|
|
|
8199
8200
|
hostBusy
|
|
8200
8201
|
});
|
|
8201
8202
|
}
|
|
8203
|
+
/** Block navigation/adoption while this flow's child-folder editor owns interaction. */
|
|
8204
|
+
setCreating(creating) {
|
|
8205
|
+
if (this.disposed || this.snapshot.creating === creating) return;
|
|
8206
|
+
this.publish({
|
|
8207
|
+
...this.snapshot,
|
|
8208
|
+
creating
|
|
8209
|
+
});
|
|
8210
|
+
}
|
|
8202
8211
|
/** Read a folder or the initial home; superseded results cannot publish into the new request. */
|
|
8203
8212
|
navigate(path) {
|
|
8204
8213
|
if (!this.canNavigate()) return false;
|
|
@@ -8281,7 +8290,7 @@ window.__ModuleLoader__.load({
|
|
|
8281
8290
|
}
|
|
8282
8291
|
/** Cancel reading immediately; a committed/Host-busy adoption cannot be reported as cancelled. */
|
|
8283
8292
|
cancel() {
|
|
8284
|
-
if (!this.
|
|
8293
|
+
if (!this.canClose()) return false;
|
|
8285
8294
|
this.withdrawRead();
|
|
8286
8295
|
this.publish(CLOSED);
|
|
8287
8296
|
return true;
|
|
@@ -8295,9 +8304,12 @@ window.__ModuleLoader__.load({
|
|
|
8295
8304
|
await Promise.allSettled([...this.tasks]);
|
|
8296
8305
|
if (this.taskFailures.length > 0) throw new AggregateError(this.taskFailures, "Mobile directory observers failed during a listing");
|
|
8297
8306
|
}
|
|
8298
|
-
|
|
8307
|
+
canClose() {
|
|
8299
8308
|
return !this.disposed && this.snapshot.open && !this.snapshot.hostBusy && !this.snapshot.committed;
|
|
8300
8309
|
}
|
|
8310
|
+
canNavigate() {
|
|
8311
|
+
return this.canClose() && !this.snapshot.creating;
|
|
8312
|
+
}
|
|
8301
8313
|
current(generation) {
|
|
8302
8314
|
return !this.disposed && this.snapshot.open && generation === this.generation;
|
|
8303
8315
|
}
|
|
@@ -8311,6 +8323,171 @@ window.__ModuleLoader__.load({
|
|
|
8311
8323
|
for (const listener of this.listeners) listener();
|
|
8312
8324
|
}
|
|
8313
8325
|
};
|
|
8326
|
+
/** A creation failure without raw response bodies or filesystem details. */
|
|
8327
|
+
var MobileDirectoryCreateError = class extends Error {
|
|
8328
|
+
code;
|
|
8329
|
+
constructor(code) {
|
|
8330
|
+
super(`Mobile directory creation failed: ${code}`);
|
|
8331
|
+
this.code = code;
|
|
8332
|
+
this.name = "MobileDirectoryCreateError";
|
|
8333
|
+
}
|
|
8334
|
+
};
|
|
8335
|
+
/** Portable single-segment validation; the Host additionally enforces its filesystem rules. */
|
|
8336
|
+
function isMobileDirectoryName(name) {
|
|
8337
|
+
return name.trim() !== "" && name.length <= 255 && name !== "." && name !== ".." && !/[\\/\0]/u.test(name);
|
|
8338
|
+
}
|
|
8339
|
+
const CREATE_CLOSED = Object.freeze({
|
|
8340
|
+
open: false,
|
|
8341
|
+
parent: null,
|
|
8342
|
+
name: "",
|
|
8343
|
+
pending: false,
|
|
8344
|
+
error: null
|
|
8345
|
+
});
|
|
8346
|
+
/** Owns one non-replayed write, its deadline, cancellation and physical teardown. */
|
|
8347
|
+
var MobileDirectoryCreator = class {
|
|
8348
|
+
create;
|
|
8349
|
+
onCreated;
|
|
8350
|
+
deadlineMs;
|
|
8351
|
+
snapshot = CREATE_CLOSED;
|
|
8352
|
+
listeners = /* @__PURE__ */ new Set();
|
|
8353
|
+
tasks = /* @__PURE__ */ new Set();
|
|
8354
|
+
failures = [];
|
|
8355
|
+
active;
|
|
8356
|
+
generation = 0;
|
|
8357
|
+
disposed = false;
|
|
8358
|
+
constructor(create, onCreated, deadlineMs = 3e4) {
|
|
8359
|
+
this.create = create;
|
|
8360
|
+
this.onCreated = onCreated;
|
|
8361
|
+
this.deadlineMs = deadlineMs;
|
|
8362
|
+
}
|
|
8363
|
+
getSnapshot = () => this.snapshot;
|
|
8364
|
+
/** Subscribe to this editor; the returned disposer withdraws the observer. */
|
|
8365
|
+
subscribe(listener) {
|
|
8366
|
+
this.listeners.add(listener);
|
|
8367
|
+
return () => {
|
|
8368
|
+
this.listeners.delete(listener);
|
|
8369
|
+
};
|
|
8370
|
+
}
|
|
8371
|
+
/** Open against one already confirmed parent; cancelled physical work retains admission. */
|
|
8372
|
+
open(parent) {
|
|
8373
|
+
if (this.disposed || this.snapshot.open || this.tasks.size > 0 || !isMobileDirectoryPath(parent)) return false;
|
|
8374
|
+
this.publish({
|
|
8375
|
+
...CREATE_CLOSED,
|
|
8376
|
+
open: true,
|
|
8377
|
+
parent
|
|
8378
|
+
});
|
|
8379
|
+
return true;
|
|
8380
|
+
}
|
|
8381
|
+
/** Edit only while no write is in flight; typing never starts a filesystem operation. */
|
|
8382
|
+
setName(name) {
|
|
8383
|
+
if (this.disposed || !this.snapshot.open || this.snapshot.pending) return;
|
|
8384
|
+
this.publish({
|
|
8385
|
+
...this.snapshot,
|
|
8386
|
+
name,
|
|
8387
|
+
error: null
|
|
8388
|
+
});
|
|
8389
|
+
}
|
|
8390
|
+
/** Admit one explicit creation. Repeated clicks/Enter cannot replay an admitted request. */
|
|
8391
|
+
submit() {
|
|
8392
|
+
if (this.disposed || !this.snapshot.open || this.snapshot.pending || this.tasks.size > 0 || this.snapshot.parent === null) return false;
|
|
8393
|
+
const name = this.snapshot.name.trim();
|
|
8394
|
+
if (!isMobileDirectoryName(name)) {
|
|
8395
|
+
this.publish({
|
|
8396
|
+
...this.snapshot,
|
|
8397
|
+
error: "invalid-name"
|
|
8398
|
+
});
|
|
8399
|
+
return false;
|
|
8400
|
+
}
|
|
8401
|
+
const parent = this.snapshot.parent;
|
|
8402
|
+
const generation = this.generation;
|
|
8403
|
+
const controller = new AbortController();
|
|
8404
|
+
this.active = controller;
|
|
8405
|
+
this.publish({
|
|
8406
|
+
...this.snapshot,
|
|
8407
|
+
pending: true,
|
|
8408
|
+
error: null
|
|
8409
|
+
});
|
|
8410
|
+
if (!this.current(generation) || controller.signal.aborted) {
|
|
8411
|
+
if (this.active === controller) this.active = void 0;
|
|
8412
|
+
if (!this.disposed) this.publish({
|
|
8413
|
+
...this.snapshot,
|
|
8414
|
+
pending: false
|
|
8415
|
+
});
|
|
8416
|
+
return false;
|
|
8417
|
+
}
|
|
8418
|
+
const timer = setTimeout(() => {
|
|
8419
|
+
controller.abort(new MobileDirectoryCreateError("timeout"));
|
|
8420
|
+
if (this.current(generation)) this.publish({
|
|
8421
|
+
...this.snapshot,
|
|
8422
|
+
error: "timeout"
|
|
8423
|
+
});
|
|
8424
|
+
}, this.deadlineMs);
|
|
8425
|
+
const task = (async () => {
|
|
8426
|
+
let created;
|
|
8427
|
+
try {
|
|
8428
|
+
created = await this.create(parent, name, controller.signal);
|
|
8429
|
+
controller.signal.throwIfAborted();
|
|
8430
|
+
if (!isMobileDirectoryPath(created)) throw new MobileDirectoryCreateError("invalid-response");
|
|
8431
|
+
} catch (error) {
|
|
8432
|
+
created = void 0;
|
|
8433
|
+
if (this.current(generation)) {
|
|
8434
|
+
const failure = controller.signal.aborted ? controller.signal.reason : error;
|
|
8435
|
+
this.publish({
|
|
8436
|
+
...this.snapshot,
|
|
8437
|
+
error: failure instanceof MobileDirectoryCreateError ? failure.code : "request-failed"
|
|
8438
|
+
});
|
|
8439
|
+
}
|
|
8440
|
+
} finally {
|
|
8441
|
+
clearTimeout(timer);
|
|
8442
|
+
if (this.active === controller) this.active = void 0;
|
|
8443
|
+
if (!this.disposed) this.publish({
|
|
8444
|
+
...this.snapshot,
|
|
8445
|
+
pending: false
|
|
8446
|
+
});
|
|
8447
|
+
}
|
|
8448
|
+
if (created !== void 0 && this.current(generation)) {
|
|
8449
|
+
this.publish(CREATE_CLOSED);
|
|
8450
|
+
this.onCreated(created);
|
|
8451
|
+
}
|
|
8452
|
+
})();
|
|
8453
|
+
this.tasks.add(task);
|
|
8454
|
+
task.then(() => {
|
|
8455
|
+
this.tasks.delete(task);
|
|
8456
|
+
}, (error) => {
|
|
8457
|
+
this.tasks.delete(task);
|
|
8458
|
+
this.failures.push(error);
|
|
8459
|
+
});
|
|
8460
|
+
return true;
|
|
8461
|
+
}
|
|
8462
|
+
/** Withdraw the editor and late results; an already completed mkdir is not rolled back. */
|
|
8463
|
+
close() {
|
|
8464
|
+
if (this.disposed || !this.snapshot.open) return false;
|
|
8465
|
+
this.generation++;
|
|
8466
|
+
this.active?.abort();
|
|
8467
|
+
this.publish({
|
|
8468
|
+
...CREATE_CLOSED,
|
|
8469
|
+
pending: this.active !== void 0
|
|
8470
|
+
});
|
|
8471
|
+
return true;
|
|
8472
|
+
}
|
|
8473
|
+
/** Stop admission synchronously and await every accepted write before completion. */
|
|
8474
|
+
async dispose() {
|
|
8475
|
+
this.disposed = true;
|
|
8476
|
+
this.generation++;
|
|
8477
|
+
this.active?.abort();
|
|
8478
|
+
this.snapshot = CREATE_CLOSED;
|
|
8479
|
+
this.listeners.clear();
|
|
8480
|
+
await Promise.allSettled([...this.tasks]);
|
|
8481
|
+
if (this.failures.length > 0) throw new AggregateError(this.failures, "Mobile directory creation observers failed");
|
|
8482
|
+
}
|
|
8483
|
+
current(generation) {
|
|
8484
|
+
return !this.disposed && this.snapshot.open && generation === this.generation;
|
|
8485
|
+
}
|
|
8486
|
+
publish(snapshot) {
|
|
8487
|
+
this.snapshot = Object.freeze(snapshot);
|
|
8488
|
+
for (const listener of this.listeners) listener();
|
|
8489
|
+
}
|
|
8490
|
+
};
|
|
8314
8491
|
//#endregion
|
|
8315
8492
|
//#region src/mobile-directory-picker.ts
|
|
8316
8493
|
/** Dedicated Mobile directory-flow occupant; stock DSH retains its own chooser. */
|
|
@@ -8335,7 +8512,17 @@ window.__ModuleLoader__.load({
|
|
|
8335
8512
|
"invalid-response": "目录响应不可用,请重试或重新连接电脑",
|
|
8336
8513
|
"request-failed": "读取目录失败,请检查连接后重试",
|
|
8337
8514
|
timeout: "读取目录超时,请重试或取消",
|
|
8338
|
-
failedOpen: "无法打开目录选择器,请重新连接后重试"
|
|
8515
|
+
failedOpen: "无法打开目录选择器,请重新连接后重试",
|
|
8516
|
+
create: "新建文件夹",
|
|
8517
|
+
createTitle: "新建文件夹名称",
|
|
8518
|
+
createAction: "创建",
|
|
8519
|
+
createInvalid: "请输入单个文件夹名称,不能包含路径分隔符。",
|
|
8520
|
+
createExists: "同名文件夹已存在。",
|
|
8521
|
+
createFailed: "无法创建文件夹,请检查该目录的写入权限后重试。",
|
|
8522
|
+
createParent: "创建位置",
|
|
8523
|
+
creating: "正在创建文件夹…",
|
|
8524
|
+
createStopping: "正在结束创建请求…",
|
|
8525
|
+
createUnknown: "无法确认创建结果,文件夹可能已创建。请返回并重新读取目录,确认后再决定是否重试。"
|
|
8339
8526
|
},
|
|
8340
8527
|
en: {
|
|
8341
8528
|
title: "Select workspace directory",
|
|
@@ -8357,7 +8544,17 @@ window.__ModuleLoader__.load({
|
|
|
8357
8544
|
"invalid-response": "The directory response is unavailable. Retry or reconnect to the computer.",
|
|
8358
8545
|
"request-failed": "Could not load this directory. Check the connection and retry.",
|
|
8359
8546
|
timeout: "Loading timed out. Retry or cancel.",
|
|
8360
|
-
failedOpen: "Could not open the directory picker. Reconnect and retry."
|
|
8547
|
+
failedOpen: "Could not open the directory picker. Reconnect and retry.",
|
|
8548
|
+
create: "New folder",
|
|
8549
|
+
createTitle: "New folder name",
|
|
8550
|
+
createAction: "Create",
|
|
8551
|
+
createInvalid: "Enter a single folder name without path separators.",
|
|
8552
|
+
createExists: "A folder with that name already exists.",
|
|
8553
|
+
createFailed: "Could not create the folder. Check write access to this directory and retry.",
|
|
8554
|
+
createParent: "Create in",
|
|
8555
|
+
creating: "Creating folder…",
|
|
8556
|
+
createStopping: "Finishing the creation request…",
|
|
8557
|
+
createUnknown: "The creation result is unknown; the folder may already exist. Go back and reload the directory before deciding whether to retry."
|
|
8361
8558
|
},
|
|
8362
8559
|
it: {
|
|
8363
8560
|
title: "Seleziona la cartella del workspace",
|
|
@@ -8378,11 +8575,21 @@ window.__ModuleLoader__.load({
|
|
|
8378
8575
|
unauthorized: "Autorizzazione non disponibile. Chiudi questa finestra e riconnettiti al computer.",
|
|
8379
8576
|
"invalid-response": "Risposta della cartella non disponibile. Riprova o riconnettiti al computer.",
|
|
8380
8577
|
"request-failed": "Impossibile caricare la cartella. Controlla la connessione e riprova.",
|
|
8578
|
+
create: "Nuova cartella",
|
|
8579
|
+
createTitle: "Nome della nuova cartella",
|
|
8580
|
+
createAction: "Crea",
|
|
8581
|
+
createInvalid: "Inserisci un singolo nome di cartella senza separatori di percorso.",
|
|
8582
|
+
createExists: "Esiste già una cartella con questo nome.",
|
|
8583
|
+
createFailed: "Impossibile creare la cartella. Controlla i permessi di scrittura e riprova.",
|
|
8381
8584
|
timeout: "Caricamento scaduto. Riprova o annulla.",
|
|
8382
|
-
failedOpen: "Impossibile aprire la selezione della cartella. Riconnettiti e riprova."
|
|
8585
|
+
failedOpen: "Impossibile aprire la selezione della cartella. Riconnettiti e riprova.",
|
|
8586
|
+
createParent: "Crea in",
|
|
8587
|
+
creating: "Creazione della cartella…",
|
|
8588
|
+
createStopping: "Chiusura della richiesta di creazione…",
|
|
8589
|
+
createUnknown: "Esito della creazione sconosciuto: la cartella potrebbe già esistere. Torna indietro e ricarica la directory prima di riprovare."
|
|
8383
8590
|
}
|
|
8384
8591
|
};
|
|
8385
|
-
/** Authenticated
|
|
8592
|
+
/** Authenticated directory browsing/creation route; no native picker or Host service is replaced. */
|
|
8386
8593
|
const WORKSPACE_DIRECTORIES_PATH = "/mobile-access/workspace-directories";
|
|
8387
8594
|
/** Ask only this plugin's live directory dialog to handle a routed native Back. */
|
|
8388
8595
|
function dismissMobileDirectoryPicker(dialog) {
|
|
@@ -8399,6 +8606,32 @@ window.__ModuleLoader__.load({
|
|
|
8399
8606
|
}
|
|
8400
8607
|
return "en";
|
|
8401
8608
|
}
|
|
8609
|
+
/** Create one child directory through the same authenticated route that lists them. */
|
|
8610
|
+
async function createDirectory(path, name, signal) {
|
|
8611
|
+
const response = await fetch(WORKSPACE_DIRECTORIES_PATH, {
|
|
8612
|
+
method: "POST",
|
|
8613
|
+
credentials: "same-origin",
|
|
8614
|
+
cache: "no-store",
|
|
8615
|
+
headers: { "content-type": "application/json" },
|
|
8616
|
+
body: JSON.stringify({
|
|
8617
|
+
path,
|
|
8618
|
+
name
|
|
8619
|
+
}),
|
|
8620
|
+
signal
|
|
8621
|
+
});
|
|
8622
|
+
if (!response.ok) {
|
|
8623
|
+
const code = response.status === 409 ? "directory-exists" : response.status === 400 ? "directory-create-failed" : response.status === 401 || response.status === 403 ? "unauthorized" : "directory-create-failed";
|
|
8624
|
+
try {
|
|
8625
|
+
await response.body?.cancel();
|
|
8626
|
+
} catch (error) {
|
|
8627
|
+
throw new MobileDirectoryCreateError(code);
|
|
8628
|
+
}
|
|
8629
|
+
throw new MobileDirectoryCreateError(code);
|
|
8630
|
+
}
|
|
8631
|
+
const payload = await response.json();
|
|
8632
|
+
if (typeof payload?.path !== "string" || !isMobileDirectoryPath(payload.path)) throw new MobileDirectoryCreateError("invalid-response");
|
|
8633
|
+
return payload.path;
|
|
8634
|
+
}
|
|
8402
8635
|
async function readDirectories(path, signal) {
|
|
8403
8636
|
const query = path === null ? "" : `?path=${encodeURIComponent(path)}`;
|
|
8404
8637
|
const response = await fetch(`${WORKSPACE_DIRECTORIES_PATH}${query}`, {
|
|
@@ -8424,25 +8657,51 @@ window.__ModuleLoader__.load({
|
|
|
8424
8657
|
const STYLES = `
|
|
8425
8658
|
dialog.dshm-picker{position:fixed;inset:0;box-sizing:border-box;width:100%;max-width:none;height:100%;max-height:none;margin:0;padding:max(16px,env(safe-area-inset-top)) max(16px,env(safe-area-inset-right)) max(16px,env(safe-area-inset-bottom)) max(16px,env(safe-area-inset-left));border:0;background:transparent;color:var(--dsw-alias-label-primary,#16181d);font:400 14px/1.5 system-ui;overflow:hidden}
|
|
8426
8659
|
dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::backdrop{background:rgb(0 0 0 / 42%)}
|
|
8427
|
-
.dshm-picker__panel{display:flex;flex-direction:column;box-sizing:border-box;width:min(680px,100%);height:min(
|
|
8428
|
-
.dshm-picker__header{display:flex;align-items:center;flex:none;gap:
|
|
8429
|
-
.dshm-picker__title{flex:1;min-width:0;margin:0;font-size:16px;font-weight:
|
|
8430
|
-
.dshm-picker__toolbar{display:flex;flex-wrap:
|
|
8431
|
-
.dshm-picker__path{
|
|
8660
|
+
.dshm-picker__panel{display:flex;flex-direction:column;box-sizing:border-box;width:min(680px,100%);height:min(720px,100%);min-height:0;overflow:hidden;border:0;border-radius:20px;box-shadow:0 0 0 .5px rgba(0 0 0 / 16%),0 3px 8px rgba(0 0 0 / 4%);background:var(--dsw-alias-bg-layer-1,#fff)}
|
|
8661
|
+
.dshm-picker__header{display:flex;align-items:center;flex:none;gap:8px;padding:4px 8px 4px 12px;border-bottom:1px solid var(--dsw-alias-border-l2,#dde0e4)}
|
|
8662
|
+
.dshm-picker__title{flex:1;min-width:0;margin:0;font-size:16px;font-weight:510;line-height:24px;overflow-wrap:anywhere}
|
|
8663
|
+
.dshm-picker__toolbar{display:flex;align-items:center;flex-wrap:nowrap;gap:8px;flex:none;padding:8px 10px}.dshm-picker__path-label{flex:1;min-width:0;display:flex;color:var(--dsw-alias-label-secondary,#606873)}
|
|
8664
|
+
.dshm-picker__path-caption{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
|
|
8665
|
+
.dshm-picker__path{box-sizing:border-box;width:100%;min-width:0;min-height:44px;padding:6px 8px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:8px;background:var(--dsw-alias-bg-layer-2,#f7f8fa);color:var(--dsw-alias-label-primary,#16181d);font:400 16px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
8432
8666
|
.dshm-picker__path[aria-invalid=true]{border-color:var(--dsw-alias-state-error-primary,#b42318)}.dshm-picker__go{align-self:flex-end}
|
|
8433
|
-
.dshm-picker__navigation{display:flex;flex:none;gap:8px;padding:0
|
|
8434
|
-
.dshm-picker__list{flex:1;min-height:0;margin:0;padding:4px
|
|
8435
|
-
.dshm-picker__row{display:flex;align-items:center;gap:
|
|
8667
|
+
.dshm-picker__navigation{display:flex;flex:none;gap:8px;padding:0}
|
|
8668
|
+
.dshm-picker__list{flex:1;min-height:0;margin:0;padding:0 10px 4px;overflow:auto;overscroll-behavior:contain;list-style:none;scroll-padding:4px}
|
|
8669
|
+
.dshm-picker__row{display:flex;align-items:center;gap:8px;box-sizing:border-box;width:100%;min-height:44px;padding:6px 8px;border:0;border-radius:8px;background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer;touch-action:manipulation}
|
|
8436
8670
|
.dshm-picker__folder{flex:none;width:20px;height:20px;color:var(--dsw-alias-label-secondary,#606873)}
|
|
8437
8671
|
.dshm-picker__name{flex:1;min-width:0;overflow-wrap:anywhere}.dshm-picker__row:hover:not(:disabled),.dshm-picker__button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#eef1f5)}
|
|
8438
8672
|
.dshm-picker__row:active:not(:disabled),.dshm-picker__button:active:not(:disabled){background:var(--dsw-alias-interactive-bg-active,#e4e8ee)}
|
|
8439
|
-
.dshm-picker__button{flex:none;box-sizing:border-box;min-width:
|
|
8673
|
+
.dshm-picker__button{flex:none;box-sizing:border-box;min-width:44px;min-height:44px;padding:6px 10px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:8px;background:transparent;color:inherit;font:500 14px/1.4 system-ui;cursor:pointer;touch-action:manipulation}
|
|
8674
|
+
.dshm-picker__icon{display:inline-flex;align-items:center;justify-content:center;width:44px;padding:0;border-color:transparent}.dshm-picker__icon>svg{display:block;width:18px;height:18px}
|
|
8440
8675
|
.dshm-picker :is(button,input):focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#2563eb);outline-offset:2px}.dshm-picker button:disabled{opacity:.5;cursor:default}
|
|
8441
|
-
.dshm-
|
|
8442
|
-
.dshm-
|
|
8443
|
-
.dshm-
|
|
8444
|
-
|
|
8676
|
+
.dshm-picker [hidden]{display:none!important}.dshm-picker__create{flex:1;min-height:0;overflow:auto;display:flex;flex-direction:column;gap:8px;padding:12px 16px}
|
|
8677
|
+
.dshm-picker__create-title{margin:0;font-size:13px;color:var(--dsw-alias-label-secondary,#606873)}
|
|
8678
|
+
.dshm-picker__create-input{box-sizing:border-box;width:100%;min-width:0;min-height:48px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l3,#cfd5dd);border-radius:10px;background:var(--dsw-alias-bg-layer-2,#f7f8fa);color:var(--dsw-alias-label-primary,#0f1115);font:inherit;font-size:16px;line-height:1.4}
|
|
8679
|
+
.dshm-picker__create-actions{display:flex;justify-content:flex-end;gap:8px}.dshm-picker__create-error{color:var(--dsw-alias-state-error-primary,#b42318)}
|
|
8680
|
+
.dshm-picker__create-trigger{margin-right:auto;border-color:transparent;padding-left:8px;padding-right:8px}
|
|
8681
|
+
.dshm-picker__status{flex:none;margin:0;padding:4px 10px;font-size:13px;color:var(--dsw-alias-label-secondary,#606873);overflow-wrap:anywhere}.dshm-picker__error{color:var(--dsw-alias-state-error-primary,#b42318)}
|
|
8682
|
+
.dshm-picker__status[hidden],.dshm-picker__button[hidden]{display:none}.dshm-picker__footer{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;flex:none;gap:8px;padding:6px 10px;border-top:1px solid var(--dsw-alias-border-l2,#dde0e4)}
|
|
8683
|
+
.dshm-picker__confirm{min-width:72px;border-color:transparent;background:var(--dsw-alias-button-primary-fill,#2563eb);color:var(--dsw-alias-label-primary-foreground,#fff)}.dshm-picker__confirm:hover:not(:disabled),.dshm-picker__confirm:active:not(:disabled){background:var(--dsw-alias-button-primary-hover,#1d4ed8)}
|
|
8684
|
+
@media(max-width:420px){dialog.dshm-picker{padding:max(8px,env(safe-area-inset-top)) max(8px,env(safe-area-inset-right)) max(8px,env(safe-area-inset-bottom)) max(8px,env(safe-area-inset-left))}}
|
|
8445
8685
|
`;
|
|
8686
|
+
/**
|
|
8687
|
+
* Whether DSH's own browse directory picker is already mounted on this page.
|
|
8688
|
+
*
|
|
8689
|
+
* DSH's adaptive `directory-picker` resolves to
|
|
8690
|
+
* `@deepseek-ai/dsh-host-directory-picker-auto`, which mounts the Host backend
|
|
8691
|
+
* and the Client face together and picks `browse` whenever the host is remotely
|
|
8692
|
+
* reachable. Its client face fills the same directory-flow slots this plugin
|
|
8693
|
+
* would use, so a plugin-owned picker would shadow the upstream one and take
|
|
8694
|
+
* away its folder creation. The desktop keeps the native OS chooser; a remote
|
|
8695
|
+
* browser can only reach the web implementation.
|
|
8696
|
+
*
|
|
8697
|
+
* @returns true when the upstream browse surface owns those slots on this page.
|
|
8698
|
+
*/
|
|
8699
|
+
function hasUpstreamBrowsePicker(view) {
|
|
8700
|
+
if (!("__DSH_BOOT__" in view)) return false;
|
|
8701
|
+
const graph = view.__DSH_BOOT__;
|
|
8702
|
+
if (typeof graph !== "object" || graph === null || !("entries" in graph) || !Array.isArray(graph.entries)) return false;
|
|
8703
|
+
return graph.entries.some((entry) => typeof entry === "object" && entry !== null && "id" in entry && entry.id === "@deepseek-ai/dsh-client-ui-directory-picker-browse");
|
|
8704
|
+
}
|
|
8446
8705
|
function openDirectoryDialog(doc, getProps, trackDisposal) {
|
|
8447
8706
|
const view = doc.defaultView;
|
|
8448
8707
|
if (view === null) throw new Error("Directory picker requires a browser window");
|
|
@@ -8468,18 +8727,40 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8468
8727
|
if (name !== void 0) value.setAttribute(`data-dshm-picker-${name}`, "");
|
|
8469
8728
|
return value;
|
|
8470
8729
|
};
|
|
8730
|
+
const iconButton = (label, outline, name) => {
|
|
8731
|
+
const value = button("", name);
|
|
8732
|
+
value.classList.add("dshm-picker__icon");
|
|
8733
|
+
value.setAttribute("aria-label", label);
|
|
8734
|
+
value.title = label;
|
|
8735
|
+
const icon = doc.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
8736
|
+
icon.setAttribute("viewBox", "0 0 24 24");
|
|
8737
|
+
icon.setAttribute("fill", "none");
|
|
8738
|
+
icon.setAttribute("stroke", "currentColor");
|
|
8739
|
+
icon.setAttribute("stroke-width", "1.75");
|
|
8740
|
+
icon.setAttribute("stroke-linecap", "round");
|
|
8741
|
+
icon.setAttribute("stroke-linejoin", "round");
|
|
8742
|
+
icon.setAttribute("aria-hidden", "true");
|
|
8743
|
+
const shape = doc.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
8744
|
+
shape.setAttribute("d", outline);
|
|
8745
|
+
icon.append(shape);
|
|
8746
|
+
value.append(icon);
|
|
8747
|
+
return value;
|
|
8748
|
+
};
|
|
8471
8749
|
const header = doc.createElement("header");
|
|
8472
8750
|
header.className = "dshm-picker__header";
|
|
8473
8751
|
const title = doc.createElement("h2");
|
|
8474
8752
|
title.className = "dshm-picker__title";
|
|
8475
8753
|
title.textContent = copy.title;
|
|
8476
|
-
const close =
|
|
8754
|
+
const close = iconButton(copy.close, "M6 6l12 12M6 18L18 6");
|
|
8477
8755
|
header.append(title, close);
|
|
8478
8756
|
const toolbar = doc.createElement("form");
|
|
8479
8757
|
toolbar.className = "dshm-picker__toolbar";
|
|
8480
8758
|
const pathLabel = doc.createElement("label");
|
|
8481
8759
|
pathLabel.className = "dshm-picker__path-label";
|
|
8482
|
-
|
|
8760
|
+
const pathCaption = doc.createElement("span");
|
|
8761
|
+
pathCaption.className = "dshm-picker__path-caption";
|
|
8762
|
+
pathCaption.textContent = copy.path;
|
|
8763
|
+
pathLabel.append(pathCaption);
|
|
8483
8764
|
const path = doc.createElement("input");
|
|
8484
8765
|
path.type = "text";
|
|
8485
8766
|
path.className = "dshm-picker__path";
|
|
@@ -8488,16 +8769,17 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8488
8769
|
path.autocomplete = "off";
|
|
8489
8770
|
path.maxLength = 8192;
|
|
8490
8771
|
pathLabel.append(path);
|
|
8491
|
-
const go =
|
|
8772
|
+
const go = iconButton(copy.go, "M5 12h14m-6-6 6 6-6 6", "load");
|
|
8492
8773
|
go.type = "submit";
|
|
8493
8774
|
go.classList.add("dshm-picker__go");
|
|
8494
8775
|
toolbar.append(pathLabel, go);
|
|
8495
8776
|
const navigation = doc.createElement("nav");
|
|
8496
8777
|
navigation.className = "dshm-picker__navigation";
|
|
8497
8778
|
navigation.setAttribute("aria-label", copy.title);
|
|
8498
|
-
const home =
|
|
8499
|
-
const up =
|
|
8779
|
+
const home = iconButton(copy.home, "m3 10 9-7 9 7v10a1 1 0 0 1-1 1h-5v-7H9v7H4a1 1 0 0 1-1-1Z", "home");
|
|
8780
|
+
const up = iconButton(copy.up, "M12 20V4m-7 7 7-7 7 7", "up");
|
|
8500
8781
|
navigation.append(home, up);
|
|
8782
|
+
toolbar.prepend(navigation);
|
|
8501
8783
|
const status = doc.createElement("p");
|
|
8502
8784
|
status.className = "dshm-picker__status";
|
|
8503
8785
|
status.setAttribute("role", "status");
|
|
@@ -8513,13 +8795,82 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8513
8795
|
footer.className = "dshm-picker__footer";
|
|
8514
8796
|
const retry = button(copy.retry, "retry");
|
|
8515
8797
|
retry.hidden = true;
|
|
8798
|
+
const create = button(copy.create, "create");
|
|
8799
|
+
create.classList.add("dshm-picker__create-trigger");
|
|
8516
8800
|
const cancel = button(copy.cancel, "cancel");
|
|
8517
8801
|
const confirm = button(copy.open, "confirm");
|
|
8518
8802
|
confirm.classList.add("dshm-picker__confirm");
|
|
8519
|
-
|
|
8520
|
-
|
|
8803
|
+
const createPanel = doc.createElement("div");
|
|
8804
|
+
createPanel.className = "dshm-picker__create";
|
|
8805
|
+
createPanel.hidden = true;
|
|
8806
|
+
const createTitle = doc.createElement("label");
|
|
8807
|
+
createTitle.className = "dshm-picker__create-title";
|
|
8808
|
+
createTitle.textContent = copy.createTitle;
|
|
8809
|
+
const createInput = doc.createElement("input");
|
|
8810
|
+
createInput.type = "text";
|
|
8811
|
+
createInput.className = "dshm-picker__create-input";
|
|
8812
|
+
createInput.setAttribute("data-dshm-picker-create-name", "");
|
|
8813
|
+
createInput.spellcheck = false;
|
|
8814
|
+
createInput.autocomplete = "off";
|
|
8815
|
+
createInput.maxLength = 255;
|
|
8816
|
+
createTitle.append(createInput);
|
|
8817
|
+
const createParent = doc.createElement("p");
|
|
8818
|
+
createParent.className = "dshm-picker__status";
|
|
8819
|
+
const createStatus = doc.createElement("p");
|
|
8820
|
+
createStatus.className = "dshm-picker__status";
|
|
8821
|
+
createStatus.setAttribute("role", "status");
|
|
8822
|
+
createStatus.setAttribute("aria-live", "polite");
|
|
8823
|
+
const createError = doc.createElement("p");
|
|
8824
|
+
createError.className = "dshm-picker__status dshm-picker__create-error";
|
|
8825
|
+
createError.setAttribute("role", "alert");
|
|
8826
|
+
createError.setAttribute("data-dshm-picker-create-error", "");
|
|
8827
|
+
createError.hidden = true;
|
|
8828
|
+
const createActions = doc.createElement("div");
|
|
8829
|
+
createActions.className = "dshm-picker__create-actions";
|
|
8830
|
+
const createCancel = button(copy.cancel, "create-cancel");
|
|
8831
|
+
const createConfirm = button(copy.createAction, "create-confirm");
|
|
8832
|
+
createActions.append(createCancel, createConfirm);
|
|
8833
|
+
createPanel.append(createParent, createTitle, createError, createStatus, createActions);
|
|
8834
|
+
footer.append(create, retry, cancel, confirm);
|
|
8835
|
+
panel.append(header, toolbar, status, error, list, createPanel, footer);
|
|
8521
8836
|
dialog.append(panel);
|
|
8522
8837
|
const browser = new MobileDirectoryBrowser(readDirectories);
|
|
8838
|
+
const creator = new MobileDirectoryCreator(createDirectory, (created) => {
|
|
8839
|
+
browser.navigate(created);
|
|
8840
|
+
});
|
|
8841
|
+
let creationNotice = false;
|
|
8842
|
+
let creationNoticeListing = null;
|
|
8843
|
+
const closeCreate = () => {
|
|
8844
|
+
const child = creator.getSnapshot();
|
|
8845
|
+
if (child.pending || child.error === "timeout" || child.error === "request-failed" || child.error === "invalid-response") {
|
|
8846
|
+
creationNotice = true;
|
|
8847
|
+
creationNoticeListing = browser.getSnapshot().listing;
|
|
8848
|
+
}
|
|
8849
|
+
if (creator.close()) {
|
|
8850
|
+
if (create.disabled) close.focus();
|
|
8851
|
+
else create.focus();
|
|
8852
|
+
}
|
|
8853
|
+
};
|
|
8854
|
+
create.addEventListener("click", () => {
|
|
8855
|
+
const parent = browser.getSnapshot().listing?.path;
|
|
8856
|
+
if (!browser.canConfirm() || parent === void 0 || !creator.open(parent)) return;
|
|
8857
|
+
creationNotice = false;
|
|
8858
|
+
createInput.focus();
|
|
8859
|
+
createInput.select();
|
|
8860
|
+
});
|
|
8861
|
+
createCancel.addEventListener("click", closeCreate);
|
|
8862
|
+
createConfirm.addEventListener("click", () => {
|
|
8863
|
+
creator.submit();
|
|
8864
|
+
});
|
|
8865
|
+
createInput.addEventListener("input", () => {
|
|
8866
|
+
creator.setName(createInput.value);
|
|
8867
|
+
});
|
|
8868
|
+
createInput.addEventListener("keydown", (event) => {
|
|
8869
|
+
if (event.key === "Enter" && !event.isComposing) {
|
|
8870
|
+
event.preventDefault();
|
|
8871
|
+
creator.submit();
|
|
8872
|
+
}
|
|
8873
|
+
});
|
|
8523
8874
|
let disposed = false;
|
|
8524
8875
|
let closedByOwner = false;
|
|
8525
8876
|
let renderedListing;
|
|
@@ -8527,22 +8878,41 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8527
8878
|
const render = () => {
|
|
8528
8879
|
if (disposed) return;
|
|
8529
8880
|
const state = browser.getSnapshot();
|
|
8881
|
+
if (creationNotice && state.listing !== creationNoticeListing) creationNotice = false;
|
|
8882
|
+
const child = creator.getSnapshot();
|
|
8530
8883
|
const adopting = state.committed || state.hostBusy;
|
|
8531
|
-
|
|
8884
|
+
const blocked = adopting || state.creating;
|
|
8885
|
+
path.disabled = blocked;
|
|
8532
8886
|
if (path.value !== state.draft) path.value = state.draft;
|
|
8533
8887
|
path.setAttribute("aria-invalid", String(state.error === "bad-path"));
|
|
8534
|
-
go.disabled =
|
|
8535
|
-
home.disabled =
|
|
8536
|
-
up.disabled =
|
|
8888
|
+
go.disabled = blocked || state.phase === "loading" || !isMobileDirectoryPath(state.draft);
|
|
8889
|
+
home.disabled = blocked;
|
|
8890
|
+
up.disabled = blocked || state.listing?.parent === void 0;
|
|
8537
8891
|
confirm.disabled = !browser.canConfirm();
|
|
8538
8892
|
cancel.disabled = adopting;
|
|
8539
8893
|
close.disabled = adopting;
|
|
8540
8894
|
retry.hidden = state.phase !== "error" || state.error === "bad-path";
|
|
8541
|
-
retry.disabled =
|
|
8542
|
-
|
|
8543
|
-
|
|
8895
|
+
retry.disabled = blocked;
|
|
8896
|
+
create.disabled = !browser.canConfirm();
|
|
8897
|
+
title.textContent = child.open ? copy.create : copy.title;
|
|
8898
|
+
toolbar.hidden = child.open;
|
|
8899
|
+
navigation.hidden = child.open;
|
|
8900
|
+
list.hidden = child.open;
|
|
8901
|
+
footer.hidden = child.open;
|
|
8902
|
+
createPanel.hidden = !child.open;
|
|
8903
|
+
if (createInput.value !== child.name) createInput.value = child.name;
|
|
8904
|
+
createInput.disabled = child.pending;
|
|
8905
|
+
createInput.setAttribute("aria-invalid", String(child.error === "invalid-name"));
|
|
8906
|
+
createConfirm.disabled = child.pending || !isMobileDirectoryName(child.name.trim());
|
|
8907
|
+
createParent.textContent = `${copy.createParent}: ${child.parent ?? ""}`;
|
|
8908
|
+
createError.textContent = child.error === null ? "" : child.error === "invalid-name" ? copy.createInvalid : child.error === "directory-exists" ? copy.createExists : child.error === "directory-create-failed" ? copy.createFailed : child.error === "unauthorized" ? copy.unauthorized : copy.createUnknown;
|
|
8909
|
+
createError.hidden = child.error === null;
|
|
8910
|
+
createStatus.textContent = child.pending && child.error === null ? copy.creating : "";
|
|
8911
|
+
createStatus.hidden = createStatus.textContent === "";
|
|
8912
|
+
status.textContent = child.pending ? copy.createStopping : creationNotice ? copy.createUnknown : adopting ? copy.adopting : state.phase === "loading" ? copy.loading : state.listing?.truncated === true ? copy.truncated : state.listing?.entries.length === 0 ? copy.empty : "";
|
|
8913
|
+
status.hidden = child.open || status.textContent === "";
|
|
8544
8914
|
error.textContent = state.error === null ? "" : copy[state.error];
|
|
8545
|
-
error.hidden = state.error === null;
|
|
8915
|
+
error.hidden = child.open || state.error === null;
|
|
8546
8916
|
list.setAttribute("aria-busy", String(state.phase === "loading"));
|
|
8547
8917
|
if (renderedListing !== state.listing) {
|
|
8548
8918
|
renderedListing = state.listing;
|
|
@@ -8572,11 +8942,21 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8572
8942
|
list.append(item);
|
|
8573
8943
|
}
|
|
8574
8944
|
}
|
|
8575
|
-
for (const row of list.querySelectorAll("button")) row.disabled =
|
|
8945
|
+
for (const row of list.querySelectorAll("button")) row.disabled = blocked;
|
|
8576
8946
|
};
|
|
8577
8947
|
const unsubscribe = browser.subscribe(render);
|
|
8948
|
+
const unsubscribeCreate = creator.subscribe(() => {
|
|
8949
|
+
const child = creator.getSnapshot();
|
|
8950
|
+
browser.setCreating(child.open || child.pending);
|
|
8951
|
+
render();
|
|
8952
|
+
});
|
|
8578
8953
|
const requestClose = () => {
|
|
8954
|
+
if (creator.getSnapshot().open) {
|
|
8955
|
+
closeCreate();
|
|
8956
|
+
return;
|
|
8957
|
+
}
|
|
8579
8958
|
if (disposed || !browser.cancel()) return;
|
|
8959
|
+
creator.close();
|
|
8580
8960
|
closedByOwner = true;
|
|
8581
8961
|
if (dialog.open) dialog.close();
|
|
8582
8962
|
getProps().onCancel();
|
|
@@ -8597,6 +8977,8 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8597
8977
|
if (disposal !== void 0) return disposal;
|
|
8598
8978
|
disposed = true;
|
|
8599
8979
|
unsubscribe();
|
|
8980
|
+
unsubscribeCreate();
|
|
8981
|
+
const creationStopped = creator.dispose();
|
|
8600
8982
|
const stopped = browser.dispose();
|
|
8601
8983
|
const failures = [];
|
|
8602
8984
|
const clean = (action) => {
|
|
@@ -8623,11 +9005,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
8623
9005
|
if (trigger?.isConnected) trigger.focus({ preventScroll: true });
|
|
8624
9006
|
});
|
|
8625
9007
|
disposal = (async () => {
|
|
8626
|
-
|
|
8627
|
-
await stopped;
|
|
8628
|
-
} catch (error) {
|
|
8629
|
-
failures.push(error);
|
|
8630
|
-
}
|
|
9008
|
+
for (const result of await Promise.allSettled([stopped, creationStopped])) if (result.status === "rejected") failures.push(result.reason);
|
|
8631
9009
|
if (failures.length > 0) throw new AggregateError(failures, "Mobile directory dialog disposal failed");
|
|
8632
9010
|
})();
|
|
8633
9011
|
return disposal;
|
|
@@ -14227,7 +14605,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
14227
14605
|
getLayout: () => ctx.get("layout"),
|
|
14228
14606
|
getSidebarRight: () => ctx.get("sidebarRight")
|
|
14229
14607
|
}));
|
|
14230
|
-
const removeDirectoryPicker = window.__DSH_MOBILE_FRONTEND__ === "dedicated" ? installMobileDirectoryPicker(ctx, document) : () => Promise.resolve();
|
|
14608
|
+
const removeDirectoryPicker = window.__DSH_MOBILE_FRONTEND__ === "dedicated" && !hasUpstreamBrowsePicker(window) ? installMobileDirectoryPicker(ctx, document) : () => Promise.resolve();
|
|
14231
14609
|
return async () => {
|
|
14232
14610
|
const removal = removeDirectoryPicker();
|
|
14233
14611
|
try {
|