dsh-mobile 0.8.7 → 0.8.9
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 +24 -2
- package/CONTRIBUTORS.md +16 -0
- package/README.en.md +12 -9
- package/README.md +12 -9
- package/apps/mobile/README.md +27 -3
- package/apps/mobile/README.zh-CN.md +27 -3
- package/apps/mobile/release.json +3 -3
- package/docs/ATTACH_EXISTING_FRPS.en.md +1 -1
- package/docs/IROH.en.md +4 -4
- package/docs/IROH.md +4 -4
- package/docs/README.md +8 -1
- package/docs/TRUSTED_NETWORKS.en.md +2 -0
- package/docs/TRUSTED_NETWORKS.md +2 -0
- package/docs/WEB_PUSH.en.md +1 -1
- package/docs/WEB_PUSH.md +1 -1
- package/lib/client.js +347 -6
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +25 -3
- package/lib/index.mjs +290 -104
- package/lib/index.mjs.map +1 -1
- package/lib/mobile-layout.js +329 -7
- package/lib/mobile-layout.js.map +1 -1
- package/native/iroh-host/README.md +1 -1
- package/package.json +4 -2
- package/packages/question-fixes/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -3864,22 +3864,46 @@ window.__ModuleLoader__.load({
|
|
|
3864
3864
|
return true;
|
|
3865
3865
|
}
|
|
3866
3866
|
/**
|
|
3867
|
+
* Whether the App's background service reports that it will actually post a
|
|
3868
|
+
* task-completion notification. All three facts must be explicit: the service
|
|
3869
|
+
* is enabled, its event stream is connected, and Android currently allows the
|
|
3870
|
+
* task channel to post (`canPost`: notifications enabled and channel importance
|
|
3871
|
+
* not NONE). The App records `notify:blocked` and does not touch
|
|
3872
|
+
* `connected`/`lastEventAt` when `canPost` is false, so treating enabled+
|
|
3873
|
+
* connected alone as a promise would let the page yield to a silent App and
|
|
3874
|
+
* drop the notification entirely.
|
|
3875
|
+
*/
|
|
3876
|
+
function backgroundNotificationsWillPost(report) {
|
|
3877
|
+
if (report === null || typeof report !== "object" || Array.isArray(report)) return false;
|
|
3878
|
+
const record = report;
|
|
3879
|
+
return record.enabled === true && record.connected === true && record.canPost === true;
|
|
3880
|
+
}
|
|
3881
|
+
/**
|
|
3867
3882
|
* Deliver one notification through the native bridge when available.
|
|
3868
3883
|
* Returns false when there is no bridge (desktop browsers keep their own
|
|
3869
3884
|
* UI) so callers can fall back; a denial flips the module sticky flag via
|
|
3870
3885
|
* onDenied for the session-lifetime nag guard.
|
|
3886
|
+
*
|
|
3887
|
+
* For task-completion notifications the page first asks the App whether its
|
|
3888
|
+
* background service will post the same event. When it will, the page yields:
|
|
3889
|
+
* one completed turn then produces exactly one notification, authored by the
|
|
3890
|
+
* plugin's Host-side event text and rendered by the App. Old Apps (no
|
|
3891
|
+
* `notification.background.get` report, no `connected` state, or no `canPost`
|
|
3892
|
+
* state) keep the previous behavior and the page still notifies.
|
|
3871
3893
|
*/
|
|
3872
3894
|
function fireTaskNotifyEvent(event, onDenied) {
|
|
3873
3895
|
const bridge = readNativeBridge();
|
|
3874
3896
|
if (bridge === void 0) return false;
|
|
3897
|
+
const deliver = () => bridge.invoke("notification.notify", {
|
|
3898
|
+
title: event.title,
|
|
3899
|
+
body: event.body,
|
|
3900
|
+
tag: event.tag,
|
|
3901
|
+
...event.navigate === void 0 ? {} : { navigate: event.navigate }
|
|
3902
|
+
});
|
|
3875
3903
|
Promise.resolve().then(() => bridge.capabilities()).then((capabilities) => {
|
|
3876
3904
|
if (!capabilities.includes("notification.notify")) return;
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
body: event.body,
|
|
3880
|
-
tag: event.tag,
|
|
3881
|
-
...event.navigate === void 0 ? {} : { navigate: event.navigate }
|
|
3882
|
-
});
|
|
3905
|
+
if (event.kind !== "done" || !capabilities.includes("notification.background.get")) return deliver();
|
|
3906
|
+
return bridge.invoke("notification.background.get", {}).then((report) => backgroundNotificationsWillPost(report) ? void 0 : deliver(), () => deliver());
|
|
3883
3907
|
}).then(() => void 0, (error) => {
|
|
3884
3908
|
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
3885
3909
|
if ((code === "permission_denied" || code === "denied") && onDenied !== void 0) onDenied();
|
|
@@ -4281,6 +4305,182 @@ window.__ModuleLoader__.load({
|
|
|
4281
4305
|
}
|
|
4282
4306
|
};
|
|
4283
4307
|
}
|
|
4308
|
+
// src/mobile-path-copy.ts
|
|
4309
|
+
/** Tap-to-copy for the stock preview header path on touch devices.
|
|
4310
|
+
*
|
|
4311
|
+
* Taps land on `PathLabel`'s nested directory/name spans, so the titled owner is
|
|
4312
|
+
* resolved through `closest()`; the changes-review file switcher keeps its own tap.
|
|
4313
|
+
*/
|
|
4314
|
+
/** The repository's shared touch predicate; kept identical to `TOUCH_PRIMARY_QUERY` (mobile-layout.ts)
|
|
4315
|
+
* and `BROWSER_TOUCH_PRIMARY_QUERY` (native-mobile.ts) by a source drift test. */
|
|
4316
|
+
const PATH_COPY_TOUCH_QUERY = "(hover: none), (pointer: coarse)";
|
|
4317
|
+
/** Stock-DSH element whose `title` carries the path shown to the user: the preview
|
|
4318
|
+
* header path bar. The presented-file card is deliberately NOT a target — its tap
|
|
4319
|
+
* stays a pure open action, so opening a file never replaces the clipboard. */
|
|
4320
|
+
const PATH_COPY_TARGET_SELECTOR = "[data-textpreview-path]";
|
|
4321
|
+
/** Replace `{path}` in a localized toast template with the copied value, literally.
|
|
4322
|
+
*
|
|
4323
|
+
* The replacement is a function because a string replacement would interpret `$&`,
|
|
4324
|
+
* `` $` `` and `$'` inside the path as replacement patterns, which would show a
|
|
4325
|
+
* mangled message for a legal file name that contains them. */
|
|
4326
|
+
function formatPathCopyMessage(template, path) {
|
|
4327
|
+
return template.replaceAll("{path}", () => path);
|
|
4328
|
+
}
|
|
4329
|
+
/** The `title` of a copy target, verbatim — trailing spaces are legal in file names —
|
|
4330
|
+
* or `undefined` when the attribute is absent or blank (blankness judged after trimming). */
|
|
4331
|
+
function pathCopyText(element) {
|
|
4332
|
+
const title = element.getAttribute("title");
|
|
4333
|
+
const blank = title?.trim();
|
|
4334
|
+
return title !== null && blank !== void 0 && blank !== "" ? title : void 0;
|
|
4335
|
+
}
|
|
4336
|
+
function isClosestCarrier(value) {
|
|
4337
|
+
return typeof value?.closest === "function";
|
|
4338
|
+
}
|
|
4339
|
+
/** Resolve the copy target owning the tapped node: the titled ancestor outside the review scope.
|
|
4340
|
+
* @param target - an event target; non-elements resolve to `null`.
|
|
4341
|
+
* @returns the element whose `title` to copy, or `null` when the tap is not a copy target. */
|
|
4342
|
+
function pathCopyOwner(target) {
|
|
4343
|
+
if (!isClosestCarrier(target)) return null;
|
|
4344
|
+
const owner = target.closest(PATH_COPY_TARGET_SELECTOR);
|
|
4345
|
+
if (owner === null) return null;
|
|
4346
|
+
if (target.closest("[data-changes-review]") !== null) return null;
|
|
4347
|
+
return pathCopyText(owner) === void 0 ? null : owner;
|
|
4348
|
+
}
|
|
4349
|
+
/** Copy through the API, falling back to `execCopy`; resolves, never rejects.
|
|
4350
|
+
* @param text - the value to place on the clipboard.
|
|
4351
|
+
* @param io - the clipboard operations to use.
|
|
4352
|
+
* @returns whether the value reached the clipboard by either route. */
|
|
4353
|
+
async function copyPathText(text, io) {
|
|
4354
|
+
try {
|
|
4355
|
+
await io.writeText(text);
|
|
4356
|
+
return true;
|
|
4357
|
+
} catch {
|
|
4358
|
+
try {
|
|
4359
|
+
return io.execCopy(text);
|
|
4360
|
+
} catch {
|
|
4361
|
+
return false;
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
}
|
|
4365
|
+
/** Build the browser clipboard adapter for one document.
|
|
4366
|
+
*
|
|
4367
|
+
* `writeText` delegates to `navigator.clipboard` when present and rejects otherwise, so
|
|
4368
|
+
* {@link copyPathText} routes to the fallback. `execCopy` copies through an off-screen but
|
|
4369
|
+
* rendered readonly textarea — `display:none` is not a reliable copy source — and restores
|
|
4370
|
+
* focus, selection, and scroll on every path.
|
|
4371
|
+
* @param document - the document that will host the fallback textarea.
|
|
4372
|
+
* @returns the adapter bound to that document and this window's navigator. */
|
|
4373
|
+
function domCopyIo(document) {
|
|
4374
|
+
return {
|
|
4375
|
+
writeText: (text) => {
|
|
4376
|
+
const clipboard = globalThis.navigator?.clipboard;
|
|
4377
|
+
if (clipboard?.writeText === void 0) return Promise.reject(/* @__PURE__ */ new Error("clipboard api unavailable"));
|
|
4378
|
+
return clipboard.writeText(text);
|
|
4379
|
+
},
|
|
4380
|
+
execCopy: (value) => {
|
|
4381
|
+
const activeElement = document.activeElement;
|
|
4382
|
+
const selection = document.getSelection();
|
|
4383
|
+
const savedRanges = selection === null ? [] : Array.from({ length: selection.rangeCount }, (_, at) => selection.getRangeAt(at).cloneRange());
|
|
4384
|
+
const root = document.documentElement;
|
|
4385
|
+
const scrollOwners = [root];
|
|
4386
|
+
for (let node = activeElement; node !== null && node !== root; node = node.parentElement ?? null) scrollOwners.push(node);
|
|
4387
|
+
const savedScroll = scrollOwners.map((owner) => ({
|
|
4388
|
+
owner,
|
|
4389
|
+
top: owner.scrollTop,
|
|
4390
|
+
left: owner.scrollLeft
|
|
4391
|
+
}));
|
|
4392
|
+
const area = document.createElement("textarea");
|
|
4393
|
+
area.value = value;
|
|
4394
|
+
area.readOnly = true;
|
|
4395
|
+
area.style.position = "fixed";
|
|
4396
|
+
area.style.left = "-9999px";
|
|
4397
|
+
document.body.appendChild(area);
|
|
4398
|
+
try {
|
|
4399
|
+
area.select();
|
|
4400
|
+
return document.execCommand("copy");
|
|
4401
|
+
} catch {
|
|
4402
|
+
return false;
|
|
4403
|
+
} finally {
|
|
4404
|
+
document.body.removeChild(area);
|
|
4405
|
+
if (selection !== null) {
|
|
4406
|
+
selection.removeAllRanges();
|
|
4407
|
+
for (const range of savedRanges) selection.addRange(range);
|
|
4408
|
+
}
|
|
4409
|
+
if (activeElement !== null) activeElement.focus?.({ preventScroll: true });
|
|
4410
|
+
for (const { owner, top, left } of savedScroll) {
|
|
4411
|
+
owner.scrollTop = top;
|
|
4412
|
+
owner.scrollLeft = left;
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
}
|
|
4416
|
+
};
|
|
4417
|
+
}
|
|
4418
|
+
/** Host the copy confirmation toast on one document.
|
|
4419
|
+
* @param document - the document whose body hosts the toast.
|
|
4420
|
+
* @returns the toast handle. */
|
|
4421
|
+
function createPathCopyToast(document) {
|
|
4422
|
+
const node = document.createElement("div");
|
|
4423
|
+
node.className = "dsh-mobile-path-toast";
|
|
4424
|
+
node.setAttribute("role", "status");
|
|
4425
|
+
node.setAttribute("aria-live", "polite");
|
|
4426
|
+
node.lang = document.documentElement.lang;
|
|
4427
|
+
document.body.appendChild(node);
|
|
4428
|
+
let timer;
|
|
4429
|
+
return {
|
|
4430
|
+
node,
|
|
4431
|
+
show(message) {
|
|
4432
|
+
node.textContent = message;
|
|
4433
|
+
node.setAttribute("data-visible", "true");
|
|
4434
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
4435
|
+
timer = setTimeout(() => {
|
|
4436
|
+
node.removeAttribute("data-visible");
|
|
4437
|
+
timer = void 0;
|
|
4438
|
+
}, 2200);
|
|
4439
|
+
},
|
|
4440
|
+
dispose() {
|
|
4441
|
+
if (timer !== void 0) {
|
|
4442
|
+
clearTimeout(timer);
|
|
4443
|
+
timer = void 0;
|
|
4444
|
+
}
|
|
4445
|
+
node.removeAttribute("data-visible");
|
|
4446
|
+
document.body.removeChild(node);
|
|
4447
|
+
}
|
|
4448
|
+
};
|
|
4449
|
+
}
|
|
4450
|
+
/** Install the delegated tap-to-copy listener and its toast.
|
|
4451
|
+
*
|
|
4452
|
+
* The listener neither prevents nor stops propagation: the preview header keeps its
|
|
4453
|
+
* own focus behavior, and taps on other elements keep their own actions. Disposal
|
|
4454
|
+
* stops new copies, cancels the fallback of a still-pending write, and drops a
|
|
4455
|
+
* completion that lands after disposal.
|
|
4456
|
+
* @param deps - document, touch gate, localized templates, optional clipboard adapter.
|
|
4457
|
+
* @returns the disposer. */
|
|
4458
|
+
function installMobilePathCopy(deps) {
|
|
4459
|
+
const source = deps.io ?? domCopyIo(deps.document);
|
|
4460
|
+
const toast = createPathCopyToast(deps.document);
|
|
4461
|
+
let disposed = false;
|
|
4462
|
+
const io = {
|
|
4463
|
+
writeText: (text) => source.writeText(text),
|
|
4464
|
+
execCopy: (value) => disposed ? false : source.execCopy(value)
|
|
4465
|
+
};
|
|
4466
|
+
const onClick = (event) => {
|
|
4467
|
+
if (disposed || !deps.isTouchPrimary()) return;
|
|
4468
|
+
const owner = pathCopyOwner(event.target);
|
|
4469
|
+
if (owner === null) return;
|
|
4470
|
+
const path = pathCopyText(owner);
|
|
4471
|
+
if (path === void 0) return;
|
|
4472
|
+
copyPathText(path, io).then((ok) => {
|
|
4473
|
+
if (disposed) return;
|
|
4474
|
+
toast.show(formatPathCopyMessage(ok ? deps.labels.copied : deps.labels.failed, path));
|
|
4475
|
+
});
|
|
4476
|
+
};
|
|
4477
|
+
deps.document.addEventListener("click", onClick);
|
|
4478
|
+
return () => {
|
|
4479
|
+
disposed = true;
|
|
4480
|
+
deps.document.removeEventListener("click", onClick);
|
|
4481
|
+
toast.dispose();
|
|
4482
|
+
};
|
|
4483
|
+
}
|
|
4284
4484
|
// src/native-mobile.ts
|
|
4285
4485
|
/**
|
|
4286
4486
|
* Viewport below which the injected chrome becomes an overlay drawer: the
|
|
@@ -4300,6 +4500,12 @@ window.__ModuleLoader__.load({
|
|
|
4300
4500
|
the query restores the fixed scrim, and its more specific [hidden] rule
|
|
4301
4501
|
keeps winning there. */
|
|
4302
4502
|
.dsh-native-mobile-backdrop,.dsh-mobile-branch-toast,.dsh-mobile-media-toast { display:none; }
|
|
4503
|
+
/* The path-copy toast confirms a clipboard write whose gate is the touch
|
|
4504
|
+
predicate, not viewport width, so its box lives outside the overlay query:
|
|
4505
|
+
a landscape phone or tablet copy must stay visible. The bottom slot keeps
|
|
4506
|
+
it clear of the branch and media toasts' shared top-center position. */
|
|
4507
|
+
.dsh-mobile-path-toast { display:none; }
|
|
4508
|
+
.dsh-mobile-path-toast[data-visible="true"] { display:block; position:fixed; z-index:330; left:50%; transform:translateX(-50%); bottom:max(12px,env(safe-area-inset-bottom)); max-width:calc(100vw - 32px); box-sizing:border-box; padding:7px 14px; border:1px solid rgb(15 23 42 / 10%); border-radius:999px; background:rgb(15 23 42 / 92%); color:#fff; font-size:13px; line-height:20px; text-align:center; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; pointer-events:none; }
|
|
4303
4509
|
/* The content-font preference may exceed Safari's focused-editable minimum.
|
|
4304
4510
|
The html-level class exists on every non-desktop mobile page, so it also
|
|
4305
4511
|
covers body-level portals (model picker search) the center column never
|
|
@@ -5496,6 +5702,14 @@ window.__ModuleLoader__.load({
|
|
|
5496
5702
|
mediaToastTimer = 0;
|
|
5497
5703
|
}, 2200);
|
|
5498
5704
|
};
|
|
5705
|
+
const disposePathCopy = installMobilePathCopy({
|
|
5706
|
+
document,
|
|
5707
|
+
isTouchPrimary: () => window.matchMedia(PATH_COPY_TOUCH_QUERY).matches,
|
|
5708
|
+
labels: {
|
|
5709
|
+
copied: label("Copiato: {path}", "Copied: {path}", "已复制:{path}"),
|
|
5710
|
+
failed: label("Impossibile copiare il percorso.", "Could not copy the path.", "无法复制路径。")
|
|
5711
|
+
}
|
|
5712
|
+
});
|
|
5499
5713
|
let mediaRequestGeneration = 0;
|
|
5500
5714
|
let disposed = false;
|
|
5501
5715
|
let boundComposer = null;
|
|
@@ -6123,6 +6337,7 @@ window.__ModuleLoader__.load({
|
|
|
6123
6337
|
disposed = true;
|
|
6124
6338
|
stopStockBack();
|
|
6125
6339
|
sidebarScrollMemory.dispose();
|
|
6340
|
+
disposePathCopy();
|
|
6126
6341
|
disposeTaskWatcher();
|
|
6127
6342
|
mediaRequestGeneration += 1;
|
|
6128
6343
|
mediaPickerAbortController.abort();
|
|
@@ -7115,6 +7330,124 @@ window.__ModuleLoader__.load({
|
|
|
7115
7330
|
}
|
|
7116
7331
|
}, copy.reset)));
|
|
7117
7332
|
}
|
|
7333
|
+
// src/composer-collapse.ts
|
|
7334
|
+
/**
|
|
7335
|
+
* Storage key for the opt-in switch.
|
|
7336
|
+
*
|
|
7337
|
+
* The setting row lives in the client bundle while the tracker lives in the
|
|
7338
|
+
* layout bundle, and tsdown.config.ts emits those as two independent CJS modules
|
|
7339
|
+
* that each inline this file. A module-level singleton therefore cannot carry the
|
|
7340
|
+
* value across: the row would flip its own copy while the layout kept the
|
|
7341
|
+
* boot-time value, and the toggle would only apply after a reload. The persisted
|
|
7342
|
+
* flag plus the two events below are the shared state instead, mirroring the
|
|
7343
|
+
* session-prefetch opt-in.
|
|
7344
|
+
*/
|
|
7345
|
+
const COMPOSER_COLLAPSE_STORAGE_KEY = "dsh-mobile.composer-collapse.enabled";
|
|
7346
|
+
const COMPOSER_COLLAPSE_EVENT = "dsh-mobile:composer-collapse-change";
|
|
7347
|
+
/**
|
|
7348
|
+
* Last value written in this page when storage refused the write.
|
|
7349
|
+
*
|
|
7350
|
+
* This is a per-bundle fallback for the blocked-storage case only: it is not the
|
|
7351
|
+
* cross-bundle transport (that is the persisted flag plus the events above). It
|
|
7352
|
+
* exists so a switch that cannot be persisted still reads back as flipped in the
|
|
7353
|
+
* same page, instead of the row snapping back while the layout obeys the event.
|
|
7354
|
+
*/
|
|
7355
|
+
let unpersistedEnabled;
|
|
7356
|
+
/** Read the persisted opt-in. Defaults to off; unreadable storage keeps it off. */
|
|
7357
|
+
function readComposerCollapseEnabled(storage) {
|
|
7358
|
+
try {
|
|
7359
|
+
const stored = (storage === void 0 ? globalThis.localStorage : storage)?.getItem("dsh-mobile.composer-collapse.enabled") ?? null;
|
|
7360
|
+
if (stored !== null) return stored === "true";
|
|
7361
|
+
return storage === void 0 ? unpersistedEnabled ?? false : false;
|
|
7362
|
+
} catch {
|
|
7363
|
+
return storage === void 0 ? unpersistedEnabled ?? false : false;
|
|
7364
|
+
}
|
|
7365
|
+
}
|
|
7366
|
+
/** Persist the opt-in and notify the layout bundle in the same page. */
|
|
7367
|
+
function writeComposerCollapseEnabled(enabled, storage) {
|
|
7368
|
+
try {
|
|
7369
|
+
const resolved = storage === void 0 ? globalThis.localStorage : storage;
|
|
7370
|
+
if (resolved === null) throw new Error("storage unavailable");
|
|
7371
|
+
resolved.setItem(COMPOSER_COLLAPSE_STORAGE_KEY, String(enabled));
|
|
7372
|
+
if (storage === void 0) unpersistedEnabled = void 0;
|
|
7373
|
+
} catch {
|
|
7374
|
+
if (storage === void 0) unpersistedEnabled = enabled;
|
|
7375
|
+
}
|
|
7376
|
+
try {
|
|
7377
|
+
globalThis.dispatchEvent?.(new CustomEvent(COMPOSER_COLLAPSE_EVENT, { detail: { enabled } }));
|
|
7378
|
+
} catch {}
|
|
7379
|
+
}
|
|
7380
|
+
/**
|
|
7381
|
+
* Subscribe to opt-in changes coming from either bundle. The listener is not
|
|
7382
|
+
* called with the initial value; read it once with readComposerCollapseEnabled().
|
|
7383
|
+
*/
|
|
7384
|
+
function subscribeComposerCollapseEnabled(listener) {
|
|
7385
|
+
const onStorage = (event) => {
|
|
7386
|
+
if (event.key !== null && event.key !== "dsh-mobile.composer-collapse.enabled") return;
|
|
7387
|
+
listener(readComposerCollapseEnabled());
|
|
7388
|
+
};
|
|
7389
|
+
const onCustom = (event) => {
|
|
7390
|
+
const detail = event.detail;
|
|
7391
|
+
listener(detail?.enabled ?? readComposerCollapseEnabled());
|
|
7392
|
+
};
|
|
7393
|
+
globalThis.addEventListener?.("storage", onStorage);
|
|
7394
|
+
globalThis.addEventListener?.(COMPOSER_COLLAPSE_EVENT, onCustom);
|
|
7395
|
+
return () => {
|
|
7396
|
+
globalThis.removeEventListener?.("storage", onStorage);
|
|
7397
|
+
globalThis.removeEventListener?.(COMPOSER_COLLAPSE_EVENT, onCustom);
|
|
7398
|
+
};
|
|
7399
|
+
}
|
|
7400
|
+
// src/composer-collapse-settings.ts
|
|
7401
|
+
const COMPOSER_COLLAPSE_COPY = {
|
|
7402
|
+
en: {
|
|
7403
|
+
title: "Reading mode: collapse composer",
|
|
7404
|
+
desc: "Collapse the composer editor while scrolling down a long conversation; tap it, focus it, or scroll back up to restore. It never collapses while a draft or a running reply is present.",
|
|
7405
|
+
on: "On",
|
|
7406
|
+
off: "Off"
|
|
7407
|
+
},
|
|
7408
|
+
zh: {
|
|
7409
|
+
title: "阅读模式:收起输入栏",
|
|
7410
|
+
desc: "向下滚动阅读长会话时收起输入框编辑区;点按、聚焦或向上回滚即恢复。有草稿或回复生成中时不会收起。",
|
|
7411
|
+
on: "已开启",
|
|
7412
|
+
off: "已关闭"
|
|
7413
|
+
},
|
|
7414
|
+
it: {
|
|
7415
|
+
title: "Modalità lettura: comprimi il campo",
|
|
7416
|
+
desc: "Comprime l’editor del campo mentre scorri una conversazione lunga; toccalo, mettilo a fuoco o scorri verso l’alto per ripristinarlo. Non si comprime con una bozza o una risposta in corso.",
|
|
7417
|
+
on: "Attivo",
|
|
7418
|
+
off: "Disattivo"
|
|
7419
|
+
}
|
|
7420
|
+
};
|
|
7421
|
+
/** Browser-only settings row; toggling it only persists the flag. */
|
|
7422
|
+
function ComposerCollapseSettings({ locale, read, write, subscribe }) {
|
|
7423
|
+
const readEnabled = read ?? (() => readComposerCollapseEnabled());
|
|
7424
|
+
const writeEnabled = write ?? ((enabled) => {
|
|
7425
|
+
writeComposerCollapseEnabled(enabled);
|
|
7426
|
+
});
|
|
7427
|
+
const enabled = (0, react.useSyncExternalStore)(subscribe ?? ((listener) => subscribeComposerCollapseEnabled(listener)), readEnabled, () => false);
|
|
7428
|
+
const copy = COMPOSER_COLLAPSE_COPY[locale];
|
|
7429
|
+
return (0, react.createElement)("label", {
|
|
7430
|
+
className: "dsh-mobile-settings_row",
|
|
7431
|
+
lang: locale === "zh" ? "zh-CN" : locale,
|
|
7432
|
+
"data-dsh-mobile-composer-collapse": enabled ? "on" : "off"
|
|
7433
|
+
}, (0, react.createElement)("div", { className: "dsh-mobile-settings_rowText" }, (0, react.createElement)("div", { className: "dsh-mobile-settings_title" }, copy.title), (0, react.createElement)("div", {
|
|
7434
|
+
className: "dsh-mobile-settings_desc",
|
|
7435
|
+
"aria-live": "polite"
|
|
7436
|
+
}, copy.desc)), (0, react.createElement)("input", {
|
|
7437
|
+
type: "checkbox",
|
|
7438
|
+
role: "switch",
|
|
7439
|
+
className: "dsh-mobile-settings_switchInput",
|
|
7440
|
+
checked: enabled,
|
|
7441
|
+
"aria-label": copy.title,
|
|
7442
|
+
"aria-checked": enabled ? "true" : "false",
|
|
7443
|
+
onChange: (event) => {
|
|
7444
|
+
writeEnabled(event.currentTarget.checked);
|
|
7445
|
+
}
|
|
7446
|
+
}), (0, react.createElement)("span", {
|
|
7447
|
+
className: "dsh-mobile-settings_selector",
|
|
7448
|
+
"aria-hidden": "true"
|
|
7449
|
+
}, enabled ? copy.on : copy.off));
|
|
7450
|
+
}
|
|
7118
7451
|
// src/mobile-display-scale.ts
|
|
7119
7452
|
/** Native App page scaling in DSH General settings, independent from local typography. */
|
|
7120
7453
|
/** Validate the native reply before displaying or reusing its values in a request. */
|
|
@@ -18004,6 +18337,11 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
18004
18337
|
preference: localFont.preference,
|
|
18005
18338
|
locale: selectedMobileControlLocale()
|
|
18006
18339
|
}));
|
|
18340
|
+
const removeComposerCollapse = ctx.slots.register({
|
|
18341
|
+
name: "settings.general.item",
|
|
18342
|
+
id: "dsh-mobile-composer-collapse",
|
|
18343
|
+
order: 15
|
|
18344
|
+
}, () => (0, react.createElement)(ComposerCollapseSettings, { locale: selectedMobileControlLocale() }));
|
|
18007
18345
|
const removeDisplayScale = ctx.slots.register({
|
|
18008
18346
|
name: "settings.general.item",
|
|
18009
18347
|
id: "dsh-mobile-display-scale",
|
|
@@ -18029,6 +18367,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
18029
18367
|
removeTaskNotifications();
|
|
18030
18368
|
removeSwitchComputer();
|
|
18031
18369
|
removeDisplayScale();
|
|
18370
|
+
removeComposerCollapse();
|
|
18032
18371
|
removeFontSize();
|
|
18033
18372
|
};
|
|
18034
18373
|
});
|
|
@@ -18150,6 +18489,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
18150
18489
|
exports.parseMobileExtensionManifest = parseMobileExtensionManifest;
|
|
18151
18490
|
exports.parseMobileRemoteProvider = parseMobileRemoteProvider;
|
|
18152
18491
|
exports.publishAuthoritativeExtensionIds = publishAuthoritativeExtensionIds;
|
|
18492
|
+
exports.readComposerCollapseEnabled = readComposerCollapseEnabled;
|
|
18153
18493
|
exports.reconcileRemovedExtensions = reconcileRemovedExtensions;
|
|
18154
18494
|
exports.registerUniqueDisposable = registerUniqueDisposable;
|
|
18155
18495
|
exports.renderDiagnosticPayloadSafely = renderDiagnosticPayloadSafely;
|
|
@@ -18162,6 +18502,7 @@ dialog.dshm-picker[open]{display:grid;place-items:center}dialog.dshm-picker::bac
|
|
|
18162
18502
|
exports.startLifecycleRefreshScheduler = startLifecycleRefreshScheduler;
|
|
18163
18503
|
exports.trustAuthenticatedGatewayConnection = trustAuthenticatedGatewayConnection;
|
|
18164
18504
|
exports.validateDiagnosticChecks = validateDiagnosticChecks;
|
|
18505
|
+
exports.writeComposerCollapseEnabled = writeComposerCollapseEnabled;
|
|
18165
18506
|
return module.exports;
|
|
18166
18507
|
}
|
|
18167
18508
|
});
|